10
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

コンソールでダブルバッファみたいなことをしてみる

Posted at

ダブルバッファとは

 画面やグラフィックを描画する際に、描画領域と同じサイズのバッファ領域をメモリ上に用意してこれに描画処理を行なう手法。
 描画過程が見えてしまったり、再描画に伴って画面がちらつくのを防ぐ手法で、バッファ領域に描画が完成したらいっぺんに画面に転送する。画面には常に描画が完了した画像が表示されるため、変化が滑らかに表される。

イメージ

double_buffer_image_0.png
表を表示している間、裏の方に書き込み、書き込みが終わると表と裏を入れ替える。
それを繰り返す感じです。

コード

多分、動きます。

ConsoleHandle.h
# ifndef _CONSOLE_HANDLE_H_
# define _CONSOLE_HANDLE_H_
# include <Windows.h>
# include<string.h>
# include<stdio.h>

typedef struct __CONSOLE {
	void(*swapConsoleHandle)();	//入れ替え
	void(*createConsoleHandle)();	//初期化
	void(*deleteConsoleHandle)();	//後始末
	void(*print)(const char str[]);	//文字を出力
	void(*clearScreen)();	        //画面をクリア
} *CONSOLE;	

CONSOLE getInstance();

# endif //_CONSOLE_HANDLE_H_
ConsoleHandle.cpp
# include "ConsoleHandle.h"


const SHORT BLACK = 0;
const SHORT WHITE = 15;
const int COLORS = 16;

static CONSOLE Console;
static HANDLE consoleHandle1;
static HANDLE consoleHandle2;
static bool isSwap;
static CONSOLE_CURSOR_INFO cursorInfo;
static CONSOLE_SCREEN_BUFFER_INFO screenInfo;
static CHAR_INFO* buffer;

static void _swapConsoleHandle();
static void _createConsoleHandle();
static void _deleteConsoleHandle();
static void _print(const char str[]);
static void _clearScreen();
static HANDLE getHandle();


CONSOLE getInstance() {	
	if (!Console) {
		Console = (CONSOLE)malloc(sizeof(*Console));
		Console->swapConsoleHandle = _swapConsoleHandle;
		Console->createConsoleHandle = _createConsoleHandle;
		Console->deleteConsoleHandle = _deleteConsoleHandle;
		Console->print = _print;
		Console->clearScreen = _clearScreen;
	}
	return Console;
}
static void _swapConsoleHandle() {
	::SetConsoleActiveScreenBuffer(getHandle());
	isSwap = !isSwap;
}
static void _createConsoleHandle() {
	//コンソールハンドルを作る
	consoleHandle1 = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,
		0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	consoleHandle2 = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,
		0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	
	cursorInfo.dwSize = 1;
	cursorInfo.bVisible = FALSE;
	::SetConsoleCursorInfo(consoleHandle1, &cursorInfo);
	::SetConsoleCursorInfo(consoleHandle2, &cursorInfo);

	::GetConsoleScreenBufferInfo(getHandle(), &screenInfo);

	isSwap = false;			//取り敢えずfalseで初期化

	
	buffer = (CHAR_INFO*)malloc(sizeof(CHAR_INFO) * screenInfo.dwSize.Y * screenInfo.dwSize.X); //バッファーを確保
	//バッファーを初期化
	for (int y = 0; y < screenInfo.dwSize.Y;++y) {
		for (int x = 0; x < screenInfo.dwSize.X;++x) {
			buffer[y * (int)screenInfo.dwSize.X + x].Attributes = WHITE + (BLACK << 4);
			buffer[y * (int)screenInfo.dwSize.X + x].Char.UnicodeChar = ' ';
		}
	}
}
static void _deleteConsoleHandle() {

	free(buffer);
	::CloseHandle(consoleHandle2);
	::CloseHandle(consoleHandle1);
	free(Console);
}
static HANDLE getHandle() {

	return isSwap ? consoleHandle1 : consoleHandle2;
}

static void _print(const char str[]) {
	
	COORD coord = { 0, 0 };																//書き込みを開始する位置 x:0y:0に設定
	COORD size = { screenInfo.dwSize.X,screenInfo.dwSize.Y };							//サイズ
	SMALL_RECT rect = { coord.X, coord.Y, screenInfo.dwSize.X, screenInfo.dwSize.Y };	//書き込む箇所を矩形で指定
	int length = strlen(str);															//文字の長さ
	for (int y = 0; y < screenInfo.dwSize.Y;++y) {
		for (int x = 0; x < screenInfo.dwSize.X;++x) {
			buffer[y * (int)screenInfo.dwSize.X + x].Char.UnicodeChar = str[x % (rand() % length + 1)];
			buffer[y * (int)screenInfo.dwSize.X + x].Attributes = rand() % COLORS + (rand()% COLORS << 4);
		}
	}

	::WriteConsoleOutputA(getHandle(), buffer,size, coord, &rect);
}	

void _clearScreen() {
	DWORD	dwNumberOfCharsWritten;	// 書き込まれたセル数
	COORD	coord = { 0, 0 };		//書き込みを開始する位置 x:0y:0に設定
	::GetConsoleScreenBufferInfo(getHandle(), &screenInfo);
	// バッファ内の指定した座標から指定した数の文字セル分だけ、前景色と背景色を設定
	::FillConsoleOutputAttribute(getHandle(), 
		WHITE + (BLACK << 4),
		screenInfo.dwSize.X * screenInfo.dwSize.Y,
		coord,
		&dwNumberOfCharsWritten
		);
	// バッファ内の指定した座標から、指定した文字を指定した数だけ書き込む
	::FillConsoleOutputCharacter(
		getHandle(), 
		' ',
		screenInfo.dwSize.X * screenInfo.dwSize.Y,
		coord,
		&dwNumberOfCharsWritten
		);
}

main.cpp
# include "ConsoleHandle.h"

int main() {
	getInstance()->createConsoleHandle(); //初期化処理
	while (1) {
                getInstance()->clearScreen(); //画面のクリア
		getInstance()->print("abcdefghijk"); //文字を出力
		getInstance()->swapConsoleHandle(); //コンソールハンドルを入れ替え
	}
	getInstance()->deleteConsoleHandle();     //解放
	return 0;
}

シングルトンで実装しろと金髪のお友達に脅されたのでそれっぽく実装しました。
ConsoleHandle.cppの中のConsoleHandle1やConsoleHandle2などは他のファイルから直接アクセスすることはできないようにカプセル化しています。アクセスするときはgetInstance()からアクセスすることができます。

system("cls"),printfしてた時よりはましだと思う。

コンソールでゲーム作りたくないですね

参考サイト

10
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?