90. UE5 RPG 实现技能的装配

news2024/11/16 5:24:29

在上一篇里,我们实现了在技能面板,点击技能能够显示出技能的相关描述以及下一级的技能的对应描述。
在这一篇里,我们实现一下技能的装配。
在之前,我们实现了点击按钮时,在技能面板控制器里存储了当前选中的技能的相关信息,有了这个信息以后,我们在实现装配时,可以使用这个数据进行处理。
在这里插入图片描述
当选中技能后,我们接着点击下面的技能插槽时,如果符合装配条件,我们将实现装配到对应的插槽。
在这里插入图片描述

添加技能类型配置

在技能装配这里,我们分了主动技能和被动技能,不同类型的技能无法装配。所以,我们需要在技能数据里设置对应的类型,我们还是使用类型标签设置
我们在技能数据结构体内增加一个配置项,用于设置类型
在这里插入图片描述
然后在标签管理这里,增加三项,主动技能,被动技能和空

	FGameplayTag Abilities_Type_Offensive; //技能类型 主动技能
	FGameplayTag Abilities_Type_Passive; //技能类型 被动技能
	FGameplayTag Abilities_Type_None; //技能类型 空 受击等技能设置

并且注册到标签管理器

	/*
	 * 当前技能类型标签
	*/
	GameplayTags.Abilities_Type_Offensive = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Abilities.Type.Offensive"),
			FString("主动技能")
			);
	GameplayTags.Abilities_Type_Passive = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Abilities.Type.Passive"),
			FString("被动技能")
			);
	GameplayTags.Abilities_Type_None = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Abilities.Type.None"),
			FString("啥也不是")
			);

接着打开UE,在技能配置里,设置技能的对应类型
在这里插入图片描述

添加技能装配事件触发

我们也注意到,技能分为两大类型,主动技能和被动技能,为了区分它们,我们需要设置对应的类型标签。
首先在装配按钮这里添加可设置的类型变量,注意把眼睛打开。
在这里插入图片描述
然后在外部设置它对应的类型
在这里插入图片描述
被动技能也设置
在这里插入图片描述
然后给按钮绑定一个点击事件
在这里插入图片描述

添加技能装配处理逻辑

我们需要在技能面板控制器里增加一个点击按钮,用来实现点击事件

	UFUNCTION(BlueprintCallable)
	void EquipButtonPressed(const FGameplayTag& SlotTag, const FGameplayTag& AbilityType); //装配技能按钮按下事件

在实现这里,我首先判断条件是否达成,然后调用ASC里的实际处理技能装配的逻辑

void USpellMenuWidgetController::EquipButtonPressed(const FGameplayTag& SlotTag, const FGameplayTag& AbilityType)
{
	const FRPGGameplayTags GameplayTags = FRPGGameplayTags::Get();

	//获取装配技能的类型
	const FGameplayTag& SelectedAbilityType = AbilityInfo->FindAbilityInfoForTag(SelectedAbility.Ability).AbilityType;
	if(!SelectedAbilityType.MatchesTagExact(AbilityType)) return; //类型不同无法装配

	//获取装配技能的输入标签
	const FGameplayTag& SelectedAbilityInputTag = GetRPGASC()->GetInputTagFromAbilityTag(SelectedAbility.Ability);
	if(SelectedAbilityInputTag.MatchesTagExact(SlotTag)) return; //如果当前技能输入和插槽标签相同,证明已经装配,不需要再处理

	//调用装配技能函数,进行处理
	GetRPGASC()->ServerEquipAbility(SelectedAbility.Ability, SlotTag);
}

接着,我们在ASC里增加多个函数,用于实现这个逻辑,为什么在ASC里,因为GA是属于GAS系统的,GAS相关的内容就放在GAS相关的类里实现处理

	UFUNCTION(Server, Reliable) //在服务器处理技能装配,传入技能标签和装配的技能标签
	void ServerEquipAbility(const FGameplayTag& AbilityTag, const FGameplayTag& Slot); 
	
	UFUNCTION(Client, Reliable) //在客户端处理技能装配
	void ClientEquipAbility(const FGameplayTag& AbilityTag, const FGameplayTag& Status, const FGameplayTag& Slot, const FGameplayTag& PreviousSlot);

	void ClearSlot(FGameplayAbilitySpec* Spec); //清除技能装配插槽的技能

	void ClearAbilitiesOfSlot(const FGameplayTag& Slot); //根据输入标签,清除技能装配插槽的技能

	static bool AbilityHasSlot(FGameplayAbilitySpec* Spec, const FGameplayTag& Slot); //判断当前技能实例是否处于目标技能装配插槽

首先,我们看一下后面三个函数,他们是为了处理技能实现的函数。

