融入动画技术的交互应用
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.****.net/six_sex/article/details/72857295这个学的,我就不复述了。
效果基本就是这样:
3. 总结
其实纯物理的车子还是蛮好玩的,当你想让他像真车一样的时候,完全依靠物理引擎是有很大的困难的,比如现在制作好的车子我想达到一种弯道漂移的效果,需要死按转弯键加空格并看时机松开,一个不小心就会转过了角度,看到一种transform和物理结合的漂移方法,效果会比我这种纯物理的好很多。