42. UE5 RPG 实现火球术伤害

上一篇,我们解决了火球术于物体碰撞的问题,现在火球术能够正确的和攻击目标产生碰撞。接下来,我们要实现火球术的伤害功能,在火球术击中目标后,给目标造成伤害。
实现伤害功能的思路是给技能一个GameplayEffect,在击中时,给目标应用GE。首先,我们在GameplayAbility(GA)身上增加一个设置GE的类,在创建火球时,在火球身上创建一个GE的实例,在火球击中目标后,使用GE的实例通过目标的ASC应用GE并造成伤害。
当然,这篇内容主要是为了测试,实际项目中药比这复杂,比如GE造成伤害时需要应用多少伤害。敌人生成时血量,防御,攻击力等等

创建GE

首先,我们创建一个新的GameplayEffect,在GE里面,我们用于测试,先不要实现复杂的伤害计算,直接固定减少20血量。
在这里插入图片描述

首先,我们要在火球的类里面增加一个变量,用于承载技能里面创建的GE
这里我们创建了一个GE实例的句柄,我们通过句柄可以获取到GE的实例等一些信息。
我们并将其设置为了蓝图可读写,并且在创建时,可以设置它在创建时可以设置其属性

	UPROPERTY(BlueprintReadWrite, meta=(ExposeOnSpawn = true)) //蓝图可读写,创建时需要将接口暴露出来方便设置
	FGameplayEffectSpecHandle DamageEffectHandle;

然后在我们之前做的技能发射器类里面,我们有一项TUDO项,就是给火球添加GE,接下来我们在技能里面实现GE实例的创建。
在ProjectileSpell文件中,我们先增加一个可以设置GE的参数,只能在蓝图中设置,可以在蓝图中读写。

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TSubclassOf<UGameplayEffect> DamageEffectClass;

然后在生成火球这里,获取到技能的ASC,通过ASC创建它的SpecHandle,并设置给我们生成的Projectile

//创建一个GE的实例,并设置给投射物
const UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetAvatarActorFromActorInfo());
const FGameplayEffectSpecHandle SpecHandle = SourceASC->MakeOutgoingSpec(DamageEffectClass, GetAbilityLevel(), SourceASC->MakeEffectContext());
Projectile->DamageEffectHandle = SpecHandle;

然后我们回到Projectile类里面,在它的碰撞体触发重叠事件时,如果当前客户端对Projectile类有绝对控制权,将获取到目标身上的ASC,然后应用我们设置的DamageEffect。ApplyGameplayEffectSpecToSelf需要的是GE的实际引用,而DamageEffectHandle是对GE的句柄,它的Data是对GE实例的弱指针,我们通过Get()函数获取到GE实例的实际指针,然后在前面加上*代表获取引用。

void AProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                  UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	//播放击中特效
	PlayImpact();

	//在重叠后,销毁自身
	if(HasAuthority())
	{
		//为目标应用GE
		if(UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OtherActor))
		{
			TargetASC->ApplyGameplayEffectSpecToSelf(*DamageEffectHandle.Data.Get());
		}
		
		Destroy();
	}
	else
	{
		//如果对actor没有权威性,将bHit设置为true,证明当前已经播放了击中特效
		bHit = true;
	}
}

测试效果

实际代码我们已经书写完成,接下来,我们要测试这样使用后,有没有效果。
我们打开AttributeSet属性集,在属性值变动后,使用UE_LOG打印对应的数据,可以查看到底谁掉血了

void UAttributeSetBase::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);

	FEffectProperties Props;
	SetEffectProperties(Data, Props);

	if(Data.EvaluatedData.Attribute == GetHealthAttribute())
	{
		SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
		UE_LOG(LogTemp, Warning, TEXT("%s 的生命值发生了修改,当前生命值:%f"), *Props.TargetAvatarActor->GetName(), GetHealth());
	}

	if(Data.EvaluatedData.Attribute == GetManaAttribute())
	{
		SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));
	}
}

