Objectless Grid Movement!

In the Beginning, There was the GPC

About seven and a half years ago, while working on Level 12 of John Janetka’s  Game Programming Course (GPC), I developed a novel way to handle orthogonal grid movement. The desired outcome was simple:

  1. Pick a target point to the left, right, above, or below your current position
  2. Move the player object toward the target point
  3. Stop when the player object’s x,y coordinates = the target point’s x,y position

When I first tackled this, I tried using an instance variable to set the target point, something like this:

/* 
If 'A' is pressed, set the targetx to be the player's x-32 pixels,
then move towards that targetx...
*/

if keyboard_check(ord("A")) {
    targetx=x-32;
    move_towards_point(targetx,y,4);
}

/*
When targetx is reached, stop.
*/

if x=targetx {
    speed=0;
}

When I tried this, I discovered that the player would keep moving to the right indefinitely. This is because (as written) targetx would always be 32 pixels ahead of the player’s x coordinate and because the player object’s x would never equal targetx, he’d never fulfill the condition to stop (i.e., if x=targetx {speed=0}).

A Novel Solution

My solution was simple but flawed. Instead of using the player’s x,y coordinates, I could use a separate “target” object and use it instead. Something like this:

  1. A target object (the red square above) is created in the direction you’re trying to move (e.g., left).
  2. The player object then moves toward the x,y coordinates of the target.
  3. The target object is destroyed when the two collide, and the player’s speed is set to zero.

You can find code examples here and here. This works fine (in isolation), but when you add a second (and third, and fourth…) object, the cracks begin to show…

Limitations and Drawbacks

Looking at the original code, you’ll see that I used a Collision Event with the target object (o_target):

//Destroy the o_target object on collision
with other {
instance_destroy()
}

//Stop the player
speed=0

The problem is that one object might collide with another’s target block, knocking it off course and causing the other object to continue without anything to stop it.

So then I substituted out o_target for an instance of it, i.e.:

target=instance_create_layer(x-32,y,o_target)

I scrapped the Collision Event and instead used the place_meeting function in the End Step Event:

if place_meeting(x,y,target) {
    if instance_exists(target){
        speed=0;
        moving=0;
    instance_destroy(target);
}

While this worked, it also added a lot of [needless] complexity. Although players would never notice (or care) how complicated my code is/was, I knew this wasn’t well implemented, which caused a slew of difficult-to-track-down issues and many troubleshooting headaches.

Back to Basics

As I sat down to work on our next game, this problem still bothered me, so I decided to try something new on a whim. I’ve been aware of local variables since Level 9 of the GPC but didn’t clearly understand when or why to use them.

Re-reading the GameMaker online Manual, it says,

“A local variable is one that we create for a specific event or function only and then discard when the event or function has finished.”

My instincts (even back then) were correct; the target should have been stored in a variable, but I needed to capture the value somehow and then decouple it from the player’s coordinates.

Pass the variable, Bob!

That’s when it hit me: why not use a local variable to capture the target coordinate? If placed in a function, it would define that variable once and throw it away when it ended. To use it, we’d have to pass that value along to an instance variable to hold on to it. Here’s what I came up with:

/*
==========================
Objectless Grid Movement!™
==========================

Variables:
dist   = sprite_width-pspeed

pspeed = the speed (in pixels) the object is traveling

targetx = the local variable of the x coordinate we want to go to, used when moving horizontally. Set using var _left or var _right.

targety = the local variable of the y coordinate we want to go to, used when moving vertically. Set using var _up or var _down.
*/

// Accept input, then move towards the target coordinate
if keyboard_check(ord("A")) and moving=0 {
    moving=1;
    var _left=x-dist;
    targetx=_left;
    move_towards_point(targetx,y,pspeed)
}

if keyboard_check(ord("D")) and moving=0 {
    moving=1;
    var _right=x+dist;
    targetx=_right;
    move_towards_point(targetx,y,pspeed)
}

if keyboard_check(ord("W")) and moving=0 {
    moving=1;
    var _up=y-dist;
    targety=_up;
    move_towards_point(x,targety,pspeed)
}

if keyboard_check(ord("S")) and moving=0 {
    moving=1;
    var _down=y+dist;
    targety=_down;
    move_towards_point(x,targety,pspeed)
}

// Stop the player once he reaches his target coordinate
if x=targetx {
    moving=0;
}

if y=targety {
    moving=0;
}

This worked perfectly and will save me a Metric Crappe Tonne™ of time and aggravation later.

Further Improvement

I want to implement custom key binds that support keyboard and gamepad input. I’m pretty sure I can accomplish the former, but I am still researching the latter. More to come!

The Fourth Level

As mentioned in a previous post, Level 4 of the GPC was not a lesson, but rather a series of challenges meant to reinforce what was taught in Level’s 2 and 3. This time, I elected to create a new game rather than modify an existing one.

I settled on a top down shooter/adventure game and added features and flourishes as I went along. I used my earlier conceived true grid movement code, but this time, I used a switch statement instead of a series of if statements (or at least fewer of them):

switch (keyboard_key)
 {
  case vk_left:
  case ord("A"):
     sprite_index=spr_player_walk_left;
     if moving=0 {
       moving=1
       instance_destroy(obj_target) 
       target=instance_create(x-64,y,obj_target)
       move_towards_point(target.x,target.y,4) 
      }
      break; 
 case vk_right:
 case ord("D"):
    sprite_index=spr_player_walk_right;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x+64,y,obj_target)
      move_towards_point(target.x,target.y,4) 
     } 
     break;
 case vk_up:
 case ord("W"):
    sprite_index=spr_player_walk_up;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x,y-64,obj_target)
      move_towards_point(target.x,target.y,4) 
    } 
    break;
 case vk_down:
 case ord("S"):
    sprite_index=spr_player_walk_down;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x,y+64,obj_target)
      move_towards_point(target.x,target.y,4) 
     } 
     break; 
 }

While trying to figure out line of sight, I came a new function:

collision_line(x,y,obj_player.x,obj_player.y,obj_barrier,1,0)

That was my first time using the collision_line, which I don’t think is covered at all in the GPC, though it might be included in his how-to’s somewhere…I came across it after watching a tutorial video by “GM Wolf” on YouTube:

This would have solved a lot of issues for me…for instance, in a previous project, I had an idea to create a trap comprised of two moving blocks which collided into and bounced off of each other. While everything worked fine, it would occasionally cause the block to get stuck in a wall. This happened because it’s movement caused it to overlap before it detected the collision, thereby getting stuck inside the wall, unable to go anywhere.

I solved this before using the place_meeting function, but was never introduced to the End Step Event, which  would have been the right way to do it. Nevertheless, I can think of other uses for collision_line and am glad I learned of it!

All in all, I spent about 2 days (most of the weekend) working on this little mini-game, and here are some of the features I included:

  • Destructible walls that advance in damage by manipulating the image_index
  • Enemies that can navigate mazes (using John Janeka’s code from Level 12)
  • Line of Sight for enemies with projectiles
  • Health, ammunition and keys global variables that persistent between rooms
  • Lock and key mechanism
  • A switch that reveals the exit when touched
  • Randomized muzzle flare and smoke when firing bullets
  • Randomized impact splatters when an enemy is hit
  • Different sounds for each impact
  • Randomized health power up sprites to add variety using a single object
  • Exits that allow you to advance to the next room
  • Capped health at 100%

What I not did include were:

  • Fail condition/game over when you run out of hitpoints
  • Start screen
  • Game Over screen

All in all, it’s a neat little game though unfinished, and good practice for more serious projects to come!