Build a GameCreating Pacific Wings

12  Rounds and Mission Complete

Goal

The mission becomes three rounds, each under its own sky: a clear day, then dusk orange, then night blue. After each Command Bomber the screen fades, and the route begins again, harder. Shoot down the third, and the screen says MISSION COMPLETE. Pacific Wings is complete.

The game as it is at the end of this chapter. Click the game, then press Enter to start.

If your game and this chapter part ways, here is the chapter’s finished project. Open it and compare. It’s the whole game, finished.

Pacific Wings, Chapter 12 finishedMyth project · 1.9 MB

To open it, start Myth Engine, choose File › Open Project... and pick the downloaded file.

12.1 The Idea

The Command Bomber goes up in the biggest explosion in the game. The red bar is gone. The sky is quiet, and your Fighter flies on alone, over a sea with nothing left to fight.

Right now, that’s where Pacific Wings stops.

After this chapter, the quiet lasts a few seconds. The screen fades out and back in, and the carrier is below you again, at the very start of the route. But the light has changed. The whole sea wears a warm orange tint, as if the day were ending. The enemies come faster. They fire sooner. At the end, another Command Bomber waits, and it takes more hits.

Bring that one down, and the route begins a third time, under a night blue sky. Bring the third down, and the screen says MISSION COMPLETE, with your final score.

Each flight of the route is a round. Pacific Wings has three.

Here’s the hard idea. A new round is the route’s Scene, started again from the beginning. Think of a play performed a second time on the same stage. The set goes back to how it was. Every actor returns to their mark. Only what you carry in your pockets comes with you.

In Myth, your pockets are the Globals. Your score is one, and so are your reserve planes and your weapon level. They ride across into the next round untouched. Everything placed in the Scene comes back new: the Fighter, the enemies, the scenery, even Game Rules.

PLAY AGAIN is another matter. Since Chapter 5, it has used Restart Game, and Restart Game empties the pockets too. Every Global goes back to where it started. So a new mission always begins at round 1, with a score of 0.

So most of this chapter is about two questions. What should carry over from round to round? And what should start again? The answers fit in one new Global, one small table and a few resets.

12.2 The Plan

Here’s how the pieces work together.

  • The Command Bomber is shot down -> Score adds 2000, then sends commandBomberScored.
  • Rounds hears it -> with rounds left, its roundBreather timer waits 3.5 seconds; after the last round, the Game State becomes Mission Complete.
  • The breather ends -> $roundRow goes up by one, Rounds sends roundOver, and Go to Scene starts the route again with a fade.
  • On roundOver -> Lost on Hit and Pause turn themselves off for the fade.
  • The route begins -> Rounds starts the kill chain and the scenery afresh, and from round 2 shows the flight HUD and the Round Sky. Round Sky Color tints the sky for this round.
  • The Game State becomes Mission Complete -> a second later, MISSION COMPLETE shows, and Best Score keeps your score.

12.3 Set Up

One picture from the Starter Kit joins the game, the Round Sky. It’s in your project already.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Round Sky
GLOBAL VARIABLES
$roundRowNumberstarts at 0
GAME SETTINGS > General > Game State
Available Game StatesTitle, Playing, Mission Failed, Mission Complete
SCENE Route: PLACE THESE
ObjectXY
Round Sky00hidden
with Z-Index = 10000
Mission Complete Title080hidden
with Z-Index = 10002
Mission Complete Score024hidden
with Z-Index = 10002
Mission Complete Menu0-40hidden
with Z-Index = 10002
REMOVED
Logic Component: Mission Failed Screen

The new Global, $roundRow, says which round this is. Like a table’s rows, it counts from 0. So round 1 is row 0. Make it from a Globals card, as you made $score.

Add Mission Complete to the Game States, as you added Mission Failed in Chapter 4. Your Logic reads it as "mission-complete", in lower case with a hyphen. Leave REMOVED and PLACE THESE for now. REMOVED waits until Mission Failed Screen’s job has moved, and PLACE THESE until its pieces exist.

12.4 A Table for Three Rounds

First, the numbers that make a round harder, all of them in one table.

12.4.1 Round Settings

Data StructureRound Settings Entry

One row of the "Round Settings" Data Table.

commandBomberHealthScaleNumberdefault 1
enemyPaceNumberdefault 1
enemySpeedNumberdefault 1
skyTintColordefault #07123d
skyAlphaNumberdefault 0
Data TableRound SettingsData Structure: Round Settings Entry
Row 0Round 1
commandBomberHealthScale . 1
enemyPace1
enemySpeed1
skyTint#07123d
skyAlpha0
Row 1Round 2
commandBomberHealthScale . 1.25
enemyPace0.85
enemySpeed1.1
skyTint#ff7a36
skyAlpha0.18
Row 2Round 3
commandBomberHealthScale . 1.5
enemyPace0.7
enemySpeed1.2
skyTint#07123d
skyAlpha0.42

Each row is a round. Read across a row, and you can see how hard it is. Every number in the first three columns scales something the game already has. Above 1, the bomber’s health and the enemies’ speed grow. Below 1, a wait shrinks. So the enemies fire sooner. Round 1 is all 1s: the game you’ve been playing, with nothing scaled at all.

The last two columns are the sky. Its skyTint is the color, and skyAlpha is how strongly it shows. A clear day is 0, no tint at all. Dusk is a light orange at 0.18, and night is the deep blue at 0.42.

Make it as you made Command Bomber Phases. The table makes Round Settings Entry, and its fields are the columns. Set the defaults first, and Round 1 needs nothing of its own. Like damageTint, skyTint is a Color.

