On this page
Reference

Control vehicles from code

Goal: Let a script drive a vehicle, then give control back to the player. This chapter assumes that you can create a C# MonoBehaviour script and attach it to a GameObject.

For keyboard and gamepad configuration, use Controls. For a vehicle that follows a route without a driving script, use AI Vehicles.

Use the RCCP namespace

Add using BoneCrackerGames; at the top of each script that calls the RCCP API, before the class declaration. The namespace lets your script find RCCP types. The class names and member names stay the same.

C#
using BoneCrackerGames;
using UnityEngine;

public class MyVehicleScript : MonoBehaviour {
    public RCCP_CarController car;
}

If an existing script reports CS0246 for an RCCP type after the upgrade, add the namespace import first. Existing specialized namespaces, for example BoneCrackerGames.Prototypes, keep their names.

Scripts in an assembly definition

A namespace import does not add an assembly reference. RCCP core uses the predefined Assembly-CSharp assembly. An assembly definition cannot reference that predefined assembly. Keep the script that calls RCCP outside the folders that your .asmdef controls. Alternatively, use an adapter in the predefined assembly. Do not add an .asmdef to the RCCP core folder to fix a namespace error.

Before you start

  • Use a vehicle that drives correctly in Your First Drive.
  • Keep the Inputs module of the vehicle enabled and Player can drive this car on. This flag also allows scripted driving.
  • Test one scripted driver at a time. Disable AI, and stop recording, replay and other scripts that control this vehicle.
  • Use a clear test area and an engine that already runs.

An input is a request, for example throttle or steering. An input handle is the object that your script receives when the script takes control. With the handle, RCCP can reject writes from a script that does not own the vehicle now.

1. Add a simple scripted driver

The example applies a small throttle input and drives straight. The example gives back control when you disable the example component. The example is a starting point for one driver only. A mission or network system must also manage its own vehicle handoffs.

  1. Create RCCP_ScriptedDriveExample.cs in your own scripts folder.
  2. Replace the contents of the file with the example below.
  3. Attach the script to an empty GameObject.
  4. Drag the vehicle root from the Hierarchy into the Car field of the component.
C#
using BoneCrackerGames;
using UnityEngine;

[DefaultExecutionOrder(-11)]
public class RCCP_ScriptedDriveExample : MonoBehaviour {
    [Tooltip("The working player vehicle this example will drive.")]
    public RCCP_CarController car;

    private RCCP_Input.ControlHandle handle;
    private readonly RCCP_Inputs inputs = new RCCP_Inputs();

    private void OnEnable() {
        if (car == null || car.Inputs == null || !car.canControl) {
            Debug.LogWarning("Assign a working car with Inputs and player control enabled.", this);
            enabled = false;
            return;
        }

        if (car.externalControl || car.Inputs.IsControlled) {
            Debug.LogWarning("Another driver already controls this car.", this);
            enabled = false;
            return;
        }

        handle = car.Inputs.AcquireControl(this);
        RCCP.SetExternalControl(car, true);
        inputs.Clear();
        inputs.throttleInput = 0.25f;
    }

    private void Update() {
        if (handle == null || !handle.Set(inputs))
            enabled = false;
    }

    private void OnDisable() {
        if (handle != null && handle.IsValid) {
            handle.Release();
            if (car != null)
                RCCP.SetExternalControl(car, false);
        }
        handle = null;
    }
}

2. Test the example and return control

  1. Wait until Unity compiles the script.
  2. Examine the Console for errors.
  3. Click Play. Do not touch the driving keys while the example runs.
  4. Select the GameObject of the example and clear the enabled checkbox of the component.
  5. Click in the Game view and try the usual driving controls again.
  6. Stop Play Mode.

Check: The vehicle drives forward under script control. After you disable the example, the vehicle accepts player input. If another driver takes control, the example disables itself. The example does not release the handle of that driver.

Troubleshoot the example

Symptom Check
The component disables itself immediately Read the Console warning of the component. Assign Car, enable Inputs and player control, and release all other scripted drivers.
The engine revs but the vehicle does not move Make sure that the vehicle drives correctly without the example. Examine the engaged gear and the vehicle setup.
The script stops controlling the vehicle Possibly another system acquired control. Examine handle.IsValid and the Console.
The player can still operate the lights or other vehicle keys while your script drives AcquireControl does not set externalControl. Use RCCP.SetExternalControl, as the example does.
A brake command makes the vehicle reverse Read the input shaping section below. If shaping is enabled, auto-reverse can change braking into reverse.
Writes succeed during a replay, but the vehicle does not react Replay delays live control changes until the replay ends. Refer to Scripted control during a replay.

The remaining sections are reference information to extend the example. You do not have to change all flags or use all methods.

