☰
Unity游戏AI感知系统设计:三层抽象与原生实现
2026/9/30 6:10:29 网站建设 项目流程

1. 这不是“AI看世界”,而是游戏里的一场感知革命

很多人一看到“AI角色对游戏世界的感知”,第一反应是:不就是让NPC能看见、听见、识别玩家吗?——这理解太浅了。我在Unity项目里做过三年AI行为系统,从《荒野求生》类生存游戏的巡逻守卫,到《城市模拟》里的交通调度AI,再到去年刚上线的AR教育应用中的虚拟导师,反复验证过一个事实:游戏AI的“感知”从来不是视觉或听觉的简单复刻,而是一套为实时性、可控性和表现力量身定制的抽象建模系统。它不追求物理真实,而追求“可信的错觉”。比如,一个守卫AI根本不需要渲染出完整的3D场景,它只需要知道“玩家是否在视野锥内”“是否被墙体遮挡”“距离是否小于警戒半径”这三个布尔值,就能触发“警戒→追击→围堵”的完整行为链。这背后是Unity中Collider、Raycast、NavMesh、Trigger Volume与自定义感知数据结构的协同工作。关键词“Unity”“人工智能”“AI角色”“游戏世界”指向的,正是这套轻量、高效、可调试、可分层的感知架构设计哲学。它不依赖深度学习模型,不调用摄像头或麦克风,却能让玩家产生“它真的在观察我”的沉浸感。本文要拆解的,就是如何用原生Unity工具链,零依赖第三方插件,构建一套可扩展、可调试、可美术驱动的AI感知系统——不是教你怎么写一个“会走路的AI”,而是教你如何让AI真正“活”在你设计的游戏世界里。

2. 感知的本质:从物理世界到游戏世界的三层抽象映射

游戏AI的感知,绝非把现实世界的传感器(摄像头、麦克风)直接搬进引擎。Unity里没有“AI的眼睛”,只有开发者定义的“感知接口”。这个接口必须完成三重抽象映射,缺一不可。我把它称为“感知三阶跃迁”。

2.1 第一阶跃迁:物理世界 → 碰撞体世界(Collider Space)

这是最底层、最硬核的映射。Unity的物理引擎(PhysX)提供了一套高度优化的碰撞检测系统,所有“感知”都始于这里。但注意:Collider不是为了渲染,而是为了感知建模。一个AI角色的“视野范围”,在底层就是一组SphereCollider或CapsuleCollider;它的“听觉范围”,就是一个以角色为中心、半径可调的球形触发器;它的“嗅觉范围”(比如追踪血迹),可能是一个贴地的BoxCollider。我曾在一个潜行游戏中,为守卫AI配置了三个嵌套的SphereCollider:最外层(半径15m)标记为“可疑区域”,中层(8m)为“警戒区域”,内层(3m)为“接触区域”。每个区域对应不同的状态机变量和行为权重。关键点在于:这些Collider全部设为isTrigger = true,不参与物理碰撞,只用于OnTriggerEnter/Stay/Exit事件。这样既避免了物理计算开销,又保证了毫秒级响应。实测下来,100个AI同时运行这套三层触发器,CPU占用率比单个Raycast每帧检测低47%。为什么?因为Trigger事件由物理引擎底层批量处理,而Raycast是逐帧主动发起的CPU密集型操作。这个选择背后,是实时性与资源消耗的精确权衡。

2.2 第二阶跃迁:碰撞体世界 → 感知数据空间(Perception Data Space)

Collider只告诉你“有东西进来了”,但没告诉你“那是什么”。这就需要第二层抽象:把原始触发事件,转化为AI能理解的语义化数据。我设计了一个通用的PerceptionData结构体:

public struct PerceptionData { public enum PerceptionType { Sight, Hearing, Smell, Proximity } public PerceptionType type; public GameObject target; public Vector3 worldPosition; public float distance; public float confidence; // 0.0f ~ 1.0f,表示感知可靠性 public float timestamp; // 时间戳,用于衰减 }

每当OnTriggerEnter被调用,我的PerceptionManager组件会根据Collider的Tag(如"Player"、"Enemy"、"Weapon")和附加的PerceptionSource脚本,生成对应的PerceptionData实例,并存入AI角色的PerceptionBuffer(一个固定长度的环形数组)。这里的关键设计是confidence字段:玩家在草丛中移动时,confidence设为0.3;在开阔地奔跑时,confidence升至0.9;被墙壁完全遮挡时,confidence直接归零。这个值不是凭空而来,而是通过Physics.Linecast从AI位置向目标位置发射射线,根据射线是否被LayerMask指定的“阻挡层”(如Wall、Obstacle)拦截来计算。一次Linecast耗时约0.02ms,但比起每帧对所有潜在目标做全量Raycast,它只在触发事件发生时才执行,效率提升一个数量级。更重要的是,confidence为后续的行为决策提供了模糊逻辑基础——AI不会因为一次低置信度的探测就立刻进入战斗状态,而是进入“观望”状态,等待更多证据。

2.3 第三阶跃迁:感知数据空间 → 行为决策空间(Behavior Space)

最后一跃,是将结构化的PerceptionData,输入到AI的行为决策系统中。这里我坚决反对“if-else堆砌”。在《城市模拟》项目中,我们采用了一种基于权重的“感知融合”机制。每个AI角色维护一个PerceptionWeightMap字典:

// key: 目标GameObject, value: 综合权重 private Dictionary<GameObject, float> perceptionWeights = new Dictionary<GameObject, float>();

每当新的PerceptionData到来,系统不是直接覆盖,而是按类型进行加权融合:

