依旧先上效果图,小球模拟被追踪的目标,小方块为发射器,小长条这模拟追踪导弹,会跟着小球的位置而转向。
思路
由于是追踪导弹,所以导弹的朝向一定是会最终朝向目标点的。假设导弹为missile,目标为target,即最终
missile.transform.forward = (target.transform.position - missile.transform.position).normalized;
我们只需要每帧使用 Vector3.Lerp 将导弹的当前朝向慢慢的转变为最终的朝向即可。
至于转角速度,假设我们定一秒钟旋转180度。那么每帧的度数应该是180*deltatime,我们可以用 Vector3.Angle 来计算出当前方向和最终方向的夹角。所以我们 Vector3.Lerp 可以这样写
Vector3 angleOffset = Vector3.Angle(自身朝向, 最终朝向);
自身朝向 = Vector3.Lerp(自身朝向, 最终朝向, 每帧的旋转角度 / angleOffset);
新增知识点
Transform.TransformDirection(Vector3),从自身坐标到世界坐标变换方向。这个操作不会受到变换的缩放和位置的影响。返回的向量与direction有同样的长度。
这么说可能有点不明所以,所以就举个例子来说明,假设我们一个物体,需要以5的速度向正前方移动,那么对他自身而言,他的速度应该为Vector3(0,0,5),因为速度是有大小有方向的,Vector3(0,0,5)可以看出,方向为z轴正方向,大小为5。
那么得知物体速度后,我们怎么移动物体呢,transform.position = transform.position + Vector3(0,0,5) ?这个显然是不行的,因为你并不知道物体的朝向,这么相加只会让物体不停的向世界坐标的Z轴正方形移动,但并不一定是物体的正方向(物体的Z轴正方向)。
因此我们需要使用到上面的方法: 物体的Transform.TransformDirection(Vector3(0,0,5)) 得到一个新的Vector3,这个Vector3就是原始Vector3基于物体本地坐标的大小方向,转换为基于世界坐标的大小和方向。
比如物体和世界坐标方向一致,那么得到的还是Vector3(0,0,5)
如果方向相反即Rotate为(0,180,0),得到的即为Vector3(0,0,-5)
若物体的Rotate为(0,90,0),得到的即为Vector3(5,0,0),物体Z轴方向为世界坐标X轴的方向
Transform.InverseTransformDirection(Vector3),即上面操作的逆操作。
一般我们移动一个物体可能会用最简单的 transform.forward * speed,速度只是一个float值,并不存在方向。但是在真实情况中,物体一般不仅仅只有一个简单的向前的速度,可能还会存在重力导致的向下的速度,空气阻力造成的减速等等,所以我们用Vector3来作为速度,会更容易处理。
实现
首先我们在场景中建个cube作为炮弹的发射器,为其添加一个脚本Emitter,作用就是每隔两秒钟生成一个炮弹
public class Emitter : MonoBehaviour
{
public GameObject missile;
float currentTime;
void Update()
{
currentTime += Time.deltaTime;
if(currentTime > 2)
{
currentTime = 0;
GameObject m = GameObject.Instantiate(missile);
m.transform.localPosition = Vector3.zero;
m.SetActive(true);
}
}
}
然后我们再建个cube作为炮弹,绑定到Emitter的missile上,同时为炮弹添加脚本TrackMissile,用来自动追踪目标
public class TrackMissile : MonoBehaviour
{
//瞄准的目标
public Transform target;
//炮弹本地坐标速度,有大小有方向。
Vector3 speed = new Vector3(0, 0, 5);
//存储转向前炮弹的本地坐标速度
Vector3 lastSpeed;
//旋转的速度,单位 度/秒
int rotateSpeed = 90;
//目标到自身连线的向量,最终朝向
Vector3 finalForward;
//自己的forward朝向和mFinalForward之间的夹角
float angleOffset;
RaycastHit hit;
void Start()
{
//将炮弹的本地坐标速度转换为世界坐标
speed = transform.TransformDirection(speed);
Debug.Log("speed:"+ speed);
}
void Update()
{
CheckHint();
UpdateRotation();
UpdatePosition();
}
//射线检测,如果击中目标点则销毁炮弹
void CheckHint()
{
if(Physics.Raycast(transform.position, transform.forward, out hit)){
if(hit.transform == target && hit.distance < 1)
{
Destroy(gameObject);
}
}
}
//更新位置
void UpdatePosition()
{
transform.position = transform.position + speed * Time.deltaTime;
}
//旋转,使其朝向目标点,要改变速度的方向
void UpdateRotation()
{
//先将速度转为本地坐标,旋转之后再变为世界坐标
lastSpeed = transform.InverseTransformDirection(speed);
ChangeForward(rotateSpeed * Time.deltaTime);
speed = transform.TransformDirection(lastSpeed);
}
void ChangeForward(float speed)
{
//获得目标点到自身的朝向
finalForward = (target.position - transform.position).normalized;
if (finalForward != transform.forward)
{
angleOffset = Vector3.Angle(transform.forward, finalForward);
if (angleOffset > rotateSpeed)
{
angleOffset = rotateSpeed;
}
//将自身forward朝向慢慢转向最终朝向
transform.forward = Vector3.Lerp(transform.forward, finalForward, speed / angleOffset);
}
}
}
最后我们建个sphere,拖到发射器的边上,用于当被追踪目标,同时将这个目标拖到炮弹TrackMissile组件的target上即可。这样最简单的一个自动追踪炮弹就完成了。