【Unity】内部構造を理解する

Unityの学習において、GameObjectを体に例えることで、Unityのコンポーネントベースのアーキテクチャを理解しやすくなります。Unityでは、すべてのゲームオブジェクトは基本的に空のコンテナとして始まります。これは人間の体で言うところの「体」に相当します。骨格だけでは機能しませんが、必要な「臓器」や「神経(イベント)」を追加することで、様々な動作や機能を持たせることができます

人間の体の作りに例える(基本)

体(GameObject)

GameObjectを「体」と考えることで、Unityのゲームオブジェクトをより包括的に捉えることができます。体はさまざまな器官や機能を統合して一つの生命体として機能します。同様に、GameObjectはそれ自体は単なる容器ですが、様々なコンポーネントやスクリプトを追加することで、目に見える形や振る舞い、機能を持つオブジェクトへと変化します。

臓器一覧(Components:部品群、パーツ群)

Unityでは、様々な「器官」に相当するコンポーネントをゲームオブジェクトに追加することで、それを機能させます。例えば、視覚を担う「目」はカメラコンポーネントに、移動能力を持たせる「足」はリジッドボディとコライダーに相当します。

足(Component:パーツ)
脳(Component:パーツ)

スクリプトを「脳」と考えることもできます。脳が体の機能を制御し、指示を出すように、スクリプトはGameObjectの振る舞いや反応を制御します。プレイヤーの入力を受け取り、オブジェクト間の相互作用やゲームのルールを定義することで、ゲーム世界内でのオブジェクトの動きや機能を決定します

このように、GameObjectを「体」とみなし、スクリプトをその「脳」として捉えることで、Unityでのゲーム開発の概念を人間の体とその機能に例えることができます。この比喩を用いることで、Unityのゲーム開発プロセスを直感的に理解しやすくなり、ゲームオブジェクトがどのように機能するのかを視覚化しやすくなります

Unityエディター

2つ目の体を作ると

コードでシミュレートしてみる(UnityEnjine内部構造の範囲)

Unityの概念を体に例えるアナロジーに基づいて、シンプルなUnityエンジンのシミュレートコードを書いてみましょう。この例では、GameObjectを「体」と考え、Componentを体の「臓器」や機能として捉えます。特に「足」に相当するコンポーネントとを作成します。そして、「脳」として機能するスクリプトを通じて、足オブジェクトの動きを制御します。

まずは、体を作る

臓器の入れ物として体を作ります

// 体を作る
var プレイヤー1の体 = new 体("太郎の体");

public class 体
{
    public string 名前 { get; set; }

    public 体(string name)
    {
        名前 = name;
    }
}

体のパーツとして足を作る

臓器一覧で管理する足を作ります
体では、臓器の一覧としたいので、臓器を継承する様にして実現しています

// 足を作る
public class 足 : 臓器
{
    public 足(体 body) : base(body)
    {
    }

    // ここに特定の機能を実装
    public void 走る()
    {
        Console.WriteLine($"{body.名前}の足で走る");
    }
}

このコードは、体関係に例えた名前付けを使った簡単なクラスの定義を示しています。具体的には、「足」クラスが定義されており、これは「臓器」クラスを継承しています。このコードの構造は、オブジェクト指向プログラミングの継承の概念を利用して、より具体的な機能(この場合は「走る」機能)を実装しています。

  • 臓器クラスの継承: クラスは臓器クラスから継承されています。これは、臓器の一種であることを意味し、臓器クラスに定義されているプロパティやメソッドをクラスでも使用できることを意味します。
  • コンストラクタ: クラスのコンストラクタは、オブジェクトを引数として受け取り、それを基底クラス(臓器クラス)のコンストラクタに渡しています。これにより、クラスは、どのに属しているかを知ることができます。
  • 走るメソッド: クラスには走るというメソッドが定義されています。このメソッドが呼び出されると、コンソールに「〇〇の足で走る」と表示されます。ここで〇〇は、オブジェクトの名前プロパティによって決まります。このメソッドは、クラスの特定の機能を示しており、がどのようにして「走る」動作を行うかを表しています。

