Build a GameCreating Crystal Cascade

1  Falling Crystals

Goal

Pieces fall into your well, one at a time. You slide them left and right, and each one locks where it comes to rest, crystal on crystal.

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

Crystal Cascade, Chapter 1 finishedZip of a Myth project · 4.5 MB

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

1.1 The Idea

The well stands empty under a bright sky. Green hills roll behind it, and nothing moves inside the glass yet. By the end, your first piece will fall.

You can slide it left or right. When it reaches the floor, it waits before locking in place. Another piece follows. Nothing breaks yet; the well only fills.

Where does a crystal go when it locks? You might picture a new Object left behind for every crystal. The game keeps a number instead. Its Well table holds one entry for each cell, a place at a column and a row. An empty cell holds 0. A filled one holds the piece’s kind, from 1 to 7.

Pictures and numbers have separate jobs. Well Cell Objects stand over the visible cells like small windows onto that table. Each one shows the crystal its entry holds, without knowing anything about moving pieces or where they came from.

The Falling Piece keeps the moving shape’s numbers. Four Falling Crystals read them and draw the shape. That invisible piece can move while the visible crystals follow, without leaving new Objects behind each time it locks.

Most of this chapter is quiet work. You’ll see the empty well after its first part. The moving piece stays invisible until the last part gives it pictures. The game’s own catch-up download is there if you need a comparison along the way.

1.2 The Plan

Each line follows a message through the game. Read it now for the picture, then come back as the names become familiar.

  • The Scene starts -> Build The Well empties the table and creates its Well Cells.
  • The Falling Piece is created -> Spawn Piece sends spawnPiece -> a new kind appears and Piece Gravity starts its timer.
  • Gravity rings -> a clear space below means fall and send pieceMoved. A filled space means rest and start the lock delay.
  • The delay ends -> lockPiece writes the crystals into Well -> wellChanged redraws the stack -> the next piece appears.
  • Move Left or Move Right -> Read The Move Keys sends moveOnce -> Move Piece tests the next column before moving.

That question about room belongs to a Function, Piece Fits. Gravity and movement both ask it, so neither has to keep another copy of the well’s rules.

1.3 Set Up

Begin with a new Blank 2D project. Install the Starter Kit through File > Install Myth Pack. The button above supplies every picture, sound and background, together with the game’s font.

Turn on Advanced Mode in Settings. Then follow the setup card’s Game Settings from top to bottom. Make the Gameplay Scene after those sizes are set, and choose Start Scene after the Scene exists. Give the Scene its Aero Sky background.

Those Objects don’t exist yet. We’ll return to it at the end of each part. The card gives the camera’s whole window in meters and pixels, so its view can match the game.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Glass Well
Crystal Blocks
SOUNDS (from the starter kit)
Move Tick
Lock Click
BACKGROUNDAero Sky
Layer Aurora SkyImage
COMMANDS (Game Settings > Input Manager)
Move LeftButton
KeybindingsLeft Arrow
Move RightButton
KeybindingsRight Arrow
GAME SETTINGS > Display > Window
TitleCrystal Cascade
X1920
Y1080
GAME SETTINGS > Display > Reference Resolution
X1920
Y1080
GAME SETTINGS > Display > Page Background (Outside the Game)
Color#06213D
SCENEGameplay (new)
Camera Main CameraX 30, Y 16.875, view 60 x 33.75 m
View Main View1920 x 1080 pixels, camera Main Camera
BackgroundAero Sky
Background Color#1A78C8
GAME SETTINGS > General > Startup
Start SceneGameplay · 2D
SCENE Gameplay: PLACE THESE
ObjectXY
Glass Well3016.875
Falling Piece033.75
Falling Crystal30-2.25
Falling Crystal30-2.25
with Per Instance Variables > Crystal = 2
Falling Crystal30-2.25
with Per Instance Variables > Crystal = 3
Falling Crystal30-2.25
with Per Instance Variables > Crystal = 4

1.4 The Well

First, give the empty glass a place to stand.

Every part needs the same measurements. Well Layout Entry gives names to those values, and Well Layout keeps them together in one table row. Make the structure before the table. The table’s Entry Type is that structure, so its fields become the columns.

Data StructureWell Layout Entry

One row of the "Well Layout" Data Table. Its fields are the columns of the table. Where the well is on screen and how big it is. One row. columns: the cells across (10); rows: the cells up, the two hidden rows above the top included (22); visibleRows: the rows the player sees (20); cellSize: the size of one cell in meters (1.5); left and bottom: the outer edges of the bottom-left cell, in meters.

columnsNumberdefault 10
rowsNumberdefault 22
visibleRowsNumberdefault 20
cellSizeNumberdefault 1.5
leftNumberdefault 22.5
bottomNumberdefault 1.875
Data TableWell LayoutData Structure: Well Layout Entry
Row 0Well
columns10
rows22
visibleRows20
cellSize1.5
left22.5
bottom1.875

