Build a GameCreating Crystal Cascade

2  Rotating and Dropping

Goal

Your pieces rotate, and against a wall they step aside to do it. A ghost waits where each piece will land. Down hurries the piece there, and Space sends it there at once.

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

Crystal Cascade, Chapter 2 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.

2.1 The Idea

A long piece falls beside the wall. You can slide it, but its shape stays the way it arrived. This chapter lets it rotate, and gives you a choice about how it falls.

An outline waits on the floor below it. That’s the ghost, showing where the piece would land if it fell straight down. Press Space and the piece lands there, leaving pale streaks of light behind. Hold Down and it falls faster instead.

Rotation needs a little room. A piece beside a wall may fit after a small step away, even when it won’t fit where it stands. That step is a kick. The game tries this rotation’s nearby places in order and keeps the first that fits.

You already have the question it needs, Piece Fits. The new parts ask that question about possible places before changing the real piece. The ghost asks it all the way down to the floor. Rotation asks it about the shape after its turn.

2.2 The Plan

  • A piece spawns or slides -> Find Landing Row searches downward -> pieceMoved redraws both the piece and its ghost.
  • A rotation key -> Rotate With Kicks tests nearby places. A successful turn updates the landing row and sends pieceShifted.
  • Down -> a shorter gravity timer. Let go, and that same timer returns to its normal pace.
  • Space -> hardDropped draws the streaks -> Hard Drop Piece moves to landingRow and locks at once.
  • A slide or rotation after touchdown -> Piece Gravity can restart its lock delay, up to its limit.

2.3 Set Up

Continue from Chapter 1, or open its catch-up project. The art and sounds are already in the Starter Kit. Add the Commands on this setup card, keeping both keys where a Command has two bindings.

Leave PLACE THESE for the ghost part below. No other pictures need placing: the hard drop creates its own streaks when you play.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Drop Streak Art
FX Soft Dot
Ghost Blocks
SOUNDS (from the starter kit)
Rotate Chime
Hard Drop
COMMANDS (Game Settings > Input Manager)
Rotate ClockwiseButton
KeybindingsUp Arrow, X
Rotate CounterclockwiseButton
KeybindingsZ, Control
Rotate 180Button
KeybindingsA
Soft DropButton
KeybindingsDown Arrow
Hard DropButton
KeybindingsSpace
SCENE Gameplay: PLACE THESE
ObjectXY
Ghost Crystal30-2.25
with Per Instance Variables > Shows Landing = Yes
Ghost Crystal30-2.25
with Per Instance Variables > Crystal = 2
with Per Instance Variables > Shows Landing = Yes
Ghost Crystal30-2.25
with Per Instance Variables > Crystal = 3
with Per Instance Variables > Shows Landing = Yes
Ghost Crystal30-2.25
with Per Instance Variables > Crystal = 4
with Per Instance Variables > Shows Landing = Yes

2.4 Where the Piece Will Land

The outline needs an answer before it can draw anything.

Add the fields marked green in Piece. landingRow will hold the answer, while hardDropRows remembers the distance of a hard drop. The remaining fields remember touchdown and lock-delay history; we’ll use them in the last part.

Data StructurePieceAddDelete

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.

kindNumberdefault 0
rotationNumberdefault 0
columnNumberdefault 3
rowNumberdefault 18
lockDelaySecondsNumberdefault 0.5
fallSecondsNumberdefault 1
moveDirectionNumberdefault 0
landingRowNumberdefault 0
hardDropRowsNumberdefault 0
lowestRowNumberdefault 0
lockResetsNumberdefault 0
hasTouchedDownBooleandefault false
isLockDelayOverBooleandefault false

2.4.1 Find Landing Row

Find Landing Row moves an imaginary copy downward, leaving the falling piece where it is. It begins at the piece’s current row. A step fits? It lowers landingRow and asks again.

The floor counts as filled, as it did in Chapter 1. Eventually the next step fails and canFall becomes false. That ends the While. The answer stays in the Piece structure, ready for several parts to share.

Its piece Parameter needs the existing Piece structure. It can then read the piece passed into the Function, without being attached to an Object itself.

FunctionFind Landing Row

Works out the lowest row the falling piece's box can fall to, straight down from where it is, and keeps it in the Piece's landingRow.

