Skip to content

パドルとボールを動かす

前章ではパドルとボールを画面に表示するまで進めました。 次にそれらをタッチインタラクションで動かしてみましょう。

パドルをスワイプ操作する

パドルをスワイプ操作で左右に動くようにしてみましょう。 以下のコードを追加します。

javascript
        // ball を作成
        const ball = new g.Sprite({
            ...
        });
        scene.append(ball); // ball をシーンに追加

        // スワイプでパドルが左右に動くようにする
        scene.onPointMoveCapture.add((event) => {
            paddle.x += event.prevDelta.x;
            paddle.modified();
        });
        // ball を作成
        const ball = new g.Sprite({
            ...
        });
        scene.append(ball); // ball をシーンに追加

        // スワイプでパドルが左右に動くようにする
        scene.onPointMoveCapture.add((event) => {
            paddle.x += event.prevDelta.x;
            paddle.modified();
        });

scene.onPointMoveCapture は画面内の任意の部分をドラッグ操作したときに通知されるトリガです。 add() メソッドに処理を追加することで、ドラッグ操作時の処理を登録することができます。

トリガの通知時に渡されるイベント event から、直前のポイントからの差分の座標 event.prevDelta を取得できます。 パドルの移動方向を x 軸方向に制限するため、ここでは paddle.x のみを変更します。

paddle.x を変更した後、その変更を Akashic Engine 側へ伝えるために paddle.modified() を実行します。

ボールを移動させる

同様にボールも移動させてみましょう。

scene.onUpdate はゲームのフレームごとに通知されるトリガです。 まずは右上へと移動するようにコードを修正してみます。

javascript
        // スワイプでパドルが左右に動くようにする
        scene.onPointMoveCapture.add((event) => {
            ...
        });

        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;
            ball.modified();
        });
        // スワイプでパドルが左右に動くようにする
        scene.onPointMoveCapture.add((event) => {
            ...
        });

        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;
            ball.modified();
        });

vx, vy にそれぞれの軸の速さを変数として格納しています。

TIP

ball.x += vxball.x = ball.x + vx と同様の処理です。 同じように ball.y -= vyball.y = ball.y - vx と同様の処理です。

y 軸方向のみ -= としている点に注意してください。 これは前章で説明したように Akashic Engine の y 軸は 下向きが正方向 であるためです。

ここでゲームを実行してみましょう。

ボールが右上方向に移動するようになりました。 しかしこれではボールが画面外へと飛び出してしまいます。 ボールが画面の端に到達したときに跳ね返るようにコードを修正してみましょう。

javascript
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            ball.modified();
        });
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            ball.modified();
        });

ボールが画面外に到達したかどうかは、ボールの端がゲーム画面の端に接したかどうかで判定することができます。

ボールの中心座標は (ball.x, ball.y) です。 ボールの上下左右の座標はそれぞれ以下のように求まります。

場所x 座標y 座標
ball.xball.y - ball.height / 2
ball.xball.y + ball.height / 2
ball.x + ball.width / 2ball.y
ball.x - ball.width / 2ball.y

図で表すと以下のようになります。

上記のコードは、ボールがゲームの右端または左端に接したときに vx を、 ゲームの上端に接したときに vy を反転させることで跳ね返りを実現しています。

TIP

変数の定義時に constlet の2つが利用されていることがわかると思います。 大きな違いとして const で定義された変数は再代入ができず、一方 let では再代入ができるという違いがあります。 詳細については MDN のドキュメント を参照してください。

ボールとパドルの衝突判定

パドルとボールを動かすことができました。 次はパドルとボールの衝突処理を実装しましょう。

Akashic Engine には衝突判定を行う組み込みメソッド g.Collision.intersect() があります。 これは2つの矩形の座標と大きさを与えると、それが交差しているかを判定して真理値を返します。

ではパドルとボールの矩形を考えてみます。

パドルとボールの距離がそれぞれの大きさよりも短い場合、パドルとボールは接していると判定できます。 このような矩形の当たり判定には g.Collision.intersect() が利用できます。

少し長いですが g.Collision.intersect() は次のように引数を指定します。

g.Collision.intersect(矩形1の x 座標, 矩形1の y 座標, 矩形1の横幅, 矩形1の縦幅, 矩形2の x 座標, 矩形2の y 座標, 矩形2の横幅, 矩形2の縦幅)

矩形1をパドル、矩形2をボールとして、先ほどの図と照らし合わせると、以下のようになります。 見やすいように改行を加えていますが処理に影響はありません。 各矩形の座標は中心にあるため、それぞれの座標から x について width / 2、 y について height / 2 を引いている座標が当たり判定の始点となっていることに注意してください。

