Unity3D汽车控制脚本

2015年01月17日 10:18 0 点赞 0 评论 更新于 2025-11-21 14:36

本文分享一个从官网论坛收集而来且经过验证可正常使用的Unity3D汽车控制脚本。由于未找到配套的skidmarks脚本,因此将与skidmarks相关的语句都进行了屏蔽,所以很遗憾无法实现刹车印效果,不过其他部分未作改动。在此将该脚本分享出来,供感兴趣的开发者一同研究。

汽车脚本代码通常较为复杂,这主要是因为国外开发者编程功底深厚,很多功能都通过代码自动生成。实际上,有些操作可以在编辑器中完成,这样能减少不少代码量。此外,制动和变速箱部分使用了较多代码,理解这部分内容可能需要一些汽车发动机知识,笔者对此就不太理解。

若你不想深入研究,也可直接使用该脚本,以下是具体的使用方法:

  1. 将脚本直接挂载到汽车车身网格上,车身需添加Rigidbody Component,同时要有四个轮子网格作为子物体。若想实现声音效果,还需添加AudioSource Component
  2. 打开Inspector面板,选择汽车脚本,将四个轮子分别连接到对应的Transform参数上。将wheelRadius参数设置为轮子网格的大小。由于WheelCollider会自动生成,因此无需手动添加。完成这些设置后,脚本即可正常运行,后续还可根据需求添加声音和灰尘效果。

以下是脚本的源代码:

#pragma strict

// 最大转弯和制动加速度能力
var maxCornerAccel = 10.0;
var maxBrakeAccel = 10.0;

// 重心高度 - 影响转弯时的倾斜
var cogY = 0.0;

// 发动机功率区间
var minRPM = 700;
var maxRPM = 6000;

// 最大发动机扭矩
var maxTorque = 400;

// 自动变速箱换挡点
var shiftDownRPM = 2500;
var shiftUpRPM = 5500;

// 齿轮比
var gearRatios = [-2.66, 2.66, 1.78, 1.30, 1.00];
var finalDriveRatio = 3.4;

// 基本操控特性修正:
// 1.0 转向不足
// 0.0 转向过度
var handlingTendency = 0.7;

// 图形化轮子对象
var wheelFR : Transform;
var wheelFL : Transform;
var wheelBR : Transform;
var wheelBL : Transform;

// 悬架设置
var suspensionDistance = 0.3;
var springs = 1000;
var dampers = 200;
var wheelRadius = 0.45;

// 地面灰尘粒子效果
var groundDustEffect : Transform;

private var queryUserInput = true;
private var engineRPM : float;
private var steerVelo = 0.0;
private var brake = 0.0;
private var handbrake = 0.0;
private var steer = 0.0;
private var motor = 0.0;
// private var skiDTIme = 0.0;
private var onGround = false;
private var cornerSlip = 0.0;
private var driveSlip = 0.0;
private var wheelRPM : float;
private var gear = 1;
// private var skidmarks : Skidmarks;
private var wheels : WheelData[];
private var wheelY = 0.0;
private var rev = 0.0;

// 供外部脚本使用的函数
// 必要时控制汽车
//===================================================================
// 返回车辆的状态字符串
function GetStatus(gui : GUIText) {
gui.text = "v=" + (rigidbody.velocity.magnitude * 3.6).ToString("f1") + " km/h\ngear= " + gear + "\nrpm= " + engineRPM.ToString("f0");
}

// 返回车辆的控制信息字符串
function GetControlString(gui : GUIText) {
gui.text = "Use arrow keys to control the jeep,\nspace for handbrake.";
}

// 启用或禁用用户控制
function SetEnableUserInput(enableInput) {
queryUserInput = enableInput;
}

// 汽车物理模拟
//===================================================================
// 一些轮子计算数据
class WheelData {
var rotation = 0.0;
var coll : WheelCollider;
var graphic : Transform;
var maxSteerAngle = 0.0;
var lastSkidMark = -1;
var powered = false;
var handbraked = false;
var originalRotation : Quaternion;
}

