C#だけで「Unityの中身」を理解する

— Component と Behaviour を分けた最小フレームワーク(car サンプル)

目的とゴール

  • 対象:C#/WinForms の学習者
  • ゴール:Unity の Scene → GameObject → Component/Behaviour と Awake/Start/Update の関係を、素の C# で体感する

設計の考え方

  • Component … 状態・機能の部品(ライフサイクルは持たない)
  • Behaviour : Component … 振る舞い(Awake/Start/Update を持つ)
  • GameObject … 部品の入れ物(Add/Get とライフサイクルの中継)

コアクラス

Component(状態の部品)

abstract class Component
{
    public GameObject gameObject { get; internal set; }
    public virtual void OnAdded() { } // 追加時の任意フック
}

Behaviour(振る舞い)

abstract class Behaviour : Component
{
    public bool enabled = true;
    public virtual void Awake() { }
    public virtual void Start() { }
    public virtual void Update(float dt) { }
}

GameObject(入れ物)

class GameObject
{
    private readonly List<Component> _components = new();
    private readonly List<Behaviour> _behaviours = new();

    public T Add<T>(T comp) where T : Component
    {
        comp.gameObject = this;
        _components.Add(comp);
        if (comp is Behaviour b) _behaviours.Add(b);
        comp.OnAdded();
        return comp;
    }

    public T Get<T>() where T : Component =>
        _components.OfType<T>().FirstOrDefault();

    internal void Awake()  { foreach (var b in _behaviours) b.Awake(); }
    internal void Start()  { foreach (var b in _behaviours) b.Start(); }
    internal void Update(float dt)
    {
        foreach (var b in _behaviours) if (b.enabled) b.Update(dt);
    }
}

代表的な部品(Component)

class Transform : Component { public float X, Y; }

class SpriteRenderer : Component { public string SpritePath; }

class AudioSource : Component
{
    public string Clip;
    public void Play() { /* 効果音を鳴らす想定の処理 */ }
}

CarController(Behaviour の例)

class CarController : Behaviour
{
    public float Speed = 4f, GoalX = 0f;

    private Transform _tr;
    private AudioSource _audio;
    private bool _reached;

    public override void Awake()
    {
        _tr = gameObject.Get<Transform>() ?? throw new InvalidOperationException();
        _audio = gameObject.Get<AudioSource>(); // あれば使う
    }

    public override void Update(float dt)
    {
        if (_reached) return;

        _tr.X += Speed * dt;

        if (_tr.X >= GoalX)
        {
            _reached = true;
            _audio?.Play();
        }
    }
}

最小 Scene とランナー

class Scene
{
    private readonly List<GameObject> _roots = new();

    public GameObject Create(string name)
    {
        var go = new GameObject(name);
        _roots.Add(go);
        return go;
    }

    public void AwakeAll() { foreach (var go in _roots) go.Awake(); }

    public void StartAll() { foreach (var go in _roots) go.Start(); }

    public void Step(float dt) { foreach (var go in _roots) go.Update(dt); }
}
class Program
{
    static void Main()
    {
        var scene = new Scene();

        // car_0
        var car = scene.Create("car_0");
        car.Add(new Transform { X = -7f, Y = -3.7f });
        car.Add(new SpriteRenderer { SpritePath = "car.png" });
        car.Add(new AudioSource   { Clip = "car_se.wav" });
        car.Add(new CarController { Speed = 4.0f, GoalX = 0.0f });

        // flag_0
        var flag = scene.Create("flag_0");
        flag.Add(new Transform { X = 0f, Y = -3.7f });
        flag.Add(new SpriteRenderer { SpritePath = "flag.png" });

        // ライフサイクル
        scene.AwakeAll();
        scene.StartAll();

        // メインループ(4秒・30FPS)
        const int fps = 30;
        float dt = 1f / fps;

        for (int i = 0; i < 4 * fps; i++)
        {
            scene.Step(dt);
            System.Threading.Thread.Sleep((int)(dt * 1000)); // 簡易スリープ
        }
    }
}

観察ポイント

  • Awake → Start → Update の順で呼ばれる
  • Update が届くのは Behaviour だけ
  • Transform.X が 速度 × 時間(dt) で増える
  • ゴール到達で AudioSource.Play() が呼ばれる

演習アイデア

  1. CarController.enabled = false にして途中で true に戻す
  2. CarController を Move(移動)と PlayOnReach(到達時音)に分割して合成
  3. SpriteRenderer.OnAdded() で Transform の有無をチェック
  4. Speed を時間で変化させる 加速 Behaviour を追加

まとめ

  • 状態=Component振る舞い=Behaviour に分けると、Unity の合成思想が腹落ちする
  • Get<T>() で依存解決を行い、依存チェックは Awake に集約
  • 実際の Unity 学習へ進む際も、責務分割と合成を意識すると設計が安定します

訪問数 5 回, 今日の訪問数 5回

Unity

Posted by hidepon