Build a GameCreating Pitch Shot

2  Listening

Goal

By the end of this chapter, the notes find their voices. They sing once, low to high, each one swelling as it sings. Then one of them sings alone: the call. Press Space, and it sings again. Under it all, the meadow’s tune plays softly, and steps aside whenever a note sings.

The game as it is at the end of this chapter. Click the game to play.

If your game and this chapter part ways, here is the chapter’s finished project. Unzip it first. Then open it and compare, or start Chapter 3 from it.

Pitch Shot, Chapter 2 finishedZip of a Myth project · 2.8 MB

To open it, right-click the downloaded file, choose Extract All... and then Extract. Start Myth Engine, choose File › Open Project... and pick the .mythproj file in the new folder. Nunito-OFL.txt beside it comes with the game.

2.1 The Idea

Sunny Meadow, the slingshot, and two golden notes bobbing in their wooden tower. It’s lovely, and it’s silent.

Now listen. A moment after the stage stands, words appear in a pill near the top, Low to high… The lower note swells and sings, F3. Half a second later, the upper one swells and sings, F4, the same note one octave up. A short pause, and the pill says Listen… One of the two sings alone.

That’s the call. It’s the note you’ll hunt for, all game long. The singing before it, every note once from the lowest to the highest, is the opening. It lets you hear what each note sounds like before you’re asked to find one.

Missed it? Press Space, or click Hear it again at the top left, and the call sings again. Wait, and it comes back by itself. And under everything, the meadow has its own tune, played softly. It steps aside each time a note sings, so a note is never lost under the music.

Here’s the problem this chapter solves. Nothing in the game is in charge of the opening. No piece of Logic points at each note in turn and says “now you.” So how do two notes, or six, take turns in the right order?

Think of a choir before a song. The conductor nods once. Each singer knows their own entry, the first right away and the second a beat later, and counts to it alone. Nobody points at anybody.

The notes work the same way. When the stage is built, one message goes out to every note at once. Each note knows its own turn, its place from low to high, and it waits that long before it sings. A message like that is a Custom Event, and this chapter has several. They’re how the pieces talk.

2.2 The Plan

Here’s how this chapter’s pieces work together. A name in bold is a piece you’ll make. A name in plain text, such as placed, is a Custom Event, a message from one piece to another.

  • Build The Stage makes a note -> tells it its row and its turn, then sends it placed -> Sing In The Opening reads the note’s pitch from the Notes table.
  • Build The Stage finishes -> sends stageBuilt to every note -> each one waits for its turn -> Play Piano Note sings its pitch, and the note swells.
  • The highest note sings -> openingFinished -> Call A Note says Listen… and picks the call -> callRequested -> Repeat The Call plays it, and again after a quiet spell.
  • Space, or the Listen Button -> Ask For The Call sends callRequested -> Repeat The Call plays the call again.
  • Logic changes $message -> Show The Message shows its words in the Message Pill for 2.6 seconds.
  • The Field starts -> Play The Place’s Tune brings in the meadow’s tune. A note sings -> noteSung -> Make Way For The Notes turns the tune down, then back up.

You’ll build them in the order you’ll hear them. First the voices, then the opening, then the words, the call and the tune.

2.3 Set Up

The Set up card is short this time.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Message Pill Art
Listen Button Art
SOUNDS (from the starter kit)
Note F3
Note F sharp3
Note G3
Note A flat3
Note A3
Note B flat3
Note B3
Note C4
Note C sharp4
Note D4
Note E flat4
Note E4
Note F4
Note F sharp4
Note G4
Note A flat4
Note A4
Note B flat4
Note B4
Note C5
Note C sharp5
Note D5
Note E flat5
Note E5
Note F5
Note F sharp5
Note G5
Music Sunny Meadow
Music Seaside Beach
Music Autumn Woods
Music Red Rock Canyon
Music Candy Hills
Music Snowy Peaks
Music Jungle Falls
Music Pirate Cove
Music Mushroom Grove
Music Volcano Isle
Music Cloud Kingdom
Music Moonlit Castle
COMMANDS (Game Settings > Input Manager)
Hear It AgainButton
KeybindingsSpace
INPUT CONTEXTS (Game Settings > Input Manager)
FieldActive at Start(rename Default)
CommandsHear It Again
GLOBAL VARIABLES
$messageTextstarts at
$calledPitchNumberstarts at 0
SCENE Field: PLACE THESE
ObjectXY
Message Pill960-150
with Z-Index = 10010
Listen Button230-70
with Z-Index = 10010

SOUNDS lists 27 piano notes, from Note F3 up to Note G5, and the twelve places’ tunes. The Starter Kit brought every one, so there’s nothing to add.

COMMANDS has one key. Hear It Again is a Button Command, and its key is Space. INPUT CONTEXTS says it belongs to Field, the context your project calls Default. Rename it, as Keys: Commands and Input Contexts shows. Default is Active at Start already, so leave that switch on.

GLOBAL VARIABLES has two lines. $message names the words to show at the top, so it’s a Text, and it starts empty. Clear the 0 in its Default. $calledPitch is the call, as a number, and it starts at 0, which means no call yet. Make both from any Logic Component’s Globals card, as in Chapter 1.

PLACE THESE waits for the Message Pill and the Listen Button, and the chapter comes back to it as each one is made.

2.4 A Voice for Every Pitch

The game sings 27 different pitches. Each one is a Sound, a recording of one piano key. So the first job is a list of them, and a way to play any one.

