第5章 Unity引擎中智能体的环境感知系统构建与实践

5.1 智能体感知环境信息的基础机制

在游戏人工智能开发领域,环境感知是智能体决策与行为的基石。一个智能的角色必须能够感知其周围的世界,才能做出符合情境的合理反应。Unity引擎为实现这一目标提供了灵活的框架和工具集,开发者可以通过多种技术手段模拟生物或机械智能体的感知能力。

5.1.1 主动轮询检测机制

轮询方式是最直接的环境感知实现方法。在这种模式下,AI角色会以固定的时间间隔主动检查周围环境的状态。这种方法类似于现实世界中生物有意识地观察周围环境的行为。

轮询机制的核心在于定时器或更新循环。在Unity中,通常通过MonoBehaviour的Update()或FixedUpdate()方法来实现。虽然实现简单直观,但轮询方式存在明显的性能缺陷——无论环境是否发生变化,系统都会持续进行检查,这可能导致不必要的计算开销。

在商业游戏项目中,轮询通常用于对实时性要求不高或感知目标有限的场景。例如,一个策略游戏中资源采集单位的资源点检测,或者角色对自身状态(如生命值、能量值)的监控。

下面是轮询感知的基础实现框架:

using UnityEngine;

public class PollingSensor : MonoBehaviour
{
    [SerializeField]
    private float detectionRadius = 10.0f;
    
    [SerializeField]
    private float pollingInterval = 0.5f;
    
    private float timeSinceLastPoll;
    
    private void Start()
    {
        timeSinceLastPoll = 0.0f;
    }
    
    private void Update()
    {
        timeSinceLastPoll += Time.deltaTime;
        
        if (timeSinceLastPoll >= pollingInterval)
        {
            PerformEnvironmentCheck();
            timeSinceLastPoll = 0.0f;
        }
    }
    
    private void PerformEnvironmentCheck()
    {
        Collider[] hitColliders = Physics.OverlapSphere(
            transform.position, 
            detectionRadius
        );
        
        foreach (Collider collider in hitColliders)
        {
            // 检测逻辑:识别玩家、敌人、物品等
            if (collider.CompareTag("Player"))
            {
                Debug.Log("玩家进入感知范围");
                // 触发相应的AI行为
            }
        }
    }
    
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, detectionRadius);
    }
}
5.1.2 事件驱动的响应机制

事件驱动方式是一种更高效、更模块化的感知实现方法。在这种模式下,AI角色不主动检查环境,而是注册对特定事件的监听,当这些事件发生时接收通知。这类似于现实世界中生物对突发声响或光线变化的反应。

事件驱动系统的优势在于其高效性和解耦性。感知系统只在相关事件发生时被激活,大大减少了不必要的计算。同时,事件生产者和消费者之间是松耦合的,便于系统的扩展和维护。

在Unity商业项目中,事件驱动常用于处理大量AI实体的感知需求,如大型多人在线游戏中NPC对玩家行为的反应,或开放世界游戏中环境变化对生物行为的影响。

下面是事件驱动感知系统的基础结构:

using UnityEngine;
using System;
using System.Collections.Generic;

// 游戏事件定义
public enum GameEventType
{
    PlayerMoved,
    WeaponFired,
    DoorOpened,
    ItemCollected
}

// 事件数据封装
public class GameEventData
{
    public GameEventType EventType;
    public Vector3 EventPosition;
    public GameObject EventSource;
    public float EventIntensity;
    public DateTime Timestamp;
}

// 事件管理器(单例模式)
public class GameEventManager : MonoBehaviour
{
    private static GameEventManager instance;
    
    private Dictionary<GameEventType, List<Action<GameEventData>>> eventListeners;
    
    public static GameEventManager Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject managerObject = new GameObject("GameEventManager");
                instance = managerObject.AddComponent<GameEventManager>();
                instance.Initialize();
                DontDestroyOnLoad(managerObject);
            }
            return instance;
        }
    }
    
    private void Initialize()
    {
        eventListeners = new Dictionary<GameEventType, List<Action<GameEventData>>>();
        
        foreach (GameEventType eventType in Enum.GetValues(typeof(GameEventType)))
        {
            eventListeners[eventType] = new List<Action<GameEventData>>();
        }
    }
    
    public void RegisterListener(GameEventType eventType, Action<GameEventData> listener)
    {
        if (eventListeners.ContainsKey(eventType))
        {
            eventListeners[eventType].Add(listener);
        }
    }
    
    public void UnregisterListener(GameEventType eventType, Action<GameEventData> listener)
    {
        if (eventListeners.ContainsKey(eventType) && eventListeners[eventType].Contains(listener))
        {
            eventListeners[eventType].Remove(listener);
        }
    }
    
    public void TriggerEvent(GameEventData eventData)
    {
        if (eventListeners.ContainsKey(eventData.EventType))
        {
            foreach (Action<GameEventData> listener in eventListeners[eventData.EventType])
            {
                listener(eventData);
            }
        }
    }
}

// 基于事件的感知器
public class EventDrivenSensor : MonoBehaviour
{
    [SerializeField]
    private float hearingRange = 20.0f;
    
    private void OnEnable()
    {
        GameEventManager.Instance.RegisterListener(GameEventType.WeaponFired, OnWeaponFired);
    }
    
    private void OnDisable()
    {
        GameEventManager.Instance.UnregisterListener(GameEventType.WeaponFired, OnWeaponFired);
    }
    
    private void OnWeaponFired(GameEventData eventData)
    {
        float distanceToEvent = Vector3.Distance(transform.position, eventData.EventPosition);
        
        if (distanceToEvent <= hearingRange)
        {
            Debug.Log($"感知到枪声,距离:{distanceToEvent},强度:{eventData.EventIntensity}");
            ProcessSoundEvent(eventData);
        }
    }
    
    private void ProcessSoundEvent(GameEventData eventData)
    {
        // 根据声音事件做出AI决策
        // 例如:转向声源方向、进入警戒状态、前往调查等
    }
}
5.1.3 触发器交互机制

触发器是Unity引擎提供的专门用于检测物体进入、停留和离开特定区域的组件。它基于物理引擎的碰撞检测系统,但设置为"Trigger"模式的碰撞体不会产生物理交互,只用于触发事件。

触发器机制特别适合实现区域性感知,如安全区域、陷阱区域、任务触发区域等。在商业游戏中,触发器常用于实现NPC的对话触发、场景切换、任务进度更新等功能。

下面是触发器感知的典型实现:

using UnityEngine;

public class AreaTriggerSensor : MonoBehaviour
{
    [SerializeField]
    private string targetTag = "Player";
    
    [SerializeField]
    private bool drawDebugGizmos = true;
    
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag(targetTag))
        {
            Debug.Log($"{targetTag}进入感知区域");
            OnTargetEntered(other.gameObject);
        }
    }
    
    private void OnTriggerStay(Collider other)
    {
        if (other.CompareTag(targetTag))
        {
            OnTargetInArea(other.gameObject);
        }
    }
    
    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag(targetTag))
        {
            Debug.Log($"{targetTag}离开感知区域");
            OnTargetExited(other.gameObject);
        }
    }
    
    protected virtual void OnTargetEntered(GameObject target)
    {
        // 子类可重写此方法实现特定行为
    }
    
    protected virtual void OnTargetInArea(GameObject target)
    {
        // 目标在区域内持续触发的逻辑
    }
    
    protected virtual void OnTargetExited(GameObject target)
    {
        // 子类可重写此方法实现特定行为
    }
    
    private void OnDrawGizmos()
    {
        if (!drawDebugGizmos)
        {
            return;
        }
        
        Collider collider = GetComponent<Collider>();
        if (collider == null)
        {
            return;
        }
        
        Gizmos.color = new Color(0, 1, 0, 0.3f);
        
        if (collider is BoxCollider boxCollider)
        {
            Gizmos.DrawCube(
                transform.position + boxCollider.center, 
                Vector3.Scale(boxCollider.size, transform.lossyScale)
            );
        }
        else if (collider is SphereCollider sphereCollider)
        {
            Gizmos.DrawSphere(
                transform.position + sphereCollider.center, 
                sphereCollider.radius * Mathf.Max(
                    transform.lossyScale.x, 
                    transform.lossyScale.y, 
                    transform.lossyScale.z
                )
            );
        }
    }
}

5.2 各类感知系统的具体实践方案

5.2.1 触发器系统的基础抽象设计

在构建复杂的AI感知系统时,创建一个可扩展的触发器基类是至关重要的。这个基类定义了所有触发器的共同接口和行为模式,使得不同类型的触发器可以统一管理。

using UnityEngine;
using System;

public abstract class TriggerBase : MonoBehaviour
{
    public string triggerId;
    public bool isActive = true;
    public float cooldownTime = 1.0f;
    
    private float lastTriggerTime;
    
    public event Action<GameObject> OnTriggerActivated;
    public event Action<GameObject> OnTriggerDeactivated;
    
    protected virtual void Start()
    {
        lastTriggerTime = -cooldownTime;
    }
    
    protected bool CanTrigger()
    {
        if (!isActive)
        {
            return false;
        }
        
        return Time.time >= lastTriggerTime + cooldownTime;
    }
    
    protected void RecordTriggerTime()
    {
        lastTriggerTime = Time.time;
    }
    
    protected virtual void ActivateTrigger(GameObject triggerTarget)
    {
        if (!CanTrigger())
        {
            return;
        }
        
        RecordTriggerTime();
        OnTriggerActivated?.Invoke(triggerTarget);
        ExecuteTriggerBehavior(triggerTarget);
    }
    
    protected virtual void DeactivateTrigger(GameObject triggerTarget)
    {
        OnTriggerDeactivated?.Invoke(triggerTarget);
        ExecuteDeactivationBehavior(triggerTarget);
    }
    
    protected abstract void ExecuteTriggerBehavior(GameObject triggerTarget);
    protected abstract void ExecuteDeactivationBehavior(GameObject triggerTarget);
    
    public virtual void EnableTrigger()
    {
        isActive = true;
    }
    
    public virtual void DisableTrigger()
    {
        isActive = false;
    }
    
    public virtual void ToggleTrigger()
    {
        isActive = !isActive;
    }
}

// 具体触发器实现: proximity trigger
public class ProximityTrigger : TriggerBase
{
    [SerializeField]
    private float activationDistance = 5.0f;
    
    [SerializeField]
    private Transform targetTransform;
    
    [SerializeField]
    private bool usePlayerAsDefaultTarget = true;
    
    private void Start()
    {
        base.Start();
        
        if (usePlayerAsDefaultTarget && targetTransform == null)
        {
            GameObject player = GameObject.FindGameObjectWithTag("Player");
            if (player != null)
            {
                targetTransform = player.transform;
            }
        }
    }
    
    private void Update()
    {
        if (targetTransform == null)
        {
            return;
        }
        
        float distance = Vector3.Distance(transform.position, targetTransform.position);
        
        if (distance <= activationDistance)
        {
            ActivateTrigger(targetTransform.gameObject);
        }
        else
        {
            DeactivateTrigger(targetTransform.gameObject);
        }
    }
    
    protected override void ExecuteTriggerBehavior(GameObject triggerTarget)
    {
        Debug.Log($"接近触发器激活:{triggerTarget.name} 进入范围");
        // 实际游戏中的触发逻辑,如开启机关、播放音效等
    }
    
    protected override void ExecuteDeactivationBehavior(GameObject triggerTarget)
    {
        Debug.Log($"接近触发器失活:{triggerTarget.name} 离开范围");
        // 离开范围后的清理逻辑
    }
    
    private void OnDrawGizmosSelected()
    {
        if (!isActive)
        {
            return;
        }
        
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireSphere(transform.position, activationDistance);
    }
}
5.2.2 感知器系统的抽象基础架构

感知器是AI角色感知环境的核心组件。一个良好的感知器基类设计应该支持多种感知类型,并提供统一的接口来处理感知数据。

using UnityEngine;
using System.Collections.Generic;

public abstract class SensorBase : MonoBehaviour
{
    [SerializeField]
    protected float updateInterval = 0.1f;
    
    [SerializeField]
    protected LayerMask detectionLayerMask = ~0;
    
