Game_Num_Basics_And_Calc

💢 仇恨系统与 AI 目标选择 (Aggro System)

仇恨系统(Aggro/Threat System)是连接怪物 AI 与玩家行为的桥梁。在 Project Vampirefall 中,由于融合了 Action Roguelike(玩家移动)与 Tower Defense(固定防御),仇恨系统必须在“被动寻路”与“主动反击”之间找到平衡。


1. 理论基础:感知与威胁 (Perception & Threat)

1.1 感知层 (Perception Layer)

怪物在决定攻击谁之前,首先必须“感知”到目标。感知机制是各种仇恨类型的触发基础。

1.2 仇恨列表 (Threat Table) - 动态仇恨管理

当怪物进入战斗状态后,它会维护一个 仇恨列表。列表中包含所有对它造成过伤害或产生过仇恨行为的实体。

1.2.1 仇恨类型细分 (Aggro Types)

仇恨类型 触发方式 初始仇恨值 衰减模式 优先级影响
基础寻路仇恨 (Base Pathfinding Threat) 怪物诞生时,自动对“基地核心”生成。 极高(无限趋近) 无衰减 只有当其他仇恨源足够高时才转移目标。
伤害仇恨 (Damage Threat) 对怪物造成伤害。 1 伤害 = 1 Threat 线性衰减(脱战后清零) 随伤害量动态变化。
治疗仇恨 (Healing Threat) 治疗队友或玩家自身。 治疗量 * 0.5 Threat 线性衰减 对所有感知范围内的怪物生效。
嘲讽仇恨 (Taunt Threat) 嘲讽技能。 目标当前最高仇恨值 + 固定值 (如 100) 短暂爆发,快速衰减 强制改变目标,优先级最高。
环境仇恨 (Environmental Threat) 破坏怪物附近的阻挡物(如墙壁、陷阱)。 固定值(如 50) 无衰减 优先级低于动态仇恨,高于基础寻路。
技能仇恨 (Ability Threat) 释放特定技能(如范围控场)。 根据技能强度计算 线性衰减 特定技能可附带额外仇恨修正。

2. 混合机制设计 (Hybrid Mechanics)

Project Vampirefall 的特殊性在于:玩家不仅要杀怪,还要保护塔。因此,我们设计了多层次的优先级决策。

2.1 优先级金字塔 (Target Priority Pyramid)

除了动态的仇恨值,我们引入静态优先级系数来修正怪物的行为倾向。这个系数会乘以最终计算出的仇恨值,形成怪物的“最终威胁值” (Final Threat)

\[FinalThreat = (RawDynamicThreat + BasePathfindingThreat + EnvironmentalThreat) \times EntityTypePriorityMod \times DistanceFactor\]
实体类型 (EntityType) 仇恨计算公式 Priority Mod 行为逻辑
基地核心 (Nexus) BasePathfindingThreat 10.0x 所有怪物的默认且最终目标。只有当其他目标提供足够高的 FinalThreat 才会转移。
嘲讽单位 (Tank Tower) DynamicThreat (含嘲讽) 5.0x 强制吸引火力。高额 PriorityMod 确保其能稳稳地拉住仇恨。
玩家 (Player) DynamicThreat 2.0x 怪物倾向于攻击高威胁的玩家。玩家的机动性是其优势,但高输出会快速积累仇恨。
防御塔 (Standard Tower) DynamicThreat 1.0x 正常优先级。怪物会在 Nexus、玩家和防御塔之间平衡选择。
召唤物 (Minions) DynamicThreat 0.5x 除非挡路或仇恨值极高,否则怪物懒得理会。
阻挡物 (Wall/Obstacle) EnvironmentalThreat 0.8x 仅当路径受阻时才成为目标,其仇恨值仅影响破拆速度而非优先追击。

2.2 风筝限制 (Anti-Kiting)

为了防止玩家利用高移速将怪物无限拉离防线(导致塔防失效),引入 Leash (牵引绳) 机制。


3. 仇恨计算公式 (Calculation)

3.1 原始仇恨值 (Raw Threat Calculation)

每种仇恨类型独立计算,然后叠加。

3.2 距离权重 (Distance Factor)

为了避免怪物无视脸上的坦克去追远处的弓箭手,距离会影响仇恨判定。

