Theming & Editing the UI

The garage UI is designed to be yours. There are two layers of customization, from quick to deep:

  1. Swap the Theme asset - one file recolors and refonts the entire garage. No prefab editing.
  2. Edit the canvas prefab - the UI is a real, hand-editable uGUI hierarchy. Move panels, resize cards, delete what you do not want.

This chapter covers both, plus the mobile/responsive system.

Layer 1: The Theme Asset

An RCCPT_Theme asset holds every color, font, and sprite the garage uses. The shipped default is Resources/RCCPT_Theme_Default: a near-black panel set with a white accent, warm orange price text (#FFB066) and a thin orange panel border at 25% alpha. Create your own with Assets > Create > BoneCracker Games > RCCP Tuner > Theme and assign it either project-wide (RCCPT_Settings.theme) or per scene (the controller's Theme Override).

The controller's Setup Health block prints which theme the scene actually resolves to, and whether it came from the scene override or the project default. That is the fastest answer to "why is my theme not applying" - see Editor Tools.

The Theme asset - the palette preview strip, the animation timings and the font slots

Scroll further down the same inspector for the sprite slots and the per-category icon array described later in this chapter.

Palette

The theme inspector draws these as a swatch grid, four per row, each labelled with its name and alpha, so you can read the palette without clicking into every field (Editor Tools).

Not every slot is wired into the shipped canvas. A color reaches the screen in one of two ways: through an RCCPT_ThemeBinder component carrying the matching role (see Theme Binders below), or because a script reads the field directly. The last column tells you which - and where a slot is dead, it says so rather than letting you chase an edit that changes nothing.

Setting Styles Reaches the shipped canvas?
Background Full-screen backdrop tint behind the whole garage overlay No - no element carries the Background role
Panel Fill color for the large HUD panels (top bar, rail, strip, drawer) Yes - 9 PanelBg binders
Panel Border Thin border color on panels No - no element carries the Border role
Accent Primary brand color - buttons, highlights, selection outlines Yes - 16 AccentButton binders, plus the active rail tab, in-cart card tint, preview tint and active selector buttons in code
Text On Accent Text drawn on top of accent-colored elements (checkout button etc.) Yes - 7 TextOnAccent binders
Text Primary text color Yes - 5 HeadingText + 11 BodyText binders
Text Dim Secondary / dimmed text (descriptions, hints) Yes - 23 DimText binders
Price Price tag text color Yes - 10 PriceText binders, plus item card prices in code
Success Positive feedback (owned, purchase success) Yes, but code only - OWNED / EQUIPPED price lines, the equipped card tint, the cart's REMAINING line
Warning Cautionary feedback No - nothing in the Tuner reads this field
Error Denied feedback (failed purchase, unaffordable prices) Yes, but code only - unaffordable price text, the cart's MISSING line
Locked Tint Dark overlay tint for a LockOverlay element No - no element carries the LockOverlay role
Card Bg Item card background fill Yes - 24 CardBg binders, plus inactive selector buttons in code

Four of the thirteen slots do nothing in the shipped canvas. Background, Panel Border and Locked Tint are wired to binder roles that the shipped hierarchy never uses, and Warning has no consumer at all - no role, no script. Editing any of them changes nothing on screen. They are kept because they still work for UI you add: drop an RCCPT_ThemeBinder set to Background, Border or LockOverlay on your own Image and the matching color starts driving it. Warning is a spare color you can read from your own scripts.

"Locked Tint" does not dim the can't-afford cards. That dimming is hardcoded: RCCPT_ItemCard lazily adds a CanvasGroup to the card and drops its alpha to 0.55 for the unaffordable state (the card stays clickable - staging an item you cannot yet afford is allowed). To change how a can't-afford card looks, edit that alpha in RCCPT_ItemCard, not the theme.

The fastest way to re-brand: change Accent (and Text On Accent to keep button labels readable). The shipped default leaves the accent white, so that one field is what turns the garage into your brand.

The UI's Color Language

Beyond the raw palette slots, the garage speaks a consistent semantic color language. Preserve these meanings when re-theming - players read state from them at a glance. If you have not played the garage yet, Using the Garage shows every one of these states in context, including a diagram of the five item-card states:

Meaning Palette slot Where you see it
Purchasable (price) Price Item card price tags, cart line prices, TOTAL
Can't afford Error Red prices, dimmed cards, the cart's MISSING line, disabled CHECKOUT
Owned / equipped Success OWNED / EQUIPPED card lines, the cart's REMAINING line. The equipped badge stays vivid; a merely-owned badge is faded
Staged in cart Accent Card background tint, the IN CART badge, the cart count on the top bar
Active selection / preview Accent + button sprite Paint COLORS / CUSTOM / FINISH modes, decal placements, the PREVIEW chip
Hints / secondary Text Dim Descriptions, hint lines, the tuning RESET button

The cart open over the garage - staged rows, the total, the REMAINING balance line and the checkout button

Note what the picture also shows about the other dimming rule: while the cart is open, the whole item strip fades and stops responding. That is the modal lock, not the can't-afford state - both use a dim, so read the price colour to tell them apart. The five card states are shown side by side in Using the Garage.

One rule worth keeping: accent-colored surfaces should use the button sprite, not the card sprite. The shipped card texture is dark, and tinting it with a bright accent multiplies into a muddy tone that swallows Text On Accent labels - the built-in selectors already swap sprites for exactly this reason.

Animation

The theme also owns the motion feel. Enable Animations (default on) is the master switch - off reproduces instant snap behavior everywhere, which some UI styles (and accessibility preferences) want.

Setting Default Controls
Panel Intro Duration 0.25 Per-panel open fade/slide (seconds, unscaled)
Panel Stagger 0.05 Delay step between panels in the open stagger
Close Fade Duration 0.15 Whole-canvas close fade
Stat Bar Speed 8 Stat bar fill smoothing (higher = snappier)
Card Scale Speed 14 Item card hover/press scale smoothing
Card Hover Scale 1.04 Card scale while hovered
Card Press Scale 0.97 Card scale while pressed
Category Fade Duration 0.08 Item strip cross-fade on category switch
Wallet Roll Duration 0.6 Rolling balance count-up/down duration

Fonts

Heading Font (headings, prices, buttons) and Body Font (descriptions). The shipped theme uses Chakra Petch - ChakraPetch-SemiBold SDF for headings and ChakraPetch-Regular SDF for body (SIL Open Font License; the license text ships in Fonts/OFL.txt, so distributing it with your game is fine). Assign any TMP font assets of your own. A binder only assigns a font when the slot is filled, so an empty slot simply leaves whatever font the text object already has.

Sprites

Three of the sprite slots are 9-slice sprites - a sprite with border values set in Unity's Sprite Editor, so its corners stay crisp when the element is stretched to any size. If you assign a sprite with no borders the binder draws it unstretched instead (Image.Type.Simple), which is why a hand-made panel background can come out looking squashed with no error in the Console.

Slot Used by Notes
Panel Sprite The PanelBg role (top bar, rail, strip, drawer, tuning panel) Shipped: rccpt_panel
Card Sprite The CardBg role (item cards, plain buttons, scrollbar track) Shipped: rccpt_card
Button Sprite The AccentButton role (CHECKOUT, UNLOCK TUNING, the PREVIEW chip, active selector buttons) Shipped: rccpt_button
Lock Icon Nothing Dead slot - see below
Cart Icon Nothing Dead slot - see below
Owned Badge Nothing Dead slot - see below
Category Icons The category rail, and the auto-generated level cards in the upgrade categories 11 entries, indexed by category

The shipped sprites live in Textures/UI/.

The lock / cart / owned-badge slots are never read. No script and no binder role pulls them, and the shipped canvas bakes rccpt_lock, rccpt_cart and rccpt_owned straight onto the Images that show them (the tuning panel's LockIcon, the top bar's cart Icon and the in-cart badge, the card's OwnedBadge). Assigning your own sprites into these three theme slots changes nothing on screen - swap them on those Image components instead, exactly as you would the card sprite.

An empty sprite slot does not give you a flat color fill. A binder overwrites an element's sprite only when the theme slot is non-empty (if (applySprite && sprite != null)). Every panel, card and button in the shipped canvas already has its sprite baked in, so clearing Panel Sprite on a theme leaves the shipped 9-slice exactly where it was and only the color changes. You get a flat color fill only on an Image you authored yourself with no sprite of its own.

Category Icons is an 11-element array indexed by the RCCPT_Category enum, in this order: Paint, Wheels, Engine, Brake, Handling, Speed, Spoiler, Siren, Decal, Neon, Tuning. You do not have to count positions - the theme inspector labels every slot with its category name, and warns with a one-click Resize To 11 fix if the array length has drifted so that some categories can never resolve an icon.

Reordering the Category Rail

The rail order is not part of the theme and is not hardcoded: it is the Category Order list on RCCPT_Settings (project-wide). The rail draws the categories in exactly that order, and the garage opens on the first one the vehicle actually supports.

The settings inspector validates the list for you: it names any category you left out ("these categories never appear in the rail") or listed twice, and offers a Rebuild Order button that keeps your order, drops the repeats and appends whatever is missing. See Editor Tools.

Applying a Theme

How Theming Works: Theme Binders

Every themed element on the canvas carries a small RCCPT_ThemeBinder component that says which role it plays. Applying a theme walks all binders and styles each element from the matching theme slot - the hierarchy itself is never rebuilt, which is why your structural edits survive re-theming. The shipped canvas holds 105 of them.

There are 13 roles. A binder styles the Image or the TMP text on its own GameObject, whichever it finds:

Binder role Applies to Pulls from the theme Used in the shipped canvas
PanelBg Image Panel color + Panel Sprite 9
CardBg Image Card Bg color + Card Sprite 24
AccentButton Image Accent color + Button Sprite 16
HeadingText TMP text Text color + Heading Font 5
BodyText TMP text Text color + Body Font 11
DimText TMP text Text Dim color + Body Font 23
PriceText TMP text Price color + Heading Font 10
TextOnAccent TMP text Text On Accent color + Heading Font 7
SuccessText TMP text Success color + Body Font none
ErrorText TMP text Error color + Body Font none
LockOverlay Image Locked Tint color (no sprite) none
Border Image Panel Border color (no sprite) none
Background Image Background color (no sprite) none

The last five roles are unused by the shipped hierarchy - that is the mechanical reason the Background, Panel Border and Locked Tint palette slots are inert, and it is also why they start working the moment you add a binder of your own with one of those roles.

Apply Sprite on the binder controls whether the role's sprite is assigned too, and only the three Image roles that carry a sprite (PanelBg, CardBg, AccentButton) are affected by it. In the shipped canvas it is on for panels and buttons, and off where an element keeps its own baked sprite - notably the item card template, whose card sprite you therefore swap directly on its Image, not through the theme.

Adding your own UI to the canvas? Drop an RCCPT_ThemeBinder on it, pick a role, and it re-themes with everything else.

Layer 2: Editing the Canvas Prefab

Prefabs/RCCPT_TunerCanvas.prefab is a deliberate design choice: a real authored hierarchy, not a UI that assembles itself at runtime. Open it in Prefab Mode and rearrange, resize, restyle, or delete like any uGUI canvas. This is the layout you are editing:

RCCPT_TunerCanvas              (Canvas + RCCPT_TunerUI + RCCPT_ResponsiveLayout)
  ContentRoot                  (everything below only shows while the garage is open)
    ViewportDrag               invisible full-screen catcher: drag = orbit, scroll/pinch = zoom
    Vignette                   decorative edge darkening (never blocks clicks)
    SafeAreaRoot               mapped to the device safe area
      TopBar                   VehicleName, WalletGroup, CartButton, ExitButton
      CategoryRail             ButtonContainer > ButtonTemplate (cloned per category)
      StatPanel                the four stat bars (upgrade categories only)
      InfoPanel                hover description panel
      ItemStrip                the bottom strip
        Title
        DecalLocationRow       FRONT / BACK / LEFT / RIGHT selector buttons
        PreviewBadge           the PREVIEW chip - on only while a hover try-on is on the car
        Scroll
          Viewport
            CardContainer
              CardTemplate     cloned per item (disabled in the prefab)
          HScrollbar           slim auto-hiding scrollbar for catalogs wider than the strip
        PaintModeRow           COLORS / CUSTOM / FINISH selector buttons
        CustomColorGroup       the custom paint picker (swatch, RGB rows, APPLY)
      TuningPanel
        LockedGroup            LockIcon, hint, UNLOCK TUNING button
        UnlockedGroup
          Scroll > Viewport > List
            SliderRowTemplate  Label, Slider, Value (cloned per tuning row)
            ColorRowTemplate   Label + Swatches (cloned per tuning color row)
          ResetButton          the RESET-to-stock button
      CartDrawer               the sliding cart
        Header
        Scroll
          Viewport
            RowContainer
              RowTemplate      Name, Price, Remove (cloned per cart line)
        EmptyLabel
        TotalLabel / TotalValue
        RemainingLabel / RemainingValue   the post-checkout balance line
        CheckoutButton / ClearButton / DiscardConfirmGroup
      Toast                    transient notifications
      LockHint                 "Close the cart to keep browsing." hint
  Audio                        garage music + UI SFX ([UI Audio & Music](09_audio.html))

Four of those children are easy to trip over while restyling:

Three ground rules keep edits safe:

  1. Move and restyle freely; be careful deleting. Each panel component (RCCPT_TopBar, RCCPT_ItemStrip...) holds wired references to its own children (a price label, a button...). Deleting a wired child disables that piece of functionality - the components null-check everything, so nothing crashes, but the feature silently stops. Deleting whole optional panels you do not want (for example StatPanel) is fine.
  2. Templates are cloned at runtime. Objects named ...Template (the item card, cart row, category button, tuning slider row) sit disabled in the prefab and are instantiated per item, then refreshed in place. Edit the template and every clone inherits the change.
  3. Colors and fonts belong to the theme. You can hard-edit an Image's color in the prefab, but the next theme apply overwrites it (that is the binder's job). To change colors, change the theme - or remove the element's RCCPT_ThemeBinder if you want it out of the theming system entirely.

Recipe: Restyle the Item Cards

The item card template lives at ItemStrip > Scroll > Viewport > CardContainer > CardTemplate (disabled). Its children: PreviewOutline, Swatch (paint color fill), Icon, Name, Price, OwnedBadge, InCartBadge.

  1. Open the canvas prefab in Prefab Mode and select CardTemplate.
  2. Want bigger cards? Change the LayoutElement.preferredWidth (default 118) and the template's height comes from the strip.
  3. Rearrange the name/price/badges like any RectTransforms; adjust font sizes on the TMP components.
  4. The card background color comes from the theme (Card Bg). The card sprite is baked into the template (its binder deliberately keeps its own sprite) - to change the card shape, swap the sprite on CardTemplate's Image component directly.
  5. Save the prefab - every card in every category now uses your layout.

Two things on the card are driven at runtime and will quietly override an edit:

The cart rows work identically: CartDrawer > Scroll > Viewport > RowContainer > RowTemplate (children Name, Price, Remove).

Mobile & Responsive Layout

The RCCPT_ResponsiveLayout component on the canvas root handles small screens with zero setup:

The wide/compact anchor presets live in the component's Managed Rects list. Five panels are managed: CategoryRail, ItemStrip, CartDrawer, InfoPanel and StatPanel. TopBar is deliberately not in the list - it stretches across the top in both layouts, so it needs no second preset.

Each entry stores a target RectTransform plus eight raw Vector2 fields: wideAnchorMin/Max, wideOffsetMin/Max and the same four again for compact. There is no capture button, so author them by hand:

  1. Set the Game View to a wide aspect (16:9) and lay the panel out where you want it in Prefab Mode or in the scene.
  2. Copy that RectTransform's Anchors and the Left/Bottom and Right/Top offsets into the entry's four wide* fields.
  3. Set the Game View to a portrait aspect at or below the threshold (9:16 for example) so the compact layout is what you are looking at.
  4. Lay the panel out again for that shape and copy the result into the four compact* fields.
  5. Verify in Play Mode. RCCPT_ResponsiveLayout applies a preset when it is enabled and again whenever the screen size changes, so switching the Game View aspect while playing swaps the layout live. It does not run in Edit Mode, so nothing moves until you press Play.

If you add a panel of your own and want it to move on phones, add a Managed Rects entry for it. A panel with no entry simply keeps its authored anchors in both layouts, which is fine for anything already anchored sensibly.

Renaming the UI Text

Every fixed label lives in one static class, RCCPT_Strings, designed as the localization seam - swap the strings at startup from code. That includes the short state words this chapter keeps naming: PreviewBadge ("PREVIEW"), TuningReset ("RESET"), CartRemaining ("REMAINING") and CartMissing ("MISSING"), alongside "CART", "CHECKOUT", the category names and the confirm dialogs. Item names come from your catalogs, never from there. See Scripting API.

Next Steps