타임라인 확장하기: 실용적인 가이드

유니티는 Unity 2017.1과 함께 타임라인을 출시했고, 그 이후로 많은 피드백을 받았습니다. 많은 개발자들과 이야기를 나누고 포럼에서 사용자들의 의견을 수렴한 결과, 많은 분들이 타임라인을 단순한 시퀀싱 도구 이상의 용도로 사용하길 원한다는 사실을 알게 되었습니다. 저는 이미 유나이트 오스틴 2017에서 타임라인을 다른 용도로 사용하는 방법에 대해 몇 차례 강연을 한 적이 있습니다(예: 유나이트 오스틴 2017).
타임라인은 처음부터 확장성을 주요 목표로 설계되었으며, 기능을 설계한 팀은 사용자가 기본 제공 클립 외에 자신만의 클립과 트랙을 만들기를 원할 것이라는 점을 항상 염두에 두고 있었습니다. 따라서 타임라인을 사용한 스크립팅에 대한 질문이 많이 있습니다. 타임라인의 기반이 되는 시스템은 강력하지만, 초심자가 아닌 경우 사용하기 어려울 수 있습니다.
먼저 타임라인이란 무엇인가요? 애니메이션 클립, 음악, 음향 효과, 카메라 샷, 파티클 효과, 기타 타임라인 등 다양한 요소를 순서대로 정렬할 수 있는 선형 편집 툴입니다. 본질적으로 Premiere®, After Effects® 또는 Final Cut®과 같은 도구와 매우 유사하지만 실시간 재생을 위해 설계되었다는 차이점이 있습니다.
타임라인의 기본 개념에 대해 자세히 알아보려면 Unity 매뉴얼의 타임라인 문서 섹션에서 해당 개념을 광범위하게 활용하는 방법을 살펴보는 것이 좋습니다.
타임라인은 Playables API 위에 구현됩니다.
여러 데이터 소스(애니메이션, 오디오 등)를 읽고 믹스하여 출력을 통해 재생할 수 있는 강력한 API 세트입니다. 이 시스템은 정밀한 프로그래밍 제어를 제공하며 오버헤드가 적고 성능에 맞게 조정됩니다. 참고로 애니메이터 컴포넌트를 구동하는 스테이트 머신의 이면에는 동일한 프레임워크가 있으며, 애니메이터를 위해 프로그래밍한 적이 있다면 익숙한 개념을 볼 수 있을 것입니다.
기본적으로 타임라인 재생이 시작되면 재생 가능한 노드로 구성된 그래프가 만들어집니다. PlayableGraph라는 트리와 같은 구조로 구성되어 있습니다.
참고: 씬(애니메이터, 타임라인 등)의 플레이가능 그래프 트리를 시각화하려면 플레이가능 그래프 비주얼라이저라는 툴을 다운로드하면 됩니다. 이 게시물에서는 이를 사용하여 다양한 사용자 지정 클립에 대한 그래프를 시각화합니다.
이제 타임라인을 확장하는 방법을 보여주는 세 가지 간단한 예시를 살펴보겠습니다. 기초를 다지기 위해 타임라인에 스크립트를 추가하는 가장 쉬운 방법부터 시작하겠습니다. 이후 대부분의 기능을 활용할 수 있도록 점차적으로 더 많은 개념이 추가될 예정입니다.
이 게시물에 사용된 모든 예제가 포함된 작은 데모 프로젝트를 패키지로 만들었습니다. 자유롭게 다운로드하여 따라해 보세요. 그렇지 않으면 게시물 자체로 즐길 수 있습니다.
참고: 에셋의 경우 각 예제에서 클래스를 구분하기 위해 접두사 ("Simple_", "Track_", "Mixer_" 등)를 사용했습니다. 아래 코드에서는 가독성을 위해 이러한 접두사를 생략했습니다.
이 첫 번째 예제는 매우 간단합니다. 사용자 지정 클립으로 조명 컴포넌트의 색상과 강도를 변경하는 것이 목표입니다. 사용자 지정 클립을 만들려면 두 개의 스크립트가 필요합니다:
- 데이터의 경우: PlayableAsset에서 상속하기
- 로직을 위한 하나: PlayableBehaviour에서 상속하기
Playable API의 핵심 원칙은 로직과 데이터의 분리입니다. 따라서 먼저 다음과 같이 하고자 하는 작업을 작성하는 PlayableBehaviour를 만들어야 합니다:
public class LightControlBehaviour : PlayableBehaviour
{
public Light light = null;
public Color color = Color.white;
public float intensity = 1f;
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (light != null)
{
light.color = color;
light.intensity = intensity;
}
}
}여기서 무슨 일이 일어나고 있나요? 먼저, 변경하려는 라이트의 프로퍼티에 대한 정보가 있습니다. 또한 PlayableBehaviour에는 재정의할 수 있는 ProcessFrame이라는 메서드가 있습니다.
ProcessFrame은 업데이트할 때마다 호출됩니다. 이 메서드에서 라이트의 프로퍼티를 설정할 수 있습니다. PlayableBehaviour에서 재정의할 수 있는 메서드 목록은 다음과 같습니다. 그런 다음 사용자 지정 클립에 대한 재생 가능한 에셋을 생성합니다:
public class LightControlAsset : PlayableAsset
{
public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1.0f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}플레이어블 에셋에는 두 가지 용도가 있습니다. 첫째, 타임라인 에셋 자체에 직렬화된 클립 데이터가 포함되어 있습니다. 둘째, Playable 그래프에 최종적으로 표시될 PlayableBehaviour를 빌드합니다.
첫 번째 줄을 보세요:
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);그러면 새 플레이블이 생성되고 여기에 커스텀 비헤이비어인 라이트컨트롤비헤이비어가 첨부됩니다. 그런 다음 재생 가능한 비헤이비어에서 조명 프로퍼티를 설정할 수 있습니다.
노출된 참조는 어떻게 되나요? 플레이가능 에셋은 에셋이므로 씬의 오브젝트를 직접 참조할 수 없습니다. 그러면 노출된 참조는 CreatePlayable이 호출될 때 객체가 해결될 것이라는 약속 역할을 합니다.
이제 타임라인에 재생 가능한 트랙을 추가하고 새 트랙을 마우스 오른쪽 버튼으로 클릭하여 사용자 지정 클립을 추가할 수 있습니다. 클립에 조명 컴포넌트를 할당하여 결과를 확인합니다.
이 시나리오에서 기본 제공 재생 가능 트랙은 방금 만든 것과 같은 간단한 재생 가능 클립을 수용할 수 있는 일반 트랙입니다. 더 복잡한 상황에서는 전용 트랙에서 클립을 호스팅해야 합니다.
첫 번째 예시에서 한 가지 주의할 점은 사용자 지정 클립을 추가할 때마다 각 클립에 라이트 컴포넌트를 할당해야 하는데, 클립이 많을 경우 지루할 수 있다는 점입니다. 트랙의 바인딩된 객체를 사용하여 이 문제를 해결할 수 있습니다.

