龍神録サンプルにリプレイを追加

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

龍神録サンプルにリプレイを追加

#1

投稿記事 by とんとん » 3年前

とんとんです。
龍神録のサンプルにリプレイの機能を追加しようと思っています。
弾幕の動きがおかしくならないように、 SRand();の初期値は保存できるようにしましたが、
自機の動きの保存の仕方に困っています。
要はキー入力の状態を保存すればいいと思うのですが、
どのように保存すればいいのでしょうか?
私の想像している方法は
毎フレームの入力状態を記憶する方法ですが、
毎フレームずつ保存するのは相当なサイズになってしまう気がします。
ご教示下さい。宜しくお願いします。

初心者のため、説明が不十分かもしれませんが、お願いします。

hide

Re: 龍神録サンプルにリプレイを追加

#2

投稿記事 by hide » 3年前

使用していないボタンや使用頻度の低いボタンを毎フレーム記録するのは意味が薄いので、
ボタンを押しているか押していないかの変化したフレームを記録するといいです。
ボタン1が1フレーム目に変化、ボタン4が15フレーム目に変化。 みたいに。

考え方は圧縮技術と似たような感じですね。 http://qiita.com/yuba/items/55226c2de81436746a8c

アバター
Dixq (管理人)
管理人
記事: 1661
登録日時: 9年前
住所: 北海道札幌市
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#3

投稿記事 by Dixq (管理人) » 3年前

この辺のアルゴリズムは昔こだわったことが
ありますが、こだわりすぎるとバグの元なので、
素直に安直に全部のキー状態を記憶しzip圧縮した方が正確です。
キーの入力状態はほとんど変わらないので
zip圧縮かけるとかなり小さくなります。
今の時代インターネット回線が高速なので軽さより
デバッグのしやすさを重視して良いと思います。

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#4

投稿記事 by ISLe » 3年前

ユーザー操作がDXライブラリのGetJoypadInputStateが返す値のように加工されていれば楽なんですけどね。
XORを取れば変化したかどうか簡単に判定できますし。

とんとん
記事: 33
登録日時: 4年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#5

投稿記事 by とんとん » 3年前

質問掲示板に書いたことを忘れて自分ひとりで出るわけ無い答えを考えていました(涙)
ありがとうございます。zip圧縮は思いつかなかったのでありがたいです。
ですが、東方桃源宮 ~ Riverbed Soul Saver. さまのリプレイの保存を見てみると、
datフォルダになんらかしてあるように思います。確かに170KB~205KBと
普通よりは沢山のメモリーを使う(このぐらいが普通ですかね?)のですが、
datフォルダでリプレイ保存をしようと思っています。
その場合、たとえばどのような方法があるのでしょうか。
上記のゲームのdat内を見ると暗号化されていてマクロ変数だとは思うのですが、
何カウント目にZキーが変化、、という感じに保存ということでいいのでしょうか?
(説明不足でしたらゴメンナサイ)

アバター
Dixq (管理人)
管理人
記事: 1661
登録日時: 9年前
住所: 北海道札幌市
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#6

投稿記事 by Dixq (管理人) » 3年前

私が容量削減に取り組んだ頃は最小で数[kb]まで小さくできました。
方法は既に出ているように「何フレーム目に何キーが変化した」という情報をファイルに保存する方法です。

> datフォルダでリプレイ保存をしようと思っています。

この意味がよく分からないのですが、ImageやSoundデータがあるアーカイブとおなじにしたいということでしょうか?

> 上記のゲームのdat内を見ると暗号化されていて

ただのバイナリファイルじゃなくて暗号化してあるんですか?

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#7

投稿記事 by ISLe » 3年前

サンプルプログラム作ってみました。
カーソルキーで箱が上下左右に動く操作の記録と再生をします。
位置はリセットしないので箱を元の場所に戻さないと再生するたびにズレていきます。

入力データが変化したとき記録します。
記録は1要素あたり、フレーム数と入力データを1バイトずつにしてます。
なので入力データが変化せず256フレームを超える場合も記録します。
ゲームで(60FPSで)4秒以上操作しないというのはレアケースと思うので無駄は少ないはずです。

コード:

#include "DxLib.h"
#include <stdint.h>

const int MODE_REC = 0;
const int MODE_PLAY = 1;

const int STATE_UP    = (1<<0);
const int STATE_DOWN  = (1<<1);
const int STATE_LEFT  = (1<<2);
const int STATE_RIGHT = (1<<3);

