Build a GameCreating Pacific Wings

11  The Command Bomber

Goal

At the end of the route, the Command Bomber comes down under its red health bar and keeps you out of its lane at the top of the screen. It fights in three phases, each one fiercer than the last. Bring it down for 2000 points.

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, or start Chapter 12 from it.

Pacific Wings, Chapter 11 finishedMyth project · 1.8 MB

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

11.1 The Idea

Fly the whole route. The last wave falls behind you, the camera gives a small shake, and the sky goes quiet.

Then a dark gray shape slides down from the top of the screen. Four engines and red wingtips, wider than any plane you’ve met. A red bar appears above it. The Command Bomber has arrived.

It sweeps from side to side across the top of the view, and you can’t follow it up there. That strip is its lane, and your Fighter stops at its edge. Twin guns rattle from its wings. A spark at its nose, and a moment later a tracer comes straight at you.

Hold Space and pour tracers into it. Every hit turns it white for an instant, and the red bar shrinks. A third of the way down, the camera shakes and a Wing Cannons tumbles out. From the next hit on, the bomber settles amber. That’s a new phase. Now fans of flak spread across the screen too. In the last phase it turns red, and everything comes faster. Empty the bar, and it goes up in the biggest explosion in the game.

That’s a lot of game for one Object, and this is the longest chapter in the book. So here’s the good news first. The Command Bomber is made of pieces you already know. Fire From Muzzle fires its guns, and Weave sweeps it from side to side. A Data Table holds its numbers, one row for each phase, like Weapon Levels in Chapter 8.

Two ideas are new. One is how a single count of hits becomes three phases. The other is how the bomber seems to fly down to meet you, without moving at all. Each gets its own time.

11.2 The Plan

Here’s how the pieces work together, one chain to a line.

  • The Command Bomber comes into view -> Command Bomber Arrival shows the health bar and closes the lane.
  • It also sends phaseStarted -> the three guns fire with this phase’s row.
  • A Fighter Tracer hits the bomber -> Command Bomber Hits sends changeMeter, and Meter with Thresholds counts it.
  • Command Bomber Hits sends commandBomberHit -> Score adds 10, and the bar shrinks.
  • The count reaches the end of a phase -> Command Bomber Health moves to the next row, and sends phaseStarted again.
  • After the last phase -> commandBomberShotDown, and Score adds 2000.

11.3 Set Up

Three pictures from the Starter Kit join the game: the Command Bomber Art, and the two halves of its health bar. They’re in your project already.

Set upWhat this chapter adds
ART (sprites, from the starter kit)
Health Track Art
Health Bar Art
Command Bomber Art
GLOBAL VARIABLES
$bomberLaneMetersNumberstarts at 0
$viewHeightNumberstarts at 0
SCENE Route: PLACE THESE
ObjectXY
Command Bomber Health Track0-20hidden
with Z-Index = 10002
Command Bomber Health Bar-120-20hidden
with Z-Index = 10003
Command Bomber10116.875
REMOVED
Data Structure: Fire When Seen Data

Two Globals are new. $bomberLaneMeters is how deep the lane is. It starts at 0, so there’s no lane at all until the bomber arrives. $viewHeight will hold how tall the view is. Make both from a Globals card, as you made $score in Chapter 3, and place the three Objects later, once you’ve made them. REMOVED names Fire When Seen Data, which goes in the second part.

Nothing on the screen shows the bomber until it’s placed, near the end of this chapter. The first two parts change things you already have. After each one, the game should play exactly as before. Keep going.

11.4 Everything That Can Cost You a Plane

First, a Tag. Right now, a touch from anything tagged Enemy Threat costs you a plane: every enemy, and every enemy tracer. The Command Bomber has to cost you one too.

Why not tag it Enemy Threat too? Everything that wears Enemy Threat today also bursts into flak when it touches you, with Crashes Into Fighter, and it’s gone. The bomber shouldn’t vanish the first time you fly into it. It should stay, and you should lose the plane. So the Tag gets a truer name, one that says only what it means.

Fighter Hazard is a Tag for everything that can cost you a plane. Each Object that wears Enemy Threat wears Fighter Hazard instead: the Enemy Tracer, the Enemy Fighter, the Enemy Ace and the Heavy Bomber. Later in this chapter, when the bomber arrives, it will wear the same Tag.

ObjectEnemy TracerAddDelete
Transform
Bounding Box > SizeX 0.68, Y 0.68
Sprite
Scene Depth Draw Order50
SpriteEnemy Tracer Art
Transform > SizeX 0.68, Y 0.68
Transform > Rotation (deg)Z -180
Collider
CollisionSensor
Shape and Transform > ShapeCapsule
Shape and Transform > SizeX 0.154, Y 0.499
Tags
Object TagsFighter Hazard
Object TagsEnemy Threat
Logic
Crashes Into Fighter
Remove When Out of View
ObjectEnemy FighterAddDelete
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
Data Structure
From LogicFire When Seen
Data StructureFire When Seen Data
Coordinate
NameGun Muzzle
Local PositionX 0, Y -0.94
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Object TagsEnemy Aircraft, Enemy Threat
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")
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")
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")
ObjectEnemy AceAddDelete
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
Data Structure
From LogicFire When Seen
Data StructureFire When Seen Data
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Object TagsEnemy Aircraft, Enemy Threat
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")
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")
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")
ObjectHeavy BomberAddDelete
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
Data Structure
From LogicFire When Seen
Data StructureFire When Seen Data
Tags
Object TagsEnemy Aircraft, Fighter Hazard
Object TagsEnemy Aircraft, Enemy Threat
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")
shotSecondsMin > KindExpression
shotSecondsMin > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")
shotSecondsMax > KindExpression
shotSecondsMax > ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")
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")

On each card, look for the marked Tags line. Open each Object and select its Tags. Each Tag it wears has a row with a red trash button, Remove Tag. Press it on Enemy Threat’s row. Then add Fighter Hazard: Create New Tag… the first time, and from the list after that. The planes’ cards mark one more thing, Fire When Seen Data, struck. That goes in the next part, by itself.

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

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
When Starting to Hit Anything Tagged Enemy Threat
WhenCollision EnterObject: tag:Enemy Threat
ModeLost (Starting Mode)
1Trigger Custom Event
Custom Event
fighterLost
2Control Sound
Sound
Fighter Lost
3Camera Shake
Intensity
@self.shakeIntensity
Seconds
@self.shakeSeconds
4Camera Flash
Color
@self.flashColor
Seconds
@self.flashSeconds
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
When Starting to Hit Anything Tagged Enemy Threat
WhenCollision EnterObject: tag:Enemy Threat
ModeProtected
(no Actions)

Lost on Hit listens for the new Tag now. On its canvas, pick Tagged Fighter Hazard in the Event’s Collision Target list. Lost and Protected share the Event, so one change covers both. The Event’s name follows by itself, and so do Return Flight’s steps that switch it.

The other change is quieter. When you lose a plane, the whole screen goes pale for a moment. In the bomber’s fight, the busiest moment of the mission, that would wash everything out. So the screen does it only before stage 4. That’s $stage, the Global from Chapter 7, and stage 4 begins at the last milestone, when the bomber is near.

Add the new Shared Variable, flashBeforeStage. Next, Camera Flash moves into a new If, on Lost’s line.

Right-click Camera Flash, and choose Delete. Then take Burst Particles off the line with Disconnect from Flow. Destroy Instance too. Lost’s line ends at Camera Shake now. Right-click Camera Shake, choose Add Unattached Action Here, pick If, and join it. Its condition is typed as the card writes it. It asks whether $stage is still below flashBeforeStage. Right-click the If, choose Add Inside Then, and make a new Camera Flash there, with the card’s two values. Last, right-click Burst Particles, and choose Connect to Sequence. It joins below the whole If, and Destroy Instance follows it the same way, back at the end of the line.

