lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
571 lines (507 loc) • 20.1 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2013, Olivier BIOT
* http://www.melonjs.org
*
*/
(function (TMXConstants) {
/**
* a generic Color Layer Object
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {String} name layer name
* @param {String} color a CSS color value
* @param {Number} z z position
*/
me.ColorLayer = me.Renderable.extend({
// constructor
init: function (name, color, z) {
// parent constructor
this._super(me.Renderable, "init", [0, 0, Infinity, Infinity]);
// apply given parameters
this.name = name;
this.color = color;
this.z = z;
this.floating = true;
},
/**
* draw the color layer
* @ignore
*/
draw : function (renderer, rect) {
// set layer opacity
var _alpha = renderer.globalAlpha();
renderer.setGlobalAlpha(_alpha * this.getOpacity());
var vpos = me.game.viewport.pos;
var color = renderer.getColor();
renderer.setColor(this.color);
renderer.fillRect(
rect.left - vpos.x, rect.top - vpos.y,
rect.width, rect.height
);
renderer.setColor(color);
// restore context alpha value
renderer.setGlobalAlpha(_alpha);
}
});
/**
* a generic Image Layer Object
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {String} name layer name
* @param {Number} width layer width in pixels
* @param {Number} height layer height in pixels
* @param {String} image image name (as defined in the asset list)
* @param {Number} z z position
* @param {me.Vector2d} [ratio=1.0] scrolling ratio to be applied
*/
me.ImageLayer = me.Renderable.extend({
/**
* constructor
* @ignore
* @function
*/
init: function (name, width, height, imagesrc, z, ratio) {
// layer name
this.name = name;
// get the corresponding image (throw an exception if not found)
this.image = (imagesrc) ? me.loader.getImage(me.utils.getBasename(imagesrc)) : null;
if (!this.image) {
throw new me.Error("'" + imagesrc + "' file for Image Layer '" + this.name + "' not found!");
}
this.imagewidth = this.image.width;
this.imageheight = this.image.height;
// a cached reference to the viewport
var viewport = me.game.viewport;
// set layer width & height
width = (width ? Math.min(viewport.width, width) : viewport.width);
height = (height ? Math.min(viewport.height, height) : viewport.height);
this._super(me.Renderable, "init", [0, 0, width, height]);
// displaying order
this.z = z;
/**
* Define the image scrolling ratio<br>
* Scrolling speed is defined by multiplying the viewport delta position (e.g. followed entity) by the specified ratio<br>
* Default value : (1.0, 1.0) <br>
* To specify a value through Tiled, use one of the following format : <br>
* - a number, to change the value for both axis <br>
* - a json expression like `json:{"x":0.5,"y":0.5}` if you wish to specify a different value for both x and y
* @public
* @type me.Vector2d
* @name me.ImageLayer#ratio
*/
this.ratio = new me.Vector2d(1.0, 1.0);
if (typeof(ratio) !== "undefined") {
// little hack for backward compatiblity
if (typeof(ratio) === "number") {
this.ratio.set(ratio, ratio);
} else /* vector */ {
this.ratio.setV(ratio);
}
}
// last position of the viewport
this.lastpos = viewport.pos.clone();
// Image Layer is considered as a floating object
this.floating = true;
// default value for repeat
this._repeat = "repeat";
this.repeatX = true;
this.repeatY = true;
/**
* Define if and how an Image Layer should be repeated.<br>
* By default, an Image Layer is repeated both vertically and horizontally.<br>
* Property values : <br>
* * 'repeat' - The background image will be repeated both vertically and horizontally. (default) <br>
* * 'repeat-x' - The background image will be repeated only horizontally.<br>
* * 'repeat-y' - The background image will be repeated only vertically.<br>
* * 'no-repeat' - The background-image will not be repeated.<br>
* @public
* @type String
* @name me.ImageLayer#repeat
*/
Object.defineProperty(this, "repeat", {
get : function get() {
return this._repeat;
},
set : function set(val) {
this._repeat = val;
switch (this._repeat) {
case "no-repeat" :
this.repeatX = false;
this.repeatY = false;
break;
case "repeat-x" :
this.repeatX = true;
this.repeatY = false;
break;
case "repeat-y" :
this.repeatX = false;
this.repeatY = true;
break;
default : // "repeat"
this.repeatX = true;
this.repeatY = true;
break;
}
}
});
// default origin position
this.anchorPoint.set(0, 0);
// register to the viewport change notification
this.handle = me.event.subscribe(me.event.VIEWPORT_ONCHANGE, this.updateLayer.bind(this));
},
/**
* updateLayer function
* @ignore
* @function
*/
updateLayer : function (vpos) {
if (0 === this.ratio.x && 0 === this.ratio.y) {
// static image
return;
}
else if (this.repeatX || this.repeatY) {
// parallax / scrolling image
this.pos.x += ((vpos.x - this.lastpos.x) * this.ratio.x) % this.imagewidth;
this.pos.x = (this.imagewidth + this.pos.x) % this.imagewidth;
this.pos.y += ((vpos.y - this.lastpos.y) * this.ratio.y) % this.imageheight;
this.pos.y = (this.imageheight + this.pos.y) % this.imageheight;
}
else {
this.pos.x += (vpos.x - this.lastpos.x) * this.ratio.x;
this.pos.y += (vpos.y - this.lastpos.y) * this.ratio.y;
}
this.lastpos.setV(vpos);
},
/**
* draw the image layer
* @ignore
*/
draw : function (renderer, rect) {
// translate default position using the anchorPoint value
var viewport = me.game.viewport;
var shouldTranslate = this.anchorPoint.y !== 0 || this.anchorPoint.x !== 0;
var translateX = ~~(this.anchorPoint.x * (viewport.width - this.imagewidth));
var translateY = ~~(this.anchorPoint.y * (viewport.height - this.imageheight));
if (shouldTranslate) {
renderer.translate(translateX, translateY);
}
// set the layer alpha value
renderer.setGlobalAlpha(renderer.globalAlpha() * this.getOpacity());
var sw, sh;
// if not scrolling ratio define, static image
if (0 === this.ratio.x && 0 === this.ratio.y) {
// static image
sw = Math.min(rect.width, this.imagewidth);
sh = Math.min(rect.height, this.imageheight);
renderer.drawImage(
this.image,
rect.left, rect.top, // sx, sy
sw, sh, // sw, sh
rect.left, rect.top, // dx, dy
sw, sh // dw, dh
);
}
// parallax / scrolling image
else {
var sx = ~~this.pos.x;
var sy = ~~this.pos.y;
var dx = 0;
var dy = 0;
sw = Math.min(this.imagewidth - sx, this.width);
sh = Math.min(this.imageheight - sy, this.height);
do {
do {
renderer.drawImage(
this.image,
sx, sy, // sx, sy
sw, sh,
dx, dy, // dx, dy
sw, sh
);
sy = 0;
dy += sh;
sh = Math.min(this.imageheight, this.height - dy);
} while (this.repeatY && (dy < this.height));
dx += sw;
if (!this.repeatX || (dx >= this.width)) {
// done ("end" of the viewport)
break;
}
// else update required var for next iteration
sx = 0;
sw = Math.min(this.imagewidth, this.width - dx);
sy = ~~this.pos.y;
dy = 0;
sh = Math.min(this.imageheight - ~~this.pos.y, this.height);
} while (true);
}
if (shouldTranslate) {
renderer.translate(-translateX, -translateY);
}
},
// called when the layer is destroyed
destroy : function () {
// cancel the event subscription
if (this.handle) {
me.event.unsubscribe(this.handle);
this.handle = null;
}
// clear all allocated objects
this.image = null;
this.lastpos = null;
}
});
/**
* a TMX Tile Layer Object
* Tiled QT 0.7.x format
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
* @param {String} orientation "isometric" or "orthogonal"
* @param {me.TMXTilesetGroup} tilesets tileset as defined in Tiled
* @param {Number} zOrder layer z-order
*/
me.TMXLayer = me.Renderable.extend({
/** @ignore */
init: function (tilewidth, tileheight, orientation, tilesets, zOrder) {
// super constructor
this._super(me.Renderable, "init", [0, 0, 0, 0]);
// tile width & height
this.tilewidth = tilewidth;
this.tileheight = tileheight;
// layer orientation
this.orientation = orientation;
/**
* The Layer corresponding Tilesets
* @public
* @type me.TMXTilesetGroup
* @name me.TMXLayer#tilesets
*/
this.tilesets = tilesets;
// the default tileset
this.tileset = (this.tilesets ? this.tilesets.getTilesetByIndex(0) : null);
/**
* All animated tilesets in this layer
* @public
* @type Array
* @name me.TMXLayer#animatedTilesets
*/
this.animatedTilesets = [];
if (this.tilesets) {
var tileset = this.tilesets.tilesets;
for (var i = 0; i < tileset.length; i++) {
if (tileset[i].isAnimated) {
tileset[i].isAnimated = false;
this.animatedTilesets.push(tileset[i]);
}
}
}
/**
* Layer contains tileset animations
* @public
* @type Boolean
* @name me.TMXLayer#isAnimated
*/
this.isAnimated = this.animatedTilesets.length > 0;
// Force pre-render off when tileset animation is used
if (this.isAnimated) {
this.preRender = false;
}
// for displaying order
this.z = zOrder;
},
/** @ignore */
initFromJSON: function (layer) {
// additional TMX flags
this.name = layer[TMXConstants.TMX_TAG_NAME];
this.cols = +layer[TMXConstants.TMX_TAG_WIDTH];
this.rows = +layer[TMXConstants.TMX_TAG_HEIGHT];
// layer opacity
var visible = typeof(layer[TMXConstants.TMX_TAG_VISIBLE]) !== "undefined" ? layer[TMXConstants.TMX_TAG_VISIBLE] : true;
this.setOpacity(visible ? +layer[TMXConstants.TMX_TAG_OPACITY] : 0);
// layer "real" size
if (this.orientation === "isometric") {
this.width = (this.cols + this.rows) * (this.tilewidth / 2);
this.height = (this.cols + this.rows) * (this.tileheight / 2);
} else {
this.width = this.cols * this.tilewidth;
this.height = this.rows * this.tileheight;
}
// check if we have any user-defined properties
me.TMXUtils.applyTMXProperties(this, layer);
// check for the correct rendering method
if (typeof (this.preRender) === "undefined") {
this.preRender = me.sys.preRender;
}
// if pre-rendering method is use, create the offline canvas
// TODO: this is really tied to the canvas api. need to abstract it.
if (this.preRender === true) {
this.layerCanvas = me.video.createCanvas(this.cols * this.tilewidth, this.rows * this.tileheight);
this.layerSurface = me.CanvasRenderer.getContext2d(this.layerCanvas);
}
// initialize the layer data array
this.initArray(this.cols, this.rows);
},
/**
* destroy function
* @ignore
* @function
*/
destroy : function () {
// clear all allocated objects
if (this.preRender) {
this.layerCanvas = null;
this.layerSurface = null;
}
this.renderer = null;
// clear all allocated objects
this.layerData = null;
this.tileset = null;
this.tilesets = null;
this.animatedTilesets = null;
},
/**
* set the layer renderer
* @ignore
*/
setRenderer : function (renderer) {
this.renderer = renderer;
},
/**
* Create all required arrays
* @ignore
*/
initArray : function (w, h) {
// initialize the array
this.layerData = [];
for (var x = 0; x < w; x++) {
this.layerData[x] = [];
for (var y = 0; y < h; y++) {
this.layerData[x][y] = null;
}
}
},
/**
* Return the TileId of the Tile at the specified position
* @name getTileId
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x x coordinate in pixel
* @param {Number} y y coordinate in pixel
* @return {Number} TileId
*/
getTileId : function (x, y) {
var tile = this.getTile(x, y);
return (tile ? tile.tileId : null);
},
/**
* Return the Tile object at the specified position
* @name getTile
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x x coordinate in pixel
* @param {Number} y y coordinate in pixel
* @return {me.Tile} Tile Object
*/
getTile : function (x, y) {
return this.layerData[~~this.renderer.pixelToTileX(x, y)][~~this.renderer.pixelToTileY(y, x)];
},
/**
* Create a new Tile at the specified position
* @name setTile
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x x coordinate in tile
* @param {Number} y y coordinate in tile
* @param {Number} tileId tileId
* @return {me.Tile} the corresponding newly created tile object
*/
setTile : function (x, y, tileId) {
var tile = new me.Tile(x, y, this.tilewidth, this.tileheight, tileId);
if (!this.tileset.contains(tile.tileId)) {
tile.tileset = this.tileset = this.tilesets.getTilesetByGid(tile.tileId);
}
else {
tile.tileset = this.tileset;
}
this.layerData[x][y] = tile;
return tile;
},
/**
* clear the tile at the specified position
* @name clearTile
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x x position
* @param {Number} y y position
*/
clearTile : function (x, y) {
// clearing tile
this.layerData[x][y] = null;
// erase the corresponding area in the canvas
if (this.preRender) {
this.layerSurface.clearRect(x * this.tilewidth, y * this.tileheight, this.tilewidth, this.tileheight);
}
},
/**
* update animations in a tileset layer
* @ignore
*/
update : function (dt) {
if (this.isAnimated) {
var result = false;
for (var i = 0; i < this.animatedTilesets.length; i++) {
result = this.animatedTilesets[i].update(dt) || result;
}
return result;
}
return false;
},
/**
* draw a tileset layer
* @ignore
*/
draw : function (renderer, rect) {
// use the offscreen canvas
if (this.preRender) {
var width = Math.min(rect.width, this.width);
var height = Math.min(rect.height, this.height);
this.layerSurface.globalAlpha = renderer.globalAlpha() * this.getOpacity();
if (this.layerSurface.globalAlpha > 0) {
// draw using the cached canvas
renderer.drawImage(
this.layerCanvas,
rect.pos.x, rect.pos.y, // sx,sy
width, height, // sw,sh
rect.pos.x, rect.pos.y, // dx,dy
width, height // dw,dh
);
}
}
// dynamically render the layer
else {
// set the layer alpha value
var _alpha = renderer.globalAlpha();
renderer.setGlobalAlpha(renderer.globalAlpha() * this.getOpacity());
if (renderer.globalAlpha() > 0) {
// draw the layer
this.renderer.drawTileLayer(renderer, this, rect);
}
// restore context to initial state
renderer.setGlobalAlpha(_alpha);
}
}
});
})(me.TMXConstants);