2.4.1 The Pitches Table

A Pitch is one row of that list. It holds a pitch’s MIDI number and the Sound that plays it. The number is the one the Notes table already uses, so F3 is 53.

Data StructurePitch

One pitch the game uses: its MIDI number and the Sounds that play it.

midiNumberdefault 60
noteSoundThing Sounddefault None

Make the Data Structure as in Chapter 1. Its noteSound is a Thing Sound, which holds one of your Sounds. Pick Sound under Things in its Type list, as A Data Structure card says. Its Value starts at None.

Data TablePitchesData Structure: Pitch
IMPORT CSV27 rows, from a file
FIRST ROWS3 of 27, to compare
Row 0F3
midi53
noteSoundNote F3
Row 1F sharp3
midi54
noteSoundNote F sharp3
Row 2G3
midi55
noteSoundNote G3

Make the Pitches table with Pitch as its Entry Type, and import pitch-shot-pitches.csv, as you did the Stages. The file holds the Sounds by name, and each noteSound cell finds its own.

2.4.2 Play Piano Note

Play Piano Note is handed a pitch, such as 53, and plays it. Every note in the game sings through it.

Picture a songbook with a page for every pitch. Play Piano Note turns to the pitch’s page, makes sure the page is really there, and plays the Sound written on it. dataFind does the turning. It searches one column of a table for a value, here the midi column of Pitches, and gives back the row where it finds it. The making sure is the same question Build The Stage asked about a roof. The playing is Control Sound.

Last, it sends noteSung, a Custom Event that any piece of Logic can listen for. Nothing listens for it yet. At the end of this chapter, Make Way For The Notes will.

FunctionPlay Piano Note

Plays the piano note of one pitch (a MIDI number: 53 is F3, 60 is middle C, 79 is G5), the Sound its row of the Pitches table names.

PARAMETERS
NameTypeStarts at
pitchNumber60
ACTIONS
1Check Data Row Exists
Action Name
pitchFound
Table
Pitches
Row
dataFind("Pitches", "midi", pitch)
2IfpitchFound.value == true
Then
3Control Sound
Sound
data("Pitches", dataFind("Pitches", "midi", pitch), "noteSound")
4Trigger Custom Event
Custom Event
noteSung

Make it with Add > Function. Its one Parameter, pitch, is a Number that starts at 60. On Control Sound, the first list says Play, and that’s the card’s. The Sound box takes a formula, so type the card’s there, quote marks and all. Trigger Custom Event’s box holds the name bare: noteSung.

Run the game. Nothing new happens, because nobody calls Play Piano Note yet. The voices are ready, and nobody has asked them to sing.

2.5 The Opening

Now the notes find their voices.

2.5.1 Note State

Each singing note has to know three more things: its row of the Notes table, its turn in the opening, and the pitch it sings.

What a note knows has to be shared. Build The Stage tells it the row and the turn, and Sing In The Opening reads them. So these three live on the note itself, in the Data Structure it already carries for its bobbing, Note State.

Data StructureNote StateAddDelete

What one singing note keeps: where it swings and which way it swings now, its row of the Notes table, its turn in the opening, and the pitch it sings.

homeYNumberdefault 0
swingDirectionNumberdefault 1
noteRowNumberdefault 0
turnNumberdefault 0
pitchNumberdefault 60

Open Note State, and add the three new fields with Add Field, all Numbers. The pitch starts at 60, middle C, until the note reads its own.

2.5.2 Sing In The Opening

Picture the moment a note is made. Build The Stage tells it noteRow and turn, and then sends it placed. That’s the note’s cue to read its pitch, the midi in its own row of Notes. Change Component Value writes it into the note’s Note State, at self.noteState.pitch: this note, its Note State, the field pitch.

When the last note is made, stageBuilt rings for all of them at once. That’s the conductor’s nod. Each note starts a timer, singTurn, and it rings once. The lowest note, turn 0, waits openingStartSeconds, 0.6 seconds. The next waits 0.52 seconds more, and the next 0.52 more again, all the way up.

When a note’s timer rings, it’s that note’s turn. It sings through Play Piano Note. And it swells: Tween grows it to 1.32 times its size over half of swellSeconds. Then a second timer, swellFull, shrinks it back to 1 over the other half. The whole swell takes 0.45 seconds, a breath in and out.

Two notes have one more job each. The lowest, on its turn, sets $message to lowToHigh, the words for the start of the opening. The highest starts openingDone, a timer that rings 0.75 seconds after its turn. When it rings, it sends openingFinished. The opening is over.

Look on the card for the five When bars: placed and stageBuilt, then the three timers. And look at its DATA STRUCTURES. It keeps nothing of its own. It reads and writes the note’s Note State, the same one Bob While Waiting uses.

Logic ComponentSing In The OpeningAttach to: Singing Note

Before the first call, the singing notes sing once from the lowest to the highest. Each note sings its own turn and swells while it sings. It reads noteRow and turn (its Note State), set by Build The Stage, when it creates the note.

