42. UE5 RPG 实现火球术伤害

news2024/12/27 18:10:12

上一篇,我们解决了火球术于物体碰撞的问题,现在火球术能够正确的和攻击目标产生碰撞。接下来,我们要实现火球术的伤害功能,在火球术击中目标后,给目标造成伤害。
实现伤害功能的思路是给技能一个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();
}





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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA毕业设计136—基于Java+Springboot+Vue的房屋租赁管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的房屋租赁管理系统(源代码数据库)136 一、系统介绍 本项目前后端分离&#xff0c;分为管理员、用户、工作人员、房东四种角色 1、用户/房东&#xff1a; …

正态性检验

t检验、方差分析&#xff08;ANOVA&#xff09;等参数检验都有一个共同的前提条件&#xff1a;样本数据必须服从正态分布&#xff0c;即样本数据必须来源于一个正态分布的总体&#xff0c;若样本数据不服从正态分布&#xff0c;就不能用以上参数检验对数据进行分析&#xff0c;…

OpenCV鼠标绘制线段

鼠标绘制线段 // 鼠标回调函数 void draw_circle(int event, int x, int y, int flags, void* param) {cv::Mat* img (cv::Mat*)param;if (event cv::EVENT_LBUTTONDBLCLK){cv::circle(*img, cv::Point(x, y), 100, cv::Scalar(0, 0, 255), -1);} }// 鼠标回调函数 void dra…

.NET 个人博客-添加RSS订阅功能

个人博客-添加RSS订阅功能 前言 个人博客系列已经完成了 留言板文章归档推荐文章优化推荐文章排序 博客地址 然后博客开源的原作者也是百忙之中添加了一个名为RSS订阅的功能&#xff0c;那么我就来简述一下这个功能是干嘛的&#xff0c;然后照葫芦画瓢实现一下。 RSS简述…

SpringBoot+RabbitMQ实现MQTT协议通讯

一、简介 MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上&#xff0c;是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议&#xff0c;为此&#xff0c;它需要一个消息中间件 。此…

阿斯达年代记游戏下载教程 阿斯达年代记下载教程

《阿斯达年代记&#xff1a;三强争霸》作为一款气势恢宏的MMORPG大作&#xff0c;是Netmarble与STUDIO DRAGON强强联合的巅峰创作&#xff0c;定于4月24日迎来全球玩家热切期待的公测。游戏剧情围绕阿斯达大陆的王权争夺战展开&#xff0c;三大派系——阿斯达联邦、亚高联盟及边…

浅谈菊风实时音视频 (RTC)与实时操作系统 (RTOS) 在智能硬件领域应用

近年来&#xff0c;菊风通过实时音视频赋能智能手表、智能门禁、智能门锁/门铃、智能眼镜等数十种智能硬件&#xff0c;与一众合作伙伴共同探索在IoT智能硬件领域的不同场景应用&#xff0c;积累了丰富的实践经验。在智能硬件中&#xff0c;RTOS因其轻量化的系统内核&#xff0…

使用Mybatis映射时间 DateTime ==> LocalDateTime

首先查看&#xff0c;数据库字段&#xff1a; 书写映射实体类对象VO&#xff1a; Data public class OrderListVO implements Serializable {private Integer orderId;private String memberName;private String orderNumber;private BigDecimal orderPrice;private String l…

element-ui upload 组件 手动多次出发 submit

element 上传组件 upload 上传成功以后&#xff0c;想重新 调用 submit()函数&#xff0c;发现是不可以进行多次触发的,。 直接上解决方法&#xff0c;在上传成功后的钩子函数里添加:fileList[0l.status ready fileList是文件列表&#xff0c;status是单文件的状态改成ready就…

全栈从0到1 3D旅游地图标记和轨迹生成

