Enemy AI
We have if statements figured out, let’s use this to add some basic AI 😁
Boolean data type
So we want the enemy to have AI support, but I still want it to have an “on/off switch”, so I can go back to 2 player mode if we want
So we’ll make a variable is_ai
to represent whether we’re using AI, but what data should we put into it? So far we’ve worked with numbers and strings, but neither of those seem quite right
Introducing boolean types! Boolean means there’s only 2 states, and we can represent it by just typing true
or false
, so let’s write our variable as follows
// oEnemyPaddle Create Event
is_ai = true;
Now that we’ve started to accumulate more data types, let’s look at them together in a table
data type | example | definition |
---|---|---|
real | 6.28 | any number (whole or decimal) |
string | "Hello World!" | any text, it must always be surrounded with "" |
boolean | false | either true or false, works great when there are only 2 options |
These are pretty much all the basic data types used in Game Maker. This is much less than other languages, for example you need to learn 8 primitive data types when using Java 😨
🤓 I listed 3 data types for Game Maker, but “technically” Game Maker only has 2. false and true are just built in real variables that are set to 0 and 1 accordingly. When game maker is trying to evaluate whether a condition is true or false, it’s just checking if it’s more or less than .5. That let’s you do something like
if(5){ /* do something */ }
BUT, it’s super confusing, so we’re better off just pretending that true and false aren’t numbers
Disabling keyboard events
We’ll start by disabling the keyboard events. In oEnemyPaddle
we can update the keyboard events as follows
// oEnemyPaddle Keyboard W Down Event
if(is_ai == false){
y = y - 4;
}
// oEnemyPaddle Keyboard S Down Event
if(is_ai == false){
y = y + 4;
}
Here we’re just surrounding the existing logic with if(is_ai == false){ ... }
. This will effectively disable those events whenever is_ai is set to true (meaning that is_ai == false
would be a false statement)
Now if you test it, we should be able to move the player paddle but not the enemy paddle
Adding AI
Now we can add the AI to the step event. The idea is the paddle should move up if the ball is above, and it should move down if the ball is below
// oEnemyPaddle Step Event
if(is_ai == true){
if(y > oBall.y){ // paddle is below the ball
y -= 6; // move up
}else{
y += 6; // move down
}
}
This is our first time using the step event 😮. The step event just triggers every frame, hopefully this isn’t too ground breaking since we’ve been working with a lot of different events lately. Step event is probably the most popular event, when I’m working on my games, the step event usually has the most code in it
Here we have nested if statements, meaning multiple if statements inside each other. Since the inner if statement (i.e. if(y > oBall.y ){...}
) is part of the code block for the outer if statement (i.e. if(is_ai == true){...}
), the inner if statement will only be checked if the outer if statement passes. It’s still just following the rules that we learned when first covering if statements, but reading code with nested if statements still takes some getting used to
technically 🤓 you never actually need to use comparison operators with boolean datatypes. The if statements conditions are just statements that evaluate to
true
orfalse
. Soif(is_ai == true)...
works, butis_ai
on it’s own is already a condition that evaluates totrue
orfalse
, soif(is_ai)...
is equally valid, while also being a tad cleaner. Anyway, take your time getting comfortable withis_ai == true
first, but eventually I’ll start trying to convert you to just useis_ai
Now when we test it out, it mostly works. It probably just looks a bit weird. The enemy paddle is trying to align the top of the paddle, with the ball, but really we’d expect it to align the center with the ball 🤔
Adding an offset
I mentioned earlier that x/y refers to the top left corner of the sprite. So in this case that means that y > oBall.y
is really saying, is my top left corner greater than the ball's top left corner
. We’ll learn to fix this later by adjusting the sprite offset, but I want you to be comfortable solving it the adhoc way first, so we can do that
// oEnemyPaddle Step Event
if(is_ai == true){
if( y + 64 > oBall.y){ // ball is below the paddle's center
y -= 6; // move up
}else{
y += 6; // move down
}
}
This is just the exact same logic except I’ve added the + 64
So if y
corresponds with the top of the paddle, and the paddle’s sprite is 128 pixels tall, that means y + 64
corresponds to the center for he paddle. Using this knowledge we can just substitute y
with y + 64
and it should have a much better effect
But I’ve got another challenge for you, right now the logic is is my center below the ball's top
, but to makes this even smoother we could update this to is my center below the ball's center
, how would you update that?
The ball’s sprite is 16 pixels tall, so the ball’s center is oBall.y + 8
// oEnemyPaddle Step Event
if(is_ai == true){
if( y + 64 > oBall.y + 8){ // ball is below the paddle's center
y -= 6; // move up
}else{
y += 6; // move down
}
}
technically 🤓, as a former Amazon software engineer, if I was reviewing this code I’d complain about magic numbers. When working in teams, it’s important that code is intuitive, and if someone’s not familiar with your code they’d be confused at all the random numbers (64? 8? 6? what do all these mean 😭). To avoid this we can make better use of variables. We could store 6 in an
ai_speed
variable, and maybe dosprite_height/2
instead of64
(you don’t know that variable yet, but we’ll use it more soon). That way it’s clear that the logic represents half the sprite’s height. Similarly we can dooBall.sprite_height/2
instead of8
. We’ll get to all this later, I’m just giving you a little taste 😉