카테고리 없음
오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 52일차(최종프로젝트 3일차 시간을 되돌려 보자!)
코드천자문
2024. 1. 16. 01:27
반응형
개요
우리 프로젝트는 시간을 건드려 퍼즐을 풀어가는 컨셉을 잡고있다.
그래서 물체의 움직임을 반대로 되돌리는 기능이 필요하게 되었고
오늘 그것을 만들었다.
"ReplayRecorder" 오브젝트의 시간을 되돌리는 스크립트
ReplayRecorder
는 객체의 움직임을 기록하고, 이를 역 재생시킨다.
이 스크립트는 Stack<FrameData>
를 사용하여 특정 시점의 프레임 데이터를 저장하며,
이 데이터를 사용해 객체의 움직임을 뒤집어 재생해 마치 시간을 되돌리는 듯 한 효과를 준다.
주요 기능
- 녹화 시작 및 중지: 객체가 움직일 때 자동으로 녹화를 시작하고 멈춘다.
- 역재생: 사용자가 클릭하면 녹화된 움직임을 역순으로 재생
- 녹화 및 재생 중지: 사용자가 다시 클릭하면 재생을 멈추고 기록된 데이터를 지운다.
구현 방법
Rigidbody
와Renderer
컴포넌트를 사용하여 객체의 물리적 상태와 시각적 표현을 조작한다.Coroutine
을 사용하여 프레임 데이터를 주기적으로 기록하고, 이를 재생한다.
주요 코드 설명
StartRecording()
,StopRecording()
: 녹화를 시작하고 중지하는 함수StartReversePlayback()
,StopPlaybackAndClearFrames()
: 역재생을 시작하고 중지하는 함수RecordFrameCoroutine()
,PlaybackCoroutine()
: 프레임을 기록하고 재생하는 코루틴InterpolateFrame()
: 지정된 시간 동안 프레임 데이터를 현재 상태와 목표 상태 사이에서 보간한다.
코드 전문
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ReplayRecorder 클래스 선언
public class ReplayRecorder : MonoBehaviour
{
// 녹화된 프레임 데이터를 저장할 스택 선언
private Stack<FrameData> recordedFrames = new Stack<FrameData>();
// 녹화 및 재생 상태를 나타내는 불린 값
private bool isRecording = false;
private bool isPlayingBack = false;
// 클릭 상태를 나타내는 불린 값
[HideInInspector] public bool isClick;
// 오브젝트의 렌더러와 리지드바디
private Renderer objectRenderer;
private Rigidbody objectRigidbody;
// 재생 코루틴
private Coroutine playbackCoroutine;
// 초기화 함수
void Start()
{
// 컴포넌트 가져오기
objectRenderer = GetComponent<Renderer>();
objectRigidbody = GetComponent<Rigidbody>();
}
// 매 프레임마다 실행되는 함수
void Update()
{
// 리지드바디가 있고, 리지드바디가 움직이고 있다면
if (objectRigidbody && !objectRigidbody.IsSleeping())
{
// 녹화 상태가 아니면서 재생 상태도 아니라면 녹화 시작
if (!isRecording && !isPlayingBack)
{
StartRecording();
}
}
// 그렇지 않고 녹화 상태라면 녹화 중지
else if (isRecording)
{
StopRecording();
}
// 클릭 상태이면서 재생 상태가 아니라면 재생 시작
if (isClick && !isPlayingBack)
{
StartReversePlayback();
}
// 클릭 상태가 아니면서 재생 상태라면 재생 중지 및 프레임 데이터 초기화
if (!isClick && isPlayingBack)
{
StopPlaybackAndClearFrames();
}
}
// 녹화 시작 함수
private void StartRecording()
{
isRecording = true;
StartCoroutine(RecordFrameCoroutine());
}
// 녹화 중지 함수
private void StopRecording()
{
isRecording = false;
StopCoroutine(RecordFrameCoroutine());
}
// 재생 시작 함수
private void StartReversePlayback()
{
objectRigidbody.isKinematic = true;
isPlayingBack = true;
playbackCoroutine = StartCoroutine(PlaybackCoroutine());
}
// 재생 중지 및 프레임 데이터 초기화 함수
private void StopPlaybackAndClearFrames()
{
StopCoroutine(playbackCoroutine);
recordedFrames.Clear();
isPlayingBack = false;
objectRigidbody.isKinematic = false;
}
// 프레임 녹화 코루틴
private IEnumerator RecordFrameCoroutine()
{
while (isRecording)
{
RecordCurrentFrame();
yield return new WaitForSeconds(0.1f);
}
}
// 프레임 재생 코루틴
private IEnumerator PlaybackCoroutine()
{
Queue<FrameData> playbackStack = new Queue<FrameData>(recordedFrames);
while (playbackStack.Count > 0 && isPlayingBack)
{
FrameData frame = playbackStack.Dequeue();
yield return StartCoroutine(InterpolateFrame(frame, 0.1f));
}
isPlayingBack = false;
objectRigidbody.isKinematic = false;
}
// 프레임 보간 코루틴
private IEnumerator InterpolateFrame(FrameData frame, float duration)
{
Vector3 startPosition = transform.position;
Quaternion startRotation = transform.rotation;
Vector3 startScale = transform.localScale;
Color startColor = objectRenderer.material.color;
float elapsedTime = 0;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = elapsedTime / duration;
transform.position = Vector3.Lerp(startPosition, frame.position, t);
transform.rotation = Quaternion.Lerp(startRotation, frame.rotation, t);
transform.localScale = Vector3.Lerp(startScale, frame.scale, t);
objectRenderer.material.color = Color.Lerp(startColor, frame.color, t);
yield return null;
}
}
// 현재 프레임 녹화 함수
private void RecordCurrentFrame()
{
FrameData frame = new FrameData
{
position = transform.position,
rotation = transform.rotation,
scale = transform.localScale,
color = objectRenderer.material.color
};
recordedFrames.Push(frame);
Debug.Log("프레임 녹화: " + recordedFrames.Count);
}
}
// 프레임 데이터를 저장할 구조체
public struct FrameData
{
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
public Color color;
}
학습 포인트
- 스택(Stack) 사용:
Stack<FrameData>
를 사용하여 프레임 데이터를 역순으로 재생합니다. 스택의 LIFO(Last In, First Out) 특성이 이 경우에 적합했다. - 코루틴(Coroutine) 활용: Unity의 코루틴을 사용하여 비동기적으로 프레임을 기록하고 재생합니다. 이는 프레임 처리를 별도의 쓰레드처럼 다루어 주기적 작업을 용이하게 만든다.
- 보간(Interpolation) 기법:
Vector3.Lerp
와Quaternion.Lerp
함수를 사용하여 위치, 회전, 크기, 색상의 부드러운 전환을 구현합니다. 보간 기법에 대한 설명은 스파르타 54일차 TIL인 보간에 대하여 에 담겨져있다.
결론 (영상)
ReplayRecorder
스크립트는 Unity에서 객체의 움직임을 기록하고, 이를 역순으로 재생하는 유용한 도구다.
이 스크립트는 벡터값만 조정하면 3D던 2D이던 어디든 사용 가능하게 만들었다.
어떤 오브젝트던 이 스크립트를 가지고 있으면 작동된다는 점이 큰 매리트다.
반응형