Build a GameCreating Pitch Shot

5  Wrong Notes

Goal

By the end of this chapter, every shot is judged as it leaves the slingshot. A wrong note shakes and sings its own pitch, so you can hear it beside the call, and the pill says “Not that one. Listen again…”. The towers stand firm and throw a wrong shot back. After two wrong notes, a gold arrow points the way.

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 6 from it.

Pitch Shot, Chapter 5 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.

5.1 The Idea

Pull back, let go, and the happy note sails into the tower. It knocks against a post, drops and rolls. And the pill says what it has said every time since Chapter 4: Missed! Listen again…

Even when you heard the call right. Even when the note flew straight at it.

That’s because the game can’t yet tell which singing note you were after. And it’s harder than it sounds. A shot rarely meets its note cleanly. It may clip a post on the way, or a plank, and spring back before it ever touches the note it was flying at. Who decides what that shot was for?

Think of a game of pool. A good player calls the pocket before the shot, and the call stands, whatever the ball bumps on the way. Pitch Shot does the same, for you. The moment the note leaves the band, the game follows the path the dots have been drawing and finds the first singing note it comes near. That note is the aimed note. Its pitch goes into $aimedPitch, and a shot that comes near no note gets 0.

After that, the flight only decides when the answer comes. The aimed note answers once, when the shot touches it, strikes the tower, or ends. A wrong note, one that isn’t the call, shakes. And it sings its own pitch, so you can hear it beside the call and compare the two.

5.2 The Plan

Here’s how the pieces work together. A name in bold is a piece you’ll make, and a name in plain text is a Custom Event.

  • The stage is built -> Build The Stage writes down where each note sits.
  • You let go -> happyNoteLaunched -> Fly asks Judge The Shot -> $aimedPitch, and Fly picks how bouncy the note is.
  • The happy note strikes a block -> Tell Of A Strike, on each block -> towerStruck.
  • The happy note touches a singing note, or towerStruck, or shotEnded -> Answer The Shot, on every singing note -> only the aimed one answers. A wrong note sings, shakes, and sends wrongNoteReached.
  • Each wrongNoteReached -> Count The Shots counts it -> wrongNoteCounted -> Ask Again says Not that one. Listen again…, or, from the second, gives a hint -> hintWanted -> Point To The Call shows the Hint Arrow.
  • When $aimedPitch is 0, shotEnded -> Ask Again says Missed! Listen again…, as before.

You’ll build it in that order: first where the notes sit, then the judging, then the answer, the words and the arrow.

5.3 Set Up

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Hint Arrow Art
GLOBAL VARIABLES
$aimedPitchNumberstarts at 0
$wrongNotesNumberstarts at 0
SCENE Field: PLACE THESE
ObjectXY
Hint Arrow9.65.4

ART names the arrow’s picture, which the Starter Kit already brought.

GLOBAL VARIABLES has two. $aimedPitch is the aimed note’s pitch, and $wrongNotes counts the wrong notes of the stage. Both are Numbers that start at 0. Add them from the canvas’s Globals card, as you made $shots.

PLACE THESE waits for the Hint Arrow, and the chapter comes back to it at the end.

5.4 Where Every Note Sits

To follow a path and see which note it passes, the game has to know where every singing note is. Build The Stage works that out already, note by note, with Where A Note Sits. It uses the answer once, to make the note. And then it forgets it.

So now it writes each spot down, in the Notes table, where Judge The Shot and the arrow can read it later.

Nothing new shows in this part, or the next. The first answer comes when the singing notes learn to speak.

5.4.1 Note Spot, Changed

Data StructureNote SpotAddDelete

One singing note of a stage: the pitch it sings and the tower it sits in, and where it sits.

pitchTextdefault (empty)
midiNumberdefault 0
towerNumberdefault 0
xNumberdefault 0
yNumberdefault 0

Add the two new fields at the bottom of the list, x and y, both of them Numbers.

5.4.2 The Notes Table, Changed

Data TableNotesData Structure: Note SpotAddDelete
IMPORT CSV470 rows, from a file
FIRST ROWS3 of 470, to compare
Row 0Stage 1 note 1
pitchF3
midi53
tower0
x0
y0
Row 1Stage 1 note 2
pitchF4
midi65
tower0
x0
y0
Row 2Stage 2 note 1
pitchF sharp3
midi54
tower0
x0
y0

There’s nothing to type here, and nothing to import again. Notes is shaped by Note Spot, so the two new fields are two new columns already, and every cell starts at 0. That’s right. The table is a plan, and a plan doesn’t need to know where its notes will stand. Build The Stage fills them in while the game runs.

