Aitutorial
Aitutorial
Introduction
In this Tutorial we will be building Enemy Ai for a 2D platformer using Game Maker. Our Enemy will Patrol an area, Follow the player and return to his patrol Area when he loses sight of the player. We will then learn how to use this enemy object as a parent for different types of enemy. This is an intermediate level tutorial, it will be assumed that you are already familiar with Game Maker's interface ,that you know how to create objects, events and that you have used GML (Game Maker Language) before. If you get stuck check out http://wiki.yoyogames.com/index.php/My_First_ Game for help with the basics and don't forget the help files.
GM:Studio GM8.1
www.dropbox.com/s/lfzf1vpdvgg8cmf/aiTutStudio.zip
www.dropbox.com/s/4bckqyh086za80z/aiTut8.1.zip
Movement
Before we can get any of our planned behaviours working we need movement. Open up parEnemy and add a Create event. In the drag and drop menu click the control tab and drag in an execute code node. These variables will control the player movement. The boolean eneDir will be used to control the enemy's direction. eneSpeed will control the speed. Failing to initialize variables may result in an error when running the game.
Create
eneDir = 1; eneSpeed = 1;
image_xscale is a variable that will set the scale of an images width. Using a negative value will flip the image on its origin. If you are using your own sprites make sure the origin is set in the center along x.
Add a step event. Drag in an execute code node and add the following code. To go left we subtract player's horizontal speed (hsp) to a negative value
AI Tutorial
When eneDir is set to 0, hsp will be set to -eneSpeed. The Values are reversed when eneDir is 1. hsp is then used to set the enemies x value which will cause it to move. eneDir = 0
Step
//left if eneDir = 0 { hsp = -eneSpeed ; image_xscale = -1 ; } //right if eneDir = 1 { hsp = eneSpeed ; image_xscale = 1 ; } //input movement x += hsp ;
If you place the enemy in the test room and run the game you can see it move to right. If you set the variable eneDir to 0 it will move to the left instead. We got the enemy to move but he will go straight through the walls, off the screen and does not fall down if he's placed off the ground. In order to create gravity Go back to the Create Event and add the following variables. vsp will represent the Vertical speed, grav is gravity and grounded will be used to check if the player is currently touching the ground or not.
Create
place_meeting(x,y, obj) Checks for a collision between two instances at the coordinate x,y.
In the step event, the value for gravity will be added to the vsp every step causing the enemy to move down. Without collision the enemy would fall forever. The Vertical Collision statement will cause the enemy to collide with objSolid. The while statement moves the enemy a pixel down every step until the two objects are touching. This will avoid problems where the enemy gets stuck in the floor. As you add more code make sure that the code under //input movement is at the very bottom.
AI Tutorial
Step
//gravity vsp += grav; //vertical Collision / landing if place_meeting(x,y+vsp,objSolid) { while !place_meeting(x,y+1,objSolid) y += 1 ; vsp = 0 ; grounded = true ; } else { grounded = false } //input movement y += vsp ;
place_meeting(x,y+vsp,objSolid)
!place_meeting(x,y+1,objSolid)
pixel
vsp
y += 1
The next step is to get the enemy to jump over small obstacles and turning a round at obstacles that are too high. These statements will check what is in front of the enemy allowing us to code in reactions. Now you'll be able to see how using eneDir allows us to change the enemies direction. The variable grid is used to represent the grid size just in case it has to be changed later on.
Create
sign checks if a number if positive of negative, returning 1 for positive numbers and -1 for negative numbers.
AI Tutorial
Step
//low obstacle if place_meeting(x+hsp,y,objSolid) { eneJump = true; alarm[0] = 5; while !place_meeting(x+sign(hsp),y,objSolid) x += sign(hsp); hsp = 0 ; } // turn around at tall object if eneDir = 0 && place_meeting(x-8,y,objSolid) && place_meeting(x-8,y-grid,objSolid) { eneDir = 1 ; } if eneDir = 1 && place_meeting(x+8,y,objSolid) && place_meeting(x+8,y-grid,objSolid) { eneDir = 0 ; }
place_meeting(x+hsp,y,objSolid)
!place_meeting(x+sign(hsp),y,objSolid)
hsp
hsp
hsp = 0 x += sign(hsp)
If you run the game now you will notice the player will not stop jumping after leaping over the first obstacle. This is because we have not yet created the alarm event called in our low obstacle statement. Add an alarm event to parEnemy and enter the following line of code.
Alarm 0
enejump = false;
Our alarm will be executed 5 steps after it is triggered allowing for the enemy to jump onto the obstacle before eneJump is set to false again. Now that our movement system is complete we can start working towards the behaviours we discussed in the beginning of the tutorial.
AI Tutorial
Patrol
Patrol is the easiest part. We will be setting boundaries around the enemys spawn point and restricting the enemy to walk between those 2 points. The Create code sets up the patrol area. To make the area bigger just increase the values of lB and rB.
Create
lB = grid*2; rB = grid*1.5; //Distance from spawn point leftBound = x - lB; rightBound = x + rB; patrol = true;
Step
Patrol area
x 0 eneDir = 1 eneDir = 0
That is it for the patrol code, if you run the game you'll see the enemy going back and forth around his spawn point.
AI Tutorial
Sight
To get the enemy to sight the player.
Step
// if if if if
blocked = collision_line(x,y,objPlayer.x,objPlayer.y,objSolid,0,1) movement in eneDir == 0 eneDir == 0 eneDir == 1 eneDir == 1 relation to the player && objPlayer.x < x { towards && objPlayer.x > x { towards && objPlayer.x > x { towards && objPlayer.x < x { towards = = = = true } false} true } false}
//is player visible if blocked < 0 && towards { patrol = false ; follow = true ; } if blocked > 0 { follow = false; patrol = true ; }
The collision line drawn between the player and the enemy will determine if there are any objects between them. By comparing which way the enemy is walking against the Player's position in relation to the enemy we can determine if the enemy is facing the player on not. This will allow the player to walk behind the enemy without being spotted. x2,y2 x1,y1
collision_line(x1,y1,x2,y2,obj ,prec,notMe) creates a line between the coordinates given and returns the id of any instance of obj intersecting the line. Returns -1 when there are no collisions.
x 0 towards = true;
x 0 towards = false;
If you run the game and hop in front of the enemy you'll see he'll leave his patrol state and resume his patrol state when the player is hidden again. The enemy will walk the whole length of the room.
Follow
The following code will have the enemy set his direction based on were the player is every 20 to 30 steps (30 steps = 1 second). I used random_range as opposed to putting in one value so that the enemy would not always take the same amount of time to turn around. Making these values higher means the enemy will take longer to change direction when the player changes direction. objPlayer.x < x
Create
checkPos = true; //Reaction Time rL = 25; //range low rH = 30; //range high
objPlayer.x > x
x 0 eneDir = 0
x 0 eneDir = 1
AI Tutorial
Step
//Follow if follow { //reaction time if checkPos { if objPlayer.x < x + grid { eneDir = 0 } if objPlayer.x > x - grid { eneDir = 1 } checkPos = false; alarm[1] = random_range(rL,rH); } }
Alarm 1
checkPos = true;
random_range(x1,x2) will pick a random float value between the values x1, and x2.
Speed
This pretty much completes the parent object for our enemy AI but first I would like to have the enemy change his speed when in different states.
Create
dSpeed = 1.5;
Step
We will use this new variable to alter the player speed. Place the new code within the existing statements for Follow and Patrol.
AI Tutorial
Attack
Now that our Parent enemy is set up we'll look at how we can build on or alter this code using child objects. First however we will go back to the Create event in parEnemy and organize our variables. I put the variables that can be changed to simulate different enemy types at the top. Duplicate parEnemy (Alt + Insert). We will use this new object to set up an enemy with a ranged attack so I renamed it objEnemyRng. Delete all the events in this new object except the Create event. We are keeping this event as we'll need to add more variables to this object. Set parEnemy as the parent. Next we added a Begin Step event. We didn't use a step event like we did in parEnemy because that would completely replace the code as opposed to adding to it.
Create
grid= 32; /// Customize /// //distance from spawn point in Patrol lB = grid*2 ; rB = grid*1.5; //Speed dSpeed = 1.5; //jump speed leap = 9; //weight grav = 0.5; //Reaction Time when following player rL = 25; //range low rH = 30; //range high //Initial Direction eneDir = 1; 0 = left; 1 = right
////////////////////////// //movement hsp = 0; vsp = 0; grounded = true; eneJump = false ; eneSpeed = defaultSpeed; //Distance from spawn point spawnPoint = x ; leftBound = x - lB; rightBound = x + rB; //States patrol = true; follow = false; checkPos = true;
AI Tutorial
Create
/// Customize /// //time between shots t1 = 20; t2 = 30; //Max distance to attack from triggerDis = grid*5; //States shoot = true;
Alarm 2
shoot = true;
instance_create( x,y,obj) creates an instance of the specified object at the coordinate x,y.
Begin Step
if distance_to_object(objPlayer) < triggerDis && follow && shoot { instance_create(x,y,parPro); // space out shots shoot = false; alarm[2] = random_range(t1,t2);
shoot = true
Open the test room and replace parEnemy with our newly created enemy. If you test the game now you should see the enemy will now start creating projectiles when the Follow state is triggered.
AI Tutorial
10
Open parPro and add the following code. This code will set the projectile's direction based on the eneDir variable of the enemy that fired it.
Create
enemy = instance_place(x,y,objEnemyRng) //Direction if enemy.eneDir = 1 { hspeed = 10; gravity = 0.02} if enemy.eneDir = 0 { hspeed = -10; gravity = 0.02}
eneDir = 1
eneDir = 0 instance_place(x ,y,obj) Returns the instance id of the instance of obj at x,y.
hspeed Variable built in all objects, Controls horizontal speed. Its value is added to the objects x value every step.
gravity Variable built in all objects, Its value is added to the objects vspeed every step.
If you would like your projectile to work on different types of objects you can create a new object, objPawn and set it as the parent of objPlayer and any other objects youd like to effect. This will allow you to check for all objects at once instead of having to create separate statements.
place_meeting(x,y,objPawn)
AI Tutorial
11
Step
//remove when pawn is hit if place_meeting(x,y,objPawn) { pawnHit = instance_place(x,y,objPawn); instance_destroy(); pawnHit.hp -= damage;
//remove when wall is hit / outside the room if place_meeting(x,y,objSolid) or x > room_width or x < 0 { instance_destroy();
Challenges
Use child objects to create your own projectiles with different damage amounts and speeds. Change the range of your projectile to simulate melee attacks. Set up an attack for the player.
AI Tutorial
12