Skip to content

Commit

Permalink
Readme update pass
Browse files Browse the repository at this point in the history
  • Loading branch information
mikewesthad committed Jul 1, 2018
1 parent 87e87f3 commit 1aec0c9
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 45 deletions.
163 changes: 120 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,48 @@ A JS plugin for fast pathfinding using [navigation meshes](https://en.wikipedia.

Table of Contents:

- [Intro](#intro)
- [Introduction](#introduction)
- [Installation](#installation)
- [As a Script](#as-a-script)
- [As a Module](#as-a-module)
- [Create a Nav Mesh](#create-a-nav-mesh)
- [Creating a Navigation Mesh](#creating-a-navigation-mesh)
- [Usage](#usage)
- [navmesh](#navmesh)
- [phaser-navmesh](#phaser-navmesh)
- [phaser2-navmesh](#phaser2-navmesh)
- [Performance Comparison](#performance-comparison)
- [Development](#development)
- [References](#references)
- [TODO](#todo)
- [To Dos](#to-dos)

## Intro
## Introduction

Pathfinding is essentially solving a maze, finding a path between two points while avoiding obstacles. When pathfinding in games, we need to:
Pathfinding is essentially the problem of solving a maze, finding a path between points while avoiding obstacles. When pathfinding in games, we need to:

1. Represent the game world in a way that defines what areas are walkable.
2. Search that representation for the shortest path.

When it comes to 2D pathfinding in [Phaser](http://phaser.io/), the [packaged solution](https://github.com/photonstorm/phaser-plugins) represents the world using [tiles](https://developer.mozilla.org/en-US/docs/Games/Techniques/Tilemaps) (a grid) and then searchs for a path using the [A\* algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm). If you have a 50 x 50 tile world, searching for a path involves searching through a representation of the world with up to 2500 locations (nodes).
When it comes to 2D pathfinding, a common approach is to represent the world using [tiles](https://developer.mozilla.org/en-US/docs/Games/Techniques/Tilemaps) (a grid) and then search for a path using the [A\* algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) ((e.g. [Phaser AStar](https://github.com/photonstorm/phaser-plugins/tree/master/AStar)). If you have a 50 x 50 tile world, searching for a path involves searching through a representation of the world with up to 2500 locations ("nodes").

This plugin uses navigation meshes to simplify that search. Instead of representing the world as a grid of tiles, it represents the walkable areas of the world as a mesh. That means that the representation of the world has far fewer nodes, and hence, can be searched much faster than the grid approach. This approach is 5x - 150x faster than Phaser's A\* plugin (see performance section).
This plugin uses navigation meshes to simplify that search. Instead of representing the world as a grid of tiles, it represents the walkable areas of the world as a mesh. That means that the representation of the world has far fewer nodes, and hence, can be searched much faster than the grid approach. This approach is 5x - 150x faster than Phaser's A\* plugin (see performance section), depending on the mesh.

The example map below (left) is a 30 x 30 map. As a grid, there are 900 nodes, but as a navmesh (right) there are 27 nodes (colored rectangles).

<img src="./doc-source/combined.png" width="700">

## Installation

This repo contains 3 related JS modules:
This repository contains 3 related JS packages:

- `navmesh` - core logic, game-engine agnostic, usable outside of Phaser.
- `phaser-navmesh` - Phaser v3 wrapper around `navmesh` that creates a Phaser 3 Scene plugin. Phaser 3 is expected to be a dependency in your project.
- `phaser2-navmesh` - Phaser v2 wrapper around `navmesh` that creates a Phaser 2 game plugin. Phaser 2 or Phaser-ce is expected to be in the global scope.

You can use any of them as a script or as a module.
You can use any of them as a script or as a module in your bundler of choice.

### As a Script

Download the distribution version that you want:
You can drop in any of the transpiled code into your project as a standalone script. Download the version that you want:

| navmesh | phaser-navmesh | phaser2-navmesh |
| --------------------------------- | ---------------------------------- | ----------------------------------- |
Expand All @@ -70,13 +73,13 @@ Download the distribution version that you want:
[11]: https://raw.githubusercontent.com/mikewesthad/navmesh/master/packages/phaser2-navmesh/dist/phaser2-navmesh.js
[12]: https://raw.githubusercontent.com/mikewesthad/navmesh/master/packages/phaser2-navmesh/dist/phaser2-navmesh.js.map

E.g. if you wanted phaser-navmesh
E.g. if you wanted phaser-navmesh, you would add this to your HTML:

```html
<script src="dist/phaser-navmesh.min.js"></script>
<script src="phaser-navmesh.min.js"></script>
```

Inside of your own script, you can now use the global PhaserNavMeshPlugin:
Inside of your own script, you can now use the global variable `PhaserNavMeshPlugin` (see library name in the above table), e.g.

```js
const game = new Phaser.Game({
Expand All @@ -89,6 +92,8 @@ const game = new Phaser.Game({
});
```

See [usage](#usage) for more information on how to use each of the three modules in this repository.

### As a Module

Install the appropriate dependency:
Expand All @@ -100,56 +105,129 @@ Install the appropriate dependency:
To use the transpiled and minified distribution of the library:

```js
import PhaserNavmesh from "phaser-navmesh";
import PhaserNavMeshPlugin from "phaser-navmesh";
```

To use the raw library (so you can transpile it to match your own project settings):

```js
import PhaserNavmesh from "phaser-navmesh/src";
import PhaserNavMeshPlugin from "phaser-navmesh/src";
```

## Create a Nav Mesh
## Creating a Navigation Mesh

See [guide](https://www.mikewesthad.com/navmesh/docs/manual/tiled-navmesh-guide.html).
Before you can dive into the code, you'll need to create a navigation mesh for your game world. This is a process of defining the walkable areas within you world. You can create it from scratch in code, but it's far easier to use a tilemap editor like Tiled to do this. See [guide](https://www.mikewesthad.com/navmesh/docs/manual/tiled-navmesh-guide.html).

## Usage

### navmesh

If you don't need the Phaser wrappers, you can construct navmeshes directly from points using the navmesh package:

```js
// This snippet assumes you've got your tilemap loaded in a variable called "tilemap"
import NavMesh from "navmesh";

/*
Imaging your game world has three walkable rooms, like this:
+-----+-----+
| | |
| 1 | 2 |
| | |
+-----------+
| |
| 3 |
| |
+-----+
*/

// The mesh is represented as an array where each element contains the points for an indivdual
// polygon within the mesh.
const meshPolygonPoints = [
[{ x: 0, y: 0 }, { x: 10, y: 0 }, { x: 10, y: 10 }, { x: 0, y: 10 }], // Polygon 1
[{ x: 10, y: 0 }, { x: 20, y: 0 }, { x: 20, y: 10 }, { x: 10, y: 10 }], // Polygon 2
[{ x: 10, y: 0 }, { x: 20, y: 10 }, { x: 20, y: 20 }, { x: 10, y: 20 }] // Polygon 3
];
const navMesh = new NavMesh(meshPolygonPoints);

// Find a path from the top left of room 1 to the bottom left of room 3
const path = navMesh.findPath({ x: 0, y: 0 }, { x: 10, y: 20 });
// ⮡ [{ x: 0, y: 0 }, { x: 10, y: 10 }, { x: 10, y: 20 }]
```

Check out the [API reference](https://www.mikewesthad.com/navmesh/docs/identifiers.html#navmesh-src) for more information.

// Register the plugin with Phaser
const navMeshPlugin = this.game.plugins.add(phaserNavmesh);
### phaser-navmesh

// Load the navMesh from the tilemap object layer "navmesh." The navMesh was created with 12.5
// pixels of space around obstacles.
const navMesh = navMeshPlugin.buildMeshFromTiled(tilemap, "navmesh", 12.5);
If you are working with Phaser 3, you can use the phaser-navmesh package, which provides a Scene plugin. See this [example](https://github.com/mikewesthad/navmesh/tree/master/packages/examples/src) for more complete usage.

const p1 = new Phaser.Point(100, 400);
const p2 = new Phaser.Point(700, 200);
const path = navMesh.findPath(p1, p2);
// -> path is now either an array of points, or null if no valid path could be found
```js
import Phaser from "phaser";
import PhaserNavMeshPlugin from "phaser-navmesh";

const game = new Phaser.Game({
type: Phaser.AUTO,
parent: "game-container",
width: 750,
height: 750,
plugins: {
scene: [
{
key: "PhaserNavMeshPlugin", // Key to store the plugin class under in cache
plugin: PhaserNavMeshPlugin, // Class that constructs plugins
mapping: "navMeshPlugin", // Property mapping to use for the scene, e.g. this.navMeshPlugin
start: true
}
]
},
scene: {
preload: preload,
create: create
}
});

function preload() {
this.load.tilemapTiledJSON("map", "tilemaps/map.json");
this.load.image("tiles", "tilemaps/tiles.png");
}

function create() {
// Set up a tilemap with at least one layer
const tilemap = this.add.tilemap("map");
const tileset = tilemap.addTilesetImage("tiles", "tiles");
const wallLayer = tilemap.createStaticLayer("walls", tileset);

// Load the navMesh from the tilemap object layer "navmesh" (created in Tiled). The navMesh was
// created with 12.5 pixels of space around obstacles.
const objectLayer = tilemap.getObjectLayer("navmesh");
const navMesh = this.navMeshPlugin.buildMeshFromTiled("mesh", objectLayer, 12.5);
const path = navMesh.findPath({x: 0, y: 0}, {x: 300, y: 400});
// ⮡ path will either be null or an array of Phaser.Geom.Point objects
}
```

Visually debugging paths:
The plugin comes with some methods for visually debugging your navmesh:

```js
navMesh.enableDebug(); // Creates a Phaser.Graphics overlay on top of the screen
navMesh.debugClear(); // Clears the overlay
navMesh.debugDrawClear(); // Clears the overlay
// Visualize the underlying navmesh
navMesh.debugDrawMesh({
drawCentroid: true,
drawBounds: false,
drawNeighbors: true,
drawPortals: true
});
// Find & visualize a specific path
const path = navMesh.findPath(follower.position, target, {
drawPolyPath: true,
drawFinalPath: true
});
// Visualize an individual path
navMesh.debugDrawPath(path, 0xffd900);
```

Check out the [API reference](https://www.mikewesthad.com/navmesh/docs/identifiers.html#phaser-navmesh-src) for more information.

### phaser2-navmesh

If you are working with Phaser 2, you can use the phaser2-navmesh package, which provides a game plugin. See this [example](https://github.com/mikewesthad/navmesh/tree/master/packages/examples-phaser2/src) for more complete usage. You can also look at the [previous section](#phaser-navmesh) for Phaser usage.

## Performance Comparison

_(Note: these comparisons were done in any earlier verison of the repo before Phaser v3 was released. The plugins tested haven't been released in v3 versions yet, so this section could use an update. That said, the results should be the same.)_
Expand Down Expand Up @@ -191,7 +269,7 @@ Long paths (600 pixels and greater length), average time per iteration:

## Development

Pull requests are welcome! If you want to run this repo locally, make sure you have [node](https://nodejs.org/en/) installed. Download the repo, open a terminal in the repo folder and run:
Pull requests are welcome (see [todos](#to-dos))! If you want to run this repo locally, make sure you have [node](https://nodejs.org/en/) installed. Download the repo, open a terminal in the repo folder and run:

```
npm install
Expand All @@ -207,7 +285,7 @@ This project uses [lerna](https://github.com/lerna/lerna) and [yarn workspaces](
The project is controlled via npm scripts. The main ones to use:

- `npm run build` - will build all the individual packages within "packages/".
- `npm run dev` - watch & serve the v3 examples. A browser window will pop up with links to the examples. If you are working on the library, this is the easiest way to do "functional testing" by using the library in a game environment.
- `npm run dev` - watch & serve the examples. A browser window will pop up with links to the examples. If you are working on the library, this is the easiest way to do "functional testing" by using the library in a game environment.
- `npm run test` - will run the automated tests against the library.

## References
Expand All @@ -220,16 +298,15 @@ Helpful resources used while building this plugin:
- [Simple Stupid Funnel Algorithm](http://digestingduck.blogspot.com/2010/03/simple-stupid-funnel-algorithm.html)
- [Advice on astar heuristics](http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html)

## TODO
## To Dos

- Documentation
- Describe the Tiled process. Adding an object layer, setting snapping, making sure vertices overlap...
- Specific Extensions
- Allow non-square navmesh from Tiled - any convex shape
- Reimplement the autotessalation version of the lib
- Try libtess in quad mode
- Features
- Allow non-square navmesh polygons from Tiled - ideally, any convex shape.
- Reimplement the autotessalation version of the lib & try libtess in quad mode.
- The astar heuristic & cost functions need another pass. They don't always produce the shortest path. Implement incomplete funneling while building the astar path?
- The navmesh assumes any polygon can reach any other polygon. This probably should be extended to put connected polygons into groups like patroljs.
- Better warnings for devs - warn on empty map, warn on disconnected map, warn if polygons are malformed.
- Factor in the layer position / scale / rotation
- Testing
- Check against tilemap that is larger than the screen
- Research
Expand Down
4 changes: 2 additions & 2 deletions tiled-navmesh-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ See the Tiled manual for more information on [objects](http://doc.mapeditor.org/

## Agent Size (Gaps)

Notice the 10px space left around the walls? That gap is because the agent is 20px wide circle. It would get stuck on corners of walls without that gap. Make sure the gaps you leave are a consistent size - you'll need to pass in the size as the third parameter to `navMeshPlugin.buildMeshFromTiled`.
Notice the 10px space left around the walls? That gap is because the agent is 20px wide circle. It would get stuck on corners of walls without that gap. Make sure the gaps you leave are a consistent size - you'll need to pass in the size as the third parameter to `plugin.buildMeshFromTiled`.

If you wanted, you _could_ leave that gap out and write more complicated path following logic for your agents that avoids getting stuck. "Baking" the agent size into nav mesh with these gaps makes the path following logic pretty simple.
If you wanted, you _could_ leave that gap out and write more complicated path following logic for your agents that avoids getting stuck. "Baking" the agent size into nav mesh with these gaps makes the path following logic pretty simple.

0 comments on commit 1aec0c9

Please sign in to comment.