The well has 22 rows, though only 20 show. The hidden rows give a new piece somewhere to arrive, above the crystals the player can see. Row 0 is at the bottom. Y counts upward, while a cell’s column starts at the left.

Well Entry describes a different kind of row: one cell of the stack. Its kind is the number of the crystal there. Give Well that Entry Type. Leave the table empty, because the game will make its rows when the Scene starts.

Data StructureWell Entry

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.

kindNumberdefault 0
Data TableWellData Structure: Well Entry

1.4.1 Empty The Well

A new game needs a clean well. Empty The Well makes one empty entry for every cell, using the measurements from Well Layout. There’s no long list to enter by hand.

This is a Function, a piece of Logic called by another piece when it needs a job done. Empty The Well has no answer to return. It does its work and finishes.

FunctionEmpty The Well

Makes the Well table one empty row per cell of the well (Well Layout: columns x rows), or empties the rows it already has.

ACTIONS
1Repeatdata('Well Layout', 0, 'columns') * data('Well Layout', 0, 'rows')times
Each time
2Control Data Row
Action Name
addedRow
Table
Well
Row
blockLocal("index")
Values
{"kind":"0"}

1.4.2 Well Cell Index

A cell has a column and a row, but the table is one long list. Well Cell Index joins those two ways of counting. It counts every whole row below the cell, then the cells to its left.

Its Return Type is Number. One Return Action gives the caller that cell’s place in the table. You’ll use this answer both to draw a crystal and to write one down.

FunctionWell Cell Index

Answers which row of the Well table holds the well cell at this column and row (row * columns + column, Well Layout).

PARAMETERS
NameTypeStarts at
columnNumber0
rowNumber0
RETURN TYPENumber
ACTIONS
1Return
Value
row * data('Well Layout', 0, 'columns') + column

1.4.3 Cell At

A Well Cell knows where it stands on the screen. Cell At works backward from that point to its column and row in the well. It counts whole cells from the lower-left edge.

The answer needs both a column and a row, so make Cell At Result first. Choose it as the Function’s Return Type. The Return Action then has a field for each part of that answer.

Data StructureCell At Result

What the Function "Cell At" returns (its former Outputs). column, row: the cell of the well a point is in (0, 0 is the bottom-left cell).

columnNumberdefault 0
rowNumberdefault 0
FunctionCell At

Answers which well cell is under a point in the world (meters): its column from the left and row from the bottom (Well Layout).

PARAMETERS
NameTypeStarts at
xNumber0
yNumber0
RETURN TYPECell At Result (a Data Structure)
ACTIONS
1Declare Variable
Name
cellSize
Value
0
2Declare Variable
Name
column
Value
0
3Declare Variable
Name
row
Value
0
4Change Variable
Variable
cellSize
Value
data('Well Layout', 0, 'cellSize')
5Change Variable
Variable
column
Value
floor((x - data('Well Layout', 0, 'left')) / cellSize)
6Change Variable
Variable
row
Value
floor((y - data('Well Layout', 0, 'bottom')) / cellSize)
7Return
column
column
row
row

1.4.4 Cell Middle

Now go the other way. Give Cell Middle a cell’s column and row, and it finds the middle of that cell on screen. Build The Well will use those points to place its windows.

Cell Middle Result holds the answer’s X and Y. Make it before the Function. The half cell in the sum takes a point on the edge into the middle.

Data StructureCell Middle Result

What the Function "Cell Middle" returns (its former Outputs). x, y: the middle of a cell of the well, in meters.

xNumberdefault 0
yNumberdefault 0
FunctionCell Middle

Answers where the middle of a well cell is in the world (meters), from its column and row (Well Layout).

PARAMETERS
NameTypeStarts at
columnNumber0
rowNumber0
RETURN TYPECell Middle Result (a Data Structure)
ACTIONS
1Declare Variable
Name
cellSize
Value
0
2Declare Variable
Name
x
Value
0
3Declare Variable
Name
y
Value
0
4Change Variable
Variable
cellSize
Value
data('Well Layout', 0, 'cellSize')
5Change Variable
Variable
x
Value
data('Well Layout', 0, 'left') + (column + 0.5) * cellSize
6Change Variable
Variable
y
Value
data('Well Layout', 0, 'bottom') + (row + 0.5) * cellSize
7Return
x
x
y
y

1.4.5 Show Well Cell

Each small window needs to remember which entry it watches. Show Well Cell Data holds that place, cellIndex. Make the structure, then add it through the + on this Logic’s Data Structures block. The Object receives it when the Logic is attached.

Data StructureShow Well Cell Data

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.

cellIndexNumberdefault 0

