Unity
UnityEditor

UnityEditorで2Dゲームの作成を楽にするツールを作ってみる

床設定ツールみたいなやつを作ってみた。

とあるゲームの企画でエンジニア以外の方とゲームを作ることになったので、2Dゲームのステージを日エンジニアの方以外でも弄り回せるようにするのが目標です。

使い方

エンプティオブジェクトにアタッチするだけ、おしまい。
あとは、床に設定したいスプライトを設定して自動調整にチェックを入れてあげれば自動でコライダーまでつけてくれる。

スクリーンショット 2018-01-29 19.28.02.png

ソースコードはこちらからでもダウンロードできます。
https://github.com/KeitaUK

FloorView.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
#endif

[RequireComponent(typeof(BoxCollider2D))]
public class FloorView : MonoBehaviour {

    [SerializeField]
    private Sprite m_stageTextureLeft;
    public Sprite stageTextureLeft{
        get{
            return m_stageTextureLeft;
        }
        set{
            m_stageTextureLeft = value;
        }
    }

    [SerializeField]
    private Sprite m_stageTextureCenter;
    public Sprite stageTextureCenter{
        get{
            return m_stageTextureCenter;
        }
        set{
            m_stageTextureCenter = value;
        }
    }

    [SerializeField]
    private Sprite m_stageTextureRight;
    public Sprite stageTextureRight{
        get{
            return m_stageTextureRight;
        }
        set{
            m_stageTextureRight = value;
        }
    }

    [SerializeField]
    private int m_floorCenterNum = 1;
    public int floorCenterNum{
        get{
            if(m_floorCenterNum < 0){
                return m_floorCenterNum *= -1;
            }
            return m_floorCenterNum;
        }
        set{
            m_floorCenterNum = value;
        }
    }

    [SerializeField]
    private float m_offsetX = 0;
    public float offsetX {
        get {
            return m_offsetX;
        }
        set {
            m_offsetX = value;
        }
    }

    [SerializeField]
    private float m_offsetY = 0;
    public float offsetY {
        get {
            return m_offsetY;
        }
        set {
            m_offsetY = value;
        }
    }

    [SerializeField]
    private float m_sizeX;
    public float sizeX {
        get {
            return m_sizeX;
        }
        set {
            m_sizeX = value;
        }
    }

    [SerializeField]
    private float m_sizeY;
    public float sizeY {
        get {
            return m_sizeY;
        }
        set {
            m_sizeY = value;
        }
    }   
    void Start () {

    }
    void Update () {

    }



}
#if UNITY_EDITOR
[CustomEditor(typeof(FloorView))]
public class FloorEditor : Editor {

    [SerializeField]
    private bool autoAdjustFlag = false;

    // 床のそれぞれの素材のゲームオブジェクト
    private GameObject m_rightTexture;
    private GameObject m_leftTexture;
    private GameObject m_centerTexture;
    private List<GameObject> m_centerTextures = new List<GameObject>();
    private bool isInitiallize = false;

    /// <summary>
    /// Editor拡張コード。
    /// GUIの表示関係を主に登録処理を主に行う。
    /// </summary>
    public override void OnInspectorGUI() {
        EditorGUI.BeginChangeCheck();
        FloorView floorView = target as FloorView;
        InitializeGUI(floorView);

        // エディター内の値が変更されたらデータの反映を行う。
        if (EditorGUI.EndChangeCheck()) {
            SetFloors(floorView);
            SetCenterTexture(floorView);
            SetFloorPosition(floorView);

            if(autoAdjustFlag) {
                SetBoxColloder2d(floorView);
            }

        }

    }

    /// <summary>
    /// Editor拡張のGUI表示を行う。
    /// 引数にはFloorViewのインスタンスが必要。
    /// </summary>
    /// <param name="floorView"></param>
    void InitializeGUI(FloorView floorView) {
        floorView.stageTextureLeft      = EditorGUILayout.ObjectField( "左側テクスチャ", floorView.stageTextureLeft, typeof(Sprite), false ) as Sprite;
        floorView.stageTextureCenter    = EditorGUILayout.ObjectField( "中央テクスチャ", floorView.stageTextureCenter, typeof(Sprite), false ) as Sprite;
        floorView.stageTextureRight     = EditorGUILayout.ObjectField( "右側テクスチャ", floorView.stageTextureRight, typeof(Sprite), false ) as Sprite;
        floorView.floorCenterNum        = EditorGUILayout.IntField( "中央床の数", floorView.floorCenterNum);
        floorView.offsetX               = EditorGUILayout.FloatField( "コライダーX座標調整", floorView.offsetX);
        floorView.offsetY               = EditorGUILayout.FloatField( "コライダーY座標調整", floorView.offsetY);
        floorView.sizeX                 = EditorGUILayout.FloatField( "コライダー横サイズ", floorView.sizeX);
        floorView.sizeY                 = EditorGUILayout.FloatField( "コライダー縦サイズ", floorView.sizeY);
        autoAdjustFlag                  = EditorGUILayout.Toggle("自動調整するか", autoAdjustFlag);
    }

