网站建设从零开始,wordpress悬浮窗口,上海建站shwzzz,网站命名规范闪电链有一个施法的过程#xff0c;就是在按键按下的过程#xff0c;会在按下的过程一直持续造成伤害#xff0c;一直等到条件不满足#xff08;技能键位抬起#xff0c;蓝量不足#xff0c;被眩晕#xff09;时#xff0c;将结束技能#xff0c;并退出技能状态。 所以…闪电链有一个施法的过程就是在按键按下的过程会在按下的过程一直持续造成伤害一直等到条件不满足技能键位抬起蓝量不足被眩晕时将结束技能并退出技能状态。 所以首先我们将实现技能的持续释放状态。
增加技能持续施法动画
首先我们在角色蓝图里增加一个变量用来记录当前是否处于持续施法状态 在动画蓝图动画更新事件回调里获取此变量并将此存储在动画蓝图中以备后用 我们接下来修改状态机将持续施法动画加入切换通过变量进行判断切换。 接下来我们在战斗接口中增加一个函数用于在蓝图中继承并修改。 BlueprintImplementableEvent 这个函数应该在蓝图中实现而不是在 C 中实现。 BlueprintNativeEvent 允许你在 C 中提供一个默认实现同时也允许在蓝图中覆盖这个实现。 CombatInterface.h UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)void SetInShockLoop(bool bInLoop);在角色蓝图里我们实现此函数通过传入的值设置变量 接下来我们创建一个新的技能类基于我们的自定义的技能基类不知道的小伙伴可以查看之前的文章或者在文章底部加群了解。 设置技能的输入标签。 为了方便测试我们将技能直接添加到初始中不需要激活即可拥有此技能。然后我们运行就可以右键释放此技能。 然后我们在技能蓝图里添加一些测试逻辑用于测试此逻辑监听了对应激活技能按键的鼠标抬起和鼠标按下。在鼠标按下时我们将变量设置为true在鼠标抬起时我们将变量设置为false。函数将会修改玩家角色身上的变量动画蓝图跟着更新。 Test Already Pressed 如果设置不但会监听后续触发如果当前已经触发了鼠标的按下或者抬起也将会触发后续事件。
绑定技能按键按下事件
我们接下来要实现对技能按键的抬起和按下事件的触发刚好回顾一下我们之前是如何绑定的技能输入。 我们在玩家控制器内覆写了SetupInputComponent函数这个函数是在设置了自定义输入组件是触发我们通过输入组件对增强输入组件进行绑定输入回调。 你可以在项目设置的输入中修改它的默认类 在输入组件里我们增加了一个绑定函数可以绑定技能键位的按下抬起和按住事件数据设置对应了输入和输入标签方便我们后续通过输入标签进行和技能匹配激活。 我们在玩家控制器里输入回调中可以获取到输入标签首先处理移动的问题如果当前输入操作没有造成移动我们将事件传递给ASC在ASC中对技能进行相关处理。 技能在实例化时我们会将技能的输入标签存储到动态标签中方便你在技能面板对技能的输入标签进行修改。 我们接着修改ASC自定义的相关输入处理我们将通过标签对技能进行匹配如果匹配到了对应的技能实例将触发在技能实例上设置的按下和抬起事件。
void URPGAbilitySystemComponent::AbilityInputTagPressed(const FGameplayTag InputTag)
{if(!InputTag.IsValid()) return;for(auto AbilitySpec : GetActivatableAbilities()){if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag)){AbilitySpecInputPressed(AbilitySpec);}}
}void URPGAbilitySystemComponent::AbilityInputTagHold(const FGameplayTag InputTag)
{if(!InputTag.IsValid()) return;for(auto AbilitySpec : GetActivatableAbilities()){if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag)){AbilitySpecInputPressed(AbilitySpec);if(!AbilitySpec.IsActive()){TryActivateAbility(AbilitySpec.Handle);}}}
}void URPGAbilitySystemComponent::AbilityInputTagReleased(const FGameplayTag InputTag)
{if(!InputTag.IsValid()) return;for(auto AbilitySpec : GetActivatableAbilities()){if(AbilitySpec.DynamicAbilityTags.HasTagExact(InputTag) AbilitySpec.IsActive()){AbilitySpecInputReleased(AbilitySpec);}}
}最后我们在自定义技能类里覆写技能基类的抬起和按下事件ASC调用时会触发这两个函数 virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
接下来就是对函数实现如果技能未激活我们将无法触发按下事件通过ASC来广播事件这样在蓝图中对应的事件将会触发。
void URPGGameplayAbility::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{Super::InputPressed(Handle, ActorInfo, ActivationInfo);//通过句柄获取技能实例FGameplayAbilitySpec* AbilitySpec ActorInfo-AbilitySystemComponent-FindAbilitySpecFromHandle(Handle);//技能实例在激活状态触发输入事件if(AbilitySpec-IsActive()){//将按下事件复制到服务器和所有相关的客户端ActorInfo-AbilitySystemComponent-InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputPressed, Handle, ActivationInfo.GetActivationPredictionKey());}
}void URPGGameplayAbility::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{Super::InputReleased(Handle, ActorInfo, ActivationInfo);//将抬起事件复制到服务器和所有相关的客户端ActorInfo-AbilitySystemComponent-InvokeReplicatedEvent(EAbilityGenericReplicatedEvent::InputReleased, Handle, ActivationInfo.GetActivationPredictionKey());
}接下来就是运行测试查看当前是否正确打印以及是否正确播放对象的动画
添加技能C类
为了方便实现后续功能我们创建一个闪电链使用的c基类 然后修改我们之前创建的技能蓝图的父类 接着我们在类里面增加一些代码我们增加一个存储鼠标命中拾取的一些相关信息内容和当前玩家控制器并增加一些保护性的变量方便后续使用。
UCLASS()
class RPG_API URPGBeamSpell : public URPGDamageGameplayAbility
{GENERATED_BODY()public:/*** 将鼠标拾取命中信息存储* param HitResult 在技能中通过TargetDataUnderMouse的task获取到的结果*/UFUNCTION(BlueprintCallable)void StoreMouseDataInfo(const FHitResult HitResult);/*** 设置拥有当前技能的玩家控制器*/UFUNCTION(BlueprintCallable)void StoreOwnerVariables();protected:UPROPERTY(BlueprintReadWrite, CategoryBeam)FVector MouseHitLocation; //鼠标拾取位置UPROPERTY(BlueprintReadWrite, CategoryBeam)TObjectPtrAActor MouseHitActor; //鼠标拾取的对象UPROPERTY(BlueprintReadWrite, CategoryBeam)TObjectPtrAPlayerController OwnerPlayerController; //拥有当前技能的玩家控制器
};在函数实现这里我们技能需要后续的拾取到内容才可进行所以如果没有拾取到将取消技能激活如果拾取到则将拾取到的位置和actor存储。
void URPGBeamSpell::StoreMouseDataInfo(const FHitResult HitResult)
{//判断当前是否拾取到内容if(HitResult.bBlockingHit){MouseHitLocation HitResult.ImpactPoint;MouseHitActor HitResult.GetActor();}else{//取消技能CancelAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true);}
}void URPGBeamSpell::StoreOwnerVariables()
{if(CurrentActorInfo){OwnerPlayerController CurrentActorInfo-PlayerController.Get();OwnerCharacter CastACharacter(CurrentActorInfo-AvatarActor);}
}接着我们编译打开技能并通过之前写的task来获取攻击距离然后将拾取信息存储以及保存玩家控制器。 接下来我们实现在技能触发时隐藏鼠标光标并在对于技能触发按键抬起时结束技能。 在技能触发时我们需要一个抬手的动作并在播放动作时让角色转向到目标这个使用的蒙太奇配置我们可以设置到角色蓝图上角色配置项里这里为了方便直接在技能上设置。 然后我们找到对应的动画创建蒙太奇。 并使用motion Wrapping来实现对角色向目标选择记得动画要设置根运动。 接着设置对应的配置项。 接下来我们要设置蒙太奇播放结束后开始攻击我们需要通知在标签管理器增加一个闪电链的蒙太奇通知标签 在动画结束时添加一个动画通知用于通知蒙太奇结束开始攻击目标 设置我们之前设置的标签 由于混合时motion wrapping不起作用我们将混入和混出时间减少 将蒙太奇设置给变量 接着在技能激活后我们调用战斗接口的需要朝向的目标位置并播放蒙太奇 由于整个技能需要战斗接口如果当前拥有者没有继承战斗接口那么就没有必要执行后续逻辑 我们将监听标签事件通过标签对应触发持续施法的动画。并在持续施法时我们将禁止角色移动。 在鼠标抬起时也就是技能结束之前我们关闭持续施法动画并将移动状态修改为行走移动组件便可以恢复移动。 接下来运行查看效果。
处理多个技能不能共同触发的问题
正常游戏进行中玩家角色在释放一个技能时是无法释放另一个技能。所以我们需要在技能蓝图里通过设置Activation Owned Tags来在技能激活时给玩家角色设置技能应用标签。 然后技能激活时将在角色身上应用对应的标签我们在控制器里对标签进行判断如果角色身上拥有此标签那将无法触发对应的操作。首先我们在代码中添加对应的阻止事件触发的标签 //阻止输入相关事件触发FGameplayTag Player_Block_InputPressed; //阻挡键位按下输入FGameplayTag Player_Block_InputHold; //阻挡键位悬停输入FGameplayTag Player_Block_InputReleased; //阻挡键位抬起输入FGameplayTag Player_Block_CursorTrace; //阻挡鼠标拾取事件然后注册 /** 阻止相关鼠标事件的触发标签*/GameplayTags.Player_Block_InputPressed UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Player.Block.InputPressed),FString(阻挡键位按下输入));GameplayTags.Player_Block_InputHold UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Player.Block.InputHold),FString(阻挡键位悬停输入));GameplayTags.Player_Block_InputReleased UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Player.Block.InputReleased),FString(阻挡键位抬起输入));GameplayTags.Player_Block_CursorTrace UGameplayTagsManager::Get().AddNativeGameplayTag(FName(Player.Block.CursorTrace),FString(阻挡鼠标拾取事件));首先我们阻止鼠标拾取因为这个比较耗性能每一帧都在拾取如果事件被阻挡我们将缓存的数据重置并将已经高亮的角色取消高亮。
void ARPGPlayerController::CursorTrace()
{//判断当前事件是否被阻挡如果事件被阻挡则清除相关内容if(GetASC() GetASC()-HasMatchingGameplayTag(FRPGGameplayTags::Get().Player_Block_CursorTrace)){if(ThisActor) ThisActor-UnHighlightActor();if(LastActor) LastActor-UnHighlightActor();ThisActor nullptr; LastActor nullptr;return;}GetHitResultUnderCursor(ECC_Visibility, false, CursorHit); //获取可视的鼠标命中结果if(!CursorHit.bBlockingHit) return; //如果未命中直接返回LastActor ThisActor;ThisActor CastIEnemyInterface(CursorHit.GetActor());if(ThisActor ! LastActor){if(ThisActor) ThisActor-HighlightActor();if(LastActor) LastActor-UnHighlightActor();}}然后就输按键的按下长按抬起事件的取消
void ARPGPlayerController::AbilityInputTagPressed(const FGameplayTag InputTag)
{//处理判断按下事件是否被阻挡if(GetASC() GetASC()-HasMatchingGameplayTag(FRPGGameplayTags::Get().Player_Block_InputPressed)){return;}void ARPGPlayerController::AbilityInputTagReleased(const FGameplayTag InputTag)
{//处理判断抬起事件是否被阻挡if(GetASC() GetASC()-HasMatchingGameplayTag(FRPGGameplayTags::Get().Player_Block_InputReleased)){return;}void ARPGPlayerController::AbilityInputTagHold(const FGameplayTag InputTag)
{//通过标签阻止悬停事件的触发if(GetASC() GetASC()-HasMatchingGameplayTag(FRPGGameplayTags::Get().Player_Block_InputHold)){return;}移动事件我们也可以阻止因为移动事件是通过长按触发的
void ARPGPlayerController::Move(const FInputActionValue InputActionValue)
{//方向控制如果阻止了按住事件将不再执行if(GetASC() GetASC()-HasMatchingGameplayTag(FRPGGameplayTags::Get().Player_Block_InputHold)){return;}接着编译我们打开UE在技能将对应的事件阻挡即可实现对应事件的阻挡。
创建释放音效
在释放闪电链时我们需要对应的表现效果所以我们要创建GameplayCue蓝图来实现在每个客户端上进行效果表现。 我们打开标签管理器添加一个触发闪电链的标签。 接下来我们创建一个GameplayCueNotify_Static类型的蓝图这种蓝图调用不会生成Actor实例所以也没有生命周期管理它适合那种一次性的生效和粒子特效使用。 我们在类默认值这里设置触发标签 覆写OnExecute节点 在节点内我们设置播放一个音效在对应的角色位置触发即可 在技能里我们在播放蒙太奇动画之前调用播放GameplayCue设置对应标签和目标以及配置 接下来我们创建一个GameplayCueNotify_Actor蓝图用来实现闪电链的特效表现因为它需要目标的位置进行更新需要在场景中生成Actor表现效果能够持续一段时间相应的性能也会有更多的消耗。 打开GameplayCueNotify_Actor蓝图你会发现和GameplayCueNotify_Static不同因为GameplayCueNotify_Actor是继承AActor类的我们可以进行更新它在场景中的表现。 它也可以通过标签激活我们在GameplayCue标签下添加一个子标签用于激活此蓝图生成实例 并设置到当前蓝图配置的类默认值里 接着我们将移除时自动销毁打开 循环的GameplayCue记得在技能结束时将其移除。 接着我们要覆写while Active它和On Active区别在于可以持续调用激活更新内容On Active只能激活一次后无法修改。 我们在持续激活里通过配置项从配置项里获取鼠标拾取位置以及附着的组件生成Niagara系统并设置为变量在销毁时将粒子系统销毁掉并将鼠标拾取位置传递给Niagara系统。 在被移除时对粒子系统进行销毁
我们可以打开Niagara粒子系统查看为闪电链开始和结束的参数这个Niagara系统可以在底部加群获取一下。 在粒子系统默认值里显示为这样 接下来我们需要在技能里调用此GameplayCue我们需要首先获取到角色的武器组件从武器组件的对应位置发射闪电所以大家要注意命名是否在GameplayCue里设置正确。 我们在战斗接口里增加一个获取武器组件的函数 /*** 获取角色使用的武器指针* return 武器骨骼网格体组件*/UFUNCTION(BlueprintNativeEvent, BlueprintCallable)USkeletalMeshComponent* GetWeapon();在角色基类里覆写一下
virtual USkeletalMeshComponent* GetWeapon_Implementation() override;直接返回武器引用
USkeletalMeshComponent* ARPGCharacter::GetWeapon_Implementation()
{return Weapon;
}接着回到闪电链技能蓝图里我们在触发蒙太奇事件开始技能循环播放时通过函数获取骨骼组件并将鼠标拾取位置传入注意这里使用的生成GameplayCue的节点是AddGameplayCue On ActorLooping循环的。 由于它是循环的所以在技能结束时不会自动销毁我们在技能结束前的处理中移除Niagara System 接下来就是运行测试效果
一些优化
为了提高系统查询对应的GameplayCue的效率我们可以将GameplayCue的文件放入到统一的文件夹内 将文件移入到文件夹 我们可以在DefaultGame.ini通过手动指定目录来提高检索速度并且可以设置多行系统查找时会优先在这些文件夹内查询
提高信息同步数量
系统默认的信息同步数量为2这个数量稍微有些偏低我们可以通过设置来提高此数量 我们可以通过搜索MaxRPCPerNetUpdate找到GameplayCueManager来查看 找到对应的函数 打上断点在GameplayCue同步是我们能够查看到同步数量默认为2 我们在DefaultEngine文件里增加修改将每次复制的数量提高到10这个不需要太高每次同步10个基本上差不多可以根据项目需要修改。 接着运行查看每次复制的数量是否更新为了10