Pac-Man Movements

Wait, are you saying it shows "0#0"all the time ? Because that first number should at least be changing, otherwise Pac-Man wouldn't be moving at all and you said Pac-Man was able to move and change directions, just not easily.. You put the code inside the Pac-Man object's Draw GUI event?

are you still using the Pac-Man object or are you using the test object on accident again?
I think I'm using the test object
 
Wait, are you saying it shows "0#0"all the time ? Because that first number should at least be changing, otherwise Pac-Man wouldn't be moving at all and you said Pac-Man was able to move and change directions, just not easily.. You put the code inside the Pac-Man object's Draw GUI event?

are you still using the Pac-Man object or are you using the test object on accident again?
OK so turns out i didn't have the GUI in neither Pac or the test but as an object of it's own so that was a derp moment. As for the proper use for it, it says that the numbers on the right does change but it changes right back to be identical to the numbers on the left. So in a way it is saving the key press but only for a split second.
 

TheouAegis

Member
Well, I guess you could try replacing
Code:
new_dir = direction;
with empty brackets:
{ }

Worst case, it should give the player an unlimited amount of time to change direction.
 

TheouAegis

Member
OK so turns out i didn't have the GUI in neither Pac or the test but as an object of it's own so that was a derp moment. As for the proper use for it, it says that the numbers on the right does change but it changes right back to be identical to the numbers on the left. So in a way it is saving the key press but only for a split second.
Okay, go back to using the normal code, not the one I posted last. Use the one that sets new_dir = direction, since that line of code was correct. What you need to change is all the keyboard_check_pressed() calls into plain old keyboard_check() calls.

The original code was intended to only make Pac-Man change directions when within a single tile's length away from a junction. That was fine and dandy, except the player has 2/15 of a second to react. lol So what you needed was a margin of error. The simplest way to do that was to let the player hold a direction indefinitely.

However, there is one significant drawback to this: multiple key presses. In the code here, holding left and up would make Pac-Man only look for an upward path, ignoring any paths to the left. This is because of the else in the code. If you removed the else, then Pac-Man would only look for the leftward path, ignoring any paths up. I mean, in either case that's the player's fault, but it's something to be aware of. That problem would rarely arise using _pressed, but as we've seen it causes other problems of its own.
 
OK so what I wanna do now is have the AI Pac-Man and I think I have the concept of it but I wanna make sure it eats all available Pac-dots and Power Pellets. Any pointers or ideas?
 

TheouAegis

Member
An AI for Pac-man? Ooh, that's tricky. If you read my posts about Ghost AI, it would be similar. You pick a target (probably instance_nearest(x,y,Pellet)'s x and y coordinates to find the nearest pellet) and save the coordinates in variables. Then have Pac-Man pick the paths that would get him to the pellets every time he reaches a junction (see the GhostAI posts about that part). But the catch is now you need to also avoid Ghosts, so if the path to the nearest pellet leads to a Ghost, you wouldn't want to take it. So like I said, it's very tricky.
 
You mean this line of codes right here?

Yeah, I've seen some of the old Pacman GM8 games and wasn't a fan of their codes, even before I looked into how Pacman was made. lol

Ghosts only change direction at intersections, or every 8 pixels (the size of a tile) originally. There is no real collision detection in the ghosts ever, subsequently. Since Neither ghosts nor Pacman can move diagonally, you can tell if they are at an intersection using the conditional
if !((x + y) mod tile_size)
Replacing tile_size with the size of your tiles. Or if your tile size is a power of 2 (e.g.,2,4,8,16,32,64), you can also use
if !(x + y & tile_size)
The (x+y) part changes if your ghosts aren't aligned to the grid quite right. For example, the original code was if !(((x-4)+(y-4)) mod 8) because ghosts and Pacman were misaligned by 4 pixels.

