GML Functions
At this point, I’ve milked variable assignments as much as I can and now it’s time to move on
It’s function time!!
Restarting the game
Right now, you can “win”, but that just means that the ball goes off the screen, and you and your friend are just stuck waiting for the rest of eternity. There are more elegant ways to handle this, but for now let’s add a restart button as a quick fix
Just add a Key Press event for the letter R to oBall
(this can be any object, but we’re already in oBall
so why not). Then add the following code
game_restart();
Now you should be able to press R in game and the game will restart!
We’ve got a few things to break down here. First off, what the heck are those ()
?
Well, game_restart
is a function, and can always tell functions apart from variables by seeing if they have the ()
(sometimes there’s more things between the parens, but we’ll get to that later)
ahem 🤓, technically you can refer to functions without (), but () are required to actually “call” the function and make it do things. This distinction probably won’t matter to you until years down the line but for some reason I felt the need to make the clarification anyway
Here’s my working definition for functions:
function: a command that does one or more of the following
- Takes inputs
- Performs an action
- Gives back outputs
In our case game_restart
performs an action (it restarts the game), but it does so without taking inputs or giving back outputs
Here’s a general form for a function that does all 3:
output = function_name(input1, input2, input3)
We’ll see plenty of examples for this later, so don’t worry if it’s confusing
On a smaller note, why did I choose Key Press instead of one of the other key events? Think about it first, there’s not necessarily a right answer
In this case you could use any of the 3 w/o noticing much of a difference
Key Down would be a little weird. Remember key down triggers on every frame where the key is held down. This would be an odd choice, since we only need to restart the game once, but there’s no harm in multiple restarts I suppose
For Key Press vs Key Released, there’s an argument for both. Key Press will trigger faster, but sometimes I’ll use Key Released if I want to communicate what you’re interacting with before the action occurs.
For example, if I setup the spacebar to be a keyboard shortcut for a play button, I might use Key Released, and then also use Key Down to perform a short hover animation on the button. This way you can see which button you’re triggering before it triggers
In this case we’re not providing feedback anyway, so Key Press is probably ideal
Fixing a bug using abs()
After playing the game for a while you might notice a bug in the collision system. Specifically, if you move the paddle to hit the ball vertically
Our collision logic assumes that the event will only be triggered once, but in reality a collision in game maker is synonymous w/ “overlap”. So if it’s still overlapping on the next frame the event could continue to trigger long after the initial contact
In our example the flipping horizontal direction on vertical collisions isn’t enough un-collide the ball on the next frame. As a result the horizontal speed gets continuously flipped, and the ball stalls horizontally untile the collision/overlap ends
To fix this, I’ll update the logic to force collisions with oPaddle
and oEnemyPaddle
, to explicitly force the ball to move right and left respectively.
Here’s oBall’s collision event w/ oEnemyPaddle:
hspeed = abs(hspeed);
This uses abs()
which takes the existing hspeed
, and then outputs a positive version, Effectively forcing the ball to move to the right regardless of the current hspeed. Here’s some more formal abs()
documentation
abs(): Short for “absolute value”, it returns the positive version of the number
- Input: an input number
- Action: if the number is negative, multiply it by -1 (2 negatives make a positive 😉), otherwise leave it unchanged
- Ouptut: positive version of the number
Then we can use similar same logic to force left movement when the ball hits the player’s paddle
Here’s oBall’s collision event w/ oPaddle:
hspeed = -abs(hspeed);
When testing it out, this edge case should be fixed
Randomize ball speed (featuring choose())
So far the ball has been really predictable, it just starts off in the same diagonal direction regardless of how many times you restart
Let’s add some randomness to the start direction, and get some more function practice in while we’re add it. Here’s the new create event for oBall
:
hspeed = choose(-4, 4);
vspeed = choose(-4, 4);
Introducing the choose()
function! This takes in a series of inputs, randomly “chooses” one, and then gives it back. So our hspeed and vspeed can both be either -4 or 4
Here’s a more formal definition:
choose(): randomly chooses from the inputs
- Input: 1 or more parameters representing the choices
- Action: Randomly chooses among the input parameters
- Output: The chosen parameter
Also note how we’re using variable assignment and functions together in the same line. We’re taking the output from choose, and then using the assignment operator to direct that output toward one of our variables
Now here’s what it looks like when we press restart (i.e. the R key)
But it seems a little odd that it only goes diagonal. We certainly don’t want it to go vertical (that would be a VERY long wait), but maybe horizontal? Why don’t you give it a try?
Moving horizontally, means our vertical speed is zero. So adding 0 to the vspeed
choices should do the trick
hspeed = choose(-2, 2);
vspeed = choose(-2, 0, 2);
On second thought, I don’t like this very much, it’s just … boring 😴 (but good job on figuring it out 😉 )
Reset the ball
The restart button is nice, but as I mentioned earlier it’s certainly not the best way to go about reseting the ball. A better approach would be to have the ball reset on it’s own once it leaves the room
There are lots of ways to do this, but since we’re on function streak, let’s run with that
Add the following code in the Outside Room event for oBall
:
instance_create_layer(xstart, ystart, layer, oBall);
instance_destroy();
woah, new event, new variables AND new functions 😨. Take a deep breath, let’s break it down
First, here’s the English translation
When I (meaning me the ball), go outside the room:
Create a new oBall instance using my layer at the position where I originally spawned (meaning the center(ish) of the screen)
Self destruct (it's that or wander in the endless space outside the room forever)
For the event, Outside Room should be pretty self explanatory. It’ll be triggered once the x
/y
value of the instance goes out the dimensions of the room
Here are the new variables:
xstart
: the x position where the instance was first created
ystart
: (you guessed it 😉) the y position where the instance was first created
layer
: represents the current layer of the object (so far that’s always been the “Instances” layer, but we can just refer to the layer variable in case that changes)
Fun Fact: I guess
oBall
is technically a variable? But you should think of it as a asset. And as a fun fact, you can refert to any of the assets in the asset browser in code. Here we usedoBall
, but if you type out any of the other ones (likesPaddle
?) they should all be highlighted the same
and here’s the functions:
instance_create_layer()
: Creates an instance at a specified layer / position
- Input: x/y/layer for the new instance, as well as the object you want to create an instance of
- Action: create an instance using the specified parameters as properties for the instance
- Output: the id of the new instance (but you don’t need to worry about this, we’re not using it)
instance_destroy()
: Destroys the current instance, no more events, it’s over
- Input: nothing
- Action: Destroys the current instance (ahem 🤓, technically it won’t get destroyed until the current frame is completed. I guarantee you’ll forget this and then relearn it the hard way when you hit a weird bug, but I figured I’d mention it anyway)
- Ouptut: nadda
Testing this out should work. When a ball leaves the room, it will destroy it’s self and a new ball will show up at the start allowing the game to continue
But that’s kind of morbid don’t you think? Does it have to die? Is there a way we can reuse the same ball?
Yes there is, and it’s actually a lot simpler too. Can you think of it?
Yep, just use the following code in the outside room event
x = xstart;
y = ystart;
hspeed = choose(-2, 2);
vspeed = choose(-2, 2);
This just moves the existing ball back to it’s starting position, and then it picks a random direction again
I’m not sure which approach I’d recommend. I prefer the reset position approach, but the problem is you’ll need to rerun all of your create event code from the outside room event. This is fine for now, but it’ll get annoying as we continue to make updates to the create event
I have a way around this problem, but you’re not ready to learn that yet. So for you I recommend going with the instance create/destroy approach, and this is the approach I’ll continue to use in the course. We can talk about the other approach more when we get there 😊
But that said, this highlights the creative side of programming. You only have limited tools, and there’s always multiple ways to mix and match to achieve your goals
Preview function names and inputs
Now that our function names are getting longer w/ more inputs, it’ll probably get hard to remember the exact names and inputs
Game Maker has some tricks to help us with this, for example as you’re typing a function name it’ll try to predict which function you’re looking for (this should happen automatically, but if not you can hit ctrl + space
). This is really helpful if you only remember part of the function name
Then if you see the one you’re looking for, you can just click on it (or use the arrow keys to navigate down to the one you’re want and then press enter)
Also once you’ve entered the function, you can put your cursor between the parenthesis preview the input order