- Learn
- Build a Game
- Crystal Cascade
- 3 Clearing Lines
3 Clearing Lines
A full row breaks. Its crystals burst into shards of their own colors, the rows above drop down, and the LINES count climbs. Every crystal that locks gives a small pop.
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 4 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.
3.1 The Idea
A full row reaches from wall to wall. Until now, it stays there like any other row, making the well shallower. This chapter lets the crystals break.
The row flashes white. Shards burst outward, the rows above drop into the gap, and LINES climbs. A piece that locks gives a small pop of light too. The well can empty as well as fill.
There’s a quiet problem inside that moment. If the row disappears at once, there’s nothing left to draw shattering. Instead, the game marks its cells first. The pictures show that mark while the numbers still remember each crystal’s color.
Only after the clear delay does the stack move. Mark, show, then move: those are separate jobs. The table keeps enough information for each to happen in its own time.
3.2 The Plan
- Lock Piece writes the crystals, then sends
pieceLockedto the well. Moving waits. - Clear Full Lines finds and marks full rows ->
wellChangedmakes their cells shatter ->linesClearedupdates the count. - The clear-delay timer rings. Kept rows move downward, and unused rows at the top empty.
- A short entry delay ends ->
spawnPiecebrings the next shape and restores its controls.
3.3 Set Up
Continue from Chapter 2 or its catch-up project. The Starter Kit already has the shard and sparkle pictures. Add the lines Global from this setup card through the + on the Logic canvas’s Globals block.
Leave the placements until the end. The well’s new work comes first; the words can wait until there is something to count.
3.4 The Well Remembers a Clear
Give a cell one more thing to remember.
Add isClearing to Well Entry, using the Changes card. The table’s rows follow their Entry Type, so this field becomes a column in Well. Its default leaves every cell unmarked.
One row of the "Well" Data Table. Its fields are the columns of the table. The stack, one row per cell: row index = row * columns + column (Well Layout), row 0 at the bottom; rows 20 and 21 are hidden above the top. kind: 0 empty, 1-7 a crystal of that kind (Piece Kinds). Its rows are made, all empty, by Empty The Well when a game begins. isClearing: the crystal is in a full line and is shattering (until the rows above drop).
3.4.1 Empty The Well
A fresh game clears the marks as well as the crystals. Update its Control Data Row. The same loop that prepares its rows now prepares both fields together.
Makes the Well table one empty row per cell of the well (Well Layout: columns x rows), or empties the rows it already has.
data('Well Layout', 0, 'columns') * data('Well Layout', 0, 'rows')times- Action Name
addedRow- Table
Well- Row
blockLocal("index")- Values
{"kind":"0","isClearing":"false"}- Values
{"kind":"0"}
Well State keeps a different memory: how many full rows the last lock found. Other Objects need to read that answer. Give Clear Full Lines this structure through its Data Structures block, then attach the new Logic to Glass Well beside Build The Well.
What the well did after the last lock: linesCleared, the number of full lines it found (0-4). Game Rules reads it when the well sends linesCleared.
On-10Glass WellX 16.5, Y 31.5Well StateWell Cell3.4.2 Row Is Full
Before breaking a row, the well asks whether every cell is taken. Row Is Full reads its cells from the Well table. An empty entry makes the answer false; only a row with no gap gets a true answer back.
This is the same separation you used for Piece Fits. A Function answers the question without drawing anything or changing the stack.
Answers whether every cell of a well row holds a crystal (Well, Well Layout columns). Row 0 is the bottom row.
- Name
checkColumn- Value
0
- Name
filledCount- Value
0
- Type
Boolean- Name
isFull- Value
false
- Variable
filledCount- Value
0
- Variable
checkColumn- Value
0
checkColumn < data('Well Layout', 0, 'columns')- Action Name
countedCell- column
checkColumn- row
row
data('Well', countedCell.value, 'kind') != 0- Variable
filledCount- Operator
+=- Value
1
- Variable
checkColumn- Operator
+=- Value
1
- Variable
isFull- Value
false
filledCount == data('Well Layout', 0, 'columns')- Variable
isFull- Value
true
- Value
isFull
3.4.3 Mark Full Line
Once a row is full, Mark Full Line walks across it and turns on isClearing for every cell. It keeps each kind unchanged. The pictures can still read which color should shatter.
Marks every cell of a full well row as clearing (the Well table's isClearing), so its crystals shatter. Row 0 is the bottom row.
- Name
cellIndex- Value
0
data('Well Layout', 0, 'columns')times- Action Name
markedCell- column
local.index- row
row
- Variable
cellIndex- Value
markedCell.value
- Table
Well- Row
cellIndex- Column
"isClearing"- Value
"true"
3.4.4 Move Row Down
After the shards appear, a kept row needs a lower home. Move Row Down copies the kind from each old cell into the corresponding cell of its target row. The target cells lose their clearing marks.
The Function handles one row. Clear Full Lines will choose which row it reads and where that row belongs. That keeps the copying separate from the decision about gaps.
Copies every crystal of one well row (fromRow) into a lower row (toRow); the copied cells are not clearing. Row 0 is the bottom row.
- Name
cellIndex- Value
0
- Name
movedKind- Value
0
data('Well Layout', 0, 'columns')times- Action Name
movedFromCell- column
local.index- row
fromRow
- Action Name
movedToCell- column
local.index- row
toRow
- Variable
movedKind- Value
data('Well', movedFromCell.value, 'kind')
- Variable
cellIndex- Value
movedToCell.value
- Table
Well- Row
cellIndex- Column
"kind"- Value
movedKind
- Table
Well- Row
cellIndex- Column
"isClearing"- Value
"false"
3.4.5 Empty Row
Moving rows down leaves old numbers at the top. Empty Row clears them away. The well can call it for every row left over once the kept rows are home.
Empties every cell of a well row: no crystal (kind 0), not clearing. Row 0 is the bottom row.
- Name
cellIndex- Value
0
data('Well Layout', 0, 'columns')times- Action Name
emptiedCell- column
local.index- row
row
- Variable
cellIndex- Value
emptiedCell.value
- Table
Well- Row
cellIndex- Column
"kind"
- Table
Well- Row
cellIndex- Column
"isClearing"- Value
"false"
3.4.6 Clear Full Lines
Now the well can put those jobs together.
Make Row Sparkle before this Logic’s Actions. It bursts along a full row, while the cells themselves will make the colored shards. The card’s shape spreads those sparkles across the line.
026rectanglew 15, h 0.469min 300, max 700min 0.469, max 3.281FX Sparkle#E8FCFFMultiple Colors#FFFFFF, #D8FBFF, #A8ECFF1.5start 1.3, end 0Random Spin OnClear Full Lines is one Logic Component, shown in three pieces here. In On Piece Locked, it starts a fresh count and asks Row Is Full about every row. A full row gains a mark, a sparkle and a place in linesCleared.
After the search, a nonzero count sends wellChanged and linesCleared, then starts the clear delay. No clear means a shorter wait. Both paths eventually ask for the next piece.
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.wellState- 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
self.wellState.linesCleared > 0- Custom Event
wellChanged
- Intensity
@self.shakePerLine * self.wellState.linesCleared- Seconds
@self.shakeSeconds
self.wellState.linesCleared == 4- Color
@self.flashColor- Seconds
@self.flashSeconds
- 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
When the clear delay ends, imagine two fingers climbing the well. One reads each old row; the other points to the next place a kept row can go. The reading finger always climbs. The target moves only when a row is kept.
A marked row is skipped, so the next kept row copies into the gap. With no gap below, a row copies into its own place. Once every old row has been considered, Empty Row clears whatever remains above the target.
The card calls those positions checkRow and targetRow. They start at the bottom. Moving upward keeps a copied row from replacing another row before that other row has been read.
- 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
- Timer Name
"entryDelay"- Repeat Every (s)
@self.entrySeconds- Ring Once
Yes
The last Event has a smaller job. When entryDelay rings, it sends spawnPiece. A lock without a clear reaches it sooner; a clear reaches it after the rows have moved.
- Custom Event
spawnPiece
Nothing sends pieceLocked yet. Run now, and your game still plays as it did in Chapter 2. The new well is waiting for the lock to hand over its turn.
3.5 Handing Over the Lock
The falling piece must wait while the well works.
3.5.1 Lock Piece
Lock Piece still writes its crystals into Well. After that, it turns off the Logics that could move, rotate or hard-drop that old shape. It asks the well to check the stack rather than spawning the next piece itself.
The messages have different listeners. pieceLocked starts the well’s clear; crystalsLocked tells the cell pictures that new crystals arrived. Read The Move Keys stays on, so it can still hear a released key while movement waits.
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
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
- 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
- Custom Event
pieceLocked- Custom Event
wellChanged
- Custom Event
spawnPiece
- Custom Event
crystalsLocked
3.5.2 Spawn Piece
The next piece wakes the controls again. Spawn Piece enables the same Logics before preparing the new shape. With no State line shown on Set Logic State, its starting choice is Enable.
Puts a new piece at the top of the well: a random kind, rotation up, its box in the hidden rows, then one row down into view if it fits.
Any Objectself.piece- Custom Event
spawnPiece
- Logic Component
Piece Gravity
- Logic Component
Move Piece
- Logic Component
Rotate Piece
- Logic Component
Hard Drop Piece
- Action Name
randomKind- Minimum
1- Maximum
dataCount('Piece Kinds')
- Component Value
self.piece.kind- Value
randomKind.value
- Component Value
self.piece.rotation- 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
The wait now belongs to the well. Its cells need one last change before they can show the whole moment.
3.6 The Crystals Show It
The marks become visible here.
Add the new fields to Show Well Cell Data. shownKind remembers what a cell last drew, and isShowingShatter remembers whether it already burst. They keep a new message from making the same shard burst twice.
Object Data of Logic "Show Well Cell" (its state that lasts between Events). cellIndex: the row of the Well table this cell shows. shownKind: the crystal kind it shows now (0 empty, -1 before the first). isShowingShatter: its crystal is bursting in a full line.
Make Crystal Frames Entry, then Crystal Frames. Its shatter field picks the white picture from Crystal Blocks. The frame number lives in a table so the drawing Logic can ask for it by meaning.
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.
8The two particle effects have different moments. Lock Glint accompanies a new crystal’s small pop. Crystal Shards bursts when a full line breaks, tinted with that crystal’s kind color. Make them before adding their Actions below.
020.469min 200, max 380min 0.234, max 1.406FX Sparkle#FFFFFFMultiple Colors#FFFFFF, #E6FDFF1.4start 0.5, end 0start 0.9, end 0070.469min 450, max 950min 4.219, max 15min 20, max 160x 0, y -51.563FX Shard#FFFFFFMultiple Colors#FFFFFF, #CCCCCC, #EEEEEE, #FFFFFFNormalstart 2.2, end 0.50.4start 0, end -540Random Spin On3.6.1 Show Well Cell
A cell can now notice a change, rather than drawing every update the same way. If it was empty and a lock filled it, it grows a little and returns to its normal size. That’s the pop.
A clearing cell takes the other path. It bursts into colored shards and shows the white shatter frame. The kind stays in the table during that moment, so the shards can still find their color. When the rows move, the same picture Logic draws the new kind at that cell.
Watch the order of the messages as you build. The well handles pieceLocked before the pictures hear crystalsLocked. A crystal already shown as shattering therefore doesn’t also count as a fresh pop.
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
- Frame
data('Well', self.showWellCellData.cellIndex, 'kind')
- 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
- Frame
data('Well', self.showWellCellData.cellIndex, 'kind')
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
Run again. Lock a piece, then fill a row. The stack can finally make room for more crystals.
3.7 Counting What Broke
The broken line leaves a number behind for the player to read, even after its crystals have vanished.
3.7.1 Count Lines
Count Lines hears linesCleared and reads the well’s linesCleared field. One is a message; the other is the answer kept in Well State. It adds the answer to lines.
Declare its well Shared Variable as an Object of the Glass Well kind. That kind makes Well State’s fields available to the Logic, because Glass Well carries the structure it needs to read. Make Game Rules, attach Count Lines, and set its well Variable to Glass Well.
Adds the lines the well cleared to the lines count (the Project Global lines).
Any ObjectGlass Well- Variable
$lines- Operator
+=- Value
@self.well.wellState.linesCleared
OnGlass WellThe two text Objects have different jobs. LINES Label keeps its word. LINES Value has a template reading the live lines Global, so it changes whenever the count does. Its Nunito font is already in the Starter Kit.
Window / UICenterUIX 128, Y 48LINESNunito28.5Bold#BFEAFF1.5Wrap to Width210Window / UICenterUIX 128, Y 48{$lines}Nunito39Bold1.5RightWrap to Width240Place Game Rules and the text Objects using the setup list. Their Screen UI offsets count from their screen anchor, while Game Rules has no picture to place on screen. Clear a row and watch LINES climb.
3.8 Play It!
- Lock a piece on an empty part of the floor and watch its new crystals pop briefly into place.
- Fill a row from wall to wall. Watch its white flash and the burst of shards, each taking the color of the crystal that broke.
- Watch the crystals above that row. After the clear delay, they move down into the gap.
- Read LINES as a row breaks. Its count rises by the number of full rows found together.
- Try pressing movement and rotation during a clear. The old piece stays out of play until the next one arrives.
- Clear a row with gaps higher in the stack. Their gaps move down too.
3.9 How It Works
The table now holds marks beside the crystals it already held. A mark lets the picture show a change before the stored stack takes its new shape.
Finding full rows and moving kept rows are separate passes over the same well. The delay between them gives the shatter time to be seen.
3.10 If It Didn’t Work
The line flashes, but the stack never moves? Check the clearDelay timer Event and its name in Start Recurring Timer. The same spelling joins the wait to its listener. Restore the timer’s name or its listener, then fill another row to watch the delay finish its work.
A row gets copied into the wrong place? Watch targetRow in the clear-delay Event. It advances only for a kept row, while checkRow advances through every old row. Compare the If’s inside Actions with the card, then run the same clear again.
The count stays at its starting value? Check Count Lines is attached to Game Rules and its well Shared Variable names Glass Well. Then compare the Global lines name with the template on LINES Value. Those pieces can now share the same answer.
The next piece won’t move? Compare the Set Logic State Actions in Spawn Piece with the ones in Lock Piece. The first set wakes what the second set rested. Restore the missing Enable Action, start another piece, and check its controls before trying the next clear.
Still stuck? Search the manual, or ask in the Myth Engine Discord (opens a new tab).
3.11 Challenge
How should a clear feel in your own game? Change the clear delay or try another sparkle color, keeping a working copy to compare. A longer pause can let a shatter hang in the air. A shorter one can make the next choice arrive sooner.
The well can make room now. Next, it shows what is coming.
