---
title: "DOTween 의 트윈 하나는 값 세 개와 람다 두 개를 든 객체다"
description: "DOMove 가 돌려주는 것은 값 세 개, getter·setter, 옵션 구조체를 든 TweenerCore 객체 하나입니다. 만들 때 전역 기본값을 복사해 두고, 첫 갱신부터 루프·지연 같은 설정을 잠그며, 재활용 때 Reset 이 두 필드를 남깁니다."
author: "유니얼"
pubDate: 2026-09-25
updatedDate: 2026-10-18
category: "game-dev/unity/libraries"
tags: ["Unity", "라이브러리", "트윈"]
canonical: https://blog.unialgames.com/posts/unity-dotween-tween/
---
```csharp
Tweener t = transform.DOMove(new Vector3(0, 3, 0), 1f);
// 다음 프레임에
t.SetLoops(-1);   // 반복이 안 된다
```

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

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

## 트윈은 제네릭 객체 하나다

```csharp
// 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` 한 줄이 이 객체를 채우는 길입니다. 세 메서드를 이어 붙이고 줄였습니다.

```csharp
// 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` 가 되고, 생성 설정 메서드는 첫 줄에서 그냥 돌아갑니다.

```csharp
// 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` 를 걸어 둔 재활용 트윈이 끝나는 과정입니다.

```csharp
// 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` 는 남깁니다.