Attach toAny Object
DATA STRUCTURES
Note Stateself.noteState
SHARED VARIABLES
NameTypeKindStarting Value
openingStartSecondsNumberConstant0.6
secondsBetweenNotesNumberConstant0.52
swellScaleNumberConstant1.32
swellSecondsNumberConstant0.45
pauseAfterOpeningSecondsNumberConstant0.75
On Placed
WhenplacedCustom Event
1Change Component Value
Component Value
self.noteState.pitch
Value
data("Notes", self.noteState.noteRow, "midi")
On Stage Built
WhenstageBuiltCustom Event
1Start Recurring Timer
Timer Name
"singTurn"
Repeat Every (s)
@self.openingStartSeconds + self.noteState.turn * @self.secondsBetweenNotes
Ring Once
Yes
On the Sing Turn Timer
WhenRecurring TimerTimer name: singTurn
1Ifself.noteState.turn == 0
Then
2Change Variable
Variable
$message
Value
'lowToHigh'
3Ifself.noteState.turn == (data("Stages", $stage - 1, "noteCount") - 1)
Then
4Start Recurring Timer
Timer Name
"openingDone"
Repeat Every (s)
@self.pauseAfterOpeningSeconds
Ring Once
Yes
5Play Piano NoteFunction
pitch
self.noteState.pitch
6Tween
Value
self.transform.scale
Target
@self.swellScale
Duration
@self.swellSeconds / 2
Ease
Quad Out
7Start Recurring Timer
Timer Name
"swellFull"
Repeat Every (s)
@self.swellSeconds / 2
Ring Once
Yes
On the Swell Full Timer
WhenRecurring TimerTimer name: swellFull
1Tween
Value
self.transform.scale
Target
1
Duration
@self.swellSeconds / 2
Ease
Quad in
On the Opening Done Timer
WhenRecurring TimerTimer name: openingDone
1Trigger Custom Event
Custom Event
openingFinished

Make it with Add > Logic Component. Pick Note State on its Data Structures card, as Bob While Waiting has it. Its five Shared Variables are Constant Numbers.

placed and stageBuilt are Custom Events. In the Add Event window, choose Custom Events > Project Events > Add Custom Event…, and type each name, as Events: the When bars shows. The three timers are Recurring Timers, named the way bob was.

A few boxes are new:

  • Change Component Value has one box for its path, so type self.noteState.pitch there, as the card spells it.
  • Start Recurring Timer has a Ring Once list, No or Yes. Pick Yes, and the timer rings once.
  • Tween needs the note’s size this time, so type self.transform.scale into its Value box. Its one number scales the note across and up alike.

In the singTurn Event, the second If comes after the first, not inside it. Add it from the Action List and join it with Connect to sequence, and the same for steps 5 to 7. Steps 2 and 4 go on their Ifs’ True lines, with Add inside Then.

2.5.3 The Singing Note, Changed

ObjectSinging NoteAddDelete
Transform
Bounding Box > SizeX 0.84, Y 0.83
Sprite
Scene Depth Draw Order35
SpriteSinging Note Art
Transform > SizeX 0.84, Y 0.83
Data Structure
From LogicBob While Waiting, Sing In The Opening
From LogicBob While Waiting
Data StructureNote State
Logic
Bob While Waiting
Sing In The Opening

The note gets a second Logic Component, the one you made. Note State? It has that already. Only the From Logic line under it changes, because now two Logic Components ask for it.

So attach the Logic, and nothing else. Open the Singing Note, switch to Logic, and click Sing In The Opening under Logic Toolbox. Switch back to Components, and the line under Note State names both. Changes cards says why that line needs nothing from you. Sing In The Opening’s five Shared Variables are on the note now too, under Variables, at the card’s values.

2.5.4 Build The Stage, Changed

Logic ComponentBuild The StageAttach to: Game RulesAddDelete

When the Field starts, builds the stage being played from the Stages, Tower Plans, Floors and Notes tables: every tower, floor by floor from the ground up, and every singing note where its pitch puts it.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
singingNoteObjectConstantNone
groundHeightNumberConstant1.3
floorHeightNumberConstant1.34
supportSpreadNumberConstant0.94
roofSpreadNumberConstant0.6
noteAboveFloorNumberConstant0.56
When the Scene Starts
WhenScene Start
1Declare Variable
Name
tower
Value
0
2Declare Variable
Name
towerRow
Value
0
3Declare Variable
Name
towerX
Value
0
4Declare Variable
Name
floorRow
Value
0
5Declare Variable
Name
floorBottom
Value
0
6Declare Variable
Type
Text
Name
roofKind
7Declare Variable
Name
row
Value
0
8Region
Label
"Towers"
Color
#C27A3A
9Repeatdata("Stages", $stage - 1, "towerCount")times
Each time
10Change Variable
Variable
tower
Value
local.index
11Change Variable
Variable
towerRow
Value
data("Stages", $stage - 1, "firstTower") + tower
12Change Variable
Variable
towerX
Value
data("Tower Plans", towerRow, "x")
13Change Variable
Variable
floorBottom
Value
@self.groundHeight
14Repeatdata("Tower Plans", towerRow, "floorCount")times
Each time
15Change Variable
Variable
floorRow
Value
data("Tower Plans", towerRow, "firstFloor") + local.index
16Build A FloorFunction
towerX
towerX
floorBottom
floorBottom
floorHeight
@self.floorHeight
supportSpread
@self.supportSpread
builder
self
material
data("Floors", floorRow, "material")
17Change Variable
Variable
floorBottom
Operator
+=
Value
@self.floorHeight
18Check Data Row Exists
Action Name
roofFound
Table
Materials
Row
data("Tower Plans", towerRow, "roof")
19IfroofFound.value == true
Then
20Change Variable
Variable
roofKind
Value
data("Materials", data("Tower Plans", towerRow, "roof"), "support")
21Place A BlockFunction
x
towerX - @self.roofSpread
y
floorBottom + data("Block Kinds", roofKind, "height") / 2
builder
self
kind
roofKind
22Place A BlockFunction
x
towerX + @self.roofSpread
y
floorBottom + data("Block Kinds", roofKind, "height") / 2
builder
self
kind
roofKind
23Region
Label
"Singing Notes"
Color
#E8B923
24Repeatdata("Stages", $stage - 1, "noteCount")times
Each time
25Change Variable
Variable
row
Value
data("Stages", $stage - 1, "firstNote") + local.index
26Where A Note SitsFunction
Action Name
noteSpot
noteRow
row
groundHeight
@self.groundHeight
floorHeight
@self.floorHeight
noteAboveFloor
@self.noteAboveFloor
27Create Instance
Object to Create
@self.singingNote
X
noteSpot.x
Y
noteSpot.y
Variable Name
newNote
28Change Variable
Variable
newNote.noteRow
Value
data("Stages", $stage - 1, "firstNote") + local.index
29Change Variable
Variable
newNote.turn
Value
local.index
30Trigger Custom Event on Object
Applies to
newNote
Custom Event
placed
31Trigger Custom Event
Custom Event
stageBuilt