The card numbers two blocks 4. The struck one is Camera Flash where it was, and the new one is the If that holds it now.

Save, and run the game, which plays as it did. Fly into an enemy. The screen still goes pale.

11.5 Two Functions to Share

The Command Bomber turns white when it’s hit, as the enemies do, and it aims at you, as the Aces do. You could build both a second time. But then every change would have to be made twice, and sooner or later the two would differ.

So both move into Functions. The old Logic calls them, and so will the bomber.

11.5.1 Flash on Hit

FunctionFlash on Hit

Flashes the target's sprite flashColor for flashSeconds, then tints it settleTint.

PARAMETERS
NameTypeStarts at
targetObject
flashColorColor#ffffff
flashSecondsNumber0.07
settleTintColor#ffd0b8
ACTIONS
1Tint SpriteFunction
target
target
color
flashColor
mode
"solid"
2Wait
Seconds
flashSeconds
3Tint SpriteFunction
target
target
color
settleTint
mode
"multiply"

Flash on Hit is the end of Takes Hits, lifted out whole. A solid white, a short wait, and then settleTint, the tint the plane keeps from then on. Its target is the plane that was hit.

Make it with Add > Function, with its Parameters on the Start block, as on every Function so far. Its two Tint Sprite calls come from the Functions group, and each one picks target for the Object to tint.

Logic ComponentTakes HitsAttach to: Enemy Fighter, Enemy Ace, Heavy BomberAddDelete

Each Fighter Tracer that hits takes one health and flashes the enemy white.

Attach toAny Object
DATA STRUCTURES
Enemy Statsself.enemyStats
SHARED VARIABLES
NameTypeKindStarting Value
flashColorColorConstant#ffffff
damageTintColorConstant#ffd0b8
flashSecondsNumberConstant0.07
hitParticlesNumberConstant10
When Starting to Hit Fighter Tracer
WhenCollision EnterObject: Fighter Tracer
1Change Component Value
Component Value
self.enemyStats.health
Operator
-=
Value
1
2Burst Particles
Particles
Flak Burst
Count
@self.hitParticles
Z Index Offset
2
3Tint SpriteFunction
target
self
color
@self.flashColor
mode
"solid"
4Wait
Seconds
@self.flashSeconds
5Tint SpriteFunction
target
self
color
@self.damageTint
mode
"multiply"
3Flash on HitFunction
target
self
flashColor
@self.flashColor
flashSeconds
@self.flashSeconds
settleTint
@self.damageTint

In Takes Hits, delete the three old steps, two Tint Sprites with a Wait between. Add the new Function in their place, from the Functions group, and join it. Its target is self already. Type the card’s @self. values into the other three boxes, and the end of Takes Hits is one call.

11.5.2 Aim at Fighter

FunctionAim at Fighter

Gives aimDegrees: the direction from the shooter toward the Fighter, at least minimumForwardMeters to the south, kept within aimConeDegrees of south (-90).

PARAMETERS
NameTypeStarts at
shooterObject
fighterObjectFighter
minimumForwardMetersNumber2.8
aimConeDegreesNumber65
RETURN TYPENumber
ACTIONS
1Declare Variable
Name
toFighterX
Value
0
2Declare Variable
Name
toFighterY
Value
0
3Declare Variable
Name
direction
Value
-90
4Check Instance Count
Action Name
fighterInSky
Object
fighter
Operator
>
Value
0
5IffighterInSky.value == true
Then
6Change Variable
Variable
toFighterX
Value
fighter.transform.position.x - shooter.transform.position.x
7Change Variable
Variable
toFighterY
Value
fighter.transform.position.y - shooter.transform.position.y
8Otherwise
9Change Variable
Variable
toFighterX
Value
0
10Change Variable
Variable
toFighterY
Value
-minimumForwardMeters
11IftoFighterY > (-minimumForwardMeters)
Then
12Change Variable
Variable
toFighterY
Value
-minimumForwardMeters
13Change Variable
Variable
direction
Value
radtodeg(atan2(toFighterY, toFighterX))
14Ifdirection < (-90 - aimConeDegrees)
Then
15Change Variable
Variable
direction
Value
-90 - aimConeDegrees
16Ifdirection > (-90 + aimConeDegrees)
Then
17Change Variable
Variable
direction
Value
-90 + aimConeDegrees
18Return
Value
direction

Aim at Fighter is the aim from Chapter 6. It finds the line from the shooter to the Fighter, keeps it heading south, and keeps it inside the cone. You built every one of these steps in Fire When Seen, with self where shooter is now. Build them again here, from the card.

What’s new is how it hands back its answer. A Function can Return a value, which the caller reads once the Function is done. Its card names the answer’s kind, RETURN TYPE Number, and its last step returns direction, the angle it worked out.

Make it with Add > Function. Its Parameters go on the Start block: shooter is an Object, and fighter an Object whose Object Kind is Fighter, as on Drop Supply. The three values it works with are Declare Variables, its first three steps. Click the small card named Function at the top left of the canvas, and pick Number in its Return Type. The last step is a Return, from the Action List’s Logic group, with direction in its Value box.

Logic ComponentFire When SeenAttach to: Enemy Fighter, Enemy Ace, Heavy BomberAddDelete

Once in view, fires its tracer from the Gun Muzzle toward the Fighter (kept within aimConeDegrees of south; 0 fires straight south), after firstShotSeconds and then every shotSecondsMin to shotSecondsMax seconds.

Attach toAny Object
DATA STRUCTURES
Fire When Seen Dataself.fireWhenSeenData
SHARED VARIABLES
NameTypeKindStarting Value
tracerObjectConstantNone
tracerSpeedNumberConstant7.7
firstShotSecondsNumberConstant1.15
shotSecondsMinNumberConstant1.4
shotSecondsMaxNumberConstant2.5
muzzleFlashParticlesNumberConstant5
fighterObjectConstantNone
aimConeDegreesNumberConstant0
minimumForwardMetersNumberConstant2.8
When Entering the View
WhenEnter View
1Start Recurring Timer
Timer Name
"enemyFire"
Repeat Every (s)
@self.firstShotSeconds
Ring Once
Yes
On the Enemy Fire Timer
WhenRecurring TimerTimer name: enemyFire
1Declare Variable
Name
direction
Value
-90
2Check Instance Count
Action Name
fighterInSky
Object
@self.fighter
Operator
>
Value
0
3IffighterInSky.value == true
Then
4Change Component Value
Component Value
self.fireWhenSeenData.toFighterX
Value
@self.fighter.transform.position.x - self.transform.position.x
5Change Component Value
Component Value
self.fireWhenSeenData.toFighterY
Value
@self.fighter.transform.position.y - self.transform.position.y
6Otherwise
7Change Component Value
Component Value
self.fireWhenSeenData.toFighterX
Value
0
8Change Component Value
Component Value
self.fireWhenSeenData.toFighterY
Value
-@self.minimumForwardMeters
9Ifself.fireWhenSeenData.toFighterY > (-@self.minimumForwardMeters)
Then
10Change Component Value
Component Value
self.fireWhenSeenData.toFighterY
Value
-@self.minimumForwardMeters
11Change Variable
Variable
direction
Value
radtodeg(atan2(self.fireWhenSeenData.toFighterY, self.fireWhenSeenData.toFighterX))
12Ifdirection < (-90 - @self.aimConeDegrees)
Then
13Change Variable
Variable
direction
Value
-90 - @self.aimConeDegrees
14Ifdirection > (-90 + @self.aimConeDegrees)
Then
15Change Variable
Variable
direction
Value
-90 + @self.aimConeDegrees
1Aim at FighterFunction
Action Name
aim
shooter
self
fighter
@self.fighter
minimumForwardMeters
@self.minimumForwardMeters
aimConeDegrees
@self.aimConeDegrees
2Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Gun Muzzle"
tracerSpeed
@self.tracerSpeed
flashParticles
@self.muzzleFlashParticles
direction
aim.value
direction
direction
flash
'Enemy Gun Muzzle'
3Control Sound
Sound
Enemy Guns
4Generate Random Number
Action Name
nextShotSeconds
Minimum
@self.shotSecondsMin
Maximum
@self.shotSecondsMax
5Start Recurring Timer
Timer Name
"enemyFire"
Repeat Every (s)
nextShotSeconds.value
Ring Once
Yes

