目次
- トーチカ設置
- 敵機配置
- 敵機移動
■トーチカ設置
トーチカ(バンカー)は、外部シーンとして実装しているので、必要なときに 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++ライブラリ 連載目次