【Unity】簡単なライフゲームを作ってみる

Unity
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
クマ〜〜〜〜
マイケル
マイケル
今日は気晴らしにUnityで簡単なライフゲームを作ってみるよ!
エレキベア
エレキベア
ライフゲームって何クマ?
マイケル
マイケル
ライフゲームとは、
生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲーム だ・・・!!
エレキベア
エレキベア
せ、生命の誕生・・・

マイケル
マイケル
ゲームといってもこんな感じで基本的に眺めて楽しむだけで、
簡単なルールで面白い変化をすることから有名になったんだ!
エレキベア
エレキベア
(なぜ壊したクマ・・・)
なるほどクマ、なんかみたことある気がするクマ
マイケル
マイケル
今回作ったサンプルプロジェクトはGitHubにあげてるので
よかったら触ってみてくださいね!

GitHub – masarito617/unity-life-game-sample

エレキベア
エレキベア
やったるクマ〜〜〜
スポンサーリンク

ライフゲームとは

マイケル
マイケル
それでは改めてライフゲームとは、
以下のように定義されているシミュレーションゲームだ!

ライフゲームとは、生命の誕生、進化、淘汰などのプロセスを

簡易的なモデルで再現したシミュレーションゲームである

ライフゲーム – Wikipedia

参考文献:
ライフゲーム – Wikipedia

エレキベア
エレキベア
(Wikiの引用だったクマか・・・)
マイケル
マイケル
セル・オートマトンという、隣接するセルの状態を元に、時間的に状態を変化させるモデルの一種で、下記のルールに従うことで再現できるんだ!


[基本ルール]
 誕生:死亡セルに隣接する生存セルが3つあれば、次の世代が誕生する。
 生存:生存セルに隣接する生存セルが2つか3つならば、次の世代でも生存する。
 過疎:生存セルに隣接する生存セルが1つ以下ならば、過疎により死滅する。
 過密:生存セルに隣接する生存セルが4つ以上ならば、過密により死滅する。

エレキベア
エレキベア
意外とシンプルなルールクマね
マイケル
マイケル
多すぎても少なすぎても生存できないという、生態学の側面も取り入れられていて、
この4つのルールだけで非常に面白い動きになるんだ!
有名なパターンとしては下記のようなものがあるよ!
・ヒキガエル
03 frog
・グライダー
02 glider
マイケル
マイケル
ヒキガエルグライダー
といった単純なパターンから・・・
・ペンタデカスロン
04 pentadecathlon
・銀河
05 galaxy
マイケル
マイケル
ペンタデカスロン銀河
といった複雑なものまで数多のパターンが発見されているよ!
エレキベア
エレキベア
ペンタデカスロン・・・??
でも見てて面白いしすごいクマ〜〜〜〜!!
マイケル
マイケル
ここまで聞いたらすぐ作れそうだしやってみたくなるよね!
早速Unityで作ってみよう!
エレキベア
エレキベア
楽しみクマ〜〜〜!!

ライフゲームの実装

マイケル
マイケル
GitHubのAssets配下にある、LifeGame.cs がライフゲームの処理です!
カメラを下記のように2D用に設定している状態でゲームオブジェクトにアタッチすると動くようになっています。

GitHub – masarito617/unity-life-game-sample

ScreenShot 2022 04 09 0 53 15↑カメラを2D用に設定

セルの生成

マイケル
マイケル
まずセルの生成についてですが、
今回は簡単に「Cubeオブジェクトを1つのセル」として作ってみました!
そのためセルの状態を示すCellStatusとその配列、そして描画更新用にMeshRendererの配列を変数として用意しています。
    /// <summary>
    /// セルの状態
    /// </summary>
    private enum CellStatus
    {
        Dead = 0, // 死亡
        Life = 1, // 生存
    }
    
    /// <summary>
    /// セル情報
    /// </summary>
    private CellStatus[] _cells; // セル配列
    private MeshRenderer[] _cellMeshRenderers; // セルが持つMeshRenderer配列
    private float _cellScale = 1.0f; // セルの大きさ
