How RCCP is built
Goal: understand where RCCP's parts live and how they work together. Start with the short overview below; the implementation tables are for developers extending RCCP.
To build a car in the Inspector, use Vehicle Setup. To tune its handling, use How the Car Drives.
The short version
| Part | Think of it as… |
|---|---|
| Vehicle root | The top object for one car, carrying its main controller and Rigidbody. |
| Module | A component with one job, such as engine sound or steering input. |
| Scene manager | The scene's record of which vehicle the player is driving. |
| Shared settings | Project-wide defaults; runtime copies allow changes while playing. |
| Public API | Methods your script can call to ask RCCP to do something. |
| Event | A notification your script can receive when something happens. |
Follow these three rules when writing code:
- Get modules through the car controller, for example
car.Engine. Avoid depending on their names or exact positions in the Hierarchy. - Drive through the input API. This lets the drivetrain, tires and assists participate.
- Use runtime settings copies for changes that should last only while playing.
Check: you know whether your change belongs to one car, the scene, or shared settings. Use the sections below when you need the implementation details.
Choose a topic
- Components and their base types
- Finding a module
- Engine-to-wheel flow
- Execution order
- Listening for events
- Settings assets and runtime copies
The component model
A vehicle is one RCCP_CarController on the root GameObject plus subsystem components on child objects. Each subsystem finds the controller above it and registers itself into a named slot. This lookup is automatic; vehicle setup still includes assigning wheels and connecting differentials to axles.
Base classes provide shared behavior for concrete components. You add the concrete component, such as an Engine, rather than an abstract base class such as RCCP_Component.
Developer detail: base types
| Base class | Used by | What it provides |
|---|---|---|
RCCP_GenericComponent |
Scene-level managers | Cached RCCPSettings, RCCPGroundMaterials, RCCPSceneManager |
RCCP_Singleton<T> (extends RCCP_GenericComponent) |
RCCP_SceneManager, RCCP_InputManager, RCCP_SkidmarksManager |
An Instance accessor that finds an existing instance or creates a GameObject for one |
RCCP_MainComponent |
RCCP_CarController |
[SelectionBase], [DisallowMultipleComponent], the cached Rigid, and every subsystem slot |
RCCP_Component (implements IRCCP_Component) |
Every vehicle subsystem | Auto-discovery of the parent controller, plus Register() |
RCCP_UpgradeComponent (extends RCCP_Component) |
Customization parts | Loadout, Save(), Load() through the vehicle's RCCP_Customizer |
RCCP_UIComponent |
Canvas components | Cached RCCPSettings and RCCPSceneManager |
RCCP_Component is deliberately not [DisallowMultipleComponent]. Unity applies that attribute to a type and its subtypes, which made two different subclasses mutually exclusive on one GameObject — AddComponent silently returned null. The attribute sits on individual concrete components that are genuinely one-per-GameObject instead.
How a component registers itself
RCCP_Component.CarController is a lazy property. On first access it runs GetComponentInParent<RCCP_CarController>(true) and, if it finds one, calls Register(carController, this).
Register is a type switch. It assigns the component into the matching slot on the controller:
Developer detail: registration table and hierarchy layouts
| Component | Lands in |
|---|---|
RCCP_Engine |
CarController.Engine |
RCCP_Clutch |
CarController.Clutch |
RCCP_Gearbox |
CarController.Gearbox |
RCCP_Differential |
Triggers CarController.UpdateDifferentials(), which re-scans into Differentials |
RCCP_Axles |
CarController.AxleManager |
RCCP_Axle |
Appended to AxleManager.Axles |
RCCP_WheelCollider |
No slot — a wheel is reached through its axle |
RCCP_AeroDynamics |
CarController.AeroDynamics |
RCCP_Audio |
CarController.Audio |
RCCP_Input |
CarController.Inputs |
RCCP_Lights |
CarController.Lights |
RCCP_Light |
Lights.RegisterLight(...) |
RCCP_Stability |
CarController.Stability |
RCCP_Damage |
CarController.Damage |
RCCP_DamageMechanics |
CarController.DamageMechanics |
RCCP_DeformationSolver |
CarController.DeformationSolver |
RCCP_Particles |
CarController.Particles |
RCCP_Customizer |
CarController.Customizer |
RCCP_Lod |
CarController.LOD |
RCCP_Modules |
CarController.ModulesManager |
RCCP_OtherAddons |
CarController.OtherAddonsManager |
Eleven addon components register one level deeper, into OtherAddonsManager: RCCP_Recorder → .Recorder, RCCP_Exhausts → .Exhausts, RCCP_Limiter → .Limiter, RCCP_Nos → .Nos, RCCP_TrailerAttacher → .TrailAttacher, RCCP_Visual_Dashboard → .Dashboard, RCCP_Exterior_Cameras → .ExteriorCameras, RCCP_AI → .AI, RCCP_WheelBlur → .WheelBlur, RCCP_FuelTank → .FuelTank, RCCP_BodyTilt → .BodyTilt.
Two failure modes are worth knowing. An addon whose OtherAddonsManager is missing disables itself rather than throwing, and so does an axle with no axle manager. And a component with no RCCP_CarController anywhere in its parents logs an error and disables itself — except RCCP_Light, RCCP_TrailerAttacher and RCCP_WheelSlipParticles, which are allowed to live outside a vehicle.
Two hierarchy layouts, both supported
Vehicles created by current versions keep every module inside one RCCP_Modules container under the root. Older vehicles keep the modules directly under the root. RCCP_Modules holds no settings and runs no code; it is organizational only, and it deliberately carries no execution order because nothing registers into it.
Because a module can be at either depth, never look one up by path. transform.Find("RCCP_Engine") works on a flat vehicle and returns null on a grouped one. Read the slot instead (carController.Engine), or use GetComponentInChildren<T>().
The slot getters resolve through ResolveModule<T>(), which searches two levels below the vehicle root, then — if the container exists — two levels below the container. That is what makes both layouts behave identically.
Where the torque goes
Power travels through five stages, each one passing an RCCP_Output object carrying two floats: NM (torque in Newton-meters) and RPM.
| Stage | Component | What it does with the output |
|---|---|---|
| 1 | RCCP_Engine |
Produces torque from RPM and throttle, then invokes outputEvent |
| 2 | RCCP_Clutch |
ReceiveOutput stores it; its own FixedUpdate applies clutch slip and re-fires |
| 3 | RCCP_Gearbox |
Multiplies by the current gear ratio and re-fires |
| 4 | RCCP_Differential |
Splits torque left/right and calls RCCP_Axle.ReceiveOutput(left, right) |
| 5 | RCCP_Axle |
Pushes per-wheel torque into the wheel accumulators |
Each stage stores the incoming value, then processes it during its own FixedUpdate (Unity's physics update). RCCP orders these updates from engine to wheel so the result can travel through the chain during the same physics step.
RCCP_Differential sets isPower on the axle it is connected to; you do not set that flag yourself. The other three axle flags — isSteer, isBrake, isHandbrake — are authored per axle.
Developer detail: wheel torque
The accumulator pipeline at the wheel
RCCP_WheelCollider does not receive torque directly. It exposes additive accumulators — AddMotorTorque, AddBrakeTorque, AddHandbrakeTorque, AddEngineBrakeTorque, AddNegativeFeedback — that anything may add into during a fixed frame. The wheel consumes them in its own FixedUpdate, writes the result into Unity's WheelCollider, and resets them for the next frame.
That is why the last three orders are load-bearing. The axle fills the accumulators at -2, stability modifies them at -1 (ESP brake torque added, motor torque cut), the wheel consumes them at 0. Reverse any two and motor torque and ESP brake torque fight on the same driven wheel.
Execution order
Every order-sensitive class carries a [DefaultExecutionOrder] attribute, so the ordering holds even in a project where the editor helper is absent. RCCP_ScriptExecutionOrderManager is an [InitializeOnLoad] editor class that writes the same values into each script's MonoImporter on every assembly reload, so the Project Settings list agrees with the attributes.
Developer detail: execution-order table
| Order | Components | Why here |
|---|---|---|
| -50 | RCCP_SceneManager, RCCP_InputManager, RCCP_SkidmarksManager |
Singletons must exist before anything reads them |
| -13 | RCCP_ReplayDirector |
Pins recorded poses and feeds recorded input before every input writer |
| -12 | RCCP_AIDynamicObstacleAvoidance |
Produces steer and brake corrections before the AI consumes them at -11 |
| -11 | RCCP_AI, RCCP_Recorder |
Call Inputs.OverrideInputs(...) before the controller polls at -10 |
| -10 | RCCP_CarController |
Main controller |
| -8 | RCCP_Limiter, RCCP_DamageMechanics |
Write Engine.cutFuel and Engine.damageTorqueMultiplier before the engine reads them |
| -7 | RCCP_Engine |
Starts the torque chain |
| -6 | RCCP_Clutch |
|
| -5 | RCCP_Gearbox, RCCP_Axles, RCCP_Lights, RCCP_Exhausts, RCCP_OtherAddons |
Parent containers must register before their children; none of them has a FixedUpdate, so sharing the slot with the gearbox is safe |
| -4 | RCCP_Differential |
|
| -3 | RCCP_AeroDynamics |
Writes Rigidbody.centerOfMass and linearDamping before the suspension solver reads them |
| -2 | RCCP_Axle |
Fills the wheel accumulators |
| -1 | RCCP_Stability, RCCP_ReplayLatePhase |
Modifies the accumulators; ABS, ESP and TCS cuts |
| 0 | RCCP_WheelCollider |
Consumes the accumulators and applies them |
| 5 | RCCP_Camera |
Runs after the vehicle has moved |
| 10 | RCCP_Customizer, RCCP_Lod, RCCP_BodyTilt, RCCP_DeformationSolver |
Visual and late work; the deformation solver writes render meshes only |
Components left at the default 0 are the order-insensitive ones, including RCCP_Audio, RCCP_DetachablePart and RCCP_CrashCamera.
The net effect is that input-to-wheel-torque latency is bounded to a single fixed frame. At undefined order the same chain pipelines across five.
Ordering your own component
| If your component… | Give it an order of |
|---|---|
Pushes input through Inputs.OverrideInputs(...) |
-11 or earlier |
| Implements a new physics subsystem | Study the accumulator order above; ordinary driving scripts should use the input API |
| Reads settled vehicle state and does not write physics | 0 or later |
Events
Use events when your script needs to react to RCCP. For example, a race manager might listen for a car spawning, while a score system might listen for an impact.
Subscribe in OnEnable and unsubscribe in OnDisable. The events are static, so a subscription can outlive the scene object that created it if you do not remove it.
Check: enabling your component adds its listener once, and disabling it removes that listener. The API Reference contains the subscription example and the complete event/signature tables.
Vehicle and AI lifecycle notifications are separate. Collision notifications also differ: some happen on every contact, while the gameplay-impact event filters small or repeated hits. Choose the event by its description in the reference rather than by its name alone.
Settings, and why you get a copy
RCCP_Settings, RCCP_GroundMaterials and RCCP_ChangableWheels are ScriptableObjects loaded from Resources. Reading .Instance gives you the asset on disk, and writing to it in Play Mode persists after you stop.
RCCP_RuntimeSettings is a static class that hands out ScriptableObject.Instantiate clones instead, created on first access. The base-class accessors RCCPSettings, RCCPGroundMaterials and RCCPChangableWheels use these copies. Use .Instance in editor code only. RCCP_RuntimeSettings.Clear() clears the static references so later access can create fresh copies. Components that already cached a copy still hold that reference; it is not a way to refresh every live component.
The scene-level singletons work the other way: RCCP_SceneManager.Instance, RCCP_InputManager.Instance and RCCP_SkidmarksManager.Instance search the scene and, finding nothing, create a GameObject named after the type and add the component to it.
Read next
- The Public API — the facade methods for spawning, controlling and transporting vehicles, which is usually what you want before reaching for a component directly.
- Controlling Vehicles from Code — a scripted driving example and input ownership rules.
- Field Reference — every setting on every component, with its default and range.
- How the Car Drives — the same drivetrain chain described for someone tuning it rather than coding against it.