Caution
This is a preview feature, not in current builds (only in a playtest preview build)
This chapter will walk you through using Custom Events, a song event that directly correlates to some script code. This is the simple version of a Scripted Song Event, as it directly relies on a stage or a song rather than being its own class.
Why use a Custom Event
A Custom Event is limited in scope but can be used in tandam with the Camera Editor, as its systems are designed for the Camera Editor to be able to reverse playback, pause animations, and properly show a visual editing mode for these events. Using a Custom Event over a Scripted Song Event is based on your needs as the event requires.
Adding a Custom Event to a chart
Pick Custom Event from the event list in the Chart Editor, or from the Add Event menu in the Camera Editor. It has the following properties:
-
name: What your scripts check for to know which event this is, such asCool Event.- The timeline shows a two letter tag for it, made from the first two words (
Cool EventbecomesCE, andcoolEventbecomesCEtoo).
- The timeline shows a two letter tag for it, made from the first two words (
-
offset: Moves when the event starts, in milliseconds. Negative values start it earlier. -
duration: How long the event runs for, in steps.0is instant. -
easeandeaseDir: The easing applied to the event's progress, likequadandOut. -
params: Any number of extra values, each with anameand avalue.- Lists are written as comma separated text, such as
true, false, trueor0xFFFF0000, 0xFF00FF00.
- Lists are written as comma separated text, such as
Handling a Custom Event
Every script that can receive song events gets Custom Events through onCustomEvent. This includes Songs, Boppers, Characters, Stages and Modules. It gets called every frame while the event runs, from 0% to 100% of its duration, and a single time at 100% if the event has no duration.
An example for fading a sprite based on the progress would be something like this:
// ... override function buildStage():Void { super.buildStage(); // Create a sprite that you can access in this script (example, in a stage) coolSprite = new FunkinSprite(0, 0).makeSolidColor(400, 400, FlxColor.CYAN); add(coolSprite); refresh(); // gotta call refresh to make the stage recognize the change // quick tip, batch your refresh calls! Don't always call it after each addition. } override function onCustomEvent(event:CustomEventScriptEvent):Void { super.onCustomEvent(event); if (event.name != 'Cool Event') return; // or use a switch case if you have a lot of events var from:Float = CustomSongEvent.paramFloat(event.eventData, 'from', 0); var to:Float = CustomSongEvent.paramFloat(event.eventData, 'to', 1); coolSprite.alpha = from + (to - from) * event.eased; } // ...
The event's properties
The CustomEventScriptEvent passed to onCustomEvent has the following read only properties:
-
name: The event's name. -
eventData: The event's data, used with the parameter helpers below. -
progress: How far through the event the song is, from0to1. -
eased:progresswith the event's ease applied. This is usually the one you want. -
elapsedMs: How many milliseconds have passed since the event started. -
durationMs: How long the event is, in milliseconds. -
isFirst:trueon the first call for this event. -
isLast:trueon the last call, at 100%.
It also has a couple of functions:
-
ease(value): Applies the event's ease to any value from0to1, for parts of an event that run on their own timing. -
seekAnimation(sprite): Puts the sprite's current animation on the frame it would be on this far into the event, and returns whether the animation would already be over.
Reading parameters
Parameters are read with static functions on CustomSongEvent, all of which take the event's eventData and the parameter's name:
-
param(data, name): The value as text, ornullif the event doesn't have it. -
paramInt(data, name, defaultValue),paramFloat(data, name, defaultValue): The value as a number, or the default if it's missing. -
paramBool(data, name, defaultValue):true,yes,onand1count astrue. -
paramList(data, name): A comma separated value split into text, or an empty list if it's missing. -
paramBoolList(data, name),paramFloatList(data, name): The same, converted into booleans or numbers. -
nameOf(data): The event's name, for when you only have theeventData.
One such event using every paramater might look like this:
override function onCustomEvent(event:CustomEventScriptEvent):Void { super.onCustomEvent(event); var data = event.eventData; switch (event.name) { // Params: target = "dad", intensity = "0.8", colors = "0xFFFF0000, 0xFF00FF00, 0xFF0000FF", visible = "true, false, true" case 'Spotlight': var target:String = CustomSongEvent.param(data, 'target') ?? 'bf'; var intensity:Float = CustomSongEvent.paramFloat(data, 'intensity', 1); var colors:Array<String> = CustomSongEvent.paramList(data, 'colors'); var shown:Array<Bool> = CustomSongEvent.paramBoolList(data, 'visible'); if (event.isFirst) { // Once at the start: pick the color and which lights show. light.color = FlxColor.fromString(colors[0]); for (i in 0...lights.length) lights[i].visible = i < shown.length && shown[i]; } // Every frame: fade in with the event's ease. light.alpha = intensity * event.eased; if (event.isLast) { // At the last point, set to the final color. light.color = FlxColor.fromString(colors[colors.length - 1]); light.alpha = intensity; } } }
Playing an animation/sound
Animations and sounds are very simple and work as you would expect, simply only play if its the first call of that event:
// ... override function onCustomEvent(event:CustomEventScriptEvent):Void { super.onCustomEvent(event); if (event.name != 'Big Hey') return; if (event.isFirst) { getDad()?.playAnimation('hey', true); FunkinSound.playOnce(Paths.sound('hey'), 1.0); } } // ...
Making events work in the Camera Editor
To make your events properly work in the camera editor you must also supply an onEventReset function override, like so:
// ... override function onEventReset():Void { super.onEventReset(); // Reset anything that your custom events might change over the length of the song, IE the coolSprite's alpha. // see sserafim.hx stage for more information coolSprite.alpha = 0; } // ...
The point of this override is to give the camera editor a blank state that it can then play events overtop, or reverse them (changing state).
Animations and sounds are automatically picked up by the camera editor, and you do not have to do anything special.