« ノベルゲーム風に一文字ずつ表示する | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/SDL版】 »

2011年11月19日 (土)

ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】

ノベルゲーム風に一文字ずつ表示する』を、UTF-8文字列を読み込むようにしてみた。

いまのところゲームプログラムでShiftJIS以外は考えなくて良い気がするけれど、ふとSDLで作るなら必要だなあと思ってしまったので実験的に作ってみた。

将来必要になるかもしれないし。

そんなわけだが、今回はDXライブラリ版。

Visual C++のC/C++標準ライブラリはUTF-8に対応していないので、Win32 APIを使ってUTF-8のマルチバイト文字をワイド文字に変換。UNICODE版のAPIが使えるので内部ではワイド文字で持つようにした。

#include "DxLib.h"
#include "VGLib.hpp"

/*
 *  禁則文字テーブルの検索
 */
static bool isProhibitLineBreakW(const wchar_t *char_table, wchar_t wc)
{
	while (*char_table != L'\0') {
		if (*char_table == wc) {
			return true;
		}
		char_table++;
	}
	return false;
}
/*
 *  行頭禁則文字かどうか
 */
static bool isProhibitLineBreakBeforeW(wchar_t *wc)
{
	static const wchar_t *char_table = {
		L"、。,.・:;?!゛゜"
		L"’”)〕]}〉》」』】"
		L"ーぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶヽヾゝゞ〃々"
		L"―‐~-="
		L"°′″℃¢%"
		L"、。,.・:;?!゙゚"
		L")]}」"
		L"ーァィゥェォッャュョ"
		L"-="
		L"%"
	};
	return isProhibitLineBreakW(char_table, *wc);
}
/*
 *  行末禁則文字かどうか
 */
static bool isProhibitLineBreakAfterW(wchar_t *wc)
{
	static const wchar_t *char_table = {
		L"‘“(〔[{〈《「『【¥$"
		L"([{「\\$"
	};
	return isProhibitLineBreakW(char_table, *wc);
}

struct WrapText
{
	static const int BUF_SIZE = 4*1024;
	static const int LINE_NUM = 64;
	wchar_t buf[BUF_SIZE];
	int line_top[LINE_NUM];
	int line_len[LINE_NUM];
	int line_num;
	int hold_len; // 送り出し保留分文字列長
	int hold_cnt; // 送り出し保留分確定猶予文字数カウンタ
	int curr_idx; // 逐次描画対象行のインデックス
	int curr_len; // 逐次描画対象行の描画済み文字列長さ

	void clear();
	void update();
	void render(int width, int lineheight);
};

static const char *pStream;

void WrapText::clear(void)
{
	line_num = 0;
	line_top[0] = 0;
	line_len[0] = 0;
	hold_len = 0;
	hold_cnt = 0;
	curr_idx = 0;
	curr_len = 0;
}

void WrapText::update()
{
	if (line_len[curr_idx] > curr_len) {
		/* 逐次描画対象行に文字列が残っているとき */
		// 一文字ずつ描画対象を伸ばしていく
		++curr_len;
	}
	else {
		/* 逐次描画対象行に文字列が残っていないとき */
		if (curr_idx < line_num - 1) {
			// 次の行に逐次描画対象を移す
			++curr_idx;
			curr_len = 0;
		}
	}
}

void WrapText::render(int width, int lineheight)
{
	char mb[MB_LEN_MAX];
	int mb_len;
	wchar_t wc;

	int ColorWhite = GetColor(255,255,255);

	while ((mb[0] = *pStream) != '\0')
	{
		pStream++;

		if (mb[0] == '\r') {
			/* 改行コードで強制改行 */
			int n = line_num;
			line_len[n] += hold_len;
			hold_len = 0;
			line_num++;
			line_top[line_num] = line_top[n] + line_len[n];
			line_len[line_num] = 0;
			continue;
		}

		mb_len = 1;
		while (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mb, mb_len, &wc, 1) < 1) {
			if (*pStream == '\0') {
				mb_len = 0;
				break;
			}
			mb[mb_len] = *(pStream++);
			mb_len++;
		}
		if (mb_len == 0) break;

		int n = line_num;
		buf[line_top[n]+line_len[n]+hold_len] = wc;
		if (isProhibitLineBreakBeforeW(&wc)) {
			/* 行頭禁則文字のとき */
			// 確定猶予カウントをリセット
			hold_cnt = 0;
		}
		else if (isProhibitLineBreakAfterW(&wc)) {
			/* 行末禁則文字のとき */
			if (hold_cnt == 0) {
				// 猶予なしなら保留分を確定
				line_len[n] += hold_len;
				hold_len = 0;
			}
			// 続く1文字を確定猶予
			hold_cnt = 1;
		}
		else {
			/* 禁則文字以外のとき */
			if (hold_cnt > 0) {
				// 猶予カウンタ分は保留
				hold_cnt--;
			}
			else {
				// 猶予なしなら保留分を確定
				line_len[n] += hold_len;
				hold_len = 0;
			}
		}
		int w = GetDrawStringWidth(&buf[line_top[n]], line_len[n]+hold_len+1);
		if (w > width) {
			if (line_len[n] == 0) {
				// 1行すべて保留分のとき確定する
				line_len[n] += hold_len;
				hold_len = 0;
			}
			line_num++;
			line_top[line_num] = line_top[n] + line_len[n];
			line_len[line_num] = 0;
		}
		hold_len += 1;
	}
	if (hold_len > 0) {
		// 保留分が残っていたら確定する
		line_len[line_num] += hold_len;
		hold_len = 0;
		hold_cnt = 0;
	}
	if (line_len[line_num] > 0) {
		// 最終行の〆
		int n = line_num;
		line_num++;
		line_top[line_num] = line_top[n] + line_len[n];
		line_len[line_num] = 0;
	}

	/*
	 *  描画
	 */
	for (int i=0; i<=curr_idx; ++i) { // 逐次描画対象行まで
		// 逐次描画対象より前の行なら全体を描画
		int len = i < curr_idx ? line_len[i] : curr_len;
		wchar_t c = buf[line_top[i] + len];
		buf[line_top[i] + len] = L'\0';
		DrawString(0, lineheight*i, &buf[line_top[i]], ColorWhite);
		buf[line_top[i] + len] = c;
	}
}

