Version: 2019.4
언어: 한국어
이벤트 함수
게임 오브젝트 생성 및 제거

시간 및 프레임 속도 관리

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

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

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

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


//JS script example
var distancePerFrame: float;

function Update() {
    transform.Translate(0, 0, distancePerFrame);
}

그러나 프레임 시간이 일정하지 않기 때문에 오브젝트는 불규칙적인 속도로 움직이는 것처럼 보일 것입니다. 프레임 시간이 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);
    }
}


//JS script example
var distancePerSecond: float;

function Update() {
    transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
}

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

고정 타임스텝(Fixed Timestep)

메인 프레임 업데이트와는 달리 Unity의 물리 시스템은 고정 타임스텝으로 작동하며 이 점은 시뮬레이션의 정확도와 일관성에 매우 중요합니다. 물리 업데이트를 시작할 때 Unity는 마지막 물리 업데이트가 끝난 시간에 고정 타임스텝 값을 추가함으로써 “알람”을 설정합니다. 그러고 나면 물리 시스템은 알람이 꺼질 때까지 계산을 수행합니다.

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

최대 허용 타임스텝(Maximum Allowed Timestep)

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

물론 상당히 많은 CPU 파워를 사용할 수 있기는 하지만 Unity에서는 효과적으로 물리 시간을 늦추어 프레임 프로세싱이 따라잡을 수 있도록 하는 옵션을 제공합니다. (Time 창의) Maximum Allowed Timestep 설정은 주어진 프레임 업데이트 동안 Unity가 물리 프로세싱에 사용하는 시간 및 FixedUpdate 호출 수에 제한을 둡니다. 프레임 업데이트 처리가 Maximum Allowed Timestep 보다 오래 걸린다면 물리 엔진은 “시간을 멈추고” 프레임 프로세싱이 따라잡도록 합니다. 일단 프레임 업데이트가 끝나고 나면, 물리 엔진은 멈췄던 때로부터 시간이 흐르지 않은 것처럼 재개됩니다. 이렇게 하면 리지드바디는 보통 실시간으로 완벽하게 움직이던 것과는 달리 약간 느려지게 됩니다. 그러나 물리 "시계"는 리지드바디가 정상적으로 움직인 것처럼 계속 따라잡습니다. 물리 시간이 느려진 것은 보통 눈에 띄지 않으며 게임플레이 성능을 위해 감수할 만합니다.

시간 스케일(Time Scale)

“불릿 타임”과 같은 특수 효과를 만들기 위해서는 게임 시간의 흐름을 늦추어 애니메이션과 스크립트 반응이 더 적은 비율로 일어나도록 하는 편이 유용할 때가 있습니다. 그뿐만 아니라 때로는 게임이 일시정지했을 때처럼 게임 시간을 완전히 멈추고자 할 때도 있습니다. 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;
    }
}

//JS script example
function Pause() {
    Time.timeScale = 0;
}

function Resume() {
    Time.timeScale = 1;
}

캡처 프레임 속도(Capture Framerate)

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

다행히도 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);
    }
}

//JS script example

// 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.
var folder = "ScreenshotFolder";
var frameRate = 25;


function 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);
}

function Update () {
    // Append filename to folder name (format is '0005 shot.png"')
    var name = String.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );

    // Capture the screenshot to the specified file.
    Application.CaptureScreenshot(name);
}

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

이벤트 함수
게임 오브젝트 생성 및 제거