Directional Light 회전에 따른 시간 변화와 그에 따른 하늘 변화

목차

1. 요구 사항

2. 설계 목표

3. 흐름도

4. 구현

        4.1. 시간 진행 ( 태양 회전으로 밤낮 속도 차등 표현 )

       4.2. 밤낮 판정 ( 태양 각도 구간을 기준으로 상태 결정 )

       4.3. 하루 경과 처리 ( 밤 → 낮 전환 시점에서 day 갱신 및 스폰 이벤트 )

       4.4. 안개 설정

       4.5. 스카이박스 전환 ( 셰이더 프로퍼티 Lerp 적용 )

       4.6. 셰이더 프로퍼티 보간

5. 개발 의도

1. 시스템 요구 사항

월드에는 시간 흐름이 존재해야 하며, 플레이어가 체감할 수 있도록 낮과 밤이 자연스럽게 전환되어야 한다.

시간 흐름은 단순히 낮/밤 토글이 아니라, 태양(광원) 방향 변화에 따라 시각 요소가 연속적으로 변하는 구조여야 한다.

낮과 밤 전환에 맞춰 안개 농도(fogDensity)가 점진적으로 변화해야 하고, 스카이박스 역시 시간대별 프리셋 간에 갑작스럽게 바뀌지 않고 부드럽게 보간되어야 한다.

또한 밤이 끝나 낮이 시작되는 시점에는 ‘하루 경과’ 이벤트가 발생해야 하며, 주기적으로 스폰을 시작하는 등 게임플레이 시스템과 연결되는 트리거가 필요하다.

이 모든 과정은 프레임 단위로 환경을 갱신하되, 상태 판정(낮/밤), 일수 갱신, 시각 효과 적용이 서로 충돌하지 않고 안정적으로 동작해야 한다.

2. 설계  목표

- 태양 회전 기반의 시간 진행

- 태양 각도로 낮/밤 상태를 판정

- 밤/낮 전환 시 day(1~7) 사이클 갱신 및 레이드 시작

- 자연스러운 안개 변화 구현

- 시간대별 Skybox Material이 있고, 이는 시간에 따라 부드럽게 전환할 것

3. 흐름도

[Update 루프 시작]

  ↓

태양 회전 (낮/밤에 따라 속도 다름)

  ↓

태양 각도 기반 밤/낮 판정(IsNight)

  ↓

밤→낮 전환 감지(previousIsNight && !isNight)

  ↓

   ├─ day 갱신

   └─ day 가 7->1이면 레이드 스폰

  ↓

FogSetting : 현재 isNight에 따라 fogDensity를 목표값으로 보간

  ↓

SunMat : 시간 구간에 맞는 skybox Material 2개 선택 후 파라미터 Lerp

  ↓

[Update 루프 종료]

이 시스템은 시간 진행(태양 회전)이 원인이 되고, 그 결과로 밤낮 판정이 결정되며, 판정 결과가 다시 Fog·하늘 연출·날짜 이벤트로 확장되는 구조다.

그래서 Update에서 매 프레임 동일한 순서로 평가하면, 전환이 부드럽고 디버깅도 쉬운 일관된 실행 계층이 된다.

4. 구현

4.1. 시간 진행 ( 태양 회전으로 밤낮 속도 차등 표현 )
[SerializeField] private float msTime = 1f;

private bool isNight = false;
private bool previousIsNight = false;

private void Update()
{
    float rotationSpeed = isNight ? 0.2f : 0.1f;
    transform.Rotate(Vector3.right, rotationSpeed * msTime * Time.deltaTime);

    float angleX = transform.eulerAngles.x;
    float angleZ = transform.eulerAngles.z;

    UpdateNightState(angleX);
    UpdateDayCycle();
    UpdateFog();
    UpdateSkybox(angleX, angleZ);

    previousIsNight = isNight;
}

태양 오브젝트의 회전을 이용해 월드의 시간 흐름을 구현하였다.

Update에서는 태양을 일정 속도로 회전시키고, 그 회전 각도를 기준으로 낮/밤 상태 판정과 환경 시스템을 갱신한다.

transform.Rotate는 Unity에서 오브젝트의 회전을 누적시키는 API로, 지정한 축을 기준으로 회전을 계속 증가시키는 방식으로 동작한다.

이 시스템에서는 태양 오브젝트를 X축 기준으로 회전시켜 하루의 흐름을 표현하였다.