接下来就是实现在敌人身上设置对应的属性值,方便我们测试,现在我们还没制作敌人的属性,正常游戏项目里面会使用数据驱动的方式,方便策划制作,不会像英雄身上的属性这样复杂,敌人的属性尤其是小怪的属性都是固定的。当时为了方便测试,我们这是使用角色的设置GE来实现敌人身上属性值的设置。
角色身上的属性值的设置是通过三个GE的设置

  1. 主要属性,为设置角色基础属性
  2. 次级属性,基于主要或其他次级属性设置自身属性
  3. 至关重要的属性,主要是血量和蓝量,在角色属性设置完成,出生时将血量和蓝量填满。
    在这里插入图片描述
    这初始化角色属性的函数现在是书写在角色基类里的,我们只需要在敌人的基类里面调用这个InitializeDefaultAttributes函数,然后在蓝图里面设置上对应的GE,就可以实现测试属性的添加。
    我们在敌人初始化ASC后,进行函数调用
void AEnemyBase::InitAbilityActorInfo()
{
	AbilitySystemComponent->InitAbilityActorInfo(this, this);
	Cast<UAbilitySystemComponentBase>(AbilitySystemComponent)->AbilityActorInfoSet();

	//通过GE初始角色的属性
	InitializeDefaultAttributes();
}

现在准备工作完成了,接下来编译UE,我们首先在火球术的技能上,设置创建的GE
在这里插入图片描述
然后在敌人的蓝图上面设置初始化属性的GE,我们创建了敌人的蓝图基类,可以在基类上面直接设置,那么,它的子类都会将此作为默认值。这里也犯懒了,懒得再创建新的了,直接使用英雄测试的GE来测试。
在这里插入图片描述
这里可以看到,我们一共攻击了BP_Goblin_Slingshot_C_3两次,每次减少20血
在这里插入图片描述
下面我将技能和技能创建的火球的源码列下来
ProjectileSpell

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "AbilitySystem/Abilities/GameplayAbilityBase.h"
#include "ProjectileSpell.generated.h"

class AProjectile;
/**
 * 
 */
UCLASS()
class AURA_API UProjectileSpell : public UGameplayAbilityBase
{
	GENERATED_BODY()

protected:

	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;

	UFUNCTION(BlueprintCallable, Category="Projectile")
	void SpawnProjectile(const FVector& ProjectileTargetLocation);
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<AProjectile> ProjectileClass;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TSubclassOf<UGameplayEffect> DamageEffectClass;
};

// 版权归暮志未晚所有。


#include "AbilitySystem/Abilities/ProjectileSpell.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "Actor/Projectile.h"
#include "Interaction/CombatInterface.h"


void UProjectileSpell::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                       const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
                                       const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
}

void UProjectileSpell::SpawnProjectile(const FVector& ProjectileTargetLocation)
{
	const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行
	if (!bIsServer) return;

	if (ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo()))
	{
		const FVector SocketLocation = CombatInterface->GetCombatSocketLocation();
		FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation(); //将方向转为旋转
		Rotation.Pitch = 0.f; //设置Pitch为0,转向的朝向将平行于地面
		
		FTransform SpawnTransform;
		SpawnTransform.SetLocation(CombatInterface->GetCombatSocketLocation());
		SpawnTransform.SetRotation(Rotation.Quaternion());
		
		//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
		AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(
			ProjectileClass,
			SpawnTransform,
			GetOwningActorFromActorInfo(),
			Cast<APawn>(GetOwningActorFromActorInfo()),
			ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

		//创建一个GE的实例,并设置给投射物
		const UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetAvatarActorFromActorInfo());
		const FGameplayEffectSpecHandle SpecHandle = SourceASC->MakeOutgoingSpec(DamageEffectClass, GetAbilityLevel(), SourceASC->MakeEffectContext());
		Projectile->DamageEffectHandle = SpecHandle;

		//确保变换设置被正确应用
		Projectile->FinishSpawning(SpawnTransform);
	}
}

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "GameplayEffectTypes.h"
#include "GameFramework/Actor.h"
#include "Projectile.generated.h"

