Documentación de Modding de Friday Night Funkin'
Esta es una documentación oficialmente escrita por The Funkin Crew Inc. (Documentación en inglés, pero esta documentación en español NO ES OFICIAL) para el Modding de Friday Night Funkin'.
Código fuente de la Documentación
Los archivos fuentes por los cuales este Libro fue generado se encuentran en GitHub.
Extra - Código Fuente de la Traducción
Los archivos o el Código fuente de esta traducción lo puedes encontrar también en GitHub
Creando un Mod de Friday Night Funkin' - Lo Fundamental
Ésta guía te llevará a través del proceso de la creación de un mod funcional y completamente compatible con Friday Night Funkin', usando los sistemas oficiales del juego para cargar contenido personalizado y scripts. Una vez tu mod esté completo podrás colocarlo en la carpeta mods en tu juego, instalalo y usa su contenido dentro del juego sin anular/eliminar el contenido del juego base y así mantener compatibilidad con otros mods.
Éste capítulo en particular repasa un montón de conceptos clave del modding en Funkin' que se utilizarán en otras guías, así que léelo con atención.
El archivo de metadatos
Para empezar, crea una nueva carpeta dentro de tu carpeta mods. Aquí es donde se almacenarán los recursos y los scripts de tu mod. A continuación, crea un nuevo archivo de texto y cámbiale el nombre a _polymod_meta.json. ¡Asegúrate de no haberlo llamado por error _polymod_meta.json.txt!
Dentro de este archivo, pondremos la información que el juego necesita para conocer tu mod. Te recomiendo hacerlo con un programa como Visual Studio Code, ya que te corregirá si colocas mal una coma o algo por el estilo.
{
"title": "Intro Mod",
"description": "Un mod introductorio.",
"contributors": [
{
"name": "EliteMasterEric"
}
],
"dependencies": {
"modA": "1.0.0"
},
"optionalDependencies": {
"modB": "1.3.2"
},
"api_version": "0.6.3",
"mod_version": "1.0.0",
"license": "Apache-2.0"
}
_polymod_meta.json tiene los siguientes campos:
title: Un nombre legible para el mod.description: Una descripción legible del mod.contributors: Una lista de objetos Contributor.homepage: Una URL donde los usuarios pueden obtener más información sobre tu mod.dependencies: Un mapa de ID de mods que son dependencias obligatorias, junto con sus números de versión.- Estos son los mods que también deben cargarse para que este mod se cargue.
- Si el mod no está incluido, fallará.
- La lista de mods se reordenará de manera que las dependencias se carguen primero.
optionalDependencies: Un mapa de ID de mods que son dependencias opcionales, junto con sus números de versión.- No es necesario que estos mods estén instalados para que este mod se cargue, pero sí obligarán a reordenar la lista de mods para que las dependencias se carguen antes que este mod.
api_version: Un número de versión utilizado para determinar si los mods son compatibles con tu copia de Funkin'. Cambia esto por el número de versión de Friday Night Funkin' que quieras admitir, preferiblemente la más reciente (0.6.3en el momento de escribir este artículo).mod_version: Un número de versión específico para tu mod. Elige cualquier versión o déjalo en1.0.0.license: La licencia bajo la que se distribuye tu mod. Elige una de aquí o déjala comoApache-2.0.
Un colaborador tiene los siguientes campos:
name: El nombre del colaborador.role: (opcional) El papel que desempeñó el colaborador, por ejemplo, «Artista» o «Programador»email: (opcional) Un correo electrónico de contactourl: (opcional) La URL de una página web
Muchos de estos campos están pensados para ser utilizados en el futuro por una próximo menú de selección de mods, que permitirá a los usuarios organizar sus modsr
Carga del mod en el juego
¡Ahora que ya tienes un archivo de metadatos, puedes iniciar el juego! Un consejo de experto: si ejecutas el juego desde la línea de comandos, podrás ver muchos mensajes de depuración útiles, como estos que indican que tu mod se ha cargado.
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.
¡Genial! Pero, por ahora, tu mod no hace nada.
Sustitución y adición de recursos
Sustitución de recursos
La función principal que te permite Polymod es sustituir recursos. Para ello, basta con añadir esos archivos a la carpeta de mods, en la misma ubicación en la que irían.
Por ejemplo, puedes sustituir los sprites de Girlfriend colocando tus nuevos sprites en la misma ubicación que tienen en la carpeta assets, que sería shared/images/characters/GF_assets.png.
En otras palabras, estructura tu mod de la siguiente manera:
-assets
-manifest
-plugins
-mods
|-myMod
|-shared
|-images
|-characters
|-GF_assets.png
|-GF_assets.xml
|-_polymod_meta.json
-Funkin.exe
Cuando el juego vaya a cargar los sprites de un personaje, realizará una solicitud interna para recuperar el archivo assets/shared/images/characters/GF_assets.png que se utilizará para la textura (y el XML correspondiente para dividir la imagen en fotogramas individuales). Cuando lo hace, Polymod intercepta esa solicitud y comprueba si hay un archivo con ese nombre entre los mods cargados y, si es así, utilizará ese en su lugar.
Adición de recursos
Polymod también te permite añadir nuevos archivos al juego. Esto es importante, ya que intentar colocar nuevos archivos en el directorio assets no funciona, ya que el juego no los reconocerá.
Aún así, hay que indicarle al juego que cargue esos recursos para que se utilicen, pero hay muchas funciones que cargan todos los archivos de una carpeta determinada (como el Registro de canciones, el Registro de personajes, el Registro de escenarios, etc.). Las veremos con más detalle más adelante.
Orden de carga de los mods
Quizás te preguntes qué ocurre cuando varios mods proporcionan un mismo archivo.
La respuesta es sencilla: el orden de los mods es importante. Si tienes instalados dos mods que sustituyen un recurso concreto, el mod que se cargue en último lugar tendrá prioridad sobre los mods que se hayan cargado antes, de forma similar al sistema de paquetes de recursos de Minecraft. Esto se evalúa archivo por archivo, por lo que si el mod A sustituye a Pico y a GF y el mod B sustituye solo a GF, y el mod B se carga después del mod A, verás al Pico del mod A y a la Girlfriend del mod B.
En la versión actual de Friday Night Funkin', no hay ninguna forma accesible de modificar el orden de carga de los mods. Los mods se cargarán por defecto en orden alfabético, cargándose primero las dependencias.
Recarga en caliente
Mientras desarrollas tu mod, puede resultarte incómodo realizar un cambio, abrir el juego, encontrarte con un error o un problema visual, tener que cerrar el juego, hacer otro cambio, volver a abrir el juego y repetir el proceso una y otra vez para conseguir los resultados deseados en tu contenido personalizado.
¡Por suerte, hay una forma mejor! Pulsa F5 para forzar al juego a vaciar su caché y recargar todos los datos del juego desde el disco, y luego reinicia el estado actual con los cambios correspondientes aplicados. Esto te permite, por ejemplo:
- Ajustar las posiciones de los elementos del escenario, o añadir otros nuevos, y luego recargar inmediatamente para verlos en el juego.
- Modificar los desplazamientos de animación de un personaje y asegurarte rápidamente de que funcionan.
- Modificar un script para resolver una excepción y volver a cargar para continuar con las pruebas sin cerrar el juego.
¡Para jugadores de FNF' Mobile existen mods que habilitan esto!
Haz una búsqueda rápida en Google como "Mobile Hot Reload Button FNF Mobile" (no se adjuntan enlaces por lo muy temporal que suele ser el Internet, y las probabilidades de que un mod quede obsoleto)
Conclusión del capítulo
En este capítulo se han tratado los aspectos básicos de la creación de mods para Friday Night Funkin'. En otras secciones, veremos cómo añadir diferentes tipos de contenido personalizado.
Creación de un mod para Friday Night Funkin': canciones y niveles (semanas/weeks) personalizados
En este capítulo se explica paso a paso el proceso para crear un mod para Friday Night Funkin' que funcione correctamente y sea totalmente compatible, utilizando los sistemas oficiales del juego para cargar contenido y scripts personalizados. Una vez que hayas terminado tu mod, podrás colocarlo en la carpeta mods de la instalación del juego y utilizar su contenido sin sobrescribir el contenido del juego base, manteniendo así la compatibilidad con otros mods.
En este capítulo se explica cómo añadir nuevas canciones jugables y niveles del modo Historia (Story Mode) al juego.
Creando un Chart
Para crear un chart, accede a la herramienta «Chart Editor». En el juego, puedes encontrarla en el menú «Debug» del menú principal (asignada a ~ por defecto). También puedes acceder a ella añadiendo una tecla de acceso rápido para «Debug Chart» en el menú de opciones (no asignada por defecto) y, a continuación, pulsando la tecla asignada mientras reproduces una canción.
Desde aquí, puedes crear un nuevo chart partir de archivos de audio, importar una de una versión anterior del juego o crear un chart a partir de datos de partituras existentes en el juego.
El uso detallado del Chart Editor merece su propia guía, pero lo básico debería ser bastante intuitivo. Por ahora, supongamos que has creado un chart de tu canción favorita y quieres convertirla en un mod que la gente pueda jugar.
Desmontando tu archivo FNFC
Cuando guardas tu chart, el juego la empaqueta en un archivo .fnfc, lo que facilita compartirla con otros creadores de charts (charters) y colaborar. Incluye el audio de la canción, junto con los datos de las notas y algunos archivos de metadatos que lo acompañan.
Para añadir la canción a nuestro mod, necesitamos extraer esa información. Esto es bastante fácil, ¡porque un archivo FNFC es en realidad, en secreto, un archivo ZIP! Cambia el nombre de tu mychart.fnfc a mychart.zip, sustituyendo la extensión del archivo para que tu sistema operativo lo reconozca como un ZIP, y extráelo para revelar su contenido, que será algo así:
-manifest.json
-mychart-metadata.json
-mychart-chart.json
-Inst.ogg
-Voices-bf.ogg
-Voices-pico.ogg
Añadiendo una canción personalizada
Al final de Creando un Chart aprendimos que los archivos .fnfc no son más que archivos .zip, por lo que basta con cambiarles el nombre y descomprimirlos. Una vez que tengamos estos archivos, solo tenemos que colocarlos en las ubicaciones correctas dentro de nuestra carpeta del mod.
- El archivo
manifest.jsonse puede descartar, nuestro mod no lo necesitará. - Los archivos
metadata.jsonychart.jsondeben ir a la carpetadata/songs/<songid>, sustituyendo<songid>por el nombre interno de nuestra canción. - Los archivos OGG deben ir en
songs/<songid>, sustituyendo de nuevo<songid>por el nombre interno de nuestra canción.
El resultado final será algo así.
-mods
|-myMod
|-data
|-songs
|-mychart
|-mychart-metadata.json
|-mychart-chart.json
|-songs
|-mychart
|-Inst.ogg
|-Voices-bf.ogg
|-Voices-pico.ogg
|-_polymod_meta.json
Cuando se inicia el juego, consulta la lista de canciones disponibles buscando en la carpeta data/songs los archivos <songid>/<songid>-metadata.json, que luego utiliza para encontrar el archivo de la lista y los archivos de canciones necesarios. ¡Genial! Pero ahora mismo, si inicias el juego, esto no hace nada. Verás que aparece mencionado en los registros sin errores, pero no se puede reproducir en el Modo Historia ni en el Modo Libre, ¿qué pasa?
source/funkin/play/song/Song.hx:579: Obteniendo metadatos de la canción para mychart
source/funkin/data/song/SongRegistry.hx:103: Datos de entrada cargados: Song(mychart)
La solución es sencilla: todas las canciones deben formar parte de un nivel del Modo Historia para aparecer en el Modo Libre.
Añadir un nivel personalizado
Añade un nuevo archivo, con el nombre interno que prefieras, a tu carpeta de mods, dentro del directorio data/levels/, con la extensión .json.
NOTA: Ten en cuenta que si tu nombre interno coincide con el de una semana del juego base o con el de otro mod, se solaparán entre sí y podrías obtener resultados inesperados.
El resultado final será algo así:
-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_meta.json
El archivo JSON de tu semana personalizada tendrá un aspecto similar al siguiente:
{
"version": "1.0.0",
"name": "MI SEMANA PERSONALIZADA",
"titleAsset": "storymenu/titles/myweek",
"background": "#F9CF51",
"songs": ["michart"],
"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
}
]
}
]
}
¡Hay mucha información aquí! Analicémosla:
version: El número de versión del formato del archivo de datos del nivel. Déjalo en1.0.0.name: El nombre legible del nivel, tal y como se muestra en la parte superior derecha del menú de la historia.titleAsset: El recurso que se utilizará para el nombre del nivel en la lista de niveles, relativo a la carpetaimagesde tu carpeta de mods.background: El fondo que se utilizará para el nivel.#F9CF51es el amarillo clásico, pero este campo admite tanto un código de color como la ruta de un archivo de imagen (relativa a la carpetaimagesdentro de tu carpeta de mods).songs: Una lista de ID de canciones que se incluirán en la semana.visible: Indica si este nivel de la historia es visible en el menú de la historia.props: Datos de los elementos que se mostrarán en el menú de la historia cuando se seleccione el nivel. Por ejemplo, la Semana 1 (Week 1) mostrará «Daddy Dearest», «Boyfriend» y «Girlfriend».
Cuando el juego se inicia, consulta la lista de niveles disponibles buscando archivos JSON en la carpeta data/levels, que luego utiliza para rellenar el menú de la historia y, a continuación, el menú de juego libre (en las vistas no alfabéticas, las canciones del juego libre aparecen ordenadas según el nivel en el que están incluidas).
Si quieres que tu canción personalizada solo aparezca en Juego libre, solo tienes que crear una semana personalizada y establecer la propiedad «visible» en false, ¡y las canciones aparecerán en 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_meta.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 Sheetoption, 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 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 theimagesdirectory in your mod folder.- For the
sparrowasset type, this must point to the path where thexmlandpngare located, without the file extension. - For the
packerasset type, this must point to the path where thetxtandpngare located, without the file extension. - For the
animateatlasasset type, this must point to the folder where theAnimation.jsonand any spritemaps are located. - For the
multisparrowasset 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 theassetPathkey to that specific animation.
- For the
scale(currently buggy): Specify the size of the character relative to the original size. For example,2.0makes 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 reaches0health. 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.0makes 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 reaches0health 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 themultisparrowasset 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.danceLeftanddanceRight: 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
-holdat 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
singLEFTanimation to make asingLEFT-holdanimation, which hasloopedas true andframeIndicesas 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
-endat the end will play an animation before returning to idle.- For example, you can define a new
singLEFT-endanimation 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 Animationsong 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
Metadatatoolbox before charting. - Edit an existing chart in your mod. Open the
metadata.jsonfile and check inplayData.charactersfor theplayer,girlfriend, andopponentkeys.
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 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
imagesfolder 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
scaleproperty.
- 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.0makes the sprite twice as big. Defaults to1.0if unspecified.alpha: Specify the opacity of the prop, with1.0being fully opaque and0.0being 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, specifysparroworpackerfor 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 (ordanceEveryis 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
idleanimation. - If
danceLeftanddanceRightanimations are specified, the game will alternate between these instead. - This value supports precision up to
0.25, where0.25plays 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 themultisparrowasset 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.5makes 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.5makes 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.5makes 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 themultisparrowasset 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.danceLeftanddanceRight: 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
-holdat 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
singLEFTanimation to make asingLEFT-holdanimation, which hasloopedas true andframeIndicesas 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
-endat the end will play an animation before returning to idle.- For example, you can define a new
singLEFT-endanimation 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 Animationsong 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
Metadatatoolbox before charting. - Edit an existing chart in your mod. Open the
metadata.jsonfile and check inplayDatafor thestagekey, 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.
- 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.
- NOTE: This is currently hardcoded to BF or Pico.
- 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,
"gf": {
"assetPath": "charSelect/neneChill",
"animInfoPath": "charSelect/neneAnimInfo",
"visualizer": true
}
},
"results": {
"music": {
"PERFECT_GOLD": "resultsPERFECT-pico",
"PERFECT": "resultsPERFECT-pico",
"EXCELLENT": "resultsEXCELLENT-pico",
"GREAT": "resultsNORMAL-pico",
"GOOD": "resultsNORMAL-pico",
"SHIT": "resultsSHIT-pico"
},
"perfectGold": [
{
"renderType": "animateatlas",
"assetPath": "shared:resultScreen/results-pico/resultsPERFECT",
"zIndex": 500,
"scale": 0.88,
"offsets": [385, 82],
"loopFrame": 91
}
],
"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 anyownedCharacterslist will be displayed for this character.unlocked: Whether the character is unlocked.- Create a scripted class for this playable character and override
isUnlocked():Boolto 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/stylesfolder (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
introidleidleEasterEggconfirmfistPumplosscharSelectand `
- 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:introStartFrameThe frame number in thefistPumpanimation where the intro animation (which loops until the rank slams down) starts.introEndFrameThe frame number in thefistPumpanimation where the intro animation ends.loopStartFrameThe frame number in thefistPumpanimation where the follow-up animation starts.loopEndFrameThe frame number in thefistPumpanimation where the follow-up animation ends.- Use
-1to use the last frame of the specified frame label.
- Use
introBadStartFrameThe frame number in thelossanimation where the intro animation starts.introBadEndFrameThe frame number in thelossanimation where the intro animation ends.loopBadStartFrameThe frame number in thelossanimation where the follow-up animation starts.loopBadEndFrameThe frame number in thelossanimation 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.0represents the top left,3represents the middle left, and8represents 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.oggto 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
animateatlassprites. UserenderTypeto tell the game which one to use. - Use
loopFrameto tell the game which frame to start looping at, or setloopedto"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.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 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.0makes the sprite twice as big. Defaults to1.0if 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,upandrightarrows. - For
noteStrumline: List of animation data for each direction and it's variations, such as<direction>Static,<direction>Press,<direction>Confirm,<direction>ConfirmHoldreplacing<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
Notestylebox from theMetadatatoolbox before charting. - Edit an existing chart in your mod. Open the
metadata.jsonfile of the song you want to modify and check inplayDatafor thenoteStylekey.
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 - Custom Sticker Packs
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 Sticker Packs to the game, and telling the game when to use them for songs.
Creating a new Sticker Pack
Sticker Pack Assets
In order to create your own sticker pack, you will need graphics for the stickers you want to use. The ones included in the base game are generally around 300x300 in size, with some variation.
Sticker Pack Data
A custom sticker pack requires creating a new JSON file in the data/stickerpacks folder.
Below is the "default" sticker pack JSON file assets/data/stickerpacks/default.json1
{
"version": "1.0.0",
"name": "Default",
"artist": "PhantomArcade3K",
"stickers": [
"transitionSwag/stickers-set-1/bfSticker1",
"transitionSwag/stickers-set-1/bfSticker2",
"transitionSwag/stickers-set-1/bfSticker3"
]
}
Let's break it all down.
version: The version number for the Sticker Pack data file format. Leave this at1.0.0.- This will increase if the data file format changes, and tell the game whether additional processing needs to be done for backwards compatibility.
name: The readable name for the sticker pack, used in places like the Chart Editor.author: The author of the sticker pack, aka the artist who created the assets.stickers: A list of all the paths for all the stickers to use, as strings.- You cannot currently specify any additional arguments, such as scale, offsets, texture smoothing, or animations.
Modifying an Existing Sticker Pack
You can use the JSONPatch feature to add or remove stickers from an existing sticker pack.
For example, to add to Boyfriend's standard sticker pack, you can use the following JSON Patch file (placed in mods/mymod/_merge/data/stickerpacks/standard-bf.json, use a different file path to patch a different sticker pack):
[
// Add an asset path to the end of the sticker list.
{ "op": "add", "path": "/stickers/-", "value": "path/to/custom/sticker" }
]
Using a Custom Sticker Pack
There are two ways the game defines a given sticker pack to be used:
- Each Playable Character defines the
stickerPackvariable, which specifies the sticker pack to be used by songs containing that character. For example,bfuses thestandard-bfsticker pack. You can define a sticker pack to be used for your custom playable character by setting thestickerPackvalue, or modify which sticker pack is used by other playable characters by using JSONPatch to modify thestickerPackvalue of that character. - Each song has a value in its metadata to define which sticker pack is used. Set the
playData.stickerPackon a song (or use JSONPatch to modify metadata of an existing song) to override which sticker pack it uses.
The game checks and uses sticker packs in this order:
- The sticker pack chosen by the song.
- The sticker pack chosen by the playable character.
- The
defaultsticker pack (which displays only Boyfriend). If you see only Boyfriend during a sticker transition, then you know neither the song or the playable character defines a sticker pack, and you should fix the issue.
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.
Migrating from v0.5.0 to v0.6.3
Migration from v0.5.0 to v0.6.3 requires some changes to mods.
Update the API Version
Eric accidentally forgot to increment the API version for the game when updating to v0.6.0, and this got fixed with v0.6.3.
This means that mods won't load unless the api_version value in the _polymod_meta.json file is at least v0.6.3. This allows mod creators to perform testing and ensure their mods are compatible before releasing an update on their distribution platforms of choice. Most mods (especially ones with minimal scripting) will need no changes to work as expected, but developers should probably do some playtesting to be sure.
Migrate from hxCodec to hxvlc
The library used for video playback has changed from hxCodec to hxvlc. This has resulted in improved performance and various bug fixes overall, but breaks mods which interact directly with the video library. This should not break any mods which use the built-in system for cutscenes in Funkin'.
Make the following changes:
// BEFORE: Imports from the package `hxcodec`
import hxcodec.flixel.FlxVideoSprite;
// AFTER: Imports from the package `hxvlc`
import hxvlc.flixel.FlxVideoSprite;
// BEFORE: Callback to onTextureSetup
video.bitmap.onTextureSetup.add(() -> { ... });
// AFTER: Callback to onFormatSetup
video.bitmap.onFormatSetup.add(() -> { ... });
// BEFORE: Play a video by path.
video.play(videoPath);
// AFTER: Load a video by path, then play if load was successful.
// You can also run video.load() in advance before playing the video.
if (video.load(videoPath)) video.play();
Options Menu Changes
The options menu received a minor refactor internally. The pages list was moved to its own class, which changes the code needed to access the "Preferences" menu (mainly done to add custom preferences).
We would like to standardize the process of adding custom user preferences to mods in the future eventually, but in the meantime you can make the necessary tweaks:
// BEFORE: Retrieve the value from the page Map.
if (Std.isOfType(currentState, OptionsState)) {
var preferencesPage = currentState.pages.get("preferences");
// Create a new option.
prefs.createPrefItemCheckbox(...);
}
// AFTER: Retrieve the value from the page Map, which is now inside a Codex.
if (Std.isOfType(currentState, OptionsState)) {
var preferencesPage = currentState.optionsCodex.pages.get("preferences");
// Create a new option.
prefs.createPrefItemCheckbox(...);
}
Sticker Changes
v0.6.0 rewrote how stickers get used by the game (and v0.6.3 rewrote it again but better this time). Any existing mods that provided stickers will probably break.
New or updating mods looking to add, remove, or replace stickers should consult the custom Sticker Packs documentation
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
keyattribute (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 theabstract enumyou 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
Funkin's implementation of HScript uses a system of scripted classes. To create a scripted class, create an .hxc file in your mods/mymod/scripts/ folder, and, using Haxe syntax, create a new class which extends the base class for the type of object you are scripting, like so:
// Remember to import each class you want to reference in your script!
import funkin.play.song.Song;
// Choose a name for your scripted class that will be unique
// Also specify the class you are extending, we choose Song here.
// This script's behaviors will extend the default behavior of the song.
class BallisticSong extends Song {
public function new() {
// You have to call the super constructor for the class you are extending, which may have different parameters.
// Check the specific documentation for more info.
super('ballistic');
}
}
List of Scriptable Classes
There is a predefined list of classes which the game has set up to be scriptable, and will automatically load and execute when relevant. More of these will be added in the future.
funkin.play.song.Songfor providing unique behavior to custom songs, including playing cutscenes and other stuff. See Scripted Songs.- See also Video Cutscenes, Ingame Cutscenes, and Dialogue Cutscenes
funkin.play.character.BaseCharacterfor providing unique behavior to custom characters (such as playing custom animations in certain circumstances). See Scripted Characters.- Note that you need to choose the correct subclass of this class for the animation type of your character!
funkin.play.character.SparrowCharacteris used for characters that have Sparrow spritesheet animations.funkin.play.character.MultiSparrowCharacteris used for characters that have several Sparrow spritesheet animations to combine into one character.funkin.play.character.PackerCharacteris used for characters that have Packer spritesheet animations.funkin.play.character.AnimateAtlasCharacteris used for characters that have Adobe Animate texture atlases.funkin.play.character.BaseCharacterhas empty stubs for all the rendering and animation handlers, and is only useful for people who want to reimplement their character's animation system by hand.
funkin.play.stage.Stagefor providing unique behavior to custom stages, such as creating custom moving props and defining when props animate or when sound effects play in sync with the stage. See Scripted Stages.funkin.ui.story.Levelfor providing unique behavior to levels in Story Mode. See Scripted Story Levels.funkin.play.notes.notekind.NoteKindfor providing unique visuals and behavior to certain kinds of notes, which can then be placed in the Chart Editor. See Custom Note Kinds.funkin.play.event.SongEventfor creating custom Song Events, which you can place in the Chart Editor and which perform game actions when they are reached. See Custom Song Eventsfunkin.ui.freeplay.charselect.PlayableCharacterfor providing unique behavior to custom playable characters. See Scripted Playable Charactersfunkin.ui.freeplay.FreeplayStylefor defining the sprites and colors used by the Freeplay menu when a given character is selected. See [WIP]funkin.play.notes.notestyle.NoteStylefor modifying the behavior of custom note styles. See [WIP]funkin.play.cutscene.dialogue.Conversationfor providing unique behavior to custom dialogue conversations. See Dialogue Cutscenesfunkin.play.cutscene.dialogue.DialogueBoxfor providing unique behavior to custom dialogue boxes used in conversations. See Dialogue Cutscenesfunkin.play.cutscene.dialogue.Speakerfor providing unique behavior to custom speakers used in conversations. See Dialogue Cutscenesfunkin.ui.freeplay.Albumfor defining custom behavior for Freeplay Albums. See [WIP]
There is also funkin.modding.module.Module for custom scripted Modules, which are scripts which receive events everywhere, rather than only in a specific context. See Scripted Modules for more information on how these work.
There are also scripted classes are also set up to be scriptable, but will only be useful if they're accessed from another script. Expect more of these to be added in the future.
funkin.graphics.FunkinSpritefor basic static or animated sprites.funkin.graphics.adobeanimate.FlxAtlasSpritefor generic sprites which use Adobe Animate texture atlasesflixel.group.FlxSpriteGroupfor groups of sprites which are included inside a state and manipulated togetherfunkin.ui.MusicBeatStatefor groups of sprites which represents a given game state. Includes additional utilities for handling script events.funkin.ui.MusicBeatSubStatefor groups of sprites representing a substate of an existing stateflixel.addons.display.FlxRuntimeShaderfor custom GLSL shadersfunkin.play.stage.Bopperfor sprites which will play an idle animation to the beat of the music when they are part of a Stage.flixel.FlxSpritefor basic static or animated sprites. Use this only if you can't use FunkinSprite.flixel.FlxStatefor basic groups of sprites which represent a given game state. Use this only if you can't use MusicBeatState.flixel.FlxSubStatefor groups of sprites representing a substate of an existing state
Creating a Friday Night Funkin' Mod - Scripted Songs
This chapter will walk you through the process of adding a script to a Song, and giving examples of the kind of custom behavior which can be implemented with this functionality.
Start by creating a scripted class file with the .hxc extension (in the mods/mymod/scripts/songs if you want to keep things organized).
// Remember to import each class you want to reference in your script!
import funkin.play.song.Song;
// Choose a name for your scripted class that will be unique, and make sure to specifically extend the Song class.
// This class's functions will override the default behavior for the song.
class BallisticSong extends Song {
public function new() {
// The constructor gets called once, when the game loads.
// The constructor takes one parameter, which is the song ID for the song you are applying the script to.
super('ballistic');
}
// Add override functions here!
}
You can then add override functions to perform custom behavior.
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.
Escribiendo y contribuyendo a este libro [TRADUCCIÓN EN PROGRESO]
Éste libro está escrito en formato Markdown, y usa mdbook para su generación.
La fuente de los archivos con los cuáles el libro original fue creado puedes encontrarlo en GitHub.
Y la fuente de los archivos por los cuales están traducción u fue creada puedes encontrarla también en 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
...
...