유니티 RPG - 42. 타임라인을 활용한 컷씬 및 던전관리
Intro
이번 포스팅에서는 던전의 진행도를 관리하는 던전매니저와
보스몬스터가 등장할 때의 컷신을 구현했습니다.
타임라인(Timeline)
유니티에서 제공하는 타임라인은 연출과 이벤트 제어를 시각적으로 관리할 수 있는 시퀀싱 툴입니다.
주로 게임내 컷씬 연출과 이벤트 트리거를 만들 때 사용되는 타임라인에 대해 정리해보았습니다.
- Timeline Asset
- 타임라인의 데이터 파일로, 타임라인으로 만든 연출정보가 이 에셋에 저장됩니다.
- Playable Director
- 타임라인을 재생하는 컴포넌트로, 타임라인을 실행하거나 중지할 수 있는 컴포넌트입니다.
- Track
- 타임라인 안에서 특정 오브젝트의 행동을 정의합니다.
- 특정 오브젝트의 애니메이션, 오디오 등의 행동을 설정할 수 있습니다.
- Clip
- 트랙 안에 들어가는 개별적인 동작입니다.
보스 등장 컷씬
타임라인을 이용하여 보스몬스터가 등장할 때 컷씬을 만들어보았습니다.
먼저 타임라인탭을 열고 던전매니저 오브젝트를 선택한다음, Create 버튼을 눌러 타임라인 에셋을 만들어줍니다.
왼쪽 상단의 + 버튼을 눌러 컷씬에 필요한 트랙을 추가해주었습니다.
- Cinemachine Track - 카메라 연출
- Activation Track - Fade 효과를 위한 Image의 활성 및 비활성화
- Animation Track - Fade 효과를 위한 Image의 Alpha값 조절
Cinemachine Track 를 사용하기 위해서 Cinemachine Brain 컴포넌트가 있는 카메라가 필요합니다.
하지만 현재 메인씬의 카메라를 씬 이동간에 DontDestroyOnLoad 를 통해 가져오는 형태입니다.
따라서 던전씬으로 변경되었을 때 코드로 카메라를 할당해주어야합니다.
던전매니저 오브젝트에 TimeLineCameraBinder 클래스를 추가하여 해당 작업을 수행합니다.
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using Cinemachine;
// 타임라인에 카메라 바인딩
public class TimeLineCameraBinder : MonoBehaviour
{
[SerializeField] PlayableDirector timelineDirector;
private GameObject mainCamera;
private void Start()
{
// 카메라 가져오기
mainCamera = Camera.main.gameObject;
if(timelineDirector == null || mainCamera == null)
{
Debug.Log("PlayableDirector 또는 Main Camera 없음");
return;
}
TimelineAsset timelineAsset = timelineDirector.playableAsset as TimelineAsset;
// 타임라인 내 트랙 중 CinemachineTrack 을 찾아 카메라 바인딩
foreach(var track in timelineAsset.GetOutputTracks())
{
if(track is CinemachineTrack)
{
timelineDirector.SetGenericBinding(track, mainCamera.GetComponent<CinemachineBrain>());
Debug.Log("Cinemachine Brain 바인딩 완료");
}
}
}
}
- 던전씬에 입장할 때 넘어온 메인카메라를 TimelineAsset에 존재하는 여러가지 트랙 중 Cinemachine Track을 찾아
다음으로 카메라 연출을 위해 가상카메라 3개를 추가하여 배치했습니다.
카메라 연출이 조금 더 자연스러워 보이기위한 Fade 효과를 위해 Image 를 만들어 화면전체를 까맣게 합니다.
카메라 연출 및 Fade 효과를 적절히 조정합니다.
이때 Fade 효과는 컷씬이 끝나면 사라져야하므로 Activateion Clip는 반드시 Cinemachine Clip 의 끝나는 지점보다는 일찍 끝나야합니다.
던전 진행도 관리
던전매니저는 플레이어가 모든몬스터를 처치했을 때 보스가 등장하도록하고, 등장함과 동시에 컷씬을 연출합니다.
또한 컷씬이 시작되면 플레이어를 특정위치로 위치하게하고,
컷씬이 플레이되는 동안에는 플레이어의 움직임을 막아야하며,
컷씬이 끝나면 다시 움직일 수 있도록 해야합니다.
그러기 위해 플레이어의 움직임을 담당하는 클래스인 PlayerController 에 컷씬중인지 판별할 플래그를 만들었습니다.
public class PlayerController : MonoBehaviour
{
public bool isCutscenePlaying = false;
private void Update()
{
//...
// 컷씬동안에는 Idle 애니메이션
if (isCutscenePlaying)
anim.SetFloat(hashSpeed, 0);
}
private void GetInput()
{
if (isDead || isCutscenePlaying)
return;
// ...
}
// 플레이어 이동로직
private void Move()
{
if (isAttacking || isDead || isCutscenePlaying)
return;
// ...
}
// 플레이어 회전로직
private void Turn()
{
if (isAttacking || moveVec == Vector3.zero || isDead || isCutscenePlaying)
return;
// ...
}
// 플레이어 공격
private void Attack()
{
if (isAttackKeyDown && !isAttacking && !isDead && !isCutscenePlaying)
{
anim.SetTrigger(hashAttackTrigger);
}
}
}
- 컷씬중에는 플레이어의 동작을 멈추도록 하였습니다.
- 또한 플레이어의 Speed 를 0으로 하여 Idle 애니메이션이 출력되도록 하였습니다.
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
/*
DungeonManager - 던전 관리
* 던전 진행도 관리
- 모든 일반몬스터 처치시 컷씬 후 보스몬스터 등장
- 보스몬스터 처치시 보상
- 플레이어 죽을시 귀환
*/
public class DungeonManager : Singleton<DungeonManager>
{
[SerializeField] private Transform startingPoint; // 플레이어 시작 위치
[SerializeField] private Transform bossSpawnPoint; // 보스 등장 위치
[SerializeField] private Transform monsters; // 일반 몬스터 그룹
[SerializeField] private Transform cutScenePlayerPos; // 컷씬 출력시 플레이어 위치
[SerializeField] private TimelineAsset[] timelineAsset; // 타임라인 에셋
[SerializeField] private GameObject cutSceneObj; // 컷씬 카메라 오브젝트
private bool bossSpawned = false; // 보스 등장여부
private PlayableDirector pd;
private void Start()
{
pd = GetComponent<PlayableDirector>();
// 이벤트 연결
pd.played += OnCutsceneStarted;
pd.stopped += OnCutsceneEnded;
// 플레이어 위치 설정
GameManager.Instance.player.transform.position = startingPoint.position;
GameManager.Instance.player.transform.rotation = Quaternion.identity;
Spawn();
}
private void Update()
{
if (bossSpawned)
return;
if(monsters.childCount == 0)
{
SpawnBoss();
}
}
// 몬스터 스폰
private void Spawn()
{
for(int i = 0;i<monsters.childCount;i++)
{
monsters.GetChild(i).gameObject.SetActive(true);
}
Debug.Log("몬스터 소환 완료");
}
// 보스몬스터 스폰
private void SpawnBoss()
{
bossSpawned = true;
// 컷씬 출력
cutSceneObj.gameObject.SetActive(false);
pd.Play(timelineAsset[0]);
Debug.Log("모든 몬스터 처리 완료");
}
// 컷씬 시작(플레이어 움직임 제어)
private void OnCutsceneStarted(PlayableDirector director)
{
GameManager.Instance.player.isCutscenePlaying = true;
// 플레이어 위치 설정
GameManager.Instance.player.transform.position = cutScenePlayerPos.position;
GameManager.Instance.player.transform.rotation = Quaternion.identity;
Debug.Log("컷씬 시작");
}
// 컷씬 끝(플레이어 움직임 제어해제)
private void OnCutsceneEnded(PlayableDirector director)
{
GameManager.Instance.player.isCutscenePlaying = false;
Debug.Log("컷씬 끝");
}
}
-
OnCutsceneStarted() : 컷씬이 시작되면 플레이어의 동작을 막고 특정위치로 이동시킵니다.
-
OnCutsceneEnded() : 컷씬이 끝나면 플레이어가 동작할 수 있도록 합니다.
-
Start() : 컷씬이 시작될때와 끝날때의 이벤트를 등록합니다.
-
SpawnBoss() : 컷씬을 출력합니다.
댓글남기기