62. UE5 RPG 近战攻击获取敌人并造成伤害

在上一篇,我们实现了通过AI行为树控制战士敌人靠近攻击目标触发近战攻击技能,并在蒙太奇动画中触发事件激活攻击的那一刻的伤害判断,在攻击时,我们绘制了一个测试球体,用于伤害范围。

在之前实现的火球术中,我们实现的是一个单体伤害技能,在近战中,我们想实现对范围内的敌人判断,并造成伤害。这也是RPG游戏的通常做法。

所以,我们将在这篇中,实现角色的死亡逻辑,然后接着实现一个函数去获取一定范围内的敌人并造成伤害。

实现死亡逻辑

在敌人接口这里,我们增加两个函数,一个用于判断角色是否死亡,另一个获取角色的Avatar,它们都被设置了BlueprintNativeEvent,它会通过蓝图初始化虚函数,并且可以在蓝图中覆写(如果在蓝图中覆写,C++版本的实现将会失效)

cpp 复制代码
	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
	bool IsDead() const; //获取当前角色是否死亡

	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
	AActor* GetAvatar(); //获取当前角色

由于每个角色都需要此功能,我们将在角色基类上面去修改它,首先覆盖它们的父类函数

cpp 复制代码
	/* ICombatInterface战斗接口 */
	virtual UAnimMontage* GetHitReactMontage_Implementation() override;
	virtual FVector GetCombatSocketLocation_Implementation() const override;
	virtual bool IsDead_Implementation() const override;
	virtual AActor* GetAvatar_Implementation() override;
	virtual void Die() override;
	/* ICombatInterface战斗接口 结束 */

创建一个变量,来记录当前是否死亡

cpp 复制代码
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	bool bDead = false; //当前角色死亡状态

实现前面创建的两个函数

cpp 复制代码
bool ARPGCharacter::IsDead_Implementation() const
{
	return bDead;
}

AActor* ARPGCharacter::GetAvatar_Implementation()
{
	return this;
}

我们现在能获取了,还需要一个设置死亡的地方,我们在实现角色死亡这里实现了一个死亡函数,它在内部调用触发每个客户端都会运行此函数,我们可以把设置逻辑写到此函数中。

在函数底部增加设置死亡的变量

实现获取范围内的敌人函数

我们现在需要实现一个函数来获取一定范围内的敌人。所以,在我们函数库增加一个新的函数用于获取。

要实现这个功能,我们可以查找以下引擎的库里面是否包含此类型的函数,稍微修改一下,在GameplayStatics文件中,有个名为ApplyRadialDamageWithFalloff函数,它和我们所需的类型差不多,创建一个球的配置项,然后忽略掉一些Actor,然后进行查询,获取到对应的Actors,跟我们所需的效果一致,我们只需要返回即可。

我们创建一个蓝图库函数,可以通过此函数获取到所有攻击位置的Actor

cpp 复制代码
	//获取到攻击位置半径内的所有动态Actor
	UFUNCTION(BlueprintCallable, Category="MyAbilitySystemLibrary|GameplayMechanics")
	static void GetLivePlayersWithinRadius(const UObject* WorldContextObject, TArray<AActor*>& OutOverlappingActors, const TArray<AActor*>& ActorsToIgnore, float Radius, const FVector& SphereOrigin);

在实现这里,仿造官方库的写法,获取到所有与设置的碰撞体碰撞的数组,然后对数据遍历,将所需的对象返回

