概述
前面提到了近战攻击,通过这个 GE 添加的伤害
void UEqZeroGameplayAbility_Melee::ApplyMeleeGameplayEffectAndCue(UAbilitySystemComponent* TargetASC, const FHitResult& HitResult)
{
auto SourceASC = GetEqZeroAbilitySystemComponentFromActorInfo();
if (!SourceASC || !TargetASC)
{
return;
}
FGameplayEffectContextHandle ContextHandle = SourceASC->MakeEffectContext();
ContextHandle.AddHitResult(HitResult);
if (DamageEffectClass)
{
FGameplayEffectSpecHandle SpecHandle = SourceASC->MakeOutgoingSpec(DamageEffectClass, 1.0f, ContextHandle);
if (SpecHandle.IsValid())
{
SourceASC->ApplyGameplayEffectSpecToTarget(*SpecHandle.Data.Get(), TargetASC);
}
}
}
DamageEffectClass是这个,以及父类
/ShooterCore/Weapons/GE_Damage_Melee.GE_Damage_Melee
/Game/GameplayEffects/Damage/GE_Damage_Basic_Instant.GE_Damage_Basic_Instant
/Game/GameplayEffects/Damage/GameplayEffectParent_Damage_Basic.GameplayEffectParent_Damage_Basic
基类:GameplayEffectParent_Damage_Basic
他有4个子类
其中 /TopDownArena/Game/Bombs/GE_Damaged_By_Bomb.GE_Damaged_By_Bomb 看起来是给手雷单独写的
另外三个再同一目录
GE_Damage_Basic_Instant 瞬发效果
GE_Damage_Basic_Periodic 周期性效果
GE_Damage_Basic_SetByCaller 使用set by caller 的效果
先从
GameplayEffectParent_Damage_Basic开始看
GameplayEffectParent_Damage_Basic

GE加了一个TAG,然后是指定了Execution
然后加了一个伤害的Cue效果我们先不看
Execution的Modifier加了以后就长这样修改了 Modifier Op 为 Add,Magnitude 从0改成1。具体看计算流程。
GE_Damage_Basic_Instant
他的瞬发子类只是多了一个标签

GE_Damage_Melee
/EqZeroCore/Weapons/GE_Damage_Melee.GE_Damage_Melee

首先也是多了标签
然后这里改成了30,父类是1,然后是Cue的逻辑