Build The Stage makes each note already. Now it tells each one who it is. Steps 28 and 29 give the new note its row and its turn, and step 30 sends it placed. When every note is made, step 31 sends stageBuilt.

The turn is local.index, the Repeat’s own count from 0. The notes come out of the table from low to high, so the lowest is turn 0.

Look at steps 28 and 29. Sing In The Opening wrote pitch with Change Component Value, through self.noteState.pitch. These two write noteRow and turn with Change Variable, and no noteState in the path. It looks like a slip. It isn’t.

Change Component Value reaches only the Logic’s own Object, through self. newNote isn’t that. It’s the answer Create Instance gave. So Build The Stage needs another way into the new note’s Note State.

Here it is. A note carries one Data Structure, and its fields are also on the note under their plain names. So Change Variable can write them there: newNote.noteRow is the new note’s noteRow. Both ways reach the same Note State.

Step 28’s Value may look familiar. It’s the same sum step 25 put in row, the note’s row of Notes, written out again in full. Type it as the card has it.

Steps 28 to 30 go inside the second Repeat, after Create Instance. Add each with Add inside Repeat on that Repeat, and join each with Connect to sequence.

  • In a Change Variable, the Variable box takes the new note’s name, a dot, and the field: type newNote.noteRow, then newNote.turn.
  • Trigger Custom Event on Object sends its message to one Object only. Pick newNote in its Applies To list. That’s the name Create Instance gave the new note.

Step 31 comes after the whole second Repeat, on the main line, so add it from the Action List and join it with Connect to sequence. It may land inside the Singing Notes frame or below it, and either way it runs the same. A Region changes nothing in how its steps run.

Run the game.

For a moment, nothing. Then the lower note swells and sings, and the upper one answers, an octave higher. They settle back and go on bobbing.

2.6 Words Near the Top

The lowest note set $message to lowToHigh a moment ago, at the start of the opening, and nothing showed. Now the words get a place to appear.

2.6.1 The Messages Table

Each Message is one line of words. The Messages table holds the game’s messages, one per row, and Logic only ever names a row.

Data StructureMessage

One short message.

wordsTextdefault (empty)
Data TableMessagesData Structure: Message
Row 0lowToHigh
wordsLow to high…
Row 1listen
wordsListen…

Make Message, with its one Text field, and clear its 0. Then make Messages with Message as its Entry Type, and add its two rows by hand: lowToHigh and listen, in the Entry column. The words end in …, which is one character, not three dots. Copy the words from the card on this page, and paste them into each cell.

2.6.2 The Message Pill

The pill is a Screen UI Object, one that lives on the screen, in pixels, while the world sits behind it. Make it with Add > Object, and pick Screen UI Object as its Object Type.

ObjectMessage Pill
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Bounding Box > SizeX 1000, Y 70
Sprite
Z Index Within Object-1
SpriteMessage Pill Art
Transform > SizeX 1000, Y 70
Text / UI
Text{data("Messages", $message, "words")}
FontNunito
Font Size30
EmphasisExtra Bold
AlignmentCenter
RenderingVector
Logic
Show The Message

Its picture is the pill, Message Pill Art. Its Z Index Within Object of -1 draws the pill one step behind the words. Its card lists no UI Interaction. Nobody clicks the pill. So remove the one it came with, using the trash button on its row.

The words are a Text / UI, and An Object card and its components has a paragraph on each of its lines. Its Text has braces, so it shows a live value: {data("Messages", $message, "words")}. Whatever row $message names, those words fill the pill. When $message is empty, it names no row, and the pill shows nothing.

2.6.3 Show The Message

Show The Message has three small jobs.

When the pill is made, it hides itself and clears $message. Why clear it? A Global keeps its value when the Field starts again. Left alone, the next stage could set the same message, and nothing would change, so nothing would show.

Whenever $message changes, it looks. If $message names something, the pill shows itself and starts hideMessage, a timer that rings once after showSeconds, 2.6 seconds. If $message is empty, the pill hides.

When hideMessage rings, it sets $message back to empty. That’s a change too. So the same Event runs again, finds it empty, and hides the pill.

Logic ComponentShow The MessageAttach to: Message Pill

