ASC学习笔记0027:直接设置属性的基础值,而不会影响当前正在生效的任何修饰符(Modifiers)

中文注释:UrealEngine-5.2.1源码-AbilitySystemComponent.h

学习内容:

cpp 复制代码
/** 设置属性的基础值。不清除现有的活动修饰符,它们将对新的基础值起作用。 */
	void SetNumericAttributeBase(const FGameplayAttribute &Attribute, float NewBaseValue);

这个方法是 Gameplay Ability System(GAS) 中用于设置属性基础值的重要函数。让我详细解释一下它的作用和使用场景:

功能说明

SetNumericAttributeBase 用于直接设置属性的基础值,而不会影响当前正在生效的任何修饰符(Modifiers)。

参数说明

  • Attribute: 要设置的游戏属性(如生命值、法力值等)

  • NewBaseValue: 新的基础值

关键特点

1. 不影响活动修饰符

cpp 复制代码
// 示例:设置生命值基础值
UAttributeSet* AttributeSet = GetAttributeSet();
AttributeSet->SetNumericAttributeBase(UAttributeSet::GetHealthAttribute(), 100.0f);

// 假设当前有 +20 的修饰符生效
// 设置前:基础值 80 + 修饰符 20 = 当前值 100
// 设置后:基础值 100 + 修饰符 20 = 当前值 120

2. 与 PreAttributeChange 的区别

cpp 复制代码
// SetNumericAttributeBase - 直接设置基础值
void YourAttributeSet::SetNumericAttributeBase(const FGameplayAttribute& Attribute, float NewBaseValue)
{
    // 这是引擎内部实现,直接修改基础值
}

// PreAttributeChange - 在属性变化前被调用,用于验证和限制
void YourAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
    // 这里可以添加验证逻辑
    if (Attribute == GetHealthAttribute())
    {
        NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
    }
}

使用场景

1. 初始化属性

cpp 复制代码
void AYourCharacter::InitializeAttributes()
{
    if (DefaultAttributeEffect && AttributeSet)
    {
        // 应用初始属性
        AttributeSet->SetNumericAttributeBase(UAttributeSet::GetHealthAttribute(), 100.0f);
        AttributeSet->SetNumericAttributeBase(UAttributeSet::GetMaxHealthAttribute(), 100.0f);
        AttributeSet->SetNumericAttributeBase(UAttributeSet::GetManaAttribute(), 50.0f);
    }
}

2. 重置基础值

cpp 复制代码
void UYourAttributeSet::OnRep_Level(const FGameplayAttributeData& OldLevel)
{
    // 等级变化时重新计算基础属性
    float NewBaseHealth = CalculateBaseHealthFromLevel();
    SetNumericAttributeBase(GetHealthAttribute(), NewBaseHealth);
}

3. 与修饰符协同工作

cpp 复制代码
// 假设当前状态:
// 基础生命值: 100
// 装备加成: +20 (修饰符)
// 当前生命值: 120

// 使用 SetNumericAttributeBase 后:
SetNumericAttributeBase(GetHealthAttribute(), 150.0f);
// 结果:
// 基础生命值: 150
// 装备加成: +20 (修饰符仍然存在)
// 当前生命值: 170

注意事项

  1. 不会触发 Gameplay Effects:这个方法直接修改基础值,不会触发普通的属性修改流程

  2. 需要手动通知:修改后可能需要手动触发属性变化通知

    cpp 复制代码
    AttributeSet->SetNumericAttributeBase(GetHealthAttribute(), NewValue);
    AttributeSet->OnHealthChanged.Broadcast(NewValue); // 手动广播事件
  3. 谨慎使用:由于绕过正常的修改流程,应在明确知道需要直接修改基础值时使用

这个方法在需要直接控制属性基础值而不影响现有修饰符的场景下非常有用,特别是在属性初始化、等级重置或特殊系统交互时。

在大型网络游戏中,SetNumericAttributeBase 的应用需要特别考虑网络同步、性能优化和架构设计。以下是具体应用场景和最佳实践:

1. 玩家属性初始化系统

分布式初始化流程

cpp 复制代码
// 在玩家控制器或游戏模式中
void AYourPlayerController::OnPlayerInitialized()
{
    if (GetLocalRole() == ROLE_Authority)
    {
        // 服务器初始化基础属性
        InitializeBaseAttributes();
        
        // 同步到所有客户端
        ClientInitializeAttributes(GetInitialAttributeData());
    }
}

