天天看点

融入动画技术的交互应用

1.简介

根据《代码本色》的章节内容:向量、力、粒子系统、物理引擎,制作了一个纯物理驱动的汽车,可以在游戏场景之中进行漫游,并会随机刷新炸弹使之偏离赛道。

融入动画技术的交互应用

2.功能介绍

使用unity来制作纯物理驱动的车子,最直接的方法就是使用wheelcollider,但它实在是有一些不尽如人意的地方,这个我们后面再讲,首先我们要来谈谈物理车的构成。

车子对象包含一个模型、一个包裹住车身的collider(最简单的方法是两个boxcollider叠加在一起,要是有网格的话就直接用meshcollider)以及四个wheelcollider,并在车子上加上Rigidbody

其次,我们的车子逼真需要的功能:后轮驱动,前轮转向;轮子转动;前轮有偏转;根据地面高低有起伏效果;搭载音效;以及车痕;

接下来我们就详细介绍这些。

2.1 WheelCollider

融入动画技术的交互应用

Suspension Distance 悬挂距离 就是真实的汽车都有减震系统,这个距离就是车轮在减震时可以移动的最大距离。

Suspension Spring 通过悬挂弹簧使物体到达目标位置,Spring是弹簧力,Damper是阻尼,阻尼越大弹簧形边越困难。

Forward Friction和Sideways Friction分别是轮胎向前的摩擦力和侧向的摩擦力,下图是摩擦力曲线。

融入动画技术的交互应用

在导入wheelcollider之后会自动赋予初值,需要自己反复的调试,比如一开始加上的时候车子会莫名其妙的飞天,当时是我弹簧悬挂的力太小了;之后还会出现向左转弯时右侧轮胎起飞,我调大了侧向摩擦力极值和渐近值才解决掉问题,刚开始我还寻找是不是自己的脚本那里有错误,了解了wheelcollider属性之后才调好。

