Unity Cinemachine 연출 #005

주환서·2026년 2월 22일
post-thumbnail

CineMachine

시네머신이란?

  1. 시네머신은 영화 같은 카메라 연출을 가능하게 해주는 도구입니다. 예전에는 카메라를 복잡한 코드로 짜야 했지만, 시네머신은 컴포넌트 설정만으로 가능하게 해줍니다.

시네머신 핵심구조

  • Cinemachine Brain: 실제 메인 카메라에 붙어있는 뇌 역할입니다. 어떤 가상 카메라를 비출지 결정하고, 카메라가 바뀔 때 부드럽게 이어주는 역할을 합니다.
  • Virtual Camera: 실제 카메라는 아니지만, 카메라의 위치와 각도와 설정값을 저장하고 있는 도구들 입니다. 곳곳에 여러대 배치할 수 있습니다.

주요 기능들

  1. Priority(우선순위)
  • 시네머신 브레인은 우선순위가 가장 높은 가상 카메라를 화면에 보여줍니다.
  • Priority 값을 높였다가 줄이는 방식으로 카메라를 제어합니다.
  1. Follow: 카메라가 일정 거리를 유지하며 따라다닐 대상입니다.
  2. Look At: 카메라가 계속 쳐다볼 대상입니다. 대상이 움직이면 카메라도 알아서 회정하고 조준합니다.
  3. Body: 어떻게 따라다닐지 결정합니다. (1인칭, 3인칭 등)
  4. Aim: 화면에 어느 지점에 대상을 둘지 결정합니다.
  5. Impulse: 카메라를 물리적으로 움직입니다.(총을 쏠 때 반동 등)

던전 게임 연출

몬스터 스폰과 던전 클리어 연출


  • 게임 시작 시 몬스터가 스폰한 곳을 비추고 몬스터를 전부 해치웠을 시 문이 열리는 곳을 비추는 연출
// RoomManager
using System.Collections;
using System.Collections.Generic;
using Unity.Cinemachine;
using UnityEngine;

public class RoomManager : MonoBehaviour
{
    [Header("오브젝트 직렬화")]
    [SerializeField] private GameObject monsterPrefab;
    [SerializeField] private Transform[] spawnPoints;
    [SerializeField] private GameObject door;

    [Header("몬스터 스탯 설정")]
    [SerializeField] private int spawnCount = 1;
    [SerializeField] private float moveSpeed = 8f;
    [SerializeField] private float damage = 5f;
    [SerializeField] private float hp = 20f;
    [SerializeField] private Vector3 monsterScale = Vector3.one;

    [Header("시네머신 카메라 연결")]
    [SerializeField] private CinemachineCamera spawnCam; // 몬스터 쪽 카메라
    [SerializeField] private CinemachineCamera doorCam;  // 문 열리는거 찍는 카메라

    private List<GameObject> _activeMonsters = new List<GameObject>();
    private bool _isCleared = false;
    private bool _isSpawned = false;
    
    public void StartRoomEvent()
    {
        StartCoroutine(Co_RoomSequence());
    }

    // 연출
    private IEnumerator Co_RoomSequence()
    {
        // 몬스터 스폰 위치 카메라
        if (spawnCam != null) spawnCam.Priority = 20; // CM_Player보다 높게 설정
        yield return new WaitForSeconds(1.5f); // 카메라가 날아가는 시간 대기

        // 몬스터 스폰
        SpawnMonsters();
        yield return new WaitForSeconds(1.0f); // 스폰된 모습 1초간 보기

        // 카메라 다시 플레이어 쪽
        if (spawnCam != null) spawnCam.Priority = 0;
        yield return new WaitForSeconds(1.5f); // 돌아오는 시간 대기

        // 몬스터 다 죽일 때 까지 게임 플레이
        while (true)
        {
            if (CheckAllDead()) break;
            yield return new WaitForSeconds(0.5f);
        }

        // 문 열리는거 찍음
        if (doorCam != null) doorCam.Priority = 20;
        yield return new WaitForSeconds(1.5f); // 날아가는 시간 대기

        // 문 열림
        OpenDoor();
        yield return new WaitForSeconds(1.0f); // 열리는 시간 대기

        // 다시 플레이어 카메라
        if (doorCam != null) doorCam.Priority = 0;
    }

