一緒にテトリス作ってみましょう!(6)
こんにちは。
今日も一日ぐずついた天気でした。
しかし、暖かくなってきたのが嬉しくて、窓の外の雨混じりの曇り空を眺めながら、こういう日もいいものだなぁ、と思ってました。
今日はゲーム画面の外枠を表示してみましょう。
実はまだ全体像をどのようにするか決めてないのですが、Game 全体を仕切ってくれる class Game があって、class GameField にはゲーム画面の表示だけを担当してもらおうと考えてます。
(社会生活と同様に、それぞれ担当してくれる係りを作っていくイメージでプログラムを作っていきましょう。)
今回作るテトリスには簡単な機能しか実装しませんので、全体のプログラムは小さくなる予定です。
そのため、プログラムは全部 MainPage.xaml.cs に書いちゃおうと思ってます。
まだ、class Game は、下のことしか決めてませんw。
上図の const は、「この値は変更しない」という意味です。変更しない値には const を付けておくと良いですよ。
Px_BLOCK は、1つのブロックのピクセル数を表しています。自分の好きな値に変更してもらって構いません。
Px_INNER_BLOCK は、ブロックの色を塗るピクセル数です。色を塗る範囲を少し小さくして、隣のブロックとの境界を見やすくします。
次に、昨日作った class GameField の Draw()(←かっこを付けることで、関数であることを強調してます)を改良します。
少し長く見えますが、内容はすごく単純です。
コピペしやすいように、下の方にコードを貼り付けておきますね。
PCS_COLOR_FIELD は、ブロック全部の個数ですが、「左右両端の壁」と「下端の壁」を個数に入れています。
_colorField は、各ブロックの色を記録する配列で、左上から 0, 1, 2, 3, … と右下まで連番で考えています。
(配列と for ループは簡単なので説明を省略しますね。)
L35, 36 でフィールド全体を黒で塗りつぶしているので、L43 でブロックが黒色だったら色を塗るのを省略してます。
(L は行番号を表してます。)
canvas.FillColor = _colorField[0]; // x = 1, y = 1
canvas.FillRectangle(1, 1, 8, 8)
canvas.FillColor = _colorField[1]; // x = 11
canvas.FillRectangle(11, 1, 8, 8)
canvas.FillColor = _colorField[2]; // x = 21
canvas.FillRectangle(21, 1, 8, 8)
...
canvas.FillColor = _colorField[11]; // x = 111
canvas.FillRectangle(111, 1, 8, 8)
canvas.FillColor = _colorField[12]; // x = 1, y = 11
canvas.FillRectangle(1, 11, 8, 8)
canvas.FillColor = _colorField[13]; // x = 11
canvas.FillRectangle(11, 11, 8, 8)
canvas.FillColor = _colorField[14]; // x = 21
canvas.FillRectangle(21, 11, 8, 8)
...
canvas.FillColor = _colorField[23]; // x = 111
canvas.FillRectangle(111, 11, 8, 8)
canvas.FillColor = _colorField[24]; // x = 1, y = 21
canvas.FillRectangle(1, 21, 8, 8)
canvas.FillColor = _colorField[25]; // x = 11
canvas.FillRectangle(11, 21, 8, 8)
canvas.FillColor = _colorField[26]; // x = 21
canvas.FillRectangle(21, 21, 8, 8)
...
canvas.FillColor = _colorField[35]; // x = 111
canvas.FillRectangle(111, 21, 8, 8)
canvas.FillColor = _colorField[36]; // x = 1. y =31
canvas.FillRectangle(1, 31, 8, 8)
以下繰り返し
Draw() を作ったので、ブロック毎に色を塗れるようになったのですが、この状態で F5 キーを押すと、プログラムがクラッシュします。
それは、_colorField の配列をちゃんと初期化してないからです。
class の初期化が必要な場合、「コンストラクタ」を利用するのが一般的です。
コンストラクタとは、class が実際に利用されるときに最初に呼び出されるものです。class と同名の関数がコンストラクタとなるので、下図の関数を作っておけばオッケーです。
まず、_colorField を全部黒色にしてから、壁の左右両端と下端を灰色にしています。
このコンストラクタを付け加えてから F5 キーを押すと画面表示がうまくいくと思いますので、是非実行してみてください。
public class GameField : IDrawable
{
const int PCS_COLUMN = 10; // 横のブロック数です
const int PCS_ROW = 20; // 縦のブロック数です
const int PCS_COLOR_FIELD = (PCS_COLUMN + 2) * (PCS_ROW + 1);
Color[] _colorField = new Color[PCS_COLOR_FIELD];
// GameField の横のピクセル数です
const int Px_WIDTH_FIELD = Game.Px_BLOCK * (PCS_COLUMN + 2);
// GameField の縦のピクセル数です
const int Px_HEIGHT_FIELD = Game.Px_BLOCK * (PCS_ROW + 1);
public GameField()
{
for (int i = 0; i < PCS_COLOR_FIELD; i++)
{ _colorField[i] = Colors.Black; }
for (int idx = 0; idx < PCS_COLOR_FIELD; idx += PCS_COLUMN + 2)
{
_colorField[idx] = Colors.LightGray;
_colorField[idx + PCS_COLUMN + 1] = Colors.LightGray;
}
for (int idx = PCS_COLOR_FIELD - 2, cnt = PCS_COLUMN; cnt > 0; idx--, cnt--)
{
_colorField[idx] = Colors.LightGray;
}
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = Colors.Black;
canvas.FillRectangle(0, 0, Px_WIDTH_FIELD, Px_HEIGHT_FIELD);
int idx = 0;
for (int y = 1; y < Px_HEIGHT_FIELD; y += Game.Px_BLOCK)
{
for (int x = 1; x < Px_WIDTH_FIELD; x += Game.Px_BLOCK, idx++)
{
if (_colorField[idx] == Colors.Black) { continue; }
canvas.FillColor = _colorField[idx];
canvas.FillRectangle(x, y, Game.Px_INNER_BLOCK, Game.Px_INNER_BLOCK);
}
}
}
}
相変わらず画面の調整はできていませんが、下のように表示されると思います。
class Game の Px_BLOCK の値を変更すると、ブロックが大きくなったり小さくなったりするので、よければ試してみてくださいね。
少しゲーム画面らしくなったので嬉しいです。
このままのプログラムで、Android, iOS, macOS でも動くんだ、と思うとなんだか楽しいですよね。
それでは。