Phaser.jsで始める簡単ゲーム開発③|果物を表示してタップで消す基本ロジックを作ろう

Phaser.jsで始める簡単ゲーム開発③|果物を表示してタップで消す基本ロジックを作ろう

この記事では、Phaser.jsを使って初心者でも簡単に楽しいゲームを作れるよう、ステップごとに分かりやすく解説していきます。最終的に、このシリーズを通して「ぱくぱくフルーツ」という、画面に現れる果物をタップして消していくシンプルなゲームを完成させることができます。「ぱくぱくフルーツ」は、クリックやタッチで果物を消すと、次の果物が出現して繰り返し遊べるような仕組みや、インタラクティブな要素やアニメーションを取り入れたゲームです。

こんにちは!前回までの記事では、Phaser.jsを使ったゲーム開発の基本設定と、テキストを画面に表示するところまでを学びました。

前回の記事▽

今回は、果物の画像を画面に表示し、ユーザーがタップすると果物が消える仕組みを実装していきます。これにより、実際のゲームらしいインタラクションが生まれ、ゲームの基礎を作ることができます。

ゲームのキャンバスサイズを設定する

まず、ゲームがどのデバイスでも快適に表示されるように、キャンバスのサイズを自動で調整する設定を行います。これにより、パソコンやスマートフォンなど、異なる画面サイズのデバイスでもゲームが常に適切なサイズで表示されるようになります。

次のコードを<script>タグの中に書き込んでいきましょう。