    [SerializeField]
    protected LayerMask occlusionLayerMask;
    
    protected float lastUpdateTime;
    protected List<PerceptionData> currentPerceptions;
    
    public List<PerceptionData> CurrentPerceptions
    {
        get { return new List<PerceptionData>(currentPerceptions); }
    }
    
    protected virtual void Awake()
    {
        currentPerceptions = new List<PerceptionData>();
        lastUpdateTime = -updateInterval;
    }
    
    protected virtual void Update()
    {
        if (Time.time >= lastUpdateTime + updateInterval)
        {
            UpdatePerceptions();
            lastUpdateTime = Time.time;
        }
    }
    
    protected abstract void UpdatePerceptions();
    
    protected virtual bool CheckOcclusion(Vector3 fromPosition, Vector3 toPosition, GameObject target)
    {
        if (occlusionLayerMask.value == 0)
        {
            return false;
        }
        
        Vector3 direction = toPosition - fromPosition;
        float distance = direction.magnitude;
        
        RaycastHit hit;
        if (Physics.Raycast(
            fromPosition, 
            direction.normalized, 
            out hit, 
            distance, 
            occlusionLayerMask
        ))
        {
            // 检查是否击中了目标本身
            if (hit.collider.gameObject != target)
            {
                return true;
            }
        }
        
        return false;
    }
    
    public virtual void ClearPerceptions()
    {
        currentPerceptions.Clear();
    }
    
    public virtual bool HasPerceptionOfTag(string tag)
    {
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject != null && perception.perceivedObject.CompareTag(tag))
            {
                return true;
            }
        }
        return false;
    }
    
    public virtual List<GameObject> GetPerceivedObjectsByTag(string tag)
    {
        List<GameObject> result = new List<GameObject>();
        
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject != null && perception.perceivedObject.CompareTag(tag))
            {
                result.Add(perception.perceivedObject);
            }
        }
        
        return result;
    }
}

// 感知数据容器
[System.Serializable]
public class PerceptionData
{
    public GameObject perceivedObject;
    public Vector3 lastKnownPosition;
    public Vector3 lastKnownVelocity;
    public float confidence; // 感知可信度(0-1)
    public float lastPerceptionTime;
    public PerceptionType perceptionType;
    
    public PerceptionData(
        GameObject obj, 
        Vector3 position, 
        PerceptionType type
    )
    {
        perceivedObject = obj;
        lastKnownPosition = position;
        perceptionType = type;
        confidence = 1.0f;
        lastPerceptionTime = Time.time;
    }
    
    public void UpdatePosition(Vector3 newPosition, float newConfidence)
    {
        lastKnownPosition = newPosition;
        confidence = newConfidence;
        lastPerceptionTime = Time.time;
    }
    
    public float GetAge()
    {
        return Time.time - lastPerceptionTime;
    }
}

public enum PerceptionType
{
    Visual,
    Auditory,
    Tactile,
    Memory,
    Item
}
5.2.3 事件管理系统的优化实现

高效的事件管理系统是大型游戏项目不可或缺的组成部分。下面是一个优化的事件管理器实现,支持优先级和事件过滤。

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;

public class EnhancedEventManager : MonoBehaviour
{
    private static EnhancedEventManager instance;
    
    [System.Serializable]
    private class EventListener
    {
        public Action<GameEventData> callback;
        public GameObject listenerObject;
        public int priority;
        public float maxDistance; // 0表示无限距离
    }
    
    private Dictionary<GameEventType, List<EventListener>> eventListeners;
    private Queue<GameEventData> eventQueue;
    
    [SerializeField]
    private int maxEventsPerFrame = 10;
    
    public static EnhancedEventManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<EnhancedEventManager>();
                if (instance == null)
                {
                    GameObject go = new GameObject("EnhancedEventManager");
                    instance = go.AddComponent<EnhancedEventManager>();
                }
            }
            return instance;
        }
    }
    
    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        instance = this;
        DontDestroyOnLoad(gameObject);
        
        Initialize();
    }
    
    private void Initialize()
    {
        eventListeners = new Dictionary<GameEventType, List<EventListener>>();
        eventQueue = new Queue<GameEventData>();
        
        foreach (GameEventType eventType in Enum.GetValues(typeof(GameEventType)))
        {
            eventListeners[eventType] = new List<EventListener>();
        }
    }
    
    private void Update()
    {
        ProcessEventQueue();
    }
    
    public void RegisterListener(
        GameEventType eventType, 
        Action<GameEventData> callback, 
        GameObject listenerObject = null, 
        int priority = 0, 
        float maxDistance = 0
    )
    {
        if (!eventListeners.ContainsKey(eventType))
        {
            eventListeners[eventType] = new List<EventListener>();
        }
        
        EventListener newListener = new EventListener
        {
            callback = callback,
            listenerObject = listenerObject,
            priority = priority,
            maxDistance = maxDistance
        };
        
        eventListeners[eventType].Add(newListener);
        
        // 按优先级排序
        eventListeners[eventType] = eventListeners[eventType]
            .OrderByDescending(l => l.priority)
            .ToList();
    }
    
    public void UnregisterListener(GameEventType eventType, Action<GameEventData> callback)
    {
        if (!eventListeners.ContainsKey(eventType))
        {
            return;
        }
        
        eventListeners[eventType].RemoveAll(l => l.callback == callback);
    }
    
    public void UnregisterAllListeners(GameObject listenerObject)
    {
        foreach (var eventType in eventListeners.Keys)
        {
            eventListeners[eventType].RemoveAll(l => l.listenerObject == listenerObject);
        }
    }
    
    public void TriggerEvent(GameEventData eventData, bool immediate = false)
    {
        if (immediate)
        {
            ProcessEventImmediately(eventData);
        }
        else
        {
            eventQueue.Enqueue(eventData);
        }
    }
    
    private void ProcessEventQueue()
    {
        int processedCount = 0;
        
        while (eventQueue.Count > 0 && processedCount < maxEventsPerFrame)
        {
            GameEventData eventData = eventQueue.Dequeue();
            ProcessEventImmediately(eventData);
            processedCount++;
        }
    }
    
    private void ProcessEventImmediately(GameEventData eventData)
    {
        if (!eventListeners.ContainsKey(eventData.EventType))
        {
            return;
        }
        
        List<EventListener> listeners = eventListeners[eventData.EventType];
        
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            EventListener listener = listeners[i];
            
            // 检查监听器是否仍然有效
            if (listener.callback == null)
            {
                listeners.RemoveAt(i);
                continue;
            }
            
            // 检查距离限制
            if (listener.maxDistance > 0 && listener.listenerObject != null)
            {
                float distance = Vector3.Distance(
                    listener.listenerObject.transform.position, 
                    eventData.EventPosition
                );
                
                if (distance > listener.maxDistance)
                {
                    continue;
                }
            }
            
            try
            {
                listener.callback(eventData);
            }
            catch (Exception e)
            {
                Debug.LogError($"事件处理错误:{e.Message}");
            }
        }
    }
    
    public void ClearEventQueue()
    {
        eventQueue.Clear();
    }
    
    public int GetListenerCount(GameEventType eventType)
    {
        if (!eventListeners.ContainsKey(eventType))
        {
            return 0;
        }
        
        return eventListeners[eventType].Count;
    }
}
5.2.4 视觉感知系统的实现细节

视觉感知是游戏AI中最复杂也最常用的感知类型。一个完整的视觉系统需要考虑视线检测、视锥范围、遮挡检测、目标识别等多个方面。

using UnityEngine;
using System.Collections.Generic;

public class VisualPerceptionSystem : SensorBase
{
    [Header("视觉参数")]
    [SerializeField]
    private float sightRange = 20.0f;
    
    [SerializeField]
    [Range(0, 360)]
    private float fieldOfView = 90.0f;
    
    [SerializeField]
    private int rayCount = 10;
    
    [SerializeField]
    private bool useRaycastScanning = true;
    
    [SerializeField]
    private float peripheralVisionMultiplier = 1.5f;
    
    [Header("目标过滤")]
    [SerializeField]
    private string[] detectableTags = { "Player", "Enemy", "NPC" };
    
    [SerializeField]
    private bool ignoreInvisibleObjects = true;
    
    private HashSet<GameObject> previouslyVisibleObjects;
    
    protected override void Awake()
    {
        base.Awake();
        previouslyVisibleObjects = new HashSet<GameObject>();
    }
    
    protected override void UpdatePerceptions()
    {
        List<PerceptionData> newPerceptions = new List<PerceptionData>();
        HashSet<GameObject> currentlyVisibleObjects = new HashSet<GameObject>();
        
        if (useRaycastScanning)
        {
            PerformRaycastScan(newPerceptions, currentlyVisibleObjects);
        }
        else
        {
            PerformOverlapScan(newPerceptions, currentlyVisibleObjects);
        }
        
        // 处理不再可见的对象
        HandleNoLongerVisibleObjects(currentlyVisibleObjects);
        
        // 更新当前感知
        currentPerceptions = newPerceptions;
        previouslyVisibleObjects = currentlyVisibleObjects;
    }
    
    private void PerformRaycastScan(List<PerceptionData> perceptions, HashSet<GameObject> visibleObjects)
    {
        float angleStep = fieldOfView / (rayCount - 1);
        float startAngle = -fieldOfView / 2;
        
        for (int i = 0; i < rayCount; i++)
        {
            float currentAngle = startAngle + (angleStep * i);
            Vector3 direction = Quaternion.Euler(0, currentAngle, 0) * transform.forward;
            
            RaycastHit[] hits = Physics.RaycastAll(
                transform.position, 
                direction, 
                sightRange, 
                detectionLayerMask
            );
            
            System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
            
            foreach (RaycastHit hit in hits)
            {
                GameObject hitObject = hit.collider.gameObject;
                
                if (hitObject == gameObject)
                {
                    continue;
                }
                
                if (!IsObjectDetectable(hitObject))
                {
                    continue;
                }
                
                if (CheckOcclusion(transform.position, hit.point, hitObject))
                {
                    continue;
                }
                
                // 计算感知可信度(基于距离和角度)
                float distanceFactor = 1.0f - Mathf.Clamp01(hit.distance / sightRange);
                float angleFactor = 1.0f - Mathf.Abs(currentAngle) / (fieldOfView / 2);
                float confidence = distanceFactor * angleFactor;
                
                // 检查是否已在感知列表中
                PerceptionData existingPerception = perceptions.Find(
                    p => p.perceivedObject == hitObject
                );
                
                if (existingPerception != null)
                {
                    // 更新已有感知(取最高可信度)
                    if (confidence > existingPerception.confidence)
                    {
                        existingPerception.UpdatePosition(hit.point, confidence);
                    }
                }
                else
                {
                    // 创建新感知
                    PerceptionData newPerception = new PerceptionData(
                        hitObject, 
                        hit.point, 
                        PerceptionType.Visual
                    )
                    {
                        confidence = confidence
                    };
                    
                    perceptions.Add(newPerception);
                }
                
                visibleObjects.Add(hitObject);
                break; // 只处理最近的可见对象
            }
        }
    }
    
    private void PerformOverlapScan(List<PerceptionData> perceptions, HashSet<GameObject> visibleObjects)
    {
        Collider[] hitColliders = Physics.OverlapSphere(
            transform.position, 
            sightRange, 
            detectionLayerMask
        );
        
        foreach (Collider collider in hitColliders)
        {
            GameObject hitObject = collider.gameObject;
            
            if (hitObject == gameObject)
            {
                continue;
            }
            
            if (!IsObjectDetectable(hitObject))
            {
                continue;
            }
            
            Vector3 directionToTarget = (hitObject.transform.position - transform.position).normalized;
            float angleToTarget = Vector3.Angle(transform.forward, directionToTarget);
            
            // 检查是否在视野内
            if (angleToTarget > fieldOfView / 2)
            {
                // 检查是否在周边视野内(可信度较低)
                if (angleToTarget > fieldOfView * peripheralVisionMultiplier / 2)
                {
                    continue;
                }
            }
            
            // 检查遮挡
            if (CheckOcclusion(transform.position, hitObject.transform.position, hitObject))
            {
                continue;
            }
            
            float distance = Vector3.Distance(transform.position, hitObject.transform.position);
            float distanceFactor = 1.0f - Mathf.Clamp01(distance / sightRange);
            float angleFactor = 1.0f - Mathf.Clamp01(angleToTarget / (fieldOfView / 2));
            float confidence = distanceFactor * angleFactor * 0.5f; // 重叠扫描可信度较低
            
            PerceptionData perception = new PerceptionData(
                hitObject, 
                hitObject.transform.position, 
                PerceptionType.Visual
            )
            {
                confidence = confidence
            };
            
            perceptions.Add(perception);
            visibleObjects.Add(hitObject);
        }
    }
    
