Asteroid Destroyer How it Works This is a summary of some of the more advance coding associated with the Asteroid Destroyer Game. Many of the events with in the game are common sense other than the following topics Asteroid Handling Explained The "Asteroid"'s properties, the "Set angle" property is set to "No". This way, the bullet behavior will still make the asteroid move along the screen, but the rotate behavior will define the angle at which the texture will be displayed, giving the "illusion" the asteroid is rotating on itself while following a straight trajectory. Then any time the asteroid will leave the play field, the wrap behavior will automatically make it appear on the other side of the screen. The idea there is that when an "Asteroid" is being shot, it will "break" into two smaller asteroids. "Asteroid" has an instance variable that keeps a "size ratio", to check if, when the asteroid gets shot, it should break into smaller parts or simply disappear. The instance variable is therefore called "Size" and is a number variable, that be used in the "sizing formula". The default value is 1. Breaking down the code (Asteroid Handling) In "esgame" the group "AsteroidHandling" (event 18) contains the code for this game mechanic. Everything happens on a collision between an "Asteroid" and a "Bullet" The first action taken is to destroy the "Bullet" to prevent this event to be executed again next tick, to prevent the "Bullet" to hit another "Asteroid". Event 20 is empty (it has no condition) so will be executed every tick. As it is a sub event to the event 19, it will only execute if the event 19 executes. And the event 19 being a triggered condition (in the "Events run top to bottom" section of the article) this sub event will only run for one tick each time. This allows to create local variables and store values in those. Using a local variable here make sure that those values are reset each tick and can't be modified/used from outside of the current event. So once a collision is detected the first step is to keep the UID (Unique Identification Number) of the "Asteroid" in a local variable. (event 20)
UID (Unique Identification Number) All objects at runtime have a unique ID assigned, which is a number starting at 0 for the first instance and incrementing by 1 for every other instance. It is returned by the object's UID expression. This number can be used to uniquely identify a single instance throughout an entire game. For example, it can be stored in instance variables or event variables and later picked again by using the Pick by unique ID condition. This will allow Construct 2 to pick this asteroid again at the end of the process to destroy it. This is in anticipation of the fact that we might spawn two new "Asteroid" instances and that Construct 2 always picks the latest spawned object. The current angle of motion of the bullet behavior of this "Asteroid" instance is also kept in a local variable. It will be used later when spawning new asteroids as trajectory basis from which the newly instances will diverge. Event 21 is a sub event to event 19 and is "paired" with event 25. This event is a test on the current picked instance (the "Asteroid" instance that has collided with a "Bullet") to see if its "Size"'s value is less than 2.5. If it is, this means that we will split the asteroid into two smaller ones. System: Set CurrentSize to Asteroid.Size + 0.5 This action stores the current "Size" (instance variable) value and adds 0.5 to it. This is for the splitting of asteroids which is explained a few lines below. System: Add 1 to Score Adds 1 to the global variable "Score", the player just scored 1 point because he hit a "split able" "Asteroid" with one of his "Bullet". The splitting into two new instances happens event 22 which is a "Repeat 2 times" sub event. It will only execute if it's parent event is executed (event 21, so when Asteroid.Size < 2.5) and will repeat the bunch of actions two times. System: Create Asteroid at Asteroid.X, Asteroid.Y on layer 0 The system creates a new "Asteroid" at the position of the currently picked "Asteroid" instance. The first time the event runs, the picked instance is the instance that was in collision with "Bullet", the second time it is this newly spawned instance. And actually, from this action, this is also the picked instance. All the following actions will apply to this newly spawned instance and only this one.
Asteroid: Set Bullet angle of motion to int((currentaom + random(125))%180) degrees This action contains a bit of math and a bit of system expressions. (the expressions each have a definition, be sure to check the manual out whenever you're wondering about them) Let's break it down: int() Is a system expression that will make the content between the parenthesis an integer. CurrentAOM Is a local variable we set earlier that contains the Bullet angle of motion of the parent Asteroid instance. random(125) Is another system expression that will return (generate) a random number. In this case it will generate a number between 0 and 124 (included) to add to the parent s angle of motion, and make sure the child s trajectory is different from its parent s. The value 125 was achieved arbitrary and through trials and errors/tweaking. ()%180 Is the mathematical "modulo" (or "modulus"?) that makes sure the result between the parenthesis can't be greater than 180. The modulo is necessary here because the bullet angle of motion can be expressed in the range of 180 to 180. Asteroid: Set size (width, height) to ( 96 / CurrentSize, 96 / CurrentSize ) Affects the width and height properties of the Sprite object type and sets the current size of the texture/object to 96 (an arbitrary start value) divided by the value of the local variable CurrentSize which is the "Size" (instance variable) value of the parent instance + 0.5. Dividing a value by a greater value returns a diminished result. So the newly spawned "Asteroid" is CurentSize smaller than its parent. Asteroid: Set "Size" (instance variable) value to CurrentSize Sets the current instance's "Size" value to the value of the local variable CurrentSize. As on each split the "Size" value is incremented, the "Asteroid"'s size gets smaller, and it allows for the event21/event25 pairing/logic. Asteroid: Set Rotate speed to random(20,180) degrees per second As earlier, the random() system expression returns a float number here (because there is no int() in the formula) between 20 and 179 (included). It sets the rotation speed (on itself, Rotate behavior) for the current "Asteroid" instance,
allowing for a bit of visual diversity on screen (not all asteroids rotates at the same speed). Asteroid: Set Bullet speed to random(asteroidmaxspeed 10,AsteroidMaxSpeed) The two last actions are here for a visual final touch. Asteroid: Set angle to Asteroid.Bullet.AngleOfMotion degrees Asteroid: Move forward random(5,15) pixels The newly spawned "Asteroid" is moved away by a few pixels from its parent. For it to work, for this one tick, the angle of the Sprite is set to its Bullet angle of motion. Then the "Asteroid" is "forwarded" by a random amount of pixels (at least 5, at max 14). This is it for the split. System: Pick all Asteroid And so now, we need to "reset the picking" thanks to the system condition "Pick all". At this point of the code, the instance picked is the second "Asteroid" child instance we spawned. That's why there's the need to "Pick all (instances" here. Picking all "Asteroids" tells Construct 2 that we want to select another instance among all of the "Asteroid" instances available. System: CurrentUID not = 0 CurrentUID being a local variable, each tick its value is reset to 0. It is unlikely that an "Asteroid" instance has the UID 0 (this UID is reserved to the very first object that was created in the project, and it wasn't an "Asteroid"). This check is not really useful, but it prevent execution if an incorrect UID (the local variable default value: 0) is stored. It's only a check, it's not worth much, and it could prevent deleting the wrong instance. Asteroid: Pick instance with UID CurrentUID Finally this common condition pi cks the parent "Asteroid" instance by its UID, which we had kept at the beginning of this process in the local variable CurrentUID. (the blank event 20) The action "Asteroid: destroy" will apply to this instance, and this instance only. Event 25 is a "Else" condition that will occur when event 21 (a test to see if the size of the "Asteroid" in collision is less than 2.5) is not executed (because its condition is not true, the "Asteroid"'s size is 3). If this event executes, it means that the "Asteroid" instance in collision with a "Bullet" "Size" instance variable value is equal to 3. In the game logic, and because that's the value chose via tweaking, it means it is the last piece, it won't split, just be destroyed. It also adds 10 points to the player's score. This closes the "AsteroidHandling" group!
Player Health Explained The last point is handled in the group "PlayerHandling" (event 14) Event 15 the "Player" collides with an "Asteroid". Player: Substract int(35/ Asteroid.Size) from Health This action does reduce the "Health" instance variable value according to the "Asteroid"'s "Size" value. If the "Player" collides with a big "Asteroid" ("Size" = 1) it will lose 35 health points. (35/1 = 35) If the "Player" collides with a small "Asteroid" ("Size" = 3) it will lose around 11 health points. (35/3 = 11.66...) The "Health" is then displayed by the "LifeBar Asteroid: Destroy The "Asteroid" is simply destroyed. Event 17, the "Player"'s "Health" is equal or less than 0. This is game over. This closes the Player Health! Wave System Explained Each wave "spawns" a limited numbers of asteroids. Once every asteroids have been destroyed, the wave is ended, we wait for an input (press return) from the player and we start the next wave. The background picture is changed every wave. This is where all the previous code and the setting of the "Asteroid" object type comes in play. Every "Asteroid" instances will have the same mechanism/game behavior. They will all react the same way to the events. And "all" that there is to do is to spawn a few of those object at the start of the wave, and the game will play as expected. The wave system is in the group "WaveSystem" in "esgame" (event 25) and makes use of the global variable "Wave" (defined in the same event sheet).
The event 28 is actually the event that checks if the wave has ended. Event 29 is useless as long as the wave hasn't ended. The conditions to check if the wave has ended are : Make sure that all the "Asteroid" have been destroyed Make sure that we are not at the start of the game, before the very first wave Make sure that "Player" is still alive That's exactly what the 3 conditions of the event 28 do. Breaking down the code (WaveSystem) System: Asteroid.count = 0 The system condition "Compare two values", first value being the common expression "Count" that returns the number of instances for the object. If the number of instances is 0, it means the player has destroyed all the asteroids. System: Wave > 0 The system condition "Compare global variable" checks the "Wave" global variable to see if it is greater than 0. 0 is the default value of the variable, and is used for the start of the game. As the wave number only increments when the player presses "Return" in between waves, on beginning of the game, the code doesn't consider the situation as an end of wave. Player: Health > 0 Checking if the instance variable "Health" of the "Player" (our ship) is greater than 0, alive. We don't want death of the player to be interpreted as wave ending. As soon as those 3 conditions are true, it is considered as the wave's end. System: Set group "PlayerMovement" deactivated txtwave: Set visible txtwave: Set text to "Wave number: " & Wave + 1 & newline & "Press ""Return"" to play" So once again "PlayerMovement" is deactivated, preventing the player from inputting any control, "txtwave" is displayed to carry the feedback to the user.
Player: Set custom movement Overall speed to 0 On end of the wave, the ship is stopped at once. This is done by neutralizing the "Custom movement" behavior "Speed" property. Brakes: Set invisible Thrust: Set invisible If the player was pressing forward or brake when the last "Asteroid" got destroyed, the according sprite(s) might still be displayed, even if the player releases the button (the "PlayerMovement" group is deactivated remember). So even if the sprites are not displayed, these actions makes sure the visuals are not displayed on wave end. Event 30 is the expected input of the player to start in a new wave. System: Set group "PlayerMovement" activated The controls to the player so that he can control the ship. txtwave: Set invisible Simply hiding the text object. The text will be modified either by the Wave System on next wave's end or by the Pausing System. System: Add 1 to Wave The global variable "Wave" gets incremented (1 is added to its current value). System: Add 0.2 to AsteroidMaxSpeed Each wave, the "general" speed of the "Asteroid" gets slightly raised. It modulates the challenge for the player. The value here is totally arbitrary and might be a strong subject to tweaking. You could even use "Wave" in this formula. ex: System: Add (Wave * 10)/100 to AsteroidMaxSpeed This formula would for example add 10% of the "Wave" value to AsteroidMaxSpeed. The first wave would then add 0.1 to the variable. The second wave would add 0.2. Etc... Sub event 32 is the spawning of new "Asteroid" instances. It is repeated "Wave times", meaning that each wave spawns as much "Asteroid" instance (wave 1 spawns one "Asteroid", wave 2 spawns 2, wave 3 spawns 3, etc...) The actions are pretty much the same as in the "AsteroidHandling" group when "Asteroid" was split into two new instances. (Event 22)
System: Create object "Asteroid" on layer 1 at ( random(windowwidth), int(choose( 90, 25,825, 890)) ) The "Asteroid" instances are not spawned from one another, they are created at a fixed position on screen, in the layer 1. They can appear randomly on all the screen's width (X), but will appear at either 90, 25, 825 or 890 (Y). int() Is still the system expression that allows to returns the content between parenthesis as an integer. choose() Is a system expression that will return one of the elements in between its parenthesis (separated by comas ","). In this action, it will return one of the four values as the spawning Y position for the "Asteroid". All four positions are out of the screen. I wanted the "Asteroid" instances to come from "outer space" to the closed world (wrap). Asteroid: Set Bullet angle of motion to angle(asteroid.x,asteroid.y,player.x,player.y) degrees Unlike the splitting, the first trajectory for the "Asteroid" will point directly to the player's ship. It forces the player to quickly react to the setting of the new wave and engage in a strategy/action. angle() Another system expression that returns the angle (in a 360 range) between the two sets of coordinates entered as parameter. The rest of the actions are the very same as the splitting method. This is the wave system.