iTween은 상당히 강력한 기능을 가지고 있으나, 아래와 같은 문제가 있다.
1. 사용가능한 컴포넌트가 제한
2. 불편한 콜백
3. 코드 가독성이 안좋음
4. 순차 애니메이션 설정이 힘듬
위와 같은 문제를 해결하기 위해 iTween과 비슷하면서도 보다 편리한 기능의 애니메이션 툴킷을 직접 만들었다. (이것도 요새 루리웹 유행인 없만루에 껴주나요???)
FFani.Tween
FFani는 Fire and Forget animation의 약자로 iTween같이 onUpdate가 필요하지 않고 start명령으로 애니메이션이 실행되는 구조를 fire and forget이라고 많이 부르는 모양인데, 이러한 기능을 보다 깜끔하게 지원하기 위한 툴킷을 직접 개발하였다. API사양과 설계는 QML이라는 언어의 애니메이션 프레임워크를 많이 참조하였다.
상세 설명
1. 사용가능한 컴포넌트
안타깝게도 iTween은 애니메이션 타겟 종류가 제한되어 있다. Transform, Renderer같은 대표적인 컴포넌트만 지원하며 4.6부터 등장한 New UI의 컴포넌트들을 애니메이션 시킬 수 없다는 것은 너무나 치명적인 문제점이었다.
단 이를 해결하기 위해 몇 가지 범용 기능을 제공하기는 한다. 예를 들어, Fade-in 애니메이션을 위해서는 UI 오브젝트의 Image컴포넌트의 color.a값을 매 프레임마다 바꾸어주어야 하며 이를 위해 iTween.ValueTo를 사용하였다.
private void FadeIn()
{
fadeScreen.SetActive (true);
Hashtable tweenParams = new Hashtable();
tweenParams.Add ("from", 1.0f);
tweenParams.Add ("to", 0.0f);
tweenParams.Add ("time", 2.0f);
tweenParams.Add ("onupdate", "OnOpacityUpdated");
iTween.ValueTo(gameObject, tweenParams);
}
private void OnOpacityUpdated(float opacity)
{
Color color = fadeScreen.GetComponent<Image>().color;
color.a = opacity;
fadeScreen.GetComponent<Image>().color = color;
if (opacity < 0.01f) {
fadeScreen.SetActive (false);
}
}
이 코드의 핵심은 매 프레임마다 OnOpacityUpdated라는 함수를 호출하여 opacity를 갱신하는 데에 있다. 이처럼 지원하지 않는 컴포넌트는 매 번 불편하게 ValueTo + 콜백을 써야한다.
새로운 툴킷을 사용하여 FadeIn 메서드를 아래와 같이 수정하였다.
private void FadeIn()
{
fadeScreen.SetActive(true);
FFani.Tween (
target: fadeScreen.GetComponent<Image>(),
propertyName: "color.a",
from: 1.0f,
to: 0.0f,
duration: 2.0f
).Start();
}
fadeScreen의 Image.color.a에 접근 가능하기 때문에 OnOpacityUpdated 메서드는 이제 필요가 없어졌다. UI를 애니메이션 시킬때마다 콜백을 하나씩 만들어야 했던 고통에서 벗어나게 되었다.
2. 콜백
애니메이션이 종료한 후에 바로 다른 코드를 실행시키기 위해서는 콜백을 설정해 주어야 한다. 예를 들어 현재 개발중인 SRPG의 경우 시작시 Fade-in과 함께 카메라 애니메이션이 시작되며 이것이 종료되면 엔트리 픽 모드로 들어간다. 엔트리 픽 모드로 들어 갈 때 약간의 카메라 줌인이 필요하며 이를 위해 아래와 같이 구현했었다.
public void onEntryPickMode()
{
// 초기 실행 부분 생략
Hashtable tweenParams = new Hashtable();
tweenParams.Add ("time", 3.0f);
tweenParams.Add ("p-osition", originPosition);
// 애니메이션 종료시 실행할 콜백을 여기서 설정한다.
tweenParams.Add ("oncomplete", "onActivateEntryPick");
tweenParams.Add ("oncompletetarget", gameObject);
iTween.MoveFrom(GameObject.Find ("CamPivot"), tweenParams);
}
public void onActivateEntryPick()
{
// 실제 중요한 코드는 여기 다 있다.
}
단지 3초간의 카메라 애니메이션을 위해서 코드가 두 개로 분리되어있다. onActivateEntryPick은 다른 곳에서는 호출되지 않으므로 굳이 나눠줄 필요가 없지만 iTween에서 반드시 콜백 함수의 이름을 넘겨 주어야 콜백이 되기 때문에 애니메이션 종료 후 실행할 코드를 별도의 메서드로 만들어야 했다.
위의 코드를 아래와 같이 수정한다.
public void onEntryPickMode()
{
// 초기 실행 부분 생략
FFani.Tween(
target: GameObject.Find ("CamPivot").transform,
propertyName: "p-osition",
from: originp-osition,
duration: 3.0f,
easingCurve: FFaniEasing.OutCubic
).Remind (
() => {
// onActivateEntryPick()의 코드를 여기로 옮김.
}
).Start();
}
콜백 함수를 람다로 전달받으므로 따로 메서드를 만들 필요 없이 애니메이션 종료 후 실행할 코드를 Remind()내부에 넣으면 된다. (주의!! 현재 애니메이션 실행시 화면이 깜빡이는 버그가 있다.)
3. 가독성
iTween를 사용할 때의 문제점은, 코드를 위에서 아래로 읽으면 파라미터를 먼저 읽고 제일 중요한 정보인MoveTo인지 MoveFrom인지, 대상 오브젝트가 무엇인지를 나중에 알게 된다는데에 있다.
Hashtable tweenParams = new Hashtable();
tweenParams.Add ("from", 1.0f);
tweenParams.Add ("to", 0.0f);
tweenParams.Add ("time", 2.0f);
// 제일 중요한 정보를 제일 나중에 알 수 있다.
iTween.ValueTo(gameObject, tweenParams);
위와 똑같은 동작을 아래와 같이 표기한다.
FFani.Tween (
target: fadeScreen.GetComponent<Image>(),
propertyName: "color.a",
from: 1.0f,
to: 0.0f,
duration: 2.0f
).Start();
대상 컴퍼넌트와 프로퍼티 이름을 맨 위에서 명확히 하였고 그 후에 from, to, duration 정보가 온다. 파라미터의 순서 자체는 코드의 동작에 전혀 문제가 없지만, 위와 같은 순서로 할 경우 가독성이 확실히 좋아진다. 또한 Hashtable API를 귀찮게 이용하지 않아도 되기 때문에 작성하기에도 훨씬 편하다.
4. 순서대로 애니메이션 플레이
iTween에서는 두 개의 애니메이션을 순서대로 플레이 시키기 위해서 delay를 이용해 애니메이션을 시간차를 두고 플레이시키는 편법을 사용하였다.
아래 코드는 장면전환시 사용하도록 fade-out -> fade-in을 순차적으로 실행하는 애니메이션이다. 이경우 fade-in은 fade-out의 플레이시간에 맞춰 delay를 주어서 애니메이션의 순서를 맞추었다.
// fade-out
Hashtable tweenParams = new Hashtable();
tweenParams.Add ("from", 0.0f);
tweenParams.Add ("to", 1.0f);
tweenParams.Add ("time", 2.0f);
iTween.ValueTo(gameObject, tweenParams);
// fade-in
Hashtable tweenParams2 = new Hashtable();
tweenParams2.Add ("from", 1.0f);
tweenParams2.Add ("to", 0.0f);
tweenParams2.Add ("time", 2.0f);
tweenParams2.Add ("delay", 2.0f);
iTween.ValueTo(gameObject, tweenParams2);
이 경우에는 뒤에 연결되는 애니메이션이 하나밖에 없으므로 별 문제가 없으나 여러 개가 연결된 애니메이션을 delay로만 연결시킬 경우, 앞의 애니메이션의 플레이 시간이 변경되면 뒤따라 오는 애니메이션의 delay 파라미터를 전부 재계산해 주어야 하는 문제점이 있다.
위의 코드를 아래와 같이 고칠 수 있다.
FFani.Serial (
FFani.Tween ( // fade-out
target: fadeScreen.GetComponent<Image>(),
propertyName: "color.a",
from: 1.0f,
to: 0.0f,
duration: 2.0f
),
FFani.Tween ( // fade-in
target: fadeScreen.GetComponent<Image>(),
propertyName: "color.a",
from: 0.0f,
to: 1.0f,
duration: 2.0f
)
).Start ();
두 개의 FFani.Tween을 FFani.Serial로 묶어줌으로써 순서대로 애니메이션을 실행 시킬 수 있다. 뒤의 애니메이션은 앞의 애니메이션이 끝나면 자동으로 실행된다. 앞의 애니메이션의 플레이시간 (duration)이 바껴도 뒤의 애니메이션이 이를 의식할 필요가 없다.
현재 내가 직접 작성한 코드는 iTween을 없애고 FFani.Tween으로 대체할 계획이다. 아직은 지원되지 않는 기능도 있고 테스트도 충분히 하지 못했기 때문에 당분간은 FFani.Tween의 완성도를 높이는 데에 주력해야 될 듯 싶다.