« 禁則処理付き折り返し文字列描画【ShiftJIS版/改行付き】 | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 »

2011年11月16日 (水)

ノベルゲーム風に一文字ずつ表示する

禁則処理付き折り返し文字列描画【ShiftJIS版/改行付き】』を改造して、ノベルゲーム風に一文字ずつ表示するサンプルプログラムを作ってみた。

文字列を折り返し処理したあと、バッファから一文字ずつ範囲を広げて描画している。

一気に折り返し処理すると負荷が大きいかもしれない。そんなときは一文字ずつ描画している行の一行先までを折り返し処理するようにして負荷を分散する。

折り返し処理が先行するからイベント埋め込むにはバッファの構造にひと工夫必要。

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

/*
 *  ShiftJISマルチバイト文字の1バイト目かどうか
 */
static bool isLeadShiftJIS(unsigned char c);

/*
 *  行頭禁則文字かどうか
 */
static bool isProhibitLineBreakBeforeA(char mb[]);
/*
 *  行末禁則文字かどうか
 */
static bool isProhibitLineBreakAfterA(char mb[]);

struct WrapText
{
	static const int BUF_SIZE = 4*1024;
	static const int LINE_NUM = 64;
	char 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()
{
	char mb;
	if (line_len[curr_idx] > curr_len) {
		/* 逐次描画対象行に文字列が残っているとき */
		// 一文字ずつ描画対象を伸ばしていく
		mb = buf[line_top[curr_idx] + curr_len];
		++curr_len;
		if (isLeadShiftJIS(mb)) {
			++curr_len;
		}
	}
	else {
		/* 逐次描画対象行に文字列が残っていないとき */
		if (curr_idx < line_num - 1) {
			// 次の行に逐次描画対象を移す
			++curr_idx;
			curr_len = 0;
		}
	}
}

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

	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;
		if (isLeadShiftJIS(mb[0])) {
			mb[1] = *(pStream++);
			mb_len = 2;
		}

		int n = line_num;
		for (int i=0; i<mb_len; ++i) {
			buf[line_top[n]+line_len[n]+hold_len+i] = mb[i];
		}
		if (isProhibitLineBreakBeforeA(mb)) {
			/* 行頭禁則文字のとき */
			// 確定猶予カウントをリセット
			hold_cnt = 0;
		}
		else if (isProhibitLineBreakAfterA(mb)) {
			/* 行末禁則文字のとき */
			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+mb_len);
		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 += mb_len;
	}
	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;
		char c = buf[line_top[i] + len];
		buf[line_top[i] + len] = '\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 =
	"口語表現は禁則処理がめんどうなのだ。\r"
	"「ギャーーーーーーーーッ!\r やっちゃった。」\r"
	"という台詞の場合、幅が狭いと破綻するが仕様がない。\r\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);
}

|

« 禁則処理付き折り返し文字列描画【ShiftJIS版/改行付き】 | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 »

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

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

コメント

コメントを書く



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




« 禁則処理付き折り返し文字列描画【ShiftJIS版/改行付き】 | トップページ | ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 »