功能演示 演示视频 体验地址 Vercel App 开发技术栈&#xff1a; NextJs&#xff08;前端框架&#xff09;React&#xff08;前端框架&#xff09;TailwindCSS &#xff08;CSS样式&#xff09;echart echart gl &#xff08;地图生成&#xff09;shadui&#xff08;UI组件…

机器视觉系统-工业光源什么是无影光

光路描述&#xff1a;通过结构或漫射板改变光路&#xff0c;最终发光角度包含了高角度 和低角度。 效果分析&#xff1a;兼具了高角度光和低角度光的效果&#xff0c;使被测物得到了多角度的照射&#xff0c;表面纹理、皱褶被弱化&#xff0c; 图像上整体均匀。 主要应用&#…

linux 上 jps 列出一堆 jar,如何快速定位 jar 文件启动位置?

例如&#xff0c;在 /data下有一个 xxx.jar &#xff0c;如果是通过 "java -jar /data/xxx.jar" 方式启动&#xff0c;则 jps会列出的名字中带 xxx.jar&#xff0c;这时再 "ps -ef | grep xxx.jar" 就会列出 更详细的信息&#xff0c;例如 "java -ja…

Spring Kafka——基于 Spring Kafka 实现动态管理 Kafka 连接和 topic 的监听

文章目录 使用 Spring Kafka 动态管理 Kafka 连接和主题监听1. 前言2. 简单的消费程序配置3. Spring Kafka 主要的相关类的说明4. KafkaListener 注解的加载执行流程解析5. 动态监听消费订阅的设计与实现 使用 Spring Kafka 动态管理 Kafka 连接和主题监听 文章内容较长&#x…

Windows电脑中护眼(夜间)模式的开启异常

我的电脑是联想小新16pro&#xff0c;Windows11版本。之前一直可以正常使用夜间模式&#xff0c;但是经过一次电脑的版本更新之后&#xff0c;我重启电脑发现我的夜间模式不能使用了。明明显示开启状态&#xff0c;但是却不能使用&#xff0c;电脑还是无法显示夜间模式。 询问…

59、回溯-括号生成

思路&#xff1a; 括号是成对出现&#xff0c;首先左括号可以放n个&#xff0c;右括号也可以放n个。如果当前左括号放了3了&#xff0c;右括号放了4个&#xff0c;错了&#xff0c;如果左括号放了5个&#xff0c;右括号放了4个。可以&#xff0c;继续放右括号即可。所以可以设…

每日一题:跳跃游戏II

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到达 nums[n - 1] 的最…

【Linux系统化学习】死锁 | 线程同步

目录 死锁 死锁的必要条件 避免死锁 线程同步 条件变量 同步概念和竞态条件 条件变量接口 创建和初始化条件变量 等待条件满足 唤醒等待 毁条件变量 为什么 pthread_cond_wait 需要互斥量? 条件变量使用规范 等待条件代码 给条件发送信号代码 死锁 死锁是指在一…

深度探讨容器化技术在网络安全中的应用与挑战

随着容器化技术的快速发展&#xff0c;尤其是Docker与Kubernetes&#xff08;K8s&#xff09;的广泛应用&#xff0c;企业IT架构正经历着从传统虚拟机向轻量级容器的深刻变革。容器化技术为提升资源利用率、加速应用部署及维护提供了强大支持&#xff0c;但同时也给网络安全带来…

用 VMare Workstation 搭建 esxi --- (一)创建 exsi 虚拟机

用 VMare Workstation 搭建 esxi 文章目录 用 VMare Workstation 搭建 esxi创建虚拟机 创建虚拟机

企业微信代开发应用登录操作

首先声明&#xff1a;企微的文档写得真烂&#xff01;&#xff01;&#xff01;有一些问题&#xff0c;官方情愿在问答区给用户一个个解答&#xff0c;也不愿意在文档写清楚&#xff0c;生怕自己工作量不饱和被优化。 概念说明 代开发应用&#xff0c;是相对于自建应用来说的。…