3D动态路障生成
- 介绍
- 设计实现
- 1.路面创建
- 2.空物体的创建
- 3.Create.cs脚本创建
- 总结
介绍
上一篇文章介绍了Mathf.Lerp的底层实现原理,这里介绍一下跑酷类游戏的动态路障生成是如何实现的。
动态路障其实比较好生成,但是难点在哪里,如果都是平面或者都是没有转弯的话还是比较好实现的,如果动态路障的实现遇到了有上坡下坡或者有转弯的地方我们如何去处理这些拐角点和上下坡的旋转和位置呢?
设计实现
简单说一下设计思路
路面的终点为坐标的原点(0,0,0),把我们的路面朝向Z轴的方向,也就是说我们生成路障时,只需要采用Z轴的深度即可。
我们在终点到起点之间创建多个空物体,这个空物体用于判断创建的路障在哪两个空物体之间,然后采用Lerp来进行插值运算,使我们在拐角处和有坡度的位置生成正确旋转与位置的路障。
生成路障时,自定义参数距离范围随机生成路障,那么它的旋转角度和高度我们可以用上述两个空物体差值运算得到。
1.路面创建
下面是我简单的用Plane拼接出来的路面终点的位置为Unity的(0,0,0)
这里我全成长度为300,也就是说图中的起点位置为(x,x,-300)
2.空物体的创建
在Road路面里面创建一个waypoints的空物体,将我们后面创建的空物体都放在里面,
我们可以在整个路面上创建无数个空物体,但是空物体的Z轴旋转和Y轴的高度是要跟路面保持一致的,因为我们后面要用这个空物体的position和rotation来进行插值运算得到路障的位置和旋转角度,这里空物体创建的越多,路障贴合路面的坡度和旋转就更加精准。最后创建一个Waypoints的脚本挂载到waypoints上去获取我们所有创建的空物体,可以用OnDrawGizmos()去绘制出来方便我们看。
创建的位置如下
这里可以看到我把每一个点的Z轴旋转角度都贴合了路面
脚本如下:
[ExecuteInEditMode]
public class waypoints : MonoBehaviour {
public Transform[] points;
void OnDrawGizmos()
{
for (int i = 0; i < points.Length; i++)
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(points[i].transform.position, 5);
}
}
}
3.Create.cs脚本创建
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Create : MonoBehaviour {
/// <summary>
/// 路障物体数组
/// </summary>
public GameObject[] obstacles;
/// <summary>
/// 路障在道路上出现的开始位置
/// </summary>
public float startLength = 10;
/// <summary>
/// 路障距上一个路障的最小距离
/// </summary>
public float minLength = 10;
/// <summary>
/// 路障距上一个路障的最大距离
/// </summary>
public float maxLength = 20;
/// <summary>
/// 与路面相贴合的路线上的脚本组件
/// </summary>
private waypoints wayPoints;
void Awake()
{
wayPoints = GameObject.Find("waypoints").GetComponent<waypoints>();
}
// Use this for initialization
void Start()
{
//创建路障
GenerateObstacle();
}
/// <summary>
/// 创建路障
/// </summary>
void GenerateObstacle()
{
//当前道路在场景中的起始Z坐标
float startZ = transform.position.z - 300;
//当前道路在场景中的结束Z坐标
float endZ = transform.position.z;
//将要产生路障的Z坐标
float z = startZ + startLength;
while (true)
{
//每隔多少米的距离产生一个路障
z += Random.Range(minLength, maxLength);
//如果将要产生路障的位置超出了这条道路则退出路障产生循环,否则产生路障
if (z > endZ)
{
break;
}
else
{
//方法计算路障位置坐标
Vector3 position = GetWayPos(z);
//方法计算路障旋转坐标
Vector3 rotation = GetWayRotate(z);
//产生一个从路障数组里取路障的随机序数
int obsIndex = Random.Range(0, obstacles.Length);
//实例化路障
Instantiate(obstacles[obsIndex], position, Quaternion.Euler(rotation.x, rotation.y, rotation.z));
}
}
}
/// <summary>
/// 获取转折点的集合索引值
/// </summary>
/// <param name="z"></param>
/// <returns></returns>
int GetPointIndex(float z)
{
//在道路上设置的转折点的集合
Transform[] points = wayPoints.points;
//转折点在集合中的序数号
int index = 0;
for (int i = 0; i < points.Length - 1; i++)
{
//根据要插入路障的Z值在集合中寻找在哪两个点之间,找到后记下序数号
if (z >= points[i].position.z && z <= points[i + 1].position.z)
{
index = i;
break;
}
}
return index;
}
Vector3 GetWayPos(float z)
{
int index = GetPointIndex(z);
//使用Lerp函数计算出插入路障处的空间坐标值
return Vector3.Lerp(wayPoints.points[index + 1].position, wayPoints.points[index].position, (z - wayPoints.points[index + 1].position.z) / (wayPoints.points[index].position.z - wayPoints.points[index + 1].position.z));
}
Vector3 GetWayRotate(float z)
{
int index = GetPointIndex(z);
return Vector3.Lerp(wayPoints.points[index + 1].eulerAngles, wayPoints.points[index].eulerAngles, (z - wayPoints.points[index + 1].position.z) / (wayPoints.points[index].position.z - wayPoints.points[index + 1].position.z));
}
}
创建完成结果如下:
总结
本片文章主要讲解Mathf.Lerp()的用法,如果有不明白的可以看我上一篇文章