5.4.3 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
27Set Data Cell
Table
Notes
Row
row
Column
"x"
Value
noteSpot.x
28Set Data Cell
Table
Notes
Row
row
Column
"y"
Value
noteSpot.y
29Create Instance
Object to Create
@self.singingNote
X
noteSpot.x
Y
noteSpot.y
Variable Name
newNote
30Change Variable
Variable
newNote.noteRow
Value
data("Stages", $stage - 1, "firstNote") + local.index
31Change Variable
Variable
newNote.turn
Value
local.index
32Trigger Custom Event on Object
Applies to
newNote
Custom Event
placed
33Trigger Custom Event
Custom Event
stageBuilt

Two new steps go between Where A Note Sits and Create Instance. Each is a Set Data Cell, which writes into one cell of a table while the game runs. It’s one of the Advanced Mode Actions, and it has a box for each of the card’s lines: Table, Row, Column and Value. The first writes noteSpot.x into the x column of this note’s row, and the second writes noteSpot.y into the y column.

Pick Notes in its Table list. Its Column box takes "x" or "y", with quotes, as the card shows. The running game’s copy changes. The table saved in your project keeps the 0s you gave it.

The two steps go in the middle of a line, so use Disconnect Incoming on Create Instance, as Actions, one at a time shows near its end.

5.5 Calling the Shot

Now the judging. It happens in one moment, as the note leaves the band.

5.5.1 Shot Sums, Changed

Data StructureShot SumsAddDelete

The working values of the Functions that aim the happy note: the pull (Aim At), and the path it will fly (Judge The Shot).

pullXNumberdefault 0
pullYNumberdefault 0
pullLengthNumberdefault 0
pullScaleNumberdefault 0
tNumberdefault 0
pathXNumberdefault 0
dropNumberdefault 0
pathYNumberdefault 0
noteRowNumberdefault 0
noteXNumberdefault 0
noteYNumberdefault 0
gapXNumberdefault 0
gapYNumberdefault 0
distanceToNoteNumberdefault 0

Ten new Numbers, all starting at 0. They’re Judge The Shot’s working values, kept on the happy note, the way Aim At keeps its own there.

5.5.2 Judge The Shot

Picture the dots again, the flip-book of Chapter 3. Thirty-three pages, a little apart. Judge The Shot reads the same flight, but it turns the pages much faster: every pathStepSeconds, 0.004 seconds of flight, up to longestPathSeconds, 5 seconds in all. At each page, it asks one question of every singing note in the stage. Am I near you?

Each page is a point on the path, worked out by the dots’ own sum. For each note, the page asks the Notes table where it sits, which is what Build The Stage wrote down. The straight line from the point to the note is distanceToNote. The rest are steps on the way.

A note is near when distanceToNote is under reach, 0.64 meters. That number isn’t a guess. The happy note’s Collider is a circle with a Radius of 0.3 meters, and the singing note’s will have 0.34. Add them, and 0.64 is the gap at which the two circles touch.

The If at the end joins five checks in one Condition box. Each keeps out a wrong answer. The first, $aimedPitch == 0, means the first note found wins, and no note further along can take its place. The other four ask that the note is within reach, and that the point is still above the grass and inside the field. A path that has already hit the ground or left the screen finds nothing more.

FunctionJudge The Shot

Follows the path the aiming dots show, from the aim at the launch speed, and sets $aimedPitch to the pitch of the first singing note it comes within reach of (0 if it reaches none). A shot is judged as it leaves the slingshot.

PARAMETERS
NameTypeStarts at
shotObjectStructural
Required Components
Slot NameshotSums
Component TypeData Structure
Required SchemaShot Sums
reachNumber0.64
pathStepSecondsNumber0.004
longestPathSecondsNumber5
groundHeightNumber1.3
fieldLeftNumber-1.2
fieldRightNumber20.4
ACTIONS
1Change Variable
Variable
$aimedPitch
Value
0
2Change Component Value
Component Value
shot.shotSums.t
Value
0
3Repeatround(longestPathSeconds / pathStepSeconds)times
Each time
4Change Component Value
Component Value
shot.shotSums.t
Operator
+=
Value
pathStepSeconds
5Change Component Value
Component Value
shot.shotSums.pathX
Value
$aimX + $launchVX * shot.shotSums.t
6Change Component Value
Component Value
shot.shotSums.drop
Value
$gravity / 2 * shot.shotSums.t * shot.shotSums.t
7Change Component Value
Component Value
shot.shotSums.pathY
Value
$aimY + $launchVY * shot.shotSums.t - shot.shotSums.drop
8Repeatdata("Stages", $stage - 1, "noteCount")times
Each time
9Change Component Value
Component Value
shot.shotSums.noteRow
Value
data("Stages", $stage - 1, "firstNote") + local.index
10Change Component Value
Component Value
shot.shotSums.noteX
Value
data("Notes", shot.shotSums.noteRow, "x")
11Change Component Value
Component Value
shot.shotSums.noteY
Value
data("Notes", shot.shotSums.noteRow, "y")
12Change Component Value
Component Value
shot.shotSums.gapX
Value
shot.shotSums.pathX - shot.shotSums.noteX
13Change Component Value
Component Value
shot.shotSums.gapY
Value
shot.shotSums.pathY - shot.shotSums.noteY
14Change Component Value
Component Value
shot.shotSums.distanceToNote
Value
hypot(shot.shotSums.gapX, shot.shotSums.gapY)
15If$aimedPitch == 0 && shot.shotSums.distanceToNote < reach && shot.shotSums.pathY > groundHeight && shot.shotSums.pathX > fieldLeft && shot.shotSums.pathX < fieldRight
Then
16Change Variable
Variable
$aimedPitch
Value
data("Notes", shot.shotSums.noteRow, "midi")

