びぼうろくってみんなやってる

みんなやってるからぼくもやる

そのうち役立ちそうなことメモっときたい。

  • 処理負荷調べたいとき。
    • Profilerの下半分、Hierarchyで見られるようにする。
using UnityEngine.Profiling;

public class TestClass
{
  public void Test(){
    Profiler.BeginSample("処理負荷測定したい");
    /*ここの負荷を測り、Profilerに表示*/
    Profiler.EndSample();
  }
}
  • interfaceの明示的な実装
    • interface経由でのみメンバーの呼び出しが可能になる。
public interface ITestClass
{
    int Number { get; }
    int INumber { get; set; }


    void Test();
    void ITest();
}

public class TestClass : ITestClass
{
    public int Number { get; private set; }
    int ITestClass.INumber { get; set; }

    public void Test(){}
    void ITestClass.ITest(){}
}
public class UseClass
{
    ITestClass iTestClass = new TestClass();
    TestClass testClass = new TestClass();

    void Execute()
    {
        int iTestNum = iTestClass.Number;
        int iTestINum = iTestClass.INumber;

        int testNum = testClass.Number;
        //int testINum = testClass.INumber; // 実体からは呼ぶことが出来ない

        iTestClass.Test();
        iTestClass.ITest();

        testClass.Test();
        //testClass.ITest(); //実体からは呼ぶことが出来ない

    }
}
  • NSubstitute
    • https://github.com/nsubstitute/NSubstitute/downloads
    • (解凍フォルダ)\lib\NET35\NSubstitute.dll
      • Unityの.NETのバージョンによってはNET40の方で
    • これををunityの"Assets/Plugins"以下へコピー
    • エディタで NSubstitute.dll を選択し、ExcludePlatformsのEditor以外のチェックをONにしておく
    • 便利。
using NSubstitute;

public class NSubstituteTest
{

    public ITestModel CreateMockModel()
    {
        var model = Substitute.For<ITestModel>();
        model.Name.Returns("たかし");
        model.Number.Returns(0);

        model.GetJobId(0).Returns(0);

        return model;
    }

}

public interface ITestModel
{
    string Name { get; }
    int Number { get; }

    int GetJobId(int id);
}

public class TestModel : ITestModel
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public int GetJobId(int id)
    {
        return 0;
    }
}

JobSystemMemo

  • [ReadOnly]と[WriteOnly]
    • [ReadOnly]はコピーを取り出すのみ
    • [WriteOnly]は新規生成を書き込むのみ
void Update()
{
  var nativeNumbers = new NativeArray<int>(length,Allocator.Temp);
    
  var jobA = new TestJobA
  {
    numbers = nativeNumbers
  };

  var jobB = new TestJobB
  {
    numbers = nativeNumbers
  };

  var jobHandleA = jobA.Schedule(length, innerLoopBatchCount);
  jobB.Schedule(length,innerLoopBatchCount,jobHandleA).Complete();

  nativeNumbers.Dispose();
}

public struct TestJobA : IJobParallelFor
{
  [ReadOnly] public NativeArray<int> numbers
  void IJobParallelFor.Execute(){}
}

public struct TestJobB : IJob
{
  [ReadOnly] public NativeArray<int> numbers;
  void IJob.Execute(){}
}
  • 上記の様な例だと、同じnativeNumbersを参照するTestJobAとTestJobBがあるため、[ReadOnly]をつけて、同時に処理しても、順番が入れ替わっても問題がないことを保証する必要があるっぽい。
  • TestJobAかBのどちらか一方の場合は、[ReadOnly]をつける必要は無い。
  • 書き方を間違えるとUnityが叱ってくれるので優しい。

  • こちらを見ながらもっと学ばねばという。

tsubakit1.hateblo.jp

JobSystemおべんきょう

