ブロックくずしを作る5「ブロック」

ブロックを追加する

ブロックくずしの重要なゲーム部品である「ブロック」を追加しましょう。

ブロックはキャンバスに描画し、球と衝突し、破壊されます。そのため、IGameCanvasPartを継承したクラスとして定義するべきでしょう。

import { IGameCanvasPart } from './game/GameCanvasPart';
import { Rectangle } from './shape/Rectangle';
import { GameMng } from './GameMng';
import { GameParameter } from './GameParameter';
/**
 * ブロックを表すクラス
 */
export class Brick implements IGameCanvasPart {
    /**
     * ブロック本体の四角
     */
    private _body = new Rectangle(0, 0,
        GameParameter.Brick.Width, GameParameter.Brick.Height);

    /**
     * ブロック本体の四角
     */
    public get Rect() { return this._body; }

    /**
     * ブロックの色
     */
    public Color: string = "cyan";

    /**
     * コンストラクタ
     */
    constructor(private _game: GameMng) {
    }

    Update(): void {
    }

    DrawCanvas(context: CanvasRenderingContext2D): void {
        context.fillStyle = this.Color;
        context.fillRect(this._body.Left, this._body.Top, this._body.Width, this._body.Height);
    }
}

このコードはまだ途中です。とりあえず、ブロックをキャンバスに表示するための機能までを作りました。

ブロックの形状は長方形なので、基本的にラケットのクラスと似たコードになります。

ブロックのサイズ

ラケットとの違いはブロックのサイズです。今後、調整も必要でしょうが、ラケットより横幅が小さく、高さは大きくなるようにしたいと考えています。具体的な数値はGameParameterで定義しています。

export const GameParameter = {
    /**
     * ブロックに関連するパラメーター
     */
    Brick: {
        /**
         * ブロックの横幅
         */
        Width: 28,
        /**
         * ブロックの縦幅
         */
        Height: 14,
    } as const,
} as const;

ブロックの色

色も違います。ラケットの色は"yellowgreen"で、GameParameterに定義していました。ブロックの色はdefault値として"cyan"にしていますが、他の色に変更できるようにしています。

これは、よくあるブロックくずしでは色の違うブロックがあって、見た目がカラフルになっていることが多いからです。また、色の違いでブロックの性能(まだ具体的に決めてはいませんが、例えば壊したときの得点、耐久力など)の違いを表すこともできます。こうした理由から、色は変更可能なクラスプロパティにしました。

Update()

Update()メソッドは、ゲームのフレームごとの状態を変化するメソッドですが、今のところブロックは(球やラケットのような)状態変化がないので空にしています。

今後、球と衝突して壊される機能を追加することになりますが、それはこのクラスのUpdate()で行うのではなく、球のUpdate()の衝突判定処理で行うことになるはずです。

もし、ゲームのフレームごとに、揺れるように動くブロックとか、下に落ちてくるブロックとかを作るなら、Update()に中身が入るでしょう。ただそれは基本的なブロックというよりも特殊なブロックという感じがするので、おそらくそのときはBrickを継承したSwayBrickとかFallBrickなどを作り、継承クラスのUpdate()をオーバーライトすることになるでしょう。

ブロックをGameMngに追加する

ブロックのクラスができましたので、GameMngのプロパティに追加し、ゲームエリアに表示してみましょう。

import { GameLoop } from './game/GameLoop';
import { GameCanvas } from './game/GameCanvas';
import { GameCanvasPart } from './game/GameCanvasPart';
import { Rectangle } from './shape/Rectangle';
import { GameParameter } from './GameParameter';
import { Racket } from './Racket';
import { Ball } from './Ball';
import { Brick } from './Brick';

/**
 * ゲームの状況を表すtype
 * start - ゲーム開始
 * stop - ゲーム停止中
 * game - ゲーム実行中
 * gameover - ゲームオーバー
 */
export type GameStatus_t = 'start' | 'stop' | 'game' | 'gameover';

export class GameMng extends GameLoop {
    /**
     * ゲームの状況
     */
    public Status: GameStatus_t = "game";

    /**
     * id="gameCanvas"のCANVAS
     */
    public Canvas = new GameCanvas("gameCanvas");