cpp 复制代码
void URPGAbilitySystemBlueprintLibrary::GetLivePlayersWithinRadius(const UObject* WorldContextObject,
	TArray<AActor*>& OutOverlappingActors, const TArray<AActor*>& ActorsToIgnore, float Radius,
	const FVector& SphereOrigin)
{
	FCollisionQueryParams SphereParams; //创建一个碰撞查询的配置
	SphereParams.AddIgnoredActors(ActorsToIgnore); //添加忽略的Actor
	
	TArray<FOverlapResult> Overlaps; //创建存储检索到的与碰撞体产生碰撞的Actor
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) //获取当前所处的场景,如果获取失败,将打印并返回Null
	{
		//获取到所有与此球体碰撞的动态物体
		World->OverlapMultiByObjectType(Overlaps, SphereOrigin, FQuat::Identity, FCollisionObjectQueryParams(FCollisionObjectQueryParams::InitType::AllDynamicObjects), FCollisionShape::MakeSphere(Radius), SphereParams);
		for(FOverlapResult& Overlap : Overlaps) //遍历所有获取到的动态Actor
		{
			//判断当前Actor是否包含战斗接口   Overlap.GetActor() 从碰撞检测结果中获取到碰撞的Actor
			const bool ImplementsCombatInterface =  Overlap.GetActor()->Implements<UCombatInterface>();
			//判断当前Actor是否存活,如果不包含战斗接口,将不会判断存活(放置的火堆也属于动态Actor,这样保证不会报错)
			if(ImplementsCombatInterface && !ICombatInterface::Execute_IsDead(Overlap.GetActor())) 
			{
				OutOverlappingActors.AddUnique(Overlap.GetActor()); //将Actor添加到返回数组,AddUnique 只有在此Actor未被添加时,才可以添加到数组
			}
		}
	}
}

接着我们在技能类里面,调用创建的此函数,中心设置为在角色上面设置的攻击位置,将自身传入忽略的数组,设置好半径,然后将返回的数组遍历,绘制测试图形,记得在遍历结束后,结束此技能。

接着查看攻击效果,可以看出,攻击时,不但可以在玩家角色身上绘制测试图形,也可以在敌人身上绘制。

应用GE

现在我们能够获得到需要造成伤害的Actor,那么接下来,我们将实现伤害的应用。按照我们之前的经验 ,我们需要一个GameplayEffect去对攻击目标造成伤害,在前面的章节49. UE5 RPG 使用Execution Calculations处理对目标造成的最终伤害我们实现了一个造成伤害的GE。并且在54. UE5 RPG 增加伤害类型 增加了技能可以造成多种属性的伤害,我们可以给近战攻击技能直接使用这个GE。

我们需要做的就是创建GE的实例,然后使用SetByCaller将技能的伤害数值传递给GE的实例,接下来,我们将在技能蓝图中通过连连看实现此功能,然后在c++中实现我们实际需要使用的函数。

首先,我们先配置技能的造成伤害的GE,并设置当前技能造成的伤害,我们这里设置的造成物理伤害,并且我们需要在曲线表格中新增加一条伤害曲线使用。

伤害我们设置了从1级到40级的伤害

完成了准备工作,接下来,我们就可以修改蓝图的逻辑,在获取到攻击目标后,我们遍历攻击目标数组,从伤害类型中,获取到所有的Keys,然后遍历获取到对应的Value(在蓝图无法直接遍历),我这个现在写的还有问题,就是给每个类型的伤害创建的一个Spec,如果只设置了一种类型的伤害,那么效果是一致的。这里主要也是给展示一下如何实现。

根据等级获取技能伤害节点是通过代码实现的节点,卸载蓝图函数库中

那么接下来,我们在C++中实现这个逻辑,这样在蓝图中只需要调用一个方法即可。

我们在RPGDamageGameplayAbility.h类里面增加一个函数,用于给目标应用伤害

cpp 复制代码
	UFUNCTION(BlueprintCallable)
	void CauseDamage(AActor* TargetActor);

在cpp文件中实现它

cpp 复制代码
void URPGDamageGameplayAbility::CauseDamage(AActor* TargetActor)
{
	//创建GE
	FGameplayEffectSpecHandle DamageSpecHandle = MakeOutgoingGameplayEffectSpec(DamageEffectClass, 1.f);
	//通过SetByCaller设置属性伤害
	for(auto Pair : DamageTypes)
	{
		const float ScaleDamage = Pair.Value.GetValueAtLevel(GetAbilityLevel());
		UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(DamageSpecHandle, Pair.Key, ScaleDamage);
	}
	//将GE应用给目标
	GetAbilitySystemComponentFromActorInfo()->ApplyGameplayEffectSpecToTarget(
		*DamageSpecHandle.Data.Get(),
		UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor));
}