    /// <summary>
    /// 床の中心位置を返す。
    /// </summary>
    /// <param name="floorView"></param>
    /// <returns>Vector2 : 現在の床の中心点を返す</returns>
    Vector2 GetCenterPosition(FloorView floorView) {
        return floorView.gameObject.transform.position;
    }

    /// <summary>
    /// 両脇のテクスチャがあるか?
    /// </summary>
    /// <returns></returns>
    void SetFloors(FloorView floorView) {
        if(!IsGeneratedTexture(floorView.gameObject, "RightTexture")) {
            m_rightTexture = new GameObject("RightTexture");
            m_rightTexture.transform.parent = floorView.transform;
            m_rightTexture.AddComponent<SpriteRenderer>();
            m_rightTexture.name = "RightTexture";
            SpriteRenderer sp = m_rightTexture.GetComponent<SpriteRenderer>();
            sp.sprite = floorView.stageTextureRight;
            m_rightTexture.transform.position = new Vector2(0,0);
        }
        if(!IsGeneratedTexture(floorView.gameObject, "LeftTexture")) {
            m_leftTexture = new GameObject("LeftTexture");
            m_leftTexture.transform.parent = floorView.transform;
            m_leftTexture.AddComponent<SpriteRenderer>();
            m_leftTexture.GetComponent<SpriteRenderer>().sprite = floorView.stageTextureLeft;
            m_leftTexture.name = "LeftTexture";
        }
        if(!IsGeneratedTexture(floorView.gameObject, "CenterTexture")) {
            m_centerTexture = new GameObject("CenterTexture");
            m_centerTexture.transform.parent = floorView.transform;
            m_centerTexture.AddComponent<SpriteRenderer>();
            m_centerTexture.GetComponent<SpriteRenderer>().sprite = floorView.stageTextureCenter;
            m_centerTexture.name = "CenterTexture";
            m_centerTexture.gameObject.SetActive(false);
        }
        //中央テクスチャがロードされていなかったら再度設定しなおす。
        if(!isInitiallize) {
            Transform childs = floorView.GetComponentInChildren<Transform>();
            foreach(Transform transformChild in childs) {
                if(transformChild.gameObject.name.Equals("CenterTextureInstance")) {
                    m_centerTextures.Add(transformChild.gameObject);
                }
            }
        }
        isInitiallize = true;
    }

    void SetCenterTexture(FloorView floorView) {
        int needFloorNum = floorView.floorCenterNum - m_centerTextures.Count;
        //必要数よりも今ある床が足りない場合
        if(needFloorNum > 0) {
            for(int i = 0 ; i < needFloorNum; i++) {
                GameObject floor = Instantiate(m_centerTexture) as GameObject;
                floor.name = "CenterTextureInstance";
                m_centerTextures.Add(floor);
                m_centerTextures[m_centerTextures.Count - 1].transform.parent = floorView.transform;
                m_centerTextures[m_centerTextures.Count - 1].gameObject.SetActive(true);
            }
        }
        if(needFloorNum < 0) {
            needFloorNum *= -1;
            for(int i = 0 ; i < needFloorNum; i++) {
                DestroyImmediate(m_centerTextures[m_centerTextures.Count - 1].gameObject);
                m_centerTextures.Remove(m_centerTextures[m_centerTextures.Count - 1]);
            }
        }
        //初期化
        if(floorView.floorCenterNum == 0){
            for(int i = 0; i < m_centerTextures.Count; i++) {
                DestroyImmediate(m_centerTextures[m_centerTextures.Count - 1].gameObject);
            }
            m_centerTextures.Clear();
        }
    }

