B - InGame systems
+ Behaviors used : None; Plugins used : System, Sprite, Keyboard, Text, Textbox, Array, Local Storage, Audio, Function, TiledBackground.
What I call "InGame systems" here are systems that will work around and in the game's logic.
It encompasses: the pausing system, the wave system, the score system and the audio system.
1 - Pausing system
The pausing system is not in a group. It's a "simple" top event, when the player presses "P", the process of pausing/unpausing kicks in.
Checking that the "Asteroid" count is over 0 helps making sure that the game has already started (and that we're not just expecting the player to press return on start or between waves).
More on this in the next chapter (Wave system).
Just notice that the conditions surrounding the player's input (the triggered condition "On key pressed") allow to determine if the game is in the correct circumstances to execute the player's intent (here, is it a viable time in the game to pause ?).
Pausing/unpausing is passing between two different states.
A simple way to pause a game is to set the "timescale" system expression value to 0. (like in event 38)
Any behavior, plugin, formula using the system expression dt (see the Delta-time and frame-rate independence tutorial) will then be paused in its execution as long as "timescale" is equal to 0.
Event 37 checks if the game is currently paused (if so, it modifies the values so that the game goes back into "playing" mode).
Event 38 will execute only if the previous event (is the game paused ? event 37) hasn't.
It then means that the game is playing, and that currently the player wants to pause it.
In this current project "timescale" does the job as mostly built-in behaviors and plugins are used.
The "regular" "timescale" value is 1.0. At 1.0 everything executes at "normal" speed. This is the way to unpause the game.
So "timescale" stops the execution/animations/movements of the game's object, but the player can still interact, still can input commands to the game.
So Event 38, we deactivate the group "PlayerMovement" (remember, it is the group in which we have put all our events concerning the player's control of the ship), shutting down the capacity for the player to input commands, other than the one currently expected and treated outside of this group: "Press "Return"".
Event 37 (unpausing) of course the group is activated, otherwise the player couldn't play the game as intended.
It's also nice to display feedback to the player, and a "Pausing" state needs to be clearly understood or the player might think the game has bugged.
As I implemented the pausing system quite late in my development (it's part of the reason why it is the last in the event-sheet and not in a group), I chose to reuse another GUI element that was already there.
In the layout game, you can see the text object "txtWave" which is a simple text object I use to give feedback to the player in between the waves (more on this in the "Wave System").
So in event 38 (pausing the game) we set "txtWave" to be visible, and set its text so it notifies the player that the game is paused and that pressing "P" again will unpause.
In event 37 (unpausing the game) we only need to set it invisible. Its text will be modified by either the wave system or the next pausing anyway.
The reuse here works because this object has about the same function in the Wave System (just being switched between visible and invisible and displaying some text in between the waves) and is correctly positioned for the job.
As I said, I implemented the Pausing system pretty late in my development but I thought it was better to begin with it in the tutorial.
It's a quick 3 events (1 top, 2 sub events) function that is pretty used/useful.
Be aware though that it strongly depends on the way you implemented your game so far.
As I had "custom inputs", in a group of events, using only timescale = 0 was not enough.
When you try to do your own pausing process, make sure that it covers all the game as you made it.
2 - Wave system
The game points list in the first pages stated:
..° 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 26 is actually the event that checks if the wave has ended.
Event 27 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
° Make sure that the game is not paused
That's exactly what the 4 conditions of the event 26 do.
+ Breaking down the code
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.
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.
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.
System: timescale not = 0
Again the system condition "Compare two values", the first value being the system expression "timescale".
This condition makes sure the game is not paused.
We already saw this expression in the Pausing System.
As soon as those 4 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"
From there, you might recognize actions already used in the Pausing System, as here the idea is to "pause" the action until the player validates the beginning of a new wave by pressing "Return".
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.
Sub event 27 is concerning Audio and will be treated in the Audio System.
Event 28 is the expected input of the player to start in a new wave.
Like in the pausing system, the trigger is "tempered" by conditions to make sure the situation is in between two waves, and not playing.
The very same conditions as in event 26 can be found to determine if the wave is ended ("Player" is alive, game is not paused) and a check on the state of the group "PlayerMovement".
If it is deactivated (by event 26) then it is the wave's end, so this is the expected input to start a new wave, the actions can be executed.
System: Set group "PlayerMovement" activated
The controls to the player so that he can control the ship.
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.
The global variable "Wave" gets incremented (1 is added to its current value).
Background: Set animation frame to wave%Background.AnimationFrameCount
A little trick here with the "modulo" to keep in the boundaries of available frames in the object "Background".
The "Wave" global variable keeps getting incremented but "tempered" by the "modulo" to never go over "Background.AnimationFrameCount", the number of frames in the current animation (3 in this case).
This makes it so that each new wave, the background is changed and rotated between the 3 available frames.
System: Add 0.1 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 29 is there for audio and will left for later.
Sub event 30 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 20)
As subtle shades though :
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).
Is still the system expression that allows to returns the content between parenthesis as an integer.
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.
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.
It increments exponentially as the player goes through waves of asteroids.
If you wanted to add something special depending on the waves, you could add a sub event to the event 28, checking for the value of the global variable "Wave".
I might write another tutorial on this.