うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】Component の Transform や GameObject にアクセスさせない

Component が public で Transform と GameObject のフィールドを持っているため、参照があれば好き勝手に変更できる そのため、外部から操作できなくするコンポーネントを作成してみた

フィールドを削除することは無理なので、参照されても null を返し warning を表示するようにしてある

using System;
using UnityEngine;

public class SealMonoBehaviour : MonoBehaviour
{
    [Obsolete]
    public new Transform transform => null;
    [Obsolete]
    public new GameObject gameObject => null;
}

しかしながら、キャストすれば問題無くアクセスできてしまう

    [SerializeField]
    private SealMonoBehaviour _sealMonoBehaviour;
    
    private void Awake()
    {
        (_sealMonoBehaviour as MonoBehaviour).transform.position = Vector3.zero; // エラーにならない
        _sealMonoBehaviour.transform.position = Vector3.zero; // エラーになる
    }

【Unity】Vector2Int, Vector3Int は JsonUtility を利用できない

Vector2Int や Vector3Int を Json として保存するために JsonUtility を利用したところうまくいかず

試しにログを見てみたところ正しく変換できていなかった

var json = JsonUtility.ToJson(Vector2Int.left);
Debug.Log(json); // {}

JsonUtility.ToJson

private フィールド、static フィールドや NonSerialized 属性を適用されるフィールドのようなサポートされていないフィールドは無視されます。

JsonUtility.ToJson を見てみると上記のように記載されています。 つまり変換できないということは内部値はシリアライズできない値となっていると推測されます

公式リポジトリからVector2Intのコードを見てみると、private なフィールドであることが分かります

    public struct Vector2Int : IEquatable<Vector2Int>, IFormattable
    {
        private int m_X;
        private int m_Y;
    }

これではそのまま Json に変換できないので、一つクラスを経由して変換する必要がでてきます

JsonUtility 用クラス

面倒だが以下のクラスを経由することで json として扱える

var json = JsonUtility.ToJson(new VectorIntJson(Vector2Int.left));
Debug.Log(json); // {"x":-1,"y":0,"z":0}
var v2i = VectorIntJson.Convert2Int(json);
Debug.Log(v2i); //(-1, 0)
using System;
using UnityEngine;

    [Serializable]
    public struct VectorIntJson
    {
        [SerializeField]
        private int x;
        [SerializeField]
        private int y;
        [SerializeField]
        private int z;
 
        public VectorIntJson(Vector2Int vector)
        {
            x = vector.x;
            y = vector.y;
            z = 0;
        }
         
        public VectorIntJson(Vector3Int vector)
        {
            x = vector.x;
            y = vector.y;
            z = vector.z;
        }
 
        public static Vector2Int Convert2Int(string json)
        {
            var vij = JsonUtility.FromJson<VectorIntJson>(json);
            return new Vector2Int(vij.x, vij.y);
        }
         
        public static Vector3Int Convert3Int(string json)
        {
            var vij = JsonUtility.FromJson<VectorIntJson>(json);
            return new Vector3Int(vij.x, vij.y, vij.z);
        }
    }

【C#】リフレクションを利用してArrayやIListにデータを追加する

        public static void AddListByReflection(this FieldInfo fieldInfo, object obj)
        {
            var elementType = fieldInfo.FieldType.GetArrayType();
            if (elementType == null)
                return;

            var instance = Activator.CreateInstance(elementType);
            if (fieldInfo.FieldType.IsArray)
            {
                var array = fieldInfo.GetValue(obj) as Array;
                var newArray = Array.CreateInstance(elementType, array.Length + 1);
                for (var i = 0; i < array.Length; i++)
                {
                    newArray.SetValue(array.GetValue(i), i);
                }
                newArray.SetValue(instance, array.Length);
                fieldInfo.SetValue(obj, newArray);
            }
            else if (fieldInfo.FieldType.IsGenericType && fieldInfo.FieldType.GetGenericTypeDefinition() == typeof(List<>))
            {
                var list = fieldInfo.GetValue(obj) as IList;
                list.Add(instance);
            }
        }

        private static Type GetArrayType(this Type type)
        {
            if (type.IsArray)
                return type.GetElementType();
 
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
                return type.GetGenericArguments()[0];

            return null;
        }

【Unity】シーン上の Graphic を Hierarchy 上で選択できるボタンを表示する

github.com

上記 Scene Drawer の Show Graphic Object の機能を拡張しました

従来通りに Scene 上に名前が表示されるのは変わらず、その名前をクリックすることで Hierarchy 上で選択してくれます

これで UI がどれか分からない問題が解決するようになりました

f:id:hacchi_man:20220309001754p:plain

【Unity】SceneView のカメラから GUI Rect の座標を取得する

[ExecuteAlways]
public class SampleMonoBehaviour : MonoBehaviour
{
    private void OnEnable()
    {
        SceneView.duringSceneGui += SceneGUI;
    }

    private void SceneGUI(SceneView sceneView)
    {
        var pos = sceneView.camera.WorldToViewportPoint(transform.position);
        Handles.BeginGUI();
        GUI.Label(new Rect(pos, new Vector2(100, 12)), "test");
        Handles.EndGUI();
    }
}

上記のように、sceneView のカメラから Viewport座標を取得しても正しい位置に表示されない

それもそのはずで、GUI の描画に利用してるのは Viewport座標を利用していないため、

Window のサイズに合わせて適切に変換してあげる必要がある

その変換処理が以下

public static class SceneViewHelper
{
    /// <summary>
    /// WorldToViewportPoint は Toolbar のサイズを考慮していないため考慮して計算する
    /// </summary>
    public static Vector3 SceneViewWorldToScreenPoint(SceneView sceneView, Vector2 worldPosition)
    {
        var pointInView = sceneView.camera.WorldToViewportPoint(worldPosition);
        var screenPosition = pointInView * sceneView.position.size;
        screenPosition.y = sceneView.position.height - screenPosition.y;
 
        return screenPosition;
    }
}

これを利用して先程の処理を書き換えるとこうなる

    private void SceneGUI(SceneView sceneView)
    {
        var pos = SceneViewHelper.SceneViewWorldToScreenPoint(sceneView, transform.position);
        Handles.BeginGUI();
        GUI.Label(new Rect(pos, new Vector2(100, 12)), "test");
        Handles.EndGUI();
    }

これで正しい位置に表示させることができる

【Unity】Inspector 上で Enum 名を変更する

Unity 2019.2 から追加された InspectorName を利用することで Inspector 上で表示する Enum 名を変更することができる

    public enum Position
    {
        [InspectorName("右")]
        Right,
        [InspectorName("左")]
        Left,
        [InspectorName("真ん中")]
        Center
    }

f:id:hacchi_man:20220308225710p:plain:w400

f:id:hacchi_man:20220308232909p:plain:w400