/*global FM*/
/**
* The simple path component can make a game object follow a predefined path.
* @class FM.SimplePathComponent
* @extends FM.Component
* @param {FM.GameObject} pOwner The game object that owns this component.
* @constructor
* @author Simon Chauvin
*/
FM.SimplePathComponent = function (pOwner) {
"use strict";
//Calling the constructor of FM.Component
FM.Component.call(this, FM.ComponentTypes.PATHFINDING, pOwner);
/**
* Waypoints constituing the path.
* @type Array
* @private
*/
this.waypoints = [];
/**
* Current index of the waypoint to reach.
* @type int
* @private
*/
this.currentIndex = 0;
/**
* Speed at which the game object should follow the path if it is a
* movement with a coefficient equals to 1.
* @type FM.Vector
* @private
*/
this.desiredSpeed = 0;
/**
* Speed at which the game object follow the path.
* @type FM.Vector
* @private
*/
this.actualSpeed = new FM.Vector(0, 0);
/**
* Whether the path is being followed or not.
* @type boolean
* @private
*/
this.active = false;
/**
* Whether the desired x position of the current waypoint was reached.
* or not.
* @type boolean
* @private
*/
this.xReached = false;
/**
* Whether the desired y position of the current waypoint was reached.
* or not.
* @type boolean
* @private
*/
this.yReached = false;
/**
* Position before stopping following the path, used to know if the game
* object following the path has been moved during the stopping time.
* @type FM.Vector
* @private
*/
this.positionBeforeStopping = new FM.Vector(0, 0);
/**
* Spatial component reference.
* @type FM.SpatialComponent
* @private
*/
this.spatial = pOwner.components[FM.ComponentTypes.SPATIAL];
/**
* Physic component reference.
* @type FM.PhysicComponent
* @private
*/
this.physic = pOwner.components[FM.ComponentTypes.PHYSIC];
//Check if the needed components are present.
if (FM.Parameters.debug) {
if (!this.spatial) {
console.log("ERROR: No spatial component was added and you need one for using the path component.");
}
if (!this.physic) {
console.log("ERROR: No physic component was added and you need one for using the path component.");
}
}
};
/**
* FM.SimplePathComponent inherits from FM.Component.
*/
FM.SimplePathComponent.prototype = Object.create(FM.Component.prototype);
FM.SimplePathComponent.prototype.constructor = FM.SimplePathComponent;
/**
* Follow the specified path.
* @method FM.SimplePathComponent#startFollowingPath
* @memberOf FM.SimplePathComponent
* @param {int} pSpeed Speed at which the game object must follow the path.
* @param {int} pIndexToStartFrom The index at which the game object should start
* following the path.
*/
FM.SimplePathComponent.prototype.startFollowingPath = function (pSpeed, pIndexToStartFrom) {
"use strict";
if (this.waypoints.length > 0) {
this.active = true;
this.currentIndex = pIndexToStartFrom || 0;
this.xReached = false;
this.yReached = false;
this.desiredSpeed = pSpeed;
//Adjust speed so that the movement is linear
var xDiff = Math.abs((this.spatial.position.x + this.physic.offset.x + this.physic.width / 2) - this.waypoints[this.currentIndex].x),
yDiff = Math.abs((this.spatial.position.y + this.physic.offset.y + this.physic.height / 2) - this.waypoints[this.currentIndex].y),
coeff;
if (xDiff < yDiff) {
coeff = xDiff / yDiff;
this.actualSpeed.x = this.desiredSpeed * coeff;
this.actualSpeed.y = this.desiredSpeed;
} else if (xDiff > yDiff) {
coeff = yDiff / xDiff;
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed * coeff;
} else {
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed;
}
} else if (FM.Parameters.debug) {
console.log("WARNING: path with no waypoints defined.");
}
if (!this.physic) {
console.log("WARNING: path added to a game object with no physic component.");
}
};
/**
* Continue following the current path where it had stopped.
* @method FM.SimplePathComponent#resumeFollowingPath
* @memberOf FM.SimplePathComponent
*/
FM.SimplePathComponent.prototype.resumeFollowingPath = function () {
"use strict";
if (this.waypoints.length > 0) {
this.active = true;
if (this.positionBeforeStopping.x !== this.spatial.position.x
|| this.positionBeforeStopping.y !== this.spatial.position.y) {
this.xReached = false;
this.yReached = false;
//Adjust speed so that the movement is linear
var xDiff = Math.abs((this.spatial.position.x + this.physic.offset.x + this.physic.width / 2) - this.waypoints[this.currentIndex].x),
yDiff = Math.abs((this.spatial.position.y + this.physic.offset.y + this.physic.height / 2) - this.waypoints[this.currentIndex].y),
coeff;
if (xDiff < yDiff) {
coeff = xDiff / yDiff;
this.actualSpeed.x = this.desiredSpeed * coeff;
this.actualSpeed.y = this.desiredSpeed;
} else if (xDiff > yDiff) {
coeff = yDiff / xDiff;
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed * coeff;
} else {
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed;
}
}
} else if (FM.Parameters.debug) {
console.log("WARNING: path with no waypoints defined.");
}
if (!this.physic) {
console.log("WARNING: path added to a game object with no physic component.");
}
};
/**
* Stop following the current path.
* @method FM.SimplePathComponent#stopFollowingPath
* @memberOf FM.SimplePathComponent
*/
FM.SimplePathComponent.prototype.stopFollowingPath = function () {
"use strict";
this.active = false;
this.physic.velocity.x = 0;
this.physic.velocity.y = 0;
this.positionBeforeStopping = new FM.Vector(this.spatial.position.x, this.spatial.position.y);
};
/**
* Erase every waypoints in the path.
* @method FM.SimplePathComponent#clearPath
* @memberOf FM.SimplePathComponent
*/
FM.SimplePathComponent.prototype.clearPath = function () {
"use strict";
this.waypoints = [];
};
/**
* Update the component.
* @method FM.SimplePathComponent#update
* @memberOf FM.SimplePathComponent
* @param {float} dt Fixed delta time in seconds since the last frame.
*/
FM.SimplePathComponent.prototype.update = function (dt) {
"use strict";
//Update the motion if the path is active
if (this.active && this.physic) {
//Update motion whether a physic component is present or not
var xPos = this.spatial.position.x + this.physic.offset.x + this.physic.width / 2,
yPos = this.spatial.position.y + this.physic.offset.y + this.physic.height / 2,
xDiff,
yDiff,
coeff;
//Update x position
if (xPos < this.waypoints[this.currentIndex].x) {
if (this.waypoints[this.currentIndex].x - xPos < this.actualSpeed.x * dt) {
this.physic.velocity.x = this.waypoints[this.currentIndex].x - xPos;
this.xReached = true;
} else {
this.physic.velocity.x = this.actualSpeed.x;
}
} else if (xPos > this.waypoints[this.currentIndex].x) {
if (xPos - this.waypoints[this.currentIndex].x < this.actualSpeed.x * dt) {
this.physic.velocity.x = xPos - this.waypoints[this.currentIndex].x;
this.xReached = true;
} else {
this.physic.velocity.x = -this.actualSpeed.x;
}
} else {
this.xReached = true;
this.physic.velocity.x = 0;
}
//Update y position
if (yPos < this.waypoints[this.currentIndex].y) {
if (this.waypoints[this.currentIndex].y - yPos < this.actualSpeed.y * dt) {
this.physic.velocity.y = this.waypoints[this.currentIndex].y - yPos;
this.yReached = true;
} else {
this.physic.velocity.y = this.actualSpeed.y;
}
} else if (yPos > this.waypoints[this.currentIndex].y) {
if (yPos - this.waypoints[this.currentIndex].y < this.actualSpeed.y * dt) {
this.physic.velocity.y = yPos - this.waypoints[this.currentIndex].y;
this.yReached = true;
} else {
this.physic.velocity.y = -this.actualSpeed.y;
}
} else {
this.yReached = true;
this.physic.velocity.y = 0;
}
//Select the next waypoint if the current has been reached
if (this.xReached && this.yReached) {
if (this.waypoints.length > this.currentIndex + 1) {
//TODO call startfollowingpath ??
this.xReached = false;
this.yReached = false;
this.currentIndex = this.currentIndex + 1;
//Adjust speed so that the movement is linear
xDiff = Math.abs(xPos - this.waypoints[this.currentIndex].x);
yDiff = Math.abs(yPos - this.waypoints[this.currentIndex].y);
if (xDiff < yDiff) {
coeff = xDiff / yDiff;
this.actualSpeed.x = this.desiredSpeed * coeff;
this.actualSpeed.y = this.desiredSpeed;
} else if (xDiff > yDiff) {
coeff = yDiff / xDiff;
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed * coeff;
} else {
this.actualSpeed.x = this.desiredSpeed;
this.actualSpeed.y = this.desiredSpeed;
}
} else {
this.active = false;
this.actualSpeed = new FM.Vector(0, 0);
this.desiredSpeed = 0;
this.physic.velocity = new FM.Vector(0, 0);
}
}
}
};
/**
* Add a waypoint to the path.
* @method FM.SimplePathComponent#add
* @memberOf FM.SimplePathComponent
* @param {int} pX X position.
* @param {int} pY Y position.
* @param {int} index Optional index at which adding the waypoint.
*/
FM.SimplePathComponent.prototype.add = function (pX, pY, index) {
"use strict";
if (!index) {
this.waypoints.push({x : pX, y : pY});
} else {
this.waypoints[index] = {x : pX, y : pY};
}
};
/**
* Remove a waypoint from the path.
* @method FM.SimplePathComponent#remove
* @memberOf FM.SimplePathComponent
* @param {int} pIndex Index of the waypoint to remove.
*/
FM.SimplePathComponent.prototype.remove = function (pIndex) {
"use strict";
this.waypoints.splice(pIndex, 1);
};
/**
* Return the waypoints of the path.
* @method FM.SimplePathComponent#getWaypoints
* @memberOf FM.SimplePathComponent
* @return {Array} Waypoints of the path.
*/
FM.SimplePathComponent.prototype.getWaypoints = function () {
"use strict";
return this.waypoints;
};
/**
* Return the current index of the waypoint to reach.
* @method FM.SimplePathComponent#getCurrentIndex
* @memberOf FM.SimplePathComponent
* @return {int} Index of the waypoint to reach.
*/
FM.SimplePathComponent.prototype.getCurrentIndex = function () {
"use strict";
return this.currentIndex;
};
/**
* Return the current waypoint to reach.
* @method FM.SimplePathComponent#getCurrentWaypoint
* @memberOf FM.SimplePathComponent
* @return {Object} Waypoint to reach, a literal with a x and y property.
*/
FM.SimplePathComponent.prototype.getCurrentWaypoint = function () {
"use strict";
return this.waypoints[this.currentIndex];
};
/**
* Return the number of waypoints.
* @method FM.SimplePathComponent#getLength
* @memberOf FM.SimplePathComponent
* @return {int} Number of waypoints.
*/
FM.SimplePathComponent.prototype.getLength = function () {
"use strict";
return this.waypoints.length;
};
/**
* Check if the last waypoint has been reached.
* @method FM.SimplePathComponent#isLastWaypointReached
* @memberOf FM.SimplePathComponent
* @return {boolean} Whether the last waypoint has been reached or not.
*/
FM.SimplePathComponent.prototype.isLastWaypointReached = function () {
"use strict";
return this.currentIndex === this.waypoints.length - 1 && !this.active;
};
/**
* Check if the path is being followed.
* @method FM.SimplePathComponent#isActive
* @memberOf FM.SimplePathComponent
* @return {boolean} Whether the path is being followed.
*/
FM.SimplePathComponent.prototype.isActive = function () {
"use strict";
return this.active;
};
/**
* Destroy the path.
* @method FM.SimplePathComponent#destroy
* @memberOf FM.SimplePathComponent
*/
FM.SimplePathComponent.prototype.destroy = function () {
"use strict";
this.waypoints = null;
this.active = null;
this.positionBeforeStopping.destroy();
this.positionBeforeStopping = null;
this.currentIndex = null;
this.actualSpeed.destroy();
this.actualSpeed = null;
this.desiredSpeed = null;
this.xReached = null;
this.yReached = null;
this.spatial = null;
this.physic = null;
FM.Component.prototype.destroy.call(this);
};