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 at1.0.0
.license
: The license your mod is distributed under. Pick one from here or just leave it asApache-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 emailurl
: (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
andchart.json
files need to go into thedata/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 at1.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 theimages
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 theimages
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 theGenerate 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.json
1
{
"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 at1.0.0
.name
: The readable name for the character, used in places like the Chart Editor.renderType
: The render type. One ofsparrow
,packer
,animateatlas
,multisparrow
.assetPath
: The main asset path to use for this character, relative to theimages
directory in your mod folder.- For the
sparrow
asset type, this must point to the path where thexml
andpng
are located, without the file extension. - For the
packer
asset type, this must point to the path where thetxt
andpng
are located, without the file extension. - For the
animateatlas
asset type, this must point to the folder where theAnimation.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 theassetPath
key to that specific animation.
- For the
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 to1.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 reaches0
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 tofalse
.danceEvery
: The frequency at which the character will play its idle animation, in beats. Optional, defaults to1
.- 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 tofalse
.startingAnimation
: The animation for the character to play when they are first loaded in. Optional, defaults toidle
.singTime
: The amount of time, in steps, for a character to keep singing after they let go of a note. Optional, defaults to8
.- 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 to1.0
.flipX
: Whether to flip the whole sprite horizontally in-game. Optional, defaults tofalse
.isPixel
: Specify whether to disable texture smoothing for this characters health icon. Optional, defaults tofalse
.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 to1
.preTransitionDelay
: The delay between when the character reaches0
health and when the death animation plays. Optional, defaults to0
.
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 to24
.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.
- For example, specifying
assetPath
: For themultisparrow
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
anddanceRight
: 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 asingLEFT-hold
animation, which haslooped
as true andframeIndices
as the last few frames of the singing animation.
- As a good example, you can copy the
- 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.
- For example, you can define a new
- 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.
- Create a new chart. Open the Chart Editor, start a chart, and select the character from the
Metadata
toolbox before charting. - Edit an existing chart in your mod. Open the
metadata.json
file and check inplayData.characters
for theplayer
,girlfriend
, andopponent
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.json
1
{
"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 at1.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 to1.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)
.
- You can access a stage prop in a script using
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.
- To use an image, specify the path relative to the
zIndex
: A value describing the relative position of the prop. Optional, defaults to0
.- 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 tofalse
.scale
: Specify the size of the prop relative to the original size. For example,2.0
makes the sprite twice as big. Defaults to1.0
if unspecified.alpha
: Specify the opacity of the prop, with1.0
being fully opaque and0.0
being completely transparent. Optional, defaults to1.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.
- A value of
animType
: If the prop you choose is animated, specifysparrow
orpacker
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 (ordanceEvery
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 to0.0
, or no bopping.- This tries to play the
idle
animation. - If
danceLeft
anddanceRight
animations are specified, the game will alternate between these instead. - This value supports precision up to
0.25
, where0.25
plays the animation four times per beat.
- This tries to play the
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 to24
.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.
- For example, specifying
assetPath
: For themultisparrow
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 to24
.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.
- For example, specifying
assetPath
: For themultisparrow
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
anddanceRight
: 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 asingLEFT-hold
animation, which haslooped
as true andframeIndices
as the last few frames of the singing animation.
- As a good example, you can copy the
- 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.
- For example, you can define a new
- 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.
- Create a new chart. Open the Chart Editor, start a chart, and select the stage from the
Metadata
toolbox before charting. - Edit an existing chart in your mod. Open the
metadata.json
file and check inplayData
for thestage
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.json
1
{
"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 at1.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 istrue
, then songs whose player character is not in anyownedCharacters
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
- Create a scripted class for this playable character and override
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 thedata/ui/freeplay/styles
folder (copy the Pico one and edit that).
- You can use
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 theimages/
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 `
- Valid animation names include
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 thefistPump
animation where the intro animation (which loops until the rank slams down) starts.introEndFrame
The frame number in thefistPump
animation where the intro animation ends.loopStartFrame
The frame number in thefistPump
animation where the follow-up animation starts.loopEndFrame
The frame number in thefistPump
animation where the follow-up animation ends.- Use
-1
to use the last frame of the specified frame label.
- Use
introBadStartFrame
The frame number in theloss
animation where the intro animation starts.introBadEndFrame
The frame number in theloss
animation where the intro animation ends.loopBadStartFrame
The frame number in theloss
animation where the follow-up animation starts.loopBadEndFrame
The frame number in theloss
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, and8
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 theimages/
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 themusic/
folder. Played during the Perfect rank animation with all SICKs.PERFECT
: The path to a music track in themusic/
folder. Played during the Perfect rank animation.EXCELLENT
: The path to a music track in themusic/
folder. Played during the Excellent rank animation.GREAT
: The path to a music track in themusic/
folder. Played during the Great rank animation.GOOD
: The path to a music track in themusic/
folder. Played during the Good rank animation.SHIT
: The path to a music track in themusic/
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. UserenderType
to tell the game which one to use. - Use
loopFrame
to tell the game which frame to start looping at, or setlooped
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.
- In the base game, this is just the same data as
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 getsSick!
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.json
1
{
"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 at1.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.- See list of assets that you can provide the data for.
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 to1.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 tofalse
.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 theleft
,down
,up
andright
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. - 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 totrue
.
- 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 totrue
.
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 to24
.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.
- For example, specifying
Using a Note Style in a Song
There are currently two ways to use your note style in a song once it's implemented.
- Create a new chart. Open the Chart Editor, start a chart, and then select the
Notestyle
box from theMetadata
toolbox before charting. - Edit an existing chart in your mod. Open the
metadata.json
file of the song you want to modify and check inplayData
for thenoteStyle
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 op
erations 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 enum
s (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 theabstract enum
you want to use and find the ACTUAL underlying value, and use that instead.abstract
s (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
...
...