Friday Night Funkin' Modding Documentation

This is an officially written and maintained documentation resource by The Funkin' Crew Inc. for modding Friday Night Funkin'.

Documentation Source Code

The source files from which this book is generated can be found on GitHub.

Creating a Friday Night Funkin' Mod - Fundamentals

This guide will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter in particular goes through a lot of core concepts of modding in Funkin' that will get used in other guides, so be sure to read through it carefully.

The Metadata File

To start, create a new folder within your mods folder. This is where your mod's assets and scripts will live. Next, create a new text file, and change its name to _polymod_meta.json. Make sure you didn't accidentally name it _polymod_meta.json.txt!

Inside this file, we will put the information the game needs in order to learn about your mod. I recommend doing this with a program like Visual Studio Code, it will correct you if you accidentally misplace a comma or something.

{
  "title": "Intro Mod",
  "description": "An introductory mod.",
  "contributors": [
    {
      "name": "EliteMasterEric"
    }
  ],
  "dependencies": {
    "modA": "1.0.0"
  },
  "optionalDependencies": {
    "modB": "1.3.2"
  },
  "api_version": "0.5.0",
  "mod_version": "1.0.0",
  "license": "Apache-2.0"
}

_polymod_meta.json has the following fields:

  • title: A readable name for the mod.
  • description: A readable description for the mod.
  • contributors: A list of Contributor objects.
  • homepage: A URL where users can learn more about your mod.
  • dependencies: A map of mod IDs which are mandatory dependencies, along with their version numbers.
    • These are the mods which must also be loaded in order for this mod to load.
    • If the mod is not included, it will fail.
    • The mod list will be reordered such that dependencies load first.
  • optionalDependencies: A map of mod IDs which are optional dependencies, along with their version numbers.
    • These mods do not necessarily need to be installed for this mod to load, but they will still force the mod list to be reordered so that the dependencies load before this mod.
  • api_version: A version number used to determine if mods are compatible with your copy of Funkin'. Change this to the version number for Friday Night Funkin' that you want to support, preferably the latest one (0.5.0 at time of writing.).
  • mod_version: A version number specifically for your mod. Choose any version or leave it at 1.0.0.
  • license: The license your mod is distributed under. Pick one from here or just leave it as Apache-2.0.

A Contributor has the following fields:

  • name: The contributor's name.
  • role: (optional) The role the contributor played, for example "Artist" or "Programmer"
  • email: (optional) A contact email
  • url: (optional) A homepage URL

Many of these fields are intended to be used in the future by an upcoming Mod Menu interface, which will allow users to organize their mods.

Loading the Mod In-Game

Now that you have a metadata file, you can start the game! Pro tip, if you run the game from the command line, you can see lots of useful debug messages, like these messages that indicate your mod has loaded!

source/funkin/modding/PolymodHandler.hx:316: Found 5 mods when scanning.
source/funkin/modding/PolymodHandler.hx:118: Attempting to load 5 mods...
...
source/funkin/modding/PolymodErrorHandler.hx:79: [INFO-] LOADING MOD - Preparing to load mod ../../../../example_mods/introMod
source/funkin/modding/PolymodErrorHandler.hx:79: [INFO-] LOADING MOD - Done loading mod ../../../../example_mods/introMod
...
source/funkin/modding/PolymodHandler.hx:169: Mod loading complete. We loaded 5 / 5 mods.

Neat! But right now, your mod doesn't do anything.

Asset Replacement and Additons

Asset Replacement

The key thing that Polymod allows you to do is to replace assets. This is done by adding those files to your mods folder in the same location as they would go.

For example, you can replace Girlfriend's sprites by placing your new sprites in the same location as they are in the assets folder, which would be shared/images/characters/GF_assets.png.

In other words, structure your mod like so:

-assets
-manifest
-plugins
-mods
 |-myMod
   |-shared
     |-images
       |-characters
         |-GF_assets.png
         |-GF_assets.xml
   |-_polymod_metadata.json
-Funkin.exe

When the game goes to load a character's sprites, it will make a request internally to retrieve the assets/shared/images/characters/GF_assets.png file to use for the texture (and the corresponding XML to split the image into individual frames). When it does, Polymod intercepts that request and checks if there is a file of that name among the loaded mods, and if so, it will use that instead.

Asset Additions

Polymod also allows you to add new files to the game. This is notable, as trying to place new files into the assets directory doesn't work, the game won't recognize those files.

The game still needs to get told to load those assets for them to get used, but there are many functions which load all the files in a given folder (such as the Song Registry, the Character Registry, the Stage Registry, etc). We'll look more into those later.

Mod Load Order

You may wonder what happens in the case where multiple mods provide a given file.

The answer is simple; mod order matters. If you have two mods installed which replace a particular asset, the mod which loads last will get precedence over mods that get loaded earlier, similar to Minecraft's resource pack system. This is evaluated on a per-file basis, so if Mod A replaces Pico and GF and Mod B replaces only GF, and Mod B is loaded after Mod A, you'll see the Pico from Mod A and the Girlfriend from Mod B.

In the current version of Friday Night Funkin', there is no accessible means of altering mod load order. Mods will load in alphabetical order by default, with dependencies being loaded first.

Hot Reloading

While developing your mod, you may find it inconvenient to make a change, open the game, encounter an error or visual issue, have to close the game, make another change, then open the game again and start the process over and over in order to achieve the desired results for your custom content.

Thankfully, there is a better way! Press F5 to force the game to dump its cache and reload all game data from disk, then restart the current state with the appropriate changes applied. This lets you, for example:

  • Tweak the positions of stage props, or add new ones, then immediately reload to see them in game.
  • Modify the animation offsets for a character and quickly ensure they work.
  • Modify a script to resolve an exception and reload to continue testing without closing the game.

Chapter Conclusion

This chapter has covered the fundamentals of creating mods for Friday Night Funkin'. In other sections, we will go over how to add different types of custom content.

Creating a Friday Night Funkin' Mod - Custom Songs and Custom Levels

This chapter will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter goes over adding new playable songs and Story Mode levels to the game.

Creating a Chart

To create a chart, access the Chart Editor tool. This can be found in-game by accessing the Debug menu from the main menu (this is bound to ~ by default). You can also access it by adding a keybind for "Debug Chart" in the options menu (not bound by default), then pressing the bound key while playing a song.

From here, you can create a new chart from audio files, import one from an older version of the game, or build a chart from existing in-game chart data.

Detailed use of the chart editor deserves its own guide, but the basic should be fairly intuitive. For now, let's assume you've made a chart of your favorite song, and want to turn it into a mod people can play.

Dissecting Your FNFC File

When you save your chart, the game packages it up into a .fnfc file, which makes it easy to share with other charters and collaborate. It includes the audio for the song, along with the note data and some metadata files to go with it.

To add the song to our mod, we need to get that info out. This is fairly easy, because an FNFC file is actually secretly a ZIP file! Rename your mychart.fnfc to mychart.zip, replacing the file extension so that your operating system recognizes it as a ZIP, and extract it to reveal its contents, which will be something like:

-manifest.json
-mychart-metadata.json
-mychart-chart.json
-Inst.ogg
-Voices-bf.ogg
-Voices-pico.ogg

Adding The Custom Song

At the end of Creating A Chart we learned that .fnfc files were just .zip archives, so we can simply rename it and unzip it. Once we have thse files, we just need to put them in the correct spots in our mod folder!

  • The manifest.json file can be discarded, our mod won't need it.
  • The metadata.json and chart.json files need to go into the data/songs/<songid> folder, replacing <songid> with the internal name for our song.
  • The OGG files need to go into songs/<songid>, again replacing <songid> with the internal name of our song.

We'll end up with something like this.

-mods
 |-myMod
   |-data
     |-songs
       |-mychart
         |-mychart-metadata.json
         |-mychart-chart.json
   |-songs
     |-mychart
       |-Inst.ogg
       |-Voices-bf.ogg
       |-Voices-pico.ogg
   |-_polymod_metadata.json

When the game starts, it queries the list of available songs by looking in the data/songs folder for <songid>/<songid>-metadata.json files, which it then uses to find the chart file and the requisite song files. Neat! But right now, if you boot up the game, this doesn't do anything. You'll see it mentioned in the logs with no complaints, but it's not playable in Story Mode or Freeplay, what gives?

source/funkin/play/song/Song.hx:579: Fetching song metadata for mychart
source/funkin/data/song/SongRegistry.hx:103:   Loaded entry data: Song(mychart)

