ミリ秒単位での入力

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

ミリ秒単位での入力

#1

投稿記事 by 玄米 » 12年前

初めまして。
早速ですが質問させていただきます。

現在、管理人様の太鼓の鉄人を参考に音ゲーを制作しております。
譜面データ読み込みとそれに合わせた音符画像を流すところまでは上手くいきました。
次に、音符の位置をミリ秒単位で管理し譜面がスタートする時間とキーの入力された時間を基に判定を出そうと考えていますが、
太鼓の鉄人ではフレーム毎に入力と判定の計算をしているようで、
そのまま流用してもBPMの速い曲の24分音符などはキー入力が処理に追いつかずテストプレイで支障が出てしまいました。
フレーム単位より細かいミリ秒単位でキー入力を検出するにはどうすれば良いでしょうか。

C言語とDXライブラリに関してはこのサイトで一通り勉強しただけでまだ初心者です。
環境はWindows7 VisualStudio2010です。

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

Re: ミリ秒単位での入力

#2

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

Windowのキー入力は設定で反応速度を変えれると思いますが、これを変えたら大丈夫だったりしないでしょうか?
「キーボード入力の反応を速くし入力速度を速くする方法 - Windows高速化への道」
http://www003.upp.so-net.ne.jp/shigeri/ ... input.html

【補足】
ちなみに1フレームは1/60秒で16.6666・・・msです。
人間工学的には、秒間30連射が出来るとも思えません。高橋名人でも最高記録は16連射程度です。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

玄米

Re: ミリ秒単位での入力

#3

投稿記事 by 玄米 » 12年前

試してみましたが変わりませんでした・・・

確かにBPM250の24分音符でも間隔は33.3ミリ秒ですが、
細かくても16.67ミリ秒単位でしか入力できないのはプレイ中に数ミリ秒単位での微調整が効かず厳しいと思うのですがどうでしょうか。
私の認識が間違っていましたら申し訳ありません。

とりあえず入力だけでも240fpsや120fpsで処理したいのですが実際に可能なのでしょうか?

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

Re: ミリ秒単位での入力

#4

投稿記事 by h2so5 » 12年前

判定処理と描画処理のスレッドを分離すれば異なる速度で動作させることは可能です。
ただ、DXライブラリでどこまで可能なのかはよく分かりません。

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

Re: ミリ秒単位での入力

#5

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

描画は1/60を超えられませんが処理だけを120fpsや240fpsにすることは出来ます。
自分でfpsを制御して、描画処理を2回に1回とかで描画だけを60fpsになるように調整してやることです。

ただ、Windows自体のキー入力などのOSシステムの動作が遅いと思うので効果があるか良く分かりません。
それとtimeBeginPeriod (1);でSleep()などの精度を成り行き上は上げる事はできますが、キーまで変わるのか確認したことはありません。
物理的にはチャタリング問題があるので一般的なキーボードは、そんな反応速度を持っていないはずです。
ちなみにtimeBeginPeriod(1);しないと100fpsは超えられません。

【補足】
どちらにしても、うまく動かないのは1/60秒のfpsが原因であることはログなどを記録して事前確認する必要があります。
それは、120fpsでも動作するか確認のために必要な機能だと思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

玄米

Re: ミリ秒単位での入力

#6

投稿記事 by 玄米 » 12年前

原因がfpsにあるか他にあるかは確認してみます。
もしfpsが原因ならば解決するのは今の私の知識では難しそうなので
まずは遊べる範囲でゲームを完成させてから勉強しようと思います。
回答ありがとうございました。

qwerty

Re: ミリ秒単位での入力

#7

投稿記事 by qwerty » 12年前

情報共有程度ですが・・・

以前タイピングゲームを作ったことがありますが、
結論として、DXライブラリでも、
人間が入力していて違和感を感じない程度には処理できますよ。
やり方はどなたかに聞いてください。


>>softya(ソフト屋)へ
私は高速タイピングを趣味としているのですが、
少し情報共有程度にお話し申し上げます。

>ちなみに1フレームは1/60秒で16.6666・・・msです。
>人間工学的には、秒間30連射が出来るとも思えません。高橋名人でも最高記録は16連射程度です。

に関してですが、おっしゃっているのは同一キーの連射であって、
タイピングのように異なるキーを押下する場合の速度は、
界隈では秒間30打鍵程度いきます。
平均20打鍵、ピーク時は秒速で30打鍵程度行きます。

