- Learn
- Build a Game
- Crystal Cascade
- 6 Game Over and Results
6 Game Over and Results
Let the well fill, watch the stack turn to stone, and read the record of your game. Then choose PLAY AGAIN for a fresh well.
The game as it is at the end of this chapter. Click the game to play.
If your game and this chapter part ways, open this chapter’s finished project to compare. You can also start Chapter 7 from it.
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.
6.1 The Idea
The stack reaches the rim. A new piece has nowhere to stand, and the bright crystals begin turning to gray stone from the floor upward. The well has been lost.
For a moment, let that change be seen. Then a glass panel opens over a dark veil. It keeps the score, lines and level, but also the small record of that game: its time, cascades, twists and best combo.
PLAY AGAIN waits underneath. Press Enter, and the well is empty once more. Your saved best stays on the device, while the new score starts fresh.
The hard part is the boundary between those games. A Global can survive restarting a Scene. A count that should start over needs an owner that resets it. We’ll give each part that responsibility rather than making one long reset routine.
The ending has a boundary too. Counting must stop when play ends, before the results arrive. The panel reads a finished record; it doesn’t keep scoring while you look at it.
6.2 The Plan
- A blocked new piece, or a lock wholly above the visible well -> Check Top Out changes the Game State and sends
gameOver. - That message freezes the record, hides the moving pictures and starts the stone wave.
- Open Results waits -> it switches input contexts, opens the panel and sends
resultsShown. - The panel shows the record. Confirm or a click on PLAY AGAIN runs Start A New Game.
- Restart Scene recreates the Objects. Each owner prepares its own values when the Scene starts.
6.3 Set Up
Continue from Chapter 5 or its catch-up project. The Starter Kit already holds the veil, panel, button pictures and menu sounds.
In Input Manager, use Default’s Edit button to change Input Context Name to Play, then Save Input Context. Keep its gameplay Commands there. Use Add Input Context to make Menus, select its row, and add Confirm with both card keys. Set its startup switch to Inactive at Start. These input contexts are groups of Commands you can switch on and off together.
Add Game Over alongside Playing in Available Game States. That’s the game’s phase, separate from which Commands can be heard. A result panel needs menu input while the Game State remains Game Over.
The placements wait until their Objects are made. Follow their order: background first, button last. These are Screen UI Objects, so their positions count from the card’s screen anchors.
Enter, SpaceMove Left, Move Right, Rotate Clockwise, Rotate Counterclockwise, Rotate 180, Soft Drop, Hard Drop, HoldConfirmPlaying, Game Over6.4 Finding the End
When is the glass full?
Add crystalsInView to Piece. Lock Piece will count how many crystals of a locking shape reached the visible well. A new shape gets a separate test.
The falling piece: its kind (1-7, Piece Kinds), rotation (0 up, 1 right, 2 down, 3 left), the well cell of its 4 x 4 box's bottom-left corner (column from the left, row from the bottom), how long it rests before it locks (lockDelaySeconds, read by the piece and its crystals), how long it takes to fall one row (fallSeconds), and which way the player is moving it (moveDirection: -1 left, 1 right, 0 still). landingRow: the lowest row its box can fall to (Find Landing Row). hardDropRows: the rows its last hard drop crossed. lowestRow: the lowest row it has reached; lockResets: the moves or rotations that restarted its lock delay since touching down there; hasTouchedDown: whether it has touched down since reaching that lowest row; isLockDelayOver: whether its lock delay ran out before it landed again. heldKind: the piece put away with Hold (0 none); hasHeld: Hold was used for this piece. lastMoveWasRotation: its last successful action was a rotation; lastKickTest: the Kicks test (0-4) that rotation used; twist: what it locked as (0 none, 1 mini, 2 full twist). crystalsInView: how many of its crystals locked inside the visible well (0: a lock out).
6.4.1 Spawn Piece
A new piece now asks about its starting place before its controls wake. A fit lets it enter as before. A failure sends pieceBlocked instead, leaving no new shape for gravity to pull through the stack.
The green branch keeps the old work: waking the controls, checking the room below and finding the landing row. The red Enable Actions at the top move inside that branch. After the starting-place test, successful spawning belongs inside Then. Otherwise only sends pieceBlocked.
On Next Piece Due also checks the Game State. Once play has ended, a late request for another piece can’t deal one into the finished well.
Puts a new piece at the top of the well: the next kind from the Queue (or the held piece, from Hold Piece), rotation up, its box in the hidden rows, then one row down into view if it fits.
Any Objectself.piece- Logic Component
Hold Piece
- Logic Component
Piece Gravity
- Logic Component
Move Piece
- Logic Component
Rotate Piece
- Logic Component
Hard Drop Piece
- Component Value
self.piece.rotation- Value
0
- Component Value
self.piece.lastMoveWasRotation- Value
false
- Component Value
self.piece.lastKickTest- Value
0
- Component Value
self.piece.column- Value
@self.startColumn
- Component Value
self.piece.row- Value
data('Well Layout', 0, 'rows') - 4
- Action Name
roomBelowNewPiece- Action Name
roomForNewPiece- piece
self- columnShift
0- rowShift
0- rowShift
-1
roomBelowNewPiece.value == true- Component Value
self.piece.row- Value
self.piece.row - 1
- piece
self
- Custom Event
pieceMoved
roomForNewPiece.value == true- Logic Component
Hold Piece
- Logic Component
Piece Gravity
- Logic Component
Move Piece
- Logic Component
Rotate Piece
- Logic Component
Hard Drop Piece
- Action Name
roomBelowNewPiece- piece
self- columnShift
0- rowShift
-1
roomBelowNewPiece.value == true- Component Value
self.piece.row- Value
self.piece.row - 1
- piece
self
- Custom Event
pieceMoved
- Custom Event
pieceBlocked
- Component Value
self.piece.kind- Value
data('Queue', 0, 'piece')
- Custom Event
pieceDealt
- Custom Event
spawnPiece
- Action Name
isPlaying- Game State
Playing
isPlaying.value == true- Component Value
self.piece.kind- Value
data('Queue', 0, 'piece')
- Custom Event
pieceDealt
- Custom Event
spawnPiece
6.4.2 Lock Piece
A piece can also lock entirely in the hidden rows. Lock Piece starts crystalsInView afresh, then counts a crystal when its row number is less than visibleRows. Rows count upward from the floor, so the visible rows have the smaller numbers. The well reads it after checking for cleared lines.
Writes the falling piece's four crystals into the Well, takes the piece out of play, tells the well it locked and the Well Cells that crystals joined.
Any Objectself.piece- Name
lockedKind- Value
0
- Timer Name
"gravity"
- Timer Name
"lockDelay"
- Variable
lockedKind- Value
self.piece.kind
- Component Value
self.piece.crystalsInView- Value
0
4times- Action Name
lockedCrystal- kind
self.piece.kind- rotation
self.piece.rotation- crystal
local.index + 1
- Action Name
lockedCell- column
self.piece.column + lockedCrystal.column- row
self.piece.row + lockedCrystal.row
- Name
cellIndex- Value
lockedCell.value
- Table
Well- Row
cellIndex- Column
"kind"- Value
lockedKind
(self.piece.row + lockedCrystal.row) < data('Well Layout', 0, 'visibleRows')- Component Value
self.piece.crystalsInView- Value
self.piece.crystalsInView + 1
- Sound
Lock Click
- Logic Component
Piece Gravity- State
Disable
- Logic Component
Move Piece- State
Disable
- Logic Component
Rotate Piece- State
Disable
- Logic Component
Hard Drop Piece- State
Disable
- Logic Component
Hold Piece- State
Disable
- Component Value
self.piece.hasHeld- Value
false
- Custom Event
holdChanged
- Custom Event
pieceLocked
- Custom Event
crystalsLocked
6.4.3 Check Top Out
Check Top Out hears both paths. On pieceBlocked, the game ends. A lock with no visible crystals ends it only when that lock cleared no line. The line check comes first.
Declare two Shared Variables: well and piece. Choose Glass Well for well and Falling Piece for piece, so each reference reaches the right structure. Set them on Game Rules, where those Variables already name the same resources for scoring. Attach Check Top Out after scoring. The Game Rules card below shows the order.
On toppedOut, the Logic changes the Game State, plays Game Over Jingle and sends gameOver. Then it disables itself. The same ending won’t be announced again while the panel waits to open.
Ends the game when the player tops out: a new piece with no room where it appears, or a piece that locked wholly in the hidden rows without clearing a line (a lock out).
Any ObjectFalling PieceGlass Well- Game State
Playing
- Custom Event
toppedOut
@self.well.wellState.linesCleared == 0 && @self.piece.piece.crystalsInView == 0- Custom Event
toppedOut
- Game State
Game Over
- Sound
Game Over Jingle
- Intensity
@self.shakeStrength- Seconds
@self.shakeSeconds
- Custom Event
gameOver
- Logic Component
Check Top Out- State
Disable
The listener is ready. Before trying it, give the record a clean beginning and a definite end.
6.5 A Record That Belongs to One Game
The next game deserves its own counters.
Add the marked fields to Scoring State. They keep the time and special clears, plus isNewBest for the ending notice. Game Rules already carries this structure under the Name scoring, so keep that name.
What Game Rules scored: isBackToBackReady, the last line clear was difficult (4 lines or a twist); isBackToBack, the last lock was a difficult clear right after another; lastClearRow, the Clear Names row of the last lock; combo, the clears in a row less one (-1: none yet; 1 and up score); bestCombo, the longest of the game. isNewBest: the game that just ended beat the best kept on this device. cascades: four-line clears this game; twists: locks that were twists; startSeconds: $playTime when play began; playedSeconds: how long the game was played.
Cascades get a flag in Clear Names. Add the field to Clear Names Entry, then use this chapter’s changed table card and its CSV. It replaces the table’s rows with the same names and rewards plus the new isCascade values.
One row of the "Clear Names" Data Table. Its fields are the columns of the table. Every kind of lock: row = twist * 5 + lines (twist 0 none, 1 mini, 2 full twist). name: what the player is told (empty: nothing); points: before the level multiplier; sound: what plays; isDifficult: a clear that counts for back-to-back (4 lines, or any twist with lines); allClearPoints: the bonus when such a clear empties the well (row = lines). isCascade: a four-line clear, counted on the results.
0No0NoSINGLE100Clear SingleNo800NoDOUBLE300Clear DoubleNo1200No6.5.1 Count Clears
A game can end with several kinds of clear behind it. Count Clears keeps the cascades and twists in separate fields, ready for the panel. The scoring row and piece already know which kind happened.
Its gameOver listener disables the counting Logic. A message that arrives after that boundary can’t add another clear to this game’s record.
Counts the game's cascades (four-line clears) and twists for the results.
Any Objectself.scoringFalling Piecedata('Clear Names', self.scoring.lastClearRow, 'isCascade') == true- Component Value
self.scoring.cascades- Value
self.scoring.cascades + 1
@self.piece.piece.twist > 0- Component Value
self.scoring.twists- Value
self.scoring.twists + 1
- Logic Component
Count Clears- State
Disable
6.5.2 Time The Game
The engine’s play clock already measures time. Time The Game remembers its reading at Scene Start, then subtracts that reading when gameOver arrives. The difference is this game’s playedSeconds.
That clock can stop while play is paused. There’s no second stopwatch to keep in step with it when you add Pause later. Keep its Scoring State name as scoring.
Times the game with the engine's play clock ($playTime, which stops while the game is paused): from when play begins to when the game ends.
Any Objectself.scoring- Component Value
self.scoring.startSeconds- Value
global('playTime')
- Component Value
self.scoring.playedSeconds- Value
global('playTime') - self.scoring.startSeconds
6.5.3 Count Lines, Score Points and Count Level
Restarting a Scene doesn’t mean every Global becomes its starting value again. Each owner now sets its own count at Scene Start: Count Lines resets lines, Score Points resets score, and Count Level resets currentLevel.
Their gameOver Events stop further counting. Keep the existing clear and level Events; these are additional boundaries around the work you already built.
Adds the lines the well cleared to the lines count (the Project Global lines).
Any ObjectGlass Well- Variable
$lines- Value
0
- Variable
$lines- Operator
+=- Value
@self.well.wellState.linesCleared
- Logic Component
Count Lines- State
Disable
Adds to the score (the Project Global score): each lock's clear (Clear Names points x level), and soft and hard drop rows.
Any Objectself.scoringGlass WellFalling Piece- Variable
$score- Value
0
- Name
clearPoints- Value
0
- Component Value
self.scoring.lastClearRow- Value
@self.piece.piece.twist * 5 + @self.well.wellState.linesCleared
- Component Value
self.scoring.isBackToBack- Value
false
@self.well.wellState.linesCleared > 0data('Clear Names', self.scoring.lastClearRow, 'isDifficult') == true- Component Value
self.scoring.isBackToBack- Value
self.scoring.isBackToBackReady
- Component Value
self.scoring.isBackToBackReady- Value
true
- Component Value
self.scoring.isBackToBackReady- Value
false
- Variable
clearPoints- Value
data('Clear Names', self.scoring.lastClearRow, 'points')
- Variable
clearPoints- Operator
*=- Value
$currentLevel
self.scoring.isBackToBack == true- Variable
clearPoints- Operator
*=- Value
@self.backToBackTimes
- Variable
$score- Operator
+=- Value
floor(clearPoints)
- Custom Event
clearScored
- Name
allClearBonus- Value
0
- Variable
allClearBonus- Value
data('Clear Names', @self.well.wellState.linesCleared, 'allClearPoints')
- Variable
allClearBonus- Operator
*=- Value
$currentLevel
- Variable
$score- Operator
+=- Value
allClearBonus
- Variable
$score- Operator
+=- Value
@self.softDropPoints
- Variable
$score- Operator
+=- Value
@self.piece.piece.hardDropRows * @self.hardDropPoints
- Logic Component
Score Points- State
Disable
Raises the level (the Project Global currentLevel) by one for every linesPerLevel lines, up to lastLevel, and says so.
Any Object- Variable
$currentLevel- Value
1
- Name
reachedLevel- Value
0
- Variable
reachedLevel- Value
1 + floor(global('lines') / @self.linesPerLevel)
reachedLevel > @self.lastLevel- Variable
reachedLevel- Value
@self.lastLevel
reachedLevel > global('currentLevel')- Variable
$currentLevel- Value
reachedLevel
- Custom Event
levelUp
- Logic Component
Count Level- State
Disable
Count Combo gets the ending listener too. Its Scoring State fields start from their defaults on the recreated Game Rules Object, so the next game can build a new chain.
Keeps the combo (clears in a row) and the best combo of the game, and scores comboPoints x combo x level from the second clear in a row.
Any Objectself.scoringGlass Well@self.well.wellState.linesCleared > 0- Component Value
self.scoring.combo- Value
self.scoring.combo + 1
self.scoring.combo > self.scoring.bestCombo- Component Value
self.scoring.bestCombo- Value
self.scoring.combo
self.scoring.combo >= 1- Variable
$score- Operator
+=- Value
@self.comboPoints * self.scoring.combo * global('currentLevel')
- Custom Event
comboScored
- Component Value
self.scoring.combo- Value
-1
- Logic Component
Count Combo- State
Disable
6.5.4 Deal Pieces
Queue is a table the game changes while playing. Deal Pieces now resets it at Scene Start, before its Create Event fills the first bags. The queueLength memory and the waiting rows can therefore agree about where a new game begins.
Keeps the Queue table full of pieces from shuffled bags of seven: deals two bags at the start, takes the first piece off whenever one is dealt, and adds a bag whenever fewer than seven are left.
Any Objectself.dealPiecesData- Table
Queue
- Component Value
self.dealPiecesData.queueLength- Value
0
2times- firstPlace
self.dealPiecesData.queueLength
- Component Value
self.dealPiecesData.queueLength- Operator
+=- Value
dataCount('Piece Kinds')
- Custom Event
nextPieceDue
- Name
placeRow- Value
0
- Variable
placeRow- Value
0
placeRow < (self.dealPiecesData.queueLength - 1)- Name
placeKind- Value
data('Queue', placeRow + 1, 'piece')
- Table
Queue- Row
placeRow- Column
"piece"- Value
placeKind
- Variable
placeRow- Operator
+=- Value
1
- Table
Queue- Row
placeRow- Column
"piece"
- Component Value
self.dealPiecesData.queueLength- Operator
-=- Value
1
self.dealPiecesData.queueLength < dataCount('Piece Kinds')- firstPlace
self.dealPiecesData.queueLength
- Component Value
self.dealPiecesData.queueLength- Operator
+=- Value
dataCount('Piece Kinds')
- Custom Event
queueChanged
6.5.5 Keep Bests
Here is the save promised in Chapter 5. When the game ends, Keep Bests compares score with bestMarathonScore. A better score updates that Global, saves it under the same persistent key used to load it, and marks isNewBest.
The best belongs to the device, rather than to one run of the Scene. A new game resets its score while that best remains. The notice reads isNewBest after the comparison has settled whether this game beat the saved score.
Reads the best score kept on this device into the Project Global bestMarathonScore when the game starts, and keeps a better score when a game ends.
Any Objectself.scoring- Action Name
savedBest- Key
"bestMarathon"- Default
0
- Variable
$bestMarathonScore- Value
savedBest.value
global('score') > global('bestMarathonScore')- Variable
$bestMarathonScore- Value
global('score')
- Action Name
savedBestWrite- Key
"bestMarathon"- Value
global('bestMarathonScore')
- Component Value
self.scoring.isNewBest- Value
true
The counters can stop. Their finished record waits while the pictures show what an ending means.
6.6 The Wave of Stone
The color leaves the stack from the bottom upward.
Add the stone field. Both Crystal Frames Entry and the table need it before a cell can choose the new frame. It names the gray frame the Well Cells will use. The stack’s kinds stay in Well; this is a change in how they are drawn.
One row of the "Crystal Frames" Data Table. Its fields are the columns of the table. Frames of the sprite "Crystal Blocks" that are not a kind of piece. One row. shatter: a crystal in a full line. stone: a crystal when the game is over.
896.6.1 Show Well Cell
Every cell hears gameOver at the same time, but waits according to its row. A low row has a shorter wait. A higher row begins later, so the same Event makes a wave rather than changing the whole stack at once.
When turnToStone rings, a filled cell shows the stone frame. An empty cell stays empty. The row’s place controls the delay, while shownKind tells the cell whether there’s a crystal to change.
One cell of the well: it finds its cell from where it is placed, then shows the crystal stored there whenever the well changes; a crystal a lock just put there pops, and a crystal in a full line bursts into shards.
Any Objectself.showWellCellData- Action Name
myCell- x
self.transform.position.x- y
self.transform.position.y
- Action Name
thisCellIndex- column
myCell.column- row
myCell.row
- Component Value
self.showWellCellData.cellIndex- Value
thisCellIndex.value
- Custom Event
wellChanged
self.showWellCellData.shownKind == 0 && data('Well', self.showWellCellData.cellIndex, 'kind') != 0- Horizontal Scale
@self.popScale- Vertical Scale
@self.popScale
- Value
self.transform.scale- Target
1- Duration
@self.popSeconds- Ease
Quad Out
- Particles
Lock Glint- Z Index Offset
6
- Custom Event
wellChanged
data('Well', self.showWellCellData.cellIndex, 'isClearing') == true && self.showWellCellData.isShowingShatter == false- Particles
Crystal Shards- Z Index Offset
6- Tint
data('Piece Kinds', data('Well', self.showWellCellData.cellIndex, 'kind') - 1, 'color')
- Component Value
self.showWellCellData.isShowingShatter- Value
data('Well', self.showWellCellData.cellIndex, 'isClearing')
- Component Value
self.showWellCellData.shownKind- Value
data('Well', self.showWellCellData.cellIndex, 'kind')
self.showWellCellData.isShowingShatter == true- Frame
data('Crystal Frames', 0, 'shatter')
- Frame
self.showWellCellData.shownKind
- Name
waitSeconds- Value
0
- Name
rowFromBottom- Value
0
- Variable
rowFromBottom- Value
floor(self.showWellCellData.cellIndex / data('Well Layout', 0, 'columns'))
- Variable
waitSeconds- Value
@self.stoneDelaySeconds + rowFromBottom * @self.stoneRowSeconds
- Timer Name
"turnToStone"- Repeat Every (s)
waitSeconds- Ring Once
Yes
self.showWellCellData.shownKind != 0- Frame
data('Crystal Frames', 0, 'stone')
6.6.2 Show Piece Crystal and Show Preview Crystal
The moving shape and previews have finished their jobs. Add their gameOver Events to hide them, leaving the stone stack visible behind the coming panel. The next Scene recreates those Objects for the new game.
One crystal of the falling piece: whenever the piece moves, it goes to its cell (or, with showsLanding, to its cell in the landing row: the ghost) and shows the piece's color; it hides in the two hidden rows, and the piece's own crystals fade while it rests before locking.
Any ObjectFalling Piece- Which
Value- Value
self.transform.opacity
- Action Name
thisCrystal- kind
@self.piece.piece.kind- rotation
@self.piece.piece.rotation- crystal
self.crystal
- Name
cellColumn- Value
@self.piece.piece.column + thisCrystal.column
- Name
cellRow- Value
@self.piece.piece.row + thisCrystal.row
self.showsLanding == true- Variable
cellRow- Value
@self.piece.piece.landingRow + thisCrystal.row
- Action Name
thisCellMiddle- column
cellColumn- row
cellRow
- X
thisCellMiddle.x- Y
thisCellMiddle.y
- Frame
data('Piece Kinds', @self.piece.piece.kind - 1, 'frame')
cellRow < data('Well Layout', 0, 'visibleRows')- Visible
Yes
- Visible
No
self.showsLanding == false- Action Name
roomBelowDimmingPiece- piece
@self.piece- columnShift
0- rowShift
-1
roomBelowDimmingPiece.value == false- Value
self.transform.opacity- Target
@self.dimmedAlpha- Duration
@self.piece.piece.lockDelaySeconds
- Visible
No
One crystal of a preview: the piece in its Queue place, or the held piece in the Hold box (Preview Slots), centered in its box at that box's cell size; hidden while there is no piece; the held piece dims once Hold was used for the falling piece.
Any Objectself.showPreviewCrystalDataFalling Piece- Name
boxScale- Value
0
- Variable
boxScale- Value
data('Preview Slots', self.slot, 'cellSize')
- Variable
boxScale- Operator
/=- Value
data('Well Layout', 0, 'cellSize')
- Horizontal Scale
boxScale- Vertical Scale
boxScale
- Custom Event
queueChanged
data('Preview Slots', self.slot, 'isHold') == true- Component Value
self.showPreviewCrystalData.previewKind- Value
@self.piece.piece.heldKind
- Component Value
self.showPreviewCrystalData.previewKind- Value
data('Queue', data('Preview Slots', self.slot, 'queuePlace'), 'piece')
self.showPreviewCrystalData.previewKind > 0- Action Name
myPlace- kind
self.showPreviewCrystalData.previewKind- crystal
self.crystal- slot
self.slot
- X
myPlace.x- Y
myPlace.y
- Frame
data('Piece Kinds', self.showPreviewCrystalData.previewKind - 1, 'frame')
data('Preview Slots', self.slot, 'isHold') == true && @self.piece.piece.hasHeld == true- Alpha
@self.usedAlpha
- Visible
Yes
- Visible
No
- Custom Event
queueChanged
- Visible
No
Let the stone settle. Give the player that beat before opening the glass panel.
6.7 The Panel Opens
The choice needs somewhere to wait before a player can choose it, even when there’s only one button.
Panel State remembers which choice is selected and whether the panel is open. The small Panel Declarations pack brings this structure and the declarations for Choose Panel Choice, Panel Choice and Show When Panel Choice. Their Events and Actions are empty.
Install it now, before making those resources. Use File > Install Myth Pack, choose the downloaded pack, review its four resources, then Install. If the window asks, select I trust this pack’s author to enable Install. This pack is part of the recipe.
The pack prepares which panel fields these references can read. Keep those declarations as they arrive, and build every Event and Action from the cards. You still make Results Panel and add its Panel State component below. Its default chosen value selects the first choice.
What a panel of choices keeps: chosen, the choice chosen now (0 = the top one); isOpen, the panel is open and its choices can be chosen.
6.7.1 Choose Panel Choice
The glow needs to travel when another button is chosen. Choose Panel Choice tells the panel where it belongs, and the pictures respond together.
Open the prepared Choose Panel Choice Function. Its panel Parameter already requires Panel State under the name panelState. That’s the default expression name for this structure. This Function will also serve panels with more choices in later chapters.
Makes choice the chosen choice of a panel (its Panel State chosen) and tells the highlights. The only writer of chosen.
panelStateData StructurePanel State- Component Value
panel.panelState.chosen- Value
choice
- Custom Event
panelChoiceChanged
6.7.2 Panel Choices
Panel Choices hears panelOpened on its own Object, marks the panel open and announces its chosen choice. Closing the panel clears that open flag and returns its choice to the first one. Make the Logic and attach it when Results Panel is ready.
A panel of choices: opens on its chosen choice when told (panelOpened; the top one unless the Logic that opens it chose another) and closes when told (panelClosed), going back to its top choice.
Any Objectself.panelState- Component Value
self.panelState.isOpen- Value
true
- panel
self- choice
self.panelState.chosen
- Component Value
self.panelState.isOpen- Value
false
- Component Value
self.panelState.chosen- Value
0
- Custom Event
panelChoiceChanged
6.7.3 Show With Results
Pictures and words can wait together. Show With Results hides its Object when created, then shows it at shownAlpha when resultsShown arrives. The veil needs a gentler alpha.
One part of the results panel: hidden during play, shown at shownAlpha when the results open.
Any Object- Visible
No
- Alpha
self.shownAlpha
- Visible
Yes
Make Results Veil, Results Panel and Results Title. Give the panel its Panel State component and both listed Logics. The veil’s Sprite Tint is set in its Sprite Properties; its later Shown Alpha override belongs to the placement.
Window / UICenterUIX 128, Y 128140White Pixel#03213FX 1920, Y 1080Window / UICenterUIX 128, Y 128141Large Glass PanelX 900, Y 705Panel StateWindow / UITop CenterUIX 128, Y 48GAME OVERNunito69Bold1.5CenterWrap to Width8706.7.4 Open Results
Open Results hears gameOver and starts its delay. When that timer rings, it turns Menus on and Play off, opens Results Panel, then announces resultsShown. The ending gets time to be seen. Only after that quiet delay can the panel accept the choice that begins another game.
Declare resultsPanel with Type Object and Kind Constant. The card accepts Objects with Panel State; here, choose Results Panel as its Object Kind. That concrete Object already carries Panel State and is the only panel this Logic will open. Open Results sends it a message without reading its fields, so this declaration needs none of the pack’s prepared field access. On Game Rules, set the value to the Results Panel resource too. Add Check Top Out, Open Results, Count Clears and Time The Game in the order shown here.
Opens the results panel resultsDelaySeconds after the game ends (resultsShown).
Any ObjectObjects with Panel State- Input Context
Play
- Input Context
Menus- Active
No
- Timer Name
"resultsDelay"- Repeat Every (s)
@self.resultsDelaySeconds- Ring Once
Yes
- Input Context
Menus
- Input Context
Play- Active
No
- Applies to
@self.resultsPanel- Custom Event
panelOpened
- Custom Event
resultsShown
Scoring StateGlass WellFalling PieceResults PanelPlace the veil, panel and title using the setup’s first lines. They hide themselves during play. The rest of the list will fill the panel with the record and its button.
6.8 Reading the Finished Game
The familiar counts can keep their familiar templates.
Make the SCORE, LINES and LEVEL label-and-value pairs from their cards. Their Globals supply the values. Across these pairs, the label and template change while the text styling stays familiar. Compare those changing fields first, then check the shared styling. A label stays a word, and its matching value stays a number.
Window / UICenterUIX 128, Y 48SCORENunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{$score:,}Nunito36Bold1.5RightWrap to Width390Window / UICenterUIX 128, Y 48LINESNunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{$lines}Nunito36Bold1.5RightWrap to Width390Window / UICenterUIX 128, Y 48LEVELNunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{$currentLevel}Nunito36Bold1.5RightWrap to Width3906.8.1 Show New Best
New Best starts hidden. Show New Best reads Game Rules’ isNewBest when resultsShown arrives, after Keep Bests has compared and saved. A game that didn’t beat the saved score leaves the notice hidden.
Set rules to Game Rules on this text Object. It reads the finished comparison; it doesn’t make another one after the best has already changed.
Says "NEW PERSONAL BEST!" on the results panel when the game that ended beat the best kept on this device.
Any ObjectGame Rules- Visible
No
@self.rules.scoring.isNewBest == true- Visible
Yes
Window / UITop CenterUIX 128, Y 48NEW PERSONAL BEST!Nunito36Bold#FFF3B01.5CenterWrap to Width870Game Rules6.8.2 Show Game Record
The remaining values live in Scoring State. Make Game Record Text, then make the TIME, CASCADES, TWISTS and BEST COMBO value Objects shown below. Add one Data Structure component to each yourself. Choose Game Record Text as its resource and set Name to shown. This is the plain component on each Object card.
What a results value shows, copied from the record of the game when the results open: timeMinutes and timeSeconds (the time played), cascades, twists, bestCombo. Each value text reads the fields it needs through its template.
Show Game Record copies the finished record into that structure when resultsShown arrives. It also divides playedSeconds for the clock. Each text template then chooses the fields it needs. TIME reads both clock fields; the other values each read one count. The labels name those counts beside them.
In Show Game Record, declare rules with Type Object, Kind Constant, and Object Kind Game Rules.
On each value Object, add and name the component before attaching Show Game Record. Its DATA STRUCTURES entry describes that same shown component. Keep that single copy. Choose Game Rules for rules in the Object’s Shared Variables section.
Return to the Logic to build the expressions that read self.shown. Its named component is ready for those values. Show With Results handles visibility alongside it. The four value Objects need this component and reference; their fixed labels do not.
For a results value: copies the record of the game from Game Rules into its Game Record Text (timeMinutes, timeSeconds, cascades, twists, bestCombo), which the template of the text reads.
Any Objectself.shownGame Rules- Component Value
self.shown.timeMinutes- Value
floor(@self.rules.scoring.playedSeconds / 60)
- Component Value
self.shown.timeSeconds- Value
@self.rules.scoring.playedSeconds - self.shown.timeMinutes * 60
- Component Value
self.shown.cascades- Value
@self.rules.scoring.cascades
- Component Value
self.shown.twists- Value
@self.rules.scoring.twists
- Component Value
self.shown.bestCombo- Value
@self.rules.scoring.bestCombo
Window / UICenterUIX 128, Y 48TIMENunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{timeMinutes}:{timeSeconds:02.2}Nunito36Bold1.5RightWrap to Width390Game Record TextGame RulesWindow / UICenterUIX 128, Y 48CASCADESNunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{cascades}Nunito36Bold1.5RightWrap to Width390Game Record TextGame RulesWindow / UICenterUIX 128, Y 48TWISTSNunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{twists}Nunito36Bold1.5RightWrap to Width390Game Record TextGame RulesWindow / UICenterUIX 128, Y 48BEST COMBONunito30Bold#BFEAFF1.5Wrap to Width390Window / UICenterUIX 128, Y 48{bestCombo}Nunito36Bold1.5RightWrap to Width390Game Record TextGame RulesPlace the record texts and New Best in the setup list’s order. Each row gives its label and value, so you can check one pair before moving to the next. The button is the last part.
6.9 Another Game
PLAY AGAIN gives the ending somewhere to go.
6.9.1 Start A New Game
The Function is short because the owners already know how to begin. Restart Scene recreates the Objects, runs their Scene Start work and prepares their starting state. It leaves the saved best on the device.
Starts the same game again: Restart Scene. Each part of the game sets itself back at Scene Start.
6.9.2 Panel Choice
The same button can answer the keyboard or the mouse. Panel Choice gives both routes the same announcement, choiceRun, so the button’s job stays separate from how you chose it.
Keep the prepared panel declaration with its Panel State component restriction. Choose Results Panel as its value on the button Object. choice identifies which choice this button represents. This panel has one, so its starting value already picks PLAY AGAIN.
One choice of a panel: while the panel is open, Confirm runs it when it is the chosen choice; the mouse over it chooses it, and a click on it runs it. Running it plays Menu Select and tells its own button (choiceRun), whose other Logic does the work of the choice.
Any ObjectObjects with Panel State@self.panel.panelState.isOpen == true && @self.panel.panelState.chosen == @self.choice- Sound
Menu Select
- Custom Event
choiceRun
@self.panel.panelState.chosen != @self.choice- panel
@self.panel- choice
@self.choice
- Sound
Menu Bubble
- panel
@self.panel- choice
@self.choice
- Sound
Menu Select
- Custom Event
choiceRun
6.9.3 Play Again
Now give that choice its meaning. It listens for choiceRun and calls Start A New Game. A different button can use the same choosing Logic and give that message a different listener.
What the PLAY AGAIN choice does: starts a new game of the same mode (Start A New Game).
Any ObjectMake PLAY AGAIN Button with its sensor Collider and the listed Logics. The mouse needs that shape. It doesn’t need to push anything in the well.
Window / UIBottom CenterUIX 128, Y 128146Aero ButtonX 442.8, Y 77.76SensorX 418.8, Y 56.76Results Panel6.9.4 Show When Panel Choice
The brighter picture is a separate glow. Show When Panel Choice checks whether the panel is open and its chosen value matches this placement’s choice. Both facts must be true before the brighter picture appears above the button.
Keep Show When Panel Choice’s prepared panel declaration too. On that brighter picture Object, set panel to the Results Panel resource in Shared Variables. Then make PLAY AGAIN Highlight and its label. The last lines in the setup list place these Objects over the button you made. The button sits under the highlight and words, so its sensor remains the thing that hears the mouse.
The highlight over one choice of a panel: shown while its panel is open and its choice is the chosen one, hidden otherwise.
Any ObjectObjects with Panel State- Custom Event
panelChoiceChanged
@self.panel.panelState.isOpen == true && @self.panel.panelState.chosen == self.choice- Visible
Yes
- Visible
No
Window / UIBottom CenterUIX 128, Y 128147Aero Button LitX 442.8, Y 77.76Results PanelWindow / UIBottom CenterUIX 128, Y 48PLAY AGAINNunito39Bold1.5CenterWrap to Width442.8Run the game and let the well fill. After the stone wave, read the panel and choose PLAY AGAIN. The first piece falls into an empty well, with the previous best still beside it.
6.10 Play It!
- Keep dropping pieces in the middle until the stack reaches the hidden rows. The game ends instead of dealing shapes into a full well.
- Watch the stack after Game Over Jingle. Its filled cells turn gray from the bottom upward; the moving piece, ghost and previews hide.
- Wait for the panel. SCORE, LINES and LEVEL match the ended game, and its time and special-clear counters stay still.
- Beat the saved best and look for NEW PERSONAL BEST! A later game that doesn’t beat it leaves that notice hidden.
- Press Enter on PLAY AGAIN. The well empties, score and lines reset, and the first level begins with newly dealt pieces.
- End another game and click the button instead. The mouse and Confirm should reach the same fresh start.
6.11 How It Works
The ending is a message. Each part hears it, finishes its own work and leaves a stable record. The panel reads after those listeners have finished.
A fresh start follows the same ownership. Restart Scene asks the pieces to begin again; each one knows which values belong to its own game. The best belongs somewhere longer lived.
6.12 If It Didn’t Work
The game keeps asking for pieces after the well fills? Compare Spawn Piece’s starting-place test and its pieceBlocked sender with Check Top Out. Does On Next Piece Due also test Playing? Restore the boundary, then keep dropping into the middle until you reach the top again.
Stone appears all at once? Check Show Well Cell uses rowFromBottom in its wait, rather than only the starting delay. Their waits should differ. Restore the sum and watch the wave climb again.
The panel appears but Enter does nothing? Check Menus includes Confirm, then Open Results turns Menus on before showing the panel. Panel Choices must also receive panelOpened on Results Panel. Try Enter again after restoring those links; the selected choice should now hear you.
The next game keeps the old score? Start with Score Points. A Global survives the restart until its owner resets it. Add that reset from the card, then choose PLAY AGAIN once more.
The record’s self.shown name stays red? Check the value Object’s Game Record Text component is named shown and its Logic is attached. Retype the expression after those names agree. The finished record can then be copied into the component that each value text reads.
Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).
6.13 Challenge
What would you want to remember about your own game? Try changing which record field a result text shows, or give its panel a different layout. You might make room for a moment you care about that this game doesn’t count yet. Keep the working record beside you while you explore.
The ending works. Next, it gets a front door.
