아이템이 위아래로 떠 있는 연출은 사인 함수 하나로 만들 수 있습니다. 어려운 부분은 함수 자체보다 기준 위치와 시간 단위를 정하는 일입니다. 매 프레임 현재 위치에 사인 값을 더하면 원하는 높이 주변에서 움직이지 않을 수 있습니다.
먼저 숫자의 의미를 정하자
위치를 기준 높이 + 진폭 × sin(2π × 경과 시간 / 주기)로 정합니다. 진폭 0.25는 기준점에서 위아래로 각각 0.25만큼 움직인다는 뜻입니다. 최저점에서 최고점까지의 거리는 0.5입니다. 주기 2초라면 2초에 한 번 원래 위상으로 돌아옵니다.
위상은 시작 위치를 정합니다. 여러 아이템을 같은 시각에 생성해도 위상을 다르게 주면 모두 동시에 출렁이지 않습니다. 위상 단위도 라디안으로 맞춥니다.
누적하지 않고 기준점에서 계산한다
FloatingItem.cs를 시각적 모델에 붙입니다. 이동하는 캐릭터의 장식이라면 캐릭터 아래 자식 오브젝트에 붙이는 편이 편합니다.
using UnityEngine;
public sealed class FloatingItem : MonoBehaviour
{
[SerializeField] private float amplitude = 0.25f;
[SerializeField, Min(0.01f)] private float period = 2f;
[SerializeField] private float phaseRadians;
private Vector3 origin;
private float elapsed;
private void Start()
{
origin = transform.localPosition;
}
private void Update()
{
elapsed += Time.deltaTime;
float phase = 2f * Mathf.PI * elapsed / Mathf.Max(period, 0.01f) + phaseRadians;
transform.localPosition = origin + Vector3.up * (amplitude * Mathf.Sin(phase));
}
}
이 구현은 Start에서 정한 기준 위치를 계속 사용합니다. 풀링으로 재사용하면서 기준 위치가 달라지는 오브젝트라면, 재배치 시점에 기준 위치와 경과 시간을 다시 설정하는 초기화 메서드가 필요합니다. 다른 스크립트가 같은 localPosition을 쓰면 서로 덮어쓰므로 연출용 자식을 분리하세요.
위상 0, 주기 2초일 때 상대 높이는 시간 0에서 0, 0.5초에서 +진폭, 1초에서 0, 1.5초에서 -진폭입니다. 같은 경과 시간에서 프레임률을 바꿔도 위치가 비슷해야 합니다.
원운동은 두 축에 나누면 된다
반지름이 radius인 XZ 평면 원은 아래처럼 계산합니다. center는 월드 기준 중심, radians는 경과 시간에 각속도를 곱한 값입니다.
Vector3 offset = new Vector3(Mathf.Cos(radians), 0f, Mathf.Sin(radians)) * radius;
transform.position = center + offset;
radians가 0이면 +X에서 출발하고 값이 커지면 +Z 쪽으로 움직입니다. 다른 시작점이 필요하면 위상을 더합니다. 초당 90도 회전시키려면 각속도를 90f * Mathf.Deg2Rad로 변환합니다.
사인 함수를 넣었다고 저절로 멈추지는 않는다
시간을 계속 sin에 넣으면 영원히 반복합니다. 한 번 이동한 뒤 멈추려면 0~1로 제한한 진행률이 필요합니다. 예를 들어 eased = (1 - cos(π × progress)) / 2를 시작점과 끝점 보간에 사용하면 양 끝에서 변화가 느려집니다. progress는 경과 시간/이동 시간이고 1에서 멈춰야 합니다.
삼각함수가 모든 부드러운 움직임의 필수 조건은 아닙니다. 가속도 기반 이동, SmoothDamp, AnimationCurve도 용도에 맞게 쓸 수 있습니다. 먼저 반복할 운동인지, 정해진 시간에 도착할 운동인지부터 구분하는 것이 좋습니다.
