ページ 11

FPSまたは小数点以下の計算について

Posted: 2013年12月19日(木) 17:24
by sql
現在、リズムにあわせてキーを押す、というだけのゲームを作っているのですが、小数点以下の計算に手間取っていて先へ進めません。

例えば、
float count = 0;//floatだろうがintだろうが1ずつしか加算されない
float xxx = 190;
float timing = xxx/60.0f;//3.1・・・・となる。
void calc()
{
count += 1.0f;
if(count == timing)
/*処理(音が鳴る)
     count = 0.0f;*/
}
とやると、当たり前ですが処理はしません、
ですのでif文の部分を
if(count == (int)timing)
としました。そうすると当たり前ですが、音が鳴る時間にズレが出来てしまいます・・・
FPSを600・・・というのはしたくないのですが、せめて少数第1位までの計算はしたいです。しかし、1ずつしか加算されないのでfloatにしようが「count」の小数点以下は0のままで計算に使えません。

どのような改善法があるのでしょうか?教えてください。(短文のためcodeタグは使いませんでした)

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月19日(木) 17:56
by non
floatの場合 判断を==にはしません。
if(count >= timing)
のように大きくなったらで判断します。
または、カウンタなどを定数倍してintegerにすることでしょう。

ただし、今回の場合は、どうすればよいのかが情報不足です。
calc()の関数はどれくらいのタイミングで呼ばれているのでしょうか?
目的などがわからないと正しいアドバイスができません。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月19日(木) 18:13
by sql
nonさん、返信ありがとうございます。
モニタのリフレッシュレートが60ですので、60fps(1秒間に60回)calc()が呼ばれるという「設定」にしてます。
ですので、上記の「timing = 3.166666」と「count」をif(count >= timing)とやってもcount = =4のときに処理が行われてしまい、音の鳴るタイミングがずれていってしまうので困っているという状況です。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月19日(木) 22:15
by へにっくす
timing=190/60=3.1666・・・
の単位は?「秒」であるならば、60で割る必要はないです。
なぜなら1秒間に60カウントされるのですから。
190に等しい時にならせばよい。

と単純に考えるのですが、そうではなさそうですかね?

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月19日(木) 23:56
by sql
へにっくすさん返信ありがとうございます。

違いますね・・・そのような考えではないです。
やっぱり1フレームで1.0加算するのに1.3のときに・・・なんていうのは無理なんですかね・・・・?

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月20日(金) 04:04
by hide
浮動小数点が内部でどう扱われていてどのようなことに気をつけなければならないか
とかはわかっていますか?
浮動小数は我々の知っている数値を”正確に”表すことができないです。
例えば10進数の0.1も二進の浮動小数に直すと無限に続くのでどこかで丸めなければなりません。

1フレーム1.0増えるのであれば 1.0→2.0→3.0 と変化するので
1.3 という状態が存在することはありません。
その間の瞬間を表したいのであれば1フレームに一回動かすという考えを変える必要があります。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月20日(金) 08:41
by non
sql さんが書きました: モニタのリフレッシュレートが60ですので、60fps(1秒間に60回)calc()が呼ばれるという「設定」にしてます。
ですので、上記の「timing = 3.166666」と「count」をif(count >= timing)とやってもcount = =4のときに処理が行われてしまい、音の鳴るタイミングがずれていってしまうので困っているという状況です。
calc()が1秒間に60回呼ばれ、そのたびに、countが1つずつ増加するということは、間隔は16.7msですね。このカウントが3.16666で処理を行いたいが4で行ったときの処理のずれが、
4-3.16666=0.833 これに16.7ms掛けて、13.9ms 遅れてしまう。しかし、仮に3で処理を行えば、0.17×16.7ms=2.8ms ですので、若干のdelayを入れてあげれば良いのでは。

他の方法としては、calc()が呼ばれる回数を増やすことでしょう。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月20日(金) 17:44
by ISLe
sql さんが書きました:モニタのリフレッシュレートが60ですので、60fps(1秒間に60回)calc()が呼ばれるという「設定」にしてます。
ですので、上記の「timing = 3.166666」と「count」をif(count >= timing)とやってもcount = =4のときに処理が行われてしまい、音の鳴るタイミングがずれていってしまうので困っているという状況です。
困っているのは、音の鳴るタイミングがずれていってしまうことでしょうか。
『いってしまう』というということはズレが累積することを表しているようです。

