DXライブラリで、メッセージウインドウを作ってそこにメッセージを表示させたいのですが、上手くいきません。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
メロンくん
記事: 16
登録日時: 11年前

DXライブラリで、メッセージウインドウを作ってそこにメッセージを表示させたいのですが、上手くいきません。

#1

投稿記事 by メロンくん » 10年前

画面にメッセージウインドウが表示されて、そこにテキストファイルから読み込んだ文字を、1行の最大文字が33文字で改行されるように表示したいのですが、33文字を過ぎてもメッセージウインドウから突き抜けて表示されてしまいます。
ただし33文字目を過ぎた余計な部分はそれと同じ文字が次の改行部分の最初からは正しく表示されて、その行も33文字を超える文字数である場合は上記の同様の不具合が起きます。
DXライブラリのWaitTimer関数を使って、文字をゆっくり表示させてみると、たとえば1行目が33文字を超えている場合、1行目の33文字を超えた部分と2行目の1文字目(全く同じ文字)は同時に表示されています。
うまく説明できなくて分かりにくいと思うのですが、適当なテキストファイルでコードを実行してもらえればどういう不具合かすぐに分かると思います。
要するに33文字を超えて表示されている部分さえ無ければいいという事なのですが。
文章はZキーで入れ替えます。
どこがどう間違っているのか全然分からなくて完全にお手上げ状態なので助けてください。お願いします。
自分のc言語歴は独学で4ヶ月くらいでvisualc++2008expresseditionを使用しています。
↓下記が実際のコードです。

Main.cpp

コード:

include "DxLib.h"
#include "Keyboard.h"
#include "message.h"
static int g_sentence=0;//g_sentenceは文章や絵の一つの単位を表す。g_sentenceを変化させると表示される文章が変えられる。
static int g_compulsion=0;//g_compulsionは強制的にメッセージを変化させる為の変数。最初に表示される文章などは何もキーが押されていなくても表示させたい為に使う。
static int g_txt=0;//どのテキストファイルを使うかを決める変数。初期設定は0
 
// WinMain関数
int WINAPI WinMain( HINSTANCE hInstance , HINSTANCE hPrevInstance ,
LPSTR lpCmdLine , int nCmdShow )
{
    ChangeWindowMode( TRUE );
    SetGraphMode( 1024 , 768 , 32 ) ;
    // DxLib 初期化
    if( DxLib_Init() == -1 ) return -1; // DXライブラリ初期化処理 エラーが起きたら終了 
    // 描画先を裏画面にセット
    SetDrawScreen( DX_SCREEN_BACK ) ;
    //初期化処理
    loadtxt();
    initMessage();
    //メインループ
    while( ProcessMessage() == 0 && CheckHitKey( KEY_INPUT_ESCAPE ) == 0 ) {
        Keyboard_Update();
        //画面クリア
        ClearDrawScreen();
        if((Keyboard_Get( KEY_INPUT_Z ) == 1) || g_compulsion==0)//Zキーが押されたか、もしくは強制的に変更する場合に、メッセージを変更する。
        {
            inputmessage(g_txt,g_sentence);
            g_sentence++;
            g_compulsion=1;
        }
        //メッセージ描画
        drawMessage();
        ScreenFlip();
    }
    DxLib_End();
    return 0;
}
Keyboard.cpp

コード:

#include "DxLib.h"

static int m_Key[256];  // キーの入力状態格納用変数

// キーの入力状態更新
void Keyboard_Update(){
        char tmpKey[256];             // 現在のキーの入力状態を格納する
        GetHitKeyStateAll( tmpKey );  // 全てのキーの入力状態を得る
        for( int i=0; i<256; i++ ){ 
                if( tmpKey[i] != 0 ){ // i番のキーコードに対応するキーが押されていたら
                        m_Key[i]++;   // 加算
                } else {              // 押されていなければ
                        m_Key[i] = 0; // 0にする
                }
        }
}

// KeyCodeのキーの入力状態を取得する
int Keyboard_Get( int KeyCode ){
        return m_Key[ KeyCode ]; // KeyCodeの入力状態を返す
} 
message.cpp

コード:

#include "DxLib.h"
#include <stdio.h>
static int g_windowColorInside,g_windowColorOutline,g_whiteColor;//色に関する変数
#define MESSAGE_FONT_SIZE 22 //メッセージのフォントの大きさ
#define MESSAGE_MAX_LENGTH 33 //文章の一行分の最大文字数
#define MESSAGE_MAX_Byte MESSAGE_MAX_LENGTH*2 //文章の一行分の最大バイト数
#define MESSAGE_MAX_LINE 4 //文章の最大行数
#define MESSAGE_BOX_X_POS 112 //メッセージボックスのX 座標
#define MESSAGE_BOX_Y_POS 600 //メッセージボックスのY 座標
static char GameText[10][256][MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE * 2] = {{{}}}; //ここに全てのテキストを読み込む
static char g_messageBuffer[MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE * 2]; //表示したいメッセージ
static char g_message[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH * 2]; //最終的に表示されるメッセージ
static int g_currentCursor = 0; //現在何文字目までを示しているか
static int g_ByteNo = 0;//現在何バイト目までを示しているか
static int g_letterByteNo=0;//g_messageの1行単位での何バイト目を示しているか
static int g_currentLineCursor = 0; //何行目の文字を示しているか

int is2byteCharacter(unsigned char code)//code が2バイト文字であるか判定する
{
	if( (code >= 0x81 && code <= 0x9F) || (code >= 0xE0 && code <= 0xFC) )return 1;//戻り値1: 2バイト文字
	else return 0;//戻り値0: 2バイト文字ではない
}

void drawMessage()
{
	//↓メッセージウインドウを描画
	DrawBox ( MESSAGE_BOX_X_POS , MESSAGE_BOX_Y_POS , MESSAGE_BOX_X_POS + 800 , MESSAGE_BOX_Y_POS + 154, g_windowColorInside, TRUE) ;
	DrawBox  (MESSAGE_BOX_X_POS, MESSAGE_BOX_Y_POS , MESSAGE_BOX_X_POS + 12, MESSAGE_BOX_Y_POS + 154, g_windowColorOutline,TRUE) ;
	DrawBox  (MESSAGE_BOX_X_POS + 800 -12, MESSAGE_BOX_Y_POS , MESSAGE_BOX_X_POS + 800, MESSAGE_BOX_Y_POS + 154, g_windowColorOutline,TRUE) ;
	DrawBox  (MESSAGE_BOX_X_POS, MESSAGE_BOX_Y_POS , MESSAGE_BOX_X_POS + 800, MESSAGE_BOX_Y_POS + 12, g_windowColorOutline,TRUE) ;
	DrawBox  (MESSAGE_BOX_X_POS, MESSAGE_BOX_Y_POS + 154 -12, MESSAGE_BOX_X_POS + 800, MESSAGE_BOX_Y_POS + 154, g_windowColorOutline,TRUE) ;
	if(g_messageBuffer[g_ByteNo]!='\0'){//g_messageBufferからg_messageへと文字をコピーしていく
		if( is2byteCharacter( g_messageBuffer[g_ByteNo] ) == 1){//現在示されているのがもし2バイト文字なら
			g_message[g_currentLineCursor][g_letterByteNo]=g_messageBuffer[g_ByteNo];
			g_letterByteNo++;
			g_ByteNo++;
			//↑g_letterByteNoとg_ByteNoを1つ進めて↓もう1バイト分、g_messageに文字をコピーする
			g_message[g_currentLineCursor][g_letterByteNo]=g_messageBuffer[g_ByteNo];
		}else g_message[g_currentLineCursor][g_letterByteNo]=g_messageBuffer[g_ByteNo];//1バイト文字なら一度だけ
		if(g_messageBuffer[g_ByteNo]!='\0'){
			g_currentCursor++;
			g_letterByteNo++;
			g_ByteNo++;
		}
		//↓行の終わりまで達して、まだコピーしていない文字があるなら、改行する
		if( g_currentCursor % MESSAGE_MAX_LENGTH == 0 && g_messageBuffer[g_ByteNo] != '\0'){
			g_currentLineCursor++;
			g_letterByteNo=0;
		}
	}
	//↓文字をメッセージウインドウに描画する
	for(int j=0;j<MESSAGE_MAX_LINE && g_message[ j ]!='\0';j++){
		DrawString(MESSAGE_BOX_X_POS + 19, MESSAGE_BOX_Y_POS + (MESSAGE_FONT_SIZE + 5) * j + 22 , g_message[ j ], g_whiteColor );
	}
}

//メッセージ関連変数初期化
void initMessage()
{
	SetFontSize(MESSAGE_FONT_SIZE);
	g_windowColorInside = GetColor(244,114,208);
	g_windowColorOutline = GetColor(216,0,115);
	//白
	g_whiteColor = GetColor(255,255,255);
}

//txtファイル読み込み
int loadtxt(){
	int x=0,y=0;
	char textNo[2]={'1','2'};
	char textname[256];
	FILE* file;
	while(x<=9)
	{
		sprintf_s(textname, "ゲームテキスト/シーン%c.txt", textNo[x]);
		if(fopen_s(&file, textname, "r") != '\0')return 0;
		while (fgets(GameText[x][y],(MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE)*2, file) != '\0' )
		{
			y++;
		}
		x++;
		y=0;
		fclose(file);
	}	
return 0;
}