首先是AbilityHasSlot,需要传入一个技能实例和一个输入标签(插槽标识),用来判断技能是否属于这个插槽

bool URPGAbilitySystemComponent::AbilityHasSlot(FGameplayAbilitySpec* Spec, const FGameplayTag& Slot)
{
	for(FGameplayTag Tag : Spec->DynamicAbilityTags)
	{
		if(Tag.MatchesTagExact(Slot))
		{
			return true;
		}
	}
	return false;
}

然后就是清除掉技能的装配的插槽,其实就是清除掉GA的输入标签

void URPGAbilitySystemComponent::ClearSlot(FGameplayAbilitySpec* Spec)
{
	const FGameplayTag Slot = GetInputTagFromSpec(*Spec);
	Spec->DynamicAbilityTags.RemoveTag(Slot);
	MarkAbilitySpecDirty(*Spec);
}

然后就是根据输入标签(插槽)清除掉所有技能的对应的插槽,这个会用到上面的两个函数。

void URPGAbilitySystemComponent::ClearAbilitiesOfSlot(const FGameplayTag& Slot)
{
	FScopedAbilityListLock ActiveScopeLock(*this);
	for(FGameplayAbilitySpec& Spec : GetActivatableAbilities())
	{
		if(AbilityHasSlot(&Spec, Slot))
		{
			ClearSlot(&Spec);
		}
	}
}

接下来就是装配函数,我们先获取到需要装配的技能实例,获取到当前装配的插槽和当前的技能的状态标签,然后将需要装配到的目标插槽的的技能清除掉,并将技能自身的插槽清除,并将对应的标签修改掉。然后触发客户端的调用,并及时将技能的修改复制到客户端(当前执行只在服务器运行,客户端不会运行,只需要将结果复制到即可)

void URPGAbilitySystemComponent::ServerEquipAbility_Implementation(const FGameplayTag& AbilityTag, const FGameplayTag& Slot)
{
	if(FGameplayAbilitySpec* AbilitySpec = GetSpecFromAbilityTag(AbilityTag))
	{
		const FGameplayTag& PrevSlot = GetInputTagFromSpec(*AbilitySpec); //技能之前装配的插槽
		const FGameplayTag& Status = GetStatusTagFromSpec(*AbilitySpec); //当前技能的状态标签

		//判断技能的状态,技能状态只有在已装配或者已解锁的状态才可以装配
		const FRPGGameplayTags GameplayTags = FRPGGameplayTags::Get();
		if(Status == GameplayTags.Abilities_Status_Equipped || Status == GameplayTags.Abilities_Status_Unlocked)
		{
			ClearAbilitiesOfSlot(Slot); //通过技能的输入标签清除掉插槽的技能
			ClearSlot(AbilitySpec); //清除掉当前技能的输入标签
			AbilitySpec->DynamicAbilityTags.AddTag(Slot); //将目标插槽的输入标签添加到技能实例的动态标签容器中

			//如果状态标签是已解锁,我们需要将其修改为已装配状态
			if(Status.MatchesTagExact(GameplayTags.Abilities_Status_Unlocked))
			{
				AbilitySpec->DynamicAbilityTags.RemoveTag(GameplayTags.Abilities_Status_Unlocked);
				AbilitySpec->DynamicAbilityTags.AddTag(GameplayTags.Abilities_Status_Equipped);
			}
			ClientEquipAbility(AbilityTag, Status, Slot, PrevSlot);
			MarkAbilitySpecDirty(*AbilitySpec); //立即将其复制到每个客户端
		}
	}
}

在客户端,我们只进行一个委托的广播,然后让控制器监听去修改
所以,我们增加一个技能装配后的委托

DECLARE_MULTICAST_DELEGATE_FourParams(FAbilityEquipped, const FGameplayTag& /*技能标签*/, const FGameplayTag& /*技能状态标签*/, const FGameplayTag& /*输入标签*/, const FGameplayTag& /*上一个输入标签*/);
FAbilityEquipped AbilityEquipped; //技能装配更新回调

然后在客户端执行的函数里进行调用

void URPGAbilitySystemComponent::ClientEquipAbility_Implementation(const FGameplayTag& AbilityTag, const FGameplayTag& Status, const FGameplayTag& Slot, const FGameplayTag& PreviousSlot)
{
	AbilityEquipped.Broadcast(AbilityTag, Status, Slot, PreviousSlot); //在客户端将更新后的标签广播
}

在控制器接收技能装配委托

接下来我们要在控制器实现对技能装配委托的监听,考虑到,技能装配后,在技能面板和Overlay里都需要使用它,我们将函数写到基类里,然后在对应的控制器里进行监听绑定。
我们在控制器基类增加一个委托回调函数

	//监听技能装配后的处理
	void OnAbilityEquipped(const FGameplayTag& AbilityTag, const FGameplayTag& Status, const FGameplayTag& Slot, const FGameplayTag& PreviousSlot) const;

