【AI&游戏】专栏-直达

Unity AI Perception 感知系统完全指南

一、引言

在游戏开发中,AI感知系统是构建智能游戏角色的核心组成部分。无论是让敌人能够发现玩家、让NPC能够响应环境变化,还是实现复杂的潜行游戏机制,都离不开一个强大而灵活的感知系统。Unity的AI Perception系统正是为此而设计的,它为开发者提供了一套完整的工具,用于实现游戏中各种智能体的感知能力。

AI Perception系统是Unity官方提供的AI框架的重要组成部分,它与Behavior Designer、NodeCanvas等行为树工具深度集成,使得开发者能够轻松实现复杂的感知-决策-行为链条。这个系统不仅仅能够检测目标是否在视野范围内,还支持多种感知方式,包括视觉、听觉、触觉等,能够满足各种类型游戏的感知需求。

本文将全面深入地介绍Unity AI Perception系统的各个方面,从基础概念到高级应用,从技术原理到实战项目。我们将一起探索如何在Unity项目中有效地利用感知系统创建引人入胜的智能游戏角色。

二、感知系统基础理论

2.1 什么是AI感知系统

AI感知系统是游戏中模拟智能体对外界环境感知能力的技术系统。在现实世界中,人类和动物通过视觉、听觉、嗅觉、触觉等感知方式来获取周围环境的信息,并据此做出决策。在游戏中,我们需要通过技术手段来模拟这种感知能力,让AI角色能够"看到"玩家、"听到"声音、"感觉到"碰撞。

感知系统的核心功能

一个完整的AI感知系统需要实现以下核心功能:

  • 信息采集:从游戏世界获取各种感知信息,如对象位置、声音、碰撞等。
  • 信号处理:对采集到的原始信息进行处理,转换为AI可以理解的语义信息。
  • 记忆存储:存储感知到的信息,形成感知记忆,支持时间衰减和遗忘机制。
  • 事件触发:当感知信息满足特定条件时,触发相应的响应事件。

感知系统的层次

AI感知系统通常包含以下几个层次:

  • 物理层:最基础的感知实现,如射线检测、碰撞检测等。
  • 语义层:对物理层检测结果进行解释,识别"看到玩家"这样的语义信息。
  • 记忆层:对感知信息进行存储和管理,形成持续的环境认知。
  • 决策层:根据感知信息做出决策,触发相应的AI行为。

2.2 感知系统类型

根据实现方式和检测对象的不同,感知系统可以分为以下几种类型:

视觉感知

视觉感知是游戏中最重要的感知方式,AI通过"眼睛"来检测目标的存在。

  • 锥形视野:最常用的方式,AI能够看到前方锥形范围内的对象。
  • 球形检测:检测周围球形范围内的所有对象。
  • 射线检测:通过发射射线来检测直线上的对象,支持遮挡检测。

听觉感知

听觉感知让AI能够检测到声音的存在和来源。

  • 声音位置检测:检测声音发生的位置。
  • 声音强度检测:根据距离衰减原理,检测近处的声音更清晰。
  • 声音类型识别:识别不同类型的声音,如脚步声、枪声、对话等。

触觉感知

触觉感知通过物理接触来获取信息。

  • 碰撞检测:检测与其他对象的物理接触。
  • 触发器检测:检测进入触发器区域的事件。
  • 力反馈检测:检测受到的力和冲击。

2.3 感知系统在游戏中的应用

感知系统是游戏AI的基础组件,在各种类型的游戏中都有广泛应用:

潜行游戏

潜行游戏是感知系统最典型的应用场景:

  • 敌人需要检测玩家的可见性。
  • 需要实现视野检测和遮挡判断。
  • 需要实现声音检测,让AI能够听到玩家的脚步声。
  • 需要实现感知记忆,敌人需要记住短暂的发现。

战斗AI

战斗游戏中的敌人AI需要精确的感知能力:

  • 检测玩家位置和距离。
  • 检测玩家是否在攻击范围内。
  • 检测友军位置,避免误伤。
  • 检测环境中的掩体和遮挡物。

NPC交互

RPG和冒险游戏中的NPC需要与环境和玩家交互:

  • 检测玩家是否在对话范围内。
  • 检测玩家的接近和离开。
  • 检测其他NPC的位置,实现社交行为。

三、Unity AI Perception 核心组件

3.1 AI Perception组件

Unity的AI Perception系统主要通过AI Perception组件来实现,这是系统的核心入口点。

组件概述

AI Perception组件通常挂载在需要感知能力的AI角色上,负责管理和协调该角色的所有感知模块。