태양이 하늘을 가로지르며 이동하는 구조를 그대로 시간 시스템의 기준으로 사용하는 방식이다.

회전 속도에는 Time.deltaTime을 곱해 프레임률과 관계없이 초 단위로 일정한 속도를 유지하도록 구현하였다.

이를 통해 FPS가 달라져도 시간 흐름이 동일하게 유지된다.

또한 msTime 변수는 시간 흐름의 스케일을 조정하기 위한 계수로, 게임 내 하루가 얼마나 빠르게 진행될지를 쉽게 튜닝할 수 있도록 설계하였다.

isNight 상태에 따라 회전 속도를 다르게 설정한 점도 특징이다.

밤에는 낮보다 약 두 배 빠르게 시간이 흐르도록 설정하였다.

이는 게임 플레이 경험을 고려한 설계로, 밤이 길어질수록 시야 제한이 커지고 플레이어의 피로도가 증가할 수 있기 때문이다.

따라서 낮에는 탐험과 활동을 중심으로 플레이하도록 시간을 충분히 제공하고, 밤은 분위기와 긴장감을 유지하되 플레이 템포를 저해하지 않도록 상대적으로 빠르게 진행되도록 조정하였다.

태양이 회전하면 transform.eulerAngles를 통해 현재 회전 각도를 얻을 수 있다.

여기서 angleX는 낮과 밤 상태를 판정하고 환경 연출(Fog, Skybox)을 계산하는 기준으로 사용되며, angleZ는 태양이 하늘의 어느 방향에 위치하는지를 판단하여 시간 구간의 방향성을 결정하는 보조 값으로 사용된다.

이렇게 얻은 각도 값을 기반으로 UpdateNightState, UpdateDayCycle, UpdateFog, UpdateSkybox 함수를 순차적으로 호출하여 월드 환경을 갱신한다.

즉 Update는 태양의 회전을 중심으로 시간 흐름, 낮/밤 판정, 환경 연출, 게임 이벤트를 연결하는 월드 환경 시스템의 메인 루프 역할을 수행한다.

4.2. 밤낮 판정 ( 태양 각도 구간을 기준으로 상태 결정 )
void UpdateNightState(float angleX)
{
    if (angleX >= 340) isNight = false;
    else if (angleX >= 170) isNight = true;
}

낮과 밤의 상태 판정은 태양 오브젝트의 X축 회전 각도를 기준으로 한다.

Unity에서 transform.eulerAngles.x는 오브젝트의 회전값을 0~360 범위의 오일러 각도로 반환하기 때문에, 태양의 회전 각도를 그대로 시간의 기준으로 사용할 수 있다.

이 시스템에서는 태양의 X축 각도를 기준으로 일정 구간을 나누어 낮과 밤 상태를 판정하도록 구현하였다.

태양 각도가 170도 이상이면 밤(isNight = true)으로 판단하고, 340도 이상이 되면 다시 낮(isNight = false) 상태로 전환된다.

이러한 방식은 별도의 시간 변수나 누적 타이머 없이도 태양의 회전 자체가 시간 흐름을 의미하게 되므로, 월드 환경과 시간 시스템을 동일한 기준으로 관리할 수 있다는 장점이 있다.

또한 각도 구간을 이용한 조건 분기는 계산이 단순하고 디버깅이 쉽다는 장점이 있다.

다만 오일러 각도 기반 분기 방식은 특정 경계값 주변에서 상태 전이가 여러 번 반복될 가능성이 있다.

예를 들어 태양 각도가 170도 근처에서 오차 범위 내로 움직일 경우, 낮과 밤 상태가 빠르게 반복 전환될 수 있다.

이를 방지하기 위해 이 시스템에서는 previousIsNight 변수를 함께 사용하여 상태 전환을 감지하도록 설계하였다.

이전 프레임의 상태와 현재 상태를 비교하여 밤에서 낮으로 변경되는 순간만 이벤트를 처리하도록 제어함으로써, 환경 이벤트나 게임 이벤트가 중복 실행되는 문제를 방지하였다.

추가적으로 안정성을 더욱 높이려면 170도와 340도 경계에 히스테리시스 구간을 두는 방법도 사용할 수 있다.

예를 들어 진입 기준과 이탈 기준을 서로 다른 값으로 설정하면 경계값 근처에서 발생할 수 있는 상태 진동을 더욱 줄일 수 있다.

