致力于赛车性能算法已有多时,在确定赛车最终版本之时,做下总结。
文章还是不会非常详尽,点到为指,不想太多利用工作时间。
在制作前,必须先了解真实车辆的原理:
车辆分前轮驱动,后轮驱动和四驱动。动力由引擎提供,反应的力到轮胎上,因此产生转数,即RPM。
引擎的功率可以由RPM得到公式为 : RPM = 引擎功率×60/2×pi , 这些都是模拟,只为了更好的为下面的动作服务。还有大众关心的“漂移” ,所谓 漂移就是,在后驱车辆中,前轮方向旋转大角度,地面给于一定向心力,同时后轮又给予更多动力,导致“漂移”动作。
基于上面原理,就更容易理解下面的文章。
我先说明,有非常多种方法来开发一款赛车。本人赛车版本经过无数多种方法,无数次改版后最终选了个大众接受的。这些方法,容我一个个道来。
首先,你要明白,车辆不可能整个套一个外壳,原因是在接触地面时,对车辆所使的力不可能达到你预期的目标,引起,必须在车辆轮胎以上做外壳碰撞,轮胎以下就需要有力来支持它始终保持不掉下来。
Unity3d里有个很神奇的东西 叫WheelCollider , 它是专门模拟轮胎支持力和轮胎转数,以及轮胎横向力,前进力,以及 悬架避震系统。
这个东西非常方便,只要你把这个东西套在4个轮胎上,调试下他的forwardFriction 和sidewaysFriction
达到你想要的效果,然后对驱动轮的motorTorque进行赋值,你的车辆就能动了。
记得你需要无数次调试 前进摩擦力和横向摩擦力 。 至于悬架系统在你需要时也可以改变其值。还有,这两个摩擦力,是一个由低到高,再由高到稳定的一条曲线。
这个WheelCollider非常好用,曾一度沉迷于此。但后来发现,他有很多不合理的地方。想要得到最好的效果,还是抛弃了他。
为了支持车辆的碰撞外壳不接触地面,必须写一个悬架动态支持力,在4个轮胎位置,支持整辆车悬浮于地面之上。
关于这个悬架动态支持力在这奉献下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
void SuspensionHoldForce() { float fullCompressionSpringForce
= this .rigidbody.mass
* 0.25f * 2.0f * -Physics.gravity.y; this .OnGround
= true ; foreach (
GameObject item in FwheelModels
) { RaycastHit
hit; bool onGround
= Physics.Raycast( item.transform.parent.position , -item.transform.parent.InverseTransformDirection(Vector3.up),
out hit,
this .suspensionTravel
+ this .radius); if (onGround
&& hit.collider.isTrigger) { onGround
= false ; float dist
= this .suspensionTravel
+ this .radius; RaycastHit[]
hits = Physics.RaycastAll( item.transform.parent.position , -item.transform.parent.InverseTransformDirection(Vector3.up) ,
this .suspensionTravel
+ this .radius
); foreach (RaycastHit
test in hits) { if (!test.collider.isTrigger
&& test.distance <= dist) { hit
= test; onGround
= true ; dist
= test.distance; } } } if (
onGround ) { Vector3
wheelVelo = this .rigidbody.GetPointVelocity
(item.transform.parent.position); Vector3
localVelo = transform.InverseTransformDirection (wheelVelo); Vector3
groundNormal = transform.InverseTransformDirection (hit.normal); float damperForce
= Vector3.Dot(localVelo, groundNormal) * 5000f; float compression
= 1.0f - ((hit.distance - radius) / suspensionTravel); Vector3
springForce = ( fullCompressionSpringForce*compression - damperForce ) * item.transform.parent.InverseTransformDirection(Vector3.up); springForce.z
= springForce.x = 0f; this .rigidbody.AddForceAtPosition(
springForce , item.transform.parent.position ); } else { this .OnGround
= false ; } } foreach (
GameObject item in BwheelModels
) { RaycastHit
hit; bool onGround
= Physics.Raycast( item.transform.parent.position, -item.transform.parent.InverseTransformDirection(Vector3.up),
out hit,
this .suspensionTravel
+ this .radius); if (onGround
&& hit.collider.isTrigger) { onGround
= false ; float dist
= this .suspensionTravel
+ this .radius; RaycastHit[]
hits = Physics.RaycastAll( item.transform.parent.position, -item.transform.parent.InverseTransformDirection(Vector3.up) ,
this .suspensionTravel
+ this .radius
); foreach (RaycastHit
test in hits) { if (!test.collider.isTrigger
&& test.distance <= dist) { hit
= test; onGround
= true ; dist
= test.distance; } } } if (
onGround ) { Vector3
wheelVelo = this .rigidbody.GetPointVelocity
(item.transform.parent.position); Vector3
localVelo = transform.InverseTransformDirection (wheelVelo); Vector3
groundNormal = transform.InverseTransformDirection (hit.normal); float damperForce
= Vector3.Dot(localVelo, groundNormal) * 5000f; float compression
= 1.0f - ( ( hit.distance - radius ) / suspensionTravel ); Vector3
springForce = ( fullCompressionSpringForce*compression - damperForce ) * item.transform.parent.InverseTransformDirection(Vector3.up); springForce.z
= springForce.x = 0f; this .rigidbody.AddForceAtPosition(
springForce , item.transform.parent.position ); } else { this .OnGround
= false ; } } } |
这里也有2种方法:一个方向是真实车辆行驶轨迹,另一个是模拟型车辆轨迹。
前者的方法是 , 将动力点放在车辆驱动轮上,例如后轮。用rigidbody的AddForceAtPosition可以做到,前轮只需要提供横向力就可以实现转弯的轨迹。但别看说说这么容易,这里面还涉及非常多的数值和曲线问题。在提供车辆动力时,你需要一条曲线,以致车辆不会匀加速,因为这样很不真实,还有在前轮横向力中,你必需是条由0到最高点,然后下降到平衡点的曲线。这样你的赛车才显得更真实。这些都需要用到几个数学知识。
后者,是用算法来模拟的一种车辆轨迹。这个算法所有作用力作用在车辆的中心点。
转弯轨迹,我是用转弯半径来表示,使得车辆在转弯时有相当的真实性,必须改变车辆转弯速度。当然,用到了些数学知识。代码奉献下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#region
计算转弯角度 void Steering(
bool canSteer
, Vector3 relativeVelocity ) { if (
canSteer && this .OnGround
) { if (
this .shiftthrottle
== 1 ) { this .transform.RotateAround(
this .transform.TransformPoint(
( this .FwheelModels[0].transform.localPosition
+ this .FwheelModels[1].transform.localPosition)
* 0.5f ) , this .transform.up
, this .rigidbody.velocity.magnitude
*2f* this .steeringInput
* Time.deltaTime * 2f ); //~
this.rigidbody.AddForceAtPosition( this.FwheelModels[0].transform.TransformDirection(Vector3.right*this.steeringInput) * 3f * this.rigidbody.mass, this.FwheelModels[0].transform.position); //~
this.rigidbody.AddForceAtPosition( this.FwheelModels[1].transform.TransformDirection(Vector3.right*this.steeringInput) * 3f * this.rigidbody.mass, this.FwheelModels[1].transform.position); return ; } if (
this .throttle
* this .transform.InverseTransformDirection( this .rigidbody.velocity).z
< 0 ) return ; float turnRadius
= 3.0f / Mathf.Sin( (90f - this .steering)
* Mathf.Deg2Rad ); float minMaxTurn
= EvaluateSpeedToTurn( this .rigidbody.velocity.magnitude); float turnSpeed
= Mathf.Clamp(relativeVelocity.z / turnRadius, -minMaxTurn / 10, minMaxTurn / 10); this .transform.RotateAround(
this .transform.position
+ this .transform.right
* turnRadius * this .steeringInput
, transform.up , turnSpeed * Mathf.Rad2Deg * Time.deltaTime * this .steeringInput
); //~
Vector3 debugStartPoint = transform.position + transform.right * turnRadius * this.steeringInput; //~
Vector3 debugEndPoint = debugStartPoint + Vector3.up * 5f; //~
Debug.DrawLine(debugStartPoint, debugEndPoint, Color.red); } } float EvaluateSpeedToTurn(
float speed
) { if (speed
> this .topSpeed
/ 2) return minimumTurn; float speedIndex
= 1 - ( speed / ( this .topSpeed
/ 2 ) ); return minimumTurn
+ speedIndex * (maximumTurn - minimumTurn); } #endregion |
这个模拟车辆轨迹,不能达到漂移的性能,但我加了一个滑动比例计算的算法,用车辆横向移动速度,和前进速度,的比例来确定,该车辆是否处于漂移状态,如处于,则启动漂移滑动程序。当然,我的赛车是很自然的,不做做。至于轮胎痕迹,就是判断是否触底后,在该点生成轮胎痕迹gameobject,如此而已。
最后,再介绍下,所有车辆都需要模拟的,行驶时,轮胎随速度旋转这个关系到车辆看起来真实性的东西。其实非常简单。不多说,发代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#region
轮胎滚动与旋转模拟 void WheelRoll() { float averageAngularVelo
= ( this .rigidbody.GetPointVelocity( this .BwheelModels[0].transform.parent.position).magnitude
+ this .rigidbody.GetPointVelocity( this .BwheelModels[0].transform.parent.position).magnitude
)/2f; float engineAngularVelo
= averageAngularVelo * 3f; float rpm
= engineAngularVelo * (60.0f/(2*Mathf.PI)) * ( this .transform.InverseTransformDirection( this .rigidbody.velocity).z
> 0f ? 1f : -1f ); //~
Debug.Log(this.transform.InverseTransformDirection(this.rigidbody.velocity).z); FwheelModels[0].transform.rotation
= FwheelModels[0].transform.parent.rotation * Quaternion.Euler (RotationValue,
this .steering
, 0); //旋转 FwheelModels[1].transform.rotation
= FwheelModels[1].transform.parent.rotation * Quaternion.Euler (RotationValue,
this .steering
, 0); //旋转 BwheelModels[0].transform.rotation
= BwheelModels[0].transform.parent.rotation * Quaternion.Euler (RotationValue, 0, 0); //旋转 BwheelModels[1].transform.rotation
= BwheelModels[1].transform.parent.rotation * Quaternion.Euler (RotationValue, 0, 0); //旋转 RotationValue
+= rpm * ( 360f/60f ) * Time.deltaTime; } #endregion |