Unity实用案例之——“吃鸡”手雷弹道模拟

最近吃鸡游戏火啊,至今也吃了好几晚的鸡了,无奈手雷就是丢不准,从窗户丢雷丢几个弹出几个,各种误伤自己人……而别人家的手雷:
这里写图片描述
一般的游戏里手雷都是盲投,不过一般游戏也不会对弹道有这么精确的要求,尽管往敌人家里丢就好了,能不能炸到人全靠缘分。那么,如果把雷精确的从窗户丢进去呢,不不不,是如何在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,到这里,我们的手雷弹道功能基本完成啦!可以看到,由于落点和地面贴的太近,落点会出现闪烁情况,当然,这里只提供基本思路,剩下的完善与优化就交在你的手里了。今天就到这里,如果你有更好的解决方案,欢迎交流讨论!

猜你喜欢

转载自blog.csdn.net/u013917120/article/details/79212448