这个脚本定义了一种特殊的子弹------风车子弹 (WindmillBullect),它继承自一个通用的子弹基类 Bullect。实现直线飞行 、存在时间限制 、命中后不立即销毁的行为;带追踪 + 生命周期管理的子弹。
继承关系
- 父类 :
Bullect
新增声明
hasTarget:是否已经锁定目标并开始追踪(防止每帧重复初始化方向,只在第一次获得目标时执行一次InitTarget。)timeVal:计时器(子弹存活)
生命周期方法
OnEnable()
private void OnEnable() { hasTarget = false; timeVal = 0; }
OnEnable在子弹 被激活(生成)时 调用(对象池复用场景尤为重要)。重置状态:未锁定目标、计时归零。
这样做避免了上次使用留下的脏数据。
Update()
检查游戏是否结束或超时,或者子弹的
timeVal>= 2.5f → 调用timeVDestoryBullect()立即销毁子弹(调用父类DestoryBullect回池)。。处理游戏暂停,不执行移动、不更新计时,子弹定在原地,直接返回
更新时间计时器,
timeVal每帧累加,但不超过 2.5 秒移动逻辑(核心)
hasTarget为true →沿自身 forward 方向移动(世界坐标系)。
hasTarget为false →检查目标是否存在且激活→ 锁定目标(hasTarget为true) → 调用InitTarget()计算朝向(延迟启动:首帧不移动,只转向)。)。
if (hasTarget) { transform.Translate(transform.forward * moveSpeed * Time.deltaTime, Space.World); } else { // 等待目标有效,转向并开始移动 if (targetTrans != null && targetTrans.gameObject.activeSelf) { hasTarget = true; InitTarget(); } }
辅助方法
private void InitTarget()
InitTarget()的主要作用是 让子弹的正面(forward 方向)指向目标,并修正子弹的最终朝向 ,使其能够在后续飞行中沿transform.forward方向直线飞向目标并符合美术期望的飞行姿态。
具体步骤
根据目标的标签(
"Item")计算瞄准点。调用
transform.LookAt让子弹的forward轴指向该点。如果子弹当前的 Y 轴欧拉角为 0,则强制旋转到 90 度。
if (targetTrans.gameObject.tag == "Item") { transform.LookAt(targetTrans.position + new Vector3(0, 0, 3)); } else { transform.LookAt(targetTrans.position); }为什么要 + new Vector3(0, 0, 3)?
原因:
物品(
"Item")可能是地面上的可破坏对象(如桶、箱子),它的 pivot 点(锚点)可能位于地面(Z=0 附近) 。若直接LookAt物品位置,子弹的朝向会指向地面,导致:
子弹飞向地面,视觉上像是"扎地"而不是水平飞向物品。
在 2D 游戏中,物品的碰撞体通常是一个水平放置的方形,子弹从侧面水平击中才合理。
解决办法:
对物品的瞄准点增加
(0, 0, 3)偏移,即让子弹朝物品上方 3 个单位(沿 Z 轴)的方向瞄准。这样子弹会从物品的"正面"或"上方"掠过,配合飞行逻辑,实际碰撞可能依然有效,且视觉上更自然。
子弹为什么要强制旋转到 90 度?
子弹模型(如风车叶片)可能设计为 XZ 平面内旋转,期望的飞行方向是 y 轴朝向侧面。
当
LookAt指向目标后,子弹的 Y 轴欧拉角有时会变成 0(例如子弹初始朝向和瞄准方向刚好使模型侧面朝上)。将 Y 角强制设为 90 度,能确保子弹的"正面"朝向正确的飞行方向,符合美术设计的视觉预期。
重写方法
protected override void OnTriggerEnter2D()
此脚本没有调用
base.OnTriggerEnter2D,意味着父类的通用碰撞逻辑(如销毁子弹)被覆盖。执行流程:
只关心标签为
"Monster"或"Item"的对象。确保碰撞对象处于激活状态。
防御性检查:如果目标是无效的,或当前物品目标已被清除,则提前返回。
伤害触发条件:
如果是怪物 → 直接造成伤害。
如果是物品 → 需要判断 该物品是否为子弹当前锁定的那个目标(通过 GameController.Instance.targetTrans 对比)。这暗示游戏可能只允许破坏当前指定的某个物品(例如任务目标)
→ 调用目标的TakeDamage方法:向碰撞体发送"TakeDamage"消息,参数为attackValue碰撞,同时播放命中特效CreateEff()
if (collision.gameObject.activeSelf) { if (targetTrans.position == null || (collision.tag == "Item" && GameController.Instance.targetTrans == null)) { return; } if (collision.tag == "Monster" || (collision.tag == "Item" && GameController.Instance.targetTrans == collision.transform)) { collision.SendMessage("TakeDamage", attackValue); CreateEffect(); } }