GAS/RPGAction学习笔记


GAS官方视频https://www.bilibili.com/video/BV1X5411V7jh

GAS文档https://docs.unrealengine.com/5.0/zh-CN/using-gameplay-abilities-in-unreal-engine/

img

堡垒之夜也是用的这个框架

img

img

其他的技能系统:

  • Able Ability System
  • Ascent Combat Framework (ACF)

img

img

img

GA流程

img

img

img

sample

img

在模块中要引用

PrivateDependencyModuleNames.AddRange(
				"GameplayAbilities",
				"GameplayTags",
				"GameplayTasks",
			}

character要继承IAbilitySystemInterface接口,以此被GAS识别

在character中声明一个UAbilitySystemComponent(ASC)组件

实现接口方法:

virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;

在构造函数中对ASC使用CreateDefaultSubObject去实例化

在character中去声明一个Ability数组去存储各种ability

UPROPERTY()
TArray<TSubclassof<UGameplayAbility>> MyAbilities;

像是打击受击掉血的流程,有两个能力GA-Attack和BeAttack,有一个GE behitt描述了伤害

属性(AS)中如血量等值不是float,而是FGameplayAttributeData结构体,有base值和cur值,方便GE(buff)作用后的数值回滚。

img

MakeOutgoingGameplayEffectSpec指可以定一泛类型GE的一个模板,然后在GE生成时读表或者数据库动态改变。更好的是通过IDetailCustomization去定制GA的UI。

img

GAS中文文档https://blog.csdn.net/pirate310/article/details/106311256

GAS英文文档https://github.com/tranek/GASDocumentation

教学博客https://www.cnblogs.com/JackSamuel/p/7155500.html

大钊-深入GAS框架

https://www.bilibili.com/video/BV1zD4y1X77M?spm_id_from=333.999.0.0img

img

img

img

img

img

img

img

img

img

img

img

l

img

img

img

img

img

img

ASC对于单机游戏可以放在Pawn上,对于联机游戏可以放在playstate上以广播和持续的保存

img

img

GASSimple

链接:https://pan.baidu.com/s/10U3QOB0oz7vMT1B-IXbMMw 提取码:uenb

Tag

先看看核心的tag是怎么标定的:

比较简单,定义了一层,分别是攻击,格挡,受击,主动

img

ASC

GASSample中ASC是挂载在character上的,在构造函数中初始化

注意这个Character继承了IAbilitySystemInterface接口,其中只有一个GetAbilitySystemComponent方法用于返回此Actor上的ASC,通过这种方式让此Actor被GAS系统所识别和使用。

class AGASSampleCharacter : public ACharacter,
	public IAbilitySystemInterface //修改:继承接口
{
   
    GENERATED_BODY()
        
public:
	AGASSampleCharacter();    
        
protected:
	//修改:添加BeginPlay
	virtual void BeginPlay() override;
        
public:

	// 修改:申明ASC
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = GameplayAbilities, meta = (AllowPrivateAccess = "true"))
	class UAbilitySystemComponent* AbilitySystem;

	// 修改:实现接口方法
	UAbilitySystemComponent* GetAbilitySystemComponent()const override;

	// 修改:声明Ability数组
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Abilities)
	TArray<TSubclassOf<UGameplayAbility>> MyAbilities;

    //此actor的属性集
	UPROPERTY()
	USampleAttributeSet* AttributeSet;
        
}
AGASSampleCharacter::AGASSampleCharacter()
{
   
    // 修改:实例化ASC
	AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));


	// 在OwnerActor的构造方法中创建的AttributeSet将会自动注册到ASC
	AttributeSet = CreateDefaultSubobject<USampleAttributeSet>(TEXT("AttributeSet"));
}

void AGASSampleCharacter::BeginPlay()
{
   
	Super::BeginPlay();

	if (nullptr != AbilitySystem)
	{
   
		// 修改:给ASC赋予技能
		if (HasAuthority() && MyAbilities.Num() > 0)
		{
   
			for (auto i = 0; i < MyAbilities.Num(); i++)
			{
   
				if (MyAbilities[i] == nullptr)
				{
   
					continue;
				}
				AbilitySystem->GiveAbility(FGameplayAbilitySpec(MyAbilities[i].GetDefaultObject(), 1, 0));
			}
		}

		// 修改:初始化ASC
		AbilitySystem->InitAbilityActorInfo(this, this);
	}
}

注意GA是通过GiveAbility赋给ASC的,一个GA相当于一个模板,通过使用一个GameplayAbility以及相关参数如等级,生成一个能力实例FGameplayAbilitySpec