Make it with Add > Function. Its shot is like Aim At’s happyNote: Object at the end of the Type list. Open its pencil on Start, and add Shot Sums under Required Components, with the Slot Name shotSums. Its working values live on shot.

There are two Repeats, one inside the other, like Build The Stage’s towers and floors. The outer one walks the path. The inner one goes through the notes at each point. The If at step 15 belongs inside the inner one, with its five checks joined by && in the Condition box.

5.5.3 Fly, Changed

Logic ComponentFlyAttach to: Happy NoteAddDelete

Flings the happy note from the aim when it is launched, lets it fly, land and roll, and ends the shot when it has stood still for a moment, left the field or flown too long.

Attach toAny Object
DATA STRUCTURES
Shot Sumsself.shotSums
Fly Dataself.flyData
COMPONENTS
self.colliderCollider
SHARED VARIABLES
NameTypeKindStarting Value
stillSpeedNumberConstant0.25
stillSecondsNumberConstant0.8
longestFlightSecondsNumberConstant9
rollingHeightNumberConstant1.7
rollingSlowdownNumberConstant2.5
fieldLeftNumberConstant-1.2
fieldRightNumberConstant20.4
fieldBottomNumberConstant-1.2
checkSecondsNumberConstant0.05
groundHeightNumberConstant1.3
screenLeftNumberConstant0.4
screenRightNumberConstant18.8
dustBelowNoteNumberConstant0.2
dustLayerNumberConstant60
hitBounceNumberConstant0.15
springBackBounceNumberConstant0.4
On Happy Note Launched
WhenhappyNoteLaunchedCustom Event
1Set Logic State
Logic Component
Pull Back
State
Disable
2Change Component Value
Component Value
self.collider.fixed
Value
false
3Change Component Value
Component Value
self.transform.gravity
Value
true
4Set Velocity
X Velocity
$launchVX
Y Velocity
$launchVY
5Judge The ShotFunction
groundHeight
@self.groundHeight
fieldLeft
@self.fieldLeft
fieldRight
@self.fieldRight
shot
self
6If$aimedPitch == $calledPitch
Then
7Set Bounciness
Value
@self.hitBounce
8Otherwise
9Set Bounciness
Value
@self.springBackBounce
10Control Sound
Sound
Sound Launch
11Start Recurring Timer
Timer Name
"watchFlight"
Repeat Every (s)
@self.checkSeconds
First Delay (s)
@self.checkSeconds
On the Watch Flight Timer
WhenRecurring TimerTimer name: watchFlight
1Declare Variable
Name
speed
Value
0
2Change Component Value
Component Value
self.flyData.flightSeconds
Operator
+=
Value
@self.checkSeconds
3Change Variable
Variable
speed
Value
hypot(self.transform.velocity.x, self.transform.velocity.y)
4Ifspeed < @self.stillSpeed
Then
5Change Component Value
Component Value
self.flyData.stillFor
Operator
+=
Value
@self.checkSeconds
6Otherwise
7Change Component Value
Component Value
self.flyData.stillFor
Value
0
8Ifself.transform.position.y < @self.rollingHeight
Then
9Set Drag (Slow Down)
Amount
@self.rollingSlowdown
10Ifself.flyData.stillFor >= @self.stillSeconds || self.flyData.flightSeconds >= @self.longestFlightSeconds || self.transform.position.x < @self.fieldLeft || self.transform.position.x > @self.fieldRight || self.transform.position.y < @self.fieldBottom
Then
11Stop Recurring Timer
Timer Name
"watchFlight"
12Burst Particles
Particles
Landing Dust
Position Relative To
World
X
clamp(self.transform.position.x, @self.screenLeft, @self.screenRight)
Y
max(self.transform.position.y - @self.dustBelowNote, @self.groundHeight)
Z Index Offset
@self.dustLayer
13Trigger Custom Event
Custom Event
shotEnded
14Destroy Instance

