Skip to main content
The PlayState class is the core gameplay state where all rhythm gaming occurs. It manages the song, characters, stage, notes, scoring, health, and all gameplay mechanics.

Overview

PlayState is implemented as a MusicBeatSubState so it can be loaded as a child of the chart editor. It handles:
  • Song playback and synchronization
  • Note rendering and hit detection
  • Character animations and stage elements
  • Health and scoring systems
  • Input processing with latency compensation
  • Camera movement and effects
  • Cutscenes and dialogue

Accessing PlayState

// Access the singleton instance (only exists during gameplay)
if (PlayState.instance != null)
{
  trace("Current health: " + PlayState.instance.health);
  trace("Current score: " + PlayState.instance.songScore);
}
PlayState.instance is null when not in gameplay. Always check for null before accessing.

Initialization

PlayStateParams

PlayState is initialized using a PlayStateParams typedef:
typedef PlayStateParams =
{
  targetSong:Song,
  ?targetDifficulty:String,
  ?targetVariation:String,
  ?targetInstrumental:String,
  ?practiceMode:Bool,
  ?botPlayMode:Bool,
  ?playtestResults:Bool,
  ?minimalMode:Bool,
  ?startTimestamp:Float,
  ?playbackRate:Float,
  ?overrideMusic:Bool,
  ?cameraFollowPoint:FlxPoint,
  ?mirrored:Bool
}
targetSong
Song
required
The song to play.
targetDifficulty
String
default:"Constants.DEFAULT_DIFFICULTY"
The difficulty to play (e.g., “easy”, “normal”, “hard”).
targetVariation
String
default:"Constants.DEFAULT_VARIATION"
The chart variation to use.
targetInstrumental
String
Alternate instrumental ID if the song supports multiple instrumentals.
practiceMode
Bool
default:"false"
Whether to start in Practice Mode (no score/health penalty).
botPlayMode
Bool
default:"false"
Whether to start in Bot Play Mode (auto-play).
minimalMode
Bool
default:"false"
If true, skips loading stage and characters, using a simple background.
startTimestamp
Float
default:"0.0"
Start position in milliseconds. Used for practice and chart playtesting.
playbackRate
Float
default:"1.0"
Song playback speed multiplier (1.0 = 100% speed).
Example:
// Load a song from the registry
var song = SongRegistry.instance.fetchEntry("tutorial");

// Create params
var params:PlayStateParams = {
  targetSong: song,
  targetDifficulty: "hard",
  practiceMode: false,
  botPlayMode: false
};

// Switch to PlayState
LoadingState.loadPlayState(params, function()
{
  FlxG.switchState(() -> new PlayState(params));
});

Key Properties

Song Properties

currentSong
Song
The currently active song.
currentDifficulty
String
The currently selected difficulty.
currentVariation
String
The currently selected chart variation.
currentInstrumental
String
The currently selected instrumental ID.
currentStage
Stage
The currently active stage with all props and characters.

Gameplay State

health
Float
Player’s current health (0-2 range, starts at 1.0).
songScore
Int
Player’s current score.
startTimestamp
Float
Starting position in milliseconds when the countdown ends.
playbackRate
Float
Song playback speed multiplier (1.0 = 100%).
isPracticeMode
Bool
Whether Practice Mode is active.
isBotPlayMode
Bool
Whether Bot Play Mode is active.
isMinimalMode
Bool
Whether Minimal Mode is active (no stage/characters).
isInCountdown
Bool
Whether the countdown before the song is active.
isInCutscene
Bool
Whether an animated cutscene is playing and gameplay is stopped.
disableKeys
Bool
Whether inputs are disabled (used after song ends or in stage editor).

Camera Properties

cameraFollowPoint
FlxObject
The object the gameplay camera follows. Tween this to move the camera smoothly.
currentCameraZoom
Float
Current camera zoom level without modifiers applied.
cameraBopIntensity
Float
default:"1.015"
Camera bop intensity multiplier applied on beat hits.
cameraZoomRate
Float
default:"4.0"
How many beats between camera zooms. Default is one zoom per measure (4 beats).

Audio Volume

instrumentalVolume
Float
default:"1.0"
Volume of the instrumental track (0.0-1.0).
playerVocalsVolume
Float
default:"1.0"
Volume of the player vocals track (0.0-1.0).
opponentVocalsVolume
Float
default:"1.0"
Volume of the opponent vocals track (0.0-1.0).