FGameplayAbilitySpecHandle UAbilitySystemComponent::GiveAbility(const FGameplayAbilitySpec& AbilitySpec);

这个基础的GASSampleCharacter有三个GA,GA是游戏逻辑的主要书写处

img

GA

看看GA_Attack_Attr以此为例,梳理下流程。

GA_Attack_Attr的ability tag为positive,代表这是个主动效果,并且BlockAbilitiesWithTag也是positive tag,代表这个tag存在时会阻塞其他的positive tag。

img

这里通过GetActorInfo拿到此GA拥有者的相关信息,包括OwnerActor(攻击方)、AvatarActor(受击方)、SKMesh、ASC等。

在这个攻击动画蒙太奇中有一个NotifyName为Hit的event,播放攻击蒙太奇触发HitEvent,判断是否找到AvatarActor。

img

找到则在受击者的ASC上触发一个受击的效果GE_hit以及收到伤害扣血的效果GE_Damage。

img

在调用蒙太奇后直接调用了CommitAbility,以提交技能进行计算如技能消耗等,这里不commit在场景中攻击就不会扣蓝。

img

CommitAbility的流程可以看下面,可以知道CommitAbility是最后可以判定GA失败的时机,不然之后GA就一定成功。

img

在蒙太奇播完后主动触发结束GE的event,GE结束时停止蒙太奇。

img

这里播放蒙太奇使用的是UE原生的节点,有说法是UE原生节点在网络同步时会有问题,一定要使用GAS的蒙太奇节点

GAS蒙太奇组合了tag和event,更加系统方便,同时还有自动结束蒙太奇的功能,比起原生节点更加强大,还是使用这个吧。。。

img

GE

GA_Attack_Attr定义了攻击的消耗属性即GE_Cost(demo中的体现即为5点蓝耗)

img

应用策略是Instant立即生效(Infinite是持续生效,HasDuration是一定时间后生效),GE设置简单的对SampleAttributeSet.Physical(这个属性是需要代码自定义的)属性即蓝量应用计算 Add -5,

应用概率ChanceToAppleToTarget为1,必定生效。

img

AS

来看看这个SampleAttribute如何在代码中定义,一开始定义了一个宏

// 定义一个增加各种Getter和Setter方法的宏
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

进入内部看看,作用注释。官方注释说得很清楚了,在自己的AS中使用ATTRIBUTE_ACCESSORS宏。

/**
 * This defines a set of helper functions for accessing and initializing attributes, to avoid having to manually write these functions.
 * It would creates the following functions, for attribute Health
 *
 *	static FGameplayAttribute UMyHealthSet::GetHealthAttribute();
 *	FORCEINLINE float UMyHealthSet::GetHealth() const;
 *	FORCEINLINE void UMyHealthSet::SetHealth(float NewVal);
 *	FORCEINLINE void UMyHealthSet::InitHealth(float NewVal);
 *
 * 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); \
	}//进行初始化

看一下SimpleAttributeSet的简单框架,以属性physic(蓝耗)为例。

一个属性集AttributeSet集成了多个属性FGameplayAttributeData,从而使得这一套属性可以在不同的ASC上使用。

属性的基本类型是FGameplayAttributeData而非float,FGameplayAttributeData中定义了currentvalue和basevalue,用于处理buff和数值回滚的情况。

UCLASS()
class GASSAMPLE_API USampleAttributeSet : public UAttributeSet
{
   
	GENERATED_BODY()
public:
    //网络同步
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

public:
	UPROPERTY(BlueprintReadOnly, Category = "Physical", ReplicatedUsing = OnRep_MaxPhysical)
	FGameplayAttributeData Physical; //定义蓝耗属性值
	ATTRIBUTE_ACCESSORS(USampleAttributeSet, Physical) //定义属性的getset和init接口
	UFUNCTION()
	void OnRep_MaxPhysical(const FGameplayAttributeData& OldValue);//属性同步时客户端调用

public:
	// 属性修改前回调
	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue);

	// GE执行后属性回调
	virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
};

在PreAttributeChange函数中对新设置的值进行范围限定,在PostGameplayEffectExecute进行值改变后的回调处理,这里直接调用AvatarActor进行相关处理不是好的写法,只是简单demo示例

void USampleAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
   
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	...
	DOREPLIFETIME(USampleAttributeSet, Physical); //对蓝耗进行网络同步
}

//进行范围限定
void USampleAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
   
	if (Attribute == GetHealthAttribute())
	{
   
		NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
	}
	if (Attribute == GetPhysicalAttribute())
	{
   
		NewValue = FMath::Clamp(NewValue, 0.f, GetMaxPhysical());
	}
}

void USampleAttributeSet
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值