むりこのーと

創作活動の記録や、日々思ったことをゆるく書いています。

【ゲーム開発】気持ちいい横スクロールアクションを作るPart3

どうも。前回に続き、開発中の横スクロールアクションゲームの開発ログを書いていきます。前回の記事はこちらです。↓

www.murinote.com

開発環境

現在の進捗

前回との比較

前回はマップの生成やカメラの設定を行いました。いわばゲームの基礎部分です。

今回はゴール後のプレイヤー強化に関する改修を行いました。

ゲーム情報表示UIの作成

まずゲーム画面右側にゲームに関する情報の表示用エリアを設けました。今はスコア(取得したコイン数)、経過時間、プレイヤーのパラメータ(詳細は後述)を表示しています。

このエリアはPhaserのコンテナという機能を使っています。これは複数のオブジェクトを一つの座標系で管理するというものです。文字通りコンテナを想像していただけると理解に難くないかと思います。

まずPhaser.GameObjects.Containerを継承したUI用のクラスを作成します。これがPhaserにおけるコンテナ機能にあたります。

export class GameInfoUI extends Phaser.GameObjects.Container {
    /**
     *
     * @param {Phaser.Scene} scene シーン
     */
    constructor(scene) {
        super(
            scene,
            COMMON_CONST.SCREEN_WIDTH - UI_CONST.GAME_INFO_UI_WIDTH,
            0
        );
        this.scene = scene;

        // UIの背景
        const background = scene.add.rectangle(
            UI_CONST.GAME_INFO_UI_WIDTH / 2,
            COMMON_CONST.SCREEN_HEIGHT / 2,
            UI_CONST.GAME_INFO_UI_WIDTH,
            COMMON_CONST.SCREEN_HEIGHT,
            UI_CONST.CHOICE_OF_POWERUP_UI_BACKGROUND_COLOR,
            1
        );
        this.add(background);

        // コンテナをシーンに追加
        scene.add.existing(this);

        let yOffset = 15;

        // スコアテキスト
        this.scoreText = this.scene.add.text(15, yOffset, "Score: 0", {
            fontFamily: "Arial Black",
            fontSize: 28,
            color: "#ffffff",
            stroke: "#000000",
            strokeThickness: 8,
            align: "center",
        });
        this.add(this.scoreText);
        yOffset += 40;

        // タイマー
        this.timeText = this.scene.add.text(
            15,
            yOffset,
            `${GAME_CONST.FORMAT_TIMER_PRE}0${GAME_CONST.FORMAT_TIMER_POST}`,
            {
                fontFamily: "Arial Black",
                fontSize: 28,
                color: "#ffffff",
                stroke: "#000000",
                strokeThickness: 8,
                align: "center",
            }
        );
        this.add(this.timeText);
        yOffset += 100;
    }
    ...
}

コンテナにまず黒の長方形を描画し、この上に要素を生成していくイメージとなります。ここで注意したいのは、コンテナのクラス生成時に指定したx座標、y座標が、コンテナ内での基準の座標となるということです。

つまりどういうことかというと、例えばコンテナの座標を(100, 50)で生成し、そのコンテナ内の左上に要素を生成したい場合は要素の座標を(0, 0)として生成し(コンテナ).add()でコンテナに追加するのですが、実際のゲーム画面上では(0, 0)の位置ではなく、(100, 50)に表示されます。コンテナに追加する要素の座標は、コンテナ上での相対位置を表しているというわけです。

少し難しいかもしれませんが、結果から言うと要素の実際の座標は、生成時に設定した座標+コンテナの座標、と考えればいいです。

パラメータと強化用選択肢の作成

前回の続きとなりますが、プレイヤーがゴールしたあとに強化用の選択肢を出現させるようにしました。現在実装できているのは「スピードアップ」「パワーアップ」の2種類となっています。

スピードアップを選択するとプレイヤーの最大速度が上昇し、より速く動けるようになります。パワーアップを選択した場合、プレイヤーのパワーが上昇します。これはまだ実装途中ですが、破壊可能なブロックを破壊することができるようになります。

実装方法としては先ほどと同じコンテナを使用しています。この選択肢の一つ一つがコンテナとなっており、ゴール時に表示されるようになっています。

        ...
        // パワーアップの種類をランダム選択する
        const powerUpTypes = [
            POWERUP_TYPE.SPEED,
            POWERUP_TYPE.SPEED,
            POWERUP_TYPE.POWER,
        ];
        for (const [index, type] of powerUpTypes.entries()) {
            console.log("PowerUpType:", type);
            // パワーアップの選択肢を表示する
            const choiceUI = new ChoiceOfPowerUpUI(
                this,
                this.screenCenterX,
                200 + index * 120,
                type
            );
            choiceUI.setScrollFactor(0);
            choiceUI.setDepth(200);
            this.uiLayer.add(choiceUI);
        }
        ...

この選択肢のクラスにて、クリックしたときの処理を紹介します。

export class ChoiceOfPowerUpUI extends Phaser.GameObjects.Container {
    /**
     *
     * @param {Phaser.Scene} scene シーン
     * @param {number} x x座標
     * @param {number} y y座標
     * @param {string} powerUpType パワーアップのタイプ
     */
    constructor(scene, x, y, powerUpType) {
        super(scene, x, y);
        this.scene = scene;
        this.powerUpType = powerUpType;

        // UIの背景
        const background = scene.add.rectangle(
            0,
            0,
            UI_CONST.CHOICE_OF_POWERUP_UI_WIDTH,
            UI_CONST.CHOICE_OF_POWERUP_UI_HEIGHT,
            UI_CONST.CHOICE_OF_POWERUP_UI_BACKGROUND_COLOR,
            0.5
        );
        this.add(background);

        // クリック判定用の矩形
        background.setInteractive(
            new Phaser.Geom.Rectangle(
                0,
                0,
                UI_CONST.CHOICE_OF_POWERUP_UI_WIDTH,
                UI_CONST.CHOICE_OF_POWERUP_UI_HEIGHT
            ),
            Phaser.Geom.Rectangle.Contains
        );

        ...

        // クリック時の処理
        background.on("pointerdown", () => {
            // パワーアップが選択されたことをシーンに通知
            this.scene.events.emit("powerupSelected", this.powerUpType);
        });
    }

ソースコードの最後のon(...)で、この選択肢がクリックされたときにシーンに対してイベントが送信されることを示しています。そしてシーン側ではイベントを受け取ったときの処理を記載します。

    /**
     * イベントリスナー設定
     */
    initEventListeners() {
        // パワーアップ選択UIのイベントリスナー
        this.events.on("powerupSelected", this.handlePowerupSelected, this);
        // シーン終了時にリスナー解除
        this.events.once("shutdown", this.cleanUpEventListeners, this);
    }

this.events.on(...)でイベントを受け取ったときに実行する処理を書いています。handlePowerupSelectedを今回は実行します。これはプレイヤーのパラメータ強化の関数なので今回は詳細説明を割愛します。

次の報告に向けて

次の報告までには以下の項目に取り組む予定です。

  • パワーパラメータを使用したブロック破壊などの処理

現在プレイヤーのパラメータのうちパワーが使われていないので、これを使ってギミックを作ろうかと思っています。

では、次回の進捗にてまた会いましょう。