在虚幻引擎中,Actor 的生命周期(BeginPlay)并不保证所有依赖的外部对象(如 PlayerState, Controller, InputComponent, AbilitySystemComponent)都已经准备好,尤其是在网络环境中。
项目定义了四个状态:
InitState.Spawned: Actor 已生成,可以通过基础检查。
InitState.DataAvailable: 必要的基础数据已存在(例如 PlayerState 已复制,Controller 已拥有 Pawn)。
InitState.DataInitialized: 核心数据不仅存在,而且已完成初始化配置(例如 ASC 已设置,输入映射已添加)。
InitState.GameplayReady: 我们可以开始游玩了(UI 准备好,不仅数据好了,表现层也好了)。
状态机使用概述
UGameFrameworkComponentManager 使用这个类的状态机来推进流程
使用需要这几个接口
// YourComponent.h
class UYourComponent : public UPawnComponent, public IGameFrameworkInitStateInterface
{
// ...
public:
// 定义特性的唯一名称,例如 "Hero", "PawnExtension"
static const FName NAME_ActorFeatureName;
//~ IGameFrameworkInitStateInterface 接口
virtual FName GetFeatureName() const override { return NAME_ActorFeatureName; }
virtual bool CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const override;
virtual void HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) override;
virtual void OnActorInitStateChanged(const FActorInitStateChangedParams& Params) override;
virtual void CheckDefaultInitialization() override; // 驱动状态机的入口
//~ End IGameFrameworkInitStateInterface
// ...
};
注意注册和反注册
// YourComponent.cpp
const FName UYourComponent::NAME_ActorFeatureName("YourFeatureName");
void UYourComponent::OnRegister()
{
Super::OnRegister();
// 尽早注册,告诉管理器"我来了"
RegisterInitStateFeature();
}
void UYourComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// 销毁时注销,避免野指针回调
UnregisterInitStateFeature();
Super::EndPlay(EndPlayReason);
}
启动状态机
void UYourComponent::BeginPlay()
{
Super::BeginPlay();
// 监听所有特性的状态变化(Name=None表示监听所有)
BindOnActorInitStateChanged(NAME_None, FGameplayTag(), false);
// 尝试立刻进入 Spawned 状态
ensure(TryToChangeInitState(EqZeroGameplayTags::InitState_Spawned));
// 检查是否能继续往下走
CheckDefaultInitialization();
}
可以理解为状态机是不是的会check一下状态能不能过度,会给你 A->B通过CanChangeInitState 问你,
你在这里写逻辑,能不能过度
bool UYourComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const
{
check(Manager);
APawn* Pawn = GetPawn<APawn>();
// 1. None -> Spawned
if (!CurrentState.IsValid() && DesiredState == EqZeroGameplayTags::InitState_Spawned)
{
return Pawn != nullptr;
}
// 2. Spawned -> DataAvailable (等待基础数据,如PlayerState)
else if (CurrentState == EqZeroGameplayTags::InitState_Spawned && DesiredState == EqZeroGameplayTags::InitState_DataAvailable)
{
// 示例:必须要等到 PlayerState 指针有效
return GetPlayerState<AEqZeroPlayerState>() != nullptr;
}
// 3. DataAvailable -> DataInitialized (等待其他组件初始化完)
else if (CurrentState == EqZeroGameplayTags::InitState_DataAvailable && DesiredState == EqZeroGameplayTags::InitState_DataInitialized)
{
// 示例:我依赖 "PawnExtension" 先初始化完
return Manager->HasFeatureReachedInitState(Pawn, UEqZeroPawnExtensionComponent::NAME_ActorFeatureName, EqZeroGameplayTags::InitState_DataInitialized);
}
return false;
}
如果可以,会调用到这一步,状态从A->B了。
你要在这里处理逻辑或者检查下一个状态
void UYourComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
{
// 如果进入了 DataInitialized 阶段,执行实际的 Setup
if (CurrentState == EqZeroGameplayTags::InitState_DataAvailable && DesiredState == EqZeroGameplayTags::InitState_DataInitialized)
{
// 比如:配置输入、绑定委托、初始化UI引用等
// InitializePlayerInput(Pawn->InputComponent);
}
}
这个状态机不是基于tick的,需要逻辑在各种初始化完成的地方调用一下check函数
void UYourComponent::CheckDefaultInitialization()
{
// 这是一个 helper 方法,会自动查找当前状态的下一个状态,并调用 CanChangeInitState 检查
// 如果通过,就调用 HandleChangeInitState 并更新状态。
static const TArray<FGameplayTag> StateChain = {
EqZeroGameplayTags::InitState_Spawned,
EqZeroGameplayTags::InitState_DataAvailable,
EqZeroGameplayTags::InitState_DataInitialized,
EqZeroGameplayTags::InitState_GameplayReady
};
CheckDefaultInitializationForImplementer(); // 或者手动循环 TryToChangeInitState
}
初始状态变化的回调。
void UYourComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params)
{
// 如果是我们关心的组件发生了状态变化
if (Params.FeatureName == UEqZeroPawnExtensionComponent::NAME_ActorFeatureName)
{
if (Params.FeatureState == EqZeroGameplayTags::InitState_DataInitialized)
{
// 既然依赖已经好了,我们再试一次初始化
CheckDefaultInitialization();
}
}
}
其实核心原理就是一个基于TAG的状态机
FGameplayTag IGameFrameworkInitStateInterface::ContinueInitStateChain(const TArray<FGameplayTag>& InitStateChain)
{
// 当前状态
int32 ChainIndex = 0;
FGameplayTag CurrentState = Manager->GetInitStateForFeature(MyActor, MyFeatureName);
while (ChainIndex < InitStateChain.Num() - 1)
{
if (CurrentState == InitStateChain[ChainIndex])
{
FGameplayTag DesiredState = InitStateChain[ChainIndex + 1];
// 能不能去下一个状态
if (CanChangeInitState(Manager, CurrentState, DesiredState))
{
// 成功了
HandleChangeInitState(Manager, CurrentState, DesiredState);
// 因为切状态了,所以要更新当前状态,然后继续循环
ensure(Manager->ChangeFeatureInitState(MyActor, MyFeatureName, ThisObject, DesiredState));
CurrentState = Manager->GetInitStateForFeature(MyActor, MyFeatureName);
}
}
ChainIndex++;
}
return CurrentState;
}
项目实际使用
OnRegister->RegisterInitStateFeature
void UEqZeroHeroComponent::OnRegister()
{
Super::OnRegister();
RegisterInitStateFeature();
}
void UEqZeroPawnExtensionComponent::OnRegister()
{
Super::OnRegister();
RegisterInitStateFeature();
}
两个函数除此之外都是些合法性检查
void IGameFrameworkInitStateInterface::RegisterInitStateFeature()
{
UObject* ThisObject = Cast<UObject>(this);
AActor* MyActor = GetOwningActor();
UGameFrameworkComponentManager* Manager = UGameFrameworkComponentManager::GetForActor(MyActor);
const FName MyFeatureName = GetFeatureName();
if (MyActor && Manager)
{
// Manager will be null if this isn't in a game world
// Actor, FName, ActorComponent
Manager->RegisterFeatureImplementer(MyActor, MyFeatureName, ThisObject);
}
}
bool UGameFrameworkComponentManager::RegisterFeatureImplementer(AActor* Actor, FName FeatureName, UObject* Implementer)
{
// 基于Actor为key,维护了一个 FeatureData的 Map
FActorFeatureData& ActorStruct = FindOrAddActorData(Actor);
FActorFeatureState* FoundState = nullptr;
for (FActorFeatureState& State : ActorStruct.RegisteredStates)
{
if (State.FeatureName == FeatureName)
{
// TODO what if it's already in the desired state?
// 如果逻辑上保证了只注册一次,不应该找到。
FoundState = &State;
}
}
// 把 FeatureName,ActorComponent 加到这个结构里面
if (!FoundState)
{
FoundState = &ActorStruct.RegisteredStates.Emplace_GetRef(FeatureName);
}
FoundState->Implementer = Implementer;
return true;
}
简单来说
UGameFrameworkComponentManager
维护了一个 Map
TMap<FObjectKey, FActorFeatureData> ActorFeatureMap;
key是Actor,Value是一个结构,每个结构代表一个 Feature。这里的Feature指的就是例如这个ActorComponent的
应该只有一个才对,但是这里为什么是TArray呢
反正第一步是注册,Actor->ActorComponent 到框架里面
不要忘了EndPlay的时候,取消注册
void UEqZeroPawnExtensionComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnregisterInitStateFeature();
Super::EndPlay(EndPlayReason);
}
void UEqZeroHeroComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnregisterInitStateFeature();
Super::EndPlay(EndPlayReason);
}
BeginPlay->启动
void UEqZeroHeroComponent::BeginPlay()
{
Super::BeginPlay();
BindOnActorInitStateChanged(UEqZeroPawnExtensionComponent::NAME_ActorFeatureName, FGameplayTag(), false);
ensure(TryToChangeInitState(EqZeroGameplayTags::InitState_Spawned));
CheckDefaultInitialization();
}
void UEqZeroPawnExtensionComponent::BeginPlay()
{
Super::BeginPlay();
BindOnActorInitStateChanged(NAME_None, FGameplayTag(), false);
ensure(TryToChangeInitState(EqZeroGameplayTags::InitState_Spawned));
CheckDefaultInitialization();
}
BindOnActorInitStateChanged
其中
BindOnActorInitStateChanged是为了注册Actor状态变化的时候会回调自己的OnActorInitStateChanged函数
void IGameFrameworkInitStateInterface::BindOnActorInitStateChanged(FName FeatureName, FGameplayTag RequiredState, bool bCallIfReached)
{
UObject* ThisObject = Cast<UObject>(this);
AActor* MyActor = GetOwningActor();
UGameFrameworkComponentManager* Manager = UGameFrameworkComponentManager::GetForActor(MyActor);
if (ensure(MyActor && Manager))
{
// Bind as a weak lambda because this is not a UObject but is guaranteed to be valid as long as ThisObject is
FActorInitStateChangedDelegate Delegate = FActorInitStateChangedDelegate::CreateWeakLambda(ThisObject,
[this](const FActorInitStateChangedParams& Params)
{
this->OnActorInitStateChanged(Params);
});
ActorInitStateChangedHandle = Manager->RegisterAndCallForActorInitState(MyActor, FeatureName, RequiredState, MoveTemp(Delegate), bCallIfReached);
}
}
TryToChangeInitState
TryToChangeInitState
这里会执行第一次流程,简单来说激素
if (CanChangeInitState(...)) // 子类复写检查条件
{
HandleChangeInitState(...); // 子类复写
Manager->ChangeFeatureInitState(...); // 这里会修改当前状态的TAG
}
具体来说
bool IGameFrameworkInitStateInterface::TryToChangeInitState(FGameplayTag DesiredState)
{
UObject* ThisObject = Cast<UObject>(this);
AActor* MyActor = GetOwningActor();
UGameFrameworkComponentManager* Manager = UGameFrameworkComponentManager::GetForActor(MyActor);
const FName MyFeatureName = GetFeatureName();
if (!Manager || !ThisObject || !MyActor)
{
return false;
}
FGameplayTag CurrentState = Manager->GetInitStateForFeature(MyActor, MyFeatureName);
if (CurrentState == DesiredState)
{
return false;
}
if (!CanChangeInitState(Manager, CurrentState, DesiredState))
{
return false;
}
HandleChangeInitState(Manager, CurrentState, DesiredState);
return ensure(Manager->ChangeFeatureInitState(MyActor, MyFeatureName, ThisObject, DesiredState));
}
CheckDefaultInitialization
这个函数会在各种地方经常调,但是内容是自己 重写的
void UEqZeroHeroComponent::CheckDefaultInitialization()
{
static const TArray<FGameplayTag> StateChain = {
EqZeroGameplayTags::InitState_Spawned,
EqZeroGameplayTags::InitState_DataAvailable,
EqZeroGameplayTags::InitState_DataInitialized,
EqZeroGameplayTags::InitState_GameplayReady };
ContinueInitStateChain(StateChain);
}
void UEqZeroPawnExtensionComponent::CheckDefaultInitialization()
{
CheckDefaultInitializationForImplementers();
static const TArray<FGameplayTag> StateChain = {
EqZeroGameplayTags::InitState_Spawned,
EqZeroGameplayTags::InitState_DataAvailable,
EqZeroGameplayTags::InitState_DataInitialized,
EqZeroGameplayTags::InitState_GameplayReady };
ContinueInitStateChain(StateChain);
}
CheckDefaultInitializationForImplementers
遍历所有Feature,触发CheckDefaultInitialization
void IGameFrameworkInitStateInterface::CheckDefaultInitializationForImplementers()
{
UObject* ThisObject = Cast<UObject>(this);
AActor* MyActor = GetOwningActor();
UGameFrameworkComponentManager* Manager = UGameFrameworkComponentManager::GetForActor(MyActor);
const FName MyFeatureName = GetFeatureName();
if (Manager)
{
TArray<UObject*> Implementers;
Manager->GetAllFeatureImplementers(Implementers, MyActor, FGameplayTag(), MyFeatureName);
for (UObject* Implementer : Implementers)
{
if (IGameFrameworkInitStateInterface* ImplementerInterface = Cast<IGameFrameworkInitStateInterface>(Implementer))
{
ImplementerInterface->CheckDefaultInitialization();
}
}
}
}
FGameplayTag IGameFrameworkInitStateInterface::ContinueInitStateChain(const TArray<FGameplayTag>& InitStateChain)
{
int32 ChainIndex = 0;
FGameplayTag CurrentState = Manager->GetInitStateForFeature(MyActor, MyFeatureName);
/*
输入参数 InitStateChain 就是定义的那四个状态的Array
*/
while (ChainIndex < InitStateChain.Num() - 1)
{
if (CurrentState == InitStateChain[ChainIndex])
{
// 能否转状态
FGameplayTag DesiredState = InitStateChain[ChainIndex + 1];
if (CanChangeInitState(Manager, CurrentState, DesiredState))
{
// 能转
HandleChangeInitState(Manager, CurrentState, DesiredState);
ensure(Manager->ChangeFeatureInitState(MyActor, MyFeatureName, ThisObject, DesiredState));
CurrentState = Manager->GetInitStateForFeature(MyActor, MyFeatureName);
}
}
ChainIndex++;
}
return CurrentState;
}
所以外部想推状态就 CheckDefaultInitialization 就可以了。
状态机的设计
static const TArray<FGameplayTag> StateChain = {
EqZeroGameplayTags::InitState_Spawned,
EqZeroGameplayTags::InitState_DataAvailable,
EqZeroGameplayTags::InitState_DataInitialized,
EqZeroGameplayTags::InitState_GameplayReady };
对象创建,数据合法,数据初始化完成,GameplayReady
| UEqZeroHeroComponent | PawnExtensionComponent | ||
|---|---|---|---|
| InitState_Spawned | 挂在Pawn就过 | 挂在Pawn就过 | |
| InitState_DataAvailable | 1. PlayerState加载完成 2. Controller和PlayerState加载配对 3. 主玩家的InputComponent,LocalPlayer加载完成 |
1. 体验加载完成PawnData有了 2. Authority和LocalControlled端检查"possessed by a controller" |
|
| InitState_DataInitialized | 等PawnExtension的DataInitialized状态(等 -->) | 所有组件都达到了DataAvailable(楼上) | |
| InitState_GameplayReady | 直接过 | 直接过 |
所有check的地方
- 启动引擎 (Start the Engine)
UEqZeroPawnExtensionComponent::BeginPlay
UEqZeroHeroComponent::BeginPlay
- 数据与依赖变化 (Data & Dependency Changes)
UEqZeroPawnExtensionComponent::SetPawnData:体验加载完成PlayerStates设置过来
UEqZeroPawnExtensionComponent::OnRep_PawnData:属性同步到客户端
UEqZeroPawnExtensionComponent::OnActorInitStateChanged:监听其他组件状态
UEqZeroHeroComponent::OnActorInitStateChanged:监听其他组件状态
- 控制权变化 (Controller Ownership Changes)
UEqZeroPawnExtensionComponent::HandleControllerChanged
- 当 Pawn 被 Possess 或 Unpossess 时调用
UEqZeroPawnExtensionComponent::HandlePlayerStateReplicated
- PlayerState 复制下来了。这意味着 Controller 可能已经和 PlayerState 关联好了。
AEqZeroPlayerState::ClientInitialize
- 客户端拥有权确认。
- 这是 APlayerState 的一个虚函数。当 Controller 在客户端被分配给 PlayerState 时调用(Controller 和 PS 握手成功)。
- 辅助与兼容 (Helpers & Misc)
UEqZeroPawnExtensionComponent::SetupPlayerInputComponent
- 这是 APawn 的标准接口。虽然我们的输入逻辑主要在 Hero 组件里,但这里调用一次是为了兜底,确保在输入组件就绪的时机再检查一次状态机。