リプレイの実装について

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

リプレイの実装について

#1

投稿記事 by ラグ » 16年前

いつもお世話になっております。

龍神録を改造してリプレイを実装しようと思いました。
ttp://www.play21.jp/board/formz.cgi?action=res&resno=22359&page=&lognum=70&id=dixq&rln=22441
などを参考に下記のように組んでみたのですが、いまいちうまく動きません。


まず構造体など。
replay構造体に、録画時に選択した難易度・機体などといった情報を記録します。
replay_key[REPLAY_MAX_TIME]に毎フレームのキー情報を記録します。REPLAY_MAX_TIMEは適当な値を入れておきます。
//リプレイに関する構造体
typedef struct{
	int key;	//キー保存用
	int mode;	//リプレイモード
	int cnt;	//カウント
	int difficulty;//難易度
	int life,bom_number;//初期ライフ、初期ボム数
	int bomknd,charflag,mshotknd;	//ボム種類、キャラ種類、メインショット種類
	int seed;	//乱数の種
}replay_t;

GLOBAL replay_t replay;//リプレイ構造体
GLOBAL unsigned char replay_key[REPLAY_MAX_TIME];	//リプレイキー情報
次にリプレイの録画・再生の関数です。
これをメインループで呼び、reply.cntをインクリメントして毎フレーム毎にreplay_key[replay.cnt]に録画・再生します。
replay.modeが0で録画、1で再生になります。
void replay_main(int mode){
	switch(mode){
		case 0://録画時
			replay_key[replay.cnt] = 0;
			if(CheckStatePad(configpad.left)>0)		replay_key[replay.cnt] |= 0X01;
			if(CheckStatePad(configpad.up)>0)		replay_key[replay.cnt] |= 0X02;
			if(CheckStatePad(configpad.right)>0)	        replay_key[replay.cnt] |= 0X04;
			if(CheckStatePad(configpad.down)>0)		replay_key[replay.cnt] |= 0X08;
			if(CheckStatePad(configpad.shot)>0)		replay_key[replay.cnt] |= 0X10;
			if(CheckStatePad(configpad.bom)>0)		replay_key[replay.cnt] |= 0X20;
			if(CheckStatePad(configpad.slow)>0)		replay_key[replay.cnt] |= 0X40;
			break;
		case 1://再生時
			replay.key=replay_key[replay.cnt];
			break;
		default:
			break;
	}
}
ゲーム中に使うボタンは上下左右+ショット+ボム+低速(あとスタートボタン)だけですので、replay_key配列をunsigned char型にし、
各ボタンを押すとそれぞれのビットを立てて、それを記録すればよいのではないかと考えました。
続いてファイルへの書き込み・読み込みです。
char *g_replay = "../dat/replay/replay_data.dat"; // replay_data.dat(リプレイファイル)
FILE *rp;

void replay_load(){
	if( ( rp = fopen( g_replay, "rb" ) ) == NULL ) {
			printf("ファイルオープンエラー\n");
		}
		else{
			//ファイルからデータを読み込む
			fread( &replay, sizeof(replay_t), 1, rp ) ;
			fclose( rp );//解放
		}
}

void replay_save(){
	if((rp = fopen( g_replay, "wb" )) == NULL){
		printf("ファイルオープンエラー\n");
		}
	else{
		fwrite( &replay, sizeof(replay_t), 1, rp );
		fclose( rp );
		}
}

char *k_replay = "../dat/replay/replay.dat"; // replay.dat(リプレイキーファイル)
FILE *kp;

void replaykey_load(){
	if( ( kp = fopen( k_replay, "rb" ) ) == NULL ) {
			printf("ファイルオープンエラー\n");
		}
		else{
			//ファイルからデータを読み込む
			fread( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp ) ;
			fclose( kp );//解放
		}
}

void replaykey_save(){
	if((kp = fopen( k_replay, "wb" )) == NULL){
		printf("ファイルオープンエラー\n");
		}
	else{
		fwrite( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp );
		fclose( kp );
		}
}
この方法だとファイルが二つ必要になりますが、他にうまい方法を見つけられませんでした。
ゲーム開始前に難易度や機体種類などの情報をreplay構造体にそれぞれ入れておき、ゲーム(STG部分)終了時にreplay_save()とreplaykey_save()を呼び出して記録します。
(長すぎると怒られたので次に続きます)

ラグ

Re:リプレイの実装について

#2

投稿記事 by ラグ » 16年前

