유니티에서 연출 작업을 위해서 선형적이지 않은 움직임이 필요할 때가 있다. 이럴 때에 나는 AnimationCurve를 자주 사용하는 편이다.
선형적과 비선형적인 움직임의 차이라면 흔히 들어봤을 법한 Easing이 있다. 관련된 내용은 아래의 GIF를 같이 보자.
Easing은 게임개발 이외에 다양한 분야에서 사용된다. 우리가 핸드폰을 켰을 때, 바로 나타나는 것이 아니라 서서히 빠르게 밝아진다거나, 웹사이트에서 버튼을 눌렀을 때, 따닥! 하고 움직이는 애니메이션들이 Easing을 활용한 연출이다.
여러 공식들이 존재하지만, 유니티에서 간단하게 Easing을 구현할 수 있는 방법은 AnimationCurve를 사용하는 것이다.
(Easing 공식 관련 사이트 : https://easings.net/)
유니티 인스펙터를 통해서 간단하게 곡선의 확인이 가능하고 이후 기획자나 다른 협업자가 간단하게 곡선을 변경할 수 있다는 점에서 AnimationCurve의 사용은 좋은 것 같다.
제대로 알아보기 전에 AnimationCurve를 적용하지 않은 움직임을 살펴보자.
Translate를 사용한 움직임은 단순하게 속도가 더해지면서 목적지까지 이동한다.
AnimationCurve를 사용한 움직임은 시간이 지날수록 속력이 빨라지는 것처럼 보인다.
하지만 실제로 측정을 했을 때, 서로 이동하는 시간은 동일함을 확인할 수 있다.
구현
전체 코드는 하단에 같이 올려두겠다.
테스트를 하기 위해서 필요한 변수들을 빼놨다. 여기에서 선언된 변수들은 인스펙터에서 우측에 있는 것 처럼 보이게 된다.
Set Target Pos는 Odin Inspector에서 제공하는 기능으로 아래에 있는 전체 코드에서는 해당 코드를 제외했다.
AnimationCurve는 Coroutine과 같이 사용되는 것이 일반적인데, Update나 프레임이 반복되는 공간에서 사용해 주면 된다.
animCurve.Evaluate(float time)에 대해서 알아보자. animCurve는 AnimationCurve로 선언해 준 변수 이름이고, Evaluate는 time을 매개로 받아서 해당 time에 해당하는 값을 반환한다.
매개변수 time에 들어가는 값은 0 ~ 1로 정규화된 값이 아니라, 해당 AnimationCurve의 최대 시간까지 받을 수 있다.
그림판으로 작성한 거라 알아보기가 힘들다면 유감이지만.. 위를 향하는 화살표는 Value를 뜻하고, 우측을 향하는 화살표는 Time을 뜻한다. 나는 간단하게 보여주기 위해서 사용한 것이기에 해당 커브의 종료 시간은 1이다.
이후, 종료 시간을 3으로 설정한다면 Evaluate에 3을 넣어줘야지만, 마지막까지의 Value를 가져올 수 있으니 확인 바란다.
Evaluate의 값을 0과 1로 사용하고 싶다면, 이후 작성할 Vector3.Lerp를 통해서 사용할 수 있다. (링크)
전체 코드 - Unity 2021. 3. 29f
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimationCurveBehaviour : MonoBehaviour
{
public AnimationCurve animCurve;
public GameObject animObject;
public Vector3 animTargetPos;
[Space(10)]
public GameObject normalObject;
public Vector3 normalTargetPos;
[Space(10)]
public float targetOffset = .0f;
public bool isStarted = false;
private void SetTargetPos()
{
animTargetPos = animObject.transform.position;
animTargetPos.x += targetOffset;
normalTargetPos = normalObject.transform.position;
normalTargetPos.x += targetOffset;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.A) || isStarted)
{
isStarted = true;
MoveNormal();
}
if (Input.GetKeyDown(KeyCode.B))
{
StartCoroutine(MoveAnimCurve());
}
}
private void MoveNormal()
{
if (Vector3.Distance(normalObject.transform.position, normalTargetPos) > 0.1f)
{
normalObject.transform.Translate(Vector3.right * 15f * Time.deltaTime);
}
}
private IEnumerator MoveAnimCurve()
{
float timer = .0f;
while (timer < 1f)
{
animObject.transform.position = new Vector3(animCurve.Evaluate(timer) * animTargetPos.x,
animObject.transform.position.y,
animObject.transform.position.z);
yield return null;
timer += Time.deltaTime;
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(animTargetPos, 0.1f);
Gizmos.DrawLine(animObject.transform.position, animTargetPos);
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(normalTargetPos, 0.1f);
Gizmos.DrawLine(normalObject.transform.position, normalTargetPos);
}
}
해당 코드로 Curve를 확인하고자 한다면, SetTargetPos 메서드를 Awake에서 선언해 주기를 바란다.
AnimationCurve 실행 시간 추가하기
위에서 설명한 내용에서는 실행 시간이 추가되어 있지 않다. 단순하게 Timer의 값이 1 이상 받게 된다면 종료하게 되어 있기 때문에 N초 동안 실행해야 할 경우, 멈춰버릴 수 있다.
방법은 위 코드에서 MoveAnimCurve() 코루틴만 수정하면 된다.
private IEnumerator MoveAnimCurve()
{
float timer = .0f;
float activeTime = 3f;
while (timer < activeTime)
{
animObject.transform.position = new Vector3(animCurve.Evaluate(timer / activeTime) * animTargetPos.x,
animObject.transform.position.y,
animObject.transform.position.z);
yield return null;
timer += Time.deltaTime / activeTime;
}
}
AnimationCurve Key 등록 및 제거
AnimationCurve curve;
curve.AddKey(Time, Value);
curve.RemoveKey(index);
AddKey(Keyframe) / AddKey(float Time, float Value)
: 해당 메서드를 통해서 키를 추가할 수 있다. 주의할 점은 생성한 AnimationCurve에 Key가 아무것도 존재하지 않아도 1번째 키 값은 Time : 0, Value : 0으로 설정되므로 확인이 필요하다.
RemoveKey(int Index)
: 해당 메서드는 index에 맞는 Key를 삭제한다.
'엔진, 프레임워크 > Unity' 카테고리의 다른 글
UI에서 Particle System 추가하는 방법 - Raw Image & Render Texture (1) | 2023.11.12 |
---|---|
Unity - Bezier Curve에 대해서 학습해보자. (0) | 2023.08.31 |
Unity Lifecycle (0) | 2023.06.24 |
[UI Toolkit] 2. UI Builder가 아닌 UXML로 만들기 (0) | 2023.05.18 |
[UI Toolkit] 1. UI Builder를 사용한 UI 배치 (0) | 2023.05.17 |