-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial
Welcome to Scenecontrol Tutorial Documentation!
This document is a tutorial designed to guide newcomers through the ArcCreate scenecontrol API. This will not cover all features, but will prepare you with enough knowledge to explore the rest on your own. Please check the Reference documentation for more information.
As usual, scripting requires knowledge of Lua, and so this guide will assume some familiarity with it. If you're new or would like a refresher on the language, please refer to the official tutorial.
The scenecontrol system is built around the concept of Channels. This only requires a single execution of Lua code, and the result can be exported to other formats that do not require Lua.
With this in mind, here are reasons to prefer the channel-based system:
- Completely deterministic: Channels define values as a function of time. The script runs once to build the channel graph, then the engine evaluates it during gameplay.
- Transparent: You have complete control over the system, which allows for faster scripting.
- Collaboration: It's easier to use and combine other people's scripts!
Tools you will need:
- ArcCreate editor (desktop, Windows/macOS/Linux)
- A sufficiently good IDE / text editor such as VSCode is recommended
Let's start from the very basic, and use the built-in commands. We'll first familiarize ourselves with the new Scenecontrol editing window.
The basic syntax for scenecontrol commands in the .aff file:
scenecontrol(timing, scenecontrolType, argument0, argument1, argument2, ...);
For example:
scenecontrol(1000, trackdisplay, 1000, 127);
-
timing(generally) signifies when the effect takes place. -
scenecontrolTypedefines the type of this command. -
argument0, argument1,...define all the arguments, which essentially dictate exactly how the command will operate.
It's also possible to nest scenecontrol commands inside a timinggroup, which may or may not make a difference depending on the command type.
ArcCreate supports these command types out of the box:
scenecontrol(timing, trackdisplay, duration, changeToAlpha);
scenecontrol(timing, hidegroup, duration, changeToAlpha);
scenecontrol(timing, enwidenlane, duration, changeToState);
scenecontrol(timing, enwidencamera, duration, changeToState);
-
trackdisplaychanges the transparency (or alpha) of the main track. -
hidegroupchanges the transparency of all notes within the same timing group of the scenecontrol command. -
enwidenlaneadds 2 more lanes to both sides of the main track (used in Pentiment, Arcana Eden and Testify). -
enwidencameramoves the camera to support enwidenlane.
Both timing and duration arguments are in milliseconds. changeToAlpha takes in a value from 0 to 255. changeToState takes in either 0 (disable) or 1 (enable).
Open the chart in ArcCreate, then click the star icon on the right side. You'll be greeted with the scenecontrol editing window.
On the top, you have:
- The Scenecontrol Type dropdown. Select the type to work with here.
- The Switch Field button. This toggles between separated fields for each argument and one combined field.
- The Refresh button. This reloads the script and refreshes the list.
In the middle is the list of events. Right now it's probably empty because your chart does not contain any event corresponding to the currently selected scenecontrol type. Add one by pressing the "+" button on any row.
From here you can change the arguments to your liking. Note that the window only shows scenecontrol events included in the currently active timing group.
That's it for the basics! Now we'll start diving into the scripting part.
To define your own scenecontrol types, start by navigating to your project folder (the one that includes the .arcproj file, the Audio folder, and your music/jacket files). Create a folder named Scenecontrol (case sensitive), and within it create a file named init.lua.
Scenecontrol/init.lua is the script that's run by default upon loading your chart file. The Scenecontrol folder is also where you'll put all of your asset files (images and such) to be read by the script(s).
For advanced users: you can also use
requireto split your Lua code into multiple script files. The file names are not important (except for the defaultinit.luaof course), as long as they are in theScenecontrolfolder.
Let's open the newly created lua file and start coding! However, for now we won't actually be defining our own scenecontrol type just yet, and focus on a concept much more fundamental: Controllers and Channels.
Level 1 of Controllers and Channels will prime you to take anything that already exists within the scene and manipulate it to your liking!
Controllers are where your custom Lua code will interact with in order to control objects in the scene. For example, you'll interact with a track controller to control the track, a sprite controller to control a 2D image sprite object, and so on.
However, instead of modifying the properties directly, you'll be doing it indirectly through Channels.
All channels have one job in common: they answer the question:
"It's now X milliseconds into the song. What's the value right now?"
Each property of a controller will have a channel attached to it, which will determine how the property will change at any point in time.
Let's look at a concrete example. We'll begin by defining a controller. Let's grab an internal controller by using Scene:
-- init.lua
local controller = Scene.track -- Grabs the track controllerSet the controller's position to somewhere else:
-- init.lua
...
controller.translationX = 1
controller.translationY = 2
controller.translationZ = 3Click the Refresh button on the scenecontrol edit window. This will reload your script, which told ArcCreate to move the track to the new position located at (1, 2, 3).
Here's a detailed explanation of what just happened:
- number 1, 2, 3 automatically casts into Channel.constant().
-
Channel.constant(value)defines a channel that will always return the value you passed in, regardless of time. - You took 3 of those channels and assigned them to 3 properties of a controller.
- Now the controller will update its values based on the set channels, which we defined to be constant values of 1, 2 and 3.
But what if we want the object to move over time? For that, Channel.constant() simply won't do. Entering: Keyframe channels.
A keyframe channel is defined by a series of Keys. If you have ever animated anything before then this concept will be familiar to you. A key is a point in time with a specific value, and the channel interpolates between them.
-- init.lua
local channel = Channel.keyframe()
channel.addKey(0, 0)
channel.addKey(1000, 1)
channel.addKey(2000, 0)
controller.translationX = channelYou could also write it more concisely:
controller.translationX = Channel.keyframe().addKey(0, 0).addKey(1000, 1).addKey(2000, 0)You can reuse channels! The same channel can be assigned to multiple properties, and changing one channel will change all properties reading from it.
local channel = Channel.keyframe().addKey(0, 0).addKey(1000, 1).addKey(2000, 0) controller.translationX = channel controller.translationY = channel controller.translationZ = channel -- even completely unrelated property is fine controller.colorR = channel
Refresh and see the result. The track should move linearly to another position from 0ms to 1000ms, then back from 1000ms to 2000ms.
If you don't want it to move linearly, use easings! Simply pass an additional string:
local channel = Channel.keyframe()
channel.addKey(0, 0, 'so')
channel.addKey(1000, 1, 'so')
channel.addKey(2000, 0, 'so')You can also change the default easing when creating the channel:
local channel = Channel.keyframe().setDefaultEasing('so')
channel.addKey(0, 0)
channel.addKey(1000, 1)
channel.addKey(2000, 0)Here's the list of all the easings supported. Each easing type can be written multiple ways.
| Name | Aliases |
|---|---|
| linear | l |
| inconstant | inconst, cnsti |
| outconstant | outconst, cnsto |
| inoutconstant | inoutconst, cnstb |
| insine | si |
| outsine | so |
| inoutsine | b |
| inquadratic | inquad, 2i |
| outquadratic | outquad, 2o |
| inoutquadratic | inoutquad, 2b |
| incubic | incube, 3i |
| outcubic | outcube, 3o |
| inoutcubic | inoutcube, 3b |
| inquartic | inquart, 4i |
| outquartic | outquart, 4o |
| inoutquartic | inoutquart, 4b |
| inquintic | inquint, 5i |
| outquintic | outquint, 5o |
| inoutquintic | inoutquint, 5b |
| inexponential | inexpo, exi |
| outexponential | outexpo, exo |
| inoutexponential | inoutexpo, exb |
| incircle | incirc, ci |
| outcircle | outcirc, co |
| inoutcircle | inoutcirc, cb |
| inback | bki |
| outback | bko |
| inoutback | bkb |
| inelastic | eli |
| outelastic | elo |
| inoutelastic | elb |
| inbounce | bni |
| outbounce | bno |
| inoutbounce | bnb |
For fun! You can also turn extrapolation of a channel on. Extrapolation continues the curve beyond the keyframes you have added.
local channel = Channel.keyframe() .setDefaultEasing('so') .setIntroExtrapolation(true) .setOuttroExtrapolation(true)
You can totally ignore the whole scenecontrol type concept, and just key everything in init.lua. But for reusability, defining types is recommended.
Let's make a simple scenecontrol type that reads in 3 numbers, and moves the track from its current position to a new one specified by those 3 numbers.
local track = Scene.track
track.translationX = Channel.keyframe()
track.translationY = Channel.keyframe()
track.translationZ = Channel.keyframe()
addScenecontrol("mytypename", 3, function(cmd)
local timing = cmd.timing
local x = cmd.args[1] -- lua starts counting from 1
local y = cmd.args[2]
local z = cmd.args[3]
track.translationX.addKey(timing, track.translationX.valueAt(timing))
track.translationX.addKey(timing + 500, x)
track.translationY.addKey(timing, track.translationY.valueAt(timing))
track.translationY.addKey(timing + 500, y)
track.translationZ.addKey(timing, track.translationZ.valueAt(timing))
track.translationZ.addKey(timing + 500, z)
end)What happened?
- We first defined the keyframe channels to add our keys into.
- We defined a scenecontrol type named "mytypename". It has 3 arguments. This means our aff command will look like this:
scenecontrol(timing, mytypename, arg1, arg2, arg3). - We defined the behaviour through a function. This function is run for every scenecontrol event in the chart. Our function reads that command and acts accordingly, adding keyframes to our channels.
Also note the use of channel.valueAt(timing). It's a handy function when working with keyframes.
This example illustrates the basic structure of a scenecontrol script:
- Setting up the controllers
- Assigning the channels
- Then defining scenecontrol types which will fill in the keys
By the way, you can name your arguments:
addScenecontrol("mytypename", {"xpos", "ypos", "zpos"}, function(cmd)
...
end){"xpos", "ypos", "zpos"} creates a list of strings which will be interpreted as names for arguments. Refresh scripts and check the window — you'll see the column headers updated.
Events with zero arguments also work. The following code below corresponds to this aff command:
scenecontrol(timing, mytypename);addScenecontrol("mytypename", nil, function(cmd) ... end) -- or -- addScenecontrol("mytypename", {}, function(cmd) ... end)
The Scene object serves 2 functions: grabbing internal controllers, and creating new controllers. We have done the former already, but only with one type so far.
To get an idea of what you can do, here's the list of all internal controllers:
| Path | Type | Description |
|---|---|---|
Scene.gameplayCamera |
CameraController | The main camera |
Scene.combo |
TextController | The combo text |
Scene.score |
TextController | The score text |
Scene.scoreTitle |
TextController | The score title text |
Scene.predictedGrade |
TextController | The grade text (Predictive score mode) |
Scene.predictedGradeBackground |
ImageController | The grade text background |
Scene.jacket |
ImageController | The jacket art |
Scene.jacketBackground |
ImageController | The jacket background |
Scene.title |
TextController | The song title |
Scene.composer |
TextController | The composer text |
Scene.difficultyText |
DifficultyController | The difficulty text |
Scene.difficultyBackground |
ImageController | The difficulty background |
Scene.hud |
CanvasController | The HUD canvas |
Scene.infoPanel |
InfoPanelController | The info panel |
Scene.pauseButton |
ImageController | The pause button |
Scene.background |
ImageController | The background image |
Scene.videoBackground |
SpriteController | The video background |
Scene.track |
TrackController | The main track |
Scene.beatlines |
BeatlinesController | The beatlines display |
Scene.singleLineL |
SpriteController | The left Memory Archive line |
Scene.singleLineR |
SpriteController | The right Memory Archive line |
Scene.skyInputLine |
GlowingSpriteController | The sky input line |
Scene.skyInputLabel |
GlowingSpriteController | The sky input label |
Scene.darken |
SpriteController | The background darkening sprite |
Scene.worldCanvas |
CanvasController | The world-space canvas |
Scene.screenCanvas |
CanvasController | The screen-space canvas |
Scene.cameraCanvas |
CanvasController | The camera-space canvas |
The
Scene.trackcontroller also exposes children such asScene.track.divideLine12,Scene.track.criticalLine1,Scene.track.extraL, etc. For a full list, see TrackSubControllers.
Instead of using internal controllers, let's try creating our own controllers. Here are the creation methods available on Scene:
| Method | Return type | Description |
|---|---|---|
Scene.createSprite(imgPath, material, renderLayer, pivot, wrapMode) |
SpriteController | Create a sprite from an image file |
Scene.createImage(imgPath, material, renderLayer, pivot, wrapMode) |
ImageController | Create an image from an image file |
Scene.createCanvas(worldSpace) |
CanvasController | Create a canvas |
Scene.createText(font, fontSize, lineSpacing, alignment, renderLayer) |
TextController | Create a text object |
Scene.getNoteGroup(tg) |
NoteGroupController | Get the controller for a timing group |
Anytime you see a = in the argument list, it means the argument has a default value and you don't have to specify it.
You can use the material argument to change the blending mode. Here's the list:
- default
- add, fastadd
- colorburn, colordodge
- darken, fastdarken
- difference, exclusion
- fastlighten, fastmultiply, fastscreen
- hardlight, lighten
- linearburn, lineardodge, linearlight
- multiply, overlay, screen
- softlight, subtract, vividlight
When choosing materials for sprites, images and texts, be aware that anything that doesn't start with
fastcan hurt performance significantly, especially on lower-end hardware.
Let's try adding a custom sprite into our scene:
local sprite = Scene.createSprite("test.png")
sprite.layer = "notes" -- automatically casted into StringChannel.constant("notes")
sprite.order = 1 -- automatically casted into Channel.constant(1)Feel free to change "test.png" to whatever image file you placed in your Scenecontrol folder.
Let's try it with an image, and change its material:
local image = Scene.createImage("test.png", "fastadd")
image.rectW = Channel.constant(300)
image.rectH = Channel.constant(200)
local canvas = Scene.createCanvas(false)
image.setParent(canvas)
canvas.layer = StringChannel.constant("notes")
canvas.sort = Channel.constant(1)Notice how you have to specify the width and height of the image this time. That's one major difference between sprites and images. Also, the Add blend mode can make your image hard to see — try other blend modes as well.
Lastly let's try creating some text:
local text = Scene.createText("Forte", 40, 1, "middlecenter", "overlay")
text.text = TextChannel.create().addKey(0, "Hello").addKey(1000, "World")
local canvas = Scene.createCanvas(false)
text.setParent(canvas)
canvas.layer = "notes"
canvas.sort = 1Text channels can also be keyframed! Here we made it display "Hello" at 0ms and then change to "World" at 1000ms.
You can also use easings on text channels! Try changing it to
addKey(0, "Hello", "so")and see what happens.
This covers the most important things to keep in mind when creating controllers. If you want to see the available properties of each type of controller, please refer to the documentation.
Level 2 of Controllers & Channels will teach you how to save tremendous amount of time when working with channels. You'll be learning about different types of channels, and how to combine channels together for various effects.
Let's first consider how we'd tackle a very simple effect: moving an object back and forth, indefinitely, in a sine wave pattern.
It's totally doable to add each keyframe manually:
-- You don't have to replicate this. This is a bad idea
local c = Channel.keyframe()
for timing = 0, Context.songLength, 2000 do
c.addKey(timing, 0, "so")
c.addKey(timing + 500, 1, "b")
c.addKey(timing + 1500, -1, "si")
endThis channel will keep oscillating between -1 and 1 in a sine wave form. It works, but we can do so much better.
In fact, the API provides a bunch of other types of channels. All of the code above can be shortened to:
local c = Channel.sine(2000, -1, 1, 0)
-- also equals to
local c = Channel.sine(Channel.constant(2000), Channel.constant(-1), Channel.constant(1), Channel.constant(0))You have a range of other channels available to you as well:
| Method | Type | Description |
|---|---|---|
Channel.keyframe() |
KeyChannel | A keyframe channel |
Channel.constant(value) |
ValueChannel | A channel with unchanging value |
Channel.random(seed, min, max) |
ValueChannel | A channel that returns random value every time |
Channel.noise(frequency, min, max, offset, octave) |
ValueChannel | A perlin noise channel |
Channel.sine(period, min, max, offset) |
ValueChannel | A sinusoidal wave channel |
Channel.cos(period, min, max, offset) |
ValueChannel | A cosine wave channel |
Channel.saw(easing, period, min, max, offset) |
ValueChannel | A saw wave with easing |
Channel.min(channelA, channelB) |
ValueChannel | Take the minimum of two channels |
Channel.max(channelA, channelB) |
ValueChannel | Take the maximum of two channels |
Channel.clamp(valueChannel, minChannel, maxChannel) |
ValueChannel | Clamp a channel between two bounds |
Channel.condition(control, threshold, ifAbove, ifEqual, ifBelow) |
ValueChannel | Conditional channel |
Channel.exp(num, exp) |
ValueChannel | Exponential channel |
By default FFT channels operate with 256 frequency bands, but you can change this with
Channel.setGlobalFFTResolution(resolution). Resolution must be an integer power of 2 (64, 128, 256, 512, ...)
It doesn't get much better than a single line of code, right? Well, it actually does. You can combine different channels together:
-- Vibrate between -2 and 2
local vibrate = Channel.noise(100, -2, 2, 0, 1)
-- Every 1000ms: change its value from 1 to 0, then jump back
local dampen = Channel.saw("so", 1000, 1, 0, 0)
-- How much it vibrates changes over time!
local combined = vibrate * dampenBy multiplying the vibrate channel with dampen, we limit how much the channel vibrates over time. When dampen returns 1 it vibrates the most; when dampen returns 0, it does not vibrate.
You can also combine keyframe channels with other channels. This opens up possibility for a very efficient technique. Let's look at enwidenlane:
local track = Scene.track
-- The main channel, which is 0 by default
local enwidenLaneFactor = Channel.keyframe().setDefaultEasing("l").addKey(0, 0)
-- Assign channels as simple transformations of the main channel
track.edgeExtraL.colorA = track.edgeExtraL.colorA + 255 * enwidenLaneFactor
track.edgeExtraR.colorA = track.edgeExtraR.colorA + 255 * enwidenLaneFactor
track.criticalLine0.colorA = track.criticalLine0.colorA + 255 * enwidenLaneFactor
track.divideLine01.colorA = track.divideLine01.colorA + 255 * enwidenLaneFactor
track.divideLine45.colorA = track.divideLine45.colorA + 255 * enwidenLaneFactor
track.criticalLine5.colorA = track.criticalLine5.colorA + 255 * enwidenLaneFactor
track.extraR.colorA = track.extraR.colorA + 255 * enwidenLaneFactor
track.extraL.colorA = track.extraL.colorA + 255 * enwidenLaneFactor
local posY = 100 * enwidenLaneFactor
track.extraL.translationY = Channel.min(track.extraR.translationY + posY, Channel.constant(0))
track.extraR.translationY = Channel.min(track.extraR.translationY + posY, Channel.constant(0))
local alpha = (1 - enwidenLaneFactor)
track.edgeLAlpha = track.edgeLAlpha * alpha
track.edgeRAlpha = track.edgeRAlpha * alpha
-- Choosing a different type name to avoid conflict with the built-in
addScenecontrol("enwidenlanelua", {"duration", "toggle"}, function(cmd)
local timing = cmd.timing
local duration = cmd.args[1]
local toggle = cmd.args[2]
enwidenLaneFactor.addKey(timing, enwidenLaneFactor.valueAt(timing))
enwidenLaneFactor.addKey(timing + duration, toggle)
end)If you're still unclear on what this code is doing, here's a brief explanation:
- The second argument, "toggle", is a value of either 0 or 1. We write this value directly into the
enwidenLaneFactorchannel. - Every property we need to change is simply some variant of the main channel, calculated directly on the channels themselves instead of individual keys.
Note that we're performing arithmetic between a channel and a number value. Internally the numbers get converted into constant channels, so this is just a convenient shorthand.
You can view what a channel is composed of by logging it:
local channel = Channel.saw("si", Channel.constant(1000), Channel.constant(1), Channel.constant(0), Channel.constant(0)) * Channel.noise(Channel.constant(100), Channel.constant(-1), Channel.constant(1), Channel.constant(0), Channel.constant(1))
channel = channel + Channel.keyframe()
channel = channel * 5
log(channel)You should see the following result when opening the Error Log:
((unnamed@saw(si,1000,0,1,0)) * (unnamed@noise(100,-1,1,0,1)) + unnamed@key(0)) * (5)
This will come in handy to figure out why the effect isn't working as you intended it to.
Note groups are a bit odd to work with. The workflow of defining the controllers, then keying them in scenecontrol definition we've been using so far aren't going to work for one simple reason: we don't know which controllers we want ahead of time, because we don't know which timing groups our events will be placed in.
The only way to be sure is to query for the controller for every scenecontrol command:
addScenecontrol("myType", {}, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
end)That's totally fine but we can't really create a new channel for every scenecontrol command. This will override the old channel for every command:
addScenecontrol("myType", 1, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
noteGroup.translationX = Channel.keyframe().addKey(0, cmd.args[1])
end)
-- This doesn't work and the note group's properties will only be assigned the last value.Instead we need a way to find out if we have already created a channel for a note group, and if not, create a new one. Introducing: named channels.
Remember when you were logging your channel and there were a bunch of "unnamed" in there? That's the default name of every channel. You can change your channels' name:
local channel = Channel.named("myChannelName").keyframe()Then if a channel is combined from multiple channels, you can separate out each one to edit with by using .find(name):
local myChannel = Channel.named("IWantThisBackLater").keyframe()
myChannel.addKey(0, 0).addKey(1000, 1).addKey(2000, 2)
local otherChannel = Channel.named("OthersCreatedThis").keyframe()
local combinedChannel = myChannel * otherChannel
local myChannelFound = combinedChannel.find("IWantThisBackLater")
-- myChannelFound is the same object as myChannelWhat this allows us to do is solve the issue we had earlier with note groups:
addScenecontrol("myType", 1, function(cmd)
local noteGroup = Scene.getNoteGroup(cmd.timingGroup)
-- Try finding the channel from the property
local channel = noteGroup.translationX.find("myType")
if channel == nil then -- If it's not found
-- Make a new channel
channel = Channel.named("myType").keyframe()
-- Assign it
noteGroup.translationX = channel
end
channel.addKey(0, cmd.args[1])
end)Always naming your channels is a very good idea, as it helps with debugging. Especially if you intend on sharing your scripts with other people to use.
Triggers allow you to react to events during gameplay, such as judgement results or value thresholds being crossed. Triggers by themselves do nothing; they must be combined with a TriggerChannel to produce a value channel.
Activate on note judgement events:
local trig = Trigger.judgement()
.onPerfect()
.ofTimingGroup(0)
.dispatch(Channel.constant(1), 500, "so")
local trigChannel = TriggerChannel.loop(trig, Channel.constant(0))
-- Use the trigger channel like any value channel
someSprite.colorA = trigChannel * 255This trigger fires on Perfect judgements (including early, late, and max) in timing group 0, dispatching a value of 1 with 500ms duration and so easing.
Available judgement filter methods: onMax(), onPerfect(), onPerfectEarly(), onPerfectLate(), onGood(), onGoodEarly(), onGoodLate(), onMiss(), onMissEarly(), onMissLate(). All can be chained to listen to multiple judgement types.
Activate when a value channel crosses a threshold:
local trig = Trigger.observe(Context.currentCombo)
.goAbove(100)
.dispatch(Channel.constant(1), 500, "so")
local trigChannel = TriggerChannel.loop(trig, Channel.constant(0))This trigger fires when the combo count goes above 100.
A TriggerChannel turns triggers into an actual value channel:
| Method | Description |
|---|---|
TriggerChannel.loop(trigger, fallback) |
Starts at fallback, jumps to the dispatched value when trigger fires, then returns to fallback
|
TriggerChannel.accumulate(trigger, fallback) |
Starts at fallback, then adds the dispatched value each time the trigger fires |
TriggerChannel.stack(triggers...) |
Combines multiple triggers into one channel |
TriggerChannel.setting(triggers...) |
Keeps the last dispatched value |
These are all small details that you should know but aren't enough to warrant an entire section about.
- Camera coordinates in aff camera commands and in the scenecontrol API are very different!
-
All file paths are relative to the
Scenecontrolfolder, NOT to the running script.For example, if within your
init.luayou referenced the scriptfolder/other_script.lua, and withinother_script.luayou tried to create a sprite with the pathimage.jpg, the actual file path should be located atScenecontrol/image.jpgand NOTScenecontrol/folder/image.jpg. - When choosing materials for sprites, images and texts, be aware that anything that doesn't start with
fastcan hurt performance significantly, especially on lower-end hardware.
- Alternative to
log,notifyis also an option, which will display a message on a toast notification.notifyWarnandnotifyErrorare also available. - You can always use
log(channel.valueAt(timing))to check a channel's value at any point in time. This can be useful to figure out the coordinates of objects in the scene. - You can get the list of all valid installed fonts with
Context.availableFonts. Only use this for debugging however and don't include this in an actual script. - Always use
localunless you do intend on making a variable global. -
Contextexposes channels for timing data:Context.bpm(tg),Context.beatLength(tg),Context.divisor(tg),Context.floorPosition(tg), as well as gameplay state:Context.currentCombo,Context.currentScore,Context.songLength,Context.screenWidth,Context.screenHeight,Context.laneFrom,Context.laneTo.
Congratulations on finishing this tutorial on the ArcCreate scenecontrol API!
You should now be armed with enough knowledge to use the reference documentation effectively, which will detail everything about the API for you to reference while writing your own scripts.
Have fun scripting!
Global
- Global Functions
- Scene
- Channel
- StringChannel
- TextChannel
- Trigger
- TriggerChannel
- Context
- Event
- Convert
Channels
Controllers
- Controller
- CanvasController
- ImageController
- SpriteController
- TextController
- CameraController
- TrackController
- NoteGroupController
Internal Controllers
Data types