写的时候主要是参考unity商店中的物理坦克
坦克移动主要是利用刚体的力与旋转摩擦力控制移动和旋转
坦克悬挂系统
当物体一档障碍物是出现一定的物理弹性
轮子的组件
绑定轮子,使轮子限定只能往Y轴旋转
悬挂的组件
这里的Connrenct Body 加的是坦克的主体
坦克移动
大概思想是:
通过给每个轮子添加力,使坦克移动
通过设置旋转摩擦力,使坦克转向
通过设置最大的旋转的角速度,这是最终坦克的运行速度
移动:按键获取获取垂直轴的数值,同时把所有轮子的旋转摩檫力调为0,控制加速。
转向:
1.原地转向(急转弯), 调节左边或者右边轮子摩檫力适当增加 ,只获取水平轴时执行
2.在运行中转向, 调节左边或者右边轮子摩檫力适当增加 ,当同时获取水平轴、垂直轴时执行
可能比较麻烦的是,在控制转向的时候,给轮子缓慢添加的摩檫力
private Rigidbody thisRigidbody;
private bool isLeft; //判断受否左边
public float Turn_Brake_Drag = 100; //旋转摩檫力
private float Torque = 2000; //添加的力
private float L_Brake_Drag; //左边摩檫力
private float R_Brake_Drag; //右边摩檫力
private float h; //水平
private float v; //竖直
private float limitTurn = 0.5f; //限制原地旋转的条件
private float normalDrag = 0; //正常摩檫力
private float Lost_Drag_Rate = 0.65F; //
private float L_Speed_Rate; //左边的旋转率
private float R_Speed_Rate; //右边的旋转率
public float Acceleration_Time = 4.0f; //加速时间
public float Deceleration_Time = 2.0f; //减速时间
private float leftForwardRate;
private float leftBackwardRate;
private float rightForwardRate;
private float rightBackwardRate;
private float acceleRate = 0;
private float deceleRate = 0;
private float MaxSpeed = 30; //限制的最大速度
private bool leftTurn = false;
private bool rightTurn = false;
void Start()
{
acceleRate = 1.0f / Acceleration_Time;
deceleRate = 1.0f / Deceleration_Time;
thisRigidbody = transform.GetComponent<Rigidbody>();
if (transform.localEulerAngles.z == 0.0f)
{
isLeft = true; // Left
}
else
{
isLeft = false; // Right
}
thisRigidbody.maxAngularVelocity = MaxSpeed;
}
void LateUpdate()
{
Acceleration_Mode();
}
void Acceleration_Mode()
{
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
if (Input.GetKey(KeyCode.A))
{
leftTurn = true;
}
else
{
leftTurn = false;
}
if (Input.GetKey(KeyCode.D))
{
rightTurn = true;
}
else
{
rightTurn = false;
}
if (v > 0) //前进
{
SetTorque(-Torque, Torque);
}
if (v < 0) //后退
{
SetTorque(Torque, -Torque);
}
tank_Turn_Drag();
if (h > 0) //左边
{
if (rightTurn)
{
SetTorque(-Torque, -Torque);
}
}
else if (h < 0)
{
if (leftTurn)
{
SetTorque(Torque, Torque);
}
}
}
private void tank_Turn_Drag()
{
if (h == 0 && L_Speed_Rate != 0 || h == 0 && R_Speed_Rate != 0)
{
thisRigidbody.angularDrag = normalDrag;
L_Brake_Drag = normalDrag;
R_Brake_Drag = normalDrag;
leftBackwardRate = normalDrag;
leftForwardRate = normalDrag;
leftForwardRate = normalDrag;
rightBackwardRate = normalDrag;
rightForwardRate = normalDrag;
R_Speed_Rate = normalDrag;
L_Speed_Rate = normalDrag;
return;
}
if (v == 0) return;
if (h > 0)
{
if (Input.GetKey(KeyCode.D))
{
rightForwardRate = Calculate_Speed_Rate(rightForwardRate, h);
R_Speed_Rate = rightForwardRate;
}
else
{
rightBackwardRate = Calculate_Speed_Rate(rightBackwardRate, 0.0f);
R_Speed_Rate = rightBackwardRate;
}
}
else if (h < 0)
{
if (Input.GetKey(KeyCode.A))
{
leftForwardRate = Calculate_Speed_Rate(leftForwardRate, h);
L_Speed_Rate = leftForwardRate;
}
else
{
leftBackwardRate = Calculate_Speed_Rate(leftForwardRate, 0.0f);
L_Speed_Rate = leftBackwardRate;
}
}
}
private void SetTorque(float Left, float Right)
{
if (isLeft)
{
L_Brake_Drag = (Lost_Drag_Rate * L_Speed_Rate) * Turn_Brake_Drag;
thisRigidbody.angularDrag = L_Brake_Drag;
thisRigidbody.AddRelativeTorque(0, Left, 0);
}
else
{
R_Brake_Drag = (Lost_Drag_Rate * R_Speed_Rate) * Turn_Brake_Drag;
thisRigidbody.angularDrag = R_Brake_Drag;
thisRigidbody.AddRelativeTorque(0, Right, 0);
}
}
float Calculate_Speed_Rate(float currentRate, float inputRate)
{
float Target_Rate = Mathf.Abs(inputRate);
if (currentRate < Target_Rate)
{
currentRate = Mathf.MoveTowards(currentRate, Target_Rate, acceleRate * Time.deltaTime);
}
else if (currentRate > Target_Rate)
{
currentRate = Mathf.MoveTowards(currentRate, Target_Rate, deceleRate * Time.deltaTime);
}
return currentRate;
}
}
履带的移动
这点的履带控制比较恶心,需要拖动不少的对象上去。
如果是,只想让履带移动,这比较简单,就是无线滚动地图的思想。
具体点是,
悬着一个轮子作为参照物,计算出这个轮子的周长。
因为一个履带的宽度都相同,由轮子的周长/履带的宽度=单率带片的旋转角度。
如果要移动,
获取(当前的旋转角度与上次的旋转角度的插值)/单率带片的旋转角度=旋转率
所以履带的的位置使用 Vector3.Lerp,这百分率=旋转率;
履带要有物理的效果主要是算Y轴的高度,具体操作看代码
怎么添加脚本?是给每个履带添加这个脚本,给履带的总父级添加脚本,需要选择Parent模式。
Static_Track 需要添加脚本,且类型选着Parent
这里有四种模式,
None,一般的履带移动,履带不会有弹性
Parent,控制履带的移动
Anchor,以一个参照点为中心点,履带自动计算从而有弹性
Dynamic,是为了辅助Anchor模式的履带,防止轮子的高度,超过履带的高度
public WheelType wheelType;
private Vector3 invisiblePos; //初始位置
private ScorllPos rearScript;
private ScorllPos frontScript;
private float time;
private float invisibleAngY; //初始的Y轴旋转
[HideInInspector]
public static float Delta_Ang_L; //左边轮子的旋转差值
[HideInInspector]
public static float Delta_Ang_R; //右边轮子的旋转差值
//parent 参照物
public ScorllPos parentScript;
public Transform wheel_Left; //参照物 左轮胎
public Transform wheel_Right; //参照物 右轮胎
[HideInInspector]
public static float Reference_Radius_L; //左一履带需要旋转多少度数
[HideInInspector]
public static float Reference_Radius_R; //右一履带需要旋转多少度数
private float leftPreviousAng; //左边上一次的旋转速度
private float rightPreviousAng; //右边上一次的旋转速度
public ScorllPos[] allWheels; //所有轮子
//None
public Transform Rear_Transform;
public Transform Front_Transform;
//Anchor
public Transform Anchor_Transform;
private float initialPosX;
private float anchorInitialPosX;
//一个履带需要旋转多少度数
private float leftAngRate;
private float rightAngRate;
//单个履带的长度
private float Length = .28f;
//移动的百分比
private float Rate_L;
private float Rate_R;
private float invertValue; //下面的履带 = 0.0f, 上面的履带 = 180.0f.
private bool isLeft;
private float halfLength;
private void Start()
{
invisibleAngY = transform.localEulerAngles.y; //初始的旋转角度
invisiblePos = transform.localPosition; //初始的位置
if (invisiblePos.y > 0.0f) //判断左右的轮子
{
isLeft = true; // Left
}
else
{
isLeft = false; // Right
}
//限制履带的旋转
if (invisibleAngY > 90.0f && invisibleAngY < 270.0f)
{
invertValue = 180.0f;
}
else
{
invertValue = 0.0f;
}
switch (wheelType)
{
case WheelType.Parent:
Parent_Settings();
break;
case WheelType.None:
InitSettings();
break;
case WheelType.Anchor:
InitSettings();
InitAnchor();
break;
case WheelType.Dynamic:
InitSettings();
InitAnchor();
break;
}
}
private void InitSettings()
{
rearScript = Rear_Transform.GetComponent<ScorllPos>(); //获取后片的位置
parentScript = transform.parent.parent.GetComponent<ScorllPos>(); //获取父级的脚本
}
private void InitAnchor()
{
halfLength = parentScript.Length * 0.5f;
frontScript = Front_Transform.GetComponent<ScorllPos>();
initialPosX = transform.localPosition.x;
anchorInitialPosX = Anchor_Transform.localPosition.x;
}
private void Update()
{
if (wheelType == WheelType.Parent)
{
Speed_Control();
}
}
private void LateUpdate()
{
switch (wheelType)
{
case WheelType.None:
Slide_Control();
break;
case WheelType.Anchor:
Anchor_Control();
Slide_Control();
break;
case WheelType.Dynamic:
Dynamic_Control();
Slide_Control();
break;
}
}
void Parent_Settings()
{
Reference_Radius_L = wheel_Left.GetComponent<MeshFilter>().mesh.bounds.extents.x; //获取轮子一半的大小
leftAngRate = 360.0f / ((2.0f * Mathf.PI * Reference_Radius_L) / Length); //计算360需要多少个履带,一个履带需要旋转多少度数
Reference_Radius_R = wheel_Right.GetComponent<MeshFilter>().mesh.bounds.extents.x; //获取轮子一半的大小
rightAngRate = 360.0f / ((2.0f * 3.14f * Reference_Radius_R) / Length);
allWheels = GetComponentsInChildren<ScorllPos>();
}
/// <summary>
/// 滑动控制
/// </summary>
private void Slide_Control()
{
if (isLeft)
{
if (rearScript != null)
{
transform.localPosition = Vector3.Lerp(invisiblePos, rearScript.invisiblePos, parentScript.Rate_L);
transform.localRotation = Quaternion.Euler(new Vector3(0.0f, Mathf.LerpAngle(invisibleAngY, rearScript.invisibleAngY, parentScript.Rate_L), 270));
}
}
else
{
if (rearScript != null)
{
transform.localPosition = Vector3.Lerp(invisiblePos, rearScript.invisiblePos, parentScript.Rate_R);
transform.localRotation = Quaternion.Euler(new Vector3(0.0f, Mathf.LerpAngle(invisibleAngY, rearScript.invisibleAngY, parentScript.Rate_R), 270));
}
}
}
/// <summary>
/// 速度控制
/// </summary>
void Speed_Control()
{
// Left
float currentAng = wheel_Left.localEulerAngles.y; // 参照轮子的旋转角度
Delta_Ang_L = Mathf.DeltaAngle(currentAng, leftPreviousAng); // 计算出旋转的产值(与上一次的旋转)
Rate_L = Calculate_Rate(Rate_L, Delta_Ang_L, leftAngRate); //计算移动的百分比
leftPreviousAng = currentAng;
// Right
currentAng = wheel_Right.localEulerAngles.y;
Delta_Ang_R = Mathf.DeltaAngle(currentAng, rightPreviousAng);
Rate_R = Calculate_Rate(Rate_R, Delta_Ang_R, rightAngRate);
rightPreviousAng = currentAng;
}
/// <summary>
/// 计算百分比
/// </summary>
/// <param name="rate"></param>
/// <param name="diffAng"></param>
/// <param name="angRate"></param>
/// <returns></returns>
private float Calculate_Rate(float rate, float diffAng, float angRate)
{
rate += (diffAng / angRate) % 1.0f;
if (rate > 1.0f)
{
rate -= 1.0f;
}
else if (rate < 0.0f)
{
rate += 1.0f;
}
return rate;
}
private void Anchor_Control()
{
// 设置当前履带的的高度
//initialPosX 是当前的履带的高度
//Anchor_Transform.localPosition.x 动态获取高度-初始的中心点的高度
invisiblePos.x = initialPosX + (Anchor_Transform.localPosition.x - anchorInitialPosX); // Axis X = hight.
float tempRad = rearScript.invisibleAngY * Mathf.Deg2Rad;
Vector3 rearEndPos = rearScript.invisiblePos + new Vector3(halfLength * Mathf.Sin(tempRad), 0.0f, halfLength * Mathf.Cos(tempRad));
tempRad = frontScript.invisibleAngY * Mathf.Deg2Rad;
Vector3 frontEndPos = frontScript.invisiblePos - new Vector3(halfLength * Mathf.Sin(tempRad), 0.0f, halfLength * Mathf.Cos(tempRad));
invisibleAngY = Mathf.Rad2Deg * Mathf.Atan((frontEndPos.x - rearEndPos.x) / (frontEndPos.z - rearEndPos.z)) + invertValue;
}
/// <summary>
/// 动态计算履带的坐标属性
/// </summary>
private void Dynamic_Control()
{
// 计算终点位置。(后片的弧度)
float tempRad = rearScript.invisibleAngY * Mathf.Deg2Rad;
//计算履带的幅度
//通过旋转的轴获取判断旋转的方向
//Mathf.Sin 计算高度
//Mathf.Cos 计算Z轴
Vector3 rearEndPos = rearScript.invisiblePos + new Vector3(halfLength * Mathf.Sin(tempRad), 0.0f, halfLength * Mathf.Cos(tempRad));
//后片的结束位置(前片的弧度)
tempRad = frontScript.invisibleAngY * Mathf.Deg2Rad;
// 计算前片的位置
Vector3 frontEndPos = frontScript.invisiblePos - new Vector3(halfLength * Mathf.Sin(tempRad), 0.0f, halfLength * Mathf.Cos(tempRad));
// 设置坐标
invisiblePos = Vector3.Lerp(rearEndPos, frontEndPos, 0.5f);
// 设置旋转的角度
invisibleAngY = Mathf.Rad2Deg * Mathf.Atan((frontEndPos.x - rearEndPos.x) / (frontEndPos.z - rearEndPos.z)) + invertValue;
}
}
取消层级之间的碰撞
因为这个坦克添加了许多的物理材质,所以这步是必要的
void Layer_Collision_Settings()
{
// Layer Collision Settings.
// Layer8 >> (对于使用GUI_Texture的旧系统)
// Layer9 >> 车轮。
// Layer10 >> . 悬架和履带加固。
// Layer11 >>
for (int i = 0; i <= 11; i++)
{
Physics.IgnoreLayerCollision(9, i, false);
Physics.IgnoreLayerCollision(11, i, false);
}
Physics.IgnoreLayerCollision(9, 9, true);
Physics.IgnoreLayerCollision(9, 11, true);
for (int i = 0; i <= 11; i++)
{
Physics.IgnoreLayerCollision(10, i, true);
}
}
静态轮子
这中轮子,只需要模拟旋转。
主要思想是,静态轮子的半径与参照轮子的半径计算出旋转率。
旋转=上面算的旋转插值*旋转率
private bool isLeft;
private bool limit = false;
public float rate; //这是旋转率
public float thisRadius; //轮子的半径
private Vector3 currentAng;
private void Update()
{
if (!limit)
{
computeRate();
limit = true;
}
Work_with_Static_Track();
}
/// <summary>
/// 计算旋转率
/// </summary>
private void computeRate()
{
if (transform.localEulerAngles.z == 0.0f)
{
isLeft = true; // Left
}
else
{
isLeft = false; // Right
}
thisRadius = GetComponent<MeshFilter>().mesh.bounds.extents.x; //获取半径
if (isLeft)
{
rate = ScorllPos.Reference_Radius_L / thisRadius;
}
else
{
rate = ScorllPos.Reference_Radius_R / thisRadius;
}
}
/// <summary>
/// 静态轮子滚动
/// </summary>
private void Work_with_Static_Track()
{
currentAng = transform.localEulerAngles;
if (isLeft)
{
currentAng.y -= ScorllPos.Delta_Ang_L * rate;
}
else
{
currentAng.y -= ScorllPos.Delta_Ang_L * rate;
}
transform.localEulerAngles = currentAng;
}