Version: 2020.3
언어: 한국어
ScriptableObject
중요 클래스 - Mathf

중요 클래스 - 시간 및 프레임 속도 관리

시간 클래스

Unity의 시간 클래스는 프로젝트에서 시간 관련 값으로 작업할 수 있는 여러 가지 중요한 기본 프로퍼티를 제공합니다.

Time 스크립트 레퍼런스 페이지의 각 멤버에 대한 설명은 쉽게 이해할 수 있으며, 그에 관해 배우기 가장 적합한 장소입니다.

다음은 일반적인 용도에 대한 아이디어를 제공하는 몇 가지 예입니다.

Time.time 프로젝트 재생 시작 후 경과한 시간을 반환합니다.

Time.deltaTime 마지막 프레임이 완료된 후 경과한 시간을 반환합니다.

Time.timeScale 시간이 경과하는 속도를 나타냅니다. 이 값을 표시하거나, 설정하여 시간이 경과하는 속도를 제어하고 이를 통해 슬로우모션 효과를 생성할 수 있습니다.

시간 관련 프로퍼티에 대한 전체 리스트는 Time 스크립트 레퍼런스 페이지를 참조하십시오.

Update 함수를 사용하면 스크립트에서 규칙적으로 입력 및 기타 이벤트를 모니터링하고 적절한 조치를 취할 수 있습니다. 예를 들어, “전진” 키가 눌렸을 때 캐릭터를 움직일 수도 있습니다. 이와 같은 시간 기반 액션을 다룰 때 기억해야 할 중요한 점은 게임의 프레임 속도가 일정하지 않으며 Update 함수 콜 사이의 시간 간격 역시 일정하지 않다는 점입니다.

그 예로 한번에 한 프레임씩 서서히 오브젝트를 전진시키는 일을 생각해 봅시다. 처음에는 각 프레임마다 고정 거리만큼 오브젝트를 옮기면 될 것처럼 보입니다.

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerFrame;
    
    void Update() {
        transform.Translate(0, 0, distancePerFrame); // this is incorrect
    }
}

그러나 프레임 시간이 일정하지 않기 때문에 오브젝트는 불규칙적인 속도로 움직이는 것처럼 보일 것입니다. 프레임 시간이 10ms이면 이 오브젝트는 distancePerFrame 에 의해 초당 100회 전진합니다. 그러나 프레임 시간이 (CPU 로드 등으로 인해) 25ms로 증가하면 이 오브젝트는 초당 40회만 전진하게 되며 따라서 더 적은 거리만큼 이동합니다. 이를 해결하려면 움직임의 크기를 Time.deltaTime에서 읽어올 수 있는 프레임 시간에 맞추어 스케일하면 됩니다.

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public float distancePerSecond;
    
    void Update() {
        transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
    }
}

이제 이동 거리가 distancePerFrame 이 아닌 distancePerSecond 로 주어진다는 점에 유의해야 합니다. 프레임 속도가 변화함에 따라 이동 단계의 크기 역시 맞추어 변하게 되며 따라서 이 오브젝트의 속력이 일정해집니다.

고정 타임스텝(fixedDeltaTime)

메인 프레임 업데이트와 달리, Unity의 물리 시스템은 고정된 타임스텝에 따라 _작동_합니다. 이는 시뮬레이션의 정확도와 일관성을 위해 중요합니다. 물리 업데이트 시작 시, Unity는 현재 시간을 따라잡는 데 필요한 만큼의 물리 업데이트를 수행합니다.

게임이나 앱이 고정된 타임스텝 값보다 높은 프레임 속도로 실행되면 각 프레임이 고정된 타임스텝 1개의 시간보다 짧다는 뜻입니다. 따라서 Unity는 프레임당 1회 또는 0회의 고정된 물리 업데이트를 수행합니다. 예를 들어, 고정된 타임스텝 값이 0.02인 경우 초당 50회의 고정된 업데이트가 수행됩니다. 이후 게임이나 앱이 초당 60프레임 가량의 속도로 실행되면 약 10개 프레임 중 1개 프레임 꼴로 고정된 업데이트가 발생하지 않는 프레임이 있을 것입니다.

게임이나 앱이 고정된 타임스텝 값보다 낮은 프레임 속도로 실행되면 각 프레임의 시간이 고정된 타임스텝 1개의 시간보다 길다는 뜻입니다. Unity는 이를 처리하기 위해 물리 시뮬레이션이 마지막 프레임 이후 경과된 시간을 “따라잡을 수 있도록” 프레임당 1회 이상의 고정된 업데이트를 수행합니다. 예를 들어, 고정된 타임스텝 값이 0.01이면 초당 100회의 고정된 업데이트가 발생합니다. 게임 앱이 초당 25프레임 가량의 속도로 실행되면 Unity는 초당 약 4회의 고정된 업데이트를 수행하여 물리 시뮬레이션의 시간을 현재 프레임 타임과 맞춥니다.

