|
发表于 2022-12-22 18:52:32
|
显示全部楼层
前记:
很多翻译都是用的机器翻译不是很准确,我认真看了英文文档,再加上中文翻译做了该摘要,方便后来的学习者,也方便自己后续查阅。
字字不易,希望多多点赞收藏。也希望多多提意见
正文:
1、AttributeSet说明
/**
* Defines the set of all GameplayAttributes for your game
* Games should subclass this and add FGameplayAttributeData properties to represent attributes like health, damage, etc
* AttributeSets are added to the actors as subobjects, and then registered with the AbilitySystemComponent
* It often desired to have several sets per project that inherit from each other
* You could make a base health set, then have a player set that inherits from it and adds more attributes
*/
UCLASS(DefaultToInstanced, Blueprintable)
class GAMEPLAYABILITIES_API UAttributeSet : public UObject
{
GENERATED_UCLASS_BODY()
public:
/** Override to disable initialization for specific properties */
virtual bool ShouldInitProperty(bool FirstInit, FProperty* PropertyToInit) const { return true; }
属性定义在AttributeSet中,由AttributeSet管理。开发者应该从UAttributeSet继承出自己的AttributeSet 。AttributeSet定义在需要属性的Actor上 面。在构造AttributeSet的时候,AttributeSet会自动的把自己注册在Owner的AbilitySystemComponent(ASC)里面。
2、怎么设计AttributeSet
一个ASC可以拥有一个或多个AttributeSets。在游戏中所有Actor共享一个巨大的AttributeSet也是可行的,每个Actor仅使用需要的属性即可。开发者也可以根据游戏设计需求,提取多个通用的AttributeSet,互相组合给不同类型的Actor用。
Attributes在内部以AttributeSetClassName.AttributeName的方式引用。当继承了AttributeSet后,所有父类的属性也必须通过父类作为前缀引用(ParentClassName.AttributeName)。
所以在一个ASC中可以有多个不同的AttributeSet但是同一类AttributeSet在一个ASC中最多只能有一个。
3、在运行时添加和删除AttributeSet
ASC可以在运行时添加和删除AttributeSet,但是这个操作很危险。如果一个客户端先于服务器删除了一个AttributeSet,之后服务器同步这个AttributeSet的属性的时候,会导致客户端crash。
运行时添加AttributeSet的用例:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().AddUnique(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();运行时删除AttributeSet的用例:
AbilitySystemComponent->GetSpawnedAttributes_Mutable().Remove(WeaponAttributeSetPointer);
AbilitySystemComponent->ForceReplication();4、定义属性
Attributes只能在C++的AttributeSet头文件中定义。在我们的AttributeSet头文件中定义如下的宏
// 定义一个增加各种Getter和Setter方法的宏
#define ATTRIBUTE_ACCESSORS( , PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)然后我们通过这个宏就可以为每个属性定义一套Get,Set,Initial的方法。
UPROPERTY(BlueprintReadOnly, Category = "Health", ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UJMZTpAttributeSet, Health);
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldValue);上面定义了一个可以同步的属性,如果需要该属性支持预测需要做如下的操作:
void UJMZTpAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(USampleAttributeSet, Health, OldValue);
}
void UJMZTpAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UJMZTpAttributeSet, Health, COND_None, REPNOTIFY_Always);
//DOREPLIFETIME(UJMZTpAttributeSet, MaxHealth);
//DOREPLIFETIME(UJMZTpAttributeSet, Physical);
//DOREPLIFETIME(UJMZTpAttributeSet, MaxPhysical);
}REPNOTIFY_Always,只有在客户端预测的值和服务器同步的值相等之后才触发回调函数OnRep_Health。如果客户端值本来就可以服务器的值一样不会触发回调函数OnRep_Health。
如果属性不需要同步,就不需要设置回调函数OnRep,和上面这些操作。
5、属性的初始化
属性初始化的方式有多种,Epic 推荐使用instant类型的GameplayEffect 。ATTRIBUTE_ACCESSORS这个宏会为每个Attribute生成一个,可以在C++里面直接调用的初始化函数。样例如下:
// InitHealth(float InitialValue) is an automatically generated function for an Attribute &#39;Health&#39; defined with the `ATTRIBUTE_ACCESSORS` macro
AttributeSet->InitHealth(100.0f);6、属性改变前的回调函数
PreAttributeChange & PreAttributeBaseChange
just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like &#34;Health = Clamp(Health, 0, MaxHealth)&#34; and NOT things like &#34;trigger this extra thing if damage is applied, etc&#34;.
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) {
if (Attribute == GetMoveSpeedAttribute())
{
// Cannot slow less than 150 units/s and cannot boost more than 1000 units/s
NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
}
}
/**
* This is called just before any modification happens to an attribute&#39;s base value when an attribute aggregator exists.
* This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
* This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
* final value of the attribute actually changing.
*/
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
AttributeSet中的任何属性的CurrentValue改变之前,都会调用PreAttributeChange。这个回调一般情况下是用来对属性的值做约束,比如Speed必须在150到1000之间。但是属性改变需要触发的事件不建议用这个回调。关于具体属性改变需要触发的事件,建议用下面的方式,更准确和高效。
UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute) (Responding to Attribute Changes).
其实我个人感觉对具体属性做合理值的修正,用PreAttributeChange效率也有点低。应该通过手写具体属性的Setter函数,在具体属性的Setter函数里做限制,这样会更高效。
PreAttributeBaseChange 略……
7、属性改变后的回调
/**
* Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an &#39;execute&#39;. E.g., a modification to the &#39;base value&#39; of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
当instant类型的GameplayEffect (GE)修改属性的BaseValue之后会触发PostGameplayEffectExecute的回调。通过这个回调我们可以做很多属性改变后的处理,比如命中的受击动画,伤害飘字等。
当PostGameplayEffectExecute被调用的时候,属性值已经被改变了,当时改变的属性值还没有同步到客户端。这个时候如果我们对属性做二次修改,客户端收到的将是修改后的值。
8、TODO
关于UAttributeSet 还有很多更复杂的操作,因为暂时还没有更详细的接触,所以没有做更进一步的阐述。将来对GAS接触的更深入的时候,再补。
UCLASS(DefaultToInstanced, Blueprintable)
class GAMEPLAYABILITIES_API UAttributeSet : public UObject
{
GENERATED_UCLASS_BODY()
public:
/** Override to disable initialization for specific properties */
virtual bool ShouldInitProperty(bool FirstInit, FProperty* PropertyToInit) const { return true; }
/**
* Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification.
* Note this is only called during an &#39;execute&#39;. E.g., a modification to the &#39;base value&#39; of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
/**
* Called just before a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an &#39;execute&#39;. E.g., a modification to the &#39;base value&#39; of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
/**
* An &#34;On Aggregator Change&#34; type of event could go here, and that could be called when active gameplay effects are added or removed to an attribute aggregator.
* It is difficult to give all the information in these cases though - aggregators can change for many reasons: being added, being removed, being modified, having a modifier change, immunity, stacking rules, etc.
*/
/**
* Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like &#34;Health = Clamp(Health, 0, MaxHealth)&#34; and NOT things like &#34;trigger this extra thing if damage is applied, etc&#34;.
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
/**
* This is called just before any modification happens to an attribute&#39;s base value when an attribute aggregator exists.
* This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
* This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
* final value of the attribute actually changing.
*/
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
/** Callback for when an FAggregator is created for an attribute in this set. Allows custom setup of FAggregator::EvaluationMetaData */
virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const { }
/** This signifies the attribute set can be ID&#39;d by name over the network. */
void SetNetAddressable();
/** Initializes attribute data from a meta DataTable */
virtual void InitFromMetaDataTable(const UDataTable* DataTable);
/** Gets information about owning actor */
FORCEINLINE AActor* GetOwningActor() const { return CastChecked<AActor>(GetOuter()); }
UAbilitySystemComponent* GetOwningAbilitySystemComponent() const;
FGameplayAbilityActorInfo* GetActorInfo() const;
/** Print debug information to the log */
virtual void PrintDebug();
// Overrides
virtual bool IsNameStableForNetworking() const override;
virtual bool IsSupportedForNetworking() const override;
virtual void PreNetReceive() override;
virtual void PostNetReceive() override;
protected:
/** Is this attribute set safe to ID over the network by name? */
uint32 bNetAddressable : 1;
}; |
|