Ghosts don't randomly change directions; they have very structured movement patterns. When the ghost reaches a junction (when x+y&7==0), it generates a list of all 4 directions around it. Another rule of ghosts is that they cannot reverse direction, so then the ghost removes the direction behind it from the list (i.e., if the ghost has direction 90, it removes 270 from the list). Then for each direction still in the list, it checks if there is a block in the way and removes that direction from the list if it is blocked. If there is only one direction left, the ghost heads in that direction; otherwise the ghost then picks a target location where it wants to go:

  • Blinky's target location is wherever Pacman is at that time.
    targetX = Pacman.x; targetY = Pacman.y;
    find_best_path();
  • Pinky's target location is 2 or 3 tiles ahead of Pacman.
    targetX = Pacman.x; targetY = Pacman.y;
    switch Pacman.direction {
    case 0: targetX += tile_size * 3; break;
    case 90: targetY -= tile_size * 3; break;
    case 180: targetX -= tile_size * 3; break;
    case 270: targetY += tile_size * 3; break;
    }
    find_best_path();
  • Inky's target location is to a pincer attack in tandem with Blinky.
    targetX = (Pacman.x * 2 - Blinky.x) mod room_width; //this was 256 in the old games
    targetY = (Pacman.y * 2 - Blinky.y) mod room_height; //again, this was 256
    find_best_path();
  • Clyde's target location was a bit more complex. If he was more than 4 tiles away from Pacman, the target location was Pacman's current position, but if he was closer than that, he would change direction; by default, he'd try to run down (I think it was down).
    if abs(Pacman.x - x) < tile_size * 4 && abs(Pacman.y - y) < tile_size * 4 {
    direction = 270;
    find_new_direction();
    }
    else {
    targetX = Pacman.x; targetY = Pacman.y;
    find_best_path();
    }
  • Fruit would try to get to the Ghost House, which obviously it can't get into, so it just moves around the field seemingly randomly.
The find_best_path() script would just be a script that would figure out which direction the target locations are relative to the ghost and then check if the best-fitting direction is in the list. If it is, it chooses that direction; but if it's not, it checks if the next best-fitting direction is in the list and chooses that. If even that's not available, it picks a random one from the list. This is the only random aspect of a ghost's movement, which usually only happens when the target location is behind the ghost.

The find_new_direction() script is also a random aspect, but it still follows the same direction picking rules as find_best_path().
 

TheouAegis

Member
Wait, are you saying it shows "0#0"all the time ? Because that first number should at least be changing, otherwise Pac-Man wouldn't be moving at all and you said Pac-Man was able to move and change directions, just not easily.. You put the code inside the Pac-Man object's Draw GUI event?

are you still using the Pac-Man object or are you using the test object on accident again?
The DnD post is right below this one. But the op of that post is still having issues. You can giveit a go and see if you have the same issues. Remember to apply what we did here to the ghost's code.
 
The DnD post is right below this one. But the op of that post is still having issues. You can giveit a go and see if you have the same issues. Remember to apply what we did here to the ghost's code.
OK so they all have the step codes as Pac-Man and the target code for each of them should be included the step?
 

TheouAegis

Member
Yes. I would pick a target pill when aligned to the grid. Although I was thinking instead of finding the nearest pill, i'd first check the adjacent cells for pills firsts.
 
Yes. I would pick a target pill when aligned to the grid. Although I was thinking instead of finding the nearest pill, i'd first check the adjacent cells for pills firsts.
And what about these codes: !((x + y), mod tile_size)!(x + y & tile_size), (x+y)
what do I do with these?
 
This is what I have but I'm not sure how I'm going about it

Step:
if direction == (new_dir + 180) mod 360 {
direction = new_dir;
speed = 3;
image_speed = 1;
switch direction {
case 0: sprite_index = Blinky_right; break;
case 90: sprite_index = Blinky_up; break;
case 180: sprite_index = Blinky_left; break;
case 270: sprite_index = Blinky_down; break;
}
}
else
if (x - grid/2) mod grid == 0 && (y-grid/2) mod grid == 0 {
var h = lengthdir_x(grid/2, new_dir);
var v = lengthdir_y(grid/2, new_dir);
if place_meeting(x+h,y+v,obj_Ghost_Collision)
new_dir = direction;
else {
direction = new_dir;
speed = 4;
image_speed = 1;
switch direction {
case 0: sprite_index = Blinky_right; break;
case 90: sprite_index = Blinky_up; break;
case 180: sprite_index = Blinky_left; break;
case 270: sprite_index = Blinky_down; break;
}
}
}
{
if !((x + y) mod grid)
targetX = obj_Player_1.x; targetY = obj_Player_1.y;
find_best_path();
}
move_wrap(1,1,2);

create:
targetX =
targetY =
find_best_path =
new_dir = direction;
sprite_index = Blinky_left;
image_speed = 1;
speed = 3;
grid = 32;
 

JaimitoEs

Member
Edit, i see the new code.... forget my comment...but why are you using directions instead of profit your array/grid to move the player? it´s a bit over complicated but, to be your first game is really good! keep up with code!
 
Last edited:

TheouAegis

Member
He's not using a real grid, in this case grid is just the size of the walls.