The fix is simple; every song must be part of a Story Mode level to appear in Freeplay.

Adding a Custom Level

Add a new file, with whatever internal name you like, into your mods folder, under the data/levels/ directory, with the .json file extension.

NOTE: Keep in mind that if your internal name is the same as a week from the base game, or of another mod, they will overlap each other and you may have unexpected results!

We'll end up with something like this.

-mods
 |-myMod
   |-data
     |-levels
       |-myweek.json
     |-songs
       |-mychart
         |-mychart-metadata.json
         |-mychart-chart.json
   |-songs
     |-mychart
       |-Inst.ogg
       |-Voices-bf.ogg
       |-Voices-pico.ogg
   |-images
     |-storymenu
       |-titles
         |-myweek.png
   |-_polymod_metadata.json

Your custom week's JSON file will look something like the following:

{
  "version": "1.0.0",
  "name": "MY CUSTOM WEEK",
  "titleAsset": "storymenu/titles/myweek",
  "background": "#F9CF51",
  "songs": ["mychart"],
  "visible": true,
  "props": [
    {
      "assetPath": "storymenu/props/dad",
      "scale": 1.0,
      "offsets": [100, 60],
      "animations": [
        {
          "name": "idle",
          "prefix": "idle0",
          "frameRate": 24
        }
      ]
    },
    {
      "assetPath": "storymenu/props/bf",
      "scale": 1.0,
      "offsets": [150, 80],
      "animations": [
        {
          "name": "idle",
          "prefix": "idle0",
          "frameRate": 24
        },
        {
          "name": "confirm",
          "prefix": "confirm0",
          "frameRate": 24
        }
      ]
    }
  ]
}

There's a lot of info here! Let's break it down:

  • version: The version number for the Level data file format. Leave this at 1.0.0.
  • name: The readable name for the Level, as displayed at the top right of the Story Menu.
  • titleAsset: The asset to use for the level name in the list of level, relative to the images folder in your mod folder.
  • background: The background to use for the level. #F9CF51 is the classic yellow, but this field takes either a color code OR an image file path (relative to the images folder in your mod folder).
  • songs: A list of song IDs to include in the week.
  • visible: Whether this story level is visible in the Story Menu.
  • props: Data for the props to display on the Story Menu when the level is selected. For example, Week 1 will display Daddy Dearest, Boyfriend, and Girlfriend.

When the game starts, it queries the list of available levels by looking in the data/levels folder for JSON files, which it then uses to populate the Story Menu, and then the Freeplay Menu (in non-alphabetical views, songs in Freeplay appear in order by what level they are included in).

If you want your custom song to only show up in Freeplay, you can just create a custom week and set the visible property to false, and the songs will show up in Freeplay!

What Are Variations?

Variations are groups of difficulties for a given song which share metadata.

As an example, the song DadBattle has eight separate difficulties (at time of writing). These are Easy, Normal, Hard, Erect (Erect Mix), Nightmare (Erect Mix), Easy (Pico Mix), Normal (Pico Mix), and Hard (Pico Mix).

These are divided into three variations; Default, Erect, and Pico. Each variation defines information like BPM (and BPM changes), artist, charter, which album the song is from, which stage to use, which character to use in those stages, and which instrumental and vocal track to use. The variation then defines which difficulties it includes, and the chart data for that variation specifies the events for that variation, and the note data and scroll speed for each difficulty.

This means that, in order to make one of these values different for a specific difficulty or remix (including changing the events), you must put that difficulty into a new variation.

The metadata.json file for a song defines what variations exist for that song; the game then looks for metadata-<variationID>.json and chart-<variationID>.json files for each listed variation.

Adding Variations to Existing Songs

Through modding, it is possible to add new variations to existing songs. This is great for adding a new difficulty or remix to an existing song (even if that song is from another mod).

Obtaining Required Files

The first step is to compose a new remix for the song. If you're making a playable character remix, the composer will have to manually make sure the original vocals line up if you want the option to use alternate instrumentals.

You then have to chart this remix. When you're done, you should have an Inst.ogg file, two Voices OGG files, a metadata.json and a chart.json.

Placing the Files

