Damage System

Table of Contents

RCCP includes a comprehensive damage system that supports four types of cosmetic vehicle damage: mesh deformation, detachable parts, wheel damage, and light breakage. All damage types are managed through the RCCP_Damage component attached to the vehicle, and every type can be toggled independently.

When a collision occurs, RCCP calculates the impulse magnitude, converts it to a contact point, and distributes the damage across all enabled subsystems within range. The system caches original mesh data at startup so that vehicles can be fully repaired at any time.

V2.58 layers three additive systems on top of this cosmetic base, all opt-in and inert until you use them:

RCCP_Damage Component

The RCCP_Damage component is the central controller for all vehicle damage. Add it to any vehicle that should support collision damage.

Key Properties

PropertyTypeDefaultDescription
automaticInstallationbooltrueWhen enabled, automatically finds all damageable meshes, detachable parts, lights, and wheels on the vehicle at startup. When disabled, you must assign each array manually in the Inspector.
damageFilterLayerMaskEverythingControls which layers can cause damage to the vehicle. Only collisions with objects on these layers will trigger damage calculations.
maximumDamagefloat0.75Maximum vertex/wheel displacement distance in meters. Limits how far any single vertex (or a wheel) can move from its original position. Also doubles as the wheel-detach trigger (see Wheel Damage). Set to 0 to disable the limit.
processInactiveGameobjectsboolfalseWhether to include inactive child GameObjects when collecting meshes and parts during automatic installation.
saveNamestringVehicle nameIdentifier used for saving and loading damage data via JSON. Auto-populated from the vehicle's GameObject name.

Zone Health & Telemetry (V2.58+)

Independently of the four cosmetic damage toggles below, RCCP_Damage tracks structural health per zone -- a lightweight, queryable damage model gameplay code can read without caring whether mesh deformation, wheel damage, part damage, or light damage are individually enabled.

Each qualifying collision resolves to one of four zones from the collision's root-local contact point: whichever local axis has the larger magnitude decides front/rear vs. left/right, and its sign picks the specific zone (+Z = Front, -Z = Rear, +X = Right, -X = Left).


public enum RCCP_DamageZone { Front, Rear, Left, Right }

Each zone holds a health value from 100 (intact) down to 0 (fully damaged). Per qualifying hit: zoneHealth[zone] -= normalizedImpulse * zoneDamageMultiplier, clamped to [0, 100]. There is no distance falloff on zone damage -- it's a flat deduction driven purely by impulse, independent of which cosmetic subsystems are enabled or where exactly the contact point landed within the zone.

Tracking is on by default because it has no behavioral consequence by itself -- it's pure telemetry. All mechanical consequences require adding the separate RCCP_DamageMechanics component (below).

Configuration

PropertyTypeDefaultDescription
trackZoneHealthbooltrueMaster toggle for zone-health accumulation. Telemetry only -- mechanical consequences require RCCP_DamageMechanics.
zoneDamageMultiplierfloat3Health removed per hit = normalizedImpulse * zoneDamageMultiplier. At the default, a typical solid hit (normalized impulse ~3) removes ~9 health -- roughly 8-12 solid hits to zero out one zone.

All four zones reset to 100 whenever the vehicle finishes repairing (see Repairing Vehicles).

Query API


// 0 (fully damaged) - 1 (intact)
float frontHealth = damage.GetZoneHealth(RCCP_DamageZone.Front);

// 0 (pristine) - 1 (all four zones fully damaged)
float overallDamage = damage.GetOverallDamageRatio();

// Accumulated displacement (meters) of one wheel from its authored rest position.
float wheelOffset = damage.GetWheelDamageOffset(0);
MethodReturnsDescription
GetZoneHealth(RCCP_DamageZone zone)float, 0-1Normalized health of one zone. 1 = intact, 0 = fully damaged. Returns 1 if zone tracking hasn't initialized yet.
GetOverallDamageRatio()float, 0-1Average damage across all four zones. 0 = pristine, 1 = every zone at zero health.
GetWheelDamageOffset(int wheelIndex)float, metersMagnitude of a wheel's displacement from its original local position -- the same accumulated state that was previously write-only internal state. Index order matches CarController.AllWheelColliders.

Damage Events

Eight static events on RCCP_Events let gameplay code react to damage without polling any component. The first three shipped in V2.51; the remaining five are new in V2.58.