class UNiagaraSystem;
class UProjectileMovementComponent;
class USphereComponent;

UCLASS()
class AURA_API AProjectile : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AProjectile();

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UProjectileMovementComponent> ProjectileMovement;

	UPROPERTY(BlueprintReadWrite, meta=(ExposeOnSpawn = true)) //蓝图可读写,创建时需要将接口暴露出来方便设置
	FGameplayEffectSpecHandle DamageEffectHandle;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void Destroyed() override;

	UFUNCTION()
	void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
private:

	//此物体的存在时间
	UPROPERTY(EditDefaultsOnly)
	float LifeSpan = 15.f;

	void PlayImpact() const;

	bool bHit;

	//碰撞球
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USphereComponent> Sphere;

	//击中粒子特效
	UPROPERTY(EditAnywhere)
	TObjectPtr<UNiagaraSystem> ImpactEffect;

	//击中音效
	UPROPERTY(EditAnywhere)
	TObjectPtr<USoundBase> ImpactSound;

	//移动循环音效
	UPROPERTY(EditAnywhere)
	TObjectPtr<USoundBase> LoopingSound;

	//储存循环音效的变量,后续用于删除
	UPROPERTY()
	TObjectPtr<UAudioComponent> LoopingSoundComponent;
};

// 版权归暮志未晚所有。


#include "Actor/Projectile.h"

#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "Aura/Aura.h"
#include "Components/AudioComponent.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
AProjectile::AProjectile()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	bReplicates = true; //服务器负责计算并更新Actor的状态,然后通过网络将这些更新复制到所有连接的客户端上。

	//初始化碰撞体
	Sphere = CreateDefaultSubobject<USphereComponent>("Sphere");
	SetRootComponent(Sphere); //设置其为根节点,
	Sphere->SetCollisionObjectType(ECC_PROJECTILE); //设置发射物的碰撞类型
	Sphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //设置其只用作查询使用
	Sphere->SetCollisionResponseToChannels(ECR_Ignore); //设置其忽略所有碰撞检测
	Sphere->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Overlap); //设置其与世界动态物体产生重叠事件
	Sphere->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Overlap); //设置其与世界静态物体产生重叠事件
	Sphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); //设置其与Pawn类型物体产生重叠事件

	//创建发射组件
	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovement");
	ProjectileMovement->InitialSpeed = 550.f; //设置初始速度
	ProjectileMovement->MaxSpeed = 550.f; //设置最大速度
	ProjectileMovement->ProjectileGravityScale = 0.f; //设置重力影响因子,0为不受影响
}

// Called when the game starts or when spawned
void AProjectile::BeginPlay()
{
	Super::BeginPlay();

	//设置此物体的存在时间
	SetLifeSpan(LifeSpan);
	
	Sphere->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::OnSphereOverlap);

	//添加一个音效,并附加到根组件上面,在技能移动时,声音也会跟随移动
	LoopingSoundComponent = UGameplayStatics::SpawnSoundAttached(LoopingSound, GetRootComponent());
}

void AProjectile::Destroyed()
{
	//如果没有权威性,并且bHit没有修改为true,证明当前没有触发Overlap事件,在销毁前播放击中特效
	if(!bHit && !HasAuthority())
	{
		//播放击中特效
		PlayImpact();
	}
	Super::Destroyed();
}

void AProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                  UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	//播放击中特效
	PlayImpact();

	//在重叠后,销毁自身
	if(HasAuthority())
	{
		//为目标应用GE
		if(UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OtherActor))
		{
			TargetASC->ApplyGameplayEffectSpecToSelf(*DamageEffectHandle.Data.Get());
		}
		
		Destroy();
	}
	else
	{
		//如果对actor没有权威性,将bHit设置为true,证明当前已经播放了击中特效
		bHit = true;
	}
}

