Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(83일차) - [팀플] Scene BootStrap 구현 및 타임라인 설정

by 독기품은토끼 2025. 9. 17.

1. 씬 로드 초기화 작업

맨 처음 씬이 로드되었을 때

캐릭터 오브젝트 자식으로 main camera가 이동되는 것과

캐릭터의 포지션 값을 지정해주는 작업이 필요했다.

 

1. 캐릭터 갖고오기

using UnityEngine;

public class PlayerRoot : MonoBehaviour
{
    public static PlayerRoot Local; // 싱글플레이 임시
    public Transform Head;

    void Awake()
    {
        Local = this; // 내 플레이어 인스턴스 기록
        DontDestroyOnLoad(gameObject);
    }
}

public static class PlayerLocator
{
    public static PlayerRoot GetLocalPlayer() => PlayerRoot.Local;
}

 

지금은 버튼을 눌렀을 때 그 버튼에서 캐릭터 오브젝트가 DontDestoryOnLoad를 호출하고 있는 것 부터가 좀 문제가 있었다.

그래서 플레이어 자체에 캐릭터가 삭제되지 않도록 구현해주었고

멀티플레이 환경을 고려해서 씬이 전환되어도 캐릭터 오브젝트는 캐릭터 자기 자신만 바라보도록 해주었다.

 

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class CharacterTransformManager : MonoBehaviour
{
    void OnEnable() => SceneManager.sceneLoaded += OnSceneLoaded;
    void OnDisable() => SceneManager.sceneLoaded -= OnSceneLoaded;

    void OnSceneLoaded(Scene s, LoadSceneMode m)
    {
        StartCoroutine(Setup());
    }

    IEnumerator Setup()
    {
        yield return null;  // 씬 객체 초기화/찾기 안정화

        var cam = Camera.main;
        if (!cam) yield break;

        var localPlayer = PlayerLocator.GetLocalPlayer();
        if (!localPlayer) yield break;

        // 플레이어 위치 초기화
        localPlayer.transform.SetPositionAndRotation(transform.position, transform.rotation);

        // 메인카메라를 로컬플레이어의 헤드에 붙이기
        Transform mount = localPlayer.Head != null ? localPlayer.Head : localPlayer.transform;

        cam.transform.SetParent(mount, worldPositionStays: false);
        cam.transform.localPosition = Vector3.zero;
        cam.transform.localRotation = Quaternion.identity;
    }
}

 

이 값을 활용해서 root로 캐릭터 오브젝트를 불러들인 다음에

카메라 트랜스폼 위치와 캐릭터의 포지션 위치를 설정해주었다.

 

캐릭터 포지션은 씬마다 약간의 룸 내부 위치 변화가 있어보여서 빈 오브젝트 만들어서 할당하는 방법으로 택했다.

 

 

2. 씨네머신과의 충돌

그런데 지금 main 카메라는 씨네머신 브레인으로 사용되고 있기 때문에

냅다 캐릭터 오브젝트 밑으로 넣어버리면 애써 만든 씨네머신 연출이 좀 이상해지는 현상이 있었다.

그래서 씨네머신 카메라는 씨네머신 타임라인이 작동될 때에만 On되고 그 외에는 Off되도록 하는 스크립트를 만들어주었다.

using UnityEngine;
using UnityEngine.Playables;
using Unity.Cinemachine;

public class CinemachineOnOff : MonoBehaviour
{
    [SerializeField] private CinemachineBrain brain; // Main Camera의 Brain
    [SerializeField] private PlayableDirector director; // 컷씬 타임라인

    private bool brainOffOnAwake = true; // 평소엔 수동(1인칭) 유지
    private bool reattachCameraToHeadOnEnd = true; // 컷씬 끝나면 카메라 재장착

    void Awake()
    {
        if (!brain) brain = GetComponent<CinemachineBrain>();
        if (brain && brainOffOnAwake) brain.enabled = false;
    }

    void OnEnable()
    {
        if (director) director.stopped += OnCutsceneEnd;
    }

    void OnDisable()
    {
        if (director) director.stopped -= OnCutsceneEnd;
    }

    public void PlayCutscene()
    {
        if (brain) brain.enabled = true;

        if (director)
        {
            director.time = 0;
            director.Play();
        }
    }

    private void OnCutsceneEnd(PlayableDirector _)
    {
        if (brain) brain.enabled = false;

        if (!reattachCameraToHeadOnEnd) return;

        var local = PlayerLocator.GetLocalPlayer();
        var cam = Camera.main;
        if (local && cam)
        {
            var mount = local.Head ? local.Head : local.transform;
            cam.transform.SetParent(mount, false);
            cam.transform.localPosition = Vector3.zero;
            cam.transform.localRotation = Quaternion.identity;
        }
    }

    public void ReturnToMainNow()
    {
        OnCutsceneEnd(director); // 기존 종료 로직 재사용
    }
}

 

 

2. 씨네머신 연출 추가 (HP바 활성화 및 카운트다운)

결투 신청 버튼을 눌렀을 때 추가적으로 나와야하는 오브젝트들이 있다.

바로 HP Bar와 격투 시작임을 알리는 카운트다운을 표시하는 것이다.

using System.Collections;
using UnityEngine;
using TMPro;

public class CountdownOnEnable : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI countdownText;
    private int start = 3; // 3부터 시작
    private float step = 0.8f; // 각 숫자 노출 시간
    private bool showFight = true; // "FIGHT!" 표시할지
    private bool autoDeactivate = false; // 끝나면 자동으로 비활성화

    void OnEnable()
    {
        StartCoroutine(Play());
    }

    IEnumerator Play()
    {
        if (!countdownText) yield break;

        for (int n = start; n >= 1; n--)
        {
            countdownText.text = n.ToString();
            yield return new WaitForSecondsRealtime(step);
        }

        if (showFight)
        {
            countdownText.text = "FIGHT!";
            yield return new WaitForSecondsRealtime(step);
        }

        if (autoDeactivate) gameObject.SetActive(false); // 타임라인에도 적용해놨는데 혹시 몰라서 같이 적용
    }
}

 

카운트다운은 3 -> 2 -> 1 -> Fight! 순으로 이동되게 구현해주었고

 

타임라인은 Active Track으로 Hp바나 카운트다운 text를 활성화 시켜주었다.

 

 

타임라인 설정

  • Playable Director > Wrap Mode
    • Hold: 타임라인이 끝난 뒤 마지막 재생 상태를 유지. (추천)
    • None: 끝나자마자 타임라인이 초기 상태로 되돌아감.
    • Loop: 타임라인을 반복 재생.
  • Activation Track > Post-Playback State (트랙별 옵션)
    • Active: 재생이 끝나면 그 게임오브젝트를 켜둠.
    • Inactive: 재생이 끝나면 꺼둠.
    • Revert: 재생이 끝나면 재생 전 상태로 복귀.

근데 On off 스크립트로 인하여서 위 작업을 해버리면 오히려 더 꼬여버려서 Wrap Mode를 그냥 None으로 해주었다.

 

 

아직 이 씨네머신 원리에 대해서 정확히 이해한 건 아니지만..

일단 구현은 되었다..!!