【Unity-学习-003】导弹,跟踪弹怎么玩?


最近在玩一款飞行射击游戏,时常驾驶着自己的飞机在枪林弹雨中穿行,然后就快要通过关卡的时候,我奋力的击杀,却依然顶不住敌军猛烈的攻势。在敌机如蛇皮般的走位中,忍着快要抓狂的心情努力的把着鼠标,但我的准星却仍然无法瞄准到敌人。想想都很气!如果这时候,能在飞机上发射出跟踪弹~~~~~~,贼舒服!


首先梳理一下,一般导弹有以下两种模式。

①跟踪

发射之后尾随目标飞行,直到击中目标,BOOM!
②预判发射之后,判断目标的飞行方向,然后前去拦截,直到击中目标,BOOM!

首先我们来说说跟踪。

一般想到的就是 transform.LookAt(Vector3 worldPos) 方法。这个方法可以让该 transform 看向这个 worldPos ,此时如果把 worldPos 改成需要追踪的目标的位置,放在 Update 中每帧调用,那么就会让 transform 在自己的生命周期中,始终看向目标。

代码如下:

using UnityEngine;

public class Rocket : MonoBehaviour
{

    public Transform target;               //跟踪目标
    public GameObject FX;                  //爆炸特效
    public float moveSpeed;                //移动速度
    public float rotateSpeed ;             //旋转速度


    private void Update()
    {
        if (target != null)    //判断当前是否有跟踪目标 如果有的话 执行 以下跟踪逻辑
        {

            transform.LookAt(target); 

           // Vector3 tempDirV = target.position - transform.position;                //计算出正确的转向
           // Quaternion rightDirQ = Quaternion.LookRotation(tempDirV);               //将转向 转化为 四元数
           // transform.rotation = Quaternion.Lerp(transform.rotation, rightDirQ, rotateSpeed);  //利用四元数插值方法 将方向赋给 transform.rotation
        }
        transform.position += transform.forward * moveSpeed * Time.deltaTime;      //向前移动
    }


    //当 有物体进入到 自己的触发器时 调用一次
    private void OnTriggerEnter(Collider other)
    {
        GameObject tempFX = Instantiate(FX, transform.position, transform.rotation);  //生成一个爆炸特效 并给予位置和旋转信息
        Destroy(gameObject);      //销毁自己
        Destroy(tempFX, 1);       //等待 1秒 销毁刚才 创建生成的爆炸特效
    }
}

上边是两种方法,除了lookAt 还有注释的另一种方法。

效果如图:


然后来说预判。

先来想想预判的逻辑是怎样的。所谓预判,就是拦截。我们得知道的数据:

target移动方向,移动速度
self移动速度

其中移动方向,有两种情况。因为逻辑是一帧一帧的运行,所以我们选取碰撞的前一帧为基准。

                              同向                              反向
在target的后方发生碰撞在target的脸前发生碰撞

 

如图所示:             

      

                                                 同向                                                                                                           反向

其中 向量 Vab = target.position - rocket.position。

L3 = Vab.magnitude。

计算出  ∠α = Vector3.angle(Vab ,target.移动方向)。 

L2 = Mathf.Cos(∠α * Mathf.Deg2Rad) * L3 。

L1 = Mathf.Sin(∠α * Mathf.Deg2Rad) * L3 。

根据勾股定理:a^{2}+b^{2}=c^{2} 得出:

                  同向                      反向
L_{1}^{2}+(L_{2}+ V_{b}*t)^{2} = (V_{a} * t)^{2}L_{1}^{2}+(L_{2} - V_{b}*t)^{2} = (V_{a} * t)^{2}

化简后可以得出:

(V_{b}^{2}-V_{a}^{2})*t^{2}+(2*L_{2}*V_{a})*t+(L_{1}^{2}+L_{2}^{2}) = 0(V_{b}^{2}-V_{a}^{2})*t^{2}-(2*L_{2}*V_{a})*t+(L_{1}^{2}+L_{2}^{2}) = 0

