본문 바로가기

UE4

[UE4] Actor의 Tick 관련 #1

0. 서문

 
Tick, 틱이란 Actor나 Component에 일정 간격(보통 프레임당)에 한번 코드 또는 블루프린트 스크립트를 실행시키는 것을 의미한다. Actor와 Component간의 프레임 단위의 태스크를 기준으로 한, 상대적인 틱 순서에 대한 이해는 게임 실행에 있어 일관성 관점에서 필수적이라 할 수 있다. (한 프레임 뒤쳐지는 문제 등등을 피하기 위해서라도...)
 
Actor와 Component의 틱 주기는 매 프레임이 될 수도 있고, 사용자(프로그래머)가 지정한 간격일 수도 있으며, 아예 Tick을 돌지 않게 할 수도 있다.
 
또한, 특정 그룹에 참여시켜 엔진의 메인 루프 내에서의 상대 순서를 설정할 수 있으며, 상호간 의존성 설정으로 어떠한 Actor, Component의 Tick 함수 실행 이후 자신의 Tick 함수 실행을 보장하게 할 수도 있다.
 

1. FTickFunction -> FActorTickFunction

 
본격적으로 세부 항목들을 살펴보기 전에 FTickFunction 구조체를 먼저 살펴보아야 한다.
FTickFunction은 Tick 함수들의 상위 추상 클래스이며, 다음과 같은 자식 클래스들이 있다.
  • FActorTickFunction
  • FActorComponentTickFunction
  • FStartPhysicsTickFunction
  • FEndPhysicsTickFunction
  • FStartAsyncSimulationTickFunction
  • FSkeletalMeshComponentClothTickFunction
  • FSkeletalMeshComponentEndPhysicsTickFunction
  • FCharacterMovementComponentPostPhysicsTickFunction
  • FPrimitiveComponentPostPhysicsTickFunction
 
이름에서 직관적으로 알 수 있듯이, AActor는 FActorTickFunction 구조체를 멤버로 가진다.
UCLASS(...생략...)
class ENGINE_API AActor : public UObject
{
    /**
     * Primary Actor tick function, which calls TickActor().
     * Tick functions can be configured to control whether ticking is enabled, at what time during a frame the update occurs, and to set up tick dependencies.
     * @see AddTickPrerequisiteActor(), AddTickPrerequisiteComponent()
     */
    UPROPERTY(EditDefaultsOnly, Category=Tick)
    struct FActorTickFunction PrimaryActorTick;
}
 
9 라인의 UPROPERTY에 보면 EditDefaultsOnly라고 되어 있다.
이는 이 변수가 클래스 기본설정에 해당한다는 의미이기에...
  • C++에서는 생성자에서만 접근해야 하고
  • 블루프린트 객체가 아닌, 블루프린트 자체 편집일 때만 변경할 수 있다.
 
FActorTickFunction의 선언부는 다음과 같다.
// "Runtime/Engine/Classes/Engine/EngineBaseTypes.h"

/**
* Tick function that calls AActor::TickActor
**/
USTRUCT()
struct FActorTickFunction : public FTickFunction
{
    GENERATED_USTRUCT_BODY()

    /**  AActor  that is the target of this tick **/
    class AActor*   Target;