Unity JobSystem

  • テラシュールブログ様の写経

  • JobSystemは並列処理らしい。

    • 並列処理が簡単にできるぞ的な。
    • あと書き方間違ってたらエラー吐いてくれる(優しい)。
    • 値型のみ使える。
    • 参照型は使えない。
  • NativeArrayなるもの。

    • NativeArrayは、ネイティブメモリのバッファをマネージコードに公開し、マネージドとネイティブの間でデータを共有することを可能にします。(公式 with Google翻訳)
    • 速いらしい。
    • 要素をあとから増やせない。
    • 構造体structのみ使える。
    • クラスclassは使えない。
    • Allocator(メモリの割当タイプ)を自分で決める必要がある。
      • Temp 割当と開放が速いらしい。1F内で開放する必要がある。
      • Persistent 割当と開放が遅いらしい。永続的な割当。Destroy,Disable等で開放する。
    • 使い終わったら必ず開放する必要がある。.Dispose();

    • TransformAccessArrayなるものもある。

      • 通常のComponentと違い、参照型なのにJobSystemで扱える。
    • 以下宣言から開放のサンプル。

using System.Linq;
using UnityEngine;
using Unity.Collections;

public class JobSystemTest : MonoBehaviour
{
  TransformAccessArray targets;
  NativeArray<RaycastCommand> commands;
  int length = 10;

  void OnEnable()
  {
    var targetList = new List<Transform>();
    targets = new TransformAccessArray(targetList.ToArray())
    commands = new NativeArray<RaycastCommand>(length, Allocator.Persistent);
  }

  void Update()
  {
    var results = new NativeArray<RaycastHit>(length, Allocator.Temp);

    //---
    //いろいろやる
    //----

    results.Dispose();
  }

  void OnDisable()
  {
    commands.Dispose();
    targets.Dispose();
  }
}
  • IJobParallelForなるもの

    • これを継承した構造体のExecute()内に記述した処理が、各ジョブで実行される。
    • IJobParallelForTransformなるものもある
  • JobHandleなるもの

    • JobHandle。(公式 with Google翻訳)
    • IJobParallelForを継承した構造体.Schedule()の返り値がこれ。
    • .Complete()でジョブの完了を待つ
    • .Schedule()の第三引数にJobHandleを登録すると、登録したJobHandleの完了を待ってから実行。
    • RaycastCommand.ScheduleBatch()なるものもある。
void RandomBounce()
{
  for(int i = 0; i < targetCount; i++)
  {
    bounce[i] = Random.Range(0.5f, 2.5f);
  }
}

void CreateJobs()
{
  handle.Complete();
  for(int i = 0; i < targetCount; i++)
  {
    commands[i] = new RaycastCommand(targets[i].position, Vector3.down);
  }

  UpdatePosition updatePositionJob = new UpdatePosition
  {
    raycastResults = results,
    velocitys = velocity,
    bounces = bounce
  };

  ApplyPosition applyPosition = new ApplyPosition
  {
    velocitys = velocity
  };

  var raycastJobHandle = RaycastCommand.ScheduleBatch(commands, results, Mathf.FloorToInt(targetCount / 3));
  var updatePositionHandle = updatePositionJob.Schedule(targetCount, Mathf.FloorToInt(targetCount / 3), raycastJobHandle);
  handle = applyPosition.Schedule(targets, updatePositionHandle);
}

struct UpdatePosition : IJobParallelFor
{
  [ReadOnly] public NativeArray<RaycastHit> raycastResults;

  public NativeArray<float> velocitys;
  public NativeArray<float> bounces;

  void IJobParallelFor.Execute(int index)
  {
    if(velocitys[index] < 0 && raycastResults[index].distance < 0.5f)
    {
      velocitys[index] = bounces[index];//Random.Rangeとかは使えない
    }
    velocitys[index] -= gravity;
  }
}

struct ApplyPosition : IJobParallelForTransform
{
  public NativeArray<float> velocitys;

  void IJobParallelForTransform.Execute(int index, TransformAccess transform)
  {
    transform.localPosition += Vector3.up * velocitys[index];
  }
}
  • [ReadOnly]が以外と大事だったりするのでまた書かないとならない。

  • 成果物。

using System.Linq;

using UnityEngine;
using UnityEngine.Jobs;

using Unity.Collections;
using Unity.Jobs;

public class JobSystemTest : MonoBehaviour
{
    [SerializeField] CanvasGroup canvasGroup;
    Transform[] transforms;

    NativeArray<RaycastCommand> commands;
    NativeArray<RaycastHit> results;

    NativeArray<float> velocity;
    NativeArray<float> bounce;

    TransformAccessArray targets;

    const int RaycastDistance = 130;
    const float gravity = 0.098f;

    int targetCount;

    JobHandle handle;

