1.8 0から学ぶUnityゲーム開発 -- オブジェクトコンポーネントの作成

前回の記事でゲームオブジェクトの機能部品コードの作成方法について事前に触れましたが、今回はこのコードがUnityでどのように使われるのか、パラメータを表示できる機能部品の書き方について詳しく解説していきます。インスペクターについて。

Unity リフレクションは宣言サイクル関数を呼び出します

前の章では、Start 関数が適切なタイミングで Unity によって呼び出されるという知識を叩き込みましたが、これは私たちの常識とは異なり、関数が呼び出されるときは次のようなコードが存在する必要があります。

classA.Start();

しかし、Unity のコンポーネント クラスでは、Unity から呼び出すためにそのようなコードは必要ありません。また、この Start 関数ですら依然としてプライベート アクセス制御であり、通常のメソッドを外部から呼び出すことはまったくできません。

そこで Unity は C# 言語の機能であるリフレクションを使用します。

非常に強力に聞こえますが、実際には、コンパイラは作成したコードの内容を記録し、コードはリフレクションを通じてどの関数や変数を作成したかを認識し、関数の呼び出し、変数の値の変更、その他の操作も行うことができます。 。

分かりませんか?大丈夫です。手動で実装しましょう。C# はポインターに関してはあまり直観的ではないため、C++ に戻って例を見てみましょう。

class Test
{
private:
    void Func() {}
};

Test t;
t.Func();  // <-- 错误,你无法调用到private下的成员函数

ここでは、新しい Test クラスを定義し、それに Func メンバー関数を与えますが、これはプライベート アクセス制御であるため、通常の状況では、Test のインスタンスを通じてこの Func 関数を呼び出すことはできません。

一方、関数ポインタを理解していれば、クラスのメンバ関数にもポインタがあることが分かるので、クラスのメンバ関数を手の届く場所にポインタとして保存するだけで済みます。電話してみましょう。魔法をかけてみましょう:

#include <iostream>

class Test
{
public:
    // 为了方便,我们将Func函数指针的类型换个名字叫MemberFunctionPointerType
    typedef void (Test::*MemberFunctionPointerType)();
    // 定义一个public函数来获取私有的成员函数指针
    MemberFunctionPointerType GetFuncPointer() { return &Test::Func; }
private:
    void Func() { std::cout << "Hello world!"; }
};

int main() {
    Test t;
    auto memberFunc = t.GetFuncPointer();
    // 调用获取到的私有函数指针
    (t.*memberFunc)();
    return 0;
}

このコードを実行すると、Hello world! という出力が表示されます。関数ポインターに慣れていない場合は、基本をもう一度学ぶことができます。

何らかの魔法により、アクセス不可能な関数の呼び出しに成功しましたが、この関数は通常の方法では呼び出し参照を見つけることができませんでした (参照は IDE 上で見つかりません)。

C# にはそのようなメカニズムが直接組み込まれています。コンパイラーはこれらのものを直接生成し、最終的にプログラムに組み込み、関数を呼び出すだけでなく、操作およびアクセスできるさらに多くのものを呼び出します。

たとえば、公式の例には次のようなコードがあります。

// Using GetType to obtain type information:
int i = 42;
Type type = i.GetType();
Console.WriteLine(type);

組み込み型 int を含む任意の C# 型の場合、GetType() を通じてこの型の情報を取得できます。また、Type クラスを通じて、どの関数、どの関数など、この型コードで定義されているあらゆる種類のものを取得できます。その後、メンバーは関数を呼び出すことができます。

次に、Unity では、ゲームの開始時に作成した GameObject または MonoBehaviour クラスが保持するコンポーネントを収集し、リフレクションを通じてコード内で必要なすべてのコールバックを取得し、対応するタイミングでインスタンスを使用します。コンポーネントクラスの+リフレクションで得られた関数情報を利用して作成したロジックを呼び出します。

実際に Unity アプローチを実装すると、次のようになります。

// 从GameObject中拿到每个组件,其中就有我们的DemoTest类
Monobehaviour behaviour = demoTest;

// 反射得到我们写的DemoTest的类型信息
Type type = behaviour.GetType();

