Racelink
Кроки посібника · 05 / 08
  1. 00Before you start
  2. 01Create a project
  3. 02Run the overlay
  4. 03Show live data
  5. 04Add settings
  6. 05Make it look right
  7. 06Build and check
  8. 07Publish to the Workshop
  9. 08Release updates

// Посібник з оверлеїв · 05

Make it look right

Racelink shows an overlay over the game, in OBS and in previews — always from the first frame. Teach the HUD to fit any size, limit its size in the manifest and add an animation that does not flicker on opening. About 20 minutes.

Цей посібник поки що лише англійською.

Keep npm run dev and Racelink running, with My HUD on screen.

Stretch the overlay

The player picks the overlay’s size, so the layout never knows it in advance. See how the HUD behaves now:

  1. Open Layouts and press Arrange overlays.
  2. Drag My HUD by its bottom-right corner: make it twice as big, then very small.

The pedal bars stretch, and the gear, speed and lap time grow and shrink with the window. Two things in the template’s src/style.css make this work:

src/style.css
#root {
/* … */
container-type: size;
}

.gear {
font-size: clamp(24px, 45cqh, 120px);
}
container-type: size
Makes #root a container: inside it, sizes can be measured as a percentage of it.
45cqh
45% of the container’s height. A taller window, a bigger digit. There is also cqw, a percentage of the width.
clamp(24px, …, 120px)
At least 24 and at most 120 pixels, so the text stays readable and does not balloon.

The template’s paddings and gaps are percentages rather than pixels for the same reason. Do the same in your own elements: the lap time from “Show live data” already uses 12cqh.

Limit the size

A tiny HUD is unreadable and a huge one is pointless. The size field in racelink.overlay.json sets the limits. The template has:

racelink.overlay.json
"size": { "default": [360, 120], "min": [200, 80], "max": [1200, 400] },
default
Width and height in pixels of a new overlay, when the player adds it to a layout.
min
It cannot be made smaller than this.
max
It cannot be made bigger than this.

See what a limit looks like:

  1. Change max to [600, 200] and save the file.
  2. In edit mode, drag My HUD’s right edge past 600 pixels. The edge turns amber and gives way a little, like rubber, and springs back to the limit when you let go. That is how the player sees the overlay cannot grow further.

If the overlay was bigger than the new limit, Racelink shrinks it to fit. Keep whichever limits suit you: [600, 200] or the original ones.

Two more options that come in handy for other overlays:

One side fixed
The same min and max on a side: "min": [200, 120], "max": [1200, 120] keeps the height at 120 and lets only the width change. That edge cannot be dragged in the editor.
"resize": "scale"
For an overlay drawn for one size: "size": { "default": [400, 300], "resize": "scale" }, with no min or max. The window is always 400 × 300, and dragging scales the whole overlay up or down, keeping its proportions, from 0.25× to 4×.

An overlay’s Smaller and Larger buttons in edit mode scale any overlay the same way — as a whole.

Animations without flicker

Animation brings an overlay to life, but not when it opens: if rows slide and fade in every time it appears, the overlay looks half-drawn in previews and in OBS. So for 1.5 seconds after ready the SDK ends every CSS transition and animation at once. The overlay opens in its final look, and after that animations play as usual.

Try it: make the gear digit flash in the throttle colour on every shift.

1. In src/main.ts, above render, add a variable for the gear on screen:

src/main.ts
let shownGear = '';

2. In render, replace the line if (gear) gear.textContent = … with:

src/main.ts
  const gearText = car.gear < 0 ? 'R' : car.gear === 0 ? 'N' : String(car.gear);
if (gear && gearText !== shownGear) {
shownGear = gearText;
gear.textContent = gearText;
// Restart the animation: take the class off, let the browser notice, put it back.
gear.classList.remove('shift');
void gear.offsetWidth;
gear.classList.add('shift');
}

3. At the end of src/style.css:

src/style.css
.gear.shift {
animation: shift 400ms ease-out;
}

@keyframes shift {
from {
color: var(--throttle);
transform: scale(1.15);
}
}

Save. With ?demo the gear flashes on every shift, but not when the page opens, although render in ready sets the gear for the first time too.

To see the difference, turn animations on from the first frame for a moment and reload:

src/main.ts
const overlay = createOverlay<Settings>({ defaults: defaultSettings, animateStart: true });

Now the gear flashes on opening as well. Remove animateStart: true: an overlay is almost always better without it.

History from the first frame

My HUD has no history, but keep this for later. An overlay that draws the past — a pedal graph of the last seconds, a G-force trail — is empty when it opens: it has seen no past yet. In previews and demo mode that looks broken. That is what overlay.demoHistory is for: a topic’s demo values over the last seconds, oldest first.

src/main.ts
overlay.on('ready', () => {
// Empty outside demo mode, so this does nothing with a real game.
for (const { sampledAt, data } of overlay.demoHistory('car', 10)) trace.push(sampledAt, data);
});

Here trace is your graph. With a game demoHistory is empty, and the graph fills with live data.

A transparent background

An overlay’s window is transparent: whatever you do not draw shows the game. The template gives html, body and #root background: transparent, and the dark panel is #root’s background with the opacity from the setting. What not to do:

  • Do not give html or body a background — it covers the game with a rectangle.
  • Do not set color-scheme, in a <meta> or in CSS: the browser paints the window white or black.

Look at the preview

Racelink shows the overlay in more places than the screen. Open Layouts and select My HUD: the Settings panel has a preview on demo data. Switch to another overlay and back: the HUD should appear finished at once — your colours, no gear flash, no empty spots.

If something goes wrong

  • Text does not resize with the window. The element’s font size is in px instead of cqh, or no element above it has container-type: size.
  • The edge does not turn amber. Look at the terminal: Racelink does not accept a manifest with a mistake (for example, min bigger than max), and it shows racelink.overlay.json has problems.
  • The gear flashes only once. The line void gear.offsetWidth; is missing: without it the browser does not notice the class was taken off, and the animation does not restart.

What you should have

  • My HUD is readable in a small window and in a big one.
  • At a limit the overlay’s edge turns amber and springs back.
  • The gear flashes on a shift, but not when the overlay opens.
  • In Racelink’s preview the HUD appears finished at once.