LyraLog13 GAS 4 GE流程

概述

前面提到了近战攻击,通过这个 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还没有配置

上一篇
下一篇