Fly calls Judge The Shot the moment it sets the note’s speed. Nothing else has happened yet. Then it picks a bounce. A shot at the call keeps hitBounce, 0.15, and settles softly against its tower. Every other shot gets springBackBounce, 0.4, so it springs back off the towers. The towers won’t have it.

Set Bounciness changes the Collider’s Bounciness while the game runs, and its one box, Value, takes the Shared Variable. Add hitBounce and springBackBounce under the Shared Variables card. Both live there.

Steps 5 to 9 go between Set Velocity and Control Sound. So use Disconnect Incoming on Control Sound first. On the Judge The Shot block, shot is a list, so pick self. Its groundHeight, fieldLeft and fieldRight take Fly’s Shared Variables, with @self. before each name, and the Else at step 8 belongs to the If at step 6.

Run the game, and shoot at the tower. It leaves the same way. But when it hits a post, it springs back farther than it used to. The pill still says Missed! every time. The game has called the shot. Nobody answers yet.

5.6 A Wrong Note Answers

Now the singing notes learn to hear the shot.

5.6.1 One Tag for Every Block

A shot can strike any of the five kinds of block. Each should ring the same bell. They already share the Tag Block, from Chapter 1. Each will carry Tell Of A Strike, so every block can report a touch in the same way.

5.6.2 Tell Of A Strike

When the happy note first touches a block, the block rings one bell: towerStruck. That’s all it does.

Logic ComponentTell Of A StrikeAttach to: Wood Post, Wood Plank, Stone Cube, Stone Plank, Ice Cube

When the happy note strikes this block, tells everyone that the shot has reached the towers.

Attach toAny Object
When Starting to Hit Happy Note
WhenCollision EnterObject: Happy Note
1Trigger Custom Event
Custom Event
towerStruck

Make it, and attach it to each of the five blocks: Wood Post, Wood Plank, Stone Cube, Stone Plank and Ice Cube. Its Event is a Collision Enter, which runs once, the moment two bodies first touch. Its Collision Target, on the Event’s own bar, is Happy Note, so a block touching another block rings nothing. Events: the When bars has it in the paragraph that begins From Chapter 5. The heading When Starting to Hit Happy Note is Myth’s own name for it.

5.6.3 The Five Blocks, Changed

ObjectWood PostAddDelete
Transform
Bounding Box > SizeX 0.24, Y 1.12
Physics > Allow RotationOn
Physics > Spin Drag0.35
Collider
Shape and Transform > SizeX 0.24, Y 1.12
Physics > Bounciness (0–1)0.04
Physics > Friction (Grip)0.75
Fixed (no editor field)On
Sprite
Scene Depth Draw Order30
SpriteWood Post Art
Transform > SizeX 0.24, Y 1.12
Tags
Object TagsBlock
Logic
Tell Of A Strike

The Wood Post now carries Tell Of A Strike. Its Tag stays Block. Attach the Logic to the other four kinds too, so every post, plank and cube can report a touch.

The other four cards differ only in their names and numbers. Here’s the Ice Cube’s.

ObjectIce CubeAddDelete
Transform
Bounding Box > SizeX 0.56, Y 0.56
Physics > Mass0.7
Physics > Allow RotationOn
Physics > Spin Drag0.2
Collider
Shape and Transform > SizeX 0.56, Y 0.56
Physics > Bounciness (0–1)0.08
Physics > Friction (Grip)0.2
Fixed (no editor field)On
Sprite
Scene Depth Draw Order30
SpriteIce Cube Art
Transform > SizeX 0.56, Y 0.56
Tags
Object TagsBlock
Logic
Tell Of A Strike

5.6.4 The Singing Note, Changed

ObjectSinging NoteAddDelete
Transform
Bounding Box > SizeX 0.84, Y 0.83
Physics > Physics ObjectOff
Collider
CollisionSensor
Shape and Transform > ShapeCircle
Shape and Transform > Radius0.34
Sprite
Scene Depth Draw Order35
SpriteSinging Note Art
Transform > SizeX 0.84, Y 0.83
Data Structure
From LogicAnswer The Shot, Bob While Waiting, Sing In The Opening
From LogicBob While Waiting, Sing In The Opening
Data StructureNote State
Data Structure
From LogicAnswer The Shot
Data StructureAnswer The Shot Data
Logic
Bob While Waiting
Sing In The Opening
Answer The Shot

The singing note gets a body too, a circle with a Radius of 0.34 meters. Attach a Collider from Physics, pick Circle in its Shape list, and type the Radius.