void AProjectile::PlayImpact() const
{
	//播放声效
	UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
	//播放粒子特效
	UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
	//将音乐停止后会自动销毁
	if(LoopingSoundComponent) LoopingSoundComponent->Stop();
}





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/574947.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

akSmart大带宽服务器基础配置科普

在数字化时代&#xff0c;服务器的性能和网络带宽成为业务发展的关键因素。RakSmart作为知名的服务器提供商&#xff0c;其大带宽服务器备受用户青睐。那么&#xff0c;RakSmart大带宽服务器的基础配置究竟有哪些呢?本文将为您揭开这一神秘面纱。 首先&#xff0c;我们来看看R…

阿里云X魔搭社区Create@AI创客松第四届冠军:MumuLab

4月13日终于迎来了线下Demo Day&#xff0c;此前阿里云 X 魔搭社区 X Datawhale CreateAI创客松已经紧锣密鼓地准备了一个多月时间&#xff0c;全球150团队报名、创作出66作品、评选出25支团队进入决赛&#xff0c;作品范围覆盖从办公效率到法律调解再到游戏互动以及构建童话世…

添加阿里云yum源

添加阿里云yum源 要添加阿里云的 yum 源&#xff0c;可以执行以下步骤&#xff1a; 首先&#xff0c;备份你的现有 yum 源配置文件&#xff0c;以防止意外更改&#xff1a; sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup然后&#xf…

跟着Datawhale重学数据结构与算法(3)---排序算法

开源链接&#xff1a;【 教程地址 】【电子网站】 【写博客的目的是记录自己学习过程&#xff0c;方便自己复盘&#xff0c;专业课复习】 数组排序&#xff1a; #mermaid-svg-F3iLcKsVv8gcmqqC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16p…

【java】equals()方法

什么是Object类 Object类是所有类的始祖&#xff0c;在Java中每个类都是由它扩展而来的。 equals()方法 在 Object 类当中&#xff0c;equals()的底层是“”&#xff0c;String 类将其重写。 和equals() 有什么区别 对于基本数据类型而言&#xff0c;比较值是否相同&#x…

污水处理设备运维注意事项有哪些

污水处理设备的运维是确保污水处理效率和处理质量的关键环节。良好的运维不仅可以延长设备的使用寿命&#xff0c;还能确保污水处理过程的稳定性和可靠性。以下是一些污水处理设备运维的重要注意事项&#xff1a; 1. 定期检查和维护 设备检查&#xff1a;定期对污水处理设备进…

FFmpeg下载教程(Windows版)

文章目录 下载地址步骤 下载地址 https://ffmpeg.org/download.html 步骤

LeetCode53. 最大子数组和

