트윈 시퀀싱

트윈을 순차·병렬로 조합해 일시정지를 인식하는 타임라인을 하나의 플루언트 체인으로 만듭니다.

트윈, 딜레이, 콜백을 하나의 일시정지 인식 타임라인으로 체이닝하세요.

인터페이스ITweenSequenceService
끄기 스위치NullTweenSequenceService
어셈블리CommonGameSystem.Core
시작부팅 시 자동 등록 (23개 서비스 중 하나) — 별도 설정 불필요

하는 일

트윈 시퀀싱 서비스는 단일 플루언트 체인으로 여러 단계의 애니메이션 타임라인을 만듭니다. 트윈을 차례로 덧붙이고, 병렬로 겹치고, 딜레이를 끼워 넣고, 중간중간 콜백을 발생시킬 수 있습니다. 각 스텝의 지속 시간은 미리 선언하며, 트윈 서비스에 전달하는 지속 시간과 일치시킵니다. 시퀀스는 시작할 때 정확한 타이밍을 계산합니다 — 개별 트윈을 폴링하는 일은 없습니다.

재생은 타임 클록을 따릅니다: 게임을 일시정지하면 Clock.Gameplay(기본값)에 바인딩된 시퀀스도 함께 멈춥니다. 패키지에 포함된 눈으로 볼 수 있는 데모 씬 Demo/MotionLab.unity에서 시퀀싱이 움직이는 모습을 확인할 수 있습니다.

빠른 시작

using System;
using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)] // Run after the framework has started.
public class MyFirstSequence : MonoBehaviour
{
    private void Start()
    {
        ITweenSequenceService sequences = ServiceLocator.Resolve<ITweenSequenceService>();
        ITweenService tween = ServiceLocator.Resolve<ITweenService>();

        float value = 0f;
        IDisposable handle = sequences.Create()
            .Append(() => tween.To(0f, 1f, 3f, v => value = v), 3f)  // step 1: 3 seconds
            .AppendInterval(0.5f)                                     // then wait half a second
            .AppendCallback(() => Debug.Log("Done!"))                 // then fire a callback
            .Play();                                                  // start playback; keep the handle to cancel
    }
}

실제 코드에서는 Awake()에서 서비스를 resolve해 캐시하고, 취소할 수 있도록 재생 핸들을 필드에 보관하세요 — 아래의 전체 예제가 그 패턴을 보여 줍니다.

API 레퍼런스

ITweenSequence — 빌더이자 핸들

멤버설명
ITweenSequence Append(Func<IDisposable> tweenFactory, float durationSeconds)이전 스텝이 끝난 뒤 시작하는 트윈을 추가합니다. 체이닝을 위해 this를 반환합니다.
ITweenSequence Join(Func<IDisposable> tweenFactory, float durationSeconds)이전 스텝과 병렬로 실행되는 트윈을 추가합니다. this를 반환합니다.
ITweenSequence AppendInterval(float seconds)순수 딜레이(트윈 없음)를 추가합니다. this를 반환합니다.
ITweenSequence AppendCallback(Action callback)타임라인의 현재 지점에 지속 시간 0의 콜백을 추가합니다. 절대 시각이 아니라 시퀀스 기준으로 발생합니다. this를 반환합니다.
IDisposable Play()빌더를 동결하고 재생을 시작합니다. 핸들을 반환하며, 취소하려면 핸들의 Dispose()를 호출하세요.
bool IsPlaying { get; }Play()와 완료 또는 취소 사이에 true.
float Elapsed { get; }지금까지의 재생 헤드 시간(클록 기반, 완료 시 Duration으로 클램프).
float Duration { get; }시퀀스 전체 길이. 시퀀스가 만들어질 때 계산됩니다.
event Action OnSequenceComplete정상 완료 시 정확히 한 번 발생합니다. 취소 시에는 절대 발생하지 않습니다.

ITweenSequenceService — 팩토리

멤버설명
ITweenSequence Create(SequenceOptions options = default)비어 있는 새 시퀀스 빌더를 만듭니다.
int ActiveSequenceCount { get; }살아 있는 시퀀스의 수(시작되었고 아직 완료·취소되지 않음).

SequenceOptions — 설정

