ページ 11

ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月21日(火) 19:02
by 旧:ふくとも
開発環境
OS:Windows7 Ultimate(Mac OS X Mavericks上のVirtual Box 4.3.12で動かしています)
Microsoft Visual C++ 2010 で DxLibraryを使用

現在ノベルゲームのテキストエンジンを作成していて、
・1行の長さを文字数で制限
・メッセージボックスに収まる行数の表示
はできているのですが、

・1行の長さを幅で制限する方法(全角/半角が混ざることが考えられるため。等幅フォントを使用)
・メッセージボックスに収まらないメッセージの時のメッセージボックスの更新の仕方
(例えばメッセージボックスが三行しか表示できず、メッセージは4行以上の場合)
この二つの実装の仕方について悩んでいます

メッセージボックスにつきましては
①表示されているメッセージの二行目、三行目を変数に格納
②表示されているメッセージを消去
③変数に格納したものをまとめて描画
④新しい行を一文字ずつ表示
という形にしたいと思っています

以下、ソースコード
コンパイルエラーは出ていません

コード:

#include "DxLib.h"

// 関数プロトタイプ宣言
int DiscriminateChar(unsigned char);
void OutString(char*, int, int, int, int, int, int);
void drawMsg();
void initGame();

// #define
// メッセージのフォントの大きさ
#define MESSAGE_FONT_SIZE 17
// 仮想バッファの最大文字数(半角60字==全角30字)
#define MESSAGE_MAX_LENGTH 60
// 仮想バッファの最大行数
#define MESSAGE_MAX_LINE 5
// メッセージボックスのX座標
#define MESSAGE_BOX_X_POS 40
// メッセージボックスのY座標
#define MESSAGE_BOX_Y_POS 340
// メッセージボックスの画像ファイル
#define MESSAGE_BOX_GRAPHIC_FILENAME "./img/msg_box.png"

// グローバル変数
// 表示したいメッセージ
char g_msg[MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE] ="HELLO WORLD!あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん1234567890";
// 画面にメッセージを表示する際に使用する仮想テキストバッファ
char g_msgBuf[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH];
// フォントハンドル
int g_FontHandle;
// 現在何文字目までを表示しているか
static int g_curCursor = 0;
// 何行目の文字を表示しているか
static int g_curLineCursor = 0;
// 色
static int g_whiteColor;
static int g_blackColor;
//メッセージボックスの画像
static int g_msgBoxGraphHandle;

// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
	//ウィンドウモードで起動
	ChangeWindowMode(TRUE);
	//画面の大きさは640 * 480
	SetGraphMode(640, 480, 16) ;
	//DxLib初期化
	if(DxLib_Init() == -1) 
		return -1;

	// 描画先を裏画面にセット
	SetDrawScreen(DX_SCREEN_BACK) ;

	// グローバル変数をセット
	initGame();
	// メインループ
	while(ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0){
		// 画面初期化
		ClearDrawScreen();
		// メッセージ描画
		drawMsg();
        // 300msec.待機
		Sleep(300);
        // 描画を表画面に
		ScreenFlip();
	}
    // フォントハンドル破棄
	DeleteFontToHandle(g_FontHandle);
    // DxLib終了処理
	DxLib_End();
    
	return 0;
}

// Codeが全角であるか判定する
// 戻り値 1:全角 0:全角ではない
int DiscriminateChar(unsigned char Code){
	if((Code >= 0x81 && Code <= 0x9F) || (Code >= 0xE0 && Code <= 0xFC))
		return 1;
	return 0;
}