Shows the message named by $message (a row of the Messages table) for a few seconds, then clears it.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
showSecondsNumberConstant2.6
When Created
WhenCreate
1Change Variable
Variable
$message
Value
""
2Set Visible
Visible
No
On Variable Changed · Message
WhenVariable ChangedGlobal Variable: message
1If$message != ""
Then
2Set Visible
Visible
Yes
3Start Recurring Timer
Timer Name
"hideMessage"
Repeat Every (s)
@self.showSeconds
Ring Once
Yes
4Otherwise
5Set Visible
Visible
No
On the Hide Message Timer
WhenRecurring TimerTimer name: hideMessage
1Change Variable
Variable
$message
Value
""

Make it, and attach it to the Message Pill. Its second When bar is Variable Changed, and its second window asks for the Global, message. The If in that Event has an Else. That’s the steps for when the If is false, and If, Else, Repeat and Region shows how to add one, in the paragraph that begins From Chapter 2. On the card, the Else is the step marked Otherwise. And "" means no words at all. Set Visible’s Visible is a Yes or No list, so pick the card’s.

Now PLACE THESE. Drag the Message Pill onto the Field. On a Screen UI Object, the placement’s Properties have a Screen Anchor, nine dots, and the top-left one is picked. Leave it there. Type 960 and -150 into its Screen offset (px). That’s 960 pixels from the left, the middle of the 1920-pixel screen, and 150 pixels down from the top. Under Rendering, type 10010 into its Z-Index, so it’s drawn over everything in the world.

Run the game. Low to high… appears in the pill as the first note sings. It stays 2.6 seconds, and then the pill is gone.

2.7 The Call

The opening is over, and openingFinished goes out. Now it’s time to pick the note you’ll hunt for, the one the whole stage turns on.

2.7.1 Call A Note

Call A Note lives on Game Rules, because the call belongs to the whole stage.

When the Field starts, it sets $calledPitch to 0. It’s a Global, like $message, so it keeps its value when the Field starts again. Left alone, an early press of Space on the next stage would sing the last stage’s call.

When openingFinished arrives, it sets $message to listen, and the pill says Listen… Next comes a tiny pause, 0.35 seconds on a timer named callNote, so the word comes before the sound.

When callNote rings, it picks one of the stage’s notes at random. Generate Random Integer picks a whole number from 0 to one less than the stage’s noteCount, and its answer is calledTurn. Stage 1 has two notes, so it picks 0 or 1. The stage’s firstNote plus that number is the call’s row in Notes, calledRow. Its midi becomes $calledPitch.

Last, it sends callRequested. Call A Note has chosen the call, and it doesn’t play it. Playing belongs to someone else.

Logic ComponentCall A NoteAttach to: Game Rules

After the opening, says "Listen…" and chooses the call, one of the stage's singing notes at random: $calledPitch is its pitch. Repeat The Call plays it.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
secondsBeforeCallNumberConstant0.35
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$calledPitch
Value
0
On Opening Finished
WhenopeningFinishedCustom Event
1Change Variable
Variable
$message
Value
'listen'
2Start Recurring Timer
Timer Name
"callNote"
Repeat Every (s)
@self.secondsBeforeCall
Ring Once
Yes
On the Call Note Timer
WhenRecurring TimerTimer name: callNote
1Declare Variable
Name
calledRow
Value
0
2Generate Random Integer
Action Name
calledTurn
Minimum
0
Maximum
data("Stages", $stage - 1, "noteCount") - 1
3Change Variable
Variable
calledRow
Value
data("Stages", $stage - 1, "firstNote") + calledTurn.value
4Change Variable
Variable
$calledPitch
Value
data("Notes", calledRow, "midi")
5Trigger Custom Event
Custom Event
callRequested

Make it with Add > Logic Component. Its value for $message is 'listen', in single quote marks. They do the same job as double ones, so type whichever the card shows. In the callNote Event, calledRow is a Declare Variable, a value for this one moment, as in Chapter 1. Its Generate Random Integer has an Action Name box too, as Bob While Waiting’s Generate Random Number did.

2.7.2 Repeat The Call

Repeat The Call plays the call whenever someone asks, with callRequested. Remember Play Piano Note? Here it sings the call.

First, it checks that there’s a call to play. Before Call A Note has chosen one, $calledPitch is still 0, so an early press of Space finds nothing and plays nothing.

If there is a call, it plays it. And it starts a quiet spell. A timer, repeatTheCall, rings every quietSeconds, 8 seconds, with its first ring 8 seconds away. Each time it rings, the call plays again, and repeatsLeft goes down by one. It starts at mostRepeats, 2. When none are left, the timer stops, and the call waits until somebody asks.

So if you never touch anything, you hear the call three times: once at the start, then twice more, 8 seconds apart. And every time you ask, the count and the quiet spell start over.

repeatsLeft has to last from one ring of the timer to the next. A Declare Variable is gone when its Event ends, so this one lives in a small Data Structure of the Logic’s own, Repeat The Call Data. Make it first, one Number.

Data StructureRepeat The Call Data

Object Data of Logic "Repeat The Call" (its state that lasts between Events).

repeatsLeftNumberdefault 0
Logic ComponentRepeat The CallAttach to: Game Rules

Plays the call whenever it is asked for, and again by itself after a quiet spell, at most twice in a row.