PARAMETERS
NameTypeStarts at
pieceObjectStructural
Required Components
Slot Namepiece
Component TypeData Structure
Required SchemaPiece
ACTIONS
1Declare Variable
Type
Boolean
Name
canFall
Value
true
2Change Component Value
Component Value
piece.piece.landingRow
Value
piece.piece.row
3Change Variable
Variable
canFall
Value
true
4WhilecanFall == true
Each time
5Piece FitsFunction
Action Name
roomUnderLanding
kind
piece.piece.kind
rotation
piece.piece.rotation
column
piece.piece.column
row
piece.piece.landingRow - 1
6IfroomUnderLanding.value == true
Then
7Change Component Value
Component Value
piece.piece.landingRow
Value
piece.piece.landingRow - 1
8Otherwise
9Change Variable
Variable
canFall
Value
false

2.4.2 Spawn Piece and Move Piece

A landing row becomes stale when the piece changes columns. Add the green Find Landing Row call to Spawn Piece and Move Piece. Each finds the new answer before sending pieceMoved, so pictures receive a whole update.

Move Piece also sends pieceShifted on its own Object. Nothing listens yet. Piece Gravity will hear it at the end of this chapter, when sliding earns more time before a lock.

Logic ComponentSpawn PieceAttach to: Falling PieceAddDelete

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
9Find Landing RowFunction
piece
self
10Trigger Custom Event
Custom Event
pieceMoved
Logic ComponentMove PieceAttach to: Falling PieceAddDelete

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
5Find Landing RowFunction
piece
self
6Trigger Custom Event
Custom Event
pieceMoved
7Trigger Custom Event on Object
Custom Event
pieceShifted

The invisible search is ready. Now give it an outline.

2.5 The Ghost

The ghost uses the same drawing Logic as the falling crystals. It changes which row that Logic reads.

2.5.1 Show Piece Crystal

Add showsLanding as a Per Instance Variable on this Logic’s block before changing the Event that reads it. An ordinary Falling Crystal leaves it off. A Ghost Crystal turns it on, making cellRow start from landingRow instead of the moving piece’s row.

The ghost keeps its color when the piece rests. Only the falling crystals fade, and only while the space below is filled. Check the changed lockDelayStarted Event.

Logic ComponentShow Piece CrystalAttach to: Falling Crystal, Ghost CrystalAddDelete

One crystal of the falling piece: whenever the piece moves, it goes to its cell (or, with showsLanding, to its cell in the landing row: the ghost) and shows the piece's color; it hides in the two hidden rows, and the piece's own crystals fade while it rests before locking.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
pieceObjectConstantNone
dimmedAlphaNumberConstant0.55
PER INSTANCE VARIABLES
NameTypeStarting Value
crystalNumber1
showsLandingBooleanNo
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
6Ifself.showsLanding == true
Then
7Change Variable
Variable
cellRow
Value
@self.piece.piece.landingRow + thisCrystal.row
8Cell MiddleFunction
Action Name
thisCellMiddle
column
cellColumn
row
cellRow
9Set Position
X
thisCellMiddle.x
Y
thisCellMiddle.y
10Set Frame
Frame
data('Piece Kinds', @self.piece.piece.kind - 1, 'frame')
11IfcellRow < data('Well Layout', 0, 'visibleRows')
Then
12Set Visible
Visible
Yes
13Otherwise
14Set Visible
Visible
No
On Lock Delay Started
WhenlockDelayStartedCustom Event
1Tween
Value
self.transform.opacity
Target
@self.dimmedAlpha
Duration
@self.piece.piece.lockDelaySeconds
1Ifself.showsLanding == false
Then
2Piece Fits HereFunction
Action Name
roomBelowDimmingPiece
piece
@self.piece
columnShift
0
rowShift
-1
3IfroomBelowDimmingPiece.value == false
Then
4Tween
Value
self.transform.opacity
Target
@self.dimmedAlpha
Duration
@self.piece.piece.lockDelaySeconds

Make Ghost Crystal with Ghost Blocks as its picture, and attach Show Piece Crystal. Choose Falling Piece for its piece Shared Variable. Its lower draw order keeps the outline behind the real crystals.