const int PLAYDATA_NUM = 256;

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	ChangeWindowMode(TRUE);
	if (DxLib_Init() != 0) return 0;
	SetDrawScreen(DX_SCREEN_BACK);

	int x = 640/2;
	int y = 480/2;

	int mode = MODE_REC;
	uint8_t play_state[PLAYDATA_NUM];
	uint8_t play_count[PLAYDATA_NUM];
	int playdata_num = 0; // 記録数
	int playdata_idx = 0; // 再生位置
	int count = 0;

	uint8_t state_last = 0, state_prev = 0;
	while (ProcessMessage() == 0 && ScreenFlip() == 0 && ClearDrawScreen() == 0)
	{
		// Pキーで再生開始
		if (CheckHitKey(KEY_INPUT_P)) {
			playdata_idx = 0;
			count = 0;
			mode = MODE_PLAY;
		}
		// Rキーで記録開始
		if (CheckHitKey(KEY_INPUT_R)) {
			playdata_num = 0;
			count = 0;
			mode = MODE_REC;
		}

		// プレイヤーの入力
		state_last = 0;
		if (CheckHitKey(KEY_INPUT_UP))    state_last |= STATE_UP;
		if (CheckHitKey(KEY_INPUT_DOWN))  state_last |= STATE_DOWN;
		if (CheckHitKey(KEY_INPUT_LEFT))  state_last |= STATE_LEFT;
		if (CheckHitKey(KEY_INPUT_RIGHT)) state_last |= STATE_RIGHT;

		// 記録モード
		if (mode == MODE_REC) {
			if (playdata_num < PLAYDATA_NUM) {
				count++;
				if (count >= 256 || state_prev ^ state_last) {
					// フレーム数が256になるか入力が変化したとき
					if (count >= 256) count = 0;
					play_state[playdata_num] = state_prev;
					play_count[playdata_num] = count;
					playdata_num++;
					count = 0;
					state_prev = state_last;
				}
			}
		}
		// 再生モード
		if (mode == MODE_PLAY) {
			state_last = 0;
			if (playdata_idx < playdata_num) {
				state_last = play_state[playdata_idx]; // プレイヤーの入力を上書き
				if (count <= 0) {
					count = play_count[playdata_idx];
					if (count == 0) count = 256;
				}
				if (--count <= 0) playdata_idx++;
			}
		}

		// 実際に箱を動かす
		if (state_last & STATE_UP)    y--;
		if (state_last & STATE_DOWN)  y++;
		if (state_last & STATE_LEFT)  x--;
		if (state_last & STATE_RIGHT) x++;

		// 箱を描く
		DrawBox(x-16, y-16, x+32, y+32, GetColor(255,255,255), TRUE);

		DrawString(0, 10, "Rキーで最初から記録 Pキーで最初から再生", GetColor(192,192,192));
		if (mode == MODE_REC) {
			if (playdata_num < PLAYDATA_NUM) {
				DrawFormatString(0, 30, GetColor(192,192,192), "記録中(%d)", playdata_num);
			} else {
				DrawString(0, 30, "記録満杯", GetColor(192,192,192));
			}
		}
		if (mode == MODE_PLAY) {
			if (playdata_idx < playdata_num) {
				DrawFormatString(0, 30, GetColor(192,192,192), "再生中(%d)", playdata_idx);
			} else {
				DrawString(0, 30, "再生終了", GetColor(192,192,192));
			}
		}
		DrawFormatString(0, 50, GetColor(192,192,192), "(%d,%d)", x, y);
	}
	
	DxLib_End();
	return 0;
}

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#8

投稿記事 by ISLe » 3年前

No.7のコードですが

70行目は無いほうが、再生が終わった後に自由に動かせるので使い勝手が良い
#なので23行目で設定してる初期モードは再生モードのほうが良い

256フレームを特別扱いするのではなく、全体に1減らして記録するほうがコードがシンプルになる

というのにあとから気付きました。

とんとん
記事: 33
登録日時: 4年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#9

投稿記事 by とんとん » 3年前

