网站建设常用问题库,作网站,网站上怎么做企业推广,壁画网站建设在这一篇#xff0c;我们在之前打下的基础下#xff0c;实现一下被动技能。 被动技能需要我们在技能栏上面选择升级解锁技能后#xff0c;将其设置到技能栏#xff0c;我们先增加被动技能使用的标签。 FGameplayTag Abilities_Passive_HaloOfProtection; //被动技能-守护光…在这一篇我们在之前打下的基础下实现一下被动技能。 被动技能需要我们在技能栏上面选择升级解锁技能后将其设置到技能栏我们先增加被动技能使用的标签。 FGameplayTag Abilities_Passive_HaloOfProtection; //被动技能-守护光环FGameplayTag Abilities_Passive_LifeSiphon; //被动技能-生命回复FGameplayTag Abilities_Passive_ManaSiphon; //被动技能-蓝量回复注册一下 /** 被动技能*/GameplayTags.Abilities_Passive_HaloOfProtection UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Abilities.Passive.HaloOfProtection),FString(守护光环));GameplayTags.Abilities_Passive_LifeSiphon UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Abilities.Passive.LifeSiphon),FString(生命自动回复));GameplayTags.Abilities_Passive_ManaSiphon UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Abilities.Passive.ManaSiphon),FString(蓝量自动回复));添加被动技能基类
我们基于技能基类创建一个派生类用于作为被动技能的基类 命名为RPGPassiveAbility 在类里增加两个函数一个是覆写激活技能函数在技能被调用激活时绑定结束回调监听如果ASC调用了结束技能并且此被动技能刚好有对应的标签我们可以通过第二个技能结束此技能实力的激活。
public:/*** 覆写激活技能函数* param Handle 技能实力的句柄* param ActorInfo 技能拥有者* param ActivationInfo 激活信息* param TriggerEventData 游戏事件信息*/virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;/*** 接收到技能结束回调函数* param AbilityTag 结束的技能标识标签*/void ReceiveDeactivate(const FGameplayTag AbilityTag);我们在ASC里增加一个新的委托定义用来定义技能结束
DECLARE_MULTICAST_DELEGATE_OneParam(FDeactivatePassiveAbility, const FGameplayTag /*技能标签*/); //中止一个技能激活的回调并在ASC类里新增一个变量
FDeactivatePassiveAbility DeactivatePassiveAbility; //取消技能激活的委托在被动技能基类里激活技能时绑定委托的监听
void URPGPassiveAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);//获取到ASCif(URPGAbilitySystemComponent* RPGASC CastURPGAbilitySystemComponent(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetAvatarActorFromActorInfo()))){//绑定技能取消回调RPGASC-DeactivatePassiveAbility.AddUObject(this, URPGPassiveAbility::ReceiveDeactivate);}
}在回调里判断委托返回的标签是否为当前被动技能的标识如果是将调用结束技能
void URPGPassiveAbility::ReceiveDeactivate(const FGameplayTag AbilityTag)
{//判断技能标签容器里是否包含此标签if(AbilityTags.HasTagExact(AbilityTag)){EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, true);}
}创建对应被动技能蓝图
有了被动技能基类我们在UE里创建对应的蓝图 基于之前创建的基类创建三个被动技能 在技能标识标签这里设置对应的标签 然后在DA_AbilityInfo里添加对应的技能的相关数据 接下来我们在UI里实现设置按钮显示哪个技能标签 接着运行查看按钮是否能够正确显示以及操作 记得在主窗口的UI上设置对应的按钮Tag 运行设置后查看是否主界面也能够跟随变动
实现被动技能的装配时激活
技能可以实现了装配并且我们在技能里监听了取消事件在触发对应回调时技能会自动取消激活。 所以我们在被动技能里设置调试节点方便测试在激活时和结束技能时都可以打印信息。 由于被动技能应用时就需要激活所以我们不需要预测它直接在服务器初始化即可。 这是我们现在的ASC里装配技能时的处理逻辑没有考虑到主动技能和被动技能的区别 接下来我们将修改技能装配的逻辑让其兼容对被动技能的处理。 前面的这一段还是一样通过技能标签获取到技能实例并获取到技能未修改前装配的槽位和状态对技能的状态进行判断
void URPGAbilitySystemComponent::ServerEquipAbility_Implementation(const FGameplayTag AbilityTag, const FGameplayTag Slot)
{const FRPGGameplayTags GameplayTags FRPGGameplayTags::Get();//获取到技能实例if(FGameplayAbilitySpec* AbilitySpec GetSpecFromAbilityTag(AbilityTag)){const FGameplayTag PrevSlot GetInputTagFromSpec(*AbilitySpec); //技能之前装配的插槽const FGameplayTag Status GetStatusTagFromSpec(*AbilitySpec); //当前技能的状态标签//判断技能的状态技能状态只有在已装配或者已解锁的状态才可以装配if(Status GameplayTags.Abilities_Status_Equipped || Status GameplayTags.Abilities_Status_Unlocked){接着我们先处理目标槽位判断目标槽位现在是否装配技能如果装配我们则获取到装配的技能实例如果槽位装配的技能和我们需要装配的技能相同则不做处理。 如果槽位的技能是被动技能我们将通过委托结束技能被动技能基类在激活技能时会监听技能结束委托 并且我们将会清除槽位所有装配的技能清除装配技能上设置的输入标签
//判断插槽是否有技能有则需要将其清除
if(!SlotIsEmpty(Slot))
{//获取目标插槽现在装配的技能if(const FGameplayAbilitySpec* SpecWithSlot GetSpecWithSlot(Slot)){//技能槽位装配相同的技能直接返回不做额外的处理if(AbilityTag.MatchesTagExact(GetAbilityTagFromSpec(*SpecWithSlot))){ClientEquipAbility(AbilityTag, Status, Slot, PrevSlot);return;}//如果是被动技能我们需要先将技能取消执行if(IsPassiveAbility(*SpecWithSlot)){DeactivatePassiveAbility.Broadcast(GetAbilityTagFromSpec(*SpecWithSlot));}ClearAbilitiesOfSlot(Slot); //清除目标插槽装配的技能}
}接下来我们对需要装配的技能判断如果它之前没有被装配到技能槽位并且还是被动技能证明技能还未被激活我们需要将技能激活。
//技能没有设置到插槽没有激活
if(!AbilityHasAnySlot(*AbilitySpec))
{//如果是被动技能装配即激活if(IsPassiveAbility(*AbilitySpec)){TryActivateAbility(AbilitySpec-Handle);}
}然后修改技能的的输入标签为装配的槽位
//修改技能装配的插槽
AssignSlotToAbility(*AbilitySpec, Slot);最后网络同步和触发装配委托回调
//回调更新UI
ClientEquipAbility(AbilityTag, Status, Slot, PrevSlot);
MarkAbilitySpecDirty(*AbilitySpec); //立即将其复制到每个客户端逻辑梳理完成下面为使用到的一些函数
bool URPGAbilitySystemComponent::SlotIsEmpty(const FGameplayTag Slot)
{FScopedAbilityListLock ActiveScopeLoc(*this);for(FGameplayAbilitySpec AbilitySpec : GetActivatableAbilities()){if(AbilityHasSlot(AbilitySpec, Slot)){return false;}}return true;
}bool URPGAbilitySystemComponent::AbilityHasSlot(const FGameplayAbilitySpec Spec, const FGameplayTag Slot)
{return Spec.DynamicAbilityTags.HasTagExact(Slot);
}bool URPGAbilitySystemComponent::AbilityHasAnySlot(const FGameplayAbilitySpec Spec)
{//通过判断动态标签是否含有Input的标签来判断技能是否装配到槽位return Spec.DynamicAbilityTags.HasTag(FGameplayTag::RequestGameplayTag(FName(InputTag)));
}FGameplayAbilitySpec* URPGAbilitySystemComponent::GetSpecWithSlot(const FGameplayTag Slot)
{FScopedAbilityListLock ActiveScopeLoc(*this);for(FGameplayAbilitySpec AbilitySpec : GetActivatableAbilities()){if(AbilityHasSlot(AbilitySpec, Slot)){return AbilitySpec;}}return nullptr;
}bool URPGAbilitySystemComponent::IsPassiveAbility(const FGameplayAbilitySpec Spec) const
{//从技能配置数据里获取到技能对于的配置信息UAbilityInfo* AbilityInfo URPGAbilitySystemLibrary::GetAbilityInfo(GetAvatarActor());const FGameplayTag AbilityTag GetAbilityTagFromSpec(Spec);const FRPGAbilityInfo Info AbilityInfo-FindAbilityInfoForTag(AbilityTag);//判断信息里配置的技能类型是否为被动技能const FGameplayTag AbilityType Info.AbilityType;return AbilityType.MatchesTagExact(FRPGGameplayTags::Get().Abilities_Type_Passive);
}void URPGAbilitySystemComponent::AssignSlotToAbility(FGameplayAbilitySpec Spec, const FGameplayTag Slot)
{const FRPGGameplayTags GameplayTags FRPGGameplayTags::Get();ClearSlot(Spec);Spec.DynamicAbilityTags.AddTag(Slot);Spec.DynamicAbilityTags.RemoveTag(GameplayTags.Abilities_Status_Unlocked);Spec.DynamicAbilityTags.AddTag(GameplayTags.Abilities_Status_Equipped);
}
void URPGAbilitySystemComponent::ClearSlot(FGameplayAbilitySpec* Spec)
{const FGameplayTag Slot GetInputTagFromSpec(*Spec);Spec-DynamicAbilityTags.RemoveTag(Slot);// MarkAbilitySpecDirty(*Spec);
}void URPGAbilitySystemComponent::ClearAbilitiesOfSlot(const FGameplayTag Slot)
{FScopedAbilityListLock ActiveScopeLock(*this);for(FGameplayAbilitySpec Spec : GetActivatableAbilities()){if(AbilityHasSlot(Spec, Slot)){ClearSlot(Spec);}}
}最后展示一下实现装配的所有代码
void URPGAbilitySystemComponent::ServerEquipAbility_Implementation(const FGameplayTag AbilityTag, const FGameplayTag Slot)
{const FRPGGameplayTags GameplayTags FRPGGameplayTags::Get();//获取到技能实例if(FGameplayAbilitySpec* AbilitySpec GetSpecFromAbilityTag(AbilityTag)){const FGameplayTag PrevSlot GetInputTagFromSpec(*AbilitySpec); //技能之前装配的插槽const FGameplayTag Status GetStatusTagFromSpec(*AbilitySpec); //当前技能的状态标签//判断技能的状态技能状态只有在已装配或者已解锁的状态才可以装配if(Status GameplayTags.Abilities_Status_Equipped || Status GameplayTags.Abilities_Status_Unlocked){//判断插槽是否有技能有则需要将其清除if(!SlotIsEmpty(Slot)){//获取目标插槽现在装配的技能if(const FGameplayAbilitySpec* SpecWithSlot GetSpecWithSlot(Slot)){//技能槽位装配相同的技能直接返回不做额外的处理if(AbilityTag.MatchesTagExact(GetAbilityTagFromSpec(*SpecWithSlot))){ClientEquipAbility(AbilityTag, Status, Slot, PrevSlot);return;}//如果是被动技能我们需要先将技能取消执行if(IsPassiveAbility(*SpecWithSlot)){DeactivatePassiveAbility.Broadcast(GetAbilityTagFromSpec(*SpecWithSlot));}ClearAbilitiesOfSlot(Slot); //清除目标插槽装配的技能}}//技能没有设置到插槽没有激活if(!AbilityHasAnySlot(*AbilitySpec)){//如果是被动技能装配即激活if(IsPassiveAbility(*AbilitySpec)){TryActivateAbility(AbilitySpec-Handle);}}//修改技能装配的插槽AssignSlotToAbility(*AbilitySpec, Slot);//回调更新UIClientEquipAbility(AbilityTag, Status, Slot, PrevSlot);MarkAbilitySpecDirty(*AbilitySpec); //立即将其复制到每个客户端}}
}运行查看装配后对应的打印是否能够正常打印。
添加被动技能表现特效
现在技能可以激活了我们需要让玩家能够知道被动技能生效的效果参照之前的debuff应用我们将参照之前的方式实现即使出现了问题那是一种比较好的解耦方式。 首先我们创建一个被动技能表现基类 命名为PassiveNiagaraComponent 在基类里我们需要一个设置标签用于启动时对应的被动技能标签然后添加一个监听回调的函数。
UCLASS()
class RPG_API UPassiveNiagaraComponent : public UNiagaraComponent
{GENERATED_BODY()public:UPassiveNiagaraComponent();//激活此被动技能特效的技能标签UPROPERTY(EditDefaultsOnly)FGameplayTag PassiveSpellTag;protected:virtual void BeginPlay() override;/*** 监听技能变动后的委托回调用于设置此实例是否需要激活* param AbilityTag 对应的技能的标签* param bActivate 激活还是关闭*/void OnPassiveActivate(const FGameplayTag AbilityTag, bool bActivate);
};在构造函数里将特效自动激活关闭 在事件开始时绑定ASC里被动技能应用委托通过监听被动技能应用委托来触发回调 在回调里判断标签是否对应根据需要开启和关闭设置特效组件的开启关闭。
UPassiveNiagaraComponent::UPassiveNiagaraComponent()
{bAutoActivate false;
}void UPassiveNiagaraComponent::BeginPlay()
{Super::BeginPlay();if(URPGAbilitySystemComponent* RPGASC CastURPGAbilitySystemComponent(UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwner()))){RPGASC-ActivatePassiveEffect.AddUObject(this, UPassiveNiagaraComponent::OnPassiveActivate);}else if(ICombatInterface* CombatInterface CastICombatInterface(GetOwner())){//AddWeakLambda 这种绑定方式的主要好处是当绑定的对象被销毁时委托不会保持对象的引用从而避免悬空指针问题和内存泄漏。CombatInterface-GetOnASCRegisteredDelegate().AddWeakLambda(this,[this](UAbilitySystemComponent* InASC){if(URPGAbilitySystemComponent* RPGASC CastURPGAbilitySystemComponent(InASC)){RPGASC-ActivatePassiveEffect.AddUObject(this, UPassiveNiagaraComponent::OnPassiveActivate);}});}
}void UPassiveNiagaraComponent::OnPassiveActivate(const FGameplayTag AbilityTag, bool bActivate)
{//判断技能标签是否一致if(AbilityTag.MatchesTagExact(PassiveSpellTag)){//判断是否需要激活if(bActivate){//不需要重复激活if(!IsActive()) Activate();}else{Deactivate();}}
}在ASC类里我们增加一个新的委托类型
DECLARE_MULTICAST_DELEGATE_TwoParams(FActivePassiveEffect, const FGameplayTag /*被动技能标签*/, bool /*激活或取消*/); //被动技能特效监听委托对应特效是否开启在ASC类里新增一个对应类型的属性
FActivePassiveEffect ActivatePassiveEffect; //被动技能对应特效委托增加一个多播函数它会在每个客户端和服务器运行保证都能够查看到对应效果设置Unreliable用来设置它不是重要的不需要优先同步 /*** 多网络被动特效委托广播让每个客户端都可以看到特效* param AbilityTag 被动技能标签* param bActivate 激活或者关闭*/UFUNCTION(NetMulticast, Unreliable)void MulticastActivatePassiveEffect(const FGameplayTag AbilityTag, bool bActivate);然后在函数里调用委托让每个客户端对应的ASC都会调用此函数
void URPGAbilitySystemComponent::MulticastActivatePassiveEffect_Implementation(const FGameplayTag AbilityTag, bool bActivate)
{ActivatePassiveEffect.Broadcast(AbilityTag, bActivate);
}然后在我们装配技能时取消被动执行时调用它传入false 在激活一个被动技能时我们设置对应的特效激活 最后就是在角色类里我们需要对应的特效组件对每一种特效都创建一个对应的特效 //光环被动技能特效组件UPROPERTY(VisibleAnywhere)TObjectPtrUPassiveNiagaraComponent HaloOfProtectionNiagaraComponent;//回血被动技能特效组件UPROPERTY(VisibleAnywhere)TObjectPtrUPassiveNiagaraComponent LifeSiphonNiagaraComponent;//回蓝被动技能特效组件UPROPERTY(VisibleAnywhere)TObjectPtrUPassiveNiagaraComponent ManaSiphonNiagaraComponent;//被动技能挂载的组件UPROPERTY(VisibleAnywhere)TObjectPtrUSceneComponent EffectAttachComponent;然后在构造函数里创建实例并挂载到特效根组件我们创建特效根组件的原因是为了保证特效不会跟随角色旋转 //实例化被动技能组件并挂载EffectAttachComponent CreateDefaultSubobjectUSceneComponent(EffectAttachPoint);EffectAttachComponent-SetupAttachment(GetRootComponent());HaloOfProtectionNiagaraComponent CreateDefaultSubobjectUPassiveNiagaraComponent(HaloOfProtectionComponent);HaloOfProtectionNiagaraComponent-SetupAttachment(EffectAttachComponent);LifeSiphonNiagaraComponent CreateDefaultSubobjectUPassiveNiagaraComponent(LifeSiphonComponent);LifeSiphonNiagaraComponent-SetupAttachment(EffectAttachComponent);ManaSiphonNiagaraComponent CreateDefaultSubobjectUPassiveNiagaraComponent(ManaSiphonComponent);ManaSiphonNiagaraComponent-SetupAttachment(EffectAttachComponent);我们需要在帧更新里去修改特效根组件的旋转让其保证相对于世界不会旋转所以需要覆写帧更新函数
virtual void Tick(float DeltaSeconds) override;在帧更新函数里我们每一帧都将其旋转值设置为相对于世界坐标默认为0
void ARPGCharacterBase::Tick(float DeltaSeconds)
{Super::Tick(DeltaSeconds);//防止特效跟随人物旋转每一帧更新修改旋转为默认EffectAttachComponent-SetWorldRotation(FRotator::ZeroRotator);
}之前我们没有使用帧更新它是关掉的现在我们需要将其开启 // 将这个字符设置为true时将每帧进行更新。不需要可以关闭提高性能。PrimaryActorTick.bCanEverTick true;接下来我们编译打开蓝图查看玩家角色蓝图是否生成了对应的组件 我们为每个特效组件设置对应的资产 并设置特效组件对应的被动技能这样在被动技能被应用时特效也将会激活。 接下来就是运行查看效果。
实现被动技能效果
在前面被动技能可以触发对应技能里的激活和结束回调节点我们可以以此为出发点来给玩家角色应用GE。 我们创建一个基础的被动技能蓝图将一些公共的配置和函数在此函数完成 在蓝图里我们添加一个设置GE类的变量 增加一个添加GE给自身的函数 再增加一个通过类删除GE的函数 默认激活技能时调用添加函数技能结束时删除GE 接着我们增加一个新的GE类 这里我先做一个蓝量回复的类类型设置为时间无限每一秒执行一次 在Modifiers里我们增加一个属性修改属性基于之前设置的蓝量回复的值 在被动技能里我们将被动技能基类修改为创建的蓝图基类 事件调用修改为调用父节点 父节点可以通过右键查找到添加 最后修改GE的默认值的类 在我们应用了被动技能后你会发现蓝量在慢慢回复并且是每一秒回复一次。 被动技能这里只是给大家一个实现思路大家可以实现更多的被动技能。