UNPKG

lettuce

Version:

Lettuce JS, Mini Mobile Framework for Romantic with DSL.

665 lines (602 loc) 22.5 kB
/* * MelonJS Game Engine * Copyright (C) 2011 - 2013, Olivier BIOT * http://www.melonjs.org * */ (function () { /** * a small class to manage loading of stuff and manage resources * There is no constructor function for me.input. * @namespace me.loader * @memberOf me */ me.loader = (function () { // hold public stuff in our singleton var api = {}; // contains all the images loaded var imgList = {}; // contains all the TMX loaded var tmxList = {}; // contains all the binary files loaded var binList = {}; // contains all the JSON files var jsonList = {}; // flag to check loading status var resourceCount = 0; var loadCount = 0; var timerId = 0; /** * check the loading status * @ignore */ function checkLoadStatus() { if (loadCount === resourceCount) { // wait 1/2s and execute callback (cheap workaround to ensure everything is loaded) if (api.onload) { // make sure we clear the timer clearTimeout(timerId); // trigger the onload callback setTimeout(function () { api.onload(); me.event.publish(me.event.LOADER_COMPLETE); }, 300); } else { console.error("no load callback defined"); } } else { timerId = setTimeout(checkLoadStatus, 100); } } /** * load Images * @example * preloadImages([ * { name : 'image1', src : 'images/image1.png'}, * { name : 'image2', src : 'images/image2.png'}, * { name : 'image3', src : 'images/image3.png'}, * { name : 'image4', src : 'images/image4.png'} * ]); * @ignore */ function preloadImage(img, onload, onerror) { // create new Image object and add to list imgList[img.name] = new Image(); imgList[img.name].onload = onload; imgList[img.name].onerror = onerror; imgList[img.name].src = img.src + api.nocache; } /** * preload TMX files * @ignore */ function preloadTMX(tmxData, onload, onerror) { function addToTMXList(data, format) { // set the TMX content tmxList[tmxData.name] = { data: data, isTMX: (tmxData.type === "tmx"), format : format }; } // add the tmx to the levelDirector if (tmxData.type === "tmx") { me.levelDirector.addTMXLevel(tmxData.name); } //if the data is in the tmxData object, don't get it via a XMLHTTPRequest if (tmxData.data) { addToTMXList(tmxData.data, tmxData.format); onload(); return; } var xmlhttp = new XMLHttpRequest(); // check the data format ('tmx', 'json') var format = me.utils.getFileExtension(tmxData.src).toLowerCase(); if (xmlhttp.overrideMimeType) { if (format === "json") { xmlhttp.overrideMimeType("application/json"); } else { xmlhttp.overrideMimeType("text/xml"); } } xmlhttp.open("GET", tmxData.src + api.nocache, true); // set the callbacks xmlhttp.ontimeout = onerror; xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4) { // status = 0 when file protocol is used, or cross-domain origin, // (With Chrome use "--allow-file-access-from-files --disable-web-security") if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) { var result = null; // parse response switch (format) { case "xml": case "tmx": case "tsx": // ie9 does not fully implement the responseXML if (me.device.ua.match(/msie/i) || !xmlhttp.responseXML) { // manually create the XML DOM result = (new DOMParser()).parseFromString(xmlhttp.responseText, "text/xml"); } else { result = xmlhttp.responseXML; } // converts to a JS object // (returns with map as a the root object, to match native json format) result = me.TMXUtils.parse(result); if (format === "tmx") { result = result.map; } // force format to json format = "json"; break; case "json": result = JSON.parse(xmlhttp.responseText); break; default: throw new api.Error("TMX file format " + format + "not supported !"); } //set the TMX content addToTMXList(result, format); // fire the callback onload(); } else { onerror(); } } }; // send the request xmlhttp.send(null); } /** * preload TMX files * @ignore */ function preloadJSON(data, onload, onerror) { var xmlhttp = new XMLHttpRequest(); if (xmlhttp.overrideMimeType) { xmlhttp.overrideMimeType("application/json"); } xmlhttp.open("GET", data.src + api.nocache, true); // set the callbacks xmlhttp.ontimeout = onerror; xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4) { // status = 0 when file protocol is used, or cross-domain origin, // (With Chrome use "--allow-file-access-from-files --disable-web-security") if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) { // get the Texture Packer Atlas content jsonList[data.name] = JSON.parse(xmlhttp.responseText); // fire the callback onload(); } else { onerror(); } } }; // send the request xmlhttp.send(null); } /** * preload Binary files * @ignore */ function preloadBinary(data, onload, onerror) { var httpReq = new XMLHttpRequest(); // load our file httpReq.open("GET", data.src + api.nocache, true); httpReq.responseType = "arraybuffer"; httpReq.onerror = onerror; httpReq.onload = function () { var arrayBuffer = httpReq.response; if (arrayBuffer) { var byteArray = new Uint8Array(arrayBuffer); var buffer = []; for (var i = 0; i < byteArray.byteLength; i++) { buffer[i] = String.fromCharCode(byteArray[i]); } binList[data.name] = buffer.join(""); // callback onload(); } }; httpReq.send(); } /** * to enable/disable caching * @ignore */ api.nocache = ""; /* * PUBLIC STUFF */ /** * onload callback * @public * @callback * @name onload * @memberOf me.loader * @example * * // set a callback when everything is loaded * me.loader.onload = this.loaded.bind(this); */ api.onload = undefined; /** * onProgress callback<br> * each time a resource is loaded, the loader will fire the specified function, * giving the actual progress [0 ... 1], as argument, and an object describing the resource loaded * @public * @callback * @name onProgress * @memberOf me.loader * @example * * // set a callback for progress notification * me.loader.onProgress = this.updateProgress.bind(this); */ api.onProgress = undefined; /** * Base class for Loader exception handling. * @name Error * @class * @memberOf me.loader * @constructor * @param {String} msg Error message. */ api.Error = me.Error.extend({ init : function (msg) { this._super(me.Error, "init", [ msg ]); this.name = "me.loader.Error"; } }); /** * just increment the number of already loaded resources * @ignore */ api.onResourceLoaded = function (res) { // increment the loading counter loadCount++; // callback ? var progress = api.getLoadProgress(); if (api.onProgress) { // pass the load progress in percent, as parameter api.onProgress(progress, res); } me.event.publish(me.event.LOADER_PROGRESS, [progress, res]); }; /** * on error callback for image loading * @ignore */ api.onLoadingError = function (res) { throw new api.Error("Failed loading resource " + res.src); }; /** * enable the nocache mechanism * @ignore */ api.setNocache = function (enable) { api.nocache = enable ? "?" + ~~(Math.random() * 10000000) : ""; }; /** * set all the specified game resources to be preloaded.<br> * each resource item must contain the following fields :<br> * - name : internal name of the resource<br> * - type : "binary", "image", "tmx", "tsx", "audio"<br> * each resource except type "tmx" must contain the following field :<br> * - src : path and file name of the resource<br> * (!) for tmx :<br> * - src : path and file name of the resource<br> * or<br> * - data : the json or xml object representation of the tmx file<br> * - format : "xml" or "json"<br> * (!) for audio :<br> * - src : path (only) where resources are located<br> * <br> * @name preload * @memberOf me.loader * @public * @function * @param {Object[]} resources * @example * var g_resources = [ * // PNG tileset * {name: "tileset-platformer", type: "image", src: "data/map/tileset.png"}, * // PNG packed texture * {name: "texture", type:"image", src: "data/gfx/texture.png"} * // TSX file * {name: "meta_tiles", type: "tsx", src: "data/map/meta_tiles.tsx"}, * // TMX level (XML & JSON) * {name: "map1", type: "tmx", src: "data/map/map1.json"}, * {name: "map2", type: "tmx", src: "data/map/map2.tmx"}, * {name: "map3", type: "tmx", format: "json", data: {"height":15,"layers":[...],"tilewidth":32,"version":1,"width":20}}, * {name: "map4", type: "tmx", format: "xml", data: {xml representation of tmx}}, * // audio resources * {name: "bgmusic", type: "audio", src: "data/audio/"}, * {name: "cling", type: "audio", src: "data/audio/"}, * // binary file * {name: "ymTrack", type: "binary", src: "data/audio/main.ym"}, * // JSON file (used for texturePacker) * {name: "texture", type: "json", src: "data/gfx/texture.json"} * ]; * ... * * // set all resources to be loaded * me.loader.preload(g_resources); */ api.preload = function (res) { // parse the resources for (var i = 0; i < res.length; i++) { resourceCount += api.load( res[i], api.onResourceLoaded.bind(api, res[i]), api.onLoadingError.bind(api, res[i]) ); } // check load status checkLoadStatus(); }; /** * Load a single resource (to be used if you need to load additional resource during the game)<br> * Given parameter must contain the following fields :<br> * - name : internal name of the resource<br> * - type : "audio", binary", "image", "json", "tmx", "tsx"<br> * each resource except type "tmx" must contain the following field :<br> * - src : path and file name of the resource<br> * (!) for tmx :<br> * - src : path and file name of the resource<br> * or<br> * - data : the json or xml object representation of the tmx file<br> * - format : "xml" or "json"<br> * (!) for audio :<br> * - src : path (only) where resources are located<br> * @name load * @memberOf me.loader * @public * @function * @param {Object} resource * @param {Function} onload function to be called when the resource is loaded * @param {Function} onerror function to be called in case of error * @example * // load an image asset * me.loader.load({name: "avatar", type:"image", src: "data/avatar.png"}, this.onload.bind(this), this.onerror.bind(this)); * * // start loading music * me.loader.load({ * name : "bgmusic", * type : "audio", * src : "data/audio/" * }, function () { * me.audio.play("bgmusic"); * }); */ api.load = function (res, onload, onerror) { // fore lowercase for the resource name res.name = res.name.toLowerCase(); // check ressource type switch (res.type) { case "binary": // reuse the preloadImage fn preloadBinary.call(this, res, onload, onerror); return 1; case "image": // reuse the preloadImage fn preloadImage.call(this, res, onload, onerror); return 1; case "json": preloadJSON.call(this, res, onload, onerror); return 1; case "tmx": case "tsx": preloadTMX.call(this, res, onload, onerror); return 1; case "audio": me.audio.load(res, onload, onerror); return 1; default: throw new api.Error("load : unknown or invalid resource type : " + res.type); } }; /** * unload specified resource to free memory * @name unload * @memberOf me.loader * @public * @function * @param {Object} resource * @return {Boolean} true if unloaded * @example me.loader.unload({name: "avatar", type:"image", src: "data/avatar.png"}); */ api.unload = function (res) { res.name = res.name.toLowerCase(); switch (res.type) { case "binary": if (!(res.name in binList)) { return false; } delete binList[res.name]; return true; case "image": if (!(res.name in imgList)) { return false; } if (typeof(imgList[res.name].dispose) === "function") { // cocoonJS implements a dispose function to free // corresponding allocated texture in memory imgList[res.name].dispose(); } delete imgList[res.name]; return true; case "json": if (!(res.name in jsonList)) { return false; } delete jsonList[res.name]; return true; case "tmx": case "tsx": if (!(res.name in tmxList)) { return false; } delete tmxList[res.name]; return true; case "audio": return me.audio.unload(res.name); default: throw new api.Error("unload : unknown or invalid resource type : " + res.type); } }; /** * unload all resources to free memory * @name unloadAll * @memberOf me.loader * @public * @function * @example me.loader.unloadAll(); */ api.unloadAll = function () { var name; // unload all binary resources for (name in binList) { if (binList.hasOwnProperty(name)) { api.unload({ "name" : name, "type" : "binary" }); } } // unload all image resources for (name in imgList) { if (imgList.hasOwnProperty(name)) { api.unload({ "name" : name, "type" : "image" }); } } // unload all tmx resources for (name in tmxList) { if (tmxList.hasOwnProperty(name)) { api.unload({ "name" : name, "type" : "tmx" }); } } // unload all in json resources for (name in jsonList) { if (jsonList.hasOwnProperty(name)) { api.unload({ "name" : name, "type" : "json" }); } } // unload all audio resources me.audio.unloadAll(); }; /** * return the specified TMX/TSX object * @name getTMX * @memberOf me.loader * @public * @function * @param {String} tmx name of the tmx/tsx element ("map1"); * @return {TMx} */ api.getTMX = function (elt) { // avoid case issue elt = "" + elt; elt = elt.toLowerCase(); if (elt in tmxList) { return tmxList[elt].data; } else { //console.log ("warning %s resource not yet loaded!",name); return null; } }; /** * return the specified Binary object * @name getBinary * @memberOf me.loader * @public * @function * @param {String} name of the binary object ("ymTrack"); * @return {Object} */ api.getBinary = function (elt) { // avoid case issue elt = "" + elt; elt = elt.toLowerCase(); if (elt in binList) { return binList[elt]; } else { //console.log ("warning %s resource not yet loaded!",name); return null; } }; /** * return the specified Image Object * @name getImage * @memberOf me.loader * @public * @function * @param {String} Image name of the Image element ("tileset-platformer"); * @return {Image} */ api.getImage = function (elt) { // avoid case issue elt = "" + elt; elt = elt.toLowerCase(); if (elt in imgList) { // return the corresponding Image object return imgList[elt]; } else { //console.log ("warning %s resource not yet loaded!",name); return null; } }; /** * return the specified JSON Object * @name getJSON * @memberOf me.loader * @public * @function * @param {String} Name for the json file to load * @return {Object} */ api.getJSON = function (elt) { elt = "" + elt; elt = elt.toLowerCase(); if (elt in jsonList) { return jsonList[elt]; } else { return null; } }; /** * Return the loading progress in percent * @name getLoadProgress * @memberOf me.loader * @public * @function * @deprecated use callback instead * @return {Number} */ api.getLoadProgress = function () { return loadCount / resourceCount; }; // return our object return api; })(); })();