tanaka's Programming Memo

プログラミングについてのメモ。

【Godot4.3】Macで2Dゲームの動きがガタついたときにやったこと

本記事は、Qiita Advent Calender 2024Godot Engine Advent Calender 2024シリーズ1の4日目の記事です。

qiita.com

2Dゲームを、手持ちのMac Book Pro(以降、MBP)で動かしたところ、動きがガタつく症状が発生しました。その原因と、調査のまとめです。

目次

原因は互換性レンダラー

あまりに簡単なオチだったので先に書きます。原因は互換性レンダラーでした。Forward+やモバイルを使っていたら滑らかに動きます。また、互換性レンダラーでも、WindowsWebブラウザーでは、滑らかに動きました。

動きがガタつくのは、互換性レンダラーで、WindowsMac上で実行した場合です。普通は、PC向けならForward+を使うので、問題は起きません。うちの古いインテルMBPだと、MoltenVKが不安定で、互換性レンダラーしか動きません。このような特殊な場合に起きる問題でした。

以下、調査内容です。

ことのおこり

Godot Meetup Tokyo Vol.3に、展示枠で応募したのがはじまりでした。ゲームジャムに、未完成で出したものを完成させて、この機会に遊んでいただこうと考えました。せっかく展示するなら、Webブラウザーではなく、ネイティブのフルスクリーンで動くようにしようと、手持ちのMBP上で動くようにビルドしてみました。

滑らかに動くことを期待していたのですが、動きがガタつきます。ブロック崩しクリッカーをまぜたSTORM OF BALLSで、特にガタつきが目立ちました。

am1tanaka.itch.io

こんなシンプルなゲームが滑らかに動かないのはおかしい、というところから、今後に備えて原因を調査しはじめました。

フレームレートを確認する

画面がガタつく原因として考えられるのは、画面の更新タイミングが安定しないことか、処理が間に合っていないことです。まずは、フレームレートを確認しました。

1秒ごとにログに出力

Godotの標準の機能で、フレームレートを確認する方法があります。

  • プロジェクトメニューから、プロジェクト設定を開く
  • 高度な設定を有効にすると、デバッグ欄が表示されるので、設定からFPSを表示するにチェック

FPSをログに表示

この設定をして実行すると、1秒ごとに、出力にFPSが表示されます。

フレームレートを出力

デバッガーのモニターで確認する

もう少ししっかり確認したい場合は、下部パネルのデバッガーのモニターを利用します。モニターは自動的に記録されるので、実行したあとに確認できます。

  • ゲームを実行する
  • 画面下部のデバッガーをクリックして、デバッガーパネルを表示する

デバッガーのモニターでFPSを確認

フレームレート(FPS)の値の欄で、直近のフレームレートが分かります。チェックを入れれば、推移が確認できます。

MBPで、フルスクリーンで実行した結果が以下です。

MBPでのフレームレートの推移

フレームレートが安定していれば、グラフは一直線になるはずです。ガタガタしているということは、フレームレートが安定していないということです。

スクリプトを書いて自力で表示する

フレームレートが安定しない原因は、Godotエディターかも知れません。以下のような簡単なスクリプトを作成して、ビルドしたアプリ単体でフレームレートを確認しました。

// 1:
class_name FrameRateLabel
extends Label

var _last_time : int
var _counting: int


func _ready() -> void:
    _last_time = Time.get_ticks_msec()


func _process(_delta: float) -> void:
    _counting += 1
    if (Time.get_ticks_msec() - _last_time) < 1000:
        return
    
    _last_time = Time.get_ticks_msec()
    text = "%d fps" % [_counting]
    _counting = 0

このスクリプトをLabelにアタッチすれば、フレームレートが表示されます。これでも、フレームレートは不安定でした。

ラベルに表示

フレームレートが安定しない現象のまとめ

フルスクリーンなら、モニターのリフレッシュレートに同期するので、フレームレートが安定すると考えていました。しかし、Mac Bookは可変リフレッシュレートがデフォルトの動作で、内臓モニターだとリフレッシュレートを指定できないようでした。

MacBook Pro や Apple Pro Display XDR でリフレッシュレートを変更する - Apple サポート (日本)

120fpsで安定して動くときがあって、その時はスムーズに動きます。しかし、再現性がありません。AppleシリコンのMacだと、フルスクリーンにするとゲームモードで動いてくれるらしいのですが、インテルMacは対応していないようです。

Mac でゲームモードを使う - Apple サポート (日本)

これが原因だと思ったのですが、フレームレートが固定のWinでもガタつくことが分かりました。ほかの原因を探します。

スクリプトの実行速度を確認する

タイトル画面が特に不安定で、70fpsになったりします。重い処理がないか、プロファイラーで調べることにしました。プロファイラーは、下部パネルのデバッガーから選べます。

下部パネルからデバッガー>プロファイラーを選択

プロファイラーは、モニターと違って、手動で開始をクリックする必要があります。また、実行するたびに下部パネルが出力に変わるのは面倒です。以下のような設定で動かしました。

  1. プロジェクトメニューから、プロジェクト設定を開く
  2. 表示>ウィンドウ欄のモードを、Windowedに設定
  3. エディターメニューから、エディター設定を開く
  4. 一般タブの、実行>Bottom Panelを選択
  5. Action on Play欄を、Open Debuggerに変更

実行時に、デバッガーパネルを開く設定

  1. デバッガーパネルを選択して、プロファイラーを選択

これで、ウィンドウで起動して、下部パネルが自動的にデバッガーになります。実行して、プロファイラーの開始ボタンを押します。

プロファイラーを開始

ボールを200個以上表示させてみました。

ボール関連の処理にかかっている時間