それと、秒間30打鍵というのは、1つ1つのキーの押下が0.05秒にも満たないわずかな時間ですが、
人間というものは不思議なもので、そのわずかな時間を「違和感」としてとらえることができます。
音ゲーを極めたり、タイムアタックをする人はわりとはっきりとわかるはずです。
なので、よりよいゲームを作ろうと思うと、そこは考慮しなければなりません。
(ちなみに個人的な感覚で申し上げるとするならば、人間の何かしらの入力に対し、その道を極めた人間がまったく違和感を
感じない程度にしたいなら、そのレスポンスは0.01秒以下でないと十分でないと感じられます。
神経学的にどうとかいうわけでなく、あくまで個人的な感覚ですがね。)

あと、

>物理的にはチャタリング問題があるので一般的なキーボードは、そんな反応速度を持っていないはずです。

ここら辺はチャタリングが何たるかを誤解されているものと思われます。

ということで、今回の件とはだいぶ逸脱した内容かもしれませんが、
情報共有程度ということで、ご参考になさってください。

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

Re: ミリ秒単位での入力

#8

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

たしかに同じキー前提で私は書いていますね。
違うキーであればチャタリング問題は無いので、私も人間の指の速度の限界まで早くすることは出来ると思います。

>(ちなみに個人的な感覚で申し上げるとするならば、人間の何かしらの入力に対し、その道を極めた人間がまったく違和感を
>感じない程度にしたいなら、そのレスポンスは0.01秒以下でないと十分でないと感じられます。
>神経学的にどうとかいうわけでなく、あくまで個人的な感覚ですがね。)

私の感覚ではバーチャファイターなど格闘ゲームは1/60フレームで最速の操作が行われていますので、そのズレの違和感までは何となく感覚として理解出来ます。
0.01秒に関してはすべての人が同じ感覚で操作できるかは分かりませんが、それぐらいの知感力がある人がいても不思議ではありません。

ただ、システム的にWidnowsはゲームセンターの機械と違いリアルタイム動作するOSではないので0.01秒の世界以前に限界は有ると思っております。
書き込み有難う御座いました。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
みけCAT
記事: 6734
登録日時: 14年前
住所: 千葉県
連絡を取る:

Re: ミリ秒単位での入力

#9

投稿記事 by みけCAT » 12年前

softya(ソフト屋) さんが書きました:ただ、システム的にWidnowsはゲームセンターの機械と違いリアルタイム動作するOSではないので0.01秒の世界以前に限界は有ると思っております。
Jubeatとか、ゲームセンターの機械にもWindowsが使われていることがあると聞きましたが…
ごめんなさい。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: ミリ秒単位での入力

#10

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

みけCAT さんが書きました:
softya(ソフト屋) さんが書きました:ただ、システム的にWidnowsはゲームセンターの機械と違いリアルタイム動作するOSではないので0.01秒の世界以前に限界は有ると思っております。
Jubeatとか、ゲームセンターの機械にもWindowsが使われていることがあると聞きましたが…
ごめんなさい。
ゲームセンターで使われるのは組み込み用のWindows Embedded だと思いますのでカスタマイズがある程度効くと思います。
あと接続される外部機器は自由に設計できるのでチャタリングなどの時間問題も短くシビアに設計できますね。

こういう拡張も有るようです。
「汎用OSにリアルタイム性を付加するアプローチ:Windowsのリアルタイム拡張「RTX」と組み込み機器への応用 (1/3) - MONOist(モノイスト)」
http://monoist.atmarkit.co.jp/mn/articl ... ws001.html

【補足】
実際に今のWindows(XPから8)でプロセス処理が実際にどの程度の周期で回すことが可能かとか、キーボードの反応速度の限界は?
あたりを実測してみないとなんとも言えないのですが。
周期自体は200fpsとかは余裕で出ると思うのですが、キーボードの反応速度はWindowsの格ゲーや音ゲーを作ったことがないで実感が無いです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

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

Re: ミリ秒単位での入力

#11

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

DXライブラリの基本機能だけで書いた打鍵間隔の検証ソフトです。
間違い等あったら教えて下さい。

ちなみに私の能力では3から4フレーム(50msから60msぐらい)間隔が限度でした。

コード:

#include "DxLib.h"