ObjectGhost Crystal
Transform
Physics > Physics ObjectOn
Sprite
Scene Depth Draw Order4
SpriteGhost Blocks
Transform > SizeX 1.5, Y 1.5
Logic
Show Piece Crystal
Shared Variables
pieceFalling Piece

Place the Ghost Crystals using PLACE THESE. Each has its own crystal number and showsLanding on. They start below the window, together, until the first message puts them at their cells.

Run the game and slide a piece. The ghost waits below the falling piece and follows it sideways, without needing any movement Logic of its own. It never starts its own gravity timer.

2.6 Rotating Beside a Wall

Now the shape can change under your fingers.

The new fields in Piece Kinds Entry name where each kind’s kicks begin and the color its hard-drop streaks will use. Add the fields first, then the values marked green in Piece Kinds.

Data StructurePiece Kinds EntryAddDelete

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.

frameNumberdefault 0
colorColordefault #ffffff
rotationColumnNumberdefault 1
rotationRowNumberdefault 2
firstKickRowNumberdefault 0
streakColorColordefault #ffffff
Data TablePiece KindsData Structure: Piece Kinds EntryAddDelete
Row 0I
frame1
color#3cd6ff
rotationColumn1.5
rotationRow1.5
firstKickRow40
streakColor#7fe6ff
Row 1O
frame2
color#ffce3a
rotationColumn1.5
rotationRow2.5
firstKickRow0
streakColor#ffe27a
Row 2T
frame3
color#b268ff
rotationColumn1
rotationRow2
firstKickRow0
streakColor#cf9dff
Row 3S
frame4
color#60e25c
rotationColumn1
rotationRow2
firstKickRow0
streakColor#9ff09a
Row 4Z
frame5
color#ff486e
rotationColumn1
rotationRow2
firstKickRow0
streakColor#ff8aa4
Row 5J
frame6
color#3a74ff
rotationColumn1
rotationRow2
firstKickRow0
streakColor#83a8ff
Row 6L
frame7
color#ff9232
rotationColumn1
rotationRow2
firstKickRow0
streakColor#ffbd7a

Kicks Entry gives each trial a columnShift and rowShift. Make that structure, then the Kicks table. Its CSV supplies the long list through Import CSV, with the first rows shown for comparison.

Data StructureKicks Entry