主要属性

  • Update Frequency:感知更新的频率,决定AI多久"看"一次周围环境。
  • Perception Modes:启用的感知模式,如视觉、听觉等。
  • Debug Draw:是否在Editor中绘制感知范围的调试信息。

核心方法

using UnityEngine.AI Perception;

// 获取感知组件
var perception = GetComponent<AIPerceptionModule_Unity>();

// 检测特定感知类型的激发源
public Transform[] GetCurrentlyPerceived(PerceptionType type)

// 检测特定类型的最近激发源
public Transform GetClosest(PerceptionType type)

// 检测特定类型的所有可见目标
public bool TryGetPerceptionImage(PerceptionType type, PerceptionImage image)

3.2 感知模块

感知模块是实现具体感知功能的组件,Unity提供了多种内置感知模块:

视觉感知模块

// 配置视觉感知
AIPerceptionModule_Visual visual = GetComponent<AIPerceptionModule_Visual>();

// 设置感知参数
visual感知范围.radius = 20f;          // 感知半径
visual感知范围.height = 5f;           // 感知高度
visual感知范围.fov = 90f;             // 视野角度
visual感知范围.minRange = 0f;         // 最小感知距离

// 设置检测层级
visual感知范围.detectableTags.Add("Player");
visual感知范围.detectableTags.Add("Enemy");

// 设置遮挡检测
visual感知范围.obstructionByObstacles = true;
visual感知范围.obstacleLayerMask = LayerMask.GetMask("Default");

听觉感知模块

// 配置听觉感知
AIPerceptionModule_Audio audio = GetComponent<AIPerceptionModule_Audio>();

// 设置感知参数
audio感知范围.radius = 30f;
audio感知范围.height = 10f;

// 配置声音感知
audio感知范围.states = new PerceptionState[2];
audio感知范围.states[0] = PerceptionState.PlayersStep;   // 玩家脚步声
audio感知范围.states[1] = PerceptionState.LoudNoise;     // 大声响

3.3 感知源

感知源是被感知对象所携带的组件,用于标识对象的身份和状态。

配置感知源

// 在被感知对象上添加感知源组件
var perceivable = gameObject.AddComponent<PerceptionSink>();

// 配置感知源参数
perceivable.SetIdentityForTags(new []{ "Player" }, "Player_001");

// 设置感知状态
perceivable.SetPerceptionState("Footstep", 1f);  // 设置脚步声状态
perceivable.SetPerceptionState("Weapon", 1f);   // 设置武器状态

3.4 感知配置器

感知配置器用于在全局层面配置感知系统的行为。

全局配置

// 配置全局感知设置
AIKit.PerceptionConfiguration perceptionConfig = new AIKit.PerceptionConfiguration();

// 设置默认感知范围
perceptionConfig.defaultRadius = 15f;
perceptionConfig.defaultFOV = 120f;

// 设置感知更新间隔
perceptionConfig.minUpdateInterval = 0.1f;
perceptionConfig.maxUpdateInterval = 0.5f;

// 应用配置
AIKit.ApplyConfiguration(perceptionConfig);

四、感知系统实现原理

4.1 视觉感知实现原理

视觉感知是AI感知系统中最复杂也是最重要的部分。其实现涉及多个技术点:

锥形视野检测

锥形视野检测确定目标是否在AI的视野范围内:

// 锥形视野检测原理
public bool IsInFOV(Transform target, Vector3 eyePosition, Vector3 forward, float fovAngle, float distance)
{
    // 计算方向向量
    Vector3 directionToTarget = (target.position - eyePosition).normalized;
    
    // 计算角度
    float angle = Vector3.Angle(forward, directionToTarget);
    
    // 检查是否在角度范围内
    if (angle > fovAngle * 0.5f)
        return false;
    
    // 检查距离
    float distanceToTarget = Vector3.Distance(eyePosition, target.position);
    if (distanceToTarget > distance)
        return false;
    
    return true;
}

遮挡检测

遮挡检测确保AI无法看到被障碍物遮挡的目标:

// 射线遮挡检测
public bool HasLineOfSight(Vector3 from, Vector3 to, LayerMask obstacleMask)
{
    // 发射射线
    RaycastHit hit;
    if (Physics.Linecast(from, to, out hit, obstacleMask))
    {
        // 检查是否被障碍物遮挡
        return false;
    }
    
    return true;
}

多层感知融合

高级感知系统可以融合多种检测方式:

