UnityでLifeGameのコードを書いた

TwitterでアップしたLifeGameのコードを書いておきます。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">気分転換にLifeGame組んだ。HighLife(23/36)設定。<br>Unityで組むのは初かも？コードは簡単なので後でどこかに上げます。 <a href="https://t.co/0IQGm4av47">pic.twitter.com/0IQGm4av47</a></p>&mdash; MIYAKE (@ScreenPocket) <a href="https://twitter.com/ScreenPocket/status/1245005416339091458?ref_src=twsrc%5Etfw">March 31, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

使用する際はこのコードのコンポーネントをつけるだけでOK。自動でMeshFilterもMeshRendererも準備されます。
こういうのをぼんやり眺めるのが結構好き。
※パラメータはご自由に調整してください。

```cs:lifegame.cs
using UnityEngine;
using UnityEngine.Rendering;

public class LifeGame : MonoBehaviour
{
    private const int kWidth = 512;
    private const int kHeight = 512;
    
    /// <summary>
    /// 格子
    /// </summary>
    private readonly int[] _grids = new int[kWidth * kHeight];
    /// <summary>
    /// 次のフレームの結果格納用
    /// </summary>
    private readonly int[] _nextGrids = new int[kWidth * kHeight];

    private MeshFilter _meshFilter;
    private MeshRenderer _meshRenderer;
    
    private Material _material;
    private Mesh _mesh;
    
    /// <summary>
    /// 位置座標
    /// 最初に確定しておいて、後はindexでピクセルの有無を切り替える
    /// </summary>
    private readonly Vector3[] _positions = new Vector3[kWidth * kHeight];
    /// <summary>
    /// index
    /// </summary>
    private readonly int[] _indices = new int[kWidth * kHeight];
    
    private void Start()
    {
        var minPos = new Vector3(-kWidth * 0.5f, -kHeight * 0.5f, 0f);
        for (int i = 0; i < kWidth * kHeight; ++i)
        {
            var x = i % kWidth;
            var y = i / kWidth;
            //4%くらいの確率でLifeを置く
            _grids[i] = Random.Range(0,100) < 4 ? 1 : 0;
            //先に頂点を並べておいてIndexの変更でピクセルを切り替える
            _positions[i] = minPos + new Vector3(x,y,0);
        }

        _meshFilter = gameObject.AddComponent<MeshFilter>();
        _mesh = new Mesh();
        _mesh.name = "GridPoint";
        _mesh.MarkDynamic();
        _mesh.SetVertices(_positions, 0, kWidth * kHeight);
        _mesh.indexFormat = IndexFormat.UInt32;
        _meshFilter.sharedMesh = _mesh;
        _mesh.RecalculateBounds();
        
        _material = new Material(Shader.Find("Unlit/Color"));
        _material.color = Color.white;
        
        _meshRenderer = gameObject.AddComponent<MeshRenderer>();
        _meshRenderer.sharedMaterial = _material;
    }

    private void Update()
    {
        int lifeCount = 0;
        for (int i = 0, count = kWidth * kHeight; i < count ; ++i)
        {
            var x = i % kWidth;
            var y = i / kWidth;
            
            //左
            var l = x == 0 ? kWidth - 1 : x - 1;
            //右
            var r = x == kWidth - 1 ? 0 : x + 1;
            //上
            var t = y == 0 ? kHeight - 1 : y - 1;
            //下
            var b = y == kHeight - 1 ? 0 : y + 1;


            //隣接生存数を集計
            var neighborCount = _grids[l + t * kWidth];//左上
            neighborCount += _grids[x + t * kWidth];//上
            neighborCount += _grids[r + t * kWidth];//右上
            neighborCount += _grids[l + y * kWidth];//左
            neighborCount += _grids[r + y * kWidth];//右
            neighborCount += _grids[l + b * kWidth];//左下
            neighborCount += _grids[x + b * kWidth];//下
            neighborCount += _grids[r + b * kWidth];//右下

            var centerIndex = x + y * kWidth;
            if (neighborCount == 3 || neighborCount == 6)
            {
                //発生
                _nextGrids[centerIndex] = 1;
            }
            else if (neighborCount == 2 || neighborCount == 3)
            {
                //現状維持
                _nextGrids[centerIndex] = _grids[centerIndex];
            }
            else
            {
                //死亡
                _nextGrids[centerIndex] = 0;
            }

            if (_nextGrids[centerIndex] != 0)
            {
                //index更新
                _indices[lifeCount] = i;
                lifeCount++;
            }
        }
        
        System.Array.Copy(_nextGrids, _grids, _nextGrids.Length);

        //index更新
        _mesh.SetIndices(_indices, 0, lifeCount,MeshTopology.Points, 0);
    }
}

```