lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
521 lines (465 loc) • 16.8 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2013, Olivier BIOT
* http://www.melonjs.org
*
*/
(function () {
// some ref shortcut
var MIN = Math.min, MAX = Math.max;
/**
* a camera/viewport Object
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {Number} minX start x offset
* @param {Number} minY start y offset
* @param {Number} maxX end x offset
* @param {Number} maxY end y offset
*/
me.Viewport = me.Renderable.extend(
/** @scope me.Viewport.prototype */ {
/** @ignore */
init : function (minX, minY, maxX, maxY) {
/**
* Axis definition :<br>
* <p>
* AXIS.NONE<br>
* AXIS.HORIZONTAL<br>
* AXIS.VERTICAL<br>
* AXIS.BOTH
* </p>
* @public
* @constant
* @type enum
* @name AXIS
* @memberOf me.Viewport
*/
this.AXIS = {
NONE : 0,
HORIZONTAL : 1,
VERTICAL : 2,
BOTH : 3
};
/**
* Camera bounds
* @public
* @constant
* @type me.Rect
* @name bounds
* @memberOf me.Viewport
*/
this.bounds = null;
// camera deadzone
this.deadzone = null;
// target to follow
this.target = null;
// axis to follow
this.follow_axis = 0;
// shake parameters
this._shake = null;
// fade parameters
this._fadeIn = null;
this._fadeOut = null;
// cache the screen rendering position
this.screenX = 0;
this.screenY = 0;
// viewport coordinates
this._super(me.Renderable, "init", [minX, minY, maxX - minX, maxY - minY]);
// real world limits
this.bounds = new me.Rect(-Infinity, -Infinity, Infinity, Infinity);
// offset for shake effect
this.offset = new me.Vector2d();
// target to follow
this.target = null;
// default value follow
this.follow_axis = this.AXIS.NONE;
// shake variables
this._shake = {
intensity : 0,
duration : 0,
axis : this.AXIS.BOTH,
onComplete : null
};
// flash variables
this._fadeOut = {
color : null,
duration : 0,
tween : null
};
// fade variables
this._fadeIn = {
color : null,
duration : 0,
tween : null
};
// set a default deadzone
this.setDeadzone(this.width / 6, this.height / 6);
},
// -- some private function ---
/** @ignore */
_followH : function (target) {
var _x = this.pos.x;
if ((target.x - this.pos.x) > (this.deadzone.right)) {
this.pos.x = ~~MIN((target.x) - (this.deadzone.right), this.bounds.width - this.width);
}
else if ((target.x - this.pos.x) < (this.deadzone.pos.x)) {
this.pos.x = ~~MAX((target.x) - this.deadzone.pos.x, this.bounds.pos.x);
}
return (_x !== this.pos.x);
},
/** @ignore */
_followV : function (target) {
var _y = this.pos.y;
if ((target.y - this.pos.y) > (this.deadzone.bottom)) {
this.pos.y = ~~MIN((target.y) - (this.deadzone.bottom), this.bounds.height - this.height);
}
else if ((target.y - this.pos.y) < (this.deadzone.pos.y)) {
this.pos.y = ~~MAX((target.y) - this.deadzone.pos.y, this.bounds.pos.y);
}
return (_y !== this.pos.y);
},
// -- public function ---
/**
* reset the viewport to specified coordinates
* @name reset
* @memberOf me.Viewport
* @function
* @param {Number} [x=0]
* @param {Number} [y=0]
*/
reset : function (x, y) {
// reset the initial viewport position to 0,0
this.pos.x = x || 0;
this.pos.y = y || 0;
// reset the target
this.target = null;
// reset default axis value for follow
this.follow_axis = null;
},
/**
* change the deadzone settings.
* the "deadzone" defines an area within the current viewport in which
* the followed entity can move without scrolling the viewport.
* @name setDeadzone
* @see me.Viewport.follow
* @memberOf me.Viewport
* @function
* @param {Number} w deadzone width
* @param {Number} h deadzone height
*/
setDeadzone : function (w, h) {
if (this.deadzone === null) {
this.deadzone = new me.Rect(0, 0, 0, 0);
}
// reusing the old code for now...
this.deadzone.pos.set(
~~((this.width - w) / 2),
~~((this.height - h) / 2 - h * 0.25)
);
this.deadzone.resize(w, h);
// force a camera update
this.updateTarget();
},
/**
* set the viewport boundaries (set to the world limit by default).
* the viewport is bound to the given coordinates and cannot move/be scrolled outside of it.
* @name setBounds
* @memberOf me.Viewport
* @function
* @param {Number} x world left limit
* @param {Number} y world top limit
* @param {Number} w world width limit
* @param {Number} h world height limit
*/
setBounds : function (x, y, w, h) {
this.bounds.pos.set(x, y);
this.bounds.resize(w, h);
},
/**
* set the viewport to follow the specified entity
* @name follow
* @memberOf me.Viewport
* @function
* @param {me.Entity|me.Vector2d} target Entity or Position
* Vector to follow
* @param {me.Viewport#AXIS} [axis=AXIS.BOTH] Which axis to follow
*/
follow : function (target, axis) {
if (target instanceof me.Entity) {
this.target = target.pos;
}
else if (target instanceof me.Vector2d) {
this.target = target;
}
else {
throw new me.Renderable.Error("invalid target for viewport.follow");
}
// if axis is null, camera is moved on target center
this.follow_axis = (
typeof(axis) === "undefined" ? this.AXIS.BOTH : axis
);
// force a camera update
this.updateTarget();
},
/**
* move the viewport position by the specified offset
* @name move
* @memberOf me.Viewport
* @function
* @param {Number} x
* @param {Number} y
* @example
* // Move the viewport up by four pixels
* me.game.viewport.move(0, -4);
*/
move : function (x, y) {
this.moveTo(~~(this.pos.x + x), ~~(this.pos.y + y));
},
/**
* move the viewport to the specified coordinates
* @name moveTo
* @memberOf me.Viewport
* @function
* @param {Number} x
* @param {Number} y
*/
moveTo : function (x, y) {
this.pos.x = (~~x).clamp(
this.bounds.pos.x,
this.bounds.width - this.width
);
this.pos.y = (~~y).clamp(
this.bounds.pos.y,
this.bounds.height - this.height
);
//publish the corresponding message
me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]);
},
/** @ignore */
updateTarget : function () {
var updated = false;
if (this.target) {
switch (this.follow_axis) {
case this.AXIS.NONE:
//this.focusOn(this.target);
break;
case this.AXIS.HORIZONTAL:
updated = this._followH(this.target);
break;
case this.AXIS.VERTICAL:
updated = this._followV(this.target);
break;
case this.AXIS.BOTH:
updated = this._followH(this.target);
updated = this._followV(this.target) || updated;
break;
default:
break;
}
}
return updated;
},
/** @ignore */
update : function (dt) {
var updated = this.updateTarget();
if (this._shake.duration > 0) {
this._shake.duration -= dt;
if (this._shake.duration <= 0) {
this._shake.duration = 0;
this.offset.setZero();
if (typeof(this._shake.onComplete) === "function") {
this._shake.onComplete();
}
}
else {
if (this._shake.axis === this.AXIS.BOTH ||
this._shake.axis === this.AXIS.HORIZONTAL) {
this.offset.x = (Math.random() - 0.5) * this._shake.intensity;
}
if (this._shake.axis === this.AXIS.BOTH ||
this._shake.axis === this.AXIS.VERTICAL) {
this.offset.y = (Math.random() - 0.5) * this._shake.intensity;
}
}
// updated!
updated = true;
}
if (updated === true) {
//publish the corresponding message
me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]);
}
// check for fade/flash effect
if ((this._fadeIn.tween != null) || (this._fadeOut.tween != null)) {
updated = true;
}
return updated;
},
/**
* shake the camera
* @name shake
* @memberOf me.Viewport
* @function
* @param {Number} intensity maximum offset that the screen can be moved
* while shaking
* @param {Number} duration expressed in milliseconds
* @param {me.Viewport#AXIS} [axis=AXIS.BOTH] specify on which axis you
* want the shake effect (AXIS.HORIZONTAL, AXIS.VERTICAL, AXIS.BOTH)
* @param {Function} [onComplete] callback once shaking effect is over
* @example
* // shake it baby !
* me.game.viewport.shake(10, 500, me.game.viewport.AXIS.BOTH);
*/
shake : function (intensity, duration, axis, onComplete) {
if (this._shake.duration > 0) {
return;
}
this._shake = {
intensity : intensity,
duration : duration,
axis : axis || this.AXIS.BOTH,
onComplete : onComplete
};
},
/**
* fadeOut(flash) effect<p>
* screen is filled with the specified color and slowly goes back to normal
* @name fadeOut
* @memberOf me.Viewport
* @function
* @param {me.Color|String} color a CSS color value
* @param {Number} [duration=1000] expressed in milliseconds
* @param {Function} [onComplete] callback once effect is over
*/
fadeOut : function (color, duration, onComplete) {
this._fadeOut.color = me.pool.pull("me.Color").copy(color);
this._fadeOut.color.alpha = 1.0;
this._fadeOut.duration = duration || 1000; // convert to ms
this._fadeOut.tween = me.pool.pull("me.Tween", this._fadeOut.color)
.to({ alpha: 0.0 }, this._fadeOut.duration)
.onComplete(onComplete || null);
this._fadeOut.tween.start();
},
/**
* fadeIn effect <p>
* fade to the specified color
* @name fadeIn
* @memberOf me.Viewport
* @function
* @param {me.Color|String} color a CSS color value
* @param {Number} [duration=1000] expressed in milliseconds
* @param {Function} [onComplete] callback once effect is over
*/
fadeIn : function (color, duration, onComplete) {
this._fadeIn.color = me.pool.pull("me.Color").copy(color);
this._fadeIn.color.alpha = 0.0;
this._fadeIn.duration = duration || 1000; //convert to ms
this._fadeIn.tween = me.pool.pull("me.Tween", this._fadeIn.color)
.to({ alpha: 1.0 }, this._fadeIn.duration)
.onComplete(onComplete || null);
this._fadeIn.tween.start();
},
/**
* return the viewport width
* @name getWidth
* @memberOf me.Viewport
* @function
* @return {Number}
*/
getWidth : function () {
return this.width;
},
/**
* return the viewport height
* @name getHeight
* @memberOf me.Viewport
* @function
* @return {Number}
*/
getHeight : function () {
return this.height;
},
/**
* set the viewport position around the specified object
* @name focusOn
* @memberOf me.Viewport
* @function
* @param {me.Renderable}
*/
focusOn : function (target) {
var bounds = target.getBounds();
this.moveTo(
target.pos.x + bounds.pos.x + (bounds.width / 2),
target.pos.y + bounds.pos.y + (bounds.height / 2)
);
},
/**
* check if the specified rectangle is in the viewport
* @name isVisible
* @memberOf me.Viewport
* @function
* @param {me.Rect} rect
* @return {Boolean}
*/
isVisible : function (rect) {
return rect.overlaps(this);
},
/**
* convert the given "local" (screen) coordinates into world coordinates
* @name localToWorld
* @memberOf me.Viewport
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} [v] an optional vector object where to set the
* converted value
* @return {me.Vector2d}
*/
localToWorld : function (x, y, v) {
v = v || new me.Vector2d();
return (v.set(x, y)).add(this.pos).sub(me.game.currentLevel.pos);
},
/**
* convert the given world coordinates into "local" (screen) coordinates
* @name worldToLocal
* @memberOf me.Viewport
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} [v] an optional vector object where to set the
* converted value
* @return {me.Vector2d}
*/
worldToLocal : function (x, y, v) {
v = v || new me.Vector2d();
return (v.set(x, y)).sub(this.pos).add(me.game.currentLevel.pos);
},
/**
* render the camera effects
* @ignore
*/
draw : function () {
// fading effect
if (this._fadeIn.tween) {
me.video.renderer.clearSurface(null, this._fadeIn.color);
// remove the tween if over
if (this._fadeIn.color.alpha === 1.0) {
this._fadeIn.tween = null;
me.pool.push(this._fadeIn.color);
this._fadeIn.color = null;
}
}
// flashing effect
if (this._fadeOut.tween) {
me.video.renderer.clearSurface(null, this._fadeOut.color);
// remove the tween if over
if (this._fadeOut.color.alpha === 0.0) {
this._fadeOut.tween = null;
me.pool.push(this._fadeOut.color);
this._fadeOut.color = null;
}
}
}
});
})();