    private bool IsObjectDetectable(GameObject obj)
    {
        // 检查标签
        bool tagMatch = false;
        foreach (string tag in detectableTags)
        {
            if (obj.CompareTag(tag))
            {
                tagMatch = true;
                break;
            }
        }
        
        if (!tagMatch)
        {
            return false;
        }
        
        // 检查可见性
        if (ignoreInvisibleObjects)
        {
            Renderer renderer = obj.GetComponent<Renderer>();
            if (renderer != null && !renderer.isVisible)
            {
                return false;
            }
        }
        
        return true;
    }
    
    private void HandleNoLongerVisibleObjects(HashSet<GameObject> currentlyVisibleObjects)
    {
        foreach (GameObject previouslyVisibleObject in previouslyVisibleObjects)
        {
            if (!currentlyVisibleObjects.Contains(previouslyVisibleObject))
            {
                Debug.Log($"{previouslyVisibleObject.name} 离开了视野");
                // 可以在这里触发"失去目标"的事件或行为
            }
        }
    }
    
    public GameObject GetMostThreateningTarget()
    {
        if (currentPerceptions.Count == 0)
        {
            return null;
        }
        
        GameObject mostThreatening = null;
        float highestThreatLevel = 0;
        
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject == null)
            {
                continue;
            }
            
            // 简单的威胁评估:基于距离和面向角度
            float distance = Vector3.Distance(
                transform.position, 
                perception.lastKnownPosition
            );
            
            Vector3 directionToTarget = (perception.lastKnownPosition - transform.position).normalized;
            float angleToTarget = Vector3.Angle(transform.forward, directionToTarget);
            
            float distanceScore = 1.0f - Mathf.Clamp01(distance / sightRange);
            float angleScore = 1.0f - Mathf.Clamp01(angleToTarget / 180.0f);
            
            float threatLevel = distanceScore * 0.7f + angleScore * 0.3f;
            threatLevel *= perception.confidence;
            
            if (threatLevel > highestThreatLevel)
            {
                highestThreatLevel = threatLevel;
                mostThreatening = perception.perceivedObject;
            }
        }
        
        return mostThreatening;
    }
    
    private void OnDrawGizmos()
    {
        if (!Application.isPlaying)
        {
            return;
        }
        
        // 绘制视野范围
        Gizmos.color = new Color(0, 1, 0, 0.1f);
        
        Vector3 forward = transform.forward * sightRange;
        Vector3 left = Quaternion.Euler(0, -fieldOfView / 2, 0) * forward;
        Vector3 right = Quaternion.Euler(0, fieldOfView / 2, 0) * forward;
        
        Vector3[] vertices = new Vector3[4];
        vertices[0] = transform.position;
        vertices[1] = transform.position + left;
        vertices[2] = transform.position + forward;
        vertices[3] = transform.position + right;
        
        Gizmos.DrawLine(vertices[0], vertices[1]);
        Gizmos.DrawLine(vertices[0], vertices[3]);
        Gizmos.DrawLine(vertices[1], vertices[2]);
        Gizmos.DrawLine(vertices[2], vertices[3]);
        
        // 绘制感知到的目标
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject == null)
            {
                continue;
            }
            
            Gizmos.color = Color.Lerp(Color.yellow, Color.red, perception.confidence);
            Gizmos.DrawLine(transform.position, perception.lastKnownPosition);
            
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(perception.lastKnownPosition, 0.5f * perception.confidence);
        }
    }
}
5.2.5 听觉感知系统的完整实现

听觉感知使AI角色能够对声音事件做出反应,如枪声、脚步声、爆炸声等。下面是完整的听觉系统实现。

using UnityEngine;
using System.Collections.Generic;

public class AuditoryPerceptionSystem : SensorBase
{
    [Header("听觉参数")]
    [SerializeField]
    private float hearingRange = 30.0f;
    
    [SerializeField]
    private float soundDecayRate = 0.1f; // 声音衰减率(每米)
    
    [SerializeField]
    private float minimumAudibleVolume = 0.1f;
    
    [Header("声音类型敏感度")]
    [SerializeField]
    private float gunshotSensitivity = 1.0f;
    
    [SerializeField]
    private float footstepsSensitivity = 0.3f;
    
    [SerializeField]
    private float explosionSensitivity = 1.5f;
    
    [SerializeField]
    private float voiceSensitivity = 0.5f;
    
    private Dictionary<GameObject, SoundPerceptionData> soundPerceptions;
    
    [System.Serializable]
    private class SoundPerceptionData
    {
        public Vector3 soundPosition;
        public float volume;
        public SoundType soundType;
        public float timestamp;
        public float confidence;
        
        public SoundPerceptionData(
            Vector3 position, 
            float vol, 
            SoundType type, 
            float time
        )
        {
            soundPosition = position;
            volume = vol;
            soundType = type;
            timestamp = time;
            confidence = Mathf.Clamp01(vol);
        }
        
        public float GetAge()
        {
            return Time.time - timestamp;
        }
    }
    
    public enum SoundType
    {
        Gunshot,
        Footsteps,
        Explosion,
        Voice,
        Environment,
        Other
    }
    
    protected override void Awake()
    {
        base.Awake();
        soundPerceptions = new Dictionary<GameObject, SoundPerceptionData>();
        
        // 注册事件监听器
        EnhancedEventManager.Instance.RegisterListener(
            GameEventType.WeaponFired, 
            OnSoundEvent, 
            gameObject, 
            0, 
            hearingRange
        );
    }
    
    protected override void OnDestroy()
    {
        base.OnDestroy();
        
        if (EnhancedEventManager.Instance != null)
        {
            EnhancedEventManager.Instance.UnregisterAllListeners(gameObject);
        }
    }
    
    private void OnSoundEvent(GameEventData eventData)
    {
        float distance = Vector3.Distance(transform.position, eventData.EventPosition);
        
        if (distance > hearingRange)
        {
            return;
        }
        
        // 计算声音衰减
        float volume = eventData.EventIntensity * GetSensitivityForEventType(eventData.EventType);
        float decayFactor = Mathf.Exp(-soundDecayRate * distance);
        float finalVolume = volume * decayFactor;
        
        if (finalVolume < minimumAudibleVolume)
        {
            return;
        }
        
        // 检查遮挡(声音穿透性)
        float occlusionFactor = CheckSoundOcclusion(eventData.EventPosition);
        finalVolume *= occlusionFactor;
        
        if (finalVolume < minimumAudibleVolume)
        {
            return;
        }
        
        SoundType soundType = ConvertEventToSoundType(eventData.EventType);
        
        SoundPerceptionData perceptionData = new SoundPerceptionData(
            eventData.EventPosition,
            finalVolume,
            soundType,
            Time.time
        );
        
        // 存储或更新声音感知
        if (eventData.EventSource != null)
        {
            soundPerceptions[eventData.EventSource] = perceptionData;
        }
        else
        {
            // 没有源对象的声音(如环境音)
            GameObject tempKey = new GameObject("TempSoundKey");
            tempKey.transform.position = eventData.EventPosition;
            soundPerceptions[tempKey] = perceptionData;
            Destroy(tempKey, 5.0f); // 5秒后清理
        }
        
        Debug.Log($"听到声音:{soundType},音量:{finalVolume:F2},位置:{eventData.EventPosition}");
        
        // 触发行为反应
        ReactToSound(perceptionData);
    }
    
    private float GetSensitivityForEventType(GameEventType eventType)
    {
        switch (eventType)
        {
            case GameEventType.WeaponFired:
                return gunshotSensitivity;
            case GameEventType.PlayerMoved:
                return footstepsSensitivity;
            default:
                return 1.0f;
        }
    }
    
    private SoundType ConvertEventToSoundType(GameEventType eventType)
    {
        switch (eventType)
        {
            case GameEventType.WeaponFired:
                return SoundType.Gunshot;
            case GameEventType.PlayerMoved:
                return SoundType.Footsteps;
            case GameEventType.DoorOpened:
                return SoundType.Environment;
            default:
                return SoundType.Other;
        }
    }
    
    private float CheckSoundOcclusion(Vector3 soundPosition)
    {
        Vector3 direction = soundPosition - transform.position;
        float distance = direction.magnitude;
        
        RaycastHit[] hits = Physics.RaycastAll(
            transform.position, 
            direction.normalized, 
            distance, 
            occlusionLayerMask
        );
        
        float occlusionFactor = 1.0f;
        
        foreach (RaycastHit hit in hits)
        {
            // 检查材质的声音穿透性
            SoundOccluder occluder = hit.collider.GetComponent<SoundOccluder>();
            if (occluder != null)
            {
                occlusionFactor *= occluder.soundPenetration;
            }
            else
            {
                // 默认穿透性
                occlusionFactor *= 0.5f;
            }
        }
        
        return occlusionFactor;
    }
    
    protected override void UpdatePerceptions()
    {
        // 清理过时的声音感知
        CleanupOldPerceptions();
        
        // 将声音感知转换为通用感知数据
        currentPerceptions.Clear();
        
        foreach (var kvp in soundPerceptions)
        {
            if (kvp.Key == null || kvp.Value.GetAge() > 10.0f)
            {
                continue;
            }
            
            PerceptionData perception = new PerceptionData(
                kvp.Key,
                kvp.Value.soundPosition,
                PerceptionType.Auditory
            )
            {
                confidence = kvp.Value.confidence,
                lastPerceptionTime = kvp.Value.timestamp
            };
            
            currentPerceptions.Add(perception);
        }
    }
    
    private void CleanupOldPerceptions()
    {
        List<GameObject> toRemove = new List<GameObject>();
        
        foreach (var kvp in soundPerceptions)
        {
            if (kvp.Key == null || kvp.Value.GetAge() > 30.0f)
            {
                toRemove.Add(kvp.Key);
            }
        }
        
        foreach (GameObject key in toRemove)
        {
            soundPerceptions.Remove(key);
        }
    }
    
    private void ReactToSound(SoundPerceptionData soundData)
    {
        // 根据声音类型和音量决定反应
        switch (soundData.soundType)
        {
            case SoundType.Gunshot:
                if (soundData.volume > 0.5f)
                {
                    Debug.Log("听到枪声!进入警戒状态");
                    // 触发警戒行为
                }
                break;
                
            case SoundType.Footsteps:
                if (soundData.volume > 0.3f)
                {
                    Debug.Log("听到脚步声,提高警觉");
                    // 触发调查行为
                }
                break;
                
            case SoundType.Explosion:
                Debug.Log("听到爆炸声!寻找掩体");
                // 触发躲避行为
                break;
        }
    }
    
    public Vector3? GetLatestSoundPosition(SoundType typeFilter = SoundType.Other)
    {
        Vector3? latestPosition = null;
        float latestTime = 0;
        
        foreach (var kvp in soundPerceptions)
        {
            if (typeFilter != SoundType.Other && kvp.Value.soundType != typeFilter)
            {
                continue;
            }
            
            if (kvp.Value.timestamp > latestTime)
            {
                latestTime = kvp.Value.timestamp;
                latestPosition = kvp.Value.soundPosition;
            }
        }
        
        return latestPosition;
    }
    
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = new Color(0, 0, 1, 0.1f);
        Gizmos.DrawWireSphere(transform.position, hearingRange);
        
