Scaleform
Type-safe wrapper for GTA5's Flash-based Scaleform UI system, providing a fluent API for loading, calling methods, and rendering scaleform movies in both 2D and 3D.
Overview
Scaleforms are Flash-based UI elements used throughout GTA5 for various HUD elements, menus, and effects. The Scaleform class provides:
- Async loading with timeout handling
- Fluent method builder for calling ActionScript methods with typed parameters
- 2D and 3D rendering with color tinting and stereo support
- Null object pattern for safe scaleform references
- Multiple request modes for different use cases
Basic Usage
"use client";
import { Scaleform, ScaleformMovies } from "@core/classes/Scaleform";
// Load a scaleform
const scaleform = await Scaleform.request(ScaleformMovies.MidsizedMessage);
// Call methods using fluent API
scaleform
.callMethod("SHOW_MIDSIZED_MESSAGE")
.pushString("Title")
.pushString("Message")
.pushInt(100)
.pushBool(true)
.send();
// Draw every frame (in a tick loop)
scaleform.drawFullscreen();
// Cleanup when done
scaleform.dispose();Request Methods
Different request methods are available depending on your use case:
| Method | Description |
|---|---|
Scaleform.request(name) | Standard request, only one instance per movie name |
Scaleform.requestInstance(name) | Allows multiple instances of the same movie |
Scaleform.requestIgnoreSuperWidescreen(name) | Ignores super widescreen adjustments |
Scaleform.requestSkipRenderWhilePaused(name) | Won't render when game is paused |
// Multiple instances of the same movie
const scaleform1 = await Scaleform.requestInstance("MIDSIZED_MESSAGE");
const scaleform2 = await Scaleform.requestInstance("MIDSIZED_MESSAGE");Calling Methods
The ScaleformMethodBuilder provides a fluent API for calling ActionScript methods:
Parameter Types
scaleform
.callMethod("SET_DATA")
.pushInt(42) // Integer
.pushFloat(3.14) // Float
.pushBool(true) // Boolean
.pushString("Hello") // String (with text label formatting)
.pushLiteralString("raw") // Raw string (no formatting)
.pushTextureString("tex") // Texture name
.pushPlayerName("Player") // Player name (special chars handled)
.send(); // Execute the callReturn Values
For methods that return values, use the async return methods:
// Get integer return value
const count = await scaleform.callMethod("GET_COUNT").returnInt();
// Get boolean return value
const isVisible = await scaleform.callMethod("IS_VISIBLE").returnBool();
// Get string return value
const text = await scaleform.callMethod("GET_TEXT").returnString();Drawing
2D Drawing
Movies are drawn in order, with the latest drawn on top.
Color values use normalized components (0.0-1.0), not 0-255. Use the color() helper to create colors.
import { StereoFlag } from "@core/classes/Scaleform";
import { color } from "@core/math";
// Draw fullscreen
scaleform.drawFullscreen();
// Draw fullscreen with color tint (normalized 0-1 values)
scaleform.drawFullscreen(color(1, 0, 0, 0.5)); // Red with 50% alpha
// Draw fullscreen with stereo mode
scaleform.drawFullscreen(undefined, StereoFlag.Normal);
// Draw at specific 2D position (x, y, width, height)
// Values are 0.0-1.0 screen coordinates
scaleform.draw2D(0.5, 0.5, 0.5, 0.5); // Centered, half screen size
// Draw two movies with masking
Scaleform.drawFullscreenMasked(foregroundMovie, maskMovie);3D Drawing
Render scaleforms in the game world:
import { vec3 } from "@core/math";
import { EulerRotationOrder } from "@core/constants/EulerRotationOrder";
// Draw in 3D world
scaleform.draw3D(
vec3(100, 200, 30), // position
vec3(0, 0, 0), // rotation (degrees)
vec3(1, 1, 1), // scale
vec3(2, 2, 1), // world size
);
// Draw solid (non-additive blending, no lighting)
scaleform.draw3DSolid(vec3(100, 200, 30), vec3(0, 0, 0), vec3(1, 1, 1), vec3(2, 2, 1));Stereo Flags
For VR/3D display support:
| Flag | Value | Description |
|---|---|---|
StereoFlag.None | 0 | No stereo rendering |
StereoFlag.Reticule | 1 | Reticule stereo mode |
StereoFlag.Normal | 2 | Normal stereo mode |
Instance Settings
// Use system time instead of game time
scaleform.setUseSystemTime(true);
// Use large render target (1280x720)
scaleform.setUseLargeRenderTarget(true);
// Use super large render target (2048x1024)
scaleform.setUseSuperLargeRenderTarget(true);Properties
// Check if loaded
if (scaleform.isLoaded) { ... }
// Check deletion status
if (scaleform.isDeleting) { ... }
if (scaleform.isFlaggedForDeletion) { ... }
// Check child container status
if (scaleform.hasChildLoaded) { ... }
// Get the movie name
console.log(scaleform.name);Common Scaleform Movies
The ScaleformMovies constant provides common movie names:
import { ScaleformMovies } from "@core/classes/Scaleform";
ScaleformMovies.MidsizedMessage; // "MIDSIZED_MESSAGE"
ScaleformMovies.MpBigMessageFreemode; // "MP_BIG_MESSAGE_FREEMODE"
ScaleformMovies.WarningMessage; // "WARNING_MESSAGE"
ScaleformMovies.InstructionalButtons; // "INSTRUCTIONAL_BUTTONS"
ScaleformMovies.Countdown; // "COUNTDOWN"
ScaleformMovies.PopupWarning; // "POPUP_WARNING"
ScaleformMovies.WeaponWheel; // "WEAPON_WHEEL"
ScaleformMovies.RadioWheel; // "RADIO_WHEEL"
ScaleformMovies.Minimap; // "MINIMAP"
// ... and moreInstructional Buttons Example
A common use case for scaleforms:
"use client";
import { Scaleform, ScaleformMovies } from "@core/classes/Scaleform";
import { DisposableStore, toDisposable } from "@core/disposable";
export class InstructionalButtons {
private scaleform: Scaleform | null = null;
private disposables = new DisposableStore();
async setup(): Promise<void> {
this.scaleform = await Scaleform.request(ScaleformMovies.InstructionalButtons);
this.scaleform.callMethod("CLEAR_ALL").send();
this.scaleform.callMethod("SET_CLEAR_SPACE").pushInt(200).send();
// Add buttons (index, control, text)
this.scaleform
.callMethod("SET_DATA_SLOT")
.pushInt(0)
.pushString("~INPUT_FRONTEND_ACCEPT~")
.pushString("Accept")
.send();
this.scaleform
.callMethod("SET_DATA_SLOT")
.pushInt(1)
.pushString("~INPUT_FRONTEND_CANCEL~")
.pushString("Cancel")
.send();
this.scaleform.callMethod("DRAW_INSTRUCTIONAL_BUTTONS").send();
this.disposables.add(
toDisposable(() => {
this.scaleform?.dispose();
}),
);
}
draw(): void {
this.scaleform?.drawFullscreen();
}
dispose(): void {
this.disposables.dispose();
}
}Big Message Example
Display mission-style messages:
"use client";
import { Scaleform, ScaleformMovies } from "@core/classes/Scaleform";
export async function showMissionPassed(title: string, subtitle: string): Promise<void> {
const scaleform = await Scaleform.request(ScaleformMovies.MpBigMessageFreemode);
scaleform
.callMethod("SHOW_SHARD_WASTED_MP_MESSAGE")
.pushString(title)
.pushString(subtitle)
.pushInt(5) // Message type
.send();
// Draw for a few seconds then cleanup
const startTime = GetGameTimer();
while (GetGameTimer() - startTime < 5000) {
scaleform.drawFullscreen();
await sleep(0);
}
scaleform.dispose();
}Null Object Pattern
Safe handling of invalid scaleforms:
const scaleform = Scaleform.fromHandle(someHandle);
if (scaleform === Scaleform.NULL) {
// Handle is invalid
return;
}
if (scaleform.exists()) {
// Scaleform is valid and loaded
scaleform.drawFullscreen();
}Scaleform is a client-only class ("use client") that wraps GTA5's native Flash-based UI system. Draw calls must be made every frame in a tick loop for the scaleform to be visible.
Always dispose of scaleforms when done to prevent memory leaks. Use DisposableStore to manage scaleform lifecycles in services.