一、添加生命与法力属性
1.添加生命属性:Source/Crunch/Public/GAS/CAttributeSet.h
private:
UPROPERTY()
FGameplayAttributeData Health;
FGameplayAttributeData 的定义:
Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.h:
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayAttributeData
{
GENERATED_BODY()
FGameplayAttributeData()
: BaseValue(0.f)
, CurrentValue(0.f)
{}
FGameplayAttributeData(float DefaultValue)
: BaseValue(DefaultValue)
, CurrentValue(DefaultValue)
{}
virtual ~FGameplayAttributeData()
{}
/** Returns the current value, which includes temporary buffs */
float GetCurrentValue() const;
/** Modifies current value, normally only called by ability system or during initialization */
virtual void SetCurrentValue(float NewValue);
/** Returns the base value which only includes permanent changes */
float GetBaseValue() const;
/** Modifies the permanent base value, normally only called by ability system or during initialization */
virtual void SetBaseValue(float NewValue);
protected:
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
float BaseValue;
UPROPERTY(BlueprintReadOnly, Category = "Attribute")
float CurrentValue;
};
FGameplayAttributeData是虚幻引擎GAS(GameplayAbilitySystem)中用于表示游戏属性的核心数据结构。该结构体具有以下关键特性:
双值存储机制:
- BaseValue:基础值,仅包含永久性修改(如角色初始属性或装备加成)
- CurrentValue:当前值,包含临时buff/debuff效果
主要功能方法:
- GetCurrentValue()/SetCurrentValue():获取/设置包含临时效果的当前值
- GetBaseValue()/SetBaseValue():获取/设置永久性基础值
- 两个构造函数:支持默认初始化为0或指定默认值
设计特点:
- 通过UPROPERTY宏实现蓝图可读性
- 推荐通过GameplayEffects修改属性以实现网络同步和预测回滚
- 通常定义在AttributeSet类中并由其处理网络同步
实际应用示例:
- 移动速度属性(MoveSpeed)的实现会使用此结构体
- 伤害(Damage)和护甲(Armor)等战斗属性也采用相同结构
- 装备系统可通过修改BaseValue实现永久属性加成
2.生成getter和setter函数,不过,有专门的宏函数:
Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.h:
* To use this in your game you can define something like this, and then add game-specific functions as necessary:
*
* #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
* GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
* GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
*
* ATTRIBUTE_ACCESSORS(UMyHealthSet, Health)
*/
#define GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
static FGameplayAttribute Get##PropertyName##Attribute() \
{ \
static FProperty* Prop = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
return Prop; \
}
#define GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
FORCEINLINE float Get##PropertyName() const \
{ \
return PropertyName.GetCurrentValue(); \
}
#define GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
FORCEINLINE void Set##PropertyName(float NewVal) \
{ \
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent(); \
if (ensure(AbilityComp)) \
{ \
AbilityComp->SetNumericAttributeBase(Get##PropertyName##Attribute(), NewVal); \
}; \
}
#define GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName) \
FORCEINLINE void Init##PropertyName(float NewVal) \
{ \
PropertyName.SetBaseValue(NewVal); \
PropertyName.SetCurrentValue(NewVal); \
}
这是Unreal Engine游戏能力系统(GAS)中用于简化属性访问的宏定义系统,主要用于定义和管理游戏属性(如生命值、法力值等)。以下是关键点解析:
ATTRIBUTE_ACCESSORS宏:
- 组合了4个基础功能宏,提供完整的属性访问能力
- 示例用法:
ATTRIBUTE_ACCESSORS(UMyHealthSet, Health)
会生成Health属性的全套方法
组成宏的功能:
GAMEPLAYATTRIBUTE_PROPERTY_GETTER
:生成获取属性元数据的静态方法GAMEPLAYATTRIBUTE_VALUE_GETTER
:生成获取当前属性值的inline方法GAMEPLAYATTRIBUTE_VALUE_SETTER
:生成通过AbilitySystem修改属性值的方法GAMEPLAYATTRIBUTE_VALUE_INITTER
:生成初始化属性值的方法
实现特点:
- 使用
##
进行token拼接,动态生成方法名 - 通过
FindFieldChecked
获取UProperty反射信息 - 所有修改操作都通过AbilitySystemComponent进行
典型应用场景:
- 在继承自
UAttributeSet
的类中使用 - 需要配合GameplayAbilitySystem组件工作
- 适用于需要网络同步的游戏属性
注意事项:
- 属性变量需要定义为
FGameplayAttributeData
类型 - Setter操作会触发属性变化事件
- 确保AbilitySystemComponent有效才能进行修改操作
这套宏系统是UE GAS的标准实践,可以大幅减少属性访问的样板代码量。
所以,只要把宏定义粘贴进来就好:
Source/Crunch/Public/GAS/CAttributeSet.h:
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
public:
ATTRIBUTE_ACCESSORS(UCAttributeSet, Health);
二、创建GameplayEffect
1.创建GameplayEffect蓝图类:
2.修改最大生命值、最大法力值:
3.怎么实际运用修改好的值呢?
在Source/Crunch/Public/GAS/CAbilitySystemComponent.h中:
创建子类:
private:
TArray<TSubclassOf<UGameplayEffect>> InitialEffects; //游戏效果子类数组:需要初始化的效果
建立一个函数:
public:
//需要初始化所有值时调用
void ApplyInitialEffects();
实现:
(1)循环遍历数组:
for (const TSubclassOf<UGameplayEffect>& EffectClass : InitialEffects)
(2)获取空间:
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingSpec(EffectClass, 1.f, MakeEffectContext());
这段代码是虚幻引擎GAS框架中创建GameplayEffectSpec的标准用法,主要功能是生成一个游戏效果规格实例的句柄。具体解析如下:
核心参数说明:
- EffectClass:要实例化的UGameplayEffect类引用
- 1.f:效果等级(Level)参数,这里设为1级
- MakeEffectContext():创建空的FGameplayEffectContext上下文对象
执行流程:
- 首先通过MakeEffectContext()创建效果上下文容器
- 然后使用MakeOutgoingSpec生成FGameplayEffectSpec运行时实例
- 最终返回FGameplayEffectSpecHandle智能指针封装
典型应用场景:
- 技能释放时创建临时效果实例
- 状态效果(如中毒、眩晕)的运行时生成
- 属性修改(伤害/治疗)的中间载体
后续操作建议:
- 可通过EffectSpecHandle.Data访问实际Spec对象
- 通常配合ApplyGameplayEffectSpecToSelf等函数应用效果
- 可动态修改Spec的Level、SetByCaller等参数
(3)对自己释放效果
ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
ApplyGameplayEffectSpecToSelf是GAS框架中用于将GameplayEffectSpec应用到当前AbilitySystemComponent的核心函数,其关键特性如下:
核心参数解析:
- GameplayEffect参数:传入已构建的FGameplayEffectSpec运行时实例
- PredictionKey参数:默认构造的预测键,用于客户端预测同步
函数特性:
- 返回FActiveGameplayEffectHandle用于管理激活的效果实例
- virtual修饰允许子类重写应用逻辑
- const引用传递避免数据拷贝
内部处理流程:
- 通过ActiveGameplayEffectsContainer管理效果生命周期
- 区分即时(Instant)/持续(Duration)/永久(Infinite)效果类型处理
- 执行Modifier运算和Execution计算
典型应用场景:
- 技能释放后应用效果到自身
- 状态效果(如BUFF/DEBUFF)的自我施加
- 属性修改效果的链式调用
注意事项:
- 需确保传入的Spec已正确设置Level和Context
- 预测键需在客户端预测场景下正确设置
- 返回的Handle可用于后续效果管理
源码:
Source/Crunch/Public/GAS/CAbilitySystemComponent.h:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "CAbilitySystemComponent.generated.h"
/**
*
*/
UCLASS()
class CRUNCH_API UCAbilitySystemComponent : public UAbilitySystemComponent
{
GENERATED_BODY()
public:
//需要初始化所有值时调用
void ApplyInitialEffects();
private:
TArray<TSubclassOf<UGameplayEffect>> InitialEffects; //游戏效果子类数组:需要初始化的效果
};
Source/Crunch/Private/GAS/CAbilitySystemComponent.cpp:
// Copyright@ChenChao
#include "GAS/CAbilitySystemComponent.h"
void UCAbilitySystemComponent::ApplyInitialEffects()
{
for (const TSubclassOf<UGameplayEffect>& EffectClass : InitialEffects)
{
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingSpec(EffectClass, 1.f, MakeEffectContext());
ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}
}
这段代码实现了GAS框架中AbilitySystemComponent的初始效果应用逻辑,主要功能解析如下:
核心流程:
- 遍历InitialEffects数组中的每个GameplayEffect类
- 通过MakeOutgoingSpec创建效果规格(Spec)
- 使用ApplyGameplayEffectSpecToSelf将效果应用到当前ASC
三、 搭建服务端、客户端调用链
1.为了防止作弊,上述代码只允许在服务器端运行,所以需要加上判定条件:
Source/Crunch/Private/GAS/CAbilitySystemComponent.cpp:
// Copyright@ChenChao
#include "GAS/CAbilitySystemComponent.h"
void UCAbilitySystemComponent::ApplyInitialEffects()
{
if (!GetOwner() || !GetOwner()->HasAuthority()) return;
for (const TSubclassOf<UGameplayEffect>& EffectClass : InitialEffects)
{
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingSpec(EffectClass, 1.f, MakeEffectContext());
ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}
}
2.第二个防止作弊的方法是:只在服务器端调用ApplyInitialEffects()
(1)我们在角色类里定义两个函数,分别在服务器初始化、客户端初始化:
void ServerSideInit(); //服务器端初始化
void ClientSideInit(); //客户端初始化
void ACCharacter::ServerSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
CAbilitySystemComponent->ApplyInitialEffects(); //效果初始化
}
void ACCharacter::ClientSideInit()
{
CAbilitySystemComponent->InitAbilityActorInfo(this, this); //应用能力系统组件前都要初始化
}
(2)我们定义了方法,现在要到只有服务器调用的方法里和只有客户端(或者监听服务器)调用的方法里去实现他们:
到角色控制器里(Source/Crunch/Public/Player/CPlayerController.h)
重写函数:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "CPlayerController.generated.h"
class ACPlayerCharacter;
/**
*
*/
UCLASS()
class CRUNCH_API ACPlayerController : public APlayerController
{
GENERATED_BODY()
public:
// 只有服务器调用的方法
virtual void OnPossess(APawn* InPawn) override;
// 只有客户端(或者监听服务器)调用的方法
virtual void AcknowledgePossession(class APawn* P) override;
private:
UPROPERTY()
TObjectPtr<ACPlayerCharacter> CPlayerCharacter;
};
Source/Crunch/Private/Player/CPlayerController.cpp:
// Copyright@ChenChao
#include "Player/CPlayerController.h"
#include "Player/CPlayerCharacter.h"
void ACPlayerController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
CPlayerCharacter = Cast<ACPlayerCharacter>(InPawn);
if (CPlayerCharacter)
{
CPlayerCharacter->ServerSideInit();
}
}
void ACPlayerController::AcknowledgePossession(class APawn* P)
{
Super::AcknowledgePossession(P);
CPlayerCharacter = Cast<ACPlayerCharacter>(P);
if (CPlayerCharacter)
{
CPlayerCharacter->ClientSideInit();
}
}
PS:为了在蓝图默认里修改初始值,需要修改:
Source/Crunch/Public/GAS/CAbilitySystemComponent.h:
private:
UPROPERTY(EditDefaultsOnly, Category="Gameplay Effects")
TArray<TSubclassOf<UGameplayEffect>> InitialEffects; //游戏效果子类数组:需要初始化的效果
顺便把之前的改掉:
Source/Crunch/Public/Character/CCharacter.h:
private:
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAbilitySystemComponent> CAbilitySystemComponent;
UPROPERTY(VisibleDefaultsOnly, Category="GamePlay Ability")
TObjectPtr<UCAttributeSet> CAttributeSet;
四、应用游戏效果并复制属性
1.在角色蓝图中设置:
这样就可以将GE_Init应用到能力系统组件了!
运行调试模式,按一下~,输入showdebug abilitysystem。看看:
哪里出错了?
到GE_Init里看看:
原来持续时间,没有改成永久:
好了:
2.创建同用的游戏效果:GE_FullStat
效果:实现状态的回满
到角色蓝图里,将游戏效果添加上:
以单机的形式,运行游戏:
3.但是,我如果以客户端的形式,运行游戏。效果就不会出现:
这是怎么回事呢?
GE_Init执行了,但是GE_FullStat没有执行
可能是5.5引擎版本上,永久效果拥有服务器权限,可以复制更新,立即效果,没有服务器效果,复制更新不了:
void UCAbilitySystemComponent::ApplyInitialEffects()
{
if (!GetOwner() || !GetOwner()->HasAuthority()) return;
for (const TSubclassOf<UGameplayEffect>& EffectClass : InitialEffects)
{
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingSpec(EffectClass, 1.f, MakeEffectContext());
ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());
}
}
方法:将属性值添加上可复制!
4.属性添加复制
Source/Crunch/Public/GAS/CAttributeSet.h:
(1)变量属性添加可复制:ReplicatedUsing = OnRep_Health
private:
UPROPERTY(ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health; //生命值
这段代码是Unreal Engine中GAS(游戏技能系统)定义可复制属性的标准写法,用于声明一个需要网络同步的生命值属性。关键要素解析如下:
属性声明:
FGameplayAttributeData
是GAS专用的属性容器,包含BaseValue
和CurrentValue
两个浮点值ReplicatedUsing
指定属性变化时的回调函数OnRep_Health
配套实现要求:
- 需在头文件使用
ATTRIBUTE_ACCESSORS
宏生成配套访问器 - 必须实现
OnRep_Health
回调函数处理客户端同步 - 需在
GetLifetimeReplicatedProps
中注册复制属性
典型应用场景:
- 基础生命值存储在
BaseValue
中 - 临时buff/debuff影响
CurrentValue
- 通过
PreAttributeChange
实现数值范围限制(如0-MaxHealth)
(2)写上OnRep_Health函数:
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldValue);
void UCAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, Health, OldValue); //通知属性复制
}
这段代码是Unreal Engine中GAS(游戏技能系统)架构下处理属性网络复制的标准实现,主要用于Health属性在客户端接收更新时的回调处理。以下是关键点解析:
核心功能:
OnRep_Health
是网络复制的回调函数,当服务器同步Health属性到客户端时自动触发GAMEPLAYATTRIBUTE_REPNOTIFY
宏是GAS系统的标准通知机制,会处理属性预测和UI更新等逻辑
配套实现要求:
- 需在头文件声明
UFUNCTION()
修饰符和回调函数原型 - 必须与
GetLifetimeReplicatedProps
中的DOREPLIFETIME_CONDITION_NOTIFY
配置对应 - 建议配合
PreAttributeChange
实现数值范围限制(如生命值不低于0)
设计规范:
- 属性定义需使用
ATTRIBUTE_ACCESSORS
宏生成配套的Getter/Setter - 对于重要属性,应在
PostGameplayEffectExecute
中处理业务逻辑(如角色死亡) - 元属性(如伤害值)通常不需要复制,仅在服务器计算
完整实现还应包含属性元数据配置和效果处理,这是GAS属性系统的标准实践。
(3)在此之前,要重写一个函数GetLifetimeReplicatedProps,来规范复制的方式:
//~Begin override
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
//~End override
void UCAttributeSet::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, Health, COND_None, REPNOTIFY_Always); //通知复制实时更新
}
这段代码是Unreal Engine中AttributeSet类的网络复制配置实现,主要用于同步游戏属性到客户端。具体分析如下:
函数作用:
GetLifetimeReplicatedProps
是UE网络复制系统的核心函数,用于声明需要同步的属性- 通过
DOREPLIFETIME_CONDITION_NOTIFY
宏将Health属性标记为需要网络复制
参数说明:
COND_None
表示无条件复制REPNOTIFY_Always
确保属性变化时始终触发复制通知- 这种配置适合需要实时同步的关键属性(如生命值)
配套实现:
- 通常需要配合
OnRep_Health
函数处理客户端接收到的属性更新 - 建议在属性变化时使用
GAMEPLAYATTRIBUTE_REPNOTIFY
宏确保正确的网络同步
设计规范:
- AttributeSet应当在OwnerActor构造函数中创建并自动注册到ASC
- 对于重要属性,建议在
PreAttributeChange
中实现数值范围限制
完整实现还应包含属性变化回调和处理逻辑,这是GAS(游戏技能系统)架构的标准实践。
效果:
源码:
Source/Crunch/Public/GAS/CAttributeSet.h:
// Copyright@ChenChao
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "AttributeSet.h"
#include "CAttributeSet.generated.h"
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
*
*/
UCLASS()
class CRUNCH_API UCAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
ATTRIBUTE_ACCESSORS(UCAttributeSet, Health);
ATTRIBUTE_ACCESSORS(UCAttributeSet, MaxHealth);
ATTRIBUTE_ACCESSORS(UCAttributeSet, Mana);
ATTRIBUTE_ACCESSORS(UCAttributeSet, MaxMana);
//~Begin override
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
//~End override
private:
UPROPERTY(ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health; //生命值
UPROPERTY(ReplicatedUsing = OnRep_MaxHealth)
FGameplayAttributeData MaxHealth; //最大生命值
UPROPERTY(ReplicatedUsing = OnRep_Mana)
FGameplayAttributeData Mana; //魔法值
UPROPERTY(ReplicatedUsing = OnRep_MaxMana)
FGameplayAttributeData MaxMana; //最大魔法值
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_Mana(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_MaxMana(const FGameplayAttributeData& OldValue);
};
Source/Crunch/Private/GAS/CAttributeSet.cpp:
// Copyright@ChenChao
#include "GAS/CAttributeSet.h"
#include "Net/UnrealNetwork.h"
void UCAttributeSet::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, Health, COND_None, REPNOTIFY_Always); //通知复制实时更新
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always); //通知复制实时更新
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, Mana, COND_None, REPNOTIFY_Always); //通知复制实时更新
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, MaxMana, COND_None, REPNOTIFY_Always); //通知复制实时更新
}
void UCAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, Health, OldValue); //通知属性复制
}
void UCAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, MaxHealth, OldValue); //通知属性复制
}
void UCAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, Mana, OldValue); //通知属性复制
}
void UCAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, MaxMana, OldValue); //通知属性复制
}