Scripting API
Everything your game code needs from the Tuner goes through one class: RCCPT_TunerController. This chapter is the reference for it, the events you can hook, the wallet, resets, localization, and the one namespace line you need.
All Tuner types live in:
using BoneCrackerGames.RCCP.Tuner;
The Controller at a Glance
var tuner = RCCPT_TunerController.Instance; // soft scene singleton (null if none in the scene)
// Open / close
tuner.OpenGarage(); // resolved vehicle: explicit -> active player -> scene scan
tuner.OpenGarage(myVehicle); // a specific RCCP_CarController
tuner.CloseGarage(); // discards any staged cart (the UI confirms first when needed)
bool open = RCCPT_TunerController.IsActive; // allocation-free static state mirror
// Economy hooks
tuner.AddCurrency(2500); // race-reward hook (persists immediately)
tuner.SetEconomyEnabled(false); // runtime master toggle (sandbox mode)
// Resets
tuner.ResetVehicleToStock(); // bound vehicle back to factory; ownership KEPT
tuner.ResetProgress(); // wipes balance/ownership/unlocks + re-seeds starting balance
// Events
tuner.OnGarageOpened += () => { };
tuner.OnGarageClosed += () => { };
tuner.OnVehicleBound += vehicle => { }; // RCCP_CarController
tuner.Wallet.OnBalanceChanged += () => { };
tuner.Session.OnCheckoutCompleted += result => { }; // RCCPT_PurchaseResult
Methods
OpenGarage() / OpenGarage(RCCP_CarController vehicle)
Opens the garage. The parameterless overload resolves the target vehicle in order: the controller's explicitVehicle field, else RCCP Scene Manager's active player vehicle, else the first RCCP_CarController found in the scene. Does nothing if the garage is already open, and logs a warning and aborts when no vehicle can be found or the vehicle has no RCCP_Customizer. A missing catalog set does not abort - the garage opens empty with a console warning telling you where to assign one.
CloseGarage()
Closes the garage, discarding any staged cart and reverting the vehicle to its owned baseline. The scene state (inputs, engine, damage, HUD, cameras) is restored exactly as it was before opening - plus any post-exit handoff overrides you enabled on the controller (Scene Setup).
AddCurrency(int amount)
Adds funds to the global wallet and writes the save immediately. This is the hook for race rewards, mission payouts, daily bonuses. Amounts of zero or less leave the balance alone - but the save is still written, so tuner.AddCurrency(0) is a deliberate "flush now" call after direct wallet or ownership writes (see The Wallet).
SetEconomyEnabled(bool state)
Runtime master toggle for the economy. false makes everything owned/free/unlocked and hides all wallet UI. Note it combines with the Economy Config's own Economy Enabled - both must be on for the economy to be active.
ResetVehicleToStock()
Factory-resets the bound vehicle's applied RCCP configuration (paint, wheels, levels, tuning) while the garage is open. Ownership and money are untouched - the player still owns everything and can re-equip for free.
ResetProgress()
Wipes the entire Tuner save - balance, all ownership, all unlocks - then re-seeds the starting balance. Vehicles' applied RCCP configurations are untouched. This is your "new game" call.
You do not need code for any of these while testing. The controller's inspector carries the same four actions as buttons in Play Mode - Open Garage / Close Garage, Add (the economy config's debug reward), Reset To Stock and Reset Progress - next to a live readout of the session state, bound vehicle, category, cart and balance. See Editor Tools.
Properties
| Property | Type | What it is |
|---|---|---|
RCCPT_TunerController.Instance |
RCCPT_TunerController | Soft scene singleton - finds the scene's controller; null when none exists (never auto-creates) |
RCCPT_TunerController.IsActive |
bool (static) | True while the garage is open. Allocation-free - safe to poll every frame |
tuner.Session |
RCCPT_TunerSession | The live cart/session state machine (events below) |
tuner.Wallet |
RCCPT_Wallet | The wallet (below) |
tuner.Ownership |
RCCPT_OwnershipStore | Who owns what - queries and grants, see Reading and Granting Ownership |
tuner.Checkout |
RCCPT_CheckoutService | The purchase pipeline the cart runs through. Its IsEffectivelyOwnedGlobal / GetEffectiveOwnedLevel are the economy-aware ownership queries the UI itself uses |
tuner.Adapter |
RCCPT_CustomizerAdapter | The one class that talks to RCCP's customization API, see A Note on the RCCP Seam |
tuner.Director |
RCCPT_CameraDirector | The garage camera, see Camera & Shots |
tuner.Catalogs / tuner.Economy / tuner.Theme / tuner.Shots |
assets | The resolved configuration (controller override, else RCCPT_Settings default) |
Events
Controller events
| Event | Signature | Fires |
|---|---|---|
OnGarageOpened |
Action |
After the garage opened and the camera took over |
OnGarageClosed |
Action |
After the garage closed and the scene was restored |
OnVehicleBound |
Action<RCCP_CarController> |
When a vehicle is bound on open (before OnGarageOpened) |
Session events (tuner.Session)
Three terms appear in this table. A cart entry (RCCPT_CartEntry) is one staged, not-yet-paid-for item: what it is, which exclusive slot it occupies, its base price, and the payload needed to apply it. A hover try-on is the temporary preview the car wears while the pointer rests on a card. The projection is what the car is currently showing - always "everything you own, plus everything in the cart, plus any pending tuning edits" - which is why a cart change repaints the car. All three are explained in Using the Garage.
| Event | Signature | Fires |
|---|---|---|
OnCheckoutCompleted |
Action<RCCPT_PurchaseResult> |
After every checkout attempt, with its result |
OnEquippedOwned |
Action |
When an owned/free item equips instantly |
OnItemStaged |
Action<RCCPT_CartEntry> |
When a click stages an item into the cart (nothing is paid yet) |
OnCategoryChanged |
Action<RCCPT_Category> |
When the player switches category |
OnProjectionChanged |
Action |
Whenever what the car is showing changed (cart edits, reverts...) |
OnHoverPreview |
Action<RCCPT_CartEntry> |
When a hover try-on lands on the car |
OnHoverCleared |
Action |
When a hover try-on ends |
RCCPT_PurchaseResult values: Success, Free, AlreadyOwned, InsufficientFunds, EconomyDisabled, Invalid - meanings in Prices & Economy.
Prefer OnItemStaged over Cart.OnChanged when you want to react to “the player just added something”. Replacing the occupant of a slot - swapping one wheel set for another - leaves the cart count unchanged, so OnChanged fires but tells you nothing new, while OnItemStaged hands you the entry that was staged. The built-in toast and the cart badge punch both ride on it.
A typical game integration needs only this much:
var tuner = RCCPT_TunerController.Instance;
tuner.OnGarageOpened += PauseMyGameSystems;
tuner.OnGarageClosed += ResumeMyGameSystems;
tuner.Session.OnCheckoutCompleted += result => {
if (result == RCCPT_PurchaseResult.Success)
Analytics.Log("garage_purchase");
};
The Wallet
var wallet = RCCPT_TunerController.Instance.Wallet;
int balance = wallet.Balance; // current funds
bool ok = wallet.CanAfford(5000);
wallet.Earn(1000); // add (ignores amounts <= 0)
bool spent = wallet.TrySpend(1000); // atomic spend - false if unaffordable
wallet.SetBalance(99999); // direct set, clamped >= 0 (debug / cheats)
wallet.OnBalanceChanged += () => { };
Which of these survive a quit
Every call above changes the save in memory. Only some of them write it to disk, and that difference is the one thing to get right before you ship:
| Call | Changes the balance | Writes the save |
|---|---|---|
tuner.AddCurrency(amount) |
Yes (amounts <= 0 are ignored) | Yes, immediately |
wallet.Earn(amount) |
Yes | No |
wallet.TrySpend(amount) |
Yes, if affordable | No |
wallet.SetBalance(amount) |
Yes (clamped >= 0) | No |
| A successful checkout in the garage | Yes | Yes - the checkout service writes once, after the whole cart is paid for |
tuner.ResetProgress() |
Yes (wipe + re-seed) | Yes |
So wallet.SetBalance(99999) on its own is lost when the player quits. If you want it kept, follow it with tuner.AddCurrency(0) - that changes nothing but flushes the whole save blob, ownership included. For anything the player earns in normal play, just use AddCurrency and stop thinking about it.
Reading and Granting Ownership
tuner.Ownership (an RCCPT_OwnershipStore) is the "who owns what" record. It has two keyspaces, exactly as described in Prices & Economy: global cosmetics keyed by a string, per-vehicle performance levels keyed by that vehicle's GUID.
var own = RCCPT_TunerController.Instance.Ownership;
// Global cosmetics. The key is "<category prefix>:<entry id>".
bool hasIt = own.IsOwnedGlobal("paint:paint_521A80"); // Midnight Purple, in the demo catalog
own.MarkOwnedGlobal("paint:paint_521A80"); // idempotent grant - a pre-order bonus, say
// Per-vehicle performance, levels 0-5, keyed by the vehicle's Vehicle Id GUID.
string guid = myVehicle.GetComponentInChildren<RCCPT_VehicleId>(true).Guid;
int engineLevel = own.GetVehicleUpgradeLevel(guid, RCCPT_UpgradeKind.Engine);
own.SetVehicleUpgradeLevel(guid, RCCPT_UpgradeKind.Engine, 3); // clamped 0-5
own.OnOwnershipChanged += RefreshMyGarageBadge;
Rather than hardcoding a key string, ask the catalog that owns the entry: myPaintCatalog.GetOwnershipKey(entry) builds the same "<prefix>:<id>" key from the entry you authored. The prefixes are paint, wheels, spoiler, siren, decal and neon; the one-time tuning unlock has its own constant, RCCPT_OwnershipStore.TuningUnlockKey ("tuning:unlock"). RCCPT_UpgradeKind is Engine, Brake, Handling, Speed.
Like the wallet, these writes are in-memory - flush them with tuner.AddCurrency(0) if the player might quit before the next checkout.
Asking "can the player use this?" is a different question.
Ownershipanswers the literal save-file question and ignores the economy master toggle.tuner.Checkout.IsEffectivelyOwnedGlobal(key)andtuner.Checkout.GetEffectiveOwnedLevel(guid, kind)report everything as owned (and every level as maxed) while the economy is switched off, which is what the garage UI itself uses. Use those two if your code must agree with what the player sees on screen.
Post-Exit Vehicle Handoff
Three public controller fields (also in the inspector) control what happens to the vehicle when the garage closes. All default off, which restores the exact pre-garage state:
tuner.enableInputsAfterExit = true; // driving inputs on
tuner.startEngineAfterExit = true; // engine running
tuner.registerAsPlayerAfterExit = true; // becomes RCCP's active player vehicle
When enabled they override the restored state for their one property; registration runs last since registering itself writes control and engine state.
Localizing the UI Strings
Every fixed UI string lives in the static class RCCPT_Strings - the deliberate localization seam. Content names (the paint, wheel and spoiler names the player reads) come from your catalogs, never from here.
The fields are plain public statics, so you assign them once per process, before the first garage opens - not once per scene, and not on the controller:
using UnityEngine;
using BoneCrackerGames.RCCP.Tuner;
public static class MyTunerLocalization {
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Apply() {
RCCPT_Strings.Cart = "SEPET";
RCCPT_Strings.Checkout = "SATIN AL";
RCCPT_Strings.ToastInsufficientFunds = "Yetersiz bakiye.";
// ...every label, toast, hint, and confirm-dialog text is a public static string
}
}
[RuntimeInitializeOnLoadMethod] is Unity's "run this once when the game starts" attribute - no GameObject and no scene wiring needed. An Awake() on a bootstrap object in your first scene does the same job; what matters is only that it runs before the player opens the garage. Because the values are statics they survive scene loads (and survive a Play-Mode session with domain reload disabled), so you never need to re-apply them per scene.
Category names are separate fields (CategoryPaint, CategoryWheels, CategoryEngine, ...) that RCCPT_Strings.CategoryLabel(RCCPT_Category) resolves. Translate the fields; leave the method alone.
The strings a translator misses
Units, deltas and state chips are strings too, and they are the ones that get skipped because they do not read like sentences:
| Field | Ships as | Where it shows |
|---|---|---|
StatUnitTorque |
NM |
The engine and braking stat rows |
StatUnitSpeed |
KM/H |
The top speed stat row |
StatUnitPercent |
% |
The handling stat row |
StatDeltaFormat |
{0} > {1} |
The "now, then after this upgrade" stat readout |
CartRemaining |
REMAINING |
The cart footer, when the wallet covers the cart |
CartMissing |
MISSING |
The cart footer, when it does not |
PreviewBadge |
PREVIEW |
The chip shown while a hover try-on is on the car |
TuningReset |
RESET |
The tuning panel's reset-to-stock button |
⚠
StatAccelerationis the TORQUE label - its name no longer matches its value. The field ships as"TORQUE": that row reads RCCP's engine torque and never was an acceleration figure, so it was relabelled while the field name stayed. If you are hunting for the torque header, translateRCCPT_Strings.StatAcceleration- there is noStatTorquefield.
The handling row is the reason StatUnitPercent exists: handling rides on RCCP's traction-helper strength, a 0-1 number that means nothing on screen, so that row prints a percentage of the stock value instead of a raw figure. Engine and braking print NM, top speed prints KM/H.
Showing Your Own Toasts
The garage's toast panel is public - useful for integration messages while the garage is open:
FindAnyObjectByType<RCCPT_Toast>().Show("Daily bonus added!");
The Vehicle Id Component
RCCPT_VehicleId is the small component that gives a vehicle a stable save identity - a 32-character GUID that keys both its Tuner ownership and its RCCP loadout (Scene Setup). Three members matter to your game code:
| Member | Type | What it does |
|---|---|---|
Guid |
string (read-only) | The stable identity. Generated the first time it is asked for if the field is empty. This is the key you pass to the per-vehicle ownership calls |
displayName |
string | Optional. The name the garage's top bar shows for this car |
writeThroughToSaveFileName |
bool (default on) | Writes the GUID into RCCP_Customizer.saveFileName so the RCCP loadout and the Tuner ownership share one key. Leave it on unless you are doing custom keying |
Fill in displayName on every vehicle you ship. When it is empty the top bar falls back to a cleaned-up transform name - (Clone) stripped, an RCCP_ / RCC_ / BCG_ prefix stripped, underscores and camel case split, the whole thing uppercased - so RCCP_PrototypeVehicle_Skyline reads as PROTOTYPE VEHICLE SKYLINE. That is a safety net, not a feature: it keeps raw prefab names off the screen, but only you know the car is meant to be called "Skyline GT-R".
var id = myVehicle.GetComponentInChildren<RCCPT_VehicleId>(true);
id.displayName = "Skyline GT-R"; // shown uppercased in the top bar
string key = id.Guid; // per-vehicle ownership key
The Paint Finish Applier
RCCPT_PaintFinishApplier is a tiny runtime component for gameplay scenes: RCCP re-applies a vehicle's saved paint color on spawn, but the finish (matte/chrome) lives in the Tuner save. Add the component next to RCCPT_VehicleId on the vehicle prefab and the saved finish re-applies on spawn in any scene - no controller required, and it does nothing when no finish was ever bought. Calling Apply() again manually is safe.
A Note on the RCCP Seam
One class - RCCPT_CustomizerAdapter - is the single seam through which all garage runtime code (UI, economy, session) touches RCCP's customization API: paint, wheels, spoilers, sirens, decals, neons, upgrade levels and tuning values. It also carries every workaround for RCCP's customization quirks (upgrade level bridging, save suppression during previews, wheel-radius restore, paint throttling...). It stamps the RCCP version those workarounds were last verified against:
RCCPT_CustomizerAdapter.VerifiedAgainstRCCPVersion // "V3.0.0"
RCCPT_CustomizerAdapter.MaxUpgradeLevel // 5 - RCCP hardcodes this in every upgrade manager
Practical consequences for you: performance upgrades go 0-5, and if a future RCCP update renames a customization API, that one file absorbs the change for the whole garage.
⚠ "Single seam" means the customization API, not the whole of RCCP. Other Tuner runtime scripts do talk to RCCP directly, for work that has nothing to do with customization: the controller and session call
RCCP.SetControl/RCCP.SetEngine/RCCP.RegisterPlayerVehicleand park the RCCP camera and canvas, the camera director and top bar read the boundRCCP_CarController, andRCCPT_VehicleIdwritesRCCP_Customizer.saveFileName. A project-wide search forRCCP_underAssets/RCCP Tuner/Scripts/returns several files, by design - it is the customization surface that funnels through one place.
The adapter is also where you ask "what was this value before anybody touched it": GetTuningFloatDefault(RCCPT_TuningFloatKind kind) returns the stock tuning value RCCP captured on Awake, falling back to the current live value when that capture has not run yet. RCCPT_TuningFloatKind is the enum naming one tuning slider each - FrontCamber, RearCamber, FrontSuspensionTarget, RearSuspensionTarget, FrontSuspensionDistance, RearSuspensionDistance, FrontSpringForce, RearSpringForce, FrontSpringDamper, RearSpringDamper. This is what backs the tuning panel's RESET button (Using the Garage).
Reach the live instance through tuner.Adapter. It exists for as long as the controller does, but it binds to a vehicle when the garage opens - so its per-vehicle queries only answer while a vehicle is bound.
Verifying an Integration
Demo/RCCPT_TunerProbe.cs is a play-mode verification harness: add the component to the showroom scene at runtime, call RunAll(), and read the JSON report at Application.persistentDataPath/RCCPT_TunerProbe/report_latest.json. It asserts the whole contract - session open/close restoration, cart semantics, ladder pricing, atomic checkout, persistence round-trips, revert correctness, economy-off behavior, camera takeover/release, audio lifecycle, and more. It wipes the Tuner save and the vehicle's RCCP save at start - never run it against progress you care about.
Next Steps
- Prefer buttons to code while you test? Editor Tools covers the controller inspector's live session panel and every menu item.
- Something not behaving? Troubleshooting & FAQ.