UNPKG

lettuce

Version:

Lettuce JS, Mini Mobile Framework for Romantic with DSL.

438 lines (389 loc) 13.7 kB
/* * 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"; } }); })();