// msgで指定した文章をstartの位置からlen文字分表示する
void OutString(char* msg, int start, int len, int posX, int posY, int color, int bufLine){
    // ループカウンタ
	int i;
	//文字数
	int maxLen = strlen(msg);

	//startの位置を変更する
	//startの位置までに全角がでてきていたら,1を足していく
	for(i = 0; i < start && msg[i] != '\0';){
		if(DiscriminateChar(msg[i])) {
			//日本語の場合,2バイト分すすめる
			++i;
			//startに1バイト分足す
			++start;
		}
		//半角の場合,1バイト分進める
		++i;
	}

	//startの位置が表示したい最大文字数より大きい場合
	if(start >= maxLen)
		return;

	//指定した位置からlen文字分表示する
	for(i = 0; i < len && msg[start + i] != '\0';){
			g_msgBuf[bufLine][i] = msg[start + i];
		if(DiscriminateChar(msg[start + i ])){
			//日本語の場合,2文字分bufferにセット
			g_msgBuf[bufLine ][i + 1] = msg[start + i + 1];
			//lenは日本語なので,1バイト分追加する
			++len;
			//2バイト分進める
			++i;
		}
		//1バイト分進める
		++i;
	}
    // 配列の最後に'\0'を代入
	g_msgBuf[bufLine][i] = '\0';

	//メッセージ描画
	DrawStringToHandle(posX, posY, g_msgBuf[bufLine], color, g_FontHandle);
}

// メッセージ描画
void drawMsg(){
    // ループカウンタ
	int i;
    // メッセージボックス描画
	DrawGraph(MESSAGE_BOX_X_POS, MESSAGE_BOX_Y_POS, g_msgBoxGraphHandle, FALSE);
    if(g_msg[g_curCursor] != '\0'){
        // 全角なら2文字分、半角なら一文字分カーソルを進める
        if(DiscriminateChar(g_msg[g_curCursor]))
           ++g_curCursor;
        ++g_curCursor;
    }
	// 半角60字分に到達->次の行へ
	// 半角で59字分、次の文字が全角の場合も次の行へ
	if(g_msg[g_curCursor] != '\0' &&
       (g_curCursor % MESSAGE_MAX_LENGTH == 0 ||
       (g_curCursor % MESSAGE_MAX_LENGTH == MESSAGE_MAX_LENGTH - 1 && DiscriminateChar(g_msg[g_curCursor + 1]))))
		++g_curLineCursor;
	// draw msg
	for(i = 0; i < MESSAGE_MAX_LINE; i++){
		if(i==g_curLineCursor){
			OutString(g_msg, i * MESSAGE_MAX_LENGTH, g_curCursor - MESSAGE_MAX_LENGTH * i,
				MESSAGE_BOX_X_POS + 8, MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15, g_blackColor, i); 
			break;
		}else
			OutString(g_msg, i * MESSAGE_MAX_LENGTH, MESSAGE_MAX_LENGTH,
				MESSAGE_BOX_X_POS + 8, MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15, g_blackColor, i); 
	}
}

// グローバル変数初期化
void initGame(){
	// 白
	g_whiteColor = GetColor(255, 255, 255);
	// 黒
	g_blackColor = GetColor(0, 0, 0);
	// メッセージボックスの画像
	g_msgBoxGraphHandle = LoadGraph(MESSAGE_BOX_GRAPHIC_FILENAME);
	// フォント指定(等幅フォントのMS ゴシックを使用)
	g_FontHandle = CreateFontToHandle("MS ゴシック" , MESSAGE_FONT_SIZE, 2 , DX_FONTTYPE_ANTIALIASING);
}
下記のページを参考にしながら開発を行っております
http://karetta.jp/book-node/game-programming/235056

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月21日(火) 21:13
by h2so5
テキスト処理が多い場合はマルチバイトではなくUnicodeで開発したほうがいいと思います。

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月21日(火) 22:29
by 旧:ふくとも
h2so5 さんが書きました:テキスト処理が多い場合はマルチバイトではなくUnicodeで開発したほうがいいと思います。
返信ありがとうございます
Unicodeに変更する際に、
・変数の型を char から wchar_t に変更する
・全角文字の範囲を書き換える
この他にするべきこと、また、気をつけるべきことがあれば教えていただきたいと思います

