質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

3888閲覧

requestAnimationFrameでフレームと再描画更新を制御したい

meron-pan

総合スコア44

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2018/09/30 14:56

編集2018/10/01 07:47

前提・実現したいこと

JavaScriptでrequestAnimationFrameでフレームと再描画更新を行うコードを書いてみました。
そこで、一秒ごとにconsole.logで変数fpsを出力したところ、変数の値が40あたりを行ったり来たりしている状態です。
(requestAnimationFrameが60回更新しているのに対し、if(ping >= 1){}内のコードは40回しか実行されない)。
if(ping >= 2)にすることで、関数内のコードの実行を30回に抑えようとすると、実際には20回しか実行されないことも確認しました。
(この時のrequestAnimationFrameの更新は60回です)
目標としては、if関数ないの変数「ping」の値を変えることで、自由にfpsを制御できるようにしたいのですがどのような改善が必要でしょうか?

発生している問題・エラーメッセージ

期待している通りの、更新制御が行えない。

該当のソースコード

Javascript

1 2//ゲーム開始時刻 3var start = Date.now(); 4 5//過去の時間(最初は初めの時間を使う) 6var pastTime = start; 7var pastSecond = start; 8var time; 9console.log(start); 10 11//fps 12var fps = 0; 13var ping = 0; 14 15render = function () { 16 //fps管理 17 time = Date.now(); 18 ping = (time - pastTime ) / (1000/60); 19 if (ping >= 1) { 20 fps += 1; 21 pastTime = time; 22 } 23 24 //一秒判定 25 if ((Math.floor(time / 1000) - Math.floor(pastSecond / 1000)) >= 1) { 26 console.log(fps); 27 pastSecond = time; 28 fps = 0; 29 30 } 31 requestAnimationFrame(render); 32 } 33 34render();

補足情報(FW/ツールのバージョンなど)

fpsの計測はfirefoxのコンソール画面での計測です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi-sun

2018/10/01 05:50

この質問文を書いたのは本当にあなたですか?過去の質問で「requestAnimationFrameを使ったアニメーションではfpsの制御ができない」と質問文に記載されていますが、出来ないと分かっている事に対してできる前提で質問文を書いているのはどうしてでしょうか。
meron-pan

2018/10/01 06:54

内容の不足で申し訳ありません。requestAnimationFrameが描画のタイミング制御ができないのは重々承知しております。
meron-pan

2018/10/01 06:59

しかしながら、実際に出せる更新速度より同等の速度、またはそれ以下の更新速度はプログラムを書くことによって、疑似的に再現できる(requestAnimationFrame60回実行するのに対し、内部の関数は30回実行する)ことも理解しているつもりです。今回はそのようなプログラムを書いた上で、理論通りに動かないために質問しました。
meron-pan

2018/10/01 07:13

タイトルが誤解を生んでしまい、申し訳ありませんでした。修正を行いましたので、正しく伝わってくれれば、幸いです
miyabi-sun

2018/10/01 07:28

なるほど、つまり単に言葉足らずで「JSの処理を軽くすれば、関節的にブラウザのFPSを引き上げることができるのではないか?」という推測が挟まっているわけですね。それを前提として「無駄な処理を減らしてFPSを引き上げたい」という質問に繋がるわけですね。この辺をちゃんと記述出来れば質問文としていい感じになると思います。
meron-pan

2018/10/01 07:36

大まかにいえば、そういうことになります。しかし、前回の質問は勉強不足と言葉足らずな部分が多く、この質問の前提として使うには、いささか問題がありすぎると思い質問を新しくしました。
meron-pan

2018/10/01 07:40

また今回はソースコードを添付し、firefoxでパフォーマンスを計測して定的に60fpsが出ていることを確認したうえで、制御用のコードが上手く動作していないことに対しての質問を行っているつもりです
meron-pan

2018/10/01 07:50

完全なる私の言葉足らずで、ご迷惑をおかけしますが、どうかよろしくお願いします
guest

回答1

0

ベストアンサー

結論から言いうならば、Date.now()を使うのをやめましょう。
こいつは「今」の時刻なので正確な時刻ではないですし(!?)、
なんかやってるのは確実なのでコストを増加するだけのノイズになっています。

代わりに第一引数としてコールバック関数に渡される
DOMHighResTimeStampを利用するようにしてください。
requestAnimationFrame - MDN

第一引数として使われる時点でもう生成された後の話なので、
使っても使わなくてもコストは変わりません。

まぁ、DOMHighResTimeStampはよく分からないDouble型の数値なので扱いはめっちゃ大変だと思いますが、
この中には何時に前回のレンダリングが行われたかの時刻が取得できるので、フレーム計算を行う上でより正確な記述ができるはずです。


質問文のようなFPSの調査をしたいのであれば、
requestAnimationFrameとsetTimeoutやsetIntervalを併用すればいいんじゃないですか?
厳密には1000ms毎ではなく、998〜1002msとかの範囲でブレるでしょうけど、
質問文のコードもどうせズレているので誤差みたいなもんでしょう。