Show Well Cell finds its cell when created, using its position to ask Cell At and then Well Cell Index. After that, wellChanged means look again at the same table entry. The entry’s kind chooses the Sprite’s frame. An empty entry shows the faint outline of an empty cell, waiting for a colored crystal to take its place.

Each Function call has an Action Name. The call named myCell gives column and row back; thisCellIndex gives one number through its value. The card shows how the next Action reads each answer.

Logic ComponentShow Well CellAttach to: Well Cell

One cell of the well: it finds its cell from where it is, then shows the crystal stored there whenever the well changes.

Attach toAny Object
DATA STRUCTURES
Show Well Cell Dataself.showWellCellData
When Created
WhenCreate
1Cell AtFunction
Action Name
myCell
x
self.transform.position.x
y
self.transform.position.y
2Well Cell IndexFunction
Action Name
thisCellIndex
column
myCell.column
row
myCell.row
3Change Component Value
Component Value
self.showWellCellData.cellIndex
Value
thisCellIndex.value
4Set Frame
Frame
data('Well', self.showWellCellData.cellIndex, 'kind')
On Well Changed
WhenwellChangedCustom Event
1Set Frame
Frame
data('Well', self.showWellCellData.cellIndex, 'kind')

Attach it to Well Cell now. Its Data Structure is marked From Logic, so attaching brings it along. There’s no second copy to add. Its picture is Crystal Blocks, which contains the empty cell and each colored crystal.

ObjectWell Cell
Transform
Physics > Physics ObjectOn
Sprite
SpriteCrystal Blocks
Transform > SizeX 1.5, Y 1.5
Data Structure
From LogicShow Well Cell
Data StructureShow Well Cell Data
Logic
Show Well Cell

1.4.6 Build The Well

The well gets its windows when the Scene begins. Build The Well moves along each visible row, creating a Well Cell in every column. Then it goes up to the next row.

The outer Repeat remembers its row as cellRow. The inner Repeat counts columns, so its own local.index means a different count. Keeping cellRow lets the two loops work together. Cell Middle supplies each new window’s position.

Declare the Shared Variable wellCell on the Logic’s block before building its Actions. Choose Object, Constant and the Well Cell kind. The Glass Well will give that Variable its actual Object resource.

Logic ComponentBuild The WellAttach to: Glass Well

When a game begins, empties the Well table (Empty The Well) and makes a Well Cell on every visible cell of the well, row by row from the bottom.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
wellCellObjectConstantNone
OBJECT KINDS
wellCellWell Cell
When the Scene Starts
WhenScene Start
1Empty The WellFunction
2Repeatdata('Well Layout', 0, 'visibleRows')times
Each time
3Declare Variable
Name
cellRow
Value
local.index
4Repeatdata('Well Layout', 0, 'columns')times
Each time
5Cell MiddleFunction
Action Name
newCellMiddle
column
local.index
row
cellRow
6Create Instance
Object to Create
@self.wellCell
X
newCellMiddle.x
Y
newCellMiddle.y

Make Glass Well, attach Build The Well, and set its wellCell Shared Variable to Well Cell. That’s the kind of Object Create Instance will make. There is no Well Cell to place by hand; Build The Well makes every one when the game begins.

ObjectGlass Well
Transform
Physics > Physics ObjectOn
Sprite
Scene Depth Draw Order-10
SpriteGlass Well
Transform > SizeX 16.5, Y 31.5
Logic
Build The Well
Shared Variables
wellCellWell Cell

Place Glass Well using the first PLACE THESE line. Run the game. The empty windows fill the glass, row by row in position, although they appear together when the Scene starts. Nothing falls yet.

1.5 The Shape Inside a Piece

The well is ready for something to fall into it.

Make Piece Kinds Entry first. The Piece Kinds table gives those values to every shape. Its first row is I, followed by the other kinds in the card’s order.

Data StructurePiece Kinds Entry

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).

frameNumberdefault 0
colorColordefault #ffffff
rotationColumnNumberdefault 1
rotationRowNumberdefault 2
Data TablePiece KindsData Structure: Piece Kinds Entry
Row 0I
frame1
color#3cd6ff
rotationColumn1.5
rotationRow1.5
Row 1O
frame2
color#ffce3a
rotationColumn1.5
rotationRow2.5
Row 2T
frame3
color#b268ff
rotationColumn1
rotationRow2
Row 3S
frame4
color#60e25c
rotationColumn1
rotationRow2
Row 4Z
frame5
color#ff486e
rotationColumn1
rotationRow2
Row 5J
frame6
color#3a74ff
rotationColumn1
rotationRow2
Row 6L
frame7
color#ff9232
rotationColumn1
rotationRow2