// 综合视觉检测
public float GetVisualPerceptionScore(Transform target)
{
    float score = 0f;
    
    // 距离评分
    float distance = Vector3.Distance(aiPosition, target.position);
    score += Mathf.Max(0, 1f - distance / maxRange);
    
    // 角度评分
    float angle = Vector3.Angle(forward, (target.position - aiPosition).normalized);
    score += Mathf.Max(0, 1f - angle / (fov * 0.5f));
    
    // 可见性评分(考虑遮挡)
    if (HasLineOfSight(eyePosition, target.position, obstacleMask))
    {
        score *= 2f;  // 完全可见时加分
    }
    
    return score;
}

4.2 听觉感知实现原理

听觉感知基于声音传播的物理原理:

声音传播模型

声音在传播过程中会衰减,遵循平方反比定律:

// 声音衰减计算
public float CalculateAudibleDistance(Vector3 soundPosition, Vector3 listenerPosition, float soundRadius)
{
    float distance = Vector3.Distance(soundPosition, listenerPosition);
    
    // 声音强度随距离平方反比衰减
    float intensity = 1f / (distance * distance);
    
    // 转换为可听距离
    return soundRadius * Mathf.Sqrt(intensity);
}

声音定位

通过双耳效应和时间差来定位声音:

// 声音方向检测
public Vector3 GetSoundDirection(Vector3 soundPosition, Vector3 listenerPosition, Vector3 listenerForward)
{
    Vector3 direction = (soundPosition - listenerPosition).normalized;
    
    // 计算左右声道音量差异来估计水平角度
    float horizontalAngle = Vector3.SignedAngle(listenerPosition, soundPosition, Vector3.up);
    
    // 估计垂直角度
    float verticalAngle = Vector3.SignedAngle(listenerPosition, soundPosition, listenerPosition.right);
    
    return new Vector3(horizontalAngle, verticalAngle, 0);
}

4.3 感知记忆机制

感知记忆允许AI记住曾经感知到但现在已经看不到的目标:

记忆数据结构

// 感知记忆条目
[System.Serializable]
public class PerceptionMemory
{
    public Transform target;           // 记忆的目标
    public Vector3 lastKnownPosition; // 最后已知位置
    public float confidence;          // 置信度 (0-1)
    public float timeSinceLastSeen;   // 上次感知到的时间
    public PerceptionType type;        // 感知类型
}

// 记忆管理器
public class PerceptionMemoryManager
{
    private List<PerceptionMemory> memories = new List<PerceptionMemory>();
    private float memoryDuration = 30f;  // 记忆持续时间
    private float confidenceDecayRate = 0.1f;  // 置信度衰减率
    
    // 更新记忆
    public void UpdateMemories()
    {
        foreach (var memory in memories)
        {
            memory.timeSinceLastSeen += Time.deltaTime;
            
            // 置信度随时间衰减
            memory.confidence = Mathf.Max(0, memory.confidence - confidenceDecayRate * Time.deltaTime);
            
            // 位置更新(目标移动则更新)
            if (memory.target != null)
            {
                memory.lastKnownPosition = memory.target.position;
            }
        }
        
        // 移除过期的记忆
        memories.RemoveAll(m => m.timeSinceLastSeen > memoryDuration);
    }
    
    // 添加或更新记忆
    public void AddOrUpdateMemory(Transform target, Vector3 position, float confidence, PerceptionType type)
    {
        var existing = memories.Find(m => m.target == target);
        
        if (existing != null)
        {
            // 更新现有记忆
            existing.lastKnownPosition = position;
            existing.confidence = confidence;
            existing.timeSinceLastSeen = 0;
        }
        else
        {
            // 添加新记忆
            memories.Add(new PerceptionMemory
            {
                target = target,
                lastKnownPosition = position,
                confidence = confidence,
                timeSinceLastSeen = 0,
                type = type
            });
        }
    }
}

4.4 感知融合策略

在复杂的AI系统中,可能需要融合多种感知信息:

加权融合

// 多感知源融合
public float GetFusedPerception(Transform target)
{
    float totalScore = 0f;
    float totalWeight = 0f;
    
    // 视觉感知权重
    if (visualModule.IsTargetPerceived(target))
    {
        float visualScore = visualModule.GetPerceptionConfidence(target);
        totalScore += visualScore * visualWeight;
        totalWeight += visualWeight;
    }
    
    // 听觉感知权重
    if (audioModule.IsTargetPerceived(target))
    {
        float audioScore = audioModule.GetPerceptionConfidence(target);
        totalScore += audioScore * audioWeight;
        totalWeight += audioWeight;
    }
    
    // 触觉感知权重
    if (tactileModule.IsTargetPerceived(target))
    {
        float tactileScore = tactileModule.GetPerceptionConfidence(target);
        totalScore += tactileScore * tactileWeight;
        totalWeight += tactileWeight;
    }
    
    return totalWeight > 0 ? totalScore / totalWeight : 0;
}