这些都解决掉之后就可以写脚本了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class carcontral : MonoBehaviour
{

    public WheelCollider WheelFL;
    public WheelCollider WheelFR;
    public WheelCollider WheelRL;
    public WheelCollider WheelRR;
    public float EngineTorgue = 600.0f;
    private float max_speed = 50;
    private float SteerAngle_lowspeed = 15;
    private float SteerAngle_highspeed = 1;
    public Transform WheelFL_transform;
    public Transform WheelFR_transform;
    public Transform WheelRL_transform;
    public Transform WheelRR_transform;
    private float init_rotationFL = 0f;
    private float init_rotationFR = 0f;
    // Use this for initialization
    public float max_braketorque=20000;
    private float current_speed;

    public float min_pitch=1.0f;
    public float max_pitch = 3.0f;
    public AudioSource sound;

    private float static_friction_coefficient = 0.4f;
    private float friction_coefficient = 0.25f;
    private float init_static_friction_coefficient;
    private float init_friction_coefficient;
    void Start()
    {
    //必须必须要调低重心,要和wheelcollider属性结合着调,否则真的很容易翻车
        Vector3 centerOfMass = GetComponent<Rigidbody>().centerOfMass;
        centerOfMass.y = -1.5f;
        GetComponent<Rigidbody>().centerOfMass = centerOfMass;

        init_static_friction_coefficient = WheelRL.forwardFriction.stiffness;
        init_friction_coefficient = WheelRL.sidewaysFriction.stiffness;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
    //后轮马达力矩控制速度的大小,如果速度小于最大速度,则马达转矩=设定的动力距乘 Input.GetAxis("Vertical");
    // Input.GetAxis("Vertical")用来获取用户输入前后(w、s或向上向下键)[-1,1];松开按键归零
    //同样的 Input.GetAxis("Horizontal")获取左右;
        GetComponent<Rigidbody>().drag = GetComponent<Rigidbody>().velocity.magnitude / 250;
        current_speed = GetComponent<Rigidbody>().velocity.magnitude;
        if(current_speed<max_speed)
        {
        WheelRL.motorTorque = EngineTorgue * Input.GetAxis("Vertical");
        WheelRR.motorTorque = EngineTorgue * Input.GetAxis("Vertical");
        }
        else
        {
         WheelRL.motorTorque = 0;
         WheelRR.motorTorque = 0;
        }

        //在低速和高速情况下前轮转动的角度有差别,低速较大,高速较小
        float steerangle = Mathf.Lerp(SteerAngle_lowspeed, SteerAngle_highspeed, GetComponent<Rigidbody>().velocity.magnitude / max_speed);
       //前轮控制方向
        WheelFL.steerAngle = steerangle * Input.GetAxis("Horizontal");
        WheelFR.steerAngle = steerangle * Input.GetAxis("Horizontal");
       //模拟手刹制动,轮子制动力矩达到最大,同时调整摩擦力、马达力矩归零
        if(Input.GetKey(KeyCode.Space))
        {
            WheelRL.motorTorque = 0;
            WheelRR.motorTorque = 0;

            WheelRL.brakeTorque = max_braketorque;
            WheelRR.brakeTorque = max_braketorque;

            //WheelFL.brakeTorque = max_braketorque;
            //WheelFR.brakeTorque = max_braketorque;


            //setstiffness(WheelFL, static_friction_coefficient, friction_coefficient);
            //setstiffness(WheelFR, static_friction_coefficient, friction_coefficient);
            setstiffness(WheelRL, static_friction_coefficient, friction_coefficient);
            setstiffness(WheelRR, static_friction_coefficient, friction_coefficient);

        }
        else
        {
            WheelRL.brakeTorque = 0;
            WheelRR.brakeTorque = 0;

            setstiffness(WheelRL, init_static_friction_coefficient, init_friction_coefficient);
            setstiffness(WheelRR, init_static_friction_coefficient, init_friction_coefficient);
        }
    }
     
    void Update()
    {
    //F键重置赛车
        if(Input.GetKey(KeyCode.F))
        {
            transform.position = new Vector3(549.9f, 48, 969.5f);
            transform.rotation = new Quaternion(0,0,0,0);
            GetComponent<Rigidbody>().velocity = new Vector3();
        }
        //根据转速,轮子转动
        init_rotationFL += WheelFL.rpm * 360 * Time.deltaTime / 60;
        init_rotationFL = Mathf.Repeat(init_rotationFL, 360);
        setrotation(WheelFL_transform, init_rotationFL,WheelFL.steerAngle);
        init_rotationFR += WheelFR.rpm * 360 * Time.deltaTime / 60;
        init_rotationFR = Mathf.Repeat(init_rotationFR, 360);
        setrotation(WheelFR_transform, init_rotationFR,WheelFR.steerAngle);
        current_speed = GetComponent<Rigidbody>().velocity.magnitude;
        WheelRL_transform.Rotate(WheelRL.rpm * 360 * Time.deltaTime / 60, 0, 0);
        WheelRR_transform.Rotate(WheelRR.rpm * 360 * Time.deltaTime / 60, 0, 0);

        setaudiopitch(current_speed);
        
    }
    //通过判断轮子是否在地面上进行轮子位置的减震调整
    void setwheelposition(Transform trans,WheelCollider wheel)
    {
        RaycastHit hit;
        bool isinground = Physics.Raycast(wheel.transform.position, -1 * wheel.transform.up, out hit, wheel.radius + wheel.suspensionDistance);
        if(isinground)
        {
            if((hit.point-wheel.transform.position).magnitude>wheel.radius)
            {
                trans.position = wheel.transform.position;
            }
            else
            {
                trans.position = hit.point + wheel.transform.up * wheel.radius;
            }
            
        }
        else
        {
            trans.position = wheel.transform.position - wheel.transform.up * wheel.suspensionDistance;
        }
    }


    void setrotation(Transform trans,float angle,float angle_y)
    {
        Vector3 a =new Vector3(angle, angle_y, 0);
        trans.localRotation = Quaternion.Euler(a);
    }

    void setstiffness(WheelCollider wheel, float static_friction_coefficient,float friction_coefficient)
    {
        WheelFrictionCurve temp = wheel.forwardFriction;
        temp.stiffness = static_friction_coefficient;
        wheel.forwardFriction = temp;

        temp = wheel.sidewaysFriction;
        temp.stiffness = friction_coefficient;
        wheel.sidewaysFriction = temp;
    }
//pitch是AudioSources属性,通过调节它来表现加速的音效
    void setaudiopitch(float current_speed)
    {
        sound.pitch = min_pitch + (current_speed / max_speed) * (max_pitch - min_pitch);
    }
}

           

对于wheelcollider来说,steerAngle(偏转角度)、motorTorque(马达力矩) 、brakeTorque (制动力矩)是我们可以在脚本中直接调用的属性,能达到我们想运动汽车的功能。

