目录

  • 4.4.1 属性集的定义 - Attribute Set Definition
  • 4.4.2 属性集的设计 - Attribute Set Design
    • 4.4.2.1 具备独立属性的子组件 - Subcomponents with Individual Attributes
    • 4.4.2.2 运行时添加和删除属性集 - Adding and Removing AttributeSets at Runtime
    • 4.4.2.3 物品的属性(武器弹药) - Item Attributes (Weapon Ammo)
      • 4.4.2.3.1 在物品上完全都用浮点数来处理 - Plain Floats on the Item
      • 4.4.2.3.2 为物品分别分配独立的属性集 - AttributeSet on the Item
      • 4.4.2.3.3 为物品分别分配独立的ASC - ASC on the Item
  • 4.4.3 定义属性 - Defining Attributes
  • 4.4.4 初始化属性 - Initializing Attributes
  • 4.4.5 PreAttributeChange()
  • 4.4.6 PostGameplayEffectExecute()
  • 4.4.7 OnAttributeAggregatorCreated()

4.4.1 属性集的定义 - Attribute Set Definition

AttributeSet会负责Attributes的定义、保存和管理。开发者可以从UAttributeSet继承并进一步拓展。在OwnerActor的构造函数中创建AttributeSet会将他自动注册到OwnerActorASC中。这一步必须在C++中完成

4.4.2 属性集的设计 - Attribute Set Design

4.4.2.1 具备独立属性的子组件 - Subcomponents with Individual Attributes

设想这样一种情形:某个Pawn上面有很多个负责抵御伤害的组件(比如说多个独立的可被破坏的护甲),如果你知道Pawn可拥有的护甲的最大数量,那么可以在该Pawn上做一个AttributeSet,其中包含着许多的生命值Attributes,比如说DamageableCompHealth0,DamageableCompHealth1等等,来表示这些可以抵御伤害的组件的逻辑上的插槽(即建立护甲和生命值属性的逻辑关联)。在你的表示护甲的类的实例上,令表示对应的生命值的插槽Attribute可以由GameplayAbilities进行读取或者由Executions来读取,从而可以知道某个护甲遭到伤害时,伤害应该结算到具体哪一个Attribute上。即使某些Pawns拥有的护甲数量比AttributeSet预先设定的数量小也没有关系,因为可以不去使用对应的Attribute,而这部分额外的内存消耗是微乎其微的。

如果你的子组件每个上面都需要很多个的Attributes,或者这个数量是未知的,亦或者子组件会被从现有个体上卸载然后被其他玩家使用(比如说你的角色死亡后掉落的武器被别人拾取),总之不管什么原因前面提到的方法无法完全解决你的问题,我的建议是直接使用老办法,即不用Attributes系统来做而改用老办法,即单独创建一些float类型的值之类的(译者注:因为此时情况的复杂性再使用Attributes来强行拓展已经是弊大于利了)。参阅后面的小节Item Attributes。

4.4.2.2 运行时添加和删除属性集 - Adding and Removing AttributeSets at Runtime

可以在运行时从ASC中添加和删除AttributeSets,当然移除掉某些AttributeSets的行为可能是很危险的。例如,如果某个AttributeSet的移除在客户端上早于在服务器端,而恰巧此时有一个Attribute值被复制到客户端,这样的话Attribute就找不对对应的AttributeSet从而导致游戏奔溃。

装备武器时:

AbilitySystemComponent->GetSpawnedAttributes_Mutable().AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();

卸载武器时:

AbilitySystemComponent->GetSpawnedAttributes_Mutable().Remove(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();
4.4.2.3 物品的属性(武器弹药) - Item Attributes (Weapon Ammo)

有多种方式可以实现带有Attributes的可装备物品(武器弹药,防具耐久等)。所有这些东西都是把值直接存在物品上。这对于能够被多个玩家装备和使用的那些物品来说是必须的。

  1. 在物品上完全都用float来处理 (推荐)
  2. 为物品分别分配独立的AttributeSet
  3. 为物品分别分配独立的ASC
4.4.2.3.1 在物品上完全都用浮点数来处理 - Plain Floats on the Item

即直接在物品类实例上使用浮点值而不是Attributes。Fortnite和GASShooter都是用这种方式来处理弹药的。对于枪械,具体就是存储最大的弹夹数量,当前弹夹中的弹药数量,后备弹药等等,把这些都以支持复制的浮点数(COND_OwnerOnly)形式存在枪械实例上。如果武器可以共享后备弹药,也就是说所有的武器都是使用的同一种弹药,那么你可以为Character添加一个代表后备弹药的Attribute及其相应的AttributeSet(重新加载能力时可以使用一个Cost GE来从后备弹药中抽取然后装填到枪械的弹夹中)。因为你的当前弹夹弹药并没有用Attributes来表示,你可能需要重写UGameplayAbility中的某些函数来检查和修改枪械上对应的float类型值。在授予技能时,令枪械作为GameplayAbilitySpec中的SourceObject,这样你才可以在技能中去访问枪械的相应数据。

为了防止枪械在快速自动开火中由于弹药数量的复制而搞乱本地的弹药数量,需要当玩家在PreReplication()中有IsFiringGameplayTag时禁用掉复制功能。你也可以在这里实现你本地的预测。

void AGSWeapon::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker)
{Super::PreReplication(ChangedPropertyTracker);DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, PrimaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));DOREPLIFETIME_ACTIVE_OVERRIDE(AGSWeapon, SecondaryClipAmmo, (IsValid(AbilitySystemComponent) && !AbilitySystemComponent->HasMatchingGameplayTag(WeaponIsFiringTag)));
}

优势:

  1. 避免了使用AttributeSets的局限性(后面会有详细内容)

限制:

  1. 无法使用现有的GameplayEffect的工作流(比如以Cost GEs来处理弹药的使用,等等)
  2. 需要进一步拓展UGameplayAbility(重写其中一些函数),来检查和处理弹药的消耗(从而应对float类型而非Attribute
4.4.2.3.2 为物品分别分配独立的属性集 - AttributeSet on the Item

在物品上使用独立的AttributeSet,当玩家装备物品时将物品上的AttributeSet添加到玩家的ASC上也是可以的,但是这样的做法也会相应的带来一些问题。我在GASShooter的早期版本中就是用这种方式来处理弹药的。武器将一些Attributes,比如最大弹夹数,当前弹夹中的弹药,后备弹药等等,存储到一个AttributeSet放到武器类上。如果武器间共享后备弹药,你可以将后备弹药这个Attribute移动到角色身上,用一个共享弹药的AttributeSet来保管。当在服务器上玩家装备武器时,武器的AttributeSet将会添加到玩家的ASC::SpawnedAttributes中。服务器将这个复制到客户端。如果武器被卸载掉,再从ASC::SpawnedAttributes中移除掉AttributeSet

AttributeSet保存在非OwnerActor的什么东西上(比如说武器),你会发现在AttributeSet上发现一些编译错误。解决方案是在BeginPlay()中构造AttributeSet,而非在构造函数中,然后还要在武器上实现实现IAbilitySystemInterface接口(当你装备武器时设置一个指向ASC的指针,就和之前在Character或者PlayerState上实现的这个接口类似)。

void AGSWeapon::BeginPlay()
{if (!AttributeSet){AttributeSet = NewObject<UGSWeaponAttributeSet>(this);}//...
}

参见 older version of GASShooter.

优势:

  1. 可以使用现有GameplayAbilityGameplayEffect的工作流 workflow(比如以Cost GEs来处理弹药的使用,等等)
  2. 在物品不多时比较容易设置

限制:

  1. 对于每种武器类型都需要去定制一个新的AttributeSet类。ASCs只能够保存一个AttributeSet类的实例,因为对某个Attribute的修改会去在ASCSpawnedAttributes数组中查找他们AttributeSet类的第一个实例。额外的同一个或者同源的AttributeSet类会被忽略掉。
  2. 出于上面的原因,那么同种类型的装备你就只能装备一把了。
  3. 移除掉某个AttributeSet是危险的行为。比如说在GASShooter里,如果玩家用火箭筒杀掉自己,玩家会立即卸载掉火箭筒这件装备(并从ASC中卸载AttributeSet)。当服务器去复制火箭筒弹药这个Attribute的变化时,客户端上的ASC上已经没有AttributeSet,这样游戏就奔溃了。
4.4.2.3.3 为物品分别分配独立的ASC - ASC on the Item

为每个物品都添加一个完整的AbilitySystemComponent是一个极端的方法。这个我既没有自己实现过,更没见过。要实现这个方案需要大量额外的工作。

Is it viable to have several AbilitySystemComponents which have the same owner but different avatars (e.g. on pawn and weapon/items/projectiles with Owner set to PlayerState)?

The first problem I see there would be implementing the IGameplayTagAssetInterface and IAbilitySystemInterface on the owning actor. The former may be possible: just aggregate the tags from all all ASCs (but watch out -HasAllMatchingGameplayTags may be met only via cross ASC aggregation. It wouldn’t be enough to just forward that calls to each ASC and OR the results together). But the later is even trickier: which ASC is the authoritative one? If someone wants to apply a GE -which one should receive it? Maybe you can work these out but this side of the problem will be the hardest: owners will multiple ASCs beneath them.

Separate ASCs on the pawn and the weapon can make sense on its own though. E.g, distinguishing between tags the describe the weapon vs those that describe the owning pawn. Maybe it does make sense that tags granted to the weapon also “apply” to the owner and nothing else (E.g, attributes and GEs are independent but the owner will aggregate the owned tags like I describe above). This could work out, I am sure. But having multiple ASCs with the same owner may get dicey.

Dave Ratti from Epic’s answer to community questions #6

优势:

  1. 可以使用现有的GameplayAbilityGameplayEffect的工作流(比如以Cost GEs来处理弹药的使用,等等)
  2. 可以重用AttributeSet类(在每个武器的ASC上重复使用)

限制:

  1. 工作量
  2. 可行性

4.4.3 定义属性 - Defining Attributes

Attributes只能通过C++来定义AttributeSet的头文件中。我建议是将这一段宏代码块儿添加到每个AttributeSet的头文件里。它会帮我们自动生成对应Attributes的访问器(getter方法)和修改器(setter方法)。

// Uses macros from AttributeSet.h
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

一个支持复制的生命值Attribute可以像这样来定义:

UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UGDAttributeSetBase, Health)