Attach toAny Object
DATA STRUCTURES
Repeat The Call Dataself.repeatTheCallData
SHARED VARIABLES
NameTypeKindStarting Value
quietSecondsNumberConstant8
mostRepeatsNumberConstant2
On Call Requested
WhencallRequestedCustom Event
1If$calledPitch > 0
Then
2Play Piano NoteFunction
pitch
$calledPitch
3Change Component Value
Component Value
self.repeatTheCallData.repeatsLeft
Value
@self.mostRepeats
4Start Recurring Timer
Timer Name
"repeatTheCall"
Repeat Every (s)
@self.quietSeconds
First Delay (s)
@self.quietSeconds
On the Repeat the Call Timer
WhenRecurring TimerTimer name: repeatTheCall
1Play Piano NoteFunction
pitch
$calledPitch
2Change Component Value
Component Value
self.repeatTheCallData.repeatsLeft
Operator
+=
Value
-1
3Ifself.repeatTheCallData.repeatsLeft <= 0
Then
4Stop Recurring Timer
Timer Name
"repeatTheCall"

Make it the same way, with Repeat The Call Data on its Data Structures card. Play Piano Note is in the Action List’s Functions group, as in Chapter 1, and its pitch box takes $calledPitch. The second Change Component Value has += in its own Operator list, as Change Variable does. Pick it, and the Value -1 is added on, so repeatsLeft goes down by one.

2.7.3 Game Rules, Changed

ObjectGame RulesAddDelete
Data Structure
From LogicRepeat The Call
Data StructureRepeat The Call Data
Data Structure
From LogicMake Way For The Notes
Data StructureMake Way For The Notes Data
Logic
Build The Stage
Call A Note
Repeat The Call
Play The Place's Tune
Make Way For The Notes
Shared Variables
singingNoteSinging Note

Game Rules gets four more Logic Components this chapter. Attach Call A Note and Repeat The Call now. Repeat The Call brings its Data Structure along, the first of the two on the card. The last two Logic Components come with the tune, at the end of the chapter.

Run the game. The opening, then Listen…, then a short pause, and one note sings alone. Wait. Eight seconds later it sings again, and once more after that. After that, quiet.

2.7.4 The Listen Button

ObjectListen Button
Transform
Presentation > SpaceWindow / UI
Presentation > Render LayerUI
Bounding Box > SizeX 400, Y 106
Sprite
Z Index Within Object-1
SpriteListen Button Art
Transform > SizeX 400, Y 106
Text / UI
TextHear it again
FontNunito
Font Size34
EmphasisExtra Bold
Color#3A1D0B
AlignmentCenter
RenderingVector
Transform > PositionX 34, Y 0
UI Interaction(as it is added)
Logic
Ask For The Call

The Listen Button is another Screen UI Object. Its picture, Listen Button Art, has a speaker drawn on its left end. The words Hear it again sit 34 pixels right of the button’s middle, in its Text / UI’s own Position, so they clear the speaker. Their color, #3A1D0B, is a dark brown. Leave UI Interaction attached, as it came.

2.7.5 Ask For The Call

The button only asks. Ask For The Call sends callRequested, and Repeat The Call decides what happens.

It listens for two things. UI Activated is a click or a tap on the button. Command Pressed is the Hear It Again Command, Space. Both send the same message.

Logic ComponentAsk For The CallAttach to: Listen Button

The Hear it again button, and its key (the Hear It Again Command, Space): asks for the call to be played again.

Attach toAny Object
On Hud Activated
WhenUI Activated
1Trigger Custom Event
Custom Event
callRequested
When Hearitagain Is Pressed
WhenCommand PressedCommand: Hear It Again
1Trigger Custom Event
Custom Event
callRequested

Make it, and attach it to the Listen Button. Its two When bars are UI Activated, which asks nothing more, and Command Pressed, which asks for its Command, Hear It Again. Their headings on the card are the names Myth gives them by itself, odd spelling and all.

Back to PLACE THESE. Drag the Listen Button onto the Field, and leave its Screen Anchor on the top-left dot. Type 230 and -70 into its Screen offset (px), and 10010 into its Z-Index.

Run the game. Wait for the call, then press Space. It sings again. Click Hear it again, and it sings again.

2.8 The Meadow’s Tune

Last of all comes the music, the soft tune that plays under everything else.

2.8.1 The Places Table

In Chapter 1, each Place got its name and its painting. Now it gets its tune.

Data StructurePlaceAddDelete

One place: its name, its backdrop and its tune.

nameTextdefault (empty)
backdropThing Spritedefault None
tuneThing Sounddefault None
Data TablePlacesData Structure: PlaceAddDelete
Row 0Place 1
nameSunny Meadow
backdropBackdrop Sunny Meadow
tuneMusic Sunny Meadow
Row 1Place 2
nameSeaside Beach
backdropBackdrop Seaside Beach
tuneMusic Seaside Beach
Row 2Place 3
nameAutumn Woods
backdropBackdrop Autumn Woods
tuneMusic Autumn Woods
Row 3Place 4
nameRed Rock Canyon
backdropBackdrop Red Rock Canyon
tuneMusic Red Rock Canyon
Row 4Place 5
nameCandy Hills
backdropBackdrop Candy Hills
tuneMusic Candy Hills
Row 5Place 6
nameSnowy Peaks
backdropBackdrop Snowy Peaks
tuneMusic Snowy Peaks
Row 6Place 7
nameJungle Falls
backdropBackdrop Jungle Falls
tuneMusic Jungle Falls
Row 7Place 8
namePirate Cove
backdropBackdrop Pirate Cove
tuneMusic Pirate Cove
Row 8Place 9
nameMushroom Grove
backdropBackdrop Mushroom Grove
tuneMusic Mushroom Grove
Row 9Place 10
nameVolcano Isle
backdropBackdrop Volcano Isle
tuneMusic Volcano Isle
Row 10Place 11
nameCloud Kingdom
backdropBackdrop Cloud Kingdom
tuneMusic Cloud Kingdom
Row 11Place 12
nameMoonlit Castle
backdropBackdrop Moonlit Castle
tuneMusic Moonlit Castle