다만 현재 구조에서는 상태 전환 감지 로직과 함께 사용되기 때문에 실제 플레이 환경에서는 충분히 안정적으로 동작하도록 설계하였다.

4.3. 하루 경과 처리 ( 밤 → 낮 전환 시점에서 day 갱신 및 스폰 이벤트 )
void UpdateDayCycle()
{
    if (previousIsNight && !isNight)
    {
        day = (day % 7) + 1; // 1~7일 반복
        if (day == 1) RaidManager.instance.SpawnStart();
    }
}

day는 1~7을 순환하는 주간 카운터다.

(day % 7) + 1 형태는 day가 7일 때 다시 1로 돌아가도록 만드는 전형적인 순환 로직이다.

이 갱신을 밤이었다가 낮이 되는 순간에만 수행하는 이유는, 하루가 경과하는 기준을 일출 시점으로 잡았기 때문이다.

플레이어 관점에서도 화면이 밝아지는 순간이 새로운 날의 시작으로 인식되기 때문에, 환경 변화와 시스템 이벤트의 기준을 자연스럽게 맞출 수 있다.

또한 day 값이 1이 되는 시점에서 RaidManager.instance.SpawnStart()를 호출하여 레이드 이벤트를 시작하도록 구성하였다.

이 구조는 7일을 하나의 사이클로 가지는 콘텐츠 흐름을 만들기 위한 것으로, 일정 주기마다 이벤트가 발생하는 월드 시스템을 구현하기 위한 트리거 역할을 한다.

RaidManager는 레이드 몬스터의 생성과 관리, 타워 디펜스 이벤트 진행을 담당하는 별도의 게임 시스템이다.

Sun 시스템은 이 매니저의 내부 로직을 직접 제어하지 않고, 하루 사이클의 시작 시점에 이벤트 트리거만 전달하는 역할을 수행한다.

즉, Sun 클래스는 월드의 시간 흐름을 관리하고, RaidManager는 그 시간 흐름을 기반으로 레이드 콘텐츠를 실행하는 구조로 역할을 분리하였다.

4.4. 안개 설정
[SerializeField] private float fogDensityCalc = 0.1f;
[SerializeField] private float nightFogDensity = 0.05f;
[SerializeField] private float dayFogDensity = 0f;
[SerializeField] private float currentFogDensity;

private void Start()
{
    currentFogDensity = dayFogDensity;
}

void UpdateFog()
{
    float targetDensity;

    if (angleX > 120 && angleX < 170)
    {
        float t = (angleX - 120) / (170 - 120);
        targetDensity = Mathf.Lerp(0, nightFogDensity, t);
    }
    else if (isNight)
    {
        targetDensity = nightFogDensity;
    }
    else
    {
        targetDensity = 0f;
    }

    currentFogDensity = Mathf.MoveTowards(currentFogDensity, targetDensity, fogDensityCalc * Time.deltaTime);
    RenderSettings.fogDensity = currentFogDensity;
}

안개는 낮과 밤의 상태에 따라 안개 농도를 단순히 전환하는 방식이 아니라, 태양의 회전 각도를 기준으로 안개 농도를 점진적으로 변화시키는 구조로 설계하였다.

단순한 상태 전환 방식은 환경 변화가 갑작스럽게 느껴질 수 있기 때문에, 시간의 흐름에 따라 안개 농도가 자연스럽게 변화하도록 구현하였다.

Start 함수에서는 현재 안개 상태를 관리하기 위한 변수인 currentFogDensity를 낮 기준값(dayFogDensity)으로 초기화한다.

이렇게 별도의 상태 변수를 두어 안개 값을 관리하면 매 프레임 RenderSettings.fogDensity를 직접 계산하는 방식보다 환경 상태의 흐름을 명확하게 유지할 수 있고, 이후 목표 안개 농도로 점진적으로 이동시키는 제어가 가능해진다.

UpdateFog 함수에서는 태양 오브젝트의 X축 회전 각도(angleX)를 기준으로 안개 농도의 목표값(targetDensity)을 계산한다.

단순히 밤이 되었을 때 안개를 즉시 증가시키는 방식은 환경 변화가 급격하게 느껴질 수 있기 때문에, 일몰 구간에서 서서히 안개가 증가하도록 설계하였다.