트랙에는 객체 또는 컴포넌트가 바인딩될 수 있으며, 이는 트랙의 각 클립이 바인딩된 객체에서 직접 작동할 수 있다는 의미입니다. 이는 매우 일반적인 동작이며 실제로 애니메이션, 활성화 및 시네마머신 트랙이 작동하는 방식입니다.
여러 클립으로 라이트의 속성을 수정하려면 라이트 컴포넌트를 바인딩된 오브젝트로 요청하는 사용자 지정 트랙을 만들 수 있습니다. 사용자 지정 트랙을 만들려면 트랙 에셋을 확장하는 다른 스크립트가 필요합니다:
[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset {}여기에는 두 가지 속성이 있습니다:
- 트랙 클립 유형은 트랙이 허용할 플레이블 에셋 유형을 지정합니다. 이 경우 커스텀 라이트 컨트롤 에셋을 지정합니다.
- 트랙 바인딩 유형은 트랙이 요청할 바인딩 유형을 지정합니다(게임 오브젝트, 컴포넌트 또는 에셋일 수 있음). 이 경우 라이트 컴포넌트가 필요합니다.
또한 트랙과 함께 작동하도록 하려면 PlayableAsset 및 PlayableBehaviour를 약간 수정해야 합니다. 참고로 더 이상 필요하지 않은 줄은 주석 처리했습니다.
public class LightControlAsset : PlayableAsset
{
public LightControlBehaviour template;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
return playable;
}
}이제 플레이가능 비헤이비어에는 Light 변수가 필요하지 않습니다. 이 경우 ProcessFrame 메서드는 트랙의 바인딩된 객체를 직접 제공합니다. 객체를 적절한 유형으로 캐스팅하기만 하면 됩니다. 멋지네요!
public class LightControlAsset : PlayableAsset
{
//public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
//lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}PlayableAsset은 더 이상 라이트 컴포넌트에 대한 노출된 레퍼런스를 보유할 필요가 없습니다. 레퍼런스는 트랙에서 관리되며 PlayableBehaviour에 직접 제공됩니다.
타임라인에서 LightControl 트랙을 추가하고 여기에 조명을 바인딩할 수 있습니다. 이제 해당 트랙에 추가하는 각 클립은 트랙에 바인딩된 Light 컴포넌트에서 작동합니다.
그래프 시각화 도구를 사용하여 이 그래프를 표시하면 다음과 같은 모양이 됩니다:

예상대로 오른쪽의 클립은 5개의 블록이 하나로 연결되는 것을 볼 수 있습니다. 하나의 상자를 트랙이라고 생각하면 됩니다. 그러면 모든 것이 타임라인의 보라색 상자에 들어갑니다.
참고: '플레이 가능'이라는 분홍색 상자는 사실 유니티에서 제공하는 무료 믹서인 플레이 가능입니다. 그렇기 때문에 클립과 같은 색입니다. 믹서란 무엇인가요? 다음 예제에서는 믹서에 대해 이야기하겠습니다.
타임라인은 클립을 겹쳐서 블렌딩 또는 크로스페이딩을 만들 수 있는 기능을 지원합니다. 사용자 지정 클립도 블렌딩을 지원합니다. 하지만 이 기능을 사용하려면 모든 클립의 데이터에 액세스하여 블렌딩하는 믹서를 만들어야 합니다.
믹서는 앞서 사용한 LightControlBehaviour와 마찬가지로 PlayableBehaviour에서 파생됩니다. 실제로는 여전히 ProcessFrame 함수를 사용합니다. 주요 차이점은 이 Playable이 트랙 스크립트에서 CreateTrackMixer 함수를 오버라이드하여 명시적으로 믹서로 선언된다는 것입니다.이제 LightControlTrack 스크립트는 다음과 같이 보입니다:
[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) {
return ScriptPlayable<LightControlMixerBehaviour>.Create(graph, inputCount);
}
}
이 트랙에 대한 재생 가능한 그래프가 생성되면 새 동작(믹서)도 생성되어 트랙의 모든 클립에 연결됩니다.
또한 로직을 PlayableBehaviour에서 믹서로 이동하려고 합니다. 따라서 이제 PlayableBehaviour는 상당히 비어 있는 것처럼 보일 것입니다:
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}
기본적으로 런타임에 플레이가능 에셋에서 가져올 데이터만 포함합니다. 반면 믹서는 ProcessFrame 함수에 모든 로직이 있습니다:
public class LightControlMixerBehaviour : PlayableBehaviour
{
// NOTE: This function is called at runtime and edit time. Keep that in mind when setting the values of properties.
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
Light trackBinding = playerData as Light;
float finalIntensity = 0f;
Color finalColor = Color.black;
if (!trackBinding)
return;
int inputCount = playable.GetInputCount (); //get the number of all clips on this track
for (int i = 0; i < inputCount; i++)
{
float inputWeight = playable.GetInputWeight(i);
ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
LightControlBehaviour input = inputPlayable.GetBehaviour();
// Use the above variables to process each frame of this playable.
finalIntensity += input.intensity * inputWeight;
finalColor += input.color * inputWeight;
}
//assign the result to the bound object
trackBinding.intensity = finalIntensity;
trackBinding.color = finalColor;
}
}믹서는 트랙에 있는 모든 클립에 액세스할 수 있습니다. 이 경우 현재 블렌드에 참여하고 있는 모든 클립의 강도 및 색상 값을 읽어야 하므로 for 루프를 사용하여 반복해야 합니다. 각 사이클마다 입력에 액세스(GetInput(i))하고 각 클립의 가중치(GetInputWeight(i))를 사용하여 최종 값을 구축하여 해당 클립이 블렌드에 얼마나 기여하고 있는지 파악합니다.
한 클립은 빨간색, 다른 클립은 흰색을 제공하는 두 개의 클립이 혼합되어 있다고 가정해 보겠습니다. 혼합이 1/4 정도 진행되면 색상은 0.25 * Color.red + 0.75 * Color.white가 되어 약간 희미한 빨간색이 됩니다.
루프가 끝나면 합계를 바인딩된 라이트 컴포넌트에 적용합니다. 이렇게 하면 다음과 같은 것을 만들 수 있습니다:

