UNPKG

lettuce

Version:

Lettuce JS, Mini Mobile Framework for Romantic with DSL.

571 lines (507 loc) 20.1 kB
/* * 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);