​​ 看的出是一元二次三项式,解 a*t^{2}+b*t+c =0 。

这样就解出来时间 t 。在 t 秒后两者相遇。

从而依据 t ,Vb ,target.position ,计算出 Boom点(C点)。也就是当前的碰撞点。

其中主要的代码逻辑如下:

 Vector3 AB= target.position - transform.position;
 float angle = Vector3.Angle(AB, targetDir);         //获取角度
 float L1 = Mathf.Sin(angle * Mathf.Deg2Rad) * AB.magnitude;         //计算L1
 float L2 = Mathf.Cos(angle * Mathf.Deg2Rad) * AB.magnitude;         //计算L2
 float a =  Vb* Vb- Va* Va;     //计算 a
 float b = 2 * Vb* L2;                                   //计算 b
 float c = L1 * L1 + L2 * L2;                                      //计算 c
 if(a == 0)                     //a = 0 的话,方程有唯一一解
 {
    rightTime  = -(c / b); 
 }
 else
 {          
    if (Vector3.Dot(AB, targetDir) < 0)        //判断 同向 还是 反向
    {
        b *= -1;                                  //如果 反向 则b应该为相反数
    }
    float detla = MathTool.GetDetla(b, a, c);     //计算 detla
    if (detla < 0) 
        return;                                   //判断 detla < 0 则方程无解
    float time1 = (-b + Mathf.Sqrt(detla)) / (2 * a); //计算 正确的时间
    float time2 = (-b - Mathf.Sqrt(detla)) / (2 * a);
    rightTime  = time1 > time2 ? time1 : time2;
 }
 Vector3 rightPos = targetDir * rightTime * Vb+ target.position;   //计算 正确的 目标点

因为方便调用,所以我将方法封装在了一个类里。具体代码逻辑如下:

Rocket.cs

using UnityEngine;

public class Rocket : MonoBehaviour
{

    public Transform target;               //跟踪目标
    public GameObject FX;                  //爆炸特效
    public float selfSpeed;                //移动速度
    public float rotateSpeed;             //旋转速度
    private float lastTime;
    private Vector3 lastPos;

    private void Start()
    {
        if (target != null)
        {
            lastTime = Time.time;               //首先初始化 lastTime 和 lastPos
            lastPos = target.position;
        }
    }
    // Update is called once per frame
    void Update()
    {
        if (target != null)
        {
            float targetSpeed = MathTool.GetRightSpeed(lastPos, target.position, lastTime, Time.time);
            Vector3 rightPos = MathTool.GetRightPos(targetSpeed, selfSpeed, lastPos, transform, target);   //计算 正确的 目标点
            if (rightPos.Equals(Vector3.zero))                                            //判断方程有解
                return;
            Quaternion rightDir = Quaternion.LookRotation(rightPos - transform.position);     //转化四元数
            transform.rotation = Quaternion.Lerp(transform.rotation, rightDir, Time.deltaTime * 25); //设置旋转
            lastPos = target.position;                                              //记录 当前帧的位置
            lastTime = Time.time;                                                   //记录 当前帧的时间  供下一帧 使用
        }
        transform.position += transform.forward * Time.deltaTime * selfSpeed;      //向前移动
    }
    
    //当 有物体进入到 自己的触发器时 调用一次
    private void OnTriggerEnter(Collider other)
    {
        GameObject tempFX = Instantiate(FX, transform.position, transform.rotation);  //生成一个爆炸特效 并给予位置和旋转信息
        Destroy(gameObject);      //销毁自己
        Destroy(tempFX, 1);       //等待 1秒 销毁刚才 创建生成的爆炸特效
    }
}

MathTool.cs

using UnityEngine;