The shape itself lives in Piece Shapes, whose rows name each crystal’s place inside the piece’s box. Make Piece Shapes Entry, then the table. Download the card’s CSV and use Import CSV in the table editor. Its first rows let you compare the result.

Data StructurePiece Shapes Entry

One row of the "Piece Shapes" Data Table. Its fields are the columns of the table. The four crystals of each kind of piece as it appears (rotation up). Row index = (kind - 1) * 4 + crystal - 1; column and row are the crystal's cell in the piece's 4 x 4 box, row 0 at the bottom. The other rotations are this shape rotated clockwise about the kind's rotation point (Piece Kinds; Crystal Cell).

columnNumberdefault 0
rowNumberdefault 0
Data TablePiece ShapesData Structure: Piece Shapes Entry
IMPORT CSV28 rows, from a file
FIRST ROWS3 of 28, to compare
Row 0I 1
column0
row2
Row 1I 2
column1
row2
Row 2I 3
column2
row2

1.5.1 Crystal Cell

Imagine carrying a little square box around the well, with the piece’s crystals always sitting at particular places inside it. Crystal Cell finds one crystal there, then rotates that place around the kind’s turning point when needed.

It returns Crystal Cell Result, a column and row inside the box. Make that structure first. The well’s column and row aren’t added here; callers add them when they need a cell in the well.

Data StructureCrystal Cell Result

What the Function "Crystal Cell" returns (its former Outputs). column, row: where one crystal of a piece sits, counted from the corner of the piece, for its kind and rotation.

columnNumberdefault 0
rowNumberdefault 0
FunctionCrystal Cell

Answers where crystal 1-4 of a piece of this kind and rotation sits in its 4 x 4 box: its column and row from the box's bottom-left corner (Piece Shapes).

PARAMETERS
NameTypeStarts at
kindNumber0
rotationNumber0
crystalNumber1
RETURN TYPECrystal Cell Result (a Data Structure)
ACTIONS
1Declare Variable
Name
shapeRow
Value
0
2Declare Variable
Name
column
Value
0
3Declare Variable
Name
row
Value
0
4Declare Variable
Name
rotationColumn
Value
0
5Declare Variable
Name
rotationRow
Value
0
6Declare Variable
Name
rotatedColumn
Value
0
7Change Variable
Variable
shapeRow
Value
(kind - 1) * 4 + crystal - 1
8Change Variable
Variable
column
Value
data('Piece Shapes', shapeRow, 'column')
9Change Variable
Variable
row
Value
data('Piece Shapes', shapeRow, 'row')
10Change Variable
Variable
rotationColumn
Value
data('Piece Kinds', kind - 1, 'rotationColumn')
11Change Variable
Variable
rotationRow
Value
data('Piece Kinds', kind - 1, 'rotationRow')
12Repeatrotationtimes
Each time
13Change Variable
Variable
rotatedColumn
Value
rotationColumn + (row - rotationRow)
14Change Variable
Variable
row
Value
rotationRow - (column - rotationColumn)
15Change Variable
Variable
column
Value
rotatedColumn
16Return
column
column
row
row

1.5.2 Well Cell Filled

A piece must stop at the floor, even though there is no crystal underneath it. Well Cell Filled treats the floor and side walls as taken. Inside, the table answers.

Its Boolean answer is true for taken and false for free, including cells that fall outside the glass. Look at its first If. The || joins alternative ways to be outside, while && inside the other path requires both tests. The card-reading page explains how those expressions go into one Condition field.

FunctionWell Cell Filled

Answers whether a cell is taken: outside the well's sides or below its floor counts as taken, above its top as free, otherwise a crystal there.

PARAMETERS
NameTypeStarts at
columnNumber0
rowNumber0
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Boolean
Name
isFilled
Value
false
2Change Variable
Variable
isFilled
Value
false
3Ifcolumn < 0 || column >= data('Well Layout', 0, 'columns') || row < 0
Then
4Change Variable
Variable
isFilled
Value
true
5Otherwise
6Well Cell IndexFunction
Action Name
testedCellIndex
column
column
row
row
7Ifrow < data('Well Layout', 0, 'rows') && data('Well', testedCellIndex.value, 'kind') != 0
Then
8Change Variable
Variable
isFilled
Value
true
9Return
Value
isFilled

1.5.3 Piece Fits

One crystal can fit while the rest hit a wall. Piece Fits checks the whole shape before saying yes. It asks Crystal Cell for each crystal, adds the box’s position, and asks whether that well cell is filled.

One filled cell makes fits false. Checking the remaining crystals can never turn it true again during that call. In its Repeat, local.index starts at 0, while the crystals start at 1. That’s why the card adds one.

FunctionPiece Fits

Answers whether a piece of this kind and rotation fits with its 4 x 4 box's bottom-left corner at this cell: each of its four crystals on a free cell of the well (Well Cell Filled).