(続きです)
そして、リプレイ再生時の初期化の際にreplay_load()とreplaykey_load()を呼び出します。
再生時はreplay_main(int mode)内でreplay_key[replay.cnt]のキー情報をreplay.keyに毎フレーム渡します。
それから、龍神録プログラミングの館8章のGetHitPadStateAll()にリプレイ再生時の場合を追加します。
void GetHitPadStateAll(){
        int i,PadInput,mul=1;
	if(replay.mode==1 ){//リプレイの再生のフラグが立っている場合は
		PadInput = replay.key;
			
		for(i=0;i<16;i++){
			if(PadInput & mul)  pad.key++;
			else                pad.key=0;
			mul*=2;
		}
			
		if(PadInput & 0X01)	pad.key[configpad.left];
		if(PadInput & 0X02)	pad.key[configpad.up];
		if(PadInput & 0X04)	pad.key[configpad.right];
		if(PadInput & 0X08)	pad.key[configpad.down];
		if(PadInput & 0X10)	pad.key[configpad.shot];
		if(PadInput & 0X20)	pad.key[configpad.bom];
		if(PadInput & 0X40)	pad.key[configpad.slow];
					
		return;//リプレイの場合ここまで
	}

	if(replay.mode==0){
       		PadInput = GetJoypadInputState( DX_INPUT_PAD1 );//パッドの入力状態を取得
       		for(i=0;i<16;i++){
                	if(PadInput & mul)  pad.key++;
                	else                pad.key=0;
                	mul*=2;
        	}
        	input_pad_or_key(&pad.key[configpad.left]   ,CheckStateKey(KEY_INPUT_LEFT    ));
        	input_pad_or_key(&pad.key[configpad.up]     ,CheckStateKey(KEY_INPUT_UP      ));
        	input_pad_or_key(&pad.key[configpad.right]  ,CheckStateKey(KEY_INPUT_RIGHT   ));
        	input_pad_or_key(&pad.key[configpad.down]   ,CheckStateKey(KEY_INPUT_DOWN    ));
        	input_pad_or_key(&pad.key[configpad.shot]   ,CheckStateKey(KEY_INPUT_Z       ));
        	input_pad_or_key(&pad.key[configpad.bom]    ,CheckStateKey(KEY_INPUT_X       ));
        	input_pad_or_key(&pad.key[configpad.slow]   ,CheckStateKey(KEY_INPUT_LSHIFT  ));
        	input_pad_or_key(&pad.key[configpad.start]  ,CheckStateKey(KEY_INPUT_ESCAPE  ));
        	input_pad_or_key(&pad.key[configpad.change] ,CheckStateKey(KEY_INPUT_LCONTROL));
	}
}


実は、replay_key配列をint型にし、録画時にreplay_key[replay.cnt]=GetJoypadInputState(DX_INPUT_KEY_PAD1);として、
再生時にはGetHitPadStateAll()内でif(replay.mode==1)PadInput = replay.keyとすることで
録画・再生には一応成功しています(怪しい動きも多いですが)。
しかしファイル容量が大きすぎるため、型をunsigned charにして出来ないか色々試しましたが、
うまくいきませんでした。
キー情報を記録する配列をunsugned char型のまま、うまくリプレイを録画・再生出来ないでしょうか。
他にも何かありましたら、是非お教えください。
よろしくお願いします。
環境はXP SP3、VC++2008EE、DXライブラリを使用しています。

Justy

Re:リプレイの実装について

#3

投稿記事 by Justy » 16年前


>この方法だとファイルが二つ必要になりますが、

