VehiclePhysicsProfileAsset — Vehicle Physics Profiles
VehiclePhysicsProfileAsset provides bulk physics overrides for vehicles without requiring asset bundle rebuilds. It extends Asset directly and functions as a companion to VehicleAsset. The profile overrides are applied at runtime to InteractableVehicle instances, modifying root rigidbody properties, wheel collider settings, friction curves, torque multipliers, and drive/brake models. At 441 lines it is a substantial asset class with its own applyTo(InteractableVehicle) method that patches the runtime vehicle.
Source code location: Unturned/Bundles/VehiclePhysicsProfileAsset.cs
Inheritance Chain
Asset
→ VehiclePhysicsProfileAssetClass Definition
csharp
public class VehiclePhysicsProfileAsset : Asset
{
public static AssetReference<VehiclePhysicsProfileAsset> defaultProfile_Boat;
public static AssetReference<VehiclePhysicsProfileAsset> defaultProfile_Car;
public static AssetReference<VehiclePhysicsProfileAsset> defaultProfile_Helicopter;
public static AssetReference<VehiclePhysicsProfileAsset> defaultProfile_Plane;
public float? rootMassOverride;
public float? rootMassMultiplier;
public float? rootDragMultiplier;
public float? rootAngularDragMultiplier;
public float? carjackForceMultiplier;
public float? wheelMassOverride;
public float? wheelMassMultiplier;
public float? wheelDampingRate;
public float? wheelStiffnessTractionMultiplier;
public float? wheelSuspensionForce;
public float? wheelSuspensionDamper;
public Friction? forwardFriction;
public Friction? sidewaysFriction;
public float? motorTorqueMultiplier;
public float? motorTorqueClampMultiplier;
public float? brakeTorqueMultiplier;
public float? brakeTorqueTractionMultiplier;
public enum EDriveModel { Front, Rear, All }
public EDriveModel? wheelDriveModel;
public EDriveModel? wheelBrakeModel;
}All physics fields are nullable (float? or struct?). A null value means "no override" — the vehicle's existing value is preserved. This allows profiles to override only specific properties while leaving others untouched.
Default Profiles
Four static default profiles correspond to the engine types:
| Profile | GUID | Used By |
|---|---|---|
defaultProfile_Boat | 47258d0dcad14cb8be26e24c1ef3449e | EEngine.BOAT |
defaultProfile_Car | 6b91a94f01b6472eaca31d9420ec2367 | EEngine.CAR |
defaultProfile_Helicopter | bb9f9f0204c4462ca7d976b87d1336d4 | EEngine.HELICOPTER |
defaultProfile_Plane | 93a47d6d40454335b4784e803628ac54 | EEngine.PLANE |
These profiles are auto-assigned by VehicleAsset.onModelLoaded when:
- No explicit
Physics_Profilereference is set on the vehicle. - The root rigidbody and wheel colliders have default Unity mass (1.0).
- The engine type is not
BLIMPorTRAIN.
Root Physics
| Field | .dat Key | Type | Description |
|---|---|---|---|
rootMassOverride | Root_Mass | float? | Exact mass value (overrides current mass) |
rootMassMultiplier | Root_Mass_Multiplier | float? | Multiplier on current mass |
rootDragMultiplier | Root_Drag_Multiplier | float? | Linear drag multiplier |
rootAngularDragMultiplier | Root_Angular_Drag_Multiplier | float? | Angular drag multiplier |
carjackForceMultiplier | Carjack_Force_Multiplier | float? | Carjack flip force modifier |
Mass can be overridden either by exact value or by multiplier. If both are set, the exact override takes precedence (checked first in applyTo):
csharp
if (rootMassOverride.HasValue)
rootRigidbody.mass = rootMassOverride.Value;
else if (rootMassMultiplier.HasValue)
rootRigidbody.mass *= rootMassMultiplier.Value;Wheel Physics
| Field | .dat Key | Type | Description |
|---|---|---|---|
wheelMassOverride | Wheel_Mass | float? | Exact wheel mass |
wheelMassMultiplier | Wheel_Mass_Multiplier | float? | Wheel mass multiplier |
wheelDampingRate | Wheel_Damping_Rate | float? | Wheel damping rate |
wheelStiffnessTractionMultiplier | Wheel_Stiffness_Traction_Multiplier | float? | Traction multiplier |
wheelSuspensionForce | Wheel_Suspension_Force | float? | Spring force |
wheelSuspensionDamper | Wheel_Suspension_Damper | float? | Spring damper |
Wheel mass is applied with a special condition: wheelMassOverride only applies if the vehicle asset does NOT have its own wheelColliderMassOverride set. This prevents the profile from overriding a vehicle-specific wheel mass setting.
Friction System
The Friction struct defines friction curves for wheel contact:
csharp
public struct Friction
{
public float extremumSlip;
public float extremumValue;
public float asymptoteSlip;
public float asymptoteValue;
public float stiffness;
}| Field | .dat Key | Type | Description |
|---|---|---|---|
forwardFriction | Wheel_Friction_Forward | Friction? | Acceleration/braking friction |
sidewaysFriction | Wheel_Friction_Sideways | Friction? | Lateral/cornering friction |
Friction is parsed from a nested .dat dictionary:
csharp
protected Friction? readFriction(IDatDictionary data, string key)
{
if (data.ContainsKey(key))
{
IDatDictionary frictionReader = data.GetDictionary(key);
Friction friction = new Friction();
friction.extremumSlip = frictionReader.ParseFloat("Extremum_Slip");
friction.extremumValue = frictionReader.ParseFloat("Extremum_Value");
friction.asymptoteSlip = frictionReader.ParseFloat("Asymptote_Slip");
friction.asymptoteValue = frictionReader.ParseFloat("Asymptote_Value");
friction.stiffness = frictionReader.ParseFloat("Stiffness");
return friction;
}
return null;
}applyTo Method
The friction values are applied to both the custom Wheel struct and the Unity WheelCollider:
csharp
if (sidewaysFriction.HasValue)
{
tire.stiffnessSideways = sidewaysFriction.Value.stiffness;
WheelFrictionCurve colliderSidewaysFriction = tire.wheel.sidewaysFriction;
sidewaysFriction.Value.applyTo(ref colliderSidewaysFriction);
tire.wheel.sidewaysFriction = colliderSidewaysFriction;
}Torque and Braking
| Field | .dat Key | Type | Description |
|---|---|---|---|
motorTorqueMultiplier | Motor_Torque_Multiplier | float? | Acceleration torque |
motorTorqueClampMultiplier | Motor_Torque_Clamp_Multiplier | float? | Torque clamp |
brakeTorqueMultiplier | Brake_Torque_Multiplier | float? | Brake force |
brakeTorqueTractionMultiplier | Brake_Torque_Traction_Multiplier | float? | Brake traction |
These override the per-wheel torque multipliers. The motorTorqueMultiplier scales the drive force applied to powered wheels. The brakeTorqueMultiplier scales braking force. Both are applied per-wheel in the applyTo loop.
Drive and Brake Models
csharp
public enum EDriveModel { Front, Rear, All }| Model | Powered/Brake Distribution |
|---|---|
Front | Wheel index < 2 (front wheels only) |
Rear | Wheel index >= 2 (rear wheels only) |
All | All wheels (4WD / 4-wheel brakes) |
csharp
if (wheelDriveModel.HasValue && tire.index >= 0)
{
switch (wheelDriveModel.Value)
{
case EDriveModel.Front: tire.isPowered = tire.index < 2; break;
case EDriveModel.Rear: tire.isPowered = tire.index >= 2; break;
case EDriveModel.All: tire.isPowered = true; break;
}
}| Field | .dat Key | Type | Description |
|---|---|---|---|
wheelDriveModel | Wheel_Drive_Model | EDriveModel? | Which wheels receive torque |
wheelBrakeModel | Wheel_Brake_Model | EDriveModel? | Which wheels have brakes |
The applyTo(InteractableVehicle) Method
The profile is applied to a live vehicle's physics components:
- Root rigidbody: Mass override/multiplier, drag multiplier, angular drag multiplier.
- Each wheel (loop through
vehicle.tires):- Stiffness traction multiplier.
- Wheel damping rate.
- Suspension spring force and damper.
- Sideways friction curve (stiffness + collider curve).
- Forward friction curve (stiffness + collider curve).
- Wheel mass override/multiplier (with
vehicle.asset.wheelColliderMassOverrideguard). - Motor torque multiplier.
- Motor torque clamp multiplier.
- Brake torque multiplier.
- Brake torque traction multiplier.
- Drive model (powered state per wheel).
- Brake model (hasBrakes state per wheel).
Skip conditions:
- Wheels with null colliders (purely visual wheels) are skipped.
wheelMassOverrideis skipped if the vehicle has its ownwheelColliderMassOverride.carjackForceMultiplieris applied to the vehicle's field directly, not in the wheel loop.
Conditional Logging
The applyTo method includes extensive logging gated behind the LOG_VEHICLE_PHYSICS_PROFILE preprocessor directive:
csharp
[System.Diagnostics.Conditional("LOG_VEHICLE_PHYSICS_PROFILE")]
private void log(InteractableVehicle vehicle, string format, params object[] args)
{
UnturnedLog.info(vehicle.asset.name + ": " + format, args);
}When enabled, every physics change is logged with before/after values for debugging.
PopulateAsset
csharp
public override void PopulateAsset(in PopulateAssetParameters p)
{
base.PopulateAsset(in p);
if (p.data.ContainsKey("Root_Mass"))
rootMassOverride = p.data.ParseFloat("Root_Mass");
if (p.data.ContainsKey("Root_Mass_Multiplier"))
rootMassMultiplier = p.data.ParseFloat("Root_Mass_Multiplier");
// ... same pattern for all fields ...
sidewaysFriction = readFriction(p.data, "Wheel_Friction_Sideways");
forwardFriction = readFriction(p.data, "Wheel_Friction_Forward");
// ... torque, drive model, brake model ...
}Each field uses ContainsKey for presence detection — the value is only parsed and set if the key exists. Missing keys leave the field as null (no override). This enables sparse profiles that change only a few values.
Use Case: Bulk Tuning Without Bundles
The primary benefit of VehiclePhysicsProfileAsset is that it allows tuning vehicle physics without rebuilding asset bundles. A modder can:
- Create a physics profile asset (
.datfile withType=VehiclePhysicsProfile). - Reference it from vehicle assets via
Physics_Profile. - Tweak values in the profile file without repacking textures, models, or audio.
- Share profiles across multiple vehicles for consistent handling.
Comparison to VehicleAsset Physics Fields
| Physics Aspect | VehicleAsset | VehiclePhysicsProfileAsset |
|---|---|---|
| Mass | wheelColliderMassOverride, hasCenterOfMassOverride | rootMassOverride, wheelMassOverride, mass multipliers |
| Drag | Not configurable | rootDragMultiplier, rootAngularDragMultiplier |
| Friction | Not configurable | Full friction curves (forward + sideways) |
| Suspension | Not configurable | wheelSuspensionForce, wheelSuspensionDamper |
| Torque | Not configurable | 4 torque/brake multipliers |
| Drive model | Implicit (front/rear by index) | Explicit EDriveModel (Front/Rear/All) |
The profile provides granular physics tuning that VehicleAsset itself does not expose. Most fine-tuning should go in the profile; the vehicle asset should define the structural relationships (which wheels exist, which are powered/steered).
Common Issues
- Default profile only for mass-1.0 vehicles — If the prefab has a non-default mass (e.g., 500kg), the default profile is not auto-assigned. All physics values remain as set in the prefab.
- Wheel mass override guard —
wheelMassOverrideis ignored if the vehicle has its ownwheelColliderMassOverride. UsewheelMassMultiplierinstead for vehicles with custom wheel mass. - Friction applies stiffness only — The
applyTomethod setstire.stiffnessSidewaysandtire.stiffnessForwardfrom the friction stiffness value, but the collider friction curve fromapplyTo(ref frictionCurve)only transfersextremumSlip,extremumValue, andasymptoteValue— it does NOT transferasymptoteSlip(due to a bug retainingfrictionCurve.asymptoteSlip). - Null checks on wheel loop — Visual-only wheels (no
WheelCollider) are skipped in the loop withif (tire.wheel == null) continue;. This means suspension, friction, and mass modifications don't apply to visual wheels — they affect collider physics only. - Blimp and train excluded — Blimps and trains don't get default profiles. They require explicit
Physics_Profilesettings or accept prefab defaults.
