Unity开发中导弹路径散射的原理与实现
- 前言
- 逻辑原理
- 代码实现
- 导弹自身脚本
- 外部控制脚本
- 应用效果
- 结语
前言
前面我们学习了导弹的追踪的效果,但是在动画或游戏中,我们经常可以看到导弹发射后的弹道是不规则的,扭扭曲曲的飞行,然后击中目标。
这期我们就讲一下不规则路径飞行的逻辑,在游戏中是如何实现的。
逻辑原理
首先迎面走来的是初级的散射效果原理图,在发射点和目标点之间有一个散射经过点,重点来了:**利用三维空间中球形公式,给定球心,随机返回球面上一点。**然后让导弹经过随机点再击打目标,就会形成随机散射的效果。
多点也是一样的道理,把路径点经过换算之后再赋值导弹路径点,然后形成不规则散射的效果。
这里可以发现,导弹的路径是折线效果,按标准应该是曲线效果。两者的区别就在于导弹在两点之间的过渡函数,折线是平滑过渡,曲线是贝塞尔曲线过渡,选的过渡函数不同实现的效果也不一样。由于贝塞尔曲线过渡较为复杂,这里就用平滑过渡演示原理
代码实现
导弹自身脚本
这里将散射的范围用变量表示,实现可控的效果,想大范围就大范围、想小范围就小范围。将脚本挂载到导弹的预制体上之后给相应的变量赋值,例如:散射半径、爆炸特效、子弹移动速度,其他变量通过外部脚本赋值。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpherePoint : MonoBehaviour
{
[Header("散射半径")]
public float radius;
public GameObject FX;//爆炸特效
public Transform endPoint; // 目标点
public List<Transform> wayPoints; // 中间点列表
public float speed = 10f; // 子弹移动速度
public int currentWaypointIndex = 0; // 当前处理的中间点索引
public Vector3 currentTargetPosition; // 当前目标位置
// Start is called before the first frame update
void Start()
{
if (wayPoints.Count > 0)
{
currentTargetPosition = GetRandomPointOnSphere(wayPoints[0].position, radius);
}
else
{
currentTargetPosition = endPoint.position;
}
}
// Update is called once per frame
void Update()
{
BulletMovement(transform);
}
/// <summary>
/// 随机获取中间点周围的散射经过点
/// </summary>
/// <param name="center">中间点坐标</param>
/// <param name="r">散射半径</param>
/// <returns></returns>
public static Vector3 GetRandomPointOnSphere(Vector3 center, float r)
{
// 生成随机的经度和纬度
float u = UnityEngine.Random.value * 2 * Mathf.PI; // 经度 [0, 2*PI]
float v = UnityEngine.Random.value * Mathf.PI; // 纬度 [0, PI]
// 将球坐标转换为笛卡尔坐标
float x = center.x + r * Mathf.Sin(v) * Mathf.Cos(u);
float y = center.y + r * Mathf.Sin(v) * Mathf.Sin(u);
float z = center.z + r * Mathf.Cos(v);
//返回指定球心的球面上随机一点
return new Vector3(x, y, z);
}
private void BulletMovement(Transform bulletTran)
{
// 子弹朝向当前目标位置
bulletTran.LookAt(currentTargetPosition);
bulletTran.position += bulletTran.forward * speed * Time.deltaTime; //向前移动
// 检查子弹是否到达当前目标位置
if (Vector3.Distance(bulletTran.position, currentTargetPosition) < 0.1f)
{
// 如果当前点不是最后一个中间点,则更新下一个目标位置为下一个中间点
if (currentWaypointIndex < wayPoints.Count)
{
currentWaypointIndex++;
if (currentWaypointIndex < wayPoints.Count)
{
currentTargetPosition = GetRandomPointOnSphere(wayPoints[currentWaypointIndex].position, radius);
}
else
{
currentTargetPosition = endPoint.position; // 最后一个中间点后,目标位置是终点
}
}
// 如果已经到达终点,可以选择销毁子弹或其他操作
else if (currentTargetPosition == endPoint.position)
{
GameObject tempFX = Instantiate(FX, bulletTran.position, bulletTran.rotation); //生成一个爆炸特效 并给予位置和旋转信息
Destroy(gameObject);//销毁自己
Destroy(tempFX, 0.3f);//销毁爆炸效果
currentWaypointIndex = 0;//重置路径索引
}
}
}
}
外部控制脚本
将导弹的击打目标和散射路径点通过脚本告诉导弹。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InstantiateBullet : MonoBehaviour
{
public GameObject bullet;//导弹预制体
public Vector3 startPoint; // 导弹出生发射点
public Quaternion missileRotation;//导弹出生时方向
public Transform endPoint; // 目标点
public List<Transform> wayPoints; // 中间点列表
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//鼠标点击左键发射导弹
if (Input.GetMouseButtonDown(0))
{
GameObject bu = Instantiate(bullet, startPoint, missileRotation);
bu.GetComponent<SpherePoint>().endPoint = endPoint;
bu.GetComponent<SpherePoint>().wayPoints = wayPoints;
}
}
}
外部脚本我挂载到了Main Camera
相机上。
应用效果
先看个正面的:
再来个侧面的:
好了,结束。
结语
学会后要多尝试,变成自己的东西,为己所用,赶快自己尝试下吧。有什么问题可以评论区或私信留言,下期见,拜拜。