고정 타임스텝의 시간은 Time 창에서 변경할 수 있으며 스크립트에서 Time.fixedDeltaTime 프로퍼티를 사용하여 읽을 수 있습니다. 타임스텝 값이 낮으면 물리 업데이트가 더 자주 일어나고 시뮬레이션이 정밀해지지만 CPU 로드 비용이 커진다는 점에 유의해야 합니다. 물리 엔진에 각별히 높은 정밀도를 필요로 하는 어려운 요건을 요구하지 않는 이상, 고정 타임스텝을 기본값으로 두어도 무방합니다.

Unity의 시간 로직

다음 흐름도는 Unity가 프레임 1개에서 시간을 세는 데 사용하는 로직을 보여주며, time, deltaTime, fixedDeltaTimemaximumDeltaTime 프로퍼티의 상호 관계도 나타냅니다.

최대 허용 타임스텝(maximumDeltaTime)

매우 느린 프레임이 발생하면 Time 창의 Maximum Allowed Timestep이 다음 프레임에서 deltaTime 값을 제한하여 매우 큰 deltaTime 값으로 인해 발생할 수 있는 원하지 않는 부작용을 방지합니다. 이 값은 스크립팅을 통해 Time.maximumDeltaTime으로도 액세스 가능합니다.

예를 들어, maximumDeltaTime 값이 0.333이며 60Hz에서 실행되는 애플리케이션에서 매우 느린 프레임(2초) 1개가 발생하면 다음 표에 나타난 것과 비슷한 동작이 나타날 것입니다.

프레임 unscaledTime 시간 deltaTime smoothDeltaTime
1 0.000 3.000 0.014 0.014
2 0.014 3.015 0.014 0.014
3 0.028 3.028 0.014 0.014
4 (a) 0.042 3.043 0.014 0.014
5 2.062 (b) 3.376 (c) 0.333 (d) 0.078
6 2.082 3.396 0.020 0.066
7 2.096 3.410 0.014 0.056
8 2.110 3.424 0.014 0.048

위 데이터는 최대 허용 타임스텝 값(maximumDeltaTime)의 효과를 나타냅니다. 해당 데이터에는 18번의 번호가 부여된 프레임 8개가 있습니다. 이 예의 4번 프레임(a)은 완료될 때까지 약 25밀리초가 소요되는 다른 프레임과는 달리 실행 중 느려져서 완료될 때까지 2초가 걸립니다. 이로 인한 영향은 다음 프레임의 값에 반영됩니다. 이러한 일시 정지는 unscaledTime 값(b)에서 약 2의 인크리먼트로 확인 가능하나, 이 예시가 실행될 때 허용된 최대 타임스텝 값은 0.333이므로 Time.time 값은 단 0.333만큼 증가합니다(c). 이 프레임의 deltaTime 값 또한 최대 허용 타임스텝으로 고정되었습니다(d).

최대 허용 타임스텝 값 또한 물리 시간 엄수에 영향을 미칩니다. 고정 타임스텝은 실시간으로 물리 시뮬레이션을 정확히 유지해 주지만 게임에서 물리가 많이 사용되고 (예를 들어 모션에서 대량의 오브젝트가 사용되는 경우 등으로 인해) 게임플레이 프레임 속도도 낮아질 경우 문제를 일으킬 수 있습니다. 메인 프레임 업데이트 프로세싱은 일반 물리 업데이트 사이에 “끼워넣어져야” 하며, 프로세싱할 것이 많다면 물리 시뮬레이션이 현재의 시간을 따라잡을 수 있도록 하나의 프레임 동안 여러 번의 물리 업데이트가 일어날 수 있습니다. 프레임 시간, 오브젝트의 포지션, 기타 프로퍼티는 프레임 시작 시점에 고정되기 때문에, 물리가 더 자주 업데이트되면서 그래픽스의 싱크가 맞지 않게 될 수 있습니다.

CPU 파워가 제한적이면 물리 시스템이 처리할 수 있는 사항에도 제한이 있습니다. 따라서 Unity는 프레임 프로세싱이 뒤처지는 경우 물리 시간을 따라잡을 수 있도록 물리 시간을 늦출 수 있는 옵션을 제공합니다. 최대 허용 타임스텝은 Unity가 특정 프레임 업데이트 중 물리와 FixedUpdate 호출을 프로세싱하는 데 소요할 수 있는 시간을 제한합니다.

