旧プログラマーによる日曜プログラミング日記

趣味は作曲、自分で一から作りたがりのユーザIT子会社の旧プログラマーによるリハビリのための日曜プログラミング日記です。

Phaser3+typescriptでSRPGを開発してみる #013 ユニットの移動(3)

しばらくぶりです。色々と忙しく、しばらくぶりになってしまいました。 仕事でもSpringBootでWebアプリやバッチを開発することになったり、プライベートではコロナ禍におけるレコーディングオンラインセッションに参加、 そのあとはコロナが弱まったことで飲み会等で、このブログを執筆するどころか、SRPG開発もかなり中断してしまいました。

それにも関わらず、見てくださる方がかなりいらっしゃって、大変嬉しい限りです。

ということで、SRPG開発は再開できていないものの、以前作ったネタでHatena Blogを書くことでリハビリしながら、ゲーム開発に戻っていきたいと思います。

本題

前回は味方ユニットを動かせる領域を表示する、という地味な内容でしたが、今回はTweenをついに動かそうと思います。 今回は、相手(敵)側も自動で動かしたいと思います。

ユニットの移動(3)

味方ユニットの移動

味方ユニットを動かすには、

  • 動かすユニットを選択(カーソルの移動は#009、#011で解説。このブログでは選択にはSpaceキーを割当)

  • 動かせる範囲を可視化(#012で解説)

  • 動かす先を設定(今回はEnterキーを割当)

  • 実際に動かす(#011で解説)

が必要です。今まで解説してきたロジックを使えば実現できます。

動かすユニットの選択と動かす先の設定

この2つの仕様を今回は同じメソッド内でやっています。 このcheckAllyUnit()メソッドは、update()メソッドから呼んでいます。 GameScene.ts

    /**
     * ゲーム中の処理
     * @override
     */
    public update(): void {

        // 移動中のユニットの確認。位置の更新
        // 移動終了するまでは以降の処理はしない
       (中略)

        // カーソルが動いているときは、移動終了まで以降の処理をしない
       (中略)


        // キー処理チェック
        if (this._cursorKeys === undefined) {
            throw Error("EG0001: cursorKeyが設定されていません。");
        }
        if (this._keys === null && this._keys === undefined) {
            throw Error("EG0002: keysが設定されていません");
        }

        // カーソル・キャラが自動で動いていない場合
        // 味方ターンの場合の初期対応
        if (this._gameStatus.turnTeam === Team.ALLY) {

            // 味方全員が終わりの場合、ターン終了
            this.checkTurnEnd(this._gameData.allyUnits);

            // カーソルを動かす
            this.moveCursor();

            // カーソルの該当セルにユニットが居た場合の処理
            this.checkAllyUnit();
        } else if (this._gameStatus.turnTeam === Team.ENEMY) {
            // (中略)敵ターンの場合
        }

checkAllyUnit()メソッドは、味方ターンで、(1)移動中のユニットが居ないとき (2)カーソルが動いていないとき に呼ばれることになります。 checkAllyUnit()メソッドの中で、現状のステータス(味方ターゲットを選択しているか否か?)に基づいて場合分けして、キー押下時のハンドリングをしています。

  • 味方ターゲットが選択中(=this.targetUnitがnullでない場合)  →Enterキーが押されたら、(移動可能であれば)その場所へ移動
  • 味方ターゲットが未選択の場合(上記以外)  →Spaceキーが押されたら、味方ターゲットを選択(targetSelectedUnit(GameUnit)メソッドが呼ばれる)

GameScene.ts

   /**
     * ユニットがいる場合の対応
     */
    protected checkAllyUnit(): void {
        // 既に味方ターゲット選択中
        if (this._targetUnit) {
            // ENTERキーを押下した場合、キャラを移動
            if (this._keys.get(GameScene.SETUP_KEY.ENTER).isDown
                && this._gameData.checkCanMoveToTargetCell(this._targetUnit, this._cursor.currentX, this._cursor.currentY)) {
                //  this.setMovingUnit(this._cursor.currentX, this._cursor.currentY)
                this._movingUnit = new MovingUnit(this._targetUnit, this._cursor.currentX, this._cursor.currentY);
                this.moveUnit(this._movingUnit);
            }
            // ESCキーを押下した場合、キャンセル
            else if (this._keys.get(GameScene.SETUP_KEY.ESC).isDown) {
                this.destroyMoveScope();
                this._targetUnit = null;

                // いったんカーソルも戻さず、ステータスを消す
                // @TODO 将来的にはカーソルをターゲットに戻す?
                this.disappearStatusInfoArea();
            }
        }

        // ターゲットを未選択の場合
        else {
            var selectedUnit: GameUnit = this.getUnitOnCursor();
            // どのキャラもいない場合はステータスと可動領域を消す
            if (!selectedUnit) {
                this.disappearStatusInfoArea();
                this.destroyMoveScope();
            }
            // キャラがいる場合はステータスと可動領域を見せる
            else if (selectedUnit) {
                this.showStatusInfoArea(selectedUnit);
                if (!selectedUnit.turnEnd) {
                    // 一旦、可動域を消す
                    this.destroyMoveScope();
                    // 再度復活
                    this.showMoveScope(selectedUnit);
                }
            }
            // カーソルに味方キャラが居て、スペースキーを押下した場合
            // ターゲットにする
            if (this._keys.get(GameScene.SETUP_KEY.SPACE).isDown
                && selectedUnit
                && (selectedUnit.team === Team.ALLY)
                && !selectedUnit.turnEnd) {
                this.targetSelectedUnit(selectedUnit);
            }
        }
    }

ユニットを選択するSPACEキーを押下されたとき、下記の通り、クラス全体のメンバー変数にセットして、 ユニットをロックオンします。

GameScene.ts

    /**
     * スペースキーを押下して選択した味方ユニットをターゲットにする
     * @param selectedUnit 
     */
    private targetSelectedUnit(selectedUnit: GameUnit) {
        this.showMoveScope(selectedUnit);
        this._targetUnit = selectedUnit as GameUnitPhaser;

        // ステータスを表示する
        this.showStatusInfoArea(selectedUnit);
    }

そして、実際に動くわけです。実際に動かすのは#011で実装したので割愛します。

敵ユニットの自動移動

一方で、敵ユニットを動かすときも、動かすモーションについては、味方ユニットのものを流用できます。 但し、動く場所をコンピュータ側で適切に決める必要があります。 この辺りは今は考えず、まずは、右に1歩ずつ動くという固定的な仕様にしたいと思います。

まず、敵ユニットを順次取得して、動かしていきます。この辺りも、いずれは、有利不利を判定して動かす順番を変えたりしたいですね。

GameScene.ts

    /**
     * ゲーム中の処理
     * @override
     */
    public update(): void {
        :
      (中略)
        :
       // カーソル・キャラが自動で動いていない場合
        // 味方ターンの場合の初期対応
        if (this._gameStatus.turnTeam === Team.ALLY) {
        // 上記参照
        } else if (this._gameStatus.turnTeam === Team.ENEMY) {
            // 敵全員が終わりの場合、ターン終了
            this.checkTurnEnd(this._gameData.enemyUnits);
            // 次の敵を移動
            this.moveEnemyUnit();
        }
    }

    /**
     * 敵ユニットを順次移動
     */
    protected moveEnemyUnit(): void {
        // 動いていない敵キャラを取得
        var turnUnits: GameUnit[] = this._gameData.enemyUnits.filter((unit: GameUnit) => {
            return unit.isAlive && !unit.turnEnd;
        });
        if (turnUnits.length === 0) {
            return;
        }

        // 戦略に従い移動を準備
        // @TODO 今はとりあえず右に1マス進むだけ
        this._targetUnit = (turnUnits[0] as GameUnitPhaser);
        let move: { x: number, y: number } = { x: turnUnits[0].currentX + 1, y: turnUnits[0].currentY };
        this._movingUnit = new MovingUnit(
            this._targetUnit,
            move.x,
            move.y);

        // 自動的に移動
        this.moveUnit(this._movingUnit);
    }

味方の移動でも使ったmoveUnit(MovingUnit)メソッドを使っています。

この結果、こんな感じで動きます(動画でなく静止画の連続ですみません)。

f:id:mtmusic34:20220122225025p:plainf:id:mtmusic34:20220122225031p:plain
自キャラ・敵キャラの移動の様子(動画をキャプチャ)

まとめ

今回はユニットの移動にもチャレンジしました。 かなり、SRPGっぽくなってきた気がしますが、まだ戦いまで至っていません。。

そろそろ戦い見据えた実装に入っていこうと思います。

過去の日記

タイトル 記載内容
#001 プロローグ Phaser3とは
#002 環境設定 インストール・ビルド環境設定
#003 はじめてのPhaser3 初回稼働・キャンバス表示
#004 マップ作成 タイルセットの読込、画面手動作成、Sceneクラスのライフサイクル
#005 Tiledの利用 Tiledによる画面マップJSON作成、マルチレイヤーの背景画面
#006 JSONファイルの読込 データのpreload
#007 ファイルのキャッシュ ゲームデータの管理・シングルトンパターン
#008 データモデル(ユニット編) SRPGに必要なデータ・管理方法
#009 カーソルの移動 キー受け付け、Spriteの活用
#010 各メソッドのコールと状態遷移 Sceneメソッドのライフサイクル・デバッグモードの作り方
#011 ユニットの移動(1) Tweenオブジェクト、味方ユニット移動
#012 ユニットの移動(2) 味方の可動領域の表示