Skip to main content
The character system manages all playable and non-playable characters, their animations, and their behavior during gameplay.

Character Architecture

BaseCharacter Class

All characters extend BaseCharacter, which extends Bopper:
class BaseCharacter extends Bopper
{
  public var characterId:String;
  public var characterName:String;
  public var characterType:CharacterType = OTHER;
  
  public var holdTimer:Float = 0;
  public var isDead:Bool = false;
  public var debug:Bool = false;
  public var currentStage:Null<Stage> = null;
}
Key Properties:
characterId
String
Unique identifier for the character (e.g., “bf”, “dad”, “gf”)
characterName
String
Display name of the character (e.g., “Boyfriend”, “Daddy Dearest”)
characterType
CharacterType
Type of character: BF (player), DAD (opponent), GF (girlfriend), or OTHER
holdTimer
Float
Tracks how long the character has been playing the current sing animation (in seconds)

Character Types

Characters are categorized by their role:
enum abstract CharacterType(String)
{
  var BF = 'bf';      // Player character
  var DAD = 'dad';    // Opponent character  
  var GF = 'gf';      // Girlfriend/background character
  var OTHER = 'other'; // Other characters
}

Character Rendering Types

FNF supports multiple rendering methods:

SparrowCharacter

Uses Sparrow sprite sheets (Adobe Animate/XML format):
class SparrowCharacter extends BaseCharacter
{
  public function new(id:String)
  {
    super(id, CharacterRenderType.Sparrow);
    // Loads from assets/images/characters/[id].png + .xml
  }
}

AnimateAtlasCharacter

Uses Adobe Animate atlas format:
class AnimateAtlasCharacter extends BaseCharacter
{
  public function new(id:String)
  {
  super(id, CharacterRenderType.AnimateAtlas);
    // Loads from assets/images/characters/[id]/ directory
  }
}

PackerCharacter

Uses Packer sprite sheets (legacy format):
class PackerCharacter extends BaseCharacter
{
  public function new(id:String)
  {
    super(id, CharacterRenderType.Packer);
    // Loads Packer format sprite sheet
  }
}

Multi-Sprite Characters

  • MultiSparrowCharacter: Multiple Sparrow sheets
  • MultiAnimateAtlasCharacter: Multiple Animate atlases

Character Data Format

Character data is stored in JSON files at assets/data/characters/[id].json:
{
  "version": "1.0.1",
  "name": "Boyfriend",
  "renderType": "sparrow",
  "assetPath": "characters/bf",
  "scale": 1.0,
  "offsets": [0, 0],
  "cameraOffsets": [-100, -100],
  "healthIcon": {
    "id": "bf",
    "scale": 1.0,
    "antialiasing": true
  },
  "animations": [
    {
      "name": "idle",
      "prefix": "BF idle dance",
      "offsets": [0, 0],
      "looped": false,
      "frameRate": 24,
      "flipX": false,
      "flipY": false
    },
    {
      "name": "singLEFT",
      "prefix": "BF NOTE LEFT",
      "offsets": [5, -6],
      "looped": false,
      "frameRate": 24
    }
  ],
  "startingAnimation": "idle",
  "flipX": false,
  "isPixel": false,
  "danceEvery": 1,
  "singTime": 8.0
}

Character Data Fields

version
String
required
Character data format version (currently “1.0.1”)
name
String
required
Display name for the character
renderType
String
required
Rendering type: "sparrow", "animateatlas", "packer", "multisparrow", or "multianimateatlas"
assetPath
String
required
Path to character assets (relative to assets/images/)
scale
Float
default:"1.0"
Base scale multiplier for the character sprite
offsets
Array<Float>
default:"[0, 0]"
Global position offsets as [x, y]
cameraOffsets
Array<Float>
default:"[0, 0]"
Camera focus point offsets as [x, y]
flipX
Bool
default:"false"
Whether to flip the character sprite horizontally
isPixel
Bool
default:"false"
Whether this is a pixel-art character (disables anti-aliasing)
danceEvery
Int
default:"1"
How many beats between idle dance animations
singTime
Float
default:"8.0"
How long (in steps) to hold sing animations
startingAnimation
String
Name of the animation to play when character is created

Animation Data

Each animation in the animations array:
name
String
required
Internal animation name (e.g., “idle”, “singLEFT”, “singDOWN”)
prefix
String
required
Animation prefix in the sprite sheet XML
offsets
Array<Float>
default:"[0, 0]"
Position offsets specific to this animation [x, y]
looped
Bool
default:"false"
Whether the animation should loop
frameRate
Int
default:"24"
Frames per second for the animation
flipX
Bool
default:"false"
Flip this animation horizontally
flipY
Bool
default:"false"
Flip this animation vertically
frameIndices
Array<Int>
Specific frame indices to use (if not using all frames)

Required Animations

Characters should define these core animations:

Idle Animations

{
  "name": "idle",
  "prefix": "BF idle dance"
}
Or alternating left/right:
[
  {
    "name": "danceLeft",
    "prefix": "BF idle dance LEFT"
  },
  {
    "name": "danceRight",
    "prefix": "BF idle dance RIGHT"
  }
]

Sing Animations