    void OnEnable()
    {
        var targetList = GetComponentsInChildren<Transform>().ToList();
        targetList.Remove(transform);
        targetCount = targetList.Count;

        foreach(var target in targetList)
        {
            target.localPosition = new Vector3(Random.Range(5, 45), 4, Random.Range(5, 45));
        }

        commands = new NativeArray<RaycastCommand>(targetCount, Allocator.Persistent);
        results = new NativeArray<RaycastHit>(targetCount, Allocator.Persistent);
        velocity = new NativeArray<float>(targetCount, Allocator.Persistent);

        for(int i = 0; i < targetCount; i++)
        {
            velocity[i] = -1.0f;
        }

        bounce = new NativeArray<float>(targetCount, Allocator.Persistent);
        targets = new TransformAccessArray(targetList.ToArray());
    }

    void OnDisable()
    {
        handle.Complete();

        commands.Dispose();
        results.Dispose();
        velocity.Dispose();

        bounce.Dispose();
        targets.Dispose();

    }

    void Update()
    {
        RandomBounce();
        CreateJobs();
    }

    void RandomBounce()
    {
        for(int i = 0; i < targetCount; i++)
        {
            bounce[i] = Random.Range(0.5f, 2.5f);
        }
    }

    void CreateJobs()
    {
        handle.Complete();
        for(int i = 0; i < targetCount; i++)
        {
            commands[i] = new RaycastCommand(targets[i].position, Vector3.down);
        }

        UpdatePosition updatePositionJob = new UpdatePosition
        {
            raycastResults = results,
            velocitys = velocity,
            bounces = bounce
        };

        ApplyPosition applyPosition = new ApplyPosition
        {
            velocitys = velocity
        };

        HitCheck hitCheckJob = new HitCheck
        {
            raycastResults = results,
            result = new NativeArray<int>(1, Allocator.TempJob)
        };

        var raycastJobHandle = RaycastCommand.ScheduleBatch(commands, results, Mathf.FloorToInt(targetCount / 3));
        var isHitJobHandle = hitCheckJob.Schedule(raycastJobHandle);
        var updatePositionHandle = updatePositionJob.Schedule(targetCount, Mathf.FloorToInt(targetCount / 3), raycastJobHandle);
        handle = applyPosition.Schedule(targets, updatePositionHandle);

        isHitJobHandle.Complete();
        canvasGroup.alpha = hitCheckJob.result[0];
        hitCheckJob.result.Dispose();
    }


    struct UpdatePosition : IJobParallelFor
    {
        [ReadOnly] public NativeArray<RaycastHit> raycastResults;

        public NativeArray<float> velocitys;
        public NativeArray<float> bounces;

        void IJobParallelFor.Execute(int index)
        {
            if(velocitys[index] < 0 && raycastResults[index].distance < 0.5f)
            {
                velocitys[index] = bounces[index];//Random.Rangeとかは使えない
            }
            velocitys[index] -= gravity;
        }
    }

    struct ApplyPosition : IJobParallelForTransform
    {
        public NativeArray<float> velocitys;

        void IJobParallelForTransform.Execute(int index, TransformAccess transform)
        {
            transform.localPosition += Vector3.up * velocitys[index];
        }
    }

    struct HitCheck : IJob
    {
        [ReadOnly] public NativeArray<RaycastHit> raycastResults;
        public NativeArray<int> result;

        void IJob.Execute()
        {
            foreach(var raycastResult in raycastResults)
            {
                if(raycastResult.distance < 1.0f) { result[0] = 1; return; }
            }
            result[0] = 0;
        }
    }
}

Zenject めも

  • 以下公式より、Google翻訳を添えて

  • DiContainer.Bind

    • すべての依存性注入フレームワークは、最終的に型をインスタンスにバインドするためのフレームワークに過ぎません。
    • Zenjectでは、依存関係のマッピングは、コンテナと呼ばれるものにバインディングを追加することによって行われます。
    • コンテナは、特定のオブジェクトのすべての依存関係を再帰的に解決することによって、アプリケーション内のすべてのオブジェクトインスタンスを作成する方法を「認識」する必要があります。
    • コンテナは、指定された型のインスタンスを作成するように要求されると、C#reflectionを使用してコンストラクタ引数のリスト、および[Inject]属性でマークされたすべてのフィールド/プロパティを検索します。次に、コンストラクターを呼び出して新しいインスタンスを作成するために必要なこれらの依存関係のそれぞれを解決しようとします。
    • したがって、各Zenjectアプリケーションは、バインドコマンドを介して行われるこれらの依存関係のそれぞれの解決方法をコンテナに伝えなければなりません。たとえば、次のクラスを指定します。
  • DiContainer.Resolve

