- Learn
- Build a Game
- Crystal Cascade
- 5 Scoring and Levels
5 Scoring and Levels
Every clear has a name, a sound and a score. Twists, combos and back-to-back clears pay more, and every ten lines the crystals fall faster.
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. Open it and compare, or start Chapter 6 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.
5.1 The Idea
A row breaks into light and LINES climbs, while the rest of the game goes on without noticing how it happened. This chapter lets it notice how you play.
Now a clear can have a name, a sound of its own and a reward beyond the line count. Clear with one piece after another and a combo grows. Back-to-back rewards successive difficult clears. Empty the whole well and ALL CLEAR! appears in a shower of sparkles.
There’s another kind of finish. Turn a T into a snug space before it locks, and it can count as a twist. It looks like turning a key in a lock: the shape can fit somewhere it couldn’t slide into upright.
The game needs a small memory for those rewards. It remembers what came before. The pictures and sounds announce what that memory says. Every ten lines brings a new level, so the well gives you less time to choose.
5.2 The Plan
- Check Twist runs before Lock Piece, keeping the result in Piece.
- Clear Full Lines counts its rows ->
lockCheckedlets Score Points choose the reward. ThenclearScoredreaches the name and combo listeners. linesClearedupdates the line count before Count Level checks for a new level.- After a clear settles, the well checks for remaining crystals. An empty well sends
wellEmptiedonce. - A soft-drop row or hard drop pays for distance, using the same score Global.
5.3 Set Up
Continue from Chapter 4 or its catch-up project. Add the Globals on this setup card. score begins empty, currentLevel begins at the first level, and bestMarathonScore waits for the saved best.
The new pictures and sounds are already in the Starter Kit. Leave the placements until their parts are made. Work down the list as the score panel, messages, level display and best panel become ready.
5.4 Remembering a Turn
First, give the piece the memory a twist needs.
Add the marked fields to Piece. lastMoveWasRotation remembers the kind of move, lastKickTest remembers which trial succeeded, and twist keeps the lock’s answer. A successful turn sets that memory. A later move or fall clears it.
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).
The T is the kind whose canTwist field lets this locking test go ahead. Add canTwist to Piece Kinds Entry. Give Piece Kinds its marked values. Keeping that fact with each kind lets Check Twist ask the table instead of hiding a particular kind number in its Actions.
One row of the "Piece Kinds" Data Table. Its fields are the columns of the table. The seven kinds of piece. Row index + 1 is the kind number the Well stores (1 = I ... 7 = L). frame: the crystal's frame in the sprite "Crystal Blocks". color: the kind's color, as a hex color (the color of the shards when one of its crystals shatters). rotationColumn, rotationRow: the point of its 4 x 4 box the kind rotates about (its rotations are its up shape rotated about it, Piece Shapes). firstKickRow: where this kind's quarter-rotation kicks start in the Kicks table. streakColor: the lighter tint of its hard-drop streak. middleColumn, middleRow: the middle of the piece in its box (rotation up), to center it in a preview. canTwist: this kind can make a twist (the T).
1#3cd6ff1.51.540#7fe6ff22.5No2#ffce3a1.52.50#ffe27a23No3#b268ff120#cf9dff1.53Yes4#60e25c120#9ff09a1.53No5#ff486e120#ff8aa41.53No6#3a74ff120#83a8ff1.53No7#ff9232120#ffbd7a1.53No5.4.1 Twist Kind
Look at the T’s middle crystal. Its corners are the cells around that point. Several taken corners mean the shape is tucked into a tight space. Walls and the floor count too.
Make Twist Corners Entry and Twist Corners before the Function. Their rows give the corner cells. The rotation tells the Function which pair is in front of the T, on the side it points toward.
One row of the "Twist Corners" Data Table. Its fields are the columns of the table. The four corners of the T's 3 x 3 box, clockwise from the top left (row 0 to 3). column and row: the corner's cell in the piece's 4 x 4 box. The two front corners of a rotation (on the side the T points to) are the corners numbered rotation and rotation + 1, going round (Twist Kind).
03232101Twist Kind keeps two tallies: filledCorners for the whole box and filledFrontCorners for its front. At least three filled corners make a twist. The front pair or the last kick trial makes it a full twist; the other qualifying shape is a mini.
At rotation 0, the front pair is top left and top right: table rows 0 and 1. A clockwise turn moves that pair to the right side. The table order lets the formula make the same trip around the corners.
The card’s && joins two requirements: the corner is filled and it is in front. Its || lets either full-twist test succeed. The Function returns one number for none, mini or full, leaving scoring to another part.
Answers what a T locks as at this cell and rotation (0 none, 1 mini, 2 full twist): with at least 3 of its box's 4 corners filled (Twist Corners, Well Cell Filled) it is a twist, full when both front corners are filled or its last rotation used the last kick test (lastKickTest), otherwise a mini.
- Name
twist- Value
0
- Name
cornerRow- Value
0
- Name
cornerFromFront- Value
0
- Name
filledCorners- Value
0
- Name
filledFrontCorners- Value
0
- Variable
twist- Value
0
- Variable
filledCorners- Value
0
- Variable
filledFrontCorners- Value
0
4times- Variable
cornerRow- Value
local.index
- Variable
cornerFromFront- Value
mod(cornerRow - rotation + 4, 4)
- Action Name
cornerCheck- column
column + data('Twist Corners', cornerRow, 'column')- row
row + data('Twist Corners', cornerRow, 'row')
cornerCheck.value == true- Variable
filledCorners- Operator
+=- Value
1
cornerCheck.value == true && cornerFromFront <= 1- Variable
filledFrontCorners- Operator
+=- Value
1
filledCorners >= 3filledFrontCorners >= 2 || lastKickTest == (data('Kicks Layout', 0, 'testsPerRotation') - 1)- Variable
twist- Value
2
- Variable
twist- Value
1
- Value
twist
5.4.2 Check Twist
The corner test only matters if rotation was the piece’s last move. Check Twist first clears its old answer, then asks Twist Kind when the kind and move history qualify. Attach it above Lock Piece on Falling Piece, so that answer exists before the lock begins.
As the piece locks: a T whose last action was a rotation asks Twist Kind what it locks as (none, mini or full twist) and keeps the answer in the piece's twist; any other piece locks as no twist.
Any Objectself.piece- Component Value
self.piece.twist- Value
0
data('Piece Kinds', self.piece.kind - 1, 'canTwist') == true && self.piece.lastMoveWasRotation == true- Action Name
lockedAs- rotation
self.piece.rotation- column
self.piece.column- row
self.piece.row- lastKickTest
self.piece.lastKickTest
- Component Value
self.piece.twist- Value
lockedAs.value
OnPieceRead The Move KeysRead The Move Keys DataSoft Drop PieceSoft Drop Piece DataHold PieceHold Piece DataDrop Streak5.4.3 Spawn Piece, Move Piece and Rotate With Kicks
A new shape has no rotation history. A successful slide clears it. A successful turn sets it and remembers the kick trial, so the test follows what the player actually did.
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- piece
self- columnShift
0- rowShift
-1
roomBelowNewPiece.value == true- Component Value
self.piece.row- Value
self.piece.row - 1
- piece
self
- Custom Event
pieceMoved
- Component Value
self.piece.kind- Value
data('Queue', 0, 'piece')
- Custom Event
pieceDealt
- Custom Event
spawnPiece
Moves the falling piece one column in its moveDirection when asked (moveOnce, from Read The Move Keys), if the piece fits there.
Any Objectself.piece- Action Name
roomBeside- piece
self- columnShift
self.piece.moveDirection- rowShift
0
roomBeside.value == true- Component Value
self.piece.column- Value
self.piece.column + self.piece.moveDirection
- Component Value
self.piece.lastMoveWasRotation- Value
false
- Sound
Move Tick
- piece
self
- Custom Event
pieceMoved
- Custom Event
pieceShifted
Rotates a piece by rotateBy quarter rotations (1 clockwise, -1 counterclockwise, 2 for 180 degrees) the standard way: it tries the five places of the Kicks table for this rotation in order and takes the first where the piece fits; where none fits, the piece does not rotate.
pieceData StructurePiece- Name
newRotation- Value
0
- Name
firstTestRow- Value
0
- Name
test- Value
0
- Type
Boolean- Name
hasRotated- Value
false
- Variable
newRotation- Value
mod(piece.piece.rotation + rotateBy + 4, 4)
- Action Name
kicksStart- kind
piece.piece.kind- rotation
piece.piece.rotation- rotateBy
rotateBy
- Variable
firstTestRow- Value
kicksStart.value
- Variable
test- Value
0
- Variable
hasRotated- Value
false
hasRotated == false && test < data('Kicks Layout', 0, 'testsPerRotation')- Action Name
roomAfterRotation- kind
piece.piece.kind- rotation
newRotation- column
piece.piece.column + data('Kicks', firstTestRow + test, 'columnShift')- row
piece.piece.row + data('Kicks', firstTestRow + test, 'rowShift')
roomAfterRotation.value == true- Component Value
piece.piece.column- Value
piece.piece.column + data('Kicks', firstTestRow + test, 'columnShift')
- Component Value
piece.piece.row- Value
piece.piece.row + data('Kicks', firstTestRow + test, 'rowShift')
- Component Value
piece.piece.rotation- Value
newRotation
- Variable
hasRotated- Value
true
- Component Value
piece.piece.lastMoveWasRotation- Value
true
- Component Value
piece.piece.lastKickTest- Value
test
- Variable
test- Operator
+=- Value
1
hasRotated == true- Sound
Rotate Chime
- piece
piece
- Custom Event
pieceMoved
- Applies to
piece- Custom Event
pieceShifted
Hard drop clears that history only when it crosses rows. A drop already resting at its landing row can therefore keep the last rotation. The same hardDropRows used for the trail tells this test whether any fall happened.
When Hard Drop is pressed, the falling piece drops straight to its landing row and locks at once, with a thud and a small shake.
Any Objectself.piece- Component Value
self.piece.hardDropRows- Value
self.piece.row - self.piece.landingRow
self.piece.hardDropRows > 0- Component Value
self.piece.lastMoveWasRotation- Value
false
- Custom Event
hardDropped
- Component Value
self.piece.row- Value
self.piece.landingRow
- Custom Event
pieceMoved
- Sound
Hard Drop
- Intensity
@self.shakeStrength- Seconds
@self.shakeSeconds
- Custom Event
lockPiece
Gravity’s falling step will clear the flag too. You’ll find that change on its card in the level part below, together with the new gravity curve. The twist’s memory is ready; now give its result a value.
5.5 Points for a Clear
The well can tell Game Rules what happened.
Scoring State holds the clear’s chosen row, the combo and the back-to-back memory. Add its component to Game Rules. Give the component the Name scoring. That’s the middle name in self.scoring, rather than the resource’s longer 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.
Make Score Points, Count Combo, Count Level and Keep Bests by name, then attach them as Game Rules’ Changes card shows. Count Level follows Count Lines. Their Actions come below. Set piece to Falling Piece in Game Rules’ Shared Variables; well already names Glass Well from Chapter 3.
Scoring StateOnGlass WellFalling PieceMake Clear Names Entry, then Clear Names. Each row gives a name, base points, sound, difficult-clear flag and all-clear bonus. Its CSV saves you typing the long table: choose its Entry Type, then Import CSV and compare the first rows.
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).
0No0SINGLE100Clear SingleNo800DOUBLE300Clear DoubleNo1200The row depends on the twist result and the number of lines. allClearPoints belongs to the plain line-count rows, because that bonus is looked up by lines alone. It isn’t another reward for the turning move.
5.5.1 Score Points
The same clear pays more at a higher level. Score Points chooses the Clear Names row and multiplies its points by currentLevel. A back-to-back clear adds its multiplier before the total is rounded down and added to score.
Its small memory distinguishes a difficult clear from a difficult clear following another. The first sets isBackToBackReady. Another difficult clear can use that readiness; a plain line clear ends it. No lines? Readiness keeps waiting.
Supply Scoring State under the name scoring where the Logic’s Data Structures block shows it. Set the Shared Variables from its card before the Events. In their declaration forms, set Object Kind to Glass Well for well and Falling Piece for piece. That exposes their fields. Kind stays Constant; it is a separate setting.
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- 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
The remaining Events pay separate rewards. An empty well earns its row’s allClearPoints, multiplied by the level. softDropRow pays for one hurried row; hardDropped pays for the recorded distance. Those messages all add to the same score.
- 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
5.5.2 Clear Full Lines
Give Clear Full Lines its new Clear Full Lines Data structure. The hasPendingClear field remembers an all-clear check that still belongs to this clear. shakeIntensity holds the combined jolt for its lines and twist.
Object Data of Logic "Clear Full Lines" (its state that lasts between Events). shakeIntensity: how hard the well shakes for this clear. hasPendingClear: this placement still owns an unsettled all-clear check.
The lockChecked message goes out after full rows are counted. Score Points can therefore read the twist and line count before linesCleared raises the level. The clear that reaches a level boundary earns its base clear reward at the old level.
After the rows settle, settleClear counts the remaining crystals. It uses hasPendingClear to finish that check once. An empty result sends wellEmptied. This waits for the stack’s new shape instead of guessing from the pictures during the shatter.
Add the green Actions and Event on the card. The new Camera Shake reads the remembered shakeIntensity. Follow the marks rather than the old step numbers when a step moves.
The line clear: after each lock it marks the full lines (they shatter); clearSeconds later every row that stays moves down into the lowest free row and the rows left at the top are emptied; entrySeconds after that (or after a lock with no full line) it asks for the next piece.
Any Objectself.wellStateself.clearFullLinesDataFalling Piece- Component Value
self.wellState.linesCleared- Value
0
data('Well Layout', 0, 'rows')times- Name
checkRow- Value
local.index
- Action Name
checkedRow- row
checkRow
checkedRow.value == true- Component Value
self.wellState.linesCleared- Value
self.wellState.linesCleared + 1
- row
checkRow
- Action Name
fullRowMiddle- column
(data('Well Layout', 0, 'columns') - 1) / 2- row
checkRow
- Particles
Row Sparkle- Position Relative To
World- X
fullRowMiddle.x- Y
fullRowMiddle.y- Z Index Offset
6
- Custom Event
lockChecked
self.wellState.linesCleared > 0- Custom Event
wellChanged
- Component Value
self.clearFullLinesData.shakeIntensity- Value
@self.shakePerLine * self.wellState.linesCleared
@self.piece.piece.twist > 0- Component Value
self.clearFullLinesData.shakeIntensity- Operator
+=- Value
@self.twistShake
- Intensity
self.clearFullLinesData.shakeIntensity- Intensity
@self.shakePerLine * self.wellState.linesCleared- Seconds
@self.shakeSeconds
self.wellState.linesCleared == 4- Color
@self.flashColor- Seconds
@self.flashSeconds
- Component Value
self.clearFullLinesData.hasPendingClear- Value
true
- Custom Event
linesCleared
- Timer Name
"clearDelay"- Repeat Every (s)
@self.clearSeconds- Ring Once
Yes
- Timer Name
"entryDelay"- Repeat Every (s)
@self.entrySeconds- Ring Once
Yes
- Name
targetRow- Value
0
- Variable
targetRow- Value
0
data('Well Layout', 0, 'rows')times- Name
checkRow- Value
local.index
- Action Name
rowStart- column
0- row
checkRow
data('Well', rowStart.value, 'isClearing') == false- fromRow
checkRow- toRow
targetRow
- Variable
targetRow- Operator
+=- Value
1
targetRow < data('Well Layout', 0, 'rows')- row
targetRow
- Variable
targetRow- Operator
+=- Value
1
- Custom Event
wellChanged
- Custom Event
settleClear
- Timer Name
"entryDelay"- Repeat Every (s)
@self.entrySeconds- Ring Once
Yes
- Custom Event
nextPieceDue
self.clearFullLinesData.hasPendingClear == true- Component Value
self.clearFullLinesData.hasPendingClear- Value
false
- Name
remainingCrystals- Value
0
data('Well Layout', 0, 'rows') * data('Well Layout', 0, 'columns')timesdata('Well', local.index, 'kind') > 0 && data('Well', local.index, 'isClearing') == false- Variable
remainingCrystals- Operator
+=- Value
1
remainingCrystals == 0- Custom Event
wellEmptied
On Glass Well, set Clear Full Lines’ piece Shared Variable to Falling Piece. Its supplied Data Structure arrives with the updated Logic.
On-10Glass WellX 16.5, Y 31.5Well StateClear Full LinesClear Full Lines DataWell CellFalling PieceMake the score panel and its label and value. Score Value’s template reads the live score Global, including the card’s comma format. Place these first setup lines, then clear a row and watch the total change.
-20Score PanelX 11.7188, Y 6.0938Window / UIBottom CenterUIX 128, Y 48SCORENunito28.5Bold#BFEAFF1.5CenterWrap to Width345Window / UIBottom CenterUIX 128, Y 48{$score:,}Nunito54Bold1.5CenterWrap to Width3605.6 Saying What Happened
The number gets a name and a voice.
5.6.1 Fade Out Message
Let the name wait. Its fade occupies the last part of showSeconds, so the whole display ends when that timer rings. Fade Out Message does that job for several text Objects. Each message chooses its own time. Its Per Instance Variables change the timing without needing another copy of those Events.
One timer starts the fade. Another hides the text. Showing it again stops the old fade and starts those waits over. A second clear can replace the first message while it is still visible.
A message on the screen: when it is shown (messageShown) it stays showSeconds and fades out over its last fadeSeconds; shown again, it starts over.
Any Object- Visible
No
- Which
Value- Value
self.transform.opacity
- Visible
Yes
- Timer Name
"fadeStart"- Repeat Every (s)
self.showSeconds - self.fadeSeconds- Ring Once
Yes
- Timer Name
"hide"- Repeat Every (s)
self.showSeconds- Ring Once
Yes
- Value
self.transform.opacity- Target
0- Duration
self.fadeSeconds
- Visible
No
5.6.2 Show Clear Name
Make Clear Name Text, with its empty clearName field. Add it to Clear Name and give that component the Name shown. The text’s template reads the field; Show Clear Name writes it when clearScored arrives.
What the Clear Name text shows. clearName: the name of the last clear (its template {clearName} reads it).
Window / UITop CenterUIX 128, Y 48{clearName}Nunito39Bold1.5CenterWrap to Width435Clear Name TextGame RulesDeclare rules as follows: Type Object and Kind Constant, with Game Rules selected in the separate Object Kind field. The helper page shows this declaration and its later value assignment. Set rules on that text Object. Show Clear Name reads the row Score Points chose. An empty name stays silent; a named clear plays its sound and sends messageShown on the text.
The Sound field is a typing box with resource suggestions. Type its data(…) expression into that box, as the card shows. You aren’t looking for a sound with that formula as its name in a fixed list.
Announces each clear: its name (Clear Names) in the Clear Name Text field clearName, which the template of this text shows ({clearName}), and its sound; a lock with no name says nothing.
Any Objectself.shownGame Rules- Component Value
self.shown.clearName- Value
data('Clear Names', @self.rules.scoring.lastClearRow, 'name')
self.shown.clearName != ""- Sound
data('Clear Names', @self.rules.scoring.lastClearRow, 'sound')
- Custom Event
messageShown
Place Clear Name using its setup line. Then clear another row. SINGLE or another name can now announce what the well did.
5.7 Clears That Remember Each Other
A chain gives the next clear a different meaning.
5.7.1 Count Combo
Clear with one piece. Clear with the next. Count Combo advances combo and begins paying extra from the second clear. A lock without a clear sends it back to its starting value.
The starting value is -1. The first clear reaches 0. The next reaches 1. bestCombo remembers the longest chain for the results panel you’ll make in Chapter 6. Keep Scoring State’s name scoring here too.
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
5.7.2 Show Combo
Make Combo Text Count and add it to Combo Text with the component Name shown. Set the Object’s rules Variable to Game Rules. Its template can then show the count Show Combo copies when comboScored arrives.
What the Combo Text shows. comboCount: the combo being announced (its template COMBO x{comboCount} reads it).
Window / UICenterUIX 128, Y 48COMBO x{comboCount}Nunito34.5Bold#A8F0FF1.5CenterWrap to Width435Combo Text CountGame RulesAnnounces a combo: its count in the Combo Text Count field comboCount, which the template of this text shows (COMBO x{comboCount}), with Combo Chime.
Any Objectself.shownGame Rules- Component Value
self.shown.comboCount- Value
@self.rules.scoring.combo
- Sound
Combo Chime
- Custom Event
messageShown
5.7.3 Show Back-To-Back
Score Points already did the counting. Show Back-To-Back reads isBackToBack from Game Rules and announces a rewarded difficult clear. It doesn’t keep another copy of the chain.
Announces a back-to-back clear: shows "BACK-TO-BACK" with Back-to-Back Sparkle.
Any ObjectGame Rules@self.rules.scoring.isBackToBack == true- Sound
Back-to-Back Sparkle
- Custom Event
messageShown
Window / UICenterUIX 128, Y 48BACK-TO-BACKNunito31.5Bold#FFF3B01.5CenterWrap to Width435Game RulesPlace Combo Text and Back-To-Back Text using the setup lines. Combo Text’s Show Seconds override belongs to that placement, separate from the Logic’s starting value.
5.8 An Empty Well
The glass can stand empty again after all that work.
Make Sparkle Shower and Crystal Fountain before their callers. One spreads light over the well, while the other sends shards up from beneath its floor. A Count on Burst Particles overrides the effect’s Burst Count. Different celebrations can share an effect.
060rectanglew 15, h 28.125min 500, max 1300min 1.406, max 8.438FX Sparkle#FFFBE6Multiple Colors#FFFFFF, #DFFCFF, #FFF7C81.5start 1.5, end 0Random Spin On090rectanglew 14.063, h 0.938min 700, max 1600min 5.625, max 24.375min 40, max 140x 0, y -28.125FX Shard#BFF4FFMultiple Colors#3CD6FF, #FFCE3A, #B268FF, #60E25C, #FF486E, #3A74FF, #FF9232, #FFFFFFNormalstart 2, end 0.4start 0, end -360Random Spin On5.8.1 Show All Clear
The shower fills the space where the stack stood before its last crystals disappeared. The fountain rises from below its floor. Show All Clear places each effect where the empty well can hold it. Fade Out Message hides the text afterward. Attach Show All Clear and Fade Out Message to All Clear, so announcing and fading can share the same Object. Set its placement timings from the card.
Celebrates an all clear: shows "ALL CLEAR!", plays All Clear Fanfare, flashes the screen, bursts sparkleCount Sparkle Shower over the well and Crystal Fountain under its floor.
Any Object- Sound
All Clear Fanfare
- Color
@self.flashColor- Seconds
@self.flashSeconds
- Action Name
wellMiddle- column
(data('Well Layout', 0, 'columns') - 1) / 2- row
(data('Well Layout', 0, 'visibleRows') - 1) / 2
- Particles
Sparkle Shower- Position Relative To
World- X
wellMiddle.x- Y
wellMiddle.y- Count
@self.sparkleCount- Z Index Offset
7
- Action Name
wellFloor- column
(data('Well Layout', 0, 'columns') - 1) / 2- row
@self.fountainRow
- Particles
Crystal Fountain- Position Relative To
World- X
wellFloor.x- Y
wellFloor.y- Z Index Offset
7
- Custom Event
messageShown
Window / UICenterUIX 128, Y 48ALL CLEAR!Nunito84Bold1.5CenterWrap to Width600An all clear may take several tries, because even a single leftover crystal keeps the celebration waiting. It waits for the last crystal.
5.9 The Well Gets Faster
Every level gives the same well a quicker pace.
5.9.1 Count Level
Count Lines updates lines first. Count Level uses that total to find the reached level without changing the count itself. It stops at lastLevel. A rise sends levelUp, so the celebration only happens when the level actually changes.
Keep Count Level below Count Lines in Game Rules’ Attached Logic. They hear the same message in that order. The values live in its Shared Variables, where a different game can choose its own pace of levels.
Raises the level (the Project Global currentLevel) by one for every linesPerLevel lines, up to lastLevel, and says so.
Any Object- 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
5.9.2 Piece Gravity
A new piece reads the level before its gravity timer starts. Piece Gravity calculates fallSeconds from its curve settings and levelsUp. At the first level, that exponent is zero: each row takes a full second. curveStartSeconds helps shape the later fall times. It is not the first interval by itself. Higher levels shorten that interval, without adding another mover or a table of times to maintain.
Its successful fall also clears the rotation memory and sends pieceFell. That message gives soft drop a way to pay only for rows gravity really moved, rather than for time holding the key.
Moves the falling piece down one row whenever the gravity timer comes due; when it cannot fall it rests, and locks after the lock delay.
Any Objectself.piece- Name
levelsUp- Value
global('currentLevel') - 1
- Component Value
self.piece.fallSeconds- Value
pow(@self.curveStartSeconds - levelsUp * @self.curveStepSeconds, levelsUp)
- Timer Name
"gravity"- Repeat Every (s)
self.piece.fallSeconds
- Event
On the Gravity Timer- Mode
Falling
- Component Value
self.piece.lowestRow- Value
self.piece.row
- Component Value
self.piece.lockResets- Value
0
- Component Value
self.piece.hasTouchedDown- Value
false
- Component Value
self.piece.isLockDelayOver- Value
false
- Action Name
roomBelowOnGravity- piece
self- columnShift
0- rowShift
-1
roomBelowOnGravity.value == true- Component Value
self.piece.row- Value
self.piece.row - 1
- Component Value
self.piece.lastMoveWasRotation- Value
false
- piece
self
- Custom Event
pieceMoved
- Custom Event
pieceFell
- Action Name
roomBelowAfterFall- piece
self- columnShift
0- rowShift
-1
roomBelowAfterFall.value == false- Custom Event
pieceLanded
- Custom Event
pieceLanded
- Event
On the Gravity Timer- Mode
Resting
self.piece.hasTouchedDown == false- Component Value
self.piece.hasTouchedDown- Value
true
- Timer Name
"lockDelay"- Repeat Every (s)
self.piece.lockDelaySeconds- Ring Once
Yes
- Custom Event
lockDelayStarted
self.piece.isLockDelayOver == true- Custom Event
lockPiece
- Component Value
self.piece.isLockDelayOver- Value
true
- Action Name
roomBelowAfterDelay- piece
self- columnShift
0- rowShift
-1
roomBelowAfterDelay.value == true- Event
On the Gravity Timer- Mode
Falling
- Custom Event
lockPiece
- piece
self
self.piece.hasTouchedDown == trueself.piece.lockResets < @self.lockResetLimit- Component Value
self.piece.lockResets- Value
self.piece.lockResets + 1
- Component Value
self.piece.isLockDelayOver- Value
false
- Timer Name
"lockDelay"- Repeat Every (s)
self.piece.lockDelaySeconds- Ring Once
Yes
- Custom Event
lockDelayStarted
- Action Name
roomBelowAfterShift- piece
self- columnShift
0- rowShift
-1
roomBelowAfterShift.value == true- Event
On the Gravity Timer- Mode
Falling
- Custom Event
pieceLanded
5.9.3 Show Level Up
The permanent LEVEL texts read currentLevel. Level Up Text announces the moment of change, with a sound and a smaller Sparkle Shower. Make its Logic, attach it with Fade Out Message, then place all three texts using their setup lines.
Window / UICenterUIX 128, Y 48LEVELNunito28.5Bold#BFEAFF1.5Wrap to Width210Window / UICenterUIX 128, Y 48{$currentLevel}Nunito39Bold1.5RightWrap to Width240Celebrates a new level: shows "LEVEL n" (its template reads currentLevel), plays Level Up and bursts sparkleCount Sparkle Shower over the well.
Any Object- Sound
Level Up
- Action Name
levelUpMiddle- column
(data('Well Layout', 0, 'columns') - 1) / 2- row
(data('Well Layout', 0, 'visibleRows') - 1) / 2
- Particles
Sparkle Shower- Position Relative To
World- X
levelUpMiddle.x- Y
levelUpMiddle.y- Count
@self.sparkleCount- Z Index Offset
7
- Custom Event
messageShown
Window / UICenterUIX 128, Y 48LEVEL {$currentLevel}Nunito66Bold#FFF3B01.5CenterWrap to Width6005.9.4 Soft Drop Piece
Soft Drop Piece hears pieceFell and asks whether Down is held. Only then does it send softDropRow for Score Points. Hard drop already sends its distance before moving, so its score needs no second distance counter.
While Soft Drop is held, the falling piece falls speedUp times faster (at least one row every slowestSeconds); letting go returns it to its normal fall.
Any Objectself.pieceself.softDropPieceData- Component Value
self.softDropPieceData.isSoftDropping- Value
true
- Custom Event
fallFaster
- Custom Event
fallNormally
- Component Value
self.softDropPieceData.isSoftDropping- Value
false
- Timer Name
"gravity"- Repeat Every (s)
self.piece.fallSeconds
self.softDropPieceData.isSoftDropping == true- Custom Event
fallFaster
- Name
fasterSeconds- Value
0
- Variable
fasterSeconds- Value
self.piece.fallSeconds / @self.speedUp
fasterSeconds > @self.slowestSeconds- Variable
fasterSeconds- Value
@self.slowestSeconds
- Timer Name
"gravity"- Repeat Every (s)
fasterSeconds
self.softDropPieceData.isSoftDropping == true- Custom Event
softDropRow
Run the game and hurry a fall, watching the score rise while every line in the well stays intact.
5.10 Your Best Score
The next game should remember a good one.
5.10.1 Keep Bests
Keep Bests reads the saved best from the device before any clear has happened in this new game. It runs when the game starts. Its persistent key belongs to this device; bestMarathonScore gives the text a live value to read. Chapter 6 will save a better score when a game ends.
Reads the best score kept on this device into the Project Global bestMarathonScore when the game starts.
Any Object- Action Name
savedBest- Key
"bestMarathon"- Default
0
- Variable
$bestMarathonScore- Value
savedBest.value
-20Score PanelX 11.7188, Y 6.0938Window / UIBottom CenterUIX 128, Y 48PERSONAL BESTNunito28.5Bold#BFEAFF1.5CenterWrap to Width345Window / UIBottom CenterUIX 128, Y 48{$bestMarathonScore:,}Nunito48Bold#FFF3B01.5CenterWrap to Width360Place the best panel and texts using the setup’s final lines. A new device has no saved best, so its starting value is expected for now.
5.11 Keeping Pictures Out of Physics
These pictures move through Logic, without needing physics bodies. Turn off Physics Object on the existing Objects shown by these Changes cards. Transform stays. Its removed On line means returning that switch to Off. The optional layout copies already have this switch Off. Keep those existing settings.
Game Rules, Glass Well and Falling Piece already had the same change on their cards above. Finish the remaining Objects here, then run again to compare the same game.
On7Drop Streak ArtX 1.5, Y 6On8Crystal BlocksX 1.5, Y 1.5Falling PieceOn4Ghost BlocksX 1.5, Y 1.5Falling PieceOnDeal PiecesDeal Pieces DataOn8Crystal BlocksX 1.5, Y 1.5Show Preview CrystalShow Preview Crystal DataFalling PieceOnCrystal BlocksX 1.5, Y 1.5Show Well CellShow Well Cell Data5.12 Play It!
- Clear a line and read its name and score. At a higher level, the same clear pays more.
- Clear with consecutive pieces. The combo announcement appears from the second clear, and a lock without a clear ends that chain.
- Follow one difficult clear with another. Watch for BACK-TO-BACK; an intervening plain clear ends its readiness.
- Hurry a fall with Down. Then try a hard drop. Both can add distance points without clearing a line.
- Reach the next level. The permanent display changes, a message flashes, and later pieces fall faster.
- Try turning a T into a snug space, then lock it without sliding or falling afterward. A qualifying twist uses its own Clear Names row and sound.
- When a clear leaves no crystals, watch ALL CLEAR! and the fountain rise from below the well.
5.13 How It Works
The well reports a lock. Game Rules interprets it, the score changes, and messages announce what happened. Each listener has its own part of the moment.
The memory makes the reward possible. A twist needs the last move; a chain needs the previous clear. The pictures can stay separate from those decisions.
5.14 If It Didn’t Work
The name appears but makes no sound? Open Show Clear Name’s Control Sound and type the table expression into Sound. Its suggestions are choices, rather than a restriction to fixed resources. Try the same clear again and listen.
An open T twists? Compare the corner tests in Twist Kind with its card. The whole-box tally must qualify before the front-corner or last-kick test can choose a full twist. Restore the outer If and try an open turn again.
A T still twists after moving? Check the flag changes in Move Piece, Piece Gravity and Hard Drop Piece. A successful fall or slide clears lastMoveWasRotation. Restore the missing change and repeat the move before locking.
The level seems late? Count Level must run after Count Lines, but Score Points handles the lock before that level rise. Compare the attachment order and message names, then clear across the boundary again. The next piece should use the new pace.
Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).
5.15 Challenge
What should your own game notice? Try a different reward in Clear Names, or give a clear its own sound. You could celebrate a close call or a neat landing in a game of your own. Keep a working copy while you decide what matters to you.
The well can celebrate now. Next, a game can end and tell its story.