Now Fire When Seen calls it. On the Enemy Fire Timer’s line, delete the old aiming steps, from Declare Variable down to the last If. Deleting an If takes everything inside it along. When you’re done, Fire From Muzzle is the first step. Fire When Seen Data goes too. Nothing reads it. On the Data Structures card, press the trash button on its row, and the planes lose their copies with it, as their cards show. Last, delete the Data Structure itself in the Resources, as REMOVED says.

The call goes in front of Fire From Muzzle, as a step went in front of the first in Chapter 9. Take Fire From Muzzle and the three steps after it off the line, each with Disconnect from Flow. Add Aim at Fighter from the Functions group, and join it. Type aim into its Action Name. Its shooter is self already. Type the card’s @self. values into the other three.

Right-click Fire From Muzzle again, and choose Connect to Sequence, then the three after it, in order. Last, type aim.value into its direction box. That’s the answer, read through the call’s name, the way you read fireRateReady.value.

Save, and run the game. Nothing should change, and that’s the test. The enemies still turn white when hit, and the Aces and Heavy Bombers still aim at you.

11.6 The Fight in a Table

Now the bomber itself, starting with what it knows about its own fight.

11.6.1 Command Bomber State

Data StructureCommand Bomber State

What the Command Bomber carries for its Logic to share: maxHealth, its full health this round; damage, the hits it has taken (Meter with Thresholds keeps it); and phaseRow, the row of Command Bomber Phases it is fighting.

maxHealthNumberdefault 0
damageNumberdefault 0
phaseRowNumberdefault 0

The bomber carries a small form, like Enemy Stats on the enemies. It has three blanks. The first, maxHealth, is its full health. The second, damage, counts the hits it has taken. The third, phaseRow, is the phase it’s fighting right now, counting from 0, the way the table’s rows count.

Several pieces of its Logic read these, so they live on the Object, where all of them can reach. Make it with Add > Data Structure, and press Add Field three times.

11.6.2 Command Bomber Phases

Data StructureCommand Bomber Phases Entry

One row of the "Command Bomber Phases" Data Table.

damageTintColordefault #ffd0b8
startShakeNumberdefault 0
startShakeSecondsNumberdefault 0
startFlakParticlesNumberdefault 0
dropsWingCannonsBooleandefault false
gunSecondsNumberdefault 0
gunFirstSecondsNumberdefault 0
gunNoseBooleandefault false
gunFlashParticlesNumberdefault 0
aimedSecondsNumberdefault 0
aimedFirstSecondsNumberdefault 0
aimedWarningSecondsNumberdefault 0
aimedNoseBooleandefault false
aimedWingsBooleandefault false
aimedWarningParticlesNumberdefault 0
aimedFlashParticlesNumberdefault 0
flakTracersNumberdefault 0
flakSecondsNumberdefault 0
flakFirstSecondsNumberdefault 0
flakWarningSecondsNumberdefault 0
flakArcDegreesNumberdefault 0
flakSpeedNumberdefault 0
flakWarningParticlesNumberdefault 0
flakFlashParticlesNumberdefault 0
flakWarningShakeNumberdefault 0
Data TableCommand Bomber PhasesData Structure: Command Bomber Phases Entry
Row 0Phase 1
damageTint#ffd0b8
startShake0
startShakeSeconds0
startFlakParticles0
dropsWingCannonsNo
gunSeconds0.78
gunFirstSeconds0.45
gunNoseNo
gunFlashParticles5
aimedSeconds1.75
aimedFirstSeconds0.8
aimedWarningSeconds0.48
aimedNoseYes
aimedWingsNo
aimedWarningParticles10
aimedFlashParticles8
flakTracers0
flakSeconds0
flakFirstSeconds0
flakWarningSeconds0
flakArcDegrees0
flakSpeed0
flakWarningParticles0
flakFlashParticles0
flakWarningShake0
Row 1Phase 2
damageTint#ffd6a0
startShake0.018
startShakeSeconds0.32
startFlakParticles36
dropsWingCannonsYes
gunSeconds0.6
gunFirstSeconds0.25
gunNoseNo
gunFlashParticles5
aimedSeconds1.35
aimedFirstSeconds0.5
aimedWarningSeconds0.5
aimedNoseNo
aimedWingsYes
aimedWarningParticles7
aimedFlashParticles5
flakTracers5
flakSeconds3.2
flakFirstSeconds0.7
flakWarningSeconds0.5
flakArcDegrees70
flakSpeed6.9
flakWarningParticles20
flakFlashParticles14
flakWarningShake0
Row 2Phase 3
damageTint#ff9c8f
startShake0.028
startShakeSeconds0.45
startFlakParticles52
dropsWingCannonsYes
gunSeconds0.46
gunFirstSeconds0.2
gunNoseYes
gunFlashParticles4
aimedSeconds1.05
aimedFirstSeconds0.35
aimedWarningSeconds0.38
aimedNoseYes
aimedWingsYes
aimedWarningParticles6
aimedFlashParticles4
flakTracers7
flakSeconds2.25
flakFirstSeconds0.45
flakWarningSeconds0.38
flakArcDegrees112
flakSpeed8
flakWarningParticles28
flakFlashParticles18
flakWarningShake0.012

Each row is one phase. Read down a column, and you can watch the fight get harder. The wait between bursts of the wing guns, gunSeconds, goes 0.78, then 0.6, then 0.46. The color it settles to after a hit, damageTint, goes from the cream every enemy wears to amber, then red.

This is the biggest table in the game. It’s long, not hard. Make it as you made Weapon Levels in Chapter 8. The table makes Command Bomber Phases Entry, and its fields are the columns. Give each field its default first. That way, Phase 1 needs only a handful of cells of its own. damageTint is a Color, as scoreColor was in Chapter 9, and the Yes or No columns are Booleans.

11.6.3 Meter with Thresholds

Logic ComponentMeter with ThresholdsAttach to: Command Bomber

Keeps a meter between minimum and maximum, changes it by changeAmount each time it receives changeMeter, and runs Meter Threshold Reached once when the meter climbs to threshold (or Meter Threshold Lost when it falls back below it). meterField names the Data Structure field the meter is kept in, where other Logic can read it.

Attach toAny Object
DATA STRUCTURES
Meter with Thresholds Dataself.meterWithThresholdsData
SHARED VARIABLES
NameTypeKindStarting Value
changeAmountNumberConstant1
minimumNumberConstant0
maximumNumberConstant100
thresholdNumberConstant100
meterFieldTextConstant(empty)
Built in: this Logic Component comes whole from the engine's template.
You add it and set its Variables on the Object; you do not build its Events.
Data StructureMeter with Thresholds Data

Object Data of Logic "Meter with Thresholds" (its state that lasts between Events).

meterNumberdefault 0

Here’s the first new idea. The bomber has 240 hits of health, in three phases. How does it know when a phase is over?