五、快速入门指南

5.1 基础设置

创建AI角色

  1. 创建一个Capsule或Character模型作为AI角色。
  2. 添加AI Perception组件。
  3. 添加NavMeshAgent或自定义移动组件。

配置视觉感知

// 添加视觉感知模块
AIPerceptionModule_Visual visual = aiGameObject.AddComponent<AIPerceptionModule_Visual>();

// 基本配置
visual感知范围.fov = 90f;           // 90度视野
visual感知范围.radius = 20f;        // 20米感知范围
visual感知范围.height = 5f;         // 5米感知高度

// 配置检测目标
visual感知范围.detectableTags.Add("Player");
visual感知范围.detectableTags.Add("Companion");

// 配置检测层级
visual感知范围.detectableLayerMask = LayerMask.GetMask("Player", "Default");

5.2 检测玩家实现

基本检测逻辑

// 检测玩家
public class SimpleDetection : MonoBehaviour
{
    public AIPerceptionModule_Unity perception;
    public string playerTag = "Player";
    
    void Update()
    {
        // 获取所有感知到的目标
        Transform[] perceived = perception.GetCurrentlyPerceived(PerceptionType.Vision);
        
        foreach (Transform target in perceived)
        {
            if (target.CompareTag(playerTag))
            {
                Debug.Log($"Detected player: {target.name}");
                OnPlayerDetected(target);
            }
        }
    }
    
    void OnPlayerDetected(Transform player)
    {
        // 触发AI响应
    }
}

5.3 敌人AI示例

完整敌人检测AI

public class EnemyDetection : MonoBehaviour
{
    [Header("Perception Settings")]
    public float detectionRange = 20f;
    public float fov = 90f;
    public LayerMask playerLayer;
    public LayerMask obstacleLayer;
    
    [Header("References")]
    public Transform eyesPosition;
    public EnemyAI enemyAI;
    
    [Header("State")]
    public bool playerDetected = false;
    public Transform currentTarget;
    
    void Update()
    {
        if (!playerDetected)
        {
            // 持续检测
            CheckForPlayer();
        }
    }
    
    void CheckForPlayer()
    {
        // 找到最近的玩家
        Transform closestPlayer = FindClosestPlayer();
        
        if (closestPlayer != null)
        {
            // 检查是否在视野内
            if (IsInVision(closestPlayer))
            {
                playerDetected = true;
                currentTarget = closestPlayer;
                enemyAI.OnTargetDetected(closestPlayer);
            }
        }
    }
    
    Transform FindClosestPlayer()
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, detectionRange, playerLayer);
        
        Transform closest = null;
        float closestDist = float.MaxValue;
        
        foreach (var collider in colliders)
        {
            float dist = Vector3.Distance(transform.position, collider.transform.position);
            if (dist < closestDist)
            {
                closestDist = dist;
                closest = collider.transform;
            }
        }
        
        return closest;
    }
    
    bool IsInVision(Transform target)
    {
        Vector3 direction = (target.position - eyesPosition.position).normalized;
        float angle = Vector3.Angle(transform.forward, direction);
        
        // 检查角度
        if (angle > fov * 0.5f)
            return false;
        
        // 检查遮挡
        if (Physics.Linecast(eyesPosition.position, target.position, obstacleLayer))
            return false;
        
        return true;
    }
}

5.4 调试工具

感知范围可视化

// 在Editor中绘制感知范围
void OnDrawGizmos()
{
    // 绘制感知范围
    Gizmos.color = Color.yellow;
    Gizmos.DrawWireSphere(transform.position, detectionRange);
    
    // 绘制FOV
    Vector3 leftBoundary = Quaternion.Euler(0, -fov * 0.5f, 0) * transform.forward * detectionRange;
    Vector3 rightBoundary = Quaternion.Euler(0, fov * 0.5f, 0) * transform.forward * detectionRange;
    
    Gizmos.color = Color.red;
    Gizmos.DrawLine(transform.position, transform.position + leftBoundary);
    Gizmos.DrawLine(transform.position, transform.position + rightBoundary);
    
    // 绘制到最近目标的连线
    if (currentTarget != null && playerDetected)
    {
        Gizmos.color = Color.green;
        Gizmos.DrawLine(transform.position, currentTarget.position);
    }
}

六、高级功能应用