Take control of a vehicle

Use AcquireControl to drive a vehicle from a script. One call takes the vehicle, and one call gives the vehicle back. The token that you get tells you if another system took the vehicle in the meantime.

Member Signature Function
AcquireControl ControlHandle AcquireControl(object owner, bool bypassInputProcessing = true) Takes exclusive scripted control and returns a token. Returns null if owner is null.
ControlHandle.Set bool Set(RCCP_Inputs newInputs) Writes the inputs for this tick. If the handle is not valid, returns false and writes nothing.
ControlHandle.Release void Release() Gives the vehicle back to usual input handling. A second call is safe. If the handle is stale, the call has no effect.
ControlHandle.IsValid bool false after another system acquires the vehicle, after a direct override call takes control, or after a release.
ControlHandle.Owner object The object that holds the vehicle now, or null.
IsControlled bool true while a system holds a token for this vehicle.
ControlOwner object The current owner, or null.

Acquire control one time. Call handle.Set(inputs) in each update. Release control when you are done. Use the same RCCP_Inputs object, as the example does. Do not allocate a new object in each frame.

Pass this as the owner. If the owner is a UnityEngine.Object and is destroyed while it holds the vehicle, RCCP_Input releases the vehicle in its next Update. RCCP_Input also logs a warning with the vehicle name. Without this release, a destroyed mission script leaves the vehicle permanently without response.

A second AcquireControl call from a different owner is permitted. The call logs a warning with the names of the two owners. The first handle becomes invalid. Thus, the system that lost the vehicle can detect the loss.

The bypassInputProcessing parameter

The default value of bypassInputProcessing is true. This value enables Skip all input shaping (overrideExternalInputs). Then RCCP skips the steering curve, counter steering, auto-reverse, pedal swap in reverse and the throttle cut during a shift. The deadzones, cruise control and hill start assist still apply.

The wheel-level steering limit still applies to scripted vehicles, also to raw input. The limit acts on grounded wheels on steering axles. To disable the limit, disable Steering limiter (steeringLimiter) on the Inputs module of the vehicle. Alternatively, set car.Inputs.steeringLimiter = false.

If a global or custom behavior preset applies, disable Limit Steering (limitSteering) on that preset. The preset value overwrites the vehicle setting. Read the resulting wheel angle through the wheel facade.

To adjust the correction strength, set car.Inputs.steeringLimiterMultiplier to a value from 0f to 1f. The default 1f keeps the full correction. 0.5f applies half of the correction, and 0f removes the correction. If a behavior preset applies, set Steering Limiter Multiplier on the preset. The preset replaces the value on the vehicle.

Keep bypassInputProcessing set to true, unless you want input shaping. If you pass false, the current shaping flag does not change. The call does not enable shaping again.

If shaping is active, the gearbox is automatic and auto-reverse is on, a full brake input at low speed selects reverse. Then a test or a cutscene that brakes drives backward.

On release, Skip all input shaping gets back the value from before the first acquire. Thus, if two systems hand the vehicle over, the vehicle still goes back to its initial state.

The older override protocol

OverrideInputs and DisableOverrideInputs are older than the token. RCCP still fully supports these methods.

Member Signature Function
OverrideInputs void OverrideInputs(RCCP_Inputs overridedInputs) Sets Inputs come from a script (overridePlayerInputs) to on and assigns the inputs object.
DisableOverrideInputs void DisableOverrideInputs() Sets Inputs come from a script to off and gives the vehicle back to usual input handling.
ResetInputs void ResetInputs() Replaces the inputs object with a new object at zero, and sets the six live axis values to zero. To continue to drive, pass your own object again with OverrideInputs or handle.Set.

OverrideInputs does not enable Skip all input shaping. To skip input shaping, enable this option yourself. The wheel-level steering limit still applies, as described above.

A direct OverrideInputs call takes control and makes all existing handles invalid. If a handle existed, the call logs a warning. The call also sets Skip all input shaping back to its value before token control.

The built-in AI uses this protocol. The AI sets externalControl itself and calls OverrideInputs in each tick.

The inputs class

RCCP_Inputs is a plain serializable class. The default constructor sets all values to zero. A seven-argument constructor takes all the values below, in this order. The Clear() method sets all values of the object to zero.

Field Range Meaning
throttleInput 0 to 1 0 = no throttle, 1 = full throttle
brakeInput 0 to 1 0 = no brake, 1 = full brake
steerInput -1 to 1 -1 = full left, 0 = center, 1 = full right
handbrakeInput 0 to 1 0 = released, 1 = fully engaged
clutchInput 0 to 1 0 = engaged, 1 = fully disengaged
nosInput 0 to 1 0 = off, 1 = full
mouseInput — Camera orbit input (Vector2), not a driving input