int Key[256]; // キーが押されているフレーム数を格納する

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

#define MARK_MAX (480)
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) {
	ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

	int timingMark[MARK_MAX] = {0};
	int MarkPoint = 0;

	// while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キーの更新)
	while( ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 && gpUpdateKey() == 0 ) {
		//  押されたらマーキング
		if( Key[ KEY_INPUT_SPACE ]==1 || Key[ KEY_INPUT_LCONTROL ]==1) {
			timingMark[MarkPoint] = 1;
		} else {
			timingMark[MarkPoint] = 0;
		}
		//  マークを進める。
		MarkPoint = ( MarkPoint + 1 ) % MARK_MAX;

		//  マークを表示
		for( int i = 0 ; i < MARK_MAX ; i++ ) {
			int mk = ( i + MarkPoint ) % MARK_MAX;
			if( timingMark[mk] == 1 ) {
				DrawLine( 300, i, 400, i, GetColor(255,255,255) );
			}
		}
		
		//	平均打鍵間隔の表示。最後の60フレームだけ計算
		double AveRate = 0.0;
		int aveCount = 0;
		int noMarkCount = 0;
		for( int i = 0 ; i < 60 ; i++ ) {
			int mk = ( MARK_MAX+MarkPoint-i-1 ) % MARK_MAX;
			if( timingMark[mk] == 1 ) {
				//	打鍵間隔を累積
				AveRate += (double)noMarkCount / 60.0f;
				aveCount++;
				noMarkCount=0;//カウント初期化
			} else {
				noMarkCount++;
			}
		}
		if( aveCount>0 ) {
			DrawFormatString( 0,0,GetColor(255,255,255), "平均打鍵間隔=%fms", AveRate * 1000.0 / (double)aveCount );
		}
	}
	
	DxLib_End(); // DXライブラリ終了処理
	return 0;
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

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

Re: ミリ秒単位での入力

#12

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

最小打鍵間隔モードを搭載。あと平均は480フレームごとになりました。
あと配列の最初の判定ミスや打鍵した瞬間を時間として含んでいなかったなどのバグを修正。
【さらに更新】120fpsバージョン。1つのキーだと50msぐらいが限界ですが、2つのキーだと8msのタイミングもイケてるかも?

コード:

#include "DxLib.h"
#include <math.h>
#pragma comment (lib, "winmm.lib") 

int Key[256]; // キーが押されているフレーム数を格納する

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

static int mStartTime;      //測定開始時刻
static int mCount;          //カウンタ
static float mFps;          //fps
static const int N = 60 * 2;	//平均を取るサンプル数
static const int FPS = 60 * 2;	//設定したFPS

bool FpsUpdate(){
	if( mCount == 0 ){ //1フレーム目なら時刻を記憶
		mStartTime = GetNowCount();
	}
	if( mCount == N ){ //60フレーム目なら平均を計算する
		int t = GetNowCount();
		mFps = 1000.f/((t-mStartTime)/(float)N);
		mCount = 0;
		mStartTime = t;
	}
	mCount++;
	return true;
}

void FpsDraw(){
	DrawFormatString(0, 460, GetColor(255,255,255), "%.1f", mFps);
}

void FpsWait(){
	int tookTime = GetNowCount() - mStartTime;	//かかった時間
	int waitTime = mCount*1000/FPS - tookTime;	//待つべき時間
	if( waitTime > 0 ){
		Sleep(waitTime);	//待機
	}
}

#define MARK_MAX (480)
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) {
	ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK ); //ウィンドウモード変更と初期化と裏画面設定

	int timingMark[MARK_MAX] = {0};
	int MarkPoint = 0;
	timeBeginPeriod(1);	//分解能の変更。
	
	// while(裏画面を表画面に反映, メッセージ処理, 画面クリア, キーの更新)
	UINT frameCount = 0 ;
	UINT frameCycle = FPS / 60;
	double doubleFPS = (double)FPS;
	while( ProcessMessage() == 0 && gpUpdateKey() == 0 ) {
		FpsUpdate();	//FPS更新
		
		//  押されたらマーキング
		if( Key[ KEY_INPUT_SPACE ]==1 || Key[ KEY_INPUT_LCONTROL ]==1) {
			timingMark[MarkPoint] = 1;
		} else {
			timingMark[MarkPoint] = 0;
		}
		//  マークを進める。
		MarkPoint = ( MarkPoint + 1 ) % MARK_MAX;
		
		//	表示タイミング?
		if( (frameCount%frameCycle)==0 ) {
			ClearDrawScreen();

			//  マークを表示
			for( int i = 0 ; i < MARK_MAX ; i++ ) {
				int mk = ( i + MarkPoint ) % MARK_MAX;
				if( timingMark[mk] == 1 ) {
					DrawLine( 300, i, 400, i, GetColor(255,255,255) );
				}
			}
			
			//	打鍵間隔の表示。
			double aveRate = 0.0;
			double aveFrame = 0.0;
			double minRate = 9999.0;
			double minFrame = 9999.0;
			int aveCount = 0;
			int noMarkCount = 0;
			for( int i = 0 ; i < MARK_MAX ; i++ ) {
				if( timingMark[i] == 1 ) {
					if( i!=0 ) {//先頭はノーカン
						//	打鍵間隔を累積
						double frame = (double)(noMarkCount+1);
						double rate = frame / doubleFPS;
						aveRate += rate;
						aveFrame += frame;
						aveCount++;
						//	最小間隔を記録
						if( minRate > rate ) {
							minRate = rate;
							minFrame = frame;
						}
					}
					noMarkCount=0;//カウント初期化
				} else {
					noMarkCount++;
				}
			}
			if( aveCount>0 ) {
				DrawFormatString( 0,0,GetColor(255,255,255), "平均打鍵間隔=%fms(%fフレーム)", aveRate * 1000.0 / (double)aveCount, aveFrame / (double)aveCount );
			}
			if( minRate<9999.0 ) {
				DrawFormatString( 0,20,GetColor(255,255,255), "最小打鍵間隔=%fms(%fフレーム)", minRate * 1000.0, minFrame );
			}
			FpsDraw();		//FPS描画
			ScreenFlip();
		}
		FpsWait();		//FPS待機
		frameCount++;
	}
	
	DxLib_End(); // DXライブラリ終了処理
	return 0;
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

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

