게임 개발

sin·cos로 떠 있는 아이템과 원운동 만들기

진폭·주기·위상을 Unity 코드에 연결합니다. 위치 누적으로 생기는 드리프트와 반복 운동을 감속에 잘못 쓰는 문제를 구분합니다.

CodingJoa 수정 2026.09.22 3분 정도 걸립니다
검은 배경의 좌표평면에 파란 y = sin(x) 곡선과 초록 y = cos(x) 곡선이 −2π부터 2π까지 겹쳐 그려져 있다. 두 곡선 모두 −1과 1 사이에서 물결치며 서로 90도만큼 어긋나 있고, x축에는 −2π, −3π/2, −π, −π/2, 0, π/2, π, 3π/2, 2π 눈금이 표시되어 있다

아이템이 위아래로 떠 있는 연출은 사인 함수 하나로 만들 수 있습니다. 어려운 부분은 함수 자체보다 기준 위치와 시간 단위를 정하는 일입니다. 매 프레임 현재 위치에 사인 값을 더하면 원하는 높이 주변에서 움직이지 않을 수 있습니다.

먼저 숫자의 의미를 정하자

위치를 기준 높이 + 진폭 × 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도 용도에 맞게 쓸 수 있습니다. 먼저 반복할 운동인지, 정해진 시간에 도착할 운동인지부터 구분하는 것이 좋습니다.

참고 API

이 글 공유하기 X Facebook 네이버

이 글은 기술 설명과 참고 자료, 글쓴이의 판단을 담고 있습니다. 예제와 실제 사용 사례의 적용 조건은 본문을 참고해주세요. 도구의 버전·가격·동작은 시간이 지나면 달라질 수 있으니, 따라 하실 때는 공식 문서를 함께 확인해주세요. 자세한 내용은 이용약관에 정리해두었습니다.