へぇ〜

  • いきなり全部理解するのも全部使うのもそれを広めるのも全部無理。

    • BindしてResolveしたりしなかったりしてInjectすると便利。という理解でいく。
  • Bindする側

  • MonoInstallerを継承してContainer.Bind<>();
  • PrefabもScene上のオブジェクトもクラスもインターフェースもファクトリーもBindできるしなんならIDもつけられる。
using UnityEngine;
using Zenject;

public class TestInstaller : MonoInstaller
{
    [SerializeField] GameObject testObject;
    [SerializeField] TestBehaviour testBehaviour;
    [SerializeField] TextAsset textAssetA;
    [SerializeField] TextAsset textAssetB;

    public override void InstallBindings()
    {
        Container.Bind<GameObject>().FromInstance(testObject).AsCached();

        Container.Bind<TestBehaviour>().FromComponentInNewPrefab(testBehaviour).AsSingle();

        Container.Bind<TextAsset>().WithId("TextAssetA").FromInstance(textAssetA).AsTransient();
        Container.Bind<TextAsset>().WithId("TextAssetB").FromInstance(textAssetB).AsTransient();

        Container.BindInterfacesAndSelfTo<TestClass>().AsSingle();
        Container.BindFactory<TestClass, TestClassFactory>();
    }

}
  • 使う側
    • [Inject] したらいい感じに使える
    • Id付きでInjectもできる。
    • DiContainerもInjectできるので好きなときにInjectしたりUnBindしたりできる。
using Zenject;

public class TestInjecter
{
    [Inject] GameObject testObject;
    [Inject] TestBehaviour testBehaviour;
    [Inject(Id = "TextAssetA")] TextAsset textAssetA;
    [Inject(Id = "TextAssetB")] TextAsset textAssetB;

    [Inject] ITestClass testClass;
    [Inject] TestClassFactory testClassFactory;

    [Inject] DiContainer container;

    public TestInjecter()
    {
        var test = testClassFactory.Create();
        Debug.Log("Number: " + test.Number + ", Name:" + test.Name);

        container.Bind<ITestClass>();

        container.Unbind<TestClassFactory>();
    }
}
  • 以下特に意味なし
public interface ITestClass
{
    int Number { get; }
    string Name { get; }
}

public class TestClass : ITestClass
{
    public int Number { get; private set; }
    public string Name { get; private set; }

    public TestClass()
    {
        Number = 0;
        Name = "たかし";

    }
}

public class TestBehaviour : MonoBehaviour
{

}

public class TestClassFactory : Factory<TestClass>
{

}



yamlとC#

  • yaml 忘れそうだよ

  • をつけた行は#をつけたところからコメント

     * MarkDownですげぇでかくなってウケる

    • 以下は配列の中身
  • てか大体下みたいな感じで書く

#ParentClass:
description: なんのかんの説明
children: 
  - name: たかし
    size: medium
    age: 10
    male: true
    
  - name: ひろし
    size: big
    age: 120
    male: false
  • C#とつなげたい私

  • C#

public class ParentClass
{
  public string description { get; private set; }
  public List<ChildClass> children { get; private set; }
}

public class ChildClass
{
  public string name { get; private set; }
  public Size size { get; private set; }
  public int age { get; private set; }
  public bool male { get; private set; }
}
ParentClass GetParentClass(TextAsset yamlTextAsset)
{
  var input = new StringReader(yamlTextAsset.text);//System.IO
  var deserializer = new DeserializerBuilder().Build();//YamlDotNet.Serialization
  return deserializer.Deserialize<ParentClass>(input);
}
  • YamlDotNetForUnityとかつかう

uGUITextとTMProを一括で扱いたい人

やりたいこと

  • uGUITextとTextMeshProを一括で扱いたい
    • TextComponentとして一つにまとめる

なにができるの

  • uGUITextとTextMeshProをUnityEditor上で好きなときに切り替えられる。

