이번 과제에서는 웨이브 방식의 맵 구성과 시네머신(Cinemachine)을 활용한 역동적인 연출, 그리고 스나이퍼 모드 시스템을 구현하는 것을 목표로 삼았다.
CM_Sniper 카메라를 활용한 줌 모드 적용Shift 단축키를 이용한 모드 전환 기능포탈의 활성화 상태에 따라 메테리얼을 교체하여 시각적 피드백을 제공한다.
using System.Collections;
using UnityEngine;
public class Potal : MonoBehaviour
{
public GameManager _gameManager;
[SerializeField] private Material _onMaterial;
[SerializeField] private Material _offMaterial;
private Renderer _renderer;
private void Start()
{
_renderer = GetComponent<Renderer>();
StartCoroutine(OnOffMaterial());
}
IEnumerator OnOffMaterial()
{
while (true)
{
if (_gameManager.IsAllDead)
{
_renderer.material = _onMaterial;
}
else if (!_gameManager.IsAllDead)
{
_renderer.material = _offMaterial;
}
yield return new WaitForSeconds(1f);
}
}
}
카메라 전환 및 시네머신 연출의 핵심 로직을 담당한다. 몬스터의 상태와 게임 진행 단계에 따라 Priority를 조절한다.
using System.Collections;
using Unity.Cinemachine;
using Unity.Mathematics;
using UnityEngine;
/*
* 플레이어가 적을 죽인다 -> 카메라가 CM_Door로 전환 -> 문이 열린다.
* -> CM_Player로 전환
*/
public class Director : MonoBehaviour
{
private GameObject _player;
[SerializeField] private GameObject _monster;
[SerializeField] private CinemachineCamera _cmPlayer;
[SerializeField] private CinemachineCamera _cmSniper;
private CinemachineBasicMultiChannelPerlin _noise;
public GameManager _gameManager;
private bool _sniperMode = false;
public bool SniperMode { get { return _sniperMode; } }
void Start()
{
_noise = _cmSniper.GetComponent<CinemachineBasicMultiChannelPerlin>();
_player = GameObject.FindWithTag("Player");
StartCoroutine(ChecktoMonsterState());
StartCoroutine(CheckGameStart());
}
IEnumerator ChecktoMonsterState()
{
while (true)
{
if (_monster == null)
{
_monster = GameObject.FindWithTag("Enemy");
if (_monster == null)
{
yield return new WaitForSeconds(0.5f);
continue;
}
}
if (_gameManager.IsStart && _gameManager.MonsterCount <= 0 && _monster != null)
{
int idx = _gameManager.CurrentRoomIndex - 1;
var currentRoom = _gameManager.Rooms[idx];
_cmPlayer.Priority = 0;
currentRoom._cmPotal.Priority = 10;
yield return new WaitForSeconds(4f);
_cmPlayer.Priority = 10;
currentRoom._cmPotal.Priority = 0;
_monster = null;
yield return new WaitUntil(() => _gameManager.MonsterCount > 0);
}
yield return new WaitForSeconds(0.5f);
}
}
IEnumerator CheckGameStart()
{
while (true)
{
if (_gameManager.IsStart && _gameManager.MonsterCount > 0)
{
int idx = _gameManager.CurrentRoomIndex - 1;
var currentRoom = _gameManager.Rooms[idx];
_cmPlayer.Priority = 0;
currentRoom._cmMonster.Priority = 10;
yield return new WaitForSeconds(4f);
_cmPlayer.Priority = 10;
currentRoom._cmMonster.Priority = 0;
yield return new WaitUntil(() => _gameManager.MonsterCount <= 0);
}
yield return new WaitForSeconds(0.5f);
}
}
public IEnumerator SniperNoise()
{
SetNoise(2f, 0.5f);
yield return new WaitForSeconds(0.5f);
SetNoise(0.4f, 0.2f);
yield break;
}
public void CheckSniperMode()
{
if (_sniperMode == false)
{
_cmPlayer.Priority = 0;
_cmSniper.Priority = 9;
_sniperMode = true;
return;
}
if (_sniperMode == true)
{
_cmPlayer.Priority = 9;
_cmSniper.Priority = 0;
_sniperMode = false;
return;
}
}
public void SetNoise(float amplitude, float frequency)
{
_noise.AmplitudeGain = amplitude;
_noise.FrequencyGain = frequency;
}
}
스나이퍼 모드 전환 및 사격 반동 연출을 호출한다.
[SerializeField] private float _sniperInterval = 2.5f;
public Director _director;
void Update()
{
if (_life <= 0) return;
if (_fireCooldown > 0)
{
_fireCooldown -= Time.deltaTime;
}
if (_fireCooldown > 0) return;
if (Input.GetAxis("Fire1") > 0f)
{
GameObject bullet = Instantiate(_prefabBullet, _firePosition.position, _firePosition.rotation);
_fireCooldown = _fireInterval;
if (_director.SniperMode)
{
_director.StartCoroutine(_director.SniperNoise());
_fireCooldown = _sniperInterval;
}
}
if (Input.GetKeyDown(KeyCode.LeftShift))
{
_director.CheckSniperMode();
}
}
RoomData 클래스를 만들어 각 방마다 스폰 위치, 포탈 카메라, 몬스터 카메라 등을 관리하여 스테이지마다 개별적인 연출이 가능하도록 설계했다.Director 클래스에서 코루틴과 while문을 활용해 MonsterCount를 실시간 체크하고, 상황에 맞게 Priority를 조절하여 카메라를 전환했다.Lens 값을 낮춘 전용 카메라를 생성하고, 사격 시 CinemachineBasicMultiChannelPerlin의 수치를 조절하여 화면 흔들림 효과(반동)를 구현했다.null일 때 예외 처리를 추가하고, WaitUntil을 사용하여 조건이 충족될 때까지 대기하도록 로직을 보완했다.MonsterCount 수치가 부정확하여 연출이 비정상적으로 작동하는 버그가 존재한다. 여러 번 재시작하면 해결되곤 하지만, 근본적인 원인을 파악하기 위해 카운트가 증감하는 로직의 시점을 면밀히 분석할 계획이다.