Unity 검색

커스텀 타임라인 마커 만들기

2019년 6월 25일 엔진 & 플랫폼 | 9 분 소요
공유

Is this article helpful for you?

Thank you for your feedback!

Unity 2019.1버전부터 타임라인(Timeline)에서 마커를 사용할 수 있습니다. 이번 블로그 게시글에서는 커스텀 마커를 만드는 방법을 다룹니다.

이전 글에서는 이벤트를 트리거하기 위해 타임라인 시그널(Signals)을 사용하는 방법에 대해 소개했습니다. 이 기능을 처음 만들었을 때 클립을 사용하면 기능이 제대로 작동하지 않는다는 사실을 알게 되었습니다. 시그널의 주요 특성 중 하나가 바로 “지속 시간”이 없다는 것이기 때문에 새로운 유형의 항목이 필요했습니다. 따라서 타임라인에 마커를 추가하게 되었습니다. 내부적으로, 시그널은 마커를 사용하여 구현됩니다. 이제 타임라인에 커스텀 마커를 추가하는 방법을 살펴보겠습니다.

이 블로그 게시글에서 사용한 코드와 에셋은 여기에서 확인할 수 있습니다.

기본 마커

마커는 타임라인 에셋에 추가할 수 있는 새로운 항목으로 특정 시점을 표시할 때 사용됩니다. 또한 마커는 활성화 클립, 오디오 클립, 애니메이션 클립 등의 클립과 마찬가지로 특수 기능이 있습니다. 이를 통해 각 워크플로에 알맞은 커스텀 마커를 생성할 수 있습니다.

새로운 유형의 마커를 추가하려면 다음과 같이 Marker 클래스를 상속하는 클래스를 만들기만 하면 됩니다.

public class SimpleMarker : UnityEngine.Timeline.Marker {}

이제 아래 그림과 같이 타임라인 마커 영역에 있는 트랙에 커스텀 마커를 자유롭게 추가할 수 있습니다.

여기서 만들어진 기본 마커는 시각적 항목일 뿐입니다. 즉, 이 마커는 트리거 시 코드를 실행할 수 없습니다. 대신 이 마커는 스냅 포인트 또는 주석의 역할을 할 수 있으며(파트 5 참조) 에디터 및 런타임의 타임라인 API를 통해서도 액세스할 수 있습니다.

코드를 실행하려면 다른 시스템과 마커를 결합해야 합니다. 시스템 작동 방식에 대해 알고 싶다면 다음 두 파트를 참고하시기 바랍니다. 그렇지 않은 경우 파트 4를 보시기 바랍니다.

파트 2 - Playable 알림

Playable API를 사용하면 PlayableGraph가 처리되는 동안 오브젝트에 알림을 보낼 수 있습니다. Playable 알림을 사용하면 이벤트가 발생했음을 대상 오브젝트에 알릴 수 있습니다. 아래에서 간단한 그래프를 만들고 수동으로 알림을 보내 봅시다.

먼저 INotification 인터페이스를 구현하는 클래스인 notification을 생성해야 합니다.

public class MyNotification : INotification
{
    public PropertyName id { get; }
}

id 프로퍼티를 사용하면 알림을 고유하게 식별할 수 있지만 이 예제에서는 해당 기능이 필요 없으므로 기본 구현을 사용하겠습니다.

다음으로 INotificationReceiver 인터페이스를 구현하는 클래스인 receiver가 필요합니다. 이 예제에서는 알림을 받으면 receiver가 그 시간을 인쇄합니다. 

class ReceiverExample : INotificationReceiver
{
   public void OnNotify(Playable origin, INotification notification, object context)
   {
       if (notification != null)
       {
           double time = origin.IsValid() ? origin.GetTime() : 0.0;
           Debug.LogFormat("Received notification of type {0} at time {1}", notification.GetType(), time);
       }
   }
}

다음 예제에서 새로운 Playable Graph와 Playable Output을 생성했습니다. AddNotificationReceiver 메서드를 사용하여 ReceiverExample을 Playable Output에 추가했습니다. m_Receiver 인스턴스는 이제 이 출력으로 전송되는 알림을 받을 수 있습니다. 

이제 알림을 보낼 수 있습니다. Playable Output에서 PushNotification 메서드를 사용해 새 알림을 푸시할 수 있습니다. 

public class ManualNotification : MonoBehaviour
{
    PlayableGraph m_Graph;
    ReceiverExample m_Receiver;

    void Start()
    {
        m_Graph = PlayableGraph.Create("NotificationGraph");
        var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

        //Create and register a receiver
        m_Receiver = new ReceiverExample();
        output.AddNotificationReceiver(m_Receiver);

        //Push a notification on the output
        output.PushNotification(Playable.Null, new MyNotification());

        m_Graph.Play();
    }
}

한 가지 주의할 점이 있습니다. PushNotification은 호출하자마자 알림이 전송되는 것이 아니라 대기열에 알림이 올라가고, 그래프가 완전히 처리될 때까지 대기열에 누적됩니다. LateUpdate 단계 바로 직전에 대기열에 있는 모든 알림이 그래프의 출력으로 전송됩니다. 모든 알림이 전송되면 대기열이 비워지고 새로운 프레임이 시작됩니다.

