콘텐츠로 이동

Unity Timeline & Signal 이벤트 시스템

Unity Timeline은 시간 기반 시퀀서로 애니메이션, 오디오, 카메라 전환을 한 타임라인에서 조율합니다. Signal Track을 활용하면 타임라인의 특정 시점에 게임 로직 이벤트를 트리거할 수 있습니다.


using UnityEngine;
using UnityEngine.Playables;
public class CutsceneManager : MonoBehaviour
{
[SerializeField] PlayableDirector director;
public void PlayCutscene()
{
director.Play();
}
public void StopCutscene()
{
director.Stop();
}
public void SeekTo(double timeInSeconds)
{
director.time = timeInSeconds;
director.Evaluate(); // 해당 시점으로 즉시 이동
}
void Start()
{
// 재생 완료 이벤트
director.stopped += OnCutsceneFinished;
}
void OnCutsceneFinished(PlayableDirector d)
{
Debug.Log("컷씬 종료");
GameManager.Instance.OnCutsceneComplete();
}
}

에디터에서:

Timeline 창 → Add → Signal Track
Signal Track에서 우클릭 → Add Signal Emitter
Signal Emitter 클릭 → Create Signal Asset (예: SpawnEnemySignal.asset)

Signal Receiver 컴포넌트를 대상 오브젝트에 추가하고 Signal Asset과 콜백 메서드를 연결합니다.


using UnityEngine;
public class TimelineEventHandler : MonoBehaviour
{
// Signal Receiver에서 연결할 메서드 (반드시 public void)
public void OnSpawnEnemy()
{
EnemySpawner.Instance.SpawnWave();
Debug.Log("Signal: 적 스폰");
}
public void OnPlayExplosion()
{
VFXManager.Instance.PlayExplosion(transform.position);
}
public void OnShowSubtitle()
{
SubtitleUI.Instance.Show("박사: 이 실험은 중단할 수 없어!");
}
}

using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
public class DynamicSignalEmitter : MonoBehaviour
{
[SerializeField] PlayableDirector director;
[SerializeField] SignalAsset mySignal;
// 코드로 특정 시점에 Signal 발신
public void EmitSignalAtTime(double time)
{
// Signal Track의 Emitter를 직접 발신
// (주로 커스텀 Track에서 사용)
var graph = director.playableGraph;
if (graph.IsValid())
{
// PlayableDirector를 통한 Signal 수동 트리거
director.time = time;
director.Evaluate();
}
}
}

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
// 클립 데이터
[System.Serializable]
public class SubtitleClip : PlayableAsset, ITimelineClipAsset
{
public string text = "";
public ClipCaps clipCaps => ClipCaps.None;
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<SubtitleBehaviour>.Create(graph);
playable.GetBehaviour().text = text;
return playable;
}
}
// 클립 동작
public class SubtitleBehaviour : PlayableBehaviour
{
public string text;
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
SubtitleUI.Instance?.Show(text);
}
public override void OnBehaviourPause(Playable playable, FrameData info)
{
SubtitleUI.Instance?.Hide();
}
}
// Track 정의
[TrackColor(0.2f, 0.8f, 0.4f)]
[TrackClipType(typeof(SubtitleClip))]
public class SubtitleTrack : TrackAsset { }

// Animation Track의 바인딩을 런타임에 교체
public void BindCharacter(GameObject character)
{
var timelineAsset = director.playableAsset as TimelineAsset;
foreach (var track in timelineAsset.GetOutputTracks())
{
if (track is AnimationTrack animTrack)
{
director.SetGenericBinding(
animTrack,
character.GetComponent<Animator>());
}
}
}

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI;
public class TimelineProgressUI : MonoBehaviour
{
[SerializeField] PlayableDirector director;
[SerializeField] Slider progressSlider;
void Update()
{
if (director.state == PlayState.Playing)
{
progressSlider.value =
(float)(director.time / director.duration);
}
}
public void OnSliderChanged(float value)
{
director.time = value * director.duration;
director.Evaluate();
}
}

Timeline은 Signal Track으로 게임 이벤트와 연결하고, 커스텀 Track으로 자막·UI 효과 등 게임 특화 기능을 확장할 수 있습니다. PlayableDirector.SetGenericBinding으로 런타임에 바인딩 대상을 교체하면 같은 타임라인을 여러 캐릭터에 재사용할 수 있습니다.