Its Collision is three buttons at the top of the Collider, above Shape and Transform: Solid, Sensor and Off. Pick Sensor. A sensor feels a touch, but it never pushes back. So the happy note passes right through a singing note, and the note still knows it was touched. Leave Physics Object Off under its Transform. Logic moves this note, so physics mustn’t push it away from its spot.

You’ll attach Answer The Shot at the end of the next part, once it’s made. It uses Note State, which the note already carries. It also brings Answer The Shot Data. Their From Logic lines fill in when you attach the Logic to the note.

5.6.5 Answer The Shot

Every singing note in the stage hears about every shot, but only one should answer, the aimed one, and only once.

A shot can reach its note three ways. It can touch it. It can strike the tower first and spring back. Or it can end somewhere else. Answer The Shot listens for all three, and turns each into one message to the note itself, shotArrived. So there’s one answer, whichever way the shot came.

That answer is an Event with two Modes, Waiting and Answered. Each new shot sets it to Waiting. In Waiting, the note asks: is my pitch the aimed one? Its pitch in Note State is the note’s midi number, the same kind of number as $aimedPitch, so the two compare. If not, the shot wasn’t for me, and I do nothing. If it is, I switch to Answered at once, so a second touch in the same shot is ignored.

And if this note isn’t the call, it answers out loud. It sings its own pitch with Play Piano Note, and it shakes, 0.06 meters to each side, every 0.05 seconds, for half a second. Last, it sends wrongNoteReached.

And if it is the call? For now it switches to Answered, and that’s all. The card’s description mentions Step 06. That’s this book’s Chapter 6, where the called note gets its answer.

The shake is a timer. Each time shake rings, the note flips to its other side, and Tween glides it there, over 0.05 seconds. shakeLeft counts the time down from shakeSeconds. When it’s used up, the note glides home, to homeX, the spot it remembered when it was made, and the timer stops.

Answer The Shot Data keeps the note’s home and the shake between timer rings. Make it before the Logic, then pick it beside Note State on the Data Structures card.

Data StructureAnswer The Shot Data

Object Data of Logic "Answer The Shot" (its state that lasts between Events).

homeXNumberdefault 0
shakeSideNumberdefault 1
shakeLeftNumberdefault 0
Logic ComponentAnswer The ShotAttach to: Singing Note

When the shot reaches this singing note and the shot was judged to be aimed at it, the note answers: it sings its own pitch and shakes, and if it is not the call it says so. (Step 06 adds what the called note does.) It reads pitch (its Note State), set by Sing In The Opening, when the note hears "placed".

Attach toAny Object
DATA STRUCTURES
Note Stateself.noteState
Answer The Shot Dataself.answerTheShotData
SHARED VARIABLES
NameTypeKindStarting Value
shakeMetersNumberConstant0.06
shakeSecondsNumberConstant0.5
shakeStepSecondsNumberConstant0.05
When Created
WhenCreate
1Change Component Value
Component Value
self.answerTheShotData.homeX
Value
self.transform.position.x
On Happy Note Launched
WhenhappyNoteLaunchedCustom Event
1Switch Mode
Event
On Shot Arrived
Mode
Waiting
When Starting to Hit Happy Note
WhenCollision EnterObject: Happy Note
1Trigger Custom Event on Object
Custom Event
shotArrived
On Tower Struck
WhentowerStruckCustom Event
1Trigger Custom Event on Object
Custom Event
shotArrived
On Shot Ended
WhenshotEndedCustom Event
1Trigger Custom Event on Object
Custom Event
shotArrived
On Shot Arrived
WhenshotArrivedCustom Event
ModeWaiting (Starting Mode)
1Ifself.noteState.pitch == $aimedPitch
Then
2Switch Mode
Event
On Shot Arrived
Mode
Answered
3Ifself.noteState.pitch != $calledPitch
Then
4Play Piano NoteFunction
pitch
self.noteState.pitch
5Change Component Value
Component Value
self.answerTheShotData.shakeLeft
Value
@self.shakeSeconds
6Start Recurring Timer
Timer Name
"shake"
Repeat Every (s)
@self.shakeStepSeconds
First Delay (s)
0
7Trigger Custom Event
Custom Event
wrongNoteReached
On Shot Arrived
WhenshotArrivedCustom Event
ModeAnswered
(no Actions)
On the Shake Timer
WhenRecurring TimerTimer name: shake
1Change Component Value
Component Value
self.answerTheShotData.shakeSide
Value
-self.answerTheShotData.shakeSide
2Change Component Value
Component Value
self.answerTheShotData.shakeLeft
Operator
+=
Value
-@self.shakeStepSeconds
3Ifself.answerTheShotData.shakeLeft > 0
Then
4Tween
Value
self.transform.position.x
Target
self.answerTheShotData.homeX + self.answerTheShotData.shakeSide * @self.shakeMeters
Duration
@self.shakeStepSeconds
5Otherwise
6Tween
Value
self.transform.position.x
Target
self.answerTheShotData.homeX
Duration
@self.shakeStepSeconds
7Stop Recurring Timer
Timer Name
"shake"