然后把GE配置上去
emmmm,然后是Attr的修改。
GE属性修改流程
if (DamageEffectClass)
{
FGameplayEffectSpecHandle SpecHandle = SourceASC->MakeOutgoingSpec(DamageEffectClass, 1.0f, ContextHandle);
if (SpecHandle.IsValid())
{
SourceASC->ApplyGameplayEffectSpecToTarget(*SpecHandle.Data.Get(), TargetASC);
}
}
从这里开始
参数GE的描述,和对方的ASC,预测key我们先忽略
套一层,让逻辑调用到对方的Apply GE To Self
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToTarget(const FGameplayEffectSpec &Spec, UAbilitySystemComponent *Target, FPredictionKey PredictionKey)
{
SCOPE_CYCLE_COUNTER(STAT_AbilitySystemComp_ApplyGameplayEffectSpecToTarget);
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
if (!AbilitySystemGlobals.ShouldPredictTargetGameplayEffects())
{
// If we don't want to predict target effects, clear prediction key
PredictionKey = FPredictionKey();
}
FActiveGameplayEffectHandle ReturnHandle;
if (Target)
{
ReturnHandle = Target->ApplyGameplayEffectSpecToSelf(Spec, PredictionKey);
}
return ReturnHandle;
}
ApplyGameplayEffectSpecToSelf
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)
{
// some check...
// 不允许预测周期性 effects
if (PredictionKey.IsValidKey() && Spec.GetPeriod() > 0.f)
{
if (IsOwnerActorAuthoritative())
{
// 服务器使用无效的预测密钥继续运行
PredictionKey = FPredictionKey();
}
else
{
// 客户端执行返回
return FActiveGameplayEffectHandle();
}
}
// 允许用户进行一系列检查,都为真的时候,才允许执行GE
for (const FGameplayEffectApplicationQuery& ApplicationQuery : GameplayEffectApplicationQueries)
{
const bool bAllowed = ApplicationQuery.Execute(ActiveGameplayEffects, Spec);
if (!bAllowed)
{
return FActiveGameplayEffectHandle();
}
}
// GE 自己的检查逻辑能否应用?
if (!Spec.Def->CanApply(ActiveGameplayEffects, Spec))
{
return FActiveGameplayEffectHandle();
}
// 客户应将预测的即时效果视为具有无限持续时间。这些效果稍后会被清除
bool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;
// 确保创建和拷贝 spec 在正确的地方
// 我们在这里用 INDEX_NONE 初始化 FActiveGameplayEffectHandle,以处理 instant立即 GE 的情况。
// 像这样初始化会将 FActiveGameplayEffectHandle 上的 bPassedFiltersAndWasExecuted 设置为 true,这样我们就能知道我们应用了一个 GE。
FActiveGameplayEffectHandle MyHandle(INDEX_NONE);
// 预测性的 立即GE被改成了 无线的GE,所以要提取缓存一下
bool bInvokeGameplayCueApplied = Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant;
bool bFoundExistingStackableGE = false;
FActiveGameplayEffect* AppliedEffect = nullptr;
FGameplayEffectSpec* OurCopyOfSpec = nullptr;
TUniquePtr<FGameplayEffectSpec> StackSpec;
{
// 如果是持续性/无限时长的 GE(或者被强制视为无限)
if (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)
{
// 加到 ActiveGameplayEffects 里面
AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);
if (!AppliedEffect)
{
return FActiveGameplayEffectHandle();
}
MyHandle = AppliedEffect->Handle;
OurCopyOfSpec = &(AppliedEffect->Spec);
}
// 如果是瞬间结算的 GE (Instant)
if (!OurCopyOfSpec)
{
// 创建副本,不需要存入容器,都是计算过程需要一个对象
StackSpec = MakeUnique<FGameplayEffectSpec>(Spec);
OurCopyOfSpec = StackSpec.Get();
UAbilitySystemGlobals::Get().GlobalPreGameplayEffectSpecApply(*OurCopyOfSpec, this);
OurCopyOfSpec->CaptureAttributeDataFromTarget(this);
}
// 通常发生在客户端预测瞬间技能时,为了防止预测回滚导致表现问题,暂时将其视为无限时长
if (bTreatAsInfiniteDuration)
{
// This should just be a straight set of the duration float now
OurCopyOfSpec->SetDuration(UGameplayEffect::INFINITE_DURATION, true);
}
}
// 更新全局上下文
UAbilitySystemGlobals::Get().SetCurrentAppliedGE(OurCopyOfSpec);
// 处理 Gameplay Cue 的堆叠 (Stacking)
if (!bSuppressGameplayCues && !Spec.Def->bSuppressStackingCues && bFoundExistingStackableGE && AppliedEffect && !AppliedEffect->bIsInhibited)
{
ensureMsgf(OurCopyOfSpec, TEXT("OurCopyOfSpec will always be valid if bFoundExistingStackableGE"));
if (OurCopyOfSpec && OurCopyOfSpec->GetStackCount() > Spec.GetStackCount())
{
// 例如重度效果叠加变成了2,调用一下接口保证效果没有消失
UAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueAddedAndWhileActive_FromSpec(this, *OurCopyOfSpec, PredictionKey);
}
}
if (bTreatAsInfiniteDuration)
{
// 这是一个即时应用程序,但我们将其视为无限持续时间来进行预测
if (!bSuppressGameplayCues)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueExecuted_FromSpec(this, *OurCopyOfSpec, PredictionKey);
}
}
else if (Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant)
{
// 这是一种非预测性的即时效果(它永远不会被添加到 ActiveGameplayEffects 中)
// 例如lyra的近战就这里走进去
ExecuteGameplayEffect(*OurCopyOfSpec, PredictionKey);
}
// 。。。 一些事件通知其他
}
对于这个近战通知
void UAbilitySystemComponent::ExecuteGameplayEffect(FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)
{
ActiveGameplayEffects.ExecuteActiveEffectsFrom(Spec, PredictionKey);
}
这里读取GE的Executions,执行,拿到输出的modify
for (const FGameplayEffectExecutionDefinition& CurExecDef : SpecToUse.Def->Executions)
{
if (CurExecDef.CalculationClass)
{
const UGameplayEffectExecutionCalculation* ExecCDO = CurExecDef.CalculationClass->GetDefaultObject<UGameplayEffectExecutionCalculation>();
check(ExecCDO);
// Run the custom execution
FGameplayEffectCustomExecutionParameters ExecutionParams(SpecToUse, CurExecDef.CalculationModifiers, Owner, CurExecDef.PassedInTags, PredictionKey);
FGameplayEffectCustomExecutionOutput ExecutionOutput;
ExecCDO->Execute(ExecutionParams, ExecutionOutput);
// 拿到输出的out modifier
bRunConditionalEffects = ExecutionOutput.ShouldTriggerConditionalGameplayEffects();
TArray<FGameplayModifierEvaluatedData>& OutModifiers = ExecutionOutput.GetOutputModifiersRef();
for (FGameplayModifierEvaluatedData& CurExecMod : OutModifiers)
{
// 具体的修改属性
ModifierSuccessfullyExecuted |= InternalExecuteMod(SpecToUse, CurExecMod);
}
}
回来完成我们的近战
技能打出去了,怎么打死呢
死亡
有了伤害,那么生命值呢?在这
AEqZeroPlayerState::AEqZeroPlayerState(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, MyPlayerConnectionType(EEqZeroPlayerConnectionType::Player)
{
AbilitySystemComponent = ObjectInitializer.CreateDefaultSubobject<UEqZeroAbilitySystemComponent>(this, TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
/*
* 这些属性集将由 AbilitySystemComponent::InitializeComponent 检测。保留一个引用,以确保这些集合在被检测前不会被垃圾回收。
* --> ASC 在初始化时会自动发现并注册它们,无需显式调用 RegisterAttributeSet
*
* 另一个地方是 GiveToAbilitySystem 里面调用 AddAttributeSetSubobject
*
* 我以前的项目是这么写的
* TArray<UAttributeSet*> AttributeSets;
* AttributeSets.Add(AttributeSet);
* AbilitySystemComponent->SetSpawnedAttributes(AttributeSets);
*/
HealthSet = CreateDefaultSubobject<UEqZeroHealthSet>(TEXT("HealthSet"));
CombatSet = CreateDefaultSubobject<UEqZeroCombatSet>(TEXT("CombatSet"));
SetNetUpdateFrequency(100.0f); // 技能需要高频一点
}
有个生命值组件,分管生命值相关的业务逻辑
UCLASS(MinimalAPI, Blueprintable, Meta=(BlueprintSpawnableComponent))
class UEqZeroHealthComponent : public UGameFrameworkComponent
{
GENERATED_BODY()
protected:
UPROPERTY()
TObjectPtr<UEqZeroAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY()
TObjectPtr<const UEqZeroHealthSet> HealthSet;
UPROPERTY(ReplicatedUsing = OnRep_DeathState)
EEqZeroDeathState DeathState;
};
这个指针,显然我们需要注意技能组件的注册和注销
UFUNCTION(BlueprintCallable, Category = "EqZero|Health")
UE_API void InitializeWithAbilitySystem(UEqZeroAbilitySystemComponent* InASC);
UFUNCTION(BlueprintCallable, Category = "EqZero|Health")
UE_API void UninitializeFromAbilitySystem();
这个是谁调用过来的呢?
AEqZeroCharacter::AEqZeroCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UEqZeroCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{
// ...
PawnExtComponent = CreateDefaultSubobject<UEqZeroPawnExtensionComponent>(TEXT("PawnExtensionComponent"));
PawnExtComponent->OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemInitialized));
PawnExtComponent->OnAbilitySystemUninitialized_Register(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &ThisClass::OnAbilitySystemUninitialized));
}
里面本质上是代理的绑定
void UEqZeroPawnExtensionComponent::OnAbilitySystemInitialized_RegisterAndCall(FSimpleMulticastDelegate::FDelegate Delegate)
{
if (!OnAbilitySystemInitialized.IsBoundToObject(Delegate.GetUObject()))
{
OnAbilitySystemInitialized.Add(Delegate);
}
if (AbilitySystemComponent)
{
Delegate.Execute();
}
}
void UEqZeroHealthSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
// ...
if ((GetHealth() <= 0.0f) && !bOutOfHealth)
{
OnOutOfHealth.Broadcast(Instigator, Causer, &Data.EffectSpec, Data.EvaluatedData.Magnitude, HealthBeforeAttributeChange, GetHealth());
}
bOutOfHealth = (GetHealth() <= 0.0f);
}
HealthSet->OnOutOfHealth.AddUObject(this, &ThisClass::HandleOutOfHealth);
死亡
void UEqZeroHealthComponent::HandleOutOfHealth(AActor* DamageInstigator, AActor* DamageCauser, const FGameplayEffectSpec* DamageEffectSpec, float DamageMagnitude, float OldValue, float NewValue)
{
#if WITH_SERVER_CODE
if (AbilitySystemComponent && DamageEffectSpec)
{
// 发送 "GameplayEvent.Death" 这个事件会触发死亡技能
{
FGameplayEventData Payload;
Payload.EventTag = EqZeroGameplayTags::GameplayEvent_Death;
Payload.Instigator = DamageInstigator;
Payload.Target = AbilitySystemComponent->GetAvatarActor();
Payload.OptionalObject = DamageEffectSpec->Def;
Payload.ContextHandle = DamageEffectSpec->GetEffectContext();
Payload.InstigatorTags = *DamageEffectSpec->CapturedSourceTags.GetAggregatedTags();
Payload.TargetTags = *DamageEffectSpec->CapturedTargetTags.GetAggregatedTags();
Payload.EventMagnitude = DamageMagnitude;
FScopedPredictionWindow NewScopedWindow(AbilitySystemComponent, true);
AbilitySystemComponent->HandleGameplayEvent(Payload.EventTag, &Payload);
}
{
/*
* 官方写的待办
* 填写上下文标签,以及任何非能力系统的来源 / 发起者标签
* 判断这是敌方击杀、自我失误击杀、队友击杀等等……
*/
FEqZeroVerbMessage Message;
Message.Verb = TAG_EqZero_Elimination_Message;
Message.Instigator = DamageInstigator;
Message.InstigatorTags = *DamageEffectSpec->CapturedSourceTags.GetAggregatedTags();
Message.Target = UEqZeroVerbMessageHelpers::GetPlayerStateFromObject(AbilitySystemComponent->GetAvatarActor());
Message.TargetTags = *DamageEffectSpec->CapturedTargetTags.GetAggregatedTags();
UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(GetWorld());
MessageSystem.BroadcastMessage(Message.Verb, Message);
}
}
#endif // #if WITH_SERVER_CODE
}
配置一个死亡技能
/EqZeroCore/Game/AbilitySet_ShooterHero.AbilitySet_ShooterHero
/Game/Characters/Heroes/Abilities/GA_Hero_Death.GA_Hero_Death
void UEqZeroGameplayAbility_Death::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
check(ActorInfo);
UEqZeroAbilitySystemComponent* EqZeroASC = CastChecked<UEqZeroAbilitySystemComponent>(ActorInfo->AbilitySystemComponent.Get());
// 取消所有能力并阻止其他能力的启动。注意这里ignore是重生技能,不能把重生技能给干掉了
FGameplayTagContainer AbilityTypesToIgnore;
AbilityTypesToIgnore.AddTag(EqZeroGameplayTags::Ability_Behavior_SurvivesDeath);
EqZeroASC->CancelAbilities(nullptr, &AbilityTypesToIgnore, this);
// 死亡技能不能被取消
SetCanBeCanceled(false);
// 这个会在ASC写一个阻塞,然后其他技能都不能触发
if (!ChangeActivationGroup(EEqZeroAbilityActivationGroup::Exclusive_Blocking))
{
UE_LOG(LogEqZeroAbilitySystem, Error, TEXT("UEqZeroGameplayAbility_Death::ActivateAbility: Ability [%s] failed to change activation group to blocking."), *GetName());
}
if (bAutoStartDeath)
{
StartDeath();
}
if (DeathCameraModeClass)
{
SetCameraMode(DeathCameraModeClass);
}
// TODO Cue
// 角色在濒死(布娃娃)状态停留多长时间,才调用 HealthComponent 上的 DeathFinished 并清理该角色。
UAbilityTask_WaitDelay* Task = UAbilityTask_WaitDelay::WaitDelay(this, DeathDuration);
Task->OnFinish.AddDynamic(this, &ThisClass::OnDeathWaitFinished);
Task->ReadyForActivation();
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}
这个Cue还是很复杂的,先等等
到这里,技能组件双端执行
void UEqZeroHealthComponent::StartDeath()
{
if (DeathState != EEqZeroDeathState::NotDead)
{
return;
}
DeathState = EEqZeroDeathState::DeathStarted;
if (AbilitySystemComponent)
{
AbilitySystemComponent->SetLooseGameplayTagCount(EqZeroGameplayTags::Status_Death_Dying, 1);
}
AActor* Owner = GetOwner();
check(Owner);
OnDeathStarted.Broadcast(Owner);
Owner->ForceNetUpdate();
}
- OnDeathStarted
这个OnDeathStarted 事件会禁用movement和collision
void AEqZeroCharacter::OnDeathStarted(AActor*)
{
DisableMovementAndCollision();
}
停移动输入,去掉碰撞,技能的阻塞GAS会处理
void AEqZeroCharacter::DisableMovementAndCollision()
{
if (GetController())
{
GetController()->SetIgnoreMoveInput(true);
}
UCapsuleComponent* CapsuleComp = GetCapsuleComponent();
check(CapsuleComp);
CapsuleComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
CapsuleComp->SetCollisionResponseToAllChannels(ECR_Ignore);
if (UEqZeroCharacterMovementComponent* MoveComp = Cast<UEqZeroCharacterMovementComponent>(GetCharacterMovement()))
{
MoveComp->StopMovementImmediately();
MoveComp->DisableMovement();
}
}
- 另外OnRep_DeathState
另外这里客户端会由于属性同步再跑一遍
void UEqZeroHealthComponent::OnRep_DeathState(EEqZeroDeathState OldDeathState)
{
const EEqZeroDeathState NewDeathState = DeathState;
DeathState = OldDeathState;
if (OldDeathState > NewDeathState)
{
UE_LOG(LogEqZero, Warning, TEXT("EqZeroHealthComponent: Predicted past server death state [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
return;
}
if (OldDeathState == EEqZeroDeathState::NotDead)
{
if (NewDeathState == EEqZeroDeathState::DeathStarted)
{
StartDeath();
}
else if (NewDeathState == EEqZeroDeathState::DeathFinished)
{
StartDeath();
FinishDeath();
}
else
{
UE_LOG(LogEqZero, Error, TEXT("EqZeroHealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
}
}
else if (OldDeathState == EEqZeroDeathState::DeathStarted)
{
if (NewDeathState == EEqZeroDeathState::DeathFinished)
{
FinishDeath();
}
else
{
UE_LOG(LogEqZero, Error, TEXT("EqZeroHealthComponent: Invalid death transition [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
}
}
ensureMsgf((DeathState == NewDeathState), TEXT("EqZeroHealthComponent: Death transition failed [%d] -> [%d] for owner [%s]."), (uint8)OldDeathState, (uint8)NewDeathState, *GetNameSafe(GetOwner()));
}
NotDead => DeathStarted => DeathFinished
后两个状态,就是技能开始的时候,timer一会后EndAbility里面触发
OnDeathStarted 在蓝图里面还有很多回调
例如 /Game/Characters/Character_Default.Character_Default

播放蒙太奇,布娃娃,隐藏装备,
最后的Death函数好像没找到实现?只是预留的?
==
重生
/ShooterCore/Game/Respawn/GA_AutoRespawn.GA_AutoRespawn
体验的 Add Abilities 里面对PlayerState的配置的,game action
/ShooterCore/Elimination/AbilitySet_Elimination.AbilitySet_Elimination
里面配置了spawn技能

这个技能是On Spawn,所以刚拿到就激活了
Server Only 因为重生的执行必须服务器
总结一下:
监听 GameplayEvent_Reset 事件 => OnPlayerReset 调用
监听 Avatar的 OnEndPlay => 对象销毁而非跨场景 => OnPlayerReset
监听 Heal Comp的 OnDeathStarted =>
- 广播 Ability_Respawn_Duration_Message 事件,内容有重生的事件,可能是给UI用的
- 重生事件到了以后 => OnPlayerReset
核心的执行重生的逻辑
void UEqZeroGameplayAbility_AutoRespawn::OnPlayerReset()
{
if (!HasAuthority(&CurrentActivationInfo))
{
return;
}
AController* ControllerToReset = GetControllerFromActorInfo();
if (!ControllerToReset)
{
return;
}
bShouldFinishRestart = false;
if (auto ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(ControllerToReset->PlayerState))
{
TArray<FGameplayAbilitySpecHandle> OutAbilityHandles;
ASC->FindAllAbilitiesWithTags(
OutAbilityHandles,
FGameplayTagContainer(EqZeroGameplayTags::Ability_Type_StatusChange_Death), true);
// Ability.Type.StatusChange.Death
for (const FGameplayAbilitySpecHandle& Handle : OutAbilityHandles)
{
ASC->CancelAbilityHandle(Handle);
}
}
FTimerDelegate TimerDelegate = FTimerDelegate::CreateWeakLambda(this, [this, ControllerToReset]()
{
if (auto GameMode = GetWorld()->GetAuthGameMode<AEqZeroGameMode>())
{
GameMode->RequestPlayerRestartNextFrame(ControllerToReset, true);
}
FEqZeroVerbMessage Message;
Message.Instigator = GetOwningPlayerState();
if (auto GameState = GetWorld()->GetGameState<AEqZeroGameState>())
{
GameState->MulticastMessageToClients(Message);
}
if (GetWorld()->GetNetMode() != NM_DedicatedServer)
{
// Ability.Respawn.Completed.Message
UGameplayMessageSubsystem::Get(this).BroadcastMessage(
EqZeroGameplayTags::Ability_Respawn_Completed_Message, Message);
}
});
GetWorld()->GetTimerManager().SetTimer(TimerHandle_ResetRequest, TimerDelegate, 0.1f, false);
}
其他都是一些事件的通知
核心是 GameMode->RequestPlayerRestartNextFrame(ControllerToReset, true);
最后这里会走去 APlayerController 或者机器人 ALyraPlayerBotController 的重启(这个是核心引擎接口了)
玩家重生
梳理一下原版的玩家重生流程
/ShooterCore/Game/B_TeamSpawningRules.B_TeamSpawningRules
这个蓝图的父类
UTDM_PlayerSpawningManagmentComponent
ULyraPlayerSpawningManagerComponent
通过GameFeatureAction加到GameState上
!并且是Server Comp
UEqZeroPlayerSpawningManagerComponent
我们直接这个类
进游戏首次加载
直接进游戏,体验加载完成。
void ALyraGameMode::OnExperienceLoaded(const ULyraExperienceDefinition* CurrentExperience)
{
for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PC = Cast<APlayerController>(*Iterator);
if ((PC != nullptr) && (PC->GetPawn() == nullptr))
{
if (PlayerCanRestart(PC))
{
RestartPlayer(PC);
}
}
}
}
在GameMode可以随时调用 RestartPlayer。他会找一个出生点,然后让角色生成
void AGameModeBase::RestartPlayer(AController* NewPlayer)
{
AActor* StartSpot = FindPlayerStart(NewPlayer);
RestartPlayerAtPlayerStart(NewPlayer, StartSpot);
}
在 AGameModeBase::FindPlayerStart_Implementation 中
AActor* AGameModeBase::FindPlayerStart_Implementation(AController* Player, const FString& IncomingName)
{
// 1. 找到 APlayerStart->PlayerStartTag 的 IncomingName 的出生点
// 2. 找 Player->StartSpot
if (ShouldSpawnAtStartSpot(Player)) // 子类可重写,默认找 Player->StartSpot 是否存在
{
return PlayerStartSpot;
}
// 3. 主要扩展点是这里
AActor* BestStart = ChoosePlayerStart(Player);
// 4. 如果 BestStart 没有就选world settings,再没有就没了
return BestStart;
}
主要的扩展点
AActor* ALyraGameMode::ChoosePlayerStart_Implementation(AController* Player)
{
if (ULyraPlayerSpawningManagerComponent* PlayerSpawningComponent = GameState->FindComponentByClass<ULyraPlayerSpawningManagerComponent>())
{
return PlayerSpawningComponent->ChoosePlayerStart(Player);
}
return Super::ChoosePlayerStart_Implementation(Player);
}
如果是机器人从这里,回头看
void ULyraBotCreationComponent::OnExperienceLoaded(const ULyraExperienceDefinition* Experience)
{
#if WITH_SERVER_CODE
if (HasAuthority())
{
ServerCreateBots();
}
#endif
}
玩家死了以后重生
APlayerController::ServerRestartPlayer_Implementation 【死亡重生技能调用这里】
GameMode->RestartPlayer(this);
AGameModeBase::FindPlayerStart_Implementation
ChoosePlayerStart 【这里逻辑跑进重生组件 的逻辑】
===
TODO
死亡的一系列Cue
DamageSelfDestruct TODO 这里有个GameData还没有配置