const config = {
    type: Phaser.AUTO,
    width: window.innerWidth,
    height: window.innerHeight,
    backgroundColor: '#add8e6',
    parent: document.body,
    scene: {
        preload: preload,
        create: create
    },
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 0 },
            debug: false
        }
    }
};
  • widthheight: それぞれウィンドウの幅と高さを設定します。window.innerWidthwindow.innerHeightを使用することで、ユーザーの画面サイズに応じたキャンバスを自動的に生成できます。
  • backgroundColor: ゲームの背景色を水色(#add8e6)に設定しています。
  • physicsオブジェクト: 物理エンジンの設定を行います。ここでは、Phaser.jsに組み込まれているArcade物理エンジンを使用しています。このエンジンはシンプルで軽量な物理演算を提供し、基本的な重力や衝突判定を処理することができます。
    • default: 'arcade': 使用する物理エンジンとしてArcadeを指定します。
    • arcade: Arcadeエンジンの詳細設定を行います。
      • gravity: { y: 0 }: ゲーム内のオブジェクトに対して重力を設定します。ここではY方向の重力を0にしているため、オブジェクトが自然に落下することはありません。
      • debug: false: デバッグモードのオン・オフを指定します。falseに設定すると、デバッグ用の表示(衝突判定の範囲など)は表示されません。

この設定により、ゲームのキャンバスは画面サイズに自動でリサイズされ、常に中央に配置されます。また、軽量な物理エンジンが適用され、今後追加する動きやインタラクションの処理がスムーズに行われます。

果物の画像を読み込み、画面に表示する

次に、果物の画像を読み込み、画面に表示する方法を学びます。ここでは、リンゴやバナナなど、さまざまな果物を画面に表示していきます。

画像の読み込み

まず、preload関数で果物の画像を読み込みます。画像ファイルはassetsフォルダにあらかじめ保存しておく必要があります。

function preload() {
    this.load.image('apple', 'assets/apple.png');
    this.load.image('grape', 'assets/grape.png');
    this.load.image('banana', 'assets/banana.png');
    this.load.image('melon', 'assets/melon.png');
    this.load.image('orange', 'assets/orange.png');
}
  • this.load.image: 各果物の画像ファイルを読み込みます。画像はassetsフォルダ内に保存しておきます。

画像の準備に困った場合は、フリー素材集で有名な「いらすとや」が便利です。規約の範囲内であれば、個人、法人、商用、非商用問わず無料で使用できます。

変数の設定

let fruits;
let currentFruit = 'apple';
const fruitTypes = ['apple', 'grape', 'banana', 'melon', 'orange'];
const fruitSize = Math.min(game.config.width, game.config.height) / 3;
  • fruits: これは、画面に表示されるすべての果物を格納するための変数です。Phaser.jsでは、複数のオブジェクトを扱う際にグループ化することで、管理がしやすくなります。
  • currentFruit: 現在表示している果物の種類を保持する変数です。初期値としてリンゴ(apple)を設定しています。この変数を使って、どの果物を表示するかを制御します。
  • fruitTypes: この配列には、ゲームで使用する果物の種類をリストにしています。リンゴ、ブドウ、バナナ、メロン、オレンジの5種類が含まれています。これらの果物はランダムに選ばれて画面に表示されます。
  • fruitSize: 画面に表示する果物のサイズを決定するための変数です。game.config.widthgame.config.heightのどちらか小さい方の1/3の大きさに設定しています。これにより、画面のサイズに応じて果物が適切な大きさで表示されます。

画面に果物を表示する

次に、create関数で読み込んだ果物を画面に表示します。

function create() {
    fruits = this.physics.add.group();
    this.input.on('pointerdown', handleTap, this);

    generateFruits.call(this);
}
  • this.physics.add.group(): 画面上に複数の果物をグループとして管理するために使用します。これにより、果物を一括で操作することが可能になります。
  • this.input.on('pointerdown', handleTap, this): ユーザーが画面をクリックまたはタップしたときにhandleTap関数が呼び出されるように設定します。

果物を生成して表示する

次に、generateFruits関数を使って、画面に果物をランダムに配置します。

function generateFruits(type) {
    for (let i = 0; i < 5; i++) {
        let x = Phaser.Math.Between(fruitSize, game.config.width - fruitSize);
        let y = Phaser.Math.Between(fruitSize, game.config.height - fruitSize);

        const type = Phaser.Utils.Array.GetRandom(fruitTypes);
        const fruit = fruits.create(x, y, type).setInteractive();

        const originalWidth = fruit.width;
        const scaleX = fruitSize / originalWidth;

        fruit.setScale(scaleX);
    }
}
  • Phaser.Math.Between(fruitSize, game.config.width - fruitSize): 果物を画面内にランダムに配置するために、X座標とY座標をランダムに設定します。このコードにより、果物が画面の端に寄りすぎないように配置されます。
  • Phaser.Utils.Array.GetRandom(fruitTypes): fruitTypes配列からランダムに果物の種類を選びます。これにより、画面に表示される果物の種類がランダムになります。
  • fruits.create(x, y, type).setInteractive(): 指定された位置に果物を表示し、それをインタラクティブ(タップ可能)に設定します。
  • fruit.setScale(scaleX): 果物のサイズを画面のサイズに合わせて調整します。これにより、どのデバイスでも果物が適切なサイズで表示されます。

果物をタップして消す仕組みを実装する

次に、ユーザーが果物をタップすると、その果物が消える仕組みを実装します。

果物をタップして消す

handleTap関数で、タップされた果物を特定し、それを消す処理を行います。

function handleTap(pointer) {
    const children = fruits.getChildren();
    for (let i = children.length - 1; i >= 0; i--) {
        const fruit = children[i];
        if (Phaser.Geom.Rectangle.Contains(fruit.getBounds(), pointer.x, pointer.y)) {
            fruit.destroy();
            break;
        }
    }
}
  • fruits.getChildren(): グループ内に含まれるすべての果物を取得します。
  • Phaser.Geom.Rectangle.Contains: ユーザーがタップした位置が果物の範囲内にあるかどうかを確認します。
  • fruit.destroy(): タップされた果物を削除します。これにより、果物が画面から消えます。

全体のコード

ここまでの、全体のコードを確認しましょう。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Fruit Tap Game</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #add8e6;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.min.js"></script>
    <script>
        const config = {
            type: Phaser.AUTO,
            width: window.innerWidth,
            height: window.innerHeight,
            backgroundColor: '#add8e6',
            parent: document.body,
            scene: {
                preload: preload,
                create: create
            },
            physics: {
                default: 'arcade',
                arcade: {
                    gravity: { y: 0 },
                    debug: false
                }
            }
        };

        const game = new Phaser.Game(config);

        let fruits;
        let currentFruit = 'apple';
        const fruitTypes = ['apple', 'grape', 'banana', 'melon', 'orange'];
        const fruitSize = Math.min(game.config.width, game.config.height) / 3;

        function preload() {
            this.load.image('apple', 'assets/apple.png');
            this.load.image('grape', 'assets/grape.png');
            this.load.image('banana', 'assets/banana.png');
            this.load.image('melon', 'assets/melon.png');
            this.load.image('orange', 'assets/orange.png');
        }

        function create() {
            fruits = this.physics.add.group();
            this.input.on('pointerdown', handleTap, this);

            generateFruits.call(this, currentFruit);
        }

        function handleTap(pointer) {
            const children = fruits.getChildren();
            for (let i = children.length - 1; i >= 0; i--) {
                const fruit = children[i];
                if (Phaser.Geom.Rectangle.Contains(fruit.getBounds(), pointer.x, pointer.y)) {
                    fruit.destroy();
                    break;
                }
            }
        }

        function generateFruits(type) {
            for (let i = 0; i < 5; i++) {
                let x = Phaser.Math.Between(fruitSize, game.config.width - fruitSize);
                let y = Phaser.Math.Between(fruitSize, game.config.height - fruitSize);

                const fruit = fruits.create(x, y, type).setInteractive();

                const originalWidth = fruit.width;
                const scaleX = fruitSize / originalWidth;

                fruit.setScale(scaleX);
            }
        }
    </script>
</body>
</html>

動作確認をしてみよう

最後に、実際にコードをブラウザで実行して、動作確認をしてみましょう。以下の点を確認してください。

  • 画面に5つの果物が表示されていること。
  • ユーザーが果物をクリックまたはタップすると、その果物が消えること。

これらが正しく動作していれば、今回のコードは成功です!次回の章では、さらにゲームに動きを加えて、より面白いものにしていきます。引き続き、楽しんでゲーム開発を進めていきましょう!

次の記事▽