unreal的UGameInstance的Tick执行问题

大纲

  • 背景
  • 解决方案
    • 方案1
    • 方案2

背景

在unreal中,UGameInstance通常会有两个,一个是在Editor World中使用的,一个是PIE中使用的!

这就导致同一个GameInstance类在运行的时候,有两个实例在跑,如果配合Tick的时候,那么EditorWorld容易因为一些运行时才初始化的对象而造成闪退!

基于上述原因,需要对Tick的执行进行一定的处理,使得,Tick能够分情况执行!

解决方案

先看看Tick的执行逻辑!(Tickable.cpp)

for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects)
{
	if (FTickableGameObject* TickableObject = static_cast<FTickableGameObject*>(TickableEntry.TickableObject))
	{
		// If it is tickable and in this world
		if (((TickableEntry.TickType == ETickableTickType::Always) || TickableObject->IsTickable()) && (TickableObject->GetTickableGameObjectWorld() == World))
		{
			const bool bIsGameWorld = InTickType == LEVELTICK_All || (World && World->IsGameWorld());
			// If we are in editor and it is editor tickable, always tick
			// If this is a game world then tick if we are not doing a time only (paused) update and we are not paused or the object is tickable when paused
			if ((GIsEditor && TickableObject->IsTickableInEditor()) ||
				(bIsGameWorld && ((!bIsPaused && TickType != LEVELTICK_TimeOnly) || (bIsPaused && TickableObject->IsTickableWhenPaused()))))
			{
				FScopeCycleCounter Context(TickableObject->GetStatId());
				TickableObject->Tick(DeltaSeconds);
				// In case it was removed during tick
				if (TickableEntry.TickableObject == nullptr)
				{
					bNeedsCleanup = true;
				}
			}
		}
	}
	else
	{
		bNeedsCleanup = true;
	}
}

在上面的源码中,不难看到有几种选择!

先把基本的实现放出来!

UCLASS(BlueprintType, Blueprintable)
class UNREALPROJECT_API UMyGameInstance : public UGameInstance, public FTickableGameObject
{
	GENERATED_BODY()
public:
    virtual TStatId GetStatId() const override{return Super::GetStatID();}
    virtual void Tick(float DeltaTime) override;
    // 后面修改的都是这里分线以下的!
	virtual bool IsTickable() const override
	{
		return true;
	}
}

方案1

virtual bool IsTickable() const override
{
	if (!GetWorld())
		return false;
	return GetWorld()->HasBegunPlay();
}

这里值得注意的是,不能直接使用GWorld;在判定World是否已经启动之前,要对GetWorld进行判空处理,在非运行时,GetWorld是空的!

方案2

virtual UWorld* GetTickableGameObjectWorld() const override
{
	return GetOuter()->GetWorld();
}

重载GetTickableGameObjectWorld,对Actor所在World和现在使用的World是否一直判定,PIE在启动的时候,会通过Context构建一个运行时使用的World;

2021-12-09斧正

经过一轮时间的测试,发现上述的并不是真的不Tick了,而是因为在我实际使用的工程中不在报错而已!那么如何才能做到真的停掉Tick呢?

因为

if (((TickableEntry.TickType == ETickableTickType::Always) || TickableObject->IsTickable()) && (TickableObject->GetTickableGameObjectWorld() == World))

TickableObject->IsTickable()恒定为false的情况下,(TickableObject->GetTickableGameObjectWorld() == World)是默认的(大多情况下是true),那么整个Tick就是由TickableEntry.TickType决定这个tick是否执行后续的逻辑了!

virtual void Init() override;
virtual void Tick(float DeltaTime) override;
virtual bool IsTickable() const override
{
	return  false;
}
virtual ETickableTickType GetTickableTickType() const override { return TickCondition; }
virtual TStatId GetStatId() const override{return Super::GetStatID();}
ETickableTickType TickCondition;

因为Init函数只有在Play的时候才执行,那么只要在Init里将TickCondition设置为TickCondition = ETickableTickType::Always;就可以了!