摄像机跟随:高速运动时拉远镜头,前进时位于车子后方,倒车时位于车子前方,缓冲跟随车子运动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class followcar : MonoBehaviour {
    public GameObject car;
    public float distance;
    public float h_distance;
    public float angle_buffer;
    public float height_buffer;

    public float field_view = 60;
    public float scaling = 1.5f;
    private float direction_state=0;
    // Use this for initialization
    void Start () {
    }
	
	// Update is called once per frame
	void Update () {
        float cam_height = transform.position.y;
        float car_height = car.transform.position.y+ height_buffer;
        float sub_height = Mathf.Lerp(cam_height, car_height, height_buffer * Time.deltaTime);

        float cam_angle = transform.eulerAngles.y;
        float car_angle = direction_state; 
        //避免倒车时角度大于360,导致摄像机视角一直转动;
        if (car_angle > 360)
            car_angle = car_angle%360;
        //进行线性插值
        float sub_angle = Mathf.Lerp(cam_angle, car_angle, angle_buffer * Time.deltaTime);
        if(sub_angle>180)
        {
           sub_angle=(360-sub_angle)*-1;
        }
        Quaternion carrotation = Quaternion.Euler(0, sub_angle, 0);

        transform.position = car.transform.position;
        transform.position -= carrotation * Vector3.forward * distance;
        Vector3 temp = transform.position;
        temp.y = sub_height;
        transform.position = temp;

        transform.LookAt(car.transform);
	}

    void FixedUpdate()
    {
        Vector3 v = car.GetComponent<Rigidbody>().velocity;
        float speed = v.magnitude;
        GetComponent<Camera>().fieldOfView = field_view + speed * scaling;
        float car_angley = car.transform.eulerAngles.y;
        Vector3 v_direction = car.transform.InverseTransformDirection(v);
        //判断车子的运动方向,更改摄像机朝向
        if (v_direction.z < -0.05f && Input.GetAxis("Vertical") < 0)
        {
            direction_state = car_angley + 180;
        }
        else if (v_direction.z > 0.05f && Input.GetAxis("Vertical") > 0 )
        {
            direction_state = car_angley;
        }

    }
}
           

摄像机跟随仍存在一个bug吧!因为我想达到一种缓冲跟随的效果,同时根据车子速度方向改变摄像机的朝向,从而就导致了一个问题是在车子的transform.rotation.y从-0.1f到0.1f时(或者反过来),摄像机为了跟随着角度进行变换会从-0.1f~-180° ~0.1f,给人的直观效果就是摄像机转了一整个大圈,还有待解决。

融入动画技术的交互应用

在地图内过一个随机时间在随机位置刷新地雷

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class creatmine : MonoBehaviour {
    float CreatTime = 2f;
    GameObject mines;
    // Use this for initialization
    void Start () {
		
	}

    // Update is called once per frame
    void Update()
    {
        CreatTime -= Time.deltaTime;
        if (CreatTime <= 0)
        {
            CreatTime = Random.Range(3, 10);
            GameObject obj = (GameObject)Resources.Load("mines");
            mines = Instantiate<GameObject>(obj);
            mines.transform.position = new Vector3(Random.Range(600, 1300), 46.76f, Random.Range(780, 1200));
        }
    }
}

           

mines是有碰撞检测的地雷模型,当碰到它的collision标签是Player的时候(也就是我们的车子);

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class explosion : MonoBehaviour {
    GameObject exmines;
    private GameObject car;
    // Use this for initialization
    void Start () {
		car= GameObject.Find("car");
    }
	
	// Update is called once per frame
	void Update () {
	}

    void OnCollisionEnter(Collision collision)
    {
        if(collision.collider.tag=="Player")
        {       
            GameObject obj = (GameObject)Resources.Load("explosion");
            exmines = Instantiate<GameObject>(obj);
            exmines.transform.position = this.transform.position;
            float radius = 100f;   
            float power = 600f;
            car.GetComponent<Rigidbody>().AddExplosionForce(power, exmines.transform.position, radius);
            Destroy(this.gameObject);
        }
    }
}
           

发生爆炸并给与车子一个爆炸力。然后销毁。

爆炸的粒子系统是根据https://blog.csdn.net/six_sex/article/details/72857295这个学的,我就不复述了。

效果基本就是这样:

融入动画技术的交互应用

3. 总结

其实纯物理的车子还是蛮好玩的,当你想让他像真车一样的时候,完全依靠物理引擎是有很大的困难的,比如现在制作好的车子我想达到一种弯道漂移的效果,需要死按转弯键加空格并看时机松开,一个不小心就会转过了角度,看到一种transform和物理结合的漂移方法,效果会比我这种纯物理的好很多。