\[DistanceFactor = 1 + \frac{K}{Distance}\]

3.3 切换目标阈值 (Switch Threshold)

为了防止怪物在两个仇恨相近的目标间频繁转头(Ping-Ponging),切换目标需要满足阈值


4. 特殊 AI 行为 (Behavior Profiles)

不同类型的怪物使用不同的仇恨逻辑模板,通过配置 EntityTypePriorityMod 和其他参数实现。

4.1 攻城者 (Sieger)

4.2 刺客 (Assassin)

4.3 狂暴者 (Berserker)

4.4 干扰者 (Disruptor)


5. 实践指南 (Implementation Guide)

5.1 Unity 组件结构

建议使用 IAggroTarget 接口和 AggroAgent 组件。

public interface IAggroTarget {
    float GetThreatModifier(AggroType aggroType); // 不同仇恨类型可有不同修正
    EntityType GetEntityType(); // 返回实体类型 (Player, TankTower, Nexus)
    bool IsValid(); // 是否死亡、隐身或不可作为目标
    Vector3 Position { get; }
    // 可以添加获取当前血量、是否施法等信息的方法
}

public enum EntityType { Player, TankTower, StandardTower, Minion, Nexus, Obstacle }
public enum AggroType { Damage, Healing, Taunt, Environmental, Ability }

public class AggroAgent : MonoBehaviour { // 挂在怪物身上
    // 仇恨列表:Key=目标 (IAggroTarget), Value=当前仇恨值
    private Dictionary<IAggroTarget, float> threatTable = new Dictionary<IAggroTarget, float>();
    
    public EntityType monsterType; // 配置怪物自身类型,用于特殊行为

    // 在怪物受到伤害、感知到治疗、被嘲讽等时调用
    public void AddThreat(IAggroTarget source, AggroType type, float rawValue) {
        float threat = rawValue;
        // 应用来源的 GetThreatModifier
        threat *= source.GetThreatModifier(type);
        
        if (!threatTable.ContainsKey(source)) threatTable[source] = 0;
        threatTable[source] += threat;
        
        CleanThreatTable(); // 清理无效目标
        CheckSwitchTarget(); // 检查是否需要切换目标
    }

    // ... (Previous code)

    private IAggroTarget FindBestTarget() {
        IAggroTarget bestTarget = null;
        float highestThreat = -1f;

        foreach (var entry in threatTable) {
            var target = entry.Key;
            float rawThreat = entry.Value;
            
            if (!target.IsValid()) continue;

            // 1. 计算 EntityType 优先级修正
            float priorityMod = GetPriorityModifier(target.GetEntityType());
            
            // 2. 计算距离权重修正
            float distance = Vector3.Distance(transform.position, target.Position);
            float distanceFactor = 1f + (distanceSensitivityK / Mathf.Max(distance, 0.1f)); // 防止除以0
            
            // 3. 合成最终仇恨
            float finalThreat = rawThreat * priorityMod * distanceFactor;

            if (finalThreat > highestThreat) {
                highestThreat = finalThreat;
                bestTarget = target;
            }
        }
        return bestTarget;
    }

    private void CheckSwitchTarget() {
        IAggroTarget potentialTarget = FindBestTarget();
        if (potentialTarget == null || potentialTarget == currentTarget) return;

        float currentFinalThreat = CalculateFinalThreat(currentTarget);
        float newFinalThreat = CalculateFinalThreat(potentialTarget);

        // 阈值判定:防止 Ping-Ponging
        float thresholdMultiplier = (IsMeleeRange(potentialTarget)) ? 1.1f : 1.3f;
        
        if (newFinalThreat > currentFinalThreat * thresholdMultiplier) {
            SetTarget(potentialTarget);
        }
    }

    private float GetPriorityModifier(EntityType type) {
        // 这里可以配置化,或者读取 ScriptableObject 配置
        switch (type) {
            case EntityType.TankTower: return 5.0f;
            case EntityType.Player: return 2.0f;
            case EntityType.Nexus: return 10.0f; // 特殊处理
            default: return 1.0f;
        }
    }
}

5.2 可视化调试

在 Scene View 中绘制线条:


6. 常见问题与解决方案