当前位置: 首页 > news >正文

AbilitySystemComponent_Abilities源码解析(一)

文章目录

  • 一、InitializeComponent
  • 二、UninitializeComponent
  • 三、OnComponentDestroyed
  • 四、TickComponent
  • 五、InitAbilityActorInfo
  • 六、GetShouldTick
    • 场景分析
      • 场景1:服务器端播放动画蒙太奇
      • 场景2:客户端接收动画同步
      • 场景3:有需要Tick的属性集
      • 场景4:没有任何需要Tick的情况
    • 设计意义和性能优化
    • 实际应用例子
  • 七、SetAvatarActor
    • 典型应用场景
    • 底层发生的变化
    • 设计意义
  • 八、ClearActorInfo
    • 典型应用场景
    • 底层发生的变化
    • 与相关函数的对比
    • 设计意义和注意事项
  • 九、OnRep_OwningActor
    • 网络复制背景
    • 典型应用场景
    • 具体网络游戏示例
    • 设计意义和网络同步
    • 错误处理场景
  • 十、RefreshAbilityActorInfo
    • 与相关函数的对比
    • 典型应用场景
    • 底层发生的变化
    • 具体游戏机制示例


一、InitializeComponent

void UAbilitySystemComponent::InitializeComponent()
{Super::InitializeComponent(); // 调用父类的初始化函数,确保基础功能正确设置// 获取拥有此能力系统组件的Actor,比如游戏中的角色或敌人AActor *Owner = GetOwner();// 初始化能力Actor信息,设置Owner和Avatar为同一个Actor// 这告诉GAS系统哪个Actor拥有这些能力,哪个Actor是这些能力效果的目标InitAbilityActorInfo(Owner, Owner);// 清理SpawnedAttributes数组中可能存在的空指针,防止后续访问时崩溃// 从后往前遍历是为了安全地移除元素(避免索引错乱)for (int32 Idx = SpawnedAttributes.Num()-1; Idx >= 0; --Idx){if (SpawnedAttributes[Idx] == nullptr){SpawnedAttributes.RemoveAt(Idx);}}// 查找Owner Actor的所有子对象,目的是自动发现并注册AttributeSetsTArray<UObject*> ChildObjects;GetObjectsWithOuter(Owner, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::Garbage);// 遍历所有子对象,将找到的AttributeSet添加到SpawnedAttributes数组中for (UObject* Obj : ChildObjects){UAttributeSet* Set = Cast<UAttributeSet>(Obj); // 尝试将对象转换为AttributeSetif (Set)  // 如果转换成功,说明找到了一个属性集{SpawnedAttributes.AddUnique(Set); // 添加到属性集列表,确保不重复}}// 标记属性集列表已更新,触发内部状态刷新和可能的网络同步SetSpawnedAttributesListDirty();
}

结合具体例子说明:
假设我们有一个英雄角色 HeroCharacter,它包含:

  1. 一个 AbilitySystemComponent(能力系统组件)
  2. 一个 HealthAttributeSet(生命值属性集,管理生命值、最大生命值等)
  3. 一个 ManaAttributeSet(法力值属性集,管理法力值、法力回复等)

执行流程:

  1. 初始化基础:首先调用父类初始化,确保组件基础功能就绪

  2. 设置Actor信息:获取 HeroCharacter 作为Owner,并调用 InitAbilityActorInfo 告诉GAS系统:“这个能力系统属于 HeroCharacter,能力效果也应用于 HeroCharacter

  3. 清理旧数据:检查现有的 SpawnedAttributes 数组,移除任何空的属性集引用

  4. 自动发现属性集

    • GetObjectsWithOuter 会找到 HeroCharacter 的所有直接子对象
    • 在遍历中会发现 HealthAttributeSetManaAttributeSet
    • 这两个属性集都会被添加到 SpawnedAttributes 数组中
  5. 完成注册:调用 SetSpawnedAttributesListDirty 通知系统属性集已更新,现在GAS可以开始管理这些属性(处理属性变化、网络同步、应用GameplayEffect等)

设计优势:这种自动发现机制意味着开发者只需要在Actor蓝图中添加AttributeSet组件,GAS就会自动识别和注册,无需手动配置,大大简化了设置流程。

二、UninitializeComponent

void UAbilitySystemComponent::UninitializeComponent()
{// 调用父类的UninitializeComponent函数,执行基础的反初始化逻辑// 这确保父类有机会清理它自己分配的资源Super::UninitializeComponent();// 清理活跃的游戏效果(ActiveGameplayEffects)// 这包括移除所有正在应用的buff/debuff,停止效果计时器,清理内部数据结构// 防止内存泄漏和悬空指针ActiveGameplayEffects.Uninitialize();
}

结合具体例子详细说明:

假设我们有一个英雄角色 HeroCharacter,它包含一个 AbilitySystemComponent,当前正应用着以下游戏效果:

  • 一个持续30秒的"攻击力提升"buff
  • 一个持续10秒的"中毒"debuff(每2秒扣血)
  • 一个永久的"火焰抗性"被动效果

执行流程和效果:

  1. Super::UninitializeComponent();

    • 调用 UActorComponent 的卸载函数
    • 停止组件tick,取消事件绑定,执行基础的组件清理工作
    • 确保组件从世界中正确分离
  2. ActiveGameplayEffects.Uninitialize();

  • 移除所有活跃效果:立即移除"攻击力提升"、"中毒"和"火焰抗性"效果
  • 停止所有计时器:停止"中毒"效果的周期性伤害计时器
  • 清理内部数据结构:清空效果列表、属性聚合器等内部容器
  • 释放引用:断开对所有GameplayEffect对象的引用,允许垃圾回收
  • 重置状态:将ActiveGameplayEffects子系统恢复到初始状态

为什么需要这样做:

  1. 防止内存泄漏:如果不清理,持续效果中的计时器和回调可能无法被正确释放
  2. 避免悬空指针:效果可能持有对已销毁Actor或Component的引用,清理可防止后续访问崩溃
  3. 状态一致性:确保组件在销毁或重新初始化时处于干净状态
  4. 网络同步:如果是网络游戏,需要正确结束效果的同步