PARAMETERS
NameTypeStarts at
kindNumber0
rotationNumber0
columnNumber0
rowNumber0
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Boolean
Name
fits
Value
true
2Declare Variable
Name
crystalColumn
Value
0
3Declare Variable
Name
crystalRow
Value
0
4Change Variable
Variable
fits
Value
true
5Repeat4times
Each time
6Crystal CellFunction
Action Name
testedCrystal
kind
kind
rotation
rotation
crystal
local.index + 1
7Change Variable
Variable
crystalColumn
Value
column + testedCrystal.column
8Change Variable
Variable
crystalRow
Value
row + testedCrystal.row
9Well Cell FilledFunction
Action Name
testedCell
column
crystalColumn
row
crystalRow
10IftestedCell.value == true
Then
11Change Variable
Variable
fits
Value
false
12Return
Value
fits

1.5.4 Piece Fits Here

Gravity doesn’t need to describe a whole piece every time it looks below. Piece Fits Here takes the Falling Piece and a shift, then asks Piece Fits about that nearby place. Nothing moves during the question.

The piece Parameter needs a Piece Data Structure. Make Piece now, with the fields on its card, before setting that requirement. We’ll put it on Falling Piece in the next part. That structure is the moving piece’s memory, from its kind to its lock delay.

Data StructurePiece

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).

kindNumberdefault 0
rotationNumberdefault 0
columnNumberdefault 3
rowNumberdefault 18
lockDelaySecondsNumberdefault 0.5
fallSecondsNumberdefault 1
moveDirectionNumberdefault 0
FunctionPiece Fits Here

Answers whether the falling piece, as it is turned now, would fit columnShift columns to the right and rowShift rows up from where it is (Piece Fits); 0 and 0 asks about where it is.

PARAMETERS
NameTypeStarts at
pieceObjectStructural
Required Components
Slot Namepiece
Component TypeData Structure
Required SchemaPiece
columnShiftNumber0
rowShiftNumber0
RETURN TYPEBoolean
ACTIONS
1Declare Variable
Type
Boolean
Name
fits
Value
true
2Piece FitsFunction
Action Name
shiftedPiece
kind
piece.piece.kind
rotation
piece.piece.rotation
column
piece.piece.column + columnShift
row
piece.piece.row + rowShift
3Change Variable
Variable
fits
Value
shiftedPiece.value
4Return
Value
fits

Run again if you’d like. The well still waits. Those questions are ready, but nothing has asked them yet.

1.6 The Falling Piece

Now give the invisible piece its work.

Make Spawn Piece, Piece Gravity, Lock Piece, Read The Move Keys and Move Piece by name. Their Actions come below. Add the Piece Data Structure to Falling Piece, then attach those Logics in its card’s order. Spawn Piece comes first so it sets the new piece before gravity reads it.

Read The Move Keys Data belongs to its Logic. Make it now and supply it through that Logic’s Data Structures block. Its two switches remember which keys are held. The Object receives this structure when that Logic is attached.

Data StructureRead The Move Keys Data

Object Data of Logic "Read The Move Keys" (its state that lasts between Events). isLeftHeld, isRightHeld: Move Left / Move Right is held down.

isLeftHeldBooleandefault false
isRightHeldBooleandefault false
ObjectFalling Piece
Transform
Physics > Physics ObjectOn
Data Structure
Data StructurePiece
Data Structure
From LogicRead The Move Keys
Data StructureRead The Move Keys Data
Logic
Spawn Piece
Piece Gravity
Lock Piece
Read The Move Keys
Move Piece

Where a Logic card names Piece under DATA STRUCTURES, choose the existing structure there. Keep the name piece, so self.piece reaches the same fields. Attach the Logic before completing its self expressions, and check the Object has one Piece component.

1.6.1 Spawn Piece

A new piece arrives upright, near the middle of the well. Spawn Piece chooses its kind and places its box in the hidden rows. If there’s room below, it brings the piece down before telling the pictures to show it.

Its startColumn Shared Variable names where the box begins. spawnPiece is sent on this Object; pieceMoved tells every picture that listens. Nothing draws the piece yet, but that second message will have listeners soon.

Logic ComponentSpawn PieceAttach to: Falling Piece

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.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
SHARED VARIABLES
NameTypeKindStarting Value
startColumnNumberConstant3
When Created
WhenCreate
1Trigger Custom Event on Object
Custom Event
spawnPiece
On Spawn Piece
WhenspawnPieceCustom Event
1Generate Random Integer
Action Name
randomKind
Minimum
1
Maximum
dataCount('Piece Kinds')
2Change Component Value
Component Value
self.piece.kind
Value
randomKind.value
3Change Component Value
Component Value
self.piece.rotation
Value
0
4Change Component Value
Component Value
self.piece.column
Value
@self.startColumn
5Change Component Value
Component Value
self.piece.row
Value
data('Well Layout', 0, 'rows') - 4
6Piece Fits HereFunction
Action Name
roomBelowNewPiece
piece
self
columnShift
0
rowShift
-1
7IfroomBelowNewPiece.value == true
Then
8Change Component Value
Component Value
self.piece.row
Value
self.piece.row - 1
9Trigger Custom Event
Custom Event
pieceMoved

