【Unreal Engine】Event TickをやめてTimerとイベントで設計する方法

作成: 2025-12-12最終更新: 2026-07-19

Event Tickの毎フレーム実行は数で牙をむく。Timer・Event Dispatcher・Collision Eventsによるイベント駆動の代替設計を図解し、Tickまみれの見張りAIを眠らせる実践まで。

「毎フレーム動いてほしいから、とりあえずEvent Tickに繋ぐ」。これで動きます。ただ、その習慣のまま敵を100体並べた日に、フレームレートは静かに崩れ始めます。

Event Tickの怖さは、1つ1つが軽くても 数を掛け算した瞬間に牙をむく ことです。この記事では、「常に見張る」から「起きたときだけ動く」へ切り替える Tick-less(ティックレス)設計 を、3つの代替手段とあわせて解説します。実践では、Tickまみれの見張りAIを「完全に眠れるAI」へ建て直します。

回し車を走り続ける人形と、ベンチでベルを待つ人形。Tickとイベント駆動のイメージ

この記事でわかること

  • Tickのコストは 60回/秒 × 体数 で爆発する
  • 脱Tickの合言葉は 「見張るのをやめて、教えてもらう」(イベント駆動)
  • 3つの代替手段: Timer・Event Dispatcher・Collision Events
  • どうしてもTickが要るときの Tick IntervalON/OFF切り替え
  • 実践:見張りAIを 「完全に眠れる」設計 に建て直す

Sponsored

Event Tickのコストと問題点

Event Tickは、Actorがアクティブな限り 毎フレーム 実行されます。60FPSなら1秒に60回。ここまでは知っているとおりで、問題はその先の掛け算です。

1体のTickは60回/秒。100体で6000回/秒。軽い処理も、数で牙をむく

100体の敵がそれぞれTickで距離チェックをすれば、 毎秒6000回 の処理になります。BlueprintはC++より実行が遅いため、この数の暴力はフレームレートに直撃します。

そして初心者が最もやりがちなTickの使い方が ポーリング(Polling) 、つまり「変化していないか、毎フレーム確認しに行く」やり方です。

やりがちな例:毎フレームの距離チェック
Event Tick → Get Distance To(プレイヤー) → Branch(500未満?) → 何かする

プレイヤーが遠くにいる99%の時間も、無駄な距離計算が走り続けます。 見張り続けるのではなく、教えてもらう 。これがTick-less設計の核心です。

望遠鏡でずっと見張り続けるのではなく、ベルが鳴ったときだけ動く。見張るのをやめて、教えてもらう
Sponsored

Tick-less設計:3つの代替手段

「何かが起こった時だけ実行する」イベント駆動への切り替えは、この3つの道具で実現します。

Tickの3つの代替手段。Timerは決めた間隔で、Event Dispatcherは起きたときだけ、Collisionは触れた瞬間だけ

手段1:周期処理は「Timer」

毎フレームは不要でも定期的に走らせたい処理(毒のスリップダメージ、AIのターゲット再検索など)は、Set Timer by Event に任せます。

Event BeginPlay → Set Timer by Event(Time: 0.5, Looping: True)
  Event ピン ← Custom Event: HealOverTime
HealOverTime → Health に +1 して Set

60FPSのTickなら1秒に60回のところ、0.5秒Timerなら 2回 。処理回数が30分の1になります。

手段2:状態変化の監視は「Event Dispatcher」

「ドアが開いたか毎フレーム確認する」のではなく、 ドアが開いた瞬間に向こうから教えてもらいます 。ドア側は開いた時に OnDoorOpened をCallするだけ、聴きたい側はBindしておくだけです。詳しい実装の型はEvent Dispatcherの記事で扱ったとおりです。

手段3:接近・接触の検出は「Collision Events」

冒頭の「距離を毎フレーム測る」問題の正解がこれです。Sphere CollisionやBoxのトリガーを置けば、 On Component Begin Overlap が「入った瞬間」に1回だけ 呼ばれます。距離計算は物理エンジンが最適化された形で肩代わりしてくれます。

On Component Begin Overlap(検知範囲のSphere) → プレイヤー判定 → 追跡開始
On Component End Overlap → 追跡終了
Sponsored

Tickが必要な場合の最適化

滑らかな移動やカメラ制御など、本当に毎フレームが必要な処理もあります。そのときは「使い方」を最適化します。

  • Tick Interval で頻度を下げる: Actorの詳細パネルで Tick Interval を設定(既定0.0=毎フレーム)。索敵のように多少の遅延が許される処理なら 0.1(毎秒10回)で十分なことが多いです
  • Tickを状態でON/OFFする: Set Actor Tick Enabled で、必要な間だけTickを生かします。「追跡中はON、待機中はOFF」という形です。次の実践でこのスイッチを組みます
追跡中はTick On、待機中はTick Offで低頻度Timerだけが動く。働くときだけ、目を覚まさせる
Sponsored

実践:見張りAIを「完全に眠れる」設計に建て直す

仕上げに、3つの道具とTickスイッチを1体のAIに統合します。題材はステルスゲームの見張り、サバイバルの徘徊モンスター、タワーディフェンスの迎撃塔——「近づくまで何もしなくていい敵」です。

スライムの検知円の外をプレイヤーが歩いている間�、スライムは眠っている。円に入られるまで、完全に眠る

再現用の準備: 敵Blueprint BP_Guard に、次のComponentと変数を用意します。

種類名前設定
ComponentDetectionSphere(Sphere Collision)Sphere Radius = 800.0(検知範囲)
変数ChaseTarget(Actor型)初期値なし。追跡相手を覚えておく
変数ChaseHandle(Timer Handle型)追跡Timerを止めるために保存する