↑セルの状態を示す変数
マイケル
マイケル
Awake処理でセルを生成して、用意した配列に格納します。
生存状態はマテリアルを2種類用意して切り替えることで表現するようにしています。
    /// <summary>
    /// 生成するセル数
    /// </summary>
    [SerializeField] private int widthCount = 32;
    [SerializeField] private int heightCount = 32;
    
    /// <summary>
    /// カメラに収まる基準セル数
    /// </summary>
    private const int BaseWidthCount = 16;
    private const int BaseHeightCount = 16;

    /// <summary>
    /// マテリアル
    /// </summary>
    private Material _lifeMaterial;
    private Material _deadMaterial;

    private void Awake()
    {
        // マテリアル生成
        _lifeMaterial = new Material(Shader.Find("Unlit/Color"));
        _lifeMaterial.color = Color.white;
        _deadMaterial = new Material(Shader.Find("Unlit/Color"));
        _deadMaterial.color = Color.black;
        
        // カメラに収まるようスケールを調整
        _cellScale = Mathf.Min((float) BaseWidthCount / widthCount, (float) BaseHeightCount / heightCount);
        
        // セル生成
        _cells = new CellStatus[widthCount * heightCount];
        _cellMeshRenderers = new MeshRenderer[widthCount * heightCount];
        for (var i = 0; i < widthCount * heightCount; i++)
        {
            var x = i % widthCount;
            var y = i / widthCount;
            
            // セルをCubeオブジェクトとして生成
            var cell = GameObject.CreatePrimitive(PrimitiveType.Cube);
            cell.transform.parent = gameObject.transform;
            cell.transform.localScale = _cellScale * Vector3.one;
            cell.transform.localPosition = _cellScale * new Vector3(
                x - (widthCount / 2.0f - _cellScale / 2.0f),
                y - (heightCount / 2.0f - _cellScale / 2.0f),
                0);

            // 初回は死亡している状態で設定
            _cells[i] = CellStatus.Dead;
            
            // MeshRendererも保持しておく
            var meshRenderer = cell.gameObject.GetComponent<MeshRenderer>();
            meshRenderer.sharedMaterial = _deadMaterial;
            _cellMeshRenderers[i] = meshRenderer;
            
        }
    }
マイケル
マイケル
カメラに映るようにセルの大きさを調整するため、BaseWidthCount、BaseHeightCount定数も用意しています。
scale1.0のセルを16×16でカメラを設定していたためですね・・・。
マイケル
マイケル
セルの生成についてはこれで完了です!!
エレキベア
エレキベア
準備完了クマね

世代の更新処理

マイケル
マイケル
次に、先ほど説明したライフゲームのルールを実装してみます。
次の世代に更新する関数を用意して、判定分を記述しましょう!!
    /// <summary>
    /// 次の世代
    /// </summary>
    public void NextLife()
    {
        // セル更新処理
        var nextCells = new CellStatus[widthCount * heightCount];
        for (var i = 0; i < widthCount * heightCount; i++)
        {
            var x = i % widthCount;
            var y = i / widthCount;
            
            // 隣接するセルのindexを取得
            var xl = x == 0 ? widthCount - 1 : x - 1;  // 左
            var xr = x == widthCount - 1 ? 0 : x + 1;  // 右
            var yt = y == 0 ? heightCount - 1 : y - 1; // 上
            var yb = y == heightCount - 1 ? 0 : y + 1; // 下
            
            // 隣接するセルの生存数をカウント
            var lifeCount = 0; 
            if (IsLifeCell(xl + yt * widthCount)) lifeCount++; // 左上
            if (IsLifeCell(x  + yt * widthCount)) lifeCount++; // 上
            if (IsLifeCell(xr + yt * widthCount)) lifeCount++; // 右上
            if (IsLifeCell(xl + y  * widthCount)) lifeCount++; // 左
            if (IsLifeCell(xr + y  * widthCount)) lifeCount++; // 右
            if (IsLifeCell(xl + yb * widthCount)) lifeCount++; // 左下
            if (IsLifeCell(x  + yb * widthCount)) lifeCount++; // 下
            if (IsLifeCell(xr + yb * widthCount)) lifeCount++; // 右下
            
            // 生存判定
            var isLife = false;
            if (!IsLifeCell(i) && lifeCount == 3)
            {
                // 死亡セルに隣接せる生存セルが3つあれば誕生
                isLife= true;
            }
            else if (IsLifeCell(i) && (lifeCount == 3 || lifeCount == 2))
            {
                // 生存セルに隣接する生存セルが2つか3つならば次の世代でも生存
                isLife= true;
            }
            nextCells[i] = isLife ? CellStatus.Life : CellStatus.Dead;
        }
        
        // 次の世帯のセルに更新
        _cells = nextCells;
        
        // 再描画
        UpdateDrawMesh();
    }
    
    /// <summary>
    /// 生存セルか?
    /// </summary>
    private bool IsLifeCell(int index)
    {
        return _cells[index] == CellStatus.Life;
    }
    
    /// <summary>
    /// メッシュ再描画
    /// </summary>
    private void UpdateDrawMesh()
    {
        for (var i = 0; i < widthCount * heightCount; i++)
        {
            _cellMeshRenderers[i].sharedMaterial = _cells[i] == CellStatus.Life ? _lifeMaterial : _deadMaterial;
        }
    }