function Start() {
// 初始化轮子数据
wheels = new WheelData[4];
for (var i = 0; i < 4; i++) {
wheels[i] = new WheelData();
}
wheels[0].graphic = wheelFL;
wheels[1].graphic = wheelFR;
wheels[2].graphic = wheelBL;
wheels[3].graphic = wheelBR;
wheels[0].maxSteerAngle = 30.0;
wheels[1].maxSteerAngle = 30.0;
wheels[2].powered = true;
wheels[3].powered = true;
wheels[2].handbraked = true;
wheels[3].handbraked = true;

for (var w in wheels) {
if (w.graphic == null) {
Debug.Log("You need to assign all four wheels for the car script!");
}
if (!w.graphic.transform.IsChildOf(transform)) {
Debug.Log("Wheels need to be children of the Object with the car script");
}
w.originalRotation = w.graphic.localRotation;

// 创建轮子碰撞器
var colliderObject = new GameObject("WheelCollider");
colliderObject.transform.parent = transform;
colliderObject.transform.position = w.graphic.position;
w.coll = colliderObject.AddComponent(WheelCollider);
w.coll.suspensionDistance = suspensionDistance;
w.coll.suspensionSpring.spring = springs;
w.coll.suspensionSpring.damper = dampers;
// 无抓地力,因为我们自己模拟操控
w.coll.forwardFriction.stiffness = 0;
w.coll.sidewaysFriction.stiffness = 0;
w.coll.radius = wheelRadius;
}

// 获取轮子高度(力的作用高度)
wheelY = wheels[0].graphic.localPosition.y;

// 设置重心
rigidbody.centerOfMass.y = cogY;

// 查找刹车痕对象
// skidmarks = FindObjectOfType(typeof(Skidmarks));

// 切换到一档
gear = 1;
}

// 更新轮子状态
function UpdateWheels() {
// 计算手刹打滑量,用于牵引图形效果
var handbrakeSlip = handbrake * rigidbody.velocity.magnitude * 0.1;
if (handbrakeSlip > 1) {
handbrakeSlip = 1;
}

var totalSlip = 0.0;
onGround = false;

for (var w in wheels) {
// 旋转轮子
w.rotation += wheelRPM / 60.0 * -rev * 360.0 * Time.fixedDeltaTime;
w.rotation = Mathf.Repeat(w.rotation, 360.0);
w.graphic.localRotation = Quaternion.Euler(w.rotation, w.maxSteerAngle * steer, 0.0) * w.originalRotation;

// 检查轮子是否接触地面
if (w.coll.isGrounded) {
onGround = true;
}

var slip = cornerSlip + (w.powered ? driveSlip : 0.0) + (w.handbraked ? handbrakeSlip : 0.0);
totalSlip += slip;

var hit : WheelHit;
var c : WheelCollider;
c = w.coll;
if (c.GetGroundHit(hit)) {
// 如果轮子接触地面,调整图形化轮子的位置以反映悬架弹簧
w.graphic.localPosition.y -= Vector3.Dot(w.graphic.position - hit.point, transform.up) - w.coll.radius;

// 如果条件合适,在地面上创建灰尘效果
if (slip > 0.5 && hit.collider.tag == "Dusty") {
groundDustEffect.position = hit.point;
groundDustEffect.particleEmitter.worldVelocity = rigidbody.velocity * 0.5;
groundDustEffect.particleEmitter.minEmission = (slip - 0.5) * 3;
groundDustEffect.particleEmitter.maxEmission = (slip - 0.5) * 3;
groundDustEffect.particleEmitter.Emit();
}

// 刹车痕效果(已屏蔽)
/*if (slip > 0.75 && skidmarks != null)
w.lastSkidMark = skidmarks.AddSkidMark(hit.point, hit.normal, (slip - 0.75) * 2, w.lastSkidMark);
else
w.lastSkidMark = -1; */
}
// else w.lastSkidMark = -1;
}

totalSlip /= wheels.length;
}

// 自动换挡
function AutomaticTransmission() {
if (gear > 0) {
if (engineRPM > shiftUpRPM && gear < gearRatios.length - 1) {
gear++;
}
if (engineRPM < shiftDownRPM && gear > 1) {
gear--;
}
}
}

// 计算当前RPM和油门下的发动机加速力
function CalcEngine() : float {
// 刹车时发动机无动力
if (brake + handbrake > 0.1) {
motor = 0.0;
}

// 如果汽车在空中,仅让发动机空转
if (!onGround) {
engineRPM += (motor - 0.3) * 25000.0 * Time.deltaTime;
engineRPM = Mathf.Clamp(engineRPM, minRPM, maxRPM);
return 0.0;
} else {
AutomaticTransmission();
engineRPM = wheelRPM * gearRatios[gear] * finalDriveRatio;
if (engineRPM < minRPM) {
engineRPM = minRPM;
}
if (engineRPM < maxRPM) {
// 模拟基本扭矩曲线
var x = (2 * (engineRPM / maxRPM) - 1);
var torqueCurve = 0.5 * (-x * x + 2);
var torqueToForceRatio = gearRatios[gear] * finalDriveRatio / wheelRadius;
return motor * maxTorque * torqueCurve * torqueToForceRatio;
} else {
// RPM限制
return 0.0;
}
}
}

