WinFormsでUnityのライフサイクルをシミュレートする

1. はじめに

Unityのオブジェクトは、特定のタイミングで実行される 「ライフサイクルイベント」 によって動作します。本チュートリアルでは、Windowsフォーム(WinForms)のイベントを利用して、Unityのライフサイクル(Awake → Start → Update → FixedUpdate → LateUpdate)をシミュレート します。


2. Unityのライフサイクルとは?

Unityのライフサイクルイベントは、オブジェクトの生成から削除まで、以下の順序で実行されます。

Unityのライフサイクル説明
Awakeオブジェクトが生成されたときに1回実行(初期化)
Startオブジェクトがアクティブになった直後に1回実行
Update毎フレーム(約16msごと)に実行
FixedUpdate物理演算のために固定間隔(例:50ms)で実行
LateUpdateUpdate の後に実行(後処理)
OnDestroyオブジェクトが削除されるときに実行

この流れを WinFormsのイベントとタイマーを活用 して再現していきます。


3. 開発環境の準備

3.1 必要なもの

  • Visual Studio 2019 / 2022
  • .NET 6+ または .NET Framework
  • C#(Windows Formsアプリ)

3.2 プロジェクトの作成

  1. Visual Studio を開く
  2. 新しいプロジェクトの作成」を選択
  3. Windows フォーム アプリ (.NET または .NET Framework)」を選択
  4. 任意のプロジェクト名を入力し、作成

4. UnityライフサイクルをWinFormsでシミュレート

4.1 フォームのイベントを登録

Unityのライフサイクルに相当する処理を、WinFormsのイベントに紐付ける必要があります。
以下の対応表を参考にしてください。

UnityのライフサイクルWinFormsのイベント説明
AwakeForm.Loadフォームが読み込まれたときに実行
StartForm.Shownフォームが表示された直後に実行
UpdateTimer.Tick毎フレームごとに実行
FixedUpdateTimer.Tick物理演算用の別のタイマーで実行
LateUpdateUpdate の後に手動呼び出しUpdate の後に実行
OnDestroyForm.FormClosingフォームが閉じられるときに実行

4.2 フォームのイベントを設定する

フォームのデザイン画面から、以下の手順でイベントを登録します。

1. MainForm のデザイン画面を開く

  1. ソリューションエクスプローラーでForm1.csを右クリックし、名前の変更でMainForm.csに変更
  2. ソリューションエクスプローラーMainForm.cs をダブルクリック

2. Load イベントを設定

  1. フォーム上の空白部分をクリック
  2. プロパティウィンドウ「イベント」タブ(⚡マーク) を開く
  3. Load イベントを見つけて MainForm_Load と入力(またはダブルクリック)

3. Shown イベントを設定

  1. 同様に Shown イベントを見つけ、 MainForm_Shown と入力(またはダブルクリック)

4. FormClosing イベントを設定

  1. FormClosing イベントを見つけ、 MainForm_FormClosing と入力(またはダブルクリック)

5. コードにイベントハンドラを追加

以下のコードを MainForm.cs に記述します。


4.3 コードの記述

using System;
using System.Windows.Forms;
using System.Diagnostics;
using Timer = System.Windows.Forms.Timer;

namespace UnityLifecycleSimulation
{
    public partial class MainForm : Form
    {
        private Timer updateTimer;  // Updateのためのタイマー(毎フレーム実行)
        private Timer fixedUpdateTimer;  // FixedUpdateのためのタイマー(一定間隔で実行)
        private Stopwatch stopwatch;  // 経過時間を測るためのストップウォッチ
        private bool startCalled = false;  // Startが一度だけ実行されるようにするためのフラグ

        public MainForm()
        {
            InitializeComponent();
            Text = "Unityライフサイクルシミュレーション"; // ウィンドウタイトルの設定
        }

        // フォームがロードされた時に呼ばれる(UnityのAwake相当)
        private void MainForm_Load(object sender, EventArgs e)
        {
            Awake();
        }

        // フォームが表示された後に呼ばれる(UnityのStart相当)
        private void MainForm_Shown(object sender, EventArgs e)
        {
            // 少し遅延させてStartを呼び出す(即座に呼ばれないように)
            Timer startTimer = new Timer { Interval = 10 };
            startTimer.Tick += (s, ev) =>
            {
                startTimer.Stop();
                Start();
            };
            startTimer.Start();
        }