Open Place, and add a third field, tune. It’s a Thing Sound, so pick Sound under Things, as for Pitch. The Places table grows a tune column by itself. A tune cell is a list of your Sounds, so pick the card’s tune in each of the twelve rows. Only Sunny Meadow plays in this chapter, but fill all twelve now. The Challenge plays another, and from Chapter 7, one stage leads into the next, and on into the next place.

2.8.2 Play The Place’s Tune

The Field starts, and the meadow’s tune drifts in. Play The Place’s Tune looks up the stage’s place by name, the way Play Piano Note looked up a pitch, and plays that place’s tune.

It doesn’t start the tune cold. It crossfades over crossfadeSeconds, 1.5 seconds, the old tune fading out as the new one fades in. If the same tune is playing already, it carries on. You won’t hear that until Chapter 7, when one stage leads into the next.

Logic ComponentPlay The Place's TuneAttach to: Game Rules

When a stage starts, crossfades to the tune of its place, the Sound its row of the Places table names (a tune already playing carries on). How loud the music is belongs to Make Way For The Notes.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
crossfadeSecondsNumberConstant1.5
When the Scene Starts
WhenScene Start
1Declare Variable
Name
placeRow
Value
0
2Change Variable
Variable
placeRow
Value
dataFind("Places", "name", data("Stages", $stage - 1, "place"))
3Check Data Row Exists
Action Name
placeFound
Table
Places
Row
placeRow
4IfplaceFound.value == true
Then
5Control Sound
Operation
Crossfade Music
Sound
data("Places", placeRow, "tune")
Crossfade Seconds
@self.crossfadeSeconds

Make it. On Control Sound, pick Crossfade Music in its first list, and a Crossfade Seconds box appears.

2.8.3 Make Way For The Notes

Play Piano Note has been sending noteSung every time a note sings. This is who it was for.

Listen to it first. A note sings, and the tune ducks away fast, in a quarter of a second. It stays down while the notes sing. A little after the last one, it creeps back, slowly, over 1.4 seconds.

Why build a fade at all? Set Volume jumps straight to a volume. It has no fade of its own, so this Logic makes one out of small steps. The tune’s whole loudness is musicVolume, 0.45. That’s a little under half, so it sits under the notes.

Two values run it, and they last from one tick of the fade to the next. So they live in its own Data Structure, Make Way For The Notes Data. targetLoudness is where the tune is heading, and loudness is where it is now. Neither is a volume. Each is a share of the tune’s whole loudness: 1 is all of it, and 0 is silence.

A timer, fadeMusic, does the moving. Every stepSeconds, 0.05 seconds, it moves loudness one step toward the target. Going down, the steps are big: each is 0.05 divided by secondsToFadeOut, 0.25, and that’s 0.2. Five steps, and it’s silent. Coming back, the steps are much smaller, so the return takes 1.4 seconds.

What brings the tune back? A second timer, musicBack, rings once, 1.6 seconds after a note sings, and sets the target to 1. Each new note starts it over. So through the whole opening, the tune stays down.

Data StructureMake Way For The Notes Data

Object Data of Logic "Make Way For The Notes" (its state that lasts between Events).

loudnessNumberdefault 1
targetLoudnessNumberdefault 1
Logic ComponentMake Way For The NotesAttach to: Game Rules

Keeps the music quiet under the notes (musicVolume), fades it down while a note sings and brings it back afterwards, so the notes are always heard clearly.

Attach toAny Object
DATA STRUCTURES
Make Way For The Notes Dataself.makeWayForTheNotesData
SHARED VARIABLES
NameTypeKindStarting Value
musicVolumeNumberConstant0.45
holdSecondsNumberConstant1.6
secondsToFadeOutNumberConstant0.25
secondsToFadeInNumberConstant1.4
stepSecondsNumberConstant0.05
When the Scene Starts
WhenScene Start
1Change Component Value
Component Value
self.makeWayForTheNotesData.loudness
Value
1
2Change Component Value
Component Value
self.makeWayForTheNotesData.targetLoudness
Value
1
3Set Volume
Audio Bus
Music
Volume
@self.musicVolume
On Note Sung
WhennoteSungCustom Event
1Change Component Value
Component Value
self.makeWayForTheNotesData.targetLoudness
Value
0
2Start Recurring Timer
Timer Name
"musicBack"
Repeat Every (s)
@self.holdSeconds
Ring Once
Yes
3Start Recurring Timer
Timer Name
"fadeMusic"
Repeat Every (s)
@self.stepSeconds
First Delay (s)
@self.stepSeconds
On the Music Back Timer
WhenRecurring TimerTimer name: musicBack
1Change Component Value
Component Value
self.makeWayForTheNotesData.targetLoudness
Value
1
2Start Recurring Timer
Timer Name
"fadeMusic"
Repeat Every (s)
@self.stepSeconds
First Delay (s)
@self.stepSeconds
On the Fade Music Timer
WhenRecurring TimerTimer name: fadeMusic
1Ifself.makeWayForTheNotesData.loudness > self.makeWayForTheNotesData.targetLoudness
Then
2Change Component Value
Component Value
self.makeWayForTheNotesData.loudness
Value
max(self.makeWayForTheNotesData.targetLoudness, self.makeWayForTheNotesData.loudness - @self.stepSeconds / @self.secondsToFadeOut)
3Ifself.makeWayForTheNotesData.loudness < self.makeWayForTheNotesData.targetLoudness
Then
4Change Component Value
Component Value
self.makeWayForTheNotesData.loudness
Value
min(self.makeWayForTheNotesData.targetLoudness, self.makeWayForTheNotesData.loudness + @self.stepSeconds / @self.secondsToFadeIn)
5Set Volume
Audio Bus
Music
Volume
self.makeWayForTheNotesData.loudness * @self.musicVolume
6Ifself.makeWayForTheNotesData.loudness == self.makeWayForTheNotesData.targetLoudness
Then
7Stop Recurring Timer
Timer Name
"fadeMusic"