Make it with Add > Logic Component. Its Data Structures card lists Note State and Answer The Shot Data. The Shared Variables hold its shake and timing, on the Singing Note. Its Declare Variables last for one Event run.

Build the shotArrived Event and its two Modes before the happyNoteLaunched Event. That Event’s Switch Mode picks Waiting from a list. Waiting comes first, and Answered stays empty. The card draws shotArrived twice, but it’s one Event, as the band’s Step was in Chapter 3.

Three Events send shotArrived. Each is a Trigger Custom Event on Object with no Applies To, so the note sends it to itself and no other note hears. Tween’s Value is self.transform.position.x, the note’s place across.

Now attach Answer The Shot to the Singing Note.

Run the game, and shoot at the note that isn’t the call. It shakes, and you hear its pitch, right after the shot gets there. Shoot at the other one, and it stays still. That’s the call. The pill still says Missed! after both. The words are next.

5.7 The Right Words

5.7.1 The Messages Table, Changed

Data TableMessagesData Structure: MessageAddDelete
Row 0lowToHigh
wordsLow to high…
Row 1listen
wordsListen…
Row 2missed
wordsMissed! Listen again…
Row 3notThatOne
wordsNot that one. Listen again…
Row 4hintHigher
wordsListen: the note you want is higher.
Row 5hintLower
wordsListen: the note you want is lower.

Three new rows. Copy and paste the words from this page. notThatOne ends with the same … as missed, one character.

5.7.2 Count The Shots, Changed

Logic ComponentCount The ShotsAttach to: Game RulesAddDelete

Counts the shots of this stage in $shots (the Shots Count at the top shows it), and how many of them reached a wrong note in $wrongNotes.

Attach toAny Object
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$shots
Value
0
2Change Variable
Variable
$wrongNotes
Value
0
On Wrong Note Reached
WhenwrongNoteReachedCustom Event
1Change Variable
Variable
$wrongNotes
Operator
+=
Value
1
2Trigger Custom Event on Object
Custom Event
wrongNoteCounted
On Happy Note Launched
WhenhappyNoteLaunchedCustom Event
1Change Variable
Variable
$shots
Operator
+=
Value
1

Count The Shots now counts wrong notes too. $wrongNotes goes back to 0 when the Field starts, and each wrongNoteReached adds one.

Next it rings a bell of its own, wrongNoteCounted, with Trigger Custom Event on Object and no Applies To, so only Game Rules hears it. Why not let Ask Again listen for wrongNoteReached itself? It needs the new count. If both heard wrongNoteReached at once, Ask Again might look before the count went up. This way, the counting comes first, and the words after it.

5.7.3 Ask Again, Changed

Logic ComponentAsk AgainAttach to: Game RulesAddDelete

After a shot that did not find the called note, tells the player and plays the call again: "Missed! Listen again…" when the shot reached no singing note, "Not that one. Listen again…" when it reached a wrong one, and after a few wrong notes which way the call is.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
secondsAfterMissNumberConstant0.6
secondsAfterWrongNoteNumberConstant1
hintAfterWrongNotesNumberConstant2
On Shot Ended
WhenshotEndedCustom Event
1Change Variable
Variable
$message
Value
'missed'
2Start Recurring Timer
Timer Name
"callAgain"
Repeat Every (s)
@self.secondsAfterMiss
Ring Once
Yes
1If$aimedPitch == 0
Then
2Change Variable
Variable
$message
Value
'missed'
3Start Recurring Timer
Timer Name
"callAgain"
Repeat Every (s)
@self.secondsAfterMiss
Ring Once
Yes
On Wrong Note Counted
WhenwrongNoteCountedCustom Event
1If$wrongNotes >= @self.hintAfterWrongNotes
Then
2If$calledPitch > $aimedPitch
Then
3Change Variable
Variable
$message
Value
'hintHigher'
4Otherwise
5Change Variable
Variable
$message
Value
'hintLower'
6Trigger Custom Event
Custom Event
hintWanted
7Otherwise
8Change Variable
Variable
$message
Value
'notThatOne'
9Start Recurring Timer
Timer Name
"callAgain"
Repeat Every (s)
@self.secondsAfterWrongNote
Ring Once
Yes
On the Call Again Timer
WhenRecurring TimerTimer name: callAgain
1Trigger Custom Event
Custom Event
callRequested

Ask Again has two kinds of miss to answer now, and it tells them apart.

When shotEnded comes, it looks at $aimedPitch first. Is it 0? Then the shot went near no note, and it’s Missed! Listen again…, as before. If not, a singing note has the answer, and Ask Again leaves it to that note.