// 服务器端初始化
void AYourPlayerController::InitializeBaseAttributes()
{
    UYourAttributeSet* AttributeSet = GetPawn()->FindComponentByClass<UYourAttributeSet>();
    if (AttributeSet)
    {
        FInitialAttributeData InitData = CalculateInitialAttributes();
        
        AttributeSet->SetNumericAttributeBase(UYourAttributeSet::GetHealthAttribute(), InitData.Health);
        AttributeSet->SetNumericAttributeBase(UYourAttributeSet::GetManaAttribute(), InitData.Mana);
        AttributeSet->SetNumericAttributeBase(UYourAttributeSet::GetStaminaAttribute(), InitData.Stamina);
        
        // 保存初始值用于重置
        CachedBaseAttributes = InitData;
    }
}

// 客户端同步
UFUNCTION(Client, Reliable)
void AYourPlayerController::ClientInitializeAttributes(const FInitialAttributeData& AttributeData);

2. 等级和职业系统

批量属性更新

cpp 复制代码
// 等级提升时的属性重计算
void UPlayerLevelSystem::OnLevelUp(int32 NewLevel)
{
    if (GetOwnerRole() != ROLE_Authority) return;
    
    UYourAttributeSet* AttributeSet = GetAttributeSet();
    FLevelAttributeData LevelData = LevelConfig->GetAttributesForLevel(NewLevel);
    
    // 批量更新基础属性,避免多次网络同步
    BatchUpdateBaseAttributes(LevelData);
}

void UPlayerLevelSystem::BatchUpdateBaseAttributes(const FLevelAttributeData& LevelData)
{
    UYourAttributeSet* AttributeSet = GetAttributeSet();
    
    // 开始批量更新
    AttributeSet->PreBatchUpdate();
    
    AttributeSet->SetNumericAttributeBase(GetHealthAttribute(), LevelData.BaseHealth);
    AttributeSet->SetNumericAttributeBase(GetManaAttribute(), LevelData.BaseMana);
    AttributeSet->SetNumericAttributeBase(GetAttackAttribute(), LevelData.BaseAttack);
    AttributeSet->SetNumericAttributeBase(GetDefenseAttribute(), LevelData.BaseDefense);
    
    // 结束批量更新,触发一次同步
    AttributeSet->PostBatchUpdate();
    
    // 记录属性变化日志(用于审计和调试)
    LogAttributeChange(GetPlayerId(), "LevelUp", LevelData);
}

3. 装备和技能系统

基础属性重算机制

cpp 复制代码
// 装备系统
void UEquipmentSystem::RecalculateBaseAttributes()
{
    if (GetOwnerRole() != ROLE_Authority) return;
    
    UYourAttributeSet* AttributeSet = GetAttributeSet();
    FBaseAttributeCalculator Calculator;
    
    // 计算来自所有装备的基础属性
    FCalculatedAttributes NewBases = Calculator.CalculateBaseAttributes(
        GetEquippedItems(), 
        GetPlayerLevel(), 
        GetPlayerClass()
    );
    
    // 更新基础值,保持当前修饰符不变
    AttributeSet->SetNumericAttributeBase(GetHealthAttribute(), NewBases.Health);
    AttributeSet->SetNumericAttributeBase(GetManaAttribute(), NewBases.Mana);
    
    // 通知客户端基础属性变化
    Multicast_OnBaseAttributesUpdated(NewBases);
}

4. 副本和场景切换

状态保存和恢复

cpp 复制代码
// 进入副本时的属性处理
void UDungeonSystem::OnEnterDungeon(ADungeonInstance* Dungeon)
{
    // 保存原始基础属性
    SaveOriginalBaseAttributes();
    
    // 应用副本特定的基础属性调整
    ApplyDungeonBaseAttributeModifiers(Dungeon);
}

void UDungeonSystem::ApplyDungeonBaseAttributeModifiers(ADungeonInstance* Dungeon)
{
    UYourAttributeSet* AttributeSet = GetAttributeSet();
    FDungeonAttributeRules Rules = Dungeon->GetAttributeRules();
    
    // 根据副本规则调整基础属性
    float NewBaseHealth = AttributeSet->GetHealthBaseValue() * Rules.HealthMultiplier;
    AttributeSet->SetNumericAttributeBase(GetHealthAttribute(), NewBaseHealth);
    
    // 记录修改用于退出时恢复
    DungeonAttributeModifiers = Rules;
}

// 退出副本时恢复
void UDungeonSystem::OnExitDungeon()
{
    RestoreOriginalBaseAttributes();
}

5. 网络优化策略

批量同步和压缩

