お茶漬けびより

"あなたに教わったことを、噛んでいるのですよ" 五等分の花嫁 7巻 「最後の試験が五月の場合」より

Akashic Engine で 敵を出して、クリックしたら弾が飛んで敵を倒すだけ

最近、Akashic Engine というライブラリで遊んでいます。

akashic-games.github.io

Akashic Engine がどういうものかというのは、公式に詳しく書いてあるのでここでは説明しませんが、簡潔に書くとどんなプラットフォームでも使えるゲームエンジンです。ドワンゴさんが開発しています。

ゲームを作って公開してみようかと考えていましたが、モチベーションの維持や学んだことを記録するためにできたことを記事に出力していこうかと思います。Akashic Engine を使ってニコ生ゲームを作ってみたい人に向けて書こうと思っているので、なるべくコピペで済む(できる)ような内容にしようと思っています。

で、今回はタイトルにあるようにクリックしたら弾っぽいものが飛んで敵に当たったら倒すような動きのコードを書きました。自分は TypeScript で書いていますが、JavaScript しか知らなくてもなんとなく分かるんじゃないかなぁと思っています。

実行すると以下のような感じです。

f:id:pickles-ochazuke:20200914013506g:plain

プロジェクトは akashic init -t typescript-minimal で作成しました。

したこと

コードの全文は最後に載せていますし、Github にも上げています。

今回行ったことは以下です。

  • 画面をクリックしたらプレイヤーの目の前に弾を出す
  • 弾は毎フレーム前に進む(今回は右に進む)
  • 毎フレーム乱数を作り、値によって敵を出す
  • 敵と弾が当たったら両方を消す(敵を倒す判定処理)

画面をクリックしたらプレイヤーの目の前に弾を出す

ユーザが画面をクリックしたときのイベントを登録するには、scene.pointDownCapture.add() を使います。add() の引数に関数を渡します。

たとえば、以下のようにします。

// 弾を管理するための変数
let bullet: g.E;
// 画面内でクリックしたら弾を飛ばしたいため
scene.pointDownCapture.add(() => {
    if (bullet == null) {
        // 弾を作成して、シーンに登録する(見えるようにする)
        bullet = shoot(scene, player);
        scene.append(bullet);
    }
})

弾は毎フレーム前に進む(今回は右に進む)

今回は弾を出したいのでその処理を行っています。shoot() 関数で弾を作っています。shoot() 関数では以下のようなことをしています。

// 弾を作成する処理
function shoot(scene: g.Scene, player: g.E): g.E {
    const bullet = new g.FilledRect({
        scene: scene,
        cssColor: "#0000FF",
        width: 20,
        height: 5,
        x: player.x + player.width, // プレイヤーの目の前に出す x
        y: player.y + player.height / 2 // プレイヤーの目の前に出す y
    });

    // 弾は出現中、まっすぐ進む(毎フレーム 5pixel 進む)
    bullet.update.add(() => {
        // 画面の外に出ると消える
        if (bullet.x > g.game.width) {
            bullet.x = 0;
            bullet.destroy();
            return;
        }
        bullet.x += 5;
        bullet.modified();
    });

    return bullet;
}

shoot() 関数では弾の生成と弾の動きの設定をしています。弾の動きは

  • 毎フレーム5pixel ずつ進む
  • 画面外に出ると消える

を行っています。

毎フレーム乱数を作り、値によって敵を出す

敵を出現させるには以下のようにしています。

// 敵を管理するための変数
let enemy: g.E;
/**
 * 毎フレーム以下のことを行う
 * 敵をランダムで出現させる(1体しか出ないようにする)
 * 敵と弾が出ていたら、当たり判定を行う
 * 判定の結果、敵と弾が当たっていたらどちらも削除する
 */
