ノベルゲームのバックログ表示のバグ

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

ノベルゲームのバックログ表示のバグ

#1

投稿記事 by Marumi » 10年前

初めまして。Marumiと申します。
現在DxLibを使用したノベルゲームを作成しており、その中でバックログ表示の機能を作成しているのですが
どうしてもバグの解決ができないため、質問致します。

バックログは、1~3行のメッセージ(string)の塊を1件とします。
(下記 list<string> log_msgsのことで、listの中身は1~3件です。このlog_msgsをBacklogMessage型として扱います。)
バックログは、全体として上記の塊を100件まで保存します。
これを list<BacklogMesssage> a_log とします。

バックログは1画面にデフォルトで最新のものを10行まで表示し、これより古いものを表示するときは
画面の上下ボタンを押すことで、表示されるメッセージをずらしていきます。

バックログを表示するとき、まず最新のものから10行(string)を読み取り、その10行目のイテレータ
(a_log_it:どのメッセージの塊か、msg_it:メッセージの塊のうち、何行目か)を保存します。
ただし、10行を読み取る前にバックログが無くなってしまったら、無くなった位置までで検索を終了します。
次に、最新から10行目のイテレータから開始して、画面の上部から [最新から10行目]、[最新から9行目]…[最新から1行目] を描画します。
こうすることで、バックログが1行だけあっても10行以上あっても、デフォルトでは画面の上部から最新の10行までのみが表示されます。

下記のように実装しました。
量が多いため申し訳ありませんが該当の関数のみ載せます。

