lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
438 lines (389 loc) • 13.7 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2014 Olivier Biot, Jason Oster, Aaron McLeod
* http://www.melonjs.org
*
*/
(function () {
/**
* a Generic Body Object <br>
* @class
* @extends me.Rect
* @memberOf me
* @constructor
* @param {me.Entity} entity the parent entity
* @param {me.Polygon[]|me.Line[]|me.Ellipse[]} [shapes] the initial list of shapes
*/
me.Body = me.Rect.extend(
/** @scope me.Body.prototype */
{
/** @ignore */
init : function (entity, shapes) {
/**
* reference to the parent entity
* @ignore
*/
this.entity = entity;
/**
* The collision shapes of the entity <br>
* @ignore
* @type {me.Polygon[]|me.Line[]|me.Ellipse[]}
* @name shapes
* @memberOf me.Body
*/
this.shapes = shapes || [];
/**
* The body collision mask, that defines what should collide with what.<br>
* (by default will collide with all entities)
* @ignore
* @type Number
* @name collisionMask
* @see me.collision.types
* @memberOf me.Body
*/
this.collisionMask = me.collision.types.ALL_OBJECT;
/**
* define the collision type of the body for collision filtering
* @public
* @type Number
* @name collisionType
* @see me.collision.types
* @memberOf me.Body
* @example
* // set the entity body collision type
* myEntity.body.collisionType = me.collision.types.PLAYER_OBJECT;
*/
this.collisionType = me.collision.types.ENEMY_OBJECT;
/**
* entity current velocity<br>
* @public
* @type me.Vector2d
* @name vel
* @memberOf me.Body
*/
if (typeof(this.vel) === "undefined") {
this.vel = new me.Vector2d();
}
this.vel.set(0, 0);
/**
* entity current acceleration<br>
* @public
* @type me.Vector2d
* @name accel
* @memberOf me.Body
*/
if (typeof(this.accel) === "undefined") {
this.accel = new me.Vector2d();
}
this.accel.set(0, 0);
/**
* entity current friction<br>
* @public
* @name friction
* @memberOf me.Body
*/
if (typeof(this.friction) === "undefined") {
this.friction = new me.Vector2d();
}
this.friction.set(0, 0);
/**
* max velocity (to limit entity velocity)<br>
* @public
* @type me.Vector2d
* @name maxVel
* @memberOf me.Body
*/
if (typeof(this.maxVel) === "undefined") {
this.maxVel = new me.Vector2d();
}
this.maxVel.set(1000, 1000);
/**
* Default gravity value of the entity<br>
* default value : 0.98 (earth gravity)<br>
* to be set to 0 for RPG, shooter, etc...<br>
* Note: Gravity can also globally be defined through me.sys.gravity
* @public
* @see me.sys.gravity
* @type Number
* @name gravity
* @memberOf me.Body
*/
this.gravity = typeof(me.sys.gravity) !== "undefined" ? me.sys.gravity : 0.98;
/**
* falling state of the object<br>
* true if the object is falling<br>
* false if the object is standing on something<br>
* @readonly
* @public
* @type Boolean
* @name falling
* @memberOf me.Body
*/
this.falling = false;
/**
* jumping state of the object<br>
* equal true if the entity is jumping<br>
* @readonly
* @public
* @type Boolean
* @name jumping
* @memberOf me.Body
*/
this.jumping = true;
// call the super constructor
this._super(
me.Rect,
// bounds the body by default
// to the parent entity
"init", [
0,
0,
entity.width,
entity.height
]
);
},
/**
* add a collision shape to this entity <br>
* (note: me.Rect objects will be converted to me.Polygon before being added)
* @name addShape
* @memberOf me.Body
* @public
* @function
* @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object
* @return {Number} the shape array length
*/
addShape : function (shape) {
if (shape.shapeType === "Rectangle") {
// ensure that rect shape are managed as polygon
this.shapes.push(shape.toPolygon());
} else {
// else polygon or circle
this.shapes.push(shape);
}
// update the body bounds to take in account the added shape
this.updateBounds();
// return the length of the shape list
return this.shapes.length;
},
/**
* return the collision shape at the given index
* @name getShape
* @memberOf me.Body
* @public
* @function
* @param {Number} [index=0] the shape object at the specified index
* @return {me.Polygon|me.Line|me.Ellipse} shape a shape object
*/
getShape : function (index) {
return this.shapes[index || 0];
},
/**
* remove the specified shape from the body shape list
* @name removeShape
* @memberOf me.Body
* @public
* @function
* @param {me.Polygon|me.Line|me.Ellipse} shape a shape object
* @return {Number} the shape array length
*/
removeShape : function (shape) {
this.shapes.remove(shape);
// update the body bounds to take in account the removed shape
this.updateBounds();
// return the length of the shape list
return this.shapes.length;
},
/**
* remove the shape at the given index from the body shape list
* @name removeShapeAt
* @memberOf me.Body
* @public
* @function
* @param {Number} index the shape object at the specified index
* @return {Number} the shape array length
*/
removeShapeAt : function (index) {
return this.removeShape(this.getShape(index));
},
/**
* By default all entities are able to collide with all other entities, <br>
* but it's also possible to specificy 'collision filters' to provide a finer <br>
* control over which entities can collide with each other.
* @name setCollisionMask
* @memberOf me.Body
* @public
* @function
* @see me.collision.types
* @param {Number} bitmask the collision mask
* @example
* // filter collision detection with collision shapes, enemies and collectables
* myEntity.body.setCollisionMask(me.collision.types.WORLD_SHAPE | me.collision.types.ENEMY_OBJECT | me.collision.types.COLLECTABLE_OBJECT);
* ...
* // disable collision detection with all other objects
* myEntity.body.setCollisionMask(me.collision.types.NO_OBJECT);
*/
setCollisionMask : function (bitmask) {
this.collisionMask = bitmask;
},
/**
* the built-in function to solve the collision response
* @protected
* @name respondToCollision
* @memberOf me.Body
* @function
* @param {me.collision.ResponseObject} response the collision response object
*/
respondToCollision: function (response) {
// the overlap vector
var overlap = response.overlapV;
// FIXME: Respond proportionally to object mass
// Move out of the other object shape
this.entity.pos.sub(overlap);
// adjust velocity
if (overlap.x !== 0) {
this.vel.x = ~~(0.5 + this.vel.x - overlap.x) || 0;
}
if (overlap.y !== 0) {
this.vel.y = ~~(0.5 + this.vel.y - overlap.y) || 0;
// cancel the falling an jumping flags if necessary
this.falling = overlap.y >= 1;
this.jumping = overlap.y <= -1;
}
// update the other entity bounds
this.entity.updateBounds();
},
/**
* update the body bounding rect (private)
* the body rect size is here used to cache the total bounding rect
* @protected
* @name updateBounds
* @memberOf me.Body
* @function
*/
updateBounds : function () {
if (this.shapes.length > 0) {
// reset the rect with default values
var _bounds = this.shapes[0].getBounds();
this.pos.setV(_bounds.pos);
this.resize(_bounds.width, _bounds.height);
for (var i = 1 ; i < this.shapes.length; i++) {
this.union(this.shapes[i].getBounds());
}
}
// update the parent entity bounds
this.entity.updateBounds();
return this;
},
/**
* set the entity default velocity<br>
* note : velocity is by default limited to the same value, see
* setMaxVelocity if needed<br>
* @name setVelocity
* @memberOf me.Body
* @function
* @param {Number} x velocity on x axis
* @param {Number} y velocity on y axis
* @protected
*/
setVelocity : function (x, y) {
this.accel.x = x !== 0 ? x : this.accel.x;
this.accel.y = y !== 0 ? y : this.accel.y;
// limit by default to the same max value
this.setMaxVelocity(x, y);
},
/**
* cap the entity velocity to the specified value<br>
* @name setMaxVelocity
* @memberOf me.Body
* @function
* @param {Number} x max velocity on x axis
* @param {Number} y max velocity on y axis
* @protected
*/
setMaxVelocity : function (x, y) {
this.maxVel.x = x;
this.maxVel.y = y;
},
/**
* set the entity default friction<br>
* @name setFriction
* @memberOf me.Body
* @function
* @param {Number} x horizontal friction
* @param {Number} y vertical friction
* @protected
*/
setFriction : function (x, y) {
this.friction.x = x || 0;
this.friction.y = y || 0;
},
/**
* compute the new velocity value
* @ignore
*/
computeVelocity : function (vel) {
// apply gravity (if any)
if (this.gravity) {
// apply a constant gravity (if not on a ladder)
vel.y += this.gravity * me.timer.tick;
// check if falling / jumping
this.falling = (vel.y > 0);
this.jumping = (this.falling ? false : this.jumping);
}
// apply friction
if (this.friction.x) {
vel.x = me.utils.applyFriction(vel.x, this.friction.x);
}
if (this.friction.y) {
vel.y = me.utils.applyFriction(vel.y, this.friction.y);
}
// cap velocity
if (vel.y !== 0) {
vel.y = vel.y.clamp(-this.maxVel.y, this.maxVel.y);
}
if (vel.x !== 0) {
vel.x = vel.x.clamp(-this.maxVel.x, this.maxVel.x);
}
},
/**
* update the body position
* @name update
* @memberOf me.Body
* @function
* @return {boolean} true if resulting velocity is different than 0
*/
update : function (/* dt */) {
// update the velocity
this.computeVelocity(this.vel);
// update player entity position
this.entity.pos.add(this.vel);
// update the entity and body bounds
this.entity.updateBounds();
// returns true if vel is different from 0
return (this.vel.x !== 0 || this.vel.y !== 0);
},
/**
* Destroy function<br>
* @ignore
*/
destroy : function () {
this.entity = null;
this.shapes = [];
}
});
/**
* Base class for Body exception handling.
* @name Error
* @class
* @memberOf me.Body
* @constructor
* @param {String} msg Error message.
*/
me.Body.Error = me.Error.extend({
init : function (msg) {
this._super(me.Error, "init", [ msg ]);
this.name = "me.Body.Error";
}
});
})();