    private void SpawnMonsters()
    {
        for (int i = 0; i < spawnCount; i++)
        {
            Transform spawnPos = spawnPoints[i % spawnPoints.Length];
            GameObject newMonster = Instantiate(monsterPrefab, spawnPos.position, spawnPos.rotation);

            MonsterAI ai = newMonster.GetComponent<MonsterAI>();
            if (ai != null)
            {
                ai.Initialize(moveSpeed, damage, hp, monsterScale);
            }

            _activeMonsters.Add(newMonster);
        }
        _isSpawned = true;
    }

    private bool CheckAllDead()
    {
        if (!_isSpawned) return false;

        foreach (GameObject monster in _activeMonsters)
        {
            if (monster != null) return false; // 살아있는 놈 있다면 다시
        }
        return true; // 다 죽었음
    }

    private void OpenDoor()
    {
        if (door != null) door.SetActive(false);
        _isCleared = true;
    }
}
// 콜리더를 이용해서 RoomEvent 작동 로직
public class CloseDoor : MonoBehaviour
{
    [SerializeField] private GameObject doorToClose; 
    [SerializeField] private RoomManager roomManager;

    private void OnTriggerEnter(Collider other)
    {
        // 플레이어랑 닿으면
        if (other.CompareTag("Player"))
        {
            // 문 닫음
            if (doorToClose != null)
            {
                doorToClose.SetActive(true);
            }

            // 연출 시작
            if (roomManager != null)
            {
                roomManager.StartRoomEvent();
            }

            // 투명 벽 끔
            gameObject.SetActive(false);
        }
    }
}

스나이퍼 모드

업로드중..

// AimController.cs
using Unity.Cinemachine;
using UnityEngine;

public class AimController : MonoBehaviour
{
    [SerializeField] private CinemachineCamera cmAim;

    void Update()
    {
        // 쉬프트를 누르면 줌인
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            cmAim.Priority = 20;
        }
        // 쉬프트를 떼면 다시 원래 카메라
        else if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            cmAim.Priority = 0;
        }
    }
}
// combat.cs 플레이어의 공격 로직
using Unity.Cinemachine;
using UnityEngine;

public class Combat : MonoBehaviour
{
    [SerializeField] private Transform _firePosition;   // 생성 위치, 회전
    [SerializeField] private float _fireInterval = 0.2f;
    [SerializeField] private float _hp = 50f;

    private bool _isAlive = true;
    private float _fireCooldown;

    private CinemachineImpulseSource _impulseSource;
    public bool IsAlive { get => _isAlive; }

    void Start()
    {
        _fireCooldown = _fireInterval;

        _impulseSource = GetComponent<CinemachineImpulseSource>();
    }

    void Update()
    {
        if (_fireCooldown > 0)
        {
            _fireCooldown -= Time.deltaTime;
        }

        if (_fireCooldown > 0) return;

        if (Input.GetAxis("Fire1") > 0f)
        {
            // 총알 생성
            GameObject bullet = PoolManager.Instance.Get();
            bullet.transform.position = _firePosition.position;
            bullet.transform.rotation = _firePosition.rotation;
            bullet.GetComponent<BulletController>().Fire();

            // 플레이어 공격력으로 보정
            //bullet.GetComponent<BulletController>().Init(30f);
            _fireCooldown = _fireInterval;

            if (_impulseSource != null)
            {
                Debug.Log("반동");
                _impulseSource.GenerateImpulse();
            }
        }
    }

0개의 댓글