scene.update.add(() => {
    // 乱数の作成(0 ~ 9 の範囲)
    const value = Math.floor(g.game.random.generate() * 10);
    // 敵がいなくて、9 が出たら敵を出現させる
    if (enemy == null && value > 8) {
        enemy = generateEnemy(scene);
        scene.append(enemy);
    }
...

敵の出現は毎フレーム行うため、scene.update.add() に渡す関数内で行っています。乱数の生成はMath.floor(g.game.random.generate() * 10) で行っています。この処理は、Akashic Engine 公式のチュートリアルを参考にしました(コピペとも言う)。

akashic-games.github.io

敵の生成はgenerateEnemy() 関数で行っています。四角を表示したいとこに出してるだけなのでとくに説明はしません(コードは最後に載っています)。

敵と弾が当たったら両方を消す(敵を倒す判定処理)

当たり判定処理は以下のように行っています。

// 敵と弾が出現していたらあたり判定を行う
if (enemy && bullet) {
    // 2者の位置から重なっていれば当たっていると判定する(true が返ってくる)
    const hit = g.Collision.intersectAreas(enemy, bullet);
    // 当たっていたらどちらも削除する
    if (hit) {
        bullet.destroy();
        bullet = null;
        enemy.destroy();
        enemy = null;
    }
}

この処理は、scene.update.add() に渡している関数内で行っています。

あたり判定はg.Collision.intersectAreas(enemy, bullet) で行っています。intersectAreas() には判定したいエンティティを渡します。今回だと enemybullet を渡しています。渡す引数は、CommonArea と同じプロパティを持っている必要があります。いわゆる g.E の派生されたものなら問題ないです。

Collision.intersectAreas() のリファレンスは以下です。

akashic-games.github.io

また CommonArea についてのリファレンスは以下です。

akashic-games.github.io

これらを行うとこの記事の初めに見せた GIF のような動きをするプログラムになります。

コード全文

最後にコード全文を載せておきます。

function main(param: g.GameMainParameterObject): void {
    const scene = new g.Scene({game: g.game});

    scene.loaded.add(() => {
        
        // プレイヤーの作成
        const player = new g.FilledRect({
            scene: scene,
            cssColor: "#ff0000",
            width: 32,
            height: 32,
            x: g.game.width / 3, // 初期位置 x
            y: g.game.height - 100 // 初期位置 y
        });

        // プレイヤーでは何もしない
        player.update.add(() => {

        });

        // 弾を管理するための変数
        let bullet: g.E;
        // 画面内でクリックしたら弾を飛ばしたいため
        scene.pointDownCapture.add(() => {
            if (bullet == null) {
                // 弾を作成して、シーンに登録する(見えるようにする)
                bullet = shoot(scene, player);
                scene.append(bullet);
            }
        })

        // 敵を管理するための変数
        let enemy: g.E;
        /**
        * 毎フレーム以下のことを行う
        * 敵をランダムで出現させる(1体しか出ないようにする)
        * 敵と弾が出ていたら、当たり判定を行う
        * 判定の結果、敵と弾が当たっていたらどちらも削除する
        */
        scene.update.add(() => {
            // 乱数の作成(0 ~ 9 の範囲)
            const value = Math.floor(g.game.random.generate() * 10);
            // 敵がいなくて、9 が出たら敵を出現させる
            if (enemy == null && value > 8) {
                enemy = generateEnemy(scene);
                scene.append(enemy);
            }

            // 敵と弾が出現していたらあたり判定を行う
            if (enemy && bullet) {
                // 2者の位置から重なっていれば当たっていると判定する(true が返ってくる)
                const hit = g.Collision.intersectAreas(enemy, bullet);
                // 当たっていたらどちらも削除する
                if (hit) {
                    bullet.destroy();
                    bullet = null;
                    enemy.destroy();
                    enemy = null;
                }
            }
        });

        scene.append(player);
    });
    g.game.pushScene(scene);
}

// 弾を作成する処理
function shoot(scene: g.Scene, player: g.E): g.E {
    const bullet = new g.FilledRect({
        scene: scene,
        cssColor: "#0000FF",
        width: 20,
        height: 5,
        x: player.x + player.width, // プレイヤーの目の前に出す x
        y: player.y + player.height / 2 // プレイヤーの目の前に出す y
    });

    // 弾は出現中、まっすぐ進む(毎フレーム 5pixel 進む)
    bullet.update.add(() => {
        // 画面の外に出ると消える
        if (bullet.x > g.game.width) {
            bullet.x = 0;
            bullet.destroy();
            return;
        }
        bullet.x += 5;
        bullet.modified();
    });

    return bullet;
}

// 敵を作成する
function generateEnemy(scene: g.Scene) {
    const enemy = new g.FilledRect({
        scene: scene,
        cssColor: "#00FF00",
        width: 32,
        height: 32,
        x: g.game.width - 100, // プレイヤーの正面に表示 x
        y: g.game.height - 100 // プレイヤーの正面に表示 y
    })
    
    return enemy;
}

export = main;

また Github にも公開しています。上記に載せたソースコードsrc/main.ts にあります。

github.com