LeetCode53. 最大子数组和 解题思路dp 代码 /* 数组长度n 9,连续的区间 那区间长度为1的区间数量是&#xff0c;9个 区间长度为2的区间数量是8个 区间长度为3的连续的区间数量为7个 .... 区间长度为9的区间数量为1个 */ class Solution { public:int maxSubArray(vector<…

文献速递:肺癌早期诊断---早期肺癌诊断:基于深度学习的循环外泌体光谱分析

Title 题目 Early-Stage Lung Cancer Diagnosis by Deep Learning-Based Spectroscopic Analysis of Circulating Exosomes 早期肺癌诊断&#xff1a;基于深度学习的循环外泌体光谱分析 Abstract 摘要 Lung cancer has a high mortality rate, but an early diagnosis can …

【极速前进】20240422:预训练RHO-1、合成数据CodecLM、网页到HTML数据集、MLLM消融实验MM1、Branch-Train-Mix

一、RHO-1&#xff1a;不是所有的token都是必须的 论文地址&#xff1a;https://arxiv.org/pdf/2404.07965.pdf 1. 不是所有token均相等&#xff1a;token损失值的训练动态。 ​ 使用来自OpenWebMath的15B token来持续预训练Tinyllama-1B&#xff0c;每1B token保存一个che…

【STM32+HAL+Proteus】系列学习教程---ADC(查询、中断、DMA模式下的电压采集)

实现目标 1、学会STM32CubeMX软件关于ADC的配置 2、掌握ADC三种模式&#xff08;查询、中断、DMA&#xff09;编程 3、具体目标&#xff1a;1、将开发板单片机采集到的电压值上传至上位机串口调试助手显示。 一、ADC 概述 1、什么是ADC? ADC&#xff08;Analog to Digit…

MyBatis入门学习二(配置文件、mapper文件、动态SQL)

目录 1、映射配置文件 1.1 properties 1.2 settings 1.3 typeAliases 1.4 typeHandlers 1.5 plugins 1.5 environments 1.6 mappers 2、映射Mapper文件 2.1 CRUD 2.1.1 select 2.1.2 Insert 2.1.3 update 2.1.4 delete 2.1.5 parameterType传入多个参数 2.1.5 …

图像在神经网络中的预处理与后处理的原理和作用(最详细版本)

1. 问题引出及内容介绍 相信大家在学习与图像任务相关的神经网络时&#xff0c;经常会见到这样一个预处理方式。 self.to_tensor_norm transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 具体原理及作用稍后解释&…

2024年vue 开发环境 Node.js于win10环境下的安装

2024年vue 开发环境 Node.js于win10环境下的安装 导航 文章目录 2024年vue 开发环境 Node.js于win10环境下的安装导航一、下载node.js二、安装node.js三、测试(一)四、环境配置五、测试(二)六、安装淘宝镜像七、安装vue脚手架 一、下载node.js Node.js 官方网站下载&#xff…

如何利用交易形态的失败进行现货黄金?

进行现货黄金理财&#xff0c;除了需要投资者对黄金投资有热情之外&#xff0c;有方法也是很重要的&#xff0c;光有热情而没有技术&#xff0c;我们的资金很可能会成为其他人的囊中之物。但如果有了现货黄金理财的技术&#xff0c;情况就可能扭转过来。下面我们就从买入的角度…

关于豆瓣电影数据抓取以及可视化

首先我们可以先了解以下网络爬虫的定义&#xff1a; 爬虫是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。它可以在互联网上自动抓取网页内容&#xff0c;将这些信息存储起来。爬虫可以抓取网站的所有网页&#xff0c;从而获取对于我们有价值的信…

BGP的路径属性

路径属性 l每条BGP路由都拥有多个的路径属性&#xff0c;有些是必须携带的&#xff0c;有些是可选添加的 lBGP的路径属性将影响最优路由的选择 lBGP路径属性是描述路由的一组参数&#xff0c;BGP根据路由的属性选择最佳路由&#xff0c;可以人为置值&#xff0c;以便执行路由…

【AI】Deepstream入门(2)Ubuntu20.04安装Deepstream

1、安装GPU驱动 本人显卡型号:RTX4060 Laptop(笔记本专用显卡) 【AI】惠普暗夜精灵9安装Ubuntu20.04+nvidia驱动 2、安装cuda、cuDNN 【AI】Ubuntu20.04安装cuda、cuDNN 3、安装TensorRT 1)下载 下载地址:https://docs.nvidia.com/deeplearning/tensorrt/archives/i…

vue报错:Do not mutate vuex store state outside mutation handlers.

vue报错&#xff1a;Do not mutate vuex store state outside mutation handlers. 原因&#xff1a;在vuex store的state外部直接修改了state的值&#xff0c;但是Vuex要求所有的state的修改必须在vuex中&#xff0c;不允许直接咋组件中修改store中的状态&#xff0c;除非通过m…

FPM 快速报表开发

背景&#xff1a; 使用FPM开发报表时&#xff0c;如果报表字段过多&#xff0c;页面拖拽等操作不方便 报表数量过多时&#xff0c;新建应用操作步骤较为繁琐 更习惯通过少量代码而非页面操作去实现功能 处理&#xff1a; 将FPM报表开发简化为类似GUI端ALV的开发过程:&#xff…