玩家检测的本质

敌人的玩家检测是在敌人前方做一次物理查询,确认玩家层上的 Collider2D 是否位于检测线内,并取得命中距离。
当前 Enemy_Skeleton 保存三个相关字段:
  • playerCheckDistance:敌人最多能检测多远。
  • playerLayer:只查询哪些 Layer。
  • isPlayerDetected:本次射线的完整命中结果。
Entity 的继承关系参见
🧬
07 Entity 基类与继承
;角色朝向参见
↔️
03 角色朝向与翻转
;射线方向和墙壁检测参见
🧱
08 墙壁检测与碰墙转向
。

RaycastHit2D 不是 bool

变量名 isPlayerDetected 看起来像布尔值,但它的真实类型是 RaycastHit2D。这个结构体保存:
  • collider:命中的 Collider2D。
  • distance:射线起点到命中点的距离。
  • point:命中的世界坐标。
  • normal:命中表面的法线。
  • rigidbody:命中对象关联的 Rigidbody2D。
  • transform:命中对象的 Transform。
所以它既能回答“有没有命中”,又能提供“命中了谁、距离多远”。
能够成立,是因为 RaycastHit2D 可以隐式转换成 bool;本质上是判断是否包含有效的 Collider。更直观的写法是:
为了让名称与类型一致,可以把变量改名为 playerHit。

LayerMask 为什么能够只找玩家

当前场景中:
  • Player GameObject 位于第 6 层。
  • Enemy_Skeleton 的 playerLayer 选择第 6 层。
  • playerCheckDistance 为 3。
因此玩家射线只报告第 6 层 Collider2D。其他 Layer 上的地面、敌人或装饰物不会作为这次查询的命中结果。
LayerMask 是位掩码,不是单个对象引用。一个掩码可以同时允许多个 Layer。

当前朝左检测的写法有问题

当前代码:
这里把 facingDir 乘到了 distance:
最大查询距离不应该负责方向。朝左时得到负距离,会使真实检测与左向 Gizmos 不一致,并导致检测不可靠或没有结果。
正确写法:
职责应始终分开:

为什么 Enemy_Skeleton 要 override CollisionChecks

Entity 已经负责地面和墙壁检测,Enemy_Skeleton 只需要在它们之后追加玩家检测:
  • override:敌人提供扩展版本。
  • base.CollisionChecks():先保留 Entity 的地面与墙壁检测。
  • 后面的 Raycast:再加入敌人独有的玩家感知。
如果漏掉 base.CollisionChecks(),敌人的地面状态和墙壁状态就不会更新。

检测距离与攻击距离不是一回事

当前代码把固定数值 1 当成攻击距离:
两个距离实际表达不同的问题:
  • playerCheckDistance:多远以内能发现玩家。
  • attackDistance:多近以内可以开始攻击。
更清楚的字段设计:
以后还可能有独立的追击停止距离,但课程当前还没有实现。

当前 isAttacking 会残留

当前代码只有在射线命中玩家时才更新 isAttacking:
假设上一帧玩家在 1 单位内:
这是状态残留:当前帧的“未命中”没有被转换成 false。
可以让每一帧的检测事实完整决定状态:
或者写得更明确:

检测到玩家不等于开始攻击

一条 Raycast 只提供感知数据。完整的数据流应当分为:
不应该让 Raycast 自己同时承担移动、动画和伤害职责。
当前 isAttacking 只是 Enemy_Skeleton 内部的私有 bool,尚未写入 Animator,也没有启用攻击 Hitbox,所以它目前只是一个决策变量。

当前检测可能穿墙

玩家射线的 LayerMask 只有 Player 层。墙体属于地形层,没有被包含在这次查询中,因此墙不会成为第一个命中对象。玩家只要位于射线方向和距离内,就可能隔墙被检测。
如果玩法需要墙体遮挡视线,可以让射线同时查询玩家层和地形层:
然后确认第一个命中物是否属于 Player 层:
Inspector 中的 sightLayer 同时选择 Player 与 Ground/Wall。最近命中物如果是墙,玩家就被遮挡。
这是视线遮挡的扩展方案,不是当前项目已经完成的功能。

直线 Raycast 的感知边界

当前检测是一条水平细线,因此:
  • 玩家在敌人前方、但高度偏离射线时可能检测不到。
  • 玩家在敌人背后不会被检测。
  • 它表达的是直线视线,不是圆形警戒范围。
  • 没有持续记忆,玩家一离开射线就应该立即丢失目标。
  • 如果起点位于自身或其他 Collider 内部,需要留意 fraction 为 0 的命中。
  • Player 没有 Collider2D,或者不在 playerLayer,也不会被命中。
以后可按玩法选择:
  • OverlapCircle:圆形警戒范围。
  • BoxCast:矩形或有宽度的前方区域。
  • 角度 + Raycast:扇形视野与遮挡检查。
  • 多条 Raycast:覆盖不同高度。
这些方式没有绝对优劣,取决于敌人需要表达哪种感知范围。

Gizmos 显示玩家检测范围

Enemy_Skeleton 在基类环境检测线之外追加红色玩家检测线:
base.OnDrawGizmos() 保留地面与墙壁辅助线;红线只表示玩家检测方向和长度。它不会证明实际 LayerMask 正确,也不会证明 Raycast 已经命中玩家。

当前 Enemy_Skeleton 实际完成程度

已经实现:
  • 继承 Entity 的组件引用、朝向和环境检测。
  • 追加玩家层 Raycast。
  • 读取玩家命中距离。
  • 根据距离设置私有 isAttacking。
  • 碰墙时调用 Flip。
尚未真正实现:
  • Movement() 在 Update 中仍被注释,没有巡逻移动。
  • 没有追击状态。
  • isAttacking 没有传给 Animator。
  • 没有敌人攻击动画与伤害判定。
  • 玩家完全离开射线时没有清除旧状态。
  • 朝左检测的方向/距离参数需要修正。
  • 地形暂时不会遮挡玩家感知。
因此当前更准确的定义是“敌人感知数据的基础”,还不是完整敌人 AI。

相关 API

  • Physics2D.Raycast:从起点沿方向,在最大距离内查询 Collider2D。
  • RaycastHit2D:保存命中对象、距离、位置和法线。
  • RaycastHit2D.collider:命中的 Collider2D;未命中时为空。
  • RaycastHit2D.distance:起点到命中点的距离。
  • LayerMask:限制查询接受哪些 Layer。
  • Transform.position:当前检测以敌人根节点的世界位置为起点。
  • Gizmos.DrawLine:绘制感知范围,不参与查询。
  • virtual / override / base:在保留 Entity 检测的基础上增加敌人检测。

官方参考