最近吃鸡游戏火啊,至今也吃了好几晚的鸡了,无奈手雷就是丢不准,从窗户丢雷丢几个弹出几个,各种误伤自己人……而别人家的手雷:
一般的游戏里手雷都是盲投,不过一般游戏也不会对弹道有这么精确的要求,尽管往敌人家里丢就好了,能不能炸到人全靠缘分。那么,如果把雷精确的从窗户丢进去呢,不不不,是如何在Unity里实现手雷的轨迹,从而预判手雷落点呢,今天我们就来讨论这个问题!
一、轨迹绘制
众所周知,手雷的弹道其实是一个抛物线,扔手雷的过程,其实就是给一个物体以一定的初速度的自由落体运动,在运动过程中没有碰倒任何物体前只受重力和空气阻力。这里我们不考虑空气阻力,那么快速来写一下轨迹方程:
velocity += Vector3.down * Gravity * Time;
position += velocity * Time;
其中velocity是矢量,表示当前运动速度,变量类型为为Vector3;Gravity表示重力大小,乘以(0, -1, 0),表示重力矢量;最终得到当前手雷运动的位置Position。
OK,到这一步很简单,那么接下来的问题是:如何将这个抛物线绘制出来?
这里我们选择用网格绘制。要绘制抛物线,我们需要先对轨迹进行预模拟,记录抛物线上的点,然后将这些点连成线,再将线连面。首先,预模拟一次抛物线,并记录一定时间间隔(Interval)的点的位置:
int PointsCount = 100;
List<Vector3> Points = new List<Vector3>();for (int i = 0; i < PointsCount; i++)
{Points.Add(pos);velocity += Vector3.down * Gravity * Interval;pos += velocity * Interval;
}
然后另写一个类,用我们记录的点生成Mesh:
using System.Collections.Generic;
using UnityEngine;public class MeshData
{public Vector3 Up = Vector3.up;private Vector3[] vertices;private int[] triangles;private Vector2[] uvs;private int vertexIndex;private int triangleIndex;private float Width;private List<Vector3> Line;public MeshData(List<Vector3> line, float width){Line = line;Width = width;vertices = new Vector3[Line.Count * 2];uvs = new Vector2[Line.Count * 2];triangles = new int[(Line.Count - 1) * 6];vertexIndex = triangleIndex = 0;int length = Line.Count;for (int i = 0; i < length; i++) {vertices[vertexIndex] = Line[i] + Up * Width;vertices[vertexIndex + length] = Line[i] - Up * Width;uvs[vertexIndex] = new Vector2(i / (float)length, 0);uvs[vertexIndex + length] = new Vector2(i / (float)length, 1);if (i < length - 1) {AddTriangle(vertexIndex, vertexIndex + length + 1, vertexIndex + length);AddTriangle(vertexIndex + length + 1, vertexIndex, vertexIndex + 1);}vertexIndex++;}}public Mesh CreateMesh(){Mesh mesh = new Mesh();mesh.vertices = vertices;mesh.triangles = triangles;mesh.uv = uvs;
// mesh.RecalculateNormals();return mesh;}void AddTriangle(int a, int b, int c){triangles[triangleIndex] = a;triangles[triangleIndex + 1] = b;triangles[triangleIndex + 2] = c;triangleIndex += 3;}
}
这里点的顺序是按行来的,用AddTriangle()方法来记录三角形绘制顺序,抛物线连线会按宽度(Width)来复制一份出来,这两条线形成的面就是最终的Mesh,面的朝向可以通过修改Up来做调整,最后的法线计算可以按需求来打开注释,我这里表示不需要,去掉不必要的计算。
有了Mesh,赶快把Mesh赋值给一个MeshFilter来看看效果吧:
MeshData data = new MeshData(Points, Width);
TrackRender.mesh = data.CreateMesh();
可以看到,It looks pretty good!这里,我希望可以更清楚的对比当前网格的密度和效果,添加了一小段代码对其进行编辑器扩展:
void OnSceneGUI(){Handles.color = Color.green;for (int i = 0, length = track.Points.Count; i < length; i++){Handles.SphereHandleCap(0, track.Points[i], Quaternion.identity, 1, EventType.Repaint);}}
小球表示间隔,效果如下:
二、落点位置
现在轨迹有了,那么接下来的问题是,轨迹不可能无限延长吧,那么终点在哪,改如何获取?
这里我用一个笨办法,用射线检测来做(当然也是因为我没有想到其他更好的办法,orz),两点之间做射线,如果有碰到碰撞层(提前设好碰撞层级),就代表到了轨迹的终点。好了,有了思路,代码极其简单:
Vector3 endPos = Vector3.zero;RaycastHit hitInfo;for (int i = 0; i < PointsCount; i++){Points.Add(pos);if (i != 0 && i%RayCastSimplify == 0){Vector3 dirVec = pos - Points[i - RayCastSimplify];if (Physics.SphereCast(Points[i - RayCastSimplify], RaycastRadius, dirVec.normalized, out hitInfo, dirVec.magnitude, mask.value)){endPos = hitInfo.point;break;}}velocity += Vector3.down * Gravity * Interval;pos += velocity * Interval;}
每次记录位置时,两点之间做射线检测,如果有碰撞,则返回,并记录终点位置endPos。最后给一张合适的贴图,并在endPos再放一个合适的圈表示落点即可(我比较懒,只给了一张圆),最终效果:
OK,到这里,我们的手雷弹道功能基本完成啦!可以看到,由于落点和地面贴的太近,落点会出现闪烁情况,当然,这里只提供基本思路,剩下的完善与优化就交在你的手里了。今天就到这里,如果你有更好的解决方案,欢迎交流讨论!