ダブルバッファリングによる内容の上書きとsystem("cls")の画面クリアから再描画の違いについて
テトリミノが動くとコンソール上にすさまじいちらつきがあったのでこれを改良するために発見したことを忘れないようにまとめておきたいと思います。同じくこの壁にぶつかった駆け出しプログラマーの助けになればなぁと思います(笑)
大事なポイントは画面を上書きすることと画面をクリアすることの違いです。
system("cls")は画面をクリアすることに該当します。
ちらつきを抑えるためには画面を上書きすることが重要です。なので今回はダブルバッファリングを用いて下記のコード
printf("\033[H");
がとても重要になります。
これはとてもシンプルでカーソルを画面左上(スタート位置)に移動するというものです。
プログラムは単純にスタート位置からゴール位置まで飛ばすことなく値を出力していきます。
tetris.c
void DrawScreen() {
// バックバッファをクリア
memset(backBuffer, 0, sizeof backBuffer);
// 枠の描画
for (int y = 0; y < FIELD_HIGHT + 2; y++) {
backBuffer[y][0] = 1; // 左枠
backBuffer[y][FIELD_WIDTH + 1] = 1; // 右枠
}
for (int x = 0; x < FIELD_WIDTH + 2; x++) {
backBuffer[FIELD_HIGHT + 1][x] = 1; // 下枠
}
// フィールドの描画
// テトリミノが一番下に来たときブロックを置いた場所(field配列)に1を入れる
for (int y = 0; y < FIELD_HIGHT; y++) {
for (int x = 0; x < FIELD_WIDTH; x++) {
if (field[y][x]==1) {
backBuffer[y + 1][x + 1] = 1;
}
}
}
// テトリミノの描画
for (int y = 0; y < mino.shape.hight; y++) {
for (int x = 0; x < mino.shape.width; x++) {
if (mino.shape.patterm[y][x]==1) {
backBuffer[mino.y + y + 1][mino.x + x + 1] = 1;
}
}
}
// バックバッファの内容をコンソールに出力
for (int y = 0; y < FIELD_HIGHT + 2; y++) {
for (int x = 0; x < FIELD_WIDTH + 2; x++) {
if (backBuffer[y][x]==1)
printf("■");
else printf(" ");
}
printf("\n");
}
}
ダブルバッファリングで内容を保存していきます。
memset(backBuffer, 0, sizeof backBuffer);
保存した内容を一度初期化する、とても重要です。
int main() {
// フィールドを初期化
initializeField();
// キーボード入力を待つループ
while (1) {
if (_kbhit()) {
switch (_getch()) {
case 'a':
selectedTetrimino.currentX--;
break;
case 'd':
selectedTetrimino.currentX++;
break;
case 's':
selectedTetrimino.currentY++;
break;
}
// カーソルを左上に準備
printf("\033[H");
DrawScreen();
}
}
カーソルを左上に持っていきbakBuffer配列で保存した内容を順にスタート(左上)から上書きしていくという流れです。この流れがとても重要になります。
printf("\033[H");
これを書いていない場合、コンソール上ではおびただしい量のテトリスが描画され続けます。
また配列には全角文字の"■"は入らないので条件分岐で出力することもポイントです。