编译打开UE,在蓝图中修改成我们创建的节点,针对每个目标调用一次函数即可。

处理玩家角色被敌人攻击不显示伤害数字的问题

我们在被敌人攻击后,无法在角色身上显示伤害的数字,我们去查看一下在AttributeSet里面如何创建的伤害数字的显示。

在显示伤害数字函数中,我们是通过获取到角色的PlayerController,然后通过调用PlayerController身上的函数来实现的。如果是敌人攻击玩家角色,我们是无法在敌人身上获取到PlayerController的。

所以这里的修改可以修改为,如果从SourceCharacter无法获取到PlayerController,那么我们判断一下目标角色身上获取,然后从目标角色身上获取PlayerController进行调用显示,这样,和这次技能有关的角色都会显示对应的伤害。

cpp 复制代码
void URPGAttributeSet::ShowFloatingText(const FEffectProperties& Props, const float Damage, bool IsBlockedHit, bool IsCriticalHit)
{
	//调用显示伤害数字
	if(Props.SourceCharacter != Props.TargetCharacter)
	{
		//从技能释放者身上获取PC并显示伤害数字
		if(ARPGPlayerController* PC = Cast<ARPGPlayerController>(Props.SourceCharacter->Controller))
		{
			PC->ShowDamageNumber(Damage, Props.TargetCharacter, IsBlockedHit, IsCriticalHit); //调用显示伤害数字
		}
		//从目标身上获取PC并显示伤害数字
		if(ARPGPlayerController* PC = Cast<ARPGPlayerController>(Props.TargetCharacter->Controller))
		{
			PC->ShowDamageNumber(Damage, Props.TargetCharacter, IsBlockedHit, IsCriticalHit); //调用显示伤害数字
		}
	}
}

接着运行查看效果。

处理多人模式下的问题

我们将运行模式修改,数量修改为两人

运行起来后,发现敌人被攻击后,会报错,原因是因为AIController为null

我们加个条件判断即可,判断AIController和黑板组件有没有被设置,如果存在才可以调用。

cpp 复制代码
void ARPGEnemy::HitReactTagChanged(const FGameplayTag CallbackTag, int32 NewCount)
{
	bHitReacting = NewCount > 0;
	GetCharacterMovement()->MaxWalkSpeed = bHitReacting ? 0.f : BaseWalkSpeed;
	//设置黑板键的值
	if(RPGAIController && RPGAIController->GetBlackboardComponent())
	{
		RPGAIController->GetBlackboardComponent()->SetValueAsBool(FName("HitReacting"), bHitReacting);
	}
	// GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, FString::Printf(TEXT("Hit React bool: %i"), bHitReacting));
}
相关推荐
电子云与长程纠缠10 小时前
UE5.3中通过编辑器工具创建大纲菜单文件夹
java·ue5·编辑器
DBBH20 小时前
UE5 第三人称学习之动画 control rig
ue5
UTwelve20 小时前
【UE5】一种老派的假反射做法,可以用于移动端,或对反射的速度、清晰度有需求的地方
ue5·虚幻引擎·着色器·虚幻4
UTwelve1 天前
【UE5】可以实时绘制的体积渲染 【第三章 体积纹理绘制 - 3.绘制体积】
ue5
1204157137 肖哥1 天前
UE5.4 PCG基础节点
ue5
DBBH2 天前
UE5 材质篇 1 如何偏移顶点
ue5·材质
孤客网络科技工作室2 天前
虚幻引擎5(UE5)学习教程
java·学习·ue5
暮志未晚Webgl2 天前
105. UE5 GAS RPG 搭建主菜单
ue5
异次元的归来2 天前
UE5相机系统初探(一)
ue5·游戏引擎·camera
DBBH4 天前
UE5 材质篇 0 创建一个材质
ue5