UE4 Tick机制
2022/7/31 23:39:21
本文主要是介绍UE4 Tick机制,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
为了管理时间,Unreal将游戏运行时间片分隔为"Ticks"。一个Tick是关卡中所有Actors更新的最小时间单位。一个tick一般是10ms-100ms(CPU性能越好,游戏逻辑越简单,tick的时间越短)。
Tick总流程
一共有8个TickGroup:
/** Determines which ticking group a tick function belongs to. */ UENUM(BlueprintType) enum ETickingGroup { /** Any item that needs to be executed before physics simulation starts. */ TG_PrePhysics UMETA(DisplayName="Pre Physics"), /** Special tick group that starts physics simulation. */ TG_StartPhysics UMETA(Hidden, DisplayName="Start Physics"), /** Any item that can be run in parallel with our physics simulation work. */ TG_DuringPhysics UMETA(DisplayName="During Physics"), /** Special tick group that ends physics simulation. */ TG_EndPhysics UMETA(Hidden, DisplayName="End Physics"), /** Any item that needs rigid body and cloth simulation to be complete before being executed. */ TG_PostPhysics UMETA(DisplayName="Post Physics"), /** Any item that needs the update work to be done before being ticked. */ TG_PostUpdateWork UMETA(DisplayName="Post Update Work"), /** Catchall for anything demoted to the end. */ TG_LastDemotable UMETA(Hidden, DisplayName = "Last Demotable"), /** Special tick group that is not actually a tick group. After every tick group this is repeatedly re-run until there are no more newly spawned items to run. */ TG_NewlySpawned UMETA(Hidden, DisplayName="Newly Spawned"), TG_MAX, };
各个TickGroup串行执行,保证有序。整个Tcik流程代码:
void UWorld::Tick( ELevelTick TickType, float DeltaSeconds ) { // TArray<FLevelCollection> LevelCollections成员变量 逐Level执行 for (int32 i = 0; i < LevelCollections.Num(); ++i) { SetupPhysicsTickFunctions(DeltaSeconds); TickGroup = TG_PrePhysics; // reset this to the start tick group FTickTaskManagerInterface::Get().StartFrame(this, DeltaSeconds, TickType, LevelsToTick); RunTickGroup(TG_PrePhysics); RunTickGroup(TG_StartPhysics); RunTickGroup(TG_DuringPhysics, false); // 不阻塞 RunTickGroup(TG_EndPhysics); RunTickGroup(TG_PostPhysics); GetTimerManager().Tick(DeltaSeconds); // 对Timer进行Tick FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds); // 对Tickable对象进行Tick // 更新相机 RunTickGroup(TG_PostUpdateWork); RunTickGroup(TG_LastDemotable); FTickTaskManagerInterface::Get().EndFrame(); } }
各Tick步骤详细说明:
Tick步骤 | 说明 |
SetupPhysicsTickFunctions(DeltaSeconds) |
① 没有注册,则注册UWorld.StartPhysicsTickFunction到TG_StartPhysics组,注册UWorld.EndPhysicsTickFunction到TG_EndPhysics组。 并将UWorld.StartPhysicsTickFunction设置为UWorld.EndPhysicsTickFunction依赖的前置TickFunction。 注:只会注册一次 后续执行对应的TickGroup时,调用相应的ExecuteTick()函数 ② DeferredPhysResourceCleanup() // 清理物理引擎资源 ③ 设置PhysScene->SetUpForFrame()。如:Gravity(重力)、MaxPhysicsDeltaTime、MaxSubstepDeltaTime、MaxSubsteps、bSubstepping等 |
FTickTaskManagerInterface::Get().StartFrame (this, DeltaSeconds, LEVELTICK_All, LevelsToTick) |
① 调用TickTaskSequencer.StartFrame(),重置FTickTaskSequencer中的数据。 ② 设置当前帧的关卡数据到FTickTaskManager.LevelList数组中。 ③ 循环遍历FTickTaskManager.LevelList数组,执行FTickTaskLevel.StartFrame(),返回为Enabled状态FTickFunction的个数 a. 设置FTickTaskLevel的FTickContext信息 b. 执行FTickTaskLevel.ScheduleTickFunctionCooldowns()函数:将TickFunctionsToReschedule中的FTickFunction按照Cooldown进行升序排序 然后设置为CoolingDown状态并放进AllCoolingDownTickFunctions列表中 c. 遍历AllCoolingDownTickFunctions链表,将Cooldown小于等于0的FTickFunction设置为Enabled状态 ④ 循环遍历FTickTaskManager.LevelList数组,执行FTickTaskLevel.QueueAllTicks() a. 遍历AllEnabledTickFunctions,逐个执行FTickFunction.QueueTickFunction()函数 ■ 先遍历当前FTickFunction依赖的前置FTickFunction的Prerequisites列表,逐个调用QueueTickFunction()函数(递归),来创建Task任务(Hold不执行) ■ 然后将这些依赖的前置FTickFunction的Task的FGraphEventRef对象设为前置任务 调用FTickTaskSequencer.QueueTickTask() --》FTickTaskSequencer.StartTickTask()为当前FTickFunction创建Task任务(Hold不执行) 然后调用FTickTaskSequencer.AddTickTaskCompletion()将创建出来的Task任务添加到FTickTaskSequencer.HiPriTickTasks或FTickTaskSequencer.TickTasks数组中 以及将创建出来的Task的FGraphEventRef对象添加到FTickTaskSequencer.TickCompletionEvents数组中 b. 遍历AllCoolingDownTickFunctions,对里面处于Enabled状态的FTickFunction执行FTickFunction.QueueTickFunction()函数(具体细节如上) |
RunTickGroup(TG_PrePhysics) |
FTickTaskManagerInterface::Get().RunTickGroup(TG_PrePhysics, bBlockTillComplete=true) a. 调用TickTaskSequencer.ReleaseTickGroup(TG_PrePhysics, bBlockTillComplete=true) ■ 调用DispatchTickGroup(ENamedThreads::GameThread, TG_PrePhysics) Unlock来执行所有FTickTaskSequencer.HiPriTickTasks[TG_PrePhysics]或FTickTaskSequencer.TickTasks[TG_PrePhysics]数组中的Task ■ 阻塞等待FTickTaskSequencer.TickCompletionEvents[TG_PrePhysics],保证当前TG_PrePhysics组所有Task执行完成 ■ 调用FTickTaskSequencer.ResetTickGroup(TG_PrePhysics),重置FTickTaskSequencer.TickCompletionEvents[TG_PrePhysics]中的数据 |
RunTickGroup(TG_StartPhysics) |
执行FStartPhysicsTickFunction.ExecuteTick()函数,发起物理模拟过程 a. 调用UWorld.StartPhysicsSim() --》FPhysScene_PhysX.StartFrame() : FPhysScene_PhysX.TickPhysScene(FGraphEventRef& InOutCompletionEvent = PhysicsSubsceneCompletion): InOutCompletionEvent = FGraphEvent::CreateGraphEvent(); PhysXCompletionTask* Task = new PhysXCompletionTask(InOutCompletionEvent, ApexScene->getTaskManager()); ApexScene->simulate(AveragedFrameTime, true, Task, SimScratchBuffer.Buffer, SimScratchBuffer.BufferSize); // 发起物理模拟(异步并行) Task->removeReference(); FGraphEventArray MainScenePrerequisites; MainScenePrerequisites.Add(PhysicsSubsceneCompletion); FGraphEventArray FinishPrerequisites = FDelegateGraphTask::CreateAndDispatchWhenReady( FDelegateGraphTask::FDelegate::CreateRaw(this, &FPhysScene_PhysX::SceneCompletionTask), GET_STATID(STAT_FDelegateGraphTask_ProcessPhysScene_Sync), &MainScenePrerequisites, ENamedThreads::GameThread, ENamedThreads::GameThread); if (FinishPrerequisites.Num()) &FinishPrerequisites, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(); 注1:当ApexScene->simulate()物理模拟完成后,会回调执行PhysXCompletionTask,则PhysicsSubsceneCompletion事件就会完成 由于SceneCompletionTask的前置MainScenePrerequisites任务列表中,只有一个PhysicsSubsceneCompletion事件 此时就会开始执行FPhysScene_PhysX::SceneCompletionTask() --》FPhysScene_PhysX::ProcessPhysScene(): PxScene* PScene = GetPxScene(); PScene->lockWrite(); bool bSuccess = ApexScene->fetchResults(true, &OutErrorCode); // 取回物理模拟计算结果 PScene->unlockWrite(); 注2:FPhysScene_PhysX::SceneCompletionTask()执行完成后,PhysicsSceneCompletion事件就会完成 |
RunTickGroup(TG_DuringPhysics, false) | FTickTaskManagerInterface::Get().RunTickGroup(TG_DuringPhysics, bBlockTillComplete=false) // 不阻塞等待 |
RunTickGroup(TG_EndPhysics) |
执行FEndPhysicsTickFunction.ExecuteTick()函数 a. 调用FPhysScene_PhysX::GetCompletionEvents()获取PhysicsSceneCompletion事件 b. 调用FPhysScene_PhysX::GetCompletionEvents()来判断PhysicsSceneCompletion事件是否完成,未完成则阻塞等待 c. PhysicsSceneCompletion事件完成后,会触发UWorld::FinishPhysicsSim() --》FPhysScene_PhysX::EndFrame(LineBatcher) |
RunTickGroup(TG_PostPhysics) | FTickTaskManagerInterface::Get().RunTickGroup(TG_PostPhysics, bBlockTillComplete=true) |
GetTimerManager().Tick(DeltaSeconds) | Timer Tick |
FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds) | Tickable Tick |
RunTickGroup(TG_PostUpdateWork) |
TG_PostUpdateWork阶段在摄像机更新之后,如武器的镭射特效必须知晓摄像机的准确朝向,就要将控制这些特效的Actor放到这个阶段。 这个阶段也可用于在逻辑帧中最靠后运行的游戏逻辑,如解决格斗游戏中两个角色在同一帧中尝试抓住对方的情况。 |
RunTickGroup(TG_LastDemotable) | FTickTaskManagerInterface::Get().RunTickGroup(TG_LastDemotable, bBlockTillComplete=true) |
FTickTaskManagerInterface::Get().EndFrame() |
① 执行FTickTaskSequencer.EndFrame()函数 ② 循环遍历FTickTaskManager.LevelList数组,执行执行FTickTaskLevel.EndFrame() a.执行FTickTaskLevel.ScheduleTickFunctionCooldowns()函数:将TickFunctionsToReschedule中的FTickFunction按照Cooldown进行排序 然后设置为CoolingDown状态并放进AllCoolingDownTickFunctions列表中 ③ 清空FTickTaskManager.LevelList数组 |
Actor / ActorComponent Tick
一个Actor对象可以包含多个ActorComponent对象。虽然它们之间是组合关系,但它们的Tick注册和开启都是互相独立的,不存在依赖关系。
FTickFunction中的数据成员:
/** * Abstract Base class for all tick functions. **/ USTRUCT() struct ENGINE_API FTickFunction { GENERATED_USTRUCT_BODY() public: // The following UPROPERTYs are for configuration and inherited from the CDO/archetype/blueprint etc /** * Defines the minimum tick group for this tick function. These groups determine the relative order of when objects tick during a frame update. * Given prerequisites, the tick may be delayed. * * @see ETickingGroup * @see FTickFunction::AddPrerequisite() */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) TEnumAsByte<enum ETickingGroup> TickGroup; /** * Defines the tick group that this tick function must finish in. These groups determine the relative order of when objects tick during a frame update. * * @see ETickingGroup */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) TEnumAsByte<enum ETickingGroup> EndTickGroup; public: /** Bool indicating that this function should execute even if the game is paused. Pause ticks are very limited in capabilities. **/ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) uint8 bTickEvenWhenPaused:1; /** If false, this tick function will never be registered and will never tick. Only settable in defaults. */ UPROPERTY() uint8 bCanEverTick:1; /** If true, this tick function will start enabled, but can be disabled later on. */ UPROPERTY(EditDefaultsOnly, Category="Tick") uint8 bStartWithTickEnabled:1; /** If we allow this tick to run on a dedicated server */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) uint8 bAllowTickOnDedicatedServer:1; /** Run this tick first within the tick group, presumably to start async tasks that must be completed with this tick group, hiding the latency. */ uint8 bHighPriority:1; /** If false, this tick will run on the game thread, otherwise it will run on any thread in parallel with the game thread and in parallel with other "async ticks" **/ uint8 bRunOnAnyThread:1; // ... ... }
注册Tick
在Actor / ActorComponent的C++类型,才能在构造函数中设置PrimaryActorTick.bCanEverTick的值,来决定是否在BeginPlay时为其注册FTickFunction。
而在Actor / ActorComponent的蓝图类型中是不能重新设置PrimaryActorTick.bCanEverTick的。
如果是蓝图类型缺省对象或CDO,不会注册FTickFunction函数,详见AActor::RegisterAllActorTickFunctions函数:
void AActor::RegisterAllActorTickFunctions(bool bRegister=true, bool bDoComponents=false) { if(!IsTemplate()) // IsTemplate缺省参数为RF_ArchetypeObject|RF_ClassDefaultObject { RegisterActorTickFunctions(bRegister); // 注册FTickFunction } }
以AMyActor / UMyActorComponent为例来说明注册FTickFunction函数过程
UMyActorComponent代码:
/**************MyActorComponent.h****************/ #pragma once #include "Components/ActorComponent.h" #include "MyActorComponent.generated.h" UCLASS() class UMyActorComponent : public UActorComponent { GENERATED_BODY() public: // Sets default values for this component's properties UMyActorComponent(); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; }; /***************MyActorComponent.cpp*******************/ #include "MyActorComponent.h" UMyActorComponent::UMyActorComponent() { PrimaryComponentTick.bCanEverTick = true; // 为该UMyActorComponent对象注册FTickFunction } // Called when the game starts void UMyActorComponent::BeginPlay() { Super::BeginPlay(); } // Called every frame void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); }
AMyActor代码:
/***************MyActor.h*******************/ #pragma once #include "GameFramework/Actor.h" #include "MyActorComponent.h" #include "MyActor.generated.h" UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AMyActor(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; private: // 若不指定EditAnywhere,在AMyActor派生的蓝图类中,看不到其MyActorComponent* MyActorComponent组件的成员数据 UPROPERTY(EditAnywhere) class UMyActorComponent* MyActorComponent; }; /***************MyActor.cpp*******************/ #include "MyActor.h" // Sets default values AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = true; // 为该AMyActor对象注册FTickFunction MyActorComponent = CreateDefaultSubobject<UMyActorComponent>(TEXT("MyActorComponent")); } // Called when the game starts or when spawned void AMyActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); }
AAMyctor的FTickFunction注册调用堆栈:
FTickTaskLevel::AddTickFunction(FTickFunction * TickFunction=0x00000253bd5c1938) FTickTaskManager::AddTickFunction(ULevel * InLevel=0x00000253b6370100, FTickFunction * TickFunction=0x00000253bd5c1938) FTickFunction::RegisterTickFunction(ULevel * Level=0x00000253b6370100) AActor::RegisterActorTickFunctions(bool bRegister=true) AActor::RegisterAllActorTickFunctions(bool bRegister=true, bool bDoComponents=false) AActor::BeginPlay() AMyActor::BeginPlay()
AAMyctor中UMyActorComponent的FTickFunction注册调用堆栈:
FTickTaskLevel::AddTickFunction(FTickFunction * TickFunction=0x00000253bde00f80) FTickTaskManager::AddTickFunction(ULevel * InLevel=0x00000253b6370100, FTickFunction * TickFunction=0x00000253bde00f80) FTickFunction::RegisterTickFunction(ULevel * Level=0x00000253b6370100) UActorComponent::SetupActorComponentTickFunction(FTickFunction * TickFunction=0x00000253bde00f80) UActorComponent::RegisterComponentTickFunctions(bool bRegister=true) UActorComponent::RegisterAllComponentTickFunctions(bool bRegister=true) AActor::BeginPlay() AMyActor::BeginPlay()
AActor::BeginPlay函数逻辑:
void AActor::BeginPlay() { SetLifeSpan( InitialLifeSpan ); RegisterAllActorTickFunctions(true, false); // 注册Actor自身的FTickFunction TInlineComponentArray<UActorComponent*> Components; GetComponents(Components); for (UActorComponent* Component : Components) { if (Component->IsRegistered() && !Component->HasBegunPlay()) { Component->RegisterAllComponentTickFunctions(true); // 注册Actor中所有UActorComponent的FTickFunction Component->BeginPlay(); } } if (GetAutoDestroyWhenFinished()) { if (UWorld* MyWorld = GetWorld()) { if (UAutoDestroySubsystem* AutoDestroySys = MyWorld->GetSubsystem<UAutoDestroySubsystem>()) { AutoDestroySys->RegisterActor(this); } } } ReceiveBeginPlay(); ActorHasBegunPlay = EActorBeginPlayState::HasBegunPlay; }
启用Tick
在Actor / ActorComponent的C++类型,在构造函数中设置PrimaryActorTick.bStartWithTickEnabled=true,就会在注册FTickFunction前将ETickState TickState设为Enabled。
在Actor / ActorComponent的蓝图类型中,可以重新设置PrimaryActorTick.bStartWithTickEnabled的值。
在后续的逻辑中,可以调用void AActor::SetActorTickEnabled(bool bEnabled)和void UActorComponent::SetComponentTickEnabled(bool bEnabled)来Enabled/Disable Tick。
设置MyBlueprintActor(从AMyActor派生)蓝图类的bStartWithTickEnabled:
设置MyBlueprintActor(从AMyActor派生)蓝图类中的UMyActorComponent组件的bStartWithTickEnabled:
Tick变量缺省值
变量 | Actor | ActorComponent | 含义 |
PrimaryActorTick.bCanEverTick | false | false |
是否注册Tick 注:只能在c++构造函数中设置 |
PrimaryActorTick.bStartWithTickEnabled | true | true | 是否在注册时就启用Tick |
PrimaryActorTick.TickGroup | TG_PrePhysics | TG_DuringPhysics |
每个TickFunction都可以指定TickGroup,但是这个TickGroup并不代表最终实际执行的TickGroup。 根据每个Tick注册的Prerequisite不同,Tick的执行Group会被延迟。 例如,如果TickFunction要求的Prerequisite是在TG_PostPhysics中的,那么即便它自己是注册为TG_PrePhyiscs, 也会被推迟到Post才能执行, TickFunction的ActualStartTickGroup会变成实际的Tick执行组。 |
PrimaryActorTick.EndTickGroup | TG_PrePhysics | TG_PrePhysics | 决定TickFunction必须在哪个TickGroup中或之前完成。 |
PrimaryActorTick.bAllowTickOnDedicatedServer | true | true | 是否在DS上执行 |
PrimaryActorTick.TickInterval |
小于等于0则每帧都更新 大于0则按照TickInterval时间间隔来更新 |
||
PrimaryActorTick.bTickEvenWhenPaused | false | false | PIE中点击Pause按钮后,是否仍然执行Tick |
PrimaryActorTick.bHighPriority | false | false |
为true,添加到FTickTaskSequencer.HiPriTickTasks数组中(作为高优先级的任务) 为false,添加到FTickTaskSequencer.TickTasks数组中(作为正常优先级的任务) 也可通过FTickFunction.SetPriorityIncludingPrerequisites(bool bInHighPriority)来修改Task的优先级 |
PrimaryActorTick.bRunOnAnyThread | false | false |
为ture,放在TaskGraph的AnyThread上跑 为false,放在GameThread上跑 |
Tick执行
Actor / ActorComponent的的Tick会被封装成FTickFunctionTask任务,由TaskGraph系统来执行。
AMyActor执行Tick的调用堆栈:
AMyActor::Tick(float DeltaTime=0.0141042992) AActor::TickActor(float DeltaSeconds=0.0141042992, ELevelTick TickType=LEVELTICK_All, FActorTickFunction & ThisTickFunction={...}) FActorTickFunction::ExecuteTick(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) TGraphTask<FTickFunctionTask>::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & NewTasks={...}, ENamedThreads::Type CurrentThread=GameThread) FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex=0, bool bAllowStall=false) FNamedTaskThread::ProcessTasksUntilIdle(int QueueIndex=0) FTaskGraphImplementation::ProcessThreadUntilIdle(ENamedThreads::Type CurrentThread=GameThread) FTickTaskSequencer::ReleaseTickGroup(ETickingGroup WorldTickGroup=TG_DuringPhysics, bool bBlockTillComplete=false) FTickTaskManager::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::Tick(ELevelTick TickType=LEVELTICK_All, float DeltaSeconds=0.0141042992)
UMyActorComponent执行Tick的调用堆栈:
UMyActorComponent::TickComponent(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, FActorComponentTickFunction * ThisTickFunction=0x000001a26f19cf80) FActorComponentTickFunction::ExecuteTick::__l2::<lambda>(float DilatedTime=0.0141042992) FActorComponentTickFunction::ExecuteTickHelper<void <lambda>(float)>(UActorComponent * Target=0x000001a26f19cf40, bool bTickInEditor=false, float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, const FActorComponentTickFunction::ExecuteTick::__l2::void <lambda>(float) & ExecuteTickFunc=void <lambda>(float DilatedTime){...}) FActorComponentTickFunction::ExecuteTick(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) TGraphTask<FTickFunctionTask>::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & NewTasks={...}, ENamedThreads::Type CurrentThread=GameThread) FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex=0, bool bAllowStall=false) FNamedTaskThread::ProcessTasksUntilIdle(int QueueIndex=0) FTaskGraphImplementation::ProcessThreadUntilIdle(ENamedThreads::Type CurrentThread=GameThread) FTickTaskSequencer::ReleaseTickGroup(ETickingGroup WorldTickGroup=TG_DuringPhysics, bool bBlockTillComplete=false) FTickTaskManager::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::Tick(ELevelTick TickType=LEVELTICK_All, float DeltaSeconds=0.0141042992)
Tick的依赖性
与其他单独的Actor不同,AController和APawn有着关联关系。以此为例说明Tick的依赖性。
每次AController执行AttachToPawn函数绑定一个Pawn对象时,就会调用AddPawnTickDependency函数来建立它们之间Tick依赖关系:
void AController::AddPawnTickDependency(APawn* NewPawn) { if (NewPawn != NULL) { bool bNeedsPawnPrereq = true; UPawnMovementComponent* PawnMovement = NewPawn->GetMovementComponent(); if (PawnMovement && PawnMovement->PrimaryComponentTick.bCanEverTick) { PawnMovement->PrimaryComponentTick.AddPrerequisite(this, this->PrimaryActorTick); // Don't need a prereq on the pawn if the movement component already sets up a prereq. if (PawnMovement->bTickBeforeOwner || NewPawn->PrimaryActorTick.GetPrerequisites().Contains(FTickPrerequisite(PawnMovement, PawnMovement->PrimaryComponentTick))) { bNeedsPawnPrereq = false; } } if (bNeedsPawnPrereq) { NewPawn->PrimaryActorTick.AddPrerequisite(this, this->PrimaryActorTick); } } }
总架构图
控制台变量
变量 | 说明 |
CriticalPathStall.TickStartFrame |
Sleep for the given time in start frame. Time is given in ms. This is a debug option used for critical path analysis and forcing a change in the critical path. 注:非shipping包可用。 |
tick.AddIndirectTestTickFunctions | Add no-op ticks to test performance of ticking infrastructure. |
tick.AddTestTickFunctions | Add no-op ticks to test performance of ticking infrastructure. |
tick.AllowAsyncComponentTicks | Used to control async component ticks. |
tick.AllowAsyncTickCleanup | If true, ticks are cleaned up in a task thread. |
tick.AllowAsyncTickDispatch | If true, ticks are dispatched in a task thread. |
tick.AllowConcurrentTickQueue |
If true, queue ticks concurrently. 注:非windows、非android平台可用。 |
tick.AnimationDelaysEndGroup | If > 0, then skeletal meshes that do not rely on physics simulation will set their animation end tick group to TG_PostPhysics. |
tick.DoAsyncEndOfFrameTasks | Experimental option to run various things concurrently with the HUD render. |
tick.DoAsyncEndOfFrameTasks.Randomize | Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread. |
tick.DoAsyncEndOfFrameTasks.ValidateReplicatedProperties | If true, validates that replicated properties haven't changed during the Slate tick. Results will not be valid if demo.ClientRecordAsyncEndOfFrame is also enabled. |
tick.HiPriSkinnedMeshes | If > 0, then schedule the skinned component ticks in a tick group before other ticks. |
tick.LightweightTimeguardThresholdMS | Threshold in milliseconds for the tick timeguard |
tick.LogTicks | Spew ticks for debugging. |
tick.RemoveTestTickFunctions | Remove no-op ticks to test performance of ticking infrastructure. |
tick.SecondsBeforeEmbeddedAppSleeps | When built as embedded, how many ticks to perform before sleeping |
tick.ShowPrerequistes | When logging ticks, show the prerequistes; debugging. |
Timer Tick
通过传入Delegate执行体和相关参数来调用TimerManager的SetTimer方法来设置一个定时器。
Timer的实现相关的逻辑:UnrealEngine\Engine\Source\Runtime\Engine\Public\TimerManager.h、UnrealEngine\Engine\Source\Runtime\Engine\Private\TimerManager.cpp
Automation System(自动化测试系统)测试代码:UnrealEngine\Engine\Source\Runtime\Engine\Private\TimerManagerTests.cpp
/** * Sets a timer to call the given native function at a set interval. If a timer is already set * for this handle, it will replace the current timer. * * @param InOutHandle If the passed-in handle refers to an existing timer, it will be cleared before the new timer is added. A new handle to the new timer is returned in either case. * @param InObj Object to call the timer function on. * @param InTimerMethod Method to call when timer fires. * @param InRate The amount of time (in seconds) between set and firing. If <= 0.f, clears existing timers. * @param InbLoop true to keep firing at Rate intervals, false to fire only once. * @param InFirstDelay The time (in seconds) for the first iteration of a looping timer. If < 0.f InRate will be used. */ template< class UserClass > FORCEINLINE void SetTimer(FTimerHandle& InOutHandle, UserClass* InObj, typename FTimerDelegate::TUObjectMethodDelegate< UserClass >::FMethodPtr InTimerMethod, float InRate, bool InbLoop = false, float InFirstDelay = -1.f); // AActor对象中调用SetTimer GetWorldTimerManager().SetTimer(TimerHandle_UpdateNetSpeedsTimer, this, &AGameNetworkManager::UpdateNetSpeedsTimer, 1.0f); // UObject对象中调用SetTimer GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateUObject(this, &UNavMeshRenderingComponent::TimerFunction), 1, true);
FTimerManager中其他一些重要函数:
FTimerHandle SetTimerForNextTick(...); // 设置下一帧调用的Timer
void ClearTimer(FTimerHandle& InHandle); // 清除名为InHandle的Timer
void ClearAllTimersForObject(void const* Object); // 清除与Object对象关联的所有Timer
void PauseTimer(FTimerHandle InHandle); // 暂停名为InHandle的Timer
void UnPauseTimer(FTimerHandle InHandle); // 继续运行名为InHandle的Timer
float GetTimerRate(FTimerHandle InHandle) const; // 获取名为InHandle的Timer的间隔
bool IsTimerActive(FTimerHandle InHandle) const; // 名为InHandle的Timer是否处于激活状态
bool IsTimerPaused(FTimerHandle InHandle) const // 名为InHandle的Timer是否处于暂停状态
bool IsTimerPending(FTimerHandle InHandle) const; // 名为InHandle的Timer是否处于Pending状态
bool TimerExists(FTimerHandle InHandle) const; // 名为InHandle的Timer是否存在
float GetTimerElapsed(FTimerHandle InHandle) const; // 名为InHandle的Timer已经运行了多少秒
float GetTimerRemaining(FTimerHandle InHandle) const; // 名为InHandle的Timer还有多少秒会被执行
bool HasBeenTickedThisFrame() const; // 当前帧是否已经执行了Timer的Tick逻辑
Timer Tick实现机制
Timer定时器是基于小根堆实现,TimerManager在每次Tick时从检查堆顶元素,如果到了执行时间就取出并执行,直到条件不满足停止本次Tick。
void FTimerManager::Tick(float DeltaTime) { // ... ... while (ActiveTimerHeap.Num() > 0) { // 堆定定时任务到达执行时间 if (InternalTime > Top->ExpireTime) { // 执行定时任务 ActiveTimerHeap.HeapPop(CurrentlyExecutingTimer, FTimerHeapOrder(Timers), /*bAllowShrinking=*/ false); Top->TimerDelegate.Execute(); } } // ... ... }
TArray<FTimerHandle> ActiveTimerHeap小根堆结构示例如下:
Timer示例
/******* 创建带参数的Timer *******/ UCLASS() class UDelegatepTimerTest : public UObject { GENERATED_BODY() public: void DelegateProc1(FString MapName) { UE_LOG(LogTemp, Log, TEXT("DelegateProc1 : %s"), *MapName); } void Test() { FTimerDelegate MyDelegate; // DECLARE_DELEGATE(FTimerDelegate); 代理类型FTimerDelegate为单播代理,无参、无返回值 MyDelegate.BindUObject(this, &UDelegatepTimerTest::DelegateProc1, FString(TEXT("FarmLand"))); // 动态传入Payload变量 GetWorld()->GetTimerManager().SetTimer(MyTimeHandle, MyDelegate, 5.0f, false); // 创建一个5.0s的一次性定时器 Payload示例 } void Clear() { GetWorld()->GetTimerManager().ClearTimer(MyTimeHandle); } private: FTimerHandle MyTimeHandle; };
Tickable Tick
Tickable Tick服务对象为C++ 类,继承自FTickableGameObject 基类,需重载Tick和GetStatId虚函数。
Tickable的实现相关的逻辑:UnrealEngine\Engine\Source\Runtime\Engine\Public\Tickable.h、UnrealEngine\Engine\Source\Runtime\Engine\Private\Tickable.cpp
新Tickable 对象初始化后会添加到单例FTickableStatics 集合中,FTickableGameObject 单例在每次 Tick 后遍历 FTickableStatics 集合中的所有 Tickable 对象并执行,因此Tickable 对象会在每一帧执行,不能设置 Tick 的时间间隔。
void FTickableGameObject::TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) { FTickableStatics& Statics = FTickableStatics::Get(); for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects) { TickableObject->Tick(DeltaSeconds); } }
Tickable示例
UCLASS() class UMyBPObject : public UObject, public FTickableGameObject { GENERATED_BODY() public: UMyBPObject(); ~UMyBPObject(); virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(MyBPObject, STATGROUP_Tickables); // 如果不希望被统计,直接返回return TStatId();即可 } virtual bool IsTickable() const override { return !this->IsDefaultSubobject(); } // CDO对象就不要Tick了 virtual void Tick(float DeltaTime) override { if (GFrameCounter % 300 == 0) { FPlatformProcess::SleepNoStats(0.03); } } };
总结
Tick类型 | 实现机制 | 插入复杂度 | 删除复杂度 | Tick复杂度 | 服务对象 | 设置Tick间隔时间 | 特点 |
Actor / ActorComponent Tick | Cooldown机制,以Tick间隔时间维护有序队列 | O(1) | O(n) | O(nlogn) | Actor / ActorComponent | 可以 |
① 通过设置Tick Group, 确定Tick被调用的时机 |
Timer Tick | 以Tick间隔时间维护小根堆 | O(logn) | O(n) | O(logn) | Delegate | 可以 | 粒度细化为Delegate |
Tickable Tick | 无序数组 | O(1) | O(n) | O(n) | C++类 | 不可以 | 每帧调用,不能设置Tick时间间隔 |
参考
Actor Ticking(UE官方文档)
Unreal TickFunc调度
硬核分析:Unreal Tick 实现
UE4中的三种Tick方式
UE4 Tick实现流程
这篇关于UE4 Tick机制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享