1.6.2 Piece Gravity

The piece falls one row each second. Near the floor, gravity asks whether another step would fit. If it won’t, the piece lands and rests before it locks.

That pause has a name, lock delay. For half a second you can still slide the piece. If it finds room below by the end of the delay, it falls again. Otherwise it locks where it rests.

Make the gravity timer Event with its Falling and Resting Modes before building the Switch Mode Actions. Falling starts on. Resting has no Actions, so the timer can ring without pulling the piece through the floor.

pieceLanded makes that change and starts the lockDelay timer. Its lockDelayStarted message will dim the crystals later. Make the Modes first, then follow each Event’s card with that landing in mind.

Logic ComponentPiece GravityAttach to: Falling Piece

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.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
On Spawn Piece
WhenspawnPieceCustom Event
1Start Recurring Timer
Timer Name
"gravity"
Repeat Every (s)
self.piece.fallSeconds
2Switch Mode
Event
On the Gravity Timer
Mode
Falling
On the Gravity Timer
WhenRecurring TimerTimer name: gravity
ModeFalling (Starting Mode)
1Piece Fits HereFunction
Action Name
roomBelowOnGravity
piece
self
columnShift
0
rowShift
-1
2IfroomBelowOnGravity.value == true
Then
3Change Component Value
Component Value
self.piece.row
Value
self.piece.row - 1
4Trigger Custom Event
Custom Event
pieceMoved
5Piece Fits HereFunction
Action Name
roomBelowAfterFall
piece
self
columnShift
0
rowShift
-1
6IfroomBelowAfterFall.value == false
Then
7Trigger Custom Event on Object
Custom Event
pieceLanded
8Otherwise
9Trigger Custom Event on Object
Custom Event
pieceLanded
On Piece Landed
WhenpieceLandedCustom Event
1Switch Mode
Event
On the Gravity Timer
Mode
Resting
2Start Recurring Timer
Timer Name
"lockDelay"
Repeat Every (s)
self.piece.lockDelaySeconds
Ring Once
Yes
3Trigger Custom Event
Custom Event
lockDelayStarted
On the Gravity Timer
WhenRecurring TimerTimer name: gravity
ModeResting
(no Actions)
On the Lock Delay Timer
WhenRecurring TimerTimer name: lockDelay
1Piece Fits HereFunction
Action Name
roomBelowAfterDelay
piece
self
columnShift
0
rowShift
-1
2IfroomBelowAfterDelay.value == true
Then
3Switch Mode
Event
On the Gravity Timer
Mode
Falling
4Return
5Trigger Custom Event on Object
Custom Event
lockPiece

1.6.3 Lock Piece

Once the delay ends, the moving shape becomes part of the stack. Lock Piece writes the kind into each crystal’s table entry. The old moving piece is still the same Object; it can now carry a new kind at the top.

Only after all its crystals are written does wellChanged go out. Every Well Cell reads the finished piece before the last message asks for a new one at the top. There is no half-written shape for the pictures to catch.

Logic ComponentLock PieceAttach to: Falling Piece

Writes the falling piece's four crystals into the Well, tells everyone the well changed, and asks for the next piece.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
On Lock Piece
WhenlockPieceCustom Event
1Declare Variable
Name
lockedKind
Value
0
2Stop Recurring Timer
Timer Name
"gravity"
3Stop Recurring Timer
Timer Name
"lockDelay"
4Change Variable
Variable
lockedKind
Value
self.piece.kind
5Repeat4times
Each time
6Crystal CellFunction
Action Name
lockedCrystal
kind
self.piece.kind
rotation
self.piece.rotation
crystal
local.index + 1
7Well Cell IndexFunction
Action Name
lockedCell
column
self.piece.column + lockedCrystal.column
row
self.piece.row + lockedCrystal.row
8Declare Variable
Name
cellIndex
Value
lockedCell.value
9Set Data Cell
Table
Well
Row
cellIndex
Column
"kind"
Value
lockedKind
10Control Sound
Sound
Lock Click
11Trigger Custom Event
Custom Event
wellChanged
12Trigger Custom Event on Object
Custom Event
spawnPiece

1.6.4 Read The Move Keys

Tap Left, and you want a single step. Hold it, and you want more. Read The Move Keys gives that tap a little room, then asks for repeated moves while the key stays down.

