mirror of
https://github.com/photonstorm/phaser
synced 2024-11-22 04:33:31 +00:00
Experimented with a PathFollower Game Object Component.
This commit is contained in:
parent
c94404986c
commit
ce1c83491d
3 changed files with 487 additions and 8 deletions
418
src/gameobjects/components/Path.js
Normal file
418
src/gameobjects/components/Path.js
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
/**
|
||||||
|
* @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 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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings for a PathFollower.
|
||||||
|
*
|
||||||
|
* @typedef {object} PathConfig
|
||||||
|
*
|
||||||
|
* @property {number} duration - The duration of the path follow.
|
||||||
|
* @property {number} from - The start position of the path follow, between 0 and 1.
|
||||||
|
* @property {number} to - The end position of the path follow, between 0 and 1.
|
||||||
|
* @property {boolean} [positionOnPath=false] - Whether to position the PathFollower on the Path using its path offset.
|
||||||
|
* @property {boolean} [rotateToPath=false] - Should the PathFollower automatically rotate to point in the direction of the Path?
|
||||||
|
* @property {number} [rotationOffset=0] - If the PathFollower is rotating to match the Path, 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.
|
||||||
|
* @property {boolean} [verticalAdjust=false] - [description]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides properties and methods used to allow a Game Object to follow a Path automatically.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Transform
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
var Path = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Path this PathFollower is following. It can only follow one Path at a time.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#path
|
||||||
|
* @type {Phaser.Curves.Path}
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
path: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the PathFollower automatically rotate to point in the direction of the Path?
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#rotateToPath
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
rotateToPath: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#pathRotationVerticalAdjust
|
||||||
|
* @type {boolean}
|
||||||
|
* @default false
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathRotationVerticalAdjust: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the PathFollower is rotating to match the Path (@see Phaser.GameObjects.Components.Path#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.Components.Path#pathRotationOffset
|
||||||
|
* @type {number}
|
||||||
|
* @default 0
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathRotationOffset: 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An additional vector to add to the PathFollowers position, allowing you to offset it from the
|
||||||
|
* Path coordinates.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#pathOffset
|
||||||
|
* @type {Phaser.Math.Vector2}
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathOffset: {
|
||||||
|
|
||||||
|
factory: function ()
|
||||||
|
{
|
||||||
|
return new Vector2();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#pathVector
|
||||||
|
* @type {Phaser.Math.Vector2}
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathVector: {
|
||||||
|
|
||||||
|
factory: function ()
|
||||||
|
{
|
||||||
|
return new Vector2();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tween used for following the Path.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#pathTween
|
||||||
|
* @type {Phaser.Tweens.Tween}
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathTween: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings for the PathFollower.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#pathConfig
|
||||||
|
* @type {?PathConfig}
|
||||||
|
* @default null
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
pathConfig: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the direction of the follower so it can change direction.
|
||||||
|
*
|
||||||
|
* @name Phaser.GameObjects.Components.Path#_prevDirection
|
||||||
|
* @type {integer}
|
||||||
|
* @private
|
||||||
|
* @since 3.12.0
|
||||||
|
*/
|
||||||
|
_prevDirection: TWEEN_CONST.PLAYING_FORWARD,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Path that this PathFollower should follow.
|
||||||
|
*
|
||||||
|
* Optionally accepts {@link PathConfig} settings.
|
||||||
|
*
|
||||||
|
* @method Phaser.GameObjects.Components.Path#setPath
|
||||||
|
* @since 3.12.0
|
||||||
|
*
|
||||||
|
* @param {Phaser.Curves.Path} path - The Path this PathFollower is following. It can only follow one Path at a time.
|
||||||
|
* @param {PathConfig} [config] - Settings for the PathFollower.
|
||||||
|
*
|
||||||
|
* @return {this} 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;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the PathFollower should automatically rotate to point in the direction of the Path.
|
||||||
|
*
|
||||||
|
* @method Phaser.GameObjects.Components.Path#setRotateToPath
|
||||||
|
* @since 3.12.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.
|
||||||
|
* @param {boolean} [verticalAdjust=false] - [description]
|
||||||
|
*
|
||||||
|
* @return {this} 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.Components.Path#isFollowing
|
||||||
|
* @since 3.12.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.Path#startFollow
|
||||||
|
* @since 3.12.0
|
||||||
|
*
|
||||||
|
* @param {(number|PathConfig)} [config={}] - The duration of the follow, or a PathFollower config object.
|
||||||
|
* @param {number} [startAt=0] - Optional start position of the follow, between 0 and 1.
|
||||||
|
*
|
||||||
|
* @return {this} 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.Components.Path#pauseFollow
|
||||||
|
* @since 3.12.0
|
||||||
|
*
|
||||||
|
* @return {this} 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.Components.Path#resumeFollow
|
||||||
|
* @since 3.12.0
|
||||||
|
*
|
||||||
|
* @return {this} 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.Components.Path#stopFollow
|
||||||
|
* @since 3.12.0
|
||||||
|
*
|
||||||
|
* @return {this} 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.Components.Path#preUpdate
|
||||||
|
* @protected
|
||||||
|
* @since 3.12.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 = Path;
|
|
@ -20,6 +20,7 @@ module.exports = {
|
||||||
GetBounds: require('./GetBounds'),
|
GetBounds: require('./GetBounds'),
|
||||||
Mask: require('./Mask'),
|
Mask: require('./Mask'),
|
||||||
Origin: require('./Origin'),
|
Origin: require('./Origin'),
|
||||||
|
Path: require('./Path'),
|
||||||
Pipeline: require('./Pipeline'),
|
Pipeline: require('./Pipeline'),
|
||||||
ScaleMode: require('./ScaleMode'),
|
ScaleMode: require('./ScaleMode'),
|
||||||
ScrollFactor: require('./ScrollFactor'),
|
ScrollFactor: require('./ScrollFactor'),
|
||||||
|
|
|
@ -5,7 +5,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var GameObjectFactory = require('../GameObjectFactory');
|
var GameObjectFactory = require('../GameObjectFactory');
|
||||||
var PathFollower = require('./PathFollower');
|
var Path = require('../components/Path');
|
||||||
|
var Sprite = require('../sprite/Sprite');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [description]
|
||||||
|
*
|
||||||
|
* @function hasGetterOrSetter
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {object} def - The object to check.
|
||||||
|
*
|
||||||
|
* @return {boolean} True if it has a getter or setter, otherwise false.
|
||||||
|
*/
|
||||||
|
function hasGetterOrSetter (def)
|
||||||
|
{
|
||||||
|
return def && def.constructor === Object && ((!!def.get && typeof def.get === 'function') || (!!def.set && typeof def.set === 'function'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new PathFollower Game Object and adds it to the Scene.
|
* Creates a new PathFollower Game Object and adds it to the Scene.
|
||||||
|
@ -16,21 +32,65 @@ var PathFollower = require('./PathFollower');
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*
|
*
|
||||||
* @param {Phaser.Curves.Path} path - The Path this PathFollower is connected to.
|
* @param {Phaser.Curves.Path} path - The Path this PathFollower is connected to.
|
||||||
* @param {number} x - The horizontal position of this Game Object in the world.
|
* @param {Phaser.GameObjects.GameObject|number} x - The GameObject to augment, or the horizontal position of this Game Object in the world.
|
||||||
* @param {number} y - The vertical 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} [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.
|
* @param {(string|integer)} [frame] - An optional frame from the Texture this Game Object is rendering with.
|
||||||
*
|
*
|
||||||
* @return {Phaser.GameObjects.PathFollower} The Game Object that was created.
|
* @return {Phaser.GameObjects.PathFollower} The Game Object that was created.
|
||||||
*/
|
*/
|
||||||
GameObjectFactory.register('follower', function (path, x, y, key, frame)
|
GameObjectFactory.register('follower', function (path, x, y, key, frame)
|
||||||
{
|
{
|
||||||
var sprite = new PathFollower(this.scene, path, x, y, key, frame);
|
var gameObject = x;
|
||||||
|
|
||||||
this.displayList.add(sprite);
|
// Create a sprite if x is not an object
|
||||||
this.updateList.add(sprite);
|
if (typeof x !== 'object')
|
||||||
|
{
|
||||||
|
gameObject = new Sprite(this.scene, x, y, key, frame);
|
||||||
|
}
|
||||||
|
|
||||||
return sprite;
|
// Mixin the Path component
|
||||||
|
var mixins = [
|
||||||
|
Path
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i = 0; i < mixins.length; i++)
|
||||||
|
{
|
||||||
|
var mixin = mixins[i];
|
||||||
|
|
||||||
|
for (var m in mixin)
|
||||||
|
{
|
||||||
|
if (typeof mixin[m] === 'function')
|
||||||
|
{
|
||||||
|
Object.defineProperty(gameObject, m, {value: mixin[m]});
|
||||||
|
}
|
||||||
|
else if (mixin[m] && typeof mixin[m].factory === 'function')
|
||||||
|
{
|
||||||
|
gameObject[m] = mixin[m].factory();
|
||||||
|
}
|
||||||
|
else if (hasGetterOrSetter(mixin[m]))
|
||||||
|
{
|
||||||
|
Object.defineProperty(gameObject, key, {
|
||||||
|
get: mixin[key].get,
|
||||||
|
set: mixin[key].set
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gameObject[m] = mixin[m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the given arguments as properties
|
||||||
|
gameObject.path = path;
|
||||||
|
gameObject.pathOffset.set(x, y);
|
||||||
|
|
||||||
|
// Add to the display list and update list
|
||||||
|
this.displayList.add(gameObject);
|
||||||
|
this.updateList.add(gameObject);
|
||||||
|
|
||||||
|
return gameObject;
|
||||||
});
|
});
|
||||||
|
|
||||||
// When registering a factory function 'this' refers to the GameObjectFactory context.
|
// When registering a factory function 'this' refers to the GameObjectFactory context.
|
||||||
|
|
Loading…
Reference in a new issue