Enemy AI in 2D Platformer

S

SilentProtagonist

Guest
Hello everyone! Newbie here.

So I'm developing a single-screen 2D platformer (think Balloon Fight on NES) and I'm trying to program flying enemies to chase the player around the screen. I've tried a couple of different options, but nothing's working quite right. So I erased any new code and brought them back to square one. Right now all they can do it move to the right (on the ground or in the air depending on if I gave them any gravity).

So...any pointers?
 
It's not really clear from your question what the difficulty is. Do you have a clear idea of what you want the enemy to do, but struggle to implement this behavior, or do you need ideas for better a chase behavior? Is collision handling part of the problem?

Anyway, here are some basic movement ideas...

The simplest kind of chase is just moving in a straight line towards the player:
GML:
// Built-in speed
direction = point_direction(x,y, oPlayer.x, oPlayer.y);
speed = 5;

// Or custom movement handling
var dir = point_direction(x,y, oPlayer.x, oPlayer.y);
var spd = 5;
x += lengthdir_x(dir, spd);
y += lengthdir_y(dir, spd);
Or chasing along both x and y separately, here with acceleration and limited speed:
GML:
if (y < oPlayer.y)
    vspeed = min(10, vspeed+4);
else
    vspeed = max(-10, vspeed-4);

if (x < oPlayer.x)
    hspeed = min(10, hspeed+4);
else
    hspeed = max(-10, hspeed-4);
Or follow the player with a randomly flipping directional offset for circular motion:
GML:
// Create event:
dir_offset_sign = choose(-1, 1);

// Step event:
if (random(30) < 1) // Chance of 1 in 30 every step to change direction
    dir_offset_sign = choose(-1, 1);

direction = point_direction(x,y, oPlayer.x, oPlayer.y) + 80*dir_offset_sign;
speed = 5;
 
S

SilentProtagonist

Guest
Sorry if my question wasn't very clear. I've always been bad at explaining things '-_-

I try and draw a clearer picture, the room I have set up is a single-screen image with a floor along the bottom, an off-screen ceiling up top, and a few floating platforms. I added room wrap on the left and right sides of the room. What I'm trying to do is create enemies that can fly around the stage and pursue the player without passing through solid objects like platforms. Right now, all my enemies can do is move to the right.

For reference, here's all the code the enemies have right now:

CREATE
vsp = 0;
grv = 0;
walksp = 3;
hsp = walksp;
vsp = walksp;

hp = 4;
flash = 0;
hitfrom = 0;


STEP
vsp = vsp + grv;

//Horizontal Collision
if (place_meeting(x+hsp,y,obj_wall))
{
while (!place_meeting(x+sign(hsp),y,obj_wall))
{
x = x + sign(hsp);
}
hsp = -hsp;
}
x = x + hsp;

//Vertical Collision
if (place_meeting(x,y+vsp,obj_wall))
{
while (!place_meeting(x,y+sign(vsp),obj_wall))
{
y = y + sign(vsp);
}
vsp = 0;
}
y = y + vsp;

///Room Wrap
move_wrap(true, false, sprite_width/2);


BEGIN STEP
if (hp <= 0)
{
with (instance_create_layer(x,y,layer,obj_dead))
{
direction = other.hitfrom;
hsp = lengthdir_x(3,direction);
vsp = lengthdir_y(3,direction)-2;
if (sign(hsp) != 0) image_xscale = sign(hsp);

}

instance_destroy();
}


DRAW
draw_self();

if (flash > 0)
{
flash--;
shader_set(sha_white);
draw_self();
shader_reset();
}
 

samspade

Member
Here is one very basic way to do it (only partial code provided):

