DOTween 의 트윈 하나는 값 세 개와 람다 두 개를 든 객체다
Esc

    DOTween 의 트윈 하나는 값 세 개와 람다 두 개를 든 객체다

    2026년 10월 18일 수정–Unity라이브러리트윈
    Tweener t = transform.DOMove(new Vector3(0, 3, 0), 1f);
    // 다음 프레임에
    t.SetLoops(-1);   // 반복이 안 된다

    트윈 참조를 필드에 두고 나중에 설정을 바꾸거나 다시 쓰려면, 그 객체가 무엇을 들고 있고 무엇이 언제 굳는지 알아야 합니다. 위 코드는 예외도 경고도 없이 아무 일도 하지 않습니다.

    DOTween 의 트윈 하나는 값 세 개와 getter·setter 두 람다, 옵션 구조체를 든 TweenerCore 객체 하나입니다. 만들 때 전역 기본값을 복사해 두고, 첫 갱신부터 생성 설정을 잠급니다. 기준은 DOTween 1.3.030(GitHub 커밋 4846808)입니다.

    트윈은 제네릭 객체 하나다

    // DOTween/Core/TweenerCore.cs
    public class TweenerCore<T1,T2,TPlugOptions> : Tweener where TPlugOptions : struct, IPlugOptions
    {
        public T2 startValue, endValue, changeValue;
        public TPlugOptions plugOptions;
        public DOGetter<T1> getter;
        public DOSetter<T1> setter;
        internal ABSTweenPlugin<T1, T2, TPlugOptions> tweenPlugin;

    T1 은 대상 값의 타입, T2 는 트윈이 값을 들고 있는 형식입니다. DOMove 는 <Vector3, Vector3, VectorOptions> 로 둘이 같고, DORotate 는 Quaternion 을 주고받으면서 오일러 각 Vector3 로 들고 있어 360 도를 넘는 끝값을 줄 수 있습니다. 시간과 상태(duration, position, loops, isPlaying), 콜백은 부모 Tween 이 듭니다. 매니저는 모든 트윈을 Tween[] 하나에 넣고 가상 메서드로만 다루므로 제네릭 인자를 몰라도 됩니다.

    DOMove 는 끝값과 기본값만 적는다

    DOMove 한 줄이 이 객체를 채우는 길입니다. 세 메서드를 이어 붙이고 줄였습니다.

    // ShortcutExtensions.DOMove
    // 람다 둘이 target 을 붙잡는다. 캡처 객체 하나와 델리게이트 둘이 생긴다
    var t = DOTween.To(() => target.position, x => target.position = x, endValue, duration);
    
    // DOTween.ApplyTo<Vector3, Vector3, VectorOptions>
    InitCheck();                                   // 처음 부른 것이면 [DOTween] 구동기가 생긴다
    var tweener = TweenManager.GetTweener<Vector3, Vector3, VectorOptions>();   // 새 TweenerCore 를 활성 배열 끝에
    Tweener.Setup(tweener, getter, setter, endValue, duration);
    
    // Tweener.Setup
    if (t.tweenPlugin == null)
        t.tweenPlugin = PluginsManager.GetDefaultPlugin<...>();   // Vector3Plugin
    
    t.getter = getter;
    t.setter = setter;
    t.endValue = endValue;                         // 끝값과 길이만 적는다
    t.duration = duration;                         // 시작값은 비어 있다. 지금 위치를 읽지 않는다
    
    // 전역 기본값을 트윈마다 복사한다
    t.autoKill = DOTween.defaultAutoKill;
    t.isRecyclable = DOTween.defaultRecyclable;
    t.easeType = DOTween.defaultEaseType;
    t.loopType = DOTween.defaultLoopType;
    t.isPlaying = (defaultAutoPlay 가 All 이나 AutoPlayTweeners);   // 기본은 곧바로 재생
    
    // 다시 DOMove
    t.SetOptions(snapping).SetTarget(target);      // target 은 DOKill(transform) 이 이 트윈을 찾는 열쇠
    return t;

    이 한 줄이 끝난 뒤 트윈 객체에 든 것입니다.

    필드 값
    getter, setter 두 람다
    tweenPlugin Vector3Plugin
    endValue, duration (0, 3, 0), 1
    startValue 비어 있음
    easeType, loopType, autoKill, isRecyclable 지금의 전역 기본값을 복사한 것
    isPlaying true
    target 이 Transform

    전역 기본값은 만들 때 복사됩니다. 이징, 루프 방식, 자동 파기, 재활용 여부가 트윈마다 필드로 들어가서, 나중에 DOTween.defaultEaseType 을 바꿔도 이미 만든 트윈은 그대로입니다. 그리고 DOMove 는 위치를 읽지 않습니다. 시작값은 비어 있다가 첫 갱신에서 getter 로 읽힙니다.

    첫 갱신부터 생성 설정이 잠긴다

    트윈이 처음 갱신되는 순간 creationLocked 가 true 가 되고, 생성 설정 메서드는 첫 줄에서 그냥 돌아갑니다.

    // DOTween/TweenSettingsExtensions.cs
    public static T SetLoops<T>(this T t, int loops) where T : Tween
    {
        if (t == null || !t.active || t.creationLocked) return t;
    첫 갱신 뒤에는 무시된다 언제든 먹힌다
    SetLoops, SetDelay, SetAutoKill, SetRelative, SetSpeedBased, SetInverted, From SetEase, SetId, SetTarget, SetLink, SetRecyclable, SetUpdate, SetOptions, OnComplete 같은 콜백

    딜레이가 남아 아직 움직이지 않는 트윈도 첫 갱신은 지나갔으므로 잠깁니다. 시간 구조를 바꾸는 설정은 만든 프레임 안에서만 먹습니다.

    재활용용 Reset 은 두 필드를 남긴다

    재활용을 켜면 죽은 트윈이 풀로 돌아가고, 같은 <T1, T2, TPlugOptions> 를 요청한 호출이 그 객체를 다시 씁니다. 흔적을 지우는 것은 Reset() 입니다. SetInverted() 와 OnPause 를 걸어 둔 재활용 트윈이 끝나는 과정입니다.

    // TweenManager.Despawn(t)
    if (t.onKill != null)
        Tween.OnTweenCallback(t.onKill, t);        // 죽기 직전 onKill 이 먼저 불린다
    
    RemoveActiveTween(t);                          // 활성 배열의 제 칸을 비운다
    
    if (t.isRecyclable)
        _pooledTweeners[빈 칸] = t;                // 재활용 트윈은 풀로
    
    t.active = false;
    t.Reset();
    
    // Tween.Reset()
    timeScale = 1;
    isBackwards = false;
    id = null; stringId = null; intId = -999;
    onStart = onPlay = onRewind = onUpdate = onComplete = onStepComplete = onKill = null;   // onPause 가 없다
    target = null;
    duration = 0; loops = 1; delay = 0;
    isRelative = false;
    creationLocked = startupDone = playedOnce = false;
    position = completedLoops = 0;
    
    // TweenerCore.Reset()
    plugOptions.Reset();
    getter = null;                                 // 람다를 놓아 풀에 있는 동안 대상을 붙잡지 않는다
    setter = null;

    SetInverted() 와 OnPause 를 건 재활용 트윈이 끝나 풀에 들어가면 이렇게 남습니다.

    필드 Reset 뒤
    target, getter, setter, onComplete 등 콜백 일곱 지워짐
    duration, loops, position 처음으로
    onPause 남음
    isInverted 남음
    easeType, 값 세 개 남지만 다음 Setup 과 첫 갱신이 다시 씀

    이징과 값 세 개는 다음 Setup 과 첫 갱신이 다시 쓰지만, isInverted 와 onPause 는 다시 쓰는 곳이 없습니다. 재활용을 켜고 OnPause 를 건 트윈 a 를 끝낸 뒤 같은 타입 b 를 만들자 ReferenceEquals(a, b) 가 true 였고, b.Pause() 한 번에 a 의 콜백이 불렸습니다. SetInverted() 한 트윈도 같은 방식으로 새 트윈의 isInverted 가 true 로 남았습니다. 위치를 duration - position 으로 뒤집는 표식이라 새 트윈이 거꾸로 재생됩니다.

    주의할 점

    1. 생성 설정은 만든 자리에서 끝낸다

    안 지키면 생기는 문제. SetLoops, SetDelay, SetRelative, From 같은 설정은 트윈이 한 번이라도 갱신된 뒤에는 조용히 무시됩니다. 트윈 참조를 필드에 두었다가 다음 프레임에 덧붙이면 아무 일도 일어나지 않는데, 같은 자리에서 SetEase 나 OnComplete 는 먹혀서 원인을 찾기 어렵습니다.

    이렇게 쓴다. 트윈을 만드는 식 한 줄에서 체인으로 모두 붙입니다. 나중에 바꿔야 하는 것이 값이면 ChangeEndValue 를 쓰고, 시간 구조가 바뀌어야 하면 트윈을 새로 만듭니다.

    2. 재활용을 켰으면 SetInverted 와 OnPause 를 쓴 트윈은 재활용에서 뺀다

    안 지키면 생기는 문제. Reset 이 isInverted 와 onPause 를 지우지 않아서, 풀에서 그 객체를 받은 다음 트윈이 반전된 채로 재생되거나 멈출 때 옛 콜백을 부릅니다. 증상이 전혀 다른 곳의 트윈에서 가끔 나타납니다.

    이렇게 쓴다. 두 기능을 쓰는 트윈에는 .SetRecyclable(false) 를 붙여 풀로 돌아가지 않게 합니다.

    3. 전역 기본값은 트윈을 만들기 전에 정한다

    안 지키면 생기는 문제. Setup 이 defaultEaseType, defaultLoopType, defaultAutoKill, defaultRecyclable 을 트윈마다 복사해 둡니다. 게임 도중 기본값을 바꾸면 이미 만든 트윈은 옛 값으로 돌고, 새 트윈만 새 값을 따릅니다. 같은 화면의 트윈끼리 동작이 갈립니다.

    이렇게 쓴다. 기본값은 DOTween.Init 이나 설정 에셋으로 게임 시작 때 한 번 정합니다. 트윈 하나만 다르게 하려면 그 트윈에 SetEase, SetAutoKill 을 붙입니다.

    정리

    DOTween 의 트윈 하나는 값 세 개와 getter·setter, 옵션 구조체를 든 TweenerCore 객체입니다.

    • DOMove 는 끝값, 길이, 전역 기본값의 복사본만 적고 위치는 읽지 않습니다.
    • 첫 갱신부터 creationLocked 가 루프, 지연, 상대값 같은 생성 설정을 조용히 막습니다.
    • 재활용 때 Reset 은 콜백, 대상, 람다를 지우지만 isInverted 와 onPause 는 남깁니다.