Created
August 18, 2011 19:22
-
-
Save dariusk/1154923 to your computer and use it in GitHub Desktop.
Game Maker Script: Find Movement Range
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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