구체적으로는 태양 각도가 120도에서 170도 사이에 위치하는 구간을 일몰 구간으로 정의하였다.

이 구간에서는 Mathf.Lerp를 사용하여 안개 농도를 0에서 nightFogDensity까지 점진적으로 증가시킨다.

Mathf.Lerp는 두 값 사이를 선형 보간하는 함수로, 여기서는 일몰 구간의 진행률을 계산하여 해가 지는 과정에서 목표 안개 농도가 점진적으로 높아지도록 하는 역할을 한다.

t 값은 (angleX - 120) / (170 - 120) 계산을 통해 일몰 구간 진행률을 0~1 범위로 정규화하여 얻는다.

태양 각도가 170도를 넘어 완전히 밤 상태가 되면 안개 농도는 nightFogDensity 값으로 유지되고, 낮 상태에서는 안개 농도를 0으로 유지한다.

이를 통해 낮에는 시야가 비교적 깨끗한 환경을 유지하고, 해가 지는 시점부터 점진적으로 안개가 증가해 밤 분위기를 자연스럽게 연출할 수 있다.

목표 안개 농도가 계산되면 Mathf.MoveTowards를 이용해 현재 안개 농도(currentFogDensity)를 목표값으로 서서히 이동시킨다.

MoveTowards는 일정 속도로 목표값에 접근하는 함수로, Lerp와 달리 최종적으로 정확한 목표값에 도달한다는 특징이 있다.

이 함수는 이미 계산된 목표 안개 농도에 대해 현재 안개 값이 부드럽게 따라가도록 만드는 역할을 한다.

fogDensityCalc 값은 안개 변화 속도를 조절하는 튜닝 파라미터로, 값이 클수록 안개가 빠르게 변화하고 작을수록 천천히 변화한다.

이를 통해 낮에서 밤으로 넘어가는 환경 연출의 체감 속도를 쉽게 조정할 수 있도록 설계하였다.

4.5. 스카이박스 전환 ( 두 프리셋 후 셰이더 프로퍼티 Lerp 적용 )
public Material skyMat;
public List<Material> skyboxList = new List<Material>();

void UpdateSkybox(float angleX, float angleZ)
{
    Material bef, aft;
    bool isNightSide = angleZ > 90;

    if (isNightSide)
    {
        if (angleX > 270) 
        { 
            bef = GetSky(16, 4); 
            aft = GetSky(11, 5); 
        }
        else 
        { 
            bef = GetSky(11, 5); 
            aft = GetSky(4, 7); 
        }
    }
    else
    {
        if (angleX > 270) 
        { 
            bef = GetSky(16, 4); 
            aft = GetSky(0, 4); 
            if (!dayTrigger) dayTrigger = true; 
        }
        else
        {
            if (dayTrigger) 
            { 
                dayTrigger = false; 
                skyDay++; 
            }

            bef = GetSky(0, 4, 3); 
            aft = GetSky(4, 7);
        }
    }

    ApplyShaderLerp(bef, aft, (angleX % 90) / 90f);
}

여기서 중요한 설계는 RenderSettings.skybox를 시간대마다 교체하는 단순한 방식이 아니라, skyMat이라는 하나의 실제 적용  Material을 기준으로 셰이더 프로퍼티를 보간하여 스카이박스를 변화시키는 방식이라는 점이다.

일반적으로 스카이박스를 교체하는 방식은 시간대가 변경되는 순간 머티리얼이 즉시 바뀌기 때문에 하늘 색이나 광원의 변화가 단계적으로 튀는 느낌이 발생할 수 있다.

반면 하나의 머티리얼을 유지한 채 셰이더 프로퍼티를 보간하면 색상, 태양 디스크, 헤일로, 그라데이션 등의 값이 연속적으로 변화하기 때문에 실제 시간이 흐르는 것처럼 자연스러운 환경 연출이 가능하다.

UpdateSkybox 함수에서는 태양의 회전 각도(angleX, angleZ)를 기반으로 현재 시간 구간에 해당하는 두 개의 스카이 프리셋(bef, aft)을 선택하고, 그 사이 값을 보간하여 적용한다.

여기서 angleZ는 태양이 하늘의 어느 방향에 위치하는지를 판단하기 위한 보조 기준으로 사용된다.

