Skip to content
this-is-bennyk edited this page Jul 16, 2022 · 13 revisions

All levels in a basic mod in the Benjine inherit from Level.gd, similar to FNF's PlayState.hx.

Basic Levels

To create a basic level, it is recommended to duplicate Level2D from the templates folder. This gives you BF, GF, and a HUD that are already set up for you.
You can then customize the following export variables of the Level:

  • Performers: Performers are characters that respond to notes being hit/missed in some shape or form. They are defined by a performer name and a character name. All performers are optional; leave the character name blank if you don't need to use it.
    • Player: The character that responds to the player's actions and step zone. There can only be one player (currently).
    • AI (typically Opponents): A character / characters that respond to their corresponding step zone. There are typically 0-1 opponents, but you can have as many as you'd like if your chart has more step zones.
    • Metronome: A character that responds to the player missing notes. There can be only one metronome (currently).
  • Characters: Characters are any beat node that inherits from Character.gd that could be used as a performer. They are defined by a name and a NodePath to the character in the level. You MUST define all characters in a scene, even if they are not immediately in use at the start of the level. The exception is if you have no performers.
  • Step Zones: Step zones are collections of lanes of notes, taken from DDR terminology. Step zones are typically visible to the player, but not always (ex. Pico on the speakers in Week 7, or when middlescroll is on). They are defined by a name and a NodePath to the step zone. In order for the correct lane data from a chart to be matched with a step zone, their names MUST be the same.
  • HUD: A node that shows the stats and health of the player. For 2D levels, it will typically also hold the step zones.
  • Default Position: The fallback position for the camera if no performers exist.

The rest of the level is up to you / whoever's designing the levels. Take a look inside the levels folder in the fnf package for examples, as well as the HaxeFlixel to Godot page for transitioning certain aspects of FNF to the Benjine.

Advanced Levels

If you plan to have custom events or effects that require code in your level, the first thing you should do is extend the script of the level file BEFORE setting any of the above properties. This way, you don't have to deal with accidentally losing the data you set for Level's export variables.

Overwritable Functions

func set_preload_variables():
    # For overwriting any of the following variables that preloads data into your level:
    countdown_voices = [
        preload("path/to/your_sound_1.ogg/mp3"),
        preload("path/to/your_sound_2.ogg/mp3"),
        preload("path/to/your_sound_3.ogg/mp3"),
        preload("path/to/your_sound_4.ogg/mp3")
    ]

    popup_combo = preload("path/to/your_popup_combo_scene.tscn/scn")

    miss_sounds = [
	preload("res://assets/sounds/missnote1.ogg"),
	preload("res://assets/sounds/missnote2.ogg"),
	preload("res://assets/sounds/missnote3.ogg")
    ]

func handle_prev_transition():
    # For creating custom transitions into your level.

func do_pre_level_story_event():
    # Does an event before the start of a level (ex. a textbox, in-game cutscene, etc.)
    # MUST either of the following:

    # some_object could also be the self, i.e. just connect(...)
    some_object.connect("signal_that_indicates_finished", self, "start_level_part_2" OR "_some_func_that_calls_start_level_part_2", [], CONNECT_DEFERRED | CONNECT_ONESHOT)

    # OR keep it as its default (already in Level.gd):
    start_level_part_2()

    # DO NOT USE YIELD STATEMENTS, AS THEY ARE UNRELIABLE WITH SWITCHING STATES.

func do_pre_level_freeplay_event():
    # Same deal as do_pre_level_story_event, but is called during freeplay instead of story mode.

func do_post_level_story_event():
    # Similar deal as do_pre_level_story_event, but is called at the end of the level.
    # There's slightly more to account for at the end of the level during Story Mode, so be sure to check the source code of this function.

func do_post_level_freeplay_event():
    # Similar deal as do_post_level_story_event, but is called during freeplay instead of story mode.

func do_level_specific_prep():
    # A special function that typically does nothing in a basic level.
    # Can be used for edge case initialization of variables (ex. changing the idle frequency of characters for specific levels)

    # Typically holds a switch statement that could look something like this:
    match song_data.name:
        "First song name":
            # Do something here
        "Second song name", "Third song name":
            # Do something that is common between both of these songs
        # ...

func start_level_part_2():
    # If you want to do a countdown-less song, here's what I recommend for the time being:
    # (Notice: I do need to test this to make sure it 100% works but this should be correct)
    Conductor.play_level_song(song_chart, level_info)

    can_pause = true
    set_process(true)
    set_process_input(true)

    Conductor.connect("finished", self, "on_conductor_song_finished")

func on_update(delta):
    # MUST start with:
    .on_update(delta)

    # Otherwise you can add more logic.
    # Recommended to check death for an early out:
    if dying:
        return

    # ...

func set_cam_follow_point(char_to_follow):
    # MUST start with:
    .set_cam_follow_point(char_to_follow)

In theory, you can also override other parts of the level if you know what you're doing.

Textboxes

Currently, modded textboxes aren't supported, but I will be fixing that asap

Manual Changes

Most aspects of basic levels are guaranteed to work with whatever settings players have on (ex. scroll type, song speed, etc.). There are several helper oobjects / scripts to ensure all aspects of basic levels work with the player's current settings.
There is no guarantee like this for advanced levels, however. There are so many things a mod team may want to do in a level that I simply can't account for, so if you're doing some fancy stuff, keep these things in mind:

  • Song Speed: In Freeplay, players can change how fast or slow the song can go. If you have hardcoded timings, be sure to divide them by Conductor.pitch_scale. If you have animations that aren't controlled by BeatNode.gd, be sure to animate the object with an AnimationPlayer and attach the script PitchScaledAnimPlayer.gd to it.
  • Scroll Type: Lanes affected by movement code and shaders are a pretty common sight in rhythm games, and FNF mods are no exception. However, keep in mind that players can change their scroll type preference, so if you do have fancy lane animations, be aware of what the player might choose. Currently, switching scroll types mid-song isn't automatic, but may be in future.
  • Botplay: Players can turn on botplay in Freeplay, and developers can enable and disable it any time during a song. If you have harmful notes that only the player should miss, be sure to make the player's harmful notes have ai_miss turned on. (If both the opponent and the player should miss these notes, turn on the same property for the opponent's harmful notes as well.)
Clone this wiki locally