動機

  • エンジニアはスクリプト側でuGUIかTMProか気にしたくない。
  • デザイナーはエディタ側を弄ってもスクリプトに触りたくはないと思ってると思ってると思う。
  • 互いが想定するコンポーネントを一つにしたい。

欲しい機能

  • UnityEditor上でuGUIとTMProを選択できる。
  • ScriptableObjectでフォントの設定を保持できる。
  • Script上ではuGUITextと同じ扱いができる。

必要なもの

  • TextComponent
    • 本体。
  • TextComponentEditor
    • エディタ拡張。
  • TextConfig
    • フォント設定のScriptableObject。

書くこと

  • TextComponent
    • [SerializeField] TextType textType
      • enumで外から選びたい。
    • [SerializeField] TextConfig TextConfig
      • フォント設定をD&Dで入れたい。
      • もちろんスクリプト側でも触れると良い。
    • string text
      • 本文
      • 混乱の元なのでEditor側には出さない

つかいかた

TextComponent

  • 新しくTextComponentをつける場合
    • オブジェクトのInspectorでAddComponentを押してTextComponentを選択。
    • TextComponentのTextTypeプルダウンでUGUIかTextMeshProを選択。
  • 既にuGUITextまたはTMProがオブジェクトにアタッチされている場合。
    • オブジェクトのInspectorでAddComponentを押してTextComponentを選択。
    • TextComponent上で右クリックして、GetValueを選択。
      • スクリプト上でTextComponent側からTextをいじれるようになる。
      • このタイミングでTextComponentのメンバ変数textに本文が入る。
    • UGUIのTextをTMProにしたい場合はTextComponentのTextTypeプルダウンで選択。
      • uGUITextが消えてTMProがアタッチされる。
    • 文章が消えるので、TextComponent上で右クリックしてSetTextを選択する。
      • 元の文章が反映される。

TextConfig

  • TextConfigを作る。
    • projectの、TextConfigを置きたい場所で右クリック-> Create -> UI -> TextConfigを選択。
      • 生成されたTextConfigを選択し、名前をつける。
      • Inspectorで値を設定する。
  • 作ったTextConfigを使いたい。
    • TextComponentのTextConfigの枠に使いたいTextConfigをD&D。
      • ドロップした時点で反映される。
    • あとはuGUIかTMProのText設定を行う。

スクリプト側での使い方。

  • [SerializeField] TextComponent textComponent; とかで宣言して、
    • textComponent.Text = "例文"; とかでstringを投げ込む。