        // UnityのAwake相当: 初期化処理
        private void Awake()
        {
            Debug.WriteLine("[Awake] 初期化処理を実行しました");
            MessageBox.Show("Awake: 初期化処理が実行されました", "Unityライフサイクル", MessageBoxButtons.OK, MessageBoxIcon.Information);

            stopwatch = new Stopwatch();
            stopwatch.Start();

            // Update用タイマー(約60FPS)
            updateTimer = new Timer { Interval = 16 };
            updateTimer.Tick += Update;

            // FixedUpdate用タイマー(物理計算向け、50msごとに実行)
            fixedUpdateTimer = new Timer { Interval = 50 };
            fixedUpdateTimer.Tick += FixedUpdate;
        }

        // UnityのStart相当: ゲーム開始時の処理
        private void Start()
        {
            if (startCalled) return;  // Startが二重に呼ばれないように
            startCalled = true;

            Debug.WriteLine("[Start] ゲーム開始処理を実行しました");
            MessageBox.Show("Start: ゲーム開始処理が実行されました", "Unityライフサイクル", MessageBoxButtons.OK, MessageBoxIcon.Information);

            updateTimer.Start();  // Updateの開始
            fixedUpdateTimer.Start();  // FixedUpdateの開始
        }

        // UnityのUpdate相当: 毎フレーム実行される処理
        private void Update(object sender, EventArgs e)
        {
            Debug.WriteLine($"[Update] フレーム更新: {stopwatch.ElapsedMilliseconds}ミリ秒経過");
            LateUpdate(); // Updateの後にLateUpdateを呼び出す
        }

        // UnityのFixedUpdate相当: 一定間隔で実行される処理(物理計算向け)
        private void FixedUpdate(object sender, EventArgs e)
        {
            Debug.WriteLine($"[FixedUpdate] 物理演算処理: {stopwatch.ElapsedMilliseconds}ミリ秒経過");
        }

        // UnityのLateUpdate相当: Update後に実行される処理
        private void LateUpdate()
        {
            Debug.WriteLine($"[LateUpdate] 後処理: {stopwatch.ElapsedMilliseconds}ミリ秒経過");
        }

