카테고리 없음

오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 52일차(최종프로젝트 3일차 시간을 되돌려 보자!)

코드천자문 2024. 1. 16. 01:27
반응형

개요

우리 프로젝트는 시간을 건드려 퍼즐을 풀어가는 컨셉을 잡고있다. 

그래서 물체의 움직임을 반대로 되돌리는 기능이 필요하게 되었고

오늘 그것을 만들었다.

"ReplayRecorder" 오브젝트의 시간을 되돌리는 스크립트

 

ReplayRecorder는  객체의 움직임을 기록하고, 이를 역 재생시킨다.
이 스크립트는 Stack<FrameData>를 사용하여 특정 시점의 프레임 데이터를 저장하며,
이 데이터를 사용해 객체의 움직임을 뒤집어 재생해 마치 시간을 되돌리는 듯 한 효과를 준다.

주요 기능

  1. 녹화 시작 및 중지: 객체가 움직일 때 자동으로 녹화를 시작하고 멈춘다.
  2. 역재생: 사용자가 클릭하면 녹화된 움직임을 역순으로 재생
  3. 녹화 및 재생 중지: 사용자가 다시 클릭하면 재생을 멈추고 기록된 데이터를 지운다.

구현 방법

  • RigidbodyRenderer 컴포넌트를 사용하여 객체의 물리적 상태와 시각적 표현을 조작한다.
  • 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;
}

학습 포인트

  1. 스택(Stack) 사용: Stack<FrameData>를 사용하여 프레임 데이터를 역순으로 재생합니다. 스택의 LIFO(Last In, First Out) 특성이 이 경우에 적합했다.
  2. 코루틴(Coroutine) 활용: Unity의 코루틴을 사용하여 비동기적으로 프레임을 기록하고 재생합니다. 이는 프레임 처리를 별도의 쓰레드처럼 다루어 주기적 작업을 용이하게 만든다.
  3. 보간(Interpolation) 기법: Vector3.LerpQuaternion.Lerp 함수를 사용하여 위치, 회전, 크기, 색상의 부드러운 전환을 구현합니다. 보간 기법에 대한 설명은 스파르타 54일차  TIL인 보간에 대하여 에 담겨져있다.

결론 (영상)

 

시간을 되돌리는 것처럼 보이는 기능

 

ReplayRecorder 스크립트는 Unity에서 객체의 움직임을 기록하고, 이를 역순으로 재생하는 유용한 도구다.
이 스크립트는 벡터값만 조정하면 3D던 2D이던 어디든 사용 가능하게 만들었다.

어떤 오브젝트던 이 스크립트를 가지고 있으면 작동된다는 점이 큰 매리트다.

반응형