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으로 해주었다.

아직 이 씨네머신 원리에 대해서 정확히 이해한 건 아니지만..
일단 구현은 되었다..!!