cpp 复制代码
// 优化的属性同步组件
void UOptimizedAttributeSyncComponent::ServerUpdateBaseAttributes(
    const TArray<FAttributeBaseUpdate>& Updates)
{
    if (GetOwnerRole() != ROLE_Authority) return;
    
    // 验证更新请求
    if (!ValidateAttributeUpdates(Updates)) return;
    
    UYourAttributeSet* AttributeSet = GetAttributeSet();
    
    // 应用批量更新
    for (const FAttributeBaseUpdate& Update : Updates)
    {
        AttributeSet->SetNumericAttributeBase(Update.Attribute, Update.NewBaseValue);
    }
    
    // 压缩并同步到客户端
    FCompressedAttributeData CompressedData = CompressAttributeData(Updates);
    Multicast_SyncBaseAttributes(CompressedData);
    
    // 记录到审计系统
    AuditSystem->LogAttributeBaseUpdates(GetPlayerId(), Updates);
}

UFUNCTION(NetMulticast, Reliable)
void UOptimizedAttributeSyncComponent::Multicast_SyncBaseAttributes(
    const FCompressedAttributeData& CompressedData);

6. 反作弊和安全考虑

服务器验证机制

cpp 复制代码
// 属性修改验证
bool UAttributeSecuritySystem::ValidateBaseAttributeChange(
    const FGameplayAttribute& Attribute, 
    float ProposedValue, 
    APlayerController* Requestor)
{
    // 1. 检查权限
    if (!Requestor->HasAuthorityToModifyAttribute(Attribute))
    {
        LogSecurityViolation(Requestor, "Unauthorized attribute modification");
        return false;
    }
    
    // 2. 检查数值合理性
    if (!IsValueWithinReasonableRange(Attribute, ProposedValue))
    {
        LogSecurityViolation(Requestor, "Attribute value out of range");
        return false;
    }
    
    // 3. 检查修改频率
    if (IsChangeRateTooHigh(Attribute, Requestor))
    {
        LogSecurityViolation(Requestor, "Attribute change rate too high");
        return false;
    }
    
    return true;
}

// 包装的安全设置方法
void UAttributeSecuritySystem::SafeSetNumericAttributeBase(
    const FGameplayAttribute& Attribute, 
    float NewBaseValue, 
    APlayerController* Requestor)
{
    if (ValidateBaseAttributeChange(Attribute, NewBaseValue, Requestor))
    {
        GetAttributeSet()->SetNumericAttributeBase(Attribute, NewBaseValue);
        
        // 记录合法修改
        AuditSystem->LogValidAttributeChange(Requestor, Attribute, NewBaseValue);
    }
}

7. 性能监控和调试

属性变化追踪

cpp 复制代码
// 性能监控
void UAttributePerformanceMonitor::TrackBaseAttributeUpdate(
    const FGameplayAttribute& Attribute, 
    float OldValue, 
    float NewValue)
{
    FAttributeUpdateEvent Event;
    Event.Attribute = Attribute;
    Event.OldValue = OldValue;
    Event.NewValue = NewValue;
    Event.Timestamp = FDateTime::UtcNow();
    Event.CallStack = CaptureCallStack();
    
    // 记录到性能数据库
    PerformanceDB->RecordAttributeUpdate(Event);
    
    // 检查性能异常
    if (DetectPerformanceAnomaly(Event))
    {
        AlertSystem->NotifyAttributePerformanceIssue(Event);
    }
}

在大型网络游戏中,SetNumericAttributeBase 的正确使用需要结合:

  • 网络同步优化:减少不必要的属性同步

  • 安全验证:防止客户端作弊

  • 性能监控:确保大规模玩家时的性能

  • 事务性操作:保证属性修改的原子性

  • 审计日志:追踪所有属性修改记录

相关推荐
doubao361 小时前
如何在海量文献中高效筛选有价值信息
人工智能·学习·自然语言处理·aigc·ai工具·ai检索
羚羊角uou1 小时前
【C++】智能指针
开发语言·c++
烤麻辣烫1 小时前
AI(新手)
人工智能·学习·机器学习·ai编程
杜子不疼.1 小时前
【C++】哈希表基础:开放定址法 & 什么是哈希冲突?
c++·哈希算法·散列表
04aaaze1 小时前
C++(C转C++)
c语言·c++·算法
青衫码上行2 小时前
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式
java·前端·javascript·学习·正则表达式
不会c嘎嘎2 小时前
C++ -- list
开发语言·c++
寅双木2 小时前
自己配一台电脑——显卡
笔记·显卡·显卡电路板·显卡散热·显卡组成·显卡接口·pcle接口
梨轻巧2 小时前
艾伦·索金 编剧课 写作课
笔记