그래프가 재생되면 OnNotify 메서드가 인수로 전송된 알림과 함께 m_Receiver 인스턴스에 호출됩니다.  플레이 모드로 전환할 때 아래 메시지가 콘솔에 나타납니다.

Received notification of type MyNotification at time 0

리시버가 알림을 정확히 받았지만, 알림을 보내는 시간을 제어하지는 못했습니다. 이 부분은 파트 3에서 이어서 다룹니다.

파트 3 - TimeNotificationBehaviour

이제 Playable Graph를 통해 원하는 시간에 알림이 전송되도록 하는 방법을 알아보겠습니다. 이를 위해서 빌트인 클래스인 TimeNotificationBehaviour를 사용합니다. 이 클래스는 표준 PlayableBehaviour이므로 어느 그래프에나 추가할 수 있고, 로직을 조금 더 포함하여 정확한 시간에 알림을 보낼 수 있도록 해줍니다. 앞의 예제를 조금 수정해보도록 합시다.

public class ScheduledNotification : MonoBehaviour
{
   PlayableGraph m_Graph;
   ReceiverExample m_Receiver;

   void Start()
   {
       m_Graph = PlayableGraph.Create("NotificationGraph");
       var output = ScriptPlayableOutput.Create(m_Graph, "NotificationOutput");

       //Create and register a receiver
       m_Receiver = new ReceiverExample();
       output.AddNotificationReceiver(m_Receiver);

       //Create a TimeNotificationBehaviour
       var timeNotificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(m_Graph);
       output.SetSourcePlayable(timeNotificationPlayable);

       //Add a notification on the time notification behaviour
       var notificationBehaviour = timeNotificationPlayable.GetBehaviour();
       notificationBehaviour.AddNotification(2.0, new MyNotification());

       m_Graph.Play();
   }
}

생성된 Playable Graph는 아래와 같습니다.

여기에서는 Playable Output에서 직접 PushNotification을 호출하는 대신 출력에 TimeNotificationBehaviour를 첨부하고 알림을 추가했습니다. 이 동작은 자동으로 정확한 시간에 알림을 출력으로 푸시합니다. 이제 다음과 같은 메시지가 콘솔에 표시됩니다.

Received notification of type MyNotification at time 2.00363647006452

이전과 다른 결과가 나왔습니다! 이제 언제 알림을 전송할지 제어할 수 있게 되었습니다!

하지만 TimeNotificationBehaviour에 알림을 추가할 때 정확하게 2초를 지정했는데 왜 알림이 정확히 2초에 전송되지 않을까요?

notificationBehaviour.AddNotification(2.0, new MyNotification());

AddNotification 메서드는 정확한 시간을 보장하지는 않습니다. Playable Graph의 시간은 Unity가 새 프레임을 렌더링하기 시작할 때 업데이트됩니다. 게임의 프레임 속도에 따라 PlayableGraph의 측정 시간이 TimeNotificationBehaviour에 알림을 추가할 때 지정한 시간과 정확하게 일치하지 않을 수 있습니다. 대신 AddNotification 메서드는 PlayableGraph의 시간이 트리거 시간을 지나면 곧바로 알림을 보내도록 보장합니다.

파트 4 - MarkerNotification

새 API는 Playable Graph에서 알림을 수동으로 전송하고 싶은 경우를 제외하고는 작업을 번거롭게 만듭니다. 따라서 타임라인에서 적절한 PlayableGraph를 자동으로 생성하면 알림을 처리할 수 있습니다.

파트 1에서 설명한 방법을 활용하여 INotification 인터페이스를 구현하는 새 마커를 생성해보겠습니다.

public class NotificationMarker : Marker, INotification
{
   public PropertyName id { get; }
}

Marker를 상속받아 INotification을 구현하는 클래스가 타임라인에 해당 알림을 지원하는 Playable Graph를 생성하도록 요청합니다. 이 마커를 빈 타임라인에 추가하면 다음과 같은 Playable Graph가 생성됩니다.

타임라인에 자체 PlayableBehaviour가 추가된 점을 제외하면 파트 3에서 생성한 Playable Graph와 거의 동일하며, 직접 Playable Graph를 생성하는 것보다 훨씬 쉽습니다!

이제 마지막으로 알림을 받을 대상을 식별해야 합니다. 방법은 시그널과 동일합니다.

  • 마커가 타임라인 헤더 영역에 있는 경우: 현재 타임라인을 재생하는 PlayableDirector를 소유한 오브젝트가 알림을 받습니다.
  • 마커가 트랙에 있는 경우: 트랙에 바인딩된 오브젝트가 알림을 받습니다.

대상 오브젝트에 위치하면서 INotificationReceiver 인터페이스를 구현하는 모든 컴포넌트가 알림을 받습니다.