多くの場合は下記のコードで正常に動くのですが、バックログがある件数(すみません、これが不明です)のとき
初期表示時や上下ボタンを押したとき、 Expression: list iterators incompatible のエラーが発生してしまいます。
(一度表示してから、次のフレームでこの関数を再実行されたときに、表示箇所の
while(msg_it != log_msgs.end() && log_line_cnt < 10){
の行でエラーとなります。デバッガで見ると、log_msgsはちゃんと3件のstringが格納されたlistになっているのですが…)
どの行が間違っているのでしょうか。
長くなってしまい申し訳ありませんが、どなたかのお力をいただきたく、よろしくお願いします。

コード:

void Scene::doScenarioBacklog(
	Window* window,
	Config* config,
	Input* input,
	GraphicManager* g_manager,
	SoundManager* s_manager,
	Scenario* scenario,
	CmdMessage* message)
{
	//初回表示時に一度だけ表示するログを指す箇所の計算をする
	static bool seeklog_flg = false; // falseの時のみ計算
	//この関数は毎フレーム呼ばれるため、イテレータはstatic変数として保存する
	static Backlog logs = message->getLog();
	static list<BacklogMessage> a_log = logs.logs();
	static list<BacklogMessage>::iterator a_log_it = a_log.end();
	
	static list<string>::iterator msg_it;
	static list<string> log_msgs;
	// 10行をカウントしたら打ち止め
	int log_line_cnt;
	// バックログ画面初期時
	if(!seeklog_flg){
		logs = message->getLog();
		a_log = logs.logs();
		a_log_it = a_log.end();
		log_line_cnt = 0;
		//最新行のある塊から古い方へ順に進める
		while(a_log_it != a_log.begin() && log_line_cnt < 10){
			if(log_line_cnt < 10) a_log_it--;
			log_msgs = *a_log_it;
			msg_it = log_msgs.end();
			//3行目から1行目に向かって進める
			while(msg_it != log_msgs.begin() && log_line_cnt < 10){
				log_line_cnt += 1;
				msg_it--;
			}
		}
		seeklog_flg = true;
	}


	//表示
	//検索した、表示するログの先頭行を指すイテレータを別の変数に保存しておく
	//(描画するためにイテレータを進める必要があり、
	// 進めてしまうと次のフレームで表示ログの先頭行がわからなくなってしまうため)
	list<BacklogMessage>::iterator tmp_a_log_it = a_log_it;
	list<string>::iterator tmp_msg_it = msg_it;
	list<string> tmp_log_msgs = log_msgs;
	log_line_cnt = 0;
	//検索した位置から最新に向かって10行を描画する
	while(a_log_it != a_log.end() && log_line_cnt < 10){
		log_msgs = *a_log_it;
		while(msg_it != log_msgs.end() && log_line_cnt < 10){
			DxLib::DrawStringToHandle(
				BACKLOG_POINT_X,
				BACKLOG_POINT_Y + (MESSAGE_FONT_SIZE + BACKLOG_LINE_SPACE) * log_line_cnt,
				(*msg_it).c_str(),
				color_white,
				message->msgFontHandle());
			msg_it++;
			log_line_cnt += 1;
		}
		if(log_line_cnt < 10) a_log_it++;
		if(a_log_it == a_log.end()) break;
		log_msgs = *a_log_it;
		if(log_line_cnt == 10) break;
		msg_it = log_msgs.begin();
	}
	//次のフレームで正常に表示できるようにするため、表示用に進めたイテレータを元に戻す
	log_msgs = tmp_log_msgs;
	a_log_it = tmp_a_log_it;
	msg_it = tmp_msg_it;

	//上ボタン
	DxLib::DrawBox(930, 200, 980, 250, color_white, TRUE);
	//下ボタン
	DxLib::DrawBox(930, 630, 980, 680, color_white, TRUE);
	//上ボタン
	//上ボタンを押すと、表示ログを一つ過去に戻す
	//デフォルトでは最新から、表示:10~1行目だが、上ボタンを1度押すと11~2行目を描画する
	if(Common::cursorIn(930, 980, 200, 250)){
		//マウスボタンが押された時。押しっぱなしでも一度のみ反応する
		if(input->getKeepMouseLeft()){
			log_msgs = *a_log_it;
			if(msg_it != log_msgs.begin()){
				msg_it--;
			}else{
				if(a_log_it != a_log.begin()){
					a_log_it--;
					log_msgs = *a_log_it;
					msg_it = log_msgs.end();
					msg_it--;
				}
			}
		}
	}
	//下ボタン
	//上ボタンと逆の動作
	if(Common::cursorIn(930, 980, 630, 680)){
		if(input->getKeepMouseLeft()){

			msg_it++;
			log_msgs = *a_log_it;
			if(msg_it == log_msgs.end()){

				a_log_it++;
				if(a_log_it == a_log.end()){
					a_log_it--;
					msg_it--;
				}else{
					msg_it = log_msgs.begin();
				}
			}
		}
	}

	//右クリックで前画面に戻る
	if(input->getKeepMouseRight() != 0){
		seeklog_flg = false;
		now_scene = eScene_scenario;
		before_scene = eScene_scenario_backlog;
	}

}

アバター
へにっくす
記事: 634
登録日時: 12年前
住所: 東京都

Re: ノベルゲームのバックログ表示のバグ

#2

投稿記事 by へにっくす » 10年前

掲示したソースをざっと見ると、msg_itに入れる対象はlog_msgsで、関数の引数CmdMessage* messageから取得したデータであることがわかりますが、この関数を抜けた後、messageの内容は不変ですか?
つまり、この関数を抜けた後、再度入るまでにリストの内容が変わっていれば当然static変数に保持したmsg_itと矛盾を生じるのでエラーになるというわけですが。
そのへんをチェックしてみることをお勧めします。
※私なら、関数内でstatic変数を用意しないで、CmdMessageクラスから常にバックログの情報を得るようにしますけど。
written by へにっくす

Marumi

Re: ノベルゲームのバックログ表示のバグ

#3

投稿記事 by Marumi » 10年前

ありがとうございます。
この関数を抜けたあと、messageの内容は全く変わっていません。
(一度この関数に入ると、以後はmessageオブジェクトを編集することは一切ありません)
また、ある条件下ではバックログの初期表示および上下ボタンを押したときに問題なく動作します。
状況としては、二度目にこの関数に入った時にエラーになる場合、ならない場合。
またエラーにならなくても、上ボタンを押下したあとにエラーになる場合、ならない場合があります。

static変数については、確かにあまり良くない書き方でしたね。
最終的にイテレータをCmdMessageクラスに保存するよう検討します。
へにっくす さんが書きました:掲示したソースをざっと見ると、msg_itに入れる対象はlog_msgsで、関数の引数CmdMessage* messageから取得したデータであることがわかりますが、この関数を抜けた後、messageの内容は不変ですか?
つまり、この関数を抜けた後、再度入るまでにリストの内容が変わっていれば当然矛盾を生じるのでエラーになるというわけですが。
そのへんをチェックしてみることをお勧めします。
※私なら、関数内でstatic変数を用意しないで、たとえばCmdMessageクラスから情報を得るようにしますけど。

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

Re: ノベルゲームのバックログ表示のバグ

#4

投稿記事 by h2so5 » 10年前

無駄に複雑に書きすぎだと思います。ログを表示するのに必要なのはログのリストと先頭からのオフセットだけですよね。
それなら毎回描画するときにオフセットからログの表示開始位置のイテレータを求めればいいわけで(std::advanceを使うかstd::vectorでランダムアクセスするか)、イテレータを保持しておく必要がないですよね。
イテレータを保持しておくという発想が間違いのもとだと思います。

Marumi

Re: ノベルゲームのバックログ表示のバグ

#5

投稿記事 by Marumi » 10年前

ありがとうございます。
確かに複雑になりすぎているとは思っています…
今回は、何件目のオブジェクトか、その中で何行目のメッセージから表示するか。という情報を
2つのイテレータをstaticにすることで保持しています。
(static変数が良いかどうかは置いておいて)staticで保持しておく情報はイテレータではなく
何件目・何行目という2つのint型変数にしておくと良い、ということなのでしょうか。
h2so5 さんが書きました:無駄に複雑に書きすぎだと思います。ログを表示するのに必要なのはログのリストと先頭からのオフセットだけですよね。
それなら毎回描画するときにオフセットからログの表示開始位置のイテレータを求めればいいわけで(std::advanceを使うかstd::vectorでランダムアクセスするか)、イテレータを保持しておく必要がないですよね。
イテレータを保持しておくという発想が間違いのもとだと思います。

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

Re: ノベルゲームのバックログ表示のバグ

#6

投稿記事 by h2so5 » 10年前

Marumi さんが書きました:ありがとうございます。
確かに複雑になりすぎているとは思っています…
今回は、何件目のオブジェクトか、その中で何行目のメッセージから表示するか。という情報を
2つのイテレータをstaticにすることで保持しています。
(static変数が良いかどうかは置いておいて)staticで保持しておく情報はイテレータではなく
何件目・何行目という2つのint型変数にしておくと良い、ということなのでしょうか。
intで管理したほうが便利です。listよりvectorのほうが良いかもしれません。
staticを使うのは止めましょう。シーンで管理しているならシーンのオブジェクトのメンバに保存しておけばいいはずです。

Marumi

Re: ノベルゲームのバックログ表示のバグ

#7

投稿記事 by Marumi » 10年前

イテレータの保存をやめ、intでの位置保存に変更したところ、エラーなく動作するようになりました。
listにするかvectorにするかは迷ったところですが、今後メッセージを途中に挿入する機能を追加することを考え
listを(とりあえず)採用しています。
ありがとうございました。
staticを使うのは、オブジェクト指向的ではないですね。CmdMessageのメンバ変数に変更したいと思います。
(オブジェクト指向的ではない、という理由以外にstaticが悪い理由があれば、教えていただければ幸いです)

h2so5様、へにっくす様、素早いアドバイスをありがとうございました。

閉鎖

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