痛みを世界に与えて!
哈哈,不开玩笑了。这一期我们实现一个类似Shinra Tensei的技能。技能可以将角色周围的敌人击飞,让他们感受痛苦!
首先大致描述一下技能的实现过程:
- 启动技能,PlayMontageAndWait。
- 通过TargetActor获取以角色为中心,球形碰撞体范围内的敌方角色,将数据传输给Gameplay Ability。
- 收到Anim Notify后,将得到数据中的全部敌人吸向角色。
- 收到Push Notify后,将敌人推开,让他们感受痛苦。
TargetActor
TargetActor是用来辅助Ability进行目标选取的,可以用来进行网络的数据传输。根据源码中的说明,TargetActor是比较消耗资源的。但是理论上我们同时启动的目标选择一般也不会很多。同时官方文档也强调了他们实现的基类是不靠谱的,建议自己通过subclass来实现类似功能避免消耗和安全性。
/**
* TargetActors are spawned to assist with ability targeting. They are spawned by ability tasks and create/determine the outgoing targeting data passed from one task to another
*
* WARNING: These actors are spawned once per ability activation and in their default form are not very efficient
* For most game
s you will need to subclass and heavily modify this actor, or you will want to implement similar functions in a game-specific actor or blueprint to avoid actor spawn costs
* This class is not well tested by internal games, but it is a useful class to look at to learn how target replication occurs
*/
代码实现
这一部分要实现一个类能够获取以释放技能者为中心,球形范围内的Actor,然后将这部分的数据提供给技能。
首先新建GameplayAbilityTargetActor的派生类。这里我们就直接把它命名为AbilityShinraTenseiTargetActor了。
打开AbilityShinraTenseiTargetActor.h。首先在public中创建派生类。重写两个方法StartTargeting和ConfirmTargetingAndContinue,然后创建一个float对象,表明技能的释放范围。
UCLASS()
class GAS_LEARN_API AAbilityShinraTenseiTargetActor : public AGameplayAbilityTargetActor
{
GENERATED_BODY()
public:
AAbilityShinraTenseiTargetActor();
virtual void StartTargeting(UGameplayAbility* Ability) override;
virtual void ConfirmTargetingAndContinue() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Ability Settings", meta=(ExposeOnSpawn=true))
float Radius;
};
重写StartTargeting
这个函数会在TargetActor开始寻找目标时运行。OwningAbility顾名思义,值的就是拥有这个TargetActor的Ability。MasterPC是控制角色的Controller,在教程中我们并没有用到CharacterController,我们的逻辑都是在Character中实现的,所以这个对象我们其实并不需要赋值。SourceActor指的是拥有调用TargeActor的Ability的Actor(套娃)。这三个对象都是在基类GameplayAbilityTargetActor中定义完成的。
void AAbilityShinraTenseiTargetActor::StartTargeting(UGameplayAbility* Ability)
{
OwningAbility = Ability;
MasterPC = Cast<APlayerController>(Ability->GetOwningActorFromActorInfo()->GetInstigatorController());
SourceActor = Ability->GetOwningActorFromActorInfo();
}
重写ConfirmTargetingAndContinue
函数中要实现的是获取技能范围内的敌人,将数据返回给Ability处理。
流程如下:
- 定义变量AbilityStartLocation用来存储技能释放位置,OverlapResults用来存储球形碰撞检测器的结果,OverlapActors用来记录在技能范围中的Actors,也是最终返回给技能的数据。
- 通过GetWorld()->OverlapMultiByObjectType建立一个球形碰撞器,如果成功,会将碰撞检测的结果写入OverlapResults。
- 如果碰撞成功,则将碰撞结果中的Actor存入OverlapActors。
- 如果OverlapActors的长度大于0,则将有效的数据通过Delegate广播出去。在Ability中就可以获取这些数据。
void AAbilityShinraTenseiTargetActor::ConfirmTargetingAndContinue()
{
//函数需要用到的对象
FVector AbilityStartLocation = FVector::ZeroVector;
TArray<FOverlapResult> OverlapResults;
TArray<TWeakObjectPtr<AActor>> OverlapActors;
//构建碰撞检测的params
FCollisionQueryParams QueryParams;
QueryParams.bTraceComplex = false;
QueryParams.bReturnPhysicalMaterial = false;
if(SourceActor)
{
QueryParams.AddIgnoredActor(SourceActor->GetUniqueID());
AbilityStartLocation = SourceActor->GetActorLocation();
}
//构建球形碰撞体,进行碰撞检测
bool TryOverlap = GetWorld()->OverlapMultiByObjectType(
OverlapResults,
AbilityStartLocation,
FQuat::Identity,
FCollisionObjectQueryParams(ECC_Pawn),
FCollisionShape::MakeSphere(Radius),
QueryParams);
//如果发生碰撞,将碰撞的actor写入OverlapActors
if(TryOverlap)
{
for(const FOverlapResult& Result: OverlapResults)
{
APawn* OverlappedPawn = Cast<APawn>(Result.Actor);
if(IsValid(OverlappedPawn))
{
OverlapActors.AddUnique(OverlappedPawn);
}
}
}
//如果OverlapActors存在数据,将数据通过delegate广播
if(OverlapActors.Num() > 0)
{
const FGameplayAbilityTargetDataHandle TargetData = StartLocation.MakeTargetDataHandleFromActors(OverlapActors);
TargetDataReadyDelegate.Broadcast(TargetData);
}
}
角色蓝图
打开自己建立的角色蓝图。新建Custom Event,命名为Push Character,实现的功能是向指定的方向施加指定力量的Impluse,用于击飞或拉动角色。实现这个功能的方法是调用Character Movement Component中的Add Impulse函数。
新建Custom Event,命名为ShinraTensei,它有一个bool变量的输入。这个事件用来表明技能的状态,当调用该事件且输入为true,表明此时需要将敌人拉向自己。当调用该事件且输入为false时,表明此时需要将敌人击飞。
当输入为true,则向自己的ASC发送一个Gameplay Event,标签为Ability.ShinraTensei.Pull,表明将敌人向角色拉动。当输入为false中,发送Event带有标签Ability.ShinraTensei.Push,表明此时要将敌人击飞。
Montage Notify
自己选择一个动画创建Montage,然后在合适的位置新建Notify,分别命名为Pull Character和Push Character。
打开角色动画蓝图。这里调用的就是刚才实现的custom Event,收到Pull Notify后,调用ShinraTensei(State=true)。收到Push Notify后,调用ShinraTensei(State=false)
Shinra Tensei Ability
Gameplay Effect
创建需要的Gameplay Effect。效果是对敌人造成伤害。我直接用了最简单的方法,通过Scalable Float直接造成50点伤害。
Gameplay Cue
创建Gameplay Cue,在这里我们通过GC播放台词。
实现的功能是在角色的Root Component上生成一个sound,播放技能语音。注意别忘了设置Tag。
BP_TargetData
创建在前面实现的TargetActor的蓝图派生类。
Gameplay Ability
创建gameplay ability命名为GA_ShinraTensei。讲解下第一张图图里的流程。
- 通过Task PlayMontageAndWait播放montage
- 通过Add GameplayCue to Owner,调用播放声音的GC。
- 然后等待Character蓝图中发送的Event,即montage中的Pull Character Notify。
- 最后调用Task Wait Target Data,选择的class为我们刚才创建的BP_ShinraTensei_TargetActor,注意,在这里设置技能范围为1000。
第二张图:
- 接受到TargetActor发送的Valid Data后,取出其中的Actor。
- 拉动角色的方向计算通过将两个位置信息vector相减即可。
- 调用Push Character拉动角色。
第三张图:
- 等待Montage中的Push Character Notify。
- 同样计算角色的击飞方向,区别是将之前位置信息相减顺序互换,同时在方向上增加一个UpVector,负责让角色除了后退,也向天上飞。
- 应用Gameplay Effect造成伤害。