Skip to content

Instantly share code, notes, and snippets.

@dariusk
Created August 18, 2011 19:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dariusk/1154923 to your computer and use it in GitHub Desktop.
Save dariusk/1154923 to your computer and use it in GitHub Desktop.
Game Maker Script: Find Movement Range
/*
This code is modified from Mnementh's code at http://gmc.yoyogames.com/index.php?showtopic=368996
I did some bug fixes, and I modified it to allow you to pass in a hash of terrain modifier overrides.
So for example, if swamp usually has a cost of 3, but you're a hovercraft, you can pass in {swamp: 1}.
NOTE THAT THIS IS A FUNCTION THAT IS CALLED FROM INSIDE A UNIT WHEN CALCULATING MOVEMENT RANGE
It's important to keep in mind that what this is doing is updating values on each tile.
Every tile has this kind of data in it:
type = "grass";
cost = 1;
reachable = false;
attackable = false;
occupied = false;
pathDepth = -1;
parent = noone;
// precalculating which tiles are to each side
Right = instance_position( x + 32 , y, oTile );
Up = instance_position( x, y - 32, oTile );
Left = instance_position( x - 32, y, oTile );
Down = instance_position( x, y + 32, oTile );
What this function does is that when the player clicks a unit to move it and we're in the game state where we need to see a movement range rendered on the map, it sets pathDepth, reachable, and parent on every tile in the map. Most tiles are unreachable and thus won't be considered for rendering. For tiles where reachable == true, we can look at that tile and draw a line to its parent tile, and back to its parent, all the way back to the player who is the root of the tree. That becomes the line we animate on when we do the pathing. PathDepth just helps us figure out how many steps of animation to plan for.
arg0 -- starting cell id
arg1 -- movement range
arg2 -- terrain modifier
*/
{
var _start, _moverange, _terrain;
// ID of the starting cell you're sitting on
_start = argument0;
// movement range (int)
_moverange = argument1;
// a hash of movement modifiers (grass: 0, water: -1000, rocks: -1, etc)
_terrain = argument2;
var _priorityOpen, _listClosed, _mapDist;
// A priority queue
_priorityOpen = ds_priority_create();
// A list
_listClosed = ds_list_create();
// A hash
_mapDist = ds_map_create();
// It will cost 0 movement points to move to your current (_start) position
ds_priority_add( _priorityOpen, _start, 0 );
// update the hash of distance costs for your start position (cost = 0)
ds_map_add( _mapDist, _start, 0 );
// You've got no parents on this node, it's the start
_start.parent = 0;
// it's reachable because you can click on yourself and not move
_start.reachable = true;
// depth is 0
_start.pathDepth = 0;
// kick off a while. Our priority queue is size 1 right now, so we enter the loop
while ( ds_priority_size( _priorityOpen ) )
{
// Pop what's currently the minimum value in the queue and set it to _current
// on iteration 1, this is the start position
var _current;
_current = ds_priority_delete_min( _priorityOpen );
// add it to the list of closed paths
ds_list_add( _listClosed, _current );
// figure out what the cost is of the current node
// this is 0 on iteration 1, but as we traverse the tree we need to know this to add the base cost to the cost of
// the next tile we're looking at
var _predist;
_predist = ds_map_find_value( _mapDist, _current );
// look at the terrain tile that is to the right of the current tile
// everything that happens next in the code is happening in side the scope of that tile to the right
with ( _current.Right )
{
var _dist, _cost;
// if the unit passed in a special terrain cost override (like water is -1000 because we can't travel through water)
// then set the current cost to the override, else leave set it to the tile's actual cost
if (ds_map_exists(_terrain,type)) _cost = ds_map_find_value(_terrain,type);
else _cost = cost;
// total distance is the cost it took to get to the tile to the left of this, PLUS the current cost as just calc'd
_dist = _predist + _cost;
// if this tile is not in our Closed list, and cost > 0, and total distance to this point is <= our move range
if ( ds_list_find_index( _listClosed, id ) == -1 && _cost && _dist <= _moverange )
{
// then if this thing is in our priority queue with pri == 0, and the tile isn't occupied already
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 && !occupied )
{
// add this to the priority queue of open nodes with a cost of _dist
// NOTICE THIS INCREASES THE SIZE OF _priorityOpen, so our while loop will go a little longer
ds_priority_add( _priorityOpen, id, _dist );
// add it to our map, too
ds_map_add( _mapDist, id, _dist );
// now set parent = _current, so we travel down the tree again
parent = _current;
// we know this tile is now reachable
reachable = true;
// increment depth of this path
pathDepth = parent.pathDepth + 1;
}
else
{
// if the cost to reach here is less than the priority value of this tile
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
{
// reset the priority value of this tile (this happens when there's two ways to
// get to the tile and one is cheaper, we want to take the cheaper route)
ds_priority_change_priority( _priorityOpen, id, _dist );
ds_map_replace( _mapDist, id, _dist );
parent = _current;
}
}
}
}
// repeat for up
with ( _current.Up )
{
var _dist, _cost;
if (ds_map_exists(_terrain,type)) _cost = ds_map_find_value(_terrain,type);
else _cost = cost;
_dist = _predist + _cost;
if ( ds_list_find_index( _listClosed, id ) == -1 && _cost && _dist <= _moverange )
{
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 && !occupied )
{
ds_priority_add( _priorityOpen, id, _dist );
ds_map_add( _mapDist, id, _dist );
parent = _current;
reachable = true;
pathDepth = parent.pathDepth + 1;
}
else
{
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
{
ds_priority_change_priority( _priorityOpen, id, _dist );
ds_map_replace( _mapDist, id, _dist );
parent = _current;
}
}
}
}
// repeat for left
with ( _current.Left )
{
var _dist, _cost;
if (ds_map_exists(_terrain,type)) _cost = ds_map_find_value(_terrain,type);
else _cost = cost;
_dist = _predist + _cost;
if ( ds_list_find_index( _listClosed, id ) == -1 && _cost && _dist <= _moverange )
{
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 && !occupied )
{
ds_priority_add( _priorityOpen, id, _dist );
ds_map_add( _mapDist, id, _dist );
parent = _current;
reachable = true;
pathDepth = parent.pathDepth + 1;
}
else
{
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
{
ds_priority_change_priority( _priorityOpen, id, _dist );
ds_map_replace( _mapDist, id, _dist );
parent = _current;
}
}
}
}
// repeat for right
with ( _current.Down )
{
var _dist, _cost;
if (ds_map_exists(_terrain,type)) _cost = ds_map_find_value(_terrain,type);
else _cost = cost;
_dist = _predist + _cost;
if ( ds_list_find_index( _listClosed, id ) == -1 && _cost && _dist <= _moverange )
{
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 && !occupied )
{
ds_priority_add( _priorityOpen, id, _dist );
ds_map_add( _mapDist, id, _dist );
parent = _current;
reachable = true;
pathDepth = parent.pathDepth + 1;
}
else
{
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
{
ds_priority_change_priority( _priorityOpen, id, _dist );
ds_map_replace( _mapDist, id, _dist );
parent = _current;
}
}
}
}
} // end while
ds_priority_destroy( _priorityOpen );
ds_list_destroy( _listClosed );
ds_map_destroy( _mapDist );
}
@jorrit12345
Copy link

Hello,
I was wondering if you could help me out:
I'm tring to use Mnementh's code but it doesnt work. Only the object at the start tile is highlighted but not the others. I found out that its because the following statements are false:
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 )
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
They can be found in this part of the scipt:

with ( _current.Right )
{
var _dist;
_dist = _predist + Cost;
if ( ds_list_find_index( _listClosed, id ) == -1 && Cost && _dist <= _moverange )
{
if ( ds_priority_find_priority( _priorityOpen, id ) == 0 )
{
ds_priority_add( _priorityOpen, id, _dist );
ds_map_add( _mapDist, id, _dist );
Parent = _current;
Reachable = true;
}
else
{
if ( _dist < ds_priority_find_priority( _priorityOpen, id ) )
{
ds_priority_change_priority( _priorityOpen, id, _dist );
ds_map_replace( _mapDist, id, _dist );
Parent = _current;
}
}
}
}

I feel like its because the tile that is referred to (_current.Right) is not added to the _priorityOpen list yet so the script cant find it and the statement will be seen as false.
How do i solve this? I find it really strange because i remember using this script a while back and it worked fine. Could it be because im using a newer version of gamemaker?

thanks in advance,
Jorrit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment