28. UE5 RPG同步面板属性(四)

在前面几篇中,我们实现了以下步骤:

  1. 首先我们需要通过c++去实现创建GameplayTag,这样可以在c++和UE里同时获取到Tag
  2. 创建一个DataAsset类,用于设置tag对应的属性和显示内容
  3. 创建AttributeMenuWidgetController实现对应逻辑

上面几步在前几篇文章中,我们都基本实现了,只有AttributeMenuWidgetController对应的逻辑没有实现。

在这一篇里面,我们将实现对应的逻辑,实现对UI数据的同步。

测试委托

在将所有的数据同步之前,我们先创建一个属性进行同步,来测试数据是否能够成功传递。首先,我们先拿力量属性测试。

首先我们从FMyGameplayTags单例里面获取力量的属性标签,然后通过标签获取到DataAsset里面的数据结构体,然后从AS里面获取到力量值,设置给结构体,并广播出去。

cpp 复制代码
	const FGameplayTag& Tag = FMyGameplayTags::Get().Attributes_Primary_Strength;
	FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(Tag);
	Info.AttributeValue = AS->GetStrength();
	AttributeInfoDelegate.Broadcast(Info);

这样,结构体内就保存了需要UI显示的初始数据,并且,我们将整个结构体广播了出去。

我们接着在WBP_TextValueRow蓝图里面,增加委托监听,将会返回一个结构体,可以从结构体内获取到四项内容。

然后我们通过结构体拿到数据,设置UI即可,后面的设置显示名称和数字,直接封装成了函数

接着运行项目,打开属性面板,会发现所有属性都换成了对应的名称

我们需要一个方法去判断是否需要更改,因为现在这种方式只要触发回调就会更新,我们可以为Widget增加一个Tag变量,可以单独去设置对应的变量值

在委托回调这里,判断标签是否相同,只有是我们设置的相同的标签的情况下,才会更新

将属性面板的名称修改掉,方便查看

对每个Widget的属性单独去修改

接着运行,就会发现,我们设置的那一项才更新了信息。

我们要将所有的属性都广播出去,我们可以一个个写下去,如下这样,但是这样重复代码多,代码量增加,不好维护,所以我们需要一种新的方式去实现。

cpp 复制代码
void UAttributeMenuWidgetController::BroadcastInitialValues()
{
	const UAttributeSetBase* AS = Cast<UAttributeSetBase>(AttributeSet);

	check(AttributeInfo);

	FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(FMyGameplayTags::Get().Attributes_Primary_Strength);
	Info.AttributeValue = AS->GetStrength();
	AttributeInfoDelegate.Broadcast(Info);

	Info = AttributeInfo->FindAttributeInfoForTag(FMyGameplayTags::Get().Attributes_Primary_Intelligence);
	Info.AttributeValue = AS->GetIntelligence();
	AttributeInfoDelegate.Broadcast(Info);
}

接下来,我们将使用Map并实现对其的遍历实现修改。

在AttributeSetBase.h文件里面增加一个委托宏DECLARE_DELEGATE_RetVal ,这个委托宏和之前的区别是它有返回值。

cpp 复制代码
DECLARE_DELEGATE_RetVal(FGameplayAttribute, FAttributeSignature);

接着,我们给AS添加一个变量属性,类型为Map,key为Tag标签,Value为对应的委托

cpp 复制代码
TMap<FGameplayTag, FAttributeSignature> TagsToAttributes;

在AS的构造函数中,我们创建有返回的委托需要绑定一个函数才可以后续返回对应的值绑定静态函数可以使用BindStatic(),如果非静态函数需要设置成员地址比如BindUObject(this, &MyClass::MyFunction),然后将委托添加到Map中,这里我们先添加两个用于测试。

cpp 复制代码
UAttributeSetBase::UAttributeSetBase()
{
	const FMyGameplayTags& GameplayTags = FMyGameplayTags::Get();

	FAttributeSignature StrengthDelegate;
	StrengthDelegate.BindStatic(GetStrengthAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Primary_Strength, StrengthDelegate);

	FAttributeSignature IntelligenceDelegate;
	IntelligenceDelegate.BindStatic(GetIntelligenceAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Primary_Intelligence, IntelligenceDelegate);
}

接着回到AttributeMenuController里面,在controller里面,我们将之前写的代码替换掉,修改为使用对Map的遍历实现对属性值的广播。

首先获取到Key,Map的Key就是属性对应的Tag,通过Tag去获取DataAsset里面存储的属性数据,然后通过value获取到属性,通过属性的GetNumericValue(AS)方法获取到对应值,再将这个属性信息结构体广播出去。

cpp 复制代码
	for (auto& Pair : AS->TagsToAttributes)
	{
		FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(Pair.Key);
		FGameplayAttribute Attr = Pair.Value.Execute();
		Info.AttributeValue = Attr.GetNumericValue(AS);
		AttributeInfoDelegate.Broadcast(Info);
	}

编译打开UE,查看属性面板

委托的方式是一种安全的方式,相对来说,创建的委托数量也比较多,我们还有一种相对比较简单的方式。首先我写一个例子

(*attr)代表我们声明了变量名称,前面加星代表我们声明的是函数指针,第二个空()代表这个函数使用时不需要传入参数

cpp 复制代码
	FGameplayAttribute (*attr)();
	attr = GetIntelligenceAttribute;// 隐式转换,函数名被解释为函数指针

	float attv = attr().GetNumericValue(this);

我们将Map的Value类型修改为FGameplayAttribute(*)()

cpp 复制代码
TMap<FGameplayTag, FGameplayAttribute(*)()> TagsToAttributes;