GML:
var x_sign = sign(target.x - x);                                     
var y_sign = sign(target.y - y);                                     
if (x_sign > 0) {                                                   
    if (hsp < moving_speed) hsp += accelleration;                             
} else {                                                             
    if (hsp > -moving_speed) hsp += -accelleration;                           
}
if (y_sign > 0) {                                                   
    if (vsp < moving_speed) vsp += accelleration;
} else {
    if (vsp > -moving_speed) vsp += -accelleration;
}
Target is an instance id. You'd want to find some way to safely get that (i.e. so you're not crashing by referencing an invalid instance) and then you're simply determining whether or not the target is to the left or right and up or down by getting the sign (positive, negative, or 0) of the difference between the enemies location and the target's location, and then accelerating in that direction up to a max speed. It'll work with your current collision code as well.

Also, shamless self plug - I just started a series on these ideas. I'll eventually get to programming exactly these types of enemies (not there yet though):

 
S

SilentProtagonist

Guest
Thanks! It's not quite there, but I'm getting closer.
They're following the player now, but they can't navigate around solid objects. (And the sprites are a little wobbly for some reason)
Anything else I should try?
 

TheouAegis

Member
Thanks! It's not quite there, but I'm getting closer.
They're following the player now, but they can't navigate around solid objects. (And the sprites are a little wobbly for some reason)
Anything else I should try?
I was going to be nice and give a breakdown of the Balloon Fight code, because I haven't played Balloon Fight except once on the NES Megamix games. I took a look at the code, identified a handful of variables, then decided to postpone it for a later date because the coding style is a little too different from what I'm used to. It's fun code to glance through, though. So I'll just go over a couple of the basics.

Screen Wrapping
This is pretty basic, and you said you already have it, but I'll go over it briefly. The simplest way is to use the Outside Room event, although that has issues when moving diagonally or changing directions at the moment of wrapping. It also relies on internal mechanics, so I would personally say don't use the built-in events. The most common alternative is to just check when x is equal to 0 or equal to room_width, then set the coordinates respectively. A better method is to check if x is less than 0 or greater than room_width, then either add or subtract room_width. A "better yet" method, and I put it in quotes because it does cause a minuscule speed difference, is to just add the room_width to x every step and then get the modulo.
Code:
x = (x + room_width) mod room_width;
This method does the same thing as adding or subtracting room_width, but removes the conditionals. If you don't add room_width first, then the range of coordinates generated by GameMaker is -room_width to +room_width. If you add room_width first, the range of coordinates is 0 to +room_width, which should be consistent across all platforms.

Target Finding
As samspade already touched upon, the formula target.x-x can be used to find the vector toward the target. Conversely, the formula x-target.x can be used to find the vector away from the target. You can use abs(target.x-x) to find the distance to the target, regardless of checking toward or away from the target. Subsequently, by using sign(target.x-x), you can find the direction toward the target. Furthermore, using sign(x-target.x), you can find the direction away from the target. In this case, -1 is to the left and +1 is to the right. Sometimes target.x will be equal to x, resulting in a vector of 0. A lot of people use the direction toward the target to set image_xscale, so if the vector is 0, the sprite is going to disappear. You can prevent this by prioritizing either direction by adding |1 to the formula.
Code:
image_xscale = sign(target.x - x | 1);
What the |1 does is set the parity of the result to odd. This means the result can never be 0, because 0 is not an odd number. This will prioritize facing right, which sometimes isn't ideal, so many people just simply ignore the vector if it is 0.

Target Finding With Screen Wrapping
The biggest issue with using target.x-x to find the vector toward the target is the direction of the vector can only be +1 if the target is to the right and -1 if the target is to the left. When you have screen wrapping, you might want the direction of the vector to be -1 if the target is too far to the right. One way to think of the solution is, since you can wrap instance positions around the screen, you can also wrap the distances between those instance positions around the screen. Just combine the two points above to determine which vector - to the left or to the right - is shorter and prioritize that one.
Code:
var A = (target.x - x);
var B = (x - target.x);
if (A + room_width) mod room_width) < (B + room_width) mod room_width
    image_xscale = sign(A);
else
    image_xscale = sign(B);
 

Bentley

Member
I was going to be nice and give a breakdown of the Balloon Fight code
Hey, I know this is an older thread, but I was curious about Balloon Fight enemies as well. Do you still know how the original code worked, and if so, would you mind sharing?
 
Top