Re: ミリ秒単位での入力

#13

投稿記事 by ISLe » 12年前

システムフックでRAWレベルメッセージを監視したり、デバイスドライバを自作するなどでミリ秒単位でのタイミング取得は可能かと思います。
アプリケーションレベルではハードウェア割り込みに即応できないので正確なタイミングを取得するのは難しいでしょう。
いつ押されたかが分かれば良いわけですが。

ただし、正確なタイミングを取得したり内部的にFPSを上げたりしても、結果の表示が60FPS単位であれば違和感は無くならないと思います。

そもそもプログラムがnフレームの処理をしているとき、画面にはn-1フレームの画像が表示されています。
1. プログラムがnフレームの画像に切り替えn+1の処理を開始したとき、画面にはnフレームの画像が表示されたばかりです。
2. プレイヤーがnフレームの画像に反応する頃には、n+1フレームの処理は終わっています。
3. nフレームの画像に反応したプレイヤーの結果は、n+2フレームに反映されることになります。

内部のFPSを上げるというのは、2.の救済措置で、打鍵の反応は上がりますが、画像とのズレは無くなりません。
連打できる速度が上がるのでタイピングには有効ですが、タイミングを合わせる音ゲーにはさほど有効ではありません。

プレイヤーは無意識に画像とのズレを補正しようとして、若干早めに打鍵してしまい、音符が細かくなるとズレの問題が顕在化します。

ズレを無くすには、判定はいま再生されている位置で行い、描画は次に再生される位置で行う、とする必要があります。


違和感の原因には、遅延だけでなく、タイミングの揺れもありますので、内部FPSを上げる際には、打鍵情報を取得するタイミングを一定に保たないとあまり意味がありません。
プログラムのどこで処理が重くなるか掴むのは難しいので内部FPSを上げるのは意外と簡単にはできないと思います。

玄米 さんが書きました:確かにBPM250の24分音符でも間隔は33.3ミリ秒ですが、
細かくても16.67ミリ秒単位でしか入力できないのはプレイ中に数ミリ秒単位での微調整が効かず厳しいと思うのですがどうでしょうか。
太鼓の達人ではそんな短い間隔の音符はあり得ないと思いますけど。
作っているのは鍵盤型ですかね。
解像度が倍あればなんとかなるのでは。

閉鎖

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