Event Tickの パフォーマンス問題
Blueprintの 「Event Tick(イベントティック)」 ノードは、毎フレーム実行されるため便利ですが、この「毎フレーム実行」こそがパフォーマンスを蝕む要因となります。
本記事では、Event Tickを最小限に抑えて高性能なゲームロジックを構築するための 「Tick-less(ティックレス)設計」 を解説します。
Event Tickのコストと問題点
Event Tickの仕組みとコスト
Event Tickは、ActorやComponentがアクティブである限り、毎フレーム 実行されるイベントです。例えば、ゲームが60FPS(Frames Per Second)で動作している場合、Event Tickは1秒間に60回実行されます。
このノードに接続されたロジックは、そのフレームで必ず処理されます。もし、100体の敵キャラクターがそれぞれEvent Tickで複雑なAI処理や距離チェックを行っていたらどうなるでしょうか?
たとえTick内の処理が非常に軽くても、Actorの数が増えるほど、その処理回数は爆発的に増加し、CPUに大きな負荷をかけます。特にBlueprintはC++に比べて実行速度が遅いため、この負荷は顕著になります。
よくある間違い:Tickで「ポーリング」を行う
初心者がEvent Tickを使いがちな典型的な例は、ポーリング(Polling) です。
例:プレイヤーとの距離を常にチェックする
// Event Tick -> Get Distance To Player -> Branch (Distance < 500) -> Do Something
この処理は、プレイヤーが近くにいようがいまいが、毎フレーム実行されます。プレイヤーが遠くにいる99%の時間も、無駄な距離計算が走り続けているのです。
Tick-less設計の代替手段
高性能な設計の鍵は、「必要な時に、必要な処理だけを実行する」 というイベント駆動型(Event-Driven) の考え方に切り替えることです。
Event Tickのように「常にチェックする」のではなく、「何かが起こった時だけ」処理を実行するようにします。
代替手段1:周期的な処理には「Timer(タイマー)」を使う
毎フレームである必要はないが、定期的に実行したい処理(例:AIのターゲット再検索、一定時間ごとの回復処理など)には、Timerノードを使用します。
Timerは、指定した時間間隔(例:0.5秒ごと)でイベントや関数を呼び出すことができます。
Blueprint例:0.5秒ごとにHPを回復する
// Event BeginPlay
// -> Set Timer by Event
// - Event: Custom Event (Heal Over Time)
// - Time: 0.5
// - Looping: True
//
// Custom Event (Heal Over Time)
// -> Add Health (e.g., +1)
この方法なら、60FPSの環境でEvent Tickが1秒間に60回実行されるのに対し、Timerはわずか2回しか実行されません。大幅な負荷軽減になります。
代替手段2:Actor間の通信には「Event Dispatcher(イベントディスパッチャー)」を使う
Actor AがActor Bの状態を常に監視(ポーリング)する代わりに、Actor Bで状態が変化したときにイベントを通知 するようにします。これがEvent Dispatcherです。
例:ドアが開いたことを通知する
- ドアActor: ドアが開く処理の最後にEvent Dispatcher(例:
OnDoorOpened)を呼び出す。 - プレイヤーActor:
OnDoorOpenedイベントに処理(例: ログ表示)をバインド(Bind) しておく。
// [ドアActor]
// Event Open Door -> ... (Open Door Logic) ... -> Call OnDoorOpened
// [プレイヤーActor]
// Event BeginPlay -> Get Door Reference -> Bind Event to OnDoorOpened -> Custom Event (Door Opened Handler)
これにより、ドアが開くという「イベント」が発生した瞬間にのみ、関連する処理が実行されます。
代替手段3:物理的な検出には「Collision Events(コリジョンイベント)」を使う
前述の「プレイヤーとの距離を常にチェックする」という問題は、Event Tickではなく、Unreal Engineに組み込まれた物理エンジンイベントで解決できます。
例:トリガーボリュームに入ったことを検出する
- トリガーボリューム:
On Component Begin Overlapイベントを使用する。 - ロジック: このイベントから処理(例: UI表示、ダメージ適用)を実行する。
// [トリガーボリュームComponent]
// On Component Begin Overlap (Other Actor is Player)
// -> Do Something (e.g., Apply Damage)
このイベントは、Actorが実際に重なり合った瞬間に一度だけ、または重なりが終了した瞬間に一度だけ実行されます。Event Tickで毎フレーム距離を計算するよりも、はるかに効率的です。
Tickが必要な場合の最適化
移動やカメラ制御など、どうしても毎フレームの更新が必要な処理も存在します。その場合は、以下の最適化を適用します。
ベストプラクティス:Tickの実行間隔を調整する
Actorのプロパティにある 「Tick Interval(ティック間隔)」 を設定することで、Tickの実行頻度を下げることができます。
- デフォルト: 0.0(毎フレーム実行)
- 設定例: 0.1(1秒間に10回実行)
AIの索敵など、多少の遅延が許容される処理であれば、この設定でパフォーマンスを大幅に改善できます。
ベストプラクティス:Tickを必要な時だけ有効/無効にする
Actorが画面外にいる、またはAIが待機状態にあるなど、Tick処理が不要な状態では、Tickを無効化します。
// Tickを無効化
// Set Actor Tick Enabled (New Tick Enabled: False)
// Tickを有効化
// Set Actor Tick Enabled (New Tick Enabled: True)
例えば、敵AIがプレイヤーを見失った場合、Tickを無効化し、Timerで数秒おきに索敵処理だけを実行するように切り替えることで、アイドル時の負荷をゼロに近づけることができます。
応用:Tick GroupとLatent Action
より高度な制御が必要な場合、以下の機能も活用できます。
💡 Tick Group(ティックグループ)
Tickの実行順序を制御するための仕組みです。ActorのプロパティでTick Groupを設定できます:
- PrePhysics: 物理シミュレーション前に実行(移動入力の処理など)
- DuringPhysics: 物理シミュレーション中に実行
- PostPhysics: 物理シミュレーション後に実行(物理結果に基づく処理)
依存関係のある処理の順序を保証したい場合に有効です。
💡 Latent Action(DelayやTimelineなど)
DelayノードやTimelineは、Tickを使わずに時間経過に基づく処理を実現する強力な代替手段です:
- Delay: 指定秒数後に処理を再開
- Timeline: 時間経過に応じた値の補間(ドアの開閉、フェードイン/アウトなど)
これらはEvent Graphで使用でき、Tick-less設計を維持しながら時間ベースの処理を実装できます。
Tick代替手段のまとめ
Event Tickは強力ですが、ゲーム全体のパフォーマンスを考慮すると、その使用は 「本当に毎フレーム必要か?」 という問いに「Yes」と答えられる場合に限定すべきです。
| Tick代替手段 | 用途 | 実行頻度 |
|---|---|---|
| Timer | 定期的な処理(回復、AIの周期的なチェック) | 指定した間隔(例: 0.5秒ごと) |
| Event Dispatcher | Actor間の非同期通信(状態変化の通知) | イベント発生時のみ |
| Collision Events | 物理的 な接触、範囲検出 | 接触・離脱の瞬間のみ |
| Tick Interval | 毎フレーム処理が必要だが、頻度を下げられる場合 | 指定した間隔(例: 0.1秒ごと) |
Tick-less設計を意識することで、あなたのUnreal Engineプロジェクトはより高速に、より安定して動作するようになるでしょう。パフォーマンスを意識した設計は、プロのテクニカルライターへの第一歩です。