        // 绘制听到的声音位置
        Gizmos.color = Color.blue;
        foreach (var kvp in soundPerceptions)
        {
            if (kvp.Key == null)
            {
                continue;
            }
            
            float age = kvp.Value.GetAge();
            if (age < 5.0f)
            {
                float alpha = 1.0f - (age / 5.0f);
                Gizmos.color = new Color(0, 0, 1, alpha * 0.5f);
                Gizmos.DrawSphere(kvp.Value.soundPosition, 0.5f * kvp.Value.volume);
                
                Gizmos.color = new Color(0, 0, 1, alpha);
                Gizmos.DrawLine(transform.position, kvp.Value.soundPosition);
            }
        }
    }
}

// 声音遮挡器组件
public class SoundOccluder : MonoBehaviour
{
    [Range(0, 1)]
    public float soundPenetration = 0.5f;
    
    [SerializeField]
    private bool affectsAllSounds = true;
    
    [SerializeField]
    private AuditoryPerceptionSystem.SoundType[] affectedSoundTypes;
}
5.2.6 触觉感知系统的实现方案

触觉感知使AI角色能够感知物理接触,如碰撞、攻击命中、环境交互等。

using UnityEngine;
using System.Collections.Generic;

public class TactilePerceptionSystem : SensorBase
{
    [Header("触觉参数")]
    [SerializeField]
    private float touchMemoryDuration = 5.0f;
    
    [SerializeField]
    private bool detectCollisions = true;
    
    [SerializeField]
    private bool detectTriggers = true;
    
    [SerializeField]
    private float damageThreshold = 10.0f; // 伤害阈值,超过此值触发特殊反应
    
    private List<TactilePerceptionData> tactileMemory;
    
    [System.Serializable]
    private class TactilePerceptionData
    {
        public GameObject contactObject;
        public Vector3 contactPoint;
        public Vector3 contactNormal;
        public float contactForce;
        public float contactTime;
        public ContactType contactType;
        
        public TactilePerceptionData(
            GameObject obj, 
            Vector3 point, 
            Vector3 normal, 
            float force, 
            ContactType type
        )
        {
            contactObject = obj;
            contactPoint = point;
            contactNormal = normal;
            contactForce = force;
            contactType = type;
            contactTime = Time.time;
        }
        
        public float GetAge()
        {
            return Time.time - contactTime;
        }
    }
    
    public enum ContactType
    {
        Collision,
        Trigger,
        Damage,
        Environmental
    }
    
    protected override void Awake()
    {
        base.Awake();
        tactileMemory = new List<TactilePerceptionData>();
    }
    
    protected override void UpdatePerceptions()
    {
        // 清理过时的触觉记忆
        CleanupOldTactileMemory();
        
        // 更新当前感知
        currentPerceptions.Clear();
        
        foreach (TactilePerceptionData tactileData in tactileMemory)
        {
            if (tactileData.contactObject == null)
            {
                continue;
            }
            
            PerceptionData perception = new PerceptionData(
                tactileData.contactObject,
                tactileData.contactPoint,
                PerceptionType.Tactile
            )
            {
                confidence = Mathf.Clamp01(tactileData.contactForce / damageThreshold),
                lastPerceptionTime = tactileData.contactTime
            };
            
            currentPerceptions.Add(perception);
        }
    }
    
    private void CleanupOldTactileMemory()
    {
        tactileMemory.RemoveAll(data => 
            data.contactObject == null || 
            data.GetAge() > touchMemoryDuration
        );
    }
    
    private void OnCollisionEnter(Collision collision)
    {
        if (!detectCollisions)
        {
            return;
        }
        
        ProcessCollision(collision, ContactType.Collision);
    }
    
    private void OnTriggerEnter(Collider other)
    {
        if (!detectTriggers)
        {
            return;
        }
        
        ProcessTrigger(other, ContactType.Trigger);
    }
    
    private void ProcessCollision(Collision collision, ContactType contactType)
    {
        ContactPoint contactPoint = collision.contacts[0];
        float impactForce = collision.impulse.magnitude;
        
        TactilePerceptionData tactileData = new TactilePerceptionData(
            collision.gameObject,
            contactPoint.point,
            contactPoint.normal,
            impactForce,
            contactType
        );
        
        tactileMemory.Add(tactileData);
        
        Debug.Log($"碰撞感知:与 {collision.gameObject.name} 碰撞,力度:{impactForce:F2}");
        
        // 根据碰撞力度做出反应
        if (impactForce > damageThreshold)
        {
            ReactToDamage(collision.gameObject, impactForce, contactPoint.point);
        }
        else
        {
            ReactToTouch(collision.gameObject, impactForce);
        }
    }
    
    private void ProcessTrigger(Collider other, ContactType contactType)
    {
        // 估算接触力(对于触发器,我们使用一个默认值)
        float estimatedForce = 1.0f;
        
        TactilePerceptionData tactileData = new TactilePerceptionData(
            other.gameObject,
            other.ClosestPoint(transform.position),
            (transform.position - other.transform.position).normalized,
            estimatedForce,
            contactType
        );
        
        tactileMemory.Add(tactileData);
        
        Debug.Log($"触发感知:进入 {other.gameObject.name} 的触发区域");
        
        ReactToTrigger(other.gameObject);
    }
    
    // 外部调用的伤害感知方法
    public void RegisterDamage(
        GameObject damageSource, 
        float damageAmount, 
        Vector3 hitPoint, 
        Vector3 hitNormal
    )
    {
        TactilePerceptionData damageData = new TactilePerceptionData(
            damageSource,
            hitPoint,
            hitNormal,
            damageAmount,
            ContactType.Damage
        );
        
        tactileMemory.Add(damageData);
        
        Debug.Log($"伤害感知:受到 {damageSource.name} 的伤害,值:{damageAmount:F2}");
        
        ReactToDamage(damageSource, damageAmount, hitPoint);
    }
    
    private void ReactToTouch(GameObject touchObject, float force)
    {
        // 基础触觉反应
        // 在实际游戏中,这里可能触发动画、音效或AI状态变化
        
        string objectTag = touchObject.tag;
        
        switch (objectTag)
        {
            case "Obstacle":
                Debug.Log("碰到障碍物,尝试绕行");
                break;
                
            case "Player":
                Debug.Log("接触到玩家");
                break;
                
            default:
                Debug.Log($"接触到物体:{touchObject.name}");
                break;
        }
    }
    
    private void ReactToTrigger(GameObject triggerObject)
    {
        // 触发器反应逻辑
        // 例如:进入特定区域触发特定行为
        
        AreaTrigger areaTrigger = triggerObject.GetComponent<AreaTrigger>();
        if (areaTrigger != null)
        {
            areaTrigger.OnEntityEnter(gameObject);
        }
    }
    
    private void ReactToDamage(GameObject damageSource, float damageAmount, Vector3 hitPoint)
    {
        // 伤害反应逻辑
        // 在实际游戏中,这里可能触发受伤动画、痛苦音效、AI战斗状态等
        
        Debug.Log($"受到攻击!来源:{damageSource.name},伤害值:{damageAmount}");
        
        // 通知AI控制器
        AIStateController stateController = GetComponent<AIStateController>();
        if (stateController != null)
        {
            stateController.OnDamageTaken(damageSource, damageAmount, hitPoint);
        }
        
        // 触发伤害事件
        GameEventData damageEvent = new GameEventData
        {
            EventType = GameEventType.WeaponFired,
            EventPosition = hitPoint,
            EventSource = damageSource,
            EventIntensity = damageAmount,
            Timestamp = System.DateTime.Now
        };
        
        EnhancedEventManager.Instance.TriggerEvent(damageEvent);
    }
    
    public bool HasRecentContactWithTag(string tag, float maxAge = 2.0f)
    {
        foreach (TactilePerceptionData data in tactileMemory)
        {
            if (data.contactObject == null)
            {
                continue;
            }
            
            if (data.contactObject.CompareTag(tag) && data.GetAge() <= maxAge)
            {
                return true;
            }
        }
        
        return false;
    }
    
    public Vector3? GetLastContactPointWithTag(string tag)
    {
        float latestTime = 0;
        Vector3? latestPoint = null;
        
        foreach (TactilePerceptionData data in tactileMemory)
        {
            if (data.contactObject == null || !data.contactObject.CompareTag(tag))
            {
                continue;
            }
            
            if (data.contactTime > latestTime)
            {
                latestTime = data.contactTime;
                latestPoint = data.contactPoint;
            }
        }
        
        return latestPoint;
    }
    
    private void OnDrawGizmos()
    {
        if (!Application.isPlaying)
        {
            return;
        }
        
        // 绘制最近的触觉接触点
        Gizmos.color = Color.magenta;
        
        foreach (TactilePerceptionData data in tactileMemory)
        {
            if (data.contactObject == null || data.GetAge() > 2.0f)
            {
                continue;
            }
            
            float alpha = 1.0f - (data.GetAge() / 2.0f);
            Gizmos.color = new Color(1, 0, 1, alpha);
            
            Gizmos.DrawSphere(data.contactPoint, 0.2f * alpha);
            Gizmos.DrawLine(transform.position, data.contactPoint);
            
            // 绘制接触法线
            Gizmos.color = new Color(0, 1, 1, alpha);
            Gizmos.DrawRay(data.contactPoint, data.contactNormal);
        }
    }
}
5.2.7 记忆感知系统的构建

记忆感知使AI角色能够记住过去感知到的信息,并在这些信息的基础上做出决策。

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

public class MemoryPerceptionSystem : MonoBehaviour
{
    [System.Serializable]
    private class MemoryRecord
    {
        public GameObject memorizedObject;
        public Vector3 lastKnownPosition;
        public Quaternion lastKnownRotation;
        public float lastMemoryUpdateTime;
        public float confidence; // 记忆可信度(随时间衰减)
        public MemoryType memoryType;
        public Dictionary<string, object> additionalData;
        
        public MemoryRecord(
            GameObject obj, 
            Vector3 position, 
            MemoryType type
        )
        {
            memorizedObject = obj;
            lastKnownPosition = position;
            lastKnownRotation = obj != null ? obj.transform.rotation : Quaternion.identity;
            lastMemoryUpdateTime = Time.time;
            confidence = 1.0f;
            memoryType = type;
            additionalData = new Dictionary<string, object>();
        }
        
        public void UpdateMemory(
            Vector3 newPosition, 
            Quaternion newRotation, 
            float newConfidence
        )
        {
            lastKnownPosition = newPosition;
            lastKnownRotation = newRotation;
            lastMemoryUpdateTime = Time.time;
            confidence = Mathf.Clamp01(newConfidence);
        }
        
        public float GetMemoryAge()
        {
            return Time.time - lastMemoryUpdateTime;
        }
        
        public bool IsValid()
        {
            return memorizedObject != null && confidence > 0.1f;
        }
    }
    
    public enum MemoryType
    {
        VisualMemory,
        AuditoryMemory,
        TactileMemory,
        LocationMemory,
        EventMemory
    }
    
    [Header("记忆参数")]
    [SerializeField]
    private float defaultMemoryDecayRate = 0.1f; // 每秒衰减量
    
    [SerializeField]
    private float maxMemoryAge = 60.0f; // 最长记忆时间(秒)
    
    [SerializeField]
    private int maxMemoryRecords = 100;
    
    [Header("记忆类型特定设置")]
    [SerializeField]
    private float visualMemoryDecayRate = 0.05f;
    
    [SerializeField]
    private float auditoryMemoryDecayRate = 0.2f;
    
    [SerializeField]
    private float importantEventMemoryDecayRate = 0.01f;
    
    private Dictionary<GameObject, MemoryRecord> memoryRecords;
    private List<MemoryRecord> locationMemories; // 位置记忆(无关联对象)
    
    private void Awake()
    {
        memoryRecords = new Dictionary<GameObject, MemoryRecord>();
        locationMemories = new List<MemoryRecord>();
    }
    
    private void Update()
    {
        UpdateMemoryDecay();
        CleanupOldMemories();
    }
    
    public void RecordVisualMemory(GameObject target, Vector3 position, float confidence)
    {
        RecordMemory(target, position, MemoryType.VisualMemory, confidence);
    }
    
    public void RecordAuditoryMemory(GameObject source, Vector3 soundPosition, float confidence)
    {
        RecordMemory(source, soundPosition, MemoryType.AuditoryMemory, confidence);
    }
    