Make its Data Structure first: two Numbers, both starting at 1. Then make the Logic, with that Data Structure on its Data Structures card, the way Repeat The Call has its own. Set Volume’s Audio Bus is a list, so pick Music. In fadeMusic, the three Ifs follow one another, none inside another, and Set Volume comes between the second and the third.

Now attach Play The Place’s Tune and Make Way For The Notes to Game Rules, as its card shows.

Run the game. The meadow’s tune comes in under the opening. It dips as the first note sings and stays low through the call. A breath later it swells back, softly.

Play it!

Watch, and listen, for each of these.

  • 0.6 seconds after the stage stands, Low to high… appears in the pill near the top. The lower note swells and sings, F3.
  • 0.52 seconds later, the upper note swells and sings, F4, the same note an octave higher. Each swell grows and shrinks back in 0.45 seconds.
  • A little over a second after Low to high… shows, Listen… takes its place, and after a short pause one note sings alone. That’s the call. Listen… clears 2.6 seconds after it showed.
  • Close the game and run it again a few times. The call isn’t always the same note, because Call A Note picks it at random each time.
  • Press Space, or click Hear it again. The call sings again. Before the call is chosen, neither one does anything.
  • Leave everything alone. The call comes back after 8 seconds, and once more 8 seconds later. Then it waits.
  • The meadow’s tune plays softly underneath. Each time a note sings, it ducks away quickly, and it comes back slowly a little while after.
How it works

The stage is built, and stageBuilt rings for every note at once. Each note waits its turn and sings. When the highest has sung, Call A Note picks the call, and Repeat The Call plays it whenever it’s asked. The pill shows whatever $message names. And the tune steps aside for every note.

Underneath it is one idea: the pieces talk in messages, and none of them needs to know who’s listening. Build The Stage doesn’t know what a note does with placed, and Ask For The Call doesn’t know how the call is played. Play Piano Note doesn’t know who hears noteSung. Each one sends its message and gets on with its job.

That’s why a new listener costs nothing. Make Way For The Notes came last, and nothing else had to change for it.

2.9 If It Didn’t Work

A chapter full of messages has one kind of slip above all others: a name that’s almost right. noteSung and noteSang look alike, and only one of them is heard. So when something stays silent, the first thing to check is the spelling.

The notes never sing? Perhaps stageBuilt never reaches them. Look at Build The Stage’s step 31, and at Sing In The Opening’s When bar. Both should say stageBuilt, letter for letter. Then look at the Singing Note’s Attached Logic. Is Sing In The Opening there, with Note State among its components?

Both notes sing at once? Look at the singTurn timer’s Repeat Every box. It needs self.noteState.turn * @self.secondsBetweenNotes, so each note waits for its own turn, and step 29 of Build The Stage is what gives each note that turn.

The notes sing, but the same pitch twice? Perhaps placed isn’t arriving, so each note still has its starting pitch of 60. Look at step 30’s Applies To, which should be newNote, and at the path in the placed Event’s Change Component Value.

No sound at all, though the notes swell? Play Piano Note can’t find the Sound, so look at the Pitches table. Do its noteSound cells name the note Sounds, Note F3 up to Note G5, and does each midi match its row’s name?

A note that swells up and stays big? Look at the swellFull timer, and at its Tween. Its Target should be 1.

The pill never shows? Perhaps the Message Pill isn’t placed, or its Z-Index is missing. Or perhaps the row name in Messages differs from what Logic sets: lowToHigh, with a capital T and a capital H.

Space does nothing? Is the key’s Command in a context that isn’t active? Hear It Again belongs in Field, and Field should say Active at Start.

No tune at all? Play The Place’s Tune finds the place by its name. So look at the Places table’s name column, where Sunny Meadow must be spelled as the Stages table spells it, capitals and all. Then look at its tune cell, which names Music Sunny Meadow.

The music never comes back? Look at musicBack, whose first step sets targetLoudness to 1. And in fadeMusic, look at the last If, which stops the timer only once loudness has arrived.

Each of these is one small fix. Make it, run the game, and listen again. And if you’d like to compare, the catch-up download in the Goal is this chapter’s game, finished.

Challenge

The opening is yours to tune. Open the Singing Note, and under Variables change secondsBetweenNotes from 0.52 to something slower, or swellScale from 1.32 to something bigger. Run it and listen. Then try stage 120, The whole choir, in Moonlit Castle: change $stage’s Default to 120 with the pencil on the Globals card, as in Chapter 1’s Challenge. Hear six notes sing low to high, to a different tune. Put everything back before Chapter 3.

Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).

Two notes sing in a meadow, and one of them is calling. In Chapter 3, the happy note arrives in the band.