When wrongNoteCounted comes, it’s a wrong note. On the first, the pill says Not that one. Listen again… From the second wrong note on, hintAfterWrongNotes, 2, it gives a hint instead of those words. Is the call higher than the note you reached? The pill says the note you want is higher, and if not, lower. And it sends hintWanted, for the arrow.

Either way, the call sings again secondsAfterWrongNote later, 1 second, a little longer than after a miss. You hear the wrong note’s pitch first, and then the call.

In the first Event, delete the old steps, and build the new If in their place, then the two steps inside it.

In the second Event, there’s an If inside an If. Follow the card’s bars for the nesting. The pitch check belongs inside the hint-count check, with its own Else. Use Add inside Then and Add inside Else. The callAgain timer belongs after the outer If, on the Event’s main line.

Run the game. Shoot at the wrong note. It shakes and sings, and the pill says Not that one. Listen again… A second later, the call. Now you can hear them side by side.

5.8 An Arrow That Points

5.8.1 The Hint Arrow

ObjectHint Arrow
Transform
Bounding Box > SizeX 0.6, Y 0.7
Sprite
Scene Depth Draw Order55
SpriteHint Arrow Art
Transform > SizeX 0.6, Y 0.7
Data Structure
From LogicPoint To The Call
Data StructurePoint To The Call Data
Logic
Point To The Call

The Hint Arrow is a gold arrow, pointing up, made as an Empty Object with its Sprite and sizes. Its Scene Depth Draw Order of 55 draws it in front of the notes and the dots.

5.8.2 Point To The Call

The arrow is placed once, and it hides until it’s wanted. When hintWanted comes, it goes looking for the wrong note you reached.

It reads the Notes table, as Judge The Shot did, and goes through the stage’s notes until it finds the row whose midi is $aimedPitch. That row’s x and y are where the wrong note sits.

Then it turns. A higher note always sits higher in its tower, since Chapter 1. So if the call is higher, the arrow points up, at 0 degrees, and sits above the note. If it’s lower, the arrow turns to 180, upside down, and sits below. Either way it goes besideMeters, 0.78 meters, to the left of the note, and shows itself.

It bobs while it shows, up and down by 0.08 meters, one swing every 0.52 seconds, the way the singing notes bob. After showSeconds, 4.5 seconds, a second timer stops the bob and hides it.

Point To The Call Data keeps the bob’s center and direction between swings, as Stretch The Band Data kept its prong. Make it first; the Logic brings it to the arrow when attached.

Data StructurePoint To The Call Data

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

baseYNumberdefault 0
swingDirectionNumberdefault 1
Logic ComponentPoint To The CallAttach to: Hint Arrow

When a hint is wanted, shows this arrow beside the wrong note the shot reached, pointing up or down towards the called note, bobbing gently, for a few seconds.

Attach toAny Object
DATA STRUCTURES
Point To The Call Dataself.pointToTheCallData
SHARED VARIABLES
NameTypeKindStarting Value
besideMetersNumberConstant0.78
aboveMetersNumberConstant0.58
bobMetersNumberConstant0.08
secondsPerSwingNumberConstant0.52
showSecondsNumberConstant4.5
When Created
WhenCreate
1Set Visible
Visible
No
On Hint Wanted
WhenhintWantedCustom Event
1Declare Variable
Name
noteRow
Value
0
2Declare Variable
Name
noteX
Value
0
3Declare Variable
Name
noteY
Value
0
4Repeatdata("Stages", $stage - 1, "noteCount")times
Each time
5Change Variable
Variable
noteRow
Value
data("Stages", $stage - 1, "firstNote") + local.index
6Ifdata("Notes", noteRow, "midi") == $aimedPitch
Then
7Change Variable
Variable
noteX
Value
data("Notes", noteRow, "x")
8Change Variable
Variable
noteY
Value
data("Notes", noteRow, "y")
9If$calledPitch > $aimedPitch
Then
10Set Rotation
Degrees
0
11Change Component Value
Component Value
self.pointToTheCallData.baseY
Value
noteY + @self.aboveMeters
12Otherwise
13Set Rotation
Degrees
180
14Change Component Value
Component Value
self.pointToTheCallData.baseY
Value
noteY - @self.aboveMeters
15Set Position
X
noteX - @self.besideMeters
Y
self.pointToTheCallData.baseY
16Set Visible
Visible
Yes
17Start Recurring Timer
Timer Name
"bob"
Repeat Every (s)
@self.secondsPerSwing
First Delay (s)
0
18Start Recurring Timer
Timer Name
"hideHint"
Repeat Every (s)
@self.showSeconds
Ring Once
Yes
On the Bob Timer
WhenRecurring TimerTimer name: bob
1Tween
Value
self.transform.position.y
Target
self.pointToTheCallData.baseY + @self.bobMeters * self.pointToTheCallData.swingDirection
Duration
@self.secondsPerSwing
Ease
Quad in Out
2Change Component Value
Component Value
self.pointToTheCallData.swingDirection
Value
-self.pointToTheCallData.swingDirection
On the Hide Hint Timer
WhenRecurring TimerTimer name: hideHint
1Stop Recurring Timer
Timer Name
"bob"
2Set Visible
Visible
No