    void SetFloorPosition(FloorView floorView) {
        float centerTexSizeX = m_centerTexture.GetComponent<SpriteRenderer>().size.x;
        float centerTexSizeY = m_centerTexture.GetComponent<SpriteRenderer>().size.y;

        //偶数の場合
        if(floorView.floorCenterNum % 2 == 0) {
            //片側だけの計算で十分
            float posX = m_centerTexture.GetComponent<SpriteRenderer>().size.x * (floorView.floorCenterNum / 2) + m_rightTexture.GetComponent<SpriteRenderer>().size.x / 2;
            float posY = 0.0f;
            m_rightTexture.transform.localPosition = new Vector2(posX, posY);
            m_leftTexture.transform.localPosition = new Vector2(-posX, -posY);

            //右側の設置
            for(int i = 0; i < m_centerTextures.Count / 2; i++){
                //初期位置のみスプライトの半分の大きさで良い
                if(i == 0) {
                    m_centerTextures[i * 2].transform.localPosition = new Vector2((centerTexSizeX / 2) * (i + 1), 0);
                }
                else {
                    //中央座標のスプライトの中心座標 + テクスチャの大きさ * テクスチャの数
                    m_centerTextures[i * 2].transform.localPosition = new Vector2(centerTexSizeX * i + (centerTexSizeX / 2), 0);
                }

            }
            //左側の設置
            for(int i = 0; i < m_centerTextures.Count / 2; i++){
                if(i == 0) {
                    m_centerTextures[(i*2) + 1].transform.localPosition = new Vector2(-(centerTexSizeX / 2) * (i + 1), 0);
                }
                else {
                    m_centerTextures[(i*2) + 1].transform.localPosition = new Vector2(-centerTexSizeX * i - (centerTexSizeX / 2), 0);
                }
            }
        }
        // 奇数の場合
        else {
            float posX = m_centerTexture.GetComponent<SpriteRenderer>().size.x * (floorView.floorCenterNum / 2) + (m_centerTexture.GetComponent<SpriteRenderer>().size.x / 2) +(m_rightTexture.GetComponent<SpriteRenderer>().size.x / 2);
            float posY = 0.0f;
            m_rightTexture.transform.localPosition = new Vector2(posX, posY);
            m_leftTexture.transform.localPosition = new Vector2(-posX, -posY);
            m_centerTextures[0].gameObject.transform.localPosition = Vector2.zero;

            //右側の設置
            for(int i = 1; i < (m_centerTextures.Count / 2) + 1; i++){
                //中央座標のスプライトの中心座標 + テクスチャの大きさ * テクスチャの数
                m_centerTextures[i * 2].transform.localPosition = new Vector2(centerTexSizeX * i, 0);       
            }
            for(int i = 1; i < (m_centerTextures.Count / 2) + 1; i++){
                //中央座標のスプライトの中心座標 + テクスチャの大きさ * テクスチャの数
                m_centerTextures[(i * 2) - 1].transform.localPosition = new Vector2(-centerTexSizeX * i, 0);            
            }   

        }

    }


    /// <summary>
    /// オブジェクトが生成されているかどうかを判定する。
    /// 生成されていた場合、テクスチャ系のグローバル変数の初期化を行う。
    /// </summary>
    /// <param name="parentObj">親オブジェクト</param>
    /// <param name="objName">生成したいオブジェクトの名前</param>
    /// <returns>true or false</returns>
    bool IsGeneratedTexture(GameObject parentObj, string objName) {
        Transform child = parentObj.GetComponentInChildren<Transform>();
        if(child.childCount == 0) {
            return false;
        }
        else {
            foreach(Transform transformChild in child) {
                if(transformChild.gameObject.name.Equals(objName)) {
                    // すでに生成されていた場合、テクスチャをセットし直す
                    SetTextureGameObject(transformChild.gameObject.name, transformChild.gameObject);
                    return true;
                }
            }
        }
        return false;
    }

    /// <summary>
    /// テクスチャ系のグローバル変数の初期化処理
    /// </summary>
    /// <param name="objName"></param>
    /// <param name="childGameObject"></param>
    void SetTextureGameObject(string objName, GameObject childGameObject) {
        if(objName.Equals("RightTexture")) {
            m_rightTexture = childGameObject;
        }
        else if(objName.Equals("LeftTexture")) {
            m_leftTexture = childGameObject;
        }
        else if(objName.Equals("CenterTexture")) {
            m_centerTexture = childGameObject;
            m_centerTexture.gameObject.SetActive(false);
        }else if(objName.Equals("CenterTextureInstance")) {
            if(!isInitiallize) {
                m_centerTextures.Add(childGameObject);
            }
        }
        else {
            return;
        }
    }

    /// <summary>
    /// 床の大きさを返す。
    /// </summary>
    /// <returns>Vector2 : 床の大きさを返す。</returns>
    Vector2 GetFloorSize(FloorView floorView) {
        if(floorView.floorCenterNum > 0) {
            float sizeX = m_rightTexture.GetComponent<SpriteRenderer>().size.x + m_centerTexture.GetComponent<SpriteRenderer>().size.x * floorView.floorCenterNum + m_leftTexture.GetComponent<SpriteRenderer>().size.x;
            float sizeY = m_rightTexture.GetComponent<SpriteRenderer>().size.y;
            return new Vector2(sizeX, sizeY);
        }
        else {
            float sizeX = m_rightTexture.GetComponent<SpriteRenderer>().size.x + m_leftTexture.GetComponent<SpriteRenderer>().size.x;
            float sizeY = m_rightTexture.GetComponent<SpriteRenderer>().size.y;
            return new Vector2(sizeX, sizeY);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="bColloder2d"></param>
    /// <param name="offsetX"></param>
    /// <param name="offsetY"></param>
    void SetBoxColloder2d(FloorView floorView) {
        BoxCollider2D bCollider2d = floorView.gameObject.GetComponent<BoxCollider2D>();
        bCollider2d.size = GetFloorSize(floorView);
        bCollider2d.offset = new Vector2(floorView.offsetX, floorView.offsetY);
    }
}
#endif

感想

エディタいじるのは初めてなのでEditorGUI.EndChangeCheck()のメソッド内に処理を書き込むのがなんか気持ち悪かった。。
もうちょっと良い書き方がありそうなきがする。。。
コードは設計考えずゴリゴリ書いたのですごく汚いです。。