public class MathTool : MonoBehaviour
{
    /// <summary>
    /// 计算目标 当前帧的移动速度
    /// </summary>
    /// <param name="lastPos"></param>
    /// <param name="currentPos"></param>
    /// <param name="lastTime"></param>
    /// <param name="currentTime"></param>
    /// <returns></returns>
    public static float GetRightSpeed(Vector3 lastPos, Vector3 currentPos, float lastTime, float currentTime)
    {
        if ((currentTime - lastTime) < 0)
            return -1;
        else if ((currentTime - lastTime) == 0)
            return 0;
        else
            return Vector3.Distance(currentPos, lastPos) / (currentTime - lastTime);
    }

    /// <summary>
    /// 计算目标 正确的移动方向
    /// </summary>
    /// <param name="lastPos"></param>
    /// <param name="currentPos"></param>
    /// <returns></returns>
    public static Vector3 GetDirection(Vector3 lastPos, Vector3 currentPos)
    {
        return (currentPos - lastPos).normalized;
    }

    /// <summary>
    /// 计算出 detla
    /// </summary>
    /// <param name="b"></param>
    /// <param name="a"></param>
    /// <param name="c"></param>
    /// <returns></returns>
    public static float GetDetla(float b, float a, float c)
    {
        return b * b - 4 * a * c;
    }

    /// <summary>
    /// 计算目标 正确的碰撞时间
    /// </summary>
    /// <param name="b"></param>
    /// <param name="detla"></param>
    /// <param name="a"></param>
    /// <param name="c"></param>
    /// <returns></returns>
    public static float GetRightTime(float b, float detla, float a, float c)
    {
        if (detla < 0) //判断 detla < 0 则方程无解
        {
            return -1;
        }
        else if (a == 0)   //at^2 + bt +c = 0  当 a == 0 时 。 
        {
            return -(b / c);
        }
        else if (detla == 0)   //detla == 0 时。
        {
            return -(b / 2 * a);
        }
        else
        {
            float time1 = (-b + Mathf.Sqrt(detla)) / (2 * a);
            float time2 = (-b - Mathf.Sqrt(detla)) / (2 * a);
            return time1 > time2 ? time1 : time2;
        }

    }

    /// <summary>
    /// 计算目标 正确的目标移动位置
    /// </summary>
    /// <param name="targetSpeed"></param>
    /// <param name="selfSpeed"></param>
    /// <param name="lastPos"></param>
    /// <param name="self"></param>
    /// <param name="target"></param>
    /// <returns></returns>
    public static Vector3 GetRightPos(float targetSpeed, float selfSpeed, Vector3 lastPos, Transform self, Transform target)
    {
        if (targetSpeed == -1)
            return Vector3.zero;
        Vector3 targetDir = GetDirection(lastPos, target.position);                    //获取 目标 移动方向
        Vector3 AB = target.position - self.position;
        float angle = Vector3.Angle(AB, targetDir);         //获取角度
        float L1 = Mathf.Sin(angle * Mathf.Deg2Rad) * AB.magnitude;         //计算L1
        float L2 = Mathf.Cos(angle * Mathf.Deg2Rad) * AB.magnitude;         //计算L2
        float a = targetSpeed * targetSpeed - selfSpeed * selfSpeed;      //计算 a
        float b = 2 * targetSpeed * L2;                                   //计算 b
        float c = L1 * L1 + L2 * L2;                                      //计算 c
        if (Vector3.Dot(AB, targetDir) < 0)                   //判断 同向 还是 反向
        {
            b *= -1;                                                      //如果 反向 则b应该为相反数
        }
        float detla = GetDetla(b, a, c);                      //计算 detla
        float rightTime = GetRightTime(b, detla, a, c);                //计算 正确的时间
        if (rightTime == -1)
            return Vector3.zero;
        Vector3 rightPos = targetDir * rightTime * targetSpeed + target.position;   //计算 正确的 目标点
        return rightPos;
    }
}

这就是预判逻辑了。接下来看看运行效果。

         

                                        同向                                                                                              反向

示例项目源码工程我打到一个包里了。想一起学习的可以下载下来看看。点我点我!


本人原创,虽然小例,但如果文章有所引用,请标明出处。

  • 14
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThursdayGame

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值