단순히 angleX 값만으로는 태양이 떠오르는 구간인지 지는 구간인지 구분하기 어렵기 때문에, Z축 각도를 함께 사용해 현재 시간 흐름의 방향을 판정한다.

이를 통해 같은 각도 범위라도 일출 방향인지 일몰 방향인지에 따라 서로 다른 하늘 프리셋을 사용할 수 있도록 설계하였다.

또한 skyDay와 dayTrigger 변수는 날짜가 변경되는 순간을 한 번만 감지하기 위한 안전 장치다.

특정 각도 구간을 통과할 때마다 skyDay를 증가시키는 방식으로 구현되어 있으며, dayTrigger를 통해 해당 구간에서 단 한 번만 증가하도록 제어한다.

이렇게 하지 않으면 Update 루프에서 같은 구간이 여러 프레임 동안 반복 평가되면서 skyDay 값이 빠르게 증가하는 문제가 발생할 수 있다.

GetSky 함수는 스카이 프리셋 선택을 위한 인덱스 계산을 캡슐화한 함수다.

Material GetSky(int startIdx, int range, int offset = 0) 
    => skyboxList[startIdx + (skyDay + offset) % range];

startIdx는 특정 시간대 프리셋 블록의 시작 인덱스를 의미하고, range는 해당 블록에서 순환하는 프리셋 개수다. (skyDay + offset) % range 계산을 통해 날짜에 따라 다른 프리셋을 선택하도록 구성하였다.

이 구조를 통해 같은 시간대라도 매일 동일한 하늘을 사용하는 것이 아니라, 날짜에 따라 약간씩 다른 스카이 프리셋이 적용되도록 만들었다.

결과적으로 월드 환경이 반복적으로 보이는 문제를 줄이고, 시간이 흐르는 느낌을 강화하는 효과를 얻을 수 있다.

다만 이 방식은 skyboxList의 인덱스 구조가 코드에서 정의된 범위와 정확히 일치해야 정상적으로 동작한다.

따라서 스카이 프리셋 에셋의 배열 순서를 명확하게 문서화하지 않으면 유지보수 과정에서 인덱스 오류가 발생할 수 있다.

4.6. 셰이더 프로퍼티 보간
private static readonly int SunDiscColor = Shader.PropertyToID("_SunDiscColor");
private static readonly int SunDiscMult  = Shader.PropertyToID("_SunDiscMultiplier");
private static readonly int SunHaloColor = Shader.PropertyToID("_SunHaloColor");
private static readonly int SunHaloExp   = Shader.PropertyToID("_SunHaloExponent");
private static readonly int SunHaloCont  = Shader.PropertyToID("_SunHaloContribution");
private static readonly int HorizonColor = Shader.PropertyToID("_HorizonLineColor");
private static readonly int HorizonExp   = Shader.PropertyToID("_HorizonLineExponent");
private static readonly int HorizonCont  = Shader.PropertyToID("_HorizonLineContribution");
private static readonly int SkyTop       = Shader.PropertyToID("_SkyGradientTop");
private static readonly int SkyBottom    = Shader.PropertyToID("_SkyGradientBottom");
private static readonly int SkyExp       = Shader.PropertyToID("_SkyGradientExponent");

void ApplyShaderLerp(Material b, Material a, float t)
{
    skyMat.SetColor(SunDiscColor, Color.Lerp(b.GetColor(SunDiscColor), a.GetColor(SunDiscColor), t));
    skyMat.SetFloat(SunDiscMult, Mathf.Lerp(b.GetFloat(SunDiscMult), a.GetFloat(SunDiscMult), t));

    skyMat.SetColor(SunHaloColor, Color.Lerp(b.GetColor(SunHaloColor), a.GetColor(SunHaloColor), t));
    skyMat.SetFloat(SunHaloExp, Mathf.Lerp(b.GetFloat(SunHaloExp), a.GetFloat(SunHaloExp), t));
    skyMat.SetFloat(SunHaloCont, Mathf.Lerp(b.GetFloat(SunHaloCont), a.GetFloat(SunHaloCont), t));

    skyMat.SetColor(HorizonColor, Color.Lerp(b.GetColor(HorizonColor), a.GetColor(HorizonColor), t));
    skyMat.SetFloat(HorizonExp, Mathf.Lerp(b.GetFloat(HorizonExp), a.GetFloat(HorizonExp), t));
    skyMat.SetFloat(HorizonCont, Mathf.Lerp(b.GetFloat(HorizonCont), a.GetFloat(HorizonCont), t));

    skyMat.SetColor(SkyTop, Color.Lerp(b.GetColor(SkyTop), a.GetColor(SkyTop), t));
    skyMat.SetColor(SkyBottom, Color.Lerp(b.GetColor(SkyBottom), a.GetColor(SkyBottom), t));
    skyMat.SetFloat(SkyExp, Mathf.Lerp(b.GetFloat(SkyExp), a.GetFloat(SkyExp), t));
}