A meter keeps count. Picture a tally on a chalkboard, with a chalk line drawn across it. Each hit adds a mark. When the marks reach the line, something happens, once. That line is the threshold.

Meter with Thresholds is that tally. Each time it hears changeMeter, it adds changeAmount to its meter. When the meter climbs to the threshold, it runs an Event, Meter Threshold Reached.

It comes whole from Myth. Make it like Vertical Shooter Controls in Chapter 1: Add > Logic Component, and pick Meter with Thresholds in the Starter list, under State. There’s nothing to build. Its tally lives in a Data Structure of its own, Meter with Thresholds Data, and you’ll set its Shared Variables on the Command Bomber.

Now the part that turns one count into three phases. The threshold isn’t a fixed number. On the bomber, it’s a small formula that reads phaseRow. In phase 1, the line sits a third of the way along the full health. When the tally reaches it, phaseRow goes up by one, and the line jumps on to two thirds. And then to the very end. The tally keeps counting, and the chalk line keeps moving ahead of it.

11.6.4 Command Bomber Health

Logic ComponentCommand Bomber HealthAttach to: Command Bomber

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

Command Bomber Health answers the meter. When the bomber is made, it writes the full health into maxHealth and sets phaseRow to 0.

After that it waits for Meter Threshold Reached, an Event with two Modes, as on Lost on Hit. Next Phase answers first. It moves phaseRow on by one, and tells the bomber’s guns with phaseStarted. It shakes the camera, bursts flak, and drops a Wing Cannons when the new row says so. One question comes last: is this the last row? If it is, the Event switches to Shot Down, so the next time the tally reaches the line, the bomber goes down.

Shot Down is the ending. The player hears it break, feels the hard shake, and sees the biggest explosion in the game. Then the bar is gone, and the lane opens again. A Declare Variable, newPhaseRow, holds the row that has begun, so each read of the table stays short.

Make it as an Empty Logic Component. It has five Shared Variables. On its Data Structures card, press the + and pick Command Bomber State under Existing Data Structures. Its row reads self.commandBomberState.

Add Create first. Then add the Event Meter Threshold Reached. Its second window asks for a meter variable. Choose Type a meter variable…, and type meterWithThresholdsData.meter, where Meter with Thresholds keeps its tally. Rename the Event’s Mode 1 to Next Phase, and build its line. Then make a New Mode… named Shot Down, and add its steps with Add Unattached Action Here on its block.

Shot Down’s Set UI Group Visibility works as in Show the Flight HUD: "commandBomber", with its quote marks, and Visible on No.

The Create Instance inside the first If takes @self.wingCannons. Its Position Relative To is Self, so the badge appears right where the bomber is.

Next Phase’s last step, Switch Mode, sits inside the last If. It picks this very Event. The bomber doesn’t carry this Logic yet, so the Event list names it beside the Logic: On Meter Threshold Reached · Meter With Thresholds Data.meter · Command Bomber Health. The first parts are the Event’s name, as on the card. Pick it, and then Shot Down in the Mode list. Once the bomber carries the Logic, the same line ends in Command Bomber instead, and your pick stays.

11.6.5 Command Bomber Hits

Logic ComponentCommand Bomber HitsAttach to: Command Bomber

Each Fighter Tracer that hits adds one to the Command Bomber's damage (changeMeter), makes sparks, reports commandBomberHit, and flashes it white, settling on its current phase's tint.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
flashColorColorConstant#ffffff
flashSecondsNumberConstant0.07
hitParticlesNumberConstant10
phaseRowNumberConstant0
When Starting to Hit Fighter Tracer
WhenCollision EnterObject: Fighter Tracer
1Trigger Custom Event on Object
Custom Event
changeMeter
2Burst Particles
Particles
Flak Burst
Count
@self.hitParticles
Z Index Offset
2
3Trigger Custom Event
Custom Event
commandBomberHit
4Flash on HitFunction
target
self
flashColor
@self.flashColor
flashSeconds
@self.flashSeconds
settleTint
data('Command Bomber Phases', @self.phaseRow, 'damageTint')

Command Bomber Hits is the bomber’s Takes Hits. When a Fighter Tracer touches it, it sends changeMeter to its own Object, and the meter counts the hit. It bursts flak, and sends commandBomberHit to anyone listening. Last, Flash on Hit turns it white and settles it on this phase’s tint.

The order matters. The meter counts the hit before commandBomberHit goes out, so everything that hears it reads the new count.

Make it as an Empty Logic Component, with a Color and three Numbers as its Shared Variables. One of them is phaseRow. But the phase changes during the fight, so how can a Shared Variable hold it? On the bomber, it’s an Expression that reads phaseRow from Command Bomber State, so it’s always the phase right now.

11.7 The Guns

Three Logic Components fire the bomber’s guns, and all three are built the same way. Each listens for phaseStarted. It stops its old timers and starts them again with the new row’s numbers. So when a phase begins, every gun changes pace at once.

11.7.1 Command Bomber Guns

Logic ComponentCommand Bomber GunsAttach to: Command Bomber

Fires straight south from the wing muzzles (and the nose when its phase says so) every gunSeconds of the current phase.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
tracerObjectConstantNone
tracerSpeedNumberConstant7.7
phaseRowNumberConstant0
On Phase Started
WhenphaseStartedCustom Event
1Stop Recurring Timer
Timer Name
"bomberGuns"
2Start Recurring Timer
Timer Name
"bomberGuns"
Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'gunSeconds')
First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'gunFirstSeconds')
On the Bomber Guns Timer
WhenRecurring TimerTimer name: bomberGuns
1Declare Variable
Name
gunFlashParticles
Value
0
2Change Variable
Variable
gunFlashParticles
Value
data('Command Bomber Phases', @self.phaseRow, 'gunFlashParticles')
3Control Sound
Sound
Enemy Guns
4Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Port Muzzle"
tracerSpeed
@self.tracerSpeed
flashParticles
gunFlashParticles
direction
-90
flash
'Enemy Gun Muzzle'
5Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Starboard Muzzle"
tracerSpeed
@self.tracerSpeed
flashParticles
gunFlashParticles
direction
-90
flash
'Enemy Gun Muzzle'
6Ifdata('Command Bomber Phases', @self.phaseRow, 'gunNose') == true
Then
7Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Nose Muzzle"
tracerSpeed
@self.tracerSpeed
flashParticles
gunFlashParticles
direction
-90
flash
'Enemy Gun Muzzle'

The wing guns fire straight south every gunSeconds, with no warning at all, and in the last phase the nose gun joins them. That’s the gunNose column.

Make it as an Empty Logic Component, with its three Shared Variables. The message phaseStarted is in the Add Event list, since Command Bomber Health sends it. The timer’s Event is named bomberGuns, typed in. Its Start Recurring Timer has a First Delay this time, the wait before the first ring. The timer’s own Event starts with a Declare Variable. It’s gunFlashParticles, read from the row once.

Each Fire From Muzzle names its muzzle, such as Port Muzzle, and fires at -90, straight south. The bomber will carry three Coordinates by those names.

11.7.2 Command Bomber Aimed Volleys

Logic ComponentCommand Bomber Aimed VolleysAttach to: Command Bomber

