phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.
428 lines (357 loc) • 13.2 kB
JavaScript
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2018 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
var Class = require('../../utils/Class');
var DegToRad = require('../../math/DegToRad');
var GetBoolean = require('../../tweens/builders/GetBoolean');
var GetValue = require('../../utils/object/GetValue');
var Sprite = require('../sprite/Sprite');
var TWEEN_CONST = require('../../tweens/tween/const');
var Vector2 = require('../../math/Vector2');
/**
* @typedef {object} PathConfig
*
* @property {number} duration - [description]
* @property {number} from - [description]
* @property {number} to - [description]
* @property {boolean} [positionOnPath=false] - [description]
* @property {boolean} [rotateToPath=false] - [description]
* @property {number} [rotationOffset=0] - [description]
* @property {boolean} [verticalAdjust=false] - [description]
*/
/**
* @classdesc
* A PathFollower Game Object.
*
* A PathFollower is a Sprite Game Object with some extra helpers to allow it to follow a Path automatically.
*
* Anything you can do with a standard Sprite can be done with this PathFollower, such as animate it, tint it,
* scale it and so on.
*
* PathFollowers are bound to a single Path at any one time and can traverse the length of the Path, from start
* to finish, forwards or backwards, or from any given point on the Path to its end. They can optionally rotate
* to face the direction of the path, be offset from the path coordinates or rotate independently of the Path.
*
* @class PathFollower
* @extends Phaser.GameObjects.Sprite
* @memberOf Phaser.GameObjects
* @constructor
* @since 3.0.0
*
* @param {Phaser.Scene} scene - [description]
* @param {Phaser.Curves.Path} path - The Path this PathFollower is following. It can only follow one Path at a time.
* @param {number} x - The horizontal position of this Game Object in the world.
* @param {number} y - The vertical position of this Game Object in the world.
* @param {string} texture - The key of the Texture this Game Object will use to render with, as stored in the Texture Manager.
* @param {(string|integer)} [frame] - An optional frame from the Texture this Game Object is rendering with.
*/
var PathFollower = new Class({
Extends: Sprite,
initialize:
function PathFollower (scene, path, x, y, texture, frame)
{
Sprite.call(this, scene, x, y, texture, frame);
/**
* The Path this PathFollower is following. It can only follow one Path at a time.
*
* @name Phaser.GameObjects.PathFollower#path
* @type {Phaser.Curves.Path}
* @since 3.0.0
*/
this.path = path;
/**
* Should the PathFollower automatically rotate to point in the direction of the Path?
*
* @name Phaser.GameObjects.PathFollower#rotateToPath
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.rotateToPath = false;
/**
* [description]
*
* @name Phaser.GameObjects.PathFollower#pathRotationVerticalAdjust
* @type {boolean}
* @default false
* @since 3.0.0
*/
this.pathRotationVerticalAdjust = false;
/**
* If the PathFollower is rotating to match the Path (@see Phaser.GameObjects.PathFollower#rotateToPath)
* this value is added to the rotation value. This allows you to rotate objects to a path but control
* the angle of the rotation as well.
*
* @name Phaser.GameObjects.PathFollower#pathRotationOffset
* @type {number}
* @default 0
* @since 3.0.0
*/
this.pathRotationOffset = 0;
/**
* An additional vector to add to the PathFollowers position, allowing you to offset it from the
* Path coordinates.
*
* @name Phaser.GameObjects.PathFollower#pathOffset
* @type {Phaser.Math.Vector2}
* @since 3.0.0
*/
this.pathOffset = new Vector2(x, y);
/**
* [description]
*
* @name Phaser.GameObjects.PathFollower#pathVector
* @type {Phaser.Math.Vector2}
* @since 3.0.0
*/
this.pathVector = new Vector2();
/**
* [description]
*
* @name Phaser.GameObjects.PathFollower#pathTween
* @type {Phaser.Tweens.Tween}
* @since 3.0.0
*/
this.pathTween;
/**
* [description]
*
* @name Phaser.GameObjects.PathFollower#pathConfig
* @type {?PathConfig}
* @default null
* @since 3.0.0
*/
this.pathConfig = null;
/**
* Records the direction of the follower so it can change direction.
*
* @name Phaser.GameObjects.PathFollower#_prevDirection
* @type {integer}
* @private
* @since 3.0.0
*/
this._prevDirection = TWEEN_CONST.PLAYING_FORWARD;
},
/**
* [description]
*
* @method Phaser.GameObjects.PathFollower#setPath
* @since 3.0.0
*
* @param {Phaser.Curves.Path} path - The Path this PathFollower is following. It can only follow one Path at a time.
* @param {PathConfig} [config] - [description]
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
setPath: function (path, config)
{
if (config === undefined) { config = this.pathConfig; }
var tween = this.pathTween;
if (tween && tween.isPlaying())
{
tween.stop();
}
this.path = path;
if (config)
{
this.startFollow(config);
}
return this;
},
/**
* [description]
*
* @method Phaser.GameObjects.PathFollower#setRotateToPath
* @since 3.0.0
*
* @param {number} value - [description]
* @param {number} [offset=0] - Rotation offset in degrees.
* @param {boolean} [verticalAdjust=false] - [description]
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
setRotateToPath: function (value, offset, verticalAdjust)
{
if (offset === undefined) { offset = 0; }
if (verticalAdjust === undefined) { verticalAdjust = false; }
this.rotateToPath = value;
this.pathRotationOffset = offset;
this.pathRotationVerticalAdjust = verticalAdjust;
return this;
},
/**
* Is this PathFollower actively following a Path or not?
* To be considered as `isFollowing` it must be currently moving on a Path, and not paused.
*
* @method Phaser.GameObjects.PathFollower#isFollowing
* @since 3.0.0
*
* @return {boolean} `true` is this PathFollower is actively following a Path, otherwise `false`.
*/
isFollowing: function ()
{
var tween = this.pathTween;
return (tween && tween.isPlaying());
},
/**
* Starts this PathFollower following its given Path.
*
* @method Phaser.GameObjects.PathFollower#startFollow
* @since 3.3.0
*
* @param {(number|PathConfig)} [config={}] - [description]
* @param {number} [startAt=0] - [description]
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
startFollow: function (config, startAt)
{
if (config === undefined) { config = {}; }
if (startAt === undefined) { startAt = 0; }
var tween = this.pathTween;
if (tween && tween.isPlaying())
{
tween.stop();
}
if (typeof config === 'number')
{
config = { duration: config };
}
// Override in case they've been specified in the config
config.from = 0;
config.to = 1;
// Can also read extra values out of the config:
var positionOnPath = GetBoolean(config, 'positionOnPath', false);
this.rotateToPath = GetBoolean(config, 'rotateToPath', false);
this.pathRotationOffset = GetValue(config, 'rotationOffset', 0);
this.pathRotationVerticalAdjust = GetBoolean(config, 'verticalAdjust', false);
this.pathTween = this.scene.sys.tweens.addCounter(config);
// The starting point of the path, relative to this follower
this.path.getStartPoint(this.pathOffset);
if (positionOnPath)
{
this.x = this.pathOffset.x;
this.y = this.pathOffset.y;
}
this.pathOffset.x = this.x - this.pathOffset.x;
this.pathOffset.y = this.y - this.pathOffset.y;
this._prevDirection = TWEEN_CONST.PLAYING_FORWARD;
if (this.rotateToPath)
{
// Set the rotation now (in case the tween has a delay on it, etc)
var nextPoint = this.path.getPoint(0.1);
this.rotation = Math.atan2(nextPoint.y - this.y, nextPoint.x - this.x) + DegToRad(this.pathRotationOffset);
}
this.pathConfig = config;
return this;
},
/**
* Pauses this PathFollower. It will still continue to render, but it will remain motionless at the
* point on the Path at which you paused it.
*
* @method Phaser.GameObjects.PathFollower#pauseFollow
* @since 3.3.0
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
pauseFollow: function ()
{
var tween = this.pathTween;
if (tween && tween.isPlaying())
{
tween.pause();
}
return this;
},
/**
* Resumes a previously paused PathFollower.
* If the PathFollower was not paused this has no effect.
*
* @method Phaser.GameObjects.PathFollower#resumeFollow
* @since 3.3.0
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
resumeFollow: function ()
{
var tween = this.pathTween;
if (tween && tween.isPaused())
{
tween.resume();
}
return this;
},
/**
* Stops this PathFollower from following the path any longer.
* This will invoke any 'stop' conditions that may exist on the Path, or for the follower.
*
* @method Phaser.GameObjects.PathFollower#stopFollow
* @since 3.3.0
*
* @return {Phaser.GameObjects.PathFollower} This Game Object.
*/
stopFollow: function ()
{
var tween = this.pathTween;
if (tween && tween.isPlaying())
{
tween.stop();
}
return this;
},
/**
* Internal update handler that advances this PathFollower along the path.
* Called automatically by the Scene step, should not typically be called directly.
*
* @method Phaser.GameObjects.PathFollower#preUpdate
* @protected
* @since 3.0.0
*
* @param {integer} time - The current timestamp as generated by the Request Animation Frame or SetTimeout.
* @param {number} delta - The delta time, in ms, elapsed since the last frame.
*/
preUpdate: function (time, delta)
{
this.anims.update(time, delta);
var tween = this.pathTween;
if (tween)
{
var tweenData = tween.data[0];
if (tweenData.state !== TWEEN_CONST.PLAYING_FORWARD && tweenData.state !== TWEEN_CONST.PLAYING_BACKWARD)
{
// If delayed, etc then bail out
return;
}
var pathVector = this.pathVector;
this.path.getPoint(tween.getValue(), pathVector);
pathVector.add(this.pathOffset);
var oldX = this.x;
var oldY = this.y;
this.setPosition(pathVector.x, pathVector.y);
var speedX = this.x - oldX;
var speedY = this.y - oldY;
if (speedX === 0 && speedY === 0)
{
// Bail out early
return;
}
if (tweenData.state !== this._prevDirection)
{
// We've changed direction, so don't do a rotate this frame
this._prevDirection = tweenData.state;
return;
}
if (this.rotateToPath)
{
this.rotation = Math.atan2(speedY, speedX) + DegToRad(this.pathRotationOffset);
if (this.pathRotationVerticalAdjust)
{
this.flipY = (this.rotation !== 0 && tweenData.state === TWEEN_CONST.PLAYING_BACKWARD);
}
}
}
}
});
module.exports = PathFollower;