然后,在实现里,我们通过传递过来的标签,实现技能数据的广播,然后在UI监听更新

void URPGWidgetController::OnAbilityEquipped(const FGameplayTag& AbilityTag, const FGameplayTag& Status, const FGameplayTag& Slot, const FGameplayTag& PreviousSlot) const
{
	const FRPGGameplayTags GameplayTags = FRPGGameplayTags::Get();

	//清除旧插槽的数据
	FRPGAbilityInfo LastSlotInfo;
	LastSlotInfo.StatusTag = GameplayTags.Abilities_Status_Unlocked;
	LastSlotInfo.InputTag = PreviousSlot;
	LastSlotInfo.AbilityTag = GameplayTags.Abilities_None;
	AbilityInfoDelegate.Broadcast(LastSlotInfo);

	//更新新插槽的数据
	FRPGAbilityInfo Info = AbilityInfo->FindAbilityInfoForTag(AbilityTag);
	Info.StatusTag = Status;
	Info.InputTag = Slot;
	AbilityInfoDelegate.Broadcast(Info);
}

我们在需要监听的派生类里,添加对其的监听

//监听技能装配的回调
	GetRPGASC()->AbilityEquipped.AddUObject(this, &USpellMenuWidgetController::OnAbilityEquipped);

编译打开代码,我们在数据接收这里,增加,在判断是否为对应插槽的更新数据,如果技能标签为空,则是清除旧插槽。
在这里插入图片描述
在技能按钮里,还需要多做一步操作就是处理技能冷却的监听处理
在这里插入图片描述

解决降级为0级技能还装配的问题

当技能等级降级为0级以后,技能的装配变为了无法装配,我们需要将此信息同步到技能栏和装配技能栏。
所以我们在降级函数中,将其增加一个委托,再调用客户端更新
在这里插入图片描述

接下来是效果展示
在这里插入图片描述

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

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

相关文章

企业高性能web服务器(nginx)

目录 Web服务器基础介绍 正常情况下的单次web服务器访问流程 Apache 经典的 Web服务端 Apache prefork 模型 Apache work模型 Apache event模型 服务端的I/O流程 服务器的I/O 磁盘I/O 网络I/O 网络I/O处理过程 I/O模型 I/O模型相关概念 同步/异步 阻塞/非阻塞 网…

Ant-Design-Vue快速入门+排坑全攻略:打造炫酷Vue应用的s实用指南!

