【Unity】文字列でGameObject, Component, 関数名, 引数を指定して関数を実行する


# Reflectionを使って文字列から関数を実行する
まずは具体例から

~~~cs:Playerクラス
class Player
{
    public void Attack(string name, int atk)
    {
        Debug.Log($"{name}　は　{atk}　のダメージを与えた！");
    }
}
~~~

このAttack関数を実行したいとき、普段ならこのように書きます

~~~cs:普段の書き方
Player player = new Player();
player.Attack("田中", 30);
~~~

今回は
・関数を実行するインスタンス
・実行する関数名
・引数のstring配列
の3つを使って実行してみます

~~~cs:Attackを実行
// リフレクションを使って関数を呼び出す
public void CallFunction()
{
    Player player = new Player(); // 関数を実行したいインスタンス
    string[] parameters = new string[] { "田中", "30" }; // 引数の文字列
    string methodName = "Attack"; //実行したい関数名

    // playerの型情報typeを取得
    Type type = player.GetType();
    // PlayerクラスのAttack関数の情報を取得
    MethodInfo method = type.GetMethod(methodName);

    // Attack関数の引数情報を取得
    ParameterInfo[] paramInfos = method.GetParameters();

    // parametersの型をstringから引数の型に直したモノを入れる配列
    object[] typedParameters = new object[parameters.Length];

    // 引数情報paranInfosで取得した型にparametersを変換
    for (int i = 0; i < parameters.Length; i++)
    {
        typedParameters[i] = Convert.ChangeType(parameters[i], paramInfos[i].ParameterType);
    }

    // 関数を実行
    method.Invoke(player, typedParameters);
}
~~~

上の具体例のインスタンス, methodName, parametersを引数に変えたものがこちら

~~~cs:引数にチェンジ
// リフレクションを使って関数を呼び出す
void CallFunctionByMethodName(object obj, string methodName, string[] parameters)
{
    // objの型情報typeを取得
    Type type = obj.GetType();
    // objのクラスの名前が methodName の関数の情報を取得する
    MethodInfo method = type.GetMethod(methodName);

    if (method != null)
    {
        ParameterInfo[] paramInfos = method.GetParameters();

        if (parameters != null && parameters.Length >= 1)
        {
            object[] typedParameters = new object[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                typedParameters[i] = 
                    Convert.ChangeType(parameters[i], paramInfos[i].ParameterType);
            }

            method.Invoke(obj, typedParameters);

        }
        else
        {
            method.Invoke(obj, null);
        }
    }
}
~~~

# GameObjectの名前とコンポーネントも文字列で指定する
さらにGameObjectの名前とコンポーネントも文字列で指定して関数を呼び出すように変更します。

文字列からGameObjectを取得するには`GameObject.Find(string name)`を利用します。
~~~cs
GameObject gameObject = GameObject.Find(gameObjectName);
~~~

文字列からコンポーネントを取得するには`Component GetComponent(string type)`を利用します。
~~~cs
gameObject.GetComponent(componentName);
~~~


呼び出したいときに渡す情報をクラスにまとめておきます。
~~~cs
class FunctionByString
{
    public string GameObjectName { get; set; }
    public string ComponentName { get; set; }
    public string FunctionName { get; set; }
    public string[] Parameters { get; set; }
}
~~~

上のFunctionByStringを渡して実行するプログラムがこちら
~~~cs
void CallFunctionBy(FunctionByString functionByString)
{
    // 文字列からGameObjectを取得
    GameObject targetObject = GameObject.Find(functionByString.GameObjectName);
    if (targetObject != null)
    {
        // 文字列からコンポーネントを取得
        Component targetComponent =
            targetObject.GetComponent(functionByString.ComponentName);
        if (targetComponent != null)
        {
            // さっき作った関数に渡す
            CallFunctionByMethodName(
                targetComponent, 
                functionByString.FunctionName, 
                functionByString.Parameters);
        }
    }
}
~~~


# 何が嬉しいか
### CSVなどで関数の実行を制御できる
イベントシーンの動きをCSVなどで制御できます




# 問題点
### GameObject.FindやGetComponentを毎回行っている
重めの処理なのでよく使うコンポーネントはDictionaryとかにまとめておいた方がよいかも

# 参考文献
https://learn.microsoft.com/ja-jp/dotnet/api/system.reflection.methodinfo.invoke?view=netframework-1.1

https://docs.unity3d.com/ja/2019.4/ScriptReference/GameObject.GetComponent.html

https://unixo-lab.com/unity/methods.html

https://qiita.com/block/items/3773c01003ee12f49761

https://stackoverflow.com/questions/50765628/how-to-convert-a-variable-type-to-another-variable-type