玩家检测的本质
敌人的玩家检测是在敌人前方做一次物理查询,确认玩家层上的 Collider2D 是否位于检测线内,并取得命中距离。
当前 Enemy_Skeleton 保存三个相关字段:
playerCheckDistance:敌人最多能检测多远。
playerLayer:只查询哪些 Layer。
isPlayerDetected:本次射线的完整命中结果。
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 检测的基础上增加敌人检测。