TypeScriptでゲームプログラム13「グローバル変数を減らす」

グローバル変数の見直し

Ball1、Rakect1、GameStatusなど、これまで多くのグローバル変数を使ってきました。グローバル変数がバラバラとあるプログラムは管理しにくくなるので、管理しやすくするために、いくつかのグループにまとめ、減らしていきます。

パラメータクラスを追加する

Area、FPS、gravityなどのグローバル変数は、変数というよりも数値を直接使うのを避けるための定数として使っています。これらをGameParameterクラスにまとめることにしました。以下にコードを示します。

/**
 * ゲームパラメーター
 */
class GameParameter {
    /**
     * FPS(Frame per second)
     */
    static readonly FPS = 20;

    /**
     * 球の移動計算で使う、重力加速度
     */
    static readonly gravity = 0.25;

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

もちろん、これまで"Area"などとしていたところは、すべて”GameParameter.Area"に置き換える必要があります。(単純な置換のため修正後のコードは省略します。)

また、パラメーターは変化しないので、すべてstatic readonlyで宣言しました。こうすることで、new GameParameter()としなくても使うことができます。

ゲーム全体の管理クラスを追加する

ゲーム全体を管理するクラスとしてGameMngクラスを追加しました。

このクラスがトップレベルのクラスであり、その他のクラスやグローバルな変数はこのクラスが管理します。

まずは、ScoreやGameStatusのような変数をグローバル変数からGameMngに移動します。

/**
 * ゲーム全体を管理するクラス
 */
class GameMng {
    /**
     * ゲームの状況
     */
    public Status: GameStatus_t = "start";

    /**
     * ゲームのスコア(点数)
     */
    public Score = 0;

    /**
     * コンストラクタ
     */
    constructor() {

    }
}

const Game = new GameMng();

これまでグローバル変数だったScoreを参照したり、変更したりしている部分は、Game.Scoreを参照するように変更します。同様にGameStatusはGame.Statusとなります。(簡単な置換のため、コードは省略します。)

球とラケットをGameMngに移動する

球はBall1、ラケットはRacket1というグローバル変数でした。

それらをGameMngのメンバに移動します。

class GameMng {
    /**
     * 球
     */
    public Ball = new Ball();

    /**
     * ラケット
     */
    public Racket = new Racket();
}

これらも、Ball1 → Game.Ball、Racket1 → Game.Racket に変わります。

ゲームの初期化処理をGameMngに移動する

ゲームを開始するときは、球やラケットの座標、点数など初期化する必要があり、その処理をInitGameSituation()という関数内で行っています。

球もラケットも点数もすべてGameMngに移動したので、初期化処理もGameMngのメソッドに移動します。

class GameMng {
    /**
     * ゲームの状態を初期化する
     */
    public InitSituation() {
        // ボールの座標、速度を初期化する。
        this.Ball.Init();

        // ラケットの座標と幅を初期化する。
        this.Racket.Init();

        // ゲームスコアのクリア
        this.Score = 0;
    }
}

ゲームキャンバス、ボタン1、メッセージをGameMngに移動する

ゲームキャンバスなどもGameMngに移動します。

class GameMng {
    /**
     * id="game"のCANVAS
     */
    public Canvas = new GameCanvasMng();

    /**
     * id="button1"のBUTTON
     */
    public Button1 = new Button1Mng();

    /**
     * id="messgae"のDIV
     */
    public Message = new MessageMng();

    /**
     * コンストラクタ
     */
    constructor() {
        this._CanvasAddParts();
    }

    /**
     * Canvas部品を登録する
     */
    private _CanvasAddParts() {
        let background = new class implements IGameCanvasPart {
            Update(): void { }
            DrawCanvas(context: CanvasRenderingContext2D): void {
                // 実際の描画処理
                // 背景を黒に
                context.fillStyle = "black";
                context.fillRect(0, 0, context.canvas.width, context.canvas.height);

                // GameParameter.Areaの範囲を白で描画
                context.strokeStyle = "white";
                let area = GameParameter.Area;
                context.strokeRect(area.Left, area.Top, area.Width, area.Height);
            }
        };
        this.Canvas.AddPart(background);
        this.Canvas.AddPart(this.Racket);
        this.Canvas.AddPart(this.Ball);
    }

Game.Canvasには、BallやRacketを登録する必要があるので、その処理もconstruct時に行ないます。

GameLoopをGameMngに継承する

まだグローバル変数として残っているのは、GameLoopです。

GameLoopは他とは異なり、GameMngに継承しました。

これまで作ってきたゲームは、「一定時間毎に、状態更新をして、表示する」という構造が基本であり、そのためのクラスがGameLoopです。このゲームプログラムの背骨や核といってよいでしょう。

ボタンやCANVASとはレベルが異なるため、メンバとして持つのではなくGameMngに継承するほうが適切であると判断しました。

class GameMng extends GameLoop {
    /**
     * コンストラクタ
     */
    constructor() {
        super();
        this.FPS = GameParameter.FPS;

        // 部品を登録
        this.AddPart(this.Canvas);
        this.AddPart(this.Message);
        this.AddPart(this.Button1);

        // CANVAS部品を登録
        this._CanvasAddParts();
    }
}

const Game = new GameMng();
// ゲーム状態の初期化
Game.InitSituation();
// ゲーム開始
Game.Start();

CANVASなどGameLoopに登録する処理は、this.AddPart()で行うようになりました。

Random()をGameMngに移動する

最後に変数ではありませんが、GameLoopクラスのFrameCounterを利用して乱数を得る関数Random()もGameMngに移動します。

class GameMng extends GameLoop {
    /**
     * Frameの値を使って、minとmaxの間の整数乱数値を得る。
     * @param min 
     * @param max 
     * @returns 
     */
    public Random(min: number, max: number) {
        let frame = this.Frame ?? 0;
        if (max < min) {
            console.log('error: max:{max} min:{min}')
        }
        let diff = max - min + 1;
        return (frame % diff) + min;
    }
}

コメント