    public void RecordLocationMemory(Vector3 location, string locationName, float importance = 0.5f)
    {
        // 创建虚拟对象来表示位置
        GameObject locationObject = new GameObject($"LocationMemory_{locationName}");
        locationObject.transform.position = location;
        
        MemoryRecord memory = new MemoryRecord(
            locationObject, 
            location, 
            MemoryType.LocationMemory
        )
        {
            confidence = importance
        };
        
        memory.additionalData["LocationName"] = locationName;
        
        locationMemories.Add(memory);
        
        // 限制位置记忆数量
        if (locationMemories.Count > maxMemoryRecords / 2)
        {
            locationMemories = locationMemories
                .OrderByDescending(m => m.confidence)
                .Take(maxMemoryRecords / 2)
                .ToList();
        }
    }
    
    public void RecordEventMemory(
        string eventDescription, 
        Vector3 eventLocation, 
        float importance
    )
    {
        GameObject eventObject = new GameObject($"EventMemory_{Time.time}");
        eventObject.transform.position = eventLocation;
        
        MemoryRecord memory = new MemoryRecord(
            eventObject, 
            eventLocation, 
            MemoryType.EventMemory
        )
        {
            confidence = importance
        };
        
        memory.additionalData["EventDescription"] = eventDescription;
        memory.additionalData["Importance"] = importance;
        
        memoryRecords[eventObject] = memory;
    }
    
    private void RecordMemory(
        GameObject target, 
        Vector3 position, 
        MemoryType type, 
        float confidence
    )
    {
        if (target == null)
        {
            return;
        }
        
        if (memoryRecords.ContainsKey(target))
        {
            // 更新现有记忆
            MemoryRecord existingRecord = memoryRecords[target];
            
            // 如果新信息可信度更高,则更新记忆
            if (confidence > existingRecord.confidence * 0.8f)
            {
                existingRecord.UpdateMemory(
                    position, 
                    target.transform.rotation, 
                    confidence
                );
                existingRecord.memoryType = type;
            }
        }
        else
        {
            // 创建新记忆
            MemoryRecord newRecord = new MemoryRecord(target, position, type)
            {
                confidence = confidence
            };
            
            memoryRecords[target] = newRecord;
            
            // 限制记忆数量
            if (memoryRecords.Count > maxMemoryRecords)
            {
                RemoveLeastImportantMemories();
            }
        }
    }
    
    private void UpdateMemoryDecay()
    {
        // 更新对象记忆的衰减
        List<GameObject> keysToUpdate = new List<GameObject>(memoryRecords.Keys);
        
        foreach (GameObject key in keysToUpdate)
        {
            if (memoryRecords[key].memorizedObject == null)
            {
                continue;
            }
            
            float decayRate = GetDecayRateForMemoryType(memoryRecords[key].memoryType);
            memoryRecords[key].confidence -= decayRate * Time.deltaTime;
        }
        
        // 更新位置记忆的衰减
        for (int i = locationMemories.Count - 1; i >= 0; i--)
        {
            locationMemories[i].confidence -= defaultMemoryDecayRate * Time.deltaTime;
        }
    }
    
    private float GetDecayRateForMemoryType(MemoryType type)
    {
        switch (type)
        {
            case MemoryType.VisualMemory:
                return visualMemoryDecayRate;
                
            case MemoryType.AuditoryMemory:
                return auditoryMemoryDecayRate;
                
            case MemoryType.EventMemory:
                return importantEventMemoryDecayRate;
                
            default:
                return defaultMemoryDecayRate;
        }
    }
    
    private void CleanupOldMemories()
    {
        // 清理过时的对象记忆
        List<GameObject> keysToRemove = new List<GameObject>();
        
        foreach (var kvp in memoryRecords)
        {
            if (!kvp.Value.IsValid() || kvp.Value.GetMemoryAge() > maxMemoryAge)
            {
                keysToRemove.Add(kvp.Key);
            }
        }
        
        foreach (GameObject key in keysToRemove)
        {
            if (memoryRecords[key].memorizedObject != null && 
                memoryRecords[key].memorizedObject.name.StartsWith("EventMemory_"))
            {
                Destroy(memoryRecords[key].memorizedObject);
            }
            memoryRecords.Remove(key);
        }
        
        // 清理过时的位置记忆
        locationMemories.RemoveAll(memory => 
            !memory.IsValid() || memory.GetMemoryAge() > maxMemoryAge
        );
    }
    
    private void RemoveLeastImportantMemories()
    {
        // 按可信度排序,移除最低的
        var sortedMemories = memoryRecords
            .OrderBy(kvp => kvp.Value.confidence)
            .ThenBy(kvp => kvp.Value.GetMemoryAge())
            .ToList();
        
        int memoriesToRemove = memoryRecords.Count - maxMemoryRecords;
        
        for (int i = 0; i < memoriesToRemove; i++)
        {
            GameObject key = sortedMemories[i].Key;
            
            if (memoryRecords[key].memorizedObject != null && 
                memoryRecords[key].memorizedObject.name.StartsWith("EventMemory_"))
            {
                Destroy(memoryRecords[key].memorizedObject);
            }
            
            memoryRecords.Remove(key);
        }
    }
    
    public bool HasMemoryOfObject(GameObject obj)
    {
        return memoryRecords.ContainsKey(obj) && memoryRecords[obj].IsValid();
    }
    
    public Vector3? GetLastKnownPosition(GameObject obj)
    {
        if (HasMemoryOfObject(obj))
        {
            return memoryRecords[obj].lastKnownPosition;
        }
        
        return null;
    }
    
    public float GetMemoryConfidence(GameObject obj)
    {
        if (HasMemoryOfObject(obj))
        {
            return memoryRecords[obj].confidence;
        }
        
        return 0f;
    }
    
    public List<GameObject> GetRememberedObjectsByType(MemoryType type, float minConfidence = 0.3f)
    {
        List<GameObject> result = new List<GameObject>();
        
        foreach (var kvp in memoryRecords)
        {
            if (kvp.Value.memoryType == type && 
                kvp.Value.confidence >= minConfidence && 
                kvp.Value.IsValid())
            {
                result.Add(kvp.Key);
            }
        }
        
        return result;
    }
    
    public List<Vector3> GetRememberedLocations(float minConfidence = 0.3f)
    {
        return locationMemories
            .Where(memory => memory.confidence >= minConfidence)
            .Select(memory => memory.lastKnownPosition)
            .ToList();
    }
    
    public void StrengthenMemory(GameObject obj, float strengthAmount)
    {
        if (memoryRecords.ContainsKey(obj))
        {
            memoryRecords[obj].confidence = Mathf.Clamp01(
                memoryRecords[obj].confidence + strengthAmount
            );
            memoryRecords[obj].lastMemoryUpdateTime = Time.time;
        }
    }
    
    public void WeakenMemory(GameObject obj, float weakenAmount)
    {
        if (memoryRecords.ContainsKey(obj))
        {
            memoryRecords[obj].confidence = Mathf.Clamp01(
                memoryRecords[obj].confidence - weakenAmount
            );
        }
    }
    
    public void ClearMemoriesOfType(MemoryType type)
    {
        List<GameObject> keysToRemove = new List<GameObject>();
        
        foreach (var kvp in memoryRecords)
        {
            if (kvp.Value.memoryType == type)
            {
                keysToRemove.Add(kvp.Key);
            }
        }
        
        foreach (GameObject key in keysToRemove)
        {
            memoryRecords.Remove(key);
        }
    }
    
    public void DebugLogMemories()
    {
        Debug.Log($"=== 记忆系统状态 ===");
        Debug.Log($"对象记忆数量:{memoryRecords.Count}");
        Debug.Log($"位置记忆数量:{locationMemories.Count}");
        
        foreach (var kvp in memoryRecords)
        {
            if (kvp.Value.IsValid())
            {
                Debug.Log($"记忆对象:{kvp.Key.name},类型:{kvp.Value.memoryType}," +
                         $"可信度:{kvp.Value.confidence:F2}," +
                         $"年龄:{kvp.Value.GetMemoryAge():F1}秒");
            }
        }
    }
    
    private void OnDrawGizmosSelected()
    {
        if (!Application.isPlaying)
        {
            return;
        }
        
        // 绘制记忆位置
        Gizmos.color = new Color(1, 0.5f, 0, 0.3f); // 橙色
        
        foreach (var kvp in memoryRecords)
        {
            if (!kvp.Value.IsValid())
            {
                continue;
            }
            
            float alpha = kvp.Value.confidence * 0.5f;
            Gizmos.color = new Color(1, 0.5f, 0, alpha);
            
            Gizmos.DrawSphere(kvp.Value.lastKnownPosition, 0.3f * kvp.Value.confidence);
            
            // 绘制到当前位置的线(如果对象已移动)
            if (kvp.Value.memorizedObject != null)
            {
                Vector3 currentPosition = kvp.Value.memorizedObject.transform.position;
                if (Vector3.Distance(currentPosition, kvp.Value.lastKnownPosition) > 0.5f)
                {
                    Gizmos.DrawLine(currentPosition, kvp.Value.lastKnownPosition);
                }
            }
        }
        
        // 绘制位置记忆
        Gizmos.color = new Color(0, 1, 1, 0.3f); // 青色
        
        foreach (MemoryRecord locationMemory in locationMemories)
        {
            if (!locationMemory.IsValid())
            {
                continue;
            }
            
            float alpha = locationMemory.confidence * 0.3f;
            Gizmos.color = new Color(0, 1, 1, alpha);
            
            Gizmos.DrawWireSphere(locationMemory.lastKnownPosition, 0.5f);
            
            if (locationMemory.additionalData.ContainsKey("LocationName"))
            {
                // 在Unity编辑器中显示位置名称
                #if UNITY_EDITOR
                UnityEditor.Handles.Label(
                    locationMemory.lastKnownPosition + Vector3.up, 
                    locationMemory.additionalData["LocationName"] as string
                );
                #endif
            }
        }
    }
}
5.2.8 物品感知系统的实现

物品感知使AI角色能够感知游戏世界中的物品,如血包、弹药、宝物、钥匙等。

using UnityEngine;
using System.Collections.Generic;

public class ItemPerceptionSystem : SensorBase
{
    [System.Serializable]
    private class ItemTypeSettings
    {
        public string itemTag;
        public float detectionRange = 10.0f;
        public float importance = 1.0f;
        public bool isAlwaysDetected = false;
        public Color debugColor = Color.white;
    }
    
    [Header("物品感知设置")]
    [SerializeField]
    private ItemTypeSettings[] itemTypeSettings;
    
    [SerializeField]
    private LayerMask itemLayerMask;
    
    [SerializeField]
    private float itemScanInterval = 0.5f;
    
    [Header("记忆设置")]
    [SerializeField]
    private bool rememberItemLocations = true;
    
    [SerializeField]
    private float itemMemoryDuration = 30.0f;
    
    private Dictionary<string, ItemTypeSettings> itemSettingsLookup;
    private Dictionary<GameObject, float> itemMemory; // 物品记忆:物品 -> 最后感知时间
    
    private float lastItemScanTime;
    
    protected override void Awake()
    {
        base.Awake();
        
        itemSettingsLookup = new Dictionary<string, ItemTypeSettings>();
        itemMemory = new Dictionary<GameObject, float>();
        
        // 初始化物品设置查找表
        foreach (ItemTypeSettings settings in itemTypeSettings)
        {
            if (!string.IsNullOrEmpty(settings.itemTag))
            {
                itemSettingsLookup[settings.itemTag] = settings;
            }
        }
        
        lastItemScanTime = -itemScanInterval;
    }
    
    protected override void Update()
    {
        base.Update();
        
        // 单独的物品扫描(可以有不同的更新频率)
        if (Time.time >= lastItemScanTime + itemScanInterval)
        {
            ScanForItems();
            lastItemScanTime = Time.time;
        }
        
        // 清理过时的物品记忆
        CleanupItemMemory();
    }
    
    protected override void UpdatePerceptions()
    {
        // 物品感知数据已经通过ScanForItems方法更新到currentPerceptions
        // 这里主要处理感知数据的后处理
        
        // 按重要性排序感知
        currentPerceptions.Sort((a, b) => 
        {
            float importanceA = GetItemImportance(a.perceivedObject);
            float importanceB = GetItemImportance(b.perceivedObject);
            return importanceB.CompareTo(importanceA); // 降序排序
        });
    }
    