ふむふむ。ありがとうございます。
色々とこういう方法があったかと思うことがあり、
これからのいろいろなことに役立てられそうです。
また、管理人さんのdatのことですが、
リプレイ保存時にそのリプレイのデータを書き込んだファイルを生成する、
というように書けばよかったかなと思っております。
また、暗号化のことですが、サクラエディタで開いたので
おそらくバイナリエディタなのかなと思います。確かにここは暗号化しなくても
いい気がしてきました。
また、 ISLeさんにサンプルコードを作っていただいてから思ったのですが、
私はサウスさんの支館でサウンドノベルを作っているのです。
なので、そこをどうリプレイに保存するのか、少しバグりそうという風に思っています。
せりふを読んだりする時間で4秒は超えるかもしれませんし、
かなりな容量になりそうなので、そこを修正して私のゲームで使えるように
していきたいと思います。
ダラダラ書きましたが、管理人さんにお答えいただいたところから、
リプレイデータをバイナリエディタで書いたとして、
そこから暗号化は必要でしょうか?
少しゲームバランスのようなところが入っていますので、
意見としてお伺いしたいです。(サンプルコードを更に作っていただけるとまたありがたいです。)
まだ初心者で意外なところで相当なメモリーを使っていたり、
コードの書き方がまだ下手な私ですが、宜しくお願いします。
コードについて、変更の仕方を思いついたときにまたお聞きしたいです。(暫くコードと睨めっこしてきます。)

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#10

投稿記事 by ISLe » 3年前

龍神録の館のサンプルの話ではなかったのですか。

No.7のサンプルは256フレームを超えると同じ入力データを重複して記録するというだけでバグる(再生できなくなる)わけではありません。
カウントデータのサイズを32ビットにすれば、2年くらい触らなくても重複しないようになりますよ。

動かしてみれば512バイトのデータだとどういう動きがどのくらいの長さ記録再生できるかってのが感覚的に分かると思うんですよ。
高橋名人並みにボタンを連打するのでなければけっこう長い時間記録できると感じてもらえる気がしたんですけどね。
最近はシューティングゲームでもボタンを連打するようなのは珍しくなってますし。
そのままコンパイルして動くコードなのですが興味を持ってはもらえなかったようですね。

サウンドノベル的なものは操作を正確にリプレイする必要があるのでしょうか。
自動先送り機能と同じふうにリプレイ中は一定時間で勝手に進むとかで良い気しますが。

とんとん
記事: 33
登録日時: 4年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#11

投稿記事 by とんとん » 3年前

こんばんは。おかしい日本語でごめんなさい。
前の返信は、龍神録サンプルに色々と変更をして、
その書き加えたコードの中に支館のコードもあるということです。
せりふを読むときは4秒以上動かさないときがあるというのは間違いないと思いますが、
コードの話では少しおかしい書きかたがあったので訂正させていただきます。
すみませんでした。

とんとん
記事: 33
登録日時: 4年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#12

投稿記事 by とんとん » 3年前

連続で書いてすみません。
書いていただいたコードを詳しく見てきて、
私のプログラムに加えてもいい気がしてきました。
初心者で物分りの悪い私です。
会話文のリプレイですが、イメージとしては東方のリプレイのようにしようと思っています。
なので、会話文中でのキー入力情報が必要だったのです。ですが、
それについても書いていただいたコードを使っても問題なかったので、
後は私のプログラムに取り込むのに必要な修正をしてお借りしようと思います。
ありがとうございました。

ISLe
記事: 2645
登録日時: 9年前
連絡を取る:

Re: 龍神録サンプルにリプレイを追加

#13

投稿記事 by ISLe » 3年前

東方はプレイしたことがないのですが
会話シーンをスキップしたりするのもリプレイに記録されるのでしょうか。
だとしたら、一気にクリアしてあとから会話シーンをゆっくり見るといった使い方はできないのですね。

No.7のサンプルコードは、No.4で書いた、加工しておくと楽、というのを示すのが重要な点で
それがリプレイの実装にも応用が利くということを表しています。

周辺機器からプレイヤーの入力を読み取る前半と、実際にゲーム内に反映させる後半をきっちり分けておけば
リプレイの実装はそのあいだに挟むだけでよく、取り外しも簡単なのです。
例えばzlibの関数を使ってリプレイデータをリアルタイムに圧縮したりしても他の部分に影響を与えません。

加工しておくと楽、というのはNo.7のサンプルコードのように加工しろ、という話ではなく
そのゲームプログラムにとって適切な形というのがあります。

会話シーンは会話シーンで、適切なリプレイデータの形というのがありますから
シューティングシーンに合わせたリプレイの実装を会話シーンに使えば無理が生じるのではないでしょうかね。

例えばオプションで会話シーンの文字単位の表示速度を変えれるようにしたりするとズレますよね。
ユーザーにとっての改善が開発者の手間になって返るのでは、ユーザーを喜ばせようという意識が疎かになりはしないでしょうか。

コードの中身以上に、
ほんのひと手間入れて構成を工夫するだけでそれ以降余分な手間がかからなくなる、
という点に注目してほしいですね。

閉鎖

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