Phaser.jsで始める簡単ゲーム開発⑤|果物に動きとアニメーションを追加しよう!

Phaser.jsで始める簡単ゲーム開発⑤|果物に動きとアニメーションを追加しよう!

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

こんにちは!前回の記事では、果物がすべて消えると次の果物が現れ、背景色が変わるという仕組みを実装しました。

前回の記事▽

この章では、さらにゲームを楽しくするために、果物に動きとアニメーションを追加します。具体的には、果物が画面の中を動き回り、端にぶつかると跳ね返るアニメーションと、果物が画面に現れる際に「ぽよよん」としたアニメーション効果を追加します。

果物が画面の中を動き回るアニメーション

まず、果物が画面の中をランダムに動き回り、画面の端にぶつかると跳ね返るアニメーションを追加します。これにより、果物が静止しているだけでなく、動きのあるインタラクティブな要素が加わり、プレイヤーを引きつけることができます。

create関数の強化

以下のコードでは、果物が画面内を動き回り、端にぶつかると跳ね返るように設定しています。

function create() {
    fruits = this.physics.add.group({
        bounceX: 1,
        bounceY: 1,
        collideWorldBounds: true
    });
    this.input.on('pointerdown', handleTap, this);

    // 最初の果物を生成
    generateFruits.call(this, currentFruit);
}
  • bounceX: 1, bounceY: 1: 果物が画面の端にぶつかったときに、X軸とY軸の両方で跳ね返るように設定します。1という値は、完全に跳ね返る(エネルギー損失がない)ことを意味します。
  • collideWorldBounds: true: 果物が画面の端(境界)に衝突することを有効にします。これにより、果物が画面の外に出ることなく、常に画面内で動き回ります。

果物が画面の中を動くアニメーション

function generateFruits(type) {
    fruits.clear(true, true);

    // 果物ごとの背景色を設定
    // ...

    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;

        // 果物が画面の中を動く
        this.tweens.add({
            targets: fruit,
            duration: Phaser.Math.Between(4000, 6000),
            x: x + Phaser.Math.Between(-100, 100),
            y: y + Phaser.Math.Between(-100, 100),
            yoyo: true,
            repeat: -1,
            ease: 'Sine.easeInOut',
        });
    }
}
  • x: x + Phaser.Math.Between(-100, 100), y: y + Phaser.Math.Between(-100, 100): このコードでは、果物が画面内をランダムに移動する距離を設定します。X軸とY軸の両方で、果物が元の位置から-100ピクセルから100ピクセルの範囲で移動するようになっています。
  • yoyo: true: 果物が指定された位置に移動した後、元の位置に戻る動きを繰り返すように設定します。これにより、果物が画面内を行ったり来たりする動きが生まれ、ゲームに動きが加わります。
  • repeat: -1: この設定は、アニメーションが無限に繰り返されることを意味します。-1は無限ループを示しています。
  • ease: 'Sine.easeInOut': アニメーションの動きが滑らかになるように、サイン波に基づいたイージング関数を使用します。これにより、果物が自然な動きで移動します。

果物が現れる際の「ぽよよん」アニメーション

次に、果物が画面に現れるときに「ぽよよん」としたアニメーションを追加します。これにより、果物が現れる際の視覚的な楽しさが増し、プレイヤーがよりインタラクションを楽しむことができます。

generateFruits関数の強化

以下のコードでは、果物が現れる際に「ぽよよん」としたスケールアニメーションを追加しています。