マイケル
マイケル
処理内容としては、隣接するセルの生存数をカウントして
生存or死亡を判定、描画更新しているだけですね!
エレキベア
エレキベア
簡単な判定クマね

ボタン押下時の処理

マイケル
マイケル
あとはこれらを呼び出す処理を記述するのみ!
今回は検証しやすいように、各ボタン押下で処理を走らせるようにしています。
マイケル
マイケル
生存セルの設定と破棄は下記の処理になります!
生存セルの設定についてはとりあえず乱数で設定するようにしました!
    /// <summary>
    /// セルのリセット
    /// </summary>
    public void ResetCell()
    {
        // セルをクリア
        _cells = new CellStatus[widthCount * heightCount];
        
        // 再描画
        UpdateDrawMesh();
    }
    
    /// <summary>
    /// セルをランダムに生成する
    /// </summary>
    public void GenerateRandomCell()
    {
        // 一定確率で命を与える
        for (var i = 0; i < widthCount * heightCount; i++)
        {
            _cells[i] = Random.Range(0, 100) < 12 ? CellStatus.Life : CellStatus.Dead;
        }
        
        // 再描画
        UpdateDrawMesh();
    }
↑生存セルの設定、破棄
マイケル
マイケル
セルの設定が完了したら、一定間隔で次の世代更新処理を呼び出しましょう!
コルーチンを使ってループ処理を記述します。
    /// <summary>
    /// ループ中か?
    /// </summary>
    private bool _isLoop = false;
    
    /// <summary>
    /// ループ開始
    /// </summary>
    public void LifeLoopStart()
    {
        if (_isLoop) return;
        StartCoroutine(LifeLoopCoroutine());
    }
    
    /// <summary>
    /// ループ停止
    /// </summary>
    public void LifeLoopStop()
    {
        _isLoop = false;
    }
    
    /// <summary>
    /// ループコルーチン
    /// </summary>
    /// <returns></returns>
    private IEnumerator LifeLoopCoroutine()
    {
        // ループ停止されるまで一定間隔で次の世帯を呼び出す
        _isLoop = true;
        while (_isLoop)
        {
            yield return new WaitForSeconds(0.1f);
            NextLife();
        }
    }
↑世代更新のループ処理
マイケル
マイケル
以上でライフゲームが動作するようになっているはずです!!
01 lifegame↑完成!!!
エレキベア
エレキベア
やったクマ〜〜〜〜!!!

おわりに

マイケル
マイケル
というわけで今回はUnityでのライフゲーム実装でした!
どうだったかな??
エレキベア
エレキベア
こんな単純なルールで面白い動きをするなんて
マジでびっくりクマ
マイケル
マイケル
ライフゲームに関しては書籍も出ているみたいだから
これも読んでみたら面白そうだね!

ライフゲイムの宇宙

マイケル
マイケル
それでは最後に繁殖型と呼ばれる複雑なパターンも作ったのでお見せしましょう・・・!
・グライダー銃
06 glider gan
エレキベア
エレキベア
グライダーが増え続けてるクマ・・・
すげえクマ・・・
マイケル
マイケル
みなさんも是非自分の手で作って遊んでみてください!!
それでは今日はこの辺で!アデュー!!
エレキベア
エレキベア
クマ〜〜〜〜〜〜

【Unity】簡単なライフゲームを作ってみる 〜完〜

コメント