Every harder thing in the game reads this table. It always reads row $roundRow. So when $roundRow goes up by one, the whole game moves down a row with it, from the enemies’ guns to the color of the sky.

12.5 The Round Is Over

Now the rounds themselves. A new Logic Component keeps them, and three that you built before learn to listen to it.

12.5.1 Score

Logic ComponentScoreAttach to: Game RulesAddDelete

Adds the points of every enemy shot down (times the kill-chain multiplier), of every pickup collected, and of every hit on and the shoot-down of the Command Bomber, to $score, until the mission ends.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
wingCannonsPointsNumberConstant250
repairCratePointsNumberConstant100
commandBomberHitPointsNumberConstant10
commandBomberPointsNumberConstant2000
On Enemy Shot Down
WhenenemyShotDownCustom Event
1Declare Variable
Name
multiplier
Value
1
2Change Variable
Variable
multiplier
Value
data('Kill Chain Multipliers', $chainRow, 'multiplier')
3Change Variable
Variable
$score
Operator
+=
Value
other.enemyStats.points * multiplier
4Trigger Custom Event on Object
Custom Event
shotDownScored
On Game State Changed
WhenGame State Changed
1Ifevent.current == "mission-failed"
Then
1Ifevent.current == "mission-failed" || event.current == "mission-complete"
Then
2Set Logic State
Logic Component
Score
State
Disable
On Wing Cannons Collected
WhenwingCannonsCollectedCustom Event
1Change Variable
Variable
$score
Operator
+=
Value
@self.wingCannonsPoints
On Repair Crate Collected
WhenrepairCrateCollectedCustom Event
1Change Variable
Variable
$score
Operator
+=
Value
@self.repairCratePoints
On Command Bomber Hit
WhencommandBomberHitCustom Event
1Change Variable
Variable
$score
Operator
+=
Value
@self.commandBomberHitPoints
On Command Bomber Shot Down
WhencommandBomberShotDownCustom Event
1Change Variable
Variable
$score
Operator
+=
Value
@self.commandBomberPoints
2Trigger Custom Event on Object
Custom Event
commandBomberScored

The bonus comes first. When the Command Bomber goes down, Score adds its 2000 and then tells Game Rules, with commandBomberScored. Trigger Custom Event on Object starts on Self, as in Chapter 9, and Rounds will listen there. Add that step at the end of On Command Bomber Shot Down.

Why not let the rounds listen for the shoot-down itself? Think about the last round. The mission ends there, and Best Score keeps whatever the score is at that moment. If the mission could end before Score counted the bonus, your best score would be 2000 short. Listening to Score makes the order sure.

The other change is for that ending too. Since Chapter 4, Score has stopped counting the moment a mission fails, and it should stop when a mission is complete as well. Click the condition of the If in On Game State Changed, and type the rest of the card’s line after it. || means or: either state stops the count.

12.5.2 Rounds

Logic ComponentRoundsAttach to: Game Rules

When the Command Bomber is shot down: if rounds are left, waits breatherSeconds and flies the route again in the next round; after the last, the mission is complete. When a round is over it sends roundOver. Each round starts with no scenery moved ahead and the kill chain at x1.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
breatherSecondsNumberConstant3.5
fadeSecondsNumberConstant0.8
On Command Bomber Scored
WhencommandBomberScoredCustom Event
1Check Game State
Action Name
stillPlaying
Game State
Playing
2IfstillPlaying.value == true
Then
3If$roundRow < (dataCount('Round Settings') - 1)
Then
4Start Recurring Timer
Timer Name
"roundBreather"
Repeat Every (s)
@self.breatherSeconds
Ring Once
Yes
5Otherwise
6Set Game State
Game State
Mission Complete
7Trigger Custom Event
Custom Event
roundOver
On the Round Breather Timer
WhenRecurring TimerTimer name: roundBreather
1Check Game State
Action Name
playingAfterBreather
Game State
Playing
2IfplayingAfterBreather.value == true
Then
3Change Variable
Variable
$roundRow
Operator
+=
Value
1
4Trigger Custom Event
Custom Event
roundOver
5Go to Scene
Scene
Route
Transition
Fade
Transition Seconds
@self.fadeSeconds
When the Scene Starts
WhenScene Start
1Change Variable
Variable
$furthestSceneryY
Value
0
2Change Variable
Variable
$chainRow
Value
0
3Check Game State
Action Name
isPlaying
Game State
Playing
4IfisPlaying.value == true
Then
5Set UI Group Visibility
UI Group
"flight"
6Set UI Group Visibility
UI Group
"sky"

Rounds sits on Game Rules and keeps $roundRow. It has three Events.

When commandBomberScored arrives, Rounds first asks whether the mission is still being played. Tracers already in the air can finish the bomber after your last plane is lost, and a failed mission shouldn’t win too. Next it looks at the table. Is this the last row? If not, it starts a timer, roundBreather, to ring once after 3.5 seconds. That’s the breather. It’s a few seconds of quiet sky, with nothing left to shoot, before the screen fades.

On the last row, the mission is complete instead. Rounds sets the Game State to Mission Complete, and sends roundOver.

When the breather ends, Rounds asks again. It looks like asking twice. But a tracer can still reach your last plane during the breather, and then the mission has failed for good. If the mission is still being played, $roundRow goes up by one, and roundOver goes out. Last, Go to Scene starts the Route again. Its Transition is Fade, and the fade takes fadeSeconds, 0.8 of a second. The screen comes back on a new round.