Every aimedSeconds of the current phase, flashes a warning at the muzzles that will fire, then fires aimed tracers toward the Fighter aimedWarningSeconds later.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
tracerObjectConstantNone
fighterObjectConstantNone
aimedTracerSpeedNumberConstant8.9
minimumForwardMetersNumberConstant2.8
aimConeDegreesNumberConstant65
phaseRowNumberConstant0
On Phase Started
WhenphaseStartedCustom Event
1Stop Recurring Timer
Timer Name
"aimedWarning"
2Stop Recurring Timer
Timer Name
"aimedVolley"
3Start Recurring Timer
Timer Name
"aimedWarning"
Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedSeconds')
First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedFirstSeconds')
On the Aimed Warning Timer
WhenRecurring TimerTimer name: aimedWarning
1Declare Variable
Name
aimedWarningParticles
Value
0
2Change Variable
Variable
aimedWarningParticles
Value
data('Command Bomber Phases', @self.phaseRow, 'aimedWarningParticles')
3Ifdata('Command Bomber Phases', @self.phaseRow, 'aimedNose') == true
Then
4Burst Particles
Particles
Enemy Gun Muzzle
Position Relative To
Coordinate
Coordinate
Nose Muzzle
Count
aimedWarningParticles
Z Index Offset
2
5Ifdata('Command Bomber Phases', @self.phaseRow, 'aimedWings') == true
Then
6Burst Particles
Particles
Enemy Gun Muzzle
Position Relative To
Coordinate
Coordinate
Port Muzzle
Count
aimedWarningParticles
Z Index Offset
2
7Burst Particles
Particles
Enemy Gun Muzzle
Position Relative To
Coordinate
Coordinate
Starboard Muzzle
Count
aimedWarningParticles
Z Index Offset
2
8Start Recurring Timer
Timer Name
"aimedVolley"
Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedWarningSeconds')
Ring Once
Yes
On the Aimed Volley Timer
WhenRecurring TimerTimer name: aimedVolley
1Declare Variable
Name
aimedFlashParticles
Value
0
2Change Variable
Variable
aimedFlashParticles
Value
data('Command Bomber Phases', @self.phaseRow, 'aimedFlashParticles')
3Control Sound
Sound
Enemy Guns
4Aim at FighterFunction
Action Name
aim
shooter
self
fighter
@self.fighter
minimumForwardMeters
@self.minimumForwardMeters
aimConeDegrees
@self.aimConeDegrees
5Ifdata('Command Bomber Phases', @self.phaseRow, 'aimedNose') == true
Then
6Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Nose Muzzle"
tracerSpeed
@self.aimedTracerSpeed
flashParticles
aimedFlashParticles
direction
aim.value
flash
'Enemy Gun Muzzle'
7Ifdata('Command Bomber Phases', @self.phaseRow, 'aimedWings') == true
Then
8Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Port Muzzle"
tracerSpeed
@self.aimedTracerSpeed
flashParticles
aimedFlashParticles
direction
aim.value
flash
'Enemy Gun Muzzle'
9Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Starboard Muzzle"
tracerSpeed
@self.aimedTracerSpeed
flashParticles
aimedFlashParticles
direction
aim.value
flash
'Enemy Gun Muzzle'

An aimed volley gives you fair warning. First a spark at the muzzles about to fire, and then, aimedWarningSeconds later, the tracers come, aimed at you. In phase 1 that’s the nose alone, a little under half a second after its spark.

Two timers make it work. The first, aimedWarning, rings every aimedSeconds and sets off the spark. Each time, it starts aimedVolley, which rings once and fires. The Ifs read two Boolean columns, aimedNose and aimedWings, so each phase picks its own muzzles. Aim at Fighter aims once per volley, and every tracer in it flies along aim.value.

Why does phaseStarted stop both timers? A warning may have gone off a moment ago, with its volley still to come. Stopping that volley’s timer calls it off, so the new phase starts clean.

Make it the way you made Command Bomber Guns. It has six Shared Variables. Its two Declare Variables, one in each timer’s Event, hold the table’s particle counts, which keeps the Burst Particles and Fire From Muzzle boxes short.

The bomber doesn’t exist yet. Its muzzles can be named anyway. Pick Coordinate in each Burst Particles’ Position Relative To. The Coordinate box beside it takes a name typed in, with no quote marks: Nose Muzzle, Port Muzzle, Starboard Muzzle. Leave its Choose a Named Coordinate button alone for now. The bomber gets three Coordinates by those names later in this chapter.

11.7.3 Command Bomber Flak

Logic ComponentCommand Bomber FlakAttach to: Command Bomber

In phases with flak: every flakSeconds, a warning at the nose, then a fan of flakTracers tracers flakArcDegrees wide, centered on south, at flakSpeed.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
tracerObjectConstantNone
warningShakeSecondsNumberConstant0.2
phaseRowNumberConstant0
On Phase Started
WhenphaseStartedCustom Event
1Stop Recurring Timer
Timer Name
"flakWarning"
2Stop Recurring Timer
Timer Name
"flakFan"
3Ifdata('Command Bomber Phases', @self.phaseRow, 'flakTracers') > 0
Then
4Start Recurring Timer
Timer Name
"flakWarning"
Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'flakSeconds')
First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'flakFirstSeconds')
On the Flak Warning Timer
WhenRecurring TimerTimer name: flakWarning
1Burst Particles
Particles
Enemy Gun Muzzle
Position Relative To
Coordinate
Coordinate
Nose Muzzle
Count
data('Command Bomber Phases', @self.phaseRow, 'flakWarningParticles')
Z Index Offset
2
2Camera Shake
Intensity
data('Command Bomber Phases', @self.phaseRow, 'flakWarningShake')
Seconds
@self.warningShakeSeconds
3Start Recurring Timer
Timer Name
"flakFan"
Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'flakWarningSeconds')
Ring Once
Yes
On the Flak Fan Timer
WhenRecurring TimerTimer name: flakFan
1Declare Variable
Name
flakArcDegrees
Value
0
2Declare Variable
Name
flakTracers
Value
0
3Declare Variable
Name
fanStep
Value
0
4Declare Variable
Name
firstAngle
Value
0
5Change Variable
Variable
flakTracers
Value
data('Command Bomber Phases', @self.phaseRow, 'flakTracers')
6Change Variable
Variable
flakArcDegrees
Value
data('Command Bomber Phases', @self.phaseRow, 'flakArcDegrees')
7Control Sound
Sound
Enemy Guns
8Burst Particles
Particles
Enemy Gun Muzzle
Position Relative To
Coordinate
Coordinate
Nose Muzzle
Count
data('Command Bomber Phases', @self.phaseRow, 'flakFlashParticles')
Z Index Offset
2
9Change Variable
Variable
firstAngle
Value
-90
10Change Variable
Variable
fanStep
Value
0
11IfflakTracers > 1
Then
12Change Variable
Variable
firstAngle
Operator
-=
Value
flakArcDegrees / 2
13Change Variable
Variable
fanStep
Value
flakArcDegrees / (flakTracers - 1)
14RepeatflakTracerstimes
Each time
15Fire From MuzzleFunction
shooter
self
tracer
@self.tracer
muzzle
"Nose Muzzle"
tracerSpeed
data('Command Bomber Phases', @self.phaseRow, 'flakSpeed')
flashParticles
0
direction
firstAngle + fanStep * local.index
flash
'Enemy Gun Muzzle'

Flak is the new attack. A spark at the nose, and a fan of tracers spreads south, 70 degrees wide in phase 2 and 112 in phase 3. Phase 1 has 0 flakTracers, so its If never starts the timer, and there’s no flak until the bomber turns amber.

The fan is the one piece of real geometry here, so take it slowly. South is -90 degrees, and the fan is centered on south. So the first tracer flies half the arc to one side of it, at firstAngle.

The others share out the arc. Five tracers have four gaps between them, so each gap is the arc divided by four. That’s fanStep, the arc over flakTracers minus 1. A Repeat then fires flakTracers times. Each tracer flies at firstAngle plus fanStep times local.index. So the first takes no gaps, the next takes one, and so on across the fan. That’s all the geometry there is.

And a fan of one tracer? There are no gaps to share, so the If skips both changes, and that single tracer flies straight south. No phase has one, but you might try it.

