Quick Reference
Profiler에서 수백~수천 개의 같은 주기 tick이 실제 병목일 때만 중앙 Update Manager를 도입하고, 등록·해제·순회 수정·예외 전파까지 매니저가 책임집니다.
- 기준 버전은 Unity 6.5 (6000.5)입니다. Custom Update Manager는 수백~수천 개의 같은 종류 tick을 측정으로 확인한 뒤 중앙에서 등록·해제·주기 제어하는 패턴입니다.
- 소수의 독립 MonoBehaviour에는 직접
Update가 더 읽기 쉽습니다. 빈 Update가 많다는 사실만으로 전 프로젝트를 중앙 매니저로 바꾸지 않습니다. - 도입하면 등록 중복, 비활성/파괴 후 해제, 순회 중 목록 수정, tick 예외의 전파 정책까지 그 매니저가 책임져야 합니다.
도입 판단
- Profiler에서
BehaviourUpdate와 managed/native callback 경계가 실제 병목인지 확인합니다. AI 수천 개, 멀리 있는 NPC의 저빈도 판단, 반복 UI polling처럼 같은 주기의 많은 대상이 후보입니다. - 중앙 매니저의 이점은 매 frame 호출 수를 줄이고, 가까운 대상은 매 frame, 먼 대상은 5 frame마다처럼 갱신 빈도를 정책화하는 데 있습니다. 개별
Update가 하던 무거운 계산을 그대로 한 리스트에 모으면 총 계산량은 줄지 않습니다. - input, 카메라, physics 경계처럼 Unity lifecycle과 순서가 중요한 코드는 각각의
Update/LateUpdate/FixedUpdate가 더 명확합니다. 한 manager가 모든 update를 대체하는 구조는 디버깅과 execution order를 악화시킬 수 있습니다.
등록과 순회
csharp
using System.Collections.Generic;
using UnityEngine;
public interface ITickable
{
void Tick();
}
public sealed class TickManager : MonoBehaviour
{
private readonly List<ITickable> active = new();
private readonly HashSet<ITickable> activeSet = new();
private readonly HashSet<ITickable> pendingRemove = new();
private bool isTicking;
public void Register(ITickable item)
{
if (isTicking && pendingRemove.Remove(item))
{
activeSet.Add(item);
return;
}
if (activeSet.Add(item))
{
active.Add(item);
}
}
public void Unregister(ITickable item)
{
if (!activeSet.Remove(item)) return;
if (isTicking) pendingRemove.Add(item);
else active.Remove(item);
}
private void Update()
{
int countAtStart = active.Count;
isTicking = true;
for (int i = 0; i < countAtStart; i++)
{
ITickable item = active[i];
if (activeSet.Contains(item)) item.Tick();
}
isTicking = false;
foreach (ITickable item in pendingRemove) active.Remove(item);
pendingRemove.Clear();
}
}Register의HashSet.Add는 같은 대상의 중복 Tick을 막습니다.OnEnable과 수동 초기화가 함께 등록해 매 frame 두 번 움직이는 문제를 피합니다.Unregister는OnDisable또는 실제 수명 종료 지점과 짝을 이룹니다. 순회 중에는 바로 List를 지우지 않고, 현재 frame의 순회 뒤에 제거해 index가 밀려 다음 대상이 건너뛰는 일을 막습니다.- 순회 중 해제했던 대상을 같은 frame에 다시 등록하면
pendingRemove에서 취소해 기존 slot을 유지합니다. 완전히 새로 등록된 대상은countAtStart때문에 다음 frame부터 Tick합니다. “등록한 즉시 같은 frame에 실행”이 필요하면 그 작업을 직접 호출하고 manager 순서에 숨기지 않습니다. Tick()에서 나온 예외를 일반적으로 삼키지 않습니다. 시스템 실패를 logging만 하고 계속 진행할지, 해당 대상 등록을 해제할지는 게임 규칙에 따라 명시적으로 결정합니다.
자주 틀리는 부분
manager가 event보다 항상 싸거나 Update보다 항상 낫지는 않습니다. 매 frame 필요한 조준 보정은 Update가 단순하고, 상태가 드물게 바뀌는 UI는 event가 낫습니다. 중앙 Tick은 대량의 같은 정책을 제어할 때만 비용 대비 가치가 생깁니다.
순회 중 List.Remove는 조용히 대상 하나를 건너뛸 수 있습니다. Tick 안에서 자신이나 다른 대상을 해제하는 흐름이 있으면 deferred remove, snapshot, reverse iteration처럼 수정 규칙을 한 가지로 정해 두세요.
참고 링크
2 sources