Tween 애니메이션 예제
public static class Tween
{
/// <param name = "time"> 경과 시간 </ param>
/// <param name = "initial"> 기본값 </ param>
/// <param name = "delta"> 종료 값과 초기 값의 차이 </ param>
/// <param name = "duration"> 수명 </ param>
public static Vector3 EaseInOutExpo ( float time, Vector3 initial, Vector3 delta, float duration )
{
if ( time <= 0f )
{
return initial ;
}
if ( time >= duration )
{
return initial + delta ;
}
time /= ( duration / 2f );
if ( time < 1f )
{
return delta / 2f * Mathf . Pow ( 2f, 10f * ( time - 1f )) + initial;
}
time -= 1f;
return delta / 2f * (-1f * Mathf . Pow ( 2f, -10f * time ) + 2f ) + initial;
}
}
애니메이션
- Tween의 지속 시간 동안 만 (TakeWhile)
- 경과 시간을 스트림으로 흘려 보내고 (Select)
- 경과 시간에서 현재 위치를 계산 (Select)
- 계산 한 현재 위치를 반영하기 (Subscribe)
void Start ()
{
var start = Time.time ; // 시작 시간
var initial = transform.position ; // 시작 위치
var delta = new Vector3 ( 5f, 0f, 0f ); // 이동 거리
var duration = 3f ; // Tween 기간
gameObject . UpdateAsObservable ()
. Select ( _ => Time.time - start ) // 경과 시간을 흘린다
. TakeWhile ( time => time <= duration ) // 지속 시간 동안 만
. Select ( time => Tween.EaseInOutExpo ( time, initial, delta, duration )) // 경과 시간에서 현재 위치를 계산
. Subscribe ( pos => transform . position = pos ); // 계산 한 값을 반영
}
Tween.EaseInOutExpo 가 반환하는 것은 tween 애니메이션으로 변한 위치, Vector3 타입입니다. 이를 Select해서 Subscribe하고 있으므로 Subscribe의 OnNext 이벤트의 인자 pos는 tween 애니메이션으로 변경된 Vector3 값이고 이를 transform.position에 할당하여 최종적인 위치 변환을 계산합니다.
Repeat 연산자를 이용한 반복
void Start ()
{
var start = Time.time; // 시작 시간
var initial = transform . position ; // 시작 위치
var delta = new Vector3 ( 5f, 0f, 0f ); // 이동 거리
var duration = 3f; / / Tween 기간
gameObject . UpdateAsObservable ()
. Select ( _ => Time.time - start ) // 경과 시간을 흘린다
. TakeWhile ( time => time <= duration ) // 지속 시간 동안 만
. Select ( time => Tween.EaseInOutExpo ( time , initial , delta , duration )) // 경과 시간에서 현재 위치를 계산
. Repeat () // 반복 처리르 위해서 Repeat 연산자를 삽입!
. Subscribe ( pos => transform.position = pos ); // 계산 한 값을 반영
}
Repeat 연산자를 삽입하는 것만으로는 의도한대로 tween 애니메이션의 반복 처리가 이루어지지 않습니다.
어느 부분이 문제일까요?
애니메이션의 첫 번째 처리후 Repeat연산자를 통해 두 번째 처리시에는 이미 시간이 duration 값을 초과했기 때문에 TakeWhile에서 스트림으로 값을 흘려 보내지 않기 때문입니다.
반복할 때마다 스트림에 흘려 보낼 시작 시간을 초기화하면 문제를 해결할 수 있습니다. 다음은 수정한 코드입니다.
void Start ()
{
var initial = transform . position ; // 시작 위치
var delta = new Vector3 ( 5f , 0f , 0f ); // 이동 거리
var duration = 3f ; // Tween 기간
Observable.Empty < float > () StartWith (() => Time.time ) // 반복마다 시작 시간을 다시 얻을
. SelectMany ( start =>
gameObject.UpdateAsObservable ()
.Select ( _ => Time.time - start )) // 경과 시간을 흘린다
. TakeWhile ( time => time <= duration ) // 지속 시간 동안 만
. Select ( time => Tween.EaseInOutExpo ( time, initial, delta, duration )) // 경과 시간에서 현재 위치를 계산
. Repeat () // 반복
. Subscribe ( pos => transform.position = pos ); // 계산 한 값을 반영
}
이제 의도한대로 반복해서 tween 애니메이션을 처리합니다.
Empaty, StartWith
SelectMany: IObservable 호출이 가능?
확장 메소드로 작성
public static class Tween
{
/// <param name = "time"> 경과 시간 </ param>
/// <param name = "initial"> 기본값 </ param>
/// <param name = "delta"> 종료 값과 초기 값의 차이 </ param>
/// <param name = "duration"> 수명 </ param>
public static Vector3 EaseInOutExpo ( float time , Vector3 initial , Vector3 delta , float duration )
{
/ * 생략 * /
}
/// <param name = "initial"> 기본값 </ param>
/// <param name = "delta"> 종료 값과 초기 값의 차이 </ param>
/// <param name = "duration"> 지속 시간 </ param>
public static IObservable < Vector3 > EaseInOutExpo < T > ( this IObservable < T > observable ,
Vector3 initial , Vector3 delta , float duration )
{
return observable . Empty < float > () StartWith (() => time . time )
. SelectMany ( start => observable . Select ( _ => time . time - start ))
. TakeWhile ( time => time <= duration )
. Select ( time => EaseInOutExpo ( time , initial , delta , duration ));
}
/// <param name = "dest"> 대상 </ param>
/// <param name = "duration"> 기간 </ param>
public static IObservable < Vector3 > MoveTo < T > ( this IObservable < T > observable ,
gameObject gameObject , Vector3 dest , float duration , EaseKind kind )
{
return observable . Empty < Vector3 > () StartWith (() => gameObject . transform . position )
. SelectMany ( src => observable .. Ease ( src , dest - src , duration , kind ))
. Do ( x => gameObject . transform . position = x );
}
}
실제 사용예는 아래에 나와 있습니다.
void Start ()
{
gameObject.UpdateAsObservable ()
.EaseInOutExpo ( transform.position , new Vector3 ( 5f, 0f, 0f ), 3f )
.Repeat ()
.Subscribe ( pos => transform.position = pos );
}
void Start ()
{
gameObject.UpdateAsObservable ()
.MoveTo ( gameObject, new Vector3 ( 5f, 0f, 0f ), 3f, Tween.EaseKind.EaseInCubic )
.Subscribe (
x => Debug.Log ( "애니메이션 작업 중" ),
() => Debug . Log ( "애니메이션 종료!" ));
}