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() が呼ばれる
演習アイデア
- CarController.enabled = false にして途中で true に戻す
- CarController を Move(移動)と PlayOnReach(到達時音)に分割して合成
- SpriteRenderer.OnAdded() で Transform の有無をチェック
- Speed を時間で変化させる 加速 Behaviour を追加
まとめ
- 状態=Component, 振る舞い=Behaviour に分けると、Unity の合成思想が腹落ちする
- Get<T>() で依存解決を行い、依存チェックは Awake に集約
- 実際の Unity 学習へ進む際も、責務分割と合成を意識すると設計が安定します
訪問数 5 回, 今日の訪問数 5回
ディスカッション
コメント一覧
まだ、コメントがありません