The third Event is Scene Start, which you met in Chapter 7, and it runs each time the route begins. Here it puts two Globals back to 0. $furthestSceneryY goes back, so the scenery starts again from the carrier. $chainRow goes back, so the kill chain starts at x1.

The other Globals look after themselves. Since Chapter 7, Stages has set $stage to 1 each time the route begins. And $bomberLaneMeters went back to 0 when the bomber fell.

Why not reset them before the fade? Because the old round goes on while the screen fades, and a shoot-down in that moment would set $chainRow again. So they’re reset as the new route begins, when the old one is truly over.

Last, it shows two UI Groups. Remember that everything placed comes back as it was placed. The first group, flight, is your HUD, the score and the reserve planes at the top of the screen. Both were placed hidden. So in round 2 they’d start hidden. Show the Flight HUD can’t help, because the Game State never changes between rounds. So Rounds shows them again.

The second group, sky, holds one Object you’ll make later in this chapter, the Round Sky. It’s a tinted pane laid over the whole sea, and it’s placed hidden too. Round 1 has a clear sky and needs no pane. From round 2, Rounds shows it.

Rounds shows both only while a mission is being played. Round 1 starts from the title, where they stay out of sight.

Make it as an Empty Logic Component. It has two Shared Variables. The first Event’s If holds a second If inside it, and that one has an Else of its own, for the last round. Add Inside Then on the first If adds the inner If, and the inner If’s own menu fills its True and False lines.

The message roundOver is new. Trigger Custom Event sends it to every Object, and once Rounds sends it, it’s in the Add Event list. Go to Scene’s Scene and Transition are lists on the block: pick Route and Fade. The two Set UI Group Visibility steps take "flight" and "sky", with their quote marks, as in Show the Flight HUD. Attach it to Game Rules. It joins the bottom of Attached Logic.

12.5.3 Lost on Hit

Logic ComponentLost on HitAttach to: FighterAddDelete