ソースコード

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class TextComponent : MonoBehaviour
{
    public enum TextType
    {
        None = 0,
        UGUI,
        TextMeshPro
    }

    public TextConfig textConfig;

    public TextType textType;
    TextType preTextType;

    RectTransform rectTransform;
    Text uGUIText;
    TextMeshProUGUI textMeshPro;

    string text;

    public string Text
    {
        set
        {
            text = value;
            switch(textType)
            {
                case TextType.None:
                    break;
                case TextType.UGUI:
                    if(uGUIText == null) { uGUIText = GetComponent<Text>(); }
                    uGUIText.text = value;
                    break;
                case TextType.TextMeshPro:
                    if(textMeshPro == null) { textMeshPro = GetComponent<TextMeshProUGUI>(); }
                    textMeshPro.text = value;
                    break;
            }
        }
    }


    [ContextMenu("GetValue")]
    void GetValue()
    {
        if(uGUIText != null || textMeshPro != null) { Debug.Log("Already have TextComponent"); return; }

        uGUIText = GetComponent<Text>();
        textMeshPro = GetComponent<TextMeshProUGUI>();

        if(uGUIText != null)
        {
            text = uGUIText.text;
            textType = TextType.UGUI;
            preTextType = textType;
        }

        if(textMeshPro != null)
        {
            text = textMeshPro.text;
            textType = TextType.TextMeshPro;
            preTextType = textType;
        }
    }

    [ContextMenu("SetText")]
    void SetText()
    {
        switch(textType)
        {
            case TextType.None:
                Debug.LogWarning("先にGetValueしてください");
                break;
            case TextType.UGUI:
                if(uGUIText == null) { Debug.LogWarning("先にGetValueしてください"); return; }
                uGUIText.text = text;
                break;
            case TextType.TextMeshPro:
                if(textMeshPro == null) { Debug.LogWarning("先にGetValueしてください"); return; }
                textMeshPro.text = text;
                break;
        }
    }


    void SetValue()
    {
        if(textType == TextType.None) { return; }
        if(textConfig == null) { return; }

        if(textConfig.IsSetAnchor)
        {
            if(rectTransform == null) { rectTransform = GetComponent<RectTransform>(); }
            rectTransform.anchorMin = new Vector2(textConfig.MinX, textConfig.MinY);
            rectTransform.anchorMax = new Vector2(textConfig.MaxX, textConfig.MaxY);
        }

        switch(textType)
        {
            case TextType.UGUI:

                if(textConfig.IsSetFont) { uGUIText.font = textConfig.UGUIFont; }
                if(textConfig.IsSetSize) { uGUIText.fontSize = textConfig.FontSize; }
                if(textConfig.IsSetColor) { uGUIText.color = textConfig.FontColor; }
                if(textConfig.IsSetAutoSize)
                {
                    uGUIText.resizeTextForBestFit = textConfig.BestFit;
                    uGUIText.resizeTextMinSize = textConfig.UGUIMinSize;
                    uGUIText.resizeTextMaxSize = textConfig.UGUIMaxSize;
                }
                if(textConfig.IsSetAlignment) { uGUIText.alignment = textConfig.UGUIAlignment; }
                break;
            case TextType.TextMeshPro:
                if(textConfig.IsSetFont) { textMeshPro.font = textConfig.TMPFont; }
                if(textConfig.IsSetSize) { textMeshPro.fontSize = textConfig.FontSize; }
                if(textConfig.IsSetColor) { textMeshPro.color = textConfig.FontColor; }
                if(textConfig.IsSetAutoSize)
                {
                    textMeshPro.enableAutoSizing = textConfig.AutoSize;
                    textMeshPro.fontSizeMin = textConfig.TMPMinSize;
                    textMeshPro.fontSizeMax = textConfig.TMPMaxSize;
                }
                if(textConfig.IsSetAlignment) { textMeshPro.alignment = textConfig.TMPAlignment; }
                if(textConfig.IsSetMaterial) { textMeshPro.material = textConfig.TMPMaterial; }
                break;
        }
    }

    public void SetTextType()
    {
        if(textType == preTextType) { SetValue(); return; }

        switch(textType)
        {
            case TextType.None:
                None();
                preTextType = textType;
                break;
            case TextType.UGUI:
                UGUI();
                break;
            case TextType.TextMeshPro:
                TextMeshPro();
                break;
        }
        SetValue();
        preTextType = textType;
    }

    void None()
    {
        if(uGUIText != null)
        {
            text = uGUIText.text;
            DestroyImmediate(uGUIText);
        }

        if(textMeshPro != null)
        {
            text = textMeshPro.text;
            DestroyImmediate(textMeshPro);
        }
    }
    void UGUI()
    {
        None();
        uGUIText = gameObject.AddComponent<Text>();
    }
    void TextMeshPro()
    {
        None();
        textMeshPro = gameObject.AddComponent<TextMeshProUGUI>();
    }
}
  • EditorScript
using UnityEditor;

[CustomEditor(typeof(TextComponent), true)]
public class TextComponentEditor : Editor
{
    TextComponent text;

    TextConfig config;
    TextComponent.TextType type;

    void OnEnable()
    {
        text = target as TextComponent;
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        text.textConfig = EditorGUILayout.ObjectField("TextConfig", text.textConfig, typeof(TextConfig), true) as TextConfig;
        text.textType = (TextComponent.TextType)EditorGUILayout.EnumPopup("TextType", text.textType);

        serializedObject.ApplyModifiedProperties();

        if(text.textConfig != config || text.textType != type)
        {
            config = text.textConfig;
            type = text.textType;
            text.SetTextType();
        }
    }
}
  • ScriptableObject
using UnityEngine;
using TMPro;

public interface ITextConfig
{
    Font UGUIFont { get; }
    TMP_FontAsset TMPFont { get; }
    int FontSize { get; }
    Color FontColor { get; }
}

[CreateAssetMenu(fileName = "TextConfig", menuName = "UI/Text Config")]
public class TextConfig : ScriptableObject, ITextConfig
{
    public enum VerticalAnchor
    {
        Top = -1,
        Middle = 0,
        Bottom,
        Stretch
    }

