2019-04-08 12:52:44 +00:00
|
|
|
/**
|
|
|
|
* @author Richard Davey <rich@photonstorm.com>
|
2020-01-15 12:07:09 +00:00
|
|
|
* @copyright 2020 Photon Storm Ltd.
|
2019-05-10 15:15:04 +00:00
|
|
|
* @license {@link https://opensource.org/licenses/MIT|MIT License}
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
var DegToRad = require('../../math/DegToRad');
|
|
|
|
var GetBoolean = require('../../tweens/builders/GetBoolean');
|
|
|
|
var GetValue = require('../../utils/object/GetValue');
|
|
|
|
var TWEEN_CONST = require('../../tweens/tween/const');
|
|
|
|
var Vector2 = require('../../math/Vector2');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides methods used for managing a Game Object following a Path.
|
|
|
|
* Should be applied as a mixin and not used directly.
|
|
|
|
*
|
|
|
|
* @namespace Phaser.GameObjects.Components.PathFollower
|
|
|
|
* @since 3.17.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
var PathFollower = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Path this PathFollower is following. It can only follow one Path at a time.
|
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.Components.PathFollower#path
|
|
|
|
* @type {Phaser.Curves.Path}
|
|
|
|
* @since 3.0.0
|
|
|
|
*/
|
|
|
|
path: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Should the PathFollower automatically rotate to point in the direction of the Path?
|
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.Components.PathFollower#rotateToPath
|
|
|
|
* @type {boolean}
|
|
|
|
* @default false
|
|
|
|
* @since 3.0.0
|
|
|
|
*/
|
|
|
|
rotateToPath: 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
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
pathOffset: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A Vector2 that stores the current point of the path the follower is on.
|
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.PathFollower#pathVector
|
|
|
|
* @type {Phaser.Math.Vector2}
|
|
|
|
* @since 3.0.0
|
|
|
|
*/
|
|
|
|
pathVector: null,
|
|
|
|
|
2020-04-17 00:53:11 +00:00
|
|
|
/**
|
2020-04-27 09:13:15 +00:00
|
|
|
* The distance the follower has traveled from the previous point to the current one, at the last update.
|
2020-04-17 00:53:11 +00:00
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.PathFollower#pathDelta
|
|
|
|
* @type {Phaser.Math.Vector2}
|
|
|
|
* @since 3.23.0
|
|
|
|
*/
|
|
|
|
pathDelta: null,
|
|
|
|
|
2019-04-08 12:52:44 +00:00
|
|
|
/**
|
|
|
|
* The Tween used for following the Path.
|
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.PathFollower#pathTween
|
|
|
|
* @type {Phaser.Tweens.Tween}
|
|
|
|
* @since 3.0.0
|
|
|
|
*/
|
|
|
|
pathTween: null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Settings for the PathFollower.
|
|
|
|
*
|
|
|
|
* @name Phaser.GameObjects.PathFollower#pathConfig
|
2019-05-09 10:57:27 +00:00
|
|
|
* @type {?Phaser.Types.GameObjects.PathFollower.PathConfig}
|
2019-04-08 12:52:44 +00:00
|
|
|
* @default null
|
|
|
|
* @since 3.0.0
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
_prevDirection: TWEEN_CONST.PLAYING_FORWARD,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the Path that this PathFollower should follow.
|
|
|
|
*
|
2019-05-09 10:57:27 +00:00
|
|
|
* Optionally accepts {@link Phaser.Types.GameObjects.PathFollower.PathConfig} settings.
|
2019-04-08 12:52:44 +00:00
|
|
|
*
|
|
|
|
* @method Phaser.GameObjects.Components.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.
|
2019-05-28 21:11:28 +00:00
|
|
|
* @param {(number|Phaser.Types.GameObjects.PathFollower.PathConfig|Phaser.Types.Tweens.NumberTweenBuilderConfig)} [config] - Settings for the PathFollower.
|
2019-04-08 12:52:44 +00:00
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set whether the PathFollower should automatically rotate to point in the direction of the Path.
|
|
|
|
*
|
|
|
|
* @method Phaser.GameObjects.Components.PathFollower#setRotateToPath
|
|
|
|
* @since 3.0.0
|
|
|
|
*
|
|
|
|
* @param {boolean} value - Whether the PathFollower should automatically rotate to point in the direction of the Path.
|
|
|
|
* @param {number} [offset=0] - Rotation offset in degrees.
|
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
setRotateToPath: function (value, offset)
|
|
|
|
{
|
|
|
|
if (offset === undefined) { offset = 0; }
|
|
|
|
|
|
|
|
this.rotateToPath = value;
|
|
|
|
|
|
|
|
this.pathRotationOffset = offset;
|
|
|
|
|
|
|
|
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.Components.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.Components.PathFollower#startFollow
|
|
|
|
* @since 3.3.0
|
|
|
|
*
|
2019-05-28 21:11:28 +00:00
|
|
|
* @param {(number|Phaser.Types.GameObjects.PathFollower.PathConfig|Phaser.Types.Tweens.NumberTweenBuilderConfig)} [config={}] - The duration of the follow, or a PathFollower config object.
|
2019-04-08 12:52:44 +00:00
|
|
|
* @param {number} [startAt=0] - Optional start position of the follow, between 0 and 1.
|
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
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 = GetValue(config, 'from', 0);
|
|
|
|
config.to = GetValue(config, 'to', 1);
|
|
|
|
|
|
|
|
var positionOnPath = GetBoolean(config, 'positionOnPath', false);
|
|
|
|
|
|
|
|
this.rotateToPath = GetBoolean(config, 'rotateToPath', false);
|
|
|
|
this.pathRotationOffset = GetValue(config, 'rotationOffset', 0);
|
|
|
|
|
|
|
|
// This works, but it's not an ideal way of doing it as the follower jumps position
|
|
|
|
var seek = GetValue(config, 'startAt', startAt);
|
|
|
|
|
|
|
|
if (seek)
|
|
|
|
{
|
|
|
|
config.onStart = function (tween)
|
|
|
|
{
|
|
|
|
var tweenData = tween.data[0];
|
|
|
|
tweenData.progress = seek;
|
|
|
|
tweenData.elapsed = tweenData.duration * seek;
|
|
|
|
var v = tweenData.ease(tweenData.progress);
|
|
|
|
tweenData.current = tweenData.start + ((tweenData.end - tweenData.start) * v);
|
|
|
|
tweenData.target[tweenData.key] = tweenData.current;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.pathOffset)
|
|
|
|
{
|
|
|
|
this.pathOffset = new Vector2(this.x, this.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.pathVector)
|
|
|
|
{
|
|
|
|
this.pathVector = new Vector2();
|
|
|
|
}
|
|
|
|
|
2020-04-17 00:53:11 +00:00
|
|
|
if (!this.pathDelta)
|
|
|
|
{
|
|
|
|
this.pathDelta = new Vector2();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pathDelta.reset();
|
|
|
|
|
2019-04-08 12:52:44 +00:00
|
|
|
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.Components.PathFollower#pauseFollow
|
|
|
|
* @since 3.3.0
|
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
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.Components.PathFollower#resumeFollow
|
|
|
|
* @since 3.3.0
|
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
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.Components.PathFollower#stopFollow
|
|
|
|
* @since 3.3.0
|
|
|
|
*
|
2020-01-26 12:36:05 +00:00
|
|
|
* @return {this} This Game Object.
|
2019-04-08 12:52:44 +00:00
|
|
|
*/
|
|
|
|
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.Components.PathFollower#pathUpdate
|
|
|
|
* @since 3.17.0
|
|
|
|
*/
|
|
|
|
pathUpdate: function ()
|
|
|
|
{
|
|
|
|
var tween = this.pathTween;
|
|
|
|
|
|
|
|
if (tween)
|
|
|
|
{
|
|
|
|
var tweenData = tween.data[0];
|
2020-04-17 00:53:11 +00:00
|
|
|
var pathDelta = this.pathDelta;
|
2020-01-15 10:42:56 +00:00
|
|
|
var pathVector = this.pathVector;
|
|
|
|
|
2020-04-17 00:53:11 +00:00
|
|
|
pathDelta.copy(pathVector).negate();
|
|
|
|
|
2020-01-16 22:44:26 +00:00
|
|
|
if (tweenData.state === TWEEN_CONST.COMPLETE)
|
2020-01-15 10:42:56 +00:00
|
|
|
{
|
|
|
|
this.path.getPoint(1, pathVector);
|
|
|
|
|
2020-04-17 00:53:11 +00:00
|
|
|
pathDelta.add(pathVector);
|
2020-01-15 10:42:56 +00:00
|
|
|
pathVector.add(this.pathOffset);
|
2020-04-17 00:53:11 +00:00
|
|
|
|
2020-01-15 10:42:56 +00:00
|
|
|
this.setPosition(pathVector.x, pathVector.y);
|
2019-04-08 12:52:44 +00:00
|
|
|
|
2020-01-15 10:42:56 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (tweenData.state !== TWEEN_CONST.PLAYING_FORWARD && tweenData.state !== TWEEN_CONST.PLAYING_BACKWARD)
|
2019-04-08 12:52:44 +00:00
|
|
|
{
|
|
|
|
// If delayed, etc then bail out
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.path.getPoint(tween.getValue(), pathVector);
|
|
|
|
|
2020-04-17 00:53:11 +00:00
|
|
|
pathDelta.add(pathVector);
|
2019-04-08 12:52:44 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = PathFollower;
|