-
Notifications
You must be signed in to change notification settings - Fork 0
Advanced Usage
Some of these may seem obvious, but may give you some ideas, nevertheless.
Short version: use the x and y property of the node object.
Long version: for performance reasons, the getX() and getY() methods on the node object get deleted in favor of the x and y properties on the node. How this works is when you add the node to a graph, the node gets properly initialized, causing getX() and getY() to be called one last time and assigned into the x and y properties. So remember, in all your callbacks that expect a node -- use x, because getX() is long gone!
Are some nodes harder to traverse than others? You can represent this in the graph so units will avoid slow spots unless it's actually faster. The example dunesAndDemise does this, but here's the core of it:
function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; };
MyNode.prototype = new crow.GridNode();
// Here we're setting a default resistance
MyNode.prototype.resistance = 1.0;
MyNode.prototype.distanceTo = function(other){
// This is calling the default distanceTo method
var baseDistance = crow.GridNode.prototype.distanceTo.apply(this, arguments);
// Now we just multiply the base distance by the average of this node's resistance
// and the other node's resistance.
var realDistance = baseDistance * (this.resistance + other.resistance) / 2;
return realDistance;
};
// Hey, it's a subclass! And it's identical to MyNode except in name.
function Road(){ MyNode.apply(this, arguments); }
Road.prototype = new MyNode();
// Another subclass, but this has a higher resistance, since it's slower to walk on.
function Sand(){ MyNode.apply(this, arguments); }
Sand.prototype = new MyNode();
Sand.prototype.resistance = 2.0; // Sand has double the "resistance" of regular pathWith the above resistances, you get the following results:
- Walking from Road -> Road is cost 1
- Walking from Road -> Sand or Sand -> Road is cost 1.5
- Walking from Sand -> Sand is cost 2
It's not usually convenient to add/remove nodes from the graph (generally not recommended anyway), but there's another way to make a node unwalkable besides removing it from the graph. And that is by writing your distanceTo method such that unwalkable terrain is Infinity distance away. Here's a quick example:
function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; }
MyNode.prototype = new crow.GridNode();
MyNode.prototype.distanceTo = function(other){
if(other instanceof Wall){
return Infinity;
} else {
return crow.GridNode.prototype.distanceTo.apply(this, arguments);
}
};
// Road is walkable
function Road(){ MyNode.apply(this, arguments); }
Road.prototype = new MyNode();
// Wall is unwalkable
function Wall(){ MyNode.apply(this, arguments); }
Wall.prototype = new MyNode();Sometimes, you want more information than is convenient to code in a single graph. In the following example, there's one graph that represents what's visible on screen (land, water) and the second graph represents what parts are walkable (some water is shallow enough to wade across). Now, in practice the water that's shallow enough to walk across should probably have a "shallow_water" type in the first graph or something, but ignore that for now.
function MyNode(arr){ this.x = arr[0]; this.y = arr[1]; }
MyNode.prototype = new crow.GridNode();
MyNode.prototype.distanceTo = function(other){
// This section will make more sense when you see the rest of the example
var walkableNode = walkableGraph.getNode(other.x, other.y);
if(!other.walkable){
return Infinity;
} else {
return crow.GridNode.prototype.distanceTo.apply(this, arguments);
}
};
function Land(){ MyNode.apply(this, arguments); }
Land.prototype = new MyNode();
function Water(){ MyNode.apply(this, arguments); }
Water.prototype = new MyNode();
// This represents a tile that is either walkable or unwalkable
function WalkableTile(arr, isWalkable){
MyNode.apply(this, arguments);
this.walkable = isWalkable;
};
// L = Land, W = Water
var terrainGraph = crow.Graph.fromArray([
"LWL",
"LWL",
"LWL"
], function(x, y, value){
if(value == "L") return new Land([x, y]);
if(value == "W") return new Water([x, y]);
});
// - is walkable
// X is not walkable
var walkableGraph = crow.Graph.fromArray([
"-X-",
"---",
"-X-"
], function(x, y, value){
return new WalkableTile([x, y], value == "-");
});There are some optimizations that I ommitted for the sake of the example. For instance, in this case, instead of actually creating a second walkable graph, you could replace the var walkableGraph declaration with:
crow.Graph.fromArray([
"-X-",
"---",
"-X-"
], function(x, y, value){
terrainGraph.getNode(x, y).walkable = value == "-";
});and update the distanceTo method appropriately.
What if the available path varies by the unit/vehicle/actor that's running the course? Aerial, motorized, infantry, and naval units all have different paths they can walk on. You could create a separate graph for each type of unit, but there's a (much) better way. You can pass in an actor to findGoal, which will then get passed as the second argument to the distanceTo method on your node, allowing you to vary distance on an actor-by-actor basis. So:
// Same node class, and...
MyNode.prototype.distanceTo = function(other, unit){
if((other instanceof WaterTile && unit instanceof BoatUnit) ||
(other instanceof LandTile && unit instanceof TruckUnit)){
return crow.GridNode.prototype.distanceTo.apply(this, arguments);
}
// Trucks can't go in water and boats can't go on land
return Infinity;
};
// Then with a graph, you can call like this:
var truck = new TruckUnit();
var path = graph.findGoal({start:..., goal:..., actor: truck});