DOTween 은 시작값을 딜레이가 끝난 첫 갱신에서 읽는다
Esc

    DOTween 은 시작값을 딜레이가 끝난 첫 갱신에서 읽는다

    2026년 10월 18일 수정–Unity라이브러리트윈
    transform.DOMoveX(10f, 1f).SetDelay(1f);   // 출발점은 언제 정해질까

    끝값만 있고 시작값이 없습니다. 코루틴이라면 var from = transform.position; 을 yield 위에 적었는지 아래에 적었는지가 답이지만, DOTween 에서는 라이브러리가 그 줄을 대신 부릅니다. 딜레이 동안 다른 코드가 대상을 옮기면 어디서 출발하는지가 여기에 달려 있습니다.

    DOTween 은 시작값을 트윈을 만들 때가 아니라 딜레이가 끝난 첫 갱신에서 읽습니다. 상대 끝값, 변화량, 속도 기반 길이도 그때 Startup 한 번에 정합니다. 기준은 설치본 DOTween 1.3.030(GitHub 커밋 4846808)입니다.

    딜레이가 끝나야 시작값을 읽는다

    트윈 하나의 갱신은 딜레이가 남아 있으면 return false 로 빠지고, 딜레이가 끝난 뒤에야 Startup 을 부릅니다. x = 0 에서 만든 트윈의 대상을 딜레이 도중 x = 4 로 옮기는 경우입니다(한 프레임 0.4초, Linear).

    if (!t.isPlaying)
        return false;
    
    t.creationLocked = true;                    // 첫 갱신에서 잠긴다. 이 뒤로 SetRelative, From 같은 설정은 무시된다
    float tDelta = dt * t.timeScale;
    
    if (!t.delayComplete)
    {
        tDelta = t.UpdateDelay(t.elapsedDelay + tDelta);   // 딜레이가 끝나면 남은 시간을 돌려준다
        if (tDelta <= 0)
            return false;                       // 아직 딜레이 중
    }
    
    if (!t.startupDone)
        t.Startup();                            // 여기서 getter 로 시작값을 읽는다
    
    // 목표 위치 계산 → DoGoto → setter

    x = 0 에서 DOMoveX(10, 1).SetDelay(1) 을 만들고, 딜레이 도중 다른 스크립트가 대상을 x = 4 로 옮기는 경우입니다. 한 프레임은 0.4초, Linear 입니다.

    프레임 기다린 시간 하는 일 x
    1 0.4 딜레이 중. 설정은 잠김 0
    2 0.8 딜레이 중. 이 프레임 뒤에 대상이 옮겨짐 4
    3 1.2 딜레이가 끝나고 0.2초가 남음. 시작값을 4 로 읽음 4 + 6 × 0.2 = 5.2

    트윈을 만든 순간의 x = 0 은 어디에도 쓰이지 않았습니다.

    재 보니 위 표 그대로 시작값이 4 로 읽히고 x = 5.2 가 됐습니다. 시작하기 전의 트윈에 Goto, Rewind, Complete, ForceInit 을 부르면 그 호출 순간 시작값이 읽힙니다.

    Startup 은 시작값, 상대 끝값, 변화량, 길이를 차례로 정한다

    순서가 곧 의존 관계입니다. 시작값이 있어야 상대 끝값을 더하고, 끝값이 확정돼야 변화량이 나오고, 변화량이 있어야 속도를 길이로 바꿉니다. x = 2 에서 DOMoveX(5, 2) 를 세 가지로 불러 봅니다.

    t.startupDone = true;                          // 맨 먼저 켠다. 실패해도 다시 하지 않는다
    
    if (!t.hasManuallySetStartValue)
        t.startValue = t.getter();                 // 대상의 지금 값
    
    if (t.isRelative)
        t.endValue += t.startValue;                // 넘긴 값은 끝값이 아니라 이동량이었다
    
    t.changeValue = t.endValue - t.startValue;     // 여기서 한 번만 계산한다
    
    if (t.isSpeedBased)
        t.duration = Mathf.Abs(t.changeValue) / t.duration;   // duration 칸의 값을 속도로 읽고 길이로 덮어쓴다
    
    t.fullDuration = t.loops > -1 ? t.duration * t.loops : Mathf.Infinity;
    
    if (t.duration <= 0)
        t.easeType = Ease.INTERNAL_Zero;

    x = 2 에서 DOMoveX(5, 2) 를 세 가지로 부르면 이렇습니다.

    호출 시작값 끝값 변화량 길이
    기본 2 5 3 2초
    .SetRelative() 2 7 5 2초
    .SetSpeedBased() 2 5 3 1.5초 (초당 2)

    순서가 곧 의존 관계입니다. 시작값이 있어야 상대 끝값을 더하고, 끝값이 확정돼야 변화량이 나오고, 변화량이 있어야 속도를 길이로 바꿉니다.

    startupDone 이 맨 먼저 켜지므로 getter 가 예외를 던져 실패하면 다시 시도하지 않고 트윈을 파기합니다. changeValue 는 여기서 한 번만 계산되고, 매 프레임은 start + change × ease 만 합니다. From() 이나 ChangeStartValue 로 시작값을 직접 준 트윈은 getter 호출을 건너뜁니다.

    속도 기반 트윈의 길이는 Startup 전에는 없습니다. x = 2 에서 만든 DOMoveX(5f, 2f).SetSpeedBased() 의 Duration() 은 만든 직후 2(넘긴 속도), 첫 갱신 뒤 1.5(거리 3 / 속도 2)였습니다(같은 세 환경). 시퀀스에 넣으면 속도 기반이 꺼지고 넘긴 숫자를 초로 씁니다.

    From 은 붙이는 줄에서 곧바로 값을 쓴다

    From() 기본형은 Startup 을 기다리지 않고, 붙이는 순간 getter 와 setter 를 부릅니다.

    // .From() 을 체인에 붙이는 순간 (setImmediately = true)
    if (t.creationLocked || !t.isFromAllowed)
        return t;                                  // 한 번이라도 갱신된 트윈에는 안 먹는다
    
    t.isFrom = true;
    var prevEnd = t.endValue;                      // 넘긴 값
    t.endValue = t.getter();                       // 지금 값이 끝값이 된다
    t.startValue = isRelative ? t.endValue + prevEnd : prevEnd;
    t.setter(t.startValue);                        // 대상을 그 자리에서 옮긴다
    t.hasManuallySetStartValue = true;             // Startup 이 시작값을 다시 읽지 않는다

    x = 2 에서 부르면 이렇습니다.

    호출 시작값 끝값 From() 직후 x
    DOMoveX(5, 1).From() 5 2 5
    DOMoveX(5, 1).From(true) 2 + 5 = 7 2 7

    시작값을 정하는 getter 와 대상에 값을 쓰는 setter 가 둘 다 From() 을 부른 그 줄에서 실행됩니다. 첫 갱신을 기다리지 않습니다.

    즉시 쓰는 경로는 Startup 과 달리 SafeMode 의 try 안에 있지 않습니다. 대상을 Destroy 한 다음 프레임에 들고 있던 Transform 으로 트윈을 만들어 봤습니다(SafeMode 켬).

    상대값은 Startup 에서 더하고, 순서를 바꾸면 두 번 더한다

    SetRelative() 는 플래그만 켜고, end += start 는 Startup 에서 합니다. 상대 이동의 기준도 딜레이가 끝난 순간의 위치입니다. From 트윈에는 SetRelative 가 막히지만, 순서를 거꾸로 쓴 .SetRelative().From() 은 막히지 않습니다. From() 이 x = 5 로 옮기고(start 5, end 2), Startup 이 켜져 있던 플래그로 end 에 start 를 한 번 더 더해 end = 7 이 됩니다.

    x = 2 에서 DOMoveX(5f, 1f) 에 만든 직후 x 끝난 뒤 x
    .SetRelative().From() 5 7
    .From(true) 7 2

    시퀀스에 건 SetRelative 도 주석은 “Has no effect on Sequences” 라고 하지만, 시퀀스의 Startup 이 자식 전부의 isRelative 를 켜서 x = 2 에서 DOMoveX(5f, 1f) 하나를 넣은 시퀀스가 7 에서 끝났습니다.

    주의할 점

    1. 딜레이 동안 대상이 움직이면 그 자리에서 출발한다고 본다

    안 지키면 생기는 문제. 딜레이가 있는 트윈의 시작값은 딜레이가 끝나는 첫 갱신에서 읽힙니다. 딜레이 동안 물리나 다른 트윈, 애니메이터가 대상을 옮기면 트윈은 옮겨진 자리에서 출발해 출발점이 튀거나 거리가 달라집니다.

    이렇게 쓴다. 출발점이 정해져 있어야 하면 From(시작값, setImmediately: false) 로 시작값을 인자로 못 박습니다. Startup 이 시작값을 다시 읽지 않습니다.

    2. From 은 대상이 살아 있는 것을 확인하고 붙인다

    안 지키면 생기는 문제. .From() 의 즉시 경로에는 SafeMode 의 try 가 없어서, 대상이 파괴됐으면 예외가 호출한 코드로 올라와 그 메서드의 나머지가 실행되지 않습니다. 예외 종류도 에디터에서는 MissingReferenceException, 플레이어에서는 NullReferenceException 이라 에디터에서 본 종류만 catch 하면 빌드에서 빠져나갑니다.

    이렇게 쓴다. From() 을 부르기 전에 if (target == null) return; 으로 거릅니다. 깜빡임이 문제되지 않으면 From(false, false) 로 미뤄 SafeMode 가 감싸는 Startup 경로를 탑니다.

    3. From 에 상대값을 줄 때는 From(true) 를 쓴다

    안 지키면 생기는 문제. .From().SetRelative() 는 조용히 무시되고, .SetRelative().From() 은 끝값에 시작값이 한 번 더 더해져 5 에서 2 가 아니라 5 에서 7 로 갑니다. 어느 쪽이든 경고가 없습니다.

    이렇게 쓴다. “지금 위치에서 얼마만큼 떨어진 곳에서 출발” 은 From(true) 로만 씁니다.

    4. 속도 기반 트윈의 길이는 첫 갱신 뒤에 읽는다

    안 지키면 생기는 문제. SetSpeedBased() 트윈의 Duration() 은 Startup 전에는 초가 아니라 속도를 돌려줍니다. 그 값으로 다른 연출의 타이밍을 맞추면 어긋납니다. 이미 시작한 트윈에 길이 없이 ChangeEndValue(newEnd, true) 를 부르면 길이를 다시 계산하지 않아서, 속도 2 로 시작한 트윈이 초당 3.9 로 움직였습니다(같은 세 환경).

    이렇게 쓴다. 길이가 필요하면 ForceInit() 을 먼저 불러 Startup 을 끝낸 뒤 Duration() 을 읽습니다. 속도 기반 트윈의 끝값을 바꿀 때는 ChangeEndValue(newEnd, 속도, true) 처럼 속도를 다시 넘깁니다.

    정리

    DOTween 은 시작값을 딜레이가 끝난 첫 갱신에서 읽고, 그때 상대 끝값, 변화량, 길이를 한 번에 정합니다.

    • 딜레이 동안 대상이 옮겨지면 옮겨진 자리에서 출발합니다. Goto 같은 호출이 먼저 오면 그 순간에 읽습니다.
    • From() 은 붙이는 줄에서 getter 와 setter 를 부르고, 이 경로는 SafeMode 가 감싸지 않습니다.
    • SetRelative 와 SetSpeedBased 는 플래그이고 실제 계산은 Startup 에서 합니다. 순서를 바꾸거나 시작 뒤에 값을 바꾸면 결과가 어긋납니다.