Skip to main content

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:

MethodDescription
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 call

Return 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:

FlagValueDescription
StereoFlag.None0No stereo rendering
StereoFlag.Reticule1Reticule stereo mode
StereoFlag.Normal2Normal 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 more

Instructional 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();
}
note

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.

tip

Always dispose of scaleforms when done to prevent memory leaks. Use DisposableStore to manage scaleform lifecycles in services.