lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
472 lines (424 loc) • 16.3 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2013, Olivier BIOT
* http://www.melonjs.org
*
* Tile QT 0.7.x format
* http://www.mapeditor.org/
*
*/
(function (TMXConstants) {
/*
* Tileset Management
*/
// bitmask constants to check for flipped & rotated tiles
var FlippedHorizontallyFlag = 0x80000000;
var FlippedVerticallyFlag = 0x40000000;
var FlippedAntiDiagonallyFlag = 0x20000000;
/**
* a basic tile object
* @class
* @extends me.Rect
* @memberOf me
* @constructor
* @param {Number} x x index of the Tile in the map
* @param {Number} y y index of the Tile in the map
* @param {Number} w Tile width
* @param {Number} h Tile height
* @param {Number} tileId tileId
*/
me.Tile = me.Rect.extend({
/** @ignore */
init : function (x, y, w, h, gid) {
/**
* tileset
* @public
* @type me.TMXTileset
* @name me.Tile#tileset
*/
this.tileset = null;
/**
* the tile transformation matrix (if defined)
* @ignore
*/
this.transform = null;
this._super(me.Rect, "init", [x * w, y * h, w, h]);
// Tile col / row pos
this.col = x;
this.row = y;
/**
* tileId
* @public
* @type int
* @name me.Tile#tileId
*/
this.tileId = gid;
/**
* True if the tile is flipped horizontally<br>
* @public
* @type Boolean
* @name me.Tile#flipX
*/
this.flippedX = (this.tileId & FlippedHorizontallyFlag) !== 0;
/**
* True if the tile is flipped vertically<br>
* @public
* @type Boolean
* @name me.Tile#flippedY
*/
this.flippedY = (this.tileId & FlippedVerticallyFlag) !== 0;
/**
* True if the tile is flipped anti-diagonally<br>
* @public
* @type Boolean
* @name me.Tile#flippedAD
*/
this.flippedAD = (this.tileId & FlippedAntiDiagonallyFlag) !== 0;
/**
* Global flag that indicates if the tile is flipped<br>
* @public
* @type Boolean
* @name me.Tile#flipped
*/
this.flipped = this.flippedX || this.flippedY || this.flippedAD;
// create a transformation matrix if required
if (this.flipped === true) {
this.createTransform();
}
// clear out the flags and set the tileId
this.tileId &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag | FlippedAntiDiagonallyFlag);
},
/**
* create a transformation matrix for this tilee
* @ignore
*/
createTransform : function () {
if (this.transform === null) {
this.transform = new me.Matrix2d();
}
// reset the matrix (in case it was already defined)
this.transform.identity();
var a = this.transform.val;
if (this.flippedAD) {
// Use shearing to swap the X/Y axis
this.transform.set(0, 1, 1, 0, a[4], a[5]);
this.transform.translate(0, this.height - this.width);
}
if (this.flippedX) {
a[0] *= -1;
a[2] *= -1;
this.transform.translate((this.flippedAD ? this.height : this.width), 0);
}
if (this.flippedY) {
a[1] *= -1;
a[3] *= -1;
this.transform.translate(0, (this.flippedAD ? this.width : this.height));
}
}
});
/**
* a TMX Tile Set Object
* @class
* @memberOf me
* @constructor
*/
me.TMXTileset = Object.extend({
// constructor
init: function (tileset) {
var i = 0;
// first gid
// tile properties (collidable, etc..)
this.TileProperties = [];
// a cache for offset value
this.tileXOffset = [];
this.tileYOffset = [];
this.firstgid = this.lastgid = +tileset[TMXConstants.TMX_TAG_FIRSTGID];
var src = tileset[TMXConstants.TMX_TAG_SOURCE];
if (src && me.utils.getFileExtension(src).toLowerCase() === "tsx") {
// load TSX
src = me.utils.getBasename(src);
// replace tileset with a local variable
tileset = me.loader.getTMX(src).tileset;
if (!tileset) {
throw new me.Error(src + " TSX tileset not found");
}
// normally tileset shoudld directly contains the required
//information : UNTESTED as I did not find how to generate a JSON TSX file
}
this.name = tileset[TMXConstants.TMX_TAG_NAME];
this.tilewidth = +tileset[TMXConstants.TMX_TAG_TILEWIDTH];
this.tileheight = +tileset[TMXConstants.TMX_TAG_TILEHEIGHT];
this.spacing = +tileset[TMXConstants.TMX_TAG_SPACING] || 0;
this.margin = +tileset[TMXConstants.TMX_TAG_MARGIN] || 0;
// set tile offset properties (if any)
this.tileoffset = new me.Vector2d(0, 0);
/**
* Tileset contains animated tiles
* @public
* @type Boolean
* @name me.TMXTileset#isAnimated
*/
this.isAnimated = false;
/**
* Tileset animations
* @private
* @type Object
* @name me.TMXTileset#animations
*/
this.animations = {};
var tiles = tileset.tiles;
if (typeof(tiles) !== "undefined") {
// native JSON format
for (i in tiles) {
if (tiles.hasOwnProperty(i) && ("animation" in tiles[i])) {
this.isAnimated = true;
this.animations[+i + this.firstgid] = {
dt : 0,
idx : 0,
frames : tiles[i].animation,
cur : tiles[i].animation[0]
};
}
}
}
var offset = tileset[TMXConstants.TMX_TAG_TILEOFFSET];
if (offset) {
this.tileoffset.x = +offset[TMXConstants.TMX_TAG_X];
this.tileoffset.y = +offset[TMXConstants.TMX_TAG_Y];
}
// set tile properties, if any
var tileInfo = tileset.tileproperties;
if (tileInfo) {
// native JSON format
for (i in tileInfo) {
if (tileInfo.hasOwnProperty(i)) {
this.setTileProperty(i + this.firstgid, tileInfo[i]);
}
}
}
else if (tileset[TMXConstants.TMX_TAG_TILE]) {
// converted XML format
tileInfo = tileset[TMXConstants.TMX_TAG_TILE];
if (!Array.isArray(tileInfo)) {
tileInfo = [ tileInfo ];
}
// iterate it
for (i = 0; i < tileInfo.length; i++) {
var tileID = +tileInfo[i][TMXConstants.TMX_TAG_ID] + this.firstgid;
var prop = {};
me.TMXUtils.applyTMXProperties(prop, tileInfo[i]);
//apply tiled defined properties
this.setTileProperty(tileID, prop);
// Get animations
if ("animation" in tileInfo[i]) {
this.isAnimated = true;
this.animations[tileID] = {
dt : 0,
idx : 0,
frames : tileInfo[i].animation.frame,
cur : tileInfo[i].animation.frame[0]
};
}
}
}
// check for the texture corresponding image
// manage inconstency between XML and JSON format
var imagesrc = (
typeof(tileset[TMXConstants.TMX_TAG_IMAGE]) === "string" ?
tileset[TMXConstants.TMX_TAG_IMAGE] : tileset[TMXConstants.TMX_TAG_IMAGE].source
);
// extract base name
imagesrc = me.utils.getBasename(imagesrc);
this.image = imagesrc ? me.loader.getImage(imagesrc) : null;
if (!this.image) {
console.log("melonJS: '" + imagesrc + "' file for tileset '" + this.name + "' not found!");
}
else {
// number of tiles per horizontal line
this.hTileCount = ~~((this.image.width - this.margin) / (this.tilewidth + this.spacing));
this.vTileCount = ~~((this.image.height - this.margin) / (this.tileheight + this.spacing));
// compute the last gid value in the tileset
this.lastgid = this.firstgid + (((this.hTileCount * this.vTileCount) - 1) || 0);
// check if transparency is defined for a specific color
var transparency = tileset[TMXConstants.TMX_TAG_TRANS] || tileset[TMXConstants.TMX_TAG_IMAGE][TMXConstants.TMX_TAG_TRANS];
// set Color Key for transparency if needed
if (typeof(transparency) !== "undefined") {
// applyRGB Filter (return a context object)
this.image = me.video.renderer.applyRGBFilter(this.image, "transparent", transparency.toUpperCase()).canvas;
}
}
},
/**
* set the tile properties
* @ignore
* @function
*/
setTileProperty : function (gid, prop) {
// set the given tile id
this.TileProperties[gid] = prop;
},
/**
* return true if the gid belongs to the tileset
* @name me.TMXTileset#contains
* @public
* @function
* @param {Number} gid
* @return {Boolean}
*/
contains : function (gid) {
return gid >= this.firstgid && gid <= this.lastgid;
},
//return an Image Object with the specified tile
getTileImage : function (tmxTile) {
// create a new image object
var _context = me.video.renderer.getContext2d(
me.video.createCanvas(this.tilewidth, this.tileheight)
);
this.drawTile(_context, 0, 0, tmxTile);
return _context.canvas;
},
// e.g. getTileProperty (gid)
/**
* return the properties of the specified tile
* @name me.TMXTileset#getTileProperties
* @public
* @function
* @param {Number} tileId
* @return {Object}
*/
getTileProperties: function (tileId) {
return this.TileProperties[tileId];
},
/**
* return the x offset of the specified tile in the tileset image
* @ignore
*/
getTileOffsetX : function (tileId) {
var offset = this.tileXOffset[tileId];
if (typeof(offset) === "undefined") {
offset = this.tileXOffset[tileId] = this.margin + (this.spacing + this.tilewidth) * (tileId % this.hTileCount);
}
return offset;
},
/**
* return the y offset of the specified tile in the tileset image
* @ignore
*/
getTileOffsetY : function (tileId) {
var offset = this.tileYOffset[tileId];
if (typeof(offset) === "undefined") {
offset = this.tileYOffset[tileId] = this.margin + (this.spacing + this.tileheight) * ~~(tileId / this.hTileCount);
}
return offset;
},
// update tile animations
update : function (dt) {
var anim = null,
duration = 0,
result = false;
for (var i in this.animations) {
if (this.animations.hasOwnProperty(i)) {
anim = this.animations[i];
anim.dt += dt;
duration = anim.cur.duration;
if (anim.dt >= duration) {
anim.dt -= duration;
anim.idx = (anim.idx + 1) % anim.frames.length;
anim.cur = anim.frames[anim.idx];
result = true;
}
}
}
return result;
},
// draw the x,y tile
drawTile : function (renderer, dx, dy, tmxTile) {
var tileid = tmxTile.tileId;
// check if any transformation is required
if (tmxTile.flipped) {
renderer.save();
// apply the tile current transform
var a = tmxTile.transform.val;
renderer.transform(
a[0], a[1],
a[2], a[3],
a[4] + dx, a[5] + dy
);
// reset both values as managed through transform();
dx = dy = 0;
}
// apply animations
if (tileid in this.animations) {
tileid = this.animations[tileid].cur.tileid;
}
else {
// get the local tileset id
tileid -= this.firstgid;
}
// draw the tile
renderer.drawImage(
this.image,
this.getTileOffsetX(tileid), this.getTileOffsetY(tileid),
this.tilewidth, this.tileheight,
dx, dy,
this.tilewidth, this.tileheight
);
if (tmxTile.flipped) {
// restore the context to the previous state
renderer.restore();
}
}
});
/**
* an object containing all tileset
* @class
* @memberOf me
* @constructor
*/
me.TMXTilesetGroup = Object.extend({
// constructor
init: function () {
this.tilesets = [];
},
//add a tileset to the tileset group
add : function (tileset) {
this.tilesets.push(tileset);
},
//return the tileset at the specified index
getTilesetByIndex : function (i) {
return this.tilesets[i];
},
/**
* return the tileset corresponding to the specified id <br>
* will throw an exception if no matching tileset is found
* @name me.TMXTilesetGroup#getTilesetByGid
* @public
* @function
* @param {Number} gid
* @return {me.TMXTileset} corresponding tileset
*/
getTilesetByGid : function (gid) {
var invalidRange = -1;
// cycle through all tilesets
for (var i = 0, len = this.tilesets.length; i < len; i++) {
// return the corresponding tileset if matching
if (this.tilesets[i].contains(gid)) {
return this.tilesets[i];
}
// typically indicates a layer with no asset loaded (collision?)
if (this.tilesets[i].firstgid === this.tilesets[i].lastgid &&
gid >= this.tilesets[i].firstgid) {
// store the id if the [firstgid .. lastgid] is invalid
invalidRange = i;
}
}
// return the tileset with the invalid range
if (invalidRange !== -1) {
return this.tilesets[invalidRange];
}
else {
throw new me.Error("no matching tileset found for gid " + gid);
}
}
});
})(me.TMXConstants);