TypeScriptでゲームプログラム2「操作キャラを追加」

ゲームらしさ

CANVAS内を球が弾んでいるだけではとてもゲームとは言えません。ゲームらしさとは何か?それは「プレイヤーが操作することで、状態が変化する。」ことです。

例えば、一般的なシューティングゲームではプレイヤーキャラ(自機)を移動させることで、障害物や敵の攻撃を避ける操作を行い、弾を発射して敵を撃破します。プレイヤーが何も操作しなければ敵を倒すことはなく、障害物や敵に当たってゲームオーバーになります。つまり、「プレイヤーの操作」が大事なポイントです。アクションゲームなどもそうです。

将棋とかオセロのようなものはシューティングゲームのような例からはちょっと外れるかもしれません。けれど、「動かす(あるいは打つ)駒を選んで、移動させる」ことも操作と考えれば同じでしょう。

プレイヤーの操作キャラ

そこで「球が弾むだけの何か」をゲームらしくするために、プレイヤーが操作するものを用意します。ブロックくずしやテニスのように、落ちてくる球を撃ち返すもの(ラケットと呼ぶことにする)があって、それを操作して動かせばゲームっぽくなるでしょう。

ラケットを描画するためのコードを以下に示します。

/**
 * 球が移動できる範囲
 */
let Area = {
    X: 10,
    Y: 10,
    Width: 300,
    Height: 300,
}

/**
 * ラケット(弾を撃ち返すキャラクター)
 */
let Racket = {
    X: 0,           // X座標
    Y: 0,           // Y座標
    Width: 40,      // ラケットの横幅
    Height: 10,     // ラケットの縦幅
};

/**
 * ゲームCANVASを描画する。
 */
function DrawGameCanvas() {
    let cvsGame = document.getElementById("game") as HTMLCanvasElement;
    if (cvsGame) {
        let width = cvsGame.width;
        let height = cvsGame.height;
        let context = cvsGame.getContext("2d");
        if (context && width > 0 && height > 0) {

            // ラケット以外の描画処理は省略

            // ラケットを描画
            context.fillStyle = "yellowgreen";
            let racket_x = Racket.X - Racket.Width / 2;
            let racket_y = Racket.Y - Racket.Height / 2;
            context.fillRect(racket_x, racket_y, Racket.Width, Racket.Height);
        }
    }
}

// ラケットのX座標とY座標を初期化する。
// X座標:初期値はAreaの横幅の真ん中
Racket.X = Area.X + Area.Width / 2;
// Y座標:初期値はAreaの底辺からラケットの縦幅の半分だけ上
Racket.Y = Area.Y + Area.Height - Racket.Height / 2

window.requestAnimationFrame(GameLoop);

ラケットの初期位置はボールが移動する範囲(Area)の下辺の中央にしました。ラケットのX座標を変えれば、ラケットが描画される場所も変わります。

次に追加するべきコードは、プレイヤーの操作でラケットのX座標を変更することです。

ラケットの操作

プレイヤーがラケットを操作する方法としては、

  1. マウスクリック
  2. マウスの移動
  3. キー入力

などが考えられます。携帯機器なら傾きを検知させる…とかもあるでしょう。今回はマウス移動でラケットを操作することにします。その仕様は以下の通りです。

  • ラケットはゲーム画面の下辺にあり、水平方向(X方向)にしか移動しない。
  • ラケットのX座標はマウスポインタと同じになるように移動する。
    • 移動するスピードはマウスポインタより遅い。(マウスポインタの移動に遅れて追随する感じ)
    • ラケットの現在位置とマウスポインタの現在位置の距離が長いほど移動スピードは速くなる。
  • ラケットが左右の端にぶつかったとき、マウスポインタの位置に関係なく、ラケットは止まる。

マウスポインタの位置を検知する

マウスポインタの位置を検知するために、CANVASに対するマウスイベントを追加します。

/**
 * ラケットの移動先のX座標
 */
let RacketDestinationX = 0;

window.addEventListener("DOMContentLoaded", () => {
    let cvsGame = document.getElementById("game") as HTMLCanvasElement;
    if (cvsGame) {
        // ゲームキャンバス内をマウスが移動したときのイベント処理を追加する。
        cvsGame.addEventListener("mousemove", (ev) => {
            RacketDestinationX = ev.offsetX;
        })
    }
});

マウスポインタがCANVAS内を移動したときに、ポインタのX座標を得て、RacketDestinationXにセットします。これを目標にラケットが追いかけるようにしていきます。

ラケットの移動

UpdateGameSituation()で、ラケットのX座標がRacketDestinationXに近づくような処理を行います。
移動範囲(Area)の端でラケットが止まる処理もここで行います。

/**
 * ゲームの状況を更新する
 */
function UpdateGameSituation() {
    let left = Area.X + Racket.Width / 2;
    let right = Area.X + Area.Width - Racket.Width / 2;

    // ラケットの移動先と現在値との距離
    let distance = RacketDestinationX - Racket.X;
    // 距離に応じて、X軸の変化速度を変更する。
    let vx = distance / 5;

    // ラケットを移動する。
    let x = Racket.X + vx;
    if (x < left) {
        x = left;
    }
    else if (x > right) {
        x = right;
    }
    Racket.X = x;
}

ラケットのX座標とマウスポインタのX座標の距離を出し、その1/5の距離を縮めるようにラケットのX座標を更新してます。
1/5を1/1にすれば、マウスポインタに追随してラケットが動くようになります。1/10や1/20などにすればもっとゆっくり移動するようになります。

実際の例

すべてのコードをまとめて、実際に動かしたときの例を以下に示します。
この例ではラケットのみを表示しており、黄色の玉の表示はしていません。

コメント