이 시스템에서는 스카이박스 셰이더의 프로퍼티에 반복적으로 접근하기 때문에, 문자열 대신 Shader.PropertyToID를 사용하여 프로퍼티 ID를 미리 캐싱하였다.

Shader.PropertyToID는 셰이더 프로퍼티 이름을 정수 ID로 변환하는 기능으로, 문자열 기반 접근보다 비용이 적고 반복 호출에 유리하다.

특히 이 시스템은 Update 루프에서 지속적으로 실행되기 때문에, 문자열을 매번 조회하는 방식보다 정수 ID를 사용하는 방식이 성능 측면에서 안정적이다.

또한 프로퍼티 이름을 코드 상수로 관리할 수 있어 오타로 인한 런타임 오류를 줄이고 유지보수 측면에서도 유리하다.

ApplyShaderLerp 함수는 이전 스카이 프리셋(b)과 다음 스카이 프리셋(a) 사이의 값을 보간하여 실제로 적용되는 스카이박스 머티리얼(skyMat)에 반영하는 역할을 한다.

이 함수는 각 프리셋 머티리얼에서 색상과 수치 값을 읽어온 뒤, Color.Lerp와 Mathf.Lerp를 사용하여 두 값 사이를 선형 보간한다.

Color.Lerp는 색상 값의 보간에 사용되고, Mathf.Lerp는 태양 강도, 헤일로 기여도, 그라데이션 지수와 같은 스칼라 파라미터를 보간하는 데 사용된다.

보간 계수 t는 UpdateSkybox 함수에서 (angleX % 90) / 90f 형태로 계산되어 이 함수로 전달된다.

이는 태양의 회전 각도를 기준으로 현재 시간 구간의 진행률을 0에서 1 사이 값으로 정규화한 것이다.

결과적으로 태양이 90도 구간을 통과하는 동안 스카이박스의 색상과 조명 파라미터가 점진적으로 다음 프리셋으로 이동하게 되며, 이를 통해 시간의 흐름에 따라 하늘 표현이 부드럽게 변화하도록 구현하였다.

이 방식의 장점은 스카이박스 머티리얼을 교체하지 않고 하나의 머티리얼을 유지한 채 셰이더 프로퍼티만 변화시키기 때문에 환경 전환이 자연스럽다는 점이다.

머티리얼 자체를 교체하는 방식은 전환 순간에 시각적인 단절이 발생할 수 있지만, 파라미터 보간 방식은 색상과 조명 값이 연속적으로 변화하기 때문에 실제 시간이 흐르는 것처럼 자연스러운 연출이 가능하다.

다만 이 구조에서는 실제로 적용되는 스카이박스 머티리얼이 skyMat 하나로 유지된다는 전제가 중요하다.

만약 다른 시스템에서 RenderSettings.skybox를 직접 교체하거나 다른 머티리얼을 참조하게 되면 보간 로직이 의도대로 동작하지 않을 수 있다.

따라서 스카이박스 연출은 항상 skyMat을 통해 제어된다는 규칙을 유지하는 것이 중요하다.

5. 개발 의도

이 밤낮 시스템은 시간 변수를 증가시키고 일정 시점에 토글하는 방식이 아니라, 태양 회전이라는 물리적(시각적으로 직관적인) 기준을 시간의 근거로 삼아, 결과적으로 이 구조는 시간 진행(태양 회전), 상태 판정(낮/밤), 환경 연출(Fog/Skybox), 게임 이벤트(Day/Spawn)를 하나의 루프 안에서 연결하는 월드 환경 시스템으로 설계되었다.

태양 각도를 기반으로 낮/밤을 판정하고, 밤에서 낮으로 전환되는 순간을 감지해 일수(day)를 갱신함으로써 게임플레이 시스템(스폰 등)과 자연스럽게 연결되도록 했다.