このコードは、クラス間の関係や継承、そして特定の機能の実装に関するシンプルな例を提供しています。体と臓器の関係をプログラミングの世界に落とし込むことで、オブジェクト指向の概念を視覚的にも直感的に理解しやすくしています。このような比喩を使うことで、プログラミングの学習者がオブジェクト指向設計の原則をより深く理解するのに役立つかもしれません。

public 足(体 body) : base(body)

: base(body)この部分は、派生クラス()のコンストラクタから基底クラス(臓器)のコンストラクタを呼び出すための構文です。これにより、インスタンスはそのインスタンスに関連付けられ、インスタンスへの参照を内部的に保持することができます。

その結果、クラス内で定義されたメソッド(例えば走るメソッド)は、インスタンスの属性やメソッドにアクセスできるようになります。ここでは、走るメソッドが実装されており、コンソールに{body.名前}の足で走ると表示することで、特定のインスタンスの名前属性を使用しています。

プレイヤー1の体に臓器を追加する

コンストラクタの引数で、体の情報(インスタンス)を渡す

// 臓器の追加
プレイヤー1の体.臓器の追加(new 足(プレイヤー1の体));
プレイヤー1の体.臓器の追加(new 脳(プレイヤー1の体));

体の機能強化

臓器の追加のためのメソッドを追加します

public class 体
{
    // 臓器を保持するリスト
    private List<臓器> 臓器一覧 = new List<臓器>();

    // コンポーネントを追加するメソッド
    public void 臓器の追加(臓器 component)
    {
        臓器一覧.Add(component);
    }

臓器とは?の元となるものを定義

このコードは、Unityのゲームオブジェクトとコンポーネントの関係を人間の体と臓器に例えて抽象化したものです。この抽象クラスを基に、様々な種類の臓器(コンポーネント)を実装し、(ゲームオブジェクト)に追加することで、ゲームオブジェクトに特定の機能や振る舞いを与えることができます。

// コンポーネントの基底クラス
public abstract class 臓器
{
    public 体 body { get; private set; }

    public 臓器(体 body)
    {
        this.body = body;
    }
}
  • 臓器クラス (Organクラス): これは抽象クラスであり、実際にインスタンス化することはできません。このクラスは、Unityのコンポーネントが持つべき基本的な特徴や振る舞いのテンプレートとして機能します。すなわち、このクラスを継承することで、具体的な「臓器」の種類(例: 心臓など)を作成することができます。
  • プロパティ:
    • public 体 body { get; private set; }: このプロパティは、臓器が属する(ゲームオブジェクト)への参照を保持します。get; private set;とすることで、このプロパティが公開されている一方で、臓器クラス外部からは設定できないようになっています。つまり、臓器は自分がどのに属しているかを知ることができますが、一度設定された後はそのを変更することはできません。
  • コンストラクタ:
    • public 臓器(体 body): このコンストラクタは、臓器クラスの新しいインスタンスを作成する際に呼び出されます。引数として(ゲームオブジェクト)を受け取り、それをbodyプロパティに設定します。これにより、新しく作成される臓器が、どのに属しているかが定義されます。

脳が考え、指令を出す

// 脳が考える
var プレイヤー1の脳 = プレイヤー1の体.臓器の特定<脳>();
プレイヤー1の脳.考える();

臓器を特定して、指示を出せる様にするメソッドを追加

public class 体
{
    // 特定の型のコンポーネントを取得するメソッド
    public T 臓器の特定<T>() where T : 臓器
    {
        foreach (var 臓器 in 臓器一覧)
        {
            if (臓器 is T)
            {
                return 臓器 as T;
            }
        }

        return null;
    }
}

このコードは、クラス内で特定のタイプの臓器(コンポーネント)を検索し、それを返すメソッド臓器の特定<T>()を定義しています。