Make it like the other two, with Nose Muzzle typed into both Coordinate boxes. The fan timer’s Event starts with four Declare Variables. They’re the values the fan works with. The Repeat’s Count is flakTracers, and its one Fire From Muzzle goes inside it, with Add Inside Repeat. In phase 3, each warning also gives the camera a small shake, from the flakWarningShake column.

11.8 The Arrival

Now the bomber’s entrance, and the lane.

11.8.1 Command Bomber Arrival

Logic ComponentCommand Bomber ArrivalAttach to: Command Bomber

When the Command Bomber comes into view: shows its health bar, keeps the Fighter out of the top laneMeters of the view, and flies on with the camera once it is formationAboveMiddleMeters above the middle of the view.

Attach toAny Object
SHARED VARIABLES
NameTypeKindStarting Value
laneMetersNumberConstant6.9
formationAboveMiddleMetersNumberConstant3.4
When Entering the View
WhenEnter View
1Declare Variable
Name
distanceToFormation
Value
0
2Declare Variable
Name
formationY
Value
0
3Set UI Group Visibility
UI Group
"commandBomber"
4Change Variable
Variable
$bomberLaneMeters
Value
@self.laneMeters
5Change Variable
Variable
formationY
Value
$viewMiddleY + @self.formationAboveMiddleMeters
6Change Variable
Variable
distanceToFormation
Value
self.transform.position.y - formationY
7Start Recurring Timer
Timer Name
"formation"
Repeat Every (s)
distanceToFormation / $scrollSpeed
Ring Once
Yes
8Trigger Custom Event on Object
Custom Event
phaseStarted
On the Formation Timer
WhenRecurring TimerTimer name: formation
1Set Velocity
Y Velocity
$scrollSpeed

Here’s the second new idea. When the bomber comes into view, it seems to fly down to meet you. It doesn’t fly south at all.

The camera flies north, as it always does, while the bomber waits in the sky. On the screen, that looks like the bomber coming down.

When it’s 3.4 meters above the middle of the view, it starts flying north at the camera’s own speed, $scrollSpeed. From then on, it holds its place near the top of the screen while the route scrolls by below.

How does it know when it’s there? It doesn’t watch. When it comes into view, it works out how far it is from that height, formationY, and divides by the camera’s speed. That’s how long until the camera has flown far enough to put the bomber at that height in the view. A timer named formation rings once after that long, and Set Velocity sends the bomber north.

Follow it through once. The bomber appears at the top. The camera flies on, and the bomber slides down the screen. The timer rings. Now the bomber holds its height, and Weave keeps it sweeping from side to side. Set Velocity leaves X Velocity empty, so the sweep goes on.

The same moment shows the health bar, sets $bomberLaneMeters to laneMeters, and starts phase 1 with phaseStarted.

Make it as an Empty Logic Component, with its two Shared Variables. formationY and distanceToFormation are Declare Variables, its first two steps. The middle of the view is $viewMiddleY, as everywhere since Chapter 4. Its Set UI Group Visibility takes "commandBomber", with its quote marks, as in Command Bomber Health.

11.8.2 The Fighter

ObjectFighter
Transform
Bounding Box > SizeX 2.88, Y 2.88
Sprite
Scene Depth Draw Order40
SpriteFighter Art
Transform > SizeX 2.88, Y 2.88
Collider
Shape and Transform > ShapeCapsule
Shape and Transform > PositionX 0, Y -0.09
Shape and Transform > SizeX 0.698, Y 1.733
Coordinate
NameCenter Muzzle
Local PositionX 0, Y 1.193
NameLeft Muzzle
Local PositionX -0.563, Y 1.058
NameRight Muzzle
Local PositionX 0.563, Y 1.058
Child Objects
NameHit Area Marker
ObjectHit Area
PositionX 0, Y -0.09
SizeX 2.781, Y 6.938
Data Structure
From LogicFocus
Data StructureFocus Data
Data Structure
From LogicReturn Flight
Data StructureReturn Flight Data
Logic
Focus
Vertical Shooter Controls
Fighter Guns
Lost on Hit
Return Flight
Shared Variables
moveSpeed > KindChangeable
moveSpeed7.8
scrollSpeed > KindExpression
scrollSpeed > Expression$scrollSpeed
topSafeInset > KindExpression
topSafeInset > Expression$bomberLaneMeters
tracerFighter Tracer

The lane is the top 6.9 meters of the view, and your Fighter may not fly there. Vertical Shooter Controls has kept a Shared Variable for it since Chapter 1. It’s topSafeInset, and it’s been 0 all along. On the Fighter, under Variables, set topSafeInset’s Kind to Expression, and type $bomberLaneMeters into its box. Until the bomber arrives it’s 0. The whole view is yours.

11.8.3 Return Flight

Logic ComponentReturn FlightAttach to: FighterAddDelete