Required for playable characters:
[
  {
    "name": "singLEFT",
    "prefix": "BF NOTE LEFT"
  },
  {
    "name": "singDOWN",
    "prefix": "BF NOTE DOWN"
  },
  {
    "name": "singUP",
    "prefix": "BF NOTE UP"
  },
  {
    "name": "singRIGHT",
    "prefix": "BF NOTE RIGHT"
  }
]

Miss Animations (Optional)

For when the player misses notes:
[
  {
    "name": "singLEFTmiss",
    "prefix": "BF NOTE LEFT MISS"
  },
  {
    "name": "singDOWNmiss",
    "prefix": "BF NOTE DOWN MISS"
  },
  {
    "name": "singUPmiss",
    "prefix": "BF NOTE UP MISS"
  },
  {
    "name": "singRIGHTmiss",
    "prefix": "BF NOTE RIGHT MISS"
  }
]

Character Position System

Position Properties

Characters use a feet-based origin system:
public var characterOrigin:FlxPoint;  // At character's feet (center-bottom)
public var cornerPosition:FlxPoint;    // Top-left corner of sprite
public var feetPosition:FlxPoint;      // Bottom-center of sprite
public var cameraFocusPoint:FlxPoint;  // Where camera should focus
Character origin is at the feet:
function get_characterOrigin():FlxPoint
{
  var xPos = (width / 2);  // Horizontal center
  var yPos = (height);     // Vertical bottom
  return new FlxPoint(xPos, yPos);
}

Camera Focus

The camera focuses on the character’s cameraFocusPoint:
public var cameraFocusPoint:FlxPoint = new FlxPoint(0, 0);
This point is automatically updated when the character moves, but not when animation offsets change. This ensures smooth camera motion.

Health Icons

Characters have associated health icons:
{
  "healthIcon": {
    "id": "bf",
    "scale": 1.0,
    "antialiasing": true,
    "isPixel": false
  }
}
healthIcon.id
String
required
ID of the icon asset (looks for assets/images/icons/[id].png)
healthIcon.scale
Float
default:"1.0"
Scale multiplier for the icon
healthIcon.antialiasing
Bool
default:"true"
Whether to use anti-aliasing on the icon

Death Animations

Player characters can define death animation settings:
{
  "death": {
    "cameraOffsets": [0, 0],
    "cameraZoom": 1.0,
    "preTransitionDelay": 0.0
  }
}
death.cameraOffsets
Array<Float>
default:"[0, 0]"
Camera offset during game over screen [x, y]
death.cameraZoom
Float
default:"1.0"
Camera zoom level during game over
death.preTransitionDelay
Float
default:"0.0"
Delay before transitioning to game over state (in seconds)

Character Bopping

Characters automatically “bop” to the music:
public var danceEvery:Int;  // Bop every X beats
public var shouldBop:Bool;  // Whether character should bop
The Bopper class handles automatic idle animation:
  • If danceLeft and danceRight exist, alternates between them
  • Otherwise plays idle animation
  • Controlled by danceEvery (beats between bops)

Combo Animations

Characters can have special animations for combos:
public var comboNoteCounts:Array<Int>;  // e.g., [50, 100, 250]
public var dropNoteCounts:Array<Int>;   // Combo break thresholds
When player hits these combo milestones, special animations can play.

Sing Time

Controls how long characters hold sing animations:
final singTimeSteps:Float;  // From character data
public var holdTimer:Float; // Current hold time
Sing animations hold for singTimeSteps to prevent rapid flickering back to idle between notes.

Character Scripts

Characters can be scripted for custom behavior:
class ScriptedCharacter extends BaseCharacter
{
  // Custom scripted behavior
}
Scripts can:
  • Override animation behavior
  • Add custom events
  • Implement special mechanics
  • Modify character properties dynamically

Loading Characters

Characters are loaded via CharacterDataParser:
var charData:CharacterData = CharacterDataParser.fetchCharacterData("bf");
var character = CharacterDataParser.createCharacter("bf");
The system:
  1. Loads JSON data from assets/data/characters/[id].json
  2. Validates render type and version
  3. Creates appropriate character class instance
  4. Loads sprite assets and animations
  5. Applies offsets and properties

Example: Creating a Custom Character

{
  "version": "1.0.1",
  "name": "Custom Character",
  "renderType": "sparrow",
  "assetPath": "characters/custom",
  "scale": 1.0,
  "offsets": [0, 0],
  "cameraOffsets": [-100, -100],
  "flipX": false,
  "isPixel": false,
  "danceEvery": 1,
  "singTime": 8.0,
  "animations": [
    {
      "name": "idle",
      "prefix": "Custom idle",
      "offsets": [0, 0],
      "looped": false,
      "frameRate": 24
    },
    {
      "name": "singLEFT",
      "prefix": "Custom left",
      "offsets": [5, -10],
      "frameRate": 24
    },
    {
      "name": "singDOWN",
      "prefix": "Custom down",
      "offsets": [-10, -20],
      "frameRate": 24
    },
    {
      "name": "singUP",
      "prefix": "Custom up",
      "offsets": [0, 20],
      "frameRate": 24
    },
    {
      "name": "singRIGHT",
      "prefix": "Custom right",
      "offsets": [-5, -10],
      "frameRate": 24
    }
  ],
  "healthIcon": {
    "id": "custom",
    "scale": 1.0
  }
}
Place sprite sheet at assets/images/characters/custom.png and custom.xml.

Build docs developers (and LLMs) love