Developer

さくさく理解する Godot 入門(ただし2Dに限る)応用編 レトロシューティングゲーム【第3回】
2021.12.09
Lv1

さくさく理解する 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++ライブラリ 連載目次