JavaScript

1// fps以外の状態変数など不要だ!! 2var fps = 0; 3 4var render = function () { 5 fps++; 6 requestAnimationFrame(render); 7} 8render(); 9 10var showFps = function () { 11 console.log(fps); 12 fps = 0; 13} 14setInterval(showFps, 1000);

ゲームやフレームというワードが質問文にありますが、
もしかしてフレーム単位で天国か地獄かが分かれるような構造のゲームを作りたいのでしょうか?

もしそうならば、JSでやるなら場当たり的なチューニングをする前に、
まずJSのイベントループの仕様を知る必要があります。

JSはHTML上に書かれたコードを超高速で全て実行し完了します。
完了してしまうならば、どうやって「イベント」を引っ掛けて実行するのか?
それはJSがイベント駆動という考え方をしているからです。

JSの関数は「変数に代入」「関数の引数に含める」「関数の戻り値に含める」ことが可能な言語であり、他の変数と違いは、尻に()を付けて実行が可能くらいしかありません。
第一級オブジェクト

JSではこの特性を利用して、やることリストのように関数を用意しておき、
「Aの条件を満たしたら、引数として渡した関数を実行してね、よろしく」という使い方をします。
これが「イベントの登録」、特定のメモリ領域に関数を保存するだけなので一旦処理は終わります。

その後、全ての処理が終わって暇になったブラウザは、定期的に巡回を行います。
「イベントを満たす条件を満たしたイベントは居ないかな?」という風な巡回です。
条件を満たしたらイベントに登録されている関数を1つずつ呼び出して実行します。
その時に投げ込まれる引数はブラウザやMDN等が定義しているので必要に応じて調べてみてください。

これが大まかな流れになります


次に、この仕様がゲームのようなフレームを気にするものを作る時に於いてどう影響するのかを見ていきます。

  • setTimeoutやrequestAnimationFrameは条件を満たした後も、イベントループの巡回を待つ必要があり、平気で数ms発火が遅延する
  • JSはシングルスレッドなので、同時に1個の処理しかできない。したがって同時に複数条件を満たした場合、どちらの関数が(制御出来ないのでほぼランダムに)先に実行され、もう片方は後回しにされる、それにより平気で数ms発火が前後する
  • requestAnimationFrameは「はい描画しました、次のフレームを作る準備をしてくださいね」と言っているので、画面への反映タイミングはどう頑張っても1フレーム遅れ

これらの事から、JSでC#のようなフレーム単位のゲームを作るのは不可能クラスに難しいです。
一時的にマシンの負荷をかけて、例えば敵の弾幕が自機を飛び越えるような間隔のレンダリングにして無傷でやり過ごす裏技がありますので、描かれなかったフレームの計算を行ったり…という対処を行うと飛躍的に処理が増えて更に重くなる…という悪循環に陥るでしょう。

ゲーム自体の設計を見直したり、何処を妥協するかをよく練って下さい。
実際のゲームの実装はフレーム落ちはまぁしゃあないと諦めている事も多いです。

投稿2018/10/01 08:23

編集2018/10/01 08:37
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meron-pan

2018/10/01 08:42

詳しい解説ありがとうございます。特に、フレームが不安定だろうが、別に構わないような設計で作成してはいるつもりですが、もしブラウザエンジンに余裕があるのでれば、できるだけ正確にフレーム更新を行っていきたいというような考えです。(60fpsでるのであれば30fpsでのフレーム更新はできるでしょって感じです)。
miyabi-sun

2018/10/01 08:52

既存のゲームエンジン的なライブラリにまかせてしまうのが楽だと思います。 自分でハンドリングするなら、「キー入力状態」「ゲームの状態を司るstate」「レンダリング用の前回のstate」を用意して、 renderはstatと前回stateの差分を取りながらあるべき値に更新し続ける キー入力等はEventTarget.addEventListenerで行う。 setInterval(fn, 1000 / 30)みたいな間隔で発火、キー入力状態を参照しながら集計を取りstateを更新していく。 …のような感じで「ブラウザの更新タイミング」と「ゲーム内部の集計締め切り時間」という2つのフレームを用意して回すと楽かつ余計な処理が増えずに綺麗に運用できると思います。
meron-pan

2018/10/01 09:04

回答ありがとうございます。ゲームの完成が目標であるならばさっさとライブラリを使うのですが、今回は個人製作で商業用並みのクオリティを目指してみようという完全なる「バカ」の試みとしてやっています(まあ、学生だからそんなアホなことが出来るのですが...。) とはいえ、なんの経験もないただの素人ですので、全くの知識がないといったような感じです。
meron-pan

2018/10/01 09:09

なので、このサイトでの質問は、おそらく素人丸出しのくだらなくて、葉のつたない質問にはなると思いますが、なにとぞ厚い温情を頂けたら幸いです。 今回も、ご丁寧に最初から最後まで解説していただきありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問