EventSignatureFired When
OnRCCPDamagedonRCCPDamaged(RCCP_CarController rccp)A vehicle first takes deformation/collision damage after being intact. Latched -- only fires on the intact-to-damaged transition, and only if a damage subsystem actually processed the hit (see the damaged-latch note under Common Issues).
OnRCCPRepairedonRCCPRepaired(RCCP_CarController rccp)A vehicle finishes repairing (damage fully reset, including zone health).
OnRCCPImpactonRCCPImpact(RCCP_CarController rccp, float impulse)A debounced collision impact (per-vehicle cooldown + minimum impulse). Use this one for gameplay reactions (sound stingers, score penalties, UI shake) -- it won't spam you with one event per contact point. The raw OnRCCPCollision event still fires on every contact and remains the right hook for damage/particle work.
OnRCCPZoneDamagedonRCCPZoneDamaged(RCCP_CarController rccp, RCCP_DamageZone zone, float health01)A collision reduces a zone's health. health01 is the zone's health after the hit, 0-1.
OnRCCPPartDetachedonRCCPPartDetached(RCCP_CarController rccp, RCCP_DetachablePart part)A detachable part fully detaches from the vehicle.
OnRCCPWheelDetachedonRCCPWheelDetached(RCCP_CarController rccp, RCCP_WheelCollider wheel)A wheel detaches due to accumulated damage.
OnRCCPLightBrokenonRCCPLightBroken(RCCP_CarController rccp, RCCP_Light light)A vehicle light breaks from collision damage.
OnRCCPCaughtFireonRCCPCaughtFire(RCCP_CarController rccp)Critical engine-zone damage ignites a fire. Requires the RCCP_DamageMechanics component -- RCCP_Damage alone never fires this one.

void OnEnable() {
    RCCP_Events.OnRCCPImpact += HandleImpact;
    RCCP_Events.OnRCCPZoneDamaged += HandleZoneDamaged;
    RCCP_Events.OnRCCPWheelDetached += HandleWheelDetached;
    RCCP_Events.OnRCCPRepaired += HandleRepaired;
}

void OnDisable() {
    RCCP_Events.OnRCCPImpact -= HandleImpact;
    RCCP_Events.OnRCCPZoneDamaged -= HandleZoneDamaged;
    RCCP_Events.OnRCCPWheelDetached -= HandleWheelDetached;
    RCCP_Events.OnRCCPRepaired -= HandleRepaired;
}

void HandleImpact(RCCP_CarController vehicle, float impulse) {
    Debug.Log(vehicle.name + " hit something with impulse " + impulse);
}

void HandleZoneDamaged(RCCP_CarController vehicle, RCCP_DamageZone zone, float health01) {
    Debug.Log(vehicle.name + " zone " + zone + " health now " + (health01 * 100f) + "%");
}

void HandleWheelDetached(RCCP_CarController vehicle, RCCP_WheelCollider wheel) {
    Debug.Log(wheel.name + " fell off " + vehicle.name);
}

void HandleRepaired(RCCP_CarController vehicle) {
    Debug.Log(vehicle.name + " is fully repaired.");
}

Mesh Deformation

Mesh deformation displaces individual vertices of the vehicle's body meshes when a collision occurs. Vertices within the deformation radius of the contact point are pushed inward along the collision direction, producing realistic crumple effects.

How It Works

  1. On collision, the system converts the contact point to local space for each mesh.
  2. An octree spatial structure is used for fast nearest-vertex lookup, avoiding the cost of iterating every vertex on every collision.
  3. Vertices within deformationRadius of the contact point are displaced. Damage is stronger at the center and falls off linearly to zero at the edge of the radius.
  4. Original mesh vertex positions are cached at startup so they can be restored during repair.
  5. Meshes must have Read/Write Enabled in their import settings. Non-readable meshes are automatically skipped with a console warning.

Configuration

PropertyTypeDefaultDescription
meshDeformationbooltrueMaster toggle for mesh deformation.
deformationRadiusfloat0.75Radius around the contact point (in meters) within which vertices are affected. Larger values create wider dents.
deformationMultiplierfloat1.0Scales the amount of vertex displacement. Internally divided by 10 (see Tuning Reference below) -- a value of 1 applies 10% of the raw computed displacement; set to 10 for a 1:1 mapping.
deformationDirectionModeenum (Legacy, Corrected)LegacyV2.58. Controls which direction dents push in. Legacy (default) reproduces the original radial-toward-root-origin vector computed in root-local space and applied directly in each mesh's local space -- this can look skewed on child meshes that are rotated relative to the root, but is kept as the default for byte-identical behavior on existing vehicles. Corrected derives the dent direction from the actual collision contact normal, transformed into each mesh's own local space via InverseTransformDirection -- fixes the skew on rotated child meshes. This only changes *direction*; falloff shape and radius behavior are unchanged in both modes.
recalculateNormalsboolfalseRecalculates mesh normals after deformation. Enable this if lighting looks incorrect on deformed areas. Costs some performance.
recalculateBoundsboolfalseRecalculates mesh bounds after deformation. Enable this if parts of the mesh disappear from view after heavy damage. Costs some performance.

