跳跃技能
class UEqZeroGameplayAbility_Jump : public UEqZeroGameplayAbility {}
C++很简单,就是角色的那几个接口
UEqZeroGameplayAbility_Jump::UEqZeroGameplayAbility_Jump(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
}
bool UEqZeroGameplayAbility_Jump::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
if (!ActorInfo || !ActorInfo->AvatarActor.IsValid())
{
return false;
}
const AEqZeroCharacter* EqZeroCharacter = Cast<AEqZeroCharacter>(ActorInfo->AvatarActor.Get());
if (!EqZeroCharacter || !EqZeroCharacter->CanJump())
{
return false;
}
if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags))
{
return false;
}
return true;
}
void UEqZeroGameplayAbility_Jump::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
CharacterJumpStop();
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UEqZeroGameplayAbility_Jump::CharacterJumpStart()
{
if (AEqZeroCharacter* EqZeroCharacter = GetEqZeroCharacterFromActorInfo())
{
if (EqZeroCharacter->IsLocallyControlled() && !EqZeroCharacter->bPressedJump)
{
EqZeroCharacter->UnCrouch();
EqZeroCharacter->Jump();
}
}
}
void UEqZeroGameplayAbility_Jump::CharacterJumpStop()
{
if (AEqZeroCharacter* EqZeroCharacter = GetEqZeroCharacterFromActorInfo())
{
if (EqZeroCharacter->IsLocallyControlled() && EqZeroCharacter->bPressedJump)
{
EqZeroCharacter->StopJumping();
}
}
}
GA蓝图
创建
/Game/Characters/Heroes/Abilities/GA_Hero_Jump.GA_Hero_Jump
只改一个TAG

OnAbilityAdded 的时候,通过注册了一个跳跃按钮到屏幕,这个是移动端的,移动端不管。
由于C++是没有 ActivateAbility的,Lyra是写蓝图里面的

先试试,
技能怎么配置就不贴图了,PawnData里面的技能集合填一填
能跳了,但是之前偷懒跳跃状态机没连
另外比起引擎里面另一个跳跃技能,这里为什么多了两个异步状态呢
void UGameplayAbility_CharacterJump::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
return;
}
ACharacter * Character = CastChecked<ACharacter>(ActorInfo->AvatarActor.Get());
Character->Jump();
}
}
开启了一个Jumping状态,这个状态会被EndAbility 打破所有Task而触发。其实只是记了一个带名字的状态
/**
* Starts a new ability state.
*
* @param StateName The name of the state.
* @param bEndCurrentState If true, all other active ability states will be ended.
*/
UFUNCTION(BlueprintCallable, Meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true", HideSpawnParms = "Instigator"), Category = "Ability|Tasks")
static UE_API UAbilityTask_StartAbilityState* StartAbilityState(UGameplayAbility* OwningAbility, FName StateName, bool bEndCurrentState = true);
激活的时候绑定一些事件。然后等待输入Release
void UAbilityTask_StartAbilityState::Activate()
{
if (Ability)
{
if (bEndCurrentState && Ability->OnGameplayAbilityStateEnded.IsBound())
{
Ability->OnGameplayAbilityStateEnded.Broadcast(NAME_None);
}
EndStateHandle = Ability->OnGameplayAbilityStateEnded.AddUObject(this, &UAbilityTask_StartAbilityState::OnEndState);
InterruptStateHandle = Ability->OnGameplayAbilityCancelled.AddUObject(this, &UAbilityTask_StartAbilityState::OnInterruptState);
}
}
等待输入Release
void UAbilityTask_WaitInputRelease::Activate()
{
StartTime = GetWorld()->GetTimeSeconds();
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC && Ability)
{
if (bTestInitialState && IsLocallyControlled())
{
FGameplayAbilitySpec *Spec = Ability->GetCurrentAbilitySpec();
if (Spec && !Spec->InputPressed)
{
OnReleaseCallback();
return;
}
}
// 注册一个输入release代理
DelegateHandle = ASC->AbilityReplicatedEventDelegate(EAbilityGenericReplicatedEvent::InputReleased, GetAbilitySpecHandle(), GetActivationPredictionKey()).AddUObject(this, &UAbilityTask_WaitInputRelease::OnReleaseCallback);
if (IsForRemoteClient())
{
if (!ASC->CallReplicatedEventDelegateIfSet(EAbilityGenericReplicatedEvent::InputReleased, GetAbilitySpecHandle(), GetActivationPredictionKey()))
{
SetWaitingOnRemotePlayerData();
}
}
}
}
跳跃状态机

起跳,跳跃循环,到顶Apex,然后下落,落地。最后时期去Idle还是Cycle
除了Land都比较简单

都是这个,Seq Player 换一个动画名字。
除了名字带Loop的状态,需要把Loop去掉
落地比较复杂


这里又是根据距离曲线推算位置
冲刺技能
/ShooterCore/Game/Dash/GA_Hero_Dash.GA_Hero_Dash