멤버설명
int MaxStepsPerSequence = 256시퀀스당 최대 스텝 수(Append/Join/AppendInterval/AppendCallback 합산). 1–4096으로 클램프.
int MaxStepsPerTick = 64한 프레임에 발생하는 최대 스텝 수. 폭주 루프를 막습니다. 1–4096으로 클램프.
int MaxConcurrentSequences = 0서비스 전체의 최대 라이브 시퀀스 수. 0은 무제한. 0–65536으로 클램프.
Clock DefaultClock = Clock.Gameplay재생 헤드를 구동하는 타임 클록. 기본값은 게임이 일시정지되면 함께 멈춥니다.
bool LetStepExceptionsPropagate = falsefalse(기본)이면 스텝 콜백이 던진 예외를 잡아 로그합니다. true이면 전파됩니다 — 개발 중에 유용합니다.
bool LogSequenceLifecycle = falsetrue이면 build/play/complete/cancel 추적을 로그합니다. 이 로그는 릴리즈 빌드에서도 항상 컴파일되어 들어갑니다.

예제

슬라이드 인과 페이드 인을 동시에 한 뒤 완료를 알리는 UI 패널:

using System;
using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]
public class PanelTransition : MonoBehaviour
{
    private ITweenSequenceService _seqService;
    private ITweenService _tween;
    private CanvasGroup _canvasGroup;
    private RectTransform _rect;
    private IDisposable _playingHandle;

    private void Awake()
    {
        _seqService = ServiceLocator.Resolve<ITweenSequenceService>();
        _tween = ServiceLocator.Resolve<ITweenService>();
        _canvasGroup = GetComponent<CanvasGroup>();
        _rect = GetComponent<RectTransform>();
    }

    public void PlayEntrance()
    {
        _playingHandle?.Dispose();  // cancel the previous run, if any
        Vector2 startPos = _rect.anchoredPosition;
        _playingHandle = _seqService.Create()
            // The Tween service only computes the value; you apply it in onUpdate.
            .Append(() => _tween.To(startPos, Vector2.zero, 0.3f, v => _rect.anchoredPosition = v), 0.3f)
            .Join(() => _tween.To(_canvasGroup.alpha, 1f, 0.3f, a => _canvasGroup.alpha = a), 0.3f)
            .AppendCallback(() => OnEntranceComplete())
            .Play();
    }

    private void OnEntranceComplete() => Debug.Log("Panel in!");

    private void OnDestroy() => _playingHandle?.Dispose();  // cleanup
}

끄는 방법

ServiceLocator.Replace<ITweenSequenceService>(new NullTweenSequenceService());

이렇게 하면 모든 시퀀스가 무음 처리됩니다. Create()는 스텝을 버리는 빌더를 반환하고, Play()는 빈 핸들을 반환하며, 콜백은 절대 발생하지 않습니다. 애니메이션이 필요 없는 테스트나 저사양 머신에서 모션을 끌 때 유용합니다. 교체 구현이 생성될 때 경고가 한 번 로그되므로 교체가 조용히 지나가는 일은 없습니다. 프로그래밍 오류 — null 트윈 팩토리, 잘못된 클록 — 는 여전히 throw하므로 실제 실수는 계속 보입니다.

흔한 함정

  • 지속 시간 불일치는 여러분의 버그입니다. 시퀀스는 Append/Join에 전달한 durationSeconds를 신뢰합니다. 이는 팩토리가 만드는 트윈의 실제 지속 시간과 일치해야 합니다. 3초로 선언했는데 트윈이 2.8초를 돌면 타이밍이 어긋납니다. 트윈 서비스는 트윈의 남은 시간을 보고할 수 없으므로 시퀀스도 이를 검사할 수 없습니다 — 같은 지속 시간 리터럴을 두 호출에 복사해 넣으세요.
  • AppendCallback은 상대적이지 절대적이지 않습니다. 시퀀스의 현재 타임라인 위치에서 발생하며, 벽시계 시각이 아닙니다. "게임 시작 5초 후"에는 스케줄러 서비스를 대신 쓰세요.
  • 클록 소스는 시작 시점에 캡처됩니다. 시퀀스는 기본적으로 Clock.Gameplay에 바인딩됩니다. 시작 이후에 Time 서비스를 교체하면(ServiceLocator.Replace<ITimeService>(…)), 시퀀싱은 만들어질 때의 원래 시간 소스를 계속 사용합니다.
  • 취소는 완료가 아닙니다. 재생 핸들의 Dispose()는 시퀀스를 취소하며, OnSequenceComplete는 발생하지 않습니다. 이 이벤트는 성공 경로 정리에만 쓰세요. 취소는 조용합니다.
  • Play() 후 빌더는 동결됩니다. Play() 이후에 Append, Join, AppendInterval, AppendCallback을 호출하면 InvalidOperationException이 발생합니다. 타임라인을 늘려야 하면 새 시퀀스를 만드세요.

관련 페이지

  • 트윈 — 이 시퀀스들이 조합하는 단일 값 애니메이션 서비스
  • 타임 — 클록 바인딩과 일시정지 동작
  • 스케줄러 — 절대 시각의 콜백. 시퀀스 콜백을 보완합니다