  • public class 体: クラスは、UnityのGameObjectに例えられます。GameObjectはさまざまなコンポーネントを持つことができる「容器」であり、このクラスも様々な臓器(コンポーネント)を持つことができると考えられます。
  • public T 臓器の特定<T>() where T : 臓器: このメソッドは、(GameObject)が持つ臓器(コンポーネント)の中から、指定された型T臓器を検索し、見つかればその臓器を返す役割を持ちます。where T : 臓器は、このメソッドがジェネリック型Tに対して型制約を設けており、T臓器クラスまたはそのサブクラスの型でなければならないことを示しています。
  • foreach (var 臓器 in 臓器一覧): この部分では、が持つ全ての臓器(コンポーネントのリストまたは配列)をループしています。各臓器に対して、指定された型Tと一致するかどうかをチェックしています。
  • if (臓器 is T): ここで、現在の臓器が求められている型Tに一致するかどうかを確認しています。isキーワードは型の一致をチェックするために使用されます。
  • return 臓器 as T:Tに一致する臓器が見つかった場合、それを型Tとして返します。asキーワードは、型変換を行い、変換できない場合はnullを返します。
  • return null: 指定された型T臓器が見つからなかった場合、メソッドはnullを返します。

このメソッドは、UnityのGetComponent<T>()メソッドに似ており、ゲームオブジェクトから特定のコンポーネントを取得する際に使用されますが、ここでは人体の臓器に例えることで直感的な理解を促しています。

プログラマのコーディング範囲

体のパーツとして脳を作る

public class 脳 : 臓器
{
    public 脳(体 body) : base(body)
    {
    }

    public void 考える()
    {
        body.臓器の特定<足>().走る();
    }
}

全体コード

// 体を作る
var プレイヤー1の体 = new 体("太郎の体");

// 臓器の追加
プレイヤー1の体.臓器の追加(new 足(プレイヤー1の体));
プレイヤー1の体.臓器の追加(new 脳(プレイヤー1の体));

// 脳が考える
var プレイヤー1の脳 = プレイヤー1の体.臓器の特定<脳>();
プレイヤー1の脳.考える();

public class 体
{
    public string 名前 { get; set; }

    // 臓器を保持するリスト
    private List<臓器> 臓器一覧 = new List<臓器>();

    public 体(string name)
    {
        名前 = name;
    }

    // コンポーネントを追加するメソッド
    public void 臓器の追加(臓器 component)
    {
        臓器一覧.Add(component);
    }

    // 特定の型のコンポーネントを取得するメソッド
    public T 臓器の特定<T>() where T : 臓器
    {
        foreach (var 臓器 in 臓器一覧)
        {
            if (臓器 is T)
            {
                return 臓器 as T;
            }
        }

        return null;
    }
}

// コンポーネントの基底クラス
public abstract class 臓器
{
    public 体 body { get; private set; }

    public 臓器(体 body)
    {
        this.body = body;
    }
}

// GameObjectクラスのサンプル実装


// カスタムコンポーネントのサンプル
public class 足 : 臓器
{
    public 足(体 body) : base(body)
    {
    }

    // ここに特定の機能を実装
    public void 走る()
    {
        Console.WriteLine($"{body.名前}の足で走る");
    }
}

public class 脳 : 臓器
{
    public 脳(体 body) : base(body)
    {
    }

    public void 考える()
    {
        body.臓器の特定<足>().走る();
    }
}

実行結果

太郎の体の足で走る

クラス図

例えをUnityで使われている名前群に置換する

// 体を作る
var car1 = new GameObject("car1");

// 臓器の追加
car1.AddComponent(new Transform(car1));
car1.AddComponent(new CarController(car1));

// 脳が考える
var carController = car1.GetComponent<CarController>();
carController.Start();

public class GameObject
{
    public string Nane { get; set; }

    // 臓器を保持するリスト
    private List<Component> Components = new List<Component>();