One row of the "Kicks" Data Table. Its fields are the columns of the table. Where to try a rotated piece, in order (the standard rotation system's wall kicks): columnShift to the right, rowShift up. Rows 0-39: J, L, S, T, Z and O quarter rotations; 40-79: I quarter rotations; 80-99: 180-degree rotations. Quarter rotation: row = firstKickRow (Piece Kinds) + (from rotation * 2, + 1 counterclockwise) * testsPerRotation + test. 180: row = firstHalfRotationRow + from rotation * testsPerRotation + test (Kicks Layout).

columnShiftNumberdefault 0
rowShiftNumberdefault 0
Data TableKicksData Structure: Kicks Entry
IMPORT CSV100 rows, from a file
FIRST ROWS3 of 100, to compare
Row 0JLSTZ up to right 1
columnShift0
rowShift0
Row 1JLSTZ up to right 2
columnShift-1
rowShift0
Row 2JLSTZ up to right 3
columnShift-1
rowShift1

Kicks Layout describes how that list is grouped. Make its entry structure before the table. The tests for each rotation sit together; the turning direction and current rotation tell the game which group to use.

Data StructureKicks Layout Entry

One row of the "Kicks Layout" Data Table. Its fields are the columns of the table. How the Kicks table is laid out. One row. testsPerRotation: places tried for each rotation, in order; firstHalfRotationRow: where the 180-degree rotations start.

testsPerRotationNumberdefault 5
firstHalfRotationRowNumberdefault 80
Data TableKicks LayoutData Structure: Kicks Layout Entry
Row 0Kicks
testsPerRotation5
firstHalfRotationRow80

2.6.1 First Kick Row

Picture the Kicks table as groups of nearby places, one group after another. First Kick Row finds where this turn’s group begins. It counts the earlier groups.

The half turn uses its own part of the table. The Function returns one number, firstTestRow, leaving the actual trials to the next Function. The long sum is counting rows, rather than moving the piece.

FunctionFirst Kick Row

Answers where the five kicks of a rotation start in the Kicks table, for a piece of this kind rotating by rotateBy from this rotation (its layout is in Kicks Layout).

PARAMETERS
NameTypeStarts at
kindNumber1
rotationNumber0
rotateByNumber1
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
firstTestRow
Value
0
2IfrotateBy == 2
Then
3Change Variable
Variable
firstTestRow
Value
data('Kicks Layout', 0, 'firstHalfRotationRow')
4Change Variable
Variable
firstTestRow
Operator
+=
Value
rotation * data('Kicks Layout', 0, 'testsPerRotation')
5Otherwise
6Change Variable
Variable
firstTestRow
Value
data('Piece Kinds', kind - 1, 'firstKickRow')
7Change Variable
Variable
firstTestRow
Operator
+=
Value
rotation * 2 * data('Kicks Layout', 0, 'testsPerRotation')
8IfrotateBy == (-1)
Then
9Change Variable
Variable
firstTestRow
Operator
+=
Value
data('Kicks Layout', 0, 'testsPerRotation')
10Return
Value
firstTestRow

2.6.2 Rotate With Kicks

Start with the piece against the wall. The new rotation may fail in its present place. The first trial asks about that place anyway; the next asks about a shifted place. A later one might fit above or beside it.

Each time, Piece Fits sees the proposed rotation and the trial’s shifts. The real piece waits. Then its column, row and rotation change together, and hasRotated stops the search. If every trial fails, the piece stays as it was.

That is why the While has two tests. It continues only while there has been no success and untried places remain. After a success, Find Landing Row updates the ghost before the pictures redraw. It sends pieceShifted, as sliding does.

FunctionRotate With Kicks

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.

PARAMETERS
NameTypeStarts at
pieceObjectStructural
Required Components
Slot Namepiece
Component TypeData Structure
Required SchemaPiece
rotateByNumber1
ACTIONS
1Declare Variable
Name
newRotation
Value
0
2Declare Variable
Name
firstTestRow
Value
0
3Declare Variable
Name
test
Value
0
4Declare Variable
Type
Boolean
Name
hasRotated
Value
false
5Change Variable
Variable
newRotation
Value
mod(piece.piece.rotation + rotateBy + 4, 4)
6First Kick RowFunction
Action Name
kicksStart
kind
piece.piece.kind
rotation
piece.piece.rotation
rotateBy
rotateBy
7Change Variable
Variable
firstTestRow
Value
kicksStart.value
8Change Variable
Variable
test
Value
0
9Change Variable
Variable
hasRotated
Value
false
10WhilehasRotated == false && test < data('Kicks Layout', 0, 'testsPerRotation')
Each time
11Piece FitsFunction
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')
12IfroomAfterRotation.value == true
Then
13Change Component Value
Component Value
piece.piece.column
Value
piece.piece.column + data('Kicks', firstTestRow + test, 'columnShift')
14Change Component Value
Component Value
piece.piece.row
Value
piece.piece.row + data('Kicks', firstTestRow + test, 'rowShift')
15Change Component Value
Component Value
piece.piece.rotation
Value
newRotation
16Change Variable
Variable
hasRotated
Value
true
17Change Variable
Variable
test
Operator
+=
Value
1
18IfhasRotated == true
Then
19Control Sound
Sound
Rotate Chime
20Find Landing RowFunction
piece
piece
21Trigger Custom Event
Custom Event
pieceMoved
22Trigger Custom Event on Object
Applies to
piece
Custom Event
pieceShifted

2.6.3 Rotate Piece

The keys can all ask the same Function. Rotate Piece passes a clockwise, counterclockwise or half-turn request to Rotate With Kicks. The Function checks the room. These Events hear the keys.

Attach the new Logic to Falling Piece before building its Events, so it can read the Piece structure already there. We’ll attach the other new Logics below in the same order as the Object’s Changes card.

Logic ComponentRotate PieceAttach to: Falling Piece

Rotates the falling piece when a Rotate key is pressed: clockwise, counterclockwise or 180 degrees (Rotate With Kicks).

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
When Rotate Clockwise Is Pressed
WhenCommand PressedCommand: Rotate Clockwise
1Rotate With KicksFunction
piece
self
rotateBy
1
When Rotate Counterclockwise Is Pressed
WhenCommand PressedCommand: Rotate Counterclockwise
1Rotate With KicksFunction
piece
self
rotateBy
-1
When Rotate 180 Is Pressed
WhenCommand PressedCommand: Rotate 180
1Rotate With KicksFunction
piece
self
rotateBy
2

Run again and rotate a piece in the middle, then near a wall. Watch the shape and ghost change together. A turn that finds no room leaves both where they were.

2.7 Choosing the Fall

Down hurries a fall. Space skips the wait altogether.

2.7.1 Soft Drop Piece

Both choices use the gravity timer from Chapter 1, with no new timer pulling against it. Soft Drop Piece restarts that timer with a shorter interval while Down is held. Releasing Down restores the ordinary fall time. Two different movers would fight over the same piece, so this Logic leaves movement to Piece Gravity.

Make Soft Drop Piece Data first. Supply it through the Logic’s Data Structures block. Its switch remembers whether Down is held between Events, including the arrival of another piece. Set the Shared Variables on the Logic, then attach it after Piece Gravity so its faster timer starts last.

Data StructureSoft Drop Piece Data

Object Data of Logic "Soft Drop Piece" (its state that lasts between Events). isSoftDropping: Soft Drop is held, so the piece falls faster.

isSoftDroppingBooleandefault false
Logic ComponentSoft Drop PieceAttach to: Falling Piece

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.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
Soft Drop Piece Dataself.softDropPieceData
SHARED VARIABLES
NameTypeKindStarting Value
speedUpNumberConstant20
slowestSecondsNumberConstant0.05
When Soft Drop Is Pressed
WhenCommand PressedCommand: Soft Drop
1Change Component Value
Component Value
self.softDropPieceData.isSoftDropping
Value
true
2Trigger Custom Event on Object
Custom Event
fallFaster
When Soft Drop Is Released
WhenCommand ReleasedCommand: Soft Drop
1Trigger Custom Event on Object
Custom Event
fallNormally
On Fall Normally
WhenfallNormallyCustom Event
1Change Component Value
Component Value
self.softDropPieceData.isSoftDropping
Value
false
2Start Recurring Timer
Timer Name
"gravity"
Repeat Every (s)
self.piece.fallSeconds
On Spawn Piece
WhenspawnPieceCustom Event
1Ifself.softDropPieceData.isSoftDropping == true
Then
2Trigger Custom Event on Object
Custom Event
fallFaster
On Fall Faster
WhenfallFasterCustom Event
1Declare Variable
Name
fasterSeconds
Value
0
2Change Variable
Variable
fasterSeconds
Value
self.piece.fallSeconds / @self.speedUp
3IffasterSeconds > @self.slowestSeconds
Then
4Change Variable
Variable
fasterSeconds
Value
@self.slowestSeconds
5Start Recurring Timer
Timer Name
"gravity"
Repeat Every (s)
fasterSeconds

The first new piece should hurry too when you keep Down held. Try releasing it midway through a fall. The same piece returns to its normal pace.

2.7.2 Fade Away

A hard drop leaves light behind, but the light shouldn’t fill the well forever. Make Drop Trail for the sparks. Its tiny pictures come from FX Soft Dot in the Starter Kit.

Particle EffectDrop Trail
Emitter
Emission > Emit Every (ms)0
Emission > Burst Count8
Emitter Shape > Emit Shaperectangle
Emitter Shape > Shape Sizew 1.219, h 0.281
Motion
Lifecycle > Each Particle Lives (ms)min 180, max 460
Motion > Speed (m/s)min 2.813, max 15
Motion > Angle (Deg)min 82, max 98
Appearance
Appearance > Particle SpriteFX Soft Dot
Appearance > Color#C8F4FF
(Color first: choosing Multiple Colors hides it)
Appearance > Color SourceMultiple Colors
Appearance > Multiple Colors#FFFFFF, #C8F4FF, #8FE3FF
Appearance > Glow1.3
Appearance Over Life > Scalestart 0.45, end 0
Appearance Over Life > Alphastart 0.85, end 0

Fade Away starts a streak bright, fades it, then removes that streak Object. The tween and removal timer have different jobs. The picture can become invisible before the Object leaves the game.

Logic ComponentFade AwayAttach to: Drop Streak

A short-lived effect: it fades out over fadeSeconds and removes itself after removeSeconds.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
startAlphaNumberConstant0.95
fadeSecondsNumberConstant0.26
removeSecondsNumberConstant0.3
When Created
WhenCreate
1Set Alpha
Alpha
@self.startAlpha
2Tween
Value
self.transform.opacity
Target
0
Duration
@self.fadeSeconds
Ease
Quad in
3Start Recurring Timer
Timer Name
"remove"
Repeat Every (s)
@self.removeSeconds
Ring Once
Yes
On the Remove Timer
WhenRecurring TimerTimer name: remove
1Destroy Instance

Make Drop Streak and attach Fade Away. This is an Object you keep in Resources without placing it in the Scene. The game will create each streak when a drop needs it.

ObjectDrop Streak
Transform
Physics > Physics ObjectOn
Sprite
Scene Depth Draw Order7
SpriteDrop Streak Art
Transform > SizeX 1.5, Y 6
Logic
Fade Away

2.7.3 Hard Drop Piece

Press Space. The piece is at the ghost before you can watch it fall. Hard Drop Piece uses the answer already found by Find Landing Row, then sends lockPiece without waiting for the lock delay.

It measures hardDropRows before moving. Its hardDropped message also goes out first, while the old row and the landing row are both known. That gives the streaks enough information to cover the fall.

Logic ComponentHard Drop PieceAttach to: Falling Piece

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.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
SHARED VARIABLES
NameTypeKindStarting Value
shakeStrengthNumberConstant0.004
shakeSecondsNumberConstant0.12
When Hard Drop Is Pressed
WhenCommand PressedCommand: Hard Drop
1Change Component Value
Component Value
self.piece.hardDropRows
Value
self.piece.row - self.piece.landingRow
2Trigger Custom Event
Custom Event
hardDropped
3Change Component Value
Component Value
self.piece.row
Value
self.piece.landingRow
4Trigger Custom Event
Custom Event
pieceMoved
5Control Sound
Sound
Hard Drop
6Camera Shake
Intensity
@self.shakeStrength
Seconds
@self.shakeSeconds
7Trigger Custom Event on Object
Custom Event
lockPiece

2.7.4 Show Drop Streaks

Each crystal gets a ribbon of pale light spanning the distance it crossed. Show Drop Streaks puts a new streak at the middle of that distance, stretches its height, and gives it the kind’s streakColor.

The Repeat creates one for each crystal, using Crystal Cell again. Its created target tells the scale and tint Actions which new Object to change. A small burst of Drop Trail marks the landing. Fade Away takes care of each streak afterward.

Declare streak on this Logic as a Constant Object Variable with the Drop Streak kind. Then attach Hard Drop Piece and Show Drop Streaks to Falling Piece. Choose Drop Streak for streak.

Logic ComponentShow Drop StreaksAttach to: Falling Piece

Draws a hard drop: a streak over the rows each crystal of the piece crosses, in a lighter tint of its color, and a puff of sparks where it lands.

Attach toAny Object
DATA STRUCTURES
Pieceself.piece
SHARED VARIABLES
NameTypeKindStarting Value
streakObjectConstantNone
streakArtMetersNumberConstant6
sparksCellsAboveMiddleNumberConstant0.4
OBJECT KINDS
streakDrop Streak
On Hard Dropped
WhenhardDroppedCustom Event
1Ifself.piece.hardDropRows > 0
Then
2Declare Variable
Name
streakScale
Value
self.piece.hardDropRows + 1
3Change Variable
Variable
streakScale
Operator
*=
Value
data('Well Layout', 0, 'cellSize') / @self.streakArtMeters
4Repeat4times
Each time
5Crystal CellFunction
Action Name
droppedCrystal
kind
self.piece.kind
rotation
self.piece.rotation
crystal
local.index + 1
6Declare Variable
Name
crystalColumn
Value
self.piece.column + droppedCrystal.column
7Declare Variable
Name
crystalRow
Value
self.piece.landingRow + droppedCrystal.row
8Cell MiddleFunction
Action Name
streakMiddle
column
crystalColumn
row
crystalRow + self.piece.hardDropRows / 2
9Create Instance
Object to Create
@self.streak
X
streakMiddle.x
Y
streakMiddle.y
10Set Scale
Applies to
created
Vertical Scale
streakScale
11Change Sprite Tint Property
Applies to
created
Value
data('Piece Kinds', self.piece.kind - 1, 'streakColor')
12Change Sprite Tint Property
Applies to
created
Property
Enabled
Value
true
13Cell MiddleFunction
Action Name
sparksPlace
column
crystalColumn
row
crystalRow + @self.sparksCellsAboveMiddle
14Burst Particles
Particles
Drop Trail
Position Relative To
World
Z Index Offset
6
X
sparksPlace.x
Y
sparksPlace.y

Check the Object card next. Check their order, then set streak as its last block shows. The green lines join your existing attachments, keeping the old Logics in place as the new ones join them.

ObjectFalling PieceAddDelete
Transform
Physics > Physics ObjectOn
Data Structure
Data StructurePiece
Data Structure
From LogicRead The Move Keys
Data StructureRead The Move Keys Data
Data Structure
From LogicSoft Drop Piece
Data StructureSoft Drop Piece Data
Logic
Spawn Piece
Piece Gravity
Lock Piece
Read The Move Keys
Move Piece
Rotate Piece
Soft Drop Piece
Hard Drop Piece
Show Drop Streaks
Shared Variables
streakDrop Streak

Run it and press Space. The stack gains a piece immediately, and its ribbons fade above it. A drop already at the floor needs no ribbon.

2.8 More Time to Slide

The floor leaves room for another choice.

2.8.1 Note Lowest Row

A piece may touch down, slide off a ledge, and land lower. Note Lowest Row remembers that progress. A new lowest row clears the old touchdown history and stops the old delay, so the next landing starts fresh.

At the same height, nothing changes. A kick that raises the piece leaves this memory alone too, because lowestRow remembers progress downward.

FunctionNote Lowest Row

When the falling piece reaches a new lowest row, remembers it, clears its touchdown and lock-delay history, and stops the old delay. Its next landing gets a fresh delay and restart count.

PARAMETERS
NameTypeStarts at
pieceObjectStructural
Required Components
Slot Namepiece
Component TypeData Structure
Required SchemaPiece
ACTIONS
1Ifpiece.piece.row < piece.piece.lowestRow
Then
2Change Component Value
Component Value
piece.piece.lowestRow
Value
piece.piece.row
3Change Component Value
Component Value
piece.piece.lockResets
Value
0
4Change Component Value
Component Value
piece.piece.hasTouchedDown
Value
false
5Change Component Value
Component Value
piece.piece.isLockDelayOver
Value
false
6Stop Recurring Timer
Applies to
piece
Timer Name
"lockDelay"

2.8.2 Piece Gravity

Remember pieceShifted from sliding and rotating? This is its listener. After touchdown, a successful shift can restart the lock delay. Piece Gravity counts those restarts in lockResets, up to lockResetLimit, so a piece eventually has to lock.

Landing and shifting ask different questions. A first landing starts the delay. A shift can restart it; reaching a lower row clears that history. Once the delay has run out, another landing at that same height locks instead of inventing a new pause.

Add Note Lowest Row before its new calls in the Changes card. Keep the Falling and Resting Modes you already have. The new On Piece Shifted Event decides whether to return to Falling or to send pieceLanded again.

Logic ComponentPiece GravityAttach to: Falling PieceAddDelete

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
SHARED VARIABLES
NameTypeKindStarting Value
lockResetLimitNumberConstant15
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
3Change Component Value
Component Value
self.piece.lowestRow
Value
self.piece.row
4Change Component Value
Component Value
self.piece.lockResets
Value
0
5Change Component Value
Component Value
self.piece.hasTouchedDown
Value
false
6Change Component Value
Component Value
self.piece.isLockDelayOver
Value
false
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
4Note Lowest RowFunction
piece
self
5Trigger Custom Event
Custom Event
pieceMoved
6Piece Fits HereFunction
Action Name
roomBelowAfterFall
piece
self
columnShift
0
rowShift
-1
7IfroomBelowAfterFall.value == false
Then
8Trigger Custom Event on Object
Custom Event
pieceLanded
9Otherwise
10Trigger 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
2Ifself.piece.hasTouchedDown == false
Then
3Change Component Value
Component Value
self.piece.hasTouchedDown
Value
true
4Start Recurring Timer
Timer Name
"lockDelay"
Repeat Every (s)
self.piece.lockDelaySeconds
Ring Once
Yes
5Trigger Custom Event
Custom Event
lockDelayStarted
6Ifself.piece.isLockDelayOver == true
Then
7Trigger Custom Event on Object
Custom Event
lockPiece
On the Gravity Timer
WhenRecurring TimerTimer name: gravity
ModeResting
(no Actions)
On the Lock Delay Timer
WhenRecurring TimerTimer name: lockDelay
1Change Component Value
Component Value
self.piece.isLockDelayOver
Value
true
2Piece Fits HereFunction
Action Name
roomBelowAfterDelay
piece
self
columnShift
0
rowShift
-1
3IfroomBelowAfterDelay.value == true
Then
4Switch Mode
Event
On the Gravity Timer
Mode
Falling
5Return
6Trigger Custom Event on Object
Custom Event
lockPiece
On Piece Shifted
WhenpieceShiftedCustom Event
1Note Lowest RowFunction
piece
self
2Ifself.piece.hasTouchedDown == true
Then
3Ifself.piece.lockResets < @self.lockResetLimit
Then
4Change Component Value
Component Value
self.piece.lockResets
Value
self.piece.lockResets + 1
5Change Component Value
Component Value
self.piece.isLockDelayOver
Value
false
6Start Recurring Timer
Timer Name
"lockDelay"
Repeat Every (s)
self.piece.lockDelaySeconds
Ring Once
Yes
7Trigger Custom Event
Custom Event
lockDelayStarted
8Piece Fits HereFunction
Action Name
roomBelowAfterShift
piece
self
columnShift
0
rowShift
-1
9IfroomBelowAfterShift.value == true
Then
10Switch Mode
Event
On the Gravity Timer
Mode
Falling
11Otherwise
12Trigger Custom Event on Object
Custom Event
pieceLanded

Run the game. Slide a piece along the floor before it locks, and its small pause begins again. The piece waits, within its limit.

2.9 Play It!

  • Move a piece sideways and watch its ghost choose the new landing place.
  • Try every rotation Command. Compare open space with a spot beside a wall, watching where the piece steps when it rotates. A successful kick shifts the piece and its ghost together.
  • Hold Down through a lock. The next piece hurries too; releasing Down restores ordinary gravity.
  • Press Space above the floor. The piece locks at the ghost, a thud sounds, and pale ribbons fade above the landing.
  • Try a hard drop without any space beneath the piece. No tall trail should appear.
  • Slide or rotate after touchdown, before the delay ends. The delay can restart, and moving lower gives the piece a fresh history.

2.10 How It Works

The ghost waits at landingRow. Hard drop uses that same answer. Rotation and sliding keep it current whenever the piece changes its place or shape.

The search happens before the movement. A trial can fail without leaving anything to undo, because the real piece stays where it was until the answer is yes.

2.11 If It Didn’t Work

The ghost sits on the piece? Check showsLanding on each Ghost Crystal placement. It belongs on those outlines, while the falling crystals leave it off. Turn it on there and run again; the outline can now read landingRow.

The ghost stays behind after a slide? Look for Find Landing Row in Move Piece, before pieceMoved. Restore that call, then slide again. The new answer reaches both sets of pictures, keeping the ghost beneath the piece as you move it.

Does holding Down slow the next piece? Check the Attached Logic order on Falling Piece. Soft Drop Piece must follow Piece Gravity, so it can restart the timer after gravity starts it normally.

A hard drop draws no light? Look at Falling Piece’s streak Shared Variable. Choose the Drop Streak resource, then try a drop with room beneath it. That gives Show Drop Streaks something to create.

No extra time after sliding? Check that sliding and successful rotation send pieceShifted on Falling Piece. The listener is in Piece Gravity. Bring back the missing message or listener, then try the same slide again.

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

2.12 Challenge

What should a hard drop leave behind in your own game? Try another streak color or a different fade. You could make a quick flash, or let the light linger long enough to show the last move. Keep a working copy while you try it.

Your pieces can now rotate beside a wall, hurry down, or drop straight to their ghost. Next, full rows finally break.