Key Methods

startCountdown()

Starts the countdown before the song begins.
public function startCountdown():Countdown
Returns: The Countdown object that was created. Example:
// Start countdown after a cutscene
function onCutsceneComplete():Void
{
  PlayState.instance.startCountdown();
}

endSong()

Ends the song and transitions to results or next song.
public function endSong(?ignoreDelay:Bool = false):Void
ignoreDelay
Bool
default:"false"
Whether to skip the end-of-song delay.

pauseGame()

Pauses the game and opens the pause menu.
function pauseGame():Void

resetCamera()

Resets the camera zoom and forces focus on the camera follow point.
public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true):Void
resetZoom
Bool
default:"true"
Whether to reset zoom to the default stage zoom.
cancelTweens
Bool
default:"true"
Whether to cancel any active camera tweens.

focusOnCharacter()

Moves the camera to focus on a specific character.
public function focusOnCharacter(char:BaseCharacter, ?useMidpoint:Bool = true):Void
char
BaseCharacter
required
The character to focus on.
useMidpoint
Bool
default:"true"
Whether to use the character’s midpoint for positioning.

Example: Custom Gameplay Mod

import funkin.play.PlayState;
import funkin.Conductor;
import flixel.FlxG;

class HealthDrainMod
{
  public static function init():Void
  {
    // Listen for beat hits
    Conductor.beatHit.add(onBeatHit);
  }
  
  static function onBeatHit():Void
  {
    if (PlayState.instance == null) return;
    
    // Drain health every 4 beats
    if (Conductor.instance.currentBeat % 4 == 0)
    {
      PlayState.instance.health -= 0.1;
      trace("Health drained! Current: " + PlayState.instance.health);
    }
  }
}

Example: Custom Camera Movement

import funkin.play.PlayState;
import flixel.tweens.FlxTween;
import flixel.tweens.FlxEase;

class CameraController
{
  public static function panToPosition(x:Float, y:Float, duration:Float = 1.0):Void
  {
    if (PlayState.instance == null) return;
    
    var followPoint = PlayState.instance.cameraFollowPoint;
    
    FlxTween.tween(followPoint, {x: x, y: y}, duration, {
      ease: FlxEase.sineInOut,
      onComplete: function(_)
      {
        trace("Camera pan complete");
      }
    });
  }
  
  public static function zoomTo(zoom:Float, duration:Float = 0.5):Void
  {
    if (PlayState.instance == null) return;
    
    FlxTween.tween(PlayState.instance, {currentCameraZoom: zoom}, duration, {
      ease: FlxEase.sineInOut
    });
  }
}

Example: Accessing Stage Characters

import funkin.play.PlayState;

class CharacterController
{
  public static function makeBoyfriendDance():Void
  {
    var playState = PlayState.instance;
    if (playState?.currentStage == null) return;
    
    var boyfriend = playState.currentStage.getBoyfriend();
    if (boyfriend != null)
    {
      boyfriend.dance();
    }
  }
  
  public static function changeDadIdle(newIdle:String):Void
  {
    var playState = PlayState.instance;
    if (playState?.currentStage == null) return;
    
    var dad = playState.currentStage.getDad();
    if (dad != null)
    {
      dad.playAnimation(newIdle, true);
    }
  }
}

Health System

Health ranges from 0.0 to 2.0, with 1.0 being the starting value:
  • Health < 0.0: Player dies (triggers game over)
  • Health = 1.0: Starting health (center of health bar)
  • Health > 2.0: Clamped to maximum
Health changes based on note hits:
  • Perfect hit: +0.023 health
  • Note miss: -0.0475 health
  • Ghost tap: Small penalty (configurable)
// Modify health directly
PlayState.instance.health += 0.1; // Heal
PlayState.instance.health -= 0.2; // Damage

// Check health state
if (PlayState.instance.health <= 0)
{
  trace("Player should die");
}

Volume Control

Control individual audio track volumes:
// Mute instrumental during dialogue
PlayState.instance.instrumentalVolume = 0.0;

// Lower opponent vocals
PlayState.instance.opponentVocalsVolume = 0.5;

// Restore all volumes
PlayState.instance.instrumentalVolume = 1.0;
PlayState.instance.playerVocalsVolume = 1.0;
PlayState.instance.opponentVocalsVolume = 1.0;

See Also

Build docs developers (and LLMs) love