よろしくお願いします

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月22日(水) 23:02
by V30
今日から、RPGのコメント的な物をクラスとして作成に取り掛かっています。只今、疲れたので途中でプロジェクトを放り投げています。

画面更新をカウントしてメッセージBOXや文字の表示開始時刻や次の文字表示までの時間を制御したり、その他仕様を細かくしていますが、やりたいことは全く同じなので、有段者の返信コメントを拝見だけさせて頂きます。

私はまだまだド素人なので、適切な助言はできません。

お互いに頑張りましょう。

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月24日(金) 01:09
by ISLe()
わたしのブログの記事ですが、参考になるでしょうか。
#この掲示板に過去にも投稿していますが。

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

このプログラムはテキストデータのストリーム入力を想定していて、バッファにメッセージを展開し、行毎の先頭を記録していくので、好きな行数で表示できるし、スクロールするまで展開処理を停止することもできるし、バッファは容量を増やしてそのままバックログとして使うこともできます。

長いメッセージを処理する場合、バッファが溢れる前に古い行を詰める処理をしなければいけません。
その処理を追加するのは簡単ですが、この記事には含まれていません。

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月25日(土) 02:13
by 旧:ふくとも
ISLe() さんが書きました:わたしのブログの記事ですが、参考になるでしょうか。
#この掲示板に過去にも投稿していますが。

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

このプログラムはテキストデータのストリーム入力を想定していて、バッファにメッセージを展開し、行毎の先頭を記録していくので、好きな行数で表示できるし、スクロールするまで展開処理を停止することもできるし、バッファは容量を増やしてそのままバックログとして使うこともできます。

長いメッセージを処理する場合、バッファが溢れる前に古い行を詰める処理をしなければいけません。
その処理を追加するのは簡単ですが、この記事には含まれていません。
返信ありがとうございます
貼っていただいたリンクを参考に進めてみようと思います

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月29日(水) 00:55
by 旧:ふくとも
先日教えていただいたリンクを拝見させていただきました。

禁則処理付き折り返し文字列描画【ShiftJIS版】のコードの191行目、

コード:

struct Main : public VGCanvas
{
    WrapText wt;
    int width;
 
    Main() : VGCanvas() {}
    void init();
    void frameUpdate(int skiiped);
    void frameRender(void);
} canvas;
の Main() : VGCanvas() {} はどういったことを行っているのでしょうか?
自分の知識不足かとは思いますが解説していただきたく思います。

また、197〜201行目の

コード:

VGCanvas *VGLibInit()
{
    ChangeWindowMode(TRUE);
    return &canvas;
}
はアプリケーションをウィンドウモードに切り替え、
VGCanbasクラスのオブジェクトcanbasのアドレスを返す関数の宣言
という理解で合っているでしょうか?

Re: ノベルゲームのテキストエンジン[C/C++]

Posted: 2014年10月30日(木) 00:04
by ISLe()

コード:

struct Main : public VGCanvas
{
    // 略
} canvas;

コード:

class Main : public VGCanvas
{
public:
    // 略
};
Main canvas;
と同じです。
Mainクラスを定義して、Main型のcanvas変数を宣言しています。


VGLibInit関数については、その解釈で合っています。

DXライブラリのDxLib_Init関数が呼ばれる前に、VGLibInitが呼ばれます。
DXライブラリのDxLib_Init関数が呼ばれた後に、VGLibInitで返したcanvasのinitメンバ関数が呼ばれます。
あとは指定したフレームレートに沿うようにcanvasのframeUpdateメンバ関数とcanvasのframeRenderメンバ関数が呼ばれます。

自前フレームワークの本体はアーカイブのDxLib/VGLibMain.cppにありますが、指定したフレームレートのVSYNCをシミュレートするだけのものですから、上記の関数に書かれた内容を適切なタイミングで呼び出しさえすれば簡素なメインルーチンでも動きます。