突然想尝试一下在Unity中模拟近防炮(拦截炮),该拦截跑应该发射物理子弹并并命中处在运动中的物体,所以在代码中至少应考虑:
- 子弹发射矢量速度vb(暂不考虑风阻)
- 目标距离 L
- 目标当前矢量速度 v
- 目标运动加速度 a
- 重力加速度 g
有以上这些变量后就可以运用初高中知识来预判目标,并得到良好的命中率:以下算法目前只适用于匀速直线运动和匀变速直线运动。
首先是制作大炮和发射部分。这里简单地将圆柱体或者立方体拉成炮管,后期再替换成正经模型:
然后创建一个Prefab物体用作炮弹,什么形状都行,但是要有Rigibody,阻力调为0;炮管可以不用设置碰撞体,如果一定要碰撞体,那么就用以下方法来避免炮弹和炮管发生碰撞,将炮管和炮弹分别设置不同Layer:
Physics.IgnoreLayerCollisionhttps://docs.unity3d.com/ScriptReference/Physics.IgnoreLayerCollision.html
接下来就是发射炮弹,用StartCoroutine()接口来让大炮不间断地连射,发射频率可以实时更改:
using System.Collections;
using UnityEngine;
public class Interceptor : MonoBehaviour
{
public Transform TargetObj, PredictedObj;//assigin target
public GameObject bullet;// bullet prefab
public float FiringRate = .3f, bulletSpeed = 10f, towardSpeed = 1f, gravityLead = 0.5f, accurancy = 0.5f;
public float flyingTime, distance, multiplyler, num_bullets_pertime = 5;
[SerializeField] Rigidbody TargetRig;
[SerializeField] Vector3 lastVelocity, acceleration;
void Start()
{
TargetRig = TargetObj.GetComponent<Rigidbody>();
StartCoroutine(Shoot());// start shooting
}
IEnumerator Shoot()
{
while (TargetObj)//如果有瞄准的物体,那么就一直发射!
{
// for (int i = 0; i <= num_bullets_pertime; i++) { Instantiate(bullet, transform.position, transform.rotation).GetComponent<Rigidbody>().velocity = transform.forward * bulletSpeed * Random.Range(0.1f, 2f); }
Instantiate(bullet, transform.position, transform.rotation).GetComponent<Rigidbody>().velocity = transform.forward * bulletSpeed;
yield return new WaitForSeconds(FiringRate);
}
}
}
接下来是核心部分,为了直观展现大炮预瞄位置,这里可以创建一个鲜艳的物体来表示,如上图中的红标,并将其绑定到 PredictedObj 变量上。只要将PredictedObj 的位置计算出来,让大炮直接调用LookAt 方法 或者 在操作面板中添加LookAt Constrant 锁定到该物体就可以实现简单的拦截打击了。
要预测PredictedObj 的位置,可以直接运用初高中物理知识:但是这里需要注意的是,最终采用的这种方法并不严谨,因为子弹在飞行的过程中距离 Distance 是会改变的, 因为物体在运动,这就涉及到动态预测——即在三维坐标系中,已知之前的1~5条件,还要加上:
6. 子弹发射时刻位置
7. 目标发射时刻位置
然后用两点距离公式和运动学公式(匀速和匀加速)连列来求解一个繁杂的二元一次方程,进而得出撞击前需要的未知时间T。
我直接裂开🤯,所以暂时不考虑方程求解部分,粗略的将未知T 变为:
这种方法最大的弊端就是炮弹越慢越难命中。Distance 在Unity中可以简单的调用:
distance = Vector3.Distance(transform.position, TargetObj.position);//get the current distance(insufficiently strict)
bulletSpeed vb 提前设定好了;
加速度 a 在 Unity中需要手动计算,只需要在Update()中保存前一帧的目标速度,然后用公式
acceleration = (TargetRig.velocity - lastVelocity) / Time.deltaTime;//acceleration
lastVelocity = TargetRig.velocity;//get the current velocity for next frame acceleration caculation
最后直接明了,一个简单公式解决:
预测物体位置
= 预测物体初始位置
最后考虑到之前所说的不严谨,就再加上一个随机的小范围的散射,来达到“碰运气”的目的,毕竟就算是严谨计算,也无法应对某些特殊和突发状况,同时尽可能提高子弹速度来缩小 的误差,这就是为什么现实生活中的近放炮往往具有超高的射速,极高的子弹速度,一定的散射比例甚至是高空爆炸。
实际效果:
Unity 开发可预测打击近防炮
不难看出在低射速,低频率和低散射时,基本只在匀速直线运动和部分匀变速直线运动中起作用,变速和变加速命中率低的可怜。原因很简单,因为炮弹太慢,飞行时间太长,还没等到炮弹到达,目标早就变到下一个状态了;接着将射速、频率、散射全部调高,命中率一下子就上去了,真是非常的amazing啊
完整代码如下,或许还能优化:
using System.Collections;
using UnityEngine;
public class Interceptor : MonoBehaviour
{
public Transform TargetObj, PredictedObj;//assigin target
public GameObject bullet;// bullet prefab
public float FiringRate = .3f, bulletSpeed = 10f, followSpeed = 1f, gravityLead = 0.5f, accurancy = 0.5f;
public float flyingTime, distance;
[SerializeField] Rigidbody TargetRig;
[SerializeField] Vector3 lastVelocity, acceleration;
void Start()
{
TargetRig = TargetObj.GetComponent<Rigidbody>();
StartCoroutine(Shoot());// start shooting
}
// Update is called once per frame
void Update()
{
if (!TargetObj) { return; }
acceleration = (TargetRig.velocity - lastVelocity) / Time.deltaTime;//acceleration
lastVelocity = TargetRig.velocity;//get the current velocity for next frame acceleration caculation
distance = Vector3.Distance(transform.position, TargetObj.position);//get the current distance(insufficiently strict)
flyingTime = distance / bulletSpeed; //dt = Distance / vb
PredictedObj.position = Vector3.Lerp(PredictedObj.position,//use Lerp to control cannon's follow speed
(
TargetObj.position // current position
+ TargetRig.velocity * flyingTime // next position with uniform linear motion: dL1 = L + dt * v
+ 0.5f * Mathf.Pow(flyingTime, 2f) * acceleration// then add distance with uniformly variable motiond during dt: dL2= dL1 + 1/2 * a * dt^2
+ 0.5f * Mathf.Pow(flyingTime, 2f) * -Physics.gravity// next add distance with gravity's uniformly variable motion during dt: dL3= dL2 + 1/2 * a * dt^2
+ new Vector3(Random.Range(-accurancy, accurancy), Random.Range(-accurancy, accurancy), Random.Range(-accurancy, accurancy))//finally add alittle random fractors to incrase hit rate chance
),
Time.deltaTime * followSpeed);
}
IEnumerator Shoot()
{
while (TargetObj)
{
// for (int i = 0; i <= num_bullets_pertime; i++) { Instantiate(bullet, transform.position, transform.rotation).GetComponent<Rigidbody>().velocity = transform.forward * bulletSpeed * Random.Range(0.1f, 2f); }
Instantiate(bullet, transform.position, transform.rotation).GetComponent<Rigidbody>().velocity = transform.forward * bulletSpeed;
yield return new WaitForSeconds(FiringRate);
}
}
}