/** * @author Richard Davey * @author Pete Baron * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * PathManager controls a list of Paths and a list of PathFollowers. * It is the central control for the majority of the Pathing API. * * @class Phaser.Plugin.PathManager * @constructor * @param {Phaser.Game} game - A reference to the current Phaser.Game instance. * @param {Phaser.PluginManager} parent - The Phaser Plugin Manager which looks after this plugin. */ Phaser.Plugin.PathManager = function (game, parent) { Phaser.Plugin.call(this, game, parent); /** * @property {array} _list - list of paths * @private */ this._list = []; /** * @property {array} _followers - list of path followers * @private */ this._followers = []; this._branchRegistry = {}; }; Phaser.Plugin.PathManager.prototype = Object.create(Phaser.Plugin.prototype); Phaser.Plugin.PathManager.prototype.constructor = Phaser.Plugin.PathManager; /** * create a new Path from JSON data * * JSON data format: * required: "coordinateSystem":, "smoothness":, "loops":, "speed":, "pointList":[ {"x":, "y":}, ... ] * optional: "branchFrom": { "path":, "point": }, "joinTo": { "path":, "point": } */ Phaser.Plugin.PathManager.prototype.createPathsFromJSON = function(jsonKey) { var parse = this.game.cache.getJSON(jsonKey); var path; var createdPaths = []; var branchList = []; parse.paths.forEach(function(config) { path = new Phaser.Path(config.coordinateSystem, config.loops); path.name = config.name; this.addPoints(path, config.pointList, config.speed); this._list.push(path); createdPaths.push(path); config.pointList.reduce(function(list, pnt, index) { if (pnt.branchType === Phaser.Path.BranchTypes.ATTACHED) { list.push({ path: path.name, branchPath: pnt.branchPath, pointIndex: index, type: pnt.branchType }); } else if (pnt.branchType === Phaser.Path.BranchTypes.JOINED) { list.push({ path: pnt.branchPath, branchPath: path.name, pointIndex: pnt.branchPointIndex, type: pnt.branchType }); } return list; }, branchList); }, this); branchList.forEach(function(branch) { var mainPath = this.findPathByName(branch.path); var branchPath = this.findPathByName(branch.branchPath); var mainPathPointIndex = branch.pointIndex; if (branch.type === Phaser.Path.BranchTypes.ATTACHED) { this.attachBranch(branchPath, mainPath, mainPathPointIndex); } else if (branch.type === Phaser.Path.BranchTypes.JOINED) { this.joinBranch(branchPath, mainPath, mainPathPointIndex, false); } }, this); return createdPaths; }; Phaser.Plugin.PathManager.prototype.addPath = function(path) { // if path has points then addPoints, otherwise don't // this.addPoints(path, parse.pointList, parse.speed); this._list.push(path); return path; }; // create a branching path and attach the start to an existing path // when a PathFollower encounters the attachment point, it will be able to switch onto this new branch // // @param: count {value, optional}, make this branch counted (it won't be taken until a follower has passed it enough times) Phaser.Plugin.PathManager.prototype.attachBranch = function(branchPath, mainPath, mainPathPointIndex, count) { if (typeof mainPath === 'string') { mainPath = this.findPathByName(mainPath); } var branchFromPoint = new Phaser.PathPoint(); if (mainPath.getPathPoint(mainPathPointIndex, branchFromPoint)) { // move the first point of the branchPath to the branchFromPoint location branchPath.origin = branchFromPoint; var branchToPoint = branchPath.getPathPointReference(0); // attach the branch (use point reference so the changes go into the path) var branchFromPointRef = mainPath.getPathPointReference(mainPathPointIndex); this._branchAttach(branchFromPointRef, branchPath, 0); branchFromPointRef.branchType = Phaser.Path.BranchTypes.ATTACHED; // attach the branch's first point back to where it branched off from (for path reversal) branchToPoint.branchPath = mainPath; branchToPoint.branchPointIndex = mainPathPointIndex; // make sure this branch knows that it's using offset coordinates based on the first path point location branchPath.coordinateSystem = Phaser.Path.CoordinateSystems.OFFSET; branchPath.type = Phaser.Path.PathTypes.BRANCH; // set up counted branches data if (count !== undefined) { branchFromPointRef.data = { type: Phaser.PathPoint.DATA_COUNTER, value: count }; } if (this._branchRegistry[branchPath.name]) { this._branchRegistry[branchPath.name].push(branchFromPointRef); } else { this._branchRegistry[branchPath.name] = [branchFromPointRef]; } } }; // attach the end of a path to an existing path // when a PathFollower encounters the attachment point, it will automatically switch onto the attached path Phaser.Plugin.PathManager.prototype.joinBranch = function(branchPath, mainPath, mainPathPointIndex, addPoint) { if (typeof addPoint === 'undefined') { addPoint = true; } if (typeof mainPath === 'string') { mainPath = this.findPathByName(mainPath); } var mainPathJoinPoint, branchLastPoint; mainPathJoinPoint = new Phaser.PathPoint(); mainPath.getPathPoint(mainPathPointIndex, mainPathJoinPoint); if (mainPathJoinPoint) { if (addPoint) { var newBranchPoint = new Phaser.PathPoint(); if (branchPath.getPathPoint(0, newBranchPoint)) { // make sure the newly added last path point is relative to the previously added first path point for the branch path by subtracting it out branchLastPoint = branchPath.addPathPoint(mainPathJoinPoint.x - newBranchPoint.x, mainPathJoinPoint.y - newBranchPoint.y, mainPathJoinPoint.vx, mainPathJoinPoint.vy, 1.0); this._branchAttach(branchLastPoint, mainPath, mainPathPointIndex); } } else { branchLastPoint = branchPath.getPathPointReference(branchPath.length - 1); this._branchAttach(branchLastPoint, mainPath, mainPathPointIndex); } branchLastPoint.branchType = Phaser.Path.BranchTypes.JOINED; } if (this._branchRegistry[branchPath.name]) { this._branchRegistry[branchPath.name].push(branchLastPoint); } else { this._branchRegistry[branchPath.name] = [branchLastPoint]; } }; // internal function, set the branching parameters of a PathPoint Phaser.Plugin.PathManager.prototype._branchAttach = function(attachPoint, branchingPath, branchToPointIndex) { attachPoint.branchPath = branchingPath; attachPoint.branchPointIndex = branchToPointIndex; }; Phaser.Plugin.PathManager.prototype._branchDetach = function(attachedPoint) { attachedPoint.branchPath = null; attachedPoint.branchPointIndex = null; }; Phaser.Plugin.PathManager.prototype.removeBranch = function(branch) { if (typeof branch === 'string') { branch = this.findPathByName(branch); } this._branchRegistry[branch.name].forEach(function(point) { this._branchDetach(point); }, this); this._branchRegistry[branch.name] = null; this.removePath(this.pathIndex(branch)); }; // @return: the Path object which is at 'index' in the list Phaser.Plugin.PathManager.prototype.getPath = function(index) { return this._list[index]; }; // add a list of points to a Path Phaser.Plugin.PathManager.prototype.addPoints = function(path, pointList, speed) { if (speed === undefined) speed = 1.0; for (var i = 0; i < pointList.length; i++) { path.addPathPoint(pointList[i].x, pointList[i].y, pointList[i].vx, pointList[i].vy, speed, pointList[i].data); } return path.numPoints(); }; // @return: the Path object matching 'name' in the list Phaser.Plugin.PathManager.prototype.findPathByName = function(name) { for (var i = 0; i < this._list.length; i++) { if (this._list[i].name == name) { return this._list[i]; } } return null; }; Phaser.Plugin.PathManager.prototype.findPathByPoint = function(point) { var l = this._list.length; for (var i = 0; i < l; i++) { if (this._list[i].pointIndex(point) > -1) { return this._list[i]; } } }; /* * FOLLOWERS * * the following functions control PathFollower objects * */ // create a new PathFollower and add it to the list // @param: physicsAdjustTime - how quickly does a physics object attempt to get back to the path's virtual particle position (milliseconds), 0 = it's not a physics object // @return: the new PathFollower object Phaser.Plugin.PathManager.prototype.addFollower = function(path, follower, speed, rotationOffset, angularOffset, callbackAtEnd, physicsAdjustTime) { var f = new Phaser.PathFollower(path, follower, speed, rotationOffset, angularOffset, callbackAtEnd, physicsAdjustTime); this._followers.push(f); return f; }; // update all PathFollower objects in the _followers list // this will automatically move them along the Paths // was called updateFollowers Phaser.Plugin.PathManager.prototype.update = function() { // move this to a plugin var //var elapsedTime = 1.0; for (var i = this._followers.length - 1; i >= 0; --i) { var f = this._followers[i]; // when a follower's update returns false, kill it if (!f.update(this.game.time.elpased)) { // callback for this follower when it dies if (f.callbackAtEnd) { f.callbackAtEnd(f.follower); } // destroy the follower f.destroy(); // remove the follower from the list this._followers.splice(i, 1); } } }; // remove all PathFollowers on this path without destroying their attached graphic objects // (eg. a long line of enemies use a path to enter, then switch to AI control on arrival maintaining their relative positions) Phaser.Plugin.PathManager.prototype.removeAllFollowers = function(path) { for (var i = this._followers.length - 1; i >= 0; --i) { var f = this._followers[i]; if (f.path == path) { // callback for this follower when it dies if (f.callbackAtEnd) { f.callbackAtEnd(f.follower); } // destroy the follower f.destroy(); // remove the follower from the list this._followers.splice(i, 1); } } }; Phaser.Plugin.PathManager.prototype.pathIndex = function(path) { return this._list.indexOf(path); }; Phaser.Plugin.PathManager.prototype.removePath = function(pathIndex) { this.removeAllFollowers(this.getPath(pathIndex)); if (pathIndex < this._list.length) { return this._list.splice(pathIndex, 1); } else { throw new Error("ERROR: Cannot remove non-existent path"); } }; /* * DEBUG DRAWING OF ALL PATHS */ // draw all paths Phaser.Plugin.PathManager.prototype.drawPaths = function(graphics) { for (var i = 0; i < this._list.length; i++) { this._list[i].debug(graphics); } };