for the ghost AI, forget everything you learned in the Pac-Man player controls. The ghosts are a) not Pac-Man, and b) not player-controlled. Youdon't need new_dir for AI. the AI will only change their directions when they are at an intersection. The same is true for the Pac-Man AI, but Pac-Man AI also needs to check every step to make sure he doesn't actually run into a ghost.

If you look at the AI from the drag-and-drop post, what is going on is the ghost first checks if it is at an intersection, then it fills an array with whether there is a path in each direction, then removes that path from the array if it's the opposite direction the ghost is currently going (doesn't apply to Pac-man AI), then picks which of the available paths is the best one to take.

The DnD AI post:
https://forum.yoyogames.com/index.php?threads/random-enemy-movement-for-a-maze-game.66768/#post-406035
 
He's not using a real grid, in this case grid is just the size of the walls.


for the ghost AI, forget everything you learned in the Pac-Man player controls. The ghosts are a) not Pac-Man, and b) not player-controlled. Youdon't need new_dir for AI. the AI will only change their directions when they are at an intersection. The same is true for the Pac-Man AI, but Pac-Man AI also needs to check every step to make sure he doesn't actually run into a ghost.

If you look at the AI from the drag-and-drop post, what is going on is the ghost first checks if it is at an intersection, then it fills an array with whether there is a path in each direction, then removes that path from the array if it's the opposite direction the ghost is currently going (doesn't apply to Pac-man AI), then picks which of the available paths is the best one to take.

The DnD AI post:
https://forum.yoyogames.com/index.php?threads/random-enemy-movement-for-a-maze-game.66768/#post-406035
Ima be honest, I'm all types of lost in that thread. I don't even know how to interpret the whole whole convo.
 

TheouAegis

Member
Ok, fine. Let's take a look at Blinky, the most basic ghost in Pac-Man. Since he is the most basic ghost, we will also make him the parent of all other ghosts.

In the Begin Step event, specify the target coordinates to move toward. In Blinky's case, the target coordinates are usually Pac-man's. When Pac-man is invincible, Blinky tries to run toward one of the corners, so we need to specify that. For Pac-Man's invincibility, I will use the global variable global.PillPower. If the ghost is dead, I use the instance variable goHome.
Code:
///Begin Step Event
if goHome {
    targetX = //whatever the x-coordinate is for the ghost's home;
    targetY = //whatever the y-coordinate is for the ghost's home;
    speed = 4;
}
else
if global.PillPower {
    targetX = 0;
    targetY = 0;
    speed = 4;
}
else {
    targetX = obj_Player_1.x;
    targetY = obj_Player_1.y;
    speed = 4;
}
Note: I set speed like this here instead of the Create Event because technically the ghosts can move at different speeds depending on if they're fleeing or attacking. In your particular case, it likely doesn't matter where you set it.

Unlike Pac-man, Blinky can only change directions when he's in the middle of a junction -- ghosts cannot go backwards normally. This is actually good for the ghosts, since the player will be unable to trick them into a back-and-forth loop. Like Pac-man, we know Blinky is in the middle of a junction when (x - grid/2) mod grid == 0 && (y-grid/2) mod grid == 0, assuming you centered the origin for Blinky's sprite. But again, unlike Pac-Man, we can't just worry about walls in the direction Blinky wants to move, but all the walls around him so he can hopefully pick the best route toward his target. To do this, we need an array of all available directions excluding the path that is directly behind Blinky -- ghosts cannot go backwards normally.

After we have the array of viable paths filled out, we need to find the best path toward the target. If there is only one available path, we take that one, otherwise we need to deduce the best available path. To figure that out, we need to find the distance to the target. When moving along a grid, the distance to any point is abs(x1-x2)+abs(y1-y2). In most cases, we don't care about the total distance toward the target, we only need to worry about which distance -- horizontal or vertical -- is more significant. If the vertical distance was greater than the horizontal distance, the ghost would try to move vertically. If that desired path is unavailable, you try the next preferred path. So if Blinky wanted to move down and right but there was a wall below him, he would try to move right. If moving in the other direction is not an option, you randomly pick one of the available options.

Note: This method is a little different from the one I gave the other guy.