Example: Adjusting Deformation Sensitivity


// Make the vehicle more resistant to dents
RCCP_Damage damage = vehicle.GetComponentInChildren<RCCP_Damage>();
damage.deformationMultiplier = 0.5f;  // Half the normal deformation
damage.deformationRadius = 0.5f;      // Smaller affected area
damage.maximumDamage = 0.4f;          // Limit maximum displacement

Detachable Parts (RCCP_DetachablePart)

Detachable parts are vehicle body panels (hoods, doors, bumpers, trunks) that can become loose and eventually fall off the vehicle when damaged. Each part uses a ConfigurableJoint to attach it to the vehicle body.

Setup Requirements

  1. The part must be a separate child GameObject of the vehicle with its own Rigidbody.
  2. A ConfigurableJoint is required (automatically created when adding the component via Reset()).
  3. The part's GameObject and children must be on the RCCP_DetachablePart layer.
  4. The joint's connectedBody should reference the vehicle's main Rigidbody.
  5. BodyTilt is optional. If the vehicle has an RCCP_BodyTilt addon, the part automatically follows it via a ParentConstraint. If not, the part simply follows the vehicle hierarchy directly -- no constraint is created, and no error is raised (fixed in V2.58; previously any detachable part on a vehicle without BodyTilt threw an NRE in Awake).

Part Types

The DetachablePartType enum identifies the role of each part:

Part TypeDamage MultiplierDescription
Bumper_F1.5xFront bumper -- takes the most damage
Bumper_R1.5xRear bumper -- takes the most damage
Trunk1.2xTrunk lid
Hood1.0xEngine hood
Other1.0xAny other body panel
Door0.8xDoors -- slightly more resistant

Damage multipliers are applied when useDamageWeighting is enabled (default: true). Bumpers absorb 50% more damage per collision than hoods, while doors absorb 20% less.

Damage Lifecycle

A detachable part goes through three stages as its strength decreases:

  1. Locked -- The ConfigurableJoint motions are locked. The part is rigidly attached to the vehicle.
  2. Loose (strength <= loosePoint) -- Joint motions are unlocked to their original settings. The part wobbles and can flap in the wind (controlled by addTorqueAfterLoose, applied in FixedUpdate so the effect is framerate-independent).
  3. Detached (strength <= detachPoint) -- The part breaks free from the vehicle, becomes an independent physics object, fires OnRCCPPartDetached, and is deactivated after deactiveAfterSeconds.

Configuration

PropertyTypeDefaultDescription
partTypeDetachablePartTypeHoodIdentifies this part's role for damage weighting.
strengthfloat100Current durability. Decreases on each collision.
lockAtStartbooltrueLock the ConfigurableJoint motions at startup so the part stays firmly attached.
isDetachablebooltrueWhether this part can fully detach. If false, the part can become loose but never falls off.
loosePointint50Strength threshold below which the part becomes loose (joint unlocks).
detachPointint0Strength threshold below which the part fully detaches from the vehicle.
deactiveAfterSecondsfloat5.0Seconds after detachment before the part's GameObject is deactivated.
addTorqueAfterLooseVector3(0,0,0)Torque applied in local space (in FixedUpdate) when the part is loose, scaled by vehicle speed. Creates a flapping effect.
useDamageWeightingbooltrueApply the part-type-based damage multiplier.
onDamagedUnityEvent<float>(none)Invoked every time this part takes damage; the float argument is the damage amount just applied to strength. V2.58 -- this field was declared since the original Enhancements pass but was never actually invoked; it now fires on every OnCollision.
COMTransformAuto-createdOptional center of mass override for the part's Rigidbody.
Broken (read-only)boolfalsePublic accessor for whether the part has fully detached.

RCCP_Damage Part Settings

The RCCP_Damage component has its own toggles that control whether detachable parts receive damage at all:

PropertyTypeDefaultDescription
partDamagebooltrueMaster toggle for detachable part damage.
partDamageRadiusfloat1.0Radius around the contact point in which parts are checked for damage.
partDamageMultiplierfloat1.0Global multiplier applied to all part damage (stacks with per-part type multipliers).

Wheel Damage

Wheel damage displaces RCCP_WheelCollider positions on collision, simulating bent axles and misaligned wheels. When damage exceeds maximumDamage, wheels can optionally detach from the vehicle entirely.

Configuration