    private void ScanForItems()
    {
        List<PerceptionData> itemPerceptions = new List<PerceptionData>();
        
        // 扫描所有配置的物品类型
        foreach (var kvp in itemSettingsLookup)
        {
            string itemTag = kvp.Key;
            ItemTypeSettings settings = kvp.Value;
            
            // 如果设置为始终检测,跳过范围检查
            if (settings.isAlwaysDetected)
            {
                GameObject[] items = GameObject.FindGameObjectsWithTag(itemTag);
                foreach (GameObject item in items)
                {
                    AddItemPerception(itemPerceptions, item, settings);
                }
            }
            else
            {
                // 范围检测
                Collider[] hitColliders = Physics.OverlapSphere(
                    transform.position, 
                    settings.detectionRange, 
                    itemLayerMask
                );
                
                foreach (Collider collider in hitColliders)
                {
                    if (collider.CompareTag(itemTag))
                    {
                        AddItemPerception(itemPerceptions, collider.gameObject, settings);
                    }
                }
            }
        }
        
        // 更新当前感知
        currentPerceptions = itemPerceptions;
        
        // 更新物品记忆
        foreach (PerceptionData perception in itemPerceptions)
        {
            if (perception.perceivedObject != null)
            {
                itemMemory[perception.perceivedObject] = Time.time;
            }
        }
    }
    
    private void AddItemPerception(
        List<PerceptionData> perceptions, 
        GameObject item, 
        ItemTypeSettings settings
    )
    {
        // 检查遮挡
        if (CheckOcclusion(transform.position, item.transform.position, item))
        {
            return;
        }
        
        // 计算感知可信度(基于距离)
        float distance = Vector3.Distance(transform.position, item.transform.position);
        float distanceFactor = 1.0f - Mathf.Clamp01(distance / settings.detectionRange);
        float confidence = distanceFactor * settings.importance;
        
        PerceptionData perception = new PerceptionData(
            item,
            item.transform.position,
            PerceptionType.Item
        )
        {
            confidence = confidence
        };
        
        perceptions.Add(perception);
    }
    
    private float GetItemImportance(GameObject item)
    {
        if (item == null)
        {
            return 0f;
        }
        
        foreach (var kvp in itemSettingsLookup)
        {
            if (item.CompareTag(kvp.Key))
            {
                return kvp.Value.importance;
            }
        }
        
        return 0f;
    }
    
    private void CleanupItemMemory()
    {
        List<GameObject> itemsToRemove = new List<GameObject>();
        
        foreach (var kvp in itemMemory)
        {
            if (kvp.Key == null || Time.time - kvp.Value > itemMemoryDuration)
            {
                itemsToRemove.Add(kvp.Key);
            }
        }
        
        foreach (GameObject item in itemsToRemove)
        {
            itemMemory.Remove(item);
        }
    }
    
    public GameObject GetMostImportantItem()
    {
        if (currentPerceptions.Count == 0)
        {
            // 检查记忆中是否有物品
            return GetMostImportantRememberedItem();
        }
        
        // 返回当前感知中最重要的物品
        if (currentPerceptions[0].perceivedObject != null)
        {
            return currentPerceptions[0].perceivedObject;
        }
        
        return null;
    }
    
    private GameObject GetMostImportantRememberedItem()
    {
        if (!rememberItemLocations || itemMemory.Count == 0)
        {
            return null;
        }
        
        GameObject mostImportantItem = null;
        float highestImportance = 0;
        
        foreach (var kvp in itemMemory)
        {
            if (kvp.Key == null)
            {
                continue;
            }
            
            float importance = GetItemImportance(kvp.Key);
            float memoryFreshness = 1.0f - Mathf.Clamp01(
                (Time.time - kvp.Value) / itemMemoryDuration
            );
            
            float totalScore = importance * memoryFreshness;
            
            if (totalScore > highestImportance)
            {
                highestImportance = totalScore;
                mostImportantItem = kvp.Key;
            }
        }
        
        return mostImportantItem;
    }
    
    public bool HasItemInMemory(GameObject item)
    {
        return itemMemory.ContainsKey(item);
    }
    
    public void ClearItemMemory(GameObject item)
    {
        if (itemMemory.ContainsKey(item))
        {
            itemMemory.Remove(item);
        }
    }
    
    public void ClearAllItemMemory()
    {
        itemMemory.Clear();
    }
    
    public List<GameObject> GetItemsOfType(string itemTag, bool includeRemembered = true)
    {
        List<GameObject> items = new List<GameObject>();
        
        // 当前感知的物品
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject != null && 
                perception.perceivedObject.CompareTag(itemTag))
            {
                items.Add(perception.perceivedObject);
            }
        }
        
        // 记忆中的物品
        if (includeRemembered && rememberItemLocations)
        {
            foreach (var kvp in itemMemory)
            {
                if (kvp.Key != null && 
                    kvp.Key.CompareTag(itemTag) && 
                    !items.Contains(kvp.Key))
                {
                    items.Add(kvp.Key);
                }
            }
        }
        
        return items;
    }
    
    public void OnItemCollected(GameObject item)
    {
        // 物品被收集,从感知和记忆中移除
        currentPerceptions.RemoveAll(p => p.perceivedObject == item);
        
        if (itemMemory.ContainsKey(item))
        {
            itemMemory.Remove(item);
        }
        
        Debug.Log($"物品 {item.name} 已被收集,从感知系统中移除");
    }
    
    private void OnDrawGizmosSelected()
    {
        if (!Application.isPlaying)
        {
            return;
        }
        
        // 绘制不同物品类型的检测范围
        foreach (var kvp in itemSettingsLookup)
        {
            ItemTypeSettings settings = kvp.Value;
            
            if (settings.detectionRange <= 0 || settings.isAlwaysDetected)
            {
                continue;
            }
            
            Gizmos.color = settings.debugColor;
            Gizmos.DrawWireSphere(transform.position, settings.detectionRange);
        }
        
        // 绘制感知到的物品
        foreach (PerceptionData perception in currentPerceptions)
        {
            if (perception.perceivedObject == null)
            {
                continue;
            }
            
            string itemTag = perception.perceivedObject.tag;
            if (itemSettingsLookup.ContainsKey(itemTag))
            {
                Color itemColor = itemSettingsLookup[itemTag].debugColor;
                float alpha = perception.confidence * 0.8f;
                
                Gizmos.color = new Color(itemColor.r, itemColor.g, itemColor.b, alpha);
                Gizmos.DrawLine(transform.position, perception.lastKnownPosition);
                
                Gizmos.color = itemColor;
                Gizmos.DrawWireSphere(perception.lastKnownPosition, 0.3f * perception.confidence);
            }
        }
        
        // 绘制记忆中的物品
        if (rememberItemLocations)
        {
            Gizmos.color = new Color(1, 1, 0, 0.3f); // 半透明黄色
            
            foreach (var kvp in itemMemory)
            {
                if (kvp.Key == null)
                {
                    continue;
                }
                
                float memoryAge = Time.time - kvp.Value;
                float memoryFreshness = 1.0f - Mathf.Clamp01(memoryAge / itemMemoryDuration);
                
                if (memoryFreshness > 0.1f)
                {
                    float alpha = memoryFreshness * 0.3f;
                    Gizmos.color = new Color(1, 1, 0, alpha);
                    
                    Gizmos.DrawWireSphere(kvp.Key.transform.position, 0.5f * memoryFreshness);
                    
                    // 绘制虚线连接到当前位置
                    DrawDottedLine(transform.position, kvp.Key.transform.position, alpha);
                }
            }
        }
    }
    
    private void DrawDottedLine(Vector3 start, Vector3 end, float alpha)
    {
        // 简单实现虚线绘制
        int segments = 10;
        for (int i = 0; i < segments; i++)
        {
            float t1 = i / (float)segments;
            float t2 = (i + 0.5f) / (float)segments;
            
            if (t2 > 1.0f)
            {
                break;
            }
            
            Vector3 point1 = Vector3.Lerp(start, end, t1);
            Vector3 point2 = Vector3.Lerp(start, end, t2);
            
            Gizmos.DrawLine(point1, point2);
        }
    }
}

5.3 综合感知系统整合实践

5.3.1 游戏场景环境搭建

在Unity中创建一个测试场景,用于演示综合感知系统:

  1. 创建地形或平面作为地面
  2. 添加光源(Directional Light)
  3. 创建几个立方体作为障碍物,设置Layer为"Obstacle"
  4. 创建一些球体作为可收集物品,分别标记为"HealthPack"、“Ammo”、“Treasure”
  5. 设置适当的碰撞体和触发器
5.3.2 创建AI士兵角色预制体

创建一个完整的AI士兵角色,整合所有感知系统:

using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class AISoldier : MonoBehaviour
{
    [Header("移动设置")]
    [SerializeField]
    private float moveSpeed = 5.0f;
    
    [SerializeField]
    private float rotationSpeed = 10.0f;
    
    [Header("感知系统")]
    [SerializeField]
    private VisualPerceptionSystem visualPerception;
    
    [SerializeField]
    private AuditoryPerceptionSystem auditoryPerception;
    
    [SerializeField]
    private TactilePerceptionSystem tactilePerception;
    
    [SerializeField]
    private MemoryPerceptionSystem memoryPerception;
    
    [SerializeField]
    private ItemPerceptionSystem itemPerception;
    
    [Header("AI状态")]
    [SerializeField]
    private AIState currentState = AIState.Patrol;
    
    private CharacterController characterController;
    private Vector3 moveDirection;
    private Vector3 targetPosition;
    
    private GameObject currentTarget;
    private GameObject lastKnownTargetPosition;
    
    private float stateTimer;
    private float patrolPointStayTime = 3.0f;
    
    private List<Vector3> patrolPoints;
    private int currentPatrolIndex;
    
    public enum AIState
    {
        Patrol,
        Investigate,
        Combat,
        Search,
        Retreat,
        CollectItem
    }
    
    private void Awake()
    {
        characterController = GetComponent<CharacterController>();
        
        // 初始化巡逻点
        patrolPoints = new List<Vector3>();
        GeneratePatrolPoints(5, 10.0f);
        
        currentPatrolIndex = 0;
        targetPosition = patrolPoints[currentPatrolIndex];
        
        // 初始化感知系统
        if (visualPerception == null)
        {
            visualPerception = GetComponent<VisualPerceptionSystem>();
        }
        
        if (auditoryPerception == null)
        {
            auditoryPerception = GetComponent<AuditoryPerceptionSystem>();
        }
        
        if (tactilePerception == null)
        {
            tactilePerception = GetComponent<TactilePerceptionSystem>();
        }
        
        if (memoryPerception == null)
        {
            memoryPerception = GetComponent<MemoryPerceptionSystem>();
        }
        
        if (itemPerception == null)
        {
            itemPerception = GetComponent<ItemPerceptionSystem>();
        }
    }
    
    private void Start()
    {
        // 记录初始位置作为记忆
        memoryPerception.RecordLocationMemory(
            transform.position, 
            "起始点", 
            0.8f
        );
        
        // 记录巡逻点
        for (int i = 0; i < patrolPoints.Count; i++)
        {
            memoryPerception.RecordLocationMemory(
                patrolPoints[i], 
                $"巡逻点{i}", 
                0.6f
            );
        }
    }
    
    private void Update()
    {
        UpdatePerception();
        UpdateState();
        MoveAI();
        
        // 调试信息
        if (Input.GetKeyDown(KeyCode.M))
        {
            memoryPerception.DebugLogMemories();
        }
    }
    
    private void UpdatePerception()
    {
        // 检查视觉感知的威胁
        GameObject visualTarget = visualPerception.GetMostThreateningTarget();
        if (visualTarget != null && visualTarget.CompareTag("Player"))
        {
            OnPlayerSpotted(visualTarget);
        }
        
        // 检查听觉感知的最新声音
        Vector3? latestSound = auditoryPerception.GetLatestSoundPosition();
        if (latestSound.HasValue && currentState != AIState.Combat)
        {
            OnSoundHeard(latestSound.Value);
        }
        
        // 检查触觉感知的伤害
        if (tactilePerception.HasRecentContactWithTag("Bullet", 1.0f))
        {
            Vector3? lastHitPoint = tactilePerception.GetLastContactPointWithTag("Bullet");
            if (lastHitPoint.HasValue)
            {
                OnDamageTaken(lastHitPoint.Value);
            }
        }
        
        // 检查物品感知
        GameObject importantItem = itemPerception.GetMostImportantItem();
        if (importantItem != null && currentState == AIState.Patrol)
        {
            // 如果物品足够重要,去收集
            float importance = itemPerception.GetItemImportance(importantItem);
            if (importance > 0.7f)
            {
                OnItemDetected(importantItem);
            }
        }
    }
    
    private void UpdateState()
    {
        stateTimer += Time.deltaTime;
        
        switch (currentState)
        {
            case AIState.Patrol:
                UpdatePatrolState();
                break;
                
            case AIState.Investigate:
                UpdateInvestigateState();
                break;
                
            case AIState.Combat:
                UpdateCombatState();
                break;
                
            case AIState.Search:
                UpdateSearchState();
                break;
                
            case AIState.CollectItem:
                UpdateCollectItemState();
                break;
        }
    }
    
    private void UpdatePatrolState()
    {
        // 到达巡逻点
        if (Vector3.Distance(transform.position, targetPosition) < 1.0f)
        {
            if (stateTimer >= patrolPointStayTime)
            {
                // 前往下一个巡逻点
                currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Count;
                targetPosition = patrolPoints[currentPatrolIndex];
                stateTimer = 0;
                
                Debug.Log($"前往下一个巡逻点:{currentPatrolIndex}");
            }
        }
    }
    
    private void UpdateInvestigateState()
    {
        // 到达调查点
        if (Vector3.Distance(transform.position, targetPosition) < 1.0f)
        {
            // 环顾四周
            transform.Rotate(0, 45 * Time.deltaTime, 0);
            
            if (stateTimer >= 5.0f)
            {
                // 调查结束,返回巡逻
                ChangeState(AIState.Patrol);
                
                // 如果记得玩家最后位置,进入搜索状态
                if (lastKnownTargetPosition != null)
                {
                    ChangeState(AIState.Search);
                    targetPosition = lastKnownTargetPosition.transform.position;
                }
            }
        }
    }
    
    private void UpdateCombatState()
    {
        if (currentTarget == null)
        {
            // 失去目标,尝试寻找
            if (lastKnownTargetPosition != null)
            {
                ChangeState(AIState.Search);
                targetPosition = lastKnownTargetPosition.transform.position;
            }
            else
            {
                ChangeState(AIState.Patrol);
            }
            return;
        }
        
        // 面向目标
        Vector3 directionToTarget = currentTarget.transform.position - transform.position;
        directionToTarget.y = 0;
        
        if (directionToTarget.magnitude > 0.1f)
        {
            Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
            transform.rotation = Quaternion.Slerp(
                transform.rotation, 
                targetRotation, 
                rotationSpeed * Time.deltaTime
            );
        }
        
        // 战斗逻辑:保持距离
        float distanceToTarget = Vector3.Distance(
            transform.position, 
            currentTarget.transform.position
        );
        
        if (distanceToTarget < 5.0f)
        {
            // 太近,后退
            moveDirection = -transform.forward;
        }
        else if (distanceToTarget > 10.0f)
        {
            // 太远,接近
            targetPosition = currentTarget.transform.position;
        }
        else
        {
            // 合适距离,左右移动
            moveDirection = Quaternion.Euler(0, 90, 0) * transform.forward * Mathf.Sin(Time.time);
        }
        
        // 模拟射击
        if (stateTimer >= 1.0f)
        {
            Debug.Log("开火!");
            stateTimer = 0;
        }
    }
    
    private void UpdateSearchState()
    {
        if (Vector3.Distance(transform.position, targetPosition) < 1.0f)
        {
            // 到达最后已知位置,环顾四周
            transform.Rotate(0, 60 * Time.deltaTime, 0);
            
            if (stateTimer >= 8.0f)
            {
                // 搜索结束,返回巡逻
                ChangeState(AIState.Patrol);
            }
        }
    }
    
    private void UpdateCollectItemState()
    {
        if (currentTarget == null)
        {
            ChangeState(AIState.Patrol);
            return;
        }
        
        targetPosition = currentTarget.transform.position;
        
        if (Vector3.Distance(transform.position, targetPosition) < 1.5f)
        {
            // 收集物品
            Debug.Log($"收集物品:{currentTarget.name}");
            
            // 通知物品感知系统
            itemPerception.OnItemCollected(currentTarget);
            
            // 销毁物品(在实际游戏中可能是禁用或播放动画)
            Destroy(currentTarget);
            
            // 返回巡逻
            ChangeState(AIState.Patrol);
        }
    }
    
    private void MoveAI()
    {
        if (currentState == AIState.Combat && currentTarget != null)
        {
            // 战斗状态的特殊移动
            characterController.Move(moveDirection * moveSpeed * Time.deltaTime);
        }
        else
        {
            // 其他状态的移动:前往目标位置
            Vector3 direction = (targetPosition - transform.position).normalized;
            direction.y = 0;
            
            if (direction.magnitude > 0.1f)
            {
                // 旋转面向移动方向
                Quaternion targetRotation = Quaternion.LookRotation(direction);
                transform.rotation = Quaternion.Slerp(
                    transform.rotation, 
                    targetRotation, 
                    rotationSpeed * Time.deltaTime
                );
                
                // 移动
                characterController.Move(direction * moveSpeed * Time.deltaTime);
            }
        }
        
        // 重力
        if (!characterController.isGrounded)
        {
            characterController.Move(Vector3.down * 9.81f * Time.deltaTime);
        }
    }
    
    private void GeneratePatrolPoints(int count, float radius)
    {
        patrolPoints.Clear();
        
        for (int i = 0; i < count; i++)
        {
            float angle = i * (360f / count);
            Vector3 point = transform.position + new Vector3(
                Mathf.Cos(angle * Mathf.Deg2Rad) * radius,
                0,
                Mathf.Sin(angle * Mathf.Deg2Rad) * radius
            );
            
            patrolPoints.Add(point);
        }
    }
    
    private void ChangeState(AIState newState)
    {
        Debug.Log($"AI状态改变:{currentState} -> {newState}");
        
        currentState = newState;
        stateTimer = 0;
        
        // 状态转换时的清理
        switch (newState)
        {
            case AIState.Patrol:
                currentTarget = null;
                currentPatrolIndex = 0;
                targetPosition = patrolPoints[currentPatrolIndex];
                break;
                
            case AIState.CollectItem:
                // 已经设置好currentTarget
                break;
        }
    }
    
    private void OnPlayerSpotted(GameObject player)
    {
        Debug.Log($"发现玩家:{player.name}");
        
        currentTarget = player;
        
        // 记录玩家位置
        if (lastKnownTargetPosition == null)
        {
            lastKnownTargetPosition = new GameObject("LastKnownPlayerPosition");
        }
        lastKnownTargetPosition.transform.position = player.transform.position;
        
        // 强化玩家记忆
        memoryPerception.StrengthenMemory(player, 0.5f);
        
        // 记录事件
        memoryPerception.RecordEventMemory(
            "发现玩家", 
            player.transform.position, 
            0.9f
        );
        
        // 进入战斗状态
        if (currentState != AIState.Combat)
        {
            ChangeState(AIState.Combat);
        }
    }
    
    private void OnSoundHeard(Vector3 soundPosition)
    {
        Debug.Log($"听到声音,前往调查:{soundPosition}");
        
        targetPosition = soundPosition;
        
        // 记录声音位置
        memoryPerception.RecordLocationMemory(
            soundPosition, 
            "可疑声音", 
            0.7f
        );
        
        if (currentState == AIState.Patrol)
        {
            ChangeState(AIState.Investigate);
        }
    }
    
    private void OnDamageTaken(Vector3 hitPoint)
    {
        Debug.Log($"受到攻击,攻击来自:{hitPoint}");
        
        // 记录伤害事件
        memoryPerception.RecordEventMemory(
            "受到攻击", 
            hitPoint, 
            1.0f
        );
        
        // 如果不在战斗状态,进入调查状态
        if (currentState != AIState.Combat)
        {
            targetPosition = hitPoint;
            ChangeState(AIState.Investigate);
        }
    }
    
    private void OnItemDetected(GameObject item)
    {
        Debug.Log($"发现重要物品:{item.name}");
        
        currentTarget = item;
        ChangeState(AIState.CollectItem);
    }
    
    public void OnDamageTakenExternal(GameObject damageSource, float damageAmount, Vector3 hitPoint)
    {
        // 供外部调用的伤害处理方法
        tactilePerception.RegisterDamage(damageSource, damageAmount, hitPoint, Vector3.up);
        
        if (damageSource.CompareTag("Player"))
        {
            OnPlayerSpotted(damageSource);
        }
        else
        {
            OnDamageTaken(hitPoint);
        }
    }
    
    private void OnDrawGizmos()
    {
        if (!Application.isPlaying)
        {
            return;
        }
        
        // 绘制巡逻点
        Gizmos.color = Color.green;
        for (int i = 0; i < patrolPoints.Count; i++)
        {
            Gizmos.DrawSphere(patrolPoints[i], 0.3f);
            
            int nextIndex = (i + 1) % patrolPoints.Count;
            Gizmos.DrawLine(patrolPoints[i], patrolPoints[nextIndex]);
            
            // 绘制到当前巡逻点的线
            if (i == currentPatrolIndex && currentState == AIState.Patrol)
            {
                Gizmos.color = Color.yellow;
                Gizmos.DrawLine(transform.position, patrolPoints[i]);
                Gizmos.color = Color.green;
            }
        }
        
        // 绘制目标位置
        if (currentState != AIState.Patrol)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireSphere(targetPosition, 0.5f);
            Gizmos.DrawLine(transform.position, targetPosition);
        }
        
        // 绘制状态标签
        #if UNITY_EDITOR
        UnityEditor.Handles.Label(
            transform.position + Vector3.up * 2, 
            $"状态: {currentState}"
        );
        
        if (currentTarget != null)
        {
            UnityEditor.Handles.Label(
                transform.position + Vector3.up * 1.5f, 
                $"目标: {currentTarget.name}"
            );
        }
        #endif
    }
}
5.3.3 玩家角色创建与交互