function generateFruits(type) {
    fruits.clear(true, true);

    // 果物ごとの背景色を設定
    // ...

    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(0); // 初期スケールを0に設定

        // 果物の「ぽよよん」アニメーション
        this.tweens.add({
            targets: fruit,
            scaleX: scaleX,
            scaleY: scaleX,
            duration: 500,
            ease: 'Bounce.easeOut'
        });

        // 果物が画面の中を動く
        this.tweens.add({
            targets: fruit,
            duration: Phaser.Math.Between(4000, 6000),
            x: x + Phaser.Math.Between(-100, 100),
            y: y + Phaser.Math.Between(-100, 100),
            yoyo: true,
            repeat: -1,
            ease: 'Sine.easeInOut',
        });
    }
}
  • fruit.setScale(0): 果物の初期スケールを0に設定します。これにより、果物が最初は画面に表示されない状態になります。
  • this.tweens.add({ ... }): Phaser.jsのtweens機能を使って、果物が現れる際にスケールアニメーションを適用します。Bounce.easeOutは、弾むようなエフェクトを与えるイージング関数です。このアニメーションによって、果物が「ぽよよん」と弾むようにして画面に登場します。
  • x: x + Phaser.Math.Between(-100, 100), y: y + Phaser.Math.Between(-100, 100): 果物が画面内をランダムに移動する距離を設定します。X軸とY軸の両方で、果物が元の位置から-100ピクセルから100ピクセルの範囲で移動するようになっています。
  • yoyo: true: 果物が指定された位置に移動した後、元の位置に戻る動きを繰り返す設定です。これにより、果物が画面内を行ったり来たりする動きを実現します。
  • repeat: -1: アニメーションが無限に繰り返される設定です。
  • ease: 'Sine.easeInOut': 自然で滑らかな動きを実現するためのイージング関数です。

全体のコード

以下は、この章で完成した全体のコードです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>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({
                bounceX: 1,
                bounceY: 1,
                collideWorldBounds: true
            });
            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();
                    if (fruits.countActive(true) === 0) {
                        const nextIndex = (fruitTypes.indexOf(currentFruit) + 1) % fruitTypes.length;
                        currentFruit = fruitTypes[nextIndex];
                        generateFruits.call(this, currentFruit);
                    }
                    break;
                }
            }
        }

        function generateFruits(type) {
            fruits.clear(true, true);

            switch (type) {
                case 'apple':
                    this.cameras.main.setBackgroundColor('#ffbcbc');
                    document.body.style.backgroundColor = '#ffbcbc';
                    break;
                case 'grape':
                    this.cameras.main.setBackgroundColor('#9ee3b2');
                    document.body.style.backgroundColor = '#9ee3b2';
                    break;
                case 'banana':
                    this.cameras.main.setBackgroundColor('#fff4b3');
                    document.body.style.backgroundColor = '#fff4b3';
                    break;
                case 'melon':
                    this.cameras.main.setBackgroundColor('#ffd1dc');
                    document.body.style.backgroundColor = '#ffd1dc';
                    break;
                case 'orange':
                    this.cameras.main.setBackgroundColor('#ffe6b3');
                    document.body.style.backgroundColor = '#ffe6b3';
                    break;
            }

            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(0); // 初期スケールを0に設定

                // 果物の「ぽよよん」アニメーション
                this.tweens.add({
                    targets: fruit,
                    scaleX: scaleX,
                    scaleY: scaleX,
                    duration: 500,
                    ease: 'Bounce.easeOut'
                });

                // 果物が画面の中を動く
                this.tweens.add({
                    targets: fruit,
                    duration: Phaser.Math.Between(4000, 6000),
                    x: x + Phaser.Math.Between(-100, 100),
                    y: y + Phaser.Math.Between(-100, 100),
                    yoyo: true,
                    repeat: -1,
                    ease: 'Sine.easeInOut',
                });
            }
        }
    </script>
</body>
</html>

動作確認をしてみよう

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

  • 画面に5つの果物が表示されていること。
  • 果物が画面の中を動き、端にぶつかると跳ね返ること。
  • ユーザーが果物をクリックまたはタップすると、その果物が消えること。
  • すべての果物が消えると、次の種類の果物が表示されること。
  • 果物が現れるときに「ぽよよん」としたアニメーションが実行されること。

これらが正しく動作していれば、今回のコードは成功です!次回の章では、さらに効果音を追加して、ゲームの臨場感を高めていきます。引き続き、楽しんでゲーム開発を進めていきましょう!

次の記事▽