Tutorial 7: What's in a Script?
Part 1: Patterns and Scripts
Rememeber that basic code in that first tutorial file? Let's take another look at it.
// Go to https://dmk.bagoum.com/docs/articles/t01.html for the tutorial.
pattern { } {
phase 0 {} {
paction 0 {
shiftphaseto(1)
}
}
// This is phase #1.
phase 0 {
type(non, "Hello World")
hp(4000)
} {
paction 0 {
position(0, 1)
//Insert code here
}
}
}
It's about time we explained what exactly is going on here.
A BDSL2 script is ultimately just code. The last statement in the script is what is "returned" by the script. In the script above, there is only one statement-- the pattern
function, which returns a PatternStateMachine. However, we could also write a script like this:
var myFloat = 6.4;
var myVector = pxy(4, 5);
myVector.Set(myFloat + myVector.y, myVector.sqrMagnitude);
myVector.magnitude;
In the backend, this script is basically converted into C#. If we were to write this script in C#, it might look like this:
public static float MyScript() {
var myFloat = 6.4f;
var myVector = new Vector2(4, 5);
myVector.Set(myFloat + myVector.y, myVector.sqrMagnitude);
return myVector.magnitude;
}
When it comes to making BehaviorEntities do things, we want the BDSL2 script to return a StateMachine (SM), and in most cases we specifically want it to return a PatternSM from the pattern
function. The BehaviorEntity will then run the StateMachine.
The pattern
function takes two arguments: a list of pattern properties (PatternProperty) and a list of phases (PhaseSM, from the phase
function). Pattern properties are currently only used to fetch boss metadata and configure background music. We'll discuss them at length in the tutorial on bosses.
Part 2: Phase Properties
A phase is a special unit of behavior which has metadata attached to it. When making bosses, each spellcard is one phase. When making stages, each stage section is one phase. The executing entity will run all the phases in sequence.
By convention, the zeroeth phase is a setup phase. In it, we should create any objects that need to last the entire length of the script, and then use the shiftphaseto(X)
command to go to the phase we are currently testing. Note that this convention is assumed by a significant amount of internal code, and your boss/stage scripts will not work properly if you try to do normal stuff in the zeroeth phase.
In phase X { ..properties } { ...actions }
, X is the phase timeout. If X is zero, then the phase has no timeout.
Try setting the phase timeout to a number like 3 or 4 and waiting out the phase. You should hear the sound of glass breaking and the message "Card Capture Failed..." in the bottom right. Card-type phases (non
, spell
, timeout
, final
) have rules for when they are considered to have been successfully captured, in Danmaku/Metadata.cs. By default, you must drain the boss HP to zero without taking any hits to capture a spell (for timeouts, you must only not take any hits). The type of the phase is set in the property type(PhaseType, PhaseTitle)
. There is also default support for dropping items based on the phase type and how the phase ends in Danmaku/Metadata.cs.
Note that setting the type is optional, and the script analysis code (in the practice architecture) will only pick up phases with a non-null type.
- Try setting the phase type to
timeout
and waiting it out. What happens? - Try setting the phase type back to
non
, and the timeout back to0
. Now try shooting down the boss by holding Z. What happens?
- Try making the boss fire some bullets. Get hit by some of the bullets and then drain the boss HP or wait out a
timeout
phase. What happens?
The HP of the boss is set in the property hp(MaxHP)
. The player shots bundled with the engine deal around 1000 DPS. There are similar properties such as hpi(MaxHP, InvulnerabilitySeconds)
, which allows you to make the boss invulnerable for some time when the phase starts.
- What do you think will happen if you don't set the HP property on a
non
type phase? - What do you think will happen if you don't set the HP property or the type property?
- What do you think will happen if you set or don't set the HP property on a
timeout
type phase?
When the phase ends, all running tasks dependent on that phase will be automatically cancelled. This includes tasks in familiars, subsummons, inodes, and so on. The engine also has support for automatically destroying all bullets on screen and clearing any shared data when the phase ends. If you set the phase type to non
, spell
, timeout
, final
, or stage
, it will do this automatically; otherwise you can use the clear
property to tell it to do so.
We mentioned the root(x, y)
property in Danmokou Chimera, but let's review it again. This property tells the boss what position it should move to before starting the phase. By default, the boss takes two seconds to move to this position, during which the phase is not yet started. However, when doing development, you can set the TeleportAtPhaseStart
value in Danmaku/Services/SaveData.cs
to true, and this will make the boss teleport to the position instead. You never need to write movement code for making the boss move to the correct starting location.
The properties type
, hp/hpi
, and root
are the most important phase properties, and you can make a fully-fledged script knowing only these three. The other important ones will come up in the discussion of other topics. You can see all the phase properties here: PhaseProperty.
Part 3: Multiple Phases
Copy this code into a blank file:
pattern {
boss "tutorial"
} {
phase 0 {} {
paction 0 {
shiftphaseto(1)
}
}
// This is phase #1.
phase 5 {
type(non, "Hello World")
hp(4000)
root(0, 2)
} {
paction 0 {
//Insert code here
}
}
// This is phase #2.
phase 5 {
type(spell, "Foo Bar")
hp(4000)
root(0, 0)
} {
paction 0 {
//Insert code here
}
}
}
Note that we've added the pattern property boss
, which automatically pulls metadata from the boss config. The boss config for "tutorial" is stored under Assets/Danmokou/SO/BossMetadata/Tutorial Boss.
In this code, we have three phases: the setup zero phase, then phase #1 "Hello World", and phase #2 "Foo Bar". Let's try a few things:
- Make sure
TeleportAtPhaseStart
is set to false, then run this code and wait out the first phase. What do you think will happen? - Set
shiftphaseto
to2
and reload the script by pressing R in the game view. What do you think will happen?
The behavior of multiple phases should make intuitive sense. Phases run in sequence, and shiftphaseto
can be used to skip to a phase for easy development.
- Make a phase #3 and give it a different title. Then set
shiftphaseto
to2
and reload the script. What do you think will happen?
Finally, here's one thing you should keep in mind, even though it might not make too much sense right now. When you call a boss script from a stage script, or when you call a stage script from a campaign, the shiftphaseto
in phase 0 is overriden to always point to phase 1. This means that shiftphaseto
can be treated as a development tool, and you don't need to set it back to 1
every time you want to run your project.
Problem Solutions
- Card capture will fail, but in the case of the
non
type card, the boss will still drop some items. - The boss will have a default HP value and also be invulnerable.
- The boss will have a default HP value and be vulnerable. (Note that if you use the
boss
pattern property, then the boss will be invulnerable.) - It doesn't matter, either way the boss is invulnerable during a timeout phase.
- The boss slowly moves to the position 0,0 and then "Foo Bar" starts.
- The boss slowly moves to the position 0,0 and then "Foo Bar" starts. ("Hello World" is skipped.)
- The boss slowly moves to the position 0,0 and then "Foo Bar" starts. ("Hello World" is skipped.) After "Foo Bar" is finished, then the phase #3 added by the user will start.