6.1 分级感知系统

在实际游戏中,往往需要实现分级感知,让不同级别的AI有不同的感知能力:

感知等级配置

public enum PerceptionLevel
{
    Normal,      // 普通感知
    Alert,       // 警觉状态
    Combat       // 战斗状态
}

public class分级感知System : MonoBehaviour
{
    public PerceptionLevel currentLevel = PerceptionLevel.Normal;
    
    [Header("Perception Settings")]
    public float normalRange = 20f;
    public float normalFOV = 90f;
    
    public float alertRange = 30f;
    public float alertFOV = 120f;
    
    public float combatRange = 50f;
    public float combatFOV = 180f;
    
    public void SetPerceptionLevel(PerceptionLevel level)
    {
        currentLevel = level;
        
        switch (level)
        {
            case PerceptionLevel.Normal:
                perceptionModule.radius = normalRange;
                perceptionModule.fov = normalFOV;
                break;
            case PerceptionLevel.Alert:
                perceptionModule.radius = alertRange;
                perceptionModule.fov = alertFOV;
                break;
            case PerceptionLevel.Combat:
                perceptionModule.radius = combatRange;
                perceptionModule.fov = combatFOV;
                break;
        }
    }
}

6.2 感知噪声系统

实现真实的噪声感知系统:

噪声事件

public class NoiseEvent
{
    public Vector3 position;
    public float loudness;      // 噪声强度 (0-1)
    public float detectionRadius; // 可检测距离
    public float duration;      // 持续时间
    public string sourceTag;    // 声源标签
    public float timeSinceCreated;
}

噪声管理器

public class NoiseManager : MonoBehaviour
{
    public List<NoiseEvent> activeNoises = new List<NoiseEvent>();
    
    // 产生噪声
    public void ProduceNoise(Vector3 position, float loudness, float duration, string sourceTag = "")
    {
        NoiseEvent noise = new NoiseEvent
        {
            position = position,
            loudness = loudness,
            detectionRadius = loudness * 30f,  // 根据响度计算检测距离
            duration = duration,
            sourceTag = sourceTag,
            timeSinceCreated = 0
        };
        
        activeNoises.Add(noise);
    }
    
    // 更新噪声
    void Update()
    {
        float deltaTime = Time.deltaTime;
        
        for (int i = activeNoises.Count - 1; i >= 0; i--)
        {
            activeNoises[i].timeSinceCreated += deltaTime;
            
            if (activeNoises[i].timeSinceCreated > activeNoises[i].duration)
            {
                activeNoises.RemoveAt(i);
            }
        }
    }
    
    // AI检测噪声
    public bool CanHearNoise(AICharacter ai, out Vector3 noisePosition)
    {
        noisePosition = Vector3.zero;
        
        foreach (var noise in activeNoises)
        {
            float distance = Vector3.Distance(ai.Position, noise.position);
            
            if (distance < noise.detectionRadius)
            {
                noisePosition = noise.position;
                return true;
            }
        }
        
        return false;
    }
}

6.3 条件感知触发

根据游戏状态动态调整感知行为:

感知状态机

public class感知StateMachine : MonoBehaviour
{
    public enum State
    {
        Idle,        // 空闲 - 感知范围最小
        Patrol,     // 巡逻 - 感知范围中等
        Alert,      // 警觉 - 感知范围扩大
        Combat      // 战斗 - 感知范围最大
    }
    
    public State currentState = State.Idle;
    
    // 状态转换
    public void OnStateChanged(State newState)
    {
        currentState = newState;
        
        switch (newState)
        {
            case State.Idle:
                SetPerception(10f, 60f, 1f);
                break;
            case State.Patrol:
                SetPerception(15f, 90f, 0.5f);
                break;
            case State.Alert:
                SetPerception(25f, 120f, 0.2f);
                break;
            case State.Combat:
                SetPerception(40f, 180f, 0f);
                break;
        }
    }
    
    void SetPerception(float range, float fov, float updateInterval)
    {
        perceptionModule.radius = range;
        perceptionModule.fov = fov;
        perceptionModule.updateInterval = updateInterval;
    }
}

6.4 群体感知

实现多个AI之间的信息共享:

群体感知网络

public class感知Network : MonoBehaviour
{
    public List<AICharacter> teamMembers = new List<AICharacter>();
    public float communicationRange = 15f;
    
    // 广播感知信息
    public void BroadcastDetection(Transform target, Vector3 lastKnownPosition)
    {
        foreach (var member in teamMembers)
        {
            if (member == null) continue;
            
            float distance = Vector3.Distance(transform.position, member.Position);
            
            if (distance < communicationRange)
            {
                // 共享目标信息
                member.OnTeamMemberDetected(target, lastKnownPosition);
            }
        }
    }
    
    // 添加团队成员
    public void AddTeamMember(AICharacter character)
    {
        if (!teamMembers.Contains(character))
        {
            teamMembers.Add(character);
        }
    }
}