struct Main : public VGCanvas
{
	WrapText wt;
	int width;

	Main() : VGCanvas() {}
	void init();
	void frameUpdate(int skiiped);
	void frameRender(void);
} canvas;

VGCanvas *VGLibInit()
{
	ChangeWindowMode(TRUE);
	return &canvas;
}

#define FONT_SIZE 20
#define MIN_WIDTH (FONT_SIZE * 10)

static const char *SampleText =
	"\xE5\x8F\xA3\xE8\xAA\x9E\xE8\xA1\xA8\xE7\x8F\xBE\xE3\x81\xAF\xE7\xA6\x81"
	"\xE5\x89\x87\xE5\x87\xA6\xE7\x90\x86\xE3\x81\x8C\xE3\x82\x81\xE3\x82\x93"
	"\xE3\x81\xA9\xE3\x81\x86\xE3\x81\xAA\xE3\x81\xAE\xE3\x81\xA0\xE3\x80\x82\r"
	"\xE3\x80\x8C\xE3\x82\xAE\xE3\x83\xA3\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC"
	"\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\x83"
	"\xEF\xBC\x81\r"
	"\xE3\x80\x80\xE3\x82\x84\xE3\x81\xA3\xE3\x81\xA1\xE3\x82\x83\xE3\x81\xA3"
	"\xE3\x81\x9F\xE3\x80\x82\xE3\x80\x8D\r"
	"\xE3\x81\xA8\xE3\x81\x84\xE3\x81\x86\xE5\x8F\xB0\xE8\xA9\x9E\xE3\x81\xAE"
	"\xE5\xA0\xB4\xE5\x90\x88\xE3\x80\x81\xE5\xB9\x85\xE3\x81\x8C\xE7\x8B\xAD"
	"\xE3\x81\x84\xE3\x81\xA8\xE7\xA0\xB4\xE7\xB6\xBB\xE3\x81\x99\xE3\x82\x8B"
	"\xE3\x81\x8C\xE4\xBB\x95\xE6\xA7\x98\xE3\x81\x8C\xE3\x81\xAA\xE3\x81\x84"
	"\xE3\x80\x82\r\r"
	"\xE3\x81\x9D\xE3\x81\x93\xE3\x81\xAF\xE9\x81\x8B\xE7\x94\xA8\xE3\x81\xA7"
	"\xE3\x82\xAB\xE3\x83\x90\xE3\x83\xBC\xE3\x81\xA0\xE3\x80\x82\r";

void Main::init()
{
	SetFontThickness(0);
	SetFontSize(FONT_SIZE);

	width = MIN_WIDTH;

	wt.clear();
	pStream = SampleText;
}

void Main::frameUpdate(int)
{
	int Sx, Sy, Cb;
	GetScreenState(&Sx, &Sy, &Cb);

	int width_old = width;
	if (CheckHitKey(KEY_INPUT_LEFT)) {
		width -= 2;
		if (width < MIN_WIDTH) {
			width = MIN_WIDTH;
		}
	}
	if (CheckHitKey(KEY_INPUT_RIGHT)) {
		width += 2;
		if (width > Sx) {
			width = Sx;
		}
	}
	if (width != width_old) {
		wt.clear();
		pStream = SampleText;
	}
	wt.update();
}

void Main::frameRender(void)
{
	int Sx, Sy, Cb;
	GetScreenState(&Sx, &Sy, &Cb);
	ClearDrawScreen();
	DrawLine(width, 0, width, Sy, GetColor(255, 0, 0));
	wt.render(width, FONT_SIZE);
}

|

« ノベルゲーム風に一文字ずつ表示する | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/SDL版】 »

C/C++」カテゴリの記事

DXライブラリ」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« ノベルゲーム風に一文字ずつ表示する | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/SDL版】 »