Its autoRepeatDelaySeconds is 0.15 seconds, a pause that keeps a tap from becoming several moves. The repeats then come 0.03 seconds apart. Those Shared Variables belong on the Logic’s block and arrive on Falling Piece with it.

Both keys can be down together. The newest press chooses the direction. Let it go while the other is held, and the older key takes over. Those two switches remember the keys.

Logic ComponentRead The Move KeysAttach to: Falling Piece

Hears Move Left and Move Right: keeps the piece's moveDirection (the key pressed last wins) and asks for one move (moveOnce) at a press and again and again while the key is held (auto-repeat). It stays on between pieces, so a key let go then is heard too.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
Read The Move Keys Dataself.readTheMoveKeysData
SHARED VARIABLES
NameTypeKindStarting Value
autoRepeatDelaySecondsNumberConstant0.15
autoRepeatSecondsNumberConstant0.03
When Move Left Is Pressed
WhenCommand PressedCommand: Move Left
1Change Component Value
Component Value
self.readTheMoveKeysData.isLeftHeld
Value
true
2Change Component Value
Component Value
self.piece.moveDirection
Value
-1
3Trigger Custom Event on Object
Custom Event
moveOnce
4Start Recurring Timer
Timer Name
"autoRepeat"
Repeat Every (s)
@self.autoRepeatSeconds
First Delay (s)
@self.autoRepeatDelaySeconds
When Move Right Is Pressed
WhenCommand PressedCommand: Move Right
1Change Component Value
Component Value
self.readTheMoveKeysData.isRightHeld
Value
true
2Change Component Value
Component Value
self.piece.moveDirection
Value
1
3Trigger Custom Event on Object
Custom Event
moveOnce
4Start Recurring Timer
Timer Name
"autoRepeat"
Repeat Every (s)
@self.autoRepeatSeconds
First Delay (s)
@self.autoRepeatDelaySeconds
When Move Left Is Released
WhenCommand ReleasedCommand: Move Left
1Change Component Value
Component Value
self.readTheMoveKeysData.isLeftHeld
Value
false
2Ifself.piece.moveDirection == (-1) && self.readTheMoveKeysData.isRightHeld == true
Then
3Change Component Value
Component Value
self.piece.moveDirection
Value
1
4Start Recurring Timer
Timer Name
"autoRepeat"
Repeat Every (s)
@self.autoRepeatSeconds
First Delay (s)
@self.autoRepeatDelaySeconds
5Otherwise
6Ifself.piece.moveDirection == (-1)
Then
7Change Component Value
Component Value
self.piece.moveDirection
Value
0
8Stop Recurring Timer
Timer Name
"autoRepeat"
When Move Right Is Released
WhenCommand ReleasedCommand: Move Right
1Change Component Value
Component Value
self.readTheMoveKeysData.isRightHeld
Value
false
2Ifself.piece.moveDirection == 1 && self.readTheMoveKeysData.isLeftHeld == true
Then
3Change Component Value
Component Value
self.piece.moveDirection
Value
-1
4Start Recurring Timer
Timer Name
"autoRepeat"
Repeat Every (s)
@self.autoRepeatSeconds
First Delay (s)
@self.autoRepeatDelaySeconds
5Otherwise
6Ifself.piece.moveDirection == 1
Then
7Change Component Value
Component Value
self.piece.moveDirection
Value
0
8Stop Recurring Timer
Timer Name
"autoRepeat"
On the Auto Repeat Timer
WhenRecurring TimerTimer name: autoRepeat
1Trigger Custom Event on Object
Custom Event
moveOnce

1.6.5 Move Piece

A key can ask for a move, but it can’t push the piece through a wall. Move Piece asks Piece Fits Here about the neighboring column first. Only a yes changes the column and sends pieceMoved.

Logic ComponentMove PieceAttach to: Falling Piece

Moves the falling piece one column in its moveDirection when asked (moveOnce, from Read The Move Keys), if the piece fits there.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
On Move Once
WhenmoveOnceCustom Event
1Piece Fits HereFunction
Action Name
roomBeside
piece
self
columnShift
self.piece.moveDirection
rowShift
0
2IfroomBeside.value == true
Then
3Change Component Value
Component Value
self.piece.column
Value
self.piece.column + self.piece.moveDirection
4Control Sound
Sound
Move Tick
5Trigger Custom Event
Custom Event
pieceMoved

Place Falling Piece now, using its setup line under PLACE THESE, and leave the remaining placements until the next part. There’s still no picture to see.

1.7 The Crystals You See

Now the moving numbers get their pictures.

1.7.1 Show Piece Crystal

Each Falling Crystal knows which crystal it is, and which Falling Piece to watch. The crystal Per Instance Variable gives it that number. The piece Shared Variable names the Falling Piece Object, whose Piece structure holds the moving shape.