    public enum HorizontalAnchor
    {
        Left = -1,
        Center = 0,
        Right,
        Stretch,
    }


    [Header("・共通")]
    [SerializeField] VerticalAnchor verticalAnchor;
    [SerializeField] HorizontalAnchor horizontalAnchor;
    [SerializeField] int fontSize;
    [SerializeField] Color fontColor = Color.white;

    [Header("・uGUI")]
    [SerializeField] Font uguiFont;
    [SerializeField] bool uguiBestFit;
    [SerializeField] int uguiMinSize;
    [SerializeField] int uguiMaxSize;
    [SerializeField] TextAnchor uguiAlignment;

    [Header("・TMPro")]
    [SerializeField] TMP_FontAsset tmpFont;
    [SerializeField] Material tmpMaterial;
    [SerializeField] bool tmpAutoSize;
    [SerializeField] int tmpMinSize;
    [SerializeField] int tmpMaxSize;
    [SerializeField] TextAlignmentOptions tmpAlignment;

    [Header("・適用フラグ")]
    [SerializeField] bool setAnchor;
    [SerializeField] bool setFont;
    [SerializeField] bool setSize;
    [SerializeField] bool setColor;
    [SerializeField] bool setAutoSize;
    [SerializeField] bool setAlignment;
    [SerializeField] bool setMaterial;

    public Font UGUIFont { get { return uguiFont; } }
    public TMP_FontAsset TMPFont { get { return tmpFont; } }
    public int FontSize { get { return fontSize; } }
    public Color FontColor { get { return fontColor; } }
    public bool BestFit { get { return uguiBestFit; } }
    public int UGUIMinSize { get { return uguiMinSize; } }
    public int UGUIMaxSize { get { return uguiMaxSize; } }
    public TextAnchor UGUIAlignment { get { return uguiAlignment; } }

    public bool AutoSize { get { return tmpAutoSize; } }
    public int TMPMinSize { get { return tmpMinSize; } }
    public int TMPMaxSize { get { return tmpMaxSize; } }
    public TextAlignmentOptions TMPAlignment { get { return tmpAlignment; } }
    public Material TMPMaterial { get { return tmpMaterial; } }

    public bool IsSetAnchor { get { return setAnchor; } }
    public bool IsSetFont { get { return setFont; } }
    public bool IsSetSize { get { return setSize; } }
    public bool IsSetColor { get { return setColor; } }
    public bool IsSetAutoSize { get { return setAutoSize; } }
    public bool IsSetAlignment { get { return setAlignment; } }
    public bool IsSetMaterial { get { return setMaterial; } }

    public float MinX
    {
        get
        {
            switch(horizontalAnchor)
            {
                case HorizontalAnchor.Left:
                    return 0f;
                case HorizontalAnchor.Center:
                    return 0.5f;
                case HorizontalAnchor.Right:
                    return 1.0f;
                case HorizontalAnchor.Stretch:
                    return 0f;
                default:
                    return 0.5f;
            }
        }
    }
    public float MaxX
    {
        get
        {
            switch(horizontalAnchor)
            {
                case HorizontalAnchor.Left:
                    return 0f;
                case HorizontalAnchor.Center:
                    return 0.5f;
                case HorizontalAnchor.Right:
                    return 1.0f;
                case HorizontalAnchor.Stretch:
                    return 1.0f;
                default:
                    return 0.5f;
            }
        }
    }
    public float MinY
    {
        get
        {
            switch(verticalAnchor)
            {
                case VerticalAnchor.Top:
                    return 1.0f;
                case VerticalAnchor.Middle:
                    return 0.5f;
                case VerticalAnchor.Bottom:
                    return 0f;
                case VerticalAnchor.Stretch:
                    return 0f;
                default:
                    return 0.5f;
            }
        }
    }
    public float MaxY
    {
        get
        {
            switch(verticalAnchor)
            {
                case VerticalAnchor.Top:
                    return 1.0f;
                case VerticalAnchor.Middle:
                    return 0.5f;
                case VerticalAnchor.Bottom:
                    return 0f;
                case VerticalAnchor.Stretch:
                    return 1.0f;
                default:
                    return 0.5f;
            }
        }
    }

}