典型调用场景:

  • 角色死亡并被销毁时
  • 组件被手动移除时
  • 关卡切换时
  • 游戏结束时

这个反初始化过程确保了 AbilitySystemComponent 能够优雅地关闭,不会留下任何残留的效果或资源。

三、OnComponentDestroyed

void UAbilitySystemComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{// 销毁活跃状态:清理所有激活的能力、游戏效果和其他活跃状态// 这确保在组件被销毁前,所有运行中的效果都被正确终止DestroyActiveState();// 遍历所有通过该组件生成的属性集(AttributeSets)// 注释解释了为什么将标记垃圾的操作从UninitializeComponent移到这里:// 因为组件可能被反初始化后重新初始化而不被销毁(例如当拥有者的关卡被移除后又重新添加到世界时)// 在这种情况下,属性集需要被保留以便重新使用for (UAttributeSet* Set : GetSpawnedAttributes()){if (Set){// 将属性集标记为垃圾,允许垃圾回收系统在适当时机回收它们// 这防止了内存泄漏,确保属性集对象被正确清理Set->MarkAsGarbage();}}// 在完成所有必要的清理工作后,最后调用父类的OnComponentDestroyed// 这是标准的UE组件销毁模式,确保父类有机会执行自己的清理逻辑Super::OnComponentDestroyed(bDestroyingHierarchy);
}

结合具体例子详细说明:
假设我们有一个英雄角色 HeroCharacter,它包含:

  • 一个 AbilitySystemComponent
  • 两个属性集:HealthAttributeSetManaAttributeSet

场景1:角色永久销毁
当玩家退出游戏或角色被永久移除时:

  1. DestroyActiveState()

    • 立即终止所有激活的能力(如正在施放的火球术)
    • 移除所有游戏效果(如攻击力buff、中毒效果)
    • 停止所有内部计时器和状态机
  2. 属性集标记为垃圾

    • HealthAttributeSetManaAttributeSet 都被标记为垃圾
    • 它们将在垃圾回收周期中被自动销毁
    • 防止内存泄漏
  3. 父类清理

    • 执行 UActorComponent 的基础销毁逻辑

场景2:关卡流式加载(解释注释中的特殊情况)

