在Unity中模拟汽车的移动

本文主要讲述用算法来实现汽车移动,不会用到Unity的物理组件。

一、为啥不用物理组件

        用不好,或者不好用。

        (1)刚体,最开始用的计算下一帧位置+刚体,Rigidbody.MovePosition(nextPoint),会出现奇怪的汽车偏移现象,应该是计算和物理同时作用在汽车上导致的。

        (2)WheelCollider,运行之后汽车疯狂的跳动。搜到的解决方式也没解决这个问题。

        (3)需求中需要画出车辆的轨迹路线,物理方式好像解决不了,还得算!

二、高度计算

        首先给路面加上Collider,然后通过从上到下的射线得到路面的高度

    public static Vector3 RaycastRoad(Vector3 targetPoint)
    {
        Vector3 ori = new Vector3(targetPoint.x, 100, targetPoint.z);
        int layer = 1 << LayerMask.NameToLayer("Road");
        Ray ray = new Ray(ori, Vector3.down);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 1000, layer))
        {
            return hit.point;
        }
        return Vector3.zero; 
    }

三、直行移动计算

        这个最简单

    private void Update()
    {
		transform.position = Utils.RaycastRoad(transform.position + transform.forward * speed * Time.deltaTime);
	}

四、上坡移动计算

        这时候需要考虑车辆的前后旋转(暂不考虑左右旋转)。如果车辆前方是Z轴方向,上坡时车辆需要绕X轴方向旋转坡度。如下图,如果单纯用车辆的位置来计算位置,图2的情况与路面的射线交汇在R点,明显将低于正确点O,车轮会陷到地下。

        

         此时需要用到车轮的位置来计算。由于不考虑左右旋转,接下来我们取两个前车轮的中间位置和两个后车轮的中间位置来计算。

	float speed = 10f;
	Transform forwardWheel;
	Transform behindWheel;
    private void Update()
    {
		Vector3 diff = transform.forward * speed * Time.deltaTime;
		Vector3 forwardPoint = Utils.RaycastRoad(forwardWheel.position + diff);
		Vector3 behindPoint = Utils.RaycastRoad(behindWheel.position + diff);
		transform.position = (forwardPoint + behindPoint) / 2f;
	}

        这里依然会有问题,前轮和后轮的实际移动向量并不相同,也不是diff的长度,上面的代码并没有完全模拟汽车的位置。

旋转车辆

    private void RotationCar()
    {
        Vector3 forwardPoint = wheelPoints[0].transform.position;
        Vector3 behindPoint = wheelPoints[2].transform.position;
        forwardPoint = Utils.RaycastRoad(forwardPoint);
        behindPoint = Utils.RaycastRoad(behindPoint);
        if (!Mathf.Approximately(forwardPoint.y , behindPoint.y))
        {
            float sinVal = Mathf.Abs(forwardPoint.y - behindPoint.y) / (forwardPoint - behindPoint).magnitude;
            float angle = GetAngleBySinVal(sinVal);
            //Debug.LogError("sinVal:" + sinVal + "," + angle);
            if(forwardPoint.y > behindPoint.y)
            {
                transform.localEulerAngles = new Vector3(-angle, transform.localEulerAngles.y, 0);
            }
            else
            {
                transform.localEulerAngles = new Vector3(angle, transform.localEulerAngles.y, 0);
            }
        }
    }

四、转弯移动计算

(1)了解汽车车轮的轨迹,我们知道四个车轮的轨迹是四个同心圆。那么我们先计算出圆心坐标。看下图,图的上边两个轮子和圆心组成了一个直角三角形,其中一个直角边的长度是轴距L(前后轮的距离),直角三角形的一个角X是车轮偏转角度,斜边为outR,那么SinX = L / outR。

        接着可以得到,左边直角边length,两个后轮坐标相减可以得到左边直角边的向量,接着可以得到圆心坐标。

    //获取圆心坐标
    private Vector3 GetCirclePoint(float angle)
    {
        Debug.Log("GetCirclePoint:" + angle);
        float absAngle = Mathf.Abs(angle);
        float absAngle2 = (absAngle * (Mathf.PI)) / 180f;
        float r = L / Mathf.Sin(absAngle2);
        outR = r;
        float length = r * Mathf.Cos(absAngle2);
        //Debug.Log("转弯半径:" + r + ",length:" + length);
        Vector3 result = Vector3.zero;
        Vector3 point1 = wheelPoints[2].transform.position;//左后轮
        point1 = new Vector3(point1.x, 0, point1.z);
        Vector3 point2 = wheelPoints[3].transform.position;//右后轮
        point2 = new Vector3(point2.x, 0, point2.z);
        Vector3 right = (point2 - point1).normalized;
        Vector3 left = (point1 - point2).normalized;
        if (angle < 0)//右转
        {         
            result = point1 + right * length;
        }
        else if(angle > 0)
        {
            result = point2 + left * length;
        }
        return result;
    }

 接下来就是圆形里面的计算了,就好像是一个钟表,计算出当前指针所在的时刻以及下一帧所在的时刻

 (2)通过弧长,计算对应的圆心角

    //已知半径和弧长,求圆心角
    private float GetCircleCenterAngle(float l, float r)
    {
        return l * 360 / (2 * Mathf.PI * r);
    }

 (3)计算出当前角度,下一帧角度

//已知圆上的点、圆心、半径,求角度
    private float GetStartAngle(Vector3 wheelPoint, Vector3 centerPoint, float r)
    {
        //Debug.LogError("GetStartAngle:" + r);
        float sinVal = Mathf.Min(Mathf.Abs(wheelPoint.z - centerPoint.z) / r, 1f); ;
        if (Mathf.Approximately(wheelPoint.x, centerPoint.x) && Mathf.Approximately(sinVal, 1f))
        {
            return 90f;
        }
        //Debug.Log("GetStartAngle:" + sinVal);
        float angleVal = (float)System.Math.Asin(sinVal);
        float result = angleVal / Mathf.PI * 180f;
        if(wheelPoint.x > centerPoint.x)
        {
            if (wheelPoint.z >= centerPoint.z)
            {
                return result;
            }else
            {
                return -result;
            }
        }
        if(wheelPoint.x <centerPoint.x)
        {
            if(wheelPoint.z >= centerPoint.z)
            {
                return 180f - result;
            }else
            {
                return 180f + result;
            }
        }
        //Debug.Log("GetStartAngle:" + result);
        return result;
    }

 (4)通过角度计算出圆上的点(轮胎下一帧的位置)

    //通过圆心、半径、角度计算圆上的坐标点
    private Vector3 GetNextPointByAngle(Vector3 centerPoint,float radius, float startAngle, float diffAngle)
    {
        //Debug.Log("GetNextPointByAngle:" + centerPoint + "," + radius + "," + startAngle + "," + diffAngle);
        float angle = startAngle + diffAngle;
        float x1 = centerPoint.x + radius * Mathf.Cos(angle * Mathf.PI / 180f);
        float y1 = centerPoint.z + radius * Mathf.Sin(angle * Mathf.PI / 180f);
        Vector3 pos = new Vector3(x1, centerPoint.y, y1);
        return pos;
    }    

五、车轮轨迹。

轨迹就是多加几倍弧长,计算出一个个圆上的坐标。

六、完。

凑合着看吧,好多数学知识都还给老师了,想到什么公式就用什么,可能有的地方比较绕。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值