        // フォームが閉じられるときに実行(UnityのOnDestroy相当)
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            updateTimer?.Stop();  // Updateのタイマーを停止
            fixedUpdateTimer?.Stop();  // FixedUpdateのタイマーを停止
            Debug.WriteLine("[OnDestroy] アプリケーション終了処理を実行しました");
        }
    }
}
[Awake] 初期化処理を実行しました
'UnityLifecycleSimulation.exe' (CoreCLR: clrhost): 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.2\System.ComponentModel.TypeConverter.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
[Start] ゲーム開始処理を実行しました
[Update] フレーム更新: 3568ミリ秒経過
[LateUpdate] 後処理: 3574ミリ秒経過
[Update] フレーム更新: 3607ミリ秒経過
[LateUpdate] 後処理: 3610ミリ秒経過
[FixedUpdate] 物理演算処理: 3623ミリ秒経過
[Update] フレーム更新: 3632ミリ秒経過
[LateUpdate] 後処理: 3634ミリ秒経過
[Update] フレーム更新: 3647ミリ秒経過
[LateUpdate] 後処理: 3650ミリ秒経過
[FixedUpdate] 物理演算処理: 3679ミリ秒経過
[Update] フレーム更新: 3681ミリ秒経過
[LateUpdate] 後処理: 3684ミリ秒経過
[Update] フレーム更新: 3701ミリ秒経過
[LateUpdate] 後処理: 3704ミリ秒経過
[Update] フレーム更新: 3737ミリ秒経過
[LateUpdate] 後処理: 3741ミリ秒経過
[FixedUpdate] 物理演算処理: 3753ミリ秒経過
[Update] フレーム更新: 3770ミリ秒経過
[LateUpdate] 後処理: 3773ミリ秒経過
[Update] フレーム更新: 3801ミリ秒経過
[LateUpdate] 後処理: 3804ミリ秒経過
[FixedUpdate] 物理演算処理: 3817ミリ秒経過
[Update] フレーム更新: 3820ミリ秒経過
[LateUpdate] 後処理: 3822ミリ秒経過
[Update] フレーム更新: 3855ミリ秒経過
[LateUpdate] 後処理: 3857ミリ秒経過
[FixedUpdate] 物理演算処理: 3886ミリ秒経過
[Update] フレーム更新: 3889ミリ秒経過
[LateUpdate] 後処理: 3892ミリ秒経過
[Update] フレーム更新: 3918ミリ秒経過
[LateUpdate] 後処理: 3920ミリ秒経過
[Update] フレーム更新: 3930ミリ秒経過
[LateUpdate] 後処理: 3933ミリ秒経過
[FixedUpdate] 物理演算処理: 3946ミリ秒経過
[Update] フレーム更新: 3948ミリ秒経過
[LateUpdate] 後処理: 3950ミリ秒経過
[Update] フレーム更新: 3977ミリ秒経過
[LateUpdate] 後処理: 3979ミリ秒経過
[FixedUpdate] 物理演算処理: 4008ミリ秒経過
[Update] フレーム更新: 4010ミリ秒経過
[LateUpdate] 後処理: 4012ミリ秒経過
[Update] フレーム更新: 4040ミリ秒経過
[LateUpdate] 後処理: 4042ミリ秒経過
[FixedUpdate] 物理演算処理: 4072ミリ秒経過
[Update] フレーム更新: 4074ミリ秒経過
[LateUpdate] 後処理: 4076ミリ秒経過
[Update] フレーム更新: 4104ミリ秒経過
[LateUpdate] 後処理: 4106ミリ秒経過
[FixedUpdate] 物理演算処理: 4130ミリ秒経過
[Update] フレーム更新: 4132ミリ秒経過
[LateUpdate] 後処理: 4134ミリ秒経過
[Update] フレーム更新: 4147ミリ秒経過
[LateUpdate] 後処理: 4149ミリ秒経過
[Update] フレーム更新: 4183ミリ秒経過
[LateUpdate] 後処理: 4185ミリ秒経過
[FixedUpdate] 物理演算処理: 4199ミリ秒経過
[Update] フレーム更新: 4202ミリ秒経過
[LateUpdate] 後処理: 4203ミリ秒経過
[Update] フレーム更新: 4233ミリ秒経過
[LateUpdate] 後処理: 4236ミリ秒経過
[FixedUpdate] 物理演算処理: 4259ミリ秒経過
[Update] フレーム更新: 4261ミリ秒経過
[LateUpdate] 後処理: 4264ミリ秒経過
[Update] フレーム更新: 4277ミリ秒経過
[LateUpdate] 後処理: 4279ミリ秒経過
[Update] フレーム更新: 4307ミリ秒経過
[LateUpdate] 後処理: 4309ミリ秒経過
[FixedUpdate] 物理演算処理: 4324ミリ秒経過
[Update] フレーム更新: 4326ミリ秒経過
[LateUpdate] 後処理: 4328ミリ秒経過
[Update] フレーム更新: 4350ミリ秒経過
[LateUpdate] 後処理: 4352ミリ秒経過
[FixedUpdate] 物理演算処理: 4384ミリ秒経過
[Update] フレーム更新: 4387ミリ秒経過
[LateUpdate] 後処理: 4388ミリ秒経過
[Update] フレーム更新: 4400ミリ秒経過
[LateUpdate] 後処理: 4403ミリ秒経過
[Update] フレーム更新: 4431ミリ秒経過
[LateUpdate] 後処理: 4434ミリ秒経過
[FixedUpdate] 物理演算処理: 4447ミリ秒経過
[Update] フレーム更新: 4450ミリ秒経過
[LateUpdate] 後処理: 4452ミリ秒経過
[Update] フレーム更新: 4482ミリ秒経過
[LateUpdate] 後処理: 4484ミリ秒経過
[OnDestroy] アプリケーション終了処理を実行しました
プログラム '[22860] UnityLifecycleSimulation.exe' はコード 0 (0x0) で終了しました。

5. まとめ

WinFormsのイベントとUnityのライフサイクルの関係を理解
フレーム更新 (Update) と物理演算 (FixedUpdate) の違いを体験
フォームのイベント登録方法を学習

このチュートリアルを通じて、Unityのオブジェクトの動作原理を Windowsアプリ開発者の視点から 学べました。
この知識を活かして、実際のUnity開発 に挑戦してみましょう!🚀