エンジニア

ライフゲームを作ってみた

投稿日:2015年3月24日 更新日:

今回の記事ではライフゲームを紹介したいと思います。
担当は大橋です。よろしくおねがいします。

LifeGame_01

ライフゲームって?

ライフゲームは、生命の誕生と死をモチーフにしたシミュレーションゲームです。
1970年にJohn Horton Conwayという人が考案しました。

「生命の誕生と死」というと、なんだか凄そうですが、
ルール自体はとても単純です。
上の画像のように、格子状に区切った盤面がライフゲームの全てです。

盤面上のマス(セル)が1つの生命を表します。
緑色のセルは「生きている」状態、黒色のセルは「死んでいる」状態です。
各セルの周囲8マスに生きているセルが何個あるかで、次のステップにそのセルがどちらの状態になるかが決まります。

セルの状態が「死」のとき

  • 周囲8マスに生きているセルが3つ → 次のステップで「生」(誕生)
セルの状態が「生」のとき

  • 周囲8マスに生きているセルが2つか3つ → 次のステップでも「生」(生存)
  • 周囲8マスに生きているセルが4つ以上 → 次のステップで「死」(過密で死滅)
  • 周囲8マスに生きているセルが1つ以下 → 次のステップで「死」(過疎で死滅)

全てのセルに対して上記ルールで、次のステップの状態を判定します。
そして次のステップにその状態に変化させます。
これを延々と繰り返すのです。

Unityで作ってみる

ライフゲームをUnity(4.6)で作ってみます。

まずはセルを作ります。
「死」状態のCube1、「生」状態のCube2を作ります。

LifeGame_02

2つの立方体をひとつのGameObjectにまとめてプレハブ化して、これをセルにします。

LifeGAme_03

セルには次のスクリプトをComponentとしてセットしておきます。

public class Cell : MonoBehaviour {
    public bool Living { get; private set; }    // このセルが生存状態か  

    private GameObject cube1;    // 死滅時  
    private GameObject cube2;    // 生存時  

    void Awake () {
        cube1 = transform.FindChild ("Cube1").gameObject;
        cube2 = transform.FindChild ("Cube2").gameObject;

        cube1.SetActive (true);
        cube2.SetActive (false);
        Living = false;
    }

    // Update is called once per frame  
    void Update () {
        Living = cube2.activeSelf;
    }

    /// <summary>  
    /// 誕生  
    /// </summary>  
    public void Birth() {
        cube1.SetActive (false);
        cube2.SetActive (true);
    }

    /// <summary>  
    /// 死滅  
    /// </summary>  
    public void Die() {
        cube1.SetActive (true);
        cube2.SetActive (false);
    }

}

次に、セルの生存と死滅の判定をして、ライフゲームのステップを進めるスクリプトを作ります。

public class LifeGame : MonoBehaviour {
    private const float CELL_SIZE = 1f;   // セルのサイズ  

    public int gridSize = 100;            // グリッドの1辺のセルの数  
    public GameObject cellPrefab;         // セルのプレハブ  

    private Cell[,] cells;                // グリッド状のセル  

    // Use this for initialization  
    void Start () {
        // グリッド状にセルを作成  
        cells = new Cell[gridSize, gridSize];

        for (int x = 0; x < gridSize; x++) {
            for (int z = 0; z < gridSize; z++) {
                // セルを作成  
                GameObject obj = Instantiate (cellPrefab) as GameObject;
                obj.transform.SetParent (transform);

                // 位置を設定  
                float xPos = (x - gridSize * 0.5f) * CELL_SIZE;
                float zPos = (z - gridSize * 0.5f) * CELL_SIZE;
                obj.transform.localPosition = new Vector3 (xPos, 0f, zPos);

                // Cellをセット  
                cells [x, z] = obj.GetComponent<Cell> ();
            }
        }
    }

    // Update is called once per frame  
    void Update () {
        // クリックしたセルの生存/死滅を切り替える  
        if (Input.GetMouseButtonDown(0)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit = new RaycastHit();

            if (Physics.Raycast(ray, out hit)){
                Cell cell = hit.collider.gameObject.transform.parent.GetComponent<Cell>();

                if (cell.Living) {
                    cell.Die ();
                } else {
                    cell.Birth ();
                }
            }
        }

        // 「S」キーで開始  
        if (Input.GetKeyDown(KeyCode.S)) {
            StartCoroutine (LifeGameCoroutine ());
        }

        // 「E」キーで停止  
        if (Input.GetKeyDown (KeyCode.E)) {
            StopAllCoroutines ();
        }
    }

    /// <summary>  
    /// ライフゲームの更新コルーチン  
    /// </summary>  
    /// <returns>The game coroutine.</returns>  
    private IEnumerator LifeGameCoroutine() {
        while (true) {
            // 全セルを更新  
            for (int x = 0; x < gridSize; x++) {
                for (int z = 0; z < gridSize; z++) {
                    UpdateCell (x, z);
                }
            }

            // ちょっと待つ  
            yield return new WaitForSeconds (0.03f);
        }
    }

    /// <summary>  
    /// セルの状態を更新  
    /// </summary>  
    /// <param name="x">The x coordinate.</param>  
    /// <param name="z">The z coordinate.</param>  
    private void UpdateCell(int cellX, int cellZ) {
        // 周囲の生存セル数  
        int count = 0;
        for (int x = cellX - 1; x <= cellX + 1; x++) {
            for (int z = cellZ - 1; z <= cellZ + 1; z++) {
                if (x == cellX && z == cellZ) {
                    continue;
                }

                if (cells [(x + gridSize) % gridSize, (z + gridSize) % gridSize].Living) {
                    count++;
                }
            }
        }

        // 誕生/死滅  
        Cell cell = cells [cellX, cellZ];
        if (cell.Living) {
            // 周囲の生存セルが1以下、または4以上のとき死滅  
            if (count <= 1 || count >= 4) {
                cell.Die ();
            }
        } else {
            // 周囲の生存セルが3つのとき誕生  
            if (count == 3) {
                cell.Birth ();
            }
        }
    }
}

これを、シーンに配置した空のGameObjectにセットしておきます。

実行してみる

出来上がったものがこちらです。
LifeGame

これを実行すると、セルがグリッド状に敷き詰められた状態になります。
好きなセルをクリックすると、そのセルの状態(生、死)が切り替わります。

LifeGame_04

いくつかのセルの状態を「生」にしたあと、「S」キーを押してシミュレーション開始です。
始めの状態によって、いろいろな動きが見れると思います。

いろんなパターン

おもしろい動きをするパターンがいくつも発見されています。
その中で、代表的なものを紹介します。

グライダー

LifeGame_05
移動します!

宇宙船

LifeGame_06
これも移動します。

パルサー

LifeGame_07
パルサーって感じです。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.