Friday Night Funkin' Cookbook
Friday Night Funkin' CookbookAdvancedScripted Characters

Scripted Characters

Reading time: 3 minutes

This chapter will walk you through the process of adding a script to a Character, 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/characters if you want to keep things organized).

// Remember to import each class you want to reference in your script!
import funkin.play.character.SparrowCharacter;

// Choose a name for your scripted class that will be unique, and make sure to specifically extend the correct character class.
// SparrowCharacter is the one to use for XML-based characters.
// This class's functions will override the default behavior for the character.
class WhittyCharacter extends SparrowCharacter {
    public function new() {
        // The constructor gets called whenever the character is spawned.
        // The constructor takes one parameter, which is the song ID for the song you are applying the script to.
        super('whitty');
    }

    // Add override functions here!
}

You can then add override functions to perform custom behavior.

Character Classes

If you use a different animation type for a character, you need to specify a different base script class, otherwise the character will fail to load.

  • funkin.play.character.SparrowCharacter is used for characters that have Sparrow spritesheet animations.
  • funkin.play.character.MultiSparrowCharacter is used for characters that have several Sparrow spritesheet animations to combine into one character.
  • funkin.play.character.PackerCharacter is used for characters that have Packer spritesheet animations.
  • funkin.play.character.AnimateAtlasCharacter is used for characters that have Adobe Animate texture atlases.
  • funkin.play.character.BaseCharacter has 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.

Custom Behavior when Playing Animations

If you want something to happen while a character is playing a specific animation or just not want to play an animation at a specific moment even when called to, you should override the playAnimation function with your own behavior. An example of what you can do with this is found in the character script file for Boyfriend1

// ...

// Line 17
override function playAnimation(name:String, restart:Bool, ignoreOther:Bool)
{
    if (name == "fakeoutDeath" && !this.debug)
    {
        doFakeoutDeath();
    }
    else
    {
        super.playAnimation(name, restart, ignoreOther);
    }
}

// ...

Custom Behavior when Finishing Animations

To trigger custom behavior when finishing animations, it is important to override the function onAnimationFinished to include your behavior. For example, the script file for Nene2 switches her knife state after a specific animation is finished:

// ...

// Line 384
function onAnimationFinished(name:String)
{
    super.onAnimationFinished(name);

    switch (currentState)
    {
        case STATE_RAISE:
            if (name == "raiseKnife")
            {
                animationFinished = true;
                transitionState();
            }

        // ...
    }
}

// ...

Custom Behavior on an Animation Frame

Several custom behaviors may require specific timings when playing certain animations, such as having haptic feedback for some of the animations. For this you would need to override the function onAnimationFrame. The script for Pico (Playable)3 uses this to create a casing mid-way throughout his gun cocking.

// ...

// Line 308
function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int)
{
    super.onAnimationFrame(name, frameNumber, frameIndex);

    // ...

    if (name == "cock" && frameNumber == 3)
    {
        createCasing();
    }
}

// ...

Custom Death Quotes

When you die in a week 7 song, the game plays one of Tankman's insults before fading in the game over music. This is done by overriding the function getDeathQuote in the character script. An example for how this is done is found in the script file for Boyfriend holding Girlfriend4

// ...

// Line 16
override function getDeathQuote():Null<String>
{
    var randomCensor:Array<Int> = [];

    if (!Preferences.naughtyness)
    {
        // 1 - cock
        // 3 - shitty
        // 8 - shit
        // 13 - fuck
        // 17 - shit, asshole
        // 21 - fucking
        randomCensor = [1, 3, 8, 13, 17, 21];
    }
    else if (Constants.CENSOR_EXPLETIVES)
    {
        randomCensor = [13, 21];
    }

    return Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor));
}

// ...

  1. https://github.com/FunkinCrew/funkin.assets/blob/main/preload/scripts/characters/bf.hxc

  2. https://github.com/FunkinCrew/funkin.assets/blob/main/preload/scripts/characters/nene.hxc

  3. https://github.com/FunkinCrew/funkin.assets/blob/main/preload/scripts/characters/pico-playable.hxc

  4. https://github.com/FunkinCrew/funkin.assets/blob/main/preload/scripts/characters/bf-holding-gf.hxc


Contributors:
Cameron Taylor
kade-github
Kolo
Last modified:
Created:
Category:  Advanced