这样,代码就可以优化成,只需要在map添加属性Tag和对应的获取属性的函数即可。

cpp 复制代码
	TagsToAttributes.Add(GameplayTags.Attributes_Primary_Strength, GetStrengthAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Primary_Intelligence, GetIntelligenceAttribute);

在AttributeMenuWidgetController里面,只需要修改成调用函数,即可返回属性对象,然后调用GetNumericValue获取数值设置即可。

cpp 复制代码
	for (auto& Pair : AS->TagsToAttributes)
	{
		FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(Pair.Key);
		Info.AttributeValue = Pair.Value().GetNumericValue(AS);
		AttributeInfoDelegate.Broadcast(Info);
	}

接着实现数值变化时,修改属性,在BindCallbacksToDependencies函数内,我们还是需要遍历Map,使用ASC的GetGameplayAttributeValueChangeDelegate监听属性的变化,并绑定匿名函数,注意中括号传入的参数,内部无法获取到外部的地址,需要中括号作为接口传入

cpp 复制代码
void UAttributeMenuWidgetController::BindCallbacksToDependencies()
{
	const UAttributeSetBase* AS = Cast<UAttributeSetBase>(AttributeSet);

	for (auto& Pair : AS->TagsToAttributes)
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Pair.Value()).AddLambda(
			[this,Pair,AS](const FOnAttributeChangeData& Data)
			{
				FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(Pair.Key);
				Info.AttributeValue = Pair.Value().GetNumericValue(AS);
				AttributeInfoDelegate.Broadcast(Info);
			}
		);
	}
}

接着运行项目,看看修改Primary 属性,Secondary属性会不会跟着变化,打开面板

护甲值和护甲穿透为

我们之前创建的碰撞盒子会修改它的韧性 +10,当韧性变为32时,护甲值和护甲穿透也跟着增长了。

大家需要多测试,查看是否有问题。

接下来,再做一个小优化,在初始化数值和绑定委托函数里,有相同的逻辑,我们可以把它抽离出来作为一个新的函数去调用。

创建一个私有函数,用于实现上面的功能

cpp 复制代码
private:

	void BroadcastAttributeInfo(const FGameplayTag& AttributeTag, const FGameplayAttribute& Attribute) const;

实现之前的逻辑,主要就是修改一下变量

cpp 复制代码
void UAttributeMenuWidgetController::BroadcastAttributeInfo(const FGameplayTag& AttributeTag, const FGameplayAttribute& Attribute) const
{
	FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(AttributeTag);
	Info.AttributeValue = Attribute.GetNumericValue(AttributeSet);
	AttributeInfoDelegate.Broadcast(Info);
}

接下来就是将上面的逻辑替换为函数即可,记得去ue里面测试功能。

AttibuteMenuWidgetController.h

cpp 复制代码
// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "UI/WidgetController/MyWidgetController.h"
#include "AttributeMenuWidgetController.generated.h"

class UAttributeInfo;
struct FMyAttributeInfo;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAttibuteInfoSignature, const FMyAttributeInfo&, Info);

/**
 * 
 */
UCLASS(BlueprintType, Blueprintable)
class AURA_API UAttributeMenuWidgetController : public UMyWidgetController
{
	GENERATED_BODY()

public:

	virtual void BindCallbacksToDependencies() override;
	virtual void BroadcastInitialValues() override;

	UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
	FAttibuteInfoSignature AttributeInfoDelegate;

protected:

	UPROPERTY(EditDefaultsOnly)
	TObjectPtr<UAttributeInfo> AttributeInfo;

private:

	void BroadcastAttributeInfo(const FGameplayTag& AttributeTag, const FGameplayAttribute& Attribute) const;
};

AttibuteMenuWidgetController.cpp

cpp 复制代码
// 版权归暮志未晚所有。


#include "UI/WidgetController/AttributeMenuWidgetController.h"

#include "MyGameplayTags.h"
#include "AbilitySystem/AttributeSetBase.h"
#include "AbilitySystem/Data/AttributeInfo.h"

void UAttributeMenuWidgetController::BindCallbacksToDependencies()
{
	const UAttributeSetBase* AS = Cast<UAttributeSetBase>(AttributeSet);
	check(AttributeInfo);

	for (auto& Pair : AS->TagsToAttributes)
	{
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Pair.Value()).AddLambda(
			[this,Pair](const FOnAttributeChangeData& Data)
			{
				BroadcastAttributeInfo(Pair.Key, Pair.Value());
			}
		);
	}
}

void UAttributeMenuWidgetController::BroadcastInitialValues()
{
	const UAttributeSetBase* AS = Cast<UAttributeSetBase>(AttributeSet);
	check(AttributeInfo);

	for (auto& Pair : AS->TagsToAttributes)
	{
		BroadcastAttributeInfo(Pair.Key, Pair.Value());
	}
}

void UAttributeMenuWidgetController::BroadcastAttributeInfo(const FGameplayTag& AttributeTag, const FGameplayAttribute& Attribute) const
{
	FMyAttributeInfo Info = AttributeInfo->FindAttributeInfoForTag(AttributeTag);
	Info.AttributeValue = Attribute.GetNumericValue(AttributeSet);
	AttributeInfoDelegate.Broadcast(Info);
}
相关推荐
Charles Ray25 分钟前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
重生之我在20年代敲代码25 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文27 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
编程零零七3 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
2401_858286114 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py4 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy4 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
C-SDN花园GGbond5 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
迷迭所归处6 小时前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ6 小时前
Java 23 的12 个新特性!!
java·开发语言·学习