javascript
if (g.Collision.intersect(
    paddle.x - paddle.width / 2,
    paddle.y - paddle.height / 2,
    paddle.width,
    paddle.height,
    ball.x - ball.width / 2,
    ball.y - ball.height / 2,
    ball.width,
    ball.height
)) {
    // 衝突したときの処理
}
if (g.Collision.intersect(
    paddle.x - paddle.width / 2,
    paddle.y - paddle.height / 2,
    paddle.width,
    paddle.height,
    ball.x - ball.width / 2,
    ball.y - ball.height / 2,
    ball.width,
    ball.height
)) {
    // 衝突したときの処理
}

以上を既存のコードに適用すると、以下のようなコードとなります。

javascript
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            if (g.Collision.intersect(
                paddle.x - paddle.width / 2,
                paddle.y - paddle.height / 2,
                paddle.width,
                paddle.height,
                ball.x - ball.width / 2,
                ball.y - ball.height / 2,
                ball.width,
                ball.height
            )) {
                vy = -vy;
            }

            ball.modified();
        });
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            if (g.Collision.intersect(
                paddle.x - paddle.width / 2,
                paddle.y - paddle.height / 2,
                paddle.width,
                paddle.height,
                ball.x - ball.width / 2,
                ball.y - ball.height / 2,
                ball.width,
                ball.height
            )) {
                vy = -vy;
            }

            ball.modified();
        });

コードの簡潔化

g.Collision.intersect() の処理が冗長になってしまいました。 このままだと paddle を別のエンティティに修正する際に複数箇所を書き換えなければなりません。 もう少し使いやすく (できれば一箇所を変えれば済むように) してみましょう。

function を使うと任意の処理を関数としてまとめることができます。 ここでは g.Collision.intersect() を使って、エンティティ二つを受け取りそれらが衝突しているかの真理値を返す関数 intersect() を作成します。

javascript
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            if (g.Collision.intersect( 
                paddle.x - paddle.width / 2, 
                paddle.y - paddle.height / 2, 
                paddle.width, 
                paddle.height, 
                ball.x - ball.width / 2, 
                ball.y - ball.height / 2, 
                ball.width, 
                ball.height 
            )) { 
            // ボールとパドルが衝突したとき
            if (intersect(ball, paddle)) {
                vy = -vy;
            }

            ball.modified();
        });

        // 任意のエンティティの衝突を判定する関数
        function intersect(e1, e2) {
            return g.Collision.intersect(
                e1.x - e1.width / 2,
                e1.y - e1.height / 2,
                e1.width,
                e1.height,
                e2.x - e2.width / 2,
                e2.y - e2.height / 2,
                e2.width,
                e2.height
            );
        }
        let vx = 8;
        let vy = 8;
        scene.onUpdate.add(() => {
            ball.x += vx;
            ball.y -= vy;

            // ボールが画面の左右に到達したとき
            if ((ball.x > game.width - ball.width / 2) || (ball.x < ball.width / 2)) {
                vx = -vx;
            }
            // ボールが画面の上端に到達したとき
            if (ball.y < ball.height / 2) {
                vy = -vy;
            }

            if (g.Collision.intersect( 
                paddle.x - paddle.width / 2, 
                paddle.y - paddle.height / 2, 
                paddle.width, 
                paddle.height, 
                ball.x - ball.width / 2, 
                ball.y - ball.height / 2, 
                ball.width, 
                ball.height 
            )) { 
            // ボールとパドルが衝突したとき
            if (intersect(ball, paddle)) {
                vy = -vy;
            }

            ball.modified();
        });

        // 任意のエンティティの衝突を判定する関数
        function intersect(e1, e2) {
            return g.Collision.intersect(
                e1.x - e1.width / 2,
                e1.y - e1.height / 2,
                e1.width,
                e1.height,
                e2.x - e2.width / 2,
                e2.y - e2.height / 2,
                e2.width,
                e2.height
            );
        }

function の定義は scene.onUpdate の外で行うように注意しましょう。

if 文の中身に注目してください。 intersect(ball, paddle) のように呼び出しのコードを簡略化できました。 このように関数として処理をまとめることで、paddle を別のエンティティに変更したい場合に修正箇所を一箇所でまとめることができます。

TIP

intersect() の引数に指定している e1, e2 という変数名は、 entity すなわちエンティティを表しています。

実行例とソースコード

Playground で実行