PropertyTypeDefaultDescription
wheelDamagebooltrueMaster toggle for wheel damage.
wheelDamageRadiusfloat2.0Radius around the contact point within which wheels are affected.
wheelDamageMultiplierfloat1.0Scales the amount of wheel displacement.
wheelDetachmentbooltrueWhen enabled, wheels that exceed maximumDamage displacement will detach from the vehicle (after a spawnDetachGrace window from spawn -- default 1 second -- so a spawn-drop impact can't instantly shed wheels).

When a wheel detaches, RCCP_WheelCollider.DetachWheel() is called and OnRCCPWheelDetached fires, which separates the wheel model from the vehicle and creates an independent physics object.

Raw wheel displacement is cosmetic by itself -- it has no effect on handling until you add RCCP_DamageMechanics (below), which reads the same accumulated offset via GetWheelDamageOffset() to drive steering misalignment and staged deflation.

Light Damage

Light damage reduces the strength of RCCP_Light components near the collision point. When a light's strength falls below its breakPoint, the light is marked as broken, turns off, and fires OnRCCPLightBroken.

RCCP_Light Damage Properties

Each RCCP_Light component has its own durability settings:

PropertyTypeDefaultDescription
isBreakablebooltrueWhether this light can be broken by collisions. Honored since V2.51 -- setting it false genuinely prevents breakage.
strengthfloat100Current durability. Reduced by the light's computed damage * 20 on each nearby collision (see Tuning Reference).
breakPointint35Strength threshold below which the light is considered broken.
brokenboolfalseRead at runtime to check if the light is broken.

RCCP_Damage Light Settings

PropertyTypeDefaultDescription
lightDamagebooltrueMaster toggle for light damage.
lightDamageRadiusfloat0.75Radius around the contact point within which lights are checked.
lightDamageMultiplierfloat1.0Scales the damage applied to lights.

Damage Mechanics (RCCP_DamageMechanics, V2.58+)

RCCP_DamageMechanics is a separate, opt-in component that turns the zone-health telemetry above into actual driving consequences. RCCP_Damage never requires it and stays purely cosmetic without it; add RCCP_DamageMechanics (menu: BoneCracker Games > Realistic Car Controller Pro > Addons > RCCP Damage Mechanics) to a vehicle that already has RCCP_Damage with trackZoneHealth enabled, and four independent systems activate.

Everything this component writes goes through inert hooks on the existing components (RCCP_Engine.damageTorqueMultiplier, RCCP_WheelCollider.damageSteerBias, RCCP_WheelCollider.deflationWobble) -- removing or disabling the component restores stock behavior, resetting every hook back to its neutral value (1 for the torque multiplier, 0 for the bias/wobble fields).

It runs at [DefaultExecutionOrder(-8)] -- the same slot as RCCP_Limiter -- so its FixedUpdate writes land before RCCP_Engine (-7) reads damageTorqueMultiplier the same frame. If no RCCP_Damage component is found on the vehicle, it logs one warning, clears any consequence it had already applied, and idles; it never throws.

Zone health drives the engine and fire systems only. Steering misalignment and staged deflation read wheel displacement instead, so they keep working with RCCP_Damage.trackZoneHealth off -- in that configuration the component logs one warning naming the two systems that will see a permanently healthy engine zone, rather than idling everything.

Engine Damage

PropertyTypeDefaultDescription
engineDamagebooltrueMaster toggle. Reduces engine torque as the engine zone takes damage.
engineZoneRCCP_DamageZoneFrontWhich zone hosts the engine. Flip to Rear for rear-engine layouts.
healthToTorqueCurveAnimationCurveLinear (0, 0.4) -> (1, 1.0)Torque multiplier over engine-zone health. Full power at health 1, 40% at health 0 -- the 0.4 floor is baked into the curve's endpoint, not a separate field.
overheatHealthThresholdfloat, 0-10.5Engine-zone health below which the radiator is considered damaged and heat starts building.
overheatBuildTimefloat, min 160Seconds until heat reaches maximum, measured at the reference RPM (where rpmToHeatRateCurve reads 1).
rpmToHeatRateCurveAnimationCurve(0, 0.1) -> (0.5, 1.0) -> (1, 2.0)Heat build rate over normalized engine RPM, X = (rpm - minEngineRPM) / (maxEngineRPM - minEngineRPM). Idling barely heats; redline heats ~2x as fast. Evaluated result is clamped at 0, so a curve dipping negative cannot actively cool.
overheatCoolTimefloat, min 145Seconds to shed all heat while the engine is off or the radiator is intact again. Independent of build rate.
overheatTorqueFadefloat, 0-10.3Additional torque fade at maximum heat -- up to 30% further power loss on top of the health curve.

Radiator heat (EngineHeat01, read-only) builds only while both conditions hold: the engine is actually running (Engine.engineRunning) and engine-zone health sits below overheatHealthThreshold. The rate is scaled by rpmToHeatRateCurve evaluated at normalized RPM, so a damaged radiator cooks quickly under sustained high revs but barely warms at idle. In every other case -- engine switched off, killed by fire, radiator intact, or repaired -- heat dissipates over overheatCoolTime.

This gating matters beyond the gauge: heat also feeds the smoke tier in RCCP_Particles (see below), so an unattended parked wreck neither creeps to 100% heat nor smokes from an engine that is not running, and it does not carry a stale torque penalty into the next start.

The final multiplier is healthToTorqueCurve.Evaluate(health) * (1 - EngineHeat01 * overheatTorqueFade), clamped to [0, 1], written to Engine.damageTorqueMultiplier every FixedUpdate.

Steering Misalignment

PropertyTypeDefaultDescription
steeringMisalignmentbooltrueMaster toggle. Bends steering alignment (toe error) as wheels take collision damage -- the car pulls to one side.
maxToeErrorfloat, 0-103Maximum toe error in degrees at full wheel displacement.
offsetToToeCurveAnimationCurveLinear (0,0) -> (1,1)Toe error over normalized wheel displacement (offset / RCCP_Damage.maximumDamage), as a fraction of maxToeError.

Applied to all wheels (a bent rear wheel makes the car crab slightly -- correct behavior). The sign comes from the lateral (local X) component of the wheel's accumulated displacement: a wheel shoved toward +X toes right, toward -X toes left.

Staged Deflation

PropertyTypeDefaultDescription
stagedDeflationbooltrueMaster toggle. Heavily damaged wheels develop a slow leak instead of staying magically inflated.
leakThresholdfloat, 0-10.5Wheel displacement (as a fraction of RCCP_Damage.maximumDamage) that triggers a slow leak.
slowLeakDurationfloat, min 010Seconds for a slow leak to fully deflate the tire, via RCCP_WheelCollider.Deflate(duration).
wobbleAmplitudefloat, 0-101.5Per-revolution visual wobble amplitude (degrees) on deflated wheel models, driven by deflationWobble.
deflatedBrakeDragfloat, min 050Constant brake drag (Nm) applied on a deflated wheel via the existing brake-torque accumulator pipeline, scaled by deflationT -- pulls the car toward the flat without fighting ESP.

The slow-leak trigger fires once, on the rising edge (when accumulated wheel displacement first crosses leakThreshold); once triggered, RCCP_WheelCollider.Deflate(slowLeakDuration) lerps radius and stiffness down over that duration, ending at the existing binary-deflation values (radius x0.8, stiffness x0.25, from RCCP_WheelCollider's own deflatedRadiusMultiplier/deflatedStiffnessMultiplier). Existing binary deflation behavior (via spike strips or Deflate()) is unchanged -- staging only animates the transition when the trigger comes from accumulated collision damage.

Fire -> Engine Death

PropertyTypeDefaultDescription
fireDamagebooltrueMaster toggle. Critical engine-zone damage ignites a fire that kills the engine unless repaired.
fireHealthThresholdfloat, 0-10.15Engine-zone health at or below which the fire can ignite.
fireIgnitionDelayfloat, min 03Seconds of sustained critical damage before the fire actually ignites.
fireToEngineDeathTimefloat, min 120Seconds of burning until the engine dies. Repair extinguishes the fire and restores everything.

Runtime read-only state: bool IsOnFire, float FireIntensity01 (0-1 progress toward engine death), float EngineHeat01 (0-1 radiator heat, also drives the VFX smoke tier below). Ignition fires OnRCCPCaughtFire once; the countdown to death then drives FireIntensity01. Engine death goes through the public CarController.KillEngine() facade (not a raw Engine.engineRunning write).

The restore latch is deliberately narrow: the component only claims the right to restart the engine when it actually stopped a running one, so a repair never hot-starts an engine the player had switched off themselves. That latch is released on repair, on disabling fireDamage, and on disabling or removing the component -- each of those hands a fire-killed engine back rather than leaving the vehicle permanently dead.

Repair Contract

RCCP_DamageMechanics subscribes to OnRCCPRepaired. When it fires, the component resets EngineHeat01, IsOnFire, FireIntensity01, and both fire timers to zero, sets Engine.damageTorqueMultiplier back to 1, and restarts the engine via CarController.StartEngine() if it had been killed by fire. Steering bias and deflation wobble don't need an explicit reset in the handler -- by the time OnRCCPRepaired fires, RCCP_Damage has already restored wheel positions and re-inflated tires, so the very next FixedUpdate naturally recomputes zero toe bias and inert deflation state from the now-pristine wheel offsets.

Cross-Cutting

PropertyTypeDefaultDescription
recomputeInertiaOnDetachboolfalseWhen enabled, subscribes to OnRCCPPartDetached/OnRCCPWheelDetached and calls RCCP_AeroDynamics.RecomputeInertia() on each -- fulfilling the V2.53 frozen-tensor contract, which lists damage-driven mass changes as a recompute trigger. Off by default because it's an extra physics recalculation per detach event.

Missing Engine or wheels (e.g. trailers, EV rigs mid-setup) simply idle their respective system -- nothing throws.

Editor

The custom inspector (RCCP_DamageMechanicsEditor) groups the four systems behind their master toggles (only the relevant fields show once a toggle is on), and while in Play Mode adds a live Runtime readout block: per-zone health (Front / Rear / Left / Right, as a percentage), Engine Heat, and Fire status (burning percentage or --). The readout uses the shared RCCP_DesignSystem.RepaintInspectorIfHovered() gate, so it only repaints while your cursor is actually over the Inspector.

The RCCP_DamageMechanics inspector in Play Mode — grouped system toggles plus the live Runtime readout block (per-zone health, Engine Heat, Fire status)

Damage VFX (RCCP_Particles, V2.58+)

RCCP_Particles remains the single owner of collision, scratch, and damage VFX. It reacts to damage state without requiring RCCP_DamageMechanics for most of it -- only the fire tier of the smoke/fire stages needs the mechanics component.

Impact Feedback

On every qualifying collision (OnCollision), beyond the existing contact-spark burst (rateOverTime = collision.impulse.magnitude / 500, unchanged):

Smoke & Fire Stages

enableDamageSmoke (bool, default false -- this is a visible behavior change, so it's opt-in) drives a looping engine-bay smoke emitter from the engine zone's health alone -- no RCCP_DamageMechanics required for the smoke tier:

The fire tier reads RCCP_DamageMechanics.IsOnFire and requires the mechanics component -- without it, the flame system never enables regardless of enableDamageSmoke. Smoke and fire drive independently of each other, so a fire-only configuration (no smoke prefab assigned) still burns correctly.

damageSmokeAnchor (Transform, optional) positions the emitter; if left empty it auto-resolves to front-center of the vehicle's body-mesh bounds at hood height (only MeshRenderer/SkinnedMeshRenderer bounds are used -- idle ParticleSystemRenderer bounds report a zero-size box at world origin and would otherwise drag the computed anchor toward (0,0,0)).

Event Bursts

Three one-shot bursts, each a single pooled ParticleSystem instance (no runtime Instantiate after Start()):

TriggerPrefab slotBehavior
Tire deflates (rising edge of RCCP_WheelCollider.deflated)blowoutBurstPrefabPlays at the wheel's position. Because staged deflation flips deflated to true the instant a slow leak begins (not when it finishes), the blowout burst fires at the start of a slow leak, not the end -- it reads as "something just gave way," which matches an instant spike-strip deflation too.
OnRCCPPartDetacheddetachDebrisPrefabPlays at the detached part's position.
OnRCCPWheelDetacheddetachDebrisPrefab (same pooled instance as part detach)Plays at the wheel's position.
OnRCCPLightBrokenglassShardPrefabPlays at the light's position.

Scrape Ramp

Long, continuous body-grinds (OnCollisionStay) now ramp up over time instead of staying at a constant rate:

PropertyTypeDefaultDescription
scrapeRampDelayfloat, min 01 (second)How long a scrape must continue before the ramp starts.
scrapeRampMaxMultiplierfloat, min 13Maximum emission multiplier reached after a long continuous scrape. 1 disables the ramp.

Once scrapeRampDelay has elapsed, the emission rate multiplier lerps from 1 to scrapeRampMaxMultiplier over the following 3 seconds of continued contact.

Soft Particles

RCCP_ParticleSoft.shader ("BoneCracker Games/RCCP/Particles/Alpha Blended Soft") is a Built-in-RP depth-fade particle shader swapped into the collision-smoke materials, softening the intersection where smoke meets the ground or body panels. It requires a camera depth texture (SOFTPARTICLES_ON); when no depth texture is available (e.g. some mobile configurations), it automatically falls back to plain, un-faded alpha blending -- the shader still renders correctly, just without the soft edge. Fallback "Legacy Shaders/Particles/Alpha Blended" covers any pipeline that can't run the custom pass at all.

Settings Reference

Five new prefab slots on RCCP_Particles, auto-assigned from RCCP_Settings in Reset() (same pattern as the three pre-existing contactSparklePrefab/scratchSparklePrefab/wheelSparklePrefab slots). Reset() only runs when the component is first added or manually reset in the Inspector, so any of these five slots that deserialize null on a pre-existing vehicle (e.g. one built before V2.58) are backfilled at runtime from the same RCCP_Settings fields in Start() -- an explicit per-vehicle assignment always wins over the backfill. A null slot on the vehicle therefore does not disable that effect by itself; the actual global off-switch is the corresponding field being null on the RCCP_Settings asset, since that is what the runtime backfill falls back to.

RCCP_Particles fieldRCCP_Settings source fieldUsed for
damageSmokePrefabdamageSmokeParticlesLooping engine-bay smoke.
damageFirePrefabdamageFireParticlesLooping fire (requires RCCP_DamageMechanics).
blowoutBurstPrefabblowoutParticlesOne-shot tire blowout burst.
detachDebrisPrefabdetachDebrisParticlesOne-shot part/wheel detach debris burst.
glassShardPrefabglassShardParticlesOne-shot light-break glass shard burst.

Mobile posture: everything is pooled and one-shot where applicable; impact lights are capped at one per contact-spark slot and can be disabled entirely (impactLights = false); soft particles auto-fallback to plain alpha blend with no depth texture; the smoke emitter is a single looping system in the 6-28 rate range.

Damage HUD (RCCP_SportyDamageUI, V2.58+)

RCCP_SportyDamageUI (Scripts/UI/, a RCCP_UIComponent) is the runtime damage readout: the four zone glows, per-wheel flat-tire icons, a radiator heat slider, and a fire warning badge + fire intensity slider. It reads the active player vehicle from RCCP_SceneManager every frame, so it works as a scene-level HUD and does not need to be parented to a vehicle. Zone glows come from RCCP_Damage.GetZoneHealth(); the heat and fire readouts require RCCP_DamageMechanics on the vehicle and hide themselves when it is absent or disabled.

Display Modes and Visibility

FieldDefaultMeaning
displayModeShowOnCollisionAlwaysOn keeps the HUD at full alpha permanently. ShowOnCollision fades it in on an event and back out afterwards.
visibilityDuration5Seconds the HUD stays up after a triggering event (ShowOnCollision only).
fadeSpeed3Alpha units per second for the fade in/out.
minCollisionForce2.5Minimum collision.relativeVelocity.magnitude that counts as a HUD-triggering impact.
holdWhileCriticaltrueKeeps the HUD visible for as long as the vehicle stays in a critical state, ignoring the visibility timer (ShowOnCollision only).
criticalHeatThreshold0.5Normalized engine heat (RCCP_DamageMechanics.EngineHeat01, 0-1) at or above which the vehicle counts as critical.

In ShowOnCollision mode the timer is refilled by four edge events: an impact at or above minCollisionForce, an overall damage ratio increase (> 0.005), a wheel newly going flat, and the engine igniting.

holdWhileCritical adds a level-triggered hold on top of that. While any of the following is true, the HUD stays at full alpha regardless of the timer:

This matters because radiator heat has no edge to trigger on -- it climbs gradually over overheatBuildTime after zone health drops below overheatHealthThreshold. Without the hold, the HUD faded out on its normal timer while the temperature gauge was still rising, hiding the readout precisely when the driver needed to watch it. The visibility timer keeps draining in the background during a hold, so once the vehicle leaves the critical state (repair, cooldown, tire re-inflation) the HUD fades out immediately rather than earning a fresh visibilityDuration.

Set holdWhileCritical = false to restore the pure timer behavior, or raise criticalHeatThreshold toward 1 to hold only on near-terminal overheating. The flag has no effect in AlwaysOn mode.

Tuning Reference: Damage Scaling Constants

These constants were previously implicit in the code. All hit-scaling shares one normalized impulse value, computed once per collision:


normalizedImpulse = collision.impulse.magnitude / 7500

In RCCP_Damage.OnCollision, a value below 0.5 is treated as zero -- the hit is discarded and no subsystem (including zone tracking) processes it at all -- and a value above 10 is capped at 10. So the effectively-processed range is [0.5, 10], not a symmetric clamp. RCCP_Particles computes the same ratio for its own impact-scaling (extra burst size, impact light) but applies a true Mathf.Clamp(x, 0, 10) -- its floor is 0, not 0.5, since it has no "discard the hit" case.

SubsystemFormulaFalloff shape
Mesh deformationdisplacement = direction * (normalizedImpulse * (1 - clamp01(dist / deformationRadius))) * (deformationMultiplier / 10)Linear, 100% at the contact point down to 0% at the radius edge.
Wheel displacementdisplacement = (normalizedImpulse * wheelDamageMultiplier / 30) * (1 - 0.5 * clamp01(distSqr / radiusSqr))Squared-distance, up to 50% reduction at the radius edge.
Part strengthdamageToApply = (normalizedImpulse * partDamageMultiplier * (1 - 0.5 * squaredFalloff)) * 5 * typeMultiplierSquared-distance, up to 50% reduction. typeMultiplier is the Part Types table above (0.8x-1.5x).
Light strengthstrengthLoss = (normalizedImpulse * lightDamageMultiplier * (1 - 0.5 * squaredFalloff)) * 20Squared-distance, up to 50% reduction.
Zone healthzoneHealth -= normalizedImpulse * zoneDamageMultiplier (default 3), clamped [0, 100]None -- flat deduction, no distance falloff, independent of which cosmetic subsystems are enabled.

These scales are unchanged by V2.58 (mesh /10, wheel /30, part x5, light x20, the 0.5 floor, and the 10 cap were all load-bearing before this release and remain load-bearing now) -- V2.58 only adds the zone-health row and the parallel VFX impulse scale.

Saving and Loading Damage

Damage state can be persisted between sessions using JSON serialization via PlayerPrefs.

DamageData Class

The RCCP_Damage.DamageData class stores the complete damage snapshot:

Detachable part state and wheel deflation are persisted and restored on load (as of dataVersion 1) -- a saved broken part re-runs its detach flow on load, a saved-loose part re-applies its loose joint state, and a saved-deflated wheel re-deflates (or a saved-inflated wheel re-inflates, restoring both directions).

Save Format & Versioning

Every save writes dataVersion = 1 and the full zone/part/wheel-deflation payload. On load:

Save / Load API


RCCP_Damage damage = vehicle.GetComponentInChildren<RCCP_Damage>();

// Save current damage state (always synchronous)
damage.Save();

// Load previously saved damage state (always synchronous)
damage.Load();

// Delete saved damage data
damage.Delete();

The saveName property is used as the PlayerPrefs key (with _DamageData appended). Make sure each vehicle has a unique saveName if you want independent save slots.

Save()/Load() always route through the synchronous RCCP_DamageData.SaveDamageRaw/LoadDamageRaw paths (see Common Issues below for what changed here in V2.58). The older RCCP_DamageData.SaveDamage/LoadDamage async overloads still exist but are [Obsolete] -- they now simply forward to the synchronous versions.

Repairing Vehicles

There are two ways to repair a vehicle:

Using the Public API


// Repair a specific vehicle
RCCP.Repair(carController);

// Repair the current player vehicle
RCCP.Repair();

Both methods set repairNow = true on the vehicle's RCCP_Damage component, which triggers the repair process on the next frame.

What Repair Does

When repairNow is set to true:

  1. Meshes -- All deformed vertices are moved back to their original positions.
  2. Wheels -- Wheel position is restored to its original local value; wheel rotation is force-set to Quaternion.identity (not restored to whatever its original rotation was). Deflated tires are re-inflated via Inflate().
  3. Detachable Parts -- Each part's OnRepair() is called: strength is restored, the ConfigurableJoint is recreated if destroyed, joint properties are restored, and the part is re-enabled if it was deactivated.
  4. Lights -- Each light's OnRepair() is called: strength is restored and broken is set to false.
  5. Zone health -- All four zones reset to 100 (V2.58).
  6. Mechanics state -- If RCCP_DamageMechanics is present, OnRCCPRepaired resets heat, fire, and the torque multiplier, and restarts the engine if it had been killed by fire (V2.58; see Repair Contract above).

Repair is a one-shot, single-pass operation, not an animated interpolation: the vertex/wheel-position math is a direct assignment back to the cached original values (vertices[i] += (original[i] - vertices[i]) algebraically reduces to vertices[i] = original[i]), so repaired becomes true in the same call that processed it -- there's no "not complete in one frame, continues on subsequent frames" case in practice. RCCP_Events.Event_OnRCCPRepaired fires once, at the end of that same pass.

Common Issues

Damage not showing on collision

Detachable parts not falling off

Repair not working

Wheel detachment not triggering

Damage Mechanics has no effect

Damage smoke/fire not appearing

Performance concerns with deformation

Related Topics


Support: bonecrackergames@gmail.com | www.bonecrackergames.com

Need help? See Troubleshooting