TypeScriptでゲームプログラム7「クラス化でコード整理」

コードを整理する

前回は、ひとまず完成したということで、全ソースコードを載せました。

しかし、このコードは正直綺麗なものではないと思います。そこで、コードを整理していきましょう。ひとまずは、球やラケット、Areaなどをクラス化して、コードを整理します。

四角形クラスを追加する

ラケットやArea(弾が動き回る範囲)は四角形(長方形)なので、まずはそれを扱うクラスを作りました。

/**
 * 四角形を表すクラス
 */
class Rect {
    /**
     * 四角形の中心のX座標
     */
    public X: number;
    /**
     * 四角形の中心のY座標
     */
    public Y: number;
    /**
     * 四角形の横幅
     */
    public Width: number;
    /**
     * 四角形の高さ
     */
    public Height: number;

    /**
     * コンストラクタ
     * 点(X1,Y1)と点(X2,Y2)を対角線上の頂点とする四角形を作る。
     * @param x1 1つ目の頂点のX座標
     * @param y1 1つ目の頂点のY座標
     * @param x2 2つ目の頂点のX座標
     * @param y2 2つ目の頂点のY座標
     */
    constructor(x1: number, y1: number, x2: number, y2: number) {
        this.X = (x1 + x2) / 2;
        this.Y = (y1 + y2) / 2;
        this.Width = Math.abs(x2 - x1);
        this.Height = Math.abs(y2 - y1);
    }

    /**
     * 四角形の左辺のX座標
     */
    public get Left() { return this.X - this.Width / 2; }

    /**
     * 四角形の右辺のX座標
     */
    public get Right() { return this.X + this.Width / 2; }

    /**
     * 四角形の上辺のY座標
     */
    public get Top() { return this.Y - this.Height / 2; }

    /**
     * 四角形の下辺のY座標
     */
    public get Bottom() { return this.Y + this.Height / 2; }
}

内部的には長方形の中心座標を(X,Y)として持ち、幅と高さを(Width,Height)として持つことにします。

四角形の座標として、今回のように中心座標を保持する形だけでなく、左上座標を保持する形も考えられます。(実際、前のコードのAreaの(X,Y)は左上座標である。)

しかしラケットや弾を中心座標で管理してきたので、Areaの(X,Y)も中心座標になるように統一していくことにしました。

それ以外は、よくある実装なので特に説明する必要はないでしょう。

AreaをRectで宣言する


/**
 * 球が移動できる範囲
 */
let Area = new Rect(10, 10, 310, 310);

/**
 * ゲーム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) {

            // Areaの描画以外のコードは省略している

            // Areaの範囲を白で描画
            context.strokeStyle = "white";
            context.strokeRect(Area.Left, Area.Top, Area.Width, Area.Height);

            //旧コードだと下のようになっていた(比較のための記載している)
            //context.strokeRect(Area.X, Area.Y, Area.Width, Area.Height);
        }
    }
}

AreaをRectクラスを使って記述するとこうなります。

球やラケットの描画や衝突処理などでもArea.XやArea.Yを参照していたので他にも修正点はありますがが、同様の修正になるので説明は省略します。

ラケットをクラス化する

次にラケットをクラス化します。ラケットは四角なので内部的にRectの形で座標などを保持します。

/**
 * ラケットを表すクラス
 */
class Racket {
    /**
     * ラケット本体の四角
     */
    private _body: Rect;

    /**
     * コンストラクタ
     */
    constructor() {
        this._body = new Rect(0, 0, 40, 10);
    }

    /**
     * ゲーム開始時の初期化処理
     */
    public Init() {
        // X座標:初期値はAreaの横幅の真ん中
        this._body.X = Area.X;
        // Y座標:初期値はAreaの下辺からラケットの縦幅の半分だけ上
        this._body.Y = Area.Bottom - this._body.Height / 2
        // 幅は40
        this._body.Width = 40;
    }

    /**
     * 状態更新
     */
    public Update() {
        let left = Area.Left + this._body.Width / 2;
        let right = Area.Right - this._body.Width / 2;

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

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

        // ラケットの幅をScoreに応じて短くする。
        let width = 40 - 2 * Math.floor(Score / 20);
        if (width < 10) {
            width = 10;
        }
        this._body.Width = width;
    }

    /**
     * CANVASへ描画する
     * @param context 
     */
    public DrawCanvas(context: CanvasRenderingContext2D) {
        context.fillStyle = "yellowgreen";
        context.fillRect(this._body.Left, this._body.Top, this._body.Width, this._body.Height);
    }
}

クラス化する前のコードでは、ラケットの状態の初期化はInitGameSituation()の中で、CANVASに描画する処理はDrawGameCanvas()の中で他の(弾やAreaの)処理と一緒に行なっていました。それらの処理はクラスメソッドとして行うように変更しています。

Update()は以前のUpdateRacket()の処理をほぼそのまま使っています。(Racket.Widthだったのがthis._body.Widthに変わっているといった違いはもちろんあります。)

ただし、AreaやScoreなどのグローバル変数をじかに参照するのはあまり綺麗ないコードではないでしょう。このあたりもゆくゆくは整理したいところです。

円クラスを追加する

次に球をクラス化したい…のですが、その前に円を表すクラスを追加します。

/**
 * 円を表すクラス
 */
class Circle {
    /**
    * 円の中心のX座標
    */
    public X: number;
    /**
     * 円の中心のY座標
     */
    public Y: number;
    /**
     * 円の半径
     */
    public Radius: number;

    /**
     * コンストラクタ
     * 点(x,y)を中心とする半径Rの円を作る。
     * @param x 中心のX座標
     * @param y 中心のY座標
     * @param r 中心の半径
     */
    constructor(x: number, y: number, r: number) {
        this.X = x;
        this.Y = y;
        this.Radius = Math.abs(r);
    }

    /**
     * 円の最も元も左のX座標
     */
    public get Left() { return this.X - this.Radius; }

    /**
     * 円の最も右のX座標
     */
    public get Right() { return this.X + this.Radius; }

    /**
     * 円の最も上のY座標
     */
    public get Top() { return this.Y - this.Radius; }

    /**
     * 円の最も下のY座標
     */
    public get Bottom() { return this.Y + this.Radius; }
}

今回はWidthやHeightは作っていませんが、今後必要なら追加します。

次は球のクラス化ですが、コードの修正量が多くなるので、今回はここまで。

コメント