Thanks @Wingnut … I think i should clarify… I mean arcade style racing game so the drifting part is not really a TOTAL drifting game … but rather when taking a high speed turn i have a function that i am working on the when you are holding the DRIFT trigger button in a turn, i give a bit of Y-Rotational boost (setAngularVelocity based of the current wheel steer direction) to kinda of swing the back of the car a bit more in the turn… I also easy up a bit on the wheelInfo.m_frictionSlip…
AGAIN… I am just making all this shit up. to create a Car Controller Script
My problem with hand braking and drifting is that when i left go of drift or hand brake… it immediate goes back to using the set rolling friction (and has no support for handling SIDEWAYS FRICTION out of the box) when will CATCH and flip or roll the car cause the tire is catching the friction at such a high speed. You have to implement a Typed Constraint (btTypeConstraint) and set the min and max friction slip for both forward and sideways friction and let the bullet physics engine proper solve and LERP to the proper friction level for that wheel… Instead of always just using a constant m_frictionSlip value.
The car controller script is quite nice, if i must say so my self:
Example Car driving script using my Physics SDK… As you see …
You use it just like would a Unity Car Controller script:
/**
* Babylon standard rigidbody vehicle controller class (Native Bullet Physics 2.82)
* @class StandardCarController
*/
class StandardCarController extends BABYLON.ScriptComponent {
private _rigidbody:BABYLON.RigidbodyPhysics = null;
private FRONT_LEFT = -1;
private FRONT_RIGHT = -1;
private BACK_LEFT = -1;
private BACK_RIGHT = -1;
private frictionSlip:number = 0;
private vehicleSteer:number = 0;
private engineForce:number = 0;
private brakingForce:number = 0;
private forwardSpeed:number = 0;
private absoluteSpeed:number = 0;
private raycastVehicle:BABYLON.RaycastVehicle = null;
private frontLeftWheelMesh:BABYLON.TransformNode = null;
private frontRightWheelMesh:BABYLON.TransformNode = null;
private backLeftWheelMesh:BABYLON.TransformNode = null;
private backRightWheelMesh:BABYLON.TransformNode = null;
private frontLeftWheelTrans:BABYLON.IUnityTransform = null;
private frontRightWheelTrans:BABYLON.IUnityTransform = null;
private backLeftWheelTrans:BABYLON.IUnityTransform = null;
private backRightWheelTrans:BABYLON.IUnityTransform = null;
private frontLeftWheelCollider:any = null;
private frontRightWheelCollider:any = null;
private backLeftWheelCollider:any = null;
private backRightWheelCollider:any = null;
private STEERING_RADIUS_RATIO: number = 0.1;
public getRaycastVehicle():BABYLON.RaycastVehicle { return this.raycastVehicle; }
public staticFriction:number = 25;
public rollingFriction:number = 5;
public dampeningSpeed:number = 25;
public lowSpeedAngle:number = 0.3;
public highSpeedAngle:number = 0.01;
public maxEngineSpeed:number = 120;
public maxEnginePower:number = 2000;
public maxBrakingForce:number = 100;
public maxReverseSpeed:number = 0.66;
public handBrakingForce:number = 5000;
public frontLockFriction:number = 1;
public backLockFriction:number = 1;
public frontDriftFriction:number = 1;
public backDriftFriction:number = 1;
public driftingInfluence:number = 1;
public steeringIncrement:number = 0.03;
protected m_frontLeftWheel:any = null;
protected m_frontRightWheel:any = null;
protected m_backLeftWheel:any = null;
protected m_backRightWheel:any = null;
protected m_physicsWorld:any = null;
public constructor(transform: BABYLON.TransformNode, scene: BABYLON.Scene, properties: any = {}) {
super(transform, scene, properties);
this.vehicleSteer = 0;
this.brakingForce = 0;
this.engineForce = 0;
this.lowSpeedAngle = this.getEditorProperty("lowSpeedAngle", this.lowSpeedAngle);
this.highSpeedAngle = this.getEditorProperty("highSpeedRatio", this.highSpeedAngle);
this.dampeningSpeed = this.getEditorProperty("dampeningSpeed", this.dampeningSpeed);
this.maxEngineSpeed = this.getEditorProperty("maxEngineSpeed", this.maxEngineSpeed);
this.maxEnginePower = this.getEditorProperty("maxEnginePower", this.maxEnginePower);
this.maxBrakingForce = this.getEditorProperty("maxBrakingForce", this.maxBrakingForce);
this.handBrakingForce = this.getEditorProperty("handBrakingForce", this.handBrakingForce);
this.frontLockFriction = this.getEditorProperty("frontLockFriction", this.frontLockFriction);
this.backLockFriction = this.getEditorProperty("backLockFriction", this.backLockFriction);
this.frontDriftFriction = this.getEditorProperty("frontDriftFriction", this.frontDriftFriction);
this.backDriftFriction = this.getEditorProperty("backDriftFriction", this.backDriftFriction);
this.driftingInfluence = this.getEditorProperty("driftingInfluence", this.driftingInfluence);
this.steeringIncrement = this.getEditorProperty("steeringIncrement", this.steeringIncrement);
this.frontLeftWheelTrans = this.getEditorProperty("frontLeftWheelMesh", this.frontLeftWheelTrans);
this.frontRightWheelTrans = this.getEditorProperty("frontRightWheelMesh", this.frontRightWheelTrans);
this.backLeftWheelTrans = this.getEditorProperty("rearLeftWheelMesh", this.backLeftWheelTrans);
this.backRightWheelTrans = this.getEditorProperty("rearRightWheelMesh", this.backRightWheelTrans);
this.frontLeftWheelCollider = this.getEditorProperty("frontLeftWheelCollider", this.frontLeftWheelCollider);
this.frontRightWheelCollider = this.getEditorProperty("frontRightWheelCollider", this.frontRightWheelCollider);
this.backLeftWheelCollider = this.getEditorProperty("rearLeftWheelCollider", this.backLeftWheelCollider);
this.backRightWheelCollider = this.getEditorProperty("rearRightWheelCollider", this.backRightWheelCollider);
this.m_physicsWorld = BABYLON.SceneManager.GetPhysicsWorld(this.scene);
///////////////////////////////////////////////////////////
// TODO: Handle With Wheel Friction Constraint
///////////////////////////////////////////////////////////
this.staticFriction = this.getEditorProperty("staticFrictionSlip", this.staticFriction);
this.rollingFriction = this.getEditorProperty("rollingFrictionSlip", this.rollingFriction);
///////////////////////////////////////////////////////////
}
protected start():void { this.initVehicleState(); }
protected update() :void { this.updateVehicleState(); }
protected destroy(): void { this.destroyVehicleState(); }
//////////////////////////////////////////////////
// Protected Character Movement State Functions //
//////////////////////////////////////////////////
protected initVehicleState(): void {
BABYLON.Utilities.ValidateTransformQuaternion(this.transform);
this._rigidbody = this.getComponent("BABYLON.RigidbodyPhysics");
if (this._rigidbody != null) {
if (this._rigidbody.hasRaycastVehicle()) {
this.raycastVehicle = this._rigidbody.getRaycastVehicle();
if (this.raycastVehicle != null) {
const frontLeftWheelName:string = (this.frontLeftWheelTrans.name != null && this.frontLeftWheelTrans.name !== "") ? this.frontLeftWheelTrans.name : null;
const frontRightWheelName:string = (this.frontRightWheelTrans.name != null && this.frontRightWheelTrans.name !== "") ? this.frontRightWheelTrans.name : null;
const backLeftWheelName:string = (this.backLeftWheelTrans.name != null && this.backLeftWheelTrans.name !== "") ? this.backLeftWheelTrans.name : null;
const backRightWheelName:string = (this.backRightWheelTrans.name != null && this.backRightWheelTrans.name !== "") ? this.backRightWheelTrans.name : null;
const frontLeftWheelLabel:string = (this.frontLeftWheelCollider.name != null && this.frontLeftWheelCollider.name !== "") ? this.frontLeftWheelCollider.name : null;
const frontRightWheelLabel:string = (this.frontRightWheelCollider.name != null && this.frontRightWheelCollider.name !== "") ? this.frontRightWheelCollider.name : null;
const backLeftWheelLabel:string = (this.backLeftWheelCollider.name != null && this.backLeftWheelCollider.name !== "") ? this.backLeftWheelCollider.name : null;
const backRightWheelLabel:string = (this.backRightWheelCollider.name != null && this.backRightWheelCollider.name !== "") ? this.backRightWheelCollider.name : null;
if (frontLeftWheelLabel != null) this.FRONT_LEFT = this.raycastVehicle.getWheelIndexByName(frontLeftWheelLabel);
if (frontRightWheelLabel != null) this.FRONT_RIGHT = this.raycastVehicle.getWheelIndexByName(frontRightWheelLabel);
if (backLeftWheelLabel != null) this.BACK_LEFT = this.raycastVehicle.getWheelIndexByName(backLeftWheelLabel);
if (backRightWheelLabel != null) this.BACK_RIGHT = this.raycastVehicle.getWheelIndexByName(backRightWheelLabel);
if (this.raycastVehicle.getNumWheels() >= 4) {
if (this.FRONT_LEFT >= 0 && frontLeftWheelName != null) {
this.m_frontLeftWheel = this.raycastVehicle.getWheelInfo(this.FRONT_LEFT);
this.frontLeftWheelMesh = this.getChildTransform(frontLeftWheelName, BABYLON.SearchType.StartsWith, false);
if (this.frontLeftWheelMesh != null) this.raycastVehicle.setWheelTransformMesh(this.FRONT_LEFT, this.frontLeftWheelMesh);
}
if (this.FRONT_RIGHT >= 0 && frontRightWheelName != null) {
this.m_frontRightWheel = this.raycastVehicle.getWheelInfo(this.FRONT_RIGHT);
this.frontRightWheelMesh = this.getChildTransform(frontRightWheelName, BABYLON.SearchType.StartsWith, false);
if (this.frontRightWheelMesh != null) this.raycastVehicle.setWheelTransformMesh(this.FRONT_RIGHT, this.frontRightWheelMesh);
}
if (this.BACK_LEFT >= 0 && backLeftWheelName != null) {
this.m_backLeftWheel = this.raycastVehicle.getWheelInfo(this.BACK_LEFT);
this.backLeftWheelMesh = this.getChildTransform(backLeftWheelName, BABYLON.SearchType.StartsWith, false);
if (this.backLeftWheelMesh != null) this.raycastVehicle.setWheelTransformMesh(this.BACK_LEFT, this.backLeftWheelMesh);
}
if (this.BACK_RIGHT >= 0 && backRightWheelName != null) {
this.m_backRightWheel = this.raycastVehicle.getWheelInfo(this.BACK_RIGHT);
this.backRightWheelMesh = this.getChildTransform(backRightWheelName, BABYLON.SearchType.StartsWith, false);
if (this.backRightWheelMesh != null) this.raycastVehicle.setWheelTransformMesh(this.BACK_RIGHT, this.backRightWheelMesh);
}
} else {
BABYLON.Tools.Warn("Invalid vehicle controller wheel count info for: " + this.transform.name);
}
} else {
BABYLON.Tools.Warn("Failed to create vehicle controller for: " + this.transform.name);
}
} else {
BABYLON.Tools.Warn("No wheel collider information found for: " + this.transform.name);
}
} else {
BABYLON.Tools.Warn("Failed to get rigidbody component: " + this.transform.name);
}
}
protected updateVehicleState(): void {
if (this._rigidbody != null && this.raycastVehicle != null) {
this.forwardSpeed = this.raycastVehicle.getRawCurrentSpeedMph();
this.absoluteSpeed = this.raycastVehicle.getAbsCurrentSpeedMph();
}
}
protected destroyVehicleState(): void {
this._rigidbody = null;
this.m_physicsWorld = null;
if (this.m_frontLeftWheel != null) {
Ammo.destroy(this.m_frontLeftWheel); // ???
this.m_frontLeftWheel = null;
}
if (this.m_frontRightWheel != null) {
Ammo.destroy(this.m_frontRightWheel); // ???
this.m_frontRightWheel = null;
}
if (this.m_backLeftWheel != null) {
Ammo.destroy(this.m_backLeftWheel); // ???
this.m_backLeftWheel = null;
}
if (this.m_backRightWheel != null) {
Ammo.destroy(this.m_backRightWheel); // ???
this.m_backRightWheel = null;
}
this.raycastVehicle = null;
this.frontLeftWheelMesh = null;
this.frontRightWheelMesh = null;
this.backLeftWheelMesh = null;
this.backRightWheelMesh = null;
this.frontLeftWheelTrans = null;
this.frontRightWheelTrans = null;
this.backLeftWheelTrans = null;
this.backRightWheelTrans = null;
this.frontLeftWheelCollider = null;
this.frontRightWheelCollider = null;
this.backLeftWheelCollider = null;
this.backRightWheelCollider = null;
}
////////////////////////////////////////////////////
// Public Vehicle Controller Movement Functions //
////////////////////////////////////////////////////
/** Drives the raycast vehicle with the specfied movement and hand braking properties. */
public drive(forward:number, turn:number, braking:boolean = false, drifting:boolean = false):void {
if (this._rigidbody == null || this.raycastVehicle == null) return;
if (this.FRONT_LEFT >= 0 && this.FRONT_RIGHT >= 0 && this.BACK_LEFT >= 0 && this.BACK_RIGHT >= 0) {
this.frictionSlip = this.rollingFriction;
this.brakingForce = 0; this.engineForce = 0;
// ..
// Update Turning Angle
// ..
let allowedTurningAngle:number = this.lowSpeedAngle;
if (this.dampeningSpeed > 0 && this.absoluteSpeed > this.dampeningSpeed) {
const turnSpeedGradient:number = BABYLON.Scalar.Clamp(0, 1, (this.absoluteSpeed / this.maxEngineSpeed));
allowedTurningAngle = BABYLON.Scalar.Lerp(this.lowSpeedAngle, this.highSpeedAngle, turnSpeedGradient);
}
if (turn > 0) { // Left
if (this.vehicleSteer < allowedTurningAngle) {
this.vehicleSteer += (this.steeringIncrement);
}
} else if (turn < 0) { // Right
if (this.vehicleSteer > -allowedTurningAngle) {
this.vehicleSteer -= (this.steeringIncrement);
}
} else {
if (this.vehicleSteer < -this.steeringIncrement) {
this.vehicleSteer += this.steeringIncrement;
} else {
if (this.vehicleSteer > this.steeringIncrement) {
this.vehicleSteer -= this.steeringIncrement;
} else {
this.vehicleSteer = 0;
}
}
}
// ..
// Update Engine Forces
// ..
let allowedEnginePower:number = (this.maxEnginePower * Math.abs(forward));
if (forward > 0.1) { // Forward
if (this.forwardSpeed < -1) this.brakingForce = this.maxBrakingForce;
else this.engineForce = allowedEnginePower;
} else if (forward < -0.1) { // Reverse
if (this.forwardSpeed > 1) this.brakingForce = this.maxBrakingForce;
else this.engineForce = -(allowedEnginePower * this.maxReverseSpeed);
} else {
this.engineForce = 0;
this.brakingForce = 10; // Static
this.frictionSlip = this.staticFriction;
}
if (this.absoluteSpeed > this.maxEngineSpeed) this.engineForce = 0;
// ..
// Update Vehicle Controls
// ..
if (braking === true && this.handBrakingForce > 0) {
// TODO: Fix Hand Brake Friction Zero Velocity (Use Wheel Friction Constraint)
if (this.raycastVehicle.lockedWheelIndexes == null) this.raycastVehicle.lockedWheelIndexes = [this.BACK_LEFT, this.BACK_RIGHT];
if (this.m_frontLeftWheel != null) this.m_frontLeftWheel.set_m_frictionSlip(this.frontLockFriction);
if (this.m_frontRightWheel != null) this.m_frontRightWheel.set_m_frictionSlip(this.frontLockFriction);
if (this.m_backLeftWheel != null) this.m_backLeftWheel.set_m_frictionSlip(this.backLockFriction);
if (this.m_backRightWheel != null) this.m_backRightWheel.set_m_frictionSlip(this.backLockFriction);
this.raycastVehicle.setSteeringAngle(this.vehicleSteer, this.FRONT_LEFT);
this.raycastVehicle.setSteeringAngle(this.vehicleSteer, this.FRONT_RIGHT);
this.raycastVehicle.setEngineForce(0, this.BACK_LEFT);
this.raycastVehicle.setEngineForce(0, this.BACK_RIGHT);
this.raycastVehicle.setBrakingForce(0, this.FRONT_LEFT);
this.raycastVehicle.setBrakingForce(0, this.FRONT_RIGHT);
this.raycastVehicle.setBrakingForce(this.handBrakingForce, this.BACK_LEFT);
this.raycastVehicle.setBrakingForce(this.handBrakingForce, this.BACK_RIGHT);
} else {
if (this.raycastVehicle.lockedWheelIndexes != null) this.raycastVehicle.lockedWheelIndexes = null;
if (this.m_frontLeftWheel != null) this.m_frontLeftWheel.set_m_frictionSlip(this.frictionSlip);
if (this.m_frontRightWheel != null) this.m_frontRightWheel.set_m_frictionSlip(this.frictionSlip);
if (this.m_backLeftWheel != null) this.m_backLeftWheel.set_m_frictionSlip(this.frictionSlip);
if (this.m_backRightWheel != null) this.m_backRightWheel.set_m_frictionSlip(this.frictionSlip);
if (drifting === true && this.absoluteSpeed > 0) {
// TODO: Drifting Support
// const wheelRotation:number = turn;
// const angularVelocity:BABYLON.Vector3 = this._rigidbody.getAngularVelocity();
// angularVelocity.y += (wheelRotation * this.absoluteSpeed * this.getDeltaSeconds() * this.STEERING_RADIUS_RATIO * this.driftingInfluence);
// this._rigidbody.setAngularVelocity(angularVelocity);
}
this.raycastVehicle.setSteeringAngle(this.vehicleSteer, this.FRONT_LEFT);
this.raycastVehicle.setSteeringAngle(this.vehicleSteer, this.FRONT_RIGHT);
this.raycastVehicle.setEngineForce(this.engineForce, this.BACK_LEFT);
this.raycastVehicle.setEngineForce(this.engineForce, this.BACK_RIGHT);
this.raycastVehicle.setBrakingForce(this.brakingForce * 0.5, this.FRONT_LEFT);
this.raycastVehicle.setBrakingForce(this.brakingForce * 0.5, this.FRONT_RIGHT);
this.raycastVehicle.setBrakingForce(this.brakingForce, this.BACK_LEFT);
this.raycastVehicle.setBrakingForce(this.brakingForce, this.BACK_RIGHT);
}
// Trace Current Speed
const msg:string = ("Speed: " + this.absoluteSpeed.toFixed(1) + " MPH --> Turn Angle: " + allowedTurningAngle.toFixed(5));
BABYLON.Utilities.PrintToScreen(msg, "#008000");
}
}
}