When a Fighter Hazard touches the Fighter, the Fighter is lost: it explodes and reports fighterLost (before the Command Bomber's stage the screen also flashes). When its round is over (roundOver, from Rounds), it turns itself off, so nothing can lose the Fighter after the win or while the screen fades to the next round.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
explosionParticlesNumberConstant88
shakeIntensityNumberConstant0.032
shakeSecondsNumberConstant0.48
flashColorColorConstant#fff1bd
flashSecondsNumberConstant0.16
flashBeforeStageNumberConstant4
When Starting to Hit Anything Tagged Fighter Hazard
WhenCollision EnterObject: tag:Fighter Hazard
ModeLost (Starting Mode)
1Trigger Custom Event
Custom Event
fighterLost
2Control Sound
Sound
Fighter Lost
3Camera Shake
Intensity
@self.shakeIntensity
Seconds
@self.shakeSeconds
4If$stage < @self.flashBeforeStage
Then
5Camera Flash
Color
@self.flashColor
Seconds
@self.flashSeconds
6Burst Particles
Particles
Plane Explosion
Count
@self.explosionParticles
Z Index Offset
2
7Destroy Instance
When Starting to Hit Anything Tagged Fighter Hazard
WhenCollision EnterObject: tag:Fighter Hazard
ModeProtected
(no Actions)
On Round Over
WhenroundOverCustom Event
1Set Logic State
Logic Component
Lost on Hit
State
Disable

The round is over, but the sky isn’t empty. A tracer the bomber fired before it fell can still reach the Fighter. During the breather, the round is still being flown, and a hit costs a plane as always. Once roundOver goes out and the screen fades, it shouldn’t.

So when roundOver arrives, Lost on Hit turns itself off, the way Set Logic State turned off Score in Chapter 4. Pick Lost on Hit in its Logic Component slot, and pick Disable.

Turning it off for good might sound risky. It isn’t. The next round’s Fighter is a new one, placed fresh, with its Lost on Hit on.

12.5.4 Pause

Logic ComponentPauseAttach to: Game RulesAddDelete

Esc pauses the world while a mission is being played; RESUME (or Esc, or Enter) carries on. When a round is over (roundOver, from Rounds), it turns itself off: the fade to the next round cannot be paused.

Attach toAny Object
When Menu Cancel Is Pressed
WhenCommand PressedCommand: Menu Cancel
1Check Game State
Action Name
isPlaying
Game State
Playing
2IfisPlaying.value == true
Then
3Control Menu
Menu Element
Pause Menu
4Set Menu Pause State
Menu Element
Pause Menu
On Resume Mission
WhenresumeMissionCustom Event
1Set Menu Pause State
Menu Element
Pause Menu
Pause the World
No
2Control Menu
Operation
Hide
Menu Element
Pause Menu
On Round Over
WhenroundOverCustom Event
1Trigger Custom Event on Object
Custom Event
resumeMission
2Set Logic State
Logic Component
Pause
State
Disable

Pause does the same, so nobody can pause the fade. But first it carries on, in case the world is paused right then. It sends resumeMission to itself, the message RESUME sends. Its own On Resume Mission hides PAUSED and lets the world run. After that, it turns itself off. Its Set Logic State picks Pause in the Logic Component slot. Every box takes what its card shows.

Both of these listen for roundOver. Add it from the Add Event list, where it waits now.

Save, and run the game. Fly the whole route and bring down the bomber. A few seconds of quiet sky, a fade, and the carrier is below you again, with your score still there. The sky looks the same, and the enemies feel the same. The next parts change that.

Bringing the bomber down takes a while. To see a round end sooner, open the Command Bomber, and under Variables give Command Bomber Health’s startingHealth a small Value, about a tenth of its 240. The bomber still fights its three phases, each a few hits long. Keep it while you test the skies, and put 240 back before the last run of the chapter.

12.6 Harder Each Round

Three pieces read the table now: the bomber, the enemies and the reinforcements. Each takes the number it had, and scales it.

12.6.1 Command Bomber Health

Logic ComponentCommand Bomber HealthAttach to: Command BomberAddDelete

Starts the Command Bomber at startingHealth; each time a phase's share of the health is used up (Meter Threshold Reached) it moves to the next row of Command Bomber Phases, and after the last it is shot down.

Attach toAny Object
DATA STRUCTURES
Command Bomber Stateself.commandBomberState
SHARED VARIABLES
NameTypeKindStarting Value
startingHealthNumberConstant240
wingCannonsObjectConstantNone
shotDownParticlesNumberConstant132
shotDownShakeNumberConstant0.045
shotDownShakeSecondsNumberConstant0.8
When Created
WhenCreate
1Change Component Value
Component Value
self.commandBomberState.maxHealth
Value
@self.startingHealth
2Change Component Value
Component Value
self.commandBomberState.maxHealth
Operator
*=
Value
data('Round Settings', $roundRow, 'commandBomberHealthScale')
3Change Component Value
Component Value
self.commandBomberState.maxHealth
Value
round(self.commandBomberState.maxHealth)
4Change Component Value
Component Value
self.commandBomberState.phaseRow
Value
0
On Meter Threshold Reached · Meter With Thresholds Data.meter
WhenMeter Threshold ReachedMeter variable: meterWithThresholdsData.meter
ModeNext Phase (Starting Mode)
1Declare Variable
Name
newPhaseRow
Value
0
2Change Component Value
Component Value
self.commandBomberState.phaseRow
Operator
+=
Value
1
3Change Variable
Variable
newPhaseRow
Value
self.commandBomberState.phaseRow
4Trigger Custom Event on Object
Custom Event
phaseStarted
5Camera Shake
Intensity
data('Command Bomber Phases', newPhaseRow, 'startShake')
Seconds
data('Command Bomber Phases', newPhaseRow, 'startShakeSeconds')
6Burst Particles
Particles
Flak Burst
Count
data('Command Bomber Phases', newPhaseRow, 'startFlakParticles')
Z Index Offset
2
7Ifdata('Command Bomber Phases', newPhaseRow, 'dropsWingCannons') == true
Then
8Create Instance
Object to Create
@self.wingCannons
Position Relative To
Self
9IfnewPhaseRow == (dataCount('Command Bomber Phases') - 1)
Then
10Switch Mode
Event
On Meter Threshold Reached · Meter With Thresholds Data.meter
Mode
Shot Down
On Meter Threshold Reached · Meter With Thresholds Data.meter
WhenMeter Threshold ReachedMeter variable: meterWithThresholdsData.meter
ModeShot Down
1Trigger Custom Event
Custom Event
commandBomberShotDown
2Control Sound
Sound
Plane Break
3Camera Shake
Intensity
@self.shotDownShake
Seconds
@self.shotDownShakeSeconds
4Burst Particles
Particles
Plane Explosion
Count
@self.shotDownParticles
Z Index Offset
2
5Set UI Group Visibility
UI Group
"commandBomber"
Visible
No
6Change Variable
Variable
$bomberLaneMeters
Value
0
7Destroy Instance

When the bomber is made, its health is startingHealth, as before, multiplied now by this round’s commandBomberHealthScale. Then the round() in the third step rounds it off. So the bomber’s health stays a whole number of hits, whatever scale a row gives.

Remember the chalk line from Chapter 11? It’s the meter’s threshold, always one step ahead of the tally. It reads maxHealth, so the phases stretch with it. Round 2’s bomber still fights in thirds. Each third is longer.

Both new steps go between the first and the one that sets phaseRow. Right-click the phaseRow step, and choose Disconnect from Flow. Add the two Change Component Values, join them, and join the step back with Connect to Sequence. The first takes *= in its Operator.

12.6.2 The Three Enemies

ObjectEnemy Fighter
Transform
Bounding Box > SizeX 2.48, Y 2.48
Sprite
Scene Depth Draw Order30
SpriteEnemy Fighter Art
Transform > SizeX 2.48, Y 2.48
Transform > Rotation (deg)Z -180
Collider
Shape and Transform > ShapeEllipse
Shape and Transform > SizeX 1.453, Y 1.046
Tags and Interactions > InteractionsNever Collide With
Tags and Interactions > Never Collide WithTagged Enemy Aircraft
Data Structure
Data StructureEnemy Stats
Input SourceData Table
Data TableEnemy Types
RowEnemy Fighter
Coordinate
NameGun Muzzle
Local PositionX 0, Y -0.94
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Logic
Fly South When Seen
Takes Hits
Shot Down
Fire When Seen
Drops Pickups
Crashes Into Fighter
Remove When Out of View
Shared Variables
tracerEnemy Tracer
tracerSpeed > KindExpression
tracerSpeed > ExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")
firstShotSeconds > KindExpression
firstShotSeconds > ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax") * data('Round Settings', $roundRow, 'enemyPace')
fighterFighter
aimConeDegrees > KindExpression
aimConeDegrees > ExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")
wingCannonsWing Cannons
repairCrateRepair Crate
wingCannonsChance > KindExpression
wingCannonsChance > ExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")
repairCrateChance > KindExpression
repairCrateChance > ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")
speed > KindExpression
speed > ExpressionobjectComponentProperty("self","enemyStats","speed") * data('Round Settings', $roundRow, 'enemySpeed')
ObjectEnemy Ace
Transform
Bounding Box > SizeX 2.56, Y 2.56
Sprite
Scene Depth Draw Order30
SpriteEnemy Ace Art
Transform > SizeX 2.56, Y 2.56
Transform > Rotation (deg)Z -180
Collider
Shape and Transform > ShapeEllipse
Shape and Transform > SizeX 1.6, Y 1.04
Tags and Interactions > InteractionsNever Collide With
Tags and Interactions > Never Collide WithTagged Enemy Aircraft
Coordinate
NameGun Muzzle
Local PositionX 0, Y -0.94
Data Structure
Data StructureEnemy Stats
Input SourceData Table
Data TableEnemy Types
RowEnemy Ace
Data Structure
From LogicWeave
Data StructureWeave Data
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Logic
Weave
Fly South When Seen
Takes Hits
Shot Down
Fire When Seen
Drops Pickups
Crashes Into Fighter
Remove When Out of View
Shared Variables
tracerEnemy Tracer
tracerSpeed > KindExpression
tracerSpeed > ExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")
firstShotSeconds > KindExpression
firstShotSeconds > ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax") * data('Round Settings', $roundRow, 'enemyPace')
fighterFighter
aimConeDegrees > KindExpression
aimConeDegrees > ExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")
weaveSpeed > KindExpression
weaveSpeed > ExpressionobjectComponentProperty("self","enemyStats","weaveSpeed")
weaveSeconds > KindExpression
weaveSeconds > ExpressionobjectComponentProperty("self","enemyStats","weaveSeconds")
weaveAngle > KindExpression
weaveAngle > ExpressionobjectComponentProperty("self","enemyStats","weaveAngle")
wingCannonsWing Cannons
repairCrateRepair Crate
wingCannonsChance > KindExpression
wingCannonsChance > ExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")
repairCrateChance > KindExpression
repairCrateChance > ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")
speed > KindExpression
speed > ExpressionobjectComponentProperty("self","enemyStats","speed") * data('Round Settings', $roundRow, 'enemySpeed')
ObjectHeavy Bomber
Transform
Bounding Box > SizeX 3.36, Y 3.36
Sprite
Scene Depth Draw Order30
SpriteHeavy Bomber Art
Transform > SizeX 3.36, Y 3.36
Transform > Rotation (deg)Z -180
Collider
Shape and Transform > ShapeEllipse
Shape and Transform > SizeX 2.468, Y 1.733
Tags and Interactions > InteractionsNever Collide With
Tags and Interactions > Never Collide WithTagged Enemy Aircraft
Coordinate
NameGun Muzzle
Local PositionX 0, Y -0.932
Data Structure
Data StructureEnemy Stats
Input SourceData Table
Data TableEnemy Types
RowHeavy Bomber
Data Structure
From LogicWeave
Data StructureWeave Data
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Logic
Weave
Fly South When Seen
Takes Hits
Shot Down
Fire When Seen
Drops Pickups
Crashes Into Fighter
Remove When Out of View
Shared Variables
tracerEnemy Tracer
tracerSpeed > KindExpression
tracerSpeed > ExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")
firstShotSeconds > KindExpression
firstShotSeconds > ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin") * data('Round Settings', $roundRow, 'enemyPace')
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax") * data('Round Settings', $roundRow, 'enemyPace')
fighterFighter
aimConeDegrees > KindExpression
aimConeDegrees > ExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")
weaveSpeed > KindExpression
weaveSpeed > ExpressionobjectComponentProperty("self","enemyStats","weaveSpeed")
weaveSeconds > KindExpression
weaveSeconds > ExpressionobjectComponentProperty("self","enemyStats","weaveSeconds")
weaveAngle > KindExpression
weaveAngle > ExpressionobjectComponentProperty("self","enemyStats","weaveAngle")
wingCannonsWing Cannons
repairCrateRepair Crate
wingCannonsChance > KindExpression
wingCannonsChance > ExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")
repairCrateChance > KindExpression
repairCrateChance > ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")
speed > KindExpression
speed > ExpressionobjectComponentProperty("self","enemyStats","speed") * data('Round Settings', $roundRow, 'enemySpeed')

Each enemy has read its own row through Expressions since Chapter 3. Now four of those Expressions also read the round’s number. Look at the speed line, and the three shot timings, near the end of each card.

Open the Enemy Fighter, and look under Variables. Its speed Expression ends where the plane’s speed is read from Enemy Stats. Add the round’s part to its end, as the card shows: * data('Round Settings', $roundRow, 'enemySpeed'). Do the same for the three shot timings, with enemyPace. Then give the Enemy Ace and the Heavy Bomber the same four endings.

A wait multiplied by 0.85 is shorter. So in round 2 the enemies fire sooner, and in round 3 sooner still.

12.6.3 Reinforcements

Logic ComponentReinforcementsAttach to: SquadronAddDelete

Sends one enemy kind in from above the view on the reinforcements timer, from the stage in its Reinforcement Orders row until the Command Bomber approaches; each one only while fewer than cap of that kind are alive.

Attach toAny Object
DATA STRUCTURES
Reinforcements Dataself.reinforcementsData
SHARED VARIABLES
NameTypeKindStarting Value
aboveCameraMetersNumberConstant11.25
PER INSTANCE VARIABLES
NameTypeStarting Value
kindTextEnemy Fighter
On Stage Reached
WhenstageReachedCustom Event
1If$stage == data('Reinforcement Orders', self.kind, 'fromStage')
Then
2Start Recurring Timer
Timer Name
"reinforcements"
Repeat Every (s)
data('Reinforcement Orders', self.kind, 'firstSeconds')
Ring Once
Yes
On the Reinforcements Timer
WhenRecurring TimerTimer name: reinforcements
1Declare Variable
Name
waitSeconds
Value
0
2Repeatdata('Reinforcement Orders', self.kind, 'perWave')times
Each time
3Check Instance Count
Action Name
roomForMore
Object
self.kind
Operator
<
Value
data('Reinforcement Orders', self.kind, 'cap')
4IfroomForMore.value == true
Then
5Generate Random Number
Action Name
laneRoll
Minimum
data('Reinforcement Orders', self.kind, 'xMin')
Maximum
data('Reinforcement Orders', self.kind, 'xMax')
6Change Component Value
Component Value
self.reinforcementsData.laneX
Value
laneRoll.value
7Change Component Value
Component Value
self.reinforcementsData.laneX
Operator
+=
Value
local.index * data('Reinforcement Orders', self.kind, 'laneStepMeters')
8Change Component Value
Component Value
self.reinforcementsData.launchSpeed
Value
data('Enemy Types', self.kind, 'speed')
9Change Component Value
Component Value
self.reinforcementsData.launchSpeed
Operator
*=
Value
data('Round Settings', $roundRow, 'enemySpeed')
10Create Instance
Object to Create
self.kind
X
self.reinforcementsData.laneX
Y
$viewMiddleY + @self.aboveCameraMeters
Launch Speed
self.reinforcementsData.launchSpeed
Launch Angle
-90
11Generate Random Number
Action Name
waitRoll
Minimum
data('Reinforcement Orders', self.kind, 'secondsMin')
Maximum
data('Reinforcement Orders', self.kind, 'secondsMax')
12Change Variable
Variable
waitSeconds
Value
waitRoll.value
13Change Variable
Variable
waitSeconds
Operator
*=
Value
data('Round Settings', $roundRow, 'enemyPace')
14Start Recurring Timer
Timer Name
"reinforcements"
Repeat Every (s)
waitSeconds
Ring Once
Yes
On Command Bomber Approaching
WhencommandBomberApproachingCustom Event
1Stop Recurring Timer
Timer Name
"reinforcements"

The reinforcements arrive from above the view, launched south at their kind’s speed. That launch speed is scaled the same way, and so is the wait before the next group.

The first new step goes inside the If, right before Create Instance. Right-click Create Instance, and choose Disconnect from Flow. Next, right-click the If and choose Add Inside Then, then pick Change Component Value and join it. Join Create Instance back after it with Connect to Sequence.

The second goes on the main line, before the last step. Take Start Recurring Timer off the line the same way, add the Change Variable from the Action List, join it, and join the timer back.

12.7 Three Skies

Now the round can be seen as well as felt.

12.7.1 Round Sky

ObjectRound Sky
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorCenter
Presentation > Render LayerUI
Bounding Box > SizeX 32, Y 32
Sprite
SpriteRound Sky
Transform > SizeX 1400, Y 1000
UI Interaction
Element TypeText
Groupsky
EventmenuChoice
Logic
Round Sky Color

Round Sky is a picture as big as the whole screen, and bigger: 1400 by 1000 pixels, so no edge ever shows. It sits over the sea and everything flying above it, and under the score. That’s its Z-Index, 10000, two under the HUD’s.

It’s a Screen UI Object with a Sprite, made the way you made the Pause Dimmer in Chapter 5. Its UI Interaction is a Text element in the Group sky, the group Rounds shows from round 2. Its Event, menuChoice, never goes anywhere, as on Score Text. Its Logic, Round Sky Color, is the next card, so leave Logic empty for now.

12.7.2 Round Sky Color

Logic ComponentRound Sky ColorAttach to: Round Sky

Tints the sky for the current round, from the Round Settings table.

Attach toAny Object
When Created
WhenCreate
1Change Sprite Tint Property
Value
data('Round Settings', $roundRow, 'skyTint')
2Change Sprite Tint Property
Property
Mode
Value
"multiply"
3Change Sprite Tint Property
Property
Enabled
Value
true
4Set Alpha
Alpha
data('Round Settings', $roundRow, 'skyAlpha')

Round Sky Color runs once, when the sky is made. The sky is placed in the Route, so that’s at the start of every round. The picture takes on the round’s color. Then its last step, Set Alpha, says how strongly the picture shows, from the row’s skyAlpha. At 0 it doesn’t show at all. At 0.42 it’s faint, and the sea shows through, as if through colored glass.

Its three Change Sprite Tint Property steps work as they did for Tint Sprite in Chapter 3. The first keeps Color in its Property list. Make it as an Empty Logic Component, and attach it to Round Sky.

Now place Round Sky from PLACE THESE, hidden, with its Z-Index. Round 1’s clear sky never needs it. From round 2, Rounds shows it, with its color already in place.

Save, and run the game. Round 1 looks as it always has. Bring down the bomber, and after the fade, the sea is warmer, its light low and orange. Round 3 is night.

12.8 Mission Complete

Last comes the end of the mission, and a screen that says so.

12.8.1 Show at Game State

Logic ComponentShow at Game StateAttach to: Mission Failed Menu, Mission Complete Menu

When the Game State becomes gameState, waits delaySeconds, then shows menu and pauses the world behind it.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
gameStateTextConstantmission-failed
menuObjectConstantNone
delaySecondsNumberConstant1
On Game State Changed
WhenGame State Changed
1Ifevent.current == @self.gameState
Then
2Start Recurring Timer
Timer Name
"showScreen"
Repeat Every (s)
@self.delaySeconds
Ring Once
Yes
On the Show Screen Timer
WhenRecurring TimerTimer name: showScreen
1Control Menu
Menu Element
@self.menu
2Set Menu Pause State
Menu Element
@self.menu

Since Chapter 4, Mission Failed Screen has waited for the mission to fail, then shown its menu. MISSION COMPLETE needs the same thing, with another state and another menu.

You could copy it, and change two names. But then there would be two Logic Components doing one job. So it becomes one Logic with three Shared Variables, and each screen says which state it waits for. Each screen’s menu carries it.

Make it as an Empty Logic Component, with its three Shared Variables. Its gameState is a Text, starting at mission-failed. Its menu is an Object, starting at None, because each screen picks its own. Its Control Menu and Set Menu Pause State take @self.menu. The rest is Mission Failed Screen’s, as you built it in Chapter 4, with the Shared Variables in place of the names. The If asks event.current == @self.gameState, and the timer waits @self.delaySeconds.

12.8.2 The Mission Failed Menu

ObjectMission Failed MenuAddDelete
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorCenter
Presentation > Render LayerUI
Bounding Box > SizeX 268, Y 60
Sprite
Z Index Within Object-1
SpriteMission Failed Panel
Transform > SizeX 268, Y 60
Text / UI
TextPLAY AGAIN
Font Size20
EmphasisBold
Color#FFCA75
AlignmentCenter
UI Interaction
GroupmissionFailed
EventplayAgain
Navigation CommandMove
Confirm CommandStart Mission
Logic
Show at Game State
Shared Variables
menuMission Failed Menu
delaySeconds3

PLAY AGAIN on the MISSION FAILED screen now shows itself. Open Mission Failed Menu, pick Logic, and click Show at Game State in Compatible Logic. Under Variables, its menu is a None button: press it, and pick Mission Failed Menu. Its gameState keeps the Logic’s mission-failed. MISSION FAILED keeps its 3-second wait from Chapter 4, so its delaySeconds is 3.

12.8.3 Game Rules

ObjectGame RulesAddDelete
Collider(as it is added)
Logic
Score
Planes
Mission Failed Screen
Mission Start
Pause
Best Score
Play Again
Stages
Weapon Level
Supply Drops
Kill Chain
Rounds
Shared Variables
fighterFighter
milestoneMilestone
wingCannonsWing Cannons
repairCrateRepair Crate

Mission Failed Screen’s job has moved, so it goes, as the Set up card’s REMOVED asks. Right-click it in the Resources, and choose Delete. The window names Game Rules, which carries it, and says it comes off there too. Press Delete. The struck row on Game Rules’ card is that same Logic, and the delete has taken it off already.

Rounds is marked as added on this card too. The card compares Game Rules with Chapter 11, and Rounds is new since then. You attached it already, when you made Rounds, so there’s nothing more to do for it here.

12.8.4 MISSION COMPLETE

ObjectMission Complete Title
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorCenter
Presentation > Render LayerUI
Bounding Box > SizeX 32, Y 32
Text / UI
TextMISSION COMPLETE
Font Size38
EmphasisBold
Color#FFF1A8
AlignmentCenter
Text BoundsWrap to Width
Transform > Size X560
UI Interaction
Element TypeText
GroupmissionComplete
EventmenuChoice
ObjectMission Complete Score
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorCenter
Presentation > Render LayerUI
Bounding Box > SizeX 32, Y 32
Text / UI
TextFINAL SCORE {$score}
Font Size22
EmphasisBold
Color#FFCA75
AlignmentCenter
UI Interaction
Element TypeText
GroupmissionComplete
EventmenuChoice
ObjectMission Complete Menu
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorCenter
Presentation > Render LayerUI
Bounding Box > SizeX 268, Y 60
Sprite
Z Index Within Object-1
SpriteMenu Button Panel
Transform > SizeX 268, Y 60
Text / UI
TextPLAY AGAIN
Font Size20
EmphasisBold
Color#8EEFFF
AlignmentCenter
UI Interaction
GroupmissionComplete
EventplayAgain
Navigation CommandMove
Confirm CommandStart Mission
Logic
Show at Game State
Shared Variables
gameStatemission-complete
menuMission Complete Menu

MISSION COMPLETE in pale gold, an amber FINAL SCORE under it, and PLAY AGAIN in pale blue on its panel. These three are the MISSION FAILED screen in new colors. Make them the way you made those in Chapter 4. You’ve built this screen once already. Their Group is missionComplete, and the panel is the Menu Button Panel, the one behind START MISSION. FINAL SCORE has two spaces before {$score}, as in Chapter 4.

The menu carries Show at Game State too, with its gameState typed as mission-complete, its menu picked as Mission Complete Menu, and the Logic’s delay of 1. That second lets the bomber’s explosion play out.

Place the three from PLACE THESE, hidden, each with its Z-Index.

12.8.5 Best Score

Logic ComponentBest ScoreAttach to: Game RulesAddDelete

Keeps the best score on this device: shows it on the title screen and raises it when a mission ends.

Attach toAny Object
When the Game Starts
WhenGame Start
1Load Persistent Value
Action Name
savedBest
Key
"bestScore"
Default
0
2Change Variable
Variable
$bestScore
Value
savedBest.value
3If$bestScore > 0
Then
4Set UI Group Visibility
UI Group
"best"
On Start Mission
WhenstartMissionCustom Event
1Set UI Group Visibility
UI Group
"best"
Visible
No
On Game State Changed
WhenGame State Changed
1Ifevent.current == "mission-failed"
Then
1Ifevent.current == "mission-failed" || event.current == "mission-complete"
Then
2If$score > $bestScore
Then
3Change Variable
Variable
$bestScore
Value
$score
4Save Persistent Value
Action Name
bestSaved
Key
"bestScore"
Value
$bestScore

Best Score raises the best score when a mission fails. A complete mission counts too. Give its If the same || ending as Score’s.

Its two Set UI Group Visibility steps take "best", as Rounds’ take "flight" and "sky".

If you gave the bomber a small startingHealth, put 240 back now. Then save, and run the game.

This is the whole of Pacific Wings. Press Enter on your own title screen, the one you made in Chapter 5. Fly the route under a clear sky, and bring down the Command Bomber. The screen fades. Fly it again under dusk, and again under night, each time a little faster and a little fiercer. When the third bomber falls, the sky goes still. A second later comes MISSION COMPLETE, with your final score under it, and PLAY AGAIN.

Play it!

Watch for each of these, one at a time.

  • Round 1 looks as it always has: a clear sky, the same enemies, the same bomber.
  • Bring down the bomber. The score jumps by 2000, the sky stays quiet for 3.5 seconds, and the screen fades.
  • Round 2 begins at the carrier, under an orange dusk. Your score, your reserve planes and your weapon level are all still there.
  • The kill chain starts round 2 at x1, and SCORE is cream again.
  • The enemies come a little faster, and fire sooner. The bomber’s red bar takes longer to empty.
  • Press Esc during the fade. Nothing pauses. Press it once round 2 has begun, and PAUSED shows as before.
  • Round 3 is night blue.
  • Bring down the third bomber. A second later, MISSION COMPLETE, with FINAL SCORE and PLAY AGAIN.
  • Lose your last plane in any round, and MISSION FAILED shows, as it always has.
  • Choose PLAY AGAIN. The title comes back, and the next mission starts at round 1, under a clear sky.
How it works

A table holds three rounds, and each row makes the game a little harder. One Global says which row is now. Every harder thing reads that row. When a round ends, the Global moves down a row, and the route starts again.

Go to Scene builds the Scene again from the beginning, and the Globals cross over untouched. So a round is a choice about what to keep. Keep it in a Global, and it carries over. Place it in the Scene, and every round it starts new, as it was placed.

Restart Game goes one step further. It starts everything again, Globals and all: your score goes back to 0, and so does $roundRow. That’s why PLAY AGAIN always begins at round 1.

12.9 If It Didn’t Work

The changes here are small, and spread across the whole game. Start with what you can see.

The bomber goes down, and nothing more happens? Look at Score first. Its last Event should end with Trigger Custom Event on Object, commandBomberScored. And is Rounds on Game Rules?

The fade comes, but the route starts as round 1 again? Perhaps $roundRow never moves. Look at the Change Variable in On the Round Breather Timer, whose Operator should be +=.

Round 2 has no score and no reserve planes at the top? Look at Rounds’ Scene Start. Its two Set UI Group Visibility steps need flight and sky, spelled as the Groups are.

The sky never changes? Look for Round Sky in Route’s Scene Explorer, the list on the right, with its Z-Index of 10000, and at its Group, which should be sky, the name Rounds shows. Then look at Round Sky Color’s second step, whose Property should be Mode.

The last bomber falls, and nothing shows? Look at Game Settings, where Mission Complete should be one of the Game States. And on Mission Complete Menu, gameState should read mission-complete, with menu picked as Mission Complete Menu itself.

PLAY AGAIN after MISSION COMPLETE does nothing? Look at Mission Complete Menu’s Event, under UI Interaction. It should be playAgain, the message Play Again has listened for since Chapter 5.

Each of these is one fix. Make it, run the game again, and you’re back with the chapter. The catch-up download in the Goal is the finished game, if you’d like to compare.

Challenge

Open Round Settings, and add a fourth row. Give it a sky of your own, a deep red, or a green no sea has ever been. Make the bomber tougher still, and the enemies quicker. Rounds counts the rows, so a fourth round works with no change to the Logic.

And then, make something of your own. Every piece of Pacific Wings is one you know now: an Object, a Logic Component, a table, a message, a Scene that starts again. What would you build with them? A different sky, a different enemy, a different game. Nobody has made it yet.

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

In Chapter 1, one navy-blue Fighter flew alone over an empty sea. Now the carrier, the islands and three Command Bombers are waiting for it, and every one of them is yours.

Free demo

Free

  • No time limit
  • Build, save and playtest your games
  • Follow every game guide and build its game

Publishing needs the full version.

Try the free demo

Full version

$84.99 USD, once

No subscription, no paid upgrades, every update included.

Everything in the free demo, plus:

  • Publish your game as one HTML file, a web folder, or desktop and mobile projects you build and sign
  • Share it or sell it, with no royalties, however much it earns
  • Let players build their own version of your game, within the parts you allow (Builder Mode)
Buy Myth Engine — $84.99

Windows 10/11, 64-bit · One person, on computers you own or control · Checkout and downloads through itch.io · Made by 3D Mage