// 汽车物理模拟
// 此汽车的物理模拟是基于基本的“Asteriods”物理进行试错扩展的,因此会有类似街机游戏的操控感。
// 这可能符合你的需求,也可能不符合,若需要更真实的物理模拟,可研究轮子碰撞器。
function HandlePhysics() {
var velo = rigidbody.velocity;
wheelRPM = velo.magnitude * 60.0 * 0.5;
rigidbody.angularVelocity = new Vector3(rigidbody.angularVelocity.x, 0.0, rigidbody.angularVelocity.z);
var dir = transform.TransformDirection(Vector3.forward);
var flatDir = Vector3.Normalize(new Vector3(dir.x, 0, dir.z));
var flatVelo = new Vector3(velo.x, 0, velo.z);
rev = Mathf.Sign(Vector3.Dot(flatVelo, flatDir));

// 倒车或静止且刹车时,切换到倒档
if ((rev < 0 || flatVelo.sqrMagnitude < 0.5) && brake > 0.1) {
gear = 0;
}

if (gear == 0) {
// 倒档时,交换刹车和油门
var tmp = brake;
brake = motor;
motor = tmp;

// 前进或静止且踩油门时,切换到前进档
if ((rev > 0 || flatVelo.sqrMagnitude < 0.5) && brake > 0.1) {
gear = 1;
}
}

var engineForce = flatDir * CalcEngine();
var totalbrake = brake + handbrake * 0.5;
if (totalbrake > 1.0) {
totalbrake = 1.0;
}
var brakeForce = -flatVelo.normalized * totalbrake * rigidbody.mass * maxBrakeAccel;
flatDir *= flatVelo.magnitude;
flatDir = Quaternion.AngleAxis(steer * 30.0, Vector3.up) * flatDir;
flatDir *= rev;
var diff = (flatVelo - flatDir).magnitude;
var cornerAccel = maxCornerAccel;
if (cornerAccel > diff) {
cornerAccel = diff;
}
var cornerForce = -(flatVelo - flatDir).normalized * cornerAccel * rigidbody.mass;
cornerSlip = Mathf.Pow(cornerAccel / maxCornerAccel, 3);
rigidbody.AddForceAtPosition(brakeForce + engineForce + cornerForce, transform.position + transform.up * wheelY);

var handbrakeFactor = 1 + handbrake * 4;
if (rev < 0) {
handbrakeFactor = 1;
}
var veloSteer = ((15 / (2 * velo.magnitude + 1)) + 1) * handbrakeFactor;
var steerGrip = (1 - handlingTendency * cornerSlip);
if (rev * steer * steerVelo < 0) {
steerGrip = 1;
}
var maxRotSteer = 2 * Time.fixedDeltaTime * handbrakeFactor * steerGrip;
var fVelo = velo.magnitude;
var veloFactor = fVelo < 1.0 ? fVelo : Mathf.Pow(velo.magnitude, 0.3);
var steerVeloInput = rev * steer * veloFactor * 0.5 * Time.fixedDeltaTime * handbrakeFactor;
if (velo.magnitude < 0.1) {
steerVeloInput = 0;
}
if (steerVeloInput > steerVelo) {
steerVelo += 0.02 * Time.fixedDeltaTime * veloSteer;
if (steerVeloInput < steerVelo) {
steerVelo = steerVeloInput;
}
} else {
steerVelo -= 0.02 * Time.fixedDeltaTime * veloSteer;
if (steerVeloInput > steerVelo) {
steerVelo = steerVeloInput;
}
}
steerVelo = Mathf.Clamp(steerVelo, -maxRotSteer, maxRotSteer);
transform.Rotate(Vector3.up * steerVelo * 57.295788);
}

function FixedUpdate() {
// 根据需要查询输入轴
if (queryUserInput) {
brake = Mathf.Clamp01(-Input.GetAxis("Vertical"));
handbrake = Input.GetButton("Jump") ? 1.0 : 0.0;
steer = Input.GetAxis("Horizontal");
motor = Mathf.Clamp01(Input.GetAxis("Vertical"));
} else {
motor = 0;
steer = 0;
brake = 0;
handbrake = 0;
}

// 如果汽车在地面上,计算操控,否则仅让发动机空转
if (onGround) {
HandlePhysics();
} else {
CalcEngine();
}

// 更新轮子图形
UpdateWheels();

// 发动机声音
audio.pitch = 0.5 + 0.2 * motor + 0.8 * engineRPM / maxRPM;
audio.volume = 0.5 + 0.8 * motor + 0.2 * engineRPM / maxRPM;
}

// 当汽车被摧毁时调用
function Detonate() {
// 销毁轮子
for (var w in wheels) {
w.coll.gameObject.active = false;
}
// 停止汽车物理模拟
enabled = false;
}

@script RequireComponent(Rigidbody)
@script RequireComponent(AudioSource)

希望以上内容能帮助你在Unity3D中实现汽车控制功能,若你在使用过程中遇到问题,欢迎留言交流。

作者信息

feifeila

feifeila

共发布了 3994 篇文章