Input processing order

When no replay runs, RCCP_Input.Update processes device inputs and scripted inputs in this order:

  1. If the token owner no longer exists, the component releases the vehicle.
  2. If Player can drive this car (canControl) or Driven by a script or AI (externalControl) changed since the last frame, the component replaces the inputs object. The new object has all values at zero.
  3. The component reads the device inputs. This step is skipped while Inputs come from a script is on.
  4. The component applies the deadzone of each axis and copies the results into the live axis values.
  5. The component applies input shaping. This step is skipped while Skip all input shaping is on.
  6. The component applies cruise control and hill start assist.

Step 4 also applies to your values. Each axis has its own deadzone. The default of throttleDeadzone, steeringDeadzone and the other four deadzones is 0.05. RCCP remaps a value above the deadzone as (value - deadzone) / (1 - deadzone). Thus, at the default deadzone, a scripted throttle of 0.5 reaches the vehicle as 0.474. If you need exact values, write 0 and 1. Alternatively, set the deadzones to zero on vehicles that only a script controls.

Because of step 2, the vehicle stops its response for a short time when you change canControl or externalControl. In that frame, step 2 replaces your inputs object with an empty object. In the next frame, pass your object again with handle.Set or OverrideInputs.

Lock the player out

Two flags on RCCP_CarController control if the device of the player reaches the vehicle. Set the flags through the facade: RCCP.SetControl(vehicle, bool) and RCCP.SetExternalControl(vehicle, bool).

Flag Default Effect against the player
Player can drive this car (canControl) true false stops the response of the vehicle to the device. The vehicle also stops its response to your script.
Driven by a script or AI (externalControl) false true marks the vehicle as script-driven. Then the vehicle also ignores the light, indicator, gear, engine and assist keys. Your own inputs still apply.

Do not disable canControl to lock the player out of a scripted sequence. In each fixed step, the vehicle reads its inputs again. While canControl is off, the vehicle writes zeros over all six inputs before the drivetrain reads them. The engine reads throttleInput_P, which is one of these values.

The vehicle also applies Apply handbrake (applyHandBrakeOnDisable), which is on by default. If Apply brake (applyBrakeOnDisable) is on, the vehicle also applies the brake. This option is off by default. Thus, a cutscene that disables canControl and then sends throttle gets a vehicle with the handbrake applied. The vehicle does not move.

Use externalControl instead. This flag stops the device and the key events, but does not change your values. The built-in AI uses the same method. Keep canControl on for all vehicles that your script drives.

Neither flag sends your input to the vehicle. To make RCCP use your values, you still need a token or an override.

All device axes and key events go through IsControllableByPlayer(). This method returns true only if all these conditions are true:

  • Player can drive this car is on.
  • Driven by a script or AI is off.
  • In Play Mode, if a player vehicle is registered, this vehicle is RCCP_SceneManager.activePlayerVehicle.

Because of the last condition, scripted vehicles in the scene do not toggle their indicators together with the player vehicle.

Use case Inputs come from a script Skip all input shaping Player can drive this car Driven by a script or AI
Player driving off off on off
AI or traffic on either on on
Cutscene, player locked out on on on on
Remote networked player on on on on
Parked vehicle that must not move off off off off

Vehicle state readouts

The fields and properties below on RCCP_CarController are live readouts. Read them to monitor the vehicle. To control the vehicle, change its configuration or its inputs.

Member Meaning
speed Road speed in km/h. Signed: negative while the vehicle moves backward.
absoluteSpeed The same value without the sign. A property that returns Mathf.Abs(speed).
wheelRPM2Speed Driven-wheel speed in km/h, always positive. Compare this value with absoluteSpeed. A difference can show tire slip, but is not a full slip diagnosis.
maximumSpeed The top speed that the gearing allows, in the highest gear at maximum RPM.
engineRPM Current engine speed in RPM, also zero when the engine is stopped.
currentGear Engaged forward gear, from 0 (first gear).
direction -1 while a reverse gear is engaged. 1 in all other gear states.
engineRunning / engineStarting Engine state.
shiftingNow true during a gear change.
reversingNow / NGearNow Reverse engaged / neutral engaged.
steerAngle Front axle steering angle in degrees, positive to the right. The value is before the steer multiplier, the Ackermann geometry and the wheel-level limit.

There are two families of input readbacks. A frequent mistake is to use the wrong family:

Suffix Description Use
_P The request of the player after the deadzones and the driver aids: throttleInput_P, brakeInput_P, steerInput_P, handbrakeInput_P, clutchInput_P, nosInput_P Game logic, HUD, scoring
_V Component readbacks: throttleInput_V, brakeInput_V, steerInput_V, handbrakeInput_V, clutchInput_V, nosInput_V, gearInput_V, fuelInput_V. Steering comes from axle values before the wheel-level limit. Effects and audio. For resulting wheel angles, use the wheel facade.

