- Learn
- Build a Game
- Pacific Wings
- 11 The Command Bomber
11 The Command Bomber
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.
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.
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.
X 0.68, Y 0.6850Enemy Tracer ArtX 0.68, Y 0.68Z -180SensorCapsuleX 0.154, Y 0.499Fighter HazardEnemy ThreatX 2.48, Y 2.4830Enemy Fighter ArtX 2.48, Y 2.48Z -180EllipseX 1.453, Y 1.046Never Collide WithTagged Enemy AircraftEnemy StatsData TableEnemy TypesEnemy FighterFire When SeenFire When Seen DataGun MuzzleX 0, Y -0.94Enemy Aircraft, Fighter HazardEnemy Aircraft, Enemy ThreatEnemy TracerExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")FighterExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")Wing CannonsRepair CrateExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")ExpressionobjectComponentProperty("self","enemyStats","speed")X 2.56, Y 2.5630Enemy Ace ArtX 2.56, Y 2.56Z -180EllipseX 1.6, Y 1.04Never Collide WithTagged Enemy AircraftGun MuzzleX 0, Y -0.94Enemy StatsData TableEnemy TypesEnemy AceWeaveWeave DataFire When SeenFire When Seen DataEnemy Aircraft, Fighter HazardEnemy Aircraft, Enemy ThreatEnemy TracerExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")FighterExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")ExpressionobjectComponentProperty("self","enemyStats","weaveSpeed")ExpressionobjectComponentProperty("self","enemyStats","weaveSeconds")ExpressionobjectComponentProperty("self","enemyStats","weaveAngle")Wing CannonsRepair CrateExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")ExpressionobjectComponentProperty("self","enemyStats","speed")X 3.36, Y 3.3630Heavy Bomber ArtX 3.36, Y 3.36Z -180EllipseX 2.468, Y 1.733Never Collide WithTagged Enemy AircraftGun MuzzleX 0, Y -0.932Enemy StatsData TableEnemy TypesHeavy BomberWeaveWeave DataFire When SeenFire When Seen DataEnemy Aircraft, Fighter HazardEnemy Aircraft, Enemy ThreatEnemy TracerExpressionobjectComponentProperty("self","enemyStats","tracerSpeed")ExpressionobjectComponentProperty("self","enemyStats","firstShotSeconds")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMin")ExpressionobjectComponentProperty("self","enemyStats","shotSecondsMax")FighterExpressionobjectComponentProperty("self","enemyStats","aimConeDegrees")ExpressionobjectComponentProperty("self","enemyStats","weaveSpeed")ExpressionobjectComponentProperty("self","enemyStats","weaveSeconds")ExpressionobjectComponentProperty("self","enemyStats","weaveAngle")Wing CannonsRepair CrateExpressionobjectComponentProperty("self","enemyStats","wingCannonsDropChance")ExpressionobjectComponentProperty("self","enemyStats","repairCrateDropChance")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
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).
Any Object- Custom Event
fighterLost
- Sound
Fighter Lost
- Intensity
@self.shakeIntensity- Seconds
@self.shakeSeconds
- Color
@self.flashColor- Seconds
@self.flashSeconds
$stage < @self.flashBeforeStage- Color
@self.flashColor- Seconds
@self.flashSeconds
- Particles
Plane Explosion- Count
@self.explosionParticles- Z Index Offset
2
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.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
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.
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
One row of the "Command Bomber Phases" Data Table.
#ffd0b8000No0.780.45No51.750.80.48YesNo108000000000#ffd6a00.0180.3236Yes0.60.25No51.350.50.5NoYes7553.20.70.5706.920140#ff9c8f0.0280.4552Yes0.460.2Yes41.050.350.38YesYes6472.250.450.38112828180.012Each 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
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.
Any Objectself.meterWithThresholdsDataObject Data of Logic "Meter with Thresholds" (its state that lasts between Events).
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
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.
Any Objectself.commandBomberState- Component Value
self.commandBomberState.maxHealth- Value
@self.startingHealth
- Component Value
self.commandBomberState.phaseRow- Value
0
- Name
newPhaseRow- Value
0
- Component Value
self.commandBomberState.phaseRow- Operator
+=- Value
1
- Variable
newPhaseRow- Value
self.commandBomberState.phaseRow
- Custom Event
phaseStarted
- Intensity
data('Command Bomber Phases', newPhaseRow, 'startShake')- Seconds
data('Command Bomber Phases', newPhaseRow, 'startShakeSeconds')
- Particles
Flak Burst- Count
data('Command Bomber Phases', newPhaseRow, 'startFlakParticles')- Z Index Offset
2
data('Command Bomber Phases', newPhaseRow, 'dropsWingCannons') == true- Object to Create
@self.wingCannons- Position Relative To
Self
newPhaseRow == (dataCount('Command Bomber Phases') - 1)- Event
On Meter Threshold Reached · Meter With Thresholds Data.meter- Mode
Shot Down
- Custom Event
commandBomberShotDown
- Sound
Plane Break
- Intensity
@self.shotDownShake- Seconds
@self.shotDownShakeSeconds
- Particles
Plane Explosion- Count
@self.shotDownParticles- Z Index Offset
2
- UI Group
"commandBomber"- Visible
No
- Variable
$bomberLaneMeters- Value
0
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
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.
Any Object- Custom Event
changeMeter
- Particles
Flak Burst- Count
@self.hitParticles- Z Index Offset
2
- Custom Event
commandBomberHit
- 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
Fires straight south from the wing muzzles (and the nose when its phase says so) every gunSeconds of the current phase.
Any Object- Timer Name
"bomberGuns"
- Timer Name
"bomberGuns"- Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'gunSeconds')- First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'gunFirstSeconds')
- Name
gunFlashParticles- Value
0
- Variable
gunFlashParticles- Value
data('Command Bomber Phases', @self.phaseRow, 'gunFlashParticles')
- Sound
Enemy Guns
- shooter
self- tracer
@self.tracer- muzzle
"Port Muzzle"- tracerSpeed
@self.tracerSpeed- flashParticles
gunFlashParticles- direction
-90- flash
'Enemy Gun Muzzle'
- shooter
self- tracer
@self.tracer- muzzle
"Starboard Muzzle"- tracerSpeed
@self.tracerSpeed- flashParticles
gunFlashParticles- direction
-90- flash
'Enemy Gun Muzzle'
data('Command Bomber Phases', @self.phaseRow, 'gunNose') == true- 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
Every aimedSeconds of the current phase, flashes a warning at the muzzles that will fire, then fires aimed tracers toward the Fighter aimedWarningSeconds later.
Any Object- Timer Name
"aimedWarning"
- Timer Name
"aimedVolley"
- Timer Name
"aimedWarning"- Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedSeconds')- First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedFirstSeconds')
- Name
aimedWarningParticles- Value
0
- Variable
aimedWarningParticles- Value
data('Command Bomber Phases', @self.phaseRow, 'aimedWarningParticles')
data('Command Bomber Phases', @self.phaseRow, 'aimedNose') == true- Particles
Enemy Gun Muzzle- Position Relative To
Coordinate- Coordinate
Nose Muzzle- Count
aimedWarningParticles- Z Index Offset
2
data('Command Bomber Phases', @self.phaseRow, 'aimedWings') == true- Particles
Enemy Gun Muzzle- Position Relative To
Coordinate- Coordinate
Port Muzzle- Count
aimedWarningParticles- Z Index Offset
2
- Particles
Enemy Gun Muzzle- Position Relative To
Coordinate- Coordinate
Starboard Muzzle- Count
aimedWarningParticles- Z Index Offset
2
- Timer Name
"aimedVolley"- Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'aimedWarningSeconds')- Ring Once
Yes
- Name
aimedFlashParticles- Value
0
- Variable
aimedFlashParticles- Value
data('Command Bomber Phases', @self.phaseRow, 'aimedFlashParticles')
- Sound
Enemy Guns
- Action Name
aim- shooter
self- fighter
@self.fighter- minimumForwardMeters
@self.minimumForwardMeters- aimConeDegrees
@self.aimConeDegrees
data('Command Bomber Phases', @self.phaseRow, 'aimedNose') == true- shooter
self- tracer
@self.tracer- muzzle
"Nose Muzzle"- tracerSpeed
@self.aimedTracerSpeed- flashParticles
aimedFlashParticles- direction
aim.value- flash
'Enemy Gun Muzzle'
data('Command Bomber Phases', @self.phaseRow, 'aimedWings') == true- shooter
self- tracer
@self.tracer- muzzle
"Port Muzzle"- tracerSpeed
@self.aimedTracerSpeed- flashParticles
aimedFlashParticles- direction
aim.value- flash
'Enemy Gun Muzzle'
- 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
In phases with flak: every flakSeconds, a warning at the nose, then a fan of flakTracers tracers flakArcDegrees wide, centered on south, at flakSpeed.
Any Object- Timer Name
"flakWarning"
- Timer Name
"flakFan"
data('Command Bomber Phases', @self.phaseRow, 'flakTracers') > 0- Timer Name
"flakWarning"- Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'flakSeconds')- First Delay (s)
data('Command Bomber Phases', @self.phaseRow, 'flakFirstSeconds')
- Particles
Enemy Gun Muzzle- Position Relative To
Coordinate- Coordinate
Nose Muzzle- Count
data('Command Bomber Phases', @self.phaseRow, 'flakWarningParticles')- Z Index Offset
2
- Intensity
data('Command Bomber Phases', @self.phaseRow, 'flakWarningShake')- Seconds
@self.warningShakeSeconds
- Timer Name
"flakFan"- Repeat Every (s)
data('Command Bomber Phases', @self.phaseRow, 'flakWarningSeconds')- Ring Once
Yes
- Name
flakArcDegrees- Value
0
- Name
flakTracers- Value
0
- Name
fanStep- Value
0
- Name
firstAngle- Value
0
- Variable
flakTracers- Value
data('Command Bomber Phases', @self.phaseRow, 'flakTracers')
- Variable
flakArcDegrees- Value
data('Command Bomber Phases', @self.phaseRow, 'flakArcDegrees')
- Sound
Enemy Guns
- Particles
Enemy Gun Muzzle- Position Relative To
Coordinate- Coordinate
Nose Muzzle- Count
data('Command Bomber Phases', @self.phaseRow, 'flakFlashParticles')- Z Index Offset
2
- Variable
firstAngle- Value
-90
- Variable
fanStep- Value
0
flakTracers > 1- Variable
firstAngle- Operator
-=- Value
flakArcDegrees / 2
- Variable
fanStep- Value
flakArcDegrees / (flakTracers - 1)
flakTracerstimes- 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
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.
Any Object- Name
distanceToFormation- Value
0
- Name
formationY- Value
0
- UI Group
"commandBomber"
- Variable
$bomberLaneMeters- Value
@self.laneMeters
- Variable
formationY- Value
$viewMiddleY + @self.formationAboveMiddleMeters
- Variable
distanceToFormation- Value
self.transform.position.y - formationY
- Timer Name
"formation"- Repeat Every (s)
distanceToFormation / $scrollSpeed- Ring Once
Yes
- Custom Event
phaseStarted
- 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
X 2.88, Y 2.8840Fighter ArtX 2.88, Y 2.88CapsuleX 0, Y -0.09X 0.698, Y 1.733Center MuzzleX 0, Y 1.193Left MuzzleX -0.563, Y 1.058Right MuzzleX 0.563, Y 1.058Hit Area MarkerHit AreaX 0, Y -0.09X 2.781, Y 6.938FocusFocus DataReturn FlightReturn Flight DataChangeable7.8Expression$scrollSpeedExpression$bomberLaneMetersFighter TracerThe 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
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.
Any Objectself.returnFlightData- Name
belowLaneY- Value
0
- Name
viewTopY- Value
0
- Name
arrivalY- Value
0
- Event
Move 2D Axis Input · Fighter- Mode
Returning (no steering)
- Event
While Fire Is Held · Fighter- Mode
Holding Fire
- Event
When Starting to Hit Anything Tagged Fighter Hazard · Fighter- Event
When Starting to Hit Anything Tagged Enemy Threat · Fighter- Mode
Protected
- Variable
arrivalY- Value
$viewMiddleY + $scrollSpeed * @self.returnSeconds
- Variable
viewTopY- Value
arrivalY + $viewHeight / 2
- Variable
belowLaneY- Value
viewTopY - $bomberLaneMeters - @self.belowLaneMeters
belowLaneY < arrivalY- Variable
arrivalY- Value
belowLaneY
- Component Value
self.returnFlightData.laneMetersWhenAimed- Value
$bomberLaneMeters
- Property
Y- Target Value
arrivalY- Target Value
$viewMiddleY + $scrollSpeed * @self.returnSeconds- Seconds
@self.returnSeconds- Ease
Smooth
- Timer Name
"arrived"- Repeat Every (s)
@self.returnSeconds- Ring Once
Yes
- Timer Name
"protectionOver"- Repeat Every (s)
@self.returnSeconds + @self.protectionSeconds- Ring Once
Yes
- Timer Name
"blink"- Repeat Every (s)
@self.blinkSeconds
- Event
Move 2D Axis Input · Fighter- Mode
Vertical Shooter Controls
- Event
While Fire Is Held · Fighter- Mode
Fire the Guns
$bomberLaneMeters > self.returnFlightData.laneMetersWhenAimed- Custom Event
startReturnFlight
- Event
Move 2D Axis Input · Fighter- Mode
Vertical Shooter Controls
- Event
While Fire Is Held · Fighter- Mode
Fire the Guns
- Event
When Starting to Hit Anything Tagged Fighter Hazard · Fighter- Event
When Starting to Hit Anything Tagged Enemy Threat · Fighter- Mode
Lost
- Timer Name
"blink"
- Component Value
self.returnFlightData.isDimmed- Value
false
self.returnFlightData.isDimmed == true- Component Value
self.returnFlightData.isDimmed- Value
false
- Component Value
self.returnFlightData.isDimmed- Value
true
- 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.
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.
Camera- Variable
$viewMiddleX- Value
self.transform.position.x
- Variable
$viewMiddleY- Value
self.transform.position.y
- 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.
Object Data of Logic "Return Flight" (its state that lasts between Events).
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.
X 4.48, Y 4.4832Command Bomber ArtX 4.48, Y 4.48Z -180EllipseX 3.43, Y 2.45Never Collide WithTagged Enemy AircraftPort MuzzleX -1.068, Y -1.068Nose MuzzleX 0, Y -1.383Starboard MuzzleX 1.068, Y -1.068Command Bomber StateMeter with ThresholdsMeter with Thresholds DataWeaveWeave DataFighter Hazard2.41.35ExpressionobjectComponentProperty("self","commandBomberState","maxHealth")Expressionround(objectComponentProperty("self","commandBomberState","maxHealth") * (objectComponentProperty("self","commandBomberState","phaseRow") + 1) / dataCount('Command Bomber Phases'))damageWing CannonsEnemy TracerFighterExpressionobjectComponentProperty("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
Removes this tracer when it touches an enemy or the Command Bomber.
Any ObjectYour 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
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.
Any Object- Name
multiplier- Value
1
- Variable
multiplier- Value
data('Kill Chain Multipliers', $chainRow, 'multiplier')
- Variable
$score- Operator
+=- Value
other.enemyStats.points * multiplier
- Custom Event
shotDownScored
event.current == "mission-failed"- Logic Component
Score- State
Disable
- Variable
$score- Operator
+=- Value
@self.wingCannonsPoints
- Variable
$score- Operator
+=- Value
@self.repairCratePoints
- Variable
$score- Operator
+=- Value
@self.commandBomberHitPoints
- 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.
Window / UITop CenterUIX 32, Y 32Health Track ArtX 244, Y 10TextcommandBombermenuChoiceWindow / UITop CenterUIX 32, Y 32Health Bar ArtX 120, Y 0X 240, Y 6TextcommandBombermenuChoiceThe 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
Shrinks this bar to the Command Bomber's share of health left each time it is hit.
Any Object- Name
healthLeft- Value
0
- Variable
healthLeft- Value
other.commandBomberState.maxHealth - other.commandBomberState.damage
- 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
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.
Any Objectevent.current == "playing"- UI Group
"flight"
- UI Group
"flight"- Visible
No
- 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.
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.
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.
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.
