2024.07.03 - [Coding/Unity] - [내일배움캠프 55일차 TIL] 2D 하루 낮밤과 다음 날 구현하기
저번에 24시간 내내 시간이 흘러가고, 시간에 맞춰 빛 색이 변화하는 기능을 구현했다. 이번에는 침대에서 잠을 자면 다음 날 오전 6시로 넘어가는 기능을 추가했다.
[ 수면 시스템 구현]
전체 코드
using System.Collections;
using TMPro;
using UnityEngine;
public class Sleep : MonoBehaviour, IInteractable
{
[SerializeField] private Color blackOut = Color.black;
[SerializeField] private AnimationCurve lightCurve; // 서서히 암전
[SerializeField] private TextMeshProUGUI nextDay;
private TimeManager timeManager;
private void Start()
{
timeManager = TimeManager.Instance;
}
public string GetInteractPrompt()
{
string str = "침대";
return str;
}
public void OnInteract()
{
StartCoroutine(SleepMode());
}
private IEnumerator SleepMode()
{
Time.timeScale = 0f; // 시간 멈춤
float duration = 1.5f; // 암전 시간 1.5초
float elapsedTime = 0f; // 경과 시간
Color BGColor = timeManager.GlobalLight.color; // 빛
while (elapsedTime < duration) // 3초 동안
{
elapsedTime += Time.unscaledDeltaTime; // 현실 시간 흐름
float curve = lightCurve.Evaluate(elapsedTime / duration); // 커브 값 계산
Color lightColor = Color.Lerp(BGColor, blackOut, curve); // 점점 까맣게 변함
timeManager.GlobalLight.color = lightColor; // 실제 빛에 적용
yield return null; // 프레임마다 업데이트
}
timeManager.GlobalLight.color = blackOut; // 암전 상태 유지
nextDay.gameObject.SetActive(true); // 다음 날 텍스트 표시
yield return new WaitForSecondsRealtime(duration); // 1.5초 대기
nextDay.gameObject.SetActive(false); // 다음 날 텍스트 미표시
timeManager.Tomorrow(); // 다음 날
Time.timeScale = 1f; // 시간 재개
}
}
현재 진행 중인 프로젝트에서는 IInteractable 인터페이스를 상속 받은 오브젝트만 인식할 수 있도록 구현했기 때문에 다음과 같은 로직으로 되어있다. 핵심 기능은 SleepMode 메서드이다.
// 필요한 부분만 추출
public class TimeManager : Singleton<TimeManager>
{
[SerializeField] private Light2D globalLight; // Light2D 인스펙터 창에서 할당
public Light2D GlobalLight
{
get => globalLight;
private set => globalLight = value;
}
public void Tomorrow()
{
days++; // 다음 날
time = 21600f; // 오전 6시
GameManager.Instance.Player.transform.position = new Vector3(0, 0, 0); // 기본 시작 위치
}
}
코드 설명
[SerializeField] private Color blackOut = Color.black;
[SerializeField] private AnimationCurve lightCurve; // 서서히 암전
[SerializeField] private TextMeshProUGUI nextDay;
private TimeManager timeManager;
private void Start()
{
timeManager = TimeManager.Instance;
}
낮밤 구현하는 것과 비슷하게 어둡게 만들 색을 지정하고 서서히 변하게 하기 위해서 애니메이션 커브를 이용한다. 타임 매니저에서 글로벌 라이트를 사용하고 있기 때문에 이걸 이용하기 위해서 타임 매니저를 가져온다.
// SleepMode() 메서드
Time.timeScale = 0f; // 시간 멈춤
float duration = 1.5f; // 암전 시간 1.5초
float elapsedTime = 0f; // 경과 시간
Color BGColor = timeManager.GlobalLight.color; // 빛
시간이 더이상 흐르지 않고 플레이어가 움직이지 못하도록 게임 시간을 0으로 설정한다. 그리고 얼마 동안 암전이 지속되게 할지 값을 설정하고, 경과 시간을 측정할 변수를 만들어준다. 그리고 타임 매니저에 있는 글로벌 라이트의 색을 미리 캐싱해서 받아온다.
// SleepMode() 메서드
while (elapsedTime < duration) // 1.5초 동안
{
elapsedTime += Time.unscaledDeltaTime; // 현실 시간 흐름
float curve = lightCurve.Evaluate(elapsedTime / duration); // 커브 값 계산
Color lightColor = Color.Lerp(BGColor, blackOut, curve); // 점점 까맣게 변함
timeManager.GlobalLight.color = lightColor; // 실제 빛에 적용
yield return null; // 프레임마다 업데이트
}
자연스러운 연출을 위해 매 프레임마다 빛 색이 바뀌어야 하기 때문에 코루틴을 사용한다. deltaTime은 게임 내 시간인 timeScale에 영향을 받지만, unscaledDeltaTime은 timeScale에 영향을 받지 않기 때문에 현실을 기반으로 하여 시간이 흐르게 한다. 게임 상의 1초와 현실의 1초도 다를뿐더러 현재 timeScale이 0이기 때문에 unscaledDeltaTime을 사용한다. 이를 프레임마다 경과 시간에 더해서 암전 시간을 넘으면 코루틴이 중단되게 한다.
암전 시간에 비해 시간이 얼마나 경과되었는지 계산해서 0~1 사이의 커브 값을 구하고, 이를 애니메이션 커브에 적용시켜서 빛이 해당 그래프에 맞춰 변하게 한다. 두 시간 값 사이의 상대적인 값이기 때문에 그래프의 최대 time 값은 1로 설정해도 무관하다.
실제 빛 색을 적용시켜야하므로 캐싱한 BGColor 변수가 아니라 직접 참조해서 값을 변화시킨다. yield return null로 이 과정을 매 프레임마다 반복하게 한다.
// SleepMode() 메서드
timeManager.GlobalLight.color = blackOut; // 암전 상태 유지
nextDay.gameObject.SetActive(true); // 다음 날 텍스트 표시
yield return new WaitForSecondsRealtime(duration); // 1.5초 대기
nextDay.gameObject.SetActive(false); // 다음 날 텍스트 미표시
timeManager.Tomorrow(); // 다음 날
Time.timeScale = 1f; // 시간 재개
1.5초 동안 서서히 어두워진 후 다음 날 텍스트를 띄우기 위해 잠시 암전 상태를 유지한다. WaitForSecondsRealtime를 사용해서 현실의 1.5초 동안 멈춰서 텍스트를 띄우고, 시간이 지나면 다시 텍스트를 제거한다. 이후 시간을 조절하기 위해서 타임 매니저의 Tomorrow 메서드를 호출하고, 동작이 끝나면 다시 시간을 원래대로 되돌려 놓는다.
// TimeManager.cs
public void Tomorrow()
{
days++; // 다음 날
time = 21600f; // 오전 6시
GameManager.Instance.Player.transform.position = new Vector3(0, 0, 0); // 기본 시작 위치
}
Tomorrow 메서드는 다음 날로 넘겨주고 시간을 오전 6시를 초로 환산한 값으로 설정해서 6시부터 다시 시간이 흘러가도록 한다. 플레이어가 기본적으로 시작하는 위치가 있다면 해당 위치로 설정한다.
문제점
화면이 까맣게 변하는 것을 빛으로 조절을 했기 때문에 UI에는 해당 변경 사항이 적용되지 않는다. 그래서 암전 상태에서 맵 위의 것들은 보이지 않지만, UI는 남아있기 때문에 다른 방법을 모색할 필요가 있다.
참고로 UI 외 빛에 영향을 받지 않는 다른 이미지들은 머터리얼에 Lit 키워드가 없어서 빛 적용이 안 된 것이다. 자세한 내용은 최상단에 첨부한 글에서 확인할 수 있다.
1. 검정색 스프라이트의 알파 값을 조절해서 화면 전체를 가린다.
2. 다른 씬으로 넘어가서 정산 기능 등을 추가한다.
이를 해결하기 위해 두 가지 방법을 생각해 봤다. 24시간을 활동 가능한데 잠을 잘 때만 정산을 하는 건 어색해서, 굳이 씬을 만드는 것보다는 스프라이트를 추가하는 것이 더 간단할 것 같았다.
하지만 UI가 가장 위에 그려지기 때문에 아무리 스프라이트를 위로 올려도 UI 는 사라지지 않았다. 그렇다고 화면만 잠깐 안 보이면 되는데 씬을 바꾸는 것은 너무 비효율적인 것 같았다.
해결 방법
[SerializeField] private GameObject mainUI; // 인스펙터 창에서 할당
private IEnumerator SleepMode()
{
Time.timeScale = 0f;
float duration = 1.5f;
float elapsedTime = 0f;
Color BGColor = timeManager.GlobalLight.color;
mainUI.SetActive(false); // UI 비활성화
while (elapsedTime < duration)
{
elapsedTime += Time.unscaledDeltaTime;
float curve = lightCurve.Evaluate(elapsedTime / duration);
Color lightColor = Color.Lerp(BGColor, blackOut, curve);
timeManager.GlobalLight.color = lightColor;
yield return null;
}
timeManager.GlobalLight.color = blackOut;
nextDay.gameObject.SetActive(true);
yield return new WaitForSecondsRealtime(duration);
nextDay.gameObject.SetActive(false);
timeManager.Tomorrow();
mainUI.SetActive(true); // UI 활성화
Time.timeScale = 1f;
}
그러다 생각한 방법은 UI 자체를 잠시 껐다 키는 방식이었다. 세 줄만 추가하면 돼서 코드를 수정하는 것도 아주 간단했다.
잠을 자면 이전과 같이 시간이 멈추면서 화면이 어두워지고, 인터랙션을 하는 것과 동시에 UI창이 사라지며 온전히 암전을 만들 수 있었다.
'Coding > Unity' 카테고리의 다른 글
[내일배움캠프 55일차 TIL] 2D 하루 낮밤과 다음 날 구현하기 (0) | 2024.07.03 |
---|---|
[내일배움캠프 50일차 TIL] 오브젝트 풀 객체 초기화 (0) | 2024.06.26 |
[내일배움캠프 48일차 TIL] 오브젝트 풀에서 큐의 동작 원리 (0) | 2024.06.24 |
[내일배움캠프 46일차 TIL] JSON 직렬화로 데이터 저장, 불러오기 (0) | 2024.06.20 |
[내일배움캠프 43일차 TIL] Input System C# 제너레이트 활용 (0) | 2024.06.18 |