주요 구현 내용 요약
1. Unity 시작 연출: Cinemachine + Timeline으로 카메라 투어 만들기
2. Unity 저장 시스템 SaveManager 통합 및 초기 데이터 처리
3. Unity에서 Tenkoku - Dynamic Sky를 활용한 게임 내 시간 시스템 구성

1. Unity 시작 연출: Cinemachine + Timeline으로 카메라 투어 만들기
목표
게임이 시작되기 전에 맵 전체를 보여주는 카메라 투어 연출을 만들어보자.
여러 포인트를 전환하면서 부드러운 시네마틱 효과를 주고,
Timeline이 끝난 뒤에는 플레이어 조작이 가능하도록 만들었다.
사용한 구성 요소
| 요소 | 설명 |
| CinemachineVirtualCamera | 가상 카메라. 각각 다른 시점을 제안 |
| Main Camera | 실제 렌더링용 카메라. CinemachineBrain 포함 |
| Timeline | 카메라 전환 연출 제어 |
| PlayableDirector | Timeline 실행 컨트롤 |
| CameraDirector | Timeline 실행용 빈 오브젝트 |
| Player | 플레이어 오브젝트 (초기 비활성화) |
| UI Root | UI 전체 루트 오브젝트 (초기 비활성화) |
구현 흐름
1. Virtual Camera 배치
- GameObject > Cinemachine > Virtual Camera 를 통해 CM_vcam1 ~ CM_vcam3 생성
- 각각의 위치는 미리 만들어 둔 CameraPath의 포인트와 일치시킴
- 각 Virtual Camera의 LookAt은 LookTargets[i]로 설정
2. Timeline 생성
- CameraDirector 오브젝트에 PlayableDirector 추가
- Timeline 생성 후 Cinemachine Track 추가
- Main Camera를 바인딩 후, CM_vcam들을 순서대로 배치 (Cinemachine Shot)
- Shot 간 간격으로 Blend 시간 조절
3. Timeline 끝나면 플레이어 조작 전환
using UnityEngine;
using UnityEngine.Playables;
public class CameraIntroController : MonoBehaviour
{
public PlayableDirector director;
public GameObject player;
public GameObject uiRoot;
void Start()
{
player.SetActive(false);
uiRoot.SetActive(false);
director.stopped += OnFinished;
director.Play();
}
void OnFinished(PlayableDirector d)
{
player.SetActive(true);
uiRoot.SetActive(true);
}
}
CameraDirector에 이 스크립트를 붙이고, 필요한 오브젝트 연결
문제 상황 & 해결
문제 1: Timeline이 끝나도 카메라가 계속 시네마틱 상태 유지
원인: 마지막 Virtual Camera가 계속 활성화되어 있었음
해결: Timeline의 마지막 CM_vcam에 Activation Track을 추가하여 Timeline 종료 시 비활성화 처리
배운 점
- Cinemachine은 실제 카메라가 아니라 시점 제안자라는 개념
- Timeline과 함께 쓰면 연출을 아주 쉽게 구성할 수 있음
- Cinemachine Track과 Activation Track 조합이 매우 강력함
- Timeline이 끝나면 반드시 카메라 제어권을 플레이어에 넘겨야 한다
전체 구조 정리
Main Camera (CinemachineBrain)
CameraDirector (PlayableDirector + CameraIntroController.cs)
CM_vcam1 ~ CM_vcam3 (Virtual Camera)
LookTargets (Look_0 ~ Look_3)
Player (초기 비활성화)
UI Root (초기 비활성화)
2. Unity 저장 시스템 SaveManager 통합 및 초기 데이터 처리
오늘 한 일
- SaveManager를 실제 저장 구조(SaveData)와 일치하도록 수정
- 임시 저장용 스크립트인 SaveTester를 자동 실행이 아닌 수동 호출 방식으로 변경
- TitleUIManager에서 NewGame 버튼을 클릭했을 때만 SaveTester가 실행되도록 연결
- 저장 경로(Application.persistentDataPath) 확인 및 save.json 생성 여부 디버깅
- Unity Hierarchy 상에서 SaveTester 오브젝트는 비활성화 상태로 두고, 필요 시만 실행
주요 배운 점
SaveData 구조와 일치하지 않으면 저장 불가
- JsonUtility.ToJson()은 [System.Serializable] 구조만 저장 가능
- 클래스가 불일치할 경우 저장은 정상적으로 호출되어도 save.json이 생성되지 않음
Start() 자동 실행 대신, public 메서드로 분리하여 명시적 실행
public void CreateInitialSaveData()
{
// SaveData 생성 및 SaveManager 저장 호출
}
TitleUIManager에서 수동으로 SaveTester 호출
saveTesterObject.SetActive(true);
saveTesterObject.GetComponent<SaveTester>().CreateInitialSaveData();
saveTesterObject.SetActive(false);
트러블슈팅
| 문제해결 | 방법 |
| save.json이 안 생김 | 저장 클래스(MyGameData) → 실제 구조(SaveData)로 교체 |
| SaveTester가 실행되자마자 저장됨 | Start() 제거하고, public 메서드로 분리 |
| 탐색기에서 저장 파일이 안 보임 | Application.persistentDataPath를 로그로 출력하고 직접 탐색기 경로 접근 |
참고 구조
SaveData 클래스 요약
public class SaveData {
public int hp, gold, level, exp, stamina;
public List<SaveItem> inventory;
public SaveEquipment equipped;
public List<string> completedQuests;
}
SaveManager 사용 예
SaveManager.Instance.SaveData(mySaveData);
var loaded = SaveManager.Instance.LoadData();
느낀점
Unity 저장 시스템에서 클래스 구조가 일치하지 않으면 저장 자체가 실패한다는 점을 명확히 알게 되었고,
Start()에 의존하지 않고 **게임 내 이벤트(NewGame 클릭)**로 저장 로직을 분리하는 게 더 유연하다는 걸 체감했다.
또한 Application.persistentDataPath를 이용한 경로 확인은 저장 디버깅 시 매우 유용했다.
3. Unity에서 Tenkoku - Dynamic Sky를 활용한 게임 내 시간 시스템 구성
오늘의 목표
- 기존 GameClock 시스템을 유지하면서,
- Tenkoku - Dynamic Sky 에셋을 연동해
- 자연스러운 낮/밤 변화와 하늘 표현을 구현하고자 했다.
진행 내용
1. Tenkoku - Dynamic Sky 설치 및 기본 구성
- Tenkoku DynamicSky 프리팹을 GameScene에 배치 (Position (0,0,0))
- Main Camera에 다음 효과 추가:
- Tenkoku Fog
- Tenkoku Sun Shafts (선택)
- Lighting 설정:
- Skybox Material: 비워두거나 무시
- Sun Source: Tenkoku Sun으로 지정
- Ambient Source: Gradient 또는 Color 사용
2. GameClock 시스템과 Tenkoku 연동
// GameClock → Tenkoku 시간 연동
tenkokuModule.useAutoTime = false;
tenkokuModule.autoTimeSync = false;
tenkokuModule.currentHour = currentHour;
tenkokuModule.currentMinute = currentMinute;
tenkokuModule.currentSecond = calculatedSecond;
tenkokuModule.RecalculateTime();
- GameClock은 매 1초마다 가상 시간 1분이 흐르도록 구성
- OnTimeChanged 이벤트를 다시 추가하여 외부에서 수면 이벤트 등 감지 가능
3. SleepManager 개선
- 새벽 2시가 되면 5초간 수면 처리 후 오전 6시로 시간 이동
- OnTimeChanged 이벤트를 통해 반응하도록 구성
4. Skybox 관련 이해 확장
- CubeMap Skybox는 시간/태양 변화 반영이 불가능
- Tenkoku는 자체적으로 하늘을 렌더링하므로 Skybox 설정을 직접 하지 않음
- 기존 SkyboxManager, DayNightCycle 시스템은 제거해도 무방
배운 점
- Tenkoku는 Skybox가 아닌 자체 Sky Sphere 렌더링을 사용하므로 기존 Skybox 시스템과는 구조적으로 다름
- GameClock을 수동으로 제어하면서도 Tenkoku에 시간 값을 직접 반영하면 매우 유연하게 시간 시스템을 만들 수 있음
- Unity의 Flare Layer는 렌즈 플레어 전용이며, Tenkoku의 Sun Shafts와는 별개
- OnTimeChanged 이벤트는 수면, 랜덤 이벤트 등 다양한 게임 시스템과 느슨하게 연결할 수 있는 확장 포인트가 됨
결과
- 자연스럽게 하늘이 변하며 시간 흐름이 시각적으로 표현됨
- 새벽 2시가 되면 자동으로 캐릭터가 잠에 들고, 아침 6시에 일어남
- 하늘뿐만 아니라 빛, 색상, 분위기까지 함께 바뀌어 몰입도 향상