//描画したいメッセージをセット
void setMessage(const char* message)
{
	//↓カーソルを初期化
	g_currentCursor = 0;
	g_ByteNo = 0;
	g_letterByteNo=0;
	g_currentLineCursor = 0;
	for(int i=0;i<MESSAGE_MAX_LINE && g_message[i]!='\0';i++){
		memset(g_message[i],'\0',sizeof(g_message[i]));
	}
	memset(g_messageBuffer,'\0',sizeof(g_messageBuffer));
	//↓メッセージをコピー
	strncpy_s( g_messageBuffer , message , strlen( message )-1);//マイナス1の部分は改行コードを消している。だからテキストの最後の行にも改行が無いとそこだけおかしくなる事に注意。
}

//読み込んだテキストファイルの一行分をセットする
void inputmessage(int txt,int sentence){
	setMessage(GameText[txt][sentence]);
}
Keyboard.h

コード:

#ifndef DEF_KEYBOARD_H //二重include防止
#define DEF_KEYBOARD_H

// キーの入力状態を更新する
void Keyboard_Update();
// 引数のキーコードのキーの入力状態を返す
int Keyboard_Get( int KeyCode );
#endif 
message.h

コード:

#ifndef DEF_Message_H //二重include防止
#define DEF_Message_H

int is2byteCharacter(unsigned char code);
void drawMessage();
void initMessage();
int loadtxt();
void setMessage(const char* message);
void inputmessage(int txt,int sentence);

#endif 

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前
住所: 東京
連絡を取る:

Re: DXライブラリで、メッセージウインドウを作ってそこにメッセージを表示させたいのですが、上手くいきません。

#2

投稿記事 by h2so5 » 10年前

このコードだと各行の文字列が完全に連続してしまいますよね。
つまり1行が6文字として1行目が「ああああああ」で二行目が「いいいいいい」だった場合、
メモリ上では「ああああああいいいいいい」とならんでいるわけで途中に'\0'がないのでDrawString関数はどこが一行目の終わりなのか判断できません。
1行目を33文字いっぱいに使っている場合でも最後には'\0'が入っていないといけません。

メロンくん
記事: 16
登録日時: 11年前

Re: DXライブラリで、メッセージウインドウを作ってそこにメッセージを表示させたいのですが、上手くいきません。

#3

投稿記事 by メロンくん » 10年前

>>h2so5さん
回答ありがとうございます。
このコードだと各行の文字列が完全に連続してしまうとのことですが
しかし、何故そうなのかが全然わからないです。
理解力の無い自分に、出来ればもう少し詳しい説明がほしいです。
自分の考えでは
void setMessage(const char* message)の中でmemset関数を使ってg_messageBufferとg_message両方の配列の全てに'\0'が入れられているので
g_messageBufferからg_messageに中身が移されても、g_messageのそれぞれの行の最後の文字の後は全部'\0'になっていると思うのですが。
h2so5さんの回答を見て、それじゃあ行の最後に'\0'を入れれば良いのかとdrawMessage()内の改行の部分で
g_message[g_currentLineCursor][g_letterByteNo]='\0';と入れてみましたが、動作は全く同じでした。
まだどこをどう直せば良いのか分からない状況です。

メロンくん
記事: 16
登録日時: 11年前

Re: DXライブラリで、メッセージウインドウを作ってそこにメッセージを表示させたいのですが、上手くいきません。

#4

投稿記事 by メロンくん » 10年前

やっと理解できました!
static char g_message[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH * 2];を
static char g_message[MESSAGE_MAX_LINE][MESSAGE_MAX_LENGTH * 2+1];に変えてから
改行の部分でg_message[g_currentLineCursor][g_letterByteNo]='\0';を加えたらすんなり正確な動作になりました。
これは配列をその分確保していなかったから当たり前でした。
setMessage(const char* message)の中でmemset関数を使って配列に全て'\0'を加えても、
テキストファイルに改行するまでの文字数があれば、二次配列g_messageの一行33文字は全てぎっしり埋まってしまうから'\0'が無くなってしまうのも考えてみれば当たり前でした。
そもそもhairetu[5];みたいな二次配列があったとしてhairetu[0]を表示すれば、普通にhairetu[0][0]からhairetu[0][4]までが表示されるという風な認識しかなかったので
'\0'が無いとhairetu[1]の中身まで表示されてしまうとは思いもしませんでした。
h2so5さんのおかげで一歩先に進めました。
本当にありがとうございました。

閉鎖

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