别忘记在头文件中定义OnRep函数:

UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

AttributeSet的.cpp文件中应该在相应的OnRep方法中添加GAMEPLAYATTRIBUTE_REPNOTIFY宏,从而支持预测系统的一些内容:

void UGDAttributeSetBase::OnRep_Health(const FGameplayAttributeData& OldHealth)
{GAMEPLAYATTRIBUTE_REPNOTIFY(UGDAttributeSetBase, Health, OldHealth);
}

最后,需要把Attribute添加到GetLifetimeReplicatedProps:

void UGDAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{Super::GetLifetimeReplicatedProps(OutLifetimeProps);DOREPLIFETIME_CONDITION_NOTIFY(UGDAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
}

REPNOTIFY_Always是告诉OnRep函数当本地值和服务器下发的值相同的时候也去进行相应的触发。默认情况下(即这里不用REPNOTIFY_Always的情况下)这两个值一样的时候是不会触发OnRep函数的。

如果某个Attribute不需要复制,类似Meta Attribute,那么OnRepGetLifetimeReplicatedProps这两步的设置是可以跳过的。

4.4.4 初始化属性 - Initializing Attributes

实际上存在很多种方法来去初始化Attributes(设置BaseValue以及CurrentValue的初始值)。Epic推荐使用一个Instant类型的GameplayEffect来完成这一步初始化(译者注:即通过应用应用一个GameplayEffect,这个GameplayEffect的施加效果就是对Attributes进行初始化)。这也是示例项目中使用的方法。

参考示例项目中的GE_HeroAttributes蓝图,其中有关于如何使用一个Instant类型的GameplayEffect来去初始化Attributes。这个GameplayEffect的实际应用是在C++的。

如果你在定义Attributes时使用了宏ATTRIBUTE_ACCESSORS,它会帮助你自动为AttributeSet里的每个Attribute都生成一个初始化方法,在C++里可以放心大胆的使用。

// InitHealth(float InitialValue) is an automatically generated function for an Attribute 'Health' defined with the `ATTRIBUTE_ACCESSORS` macro
AttributeSet->InitHealth(100.0f);

更多的Attributes的初始化方法可以进一步参阅AttributeSet.h

注意: 在版本4.42之前,FAttributeSetInitterDiscreteLevels是和FGameplayAttributeData无法协调的。它是在Attributes还是原始浮点数时创建,并且嫌弃FGameplayAttributeData不是Plain Old DataPOD)。在4.24版本之后这个问题就被修复掉了 https://issues.unrealengine.com/issue/UE-76557。

4.4.5 PreAttributeChange()

PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)AttributeSet中重要的函数方法之一,主要是响应Attribute中的CurrentValue的修改发生之前的调用。这里最好就是去做一些对输入的限制和调整,利用NewValue来将可能会被应用到CurrentValue上的修改限制到某个合理的区间范围。

比如示例项目中将移动速度的修改器的限制如下:

if (Attribute == GetMoveSpeedAttribute())
{// Cannot slow less than 150 units/s and cannot boost more than 1000 units/sNewValue = FMath::Clamp<float>(NewValue, 150, 1000);
}

其中GetMoveSpeedAttribute()函数是前面我们提到的宏代码块生成的函数之一(Defining Attributes)。

任何对Attributes的修改都会先调用这个方法,无论是使用Attribute的设置器(setters)(Defining Attributes)亦或是使用GameplayEffects

**注意:**此处的截取操作并没有永久的修改ASC的修改器。它改变的实际上只是通过对修改器的查询而返回的值。这意味着任何修改器(比如GameplayEffectExecutionCalculationsModifierMagnitudeCalculations)在重计算CurrentValue时都需要再实现截取的操作。

注意Epic的对PreAttributeChange()的注释提到,不要去使用它来处理游玩相关的事件,而只是把它用作数值的修正和处理。监听Attribute的变化而产生的和游玩相关的事件(译者注:比如说生命值、弹药数等属性的UI响应事件)的推荐的处理方案是使用UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute) (Responding to Attribute Changes)。

4.4.6 PostGameplayEffectExecute()

PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data)只是在由Instant类型的GameplayEffect对某个AttributeBaseValue修改之后才会触发。这里可以进一步做一些Attribute相关的操作。

例如,在示例项目中我们令生命值的Attribute减去最终伤害值的Meta Attribute。如果有护盾的Attribute的话,我们可以在这里先让护盾值减去伤害值,然后再把剩余伤害(如果还有的话)应用到生命值上。示例项目也在这个位置来应用受击动画,显示伤害飘字,并且为击杀者赋予经验和金币奖励。从设计上说,伤害值的Meta Attribute将始终通过Instant类型的GameplayEffect来设置,并且永远不需要通过Attribute的设置器(setter)来设置。

其他一些仅由Instant类型的GameplayEffect来改变其BaseValueAttributes,比如法力值和体力值,也可以在这里通过其最大值对应的Attributes来进行截取操作。

**注意:**当调用PostGameplayEffectExecute(),对Attribute的修改就已经生效了,但是还没有复制回客户端,所以在此处进行截取操作的话实际上不会进行两次值的复制。客户端仅收到截取过后的结果(最终值)。

4.4.7 OnAttributeAggregatorCreated()

OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator)会在AttributeSet中的某个Attribute的聚合器Aggregator创建时进行触发。这里可以自定义设置FAggregatorEvaluateMetaDataAggregator使用AggregatorEvaluateMetaData,基于所有应用到当前AttributeModifiers来计算该Attribute的的CurrentValue。默认为情况下,Aggregator使用AggregatorEvaluateMetaData来确定哪些Modifiers符合MostNegativeMod_AllPositiveMods的要求,而MostNegativeMod_AllPositiveMods允许所有的正面的Modifiers后仅仅最负面的Modifiers。Paragon(虚幻争霸)中使用的就是这种方式,对于负面的减速效果的话在某个具体的时间点不管施加多少个只应用最负面的那个,而所有的正面的加速效果则全盘应用。没有通过要求的Modifiers仍然会存在于ASC上,只是不会进一步汇总到CurrentValue里。当某些情况发生变化后,他们可能又有可能性来通过验证,比如说之前影响最大的减速效果结束了,那么就会从还没超时的Modifier(如果还有的话)中挑一个减速效果最强的应用。

上述例子中的AggregatorEvaluateMetaData的使用:

virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const override;
void UGSAttributeSetBase::OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const
{Super::OnAttributeAggregatorCreated(Attribute, NewAggregator);if (!NewAggregator){return;}if (Attribute == GetMoveSpeedAttribute()){NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods;}
}

自定义的AggregatorEvaluateMetaData限定符应该以静态变量的形式添加到FAggregatorEvaluateMetaDataLibrary