七、与行为系统集成

7.1 与行为树集成

将感知系统与Behavior Designer等行为树工具集成:

感知条件节点

using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;

[TaskCategory("Perception")]
[TaskDescription("检测目标是否在视野范围内")]
public class CanSeeTarget : Conditional
{
    public SharedTransform target;
    public SharedFloat viewDistance = 20f;
    public SharedFloat viewAngle = 90f;
    
    private AICharacter aiCharacter;
    
    public override void OnStart()
    {
        aiCharacter = GetComponent<AICharacter>();
    }
    
    public override TaskStatus OnUpdate()
    {
        if (target.Value == null)
            return TaskStatus.Failure;
        
        if (aiCharacter.Perception.CanSee(target.Value))
            return TaskStatus.Success;
        
        return TaskStatus.Failure;
    }
}

感知动作节点

[TaskCategory("Perception")]
[TaskDescription("设置感知范围")]
public class SetPerceptionRange : Action
{
    public SharedFloat range;
    public SharedFloat fov;
    
    private AICharacter aiCharacter;
    
    public override void OnStart()
    {
        aiCharacter = GetComponent<AICharacter>();
    }
    
    public override TaskStatus OnUpdate()
    {
        aiCharacter.Perception.SetRange(range.Value);
        aiCharacter.Perception.SetFOV(fov.Value);
        
        return TaskStatus.Success;
    }
}

7.2 状态转换集成

感知触发状态转换:

public class感知StateTransitioner : MonoBehaviour
{
    public EnemyStateMachine stateMachine;
    public AICharacter aiCharacter;
    
    void Update()
    {
        // 检测目标
        if (aiCharacter.Perception.CanSeePlayer())
        {
            stateMachine.TransitionTo(EnemyState.Chase);
        }
        else if (aiCharacter.Perception.CanHearThreat())
        {
            stateMachine.TransitionTo(EnemyState.Investigate);
        }
    }
}

7.3 决策系统集成

感知信息用于AI决策:

public class感知BasedDecision : MonoBehaviour
{
    public AICharacter aiCharacter;
    
    public DecisionResult MakeDecision()
    {
        // 获取感知信息
        var perception = aiCharacter.Perception;
        
        // 检查各种条件
        if (perception.CanSeePlayer())
        {
            // 看到玩家
            if (perception.GetDistanceToPlayer() < attackRange)
            {
                return DecisionResult.Attack;
            }
            else
            {
                return DecisionResult.Chase;
            }
        }
        else if (perception.HasMemoryOfPlayer())
        {
            // 记得玩家最后位置
            return DecisionResult.Investigate;
        }
        else if (perception.CanHearNoise())
        {
            // 听到声音
            return DecisionResult.Investigate;
        }
        
        // 默认巡逻
        return DecisionResult.Patrol;
    }
}

八、性能优化与最佳实践

8.1 感知频率优化

不是所有AI都需要每帧感知更新:

分级更新策略

public class Optimized感知Update : MonoBehaviour
{
    // 根据AI状态设置更新间隔
    public float patrolUpdateInterval = 0.5f;
    public float alertUpdateInterval = 0.2f;
    public float combatUpdateInterval = 0.1f;
    
    private float currentInterval;
    private float timer;
    
    void Update()
    {
        timer += Time.deltaTime;
        
        if (timer >= currentInterval)
        {
            timer = 0;
            UpdatePerception();
        }
    }
    
    public void SetAIState(AIState state)
    {
        switch (state)
        {
            case AIState.Patrol:
                currentInterval = patrolUpdateInterval;
                break;
            case AIState.Alert:
                currentInterval = alertUpdateInterval;
                break;
            case AIState.Combat:
                currentInterval = combatUpdateInterval;
                break;
        }
    }
}

8.2 空间分区优化

大规模场景中使用空间分区优化感知检测:

public class Spatial感知Optimization : MonoBehaviour
{
    // 八叉树或其他空间分区结构
    private Octree<Transform> perceptionOctree;
    
    // 只检测附近的潜在目标
    public List<Transform> GetNearbyTargets(Vector3 position, float range)
    {
        return perceptionOctree.Query(position, range);
    }
}

8.3 感知缓存

避免重复计算感知信息:

public class感知Cache : MonoBehaviour
{
    private class CachedPerception
    {
        public Transform target;
        public bool isVisible;
        public float distance;
        public Vector3 lastPosition;
        public float cacheTime;
    }
    
    private Dictionary<Transform, CachedPerception> perceptionCache = new Dictionary<Transform, CachedPerception>();
    private float cacheDuration = 0.1f;
    
    // 获取缓存的感知结果
    public bool GetCachedPerception(Transform target, out CachedPerception cached)
    {
        if (perceptionCache.TryGetValue(target, out cached))
        {
            if (Time.time - cached.cacheTime < cacheDuration)
            {
                return true;
            }
        }
        
        cached = null;
        return false;
    }
}

8.4 最佳实践建议

设计建议

  • 根据游戏类型选择合适的感知范围。
  • 使用分级感知系统适应不同游戏状态。
  • 实现感知记忆增加AI智能感。
  • 考虑性能平衡感知精度和频率。

性能建议

  • 感知更新使用适当间隔而非每帧。
  • 使用LayerMask减少不必要的检测。
  • 实现感知结果缓存避免重复计算。
  • 对于大量AI使用空间分区优化。

调试建议

  • 使用Gizmos绘制感知范围辅助调试。
  • 添加日志输出感知事件。
  • 创建测试场景验证感知逻辑。
  • 使用Profiler监测感知系统性能。

九、实战案例:潜行游戏敌人AI

9.1 项目概述

创建一个完整的潜行游戏敌人AI系统,包含视觉感知、听觉感知和感知记忆功能。

9.2 系统架构

感知系统组件

EnemyPerceptionSystem
├── VisualPerception (视觉感知)
│   ├── FOV检测
│   ├── 遮挡检测
│   └── 距离计算
├── AudioPerception (听觉感知)
│   ├── 声音检测
│   └── 方向识别
├── PerceptionMemory (感知记忆)
│   ├── 记忆存储
│   └── 置信度衰减
└── StateIntegration (状态集成)
    └── 与AI状态机连接

9.3 完整实现

敌人感知控制器

public class EnemyPerceptionController : MonoBehaviour
{
    [Header("Visual Perception")]
    public float visualRange = 25f;
    public float visualAngle = 90f;
    public LayerMask playerLayer;
    public LayerMask obstacleLayer;
    public Transform eyesPosition;
    
    [Header("Audio Perception")]
    public float audioRange = 20f;
    
    [Header("Memory")]
    public float memoryDuration = 30f;
    public float suspicionTime = 10f;
    
    [Header("State")]
    public Transform currentTarget;
    public Vector3 lastKnownPosition;
    public PerceptionState state = PerceptionState.Patrol;
    
    private PerceptionMemory memory;
    private EnemyStateMachine stateMachine;
    
    void Start()
    {
        memory = new PerceptionMemory(memoryDuration);
        stateMachine = GetComponent<EnemyStateMachine>();
    }
    
    void Update()
    {
        // 根据状态更新感知行为
        switch (state)
        {
            case PerceptionState.Patrol:
                UpdatePatrolPerception();
                break;
            case PerceptionState.Suspicious:
                UpdateSuspiciousPerception();
                break;
            case PerceptionState.Combat:
                UpdateCombatPerception();
                break;
        }
        
        // 更新记忆
        memory.UpdateMemories();
    }
    
    void UpdatePatrolPerception()
    {
        // 检测视觉目标
        Transform visibleTarget = DetectVisiblePlayer();
        
        if (visibleTarget != null)
        {
            // 玩家被发现
            memory.AddMemory(visibleTarget, visibleTarget.position, 1f);
            currentTarget = visibleTarget;
            lastKnownPosition = visibleTarget.position;
            stateMachine.OnPlayerDetected();
            state = PerceptionState.Combat;
        }
        
        // 检测声音
        if (DetectSound(out Vector3 soundPos))
        {
            memory.AddSoundMemory(soundPos, 0.8f);
            lastKnownPosition = soundPos;
            stateMachine.OnSoundHeard(soundPos);
            state = PerceptionState.Suspicious;
        }
    }
    
    void UpdateSuspiciousPerception()
    {
        // 检查是否重新发现玩家
        Transform visibleTarget = DetectVisiblePlayer();
        
        if (visibleTarget != null)
        {
            currentTarget = visibleTarget;
            lastKnownPosition = visibleTarget.position;
            stateMachine.OnPlayerDetected();
            state = PerceptionState.Combat;
        }
        
        // 检查记忆是否过期
        if (memory.GetMemoryConfidence() < 0.1f)
        {
            stateMachine.ReturnToPatrol();
            state = PerceptionState.Patrol;
        }
    }
    
