Documentation Index
Fetch the complete documentation index at: https://mintlify.com/FunkinCrew/Funkin/llms.txt
Use this file to discover all available pages before exploring further.
The stage system manages the visual environment, background props, character positioning, and camera behavior during gameplay.
Stage Architecture
Stage Class
Stages are groups of props rendered in PlayState:
class Stage extends FlxSpriteGroup
{
public var stageName:String;
public var camZoom:Float; // Default camera zoom
var namedProps:Map<String, StageProp>;
var characters:Map<String, BaseCharacter>;
var boppers:Array<Bopper>;
}
Key Properties:
Display name of the stage
Default camera zoom level for this stage
Props that can be referenced by name in scripts
Stage data is stored in JSON files at assets/data/stages/[id].json:
{
"version": "1.0.0",
"name": "Main Stage",
"cameraZoom": 1.0,
"directory": "shared",
"props": [
{
"name": "bg",
"assetPath": "stages/mainStage/stageback",
"position": [-600, -200],
"zIndex": 0,
"scale": 1.0,
"alpha": 1.0,
"scroll": [0.9, 0.9],
"isPixel": false
},
{
"name": "stageFront",
"assetPath": "stages/mainStage/stagefront",
"position": [-650, 600],
"zIndex": 500,
"scale": 1.0,
"scroll": [1.0, 1.0]
}
],
"characters": {
"bf": {
"position": [770, 100],
"zIndex": 1000,
"scale": 1.0,
"cameraOffsets": [-100, -100]
},
"dad": {
"position": [100, 100],
"zIndex": 1000,
"scale": 1.0,
"cameraOffsets": [100, -100]
},
"gf": {
"position": [400, 130],
"zIndex": 900,
"scale": 1.0,
"cameraOffsets": [0, 0]
}
}
}
Stage Data Fields
Stage data format version (currently “1.0.0”)
Display name for the stage
Default camera zoom level
Asset directory for stage props (for modding support)
props
Array<StageDataProp>
required
Array of prop definitions (see below)
characters
StageDataCharacters
required
Position and settings for bf, dad, and gf
Stage Props
Props are the visual elements that make up a stage.
Prop Data Structure
type StageDataProp = {
name?: string; // Optional name for script access
assetPath: string; // Path to image or "#color" for solid color
position: [number, number]; // [x, y] position
zIndex?: number; // Render order (default: 0)
scale?: number | [number, number]; // Scale or [scaleX, scaleY]
alpha?: number; // Opacity (default: 1.0)
scroll?: [number, number]; // Parallax scrolling (default: [1, 1])
isPixel?: boolean; // Disable anti-aliasing (default: false)
flipX?: boolean; // Flip horizontally (default: false)
flipY?: boolean; // Flip vertically (default: false)
danceEvery?: number; // Bop every X beats (default: 0)
animations?: Array<AnimationData>; // Prop animations
startingAnimation?: string; // Initial animation name
animType?: string; // "sparrow", "packer", "animateatlas"
};
Basic Prop
Simple static image:
{
"name": "background",
"assetPath": "stages/myStage/bg",
"position": [0, 0],
"zIndex": 0
}
Solid Color Prop
Create colored rectangles:
{
"name": "colorBg",
"assetPath": "#FF6B9D",
"position": [0, 0],
"scale": [1280, 720],
"zIndex": -100
}
When assetPath starts with #, it’s treated as a color code, and scale defines the rectangle size.
Animated Props
Props with animations:
{
"name": "speaker",
"assetPath": "stages/myStage/speaker",
"position": [100, 200],
"zIndex": 100,
"animType": "sparrow",
"danceEvery": 1,
"animations": [
{
"name": "idle",
"prefix": "speaker idle",
"frameRate": 24,
"looped": false
},
{
"name": "bump",
"prefix": "speaker bump",
"frameRate": 24,
"looped": false
}
],
"startingAnimation": "idle"
}
Bopping Props
Props that bop to the music:
{
"name": "bopProp",
"assetPath": "stages/myStage/bop",
"position": [500, 300],
"danceEvery": 1,
"animations": [
{
"name": "danceLeft",
"prefix": "prop bop left"
},
{
"name": "danceRight",
"prefix": "prop bop right"
}
]
}
Set danceEvery to the number of beats between bops (e.g., 1 = every beat, 2 = every other beat).
Z-Index System
The zIndex determines render order:
Typical z-index ranges:
- Background layers: -1000 to 0
- Mid-ground props: 0 to 500
- Front-ground props: 500 to 999
- Girlfriend: 900
- Boyfriend/Dad: 1000
- Foreground overlays: 1001+
var zIndex:Int = 0; // Higher numbers render in front
Props and characters are automatically sorted by z-index during stage construction.
The scroll property creates depth through parallax:
{
"scroll": [0.9, 0.9] // Moves 90% as much as camera
}
Scroll factor values:
[1.0, 1.0]: Moves 1:1 with camera (foreground)
[0.5, 0.5]: Moves half as much (mid-ground)
[0.0, 0.0]: Static, doesn’t move (UI elements)
[2.0, 2.0]: Moves twice as much (rare, special effects)
Example layers:
{
"props": [
{
"name": "sky",
"assetPath": "stages/outdoor/sky",
"position": [0, -200],
"scroll": [0.1, 0.1],
"zIndex": -500
},
{
"name": "mountains",
"assetPath": "stages/outdoor/mountains",
"position": [0, 0],
"scroll": [0.5, 0.5],
"zIndex": -200
},
{
"name": "ground",
"assetPath": "stages/outdoor/ground",
"position": [0, 500],
"scroll": [1.0, 1.0],
"zIndex": 0
}
]
}
Character Positioning
Stages define where characters stand:
{
"characters": {
"bf": {
"position": [770, 100],
"zIndex": 1000,
"scale": 1.0,
"cameraOffsets": [-100, -100]
},
"dad": {
"position": [100, 100],
"zIndex": 1000,
"scale": 1.0,
"cameraOffsets": [100, -100]
},
"gf": {
"position": [400, 130],
"zIndex": 900,
"scale": 1.0,
"cameraOffsets": [0, 0]
}
}
}
Character Data Fields
Character position as [x, y] (at character’s feet)
Render order relative to props
Scale multiplier applied to character (in addition to character’s base scale)
cameraOffsets
Array<Float>
default:"[0, 0]"
Camera focus offset as [x, y] when focusing on this character
Camera System
Camera Zoom
The stage defines a default camera zoom:
public var camZoom:Float; // From stage data
This is applied when the stage loads and can be dynamically changed during gameplay.
Camera Focus Points
The camera focuses on character positions:
public var cameraFollowPoint:FlxObject; // In PlayState
When a character sings, the camera moves to their cameraFocusPoint:
// Character's camera focus point
var focusPoint = character.cameraFocusPoint;
focusPoint.x = character.x + character.cameraOffsets[0];
focusPoint.y = character.y + character.cameraOffsets[1];
Camera offsets shift focus:
- Boyfriend:
[-100, -100] (left and up)
- Dad:
[100, -100] (right and up)
- Girlfriend:
[0, 0] (centered)
Camera Events
Stages can respond to camera events via scripts:
public function onCreate(event:ScriptEvent):Void
public function onUpdate(event:UpdateScriptEvent):Void
public function onBeatHit(event:SongTimeScriptEvent):Void
Stage Props API
Accessing Props
Named props can be accessed in scripts:
// Get a prop by name
var prop:StageProp = stage.getNamedProp("background");
// Modify prop properties
prop.alpha = 0.5;
prop.x += 100;
prop.playAnimation("bump");
Prop Animation
Animate props dynamically:
// Play animation
prop.playAnimation("idle");
// With callback
prop.playAnimation("bump", true, false, 0, function() {
trace("Animation finished!");
});
Adding Props at Runtime
// Create new prop
var newProp = new StageProp();
newProp.loadTexture("stages/myStage/newProp");
newProp.x = 500;
newProp.y = 300;
newProp.zIndex = 100;
// Add to stage
stage.addProp(newProp);
stage.refresh(); // Re-sort by zIndex
Stage Lighting
Stages can implement lighting effects:
// In stage script
function onBeatHit(event:SongTimeScriptEvent):Void
{
if (event.beat % 4 == 0) {
// Flash lights on downbeat
var lights = stage.getNamedProp("lights");
lights.alpha = 1.0;
FlxTween.tween(lights, {alpha: 0.5}, 0.5);
}
}
Pixel Art Stages
For pixel-art stages, disable anti-aliasing:
{
"props": [
{
"name": "pixelBg",
"assetPath": "stages/pixel/bg",
"position": [0, 0],
"isPixel": true,
"scale": 6
}
]
}
Pixel art best practices:
- Set
isPixel: true on all props
- Use integer scale factors (6 is common)
- Save sprites at native resolution, scale up in-engine
- Ensure characters also have
isPixel: true
Stage Scripts
Create custom stage behavior with scripts:
File: assets/data/stages/myStage.hxs
import funkin.play.stage.Stage;
import flixel.tweens.FlxTween;
class MyStage extends Stage
{
var bgProp:StageProp;
public override function onCreate(event:ScriptEvent):Void
{
super.onCreate(event);
bgProp = getNamedProp("background");
// Custom initialization
}
public override function onBeatHit(event:SongTimeScriptEvent):Void
{
super.onBeatHit(event);
// Pulse background on beat
if (event.beat % 2 == 0) {
FlxTween.tween(bgProp.scale, {x: 1.05, y: 1.05}, 0.3, {
onComplete: function(_) {
FlxTween.tween(bgProp.scale, {x: 1.0, y: 1.0}, 0.3);
}
});
}
}
}
Stage Loading
Stages are loaded via StageRegistry:
var stage:Stage = StageRegistry.instance.fetchEntry("mainStage");
The loading process:
- Loads JSON data from
assets/data/stages/[id].json
- Creates
Stage instance
- Instantiates all props from
props array
- Loads prop assets and animations
- Sorts props by z-index
- Positions characters based on
characters data
- Applies camera zoom
- Calls
onCreate() event
Example: Complete Stage
{
"version": "1.0.0",
"name": "Custom Stage",
"cameraZoom": 0.9,
"props": [
{
"name": "sky",
"assetPath": "#87CEEB",
"position": [0, 0],
"scale": [2000, 1000],
"zIndex": -1000,
"scroll": [0.1, 0.1]
},
{
"name": "back",
"assetPath": "stages/custom/background",
"position": [-600, -200],
"zIndex": -100,
"scroll": [0.9, 0.9]
},
{
"name": "floor",
"assetPath": "stages/custom/floor",
"position": [-650, 600],
"zIndex": 0
},
{
"name": "speaker",
"assetPath": "stages/custom/speaker",
"position": [50, 400],
"zIndex": 100,
"danceEvery": 1,
"animType": "sparrow",
"animations": [
{
"name": "danceLeft",
"prefix": "speaker left",
"frameRate": 24
},
{
"name": "danceRight",
"prefix": "speaker right",
"frameRate": 24
}
]
},
{
"name": "lights",
"assetPath": "stages/custom/spotlights",
"position": [0, 0],
"zIndex": 2000,
"alpha": 0.7,
"blend": "add"
}
],
"characters": {
"bf": {
"position": [850, 200],
"zIndex": 1000,
"cameraOffsets": [-100, -100]
},
"dad": {
"position": [150, 200],
"zIndex": 1000,
"cameraOffsets": [100, -100]
},
"gf": {
"position": [500, 150],
"zIndex": 900,
"cameraOffsets": [0, 0]
}
}
}