    public GameObject(string name)
    {
        Nane = name;
    }

    // コンポーネントを追加するメソッド
    public void AddComponent(Component component)
    {
        Components.Add(component);
    }

    // 特定の型のコンポーネントを取得するメソッド
    public T GetComponent<T>() where T : Component
    {
        foreach (var Component in Components)
        {
            if (Component is T)
            {
                return Component as T;
            }
        }

        return null;
    }
}

// コンポーネントの基底クラス
public abstract class Component
{
    public GameObject body { get; private set; }

    public Component(GameObject body)
    {
        this.body = body;
    }
}

// GameObjectクラスのサンプル実装


// カスタムコンポーネントのサンプル
public class Transform : Component
{
    public Transform(GameObject body) : base(body)
    {
    }

    // ここに特定の機能を実装
    public void 走る()
    {
        Console.WriteLine($"{body.Nane}の足で走る");
    }
}

public class CarController : Component
{
    public CarController(GameObject body) : base(body)
    {
    }

    public void Start()
    {
        body.GetComponent<Transform>().走る();
    }
}

医者が患者を診察するサンプル

namespace UnityEngine
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            // 体を作る
            var プレイヤー1の体 = new 体("太郎の体");
            var 医者の体 = new 体("駿の体");

            // 臓器の追加
            プレイヤー1の体.臓器の追加(new 足(プレイヤー1の体));
            プレイヤー1の体.臓器の追加(new 脳(プレイヤー1の体));

            医者の体.臓器の追加(new 医者脳(医者の体));

            // 脳が考える
            var プレイヤー1の脳 = プレイヤー1の体.臓器の特定<脳>();
            プレイヤー1の脳.考える();

            var 医者の脳 = 医者の体.臓器の特定<医者脳>();
            医者の脳.考える();
        }
    }
}
using UnityEngine;

public class 医者脳 : 臓器
{
    // patientBody: 患者の体
    体 patientBody;

    // コンストラクタはUnityEngineの動き
    public 医者脳(体 body) : base(body)
    {
    }

    public void 考える()
    {
        patientBody = 体.見つける("太郎の体");
        診察する();
    }

    void 診察する()
    {
        Console.WriteLine($"{body.名前}が{patientBody.名前}を診察した");
    }
}

参考

Unity Engine の「体」に例えた構造

  • GameObject – “体" (Body): Unityのゲームオブジェクトは、全てのコンポーネントを保持する容器であり、これを体全体に例えます。全ての機能はこの「体」を通じて表現されます。
  • Transform – “骨格" (Skeleton): Transformコンポーネントは、ゲームオブジェクトの位置、回転、スケールを制御します。これを骨格に例えることができ、体の形と基本的な構造を提供します。
  • Rigidbody – “筋肉" (Muscles): Rigidbodyコンポーネントは物理法則に基づいてゲームオブジェクトを動かします。これを筋肉に見立て、体(ゲームオブジェクト)を動かす力となります。
  • Collider – “皮膚" (Skin): Colliderはオブジェクトの物理的な境界を定義します。触れたり触れられたりする感覚を提供する皮膚に例えられます。
  • Script – “脳" (Brain): スクリプトはゲームオブジェクトの振る舞いを定義し、コントロールします。脳として、体の機能を調整し、意志を実行します。
  • Camera – “目" (Eyes): Cameraコンポーネントはゲームの視界を提供します。これを目に見立て、プレイヤーがゲーム世界を見る窓となります。
  • AudioSource – “耳" (Ears): AudioSourceコンポーネントはゲーム内で音を生成します。これを耳に見立て、ゲーム世界の聴覚的な側面を担います。

このようにUnityエンジンの各コンポーネントを体の部分に例えることで、ゲーム開発の概念を直感的に捉えやすくすることができます。この比喻を通じて、Unityでのゲーム開発がより理解しやすくなることを願っています。

C#

Posted by hidepon