223個のボールを処理して、3msもかかっていません。Physics 2Dなども、負担はなく、処理時間は余裕がありそうです。

処理ごとの実行時間は、時間の表示設定を、包括から自己に変更すると確認できます。

各処理ごとの処理時間

当たり判定のShapeCastが一番重くて、0.86msかかっています。それでも、1msもかかっていないので、気にしなくてよいでしょう。

設定で試したこと

ここまでの調査で、次のようなことが分かりました。

  • MBPでは、フレームレートが安定しない
  • 処理速度には余裕があるので、スクリプトの高速化などは不要

最大フレームレートを設定する

フレームレートを一定にできれば、滑らかに動かせそうです。MBPが可変リフレッシュレートでも、最大フレームレートをGodotで設定すればいけるのではないかと考えました。設定は、プロジェクト設定の実行欄にあります。

最大FPSを60、デルタ時間のスムージングをオフ

これでも安定しないため、デルタ時間のスムージングをオフにしてみました。この設定で、やや安定したように感じたのですが、気のせいでした。その時は、運よく120fpsで安定動作していたのでしょう。デフォルトに戻しても、120fpsで安定していれば滑らかに動くので、解決策にならないことが分かりました。

処理をprocessに移動

MBPでの解決は難しそうなのでひとまず保留して、固定フレームレートのWindowsで滑らかに動くことを確認しようと考えました。しかし、期待に反して、フレームレートが固定されるWindowsのフルスクリーンでも処理がガタつきました。

こうなると、フレーム更新と物理更新の干渉が疑われます。そこで、物理更新に実装していた移動処理を、_processに移してみました。move_and_collideで実装していた移動処理は、次のようにグローバル座標移動に変更しました。

   #move_and_collide(step * Vector2.DOWN)
    global_position += step * Vector2.DOWN

フレームレートが一定で、移動量が同じなら、滑らかに動くはずです。ところが、これでもガタつきが解消しません。こうなると、Godotの画面更新が不安定だとしか考えられません。

もはや、エンジンのコードに手を入れるしかなさそうです。ここで一度諦めたのですが、こんな簡単な処理ができないはずがありません。ここで、互換性レンダラーが疑わしいことに思い至りました。レンダラーをForward+にしたところ、無事、滑らかに動きました。モバイルも滑らかです。ここまでにやった対策を、すべてデフォルトに戻しても、滑らかなままです。ということで、原因が互換性レンダラーだったという結論になりました。

今回の調査で気になったところ

今回の調査で気になったことを、おまけで書きます。

モニターのインポートプロセス欄はProcessの間違い

デバッガーのモニターに表示されるインポートプロセスで混乱しました。

謎のインポートプロセス

インポートプロセスって、アセットをインポートする作業ですよね?なぜ、実行中に発生するのか。しかも、フレーム更新と同程度の16msもかかっています。ググったりあれこれ調べてみたのですが、よくわからず。ふと、英語表記にしてみたところ、Processとなっていました。それは16msかかりますね。翻訳のミスでした。

すでに修正が出ているかも知れませんが、まだなら報告方法を調べて報告します。とりあえず、Weblateで修正して保存しました(2024/12/4)

プロファイラーで待ち時間が知りたい

Godotのプロファイラーですが、各項目にかかっている時間はわかるのですが、「待ち時間」が分かりません。

待ち時間がない

Godotのプロファイラーでは、個別の項目や、ある処理グループにかかった時間はわかるのですが、それらがフレームの更新時間内にどのような順序で、総合してどれぐらい時間を使っていて、余っている時間がどれぐらいかが分かりません。見落としがありそうな気がしますが、見つけることができませんでした。

UnityのProfilerでは、以下のようにターゲットの時間に対するCPUとGPUの使用率を見ることができます。

UnityのProfiler

また、スレッドごとにかかった時間を、並べて見せてくれます。処理の余力など、把握しやすくなっています。

GODOT DOCSのThe Profiler — Godot Engine (stable) documentation in Englishを見ると、主要なデータとして、Frame time, Physics frame, Idle time, Physics timeという項目が示されています。名前的に、Idle timeがフレーム更新までの待ち時間だろうと思ったのですが、プロファイラーに見当たりません。はて?と思ってマニュアルを読んでみると、Idle timeは、_processを含めた物理更新以外にかかった時間の合計だと書かれていました。どうやら、Process Timeのことを、以前はIdle timeと書いていたようです。本来のIdle timeが欲しい・・・。

まとめ

Webブラウザーだと、互換性レンダラーでもPCよりもガタつきが気になりません。問題になるのは、WinやMac上で、互換性レンダラーを使った場合のようです。PC向けなら、通常はForward+を使うので、想定されていないのかも知れません。

うちの古いMBPのように、Vulkanが安定して動かないビデオカードを持ったPCでは、互換性レンダラーにしないとGodotが落ちて使えません。Godot4.4で、Metalの対応がはじまったようですが、残念ながらインテルMacは当面は対象外とのことでした。

Dev snapshot: Godot 4.4 dev 1 – Godot Engine

別件ですが、Forward+やモバイルは、互換性に比べて、入力への反応が鈍いような気がしました。フレームバッファの扱いが違っているからかも知れませんし、気のせいかもしれません。

今回の件は、Windowsがなければ解明できませんでした。複数の環境を持っているのは大切ですね。また、Mac向けには、通常のForward+版だけではなく、互換性レンダラー版のビルドを用意しないと、落ちる可能性があるのは気を付けたいところです。

本記事は、Qiita Advent Calender 2024Godot Engine Advent Calender 2024シリーズ1の4日目の記事でした。

qiita.com

参考URL