As the speed increases, the steering curve can decrease steerInput_P and steerInput_V. Neither value keeps the original full steering request. If your game needs that request, read the request from your driver before RCCP applies input shaping.

steerInput_V is the average of the angle of each steering axle divided by the maximum angle of that axle. For the resulting wheel angle, read SteerAngleDeg of each wheel after the solver step. Refer to wheel readouts and timing.

For per-wheel RPM, contact, physical slip and compatibility slip, use the wheel API.

Change gear from code

The five shift methods are on RCCP_Gearbox. While shiftingNow is true, all five methods return immediately and do nothing. Thus, a call in each frame is safe, but a queued call is not. A shift takes Shift time (shiftingTime) seconds. The default is 0.2.

Method Function
ShiftUp() Up one gear. From reverse, the gearbox goes to gear 0.
ShiftDown() Down one gear. At gear 0, the method calls ShiftReverse().
ShiftToGear(int gear) Directly to a forward gear index, or -1 for reverse. The method refuses reverse while speed is above Max speed to engage reverse.
ShiftReverse() Into reverse. The method refuses the shift while speed is above Max speed to engage reverse (maxSpeedToShiftReverse). The default is 20 km/h.
ShiftToN() Toggles between neutral and forward. The method is a toggle, not a set. A second call goes back to forward.

ShiftUp and ShiftDown also do nothing while forceToNGear is on. With the Automatic_DNRP transmission, these methods do nothing unless automaticGearSelector is D. With this transmission, ShiftReverse does nothing unless the selector is R.

Transmission (transmissionType) accepts Manual, Automatic or Automatic_DNRP. To set the transmission from the facade, use RCCP.SetAutomaticGear(vehicle, bool) or its TransmissionType overload. The high-level state is currentGearState.gearState. The value is Park, InReverseGear, Neutral or InForwardGear.

TargetSpeeds (the target speed of each gear) returns the same array on each read. RCCP fills the array again with the current values. If your code keeps the values, copy the array.

For full control, for example in a replay or a custom drivetrain, use OverrideGear(int targetGear, float targetGearInput, CurrentGearState.GearState gearState). This method sets Override gear (overrideGear) to on. Until you call DisableOverride(), the gearbox skips its automatic shifting, its gear-state updates and the rev-match blip. The shift methods do not examine Override gear. Thus, do not call the shift methods while the override is on.

Scripted control during a replay

RCCP_ReplayDirector holds the input lock during a playback. During the lock, RCCP keeps your writes but does not apply them. This applies to OverrideInputs, DisableOverrideInputs, ResetInputs, AcquireControl, Release and ControlHandle.Set. The writes go to shadow copies and apply when the replay ends.

Thus, two systems never drive the vehicle at the same time. ControlHandle.Set still returns true. To know if a replay runs, read RCCP.IsReplayActive.

Common mistakes

Mistake Reason
Write Rigidbody.linearVelocity or angularVelocity to move a vehicle This bypasses the full drivetrain. Grip, assists and audio do not see the movement, and the wheels resist the movement. Send inputs instead.
Disable Player can drive this car during a scripted sequence This sets the drivetrain inputs to zero and applies the handbrake. Use Driven by a script or AI.
Set the live throttleInput / steerInput fields on RCCP_Input These fields are outputs. RCCP writes them again from the inputs object in each frame. Write the inputs object.
Read native wheel simulation values Read the wheel facade.
Use compatibility slip as physical slip Use the separate slip readouts.
overrideInternalInputs Obsolete alias for overridePlayerInputs.
Set Driven by a script or AI and expect the vehicle to obey This flag only marks the vehicle as not a player vehicle. Without a token or an override, nothing drives the vehicle.
Call a shift method in a loop until the shift occurs The method does nothing while shiftingNow is true. Monitor that flag. Do not call the method again and again.
Set cutFuel on RCCP_Engine to cut the fuel cutFuel is an output flag. The rev limiter and launch control write this flag. Call car.Engine.LimitFuel(maxFuel) in each FixedUpdate instead, from a script with an order below -7. The engine applies the limit in its next update and then clears the limit.
  • The Public API: the facade methods in this chapter, with their full signatures.
  • Controls: the source of device input before this processing.
  • AI Vehicles: the built-in waypoint AI, which drives vehicles through the same override path.
  • Field Reference: all settings in this chapter, with their defaults and ranges.
BoneCracker Games · Realistic Car Controller Pro 2Back to top ↑
Document details

Generated from: 29_reference_scripting.md
Date: 2026-11-07 00:57
Generated by md-to-html