예제에서는 타임라인 헤더 영역에 NotificationMarker 2개를 추가했습니다. 또한 타임라인을 재생하는 오브젝트에 NotificationReceiver를 추가했습니다.

콘솔은 이제 다음과 같은 메시지를 표시합니다.

Received notification of type NotificationMarker at time 1.00330553948879

Received notification of type NotificationMarker at time 2.016666666666

파트 3에서 본 것과 정확히 같은 메시지입니다.

INotification 인터페이스를 구현하는 마커만이 적절한 Playable Graph를 생성해 알림을 지원합니다. 아래 표에서 커스텀 마커를 만드는 각 방법의 특징을 비교하여 확인할 수 있습니다.

파트 5 - 커스텀 스타일

커스텀 마커는 일반적으로 “핀” 아이콘으로 표시되지만, 원하는 이미지로 아이콘을 변경할 수 있습니다. 아이콘 변경 방법을 알아보기 위해 먼저 주석 마커(Annotation marker)를 생성하겠습니다.

첫 번째 단계에서는 스타일 시트를 생성합니다. 스타일 시트를 사용해 에디터의 시각적인 모습을 변경할 수 있습니다. Editor 폴더 내 StyleSheets/Extensions 폴더에 common.uss라는 파일을 추가하면 됩니다. 예제에서는 다음 위치에 새 파일을 추가했습니다. 

“5-Annotation/Editor/Stylesheets/Extensions/common.uss”

USS(Unity Style Sheet, Unity 스타일 시트) 파일은 아래와 같이 새로운 시각적 요소를 나타내기 위해 CSS와 유사한 구문을 사용합니다.

Annotation
{
   width:18px;
   height:18px;
   background-image: resource("Assets/5-Annotation/Editor/pencil.png");
}

위 스타일에서 연필 아이콘을 사용하도록 하고 크기 프로퍼티도 지정했습니다. 이제 화면에 마커를 표시할 때 해당 스타일을 사용하도록 타임라인에 요청할 수 있습니다.

[CustomStyle("Annotation")]
public class Annotation : Marker
{
   [TextArea] public string annotation;
}

CustomStyle 속성을 통해 스타일을 지정할 수 있습니다. 예제에서는 common.uss 파일에 추가된 Annotation 스타일을 사용했습니다.

이제 이 주석 마커를 타임라인에 추가하면 앞서 생성한 커스텀 스타일을 사용하게 됩니다.

파트 6 - 기능 조합하기

마커와 알림을 활용하는 방법을 시연하기 위해 Github 저장소에 Jump 마커를 추가했습니다. 이 마커는 JumpReceiver와 함께 사용하여 타임라인의 한 지점에서 다른 지점으로 “점프”합니다. 목표 지점은 Destination 마커로 지정됩니다. 다음 예제는 커스텀 스타일을 포함해 이 블로그 게시글에서 다룬 모든 내용이 포함되어 있습니다.

주황색 화살표는 점프 지점이고 보라색 화살표는 목표 지점입니다. 구현 방법은 여기에서 확인하시기 바랍니다.

알림과 마커는 Playable Graph와 타임라인 API에 추가된 아주 유용한 기능입니다. 이번 예제가 마커를 사용하는 데 많은 도움이 되었길 바랍니다. 도움이 필요할 경우 타임라인 포럼에 문의해 주시기 바랍니다.

번외: 파트 7 - 클립 알림

지금까지 PlayableGraph가 알림을 보내고 타임라인이 이를 사용해 마커의 기능을 강화하는 것을 살펴봤습니다. 그렇다면 클립은 어떨까요? 클립도 알림을 보낼 수 있을까요?

보낼 수 있습니다!

클립은 Playable Behaviour에서 알림을 보낼 수 있습니다. 

public class ClipNotificationBehaviour : PlayableBehaviour
{
   double m_PreviousTime;

   public override void OnGraphStart(Playable playable)
   {
       m_PreviousTime = 0;
   }

   public override void ProcessFrame(Playable playable, FrameData info, object playerData)
   {
       if ((int)m_PreviousTime < (int)playable.GetTime())
       {
           info.output.PushNotification(playable, new MyNotification());
       }

       m_PreviousTime = playable.GetTime();
   }
}

이 예제에서는 클립을 처리하는 중 매 초마다 알림을 푸시합니다. 이 클립을 타임라인에 추가하고, 타임라인을 구동하는 오브젝트에 NotificationReceiver를 추가했을 때 출력되는 메시지는 다음과 같습니다.

Received notification of type MyNotification at time 1.01593019999564

Received notification of type MyNotification at time 2.00227000191808

Received notification of type MyNotification at time 3.01137680560353

위 코드가 제대로 작동하는 것을 확인할 수 있습니다. 클립이 이미 대부분의 기능을 지원하기 때문에 고유의 Playable Behaviour 클래스를 이용해 알림을 보내고 싶은 경우 마커를 사용할 필요가 없습니다.

2019년 6월 25일 엔진 & 플랫폼 | 9 분 소요

Is this article helpful for you?

Thank you for your feedback!