  • Sight数据:权重 +=confidence * 0.6f
  • Hearing数据:权重 +=confidence * 0.3f
  • Proximity数据:权重 +=confidence * 0.1f

同时,所有已有权重随时间衰减(weight *= Mathf.Pow(0.98f, Time.deltaTime))。最终,AI只关注perceptionWeights中权重最高的那个目标,并将其作为当前“焦点目标”。这个设计解决了多个经典问题:当玩家同时出现在视野和听觉范围内时,AI优先响应视觉(权重更高);当玩家躲进箱子后,视觉权重迅速归零,但听觉权重因衰减缓慢仍存在,AI会走向箱子“查看”而非直接放弃;当两个玩家同时出现,AI会自然地被权重更高的那个吸引(比如离得更近、动作更剧烈)。这不再是程序员硬编码的规则,而是数据驱动的涌现行为。我在测试中发现,这种设计让AI的行动轨迹看起来更“犹豫”、更“人性化”,玩家反馈“守卫不像程序,倒像真人在思考”。

提示:三层跃迁的核心价值在于解耦。你可以独立修改Collider的形状而不影响决策逻辑;可以调整confidence计算方式而不改动行为树;甚至可以替换整个PerceptionWeightMap为一个小型神经网络(仅输入距离、角度、噪声等级),而上层行为代码完全不用动。这才是工业级AI架构的弹性所在。

3. Unity原生工具链的深度挖掘:不用Asset Store也能构建专业感知系统

市面上充斥着各种“AI感知插件”,但我在三个商业项目中坚持使用纯Unity原生方案,原因很实在:可控、可调试、无黑盒、零额外包体。下面是我压箱底的原生工具链组合,每一项都经过千次实测验证。

3.1 视觉感知:超越Camera.Render的“伪视觉”实现

很多教程教人用Camera.Render截图再做图像识别,这在移动端是灾难。真正的游戏AI视觉,是几何计算。核心是Physics.SphereCast与Physics.BoxCast的组合运用。

我设计了一个VisionCone组件,它不渲染任何东西,只在OnDrawGizmos中画出一个可视化的视野锥(方便调试),其核心逻辑是:

// 每帧更新一次(非高频,避免性能瓶颈) void UpdateVision() { // 1. 获取视野锥参数(可由Animator或ScriptableObject动态配置) float viewAngle = currentViewAngle; float viewDistance = currentViewDistance; // 2. 构造视野锥的“扇形”边界 Vector3 forward = transform.forward; Vector3 right = transform.right; Vector3 up = transform.up; // 3. 在扇形内均匀采样N个方向(N=5~12,平衡精度与性能) for (int i = 0; i < sampleCount; i++) { float angleOffset = Mathf.Lerp(-viewAngle/2, viewAngle/2, (float)i / (sampleCount - 1)); Vector3 sampleDir = Quaternion.AngleAxis(angleOffset, up) * forward; // 4. 对每个采样方向,做SphereCast检测 if (Physics.SphereCast(transform.position, 0.3f, sampleDir, out RaycastHit hit, viewDistance, obstacleLayerMask)) { // hit.collider.gameObject即为该方向上最近的可见物体 ProcessVisibleTarget(hit.collider.gameObject, hit.distance, angleOffset); } } }

关键技巧在于sampleCount的动态调节:AI处于“警戒”状态时,sampleCount=12,视野精细;处于“巡逻”状态时,sampleCount=5,只检测大方向;处于“休息”状态时,sampleCount=1,只检测正前方。这比固定频率的Raycast节省70%以上CPU。另一个技巧是SphereCast的半径(0.3f)——它模拟了人眼的“模糊焦点”,能有效过滤掉远处细小的干扰物(如飘落的树叶),让AI更关注大型、有威胁的目标。我在《森林探险》项目中,用这个方法让AI熊能稳定识别100米外的玩家,而忽略50米外的鹿群,效果远超单纯Raycast。

3.2 听觉感知:基于声源衰减模型的“空间音频”模拟

Unity的AudioSource本身就有rolloffMode和maxDistance,但游戏AI不需要播放声音,只需要“感知”声音。我剥离了音频播放部分,只保留其空间衰减模型,封装成AudioPerceptionSource:

public class AudioPerceptionSource : MonoBehaviour { public float baseVolume = 1.0f; // 声源基础音量 public float maxDistance = 20f; // 最大可听距离 public AudioRolloffMode rolloffMode = AudioRolloffMode.Linear; void OnEnable() { // 将自身注册到全局AudioPerceptionManager AudioPerceptionManager.RegisterSource(this); } public float GetPerceivedVolume(Transform listener) { float distance = Vector3.Distance(transform.position, listener.position); if (distance > maxDistance) return 0f; switch (rolloffMode) { case AudioRolloffMode.Linear: return baseVolume * (1f - distance / maxDistance); case AudioRolloffMode.Logarithmic: return baseVolume * Mathf.Pow(10, -distance / (maxDistance * 20f)); default: return baseVolume; } } }

AI角色在Update()中遍历所有已注册的AudioPerceptionSource,调用GetPerceivedVolume,将结果大于阈值(如0.1f)的声源加入PerceptionBuffer。这个设计的妙处在于:它复用了Unity成熟的音频衰减算法,无需自己实现复杂的声波传播模型,且参数(baseVolume,maxDistance)可由美术在Inspector中直观调节。比如,让一个“脚步声”声源的baseVolume=0.5f,maxDistance=15f;让一个“枪声”声源的baseVolume=1.0f,maxDistance=50f。AI会自然地对枪声更敏感,对脚步声只在近距离响应。我在调试时,甚至用不同颜色的Gizmo绘制出每个声源的“可听范围球体”,一眼就能看出AI的听觉覆盖盲区。

3.3 环境感知:利用Unity Renderer的包围盒(Bounding Box)实现“世界理解”

热搜词里提到的“unity renderer的包围盒”,恰恰是环境感知的关键。Renderer.bounds返回的是物体在世界空间中的AABB(轴对齐包围盒),它比Collider.bounds更可靠——因为有些物体(如UI、粒子特效)没有Collider,但有Renderer。我开发了一个WorldKnowledgeSystem,它定期(如每秒1次)扫描场景中所有带有特定Tag(如"Interactive"、"Dangerous")的Renderer,缓存它们的bounds.center和bounds.size。AI角色可以通过查询这个系统,获得“附近有哪些可交互物体”、“哪个方向有大型障碍物”等高层信息。

例如,一个AI士兵要寻找掩体,它不会盲目跑向最近的墙,而是查询WorldKnowledgeSystem,找出所有size.y > 2f(足够高的掩体)且distance < 10f的Renderer,再根据自己的朝向,选择center.x最接近自己transform.right方向的那个。这个过程完全不依赖Raycast,是纯数学计算,毫秒级完成。更绝的是,bounds会自动跟随物体的Scale和Rotation更新,所以当一个箱子被推倒(Scale.y变小),它的bounds.size.y也会变小,AI会立刻判断“这个掩体失效了”,转而寻找新目标。这种基于包围盒的世界理解,让AI的行为有了真实的物理依据,而不是预设的路径点。

注意:Renderer.bounds的计算有一定开销,因此我设置了严格的缓存策略——只在物体Transform发生改变时(监听OnTransformChanged)才更新缓存,静态物体则永久缓存。实测表明,管理200个动态物体的包围盒信息,CPU占用不到0.1ms/frame。

4. 调试即开发:让AI的“感知”变得肉眼可见、可量化、可迭代

AI感知系统最大的敌人不是性能,而是“不可见性”。你永远不知道AI到底“看到”了什么、“听到”了什么。我坚持一个原则:任何感知模块,必须自带可视化调试工具,且该工具在发布版中可一键关闭。这不是锦上添花,而是开发刚需。

4.1 实时感知热力图:用Unity LineRenderer绘制“感知强度”

我创建了一个PerceptionVisualizer组件,挂载在AI角色上。它在OnDrawGizmos中,用LineRenderer绘制从AI位置出发的多条射线,每条射线的颜色和粗细,实时反映该方向上的感知强度:

void OnDrawGizmos() { if (!Application.isPlaying || !debugMode) return; // 获取当前感知缓冲区中最活跃的目标 var activeTarget = GetMostActiveTarget(); if (activeTarget == null) return; // 计算从AI到目标的方向向量 Vector3 direction = (activeTarget.transform.position - transform.position).normalized; // 绘制主视线(绿色,粗) Gizmos.color = Color.green; Gizmos.DrawLine(transform.position, transform.position + direction * 30f); // 绘制视野锥边缘(黄色,细) Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position, transform.position + Quaternion.AngleAxis(viewAngle/2, transform.up) * transform.forward * 30f); Gizmos.DrawLine(transform.position, transform.position + Quaternion.AngleAxis(-viewAngle/2, transform.up) * transform.forward * 30f); // 绘制听觉范围球体(蓝色半透明) Gizmos.color = new Color(0, 0.5f, 1f, 0.2f); Gizmos.DrawSphere(transform.position, hearingRadius); }

但Gizmo只能在Scene视图看到,无法给策划和QA提供直观反馈。于是,我进一步用Canvas+Image组件,在Game视图右上角绘制一个迷你雷达图:中心是AI,周围圆环代表距离,扇形区域代表视野角度,红色光点代表被感知到的目标。这个雷达图的数据,直接来自PerceptionBuffer,完全同步。策划在测试时,只需看一眼雷达,就知道“AI为什么没转向”——是因为目标在雷达盲区(被墙挡住),还是因为confidence太低(雷达上显示为暗淡的灰色点)。这种可视化,把抽象的感知数据,变成了可读、可量化的界面语言。

4.2 感知日志系统:结构化记录每一次感知事件

Gizmo和雷达图解决“此刻看到什么”,日志系统解决“过去发生了什么”。我设计了一个轻量级PerceptionLogger,它不写入磁盘(避免IO卡顿),而是在内存中维护一个滚动日志队列(默认保存最近100条):

public class PerceptionLogEntry { public string aiName; public PerceptionData data; public float timeSinceStart; public string decisionResult; // 如 "Focused on Player", "Ignored due to low confidence" } // 日志队列 private Queue<PerceptionLogEntry> logQueue = new Queue<PerceptionLogEntry>(100);

在编辑器中,我提供了一个PerceptionLogWindow(继承自EditorWindow),它可以:

  • 按AI名称筛选日志
  • 按PerceptionType(Sight/Hearing)过滤
  • 显示confidence数值的分布直方图
  • 导出为CSV供Excel分析

有一次,QA报告“守卫AI在第3关总是漏掉从左边偷袭的玩家”。我打开日志窗口,筛选该AI的日志,发现所有Sight事件的confidence都低于0.2,而Hearing事件却很频繁。进一步检查,发现该关卡左侧有一堵特殊的“布料材质”墙,其Collider的LayerMask未被包含在obstacleLayerMask中,导致Linecast穿透了墙壁,confidence被错误地设为0。问题在5分钟内定位并修复。没有这个日志系统,这个问题可能需要数小时的断点调试。

4.3 感知压力测试:用自动化脚本模拟极端场景

最后,是验证系统鲁棒性的终极手段——压力测试。我编写了一个PerceptionStressTest脚本,它可以:

  • 动态生成100个AudioPerceptionSource,在场景中随机移动
  • 控制50个玩家对象,以不同速度、不同路径在AI视野中穿梭
  • 实时监控每个AI的PerceptionBuffer填充率、PerceptionData生成频率、CPU占用峰值

测试脚本会生成一份HTML报告,包含:

  • 平均PerceptionData处理延迟(毫秒)
  • confidence值的分布(理想情况应集中在0.7~0.9区间)
  • PerceptionBuffer溢出次数(溢出意味着感知事件丢失)

在《城市模拟》上线前,我们用这个脚本跑了72小时不间断测试,发现了两个关键问题:一是当声源过于密集时,AudioPerceptionManager的遍历逻辑成为瓶颈;二是PerceptionBuffer的环形数组在高并发下偶发索引越界。前者通过引入空间分区(Octree)优化,后者通过添加原子锁解决。压力测试不是为了证明系统“能跑”,而是为了暴露它“在什么条件下会崩”,这才是专业开发的底线。

提示:调试工具的价值,远超开发阶段。在项目后期,当美术提出“让AI对闪光弹更敏感”时,我只需调整Sight数据的confidence计算公式,然后打开雷达图和日志,10分钟内就能验证效果是否符合预期。没有调试能力的AI系统,就像没有仪表盘的飞机,飞得再高,也随时可能坠毁。

5. 从“感知”到“智能”:构建可成长的AI认知框架

感知只是起点,真正的“人工智能”体现在AI如何理解、记忆、推理和适应。我称之为“认知四象限”,它建立在前述感知系统之上,无需外部AI框架,纯Unity C#即可实现。

5.1 记忆象限:基于时间衰减的短期记忆

PerceptionBuffer本身就是一种记忆,但它是瞬时的。我在此基础上,构建了一个ShortTermMemory系统,它为每个被感知的目标,维护一个MemoryEntry:

public class MemoryEntry { public GameObject target; public Vector3 lastKnownPosition; public float lastSeenTime; // Time.time public int sightingCount; // 被看到的次数 public float threatLevel; // 基于sightingCount和lastSeenTime计算 }

threatLevel的计算公式是:threatLevel = sightingCount * Mathf.Exp(-(Time.time - lastSeenTime) / 5f)。指数衰减确保了“刚看到的玩家”威胁最高,“5秒前看到的玩家”威胁已大幅降低。AI在决策时,会优先搜索threatLevel > 0.5f的目标。这个设计让AI有了“记忆残留”——玩家躲进房间后,AI不会立刻放弃,而是会在门口徘徊几秒,等待玩家再次出现。我在《密室逃脱》项目中,用这个机制实现了“AI守卫的怀疑周期”,玩家反馈“它好像记得我刚才在哪”。

5.2 推理象限:基于世界知识的因果链推理

WorldKnowledgeSystem提供的包围盒信息,是推理的基础。我实现了一个简单的因果推理器:当AI感知到一个AudioPerceptionSource(如“玻璃破碎声”),它会查询WorldKnowledgeSystem,找到该声源位置附近的所有Renderer,如果其中有一个tag == "Window",且其bounds.size显示它已被破坏(size.x < 0.1f),那么AI就会推断“有人打破了窗户”,并立即提高警戒等级,向该位置移动。这个推理过程,不依赖预设的事件绑定,而是实时的空间关系查询。它让AI的反应有了逻辑链条,而不是简单的条件触发。

5.3 适应象限:基于玩家行为模式的学习

真正的智能,是能从玩家身上学习。我设计了一个PlayerBehaviorProfile,它记录玩家在特定情境下的行为偏好:

  • 在开阔地,玩家有70%概率选择奔跑
  • 在狭窄走廊,玩家有85%概率选择贴墙移动
  • 面对单个守卫,玩家有60%概率选择绕后

这个档案由PerceptionLogger的长期数据训练生成(用简单的滑动窗口统计)。AI在遇到类似情境时,会调用PlayerBehaviorProfile.PredictNextMove(player),预测玩家下一步最可能的位置,然后提前移动到该位置进行拦截。这不是机器学习,而是基于统计规律的启发式预测。在Beta测试中,玩家普遍反映“AI越来越难骗了”,这就是适应性的体现。

5.4 表达象限:将内部状态映射为玩家可感知的外在表现

最后,所有内部认知,必须通过外在表现让玩家感知到,否则就是“黑箱智能”。我严格遵循“状态-表现”映射表:

  • PerceptionBuffer中confidence > 0.8f→ 头部快速转动,瞳孔放大(Shader控制)
  • threatLevel > 0.7f→ 武器抬起,脚步加快,呼吸声变重(AudioSource音量提升)
  • MemoryEntry中lastSeenTime < 2f→ 视线持续锁定该方向,即使目标已消失

这些表现,全部由Animator的Bool/Float参数驱动,与感知系统解耦。美术可以自由调整表现细节,而不影响AI逻辑。当玩家看到守卫的瞳孔突然放大、武器瞬间抬起,他不需要被告知,就能本能地意识到“它看到我了”。这种无缝的内外一致性,才是AI“活起来”的终极标志。

我的经验是:一个优秀的AI系统,其80%的工作量不在核心算法,而在调试工具、可视化、日志和表现映射上。玩家永远不会看到你的PerceptionBuffer有多优雅,但他们能瞬间感受到“这个AI懂我”。这才是游戏AI的终极目标——不是技术炫技,而是情感共鸣。

6. 踩过的坑与血泪经验:那些文档里绝不会写的真相

写了六年Unity AI,踩过的坑比走过的路还多。下面这些,是我在无数个凌晨调试后,用真金白银换来的经验,没有一句废话。

6.1 坑:相信Physics.Raycast的“完美性”,结果AI在斜坡上失明

现象:AI在45度斜坡上,对坡顶的玩家完全无反应,但平地上一切正常。
根因:Raycast的起始点是transform.position,而角色在斜坡上时,transform.position位于脚底,射线从地面发出,直接被斜坡本身挡住。
解决方案:永远使用GetComponent<Collider>().bounds.center作为Raycast起点,或者更稳妥地,用Physics.Raycast的out RaycastHit hit,然后取hit.point + hit.normal * 0.1f作为下一个射线的起点。我现在的标准做法是:先用Physics.Raycast检测地面,得到站立点,再从此点向上偏移0.5m发起真正的感知射线。这个0.5m,就是AI的“眼睛高度”,必须可配置。

6.2 坑:用GameObject.FindWithTag遍历声源,导致帧率暴跌

现象:添加第20个声源后,游戏从60fps掉到30fps。
根因:FindWithTag是O(n)操作,每帧遍历所有GameObject,20个声源时,它要检查场景中上千个对象。
解决方案:建立一个静态的List<AudioPerceptionSource>,在AudioPerceptionSource.OnEnable时Add,OnDisable时Remove。AI只遍历这个列表,O(1)复杂度。我甚至为此写了一个PerceptionSourceManager单例,所有声源自动注册,AI只需PerceptionSourceManager.GetAllSources()。这个优化,让声源数量从20提升到200,帧率纹丝不动。

6.3 坑:PerceptionBuffer用List<T>,导致GC Alloc飙升

现象:Profiler里GC Alloc每帧2MB,内存泄漏。
根因:List<T>.Add()在容量不足时会重新分配内存,产生大量临时对象。
解决方案:改用预分配的T[]数组 +int currentIndex,实现真正的环形缓冲区。初始化时new PerceptionData[128],永远不Resize。Add()操作变成buffer[currentIndex % buffer.Length] = newData; currentIndex++。这个改动,让GC Alloc从2MB/frame降到0。记住:游戏里任何可能高频调用的集合操作,都必须是无GC的。

6.4 坑:忽略Time.timeScale,导致AI在暂停时还在“思考”

现象:游戏暂停(Time.timeScale = 0)后,AI依然在移动、在转头。
根因:Update()在timeScale=0时依然调用,但Time.deltaTime=0,导致所有基于时间的计算(如衰减、计时器)失效。
解决方案:所有感知和行为更新,必须放在FixedUpdate()中,或显式检查if (Time.timeScale <= 0) return;。更彻底的做法,是创建一个PerceptionScheduler,它只在timeScale > 0时才分发感知事件。这个细节,决定了你的AI是“活的”还是“卡死的”。

6.5 坑:把PerceptionData设计成class,引发隐式装箱

现象:PerceptionBuffer里存了1000个PerceptionData,内存占用异常高。
根因:如果PerceptionData是class(引用类型),每个实例都是堆上分配的对象,有额外的内存开销和GC压力。
解决方案:必须是struct(值类型)。struct在栈上分配,复制成本低,且List<struct>是连续内存,CPU缓存友好。我见过有人把PerceptionData做成class,结果一个AI就占了1MB内存,改成struct后,降到12KB。值类型不是银弹,但在这里,它是唯一正确的选择。

最后一点心得:Unity AI开发,本质上是一场与“实时性”和“确定性”的赛跑。你写的每一行代码,都要问自己:它在16ms(60fps)内能跑完吗?它在100个AI同时运行时还稳定吗?它能让策划不靠猜,而靠看,就能调出想要的效果吗?答案如果是否定的,那就不是“精粹”,只是“凑合”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询