    void UpdateCombatPerception()
    {
        // 持续追踪玩家
        Transform visibleTarget = DetectVisiblePlayer();
        
        if (visibleTarget != null)
        {
            currentTarget = visibleTarget;
            lastKnownPosition = visibleTarget.position;
            memory.AddMemory(visibleTarget, visibleTarget.position, 1f);
        }
        else if (currentTarget != null)
        {
            // 玩家丢失,更新最后已知位置
            memory.AddMemory(currentTarget, lastKnownPosition, memory.GetMemoryConfidence() * 0.95f);
            
            // 转向最后已知位置搜索
            stateMachine.OnPlayerLost(lastKnownPosition);
        }
    }
    
    Transform DetectVisiblePlayer()
    {
        Collider[] players = Physics.OverlapSphere(transform.position, visualRange, playerLayer);
        
        foreach (var player in players)
        {
            if (IsInFieldOfView(player.transform) && HasLineOfSight(player.transform))
            {
                return player.transform;
            }
        }
        
        return null;
    }
    
    bool IsInFieldOfView(Transform target)
    {
        Vector3 direction = (target.position - eyesPosition.position).normalized;
        float angle = Vector3.Angle(transform.forward, direction);
        return angle < visualAngle * 0.5f;
    }
    
    bool HasLineOfSight(Transform target)
    {
        RaycastHit hit;
        if (Physics.Linecast(eyesPosition.position, target.position, out hit, obstacleLayer))
        {
            return hit.transform == target;
        }
        return true;
    }
    
    bool DetectSound(out Vector3 soundPosition)
    {
        // 实现声音检测逻辑
        soundPosition = Vector3.zero;
        return false;
    }
}

感知记忆类

public class PerceptionMemory
{
    private class MemoryEntry
    {
        public Transform target;
        public Vector3 position;
        public float confidence;
        public float timeSinceCreation;
    }
    
    private List<MemoryEntry> memories = new List<MemoryEntry>();
    private float memoryDuration;
    private float decayRate = 0.05f;
    
    public PerceptionMemory(float duration)
    {
        memoryDuration = duration;
    }
    
    public void AddMemory(Transform target, Vector3 position, float confidence)
    {
        var existing = memories.Find(m => m.target == target);
        
        if (existing != null)
        {
            existing.position = position;
            existing.confidence = confidence;
            existing.timeSinceCreation = 0;
        }
        else
        {
            memories.Add(new MemoryEntry
            {
                target = target,
                position = position,
                confidence = confidence,
                timeSinceCreation = 0
            });
        }
    }
    
    public void UpdateMemories()
    {
        for (int i = memories.Count - 1; i >= 0; i--)
        {
            memories[i].timeSinceCreation += Time.deltaTime;
            memories[i].confidence = Mathf.Max(0, memories[i].confidence - decayRate * Time.deltaTime);
            
            if (memories[i].timeSinceCreation > memoryDuration || memories[i].confidence <= 0)
            {
                memories.RemoveAt(i);
            }
        }
    }
    
    public float GetMemoryConfidence()
    {
        if (memories.Count == 0) return 0;
        
        float highest = 0;
        foreach (var memory in memories)
        {
            highest = Mathf.Max(highest, memory.confidence);
        }
        
        return highest;
    }
}

十、总结与展望

10.1 核心要点回顾

通过本文的详细介绍,读者应该对Unity AI Perception系统有了全面的了解:

  1. 感知系统基础:理解AI感知的基本概念和类型。
  2. 核心组件:掌握AI Perception组件和感知模块的使用方法。
  3. 实现原理:理解视觉、听觉感知和记忆系统的实现原理。
  4. 高级应用:掌握分级感知、噪声系统等高级功能。
  5. 集成方法:了解与行为树和决策系统的集成方式。
  6. 性能优化:掌握感知系统的性能优化技巧。

10.2 适用场景

AI Perception系统特别适合以下游戏类型:

  • 潜行游戏
  • 战术射击游戏
  • RPG游戏中的敌人AI
  • 开放世界游戏中的NPC
  • 任何需要智能感知的游戏

10.3 学习资源

进一步学习AI Perception的资源:

  • Unity官方文档
  • AI导航系统文档
  • 行为树集成文档

10.4 未来发展

Unity的AI感知系统将持续发展:

  • 更精确的感知算法
  • 更好的性能优化
  • 更灵活的集成方式
  • 与机器学习系统的结合

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