// 然后找一下Start方法
MethodInfo startFunction = type.GetMethod("Start", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

// 然后在适当的时机调用Start方法,第一个参数是调用哪个实例的方法,第二个是参数是方法的参数数组
startFunction.Invoke(demoTest, null);

これは、Start メソッドを呼び出すコードが表示されないにもかかわらず、実際には呼び出されている理由を説明しています。

Start メソッドに加えて、呼び出すことができるメソッドは他にもたくさんあります。完全なリストについては、公式ドキュメントのこの図を参照してください。

Unity - マニュアル: イベント関数の実行順序https://docs.unity3d.com/Manual/ExecutionOrder.html

Unity 表示コンポーネントの編集可能なパラメータ

前の記事では、Cube に付属するコンポーネントの一部には、パラメーターを変更できる入力ボックスがあることがわかりました。たとえば、Transform コンポーネントは、位置の XYZ 座標の値を変更できます。コンポーネントに追加する必要がある場合は、エディターが変更を提示した場合はどうすればよいですか?

実際、これは非常に単純で、上記によると、Unity はリフレクションを通じて作成したコード内のメンバー変数と関数を取得し、その後、Unity は同じメソッドを使用してエディターに表示したいパラメーターを取得します。修正。

次のいずれかを満たすメンバー変数は、リフレクションを通じて Unity によって収集されます。

  1. アクセス制御はパブリックです
  2. アクセス制御はパブリックではありませんが、[SerializeField] などの属性が追加されます。次に例を示します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DemoTest : MonoBehaviour
{
    public int testValue1;
    
    [SerializeField]
    private int testValue2;

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Hello World!");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

変更して Unity を自動的にコンパイルした後、Cube を再度選択してインスペクター パネルを観察できます。

2 つの変数が有効になっていることがわかります。また、Unity は表示名の最適化にも役立ちました。

ただし、すべてのタイプのデータをエディターに表示できるわけではないことに注意してください。表示をサポートするには、データ型がシリアル化可能である必要があります。Unity はデフォルトで多くのタイプのシリアル化操作をサポートしていますが、カスタム クラスはサポートされていませんUnity でこれをサポートしたい場合は、対応するクラスの定義にシリアル化可能な属性を追加する必要があります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DemoTest : MonoBehaviour
{
    public int testValue1;
    
    [SerializeField]
    private int testValue2;

    [System.Serializable]   // <-- 这一行是关键,必须有这个属性,否则Unity是无法识别这个类型数据的
    public class CustomDataType
    {
        public int nestValue;
    }

    public CustomDataType testValue3;

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Hello World!");
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

メンバーがカスタム タイプの場合、このカスタム タイプは [System.Serializable] 属性を追加する必要があることがわかります。そうしないと、Unity によって認識されず、変更後の効果は次のようになります。

これで、Cube DemoTest のいくつかのパラメータの値を任意に調整できるようになりました。その後、この時点でさらに疑問が生じます。変更した値はどこに保存されているのでしょうか?

Unityのリソースのシリアル化

上記の質問の続きですが、シリアル化に関しては、コンポーネントのパラメーターを変更するだけでなく、Scene 内にシーンを直接構築して新しい GameObject を作成し、実際に新しいデータを生成していると直感的に考えることができます。次に、データ シリアル化の実装はファイルに実装する必要があり、最初から最後まで Demo というリソース ファイルを作成しただけなので、すべてのデータは Demo のリソース ファイルに保存されたと考えるのが論理的ですシーン。

そうです。Ctrl+S を押して現在の変更を保存し、リソース マネージャーでこのデモ ファイルを直接見つけてみましょう (Mac では、Finder にあるはずです)。

このファイルは実際にはテキスト ファイルであり、任意のテキスト エディターを使用して開くことができます。ここでは vscode で開きます。

最初に、このファイルが実際には YAML 形式のファイルであることがわかります。シーン内で作成した Cube という唯一のオブジェクトを検索すると、このファイルを見つけることができます。

ご存知ですか? この Cube は GameObject であり、GameObject には m_Component 配列があり、この GameObject 上のコンポーネントを格納するために使用されます。それらは 5 つあります。その他には m_Name の値などが含まれます。 BoxCollider コンポーネントなどの具体的な詳細があり、次に作成した DemoTest コンポーネントを見ていきます。

コンポーネントの値を変更していない場合、これらの変数はデフォルト値のままであるため、実際には表示されません。インスペクターで値を手動で変更し、保存して確認してみましょう。

エディター上で変更した値が表示されることがわかります。ここでは、Unity が実際にリフレクションを通じてシリアル化したい変数を取得し、シリアル化をサポートするコンテンツがデフォルトで表示されることがわかります。 Inspector では、変数に対応する値が特定のリソース ファイルにシリアル化されます。たとえば、シーン内のすべてのコンテンツは、シーンが配置されているリソース ファイルにシリアル化されます (もちろん、プレハブについては後で説明しますが、例外もあります) )。

次の章

この章では、Unity が GameObject の最も重要なコンポーネントをどのように編成するか、作成したコードを呼び出す方法、設定データをシリアル化する方法について詳しく説明し、Unity がシーン全体をどのように編成するかをより明確に理解できるようになります。データ。

次の章では、もう少し簡単に、ゲームをパッケージ化してリリースする方法について説明します。このステップは少しだけで完了しますが、他の人に体験してもらうためにリリースすることは、私たちにとって重要なステップです。

おすすめ

転載: blog.csdn.net/z175269158/article/details/129832302