假设游戏使用关卡流式加载:

  1. 玩家从"森林关卡"移动到"城堡关卡"
  2. "森林关卡"被卸载,HeroCharacter 的组件被反初始化(UninitializeComponent
  3. 但角色对象本身没有被销毁
  4. 当玩家返回"森林关卡"时,组件被重新初始化

为什么需要在这里而不是在 UninitializeComponent 中标记属性集:

  • 如果在上面的场景2中,在 UninitializeComponent 就标记属性集为垃圾:

    • 当关卡重新加载时,属性集已经被销毁
    • 重新初始化时会找不到属性集,导致错误
  • OnComponentDestroyed 中标记:

    • 只有当真正确销毁组件时(场景1),属性集才会被标记为垃圾
    • 在临时反初始化场景(场景2)中,属性集得以保留,可以在重新初始化时继续使用

设计意义:
这段代码体现了资源管理的精细控制,区分了"临时停用"和"永久销毁"两种情况,确保在组件生命周期管理中既不会内存泄漏,也不会在需要重新使用时丢失重要资源。

四、TickComponent

void UAbilitySystemComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{	// 开始性能统计范围,用于统计AbilityTasks的Tick耗时// 在性能分析工具中可以看到这个统计项SCOPE_CYCLE_COUNTER(STAT_TickAbilityTasks);// CSV性能统计,用于在CSV文件中记录AbilityTasks的独占执行时间CSV_SCOPED_TIMING_STAT_EXCLUSIVE(AbilityTasks);// 如果当前组件在权威端(服务器端),则更新动画蒙太奇的复制数据// 这确保服务器的动画状态能够正确同步到客户端if (IsOwnerActorAuthoritative()){AnimMontage_UpdateReplicatedData();}// 调用父类的TickComponent,确保基础组件的每帧逻辑得到执行Super::TickComponent(DeltaTime, TickType, ThisTickFunction);// 遍历所有生成的属性集,检查是否有需要每帧更新的可Tick属性集for (UAttributeSet* AttributeSet : GetSpawnedAttributes()){// 尝试将属性集转换为可Tick的接口ITickableAttributeSetInterface* TickableSet = Cast<ITickableAttributeSetInterface>(AttributeSet);// 如果属性集实现了可Tick接口且应该被Tick,则调用其Tick函数if (TickableSet && TickableSet->ShouldTick()){TickableSet->Tick(DeltaTime);}}
}

结合具体例子详细说明:

假设我们有一个多人游戏中的英雄角色 HeroCharacter,它包含:

  • 一个 AbilitySystemComponent
  • 一个 HealthAttributeSet(基础生命值属性)
  • 一个 SpecialAttributeSet(特殊属性集,实现了 ITickableAttributeSetInterface

执行流程和具体作用:

  1. 性能统计
SCOPE_CYCLE_COUNTER(STAT_TickAbilityTasks);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(AbilityTasks);
  • 在开发阶段,这些宏帮助开发者监控AbilityTasks的性能
  • 如果发现 STAT_TickAbilityTasks 耗时过高,说明需要优化能力任务逻辑
  1. 服务器端动画同步
if (IsOwnerActorAuthoritative())
{AnimMontage_UpdateReplicatedData();
}

例子:英雄在服务器端施放"旋风斩"技能,播放相应的动画蒙太奇:

  • 服务器会通过 AnimMontage_UpdateReplicatedData() 将动画状态(播放位置、速率等)同步到所有客户端
  • 客户端收到数据后,会同步播放相同的动画,确保所有玩家看到的动画效果一致
  1. 父类Tick
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
  • 执行 UActorComponent 的基础Tick逻辑
  • 处理组件的基础更新任务
  1. 可Tick属性集的更新
for (UAttributeSet* AttributeSet : GetSpawnedAttributes())
{ITickableAttributeSetInterface* TickableSet = Cast<ITickableAttributeSetInterface>(AttributeSet);if (TickableSet && TickableSet->ShouldTick()){TickableSet->Tick(DeltaTime);}
}

具体例子:

  • HealthAttributeSet:普通属性集,不需要每帧更新
  • SpecialAttributeSet:实现了 ITickableAttributeSetInterface,可能需要每帧更新:
    场景1 - 持续恢复效果:
// 在SpecialAttributeSet的Tick函数中
void USpecialAttributeSet::Tick(float DeltaTime)
{// 每帧恢复1%的法力值float ManaRegen = GetMaxMana() * 0.01f * DeltaTime;SetMana(FMath::Min(GetMana() + ManaRegen, GetMaxMana()));
}

场景2 - 随时间衰减的buff:

void USpecialAttributeSet::Tick(float DeltaTime)
{// 随时间减少移动速度加成if (GetMoveSpeedBonus() > 0.0f){float DecayAmount = 10.0f * DeltaTime; // 每秒衰减10个单位SetMoveSpeedBonus(FMath::Max(0.0f, GetMoveSpeedBonus() - DecayAmount));}
}

场景3 - 周期性效果检查:

void USpecialAttributeSet::Tick(float DeltaTime)
{CooldownTimer += DeltaTime;if (CooldownTimer >= 1.0f) // 每秒执行一次{CooldownTimer = 0.0f;// 检查并处理周期性效果ProcessPeriodicEffects();}
}

设计意义:

  • 性能优化:通过接口检查,只有真正需要Tick的属性集才会被更新,避免不必要的性能开销
  • 扩展性:允许属性集实现复杂的随时间变化的逻辑,而不需要依赖外部的Timer或Tick管理
  • 网络同步:确保在权威端正确处理需要网络同步的动画数据

这种设计使得属性系统既能够处理静态的属性值,也能够处理动态的、随时间变化的属性逻辑,为复杂的游戏机制提供了强大的支持。

五、InitAbilityActorInfo

void UAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{// 验证AbilityActorInfo指针有效,如果无效则触发断言check(AbilityActorInfo.IsValid());// 记录AvatarActor是否之前为空,用于后续判断是否需要处理延迟的GameplayCuesbool WasAbilityActorNull = (AbilityActorInfo->AvatarActor == nullptr);// 检查AvatarActor是否发生变化bool AvatarChanged = (InAvatarActor != AbilityActorInfo->AvatarActor);// 使用新的Owner和Avatar Actor初始化AbilityActorInfo结构体// 这个结构体包含了GAS系统需要知道的关于Actor的所有信息AbilityActorInfo->InitFromActor(InOwnerActor, InAvatarActor, this);// 设置组件的主人Actor(通常是PlayerController或AI Controller)SetOwnerActor(InOwnerActor);// 缓存之前的AvatarActor以便后续比较,然后设置新的AvatarActor// AvatarActor通常是实际表现能力的Pawn或Characterconst AActor* PrevAvatarActor = GetAvatarActor_Direct();SetAvatarActor_Direct(InAvatarActor);// 如果之前AvatarActor为空而现在不为空,需要处理延迟的GameplayCues// 这些Cues可能因为之前没有AvatarActor而无法在NetDeltaSerialize中执行if ((WasAbilityActorNull || PrevAvatarActor == nullptr) && InAvatarActor != nullptr){HandleDeferredGameplayCues(&ActiveGameplayEffects);}// 如果AvatarActor发生变化,通知所有相关的能力实例if (AvatarChanged){ABILITYLIST_SCOPE_LOCK(); // 锁定能力列表,防止在遍历时被修改// 遍历所有可激活的能力规格for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items){if (Spec.Ability){// 根据能力的实例化策略分别处理if (Spec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor){// 对于每个Actor实例化一次的能力,获取其主要实例UGameplayAbility* AbilityInstance = Spec.GetPrimaryInstance();// 如果能力实例存在,通知它Avatar已设置if (AbilityInstance){AbilityInstance->OnAvatarSet(AbilityActorInfo.Get(), Spec);}}else{// 对于非实例化或每次激活都实例化的能力,直接通知能力类Spec.Ability->OnAvatarSet(AbilityActorInfo.Get(), Spec);}}}}// 获取全局的GameplayTag响应表并注册此组件响应事件if (UGameplayTagReponseTable* TagTable = UAbilitySystemGlobals::Get().GetGameplayTagResponseTable()){TagTable->RegisterResponseForEvents(this);}// 重置本地动画蒙太奇信息LocalAnimMontageInfo = FGameplayAbilityLocalAnimMontage();// 如果在权威端(服务器),重置复制的动画蒙太奇信息if (IsOwnerActorAuthoritative()){SetRepAnimMontageInfo(FGameplayAbilityRepAnimMontage());}// 如果有等待处理的蒙太奇复制,立即处理if (bPendingMontageRep){OnRep_ReplicatedAnimMontage();}
}

结合具体例子详细说明:

假设我们有一个多人游戏场景,玩家控制一个英雄角色:

场景:玩家重生或切换角色

// 当玩家重生或获得新角色时调用
InitAbilityActorInfo(PlayerController, NewHeroCharacter);

执行流程和具体作用:

  1. 初始检查和状态记录
check(AbilityActorInfo.IsValid());
bool WasAbilityActorNull = (AbilityActorInfo->AvatarActor == nullptr);
bool AvatarChanged = (InAvatarActor != AbilityActorInfo->AvatarActor);
  • 确保AbilityActorInfo有效
  • 记录之前的AvatarActor状态,用于后续判断
  1. 初始化Actor信息
AbilityActorInfo->InitFromActor(InOwnerActor, InAvatarActor, this);
SetOwnerActor(InOwnerActor);

例子:

  • InOwnerActor = PlayerController(控制者)
  • InAvatarActor = HeroCharacter(实际角色)
  • 这样设置后,GAS知道哪个Controller拥有能力,哪个Character执行能力
  1. 处理延迟的GameplayCues
if ((WasAbilityActorNull || PrevAvatarActor == nullptr) && InAvatarActor != nullptr)
{HandleDeferredGameplayCues(&ActiveGameplayEffects);
}

具体场景:

  • 玩家死亡后重生,之前的AvatarActor为null
  • 重生后获得新的HeroCharacter
  • 这时需要执行之前因为缺少Avatar而延迟的视觉效果
  • 比如:重生时立即显示的血条特效、复活光环等
  1. 通知能力实例Avatar变化
if (AvatarChanged)
{for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items){// 通知每个能力实例}
}

例子:

  • 玩家从"法师"切换到"战士"角色
  • 所有能力都需要知道新的AvatarActor
  • OnAvatarSet 让能力有机会:
    • 绑定到新角色的骨骼网格体
    • 重新设置动画实例
    • 更新能力特效的附着点
    • 重新计算基于新角色属性的数值
  1. GameplayTag响应表注册
if (UGameplayTagReponseTable* TagTable = UAbilitySystemGlobals::Get().GetGameplayTagResponseTable())
{TagTable->RegisterResponseForEvents(this);
}

作用:

  • 注册组件响应GameplayTag事件
  • 当特定的GameplayTag被添加或移除时,自动触发对应的效果
  • 比如:当角色获得"Burning"标签时自动播放燃烧特效
  1. 动画蒙太奇系统重置
LocalAnimMontageInfo = FGameplayAbilityLocalAnimMontage();
if (IsOwnerActorAuthoritative())
{SetRepAnimMontageInfo(FGameplayAbilityRepAnimMontage());
}
if (bPendingMontageRep)
{OnRep_ReplicatedAnimMontage();
}

作用:

  • 重置所有动画蒙太奇状态
  • 在服务器端初始化复制数据
  • 处理任何等待的蒙太奇复制,确保动画同步

典型应用场景:

  1. 角色重生:

    • 玩家死亡后重生,需要重新初始化所有能力到新角色
  2. 角色切换:

    • 在MOBA游戏中切换英雄
    • 在RPG游戏中切换职业
  3. 坐骑系统:

    • 玩家上马/下马,AvatarActor发生变化
  4. 附身控制:

    • AI或玩家控制不同的角色实体

设计意义:
这个函数是GAS系统中Actor身份变化的核心处理点,确保当控制权或物理表现实体发生变化时,所有能力、效果和视觉表现都能正确迁移到新的Actor上,维持游戏状态的连贯性和正确性。

六、GetShouldTick

bool UAbilitySystemComponent::GetShouldTick() const 
{// 检查是否需要更新复制的动画蒙太奇信息:// - 必须在权威端(服务器)// - 并且复制的蒙太奇没有停止const bool bHasReplicatedMontageInfoToUpdate = (IsOwnerActorAuthoritative() && GetRepAnimMontageInfo().IsStopped == false);// 如果需要更新复制的蒙太奇信息,则返回true(需要Tick)if (bHasReplicatedMontageInfoToUpdate){return true;}// 先检查父类是否需要Tickbool bResult = Super::GetShouldTick();    // 如果父类不需要Tick,再检查是否有需要Tick的属性集if (bResult == false){// 遍历所有生成的属性集for (const UAttributeSet* AttributeSet : GetSpawnedAttributes()){// 检查属性集是否实现了ITickableAttributeSetInterface接口const ITickableAttributeSetInterface* TickableAttributeSet = Cast<const ITickableAttributeSetInterface>(AttributeSet);// 如果属性集可Tick且应该被Tick,则返回trueif (TickableAttributeSet && TickableAttributeSet->ShouldTick()){bResult = true;break; // 找到一个需要Tick的属性集就足够,可以跳出循环}}}return bResult;
}

结合具体例子详细说明:

这个函数决定了 UAbilitySystemComponent 是否需要每帧进行更新(Tick),是一个性能优化的关键函数。

场景分析

场景1:服务器端播放动画蒙太奇

// 服务器端,英雄正在播放攻击动画蒙太奇
const bool bHasReplicatedMontageInfoToUpdate = (true && false == false); // 返回true
  • 英雄在服务器端施放"旋风斩"技能,播放动画蒙太奇
  • IsOwnerActorAuthoritative() = true(服务器端)
  • GetRepAnimMontageInfo().IsStopped = false(动画正在播放)
  • 结果:返回 true,组件需要Tick来同步动画状态到客户端

场景2:客户端接收动画同步

// 客户端,只是接收服务器的动画同步数据
const bool bHasReplicatedMontageInfoToUpdate = (false && ...); // 返回false
  • 客户端上的同一个英雄角色
  • IsOwnerActorAuthoritative() = false(客户端)
  • 结果:第一个条件不满足,继续检查其他条件

场景3:有需要Tick的属性集

// 假设有一个需要每帧更新的特殊属性集
class URegenerationAttributeSet : public UAttributeSet, public ITickableAttributeSetInterface
{
public:virtual bool ShouldTick() const override { return true; }virtual void Tick(float DeltaTime) override {// 每帧恢复生命值Health = FMath::Min(Health + HealthRegenRate * DeltaTime, MaxHealth);}
};

在这种情况下:

  1. 父类 Super::GetShouldTick() 返回 false
  2. 遍历属性集时发现 URegenerationAttributeSet 实现了 ITickableAttributeSetInterface
  3. TickableAttributeSet->ShouldTick() 返回 true
  4. 结果:返回 true,组件需要Tick来更新属性集的逻辑

场景4:没有任何需要Tick的情况

// 服务器端没有播放动画,也没有需要Tick的属性集
const bool bHasReplicatedMontageInfoToUpdate = (true && true); // GetRepAnimMontageInfo().IsStopped == true
bool bResult = Super::GetShouldTick(); // 假设返回false
// 遍历所有属性集,没有找到需要Tick的
  • 服务器端英雄处于闲置状态,没有播放动画
  • 所有属性集都不需要Tick
  • 结果:返回 false,组件不需要Tick,节省性能

设计意义和性能优化

  1. 按需Tick:只在真正需要的时候才进行Tick,避免不必要的性能开销
  2. 分层检查
    • 首先检查最高优先级的蒙太奇同步需求
    • 然后检查父类的基础需求
    • 最后检查属性集的Tick需求
  3. 网络优化
    • 服务器需要Tick来同步动画状态
    • 客户端通常不需要为此Tick(除非有本地预测等高级功能)
  4. 可扩展性
    • 通过 ITickableAttributeSetInterface 接口,允许属性集声明自己的Tick需求
    • 新的属性集可以轻松集成到现有的Tick系统中

实际应用例子

游戏中的持续恢复效果:

// 生命恢复属性集
class UHealthRegenAttributeSet : public UAttributeSet, public ITickableAttributeSetInterface
{float Health;float MaxHealth;float HealthRegenRate; // 每秒恢复量public:virtual bool ShouldTick() const override { // 只有当生命值不满且恢复率大于0时才需要Tickreturn Health < MaxHealth && HealthRegenRate > 0.0f; }virtual void Tick(float DeltaTime) override {Health = FMath::Min(Health + HealthRegenRate * DeltaTime, MaxHealth);}
};

在这种情况下,只有当角色生命值不满且确实在恢复时,属性集才会要求Tick,进一步优化了性能。

这种精细的Tick控制机制确保了GAS系统在提供强大功能的同时,也能保持良好的运行时性能。

七、SetAvatarActor

void UAbilitySystemComponent::SetAvatarActor(AActor* InAvatarActor)
{// 验证AbilityActorInfo指针有效,如果无效则触发断言崩溃// 确保在设置AvatarActor之前,AbilityActorInfo已经被正确初始化check(AbilityActorInfo.IsValid());// 使用当前的OwnerActor和新的AvatarActor重新初始化能力Actor信息// 这会更新GAS系统中所有相关的引用和状态,确保系统知道新的AvatarActorInitAbilityActorInfo(GetOwnerActor(), InAvatarActor);
}

结合具体例子详细说明:

这个函数是GAS系统中一个关键的接口函数,用于动态改变AvatarActor(即实际表现和执行能力的Actor)。

典型应用场景

场景1:角色坐骑系统

// 玩家上马
void AHeroCharacter::MountHorse(AHorse* Horse)
{// 将AbilitySystemComponent的AvatarActor设置为马AbilitySystemComponent->SetAvatarActor(Horse);// 现在所有的能力都会在马这个Actor上执行// 比如"冲刺"能力现在会让马冲刺,而不是角色自己冲刺
}// 玩家下马
void AHeroCharacter::DismountHorse()
{// 将AvatarActor恢复为角色自己AbilitySystemComponent->SetAvatarActor(this);
}

执行流程:

  1. check(AbilityActorInfo.IsValid()) - 确保AbilityActorInfo有效
  2. InitAbilityActorInfo(GetOwnerActor(), Horse) - 重新初始化,通知所有能力系统现在Avatar是马
  3. 所有激活的能力、GameplayEffect和GameplayCue都会重新绑定到新的AvatarActor

场景2:角色变形/变身

// 英雄变身为巨龙
void AHeroCharacter::TransformToDragon(ADragonForm* DragonForm)
{// 保存当前形态PreviousForm = this;// 切换到巨龙形态AbilitySystemComponent->SetAvatarActor(DragonForm);// 现在能力系统会在巨龙Actor上执行// "喷火"能力现在会从龙的嘴里喷出火焰
}

场景3:灵魂附体/控制转移

// 玩家控制另一个角色
void APlayerController::PossessNewCharacter(ACharacter* NewCharacter)
{// 解除对原角色的控制UnPossess();// 控制新角色Possess(NewCharacter);// 更新AbilitySystemComponent的AvatarActorAbilitySystemComponent->SetAvatarActor(NewCharacter);
}

场景4:分身体系

// 创建镜像分身
void AMageCharacter::CreateMirrorImage()
{AMirrorImage* MirrorImage = SpawnMirrorImage();// 将AbilitySystemComponent切换到分身// 这样玩家可以控制分身施放技能AbilitySystemComponent->SetAvatarActor(MirrorImage);// 设置定时器,一段时间后切换回来GetWorldTimerManager().SetTimer(SwitchBackTimer, this, &AMageCharacter::SwitchBackToMainBody, 10.0f);
}void AMageCharacter::SwitchBackToMainBody()
{// 切换回主体AbilitySystemComponent->SetAvatarActor(this);
}

底层发生的变化

当调用 SetAvatarActor 时,InitAbilityActorInfo 会执行以下关键操作:

  1. 更新AbilityActorInfo引用:
AbilityActorInfo->InitFromActor(GetOwnerActor(), InAvatarActor, this);
  1. 处理延迟的GameplayCues:

    • 如果之前没有AvatarActor,现在有了,会执行之前延迟的视觉效果
  2. 通知所有能力实例:

    • 调用每个能力的 OnAvatarSet,让能力有机会重新绑定到新的AvatarActor
  3. 重置动画系统:

    • 清理旧的动画蒙太奇状态
    • 初始化新的动画复制数据

设计意义

  1. 动态角色系统:允许在运行时切换控制的目标,支持坐骑、变身、附体等复杂游戏机制
  2. 能力复用:同样的能力可以在不同的AvatarActor上执行,实现"一套能力,多种表现"
  3. 网络同步:确保AvatarActor的变化在网络游戏中正确同步
  4. 资源管理:正确处理新旧AvatarActor之间的资源绑定和解绑

注意事项

// 重要:在切换AvatarActor前需要确保
void SafeAvatarSwitch(AActor* NewAvatar)
{// 1. 检查AbilitySystemComponent是否有效if (!AbilitySystemComponent || !AbilitySystemComponent->AbilityActorInfo.IsValid()){return;}// 2. 检查新Avatar是否有效if (!NewAvatar){UE_LOG(LogTemp, Warning, TEXT("Attempting to set null AvatarActor!"));return;}// 3. 执行安全的Avatar切换AbilitySystemComponent->SetAvatarActor(NewAvatar);
}

这个函数虽然代码简单,但通过调用 InitAbilityActorInfo 触发了GAS系统中一系列重要的重新初始化操作,是支持动态Avatar系统的核心接口。

八、ClearActorInfo

void UAbilitySystemComponent::ClearActorInfo()
{// 验证AbilityActorInfo指针有效,如果无效则触发断言崩溃// 确保在清理操作前AbilityActorInfo已经被正确初始化check(AbilityActorInfo.IsValid());// 清除AbilityActorInfo中存储的所有Actor相关信息// 这包括OwnerActor、AvatarActor、AnimInstance、SkeletalMeshComponent等关键引用AbilityActorInfo->ClearActorInfo();// 将组件的OwnerActor设置为nullptr// OwnerActor通常是控制这个能力系统的Controller(玩家控制器或AI控制器)SetOwnerActor(nullptr);// 直接将AvatarActor设置为nullptr// AvatarActor是实际执行能力和表现效果的物理实体(如Character或Pawn)SetAvatarActor_Direct(nullptr);
}

结合具体例子详细说明:

这个函数用于完全清除GAS系统与Actor的关联,通常在需要彻底断开连接时使用。

典型应用场景

场景1:角色死亡和彻底销毁

void AHeroCharacter::OnDeath()
{// 1. 首先停止所有活跃的能力和效果AbilitySystemComponent->CancelAllAbilities();AbilitySystemComponent->RemoveAllActiveGameplayEffects();// 2. 清除Actor信息,断开GAS系统与角色的关联AbilitySystemComponent->ClearActorInfo();// 3. 然后销毁角色(或进入死亡状态)StartDeathSequence();
}

执行流程分析:

  1. check(AbilityActorInfo.IsValid()) - 确保有有效的Actor信息可以清理

  2. AbilityActorInfo->ClearActorInfo() - 清除所有Actor相关引用:

    • OwnerActor = nullptr
    • AvatarActor = nullptr
    • AnimInstance = nullptr
    • SkeletalMeshComponent = nullptr
    • 其他组件引用全部重置
  3. SetOwnerActor(nullptr) - 明确设置Owner为null

  4. SetAvatarActor_Direct(nullptr) - 明确设置Avatar为null

场景2:关卡流式卸载

// 当关卡被卸载时,清理所有角色的GAS关联
void UGameLevel::OnLevelUnloaded()
{for (AHeroCharacter* Hero : AllHeroesInLevel){if (Hero && Hero->AbilitySystemComponent){// 清除Actor信息,但保留组件本身Hero->AbilitySystemComponent->ClearActorInfo();}}
}

场景3:角色重置/重生前的清理

void AHeroCharacter::ResetCharacter()
{// 在重新初始化前先完全清理AbilitySystemComponent->ClearActorInfo();// 然后重新设置到新的ActorAbilitySystemComponent->InitAbilityActorInfo(PlayerController, this);
}

底层发生的变化

AbilityActorInfo->ClearActorInfo() 具体清理的内容

// 模拟FGameplayAbilityActorInfo::ClearActorInfo()的实现
void FGameplayAbilityActorInfo::ClearActorInfo()
{OwnerActor = nullptr;AvatarActor = nullptr;PlayerController = nullptr;AnimInstance = nullptr;SkeletalMeshComponent = nullptr;MovementComponent = nullptr;// 其他组件引用...
}

与相关函数的对比

ClearActorInfo() vs UninitializeComponent()

// ClearActorInfo() - 只清理Actor引用,组件本身保持活动
void Example1()
{AbilitySystemComponent->ClearActorInfo();// 组件仍然存在,可以后续重新初始化// 适用于角色重生、重新初始化等场景
}// UninitializeComponent() - 完整的组件反初始化
void Example2()  
{AbilitySystemComponent->UninitializeComponent();// 组件进入非活动状态,清理所有内部状态// 适用于组件即将被销毁的场景
}

设计意义和注意事项

设计意义:

  1. 安全断开:确保在Actor生命周期结束时安全地断开GAS系统的关联
  2. 防止悬空指针:避免持有对已销毁Actor的引用,防止崩溃
  3. 状态重置:为重新初始化提供干净的基础
  4. 资源管理:确保所有Actor相关资源被正确释放

重要注意事项:

void SafeClearActorInfo()
{// 在调用ClearActorInfo之前应该:// 1. 取消所有活跃能力AbilitySystemComponent->CancelAllAbilities();// 2. 移除所有活跃效果AbilitySystemComponent->RemoveAllActiveGameplayEffects();// 3. 停止所有能力任务AbilitySystemComponent->CancelAllTasks();// 4. 然后执行清理AbilitySystemComponent->ClearActorInfo();
}

错误使用示例:

void IncorrectUsage()
{// 错误:在清理后尝试使用能力系统AbilitySystemComponent->ClearActorInfo();AbilitySystemComponent->TryActivateAbility(SomeAbilityHandle); // 崩溃!没有有效的Actor信息// 正确做法:清理后要么重新初始化,要么不再使用
}

这个函数是GAS系统生命周期管理的关键部分,确保在Actor关系发生变化时能够安全、干净地进行状态转移,是构建健壮的角色系统的重要基础。

九、OnRep_OwningActor

void UAbilitySystemComponent::OnRep_OwningActor()
{// 确保AbilityActorInfo有效,如果无效则输出错误并返回// ensure宏在调试模式下会弹出断言对话框,在发布模式下会记录错误if (!ensure(AbilityActorInfo.IsValid())){return;}// 获取当前的OwnerActor和AvatarActorAActor* LocalOwnerActor = GetOwnerActor();AActor* LocalAvatarActor = GetAvatarActor_Direct();// 检查当前的Actor与AbilityActorInfo中存储的是否一致// 如果不一致,说明发生了网络复制,需要更新状态if (LocalOwnerActor != AbilityActorInfo->OwnerActor || LocalAvatarActor != AbilityActorInfo->AvatarActor){// 如果新的OwnerActor有效,重新初始化能力Actor信息if (LocalOwnerActor != nullptr){InitAbilityActorInfo(LocalOwnerActor, LocalAvatarActor);}else{// 如果OwnerActor变为nullptr,清除所有Actor信息ClearActorInfo();}}
}

结合具体例子详细说明:

这是一个网络复制通知函数,当 OwningActor 在网络复制过程中发生变化时被自动调用。

网络复制背景

在虚幻引擎的网络模型中:

  • 服务器是权威端,拥有真实数据
  • 客户端通过网络复制接收服务器数据的变化
  • OnRep_ 函数在客户端检测到复制变量变化时自动调用

典型应用场景

场景1:玩家控制器切换角色(网络游戏)

// 服务器端
void APlayerController::PossessNewCharacter(ACharacter* NewCharacter)
{// 服务器设置新的OwningActorAbilitySystemComponent->SetOwnerActor(NewCharacter);// 这个变化会自动通过网络复制到客户端
}// 客户端 - 自动调用OnRep_OwningActor
void UAbilitySystemComponent::OnRep_OwningActor()
{// LocalOwnerActor 现在是通过网络复制得到的新角色// LocalAvatarActor 可能还是旧的Avatar// 检测到变化,调用InitAbilityActorInfo重新初始化InitAbilityActorInfo(NewCharacter, NewCharacter);
}

执行流程分析:

  1. 有效性检查
if (!ensure(AbilityActorInfo.IsValid()))
{return;
}
  • 确保AbilityActorInfo有效,防止空指针访问
  • 如果无效,记录错误并提前返回
  1. 获取当前Actor引用
AActor* LocalOwnerActor = GetOwnerActor();
AActor* LocalAvatarActor = GetAvatarActor_Direct();
  • 获取通过网络复制得到的最新OwnerActor
  • 获取当前的AvatarActor引用
  1. 检测变化
if (LocalOwnerActor != AbilityActorInfo->OwnerActor || LocalAvatarActor != AbilityActorInfo->AvatarActor)
  • 比较复制得到的新值与AbilityActorInfo中存储的旧值
  • 如果发现不一致,说明发生了网络复制变化
  1. 处理变化
if (LocalOwnerActor != nullptr)
{InitAbilityActorInfo(LocalOwnerActor, LocalAvatarActor);
}
else
{ClearActorInfo();
}
  • 如果新的OwnerActor有效:重新初始化整个系统
  • 如果新的OwnerActor为空:彻底清理系统

具体网络游戏示例

MOBA游戏中的英雄重生:

// 服务器端 - 英雄死亡后重生
void AHeroPlayerState::OnRespawn()
{// 服务器生成新的英雄角色AHeroCharacter* NewHero = SpawnNewHero();// 设置AbilitySystemComponent的Owner为新英雄AbilitySystemComponent->SetOwnerActor(NewHero);// 这个变化会通过网络复制到客户端
}// 客户端 - 自动接收变化
void UAbilitySystemComponent::OnRep_OwningActor()
{// 此时:// LocalOwnerActor = 新重生的英雄角色// AbilityActorInfo->OwnerActor = 旧的已死亡角色(或nullptr)// 检测到变化,重新初始化能力系统InitAbilityActorInfo(NewHero, NewHero);// 现在客户端的能力系统正确关联到新英雄// 可以正常显示技能UI、处理输入等
}

MMORPG中的坐骑系统:

// 服务器端 - 玩家上马
void APlayerCharacter::Server_MountHorse_Implementation(AHorse* Horse)
{// 服务器设置Avatar为马AbilitySystemComponent->SetAvatarActor(Horse);
}// 客户端 - 接收Avatar变化
void UAbilitySystemComponent::OnRep_OwningActor()
{// 检测到AvatarActor从玩家角色变为马if (LocalOwnerActor != nullptr){// 重新初始化,现在能力在马身上执行InitAbilityActorInfo(PlayerController, Horse);// 更新UI显示马的技能UpdateAbilityUI();}
}

设计意义和网络同步

为什么需要这个函数:

  1. 网络状态同步:确保客户端的能力系统状态与服务器保持一致
  2. 预测修正:当客户端的预测与服务器权威状态不一致时进行修正
  3. 无缝切换:支持在网络游戏中动态切换控制的目标
  4. 错误恢复:在网络异常情况下恢复正确的状态

网络复制流程:

服务器改变OwnerActor → 网络复制 → 客户端检测变化 → OnRep_OwningActor → 重新初始化

错误处理场景

void UAbilitySystemComponent::OnRep_OwningActor()
{// 场景1:网络延迟导致暂时性不一致// ensure确保系统健壮性,不会因为临时状态崩溃// 场景2:服务器清理了Actor,客户端也需要清理if (LocalOwnerActor == nullptr){ClearActorInfo(); // 安全清理ShowReconnectMessage(); // 显示重新连接UI}// 场景3:客户端预测错误,需要根据服务器状态修正if (LocalOwnerActor != AbilityActorInfo->OwnerActor){// 放弃本地预测,采用服务器权威状态InitAbilityActorInfo(LocalOwnerActor, LocalAvatarActor);}
}

这个函数是GAS系统在网络游戏中保持同步的关键机制,确保当控制权或Avatar在网络中发生变化时,所有客户端都能正确更新其本地状态,维持游戏的连贯性和一致性。

十、RefreshAbilityActorInfo

void UAbilitySystemComponent::RefreshAbilityActorInfo()
{// 验证AbilityActorInfo指针有效,如果无效则触发断言崩溃// 确保在执行刷新操作前AbilityActorInfo已经被正确初始化check(AbilityActorInfo.IsValid());// 使用当前存储的OwnerActor和AvatarActor重新初始化AbilityActorInfo// 这会刷新所有Actor相关的组件引用(如AnimInstance、SkeletalMeshComponent等)// 但不会改变OwnerActor和AvatarActor本身AbilityActorInfo->InitFromActor(AbilityActorInfo->OwnerActor.Get(), AbilityActorInfo->AvatarActor.Get(), this);
}

结合具体例子详细说明:

这个函数用于刷新AbilityActorInfo中的组件引用,而不改变OwnerActor和AvatarActor本身。

与相关函数的对比

首先理解这个函数与其他初始化函数的区别:

// InitAbilityActorInfo - 完全重新初始化,改变Owner/Avatar
void FullReinit()
{// 改变Actor引用,重新设置所有状态InitAbilityActorInfo(NewOwner, NewAvatar);
}// RefreshAbilityActorInfo - 刷新组件引用,不改变Actor
void RefreshOnly()
{// 不改变Owner/Avatar,只刷新组件引用RefreshAbilityActorInfo();
}

典型应用场景

场景1:骨骼网格体动态切换

// 角色更换装备或外观
void AHeroCharacter::ChangeSkeletalMesh(USkeletalMesh* NewMesh)
{// 更换骨骼网格体GetMesh()->SetSkeletalMesh(NewMesh);// 刷新AbilityActorInfo以更新AnimInstance等引用AbilitySystemComponent->RefreshAbilityActorInfo();// 现在能力系统知道新的骨骼网格体和动画实例// 动画能力可以正确播放在新的骨骼网格体上
}

执行流程分析:

  1. check(AbilityActorInfo.IsValid()) - 确保有有效的Actor信息可以刷新
  2. AbilityActorInfo->InitFromActor(...) - 重新初始化组件引用:
    • 更新 AnimInstance 引用到新的骨骼网格体的动画实例
    • 更新 SkeletalMeshComponent 引用到新的网格体组件
    • 更新其他组件引用(MovementComponent等)

场景2:动画蓝图重新编译后

// 当动画蓝图在编辑器中重新编译后
void AHeroCharacter::OnAnimBlueprintRecompiled()
{// 刷新AbilityActorInfo以获取新的AnimInstanceAbilitySystemComponent->RefreshAbilityActorInfo();// 现在GameplayAbility可以正确访问重新编译后的动画蓝图
}

场景3:组件在运行时被替换

// 动态替换移动组件
void AHeroCharacter::ReplaceMovementComponent()
{// 移除旧组件UMovementComponent* OldComponent = GetMovementComponent();OldComponent->DestroyComponent();// 创建新组件UMovementComponent* NewComponent = NewObject<UMovementComponent>(this);NewComponent->RegisterComponent();// 刷新AbilityActorInfo以获取新的MovementComponent引用AbilitySystemComponent->RefreshAbilityActorInfo();
}

底层发生的变化

InitFromActor 具体刷新的内容

// 模拟FGameplayAbilityActorInfo::InitFromActor的实现细节
void FGameplayAbilityActorInfo::InitFromActor(AActor* InOwnerActor, AActor* InAvatarActor, UObject* InOwnerComponent)
{// 更新组件引用,但不改变Owner/Avatarif (InAvatarActor){// 重新获取骨骼网格体组件SkeletalMeshComponent = InAvatarActor->FindComponentByClass<USkeletalMeshComponent>();// 重新获取动画实例if (SkeletalMeshComponent){AnimInstance = SkeletalMeshComponent->GetAnimInstance();}// 重新获取移动组件MovementComponent = InAvatarActor->FindComponentByClass<UMovementComponent>();// 重新获取其他相关组件...}
}

具体游戏机制示例

装备系统 - 武器切换:

void AHeroCharacter::EquipWeapon(AWeapon* NewWeapon)
{// 卸下当前武器if (CurrentWeapon){CurrentWeapon->Unequip();}// 装备新武器CurrentWeapon = NewWeapon;CurrentWeapon->EquipTo(GetMesh(), "WeaponSocket");// 武器可能带有新的骨骼网格体或动画// 刷新AbilityActorInfo以确保能力系统使用正确的组件AbilitySystemComponent->RefreshAbilityActorInfo();// 现在"攻击"能力会使用新武器的动画和特效
}

形态变换 - 组件更新:

void AShapeShifter::TransformToWolf()
{// 切换到狼的骨骼网格体GetMesh()->SetSkeletalMesh(WolfSkeletalMesh);GetMesh()->SetAnimInstanceClass(WolfAnimBlueprint);// 刷新组件引用AbilitySystemComponent->RefreshAbilityActorInfo();// 现在能力系统知道我们使用的是狼的动画实例// "嚎叫"能力可以正确播放狼的嚎叫动画
}

设计意义

  1. 动态组件支持:允许在运行时更换组件而不破坏能力系统的功能
  2. 热重载兼容:支持在编辑器中进行动画蓝图等资源的热重载
  3. 性能优化:比完全重新初始化更轻量,只更新必要的组件引用
  4. 错误恢复:当组件引用意外失效时,可以通过刷新恢复功能

注意事项

void AppropriateUsage()
{// 适合使用RefreshAbilityActorInfo的场景:// 1. 组件引用发生变化,但Actor身份不变// 2. 需要更新AnimInstance、SkeletalMeshComponent等引用// 3. 修复悬空的组件指针// 不适合使用的场景:// 1. OwnerActor或AvatarActor本身发生变化(应使用InitAbilityActorInfo)// 2. 需要完全重新初始化能力系统// 3. Actor被销毁或彻底更换
}

这个函数是GAS系统中维护组件引用一致性的重要工具,特别适用于那些需要在运行时动态更换组件而不改变Actor身份的游戏机制。

http://www.dtcms.com/a/467709.html

相关文章:

  • 中国建设银行网站在哪上市沛县互助网站开发
  • 代理ip注册网站都通不过公司做网页去哪找
  • 网站建设课程设计实验报告网站制作 搜索
  • 公司里面有人员增减要去哪个网站做登记做做网站入口
  • 万网手机网站客户管理系统的需求分析
  • 网站被黑了多久恢复云开发和普通开发区别
  • 江阴建设局官方网站WordPress设置API
  • 网站上如何做相关推荐tomcat加jsp做网站
  • 东莞建设网站的公司简介网站做一个要多少钱
  • 北京品牌建设网站公司排名做阿里巴巴好还是网站好
  • 淘宝客导购网站怎么建设济南网站建设策划方案
  • 单页面网站如何seo做淘宝网站用什么软件有哪些
  • 捕鱼网站建设多用户商城系统哪里有
  • 如何解决 pip install -r requirements.txt 不支持在文件中使用 --user 等命令行选项 问题
  • phpcms网站转移沧州市网站建设价格
  • 建设银行对账网站wordpress漏洞教程
  • 长春市做网站摄影网站知乎
  • 北京微网站制作世界工厂网app
  • 怎样购买起名软件自己做网站百度快速收录开通
  • 网站建设的技术有哪些方面网易企业邮箱手机上登录不了
  • Swift 枚举
  • 网站正在建设中的图片素材制作高端网站公司排名
  • 网站建设 有道翻译学网站建设需要多长时间
  • 手机网站优化排名首页深圳服装外贸网站建设
  • 网站建设常识网站怎么做首页比较好
  • AMOLED 和Min-LED 区别
  • c2c网站架构主机托管业务
  • STM32中printf的重定向详解
  • 网站pv怎么统计c 创建一个网站怎么做
  • 网站建设投福州网络营销推广