    /**
     * ゲームエリア
     */
    public Area: Rectangle | undefined;

    /**
     * 球
     */
    public Ball = new Ball(this);

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

    /**
     * ブロック
     */
    public BrickList: Brick[] = [];

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

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

        // CANVAS部品を登録
        // ゲームエリア(を示す枠)
        let tmp = new GameCanvasPart();
        tmp.onDrawCanvas = context => {
            if (this.Area) {
                context.strokeStyle = "white";
                context.strokeRect(this.Area.Left, this.Area.Top, this.Area.Width, this.Area.Height);
            }
            else {
                let text = `ゲームエリアを設定できません。`;
                context.strokeStyle = "red";
                context.strokeText(text, 0, 12);
            }
        }
        this.Canvas.AddPart(tmp);
        // 球
        this.Canvas.AddPart(this.Ball);
        // ラケット
        this.Canvas.AddPart(this.Racket);
        // ブロック
        for (let i = 0; i < 15; i++) {
            let brick = new Brick(this);
            this.BrickList.push(brick);
            this.Canvas.AddPart(brick);
        }
    }

    /**
     * ゲームの状態を初期化する
     */
    public InitSituation() {
        // 球を初期化する
        this.Ball.Init();

        // ラケットを初期化する。
        this.Racket.Init();

        // ブロックを初期化する。
        let area = this.Area!;
        for (let i = 0; i < 15; i++) {
            let brick = this.BrickList[i];
            let x = i % 5;
            let y = (i - x) / 5;
            brick.Rect.X = (GameParameter.Brick.Width + 1) * (x - 2) + area.X;
            brick.Rect.Y = (GameParameter.Brick.Height + 1) * (y + 1) + area.Top;
            if (y == 0) {
                brick.Color = "magenta";
            }
        }
    }

    /**
     * ゲームキャンバスの準備ができてから行なう処理
     */
    private async _AfterReadyCanvas() {
        await this.Canvas.WaitUntil_GetHtmlElement();
        let width = this.Canvas.Width;
        let height = this.Canvas.Height;
        if (width > GameParameter.CanvasMinWidth && height > GameParameter.CanvasMinHeight) {
            this.Area = new Rectangle(20, 20, width - 20, height - 40);

            // ゲーム状態の初期化
            this.InitSituation();
        }
    }

    /**
     * Frameの値を使って、minとmaxの間の整数乱数値を得る。
     * @param min 
     * @param max 
     * @returns min以上、max以下の欄数値
     */
    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;
    }
}

ブロックリストの追加

球やラケットと1つしかありませんが、普通のブロックくずしはブロックが複数あります。そのためブロックは配列BrickListで保持することにします。(45-48行目)

ブロックの作成と登録

今回は横に5×縦に3つ=合計15個のブロックを表示することにしました。そのため15個のBrickをnewしてBrickListに登録します。同時にCanvasに登録します。(79-84行目)

ブロックの初期化

各ブロックをゲームエリア内に並べて配置するには、ゲームエリアの情報が必要です。そのため球やラケット同様に、InitSituation()メソッドの中でブロックの情報を初期化します。(97-108行目)

BrickListに登録されているブロックのうち、最初の5個(コード中のyが0になる)をゲームエリアの上辺近くの中央付近に1ピクセルずつ間を空けて並べています。そして、続く5個をその下に並べ、さらに残りの5個をその下に並べています。

また、最初の5個は色を"magenta"にしています。

ブロックの座標をbrick.Rect.Xとbrick.Rect.Yで指定していますが、これはあまり適切なコーディングではありません。本来Rectは描画や球との衝突判定のために座標を参照するためのプロパティであって、座標を変えるために使うのは行儀の悪いコードです。今後修正する必要がありますが、今は暫定的にこのようなコードにしています。

ここまでを実行する

ここまでを実際に実行すると下のようになります。

まだ、球とブロックの衝突処理を行っていません。また、球の方がブロックよりも先にCanvasに登録されているため、球を描画→ブロックを描画となっています。そのため、球がブロックの裏をすり抜けて動くように見えるはずです。

SCORE:10000
メッセージ

コメント