Before(Tick版)は、敵全員が毎フレーム距離を測ったうえ、追跡も Event Tick → AI Move To で毎フレーム目的地を再発行していました。 これは索敵も移動も、まさにこの記事がやめようと言っているポーリングそのもの です。Afterでは、追跡もTimerに載せ替えて、Tickを1行も使わずに建て直します。

  1. 入られた瞬間だけ、追跡Timerを回し始める: 検知範囲に触れたら、0.2秒間隔で目的地を更新する追跡ループを起動します

    検知範囲のOverlapからCast To BP_Playerを通り、ChaseTargetを設定してChaseStep Timerを起動するノードグラフ
    On Component Begin Overlap(DetectionSphere)
      → Cast To BP_Player(Other Actor)
          成功 → Set ChaseTarget(= Other Actor)
              → Is Timer Active by Handle(ChaseHandle)
                  False → Set Timer by Event(Time: 0.2, Looping: true)
                              Event ピン ← Custom Event: ChaseStep
                          → Set ChaseHandle(= Return Value)   ← 止める用に保存
    
    ChaseStep(0.2秒ごと。Tickではない)
      → Is Valid(ChaseTarget)
          Valid → AI Move To(Pawn: self, Destination: ChaseTarget の位置)
    

    AI Move To は目的地に向かう移動を 一度発行すれば、あとはエンジンが移動を続けてくれる ノードです。毎フレーム呼び直す必要はなく、0.2秒ごとにプレイヤーの最新位置へ目的地を更新するだけで、追跡は滑らかに成立します。60回/秒だった目的地の再発行が、 5回/秒 で済みます。

    Is Timer Active by Handle でガードしているのは、プレイヤーが検知円を出たり入ったりするたびに追跡Timerが二重・三重に積み重なるのを防ぐためです。 再入でTimerが増殖するのは、この手の設計で最もハマりやすいバグ です。

  2. 出て行ったら、追跡を止めて眠る: 範囲外へ出たら追跡Timerを破棄し、低頻度の見回りTimerだけ残します

    On Component End Overlap(DetectionSphere)
      → Clear and Invalidate Timer by Handle(ChaseHandle)    ← 追跡を止める
      → Set ChaseTarget(= None)
      → Set Timer by Event(Time: 0.5, Looping: true)
          Event ピン ← Custom Event: LookAround
    LookAround → 軽い周囲チェック(向きを変える程度)
    
    検知範囲から出たら追跡Timerを破棄し、0.5秒の見回りTimerだけを残すノードグラフ

    追跡中でも目的地の更新は5回/秒、見回り中は2回/秒。 この敵は最初から最後までEvent Tickを1つも使いません 。存在感は残しつつ、コストはほぼゼロです。

    1つ落とし穴: Clear and Invalidate Timer by Handle を呼ばずに Set ChaseTarget(None) だけにすると、追跡Timerは回り続けて Is Valid が毎回falseになるだけの空回りが残ります。 止めるべきTimerは、Handleを保存して確実に破棄 します。

Playして確かめましょう。敵の数を10体、50体と増やしても、あなたが検知円の外にいる限り、 フレームレートはほとんど変わらないはず です。円に踏み込んだ敵だけが追跡Timerを回し、離れればそれも止まる。Statsコマンドstat game を表示しながらビフォーアフターを見比べると、Game時間の差が数字で確認できます。追跡ロジックが複雑になってきたら、敵AIの本筋である Behavior Treeで敵AIを実装するMove To タスクへ移すのが次の一歩です。

ポイントは2つです。

  • 「起きている敵」の数を制御する設計: 全員が働く設計から、「近くの数体だけ働く」設計へ。負荷は敵の総数ではなく 同時に起きている数 で決まるようになります
  • Tickは悪ではなく、贅沢品: 追跡中の滑らかな動きにはTickが最適です。禁止ではなく「必要な瞬間だけ贅沢する」という付き合い方が、Tick-less設計の本当の意味です

おまけ:先に知っておくと良いこと

  • DelayとTimelineもTick-lessの仲間: Delay(数秒後に再開)や Timeline(時間経過で値を補間するノード。ドアの開閉やフェード)は、Tickを書かずに時間ベースの処理を実現します(→ FunctionとMacroの違いで触れた遅延ノードの世界です)
  • Tick Groupという上級設定: Tickの実行順序は Tick Group(PrePhysics / DuringPhysics / PostPhysics)で制御できます。「物理の結果を受けてから処理したい」ような依存関係が出てきたら思い出してください
  • ComponentのTickも忘れずに: 自作Componentは既定でTickが有効です。使わないなら Start with Tick Enabled をオフに(→ Blueprint Componentの記事

まとめ

代替手段用途実行頻度
Timer定期処理(回復、周期的な索敵)指定間隔(例: 0.5秒ごと)
Event Dispatcher状態変化の通知イベント発生時のみ
Collision Events接近・接触の検出触れた/離れた瞬間のみ
Tick Interval / ON-OFFどうしても毎フレームが要る処理必要なときだけ

Tickを繋ぐ前の合言葉は 「これは本当に毎フレーム必要か?」 。Noなら3つの道具へ、Yesでも「必要な間だけ」に絞ります。それだけで、あなたのゲームは体感できるレベルで軽くなります。

パフォーマンスの現状を数字で知りたくなったら、Statsコマンドでボトルネックを特定するへ進みましょう。

あなたのプロジェクトでいまEvent Tickに繋がっている処理、全部言えますか? 1つずつ「毎フレーム必要?」と問い詰めてみてください。半分は、今夜から眠らせてあげられるはずです。