🎉

GodotEngineでライフゲームを作ってみる

2021/12/31に公開

この記事について

GodotEngineでライフゲームを作った時の過程を記録しました。

仕様

  • フィールドサイズ30x30固定にしてセル900個を使うよ
  • GDScriptで書くよ
  • 上下と左右はループさせるよ
  • パフォーマンスより簡単な作り方を優先させるよ

完成品はこちらです。ドン!

作ります

1.全体の設定

とりあえずゲームのウィンドウサイズくらいは設定しておきますけどやんなくても良いです。

  • プロジェクト設定 -> Display -> Windowで画面の設定をします。
    • widthとheightを480にする
    • Stretch/Modeを2d、Aspectをkeepにする

初期サイズ480x480のサイズ可変ウィンドウです。
ゲーム自体の縦横比は固定なので状況に応じて黒帯が入ります。

プロジェクト設定のDisplay/Windowの項は使用頻度高めな気がするので、色々試してみると良いかもしれないです。

2.シーンを作る

まず一番大事な「何がやりたいか」を書いておくと

  • セルを表現するためスプライトを900個、縦横に並べたーい!
  • それをライフゲームのルールに則って表示、非表示させたーい!

こんだけ。この2つができれば完成!です。いろいろ考えた結果、

  • Cell.tscn (セルの設計図。Unityのプレハブ的な感じ)
  • Main.tscn (ゲームのメインシーン。このシーンにインスタンス化したセルを並べてスクリプトで制御する)

まずはこの2つのシーンを作ることにします。

Cell.tscn

Main.tscn

これでエディタでの作業は終わりです。慣れたら超楽勝です。

3.スクリプトを書く

GDScriptの基本的な書き方は省略。
Main.gdはこんな感じです。

Main.gd
extends Node

# CellをコレクションするDictionary
# キーをVector2にして2次元配列みたいに使います。
var cells = {}  

# フィールドのサイズとスプライトのサイズを定数で書いておきます
const FIELD_SIZE = 30 
const SPRITE_SIZE = 16 

# インスタンス化したいシーンをpreloadしておくといい感じ。
onready var cell_scene = preload("res://Cell.tscn")

## ゲーム起動時に1回だけやりたいことを_ready関数に書く
func _ready():
    randomize() # 乱数を毎回変えるおまじない。

    # ここのループでセルをいっぱい作る
    for y in FIELD_SIZE:
        for x in FIELD_SIZE:
            var _cell = cell_scene.instance() # Cell.tscnからセルをインスタンスにする
            _cell.position = Vector2(x, y) * SPRITE_SIZE # 画面上の座標を決める
            _cell.visible = randi() % 2 # 乱数で表示、非表示を決める
            cells[Vector2(x, y)] = _cell # Dictionaryにセルへの参照を入れる
            add_child(_cell) # add_childしてツリーにくっつける

# ゲームの毎フレームやりたいことは_process関数に書く
func _process(_delta):
    var next = {} # 次の世代でのCellの生死をメモしておくDictionary
    for key in cells: # 全セルをチェック
        var lives = 0 # 周りのセルが何個生きているかメモする変数

        # ここのループで周囲のセルを調べる
        for dy in range(-1, 2):
            for dx in range(-1, 2):
                if dy == 0 && dx == 0: continue # 自分自身は見ないようにする。

                # 一番上の行のセルのさらに上を調べる時は1番下とか、
                # 一番左の列のセルのさらに左を調べる時は一番右とかなるようにする。
                var check_pos = Vector2(_helper(key.x, dx), _helper(key.y, dy)) 

                # 調査対象のセルが表示状態ならlivesをインクリメント。
                if cells[check_pos].visible: lives += 1

        # lives == 3、もしくは lives == 2 で今生きている状態なら次の世代は生きる。
        next[key] = lives == 3 || (cells[key].visible && lives == 2)
    
    # 全セルを更新するためのループ
    for key in cells:
        nextの値を元にセルの表示を変更する
        cells[key].visible = next[key]

# 余剰とかいい感じに使って調査対象になるセルを決めます。
func _helper(a, b):
    return int(a + b + FIELD_SIZE) % FIELD_SIZE

4.完成

以上で完成です。エディタでの作業は30分、コードも40行くらいなので30分あれば書けるかと思います。

おまけ

ここまで読んでいて、もし、
「んでも出来上がるのって所詮ただのライフゲームでしょ?」
とか、
「もっと面白いゲームのチュートリアルよこせバカ」
みたいな事を思っているとしたらちょっとオトクな情報です。

ライフゲームは驚きと感動に溢れた超絶面白コンテンツなんですよ!

ということで最後におすすめ動画へのリンクを張っておきます。
これ見たとき私は超ビックリしたのですが、もう10年くらい経つんですね(しみじみ)
https://www.nicovideo.jp/watch/sm19347846

初投稿にお付き合い頂きありがとうございました。

2021/12/31 書いた
2022/01/01 Main.gdに解説コメントを追記

Discussion