Flies a reserve plane up from below the screen to the middle (under the Command Bomber's lane while it is on screen; if the lane closes during the flight, it flies in again, under the lane), protected and blinking, then gives back its controls and guns.

Attach toAny Object
DATA STRUCTURES
Return Flight Dataself.returnFlightData
SHARED VARIABLES
NameTypeKindStarting Value
returnSecondsNumberConstant1.2
protectionSecondsNumberConstant2
belowLaneMetersNumberConstant2.5
blinkSecondsNumberConstant0.1
dimAlphaNumberConstant0.25
On Start Return Flight
WhenstartReturnFlightCustom Event
1Declare Variable
Name
belowLaneY
Value
0
2Declare Variable
Name
viewTopY
Value
0
3Declare Variable
Name
arrivalY
Value
0
4Switch Mode
Event
Move 2D Axis Input · Fighter
Mode
Returning (no steering)
5Switch Mode
Event
While Fire Is Held · Fighter
Mode
Holding Fire
6Switch Mode
Event
When Starting to Hit Anything Tagged Fighter Hazard · Fighter
Event
When Starting to Hit Anything Tagged Enemy Threat · Fighter
Mode
Protected
7Change Variable
Variable
arrivalY
Value
$viewMiddleY + $scrollSpeed * @self.returnSeconds
8Change Variable
Variable
viewTopY
Value
arrivalY + $viewHeight / 2
9Change Variable
Variable
belowLaneY
Value
viewTopY - $bomberLaneMeters - @self.belowLaneMeters
10IfbelowLaneY < arrivalY
Then
11Change Variable
Variable
arrivalY
Value
belowLaneY
12Change Component Value
Component Value
self.returnFlightData.laneMetersWhenAimed
Value
$bomberLaneMeters
13Tween Property
Property
Y
Target Value
arrivalY
Target Value
$viewMiddleY + $scrollSpeed * @self.returnSeconds
Seconds
@self.returnSeconds
Ease
Smooth
14Start Recurring Timer
Timer Name
"arrived"
Repeat Every (s)
@self.returnSeconds
Ring Once
Yes
15Start Recurring Timer
Timer Name
"protectionOver"
Repeat Every (s)
@self.returnSeconds + @self.protectionSeconds
Ring Once
Yes
16Start Recurring Timer
Timer Name
"blink"
Repeat Every (s)
@self.blinkSeconds
On the Arrived Timer
WhenRecurring TimerTimer name: arrived
1Switch Mode
Event
Move 2D Axis Input · Fighter
Mode
Vertical Shooter Controls
2Switch Mode
Event
While Fire Is Held · Fighter
Mode
Fire the Guns
1If$bomberLaneMeters > self.returnFlightData.laneMetersWhenAimed
Then
2Trigger Custom Event on Object
Custom Event
startReturnFlight
3Otherwise
4Switch Mode
Event
Move 2D Axis Input · Fighter
Mode
Vertical Shooter Controls
5Switch Mode
Event
While Fire Is Held · Fighter
Mode
Fire the Guns
On the Protection Over Timer
WhenRecurring TimerTimer name: protectionOver
1Switch Mode
Event
When Starting to Hit Anything Tagged Fighter Hazard · Fighter
Event
When Starting to Hit Anything Tagged Enemy Threat · Fighter
Mode
Lost
2Stop Recurring Timer
Timer Name
"blink"
3Change Component Value
Component Value
self.returnFlightData.isDimmed
Value
false
4Set Alpha
On the Blink Timer
WhenRecurring TimerTimer name: blink
1Ifself.returnFlightData.isDimmed == true
Then
2Change Component Value
Component Value
self.returnFlightData.isDimmed
Value
false
3Set Alpha
4Otherwise
5Change Component Value
Component Value
self.returnFlightData.isDimmed
Value
true
6Set Alpha
Alpha
@self.dimAlpha

Two Switch Mode steps on this card show Enemy Threat struck and Fighter Hazard added. They changed by themselves. That was back when Lost on Hit changed. Leave them.

A reserve plane flies up from below the screen, to the middle. With the lane closed, the middle can sit too close to it, and the moment the plane’s controls came back, they would shove it down. So Return Flight aims lower, belowLaneMeters under the lane.

Work it out the way the plane would. The middle of the view when the plane arrives is arrivalY, as before. The top of the view then is viewTopY, half the view’s height higher, and under the lane is belowLaneY. If belowLaneY is the lower one, the If puts it in arrivalY’s place. The lower one always wins.

One more case. A plane can be on its way when the bomber comes into view, and the lane closes over the spot it was aiming for. So Return Flight remembers the lane it aimed for, in laneMetersWhenAimed. When the plane arrives, it checks. If the lane has grown since, the plane flies in again, under the lane, and its protection starts over. If not, it gets its controls and guns back, as before.

The view’s height comes from $viewHeight. Track the View writes it now too, from the camera’s own Camera component, as a third step.

Logic ComponentTrack the ViewAddDelete

Runs on the Main Camera. Each Begin Step it writes where the view is, in Globals that Logic on other Objects reads to place planes, enemies, pickups and scenery relative to the view.

Attach toCamera
On Begin Step
WhenBegin Step
1Change Variable
Variable
$viewMiddleX
Value
self.transform.position.x
2Change Variable
Variable
$viewMiddleY
Value
self.transform.position.y
3Change Variable
Variable
$viewHeight
Value
self.camera.viewport.height

Add that Change Variable to Track the View, from its card. Back in Return Flight, add one Shared Variable, belowLaneMeters, and one field to Return Flight Data, laneMetersWhenAimed, a Number at 0.

Data StructureReturn Flight DataAddDelete

Object Data of Logic "Return Flight" (its state that lasts between Events).

isDimmedBooleandefault false
laneMetersWhenAimedNumberdefault 0

In On Start Return Flight, three Declare Variables go first, and six new steps go between the third Switch Mode and Tween Property. The Declare Variables go in front with the round handles. Add one, click the round handle at the bottom of the Mode’s block, then the new block’s top handle. Do arrivalY first, then viewTopY, then belowLaneY, each in front of the last. For the six, take Tween Property and the three Start Recurring Timers off the line, each with Disconnect from Flow. Add the new steps, the If among them, and join each one. Then join Tween Property and the three timers again, in order. Change Tween Property’s Target Value to arrivalY.

On the Arrived Timer, the two old steps move into an Else. The shortest way is to make them again. Delete them, and add the If: $bomberLaneMeters > self.returnFlightData.laneMetersWhenAimed. On its True line, Trigger Custom Event on Object sends startReturnFlight to the plane itself. Add Else, then Add Inside Else twice, for the two Switch Modes, set as before.

11.9 The Command Bomber

Every piece is ready. Now they meet, on one Object.

ObjectCommand Bomber
Transform
Bounding Box > SizeX 4.48, Y 4.48
Sprite
Scene Depth Draw Order32
SpriteCommand Bomber Art
Transform > SizeX 4.48, Y 4.48
Transform > Rotation (deg)Z -180
Collider
Shape and Transform > ShapeEllipse
Shape and Transform > SizeX 3.43, Y 2.45
Tags and Interactions > InteractionsNever Collide With
Tags and Interactions > Never Collide WithTagged Enemy Aircraft
Coordinate
NamePort Muzzle
Local PositionX -1.068, Y -1.068
NameNose Muzzle
Local PositionX 0, Y -1.383
NameStarboard Muzzle
Local PositionX 1.068, Y -1.068
Data Structure
Data StructureCommand Bomber State
Data Structure
From LogicMeter with Thresholds
Data StructureMeter with Thresholds Data
Data Structure
From LogicWeave
Data StructureWeave Data
Tags
Object TagsFighter Hazard
Logic
Command Bomber Health
Meter with Thresholds
Command Bomber Hits
Command Bomber Arrival
Weave
Command Bomber Guns
Command Bomber Aimed Volleys
Command Bomber Flak
Shared Variables
weaveSpeed2.4
weaveSeconds1.35
maximum > KindExpression
maximum > ExpressionobjectComponentProperty("self","commandBomberState","maxHealth")
threshold > KindExpression
threshold > Expressionround(objectComponentProperty("self","commandBomberState","maxHealth") * (objectComponentProperty("self","commandBomberState","phaseRow") + 1) / dataCount('Command Bomber Phases'))
meterFielddamage
wingCannonsWing Cannons
tracerEnemy Tracer
fighterFighter
phaseRow > KindExpression
phaseRow > ExpressionobjectComponentProperty("self","commandBomberState","phaseRow")

The Command Bomber is a 2D Object, with its picture turned to Z -180, nose south, like every enemy’s. It wears one Tag, Fighter Hazard. Its Collider is an Ellipse. Like the enemies in Chapter 7, it lets other planes pass through it: Never Collide With, Tagged Enemy Aircraft.

Its three Coordinates are its muzzles, so click Coordinate in the toolbox three times.

Click Data Structure in the Data group. It attaches Enemy Stats, the first one it finds, so pick Command Bomber State in its list. Do that before the Logic. Then Command Bomber State is the bomber’s own, as its card shows.

In Logic, click the eight Logic Components in the card’s order, and they line up the same way. Command Bomber Health goes first. Its Create writes the full health before anything reads it.

Two more Data Structures arrive with the Logic, Meter with Thresholds Data and Weave Data. Then the Shared Variables, under Variables, where most keep the Logic’s own values. Weave’s speed and beat are the bomber’s own, 2.4 and 1.35. Meter with Thresholds’ maximum and threshold are Expressions, each typed whole from the card. They read maxHealth and phaseRow from Command Bomber State. meterField is damage, typed bare. That names the field of Command Bomber State where the meter keeps its count, so the health bar can read it. The Objects, wingCannons, tracer and fighter, are None buttons: pick Wing Cannons, Enemy Tracer and Fighter. Last comes phaseRow. It’s one Shared Variable for all four Logic Components that read it. It’s an Expression too.

11.9.1 Breaks on Hit

Logic ComponentBreaks on HitAttach to: Fighter TracerAddDelete

Removes this tracer when it touches an enemy or the Command Bomber.

Attach toAny Object
When Starting to Hit Anything Tagged Enemy Aircraft
WhenCollision EnterObject: tag:Enemy Aircraft
1Destroy Instance
When Starting to Hit Command Bomber
WhenCollision EnterObject: Command Bomber
1Destroy Instance

Your tracers break on enemies, and the bomber isn’t one, so Breaks on Hit needs a second Event. Add Collision Enter with Command Bomber as its Collision Target. Give it Destroy Instance.

11.9.2 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
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

Score adds 10 points for every hit on the bomber, and 2000 more when it finally goes down. Add the two new Shared Variables, then the two Events. Both messages are in the Add Event list already, since Command Bomber Hits and Command Bomber Health send them.

Open Route, and place the Command Bomber from PLACE THESE, at the far north end of the route.

Save, and run the game. Fly the whole route, and keep your plane. After the last wave, the bomber slides down from the top, and its guns open up. Hold Space and keep at it. There’s no health bar yet. Even so, every hit turns it white, and SCORE climbs by 10.

11.10 The Health Bar

Last comes the red bar at the top of the screen, the one that shows how much fight is left.

ObjectCommand Bomber Health Track
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorTop Center
Presentation > Render LayerUI
Bounding Box > SizeX 32, Y 32
Sprite
SpriteHealth Track Art
Transform > SizeX 244, Y 10
UI Interaction
Element TypeText
GroupcommandBomber
EventmenuChoice
ObjectCommand Bomber Health Bar
Transform
Presentation > SpaceWindow / UI
Presentation > Screen AnchorTop Center
Presentation > Render LayerUI
Bounding Box > SizeX 32, Y 32
Sprite
SpriteHealth Bar Art
Transform > PositionX 120, Y 0
Transform > SizeX 240, Y 6
UI Interaction
Element TypeText
GroupcommandBomber
EventmenuChoice
Logic
Show Command Bomber Health

The health bar is two Screen UI Objects, one over the other. The track is the dark frame. The bar is the red inside it. Both keep their UI Interaction, with the Group commandBomber, so they show and hide together. Their Event, menuChoice, never goes anywhere, as on Score Text.

Look at the bar’s numbers. Its picture sits 120 pixels right of its Object’s middle. And on the Set up card, the Object is placed 120 to the left. Why the shuffle? Set Scale shrinks a thing toward its Object’s middle. With the middle at the bar’s left end, the bar shrinks toward the left, the way a health bar should. The two 120s cancel out, and the bar sits inside its track.

11.10.1 Show Command Bomber Health

Logic ComponentShow Command Bomber HealthAttach to: Command Bomber Health Bar

Shrinks this bar to the Command Bomber's share of health left each time it is hit.

Attach toAny Object
On Command Bomber Hit
WhencommandBomberHitCustom Event
1Declare Variable
Name
healthLeft
Value
0
2Change Variable
Variable
healthLeft
Value
other.commandBomberState.maxHealth - other.commandBomberState.damage
3Set Scale
Horizontal Scale
healthLeft / other.commandBomberState.maxHealth

Each time commandBomberHit arrives, the bar reads the bomber’s Command Bomber State. Full health minus damage is healthLeft. That over the full health is the share left, from 1 down to 0, and Set Scale makes it the bar’s Horizontal Scale.

The other here is the bomber that sent the message, as other on Score is the enemy shot down. It’s under the block’s Event Variables, with nothing to make. healthLeft is a Declare Variable. Make it as an Empty Logic Component, and attach it to the bar.

11.10.2 Show the Flight HUD

Logic ComponentShow the Flight HUDAttach to: Score TextAddDelete

Shows the flight HUD (SCORE and the reserve plane icons) while the mission is being played, and hides it, with the Command Bomber's health bar, when the mission ends.

Attach toAny Object
On Game State Changed
WhenGame State Changed
1Ifevent.current == "playing"
Then
2Set UI Group Visibility
UI Group
"flight"
3Otherwise
4Set UI Group Visibility
UI Group
"flight"
Visible
No
5Set UI Group Visibility
UI Group
"commandBomber"
Visible
No

When a mission fails during the fight, the bar should go with the rest of the flight HUD. Add one more Set UI Group Visibility to the Otherwise, with Add Inside Else on its Else. Its UI Group is "commandBomber", with Visible on No.

Open Route, and place the track and the bar from PLACE THESE, each hidden, with its own Z-Index. Command Bomber Arrival shows them when the time comes.

Save, and run the game.

Fly the route once more. The bomber slides down, and the red bar appears at the top of the screen. Every hit nibbles at it from the right. A third of the way along, the camera jolts, a Wing Cannons tumbles toward you, and the bomber turns amber. Fans of flak spread across the screen. Keep going. The red comes, and everything is faster. And at last the bar is empty, and the sky fills with the biggest explosion you’ve made.

Play it!

Watch for each of these, one at a time.

  • After the last wave, a small shake from Stages, the last milestone since Chapter 7. The bomber is near.
  • It slides down from the top of the screen, and the red health bar appears above everything.
  • Try to fly up to it. Your Fighter stops below its lane, the top 6.9 meters of the view.
  • In phase 1, the wing guns fire straight down every 0.78 seconds. Before each aimed tracer, the nose sparks.
  • Hit it. It turns white, a puff of flak bursts, SCORE climbs by 10, and the bar shrinks from the right.
  • At the end of phase 1, the camera shakes, a Wing Cannons drops, and the bomber settles amber after its next hit. Fans of five tracers begin.
  • In phase 3, it turns red. The nose gun joins the wings, and the fans have seven tracers, with a small shake before each.
  • Fly into the bomber. You lose the plane, the bomber flies on, and the screen doesn’t go pale.
  • Lose a plane during the fight. The reserve flies in under the lane and stops there.
  • Bring it down. A huge explosion, a hard shake, 2000 points, and the bar is gone. The lane opens. The top of the view is yours again.
How it works

A tally counts the hits, and a moving line turns the tally into phases. A table says what each phase does, and three guns read it. A lane keeps you below. A timer brings the bomber down to meet you.

Every piece of it is one you had already: Fire From Muzzle, Weave, Modes, a table, a Function. A big enemy is many small pieces that read one row together. To change the fight, change the row.

11.11 If It Didn’t Work

There are many pieces here, so start with what you can see, and follow it back.

Press Play, and a window called Can’t run yet opens? Its message names the piece and what’s wrong with it, so fix that, and press Play again.

The enemies stopped turning white, or stopped aiming, after the first part? Look at the new Function in Takes Hits, whose target should be self. In Fire When Seen, the call should be named aim, and Fire From Muzzle’s direction should read aim.value.

The bomber never appears? Look for it in Route, at the far north end, and for Command Bomber Arrival in its Attached Logic.

It slides down and keeps going, off the bottom of the screen? Perhaps the formation timer never rings. Look at Command Bomber Arrival’s Start Recurring Timer, and at the Y Velocity of its Set Velocity.

It never fires? Its guns wait for phaseStarted, so look at Command Bomber Arrival’s last step. Each gun’s first Event should listen for phaseStarted too.

Your tracers hit it, but nothing counts? Look at Command Bomber Hits first: its Collision Target should be Fighter Tracer. On the bomber, meterField should read damage.

The phases never change? Look at the threshold’s formula on the bomber, and at the meter variable on Command Bomber Health’s Event, which should be meterWithThresholdsData.meter.

The bar is empty, and the bomber flies on? Perhaps Next Phase’s Switch Mode has an empty list. Its Event should be the one beside Command Bomber, and its Mode Shot Down.

Your Fighter flies into the lane? Look at topSafeInset under the Fighter’s Variables, which should be an Expression, $bomberLaneMeters.

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 this chapter’s game, finished, if you’d like to compare.

Challenge

Make the fight your own. Open Command Bomber Phases, and change a row. Give phase 1 a small fan of flak of its own, a few tracers wide. Or set a phase’s flakTracers to 1, and watch its single tracer fly straight south. Or add a fourth row. Command Bomber Health finds the last row by counting, and the threshold shares the health among however many rows there are. So a fourth phase works with no change to the Logic. Put things back before Chapter 12.

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

Your route has an end now, and something waiting there. In Chapter 12, the route begins again, under a darker sky.