Ant-Design-Vue 是一个基于 Vue.js 的高质量 UI 组件库,适用于企业级后台产品的快速开发。下面将提供一份快速上手指南,并分享一些常见的“坑”和解决方案。 一、Ant-Design-Vue 快速上手指南 1. 安装与引入 确保安装了 Node.js(推荐使用最新…

数据结构基础详解(C语言): 栈与队列的详解附完整代码

数据结构 栈 栈的核心重点: 栈是只能从表尾插入和删除的数据结构。 栈的顺序存储结构由两部分组成,top指针和数组。 链栈其实本质就是单链表头插法 文章目录 数据结构 栈1.栈的基本概念1.1 栈的常用操作 2.栈的存储结构2.1 栈的顺序存储结构2.1.1 栈的定…

环境配置1-MobaXterm服务器中Anaconda、Pytorch的安装

①登录 Login as 输入密码时密码不显示,正常输入即可 ②进入指定的下载目录 出现类似界面后,键盘操作Ctrlc即可进行输入 cd / …….(要下载到的目录名称)/ Anaconda的安装 ①输入wget https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux…

如何M3U8视频切片保存到本地,见过视频被别人拿走了吗?

一、数据来源分析 1.明确需求 网址:https://v.qq.com/x/cover/mzc002006n62s11/e0045s2g2eg.html 数据:视频 2.抓包分析 F12 刷新网站 搜索关键字找到对应的数据位置 M3U8 https://vd6.l.qq.com/proxyhttp 二、代码实现步骤 发送请求 …

Linux磁盘操作之df命令

Linux使用df命令,可以查看系统中每个文件系统的总容量、已使用空间、可用空间和使用率。这对于了解磁盘空间的使用情况以及判断是否需要清理或扩展磁盘空间非常有用。 参数说明 df是一个用于显示磁盘空间使用情况的命令,df是disk free的缩写&#xff0…

Kafka事件(消息、数据、日志)的存储

1、查看有关kafka日志配置文件的信息 2、查看kafka全部主题的日志文件 3、查看每个主题的日志文件 4、__consumer_offsets-xx文件夹的作用 package com.power;public class Test {public static void main(String[] args) {int partition Math.abs("myTopic".hashCo…

穿越时光的经典:从LeNet到ResNet,机器学习中的CNN架构进化史

在机器学习的浩瀚星空中,卷积神经网络(Convolutional Neural Networks, CNNs)无疑是最为耀眼的星辰之一,它们以其卓越的图像处理能力,在计算机视觉领域书写了无数辉煌篇章。从最初的简单架构到如今复杂而高效的模型&am…

嵌入式UI开发-lvgl+wsl2+vscode系列:9、控件(Widgets)(二)

一、前言 接下来我们总结第二部分的控件。 二、示例 1、image(图像) 1.1、示例1 #include "../../lv_examples.h" #if LV_USE_IMAGE && LV_BUILD_EXAMPLESvoid lv_example_image_1(void) {LV_IMAGE_DECLARE(img_cogwheel_argb);lv…

【算法进阶2-动态规划】最长公共子序列、欧几里得算法-分数、RSA算法-密码于加密

1 最长公共子序列 2 欧几里得算法 2.1 欧几里得算法-分数 3 RSA算法-密码于加密 1 最长公共子序列 -个序列的子序列是在该序列中删去若干元素后得 到的序列。 例:“ABCD”和“BDF”都是“ABCDEFG”的子序列最长公共子序列(LCS)问题:给定两个序列X和Y,求X和Y长度最大…

请你谈谈:async与await是如何控制异步操作的执行顺序

async/await 是 JavaScript 中用于处理异步操作的一种语法糖,它使得异步代码的编写、阅读和维护变得更加容易和直观。async 和 await 关键字是在 ES2017(ES8)中引入的,旨在简化基于 Promise 的异步操作。 1 async async 是一个函…

Pytorch如何指定device(cuda or cpu)例子解析

代码示例: 在PyTorch中,指定设备(CPU或CUDA)是一个非常重要的步骤,特别是当你在进行深度学习训练时。以下是一些指定设备的详细例子: 检查CUDA是否可用: 首先,你需要检查你的机器是否支持CUDA&…

【C++ Primer Plus习题】5.9

问题: 解答: #include <iostream> #include <cstring> using namespace std;#define SIZE 20int main() {string words[SIZE];string done "done";int count 0;while (true){cout << "请输入单词:" << endl;cin >> words…

2054. 骑马修栅栏

代码 #include<bits/stdc.h> using namespace std; int mp[505][505]; queue<int> ans; int du[505]; int n0,m,u,v;void dfs(int i) {for(int j1;j<n;j){if(mp[i][j]>1){mp[i][j]--;mp[j][i]--;dfs(j);}}ans.push(i); } int main() {cin>>m;for(int …

javaSpringBootmysql的大学生心理健康管理系统39182-计算机毕业设计项目选题推荐(附源码)

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;高校当然也不例外。大学生心理健康管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c…

一文搞懂不确定性原理

在讲这个之前&#xff0c;我们先要搞清楚&#xff0c;什么是不确定性原理&#xff1f;然后再解释它为什么就是不确定的呢&#xff1f; 我还模糊记得我第一次接触这个东西的时候&#xff0c;是在高中物理教材上面提了一下。其中印象最深的就是&#xff1a;动量确定&#xff0c;…

记录|Steam登录不上,打开速度慢等问题

目录 前言一、方法1二、方法2&#xff1a;cmd指令三、steam账号可以多台电脑一起用吗&#xff1f;更新时间 前言 参考视频&#xff1a; Steam登不上&#xff1f;商店打不开&#xff1f;多种方案助你解决问题&#xff01; 一、方法1 打开Steam的快捷方式的“属性”&#xff0c;…

[ICLR-24] LRM: Large Reconstruction Model for Single Image to 3D

[pdf | proj | code] 本文首次提出大型重建模型&#xff08;Large Reconstruction Model, LRM&#xff09;&#xff0c;实现5s内对单图物体的3D重建。在128张A100&#xff08;40G&#xff09;上训练30 epochs。 LRM包含三个部分&#xff0c;具体框架如下&#xff1a; 图片编码…

[C++] 初识 智能指针

标题&#xff1a;[C] 初识 智能指针 水墨不写bug 目录 一、前言 二、智能指针 1. 什么是RAII&#xff1f; 2.智能指针分类 三、智能指针简介 1.std::auto_ptr 2.std::unique_ptr 3.std::shared_ptr 正文开始&#xff1a; 一、前言 C智能指针的出现是有一定的背景的&am…

shell程序设计入门(三)

shell程序设计入门&#xff08;三&#xff09; 导语命令简单命令break:命令continueechoevalexecexitexprprintfreturnshift 复杂指令.exportsetunsettrapfindgrep 总结参考文献 导语 本篇介绍一些shell中常用的复杂命令及其使用&#xff0c;如set、echo、expr等命令 命令 简…