创建玩家角色,使其能够与AI系统交互:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 8.0f;
    
    [SerializeField]
    private float mouseSensitivity = 2.0f;
    
    [SerializeField]
    private GameObject bulletPrefab;
    
    [SerializeField]
    private Transform bulletSpawnPoint;
    
    [SerializeField]
    private float bulletSpeed = 30.0f;
    
    private CharacterController characterController;
    private float verticalRotation = 0;
    private float verticalVelocity = 0;
    
    private void Awake()
    {
        characterController = GetComponent<CharacterController>();
        Cursor.lockState = CursorLockMode.Locked;
    }
    
    private void Update()
    {
        HandleMovement();
        HandleRotation();
        HandleActions();
        
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Cursor.lockState = CursorLockMode.None;
        }
    }
    
    private void HandleMovement()
    {
        // 键盘输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        Vector3 move = transform.right * horizontal + transform.forward * vertical;
        move = move.normalized * moveSpeed;
        
        // 重力
        if (characterController.isGrounded)
        {
            verticalVelocity = -1.0f;
        }
        else
        {
            verticalVelocity += Physics.gravity.y * Time.deltaTime;
        }
        
        move.y = verticalVelocity;
        
        // 移动
        characterController.Move(move * Time.deltaTime);
        
        // 触发移动事件(供AI听觉感知)
        if ((Mathf.Abs(horizontal) > 0.1f || Mathf.Abs(vertical) > 0.1f) && characterController.isGrounded)
        {
            TriggerFootstepEvent();
        }
    }
    
    private void HandleRotation()
    {
        // 鼠标输入
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
        
        // 水平旋转
        transform.Rotate(0, mouseX, 0);
        
        // 垂直旋转(摄像机)
        verticalRotation -= mouseY;
        verticalRotation = Mathf.Clamp(verticalRotation, -90, 90);
        
        Camera.main.transform.localRotation = Quaternion.Euler(verticalRotation, 0, 0);
    }
    
    private void HandleActions()
    {
        // 开火
        if (Input.GetMouseButtonDown(0))
        {
            FireWeapon();
        }
        
        // 制造声音(测试用)
        if (Input.GetKeyDown(KeyCode.Space))
        {
            MakeNoise();
        }
    }
    
    private void FireWeapon()
    {
        if (bulletPrefab == null || bulletSpawnPoint == null)
        {
            return;
        }
        
        // 创建子弹
        GameObject bullet = Instantiate(
            bulletPrefab, 
            bulletSpawnPoint.position, 
            bulletSpawnPoint.rotation
        );
        
        Rigidbody rb = bullet.GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.velocity = bulletSpawnPoint.forward * bulletSpeed;
        }
        
        // 触发武器开火事件
        GameEventData gunshotEvent = new GameEventData
        {
            EventType = GameEventType.WeaponFired,
            EventPosition = bulletSpawnPoint.position,
            EventSource = gameObject,
            EventIntensity = 1.0f,
            Timestamp = System.DateTime.Now
        };
        
        EnhancedEventManager.Instance.TriggerEvent(gunshotEvent);
        
        Debug.Log("玩家开火");
    }
    
    private void TriggerFootstepEvent()
    {
        // 定期触发脚步声事件
        if (Time.frameCount % 30 == 0) // 每30帧触发一次
        {
            GameEventData footstepEvent = new GameEventData
            {
                EventType = GameEventType.PlayerMoved,
                EventPosition = transform.position,
                EventSource = gameObject,
                EventIntensity = 0.3f,
                Timestamp = System.DateTime.Now
            };
            
            EnhancedEventManager.Instance.TriggerEvent(footstepEvent);
        }
    }
    
    private void MakeNoise()
    {
        Debug.Log("玩家制造噪音");
        
        GameEventData noiseEvent = new GameEventData
        {
            EventType = GameEventType.WeaponFired,
            EventPosition = transform.position,
            EventSource = gameObject,
            EventIntensity = 0.5f,
            Timestamp = System.DateTime.Now
        };
        
        EnhancedEventManager.Instance.TriggerEvent(noiseEvent);
    }
    
    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        // 玩家碰撞触发事件
        if (hit.gameObject.CompareTag("Door"))
        {
            GameEventData doorEvent = new GameEventData
            {
                EventType = GameEventType.DoorOpened,
                EventPosition = hit.point,
                EventSource = gameObject,
                EventIntensity = 0.4f,
                Timestamp = System.DateTime.Now
            };
            
            EnhancedEventManager.Instance.TriggerEvent(doorEvent);
        }
    }
}
5.3.4 感知范围可视化与调试系统

创建一个调试系统,用于可视化AI的感知范围和信息:

using UnityEngine;

public class AIDebugVisualizer : MonoBehaviour
{
    [SerializeField]
    private AISoldier aiSoldier;
    
    [SerializeField]
    private bool showVisualRange = true;
    
    [SerializeField]
    private bool showAuditoryRange = true;
    
    [SerializeField]
    private bool showMemoryMarkers = true;
    
    [SerializeField]
    private bool showItemDetectionRanges = false;
    
    private void OnDrawGizmos()
    {
        if (aiSoldier == null || !Application.isPlaying)
        {
            return;
        }
        
        // 获取感知系统组件
        VisualPerceptionSystem visualPerception = 
            aiSoldier.GetComponent<VisualPerceptionSystem>();
        AuditoryPerceptionSystem auditoryPerception = 
            aiSoldier.GetComponent<AuditoryPerceptionSystem>();
        MemoryPerceptionSystem memoryPerception = 
            aiSoldier.GetComponent<MemoryPerceptionSystem>();
        ItemPerceptionSystem itemPerception = 
            aiSoldier.GetComponent<ItemPerceptionSystem>();
        
        if (showVisualRange && visualPerception != null)
        {
            DrawVisualPerception(visualPerception);
        }
        
        if (showAuditoryRange && auditoryPerception != null)
        {
            DrawAuditoryPerception(auditoryPerception);
        }
        
        if (showMemoryMarkers && memoryPerception != null)
        {
            DrawMemoryMarkers(memoryPerception);
        }
        
        if (showItemDetectionRanges && itemPerception != null)
        {
            DrawItemDetectionRanges(itemPerception);
        }
        
        // 绘制AI当前状态
        DrawAIState(aiSoldier);
    }
    
    private void DrawVisualPerception(VisualPerceptionSystem visualPerception)
    {
        // 使用反射获取私有字段(在生产环境中应该提供公共方法)
        #if UNITY_EDITOR
        System.Reflection.FieldInfo sightRangeField = 
            typeof(VisualPerceptionSystem).GetField(
                "sightRange", 
                System.Reflection.BindingFlags.NonPublic | 
                System.Reflection.BindingFlags.Instance
            );
        
        System.Reflection.FieldInfo fieldOfViewField = 
            typeof(VisualPerceptionSystem).GetField(
                "fieldOfView", 
                System.Reflection.BindingFlags.NonPublic | 
                System.Reflection.BindingFlags.Instance
            );
        
        if (sightRangeField != null && fieldOfViewField != null)
        {
            float sightRange = (float)sightRangeField.GetValue(visualPerception);
            float fieldOfView = (float)fieldOfViewField.GetValue(visualPerception);
            
            // 绘制视锥
            DrawVisionCone(visualPerception.transform, sightRange, fieldOfView, Color.green);
        }
        #endif
    }
    
    private void DrawVisionCone(Transform observer, float range, float fov, Color color)
    {
        int segments = 20;
        float stepAngle = fov / segments;
        float halfFov = fov / 2;
        
        Vector3 forward = observer.forward * range;
        Vector3 lastPoint = observer.position;
        
        Gizmos.color = new Color(color.r, color.g, color.b, 0.1f);
        
        // 绘制视锥扇形
        for (int i = 0; i <= segments; i++)
        {
            float angle = -halfFov + (stepAngle * i);
            Vector3 direction = Quaternion.Euler(0, angle, 0) * forward;
            Vector3 point = observer.position + direction;
            
            if (i > 0)
            {
                Gizmos.DrawLine(observer.position, point);
            }
            
            if (i > 0 && i < segments)
            {
                Gizmos.DrawLine(lastPoint, point);
            }
            
            lastPoint = point;
        }
        
        // 绘制中心线
        Gizmos.color = new Color(color.r, color.g, color.b, 0.3f);
        Gizmos.DrawRay(observer.position, forward);
    }
    
    private void DrawAuditoryPerception(AuditoryPerceptionSystem auditoryPerception)
    {
        #if UNITY_EDITOR
        System.Reflection.FieldInfo hearingRangeField = 
            typeof(AuditoryPerceptionSystem).GetField(
                "hearingRange", 
                System.Reflection.BindingFlags.NonPublic | 
                System.Reflection.BindingFlags.Instance
            );
        
        if (hearingRangeField != null)
        {
            float hearingRange = (float)hearingRangeField.GetValue(auditoryPerception);
            
            Gizmos.color = new Color(0, 0, 1, 0.05f);
            Gizmos.DrawSphere(auditoryPerception.transform.position, hearingRange);
            
            Gizmos.color = new Color(0, 0, 1, 0.2f);
            Gizmos.DrawWireSphere(auditoryPerception.transform.position, hearingRange);
        }
        #endif
    }
    
    private void DrawMemoryMarkers(MemoryPerceptionSystem memoryPerception)
    {
        // 在实际实现中,这里应该调用MemoryPerceptionSystem的公共方法
        // 获取记忆的位置并绘制标记
        
        Gizmos.color = new Color(1, 0.5f, 0, 0.3f);
        
        // 这里只是示例,实际应该从memoryPerception获取数据
        // 绘制一些测试标记
        for (int i = 0; i < 5; i++)
        {
            Vector3 memoryPosition = memoryPerception.transform.position + 
                new Vector3(
                    Mathf.Cos(i * 72 * Mathf.Deg2Rad) * 5, 
                    0, 
                    Mathf.Sin(i * 72 * Mathf.Deg2Rad) * 5
                );
            
            Gizmos.DrawSphere(memoryPosition, 0.3f);
        }
    }
    
    private void DrawItemDetectionRanges(ItemPerceptionSystem itemPerception)
    {
        // 类似地,绘制物品检测范围
        Gizmos.color = new Color(1, 1, 0, 0.1f);
        Gizmos.DrawWireSphere(itemPerception.transform.position, 10.0f);
    }
    
    private void DrawAIState(AISoldier ai)
    {
        #if UNITY_EDITOR
        // 在AI上方绘制状态信息
        UnityEditor.Handles.Label(
            ai.transform.position + Vector3.up * 3, 
            $"状态: {ai.CurrentState}\n" +
            $"目标: {(ai.CurrentTarget != null ? ai.CurrentTarget.name : "无")}"
        );
        #endif
    }
}

5.4 游戏运行结果与分析

5.4.1 测试场景运行结果

当运行包含上述系统的Unity场景时,可以观察到以下行为:

  1. AI士兵的巡逻行为

    • AI士兵按照预设的巡逻点循环移动
    • 在每个巡逻点停留几秒,模拟观察行为
    • 巡逻路径在场景中可视化显示
  2. 视觉感知效果

    • 当玩家进入AI的视野范围时,AI立即发现玩家
    • 视野范围受障碍物遮挡影响
    • AI会面向玩家并进入战斗状态
  3. 听觉感知效果

    • 玩家开火或制造噪音时,AI会听到声音
    • AI会前往声音来源位置进行调查
    • 声音强度随距离衰减
  4. 触觉感知效果

    • 当玩家子弹击中AI时,AI会感知到伤害
    • AI会记录伤害来源并做出相应反应
  5. 物品感知效果

    • AI会检测到场景中的血包、弹药等物品
    • 根据物品重要性,AI可能中断当前行为去收集物品
  6. 记忆系统效果

    • AI记住玩家的最后已知位置
    • 即使玩家离开视野,AI仍会前往最后已知位置搜索
    • AI记住重要事件和位置
5.4.2 性能优化建议

在实际游戏项目中,感知系统可能涉及大量计算,以下是一些优化建议:

  1. 分层更新

    • 不是所有AI都需要每帧更新感知
    • 根据AI的重要性或距离玩家远近,设置不同的更新频率
  2. 空间分区

    • 使用四叉树、八叉树或网格系统管理场景中的对象
    • 只检测附近分区中的对象,减少检测数量
  3. 异步处理

    • 将昂贵的感知计算(如光线投射)放在单独的线程或协程中
    • 使用Job System和Burst Compiler进行高性能计算
  4. 细节层次(LOD)

    • 根据AI与玩家的距离,使用不同精度的感知
    • 远处的AI使用简化的感知逻辑
  5. 事件聚合

    • 合并短时间内发生的多个同类事件
    • 避免重复处理相似的事件
5.4.3 商业项目应用扩展

在实际商业项目中,感知系统可以进一步扩展:

  1. 团队感知共享

    • AI队友之间共享感知信息
    • 实现团队协作和战术配合
  2. 环境互动感知

    • AI感知环境变化(如灯光、天气、时间)
    • 根据环境调整行为模式
  3. 学习与适应

    • AI学习玩家的行为模式
    • 根据历史互动调整感知策略
  4. 情绪影响感知

    • AI的情绪状态影响感知能力
    • 愤怒时可能扩大攻击范围,恐惧时可能缩小感知范围
  5. 多感官融合

    • 整合多种感官信息,形成更完整的环境认知
    • 不同感官信息相互验证,提高感知可靠性

通过本章介绍的综合感知系统,开发者可以在Unity中创建出具有高度真实感和沉浸感的AI角色。这些系统不仅适用于第一人称射击游戏,还可以通过调整参数和逻辑,应用于潜行游戏、角色扮演游戏、实时战略游戏等多种游戏类型。关键是理解每种感知机制的原理,并根据具体游戏需求进行适当的调整和扩展。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