Code:
///Step Event
if (x - grid/2) mod grid == 0 && (y-grid/2) mod grid == 0 {
    for(var paths, i=0; i<4; i++;)
        paths[i] = -1;
    var n = 0;
    for(i=0; i<360; i+= 90;) {
        if i != (direction + 180) mod 360
        if !place_meeting(x+lengthdir_x(grid/2,i), y+lengthdir_y(grid/2,i), obj_Pac_Collision)
            paths[n++] = i;
    }
    if paths[0] == -1 {  //this should NEVER happen, but just in case...
        direction = 270;
        exit;
    }
    if paths[1] == -1 {  //this means there is only one path available
        direction = paths[0];
        exit;
    }
    var h = abs(targetX - x);
    var v = abs(targetY - y);
    var dx = point_direction(x,y,targetX,y);
    var dy = point_direction(x,y,x,targetY);
    if h > v {
    //Try to move horizontally
        for(i=0; i<4; i++;)
            if paths[i] == dx {
                direction = dx;
                exit;
            }
        for(i=0; i<4; i++;)
            if paths[i] == dy {
                direction = dy;
                exit;;
            }
    }
    else
    if v > h {
    //Try to move vertically
        for(i=0; i<4; i++;)
            if paths[i] == dy {
                direction = dy;
                exit;
            }
        for(i=0; i<4; i++;)
            if paths[i] == dx {
                direction = dx;
                exit;
            }
    }
    //If both distances are the same or both desired paths are unavailable, pick a random one
    i = irandom(3);
    while paths[i] == -1
        i = ++i mod 4;
    direction = paths[i];
}
Or if you would rather use a ds_list instead of an array, you could try the following.

Code:
///Create Event
paths = ds_list_create();
Code:
///Step Event
if (x - grid/2) mod grid == 0 && (y-grid/2) mod grid == 0 {
    ds_list_clear(paths);
    for(i=0; i<360; i+= 90;)
        if i != (direction + 180) mod 360
        && !place_meeting(x+lengthdir_x(grid/2,i), y+lengthdir_y(grid/2,i), obj_Pac_Collision)
            ds_list_add(paths,i);
    if !ds_list_size(paths) {  //this should NEVER happen, but just in case...
        direction = 270;
        exit;
    }
    if ds_list_size(paths) == 1 {  //this means there is only one path available
        direction = paths[|0];
        exit;
    }
    var h = abs(targetX - x);
    var v = abs(targetY - y);
    var dx = point_direction(x,y,targetX,y);
    var dy = point_direction(x,y,x,targetY);
    if h > v {
    //Try to move horizontally
        if ds_list_find_index(paths, dx) > -1 {
            direction = dx;
            exit;
        }
        if ds_list_find_index(paths,dy) > -1 {
            direction = dy;
            exit;
        }
    }
    else
    if v > h {
    //Try to move vertically
        if ds_list_find_index(paths,dy) > -1 {
            direction = dy;
            exit;
        }
        if ds_list_find_index(paths,dx) > -1 {
            direction = dx;
            exit;
        }
    }
    //If both distances are the same or both desired paths are unavailable, pick a random one
    i = irandom(3) * 90;
    while ds_list_find_index(paths,i) < 0
        i = (i + 90) mod 360;
    direction = i;
}
 
Last edited:

TheouAegis

Member
Nothing, unless an unknown variable error pops up, in which case you need to define that variable.

Edit: You need to set goHome to false in Create Event.

Set global.PillPower to false in Pac-man's create event; when Pac-man becomes invincible, set global.PillPower to true and set an alarm to how long you want it to last. Then in the alarm event, set global.PillPower back to false.
 
Last edited:
Don't worry it was just a simple switch from ":" to ";" in line 30. The problem I'm having rn is figuring out how to refrence goHome more than once. I've tried saying "goHome = (targetX and targetY)" but that didn't work.
 
Last edited:

TheouAegis

Member
goHome is just a true-false variable. When Pac-man eats a ghost, you set that ghost's goHome to 1 (true) and change its sprite to the disembodied eyes. The Begin Step event will then see that goHome is true and then update targetX and targetY in that ghost.
 
goHome is just a true-false variable. When Pac-man eats a ghost, you set that ghost's goHome to 1 (true) and change its sprite to the disembodied eyes. The Begin Step event will then see that goHome is true and then update targetX and targetY in that ghost.
this error says otherwise