Next, place the assets in the correct spots in our mod folder! Rename each of the files, adding a variation ID of your choice to the end (so if you're making an erect remixes, rename Inst.ogg to Inst-erect.ogg):

-mods
 |-myMod
   |-data
     |-songs
       |-mychart
         |-mychart-metadata-erect.json
         |-mychart-chart-erect.json
   |-songs
     |-mychart
       |-Inst-erect.ogg
       |-Voices-bf-erect.ogg
       |-Voices-pico-erect.ogg
   |-_polymod_metadata.json

Registering the Variation in the JSON Data

Each chart includes a songVariations array, which lets the game know which variations the song has available so it can query their respective metadata files. In order to get the game to load your custom variation, you need to modify the metadata.json file for the song's chart, so the game knows that variation exists.

If the song is from your own mod

If the base song you're adding the remix to is from your own mod, you can just add the variation to the metadata.json for your chart.

Add your variation ID to the playData.songVariations array (creating the key if it doesn't exist).

{
    "playData": {
        "songVariations": ["erect"] // Add your new variation to this array.
        // ...
    }
    // ...
}

If the song is from the base game, or a different mod

If the base song you're adding to is from a different mod, you don't want to replace the underlying data in case it changes. You want to instead apply a JSON Patch to the file (which Polymod provides the ability to do).

Create a _merge folder in your mod folder, then create a file within that directory whose path matches the one you want to patch, like so:

-mods
 |-myMod
   |-_merge
     |-data
       |-songs
         |-mychart
           |-mychart-metadata.json

Then we apply a simple patch, which adds a new value to the playData.songVariations array. Edit the JSON file and add these contents:

[
    { "op": "add", "path": "/playData/songVariations/-", "value": "erect" } // Add a new value erect to the end of the songVariations array.
]

The patching system is very flexible; it works on any JSON file (base game or provided by another mod) and has support for advanced behavior. See Merging Files for more information.

Conclusion

Now, when you start the game, your additional variation should be available in-game!

If you created a character remix, make sure the player character for the remix is included in the ownedCharacters data for your custom playable character. See Custom Playable Characters for more info.

Creating a Friday Night Funkin' Mod - Custom Characters

This chapter will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter goes over adding new characters to the game, and using them in a level. If you are looking for adding characters to the character select screen, see Custom Playable Characters.

Character Spritesheet Formats

The individual sprites of a character's animations must be combined into a spritesheet for the game to use them. Friday Night Funkin' supports one of several formats:

  • sparrow: Combines the images into a large sheet, then provides an XML file containing the coordinates of each frame with it. Can be exported directly from Adobe Animate or Flash CS6 using the Generate Sprite Sheet option, or can be created from individual frames using Free Texture Packer (note that Free Texture Packer refers to this format as Starling).

  • packer: Combines images into a sheet, then provides a TXT file containing the coordinates of each frame.

  • animateatlas: Created exclusively when using Adobe Animate, this exports individual symbols into a large sheet, then provides a JSON file with data to split up each symbol, then provides a second JSON to arrange those symbols into animations. Great for performance, especially for characters which were made by rearranging smaller parts. We use the FlxAnimate Haxelib for this.

  • multisparrow: Allows for different groups of animations to be exported into separate Sparrow spritesheets, then combined together into one character.

Creating a Character

A custom character requires creating a new JSON file in the data/characters folder. Below is an example of Girlfriend's character data file, from assets/data/characters/gf.json1

{
  "version": "1.0.0",
  "name": "Girlfriend",
  "renderType": "sparrow",
  "assetPath": "characters/GF_assets",
  "startingAnimation": "danceRight",
  "singTime": 8.0,
  "animations": [
    {
      "name": "danceLeft",
      "prefix": "GF Dancing Beat",
      "frameIndices": [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
      "offsets": [0, -9]
    },
    {
      "name": "danceRight",
      "prefix": "GF Dancing Beat",
      "frameIndices": [
        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
      ],
      "offsets": [0, -9]
    },
    {
      "name": "sad",
      "prefix": "gf sad",
      "frameIndices": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
      "offsets": [2, -21]
    },
    {
      "name": "singLEFT",
      "prefix": "GF left note",
      "offsets": [0, -19]
    },
    {
      "name": "singDOWN",
      "prefix": "GF Down Note",
      "offsets": [0, -20]
    },
    {
      "name": "singUP",
      "prefix": "GF Up Note",
      "offsets": [0, 4]
    },
    {
      "name": "singRIGHT",
      "prefix": "GF Right Note",
      "offsets": [0, -20]
    },
    {
      "name": "cheer",
      "prefix": "GF Cheer",
      "offsets": [0, 0]
    },
    {
      "name": "combo50",
      "prefix": "GF Cheer",
      "offsets": [0, 0]
    },
    {
      "name": "drop70",
      "prefix": "gf sad",
      "frameIndices": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
      "offsets": [2, -21]
    },
    {
      "name": "hairBlow",
      "prefix": "GF Dancing Beat Hair blowing",
      "frameIndices": [0, 1, 2, 3],
      "offsets": [45, -8]
    },
    {
      "name": "hairFall",
      "prefix": "GF Dancing Beat Hair Landing",
      "frameIndices": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
      "offsets": [0, -9]
    },
    {
      "name": "scared",
      "prefix": "GF FEAR",
      "offsets": [-2, -17]
    }
  ]
}

The available fields are:

  • version: The version number for the Character data file format. Leave this at 1.0.0.
  • name: The readable name for the character, used in places like the Chart Editor.
  • renderType: The render type. One of sparrow, packer, animateatlas, multisparrow.
  • assetPath: The main asset path to use for this character, relative to the images directory in your mod folder.
    • For the sparrow asset type, this must point to the path where the xml and png are located, without the file extension.
    • For the packer asset type, this must point to the path where the txt and png are located, without the file extension.
    • For the animateatlas asset type, this must point to the folder where the Animation.json and any spritemaps are located.
    • For the multisparrow asset type, point to the path where your main Sparrow spritesheet is located. On each animations which uses a different Sparrow spritesheet from the main one, add the assetPath key to that specific animation.
  • scale (currently buggy): Specify the size of the character relative to the original size. For example, 2.0 makes the sprite twice as big. Optional, defaults to 1.0.
  • healthIcon: Data for the health icon to display in-game. For example, Boyfriend will obviously use Boyfriend's health icon. Optional, defaults its ID to character's ID.
  • death: Data for the death screen to use, when the character reaches 0 health. Optional, doesn't default to a specific object.
  • offsets: The global offset to the character's position, in pixels. Optional, defaults to [0, 0].
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • cameraOffsets: The amount to offset the camera by while focusing on the character. Optional, default value focuses on the character directly.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • isPixel: Specify whether to disable texture smoothing for the character. Optional, defaults to false.
  • danceEvery: The frequency at which the character will play its idle animation, in beats. Optional, defaults to 1.
    • Increasing this number will make the character dance less often.
  • flipX: Whether to flip the whole sprite horizontally in-game. Useful for characters that could also be played (Pico). Optional, defaults to false.
  • startingAnimation: The animation for the character to play when they are first loaded in. Optional, defaults to idle.
  • singTime: The amount of time, in steps, for a character to keep singing after they let go of a note. Optional, defaults to 8.
    • Decrease this if the character seems to hold their poses for too long after their section is done.
    • Increase this if the character resets to the idle animation in the middle of their singing animations.
  • animations: A list of animation data objects for the character.

Health Icon data is structured like so:

  • id: The ID to use for the health icon, defaults to character's ID.
  • scale: Specify the size of the health icon relative to the original size. For example, 2.0 makes the sprite twice as big. Optional, defaults to 1.0.
  • flipX: Whether to flip the whole sprite horizontally in-game. Optional, defaults to false.
  • isPixel: Specify whether to disable texture smoothing for this characters health icon. Optional, defaults to false.
  • offsets: The offset of the health icon, in pixels. Optional, defaults to [0, 0].
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.

Death data is structured like so:

  • cameraOffsets: The amount to offset the camera by while focusing on this character as they die. Optional, defaults to [0, 0].
    • Default value focuses on the character's graphic midpoint.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • cameraZoom: The amount to zoom the camera by while focusing on this character as they die. Optional, defaults to 1.
  • preTransitionDelay: The delay between when the character reaches 0 health and when the death animation plays. Optional, defaults to 0.

Animation data is structured like so:

  • name: The internal animation name for the game to use.
  • prefix: The animation name as specified by your spritesheet.
    • For Sparrow or Packer, check inside the data file to see what each set of frames is named, and use that as the prefix, excluding the frame numbers at the end.
    • For Animate Atlases, use either the frame label or the symbol name of the animation you want to play.
  • offsets: Some animations may need their positions to be corrected relative to the idle animation.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • looped: Whether to loop this animation in-game. If false, the animation will pause when it ends, until the game commands the character to do something else.
  • flipX: Whether to flip the sprites of this animation horizontally in-game.
  • flipY: Whether to flip the sprites of this animation vertically in-game.
  • frameRate: A frame rate value, defaulting to 24.
  • frameIndices: Optionally specify an array of frame numbers (starting at frame 0!) to use from a given prefix for this animation.
    • For example, specifying [0, 1, 2, 3] will make this animation only use the first 4 frames of the given prefix.
  • assetPath: For the multisparrow asset type specifically. Define a secondary Sparrow spritesheet which will be loaded, and which contains the frames for this animation.

The animation names the game uses by default are:

  • idle: For the idle animation.
  • danceLeft and danceRight: Supercedes the idle animation with one that toggles between two animations.
  • singLEFT, singDOWN, singUP, singRIGHT: The animations for playing notes, when the character is a player or opponent.
  • singLEFTmiss, singDOWNmiss, singUPmiss, singRIGHTmiss: The animations for missing notes, when the character is a player.
  • Adding a new singing animation with the name of an existing animation with -hold at the end will play the animation after the first one ends, while the character is still singing.
    • As a good example, you can copy the singLEFT animation to make a singLEFT-hold animation, which has looped as true and frameIndices as the last few frames of the singing animation.
  • Adding a new singing animation with the name of an existing animation with -end at the end will play an animation before returning to idle.
    • For example, you can define a new singLEFT-end animation to cleanly transition into the idle animation.
  • You can add other animations by name, but you'll have to play them with a script, or a Play Animation song event in the Chart Editor.

When the game starts, it queries the list of possible characters by searching in the data/characters folder for JSON files. This gets used to preload data which is used later when the character is loaded in a stage.

Replacing/Reskinning an Existing Character

As a short aside, you can create a JSON with the same filename as an existing character (from the base game, or from a mod if your mod loads after it) and it will replace it. This can be used to create more elaborate reskins for characters, such as ones that use a different render type.

Using a Character in a Song

There are two ways to use your character in a song once it's implemented.

  1. Create a new chart. Open the Chart Editor, start a chart, and select the character from the Metadata toolbox before charting.
  2. Edit an existing chart in your mod. Open the metadata.json file and check in playData.characters for the player, girlfriend, and opponent keys.

Once the chart which references your character is in your mod folder, simply start the game with your mod installed.

Fixing Character Offsets

Uh Oh! Upon using your character in a song, you might have noticed that with each note hit, the character has weird offsets which makes it wobble back and forth. Let's fix that.

Accessing the Animation Editor

To fix offsets for you character, you first have to access the Animation Editor tool. This can be found in-game by accessing the Debug menu from the main menu (this is bound to ~ by default) and selecting "Animation Editor" option.

Once you have accessed the tool, it might be a little overwhelming at first, but everything is pretty straightforward.

Fixing the Offsets

The first thing you have to do is click 2 on your keyboard to switch to Animation Mode in order to properly fix offsets for each animation. Then, you need to select your character from the Character section in the UI box that is located in the top-left corner.

HINT: The best thing to do to speed up your process, it to toggle Onion Skin mode by pressing F. This will show the previous animation played being half transparent. This can help speeding up the proccess, since you will be able to to properly line up the animation with the previous one.

The UI will show you all of the possible controls and shortcuts, to make your proccess of fixing the character offsets much easier.

Saving Offsets

Once you are happy with your result, simply press ESC on your keyboard to save the Character Data file.

  • Currently there is a bug which makes the file saving system not automatically put character's ID in the file name, which you will have to do yourself. Simply name the file the ID of your character followed by .json.

From the "Creating a Character" chapter you will know, that you have to place this character data JSON file in data/characters. Then, you can simply use Hot Reloading to check the offsets without restarting the game.

Creating a Friday Night Funkin' Mod - Custom Stages

This chapter will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter goes over adding new stages to the game, and using them in a song.

Creating a Stage

Stage Assets

The image assets required for a stage should be placed into the shared/images/ folder within your mod, ideally somewhere organized like a subfolder with the name of the stage.

Stage Data

A custom stage requires creating a new JSON file in the data/stages folder

Below is the "Main Stage" json file from Week 1 assets/data/stages/mainStage.json1

{
  "version": "1.0.1",
  "name": "Main Stage",
  "cameraZoom": 1.1,
  "props": [
    {
      "zIndex": 10,
      "position": [-600, -200],
      "scale": [1, 1],
      "name": "stageBack",
      "assetPath": "stages/mainStage/stageback",
      "scroll": [0.9, 0.9]
    },
    {
      "zIndex": 20,
      "position": [-650, 600],
      "scale": [1.1, 1.1],
      "name": "stageFront",
      "assetPath": "stages/mainStage/stagefront",
      "scroll": [0.9, 0.9]
    },
    {
      "zIndex": 30,
      "position": [-500, -300],
      "scale": [0.9, 0.9],
      "name": "stageCurtains",
      "assetPath": "stages/mainStage/stagecurtains",
      "scroll": [1.3, 1.3]
    }
  ],
  "characters": {
    "bf": {
      "zIndex": 300,
      "position": [989.5, 885],
      "cameraOffsets": [-100, -100]
    },
    "dad": {
      "zIndex": 200,
      "position": [335, 885],
      "cameraOffsets": [150, -100]
    },
    "gf": {
      "zIndex": 100,
      "cameraOffsets": [0, 0],
      "position": [751.5, 787]
    }
  }
}

The available fields are:

  • version: The version number for the Stage data file format. Leave this at 1.0.1.
  • name: The readable name for the stage, used in places like the Chart Editor.
  • cameraZoom: The default camera zoom level for the stage. Optional, defaults to 1.0.
  • props: An array of props to add to the stage, specifying what sprites should be used and where those sprites should be positioned.
  • characters: Data on how characters in the stage should be positioned.

Stage prop data is structured like so:

  • name: An internal name for this prop. Good for keeping track of things. Keep each prop name unique!
    • You can access a stage prop in a script using PlayState.instance.currentStage.getNamedProp(name).
  • assetPath: The asset used to display the prop. This can be either an image or a color.
    • To use an image, specify the path relative to the images folder in your mod folder.
    • To use a color, specify a hex code. This creates a 1x1 pixel square of the specified color, which you can resize with the scale property.
  • zIndex: A value describing the relative position of the prop. Optional, defaults to 0.
    • Elements with higher values get placed in front of elements with lower values.
  • position: The horizontal and vertical position of the prop, in pixels.
  • isPixel: Specify whether to display this image as a pixel prop (disabling texture smoothing). Optional, defaults to false.
  • scale: Specify the size of the prop relative to the original size. For example, 2.0 makes the sprite twice as big. Defaults to 1.0 if unspecified.
  • alpha: Specify the opacity of the prop, with 1.0 being fully opaque and 0.0 being completely transparent. Optional, defaults to 1.0.
  • scroll: Specify how much scroll factor, or how much the prop moves relative to the camera horizontally and vertically. Defaults to [0.0, 0.0] if unspecified.
    • A value of [0, 0] causes the prop to not move at all in relation to the camera.
    • A value of [1, 1] causes the prop to move 1-to-1 with the camera. Characters usually have a scroll factor of [1, 1].
    • A value of [0, 1] causes the prop to only move vertically in relation to the camera as focus changes.
    • A value of [0.5, 0.5] causes the prop to move less relative to props configured to move 1-to-1
    • A value of [2, 2] is valid and causes the prop to move 2-to-1 as the camera moves.
  • animType: If the prop you choose is animated, specify sparrow or packer for which animation type you're using.
    • Most of the game's spritesheets are Sparrow v2 sheets exported from Adobe Animate.
  • animations: A list of animation data objects for the stage prop.
  • startingAnimation: The animation to play on the prop when the stage starts. If the animation is configured to loop, it will play forever unless a script calls a different one (or danceEvery is greater than 0). If unspecified, no animation will play unless a script does so.
  • danceEvery: If non-zero, this prop will play an animation every X beats of the song. Defaults to 0.0, or no bopping.
    • This tries to play the idle animation.
    • If danceLeft and danceRight animations are specified, the game will alternate between these instead.
    • This value supports precision up to 0.25, where 0.25 plays the animation four times per beat.

Stage prop animation data is structured like so:

  • name: The internal animation name for the game to use.
  • prefix: The animation name as specified by your spritesheet.
    • For Sparrow or Packer, check inside the data file to see what each set of frames is named, and use that as the prefix, excluding the frame numbers at the end.
    • For Animate Atlases, use either the frame label or the symbol name of the animation you want to play.
  • offsets: Some animations may need their positions to be corrected relative to the idle animation.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • looped: Whether to loop this animation in-game. If false, the animation will pause when it ends, until the game commands the character to do something else.
  • flipX: Whether to flip the sprites of this animation horizontally in-game.
  • flipY: Whether to flip the sprites of this animation vertically in-game.
  • frameRate: A frame rate value, defaulting to 24.
  • frameIndices: Optionally specify an array of frame numbers (starting at frame 0!) to use from a given prefix for this animation.
    • For example, specifying [0, 1, 2, 3] will make this animation only use the first 4 frames of the given prefix.
  • assetPath: For the multisparrow asset type specifically. Define a secondary Sparrow spritesheet which will be loaded, and which contains the frames for this animation.

Character data is structured like so:

  • bf: Data about the stage's player character.
    • zIndex: A value describing the relative position of the player character. Elements with higher values get placed in front of those with lower values.
    • position: The X and Y position where the character should be positioned, relative to other props in the stage.
    • scale: The relative scale to display the character at. For example, 0.5 makes the character half as big as the default.
    • cameraOffsets: When the camera focuses on this character, focus on the character's center, then apply camera offsets to shift the camera focus to the desired spot.
  • dad: Data about the stage's opponent character.
    • zIndex: A value describing the relative position of the opponent character. Elements with higher values get placed in front of those with lower values.
    • position: The X and Y position where the character should be positioned, relative to other props in the stage.
    • scale: The relative scale to display the character at. For example, 0.5 makes the character half as big as the default.
    • cameraOffsets: When the camera focuses on this character, focus on the character's center, then apply camera offsets to shift the camera focus to the desired spot.
  • gf: Data about the stage's background character.
    • zIndex: A value describing the relative position of the background character. Elements with higher values get placed in front of those with lower values.
    • position: The X and Y position where the character should be positioned, relative to other props in the stage.
    • scale: The relative scale to display the character at. For example, 0.5 makes the character half as big as the default.
    • cameraOffsets: When the camera focuses on this character, focus on the character's center, then apply camera offsets to shift the camera focus to the desired spot.

Animation data is structured like so:

  • name: The internal animation name for the game to use.
  • prefix: The animation name as specified by your spritesheet.
    • For Sparrow or Packer, check inside the data file to see what each set of frames is named, and use that as the prefix, excluding the frame numbers at the end.
    • For Animate Atlases, use either the frame label or the symbol name of the animation you want to play.
  • offsets: Some animations may need their positions to be corrected relative to the idle animation.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • looped: Whether to loop this animation in-game. If false, the animation will pause when it ends, until the game commands the character to do something else.
  • flipX: Whether to flip the sprites of this animation horizontally in-game.
  • flipY: Whether to flip the sprites of this animation vertically in-game.
  • frameRate: A frame rate value, defaulting to 24.
  • frameIndices: Optionally specify an array of frame numbers (starting at frame 0!) to use from a given prefix for this animation.
    • For example, specifying [0, 1, 2, 3] will make this animation only use the first 4 frames of the given prefix.
  • assetPath: For the multisparrow asset type specifically. Define a secondary Sparrow spritesheet which will be loaded, and which contains the frames for this animation.

The animation names the game uses by default are:

  • idle: For the idle animation.
  • danceLeft and danceRight: Supercedes the idle animation with one that toggles between two animations.
  • singLEFT, singDOWN, singUP, singRIGHT: The animations for playing notes, when the character is a player or opponent.
  • singLEFTmiss, singDOWNmiss, singUPmiss, singRIGHTmiss: The animations for missing notes, when the character is a player.
  • Adding a new singing animation with the name of an existing animation with -hold at the end will play the animation after the first one ends, while the character is still singing.
    • As a good example, you can copy the singLEFT animation to make a singLEFT-hold animation, which has looped as true and frameIndices as the last few frames of the singing animation.
  • Adding a new singing animation with the name of an existing animation with -end at the end will play an animation before returning to idle.
    • For example, you can define a new singLEFT-end animation to cleanly transition into the idle animation.
  • You can add other animations by name, but you'll have to play them with a script, or a Play Animation song event in the Chart Editor.

When the game starts, it queries the list of possible characters by searching in the data/characters folder for JSON files. This gets used to preload data which is used later when the character is loaded in a stage.

Using a Stage in a Song

There are two ways to use your stage in a song once it's implemented.

  1. Create a new chart. Open the Chart Editor, start a chart, and select the stage from the Metadata toolbox before charting.
  2. Edit an existing chart in your mod. Open the metadata.json file and check in playData for the stage key, and set it to your internal ID.

Once the chart which references your stage is in your mod folder, simply start the game with your mod installed.

Creating a Friday Night Funkin' Mod - Custom Playable Characters

This chapter will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter goes over adding new playable characters to the game, ensuring that they appear in the Character Select menu, determining whether the character has been unlocked, customizing the appearance of the Freeplay menu, and adding custom remixes to them.

Required Assets

In order to make a fleshed out custom character that can be used from Character Select in Friday Night Funkin', you need a large number of textures, animations, and audio tracks:

  • A custom character
    • This requires a set of singing animations for the character.
  • At least one custom song that uses that character; this can be either a new song or a variation added to an existing song.
    • This requires an instrumental and split vocal tracks.
  • A pixel icon asset to use for that character's icon in the Character Select grid.
    • This supports either a static image or a Sparrow spritesheet (which includes the animation to play when the character is selected).
  • A namecard asset to use for that character's name above them in the Character Select menu.
    • The pixellation effect is done in code so this just needs to be a single static image.
  • Animations for the character to use in the Character Select menu.
    • This is currently hard-coded to use an Adobe Animate texture atlas and cannot use a Sparrow spritesheet.
    • The character needs animations for unlock, idle, slide in, slide out, select, and deselect.
  • Assets to use for the character's Girlfriend character to the left of them in the Character Select menu.
    • This is currently hard-coded to use an Adobe Animate texture atlas and cannot use a Sparrow spritesheet.
    • The character needs animations for unlock, idle, slide in, slide out, select, and deselect.
    • NOTE: This is hardcoded to use only GF or Nene right now I swear I'll fix this soon.
  • Assets for the character to use on the Freeplay menu.
    • This is currently hardcoded to use an Adobe Animate texture atlas.
    • The character needs animations for leaping in, idle, confirm, and moving to character select. It also optionally has an idle and cartoon animation.
  • Assets for the character's Freeplay skin and the backing card.
    • This requires a variety of assets but can use Boyfriend's as a fallback.
  • Assets for the character's animations in the Results screen.
    • Each rank has its own animation and music, but animations can be reused between ranks and results themes can fall back to the default.
    • Rank animations are Loss, Good, Great, Excellent, Perfect, and Perfect Gold (the base game uses the same animation for Perfect and Perfect Gold)
    • Each also can take its own music, but you can reuse Boyfriend's as a good placeholder.

Creating a Playable Character

A custom playable character requires creating a new JSON file in the data/characters folder. Below is an example of Pico's character data file, from assets/data/players/pico.json1

{
  "version": "1.0.0",
  "name": "Pico",
  "ownedChars": [
    "pico",
    "pico-playable",
    "pico-blazin",
    "pico-christmas",
    "pico-dark"
  ],
  "showUnownedChars": false,
  "unlocked": true,
  "freeplayStyle": "pico",
  "freeplayDJ": {
    "assetPath": "freeplay/freeplay-pico",
    "text1": "PICO",
    "text2": "GOD DAMN HE DOWN ON THE NUT",
    "text3": "ZEBOIM DAMN IMA NUT",
    "fistPump": {
      "introStartFrame": 0,
      "introEndFrame": 4,

      "loopStartFrame": 4,
      "loopEndFrame": -1,

      "introBadStartFrame": 0,
      "introBadEndFrame": 0,

      "loopBadStartFrame": 0,
      "loopBadEndFrame": -1
    },
    "charSelect": {
      "transitionDelay": 0.45
    },
    "animations": [
      {
        "name": "intro",
        "prefix": "pico dj intro",
        "offsets": [631.7, 362.6]
      },
      {
        "name": "idle",
        "prefix": "Pico DJ",
        "offsets": [625, 360]
      },
      {
        "name": "idleEasterEgg",
        "prefix": "Pico DJ afk",
        "offsets": [625, 360]
      },
      {
        "name": "confirm",
        "prefix": "Pico DJ confirm",
        "offsets": [625, 360]
      },
      {
        "name": "fistPump",
        "prefix": "pico cheer",
        "offsets": [975, 260]
      },
      {
        "name": "loss",
        "prefix": "Pico DJ loss",
        "offsets": [625, 360]
      },
      {
        "name": "charSelect",
        "prefix": "Pico DJ to CS",
        "offsets": [625, 360]
      }
    ]
  },
  "charSelect": {
    "position": 3
  },
  "results": {
    "music": {
      "PERFECT_GOLD": "resultsPERFECT-pico",
      "PERFECT": "resultsPERFECT-pico",
      "EXCELLENT": "resultsEXCELLENT-pico",
      "GREAT": "resultsNORMAL-pico",
      "GOOD": "resultsNORMAL-pico",
      "SHIT": "resultsSHIT-pico"
    },
    "perfect": [
      {
        "renderType": "animateatlas",
        "assetPath": "shared:resultScreen/results-pico/resultsPERFECT",
        "zIndex": 500,
        "scale": 0.88,
        "offsets": [385, 82],
        "loopFrame": 91
      }
    ],
    "excellent": [
      {
        "renderType": "animateatlas",
        "assetPath": "shared:resultScreen/results-pico/resultsGREAT",
        "zIndex": 500,
        "scale": 1.25,
        "offsets": [350, 25],
        "looped": true,
        "loopFrame": 32
      }
    ],
    "great": [
      {
        "renderType": "animateatlas",
        "assetPath": "shared:resultScreen/results-pico/resultsGREAT",
        "zIndex": 500,
        "scale": 1.25,
        "offsets": [350, 25],
        "looped": true,
        "loopFrame": 32
      }
    ],
    "good": [
      {
        "renderType": "animateatlas",
        "assetPath": "shared:resultScreen/results-pico/resultsGOOD",
        "zIndex": 500,
        "scale": 1.25,
        "offsets": [350, 25],
        "loopFrame": 41
      }
    ],
    "loss": [
      {
        "renderType": "animateatlas",
        "assetPath": "shared:resultScreen/results-pico/resultsSHIT",
        "zIndex": 500,
        "offsets": [-185, -125],
        "loopFrame": 0
      }
    ]
  }
}

The available fields are:

  • version: The version number for the Playable Character data file format. Leave this at 1.0.0.
  • name: The readable name for the character, used internally.
  • ownedCharacters: The list of Characters this character owns.
    • When determining which songs to display in Freeplay, the game checks for any songs where the player character is in this list and displays those. Songs where the player character is in another array are not displayed.
  • showUnownedChars: If this value is true, then songs whose player character is not in any ownedCharacters list will be displayed for this character.
  • unlocked: Whether the character is unlocked.
    • Create a scripted class for this playable character and override isUnlocked():Bool to make this conditional. See Scripted Playable Characters
  • freeplayStyle: The ID for a Freeplay style to display.
    • You can use "bf" here to use Boyfriend's Freeplay style as a default, or create a new JSON file in the data/ui/freeplay/styles folder (copy the Pico one and edit that).
  • freeplayDJ: Data for how the character displays as the DJ in the Freeplay menu.
  • charSelect: Data for how the character displays in the Character Select menu.
  • results: Data for how the character displays in the Results screen.

Freeplay DJ data is structured like so:

  • assetPath: The folder where the Animate Atlas for this character is located, relative to the images/ folder.
    • Note that Sparrow atlases are not supported for Freeplay animations.
  • animations: A list of animation data for the character.
    • Valid animation names include intro idle idleEasterEgg confirm fistPump loss charSelect and `
  • charSelect: A structured data object containing:
    • transitionDelay: A duration (in seconds) between when the character's transition to Character Select starts and the camera starts to fade.
  • fistPump: A structured data object containing:
    • introStartFrame The frame number in the fistPump animation where the intro animation (which loops until the rank slams down) starts.
    • introEndFrame The frame number in the fistPump animation where the intro animation ends.
    • loopStartFrame The frame number in the fistPump animation where the follow-up animation starts.
    • loopEndFrame The frame number in the fistPump animation where the follow-up animation ends.
      • Use -1 to use the last frame of the specified frame label.
    • introBadStartFrame The frame number in the loss animation where the intro animation starts.
    • introBadEndFrame The frame number in the loss animation where the intro animation ends.
    • loopBadStartFrame The frame number in the loss animation where the follow-up animation starts.
    • loopBadEndFrame The frame number in the loss animation where the follow-up animation ends.

Character Select data is structured like so:

  • position: The preferred grid square for the character in the Character Select grid.
    • 0 represents the top left, 3 represents the middle left, and 8 represents the bottom right.
    • Characters are evaluated alphabetically, and if the slot is already occupied, they will be shifted over until they fit.
    • At time of writing (v0.5.1) only 9 total characters can fit in the grid.
  • gf: (NEW with v0.5.1) A structured data object containing:
    • assetPath: The folder where the Animate Atlas for this character is located, relative to the images/ folder.
      • Note that Sparrow atlases are not supported.
    • animInfoPath: A path to a Flash JSFL file describing character sliding movement.
    • visualizer: Whether the character is hooked up to display a visualizer (like Nene's ABot).
      • Check the Nene Character Select FLA to see how to implement this.

Results data is structured like so:

  • music: A structured data object containing:
    • PERFECT_GOLD: The path to a music track in the music/ folder. Played during the Perfect rank animation with all SICKs.
    • PERFECT: The path to a music track in the music/ folder. Played during the Perfect rank animation.
    • EXCELLENT: The path to a music track in the music/ folder. Played during the Excellent rank animation.
    • GREAT: The path to a music track in the music/ folder. Played during the Great rank animation.
    • GOOD: The path to a music track in the music/ folder. Played during the Good rank animation.
    • SHIT: The path to a music track in the music/ folder. Played during the Loss rank animation.
    • Make sure to include a metadata file in the folder to tell the game what BPM the song is, and how to loop it.
    • Include a variation of the track with the suffix -intro.ogg to play that track once before playing the main music track.
  • perfect: An array of animation data structures, describing the animation played when the player gets a Perfect rank.
    • Data is in the form of an array of animation objects.
    • You can use Sparrow animations or animateatlas sprites. Use renderType to tell the game which one to use.
    • Use loopFrame to tell the game which frame to start looping at, or set looped to "false" to just play the animation once.
  • excellent: Data for the animation played when the player gets a Excellent rank.
  • great: Data for the animation played when the player gets a Great rank.
  • good: Data for the animation played when the player gets a Good rank.
  • loss: Data for the animation played when the player gets a Loss rank.
  • perfectGold: (NEW with v0.5.1) Data for the animation played when the player gets a Perfect rank with all SICKs.
    • In the base game, this is just the same data as perfect, but you can make it different if you like.

Creating a Friday Night Funkin' Mod - Custom Note Styles

This chapter will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter goes over adding new Note Styles to the game, and using them in a song.

Creating a Note Style

Note Style Assets

In order to create your own note style, you will need assets for a variety of elements:

  • note: The general notes for the song. These display in the chart editor as well as in-game as the notes the player must hit.
  • holdNote: The sprites for the hold notes which require the key to be held rather than pressed.
  • noteStrumline: The notes on the strumline, which react when the player presses the keys, and provide visual feedback for when notes need to be hit.
  • noteSplash: The special effect when the player gets Sick! rating.
  • holdNoteCover: The special effect when the player is holding a hold note.
  • countdown: The assets (both the sprites and audio) for the countdown that happens before the song starts.
  • judgementShit: The assets for judgements, which display how well you hit a note.
  • comboNumber0: The assets for the combo counter, which displays when you hit a certain number of notes in a row.

These assets should be placed in your mod folder, ideally somewhere organized. They don't have to be in any specific location, just one that you can directly reference from your note style's JSON data file.

Note Style Fallback

Note styles use a recursive fallback system; essentially, you can specify just one or several assets for a note style. If a fallback note style is specified, the parent will be queried for any assets that aren't included. If the parent doesn't have an asset, ITS parent will be referenced, and so on. This allows you to easily make note styles which perform only slight variations on the style they are based on.

Note Style Data

A custom note style requires creating a new JSON file in the data/notestyles folder.

Below is the "funkin" (aka the default) note style json file assets/data/notestyles/funkin.json1

{
  "version": "1.1.0",
  "name": "Funkin'",
  "author": "PhantomArcade",
  "fallback": null,
  "assets": {
    "note": {
      "assetPath": "shared:notes",
      "scale": 1.0,
      "data": {
        "left": { "prefix": "noteLeft" },
        "down": { "prefix": "noteDown" },
        "up": { "prefix": "noteUp" },
        "right": { "prefix": "noteRight" }
      }
    },
    "noteStrumline": {
      "assetPath": "shared:noteStrumline",
      "scale": 1.55,
      "offsets": [0, 0],
      "data": {
        "leftStatic": { "prefix": "staticLeft0" },
        "leftPress": { "prefix": "pressLeft0" },
        "leftConfirm": { "prefix": "confirmLeft0" },
        "leftConfirmHold": { "prefix": "confirmLeft0" },
        "downStatic": { "prefix": "staticDown0" },
        "downPress": { "prefix": "pressDown0" },
        "downConfirm": { "prefix": "confirmDown0" },
        "downConfirmHold": { "prefix": "confirmDown0" },
        "upStatic": { "prefix": "staticUp0" },
        "upPress": { "prefix": "pressUp0" },
        "upConfirm": { "prefix": "confirmUp0" },
        "upConfirmHold": { "prefix": "confirmUp0" },
        "rightStatic": { "prefix": "staticRight0" },
        "rightPress": { "prefix": "pressRight0" },
        "rightConfirm": { "prefix": "confirmRight0" },
        "rightConfirmHold": { "prefix": "confirmRight0" }
      }
    },
    "holdNote": {
      "assetPath": "NOTE_hold_assets",
      "data": {}
    },
    "noteSplash": {
      "assetPath": "",
      "data": {
        "enabled": true
      }
    },
    "holdNoteCover": {
      "assetPath": "",
      "data": {
        "enabled": true
      }
    },
    "countdownThree": {
      "data": {
        "audioPath": "shared:gameplay/countdown/funkin/introTHREE"
      },
      "assetPath": null
    },
    "countdownTwo": {
      "data": {
        "audioPath": "shared:gameplay/countdown/funkin/introTWO"
      },
      "assetPath": "shared:ui/countdown/funkin/ready",
      "scale": 1.0,
      "isPixel": false
    },
    "countdownOne": {
      "data": {
        "audioPath": "shared:gameplay/countdown/funkin/introONE"
      },
      "assetPath": "shared:ui/countdown/funkin/set",
      "scale": 1.0,
      "isPixel": false
    },
    "countdownGo": {
      "data": {
        "audioPath": "shared:gameplay/countdown/funkin/introGO"
      },
      "assetPath": "shared:ui/countdown/funkin/go",
      "scale": 1.0,
      "isPixel": false
    },
    "judgementSick": {
      "assetPath": "default:ui/popup/funkin/sick",
      "scale": 0.65,
      "isPixel": false
    },
    "judgementGood": {
      "assetPath": "default:ui/popup/funkin/good",
      "scale": 0.65,
      "isPixel": false
    },
    "judgementBad": {
      "assetPath": "default:ui/popup/funkin/bad",
      "scale": 0.65,
      "isPixel": false
    },
    "judgementShit": {
      "assetPath": "default:ui/popup/funkin/shit",
      "scale": 0.65,
      "isPixel": false
    },
    "comboNumber0": {
      "assetPath": "default:ui/popup/funkin/num0",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber1": {
      "assetPath": "default:ui/popup/funkin/num1",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber2": {
      "assetPath": "default:ui/popup/funkin/num2",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber3": {
      "assetPath": "default:ui/popup/funkin/num3",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber4": {
      "assetPath": "default:ui/popup/funkin/num4",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber5": {
      "assetPath": "default:ui/popup/funkin/num5",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber6": {
      "assetPath": "default:ui/popup/funkin/num6",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber7": {
      "assetPath": "default:ui/popup/funkin/num7",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber8": {
      "assetPath": "default:ui/popup/funkin/num8",
      "isPixel": false,
      "scale": 0.45
    },
    "comboNumber9": {
      "assetPath": "default:ui/popup/funkin/num9",
      "isPixel": false,
      "scale": 0.45
    }
  }
}

There is quite a lot to unravel, let's break it all down.

  • version: The version number for the Note Style data file format. Leave this at 1.0.0.
  • name: The readable name for the note style, used in places like the Chart Editor.
  • author: The author of the note style, aka the artist who created the assets.
  • fallback: The note style ID to use as a fallback note style. Any assets not included for this note style will use the parent's asset instead. "funkin" is the recommended default in order to use the default base game assets for anything that is not specified.
  • assets: A list of asset data for each of the assets for the note style.

Asset data is structured like so:

  • assetPath: The main asset path to use for this asset.
  • scale: Specify the size of the asset relative to the original size. For example, 2.0 makes the sprite twice as big. Defaults to 1.0 if unspecified.
  • offsets: Some animations may need their positions to be corrected relative to the idle animation.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • isPixel: Specify whether to display this asset as being pixel (disabling texture smoothing). Optional, defaults to false.
  • data: The structure of note style asset data objects that depends on the asset you are going to edit.

Note Style Asset data is structured like so:

  • For note: List of animation data for the left, down, up and right arrows.
  • For noteStrumline: List of animation data for each direction and it's variations, such as <direction>Static, <direction>Press, <direction>Confirm, <direction>ConfirmHold replacing <direction> with it's each and every corresponding direction.
    • As you may see from the example1 that was given, animation data for the confirm and confirmHold match up, however you can make it have unique animations.
  • For holdNote: Currently has no list of animation data that can be set.
  • For noteSplash: There is currently no list of animation data available that can be set. It is likely to be added in the future.
    • enabled: Specify whether to display the asset. Optional, defaults to true.
  • For holdNoteCover: There is currently no list of animation data available that can be set. It is likely to be added in the future.
    • enabled: Specify whether to display the asset. Optional, defaults to true.

Animation data is structured like so:

  • prefix: The animation name as specified by your spritesheet.
    • For Sparrow or Packer, check inside the data file to see what each set of frames is named, and use that as the prefix, excluding the frame numbers at the end.
    • For Animate Atlases, use either the frame label or the symbol name of the animation you want to play.
  • offsets: Some animations may need their positions to be corrected relative to the idle animation.
    • Use an array of two decimal values, the first for horizontal position and the second for vertical position.
  • looped: Whether to loop this animation in-game. If false, the animation will pause when it ends, until the game commands the asset to do something else.
  • flipX: Whether to flip the sprites of this animation horizontally in-game.
  • flipY: Whether to flip the sprites of this animation vertically in-game.
  • frameRate: A frame rate value, defaulting to 24.
  • frameIndices: Optionally specify an array of frame numbers (starting at frame 0!) to use from a given prefix for this animation.
    • For example, specifying [0, 1, 2, 3] will make this animation only use the first 4 frames of the given prefix.

Using a Note Style in a Song

There are currently two ways to use your note style in a song once it's implemented.

  1. Create a new chart. Open the Chart Editor, start a chart, and then select the Notestyle box from the Metadata toolbox before charting.
  2. Edit an existing chart in your mod. Open the metadata.json file of the song you want to modify and check in playData for the noteStyle key.

Once the chart which references your note style is in your mod folder, simply start the game with your mod installed.

Creating a Friday Night Funkin' Mod - Migrating Mods to Newer Versions

Occasionally, in order to make improvements to the modding system for Friday Night Funkin', the team has to make breaking changes to features that older mods were using, causing them to have unexpected beahavior or otherwise no longer function properly. When we do this, we update the API version rule, automatically flagging any older mods and preventing them from being loaded to ensure the stability of the game.

This chapter will walk you through the process of making older mods compatible with newer versions of Friday Night Funkin'. Once you compete the steps in this guide, your mod should function as expected on newer versions.

Migrating from v0.1.0 to v0.5.0

Rewriting JSON merge files

In v0.5.0, the system for merging into JSON files was fundamentally reworked. Any mods which used this system previously need to refactor these files in order to work properly.

In your mod's _merge folder, look for any json files and rewrite their contents.

// This is the format used by older versions of the game.
{
    "merge": [
        // Set `data.difficulty` to "super_hard"
        {
	        "target": "data.difficulty",
	        "payload": "super_hard"
	    },
        // In the second element of the `data.nested.enemies` array, set `weapon` to "minigun"
	    {
	        "target": "data.nested.enemies[1].weapon",
	        "payload": "minigun"
	    }
    ]
}
// This is the format which will be used starting with v0.5.0.
[
  { "op": "replace", "path": "/playData/characters/opponent", "value": "monster" }, // Replace the value of opponent with monster.
  { "op": "add", "path": "/playData/characters/girlfriend", "value": "nene" }, // Add a new key girlfriend with the value Nene.
  { "op": "add", "path": "/playData/difficulties/1", "value": "funky" }, // Add a new value funky to the difficulty array, after easy
  { "op": "add", "path": "/playData/difficulties/-", "value": "expert" }, // Add a new value expert to the end of the difficulty array.
  { "op": "remove", "path": "/playData/garbageValue" }, // Remove the key garbageValue from the data entirely
  { "op": "test", "path": "/playData/garbageValue", "value": 37 } // Test that a given value is in the JSON. If this operation fails, the patches will be rejected.
]

More information about this new system can be found at Merging Files.

Removal of Flixel UI

Flixel UI is a library used for developing creating UI elements and managing UI events in HaxeFlixel. In the past, this was used to power the UI of the Chart Editor, but the development team regularly found the library to be frustrating to use, and eventually switched to HaxeUI for most of its user interfaces.

In Friday Night Funkin' v0.5.0, the last places that the game used this library were refactored, and the game now exclusively uses a combination of manual sprite placement and HaxeUI for its user interfaces. As a result, Flixel UI was removed as a dependency.

Any mods which utilized functions and classes provided by Flixel UI may need refactoring to compensate.

Updating the API version

Once all the migration steps above have been performed, the last step is to modify your mod's API version string. In your mod's _polymod_meta.json file, locate the "api_version" property and set it to "0.5.0".

{
    // ...

    // Change this value from "0.1.0" to "0.5.0"
    "api_version": "0.5.0",

    // ...
}

NOTE: For versions of Friday Night Funkin' between v0.3.0 and v0.4.1, the modding system always looked for "0.1.0" for the api_version and refused to load the mod in all other cases. With the v0.5.0 update, this version will now change to match the game version with every update, but only breaking changes will forcibly disable mods. Those breaking changes will be documented on this page.

Creating a Friday Night Funkin' Mod - Appending And Merging Files

There will often be times where you want to modify the contents of an existing file. This is relatively straightforward for textures, but for data files, things get more complicated. You often don't want to replace the entire file, but instead just a smaller chunk, or even a single value. Thankfully, Polymod provides a function for doing this.

Appending Files

Adding values to the end of a data file is pretty straight-forward, but it depends on the file type you want to append to.

Create a new file in the _append folder of your mod, with a path matching the file you want to append to. For example, to append to assets/data/introText.txt, you would place your file at mods/mymod/_append/data/introText.txt

Appending to TXT Files

If the file extension of the append file is .txt, the contents of the file will be simply appended to the end of the target file.

Appending to CSV/TSV Files

If the file extension of the append file is .csv or .tsv, the rows in the sheet will be added to the end of the target sheet.

Appending to XML Files

TODO: Fill this out.

Appending to JSON Files

If the file extension of the append file is .json, the value will be parsed and naively appended to the target data.

For example, given the source file data/mydata.json:

{
  "test1": [1, 2, 3],
  "test2": {
    "foo": "bar"
  },
  "test3": "baz"
}

We can provide the file mods/mymod/_append/data/mydata.json:

{
  "test4": "hello",
  "test2": {
    "fizz": "buzz"
  }
}

And Polymod will mutate it to get this result:

{
  // Unreferenced values are untouched.
  "test1": [1, 2, 3],
  // Included values are placed in directly, not merged!
  "test2": {
    "fizz": "buzz"
  },
  "test3": "baz",
  // New values are simply included.
  "test4": "hello"
}

If you want something more particular, see Merging into JSON Files for a more powerful and flexible approach.

Merging

Merging files into a data file is required for more complicated operations, such as inserting data somewhere other than the start of the document, replacing certain data with new data, or even deleting certain data from the document.

By using _merge files, rather than replacing the data files entirely, you make your mod fully compatible with both changes to the base game and to changes made by other mods. Merge files are applied in mod load order, meaning that multiple mods can make changes to the same file without any conflicts!

Merging into TXT Files

TODO

Merging into CSV/TSV Files

CSV and TSV files can be merged as well. In this case, the mod loader will look for any rows in the base file whose first cell matches the same value as those in the merge file, and replace them with the rows from the merge file.

Merging into XML Files

NOTE: The behavior of Merging XML files may change significantly in the near future.

For XML, you must create an XML document containing the desired, values, with additional information to inform Polymod about where to insert it.

Say you have a big complicated XML file at data/stuff.xml with lots of nodes:

<?xml version="1.0" encoding="utf-8" ?>
<data>
   <!--lots of complicated stuff-->
   <mode id="difficulty" values="easy"/>
   <!--even more complicated stuff-->
</data>

And you want it to say this instead:

<?xml version="1.0" encoding="utf-8" ?>
<data>
   <!--lots of complicated stuff-->
   <mode id="difficulty" values="super_hard"/>
   <!--even more complicated stuff-->
</data>

Basically we want to change this one tag from this:

<mode id="difficulty" values="easy"/>

to this:

<mode id="difficulty" values="super_hard"/>

This is the file you would put in <modroot>/<mergeFolder>/data/stuff.xml:

<?xml version="1.0" encoding="utf-8" ?>
<data>
    <mode id="difficulty" values="super_hard">
        <merge key="id" value="difficulty"/>
    </mode>
</data>

This file contains both data and merge instructions. The <merge> child tag tells the mod loader what to do, and will not be included in the final data. The actual payload is just this:

<mode id="difficulty" values="super_hard">

The <merge> tag instructs the mod loader thus:

  • Look for any tags with the same name as my parent (in this case, <mode>)
  • Look within said tags for a key attribute (in this case, one named "id")
  • Check if the key's value matches what I'm looking for (in this case, "difficulty")

As soon as it finds the first match, it stops and merges the payload with the specified tag. Any attributes will be added to the base tag (overwriting any existing attributes with the same name, which in this case changes values from "easy" to just "super_hard", which is what we want). Furthermore, if the payload has child nodes, all of its children will be merged with the target tag as well.

Merging into JSON Files

Merging into JSON files is done using a JSON Patch document. (NOTE: This significantly differs from JSON patch files created for v0.4.1 and earlier, which used a different system that honestly kinda sucked).

Say we have a JSON data file data/songs/mysong-metadata.json like below:

{
  "version": "2.1.0",
  "playData": {
    "characters": {
      "player": "bf",
      "opponent": "dad"
    },
    "difficulties": [
      "easy", "normal", "hard"
    ],
    "garbageValue": 37,
    "stage": "mainStage"
  }
}

We can modify the above data with a document mods/mymod/_merge/data/songs/mysong-metadata.json:

[
  { "op": "replace", "path": "/playData/characters/opponent", "value": "monster" }, // Replace the value of opponent with monster.
  { "op": "add", "path": "/playData/characters/girlfriend", "value": "nene" }, // Add a new key girlfriend with the value Nene.
  { "op": "add", "path": "/playData/difficulties/1", "value": "funky" }, // Add a new value funky to the difficulty array, after easy
  { "op": "add", "path": "/playData/difficulties/-", "value": "expert" }, // Add a new value expert to the end of the difficulty array.
  { "op": "remove", "path": "/playData/garbageValue" }, // Remove the key garbageValue from the data entirely
  { "op": "test", "path": "/playData/garbageValue", "value": 37 } // Test that a given value is in the JSON. If this operation fails, the patches will be rejected.
]

The operations supported are add, remove, replace, move, copy, and test. If any operation in a JSON Patch document fails, all of the modifications in that file will be reverted.

The add, replace, and test operations require a value key (which can be any JSON data, including an array or object), and the move and copy operations require a from key, which is a path value indicating where to move or copy from.

The path must be a string of either property names or array indexes (starting at 0), starting with and separated by slashes (/). For example, /playData/characters/opponent.

The path may also be a JSONPath string, which allows robustly specifying a target path with support for filtering logic. You can read more here: https://goessner.net/articles/JsonPath/

Creating a Friday Night Funkin' Mod - Using HScript

This guide will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

The previous chapters provides for plenty of functionality, and should be sufficient to make your own custom story weeks complete with custom characters, stages with static and animated props, and songs with custom chart events. However, this does not provide for advanced functionality, such as cutscenes, custom mechanics, or other custom behavior. The following chapters describe how to implement your own custom behavior, and this chapter breaks down important fundamentals on how scripting works and how to resolve common issues encountered while scripting.

What is HScript?

HScript is an interpreted scripting language used by Polymod to provide custom functionality to Friday Night Funkin'. The game detects scripts provided in .hxc files, interprets them, and executes them to perform custom functionality in-game.

Mods are not the only thing that utilizes this functionality; the base game itself uses scripts to play animations and cutscenes, manipulate stage props, and even add custom gameplay, in a manner that allows for faster iteration than would be possible without scripts.

HScript is extremely similar to Haxe, and provides access to almost all the variables and classes available in the game, with a few notes:

  • Each scripted class must have a unique name from every other scripted class. If your mod has two scripted classes with the same name, or even if two different mods have scripted classes of the same name, one copy will be completely suppressed, which can result in unexpected behavior.
  • Private variables are simply accessible, be careful what you mess with.
  • Certain classes are blacklisted, to help protect users against malicious scripts.
  • abstract enums (a Haxe language feature which provides values which act like an Enum, but are actually some other type like a String or Integer) are inaccessible. Check the code for the abstract enum you want to use and find the ACTUAL underlying value, and use that instead.
  • abstracts (a Haxe language feature which applies additional methods to an existing class) are inaccessible. You will have to find some workaround to interact with these values.

Scripted Classes

Scripted Songs

Creating a Friday Night Funkin' Mod - Scripted Modules

This guide will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter will walk you through the process of creating a scripted Module.

Creating a Friday Night Funkin' Mod - Scripted Modules

This guide will walk you through the process of creating a functioning, fully compatible Friday Night Funkin' mod, using the game's official systems for loading custom content and scripts. Once your mod is complete, you will be able to place it in the mods folder in your game install and use its content in-game without overriding the base game content and still maintain compatibility with other mods.

This chapter will walk you through the process of using a custom runtime shader to alter the appearance of a given sprite or camera using GLSL.

Writing And Contributing to This Book

This book is written in Markdown format, and uses mdbook for generation.

The source files from which this book is generated can be found on GitHub.

Things to Consider

  • How should we handle explaining directory paths? The source code has some of the assets split into libraries (preload/, songs, shared, etc.) however most people modding don't need to really mind the source code versions of asset paths.
  • Currently Chapter 10: Appending and merging Files renders as "Chapter 5"

Style Guide

Folder/Chapter Structure

SUMMARY.md is how mdbook generates the chapters, sub-chapters, of all the markdown files. In there you can see how the book is laid out in terms of it's content.

Folder structure is entirely independant from content ordering, so we can organize generally how we please as long as SUMMARY.md is pointing to the right files.

Folder names should be {chapter number}-{chapter title}, generally despite how long the chapter title may end up being From this guides source:

src/
    01-fundamentals/
    02-custom-songs-and-custom-levels/
    03-custom-characters/
    ... // and so on...

The main chapter page should be {chapter number}-00-{chapter title}.md in the chapter's folder. From this guides source:

src/
    01-fundamentals/
        01-00-fundamentals.md
        ...

    02-custom-songs-and-custom-levels/
        02-00-custom-songs-and-custom-levels.md
        ...
    ...

Then each successive sub-chapter should follow {chapter number}-{sub-chapter number}-{subchapter title}.md From this guides source:

src/
    01-fundamentals/
        01-00-fundamentals.md
        01-01-the-metadata-file.md
        01-02-loading-the-mod-in-game.md
        ...

    02-custom-songs-and-custom-levels/
        02-00-custom-songs-and-custom-levels.md
        02-01-creating-a-chart.md
        02-02-adding-the-custom-song.md
        02-03-adding-a-custom-level.md
    03-custom-characters/
        03-00-custom-characters.md
        03-01-character-assets.md
        ...
    ...