    /**
        * Abstract function actually execute the tick.
        * @param DeltaTime - frame time to advance, in seconds
        * @param TickType - kind of tick for this frame
        * @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created
        * @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete.
    **/
    ENGINE_API virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef&MyCompletionGraphEvent) override;
    /** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/
    ENGINE_API virtual FString DiagnosticMessage() override;
};
 
21 라인의 ExecuteTick 가상함수 override 구현부에서 Actor::TickActor 함수를 호출하는 방식이다.
다음은 Actor.cpp에 구현되어 있는 FActorTickFunction::ExecuteTick 함수의 구현 내용이다.
void FActorTickFunction::ExecuteTick(
    float DeltaTime, enum ELevelTick TickType,
    ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent
)
{
    // const Actor* Target
    if (Target && !Target->IsPendingKillOrUnreachable())
    {
        if (TickType != LEVELTICK_ViewportsOnly || Target->ShouldTickIfViewportsOnly())
        {
            FScopeCycleCounterUObject ActorScope(Target);
            // Target Actor의 TickActor 함수 호출하는 것을 확인
            Target->TickActor(DeltaTime*Target->CustomTimeDilation, TickType, *this);
        }
    }
}
 
그 외 대부분의 기능은 FTickFunction에 정의/구현되어 있으나, 방대하여 아래의 각론 설명에서 나누어 설명토록 하겠다.
 

2. Enable/Disable

FTickFunction의 Tick 활성/비활성 부분에 관련된 멤버 변수들부터 살펴보도록 하자.
USTRUCT()
struct ENGINE_API FTickFunction
{
public:
    // Game이 pause된 상태에서도 tick을 돌려야 하는지 여부 설정. 일시 정지된 상태에서의 tick 처리는 매우 제한적이다. 기본값은 false
    UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay)
    uint8 bTickEvenWhenPaused:1;

    // false로 설정시 절대 tick 대상으로 등록되지 않고, tick이 돌지도 않음
    // 이후 SetActorTickEnabled 함수로 enable 시켜도 소용없음
    // 반드시 C++ 생성자에서 값을 설정해야 함. 기본값은 false
    UPROPERTY()
    uint8 bCanEverTick:1;

    // true이면 BeginePlay 이후부터 바로 Tick 함수가 실행되며, 이후 disable 시킬 수 있음
    // 기본값은 true
    UPROPERTY(EditDefaultsOnly, Category="Tick")
    uint8 bStartWithTickEnabled:1;
}
 
이 중에서 핵심적인 변수들은 bCanEverTick과 bStartWithTickEnabled이며, 이 둘은 AActor 클래스의 생성자에서 설정되어야 한다. 특히 bCanEverTick은 블루프린트에서 표시만 될 뿐 편집이 불가능하다.
이 두 변수의 설정/조합으로 인해 다음과 같은 경우들을 분기시킬 수 있다.
ATestActor::ATestActor
{
    // 기본값이 false이므로, 굳이 이렇게 할 필욘 없지만...
    // Tick 처리를 아예 하지 않게 된다.
    PrimaryActorTick.bCanEverTick = false;

    // Tick 처리 가능 + 시작하자마자 Tick 처리를 원할 때
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.bStartWithTickEnabled = true;


    // Tick 처리 가능 + 이후 Actor::SetActorTickEnabled를 통해 Tick 처리를 활성화 시키고 싶을 때
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.bStartWithTickEnabled = false;
}
 
PrimaryActorTick.bCanEverTick의 기본값은 false이며, 이것이 생성자에서 true로 설정되지 않으면, 필요 시점에 Tick 활성화가 불가능하므로, Actor가 아예 Tick을 받을 일이 없지 않은 한 신경써서 셋팅해 주어야 한다.
 
Actor 클래스에서 구현된 Tick enable/disable 관련 함수는 다음의 것들이 있으며, FActorTickFunction의 멤벼 변수를 직접 조작하거나, FActorTickFunction의 멤버 함수들을 대리 호출하는 식으로 구현되어 있다.
UCLASS(...생략...)
class ENGINE_API AActor : public UObject
{
    // bCanEverTick 값 반환
    FORCEINLINE bool CanEverTick() const { return PrimaryActorTick.bCanEverTick; }

    // 이 Actor의 Tick이 활성화되었는가 여부 반환
    UFUNCTION(BlueprintCallable, Category="Utilities")
    bool IsActorTickEnabled() const;

    // Tick을 활성/비활성화시키는 함수
    // PrimaryActorTick.bCanEverTick = false이면, 등록되지 않아 무시된다
    UFUNCTION(BlueprintCallable, Category="Utilities")
    void SetActorTickEnabled(bool bEnabled);

    // Game pause 상태에서의 Tick 가능 여부(PrimaryActorTick.bTickEvenWhenPaused) 반환
    UFUNCTION(BlueprintCallable, Category="Utilities")
    bool GetTickableWhenPaused();

    // Game pause 상태에서의 Tick 가능 여부(PrimaryActorTick.bTickEvenWhenPaused) 설정
    UFUNCTION(BlueprintCallable, Category="Utilities")
    void SetTickableWhenPaused(bool bTickableWhenPaused);
}

 

AActor::SetActorTickEnabled 함수만 내용을 살펴보면, 위에서 언급한 PrimaryActorTick.bCanEverTick에 대한 설명이 이해가 갈 것이다.
void AActor::SetActorTickEnabled(bool bEnabled)
{
    // PrimaryActorTick.bCanEverTick 값이 false일 경우 무시
    if (!IsTemplate() && PrimaryActorTick.bCanEverTick)
    {
        PrimaryActorTick.SetTickFunctionEnable(bEnabled);
    }
}
 

3. Tick 콜백 함수

Tick 처리 콜백은 다음과 같이 선언되어 있으며, 자유롭게 오버라이드가 가능하다.
// Actor가 Tick을 받을 때의 콜백이며, Actor의 성격에 따라 가상함수 오버라이드할 것
// PrimaryActorTick.bCanEverTick의 값이 false이면 Tick이 동작하지 않음에 주의할 것
virtual void Tick(float DeltaSeconds);

 

4. Interval

기본적으로 Tick은 every frame마다 수행되기에, 굳이 every frame이 필요치 않은 녀석의 경우 성능상의 이득을 위해 Tick 주기를 의도적으로 늘릴 필요가 있다. (60fps의 경우 산술적인 Tick interval은 16.666ms)
FTickFunction 구조체에 TickInterval 멤버 변수가 존재하며, Actor의 경우 이에 대한 Getter/Setter 함수가 존재한다.
// FTickFunction의 멤버 변수
USTRUCT()
struct ENGINE_API FTickFunction
{
public:
    // Tick 함수가 실행될 초(second) 단위 frequency
    // 0과 같거나 0보다 작은 값이 셋팅되면, Tick은 every frame 실행된다
    // 기본값은 0.f
    UPROPERTY(EditDefaultsOnly, Category="Tick", meta=(DisplayName="Tick Interval (secs)"))
    float TickInterval;
}


// AActor의 Gettet/Setter 멤버 함수
UCLASS(...생략...)
class ENGINE_API AActor : public UObject
{
    // PrimaryTickFunction의 TickInterval 값 변경
    // 이 함수는 Tick 활성/비활성화에는 관여치 않으며, 변경된 TickInterval은 다음 Tick부터 적용된다
    UFUNCTION(BlueprintCallable, Category="Utilities")
    void SetActorTickInterval(float TickInterval);
   
    // PrimaryTickFunction.TickInterval 값 반환
    UFUNCTION(BlueprintCallable, Category="Utilities")
    float GetActorTickInterval() const;
}
 
내용이 길어져 글을 2개로 분할한다.
2부 문서는 여기를 참고하기 바란다.