___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event1
for object obj_Blinky:
Variable obj_Blinky.goHome(100007, -2147483648) not set before reading it.
at gml_Object_obj_Blinky_Step_1 (line 2) - if goHome {
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_obj_Blinky_Step_1 (line 2)
 

TheouAegis

Member
For ghosts, you only need to set the sprite sprite properties in the Create event. You can change the sprite_index when changing directions, but set the image_speed once.

Are you talking about the ghost stopping or Pacman AI stopping? The ghost should never stop unless you set speed to 0.
 
For ghosts, you only need to set the sprite sprite properties in the Create event. You can change the sprite_index when changing directions, but set the image_speed once.

Are you talking about the ghost stopping or Pacman AI stopping? The ghost should never stop unless you set speed to 0.
OK turns out I had to adjust the origin. So as of now Blinky is officially pursuing Pac-Man. now I need to add the animation as well as fix the warping problem because instead of him going from one tunnel to the other he just hang outside the wall view.
 

TheouAegis

Member
Did you use move_wrap(true,true,2) like you did with Pac-Man?

If I remember right, the ghosts had special exceptions for going through the warp tunnels. Try using move_wrap(true,true,4). If that doesn't work, you may need to set an alarm when the ghost wraps and in that alarm's event just put an empty comment so the alarm counts down without actually doing anything, and then tell the ghost to not try to change directions when the alarm is greater than 0 (but try adjusting move_wrap first).

Is your ghost moving with speed 4 just like you did with Pac-Man?
 
Yes I did use speed 4 for the ghost and the same move_wrap code pac-man has. Also for the alarm even't do I just say "if wrap = !direction"?
 
Last edited:

TheouAegis

Member
Don't put anything in the alarm if you use an alarm. But first, try just setting the move_wrap to 4 instead of 2.

OOH! Or when the ghost wraps, increase it manually also.

x+=hspeed;
y+=vspeed;

I think there is an event for when it wraps.
 
OK so all ghost is now the child of Blinky and are following target successfully. I tried to change their sprites so they would look like they're going to said direction but no luck.
 

TheouAegis

Member
You should make up 4 variables or an array in the Create event of each ghost for each sprite, then reference those variables/array in the Step event instead of actual sprites.

E.g.:

sprite_up = Blinky_Up;
sprite_down = Blinky_Down;
sprite_right = Blinky_Right;
sprite_left = Blinky_Left;

Then in the Step event, reference sprite_up instead of Blinky_Up.
 

TheouAegis

Member
Well, when direction is 180,it'd be sprite_left, but yes.

By using a set of variables like that, you can define the variables in each ghost's create event, define the target location in each ghost's Begin Step event (if you want them to have different movements), then have just o e Step event between them all. This keeps code much cleaner and easier to manage.
 
Well, when direction is 180,it'd be sprite_left, but yes.

By using a set of variables like that, you can define the variables in each ghost's create event, define the target location in each ghost's Begin Step event (if you want them to have different movements), then have just o e Step event between them all. This keeps code much cleaner and easier to manage.
OMG I ain't realize what I typed until you pointed it out, I'm so sorry lol. OK so this is what result in:

step

///Step Event
if (x - grid/2) mod grid == 0 && (y-grid/2) mod grid == 0 {
for(var paths, i=0; i<4; i++;)
paths = -1;
var n = 0;
for(i=0; i<360; i+= 90;) {
if i != (direction + 180) mod 360
if !place_meeting(x+lengthdir_x(grid/2,i), y+lengthdir_y(grid/2,i), obj_Pac_Collision)
paths[n++] = i;
}
if paths[0] == -1 { //this should NEVER happen, but just in case...
direction = 270;
exit;
}
if paths[1] == -1 { //this means there is only one path available
direction = paths[0];
exit;
}
var h = abs(targetX - x);
var v = abs(targetY - y);
var dx = point_direction(x,y,targetX,y);
var dy = point_direction(x,y,x,targetY);
if h > v {
//Try to move horizontally
for(i=0; i<4; i++;)
if paths == dx {
direction = dx;
exit;
}
for(i=0; i<4; i++;)
if paths == dy {
direction = dy;
exit;;
}
}
else
if v > h {
//Try to move vertically
for(i=0; i<4; i++;)
if paths == dy {
direction = dy;
exit;
}
for(i=0; i<4; i++;)
if paths == dx {
direction = dx;
exit;
}
}
//If both distances are the same or both desired paths are unavailable, pick a random one
i = irandom(3);
while paths == -1
i = ++i mod 4;
direction = paths;
}
{
move_wrap(1,1,2);
}
{
direction = 180;
sprite_index = sprite_left;
}
{
direction = 0;
sprite_index = sprite_right;
}
{
direction = 90;
sprite_index = sprite_up;
}
{
direction = 270;
sprite_index = sprite_down;
}
 

TheouAegis

Member
Oh, you need to make those conditionals.
E.g.:

switch direction {
case 0: sprite_index = sprite_right; break
case 90: sprite_index = sprite_up; break
case 180: sprite_index = sprite_left; break
case 270: sprite_index = sprite_down; break
}
 
Top