2 - The asteroids
See the object type "Asteroid" which is a sprite with a texture from the free bundle. You can select the instance above the top-left corner of the layout in "Layout view".
"Asteroid" wraps like the ship does (Wrap behavior), it moves "automaticly" (Bullet behavior) and it rotates on itself (Rotate behavior).
In 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 I'll be able to use in the "sizing formula").
The default value is 1.
+ Breaking down the code
In "esGame" the group "AsteroidHandling" (event 16) 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 18 is empty (it has no condition) so will be executed every tick.
As it is a sub event to the event 17, it will only execute if the event 17 executes.
And the event 17 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 allows to 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 of the "Asteroid" in a local variable. (event 18)
This will allow 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 C2 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 19 is a sub event to event 17 and is "paired" with event 23.
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.
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 20 which is a "Repeat 2 times" sub event.
It will only execute if it's parent event is executed (event 19, 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
Edit: In the original capx and screenshots, the action was a "Asteroid: Spawn Asteroid", but stable release r103.2 (or earlier) broke it, so I have had to switch the action there to keep the game working as intended. The capx has been updated too.
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 maths 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:
Is a system expression that allows to make sure that the content between the parenthesis will be an integer.
Is a local variable we set earlier that contains the Bullet angle of motion of the parent "Asteroid" instance.
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. It seemed to achieve the effect I was visualising, so it's good enough for me.
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 event19/event23 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)
Notice "AsteroidMaxSpeed" is a global variable I've set up while tweaking to get the nicest global speed for the Asteroids in my opinion.
The speed is randomized but kept in a close range so the Asteroids will generally move at the same speed (modulated by 10 pixels per seconds).
As one of the aim here is to produce a full project in less than 100 events, if you were to need space for one more event, you could put solid values (40,50) in this action and delete the global variable.
I kept it in here to show this as a tip for when you are tweaking. It is faster to edit only this one value and hit "Preview".
Also sometime, if the value is to be used across several spots of the event sheet/project, it is a viable solution to keep the value in a single spot and refer to it through its variable name in the sheets.
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 C2 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 picks 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 18)
The action "Asteroid: destroy" will apply to this instance, and this instance only.
I won't talk about the sub event 22 for now, it is part of the Audio system explained later in this tutorial.
Event 23 is a "Else" condition that will occur when event 19 (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 I arbitrary 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.
As earlier, the sub event 24 is for the audio and discarded in the discussion for now.
This closes the "AsteroidHandling" group
Remember the game point list in the first pages of the tutorial ?
Asteroid clone - base Mechanism
..° Ship moving like in the original Asteroid game (gravity 0-like type of physics, the ship rotates on itself, it can go forward, brake, momentum is present, wraps from one side of the screen to the other; it also can shoot at asteroids), controlled by the player.
..° Asteroids are moving rocks on a straight trajectory, warping on the sides of the screen like the player's ship, each asteroid hit by a bullet splits into two smaller asteroids, or if it is already the "smallest" allowed, is destroyed. Splitting an asteroid score 1 point, destroying an asteroid scores 10.
..° If the ship collides with an asteroid, the ship loses health up to 0 where it is game over. The asteroid is just destroyed, no score added.
The last point is handled in the group "PlayerHandling" (event 12)
Event 13 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", but this matter will be discussed later in the tutorial.
The "Asteroid" is simply destroyed.
Event 15, the "Player"'s "Health" is equal or less than 0.
This is game over.
For now, all you need to know about it is that it sets all the values required in the game logic, and goes to the "Score" layout.
More on this later.
With this group "PlayerHandling" you can see that the last basic mechanic of game-play is implemented.
This closes the presentation of the base mechanism elements, the game element the players can interact with.