You’ve built this before. The bob works like Bob While Waiting, Tween with Ease Quad in Out, and swingDirection flips each time. Set Visible has a Yes / No list, as before. Set Rotation has one box, Degrees. The If at step 9 comes after the Repeat, outside it. Join it on the Event’s main line.

Point To The Call brings its own Data Structure to the Hint Arrow. Its Shared Variables set the bob and its timing; the Declare Variables find the note for this one hint.

Attach it to the Hint Arrow, and then PLACE THESE. Drag the Hint Arrow onto the Field at 9.6 and 5.4. It moves itself before it shows, so the spot only matters for the moment before.

Run the game. Shoot at a wrong note twice. The second time, a gold arrow appears beside it, bobbing, pointing toward the call, and the pill tells you which way to listen.

Play it!

Listen, and watch, for each of these.

  • Shoot at the wrong note. It shakes side to side for half a second and sings its own pitch. The pill says Not that one. Listen again…, and a second later the call sings.
  • Listen to the two together. The wrong note, then the call, close enough to compare.
  • The happy note springs back off the tower, farther than in Chapter 4. Every block stays where it stood.
  • Clip a post on the way to the wrong note. The wrong note still answers, the moment the post is struck.
  • Shoot at the wrong note again. Now a gold arrow bobs beside it, pointing up or down toward the call, and the pill says Listen: the note you want is higher. or …lower. After 4.5 seconds, the arrow is gone.
  • Shoot where no note is, high over the tower. Missed! Listen again…, and the call 0.6 seconds after the dust puffs, as before.
  • Shoot at the call itself. The happy note settles softly against the tower, and nothing answers yet. The call comes back after its quiet spell.
How it works

The stage writes down where its notes sit. You let go, and Fly asks Judge The Shot to call the shot along the dots’ path. The aimed note’s pitch goes into $aimedPitch, and then the flight plays out. A block it strikes rings towerStruck. Every singing note hears the shot arrive, but only the aimed one answers, and only once. A wrong note sings and shakes. Count The Shots counts it, and Ask Again finds the words and, from the second, the arrow.

Underneath it is one idea: decide early, answer later. The shot is judged in one moment, from the path, before the note has flown a meter. After that, nothing the note bumps into can change what it was for. The answer only waits for its moment.

That’s why a shot that clips the tower still finds its note. Like a called pocket in pool, the call stands.

5.9 If It Didn’t Work

This chapter judges in one place and answers in another, so a slip can hide between them. Watch which half goes quiet. If no note ever answers, look at the judging. If the wrong one does, look at the answer.

No note ever answers? Perhaps $aimedPitch never leaves 0. Look at Build The Stage’s two Set Data Cells. Is Notes picked in Table, and is Column "x" or "y", with quotes? Then look at Fly. Is Judge The Shot joined into its line, between Set Velocity and Control Sound, and not left Unattached?

Every note shakes, or the wrong one? Look at the first If of Answer The Shot’s Waiting. It compares self.noteState.pitch with $aimedPitch, and the If inside it compares it with $calledPitch, with != between them.

A note answers twice in one shot? Look at step 2 of Waiting. It should switch On Shot Arrived to Answered, in its second list.

The happy note bumps a singing note like a wall? Its Collision is Solid. Pick Sensor.

A wrong note shakes, but the pill still says Missed!? Look at Ask Again’s first Event. Its steps sit inside an If that asks $aimedPitch == 0.

The arrow never shows? Is the Hint Arrow placed in the Field? Is hintWanted spelled the same in Ask Again and in Point To The Call’s When bar? Or perhaps the arrow points the wrong way. Look at its two Set Rotations, 0 under the If and 180 under the Else.

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 hints are yours to tune. Under Variables on Game Rules, change hintAfterWrongNotes from 2 to 1, and the arrow comes after the first wrong note. On the Singing Note, change shakeMeters under Variables from 0.06 to 0.15. A wrong note shakes its head harder. Or change springBackBounce under the Happy Note’s Variables from 0.4 to 0.8, and the tower throws a wrong shot back across the meadow. Which feels kindest to someone still learning to listen? Put everything back before Chapter 6.

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

A wrong note answers, and the right one waits. In Chapter 6, the tower comes down.