(最大)60分の1秒のズレが気になるということではないのでしょうか。
個々のズレが60分の1秒を超えるとすればtimingの側で誤差を累積していると思われます。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月20日(金) 20:56
by sql
自分の質問の仕方が悪かったのかもしれません。

とりあえず解決したいこと、それは「3.166...(=timing)フレームごとに音を鳴らす」ということです。上のコードはあくまで例です。
そして、calc()は毎フレーム(1秒に60回)呼ばれます。そして、calc()の中では毎フレームcountが1.0fずつ加算されます。
そのcalc()のなかで「if(count >= timing)」とやってしまうとcount == 4のときに音がなってしまいます。
つまり「自分の求めているリズムより遅いリズムで音が鳴る」という風になってしまいます。
これを直したいのです。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月21日(土) 00:10
by ISLe
60分の1秒以下の遅延が実際に知覚できるということですね。

そうなるとマルチスレッドにして高精度タイマを監視するということになりますかね。
そっちに負荷が掛かり過ぎて音楽再生そのものが遅延する可能性が無きにしも非ずですが。

Re: FPSまたは小数点以下の計算について

Posted: 2013年12月21日(土) 14:51
by spaaaark・∀・
なるほど、確かにデバッグにおいて正確にタイミングを見計らうと4枚目のフレームにおいて音がなってしまいますね。
恐らく質問者さんはそこが気に入らないのではないかと推測します。

解決方法ですけど、質問者さんもお分かり頂けている通り、calc関数は1フレーム(≒1/60秒)毎に呼び出されています。
逆に言えば、calc関数は1フレーム(≒1/60秒)にしか呼び出されておらず、3.166...フレームに一回音を鳴らすのであれば、
それを処理できるような回数calc関数を呼ぶ必要があるはずです。
non さんが書きました:他の方法としては、calc()が呼ばれる回数を増やすことでしょう。
とおっしゃられておられましたが、この方法です。
3.166...フレームは、3.16.../60 ≒ 5.3ms(かな?)なので、これを満たすためには、0.1msの時間差は容赦してもらうとしても、
0.3msに一回calc関数を呼び出す必要があります。これはFPSに直すと3333.3...FPSとなります。

さて、それを考えると何フレームごとに関数を呼び出すか計算するのが面倒になる、またFPSはある程度変動してしまうので、
僕は時間軸を使う事をお勧めします。
次に音の鳴るタイミングを、プログラムが起動したor前の音が鳴った直後から指定しておくのがポイントです。
コードにすればこんな感じでしょうか。ただし、モニターとの垂直同期は切っておく必要があります。

コード:

void calc(){
  /*time.hをincludeしておきます(C++なら<ctime>です)*/
  const float interval = 5.3f; // 間隔(単位:ms)
  static float Next = (float)(clock() / CLOCKS_PER_SEC * 1000) + interval; // 初めてcalc関数が呼ばれた時のみ更新(単位:ms)
  const float Now = (float)(clock() / CLOCKS_PER_SEC * 1000); // calc関数が呼び出される毎に更新(単位:ms)
  if(Now>=Next){
    Next = Now + interval;
    /*音の鳴る処理*/
  }
}
といった具合です。(動作テストしてないため保証はしません。)
ただしこうすると1フレームごとのデバッグができないことに注意が必要です。もしデバッグをしたいなら、処理を止めずに
float Debug = (Now - Next) * -1; の値を画面上に表示すればいいかもしれません。
この変数を使うと前の音が鳴った時間からの経過時間がわかります。
オフトピック
この時間で1拍ごとにキーを押させようとすると、
1000/5.3 * 60 = 11320.755回一分間に押させることになってしまうのですが、これは大丈夫なのでしょうか。
もし190BPM(=1分間に190回拍を打つ)にするなら最初のコードのtiming変数は60000.0f[ms]をxxxで割る必要がありますが…。