[color=#d0d0ff" face="monospace]
fwrite( &replay, sizeof(replay_t), 1, rp );
fwrite( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp );
[/color]

で書き込み、
[color=#d0d0ff" face="monospace]
fread( &replay, sizeof(replay_t), 1, rp ) ;
fread( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp )
[/color]

で読み込みとすればファイルは1つで済みます。




>うまくリプレイを録画・再生出来ないでしょうか

 よくわからないところがあるのですが、 GetHitPadStateAll() [color=sans-serif]replay.mode==1[/font] のとき、
[color=#d0d0ff" face="sans-serif]if(PadInput & 0X01) pad.key[configpad.left];[/color] は何をしているのでしょうか?

ラグ

Re:リプレイの実装について

#4

投稿記事 by ラグ » 16年前

>Justyさん

>
> >この方法だとファイルが二つ必要になりますが、
>

> [color=#d0d0ff" face="monospace]
> fwrite( &replay, sizeof(replay_t), 1, rp );
> fwrite( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp );
> [/color]

> で書き込み、
> [color=#d0d0ff" face="monospace]
> fread( &replay, sizeof(replay_t), 1, rp ) ;
> fread( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp )
> [/color]

> で読み込みとすればファイルは1つで済みます。

すみません、Justyさんが書いて下さったこのコードですが、私が書いたのと同じように見えます。
不勉強なのでよくわからないのですが、これは例えば、
char *g_replay = "../dat/replay/replay_data.dat"; // replay_data.dat(リプレイファイル)
FILE *rp;
char *k_replay = "../dat/replay/replay.dat"; // replay.dat(リプレイファイル)
FILE *kp;

void replay_load(){
	//ファイルからデータを読み込む
	fread( &replay, sizeof(replay_t), 1, rp ) ;
	fread( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp ) ;
	fclose( rp );//解放
	fclose( kp );//解放
}

void replay_save(){
	fwrite( &replay, sizeof(replay_t), 1, rp );
	fwrite( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, kp );
	fclose( rp );
	fclose( kp );
}
のように、同じ関数内で書き込みもしくは読み込みを行えばよいということでしょうか。
調べてみたのですがやはりわからず…申し訳ないのですが、もっと教えて頂けないでしょうか。


>
> >うまくリプレイを録画・再生出来ないでしょうか
>

>  よくわからないところがあるのですが、 GetHitPadStateAll() [color=sans-serif]replay.mode==1[/font] のとき、
> [color=#d0d0ff" face="sans-serif]if(PadInput & 0X01) pad.key[configpad.left];[/color] は何をしているのでしょうか?
>

ここについては、まずreplay.mode==0以下の
[color=#d0d0ff" face="sans-serif]input_pad_or_key(&pad.key[configpad.left] ,CheckStateKey(KEY_INPUT_LEFT ));[/color]
において、input_pad_or_keyという関数がありまして、これを見てみると
//引数1と引数2のうち大きい方を引数1に代入する
void input_pad_or_key(int *p, int k){
        *p = *p>k ? *p : k;
}

と書かれていました。
なので、結局のところキー情報をpad.key[/url]内に入れれば動いてくれるものと考えました。
また、リプレイ中はパッドやキーボードを触っても、リプレイ中の自機がそれにつられて動いたりしないようにしたいので、 [color=#d0d0ff" face="sans-serif]if(PadInput & 0X01) pad.key[configpad.left];[/color]
においては、例えばPadInputの第1ビットが立っているなら左に動いて欲しいので、 [color=#d0d0ff" face="sans-serif]if(PadInput & 0X01) pad.key[configpad.left];[/color]
としました。

ちなみに、同じKey.cpp内でpad構造体が宣言されております。
//パッドに関する構造体
typedef struct{
        int key[PAD_MAX];
}pad_t;
PAD_MAXは16になっています。
お手数をおかけしますが、よろしくお願いします。

Justy

Re:リプレイの実装について

#5

投稿記事 by Justy » 16年前


>同じ関数内で書き込みもしくは読み込みを行えばよいということでしょうか

 あー、そのままコピペしてたんで少し間違っていました。
 同じ関数内で、というのはその通りですが、FILEポインタは1つです。

[color=#d0d0ff" face="monospace]
static const char replayFilePath[/url] = "../dat/replay/replay.dat";
void replay_load()
{
FILE *fp = fopen( replayFilePath, "rb" );
if(!fp) return;

fread( &replay, sizeof(replay_t), 1, fp ) ;
fread( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, fp ) ;
fclose( fp );
}

void replay_save()
{
FILE *fp = fopen( replayFilePath, "wb" );
if(!fp) return;

fwrite( &replay, sizeof(replay_t), 1, fp );
fwrite( replay_key, sizeof(unsigned char), REPLAY_MAX_TIME, fp );
fclose( fp );
}
[/color]

ってところでしょうか。



if(PadInput & 0X01) pad.key[configpad.left];

 この行をよくみてください。
 これは if文が真だったとき、関数を呼び出しているわけでもなく、pad.key[/url]の中身を
書き換えているわけでもなく、だた配列があるだけです。

 別の例でいうと
[color=#d0d0ff" face="monospace]
int a[2];
a[0];
a[1];
[/color]

としているようなもので、全く意味がありません。


 龍神録はあまり詳しくはないのですが、リプレイ情報を受けた PadInputから pad.keyに情報を反映するなら
[color=#d0d0ff" face="monospace]
if(PadInput & 0x01) pad.key[configpad.left]++;
else pad.key[configpad.left] = 0;
[/color]

のようにしないと正しく反映されないと思われます。


 ついでにいえば、
[color=#d0d0ff" face="monospace]
for(i=0;i<16;i++){
if(PadInput & mul) pad.key++;
else pad.key=0;
mul*=2;
}
[/color]

このあたりの処理もリプレイ時はまずそうです。

 ここでの pad.key[/url]への操作はコンフィグ情報を一切考慮していませんし、
同じことをすぐ下のところでやろうとしているはずです(今はうまく動いていませんが)。

 このあたり処理を見直して見た方がいいかと思います。


 とはいえ、これだけで unsugned char型の時うまくリプレイを録画・再生できない現象が直る、
とはいえないので、まずはこれらを直してみて、それでもうまくいかないようでしたら
その時のコードと、どううまくいかなかったのか等を書いて再質問をしてください。

ラグ

Re:リプレイの実装について

#6

投稿記事 by ラグ » 16年前

>Justyさん

結論から言いますと、Justyさんの教えてくださった通りに書いたところ、やりたかったように動いてくれました。
Justyさん、ありがとうございます。

まず、ファイルは無事ひとつにまとめることが出来ました。
こんな方法があるとは知らず、とても勉強になりました。

次に、Key.cpp内のvoid GetHitPadStateAll()は下のようになりました。
func_stateというのはメインループ内でゲームの状態を判別するために使用されており、ゲーム(STG部分)メインが100、一時停止が101、コンティニュー状態が102になっています。
void GetHitPadStateAll(){
	int i,PadInput,mul=1;
	int PadInput2;

	if(replay.mode==1 &&
		(func_state!=101 || func_state!=102)){//リプレイの再生のフラグが立っている場合は
		PadInput = replay.key;
		PadInput2= GetJoypadInputState( DX_INPUT_PAD1 );
			
		if(PadInput & 0X01)	pad.key[configpad.left]++;	else pad.key[configpad.left]=0;
		if(PadInput & 0X02)	pad.key[configpad.up]++;	else pad.key[configpad.up]=0;
		if(PadInput & 0X04)	pad.key[configpad.right]++;	else pad.key[configpad.right]=0;
		if(PadInput & 0X08)	pad.key[configpad.down]++;	else pad.key[configpad.down]=0;
		if(PadInput & 0X10)	pad.key[configpad.shot]++;	else pad.key[configpad.shot]=0;
		if(PadInput & 0X20)	pad.key[configpad.bom]++;	else pad.key[configpad.bom]=0;
		if(PadInput & 0X40)	pad.key[configpad.slow]++;	else pad.key[configpad.slow]=0;
					
		if(PadInput2 & 128)	pad.key[configpad.start]++;
		else				pad.key[configpad.start]=0;
		input_pad_or_key(&pad.key[configpad.start]  ,CheckStateKey(KEY_INPUT_ESCAPE  ));
			
	}

	if(replay.mode==0 ||
		(replay.mode==1 && func_state==101)){
        PadInput = GetJoypadInputState( DX_INPUT_PAD1 );//パッドの入力状態を取得
        for(i=0;i<16;i++){
                if(PadInput & mul)  pad.key++;
                else                pad.key=0;
                mul*=2;
        }
        input_pad_or_key(&pad.key[configpad.left]   ,CheckStateKey(KEY_INPUT_LEFT    ));
        input_pad_or_key(&pad.key[configpad.up]     ,CheckStateKey(KEY_INPUT_UP      ));
        input_pad_or_key(&pad.key[configpad.right]  ,CheckStateKey(KEY_INPUT_RIGHT   ));
        input_pad_or_key(&pad.key[configpad.down]   ,CheckStateKey(KEY_INPUT_DOWN    ));
        input_pad_or_key(&pad.key[configpad.shot]   ,CheckStateKey(KEY_INPUT_Z       ));
        input_pad_or_key(&pad.key[configpad.bom]    ,CheckStateKey(KEY_INPUT_X       ));
        input_pad_or_key(&pad.key[configpad.slow]   ,CheckStateKey(KEY_INPUT_LSHIFT  ));
        input_pad_or_key(&pad.key[configpad.start]  ,CheckStateKey(KEY_INPUT_ESCAPE  ));
        input_pad_or_key(&pad.key[configpad.change] ,CheckStateKey(KEY_INPUT_LCONTROL));
	}
}


Justyさんのご指摘通り、
if(PadInput & 0X01) pad.key[configpad.left];
のような箇所を
if(PadInput & 0X01) pad.key[configpad.left]++; else pad.key[configpad.left]=0;
といったように書き換えたところ、うまくいきました。
pad.key[/url]に値を入れなければいけなかったようです。
また、リプレイ再生中にも一時停止が出来るよう、
PadInput2= GetJoypadInputState( DX_INPUT_PAD1 );

if(PadInput2 & 128)	pad.key[configpad.start]++;
else			pad.key[configpad.start]=0;
input_pad_or_key(&pad.key[configpad.start]  ,CheckStateKey(KEY_INPUT_ESCAPE  ));

という処理をreplay.mode==1のときに追加しました。
これでリプレイ再生中(STG部分)はパッドのスタートボタンとキーボードのEsc以外に反応しなくなり、
その上で一時停止中はちゃんとパッドやキーボードに反応するようになりました。

時々リプレイがずれたり、他にも実装したいリプレイ機能はありますが、これでとりあえずはちゃんと録画・再生されるリプレイが出来ました。
一人で考えていたら全く出来なかったかもしれません。
Justyさんには重ねて御礼申し上げます。
本当にありがとうございました。
これからも勉強していきたいと思います。

閉鎖

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