4.4 属性集 - Attribute Set相关推荐

  1. EBS OAF开发中属性集(Attribute Set)的介绍和手工实现

    EBS OAF开发中属性集(Attribute Set)的介绍和手工实现 (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处:否则请与本人联系,违者必究) 在OAF开发中就和F ...

  2. Magento后台添加商品属性集属性集详细教程

    第一步进入属性列表页: 第二步点击Add New Attribute进入属性添加页面: 第三步按照下面样式填写: 然后点击Save Attribute保存,然后列表页中就新加了该属性: 第四步进入属性 ...

  3. Java实现构建函数依赖与函数依赖集的类、求函数依赖集的闭包、属性集闭包、判断属性集是否为候选码/超码、求集合的全部子集

    求函数依赖集闭包 本文讲解具体代码实现,相关概念可查阅资料(其实我只是想把代码保存到博客上嘿嘿 这算是我第一次能够用构建一个个类来解决一个较大的问题(不再是像考试一样在一个类里写n个函数了ORZ),在 ...

  4. 编写windows 控件需要注意的几个标签属性(Attribute)

    我们在编程控件(Control)或者是组件(Component)的时候总是要暴露一些属性(Property,不同于Attribute)给用户进行配置,以得到可以变化的功能. mapserver .NE ...

  5. attribute java c_属性别名(Attribute Aliasing)

    属性别名(Attribute Aliasing) 属性别名用于将成员变量序列化为XML属性. 让我们再次修改我们的示例并将以下代码添加到其中.xstream.useAttributeFor(Stude ...

  6. 【Java基础】Java中的持久属性集Properties

    Properties 类的介绍 Properties 类表示了一个持久的属性集.Properties 可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串.一个属性列表可包含另一个属性列 ...

  7. Properties 持久的属性集

    特点: 1.Hashtable的子类,map集合中的方法都可以用. 2.该集合没有泛型.键值都是字符串. 3.它是一个可以持久化的属性集.键值可以存储到集合中,也可以存储到持久化的设备(硬盘.U盘.光 ...

  8. [html] HTML全局属性(global attribute)有哪些(包含H5)?

    [html] HTML全局属性(global attribute)有哪些(包含H5)? classiddatatypehrefstylewidthheighttargecheckeddisabledr ...

  9. java props_spring:使用prop标签为Java持久属性集注入值

    spring:使用标签为Java持久属性集注入值 使用 spring 提供的为Java持久属性集注入值,也就是向 java.util.Properties 对象中注入值. 是简化了的 ,该元素对应配置 ...

最新文章

  1. CSS中的层叠性、继承性、优先级、权重
  2. 情人节找个程序员当男朋友,一般都不会太差
  3. pads元件类型如何修改_pads logic元件库修改了,怎样更新到原理图
  4. 处理quartz 异常 Couldn‘t retrieve trigger: No record found for selection of Trigger with key:
  5. 冒泡排序面向对象java_冒泡排序
  6. 开发工具篇 程序员计算器
  7. ddos应急处理_来看看DDoS攻击应急预案
  8. 智能车基于RT1064+无线串口透传模块利用MATLAB辅助调节PID参数
  9. 会员管理小程序实战开发教程(六)-会员查询功能
  10. 前端框架MVC和MVVM的理解
  11. html只显示一句话_您所说的话:如何最大化多显示器设置
  12. 分析Adobe Illustrator CC(AI)中的橡皮擦和直线工具
  13. iOS - 动态库上架瘦身(去调虚拟机架构),不然验证会报错。
  14. 考试提交答案后台代码
  15. 黄聪:Windows7立体声混音设置方法(stereo mix)(转)
  16. 【考生说】持之以恒才是胜利的推动器、一周时间通过C认证的同学怎么说
  17. 怎么用计算机算三角函数值,手机计算器怎么算三角函数值
  18. OCR文字识别工具,轻松帮你识别图片上的文字
  19. 从零开始搭建仿抖音短视频APP--后端开发粉丝业务模块(3)
  20. mt4 python_迈达克软件公司承认Python运算对量化交易的重要性将Python与Metatrader 5集成一体...

热门文章

  1. sklearn安装清华镜像
  2. 宝塔nginx配置wss协议
  3. 工程机械比例阀电流采集方案
  4. 组态王图素制作_组态王教程很实用.doc
  5. C程序括号匹配检查(带详细注释)
  6. jenkins占据内存过大
  7. 华为hs8145v5如何改桥接_华为HS8145v5改华为界面和默认超密
  8. 购买或者租用服务器注意项
  9. 缓和曲线计算机编程,缓和曲线起点
  10. matlab二维怎么定义,matlab中如何定义一个10*2的二维数组,用来储存后面算出的数值...