Unity3D敌人AI避障方向选择的条件判断优化方案咨询
嘿,这个需求我太熟悉了——用位掩码重构方向判断绝对是解决大量if嵌套的最优解,不仅代码会清爽很多,后续维护和扩展也方便。我给你梳理一套落地的思路,一步步来:
第一步:用带Flags特性的枚举定义方向位掩码
先把7个方向对应到不同的二进制位上,C#里的[Flags]特性能让我们方便地做位运算,把多个方向打包成一个整数:
[Flags] public enum EnemyMoveDirection { None = 0, // 00000000(无可行方向) ForwardLeft = 1 << 0, // 00000001(左前方) ForwardRight = 1 << 1, // 00000010(右前方) Left = 1 << 2, // 00000100(左侧) Right = 1 << 3, // 00001000(右侧) BackLeft = 1 << 4, // 00010000(左后方) BackRight = 1 << 5, // 00100000(右后方) Back = 1 << 6 // 01000000(正后方) }
每个方向对应一个独有的二进制位,这样我们就能用一个枚举值来表示所有可行方向的集合。
第二步:收集可行方向的位掩码
遍历你要检测的7个方向,对每个方向发射Raycast,如果没有击中障碍物,就把对应的位加到掩码里:
private EnemyMoveDirection GetAvailableDirections() { EnemyMoveDirection available = EnemyMoveDirection.None; LayerMask obstacleMask = LayerMask.GetMask("Obstacles"); // 只检测障碍物层,排除自身和玩家 float rayDistance = 1.5f; // 根据你的游戏场景调整射线长度 // 检测左前方 if (!Physics.Raycast(transform.position, transform.TransformDirection(new Vector3(-1, 0, 1).normalized), rayDistance, obstacleMask)) { available |= EnemyMoveDirection.ForwardLeft; } // 检测右前方 if (!Physics.Raycast(transform.position, transform.TransformDirection(new Vector3(1, 0, 1).normalized), rayDistance, obstacleMask)) { available |= EnemyMoveDirection.ForwardRight; } // 依次检测左侧、右侧、左后方、右后方、正后方,逻辑和上面一致 // ... return available; }
这里要注意用transform.TransformDirection把局部方向转换成世界空间的射线方向,同时用LayerMask避免误检测到敌人自身。
第三步:按优先级从掩码中选最优方向
核心的方向选择逻辑来了,我们把优先级组做成一个列表,按顺序检查,完全不用嵌套if:
private EnemyMoveDirection SelectBestDirection(EnemyMoveDirection availableDirections) { // 定义优先级顺序的方向组,越靠前优先级越高 var priorityGroups = new List<EnemyMoveDirection> { EnemyMoveDirection.ForwardLeft | EnemyMoveDirection.ForwardRight, EnemyMoveDirection.Left | EnemyMoveDirection.Right, EnemyMoveDirection.BackLeft | EnemyMoveDirection.BackRight, EnemyMoveDirection.Back }; foreach (var group in priorityGroups) { // 用位与运算提取当前优先级组里的可行方向 var validInGroup = availableDirections & group; if (validInGroup == EnemyMoveDirection.None) continue; // 这个优先级组没有可行方向,跳过 // 把当前组里的可行方向拆成列表,方便随机选择 List<EnemyMoveDirection> options = new List<EnemyMoveDirection>(); foreach (EnemyMoveDirection dir in Enum.GetValues(typeof(EnemyMoveDirection))) { if (dir == EnemyMoveDirection.None) continue; if ((validInGroup & dir) != EnemyMoveDirection.None) { options.Add(dir); } } // 随机选一个返回 return options[Random.Range(0, options.Count)]; } // 兜底:如果所有方向都被挡住(理论上不会出现,留个保险) return EnemyMoveDirection.None; }
这个逻辑的好处是,以后要调整优先级或者新增方向,只要修改priorityGroups列表或者枚举就行,完全不用动核心判断逻辑。
第四步:把方向转换成移动向量
最后,我们可以做一个字典,把每个方向映射成局部空间的移动向量,这样选完方向后直接拿来用:
private Dictionary<EnemyMoveDirection, Vector3> _directionToVector = new Dictionary<EnemyMoveDirection, Vector3> { { EnemyMoveDirection.ForwardLeft, new Vector3(-1, 0, 1).normalized }, { EnemyMoveDirection.ForwardRight, new Vector3(1, 0, 1).normalized }, { EnemyMoveDirection.Left, new Vector3(-1, 0, 0).normalized }, { EnemyMoveDirection.Right, new Vector3(1, 0, 0).normalized }, { EnemyMoveDirection.BackLeft, new Vector3(-1, 0, -1).normalized }, { EnemyMoveDirection.BackRight, new Vector3(1, 0, -1).normalized }, { EnemyMoveDirection.Back, new Vector3(0, 0, -1).normalized } }; // 在Update里调用逻辑 void Update() { var available = GetAvailableDirections(); var bestDir = SelectBestDirection(available); if (bestDir != EnemyMoveDirection.None) { Vector3 moveDir = transform.TransformDirection(_directionToVector[bestDir]); transform.Translate(moveDir * moveSpeed * Time.deltaTime, Space.World); } }
额外优化建议
- 可以把射线检测的参数(方向向量、检测距离、LayerMask)做成可配置的数组,甚至存在ScriptableObject里,不用改代码就能调整每个方向的检测规则。
- 如果敌人碰撞体较大,用SphereCast或BoxCast代替Raycast,能更准确判断是否会撞墙,避免“射线能过但身体会卡”的情况。
- 可以加入玩家位置的权重:如果玩家在某个可行方向的范围内,优先选择朝向玩家的方向,只要在收集可行方向时给对应位加权重,或者在随机选择时做加权随机即可。
这样整个逻辑就非常清晰,完全摆脱了大量的if嵌套,而且扩展性拉满!
内容的提问来源于stack exchange,提问作者Edward Severinsen
相关产品推荐
相关产品推荐