配置一个冲刺技能,输入和GA

核对一下配置,这个是GA的TAG和激活时,拥有者会加上什么TAG
还有一个Trigger,就是可以通过send event trigger 这个TAG能激活这个技能。这里可能先不用。
输入配置,配置了InputAction的冲刺,触发了TAG InputTag.Ability.Dash,然后找到Ability然后触发。
void UEqZeroGameplayAbility_Dash::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
// 只在本地控制端计算方向,确保客户端和服务器使用相同的数据
if (IsLocallyControlled())
{
// 计算合法的方向 和 冲刺蒙太奇动画
// 客户端提交技能
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
CancelAbility(Handle, ActorInfo, ActivationInfo, true);
return;
}
// 如果蹲伏就起来
if (AEqZeroCharacter* Character = GetEqZeroCharacterFromActorInfo())
{
if (Character->IsCrouched())
{
Character->UnCrouch();
}
}
// 这里是权威,其实如果我们讨论DS,前面IsLocallyControlled已经把DS的服务器去掉了
// 所以总是走else
if (HasAuthority(&ActivationInfo))
{
// 单机或 Listen Server:直接执行
ExecuteDash(Direction, Montage);
}
else
{
// DS 客户端:本地预测执行 + RPC 通知服务器
ExecuteDash(Direction, Montage);
ServerSendInfo(Direction, Montage);
}
}
}
服务器的Commit技能,然后 ExecuteDash
void UEqZeroGameplayAbility_Dash::ServerSendInfo_Implementation(const FVector& InDirection, UAnimMontage* InMontage)
{
Direction = InDirection;
Montage = InMontage;
CommitAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo);
ExecuteDash(Direction, Montage);
}
注意客户端和服务器都来到了 ExecuteDash
这里面是 UAbilityTask_PlayMontageAndWait 播放蒙太奇和 UAbilityTask_ApplyRootMotionConstantForce 进行RootMotion
void UEqZeroGameplayAbility_Dash::ExecuteDash(const FVector& InDirection, UAnimMontage* InMontage)
{
Direction = InDirection;
Montage = InMontage;
UE_LOG(LogTemp, Warning, TEXT("[Dash] ExecuteDash called on %s | Direction: %s"),
HasAuthority(&CurrentActivationInfo) ? TEXT("Server") : TEXT("Client"),
*Direction.ToString());
// Play Montage (确保 Montage 动画不包含 Root Motion,只包含姿势动画)
if (InMontage)
{
UAbilityTask_PlayMontageAndWait* MontageTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
this,
FName("PlayDashMontage"),
InMontage,
1.0f,
NAME_None,
true
);
if (MontageTask)
{
MontageTask->OnCompleted.AddDynamic(this, &UEqZeroGameplayAbility_Dash::OnMontageCompleted);
MontageTask->OnInterrupted.AddDynamic(this, &UEqZeroGameplayAbility_Dash::OnMontageInterrupted);
MontageTask->OnCancelled.AddDynamic(this, &UEqZeroGameplayAbility_Dash::OnMontageCancelled);
MontageTask->ReadyForActivation();
}
}
// Apply Root Motion Constant Force
if (Strength > 0.0f && RootMotionDuration > 0.0f)
{
UAbilityTask_ApplyRootMotionConstantForce* RootMotionTask = UAbilityTask_ApplyRootMotionConstantForce::ApplyRootMotionConstantForce(
this,
FName("DashRootMotion"),
InDirection,
Strength,
RootMotionDuration,
true,
nullptr,
ERootMotionFinishVelocityMode::ClampVelocity,
FVector::ZeroVector,
1000.0f,
false
);
if (RootMotionTask)
{
RootMotionTask->OnFinish.AddDynamic(this, &UEqZeroGameplayAbility_Dash::OnRootMotionFinished);
RootMotionTask->ReadyForActivation();
}
}
if (HasAuthority(&CurrentActivationInfo))
{
// TODO gameplay cue
}
}
这里cue是一些粒子特效,晚点弄。。。
把CD的GE加出来,配置在GA的cooldown哪里的effect
/Game/GameplayEffects/GE_HeroDash_Cooldown.GE_HeroDash_Cooldown

TODO 缺一个UI的CD
近战

UEqZeroGameplayAbility_Melee
如何释放看代码吧
另外还有一个 AN_Melee SendGameplayEventToActor GameplayEvent.MeleeHit 这个没看到哪里引用了。
可能是为了通过在动画播放到某个状态的时候触发攻击,然后技能里面WaitGameplayEvent
void UEqZeroGameplayAbility_Melee::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
CommitAbility(Handle, ActorInfo, ActivationInfo);
PlayMeleeMontage();
if (HasAuthority(&ActivationInfo))
{
MeleeCapsuleTrace();
}
}
最后会落实到对某个目标的 ASC 是加一个 effect
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是这个
/Game/GameplayEffects/Damage/GE_Damage_Basic_Instant.GE_Damage_Basic_Instant