在游戏中,游戏的角色属性会根据游戏玩法也不同,当前制作的RPG中,主要分为两种属性,一种是主要属性另一种次级属性。这两种类型属性的区分主要数值的计算是否需要依托于其它数值
举个例子,你的体力值主要在于角色的个人成长增加以及装备等的增加,它不依托于其它属性,所以是主要属性。而你的血量上限需要依托于体力数值,根据体力进行一定的比例设置血量上线,它需要依托于体力属性,所以是次级属性。
接下来,我们首先将属性设置完整,这个属性一般需要策划进行设计,然后程序进行实现,如果自己学习,那么你就是策划,也是程序,所以,接下来我们自己设计一下属性。
经过了长达几分钟的思考,主要属性为一下几项:
- Strength 力量
- Intelligence 智力
- Resilience 韧性
- Vigor 体力
而次要属性里面我们设计了多项数值,依托于上面的主要属性,主要用于战斗中
- MaxHealth 血量上限,基于Vigor 体力属性计算
- MaxMana 蓝量上限,基于Intelligence 智力属性
- Armor 防御,基于Resilience 韧性属性计算, 降低所受伤害
- ArmorPenetration 护甲穿透,基于Resilience 韧性属性计算,降低敌人的防御,增加暴击率
- BlockChance 格挡率 ,基于Armor 防御属性计算,增加格挡伤害概率,触发时,降低一半所受伤害
- CriticalHitChance 暴击率,基于ArmorPenetration 护甲穿透属性计算,增加触发暴击伤害的概率
- CriticalHitDamage 暴击伤害,基于ArmorPenetration 护甲穿透属性计算,触发暴击时基于增加的伤害量
- CriticalHitResistance 暴击抵抗,基于Armor 防御属性计算,降低敌人的暴击概率
- HealthRegeneration 血量自动恢复,基于Vigor 体力属性计算,每秒自动恢复一定血量
- ManaRegeneration 蓝量自动恢复,基于Intelligence 智力属性,每秒自动恢复蓝量
在AS增加新的属性
我们需要将上面提到的所有属性添加到AS里面,虽然之前的文章说了很多遍如何添加了,这里再复习一遍加强记忆。
在AttributeSet类的上面,我们先定义了一个宏,在调用这个宏的时候,会给我们生成属性对应的定义:
// 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)
这个宏定义生成四个调用的函数使用。
接下里就是定义一个属性的步骤:
- 首先创建一个属性,定义好蓝图可读,定义和服务器的同步函数,以及分类
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Strength, Category="Primary Attributes")FGameplayAttributeData Strength; //力量
- 设置宏,生成对属性的操作函数
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Strength, Category="Primary Attributes")FGameplayAttributeData Strength; //力量ATTRIBUTE_ACCESSORS(UAttributeSetBase, Strength);
ATTRIBUTE_ACCESSORS宏调用后,最终会生成四个函数,那Strength举例,会生成四个函数分别是:GetStrengthAttribute() 获取属性引用 GetStrength() 获取属性值 SetStrength(float NewValue) 设置属性值 InitStrength(float NewValue) 初始化属性值
- 定义OnRep_Strength,这个函数会在服务器上的属性值发生变化时,将服务器的数值同步到本地。
UFUNCTION()void OnRep_Strength(const FGameplayAttributeData& OldStrength) const;
在cpp中的实现
void UAttributeSetBase::OnRep_Strength(const FGameplayAttributeData& OldStrength) const
{GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Strength, OldStrength);
}
这个宏会生成对应的函数回调,在服务器值修改时,同步到本地。
https://docs.unrealengine.com/5.3/zh-CN/conditional-property-replication-in-unreal-engine/
4. 最后一步就是在GetLifetimeReplicatedProps回调函数中注册此属性提交服务器同步,在本地属性发生改变时,将此值提交给服务器。
void UAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{Super::GetLifetimeReplicatedProps(OutLifetimeProps);//主要属性DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Strength, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Intelligence, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Resilience, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Vigor, COND_None, REPNOTIFY_Always);//次级属性DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Health, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, MaxHealth, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Mana, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, MaxMana, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Armor, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, ArmorPenetration, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, BlockChance, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, CriticalHitChance, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, CriticalHitDamage, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, CriticalHitResistance, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, HealthRegeneration, COND_None, REPNOTIFY_Always);DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, ManaRegeneration, COND_None, REPNOTIFY_Always);
}
通过以上四个步骤,我们将所有的属性都注册一下。编译运行一下,showdebug abilitysystem 看一下属性
实现次级属性的设置
上面,我们把属性都创建了出来,而次级属性需要在主要属性变动的时候,实时变动,这里也不卖关子了,要实现这种效果,我们使用GE的Infinite,在主要属性变动,实时OverWrite覆写值即可。
接下来,我们将实现此功能,在上一章中,我们在角色的基础类中实现了一个方法,实现了主要属性数值的添加
void ACharacterBase::InitializePrimaryAttributes() const
{check(IsValid(GetAbilitySystemComponent()));check(DefaultPrimaryAttributes);const FGameplayEffectContextHandle ContextHandle = GetAbilitySystemComponent()->MakeEffectContext();const FGameplayEffectSpecHandle SpecHandle = GetAbilitySystemComponent()->MakeOutgoingSpec(DefaultPrimaryAttributes, 1.0f, ContextHandle);GetAbilitySystemComponent()->ApplyGameplayEffectSpecToTarget(*SpecHandle.Data.Get(), GetAbilitySystemComponent());
}
接下来,我们要修改这个函数可以复用,因为我们将需要添加主要属性和次要属性,这两个属性需要分开添加,我们将这个函数增加两个参数,一个是类作为参数传入,另一个则是玩家等级(后续可以根据等级设置不同的属性)
void ACharacterBase::ApplyEffectToSelf(TSubclassOf<UGameplayEffect> GameplayEffectClass, float Level) const
{check(IsValid(GetAbilitySystemComponent()));check(GameplayEffectClass);const FGameplayEffectContextHandle ContextHandle = GetAbilitySystemComponent()->MakeEffectContext();const FGameplayEffectSpecHandle SpecHandle = GetAbilitySystemComponent()->MakeOutgoingSpec(GameplayEffectClass, Level, ContextHandle);GetAbilitySystemComponent()->ApplyGameplayEffectSpecToTarget(*SpecHandle.Data.Get(), GetAbilitySystemComponent());
}
增加一个GE属性,用于在角色身上设置次级属性的GE
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="Attributes")TSubclassOf<UGameplayEffect> DefaultSecondaryAttributes;
增加一个函数,用于初始化两个GE,调用ApplyEffectToSelf函数
void ACharacterBase::InitializeDefaultAttributes() const
{ApplyEffectToSelf(DefaultPrimaryAttributes, 1.f);ApplyEffectToSelf(DefaultSecondaryAttributes, 1.f);
}
最后,在英雄角色初始化数据的地方,修改为调用InitializeDefaultAttributes()即可
编译打开UE,在角色属性查看是否可以设置两个类
后面,我们只需要实现次级属性的GE应用即可
实现次级属性的GE
前面提到了,我们要实现次级属性的设置,需要使用GameplayEffect去实现,所以,我们创建一个专门设置次级属性的GE
设置应用给角色
设置持续时间为无限
先增加一个属性进行测试,这里我们使用对Maxhealth进行修改,比例和体力一比一,就是多少体力就有多少最大血量,运算模式记得修改为覆盖(覆写)
运行查看,发现体力和最大血量是相同的
接下来我们修改碰撞盒子,在碰撞事件中,增加体力十点,查看最大血量是否跟着改变
结果显而易见,通过利用GE我们简简单单就实现了对属性的实时修改。
接下来就是对大批量的属性进行修改,这个工作主要是策划进行设计,作为独立游戏,没有策划,你就是策划,我们就随意写一套用作测试所有属性好了。
这里,我将所有的次级属性都进行了设置
下面对每个属性设置的参数一一截图
运行项目,showdebug abilitysystem 发现属性都正常设置
如果属性过多,后面使用debug查看会很麻烦,所以一般情况,都有一个属性面板查看角色的属性,后面,我们将制作一个属性面板进行属性查看