프레임 업데이트 처리에 소요되는 시간이 최대 허용 타임스텝보다 긴 경우, 물리 엔진은 “시간을 멈추고” 프레임 프로세싱이 시간을 따라잡을 수 있도록 합니다. 프레임 업데이트가 완료되면 물리는 시간을 정지한 이후 시간이 경과하지 않은 것처럼 재개됩니다. 결과적으로 리지드바디가 일반적인 경우처럼 완벽하게 실시간으로 움직이지 않으며, 약간 느려집니다. 그러나 물리 “시계”는 계속해서 리지드바디가 정상적으로 움직인 것처럼 추적합니다. 물리 시간의 지연은 보통 알아차릴 수 없으며, 게임플레이 성능과 맞바꾸더라도 무리가 없는 사항입니다.

시간 스케일

“불릿 타임”과 같은 특수 효과를 만들기 위해서는 게임 시간의 흐름을 늦추어 애니메이션과 스크립트 반응이 더 적은 비율로 일어나도록 하는 편이 유용할 때가 있습니다. 그뿐만 아니라 때로는 게임이 일시정지했을 때처럼 게임 시간을 완전히 멈추고자 할 때도 있습니다. Unity에서는 Time Scale 프로퍼티를 통해 게임 시간이 현실 시간에 대비하여 흘러가는 속도를 조절할 수 있습니다. 이 스케일이 1.0으로 설정되어 있으면 게임 시간은 현실 시간과 동일합니다. 값이 2.0이 되면 Unity에서는 시간이 두 배 빨리 흘러갑니다(즉, 액션 스피드가 올라갑니다). 한편 값이 0.5이면 게임플레이 속도가 절반으로 줄어듭니다. 값이 0이면 시간이 완전히 “멈추게” 됩니다. 시간 스케일은 실제로 느리게 실행되도록 만드는 것이 아니라 Time.deltaTimeTime.fixedDeltaTime을 통해 Update 및 FixedUpdate 함수에 전해지는 타임 스텝만을 변경한다는 점에 유의해야 합니다. 게임 시간이 느려지면 Update 함수는 보통 경우보다 더 자주 호출되게 되지만, 각 프레임마다 전해지는 deltaTime 스텝은 단순히 줄어듭니다. 다른 스크립트 함수는 시간 스케일의 영향을 받지 않으므로 예를 들어, 게임이 일시정지 상태일 때 일반 상호작용을 하는 GUI를 표시할 수 있습니다.

Time 창에는 시간 스케일을 전역으로 설정할 수 있도록 해 주는 프로퍼티가 있으나 일반적으로 더 유용한 방법은 Time.timeScale 프로퍼티를 사용하여 이 값을 스크립트에서 설정하는 것입니다.

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void Pause() {
        Time.timeScale = 0;
    }
    
    void Resume() {
        Time.timeScale = 1;
    }
}

캡처 프레임 속도

게임플레이를 비디오로 녹화하는 것은 시간 관리에 있어 아주 특수한 경우에 해당합니다. 화면 이미지를 저장하는 작업은 상당한 시간이 걸리기 때문에 일반 게임플레이 도중에 화면 녹화를 시도하면 평소의 게임 프레임 속도가 급격히 떨어지게 됩니다. 따라서 게임의 실제 성능을 반영하지 못하는 비디오가 만들어지게 됩니다.

다행히도 Unity는 이 문제를 해결할 수 있는 Capture Framerate 프로퍼티를 제공합니다. 프로퍼티 값을 0이 아닌 값으로 설정하면 게임 시간이 느려지고, 프레임 업데이트는 정확히 규칙적인 간격으로 일어납니다. 프레임 간의 시간 간격은 1/Time.captureFramerate과 같기 때문에, 프로퍼티 값이 5.0으로 설정되면 업데이트는 매 1/5초마다 발생합니다. 프레임 속도 요청이 실질적으로 감소하기 때문에 Update 함수가 스크린샷을 저장하거나 다른 동작을 수행할 시간이 확보됩니다.

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    // Capture frames as a screenshot sequence. Images are
    // stored as PNG files in a folder - these can be combined into
    // a movie using image utility software (eg, QuickTime Pro).
    // The folder to contain our screenshots.
    // If the folder exists we will append numbers to create an empty folder.
    string folder = "ScreenshotFolder";
    int frameRate = 25;
        
    void Start () {
        // Set the playback framerate (real time will not relate to game time after this).
        Time.captureFramerate = frameRate;
        
        // Create the folder
        System.IO.Directory.CreateDirectory(folder);
    }
    
    void Update () {
        // Append filename to folder name (format is '0005 shot.png"')
        string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
        
        // Capture the screenshot to the specified file.
        Application.CaptureScreenshot(name);
    }
}

이 기법을 사용하여 녹화한 비디오가 일반적으로 품질이 좋기는 하지만 속도가 급격히 느려지면 게임을 플레이하기 어렵습니다. 테스트 플레이어의 작업을 지나치게 복잡하게 만들지 않으면서 충분한 녹화 시간을 허용하기 위해 Time.captureFramerate 값을 바꿔가면서 실험해 보아야 합니다.

ScriptableObject
중요 클래스 - Mathf