이제 빨간색 상자가 바로 프로그래밍한 믹서 재생 가능이며, 이제 모든 권한을 갖게 된 것을 확인할 수 있습니다. 이는 위의 예제 2에서 Unity에서 제공하는 기본 믹서를 사용한 것과는 대조적입니다.
또한 그래프가 블렌드 중간에 있기 때문에 녹색 상자 2와 3은 모두 믹서에 밝은 선이 연결되어 있어 가중치가 각각 0.5와 비슷하다는 것을 알 수 있습니다.
믹서에서 블렌드를 구현할 때마다 로직을 결정하는 것은 사용자의 몫이라는 점을 명심하세요. 두 가지 색상을 블렌딩하는 것은 쉽지만, AI 시스템에서 서로 다른 AI 상태를 나타내는 두 개의 클립을 블렌딩하면 어떻게 될까요? UI에 두 줄의 대화가 있나요? 스톱모션 애니메이션에서 두 개의 정적 포즈를 어떻게 블렌딩하나요? 블렌드가 연속적이지 않고 '계단식'일 수도 있습니다(따라서 포즈가 서로 변하지만 불연속적으로 변합니다): 0, 0.25, 0.5, 0.75, 1).
이 강력한 시스템을 마음껏 활용하면 시나리오는 흥미진진하고 무궁무진합니다!
이 가이드의 마지막 단계로 이전 예제로 돌아가서 '템플릿'이라고 하는 것을 사용하여 데이터를 이동하는 다른 방법을 구현해 보겠습니다. 이 패턴의 가장 큰 장점은 템플릿의 속성을 키프레임으로 설정할 수 있어 타임라인에서 바로 사용자 지정 클립의 애니메이션을 만들 수 있다는 점입니다.
이전 예제에서는 PlayableAsset과 PlayableBehaviour 모두에서 Light 컴포넌트, 색상 및 강도에 대한 참조가 있었습니다. 데이터는 인스펙터의 플레이블 에셋에서 설정한 다음 런타임에 그래프를 생성할 때 플레이블 비헤이비어에 복사했습니다.
이 방법은 유효한 방법이지만 항상 동기화 상태를 유지해야 하는 데이터를 복제하게 됩니다. 이는 쉽게 실수로 이어질 수 있습니다. 대신 PlayableAsset에 참조를 생성하여 PlayableBehaviour "템플릿"이라는 개념을 사용할 수 있습니다.따라서 먼저 LightControlAsset을 다음과 같이 다시 작성합니다:
public class LightControlAsset : PlayableAsset
{
public LightControlBehaviour template;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
return playable;
}
}라이트 컨트롤 에셋에는 이제 값 자체가 아닌 라이트 컨트롤 비헤이비어에 대한 레퍼런스만 있습니다. 이전보다 훨씬 적은 코드입니다!
LightControlBehaviour는 변경하지 않은 채로 둡니다:
[System.Serializable]
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}이제 타임라인에서 클립을 선택하면 템플릿에 대한 참조가 이 인스펙터를 자동으로 생성합니다:

이 스크립트가 준비되면 애니메이션을 만들 준비가 된 것입니다. 새 클립을 만들면 트랙 헤더에 빨간색 원형 버튼이 표시됩니다. 즉, 이제 애니메이터를 추가하지 않고도 클립을 키프레임으로 만들 수 있습니다. 빨간색 버튼을 클릭하고 클립을 선택한 다음 키를 만들 재생헤드를 원하는 위치에 배치하고 해당 속성 값을 변경하기만 하면 됩니다.
흰색 상자 버튼을 클릭하여 커브 보기를 확장하여 키프레임으로 만든 커브를 볼 수도 있습니다:

타임라인 클립을 더블 클릭하면 Unity에서 애니메이션 패널을 열고 타임라인에 연결할 수 있다는 추가 혜택이 있습니다. 이 버튼이 표시되면 링크되어 있음을 알 수 있습니다:

이 경우 타임라인과 애니메이션 창 모두에서 스크러빙할 수 있으며 재생 헤드가 동기화된 상태로 유지되므로 키 프레임을 완벽하게 제어할 수 있습니다. 이제 애니메이션 창에서 애니메이션을 수정하여 보다 편안한 환경에서 키프레임 작업을 할 수 있습니다:

이 보기에서는 애니메이션 커브와 도프시트의 모든 기능을 사용하여 사용자 지정 클립의 애니메이션을 세밀하게 다듬을 수 있습니다.
참고: 이러한 방식으로 애니메이션을 적용하면 애니메이션 클립을 만들게 됩니다. 타임라인 자산에서 찾을 수 있습니다:

이 포스팅이 스크립팅을 통해 타임라인을 한 단계 더 발전시킬 수 있는 무한한 가능성에 대한 유용한 소개가 되었기를 바랍니다.
질문, 피드백, 타임라인 창작물이 있으면 트위터로 알려주세요!