On pieceMoved, Show Piece Crystal finds its crystal inside the shape. It adds the piece’s place in the well and asks Cell Middle where to stand. Then it shows the kind’s colored frame, hiding crystals above the visible rows.

The landing message does a smaller job. Over the lock delay, Alpha fades toward dimmedAlpha, making the resting piece look different from a moving one. A new move stops that fade and brings it back to full brightness.

Logic ComponentShow Piece CrystalAttach to: Falling Crystal

One crystal of the falling piece: whenever the piece moves, it goes to its cell and shows the piece's color; it hides in the two hidden rows above the well.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
pieceObjectConstantNone
dimmedAlphaNumberConstant0.55
PER INSTANCE VARIABLES
NameTypeStarting Value
crystalNumber1
OBJECT KINDS
pieceFalling Piece
On Piece Moved
WhenpieceMovedCustom Event
1Control Tween
Which
Value
Value
self.transform.opacity
2Set Alpha
3Crystal CellFunction
Action Name
thisCrystal
kind
@self.piece.piece.kind
rotation
@self.piece.piece.rotation
crystal
self.crystal
4Declare Variable
Name
cellColumn
Value
@self.piece.piece.column + thisCrystal.column
5Declare Variable
Name
cellRow
Value
@self.piece.piece.row + thisCrystal.row
6Cell MiddleFunction
Action Name
thisCellMiddle
column
cellColumn
row
cellRow
7Set Position
X
thisCellMiddle.x
Y
thisCellMiddle.y
8Set Frame
Frame
data('Piece Kinds', @self.piece.piece.kind - 1, 'frame')
9IfcellRow < data('Well Layout', 0, 'visibleRows')
Then
10Set Visible
Visible
Yes
11Otherwise
12Set Visible
Visible
No
On Lock Delay Started
WhenlockDelayStartedCustom Event
1Tween
Value
self.transform.opacity
Target
@self.dimmedAlpha
Duration
@self.piece.piece.lockDelaySeconds

Make Falling Crystal and attach the Logic. In its Shared Variables, set piece to the Falling Piece resource. All its placements watch that same kind of Object, and this Scene has one Falling Piece.

ObjectFalling Crystal
Transform
Physics > Physics ObjectOn
Sprite
Scene Depth Draw Order8
SpriteCrystal Blocks
Transform > SizeX 1.5, Y 1.5
Logic
Show Piece Crystal
Shared Variables
pieceFalling Piece

Place the Falling Crystals using the remaining setup lines. They start together below the window. Change each placement’s crystal Per Instance Variable as the card shows, so each watches a different crystal of the shape.

Run the game. The first piece appears near the rim, falls into view, and answers the arrow keys. The empty well is waiting underneath it.

1.8 Play It!

  • Watch a piece fall without touching a key, moving down a row at a time until it meets the floor.
  • Tap Left or Right. It slides, and a short tick marks a move that fits.
  • Hold a direction. After its small pause, the piece keeps stepping toward the wall.
  • Hold both directions together. Release the newer key and watch the piece move the other way, answering the older key still held down.
  • Let a piece reach the floor. Its crystals dim during the delay, then become part of the stack with Lock Click.
  • Wait for the next piece. The locked crystals stay where they landed, and the new piece comes from above.

1.9 How It Works

The piece moves. Its crystals show the new place. A lock writes numbers, and the well’s windows show those instead. The pictures change because the numbers did.

That separation lets every part use the same questions, even though they put the answers to different work. Piece Fits knows about room, without drawing anything. Show Well Cell knows about drawing, without deciding where a piece belongs.

1.10 If It Didn’t Work

The empty well has no windows? Look at Glass Well’s Build The Well attachment, then its wellCell Shared Variable. It should name Well Cell. Run again, and the Scene creates the windows for you.

Only one crystal seems to move? The placements may all have the same crystal value. Change their Per Instance Variables to match the setup card, then run again. Each picture now has its own place in the shape.

A self name stays red? Read it from the left, and compare its Data Structure name and field. Attach the Logic to its Object and give it the structures its card lists. Retype the expression once those names exist.

Does the piece pass through a wall? Compare Piece Fits Here with Well Cell Filled, watching which answer each Function returns to its caller. A true answer means filled in one Function and fits in the other. Match the If tests with their cards, then try the wall again.

The piece won’t fall? In Piece Gravity, check the gravity Event starts in Falling. Make both Modes first. Choose them in Switch Mode, then run again and watch the next piece begin to fall.

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

1.11 Challenge

What kind of fall would suit your own game? Try changing fallSeconds in Piece, then run it and watch the piece cross the well. You could give a slow piece more time for planning, or make a quick one demand earlier choices. Keep a copy of the working project while you explore.

Your well can fill now. Next, its pieces learn to rotate and drop.