さくさく理解する Godot 入門(ただし2Dに限る)応用編 レトロシューティングゲーム【第3回】
目次
- トーチカ設置
- 敵機配置
- 敵機移動
■トーチカ設置
トーチカ(バンカー)は、外部シーンとして実装しているので、必要なときに instance() を使って実体化する。
1面クリアして2面目に行くときは、トーチカは初期化されるので、bunkers 配列でバンカーオブジェクトを覚えておき、 setup_bunkers() ではまずそれらを削除してから新規にトーチカを4つ配置する。
var Bunker8 = load("res://Bunker8.tscn") var bunkers = [] # バンカー(防御壁) .... func setup_bunkers(): # トーチカ(バンカー)設置 for ix in range(bunkers.size()): bunkers[ix].queue_free() # 古いトーチカを削除 bunkers = [] for x in range(4): var bkr = Bunker8.instance() # トーチカをインスタンス化 bkr.position = Vector2((x+1)*100, 580) # 位置設定 add_child(bkr) # 子ノードとして追加 bunkers.push_back(bkr) # bunkers 配列に記憶 func _ready(): ..... setup_bunkers() # トーチカ(バンカー)設置 .....
■敵機配置
敵機の初期配置処理は _ready() から呼ばれる setup_enemies() で行われる。
var Enemy1 = load("res://Enemy1.tscn") # 敵機用(外部)シーンをロード var enemies = [] # 敵機管理用配列 var nEnemies = 0 # 敵機数 ..... func setup_enemies(): $UFO.position.x = -1 # UFO を画面外に移動 nEnemies = ENEMY_N_HORZ * ENEMY_N_VERT # 初期敵機数 enemies.resize(ENEMY_N_HORZ * ENEMY_N_VERT) # 敵機配列をリサイズ for y in range(ENEMY_N_VERT): # # 敵機 y 座標 var py = (ENEMY_N_VERT - 1 - y + min(level, 4)) * ENEMY_V_PITCH + ENEMY_Y0 for x in range(ENEMY_N_HORZ): var px = x * ENEMY_H_PITCH + ENEMY_X0 # 敵機 x 座標 var enemy = Enemy1.instance() # 敵機インスタンス作成 enemy.position = Vector2(px, py) # 敵機位置設定 enemy.get_node("Sprite").frame = y & 0x1e # 敵機画像設定 add_child(enemy) # 敵機ノードを画面に追加 var ix : int = x+y*ENEMY_N_HORZ; # ix: 敵機通し番号 enemies[ix] = enemy # 敵機ノードを配列で管理
最初に UFO を画面外に移動し、見えないようにしている。
敵機は、横:ENEMY_N_HORZ、縦:ENEMY_N_VERT の2次元グリッド上に並ぶので、 2次元配列で敵機を管理するのが自然なのだが、一般的に遅いし、メモリ効率も良くなく、筆者が2次元配列をあまり好きではないので、 本アプリでは1次元配列で敵機を管理することにしている。
y, x で二重forループを回し、ロード済みの Enemy1 クラスからインスタンスを作成し、x, y 座標を設定し、 ノードツリーに追加している。
行ごとに Sprite ノードの frame プロパティを 0, 0, 2, 2, 4 に設定することで、3種類の敵機画像を表示している。
生成したノードへの参照は enemies 配列に保存し管理している。
■敵機移動
敵機の移動は、EnemyMoveTimer のタイムアウト処理関数 _on_EnemyMoveTimer_timeout() から0.02秒ごとにコールされる moveEnemies() により処理される。
var mv_ix = 0 var move_down : bool = false # 敵機下移動 var move_right : bool = false # 敵機右移動 var en_collied : bool = false # 敵機が左右端に達した ..... func moveEnemies(): # 敵移動処理 if gameOver || paused: # ゲームオーバー、ポーズ時は移動しない return if enemies[mv_ix] != null: # 次の敵機が存在していれば if move_down: # 下方向移動 enemies[mv_ix].position.y += ENEMY_V_PITCH / 2 elif move_right: # 右方向移動 enemies[mv_ix].position.x += ENEMY_MOVE_UNIT if enemies[mv_ix].position.x >= MAX_ENEMY_X: en_collied = true; # 右端に達した → フラグON else: # 左方向移動 enemies[mv_ix].position.x -= ENEMY_MOVE_UNIT if enemies[mv_ix].position.x <= MIN_ENEMY_X: en_collied = true; # 左端に達した → フラグON if enemies[mv_ix].position.y >= MAX_ENEMY_Y: # 侵略された場合 invaded = true mv_ix = next_enemy(mv_ix) # 次の移動敵機取得
最初にゲームオーバーまたはポーズ中かどうかを判定し、その場合は何も処理を行わない。
敵機は最初左方向に移動し、敵機のどれかが左端に達した場合は下に移動し、ついで右方向に移動する。 右端に達した場合も同様だ。
これを実現するために move_down, move_right フラグを用意している。これらがONであえば、下移動、右移動というわけだ。
敵機はひとつずつ移動するので、mv_ix で移動する敵機配列インデックスを保持しておき、enemies[mv_ix] が null でなければ、 そのノードに対する移動処理を行う。
移動処理は簡単で、移動方向に応じて x または y 方向移動量を足し込むだけだ。 ただし、左端または右端に達した場合は move_down フラグをONにする。 また、再下端に達した場合は、侵略されたことになるので invaded フラグをONにしている。
next_enemy(ix) は、次に移動処理する敵機の ix を取得する関数で、下記のように実装される。
func next_enemy(ix): # 次に移動する敵機 ix を取得 while nEnemies != 0: # 敵機が残っている間 ix += 1 if ix == ENEMY_N_HORZ * ENEMY_N_VERT: # 最後の敵機を超えた場合 if move_down: # 全部の敵機が下に移動した場合 move_down = false elif en_collied: # 左右端に達している場合 en_collied = false move_right = !move_right # 左右移動方向反転 move_down = true # 下移動フラグON ix = 0 if enemies[ix] != null: # ix の敵機が生きている場合 break return ix
基本的に enemies[ix] が null であれば ix をインクリメントしてループしているだけだが、 最後の敵機の次の場合は、ix を最初に戻す。また、下移動が完了したので move_down フラグをOFFにする。 左右端に達している場合は、左右端到達フラグをOFFにし、移動方向を左右逆にし、move_down フラグをONにする。
TechProjin Godot入門 関連連載リンク
Godotで学ぶゲーム制作
さくさく理解するGodot入門 連載目次
標準C++ライブラリの活用でコーディング力UP!
「競技プログラミング風」標準C++ライブラリ 連載目次