サウンドノベル風ゲームの改行方法

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
smly

サウンドノベル風ゲームの改行方法

#1

投稿記事 by smly » 12年前

現在、ゲーム作りで学ぶ! 実践的C言語プログラミング(http://karetta.jp/book-cover/game-programming)を読みながらDXライブラリを使用したサウンドノベル風ゲームを作成しています。
対象ページは(http://karetta.jp/book-node/game-programming/235076)です。

上記サイトにあるプログラムを書き換え、画面に表示したい文字列の中に改行用の文字(例えば@とします)が含まれていた場合に
文字列を改行して表示する方法を探しています。

このプログラムでは、一見すると 読み込んだ文字列のカーソル位置(g_message[g_currentCursor])に改行文字@が来た時にg_currentCursor++をすれば良いように見えますが、
実際にはこれは間違いで、改行文字が出現した後はwriteSubstring()の引数startおよびlenに与える値を変化させる必要があります。

g_currentCursorは何のためにあるかというと、既に表示が完了した行は一瞬で表示し、現在表示中の行は1文字ずつ画面に表示させていく、ということを実現するためにあります。
(指定文字数(30文字)を超えると自動的に改行されます。)

ただ、改行文字の出現に応じて与える第2、第3引数start, lenの値をどう書けば良いか分からず困っています。

改行文字の出現に応じて変化するフラグを作成したり、表示した文字をカウントする変数を作成するなど多くのことを試したのですが、
どれも上手くいかず困っています。
長いコードとなってしまいますが、どなたがご教示願えませんでしょうか。

コード:

#include "DxLib.h"

//注意点:文字コードはSHIFT_JIS(Windows標準)を前提としている
//                他の文字コードでは正常に動作しない
//SHIFT_JISの場合,日本語は2バイトで表される
//上位バイトが0x81~0x9F、0xE0~0xFCの範囲に収まる

int isJapaneseCharacter(unsigned char code);
void writeSubstring(char* message, int start, int len,
                 int posX, int posY, int color, int bufferLine);
void drawMessage();
void initGame();

//メッセージのフォントの大きさ
#define MESSAGE_FONT_SIZE 20
//仮想バッファの最大文字数
#define MESSAGE_MAX_LENGTH 30
//仮想バッファの最大行数
#define MESSAGE_MAX_LINE 5
//メッセージボックスのX座標
#define MESSAGE_BOX_X_POS 40
//メッセージボックスのY座標
#define MESSAGE_BOX_Y_POS 340
//メッセージボックスの画像ファイル
#define MESSAGE_BOX_GRAPHIC_FILENAME "./Pic/boxd3.jpg"

//表示したいメッセージ
char g_message[MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE];

//画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH];

//メッセージボックス関係

//現在何文字目までを表示しているか
static int g_currentCursor = 0;
//何行目の文字を表示しているか
static int g_currentLineCursor = 0;
//白
static int g_whiteColor;
//黒
static int g_blackColor;
//メッセージボックスの画像
static int g_messageBoxGraphicHandle;


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


//messageで指定した文章を start の位置から len 文字分表示する
void writeSubstring(char* message, int start, int len,
                 int posX, int posY, int color, int bufferLine)
{
        int i;
        //文字数
        int maxLen = strlen( message );
        
        //startの位置を変更する
        //startの位置までに日本語がでてきていたら,1を足していく
        for( i = 0; i < start && message[i] != '\0'; ) {
                if( isJapaneseCharacter( message[i] ) ) {
                        //日本語の場合,2バイト分すすめる
                        i += 2;
                        //startに1バイト分足す
                        start++;
                }else {
                        //半角文字の場合,1バイト分進める
                        i++;
                }
        }

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

        //指定した位置からlen文字分表示する
        for( i = 0; i < len && message[ start + i ] != '\0'; ) {
                if( isJapaneseCharacter( message[ start + i ] ) ) {
                        //日本語の場合,2文字分bufferにセット
                        g_messageBuffer[ bufferLine ][ i ] = message[ start + i ];
                        g_messageBuffer[ bufferLine ][ i + 1 ] = message[ start + i + 1 ];
                        //lenは日本語なので,1バイト分追加する
                        len++;
                        //2バイト分進める
                        i += 2;
                }else {
                        //半角文字1文字をセット
                        g_messageBuffer[ bufferLine ][ i ] = message[ start + i ];
                        //1バイト分進める
                        i++;
                }
        }
        g_messageBuffer[ bufferLine ][i] = '\0';

        //メッセージ描画
        DrawString(posX, posY, g_messageBuffer[ bufferLine ], color );
}


//メッセージ描画
void drawMessage()
{
        int i;

        //文字が1文字もセットされていなかったらメッセージボックスを表示しない
        if( strnlen(g_message, MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ) <= 0 ) {
                return;
        }

        //メッセージボックス描画
        DrawGraph( MESSAGE_BOX_X_POS, MESSAGE_BOX_Y_POS, g_messageBoxGraphicHandle, FALSE );
        
        if( g_message[g_currentCursor] != '\0' ) {
                g_currentCursor++;
        }

        //MESSAGE_MAX_LENGTH まで文字を描画したら段落を切り替える
        if( g_currentCursor % MESSAGE_MAX_LENGTH == 0 ) {
                if( g_message[g_currentCursor] != '\0' ) {
                        g_currentLineCursor++;
                }
        }

        //メッセージ描画部分
        for( i = 0; i < MESSAGE_MAX_LINE; i++ ) {
                if( i == g_currentLineCursor ) {
                        //メッセージ風に表示
                        writeSubstring( g_message, i * MESSAGE_MAX_LENGTH ,
                                g_currentCursor - MESSAGE_MAX_LENGTH * i,
                                MESSAGE_BOX_X_POS + 15,
                                MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15,
                                g_blackColor, i );
                        break;
                }else {
                        //メッセージをそのまま表示
                        writeSubstring( g_message, i * MESSAGE_MAX_LENGTH ,
                                MESSAGE_MAX_LENGTH, MESSAGE_BOX_X_POS + 15,
                                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_messageBoxGraphicHandle = LoadGraph( MESSAGE_BOX_GRAPHIC_FILENAME );
}

//描画したいメッセージをセット
void setMessage(const char* message)
{
        //カーソルを初期化
        g_currentCursor = 0;
        g_currentLineCursor = 0;

        //メッセージをコピー
        strncpy( g_message, message, MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE);
}

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();

                //F1 F2を押すとメッセージをセットする
                //F3を押すと空の文字列をセットする
                if( CheckHitKey( KEY_INPUT_F1 ) ) {
                        setMessage("はろーわーるど");
                }else if( CheckHitKey( KEY_INPUT_F2 ) ) {
                        setMessage("HELLO WORLD!あいうえおかきくけこさしすせそたちつてとな" 
                        "にぬねのはひふへほまみむめもやゆよらりるれろわをん");
                }else if( CheckHitKey( KEY_INPUT_F3 ) ) {
                        setMessage("");
                }

                //メッセージ描画
                drawMessage();
                
                Sleep( 100 );
                ScreenFlip();
        }

        DxLib_End();
        return 0;
}

smly

Re: サウンドノベル風ゲームの改行方法

#2

投稿記事 by smly » 12年前

失礼致しました。投稿メッセージに間違いがあったので、修正します。
以下修正です。
----------------
現在、ゲーム作りで学ぶ! 実践的C言語プログラミング(http://karetta.jp/book-cover/game-programming)を読みながらDXライブラリを使用したサウンドノベル風ゲームを作成しています。
対象ページは(http://karetta.jp/book-node/game-programming/235076)です。


上記サイトにあるプログラムを書き換え、画面に表示したい文字列の中に改行用の文字(例えば@とします)が含まれていた場合に
文字列を改行して表示する方法を探しています。

このプログラムでは、一見すると 読み込んだ文字列のカーソル位置(g_message[g_currentCursor])に改行文字@が来た時にg_currentLineCursor++をすれば良いように見えますが、
実際にはこれは間違いで、改行文字が出現した後はwriteSubstring()の引数startおよびlenに与える値を変化させる必要があります。

g_currentLineCursorは何のためにあるかというと、既に表示が完了した行は一瞬で表示し、現在表示中の行は1文字ずつ画面に表示させていく、ということを実現するためにあります。
(指定文字数(30文字)を超えると自動的に改行されます。)

ただ、改行文字の出現に応じて与える第2、第3引数start, lenの値をどう書けば良いか分からず困っています。

改行文字の出現に応じて変化するフラグを作成したり、表示した文字をカウントする変数を作成するなど多くのことを試したのですが、
どれも上手くいかず困っています。
長いコードとなってしまいますが、どなたがご教示願えませんでしょうか。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: サウンドノベル風ゲームの改行方法

#3

投稿記事 by softya(ソフト屋) » 12年前

ちょっと試すのに時間が掛かりそうなので夜までお待ちください。
ただ、「ゲーム作りで学ぶ! 実践的C言語プログラミング」のコードは考え方の参考にする程度なら良いのですが、ちゃんと組むならZEROから組んだほうが良いかもしれません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: サウンドノベル風ゲームの改行方法

#4

投稿記事 by ISLe » 12年前

直接の回答ではないのですが、良かったら参考にしてください。
禁則処理も付いてます。
ノベルゲーム風に一文字ずつ表示する: ISLeのビデオゲーム工房

non
記事: 1097
登録日時: 15年前

Re: サウンドノベル風ゲームの改行方法

#5

投稿記事 by non » 12年前

サンプルプログラムの1行の文字数が30文字ってのは、全角半角の区別なく30文字のようですが、
配列は
#define MESSAGE_MAX_LENGTH 30
char g_messageBuffer[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH];
のようにバイト数で確保しているみたいですが、私の勘違い?

さて、私がこのプログラムを元に作るなら、
setMessage関数で、messageを2次元配列
char g_message[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH*2+1];
に1行ずつ入れます。30文字目が漢字の2バイト目のときにちょっと工夫が必要です。
もちろん、@が出ると、行を変えます。
あとは drawMessage関数の描画部分を使って表示します。
フラグはなくてよいかと思います。

ただ、@はコードが0x40で、漢字2バイト目が0x40の漢字が存在しますので、漢字2バイト目と区別する必要が
あります。サンプルでは、漢字1バイト目のチェックする関数が作られてますから、1つ前が漢字1バイト目かチェック
すればいいでしょう。
non

smly

Re: サウンドノベル風ゲームの改行方法

#6

投稿記事 by smly » 12年前

>softya(ソフト屋)様
申し訳ありません。お手数をおかけしますが、自分では解決できなかったので…
宜しくお願いします。

>ISLe様
ありがとうございます。
ご紹介の内容は理解に少し時間がかかりそうですが、これを参考に上手く適用できればかなり良いものができそうです。
参考にさせていただきます。

>non様
仰るとおり、バイト数にて文字数を確保しています。
実際に運用する際は全角文字のみに限定する予定です。文字数自体はあまり気にしないでください。
なるほどg_messageを2次元配列にするとは考えもしませんでした。
実装は少しコツが要りそうですが、試しにこの方法での実装を検討してみます。

smly

Re: サウンドノベル風ゲームの改行方法

#7

投稿記事 by smly » 12年前

サンプルコードに対し変更箇所が多すぎるため載せられませんが、non様の方法で解決することができました。
ありがとうございます。
今後ISLe様の方法も実装してみます。

閉鎖

“C言語何でも質問掲示板” へ戻る