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


    在上一篇,我们实现了通过AI行为树控制战士敌人靠近攻击目标触发近战攻击技能,并在蒙太奇动画中触发事件激活攻击的那一刻的伤害判断,在攻击时,我们绘制了一个测试球体,用于伤害范围。
    在之前实现的火球术中,我们实现的是一个单体伤害技能,在近战中,我们想实现对范围内的敌人判断,并造成伤害。这也是RPG游戏的通常做法。
    所以,我们将在这篇中,实现角色的死亡逻辑,然后接着实现一个函数去获取一定范围内的敌人并造成伤害。

    实现死亡逻辑

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

    	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
    	bool IsDead() const; //获取当前角色是否死亡
    
    	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
    	AActor* GetAvatar(); //获取当前角色
    

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

    	/* 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战斗接口 结束 */
    

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

    protected:
    	// Called when the game starts or when spawned
    	virtual void BeginPlay() override;
    
    	bool bDead = false; //当前角色死亡状态
    

    实现前面创建的两个函数

    bool ARPGCharacter::IsDead_Implementation() const
    {
    	return bDead;
    }
    
    AActor* ARPGCharacter::GetAvatar_Implementation()
    {
    	return this;
    }
    

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

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

    我们现在需要实现一个函数来获取一定范围内的敌人。所以,在我们函数库增加一个新的函数用于获取。
    要实现这个功能,我们可以查找以下引擎的库里面是否包含此类型的函数,稍微修改一下,在GameplayStatics文件中,有个名为ApplyRadialDamageWithFalloff函数,它和我们所需的类型差不多,创建一个球的配置项,然后忽略掉一些Actor,然后进行查询,获取到对应的Actors,跟我们所需的效果一致,我们只需要返回即可。
    在这里插入图片描述
    我们创建一个蓝图库函数,可以通过此函数获取到所有攻击位置的Actor

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

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

    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类里面增加一个函数,用于给目标应用伤害

    	UFUNCTION(BlueprintCallable)
    	void CauseDamage(AActor* TargetActor);
    

    在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进行调用显示,这样,和这次技能有关的角色都会显示对应的伤害。

    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和黑板组件有没有被设置,如果存在才可以调用。

    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));
    }
    
  • 相关阅读:
    Mac MySQL初始登录root报错access denied解决方法
    Transphorm赢得美国能源部先进能源研究计划署的合同,提供具有双向电流和电压控制功能的新型四象限氮化镓开关
    15:00面试,15:08就出来了,问的问题有点变态。。。
    Python实现Catboost分类模型(CatBoostClassifier算法)项目实战
    Redis快速入门
    如何免费给PDF文件添加标注?
    一次 Keepalived 高可用的事故,让我重学了一遍它!
    基于PHP的线上购物商城,MySQL数据库,PHPstudy,原生PHP,前台用户+后台管理,完美运行,有一万五千字论文。
    工业品商城软件java和PHP哪个好?
    绘图(二)五子棋小游戏
  • 原文地址:https://blog.csdn.net/qq_30100043/article/details/139355635