導入:なぜあなたのゲームはカクつくのか?
Godot Engineで開発を進める中で、多くの開発者が直面する壁、それがフレームレート(FPS)の不安定さです。特にアクションゲームや多数のオブジェクトが動くシーンで発生する「カクつき」や「ジッター」は、プレイヤーの没入感を著しく損ない、ゲームの品質を根本から揺るがします。
「V-Syncを有効にしたのに滑らかにならない」「_processと_physics_processの使い分けが分からない」「最適化のためにObject Poolingを導入すべきか迷っている」…こうした悩みは、Godotのレンダリングと物理演算の仕組みを深く理解することで解決できます。
この記事は、単なる機能紹介に留まりません。FPSが不安定になる根本原因を解き明かし、具体的な解決策を実践的なコードと表形式の比較で徹底的に解説します。この記事を読めば、あなたは以下の知識を習得し、自信を持ってパフォーマンス最適化に取り組めるようになります。
- 課題解決: FPSの不安定さ(ジッター)を解消する「物理補間」の具体的な実装方法を学べる。
- 正しい理解: Godotの物理演算と描画の仕組み、そして
deltaの正しい使い方を完全に理解できる。 - 的確な判断: プロファイリングに基づき、Object Poolingなどの最適化手法をいつ、どのように適用すべきか判断できるようになる。
- 実践的知識: V-Syncの各モードの違いや
Engine.max_fpsの活用法など、一歩進んだフレームレート管理術を習得できる。
FPSが不安定になる根本原因:物理ティック vs レンダリングフレーム
Godotにおけるパフォーマンス問題を理解する鍵は、物理ティック(Physics Tick) とレンダリングフレーム(Rendering Frame) という2つの独立したサイクルの違いを認識することです。この非同期性が、ジッターの直接的な原因となります。
| 概念 | 物理ティック (Physics Tick) | レンダリングフレーム (Rendering Frame) |
|---|---|---|
| 役割 | 物理演算、衝突判定 (_physics_process) | 画面描画、入力処理 (_process) |
| 実行頻度 | 固定 (デフォルト60Hz) | 可変 (ハードウェア性能、V-Sync設定に依存) |
| 目的 | 物理シミュレーションの再現性と一貫性を保証 | 可能な限り滑らかな映像をプレイヤーに提供 |
delta | 固定値 (1 / 物理ティックレート) | 可変値 (前のフレームからの経過時間) |
なぜジッターが起きるのか?
例えば、物理が60Hz、モニターが144Hzの場合を考えます。物理エンジンは1/60秒ごとにオブジェクトの位置を更新しますが、画面は1/144秒ごとに描画しようとします。結果として、複数回の描画フレームでオブジェクトが同じ位置に留まり、次の物理ティックで突然位置がジャンプする「階段状の動き」としてプレイヤーの目に映ります。これがジッターの正体です。
解決策1:V-Syncとmax_fpsによるフレームレート制御
最も基本的な対策は、レンダリングフレームレートを制御することです。GodotはV-Sync(垂直同期)とEngine.max_fpsという2つの主要な方法を提供します。
V-Sync(垂直同期)の活用
V-Syncは、レンダリングフレームレートをモニターのリフレッシュレートに同期させる技術です。これにより、画面が途中で更新されることによる「ティアリング」を防ぎます。
プロジェクト設定の Display > Window > Vsync > Vsync Mode から設定できます。
| V-Syncモード | 概要 | メリット | デメリット |
|---|---|---|---|
| Disabled | V-Syncを無効化。 | FPS上限がなくなり、入力遅延が最小化される。 | ティアリングが激しく発生する。 |
| Enabled | 常にV-Syncを有効化。 | ティアリングを完全に防止できる。 | FPSがリフレッシュレートを下回ると、描画が次の同期タイミングまで待たされ、入力遅延が増加する。 |
| Adaptive | FPSがリフレッシュレートを上回る時だけ同期。 | ティアリングを防ぎつつ、FPS低下時の入力遅延を回避できる。 | 対応しているハードウェアが必要。 |
| Mailbox | 常に最新のレンダリング済みフレームを保持し、同期タイミングで表 示。 | ティアリングと入力遅延の両方を最小限に抑える。 | VRAM使用量が増加し、わずかな追加遅延が発生する可能性がある。 |
注意:外部設定の上書き GodotでV-Syncを設定しても有効にならない場合、NVIDIA Control PanelやAMD Radeon Softwareなどのグラフィックカード設定がGodotの設定を上書きしている可能性があります。ドライバー側の設定で「V-Sync: アプリケーションによるコントロール」を選択してください。
Engine.max_fpsによる手動制限
V-Syncに頼らず、特定のFPSに固定したい場合に有効です。例えば、意図的にレトロゲーム風の低いFPSにしたり、モバイルデバイスでのバッテリー消費を抑えたりする目的で使われます。
# GameController.gd
func _ready():
# ゲームの最大FPSを60に設定
# V-SyncがDisabledの場合にのみ有効
Engine.max_fps = 60
max_fpsは、_ready()関数内などで一度設定すれば、ゲーム全体で有効になります。ただし、V-SyncがEnabledの場合、max_fpsは無視されることに注意してください。
解決策2:物理補間(Physics Interpolation)によるジッターの根絶
フレームレート制御だけでは、物理ティックとレンダリングフレームの非同期 性という根本問題は解決しません。そこで登場するのが物理補間です。これは、固定された物理ティック間のオブジェクトの位置を、レンダリング時に滑らかに補間描画する技術であり、ジッターに対する最も効果的な解決策です。
プロジェクト設定で有効化する(推奨)
Godot 4.3以降、2Dと3Dの両方で物理補間が標準サポートされました。ほとんどの場合、以下の設定を有効にするだけで問題は解決します。
- プロジェクト > プロジェクト設定 を開く
- Physics > Common > Physics Interpolation を
Onに設定する
これだけで、CharacterBody2D/3DやRigidBody2D/3Dなど、物理演算の影響を受けるほとんどのノードの動きが自動的に補間され、非常に滑らかになります。
手動での補間実装(カメラ追従など)
物理ノードではないオブジェクト(例:カメラ、UI要素)を物理ノードに追従させる場合、手動での補間が必要になります。Engine.get_physics_interpolation_fraction() を使うことで、現在のレンダリングフレームが物理ティック間でどの位置にあるか(0.0〜1.0)を取得できます。
以下は、物理補間を有効にしたプレイヤーキャラクターに、カメラが滑らかに追従する実践的なコード例です。
# SmoothCamera2D.gd
# Camera2Dノードにアタッチ
extends Camera2D
# 追従対象のノード(プレイヤーなど)をインスペクターから設定
@export var target: Node2D
# 追従の滑らかさ。値が小さいほど滑らかになる
@export var smoothing: float = 0.1
var _previous_target_position: Vector2
func _ready():
if target:
# 初期位置をターゲットに合わせる
global_position = target.global_position
_previous_target_position = target.global_position
else:
push_warning("SmoothCamera2D: Target node is not set.")
func _process(delta):
if not target:
return
# 物理補間が有効な場合、ターゲットのレンダリング位置は常に補間されている
# しかし、カメラのような非物理ノードは自前で補間する必要がある
var interpolation_fraction = Engine.get_physics_interpolation_fraction()
# 1フレーム前のターゲット位置と現在のターゲット位置を補間して、
# カメラが目指すべき「真の」現在位置を計算する
var interpolated_target_position = _previous_target_position.lerp(target.global_position, interpolation_fraction)
# カメラ自身の位置を、計算された目標位置に向かってさらに滑らかに移動させる
global_position = global_position.lerp(interpolated_target_position, smoothing)
func _physics_process(delta):
if not target:
return
# 次のフレームの補間のために、物理ティック時点でのターゲット位置を保存しておく
_previous_target_position = target.global_position
このコードでは、2段階のlerp(線形補間)を行っています。
- 物理ティック間のターゲットの動きを補間
- カメラ自身の動きをさらに滑らかにする
これにより、プレイヤーの動きが急であっても、カメラは映画のようにスムーズに追従します。
よくある間違いとベストプラクティス
最適化に取り組む上で、開発者が陥りがちな罠と、それを避けるためのベストプラクティスをまとめました。
| トピック | よくある間違い(アンチパターン) | ベストプラクティス |
|---|---|---|
| 処理の記述場所 | 移動処理やロジックをすべて_processに書いてしまう。 | 物理的な動きは_physics_process、描画や入力に関する処理は_process に書く。deltaの役割を正しく理解する。 |
| 最適化の順序 | 「重そうだから」という推測でObject Poolingなどを導入する。 | 「推測するな、計測せよ」。Godotのプロファイラでボトルネックを特定してから、的確な最適化手法を選択する。 |
| Object Pooling |