Last active
August 29, 2015 13:57
-
-
Save peterflynn/9635216 to your computer and use it in GitHub Desktop.
Brackets/Tern bug repro case
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license MelonJS Game Engine | |
* @copyright (C) 2011 - 2013 Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
* melonJS is licensed under the MIT License. | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
*/ | |
/** | |
* (<b>m</b>)elonJS (<b>e</b>)ngine : All melonJS functions are defined inside of this namespace.<p> | |
* You generally should not add new properties to this namespace as it may be overwritten in future versions. | |
* @namespace | |
*/ | |
window.me = window.me || {}; | |
(function($) { | |
// Use the correct document accordingly to window argument | |
var document = $.document; | |
/** | |
* me global references | |
* @ignore | |
*/ | |
me = { | |
// library name & version | |
mod : "melonJS", | |
version : "0.9.11" | |
}; | |
/** | |
* global system settings and browser capabilities | |
* @namespace | |
*/ | |
me.sys = { | |
// Global settings | |
/** | |
* Game FPS (default 60) | |
* @type Int | |
* @memberOf me.sys | |
*/ | |
fps : 60, | |
/** | |
* enable/disable frame interpolation (default disable)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
interpolation : false, | |
/** | |
* Global scaling factor(default 1.0) | |
* @type me.Vector2d | |
* @memberOf me.sys | |
*/ | |
scale : null, //initialized by me.video.init | |
/** | |
* enable/disable video scaling interpolation (default disable)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
scalingInterpolation : false, | |
/** | |
* Global gravity settings <br> | |
* will override entities init value if defined<br> | |
* default value : undefined | |
* @type Number | |
* @memberOf me.sys | |
*/ | |
gravity : undefined, | |
/** | |
* Specify either to stop on audio loading error or not<br> | |
* if me.debug.stopOnAudioLoad is true, melonJS will throw an exception and stop loading<br> | |
* if me.debug.stopOnAudioLoad is false, melonJS will disable sounds and output a warning message in the console <br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
stopOnAudioError : true, | |
/** | |
* Specify whether to pause the game when losing focus.<br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
pauseOnBlur : true, | |
/** | |
* Specify whether to unpause the game when gaining focus.<br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
resumeOnFocus : true, | |
/** | |
* Specify whether to stop the game when losing focus or not<br> | |
* The engine restarts on focus if this is enabled. | |
* default value : false<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
stopOnBlur : false, | |
/** | |
* Specify the rendering method for layers <br> | |
* if false, visible part of the layers are rendered dynamically (default)<br> | |
* if true, the entire layers are first rendered into an offscreen canvas<br> | |
* the "best" rendering method depends of your game<br> | |
* (amount of layer, layer size, amount of tiles per layer, etc…)<br> | |
* note : rendering method is also configurable per layer by adding this property to your layer (in Tiled)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
preRender : false, | |
// System methods | |
/** | |
* Compare two version strings | |
* @public | |
* @function | |
* @param {String} first First version string to compare | |
* @param {String} [second="0.9.11"] Second version string to compare | |
* @return {Number} comparison result <br>< 0 : first < second <br>0 : first == second <br>> 0 : first > second | |
* @example | |
* if (me.sys.checkVersion("0.9.5") > 0) { | |
* console.error("melonJS is too old. Expected: 0.9.5, Got: " + me.version); | |
* } | |
*/ | |
checkVersion : function (first, second) { | |
second = second || me.version; | |
var a = first.split("."); | |
var b = second.split("."); | |
var len = Math.min(a.length, b.length); | |
var result = 0; | |
for (var i = 0; i < len; i++) { | |
if (result = +a[i] - +b[i]) { | |
break; | |
} | |
} | |
return result ? result : a.length - b.length; | |
} | |
}; | |
// a flag to know if melonJS | |
// is initialized | |
var me_initialized = false; | |
/*--- | |
DOM loading stuff | |
---*/ | |
var readyBound = false, isReady = false, readyList = []; | |
// Handle when the DOM is ready | |
function domReady() { | |
// Make sure that the DOM is not already loaded | |
if (!isReady) { | |
// be sure document.body is there | |
if (!document.body) { | |
return setTimeout(domReady, 13); | |
} | |
// clean up loading event | |
if (document.removeEventListener) { | |
document.removeEventListener("DOMContentLoaded", domReady, false); | |
} else { | |
$.removeEventListener("load", domReady, false); | |
} | |
// Remember that the DOM is ready | |
isReady = true; | |
// execute the defined callback | |
for ( var fn = 0; fn < readyList.length; fn++) { | |
readyList[fn].call($, []); | |
} | |
readyList.length = 0; | |
} | |
} | |
// bind ready | |
function bindReady() { | |
if (readyBound) { | |
return; | |
} | |
readyBound = true; | |
// directly call domReady if document is already "ready" | |
if (document.readyState === "complete") { | |
return domReady(); | |
} else { | |
if (document.addEventListener) { | |
// Use the handy event callback | |
document.addEventListener("DOMContentLoaded", domReady, false); | |
} | |
// A fallback to window.onload, that will always work | |
$.addEventListener("load", domReady, false); | |
} | |
} | |
/** | |
* The built in window Object | |
* @external window | |
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window.window} | |
*/ | |
/** | |
* Specify a function to execute when the DOM is fully loaded | |
* @memberOf external:window# | |
* @alias onReady | |
* @param {Function} handler A function to execute after the DOM is ready. | |
* @example | |
* // small main skeleton | |
* var game = { | |
* // Initialize the game | |
* // called by the window.onReady function | |
* onload: function() { | |
* | |
* // init video | |
* if (!me.video.init('screen', 640, 480, true)) { | |
* alert("Sorry but your browser does not support html 5 canvas. "); | |
* return; | |
* } | |
* | |
* // initialize the "audio" | |
* me.audio.init("mp3,ogg"); | |
* | |
* // set callback for ressources loaded event | |
* me.loader.onload = this.loaded.bind(this); | |
* | |
* // set all ressources to be loaded | |
* me.loader.preload(game.resources); | |
* | |
* // load everything & display a loading screen | |
* me.state.change(me.state.LOADING); | |
* }, | |
* | |
* // callback when everything is loaded | |
* loaded: function () { | |
* // define stuff | |
* // .... | |
* | |
* // change to the menu screen | |
* me.state.change(me.state.MENU); | |
* } | |
* }; // game | |
* | |
* // "bootstrap" | |
* window.onReady(function() { | |
* game.onload(); | |
* }); | |
*/ | |
$.onReady = function(fn) { | |
// Attach the listeners | |
bindReady(); | |
// If the DOM is already ready | |
if (isReady) { | |
// Execute the function immediately | |
fn.call($, []); | |
} else { | |
// Add the function to the wait list | |
readyList.push(function() { | |
return fn.call($, []); | |
}); | |
} | |
return this; | |
}; | |
// call the library init function when ready | |
$.onReady(function() { | |
_init_ME(); | |
}); | |
/************************************************************************************/ | |
/* | |
* some "Javascript API" patch & enhancement | |
*/ | |
var initializing = false, fnTest = /var xyz/.test(function() {/**@nosideeffects*/var xyz;}) ? /\bparent\b/ : /[\D|\d]*/; | |
/** | |
* a deep copy function | |
* @ignore | |
*/ | |
var deepcopy = function (obj) { | |
if (null == obj || "object" !== typeof obj) { | |
return obj; | |
} | |
// hold the copied object | |
var copy; | |
// Array copy | |
if( obj instanceof Array ) { | |
copy = []; | |
Object.setPrototypeOf(copy, Object.getPrototypeOf(obj)); | |
for( var i = 0, l = obj.length; i < l; i++) { | |
copy[i] = deepcopy(obj[i]); | |
} | |
return copy; | |
} | |
// Date copy | |
if (obj instanceof Date) { | |
copy = new Date(); | |
copy.setTime(obj.getTime()); | |
return copy; | |
} | |
// else instanceof Object | |
copy = {}; | |
Object.setPrototypeOf(copy, Object.getPrototypeOf(obj)); | |
for( var prop in obj ) { | |
if (obj.hasOwnProperty(prop)) copy[prop] = deepcopy(obj[prop]); | |
} | |
return copy; | |
}; | |
/** | |
* JavaScript Inheritance Helper <br> | |
* Based on <a href="http://ejohn.org/">John Resig</a> Simple Inheritance<br> | |
* MIT Licensed.<br> | |
* Inspired by <a href="http://code.google.com/p/base2/">base2</a> and <a href="http://www.prototypejs.org/">Prototype</a><br> | |
* @param {Object} object Object (or Properties) to inherit from | |
* @example | |
* var Person = Object.extend( | |
* { | |
* init: function(isDancing) | |
* { | |
* this.dancing = isDancing; | |
* }, | |
* dance: function() | |
* { | |
* return this.dancing; | |
* } | |
* }); | |
* | |
* var Ninja = Person.extend( | |
* { | |
* init: function() | |
* { | |
* this.parent( false ); | |
* }, | |
* | |
* dance: function() | |
* { | |
* // Call the inherited version of dance() | |
* return this.parent(); | |
* }, | |
* | |
* swingSword: function() | |
* { | |
* return true; | |
* } | |
* }); | |
* | |
* var p = new Person(true); | |
* p.dance(); // => true | |
* | |
* var n = new Ninja(); | |
* n.dance(); // => false | |
* n.swingSword(); // => true | |
* | |
* // Should all be true | |
* p instanceof Person && p instanceof Class && | |
* n instanceof Ninja && n instanceof Person && n instanceof Class | |
*/ | |
Object.extend = function(prop) { | |
// _super rename to parent to ease code reading | |
var parent = this.prototype; | |
// Instantiate a base class (but only create the instance, | |
// don't run the init constructor) | |
initializing = true; | |
var proto = new this(); | |
initializing = false; | |
function addSuper(name, fn) { | |
return function() { | |
var tmp = this.parent; | |
// Add a new ._super() method that is the same method | |
// but on the super-class | |
this.parent = parent[name]; | |
// The method only need to be bound temporarily, so we | |
// remove it when we're done executing | |
var ret = fn.apply(this, arguments); | |
this.parent = tmp; | |
return ret; | |
}; | |
} | |
// Copy the properties over onto the new prototype | |
for ( var name in prop) { | |
// Check if we're overwriting an existing function | |
proto[name] = typeof prop[name] === "function" && | |
typeof parent[name] === "function" && | |
fnTest.test(prop[name]) ? addSuper(name, prop[name]) : prop[name]; | |
} | |
// The dummy class constructor | |
function Class() { | |
if (!initializing) { | |
for( var prop in this ) { | |
// deepcopy properties if required | |
if( typeof(this[prop]) === 'object' ) { | |
this[prop] = deepcopy(this[prop]); | |
} | |
} | |
if (this.init) { | |
this.init.apply(this, arguments); | |
} | |
} | |
return this; | |
} | |
// Populate our constructed prototype object | |
Class.prototype = proto; | |
// Enforce the constructor to be what we expect | |
Class.constructor = Class; | |
// And make this class extendable | |
Class.extend = Object.extend;//arguments.callee; | |
return Class; | |
}; | |
if (typeof Object.create !== 'function') { | |
/** | |
* Prototypal Inheritance Create Helper | |
* @param {Object} Object | |
* @example | |
* // declare oldObject | |
* oldObject = new Object(); | |
* // make some crazy stuff with oldObject (adding functions, etc...) | |
* ... | |
* ... | |
* | |
* // make newObject inherits from oldObject | |
* newObject = Object.create(oldObject); | |
*/ | |
Object.create = function(o) { | |
function _fn() {} | |
_fn.prototype = o; | |
return new _fn(); | |
}; | |
} | |
/** | |
* The built in Function Object | |
* @external Function | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function|Function} | |
*/ | |
if (!Function.prototype.bind) { | |
/** @ignore */ | |
var Empty = function () {}; | |
/** | |
* Binds this function to the given context by wrapping it in another function and returning the wrapper.<p> | |
* Whenever the resulting "bound" function is called, it will call the original ensuring that this is set to context. <p> | |
* Also optionally curries arguments for the function. | |
* @memberof! external:Function# | |
* @alias bind | |
* @param {Object} context the object to bind to. | |
* @param {} [arguments...] Optional additional arguments to curry for the function. | |
* @example | |
* // Ensure that our callback is triggered with the right object context (this): | |
* myObject.onComplete(this.callback.bind(this)); | |
*/ | |
Function.prototype.bind = function bind(that) { | |
// ECMAScript 5 compliant implementation | |
// http://es5.github.com/#x15.3.4.5 | |
// from https://github.com/kriskowal/es5-shim | |
var target = this; | |
if (typeof target !== "function") { | |
throw new TypeError("Function.prototype.bind called on incompatible " + target); | |
} | |
var args = Array.prototype.slice.call(arguments, 1); | |
var bound = function () { | |
if (this instanceof bound) { | |
var result = target.apply( this, args.concat(Array.prototype.slice.call(arguments))); | |
if (Object(result) === result) { | |
return result; | |
} | |
return this; | |
} else { | |
return target.apply(that, args.concat(Array.prototype.slice.call(arguments))); | |
} | |
}; | |
if(target.prototype) { | |
Empty.prototype = target.prototype; | |
bound.prototype = new Empty(); | |
Empty.prototype = null; | |
} | |
return bound; | |
}; | |
} | |
if (!window.throttle) { | |
/** | |
* a simple throttle function | |
* use same fct signature as the one in prototype | |
* in case it's already defined before | |
* @ignore | |
*/ | |
window.throttle = function( delay, no_trailing, callback, debounce_mode ) { | |
var last = Date.now(), deferTimer; | |
// `no_trailing` defaults to false. | |
if ( typeof no_trailing !== 'boolean' ) { | |
no_trailing = false; | |
} | |
return function () { | |
var now = Date.now(); | |
var elasped = now - last; | |
var args = arguments; | |
if (elasped < delay) { | |
if (no_trailing === false) { | |
// hold on to it | |
clearTimeout(deferTimer); | |
deferTimer = setTimeout(function () { | |
last = now; | |
return callback.apply(null, args); | |
}, elasped); | |
} | |
} else { | |
last = now; | |
return callback.apply(null, args); | |
} | |
}; | |
}; | |
} | |
if (typeof Date.now === "undefined") { | |
/** | |
* provide a replacement for browser not | |
* supporting Date.now (JS 1.5) | |
* @ignore | |
*/ | |
Date.now = function(){return new Date().getTime();}; | |
} | |
if(typeof console === "undefined") { | |
/** | |
* Dummy console.log to avoid crash | |
* in case the browser does not support it | |
* @ignore | |
*/ | |
console = { | |
log: function() {}, | |
info: function() {}, | |
error: function() {alert(Array.prototype.slice.call(arguments).join(", "));} | |
}; | |
} | |
/** | |
* Executes a function as soon as the interpreter is idle (stack empty). | |
* @memberof! external:Function# | |
* @alias defer | |
* @param {} [arguments...] Optional additional arguments to curry for the function. | |
* @return {Number} id that can be used to clear the deferred function using clearTimeout | |
* @example | |
* // execute myFunc() when the stack is empty, with 'myArgument' as parameter | |
* myFunc.defer('myArgument'); | |
*/ | |
Function.prototype.defer = function() { | |
var fn = this, args = Array.prototype.slice.call(arguments); | |
return window.setTimeout(function() { | |
return fn.apply(fn, args); | |
}, 0.01); | |
}; | |
if (!Object.defineProperty) { | |
/** | |
* simple defineProperty function definition (if not supported by the browser)<br> | |
* if defineProperty is redefined, internally use __defineGetter__/__defineSetter__ as fallback | |
* @param {Object} obj The object on which to define the property. | |
* @param {String} prop The name of the property to be defined or modified. | |
* @param {Object} desc The descriptor for the property being defined or modified. | |
*/ | |
Object.defineProperty = function(obj, prop, desc) { | |
// check if Object support __defineGetter function | |
if (obj.__defineGetter__) { | |
if (desc.get) { | |
obj.__defineGetter__(prop, desc.get); | |
} | |
if (desc.set) { | |
obj.__defineSetter__(prop, desc.set); | |
} | |
} else { | |
// we should never reach this point.... | |
throw "melonJS: Object.defineProperty not supported"; | |
} | |
}; | |
} | |
/** | |
* Get the prototype of an Object. | |
* @memberOf external:Object# | |
* @alias getPrototypeOf | |
* @param {Object} obj Target object to inspect. | |
* @return {Prototype} Prototype of the target object. | |
*/ | |
Object.getPrototypeOf = Object.getPrototypeOf || function (obj) { | |
return obj.__proto__; | |
}; | |
/** | |
* Set the prototype of an Object. | |
* @memberOf external:Object# | |
* @alias setPrototypeOf | |
* @param {Object} obj Target object to modify. | |
* @param {Prototype} prototype New prototype for the target object. | |
* @return {Object} Modified target object. | |
*/ | |
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, prototype) { | |
obj.__proto__ = prototype; | |
return obj; | |
}; | |
/** | |
* The built in String Object | |
* @external String | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String|String} | |
*/ | |
if(!String.prototype.trim) { | |
/** | |
* returns the string stripped of whitespace from both ends | |
* @memberof! external:String# | |
* @alias trim | |
* @return {String} trimmed string | |
*/ | |
String.prototype.trim = function () { | |
return (this.replace(/^\s+/, '')).replace(/\s+$/, ''); | |
}; | |
} | |
if(!String.prototype.trimRight) { | |
/** | |
* returns the string stripped of whitespace from the right end of the string. | |
* @memberof! external:String# | |
* @alias trimRight | |
* @return {String} trimmed string | |
*/ | |
String.prototype.trimRight = function () { | |
return this.replace(/\s+$/, ''); | |
}; | |
} | |
/** | |
* add isNumeric fn to the string object | |
* @memberof! external:String# | |
* @alias isNumeric | |
* @return {Boolean} true if string contains only digits | |
*/ | |
String.prototype.isNumeric = function() { | |
return (this !== null && !isNaN(this) && this.trim() !== ""); | |
}; | |
/** | |
* add a isBoolean fn to the string object | |
* @memberof! external:String# | |
* @alias isBoolean | |
* @return {Boolean} true if the string is either true or false | |
*/ | |
String.prototype.isBoolean = function() { | |
return (this !== null && ("true" === this.trim() || "false" === this.trim())); | |
}; | |
/** | |
* add a contains fn to the string object | |
* @memberof! external:String# | |
* @alias contains | |
* @param {String} string to test for | |
* @return {Boolean} true if contains the specified string | |
*/ | |
String.prototype.contains = function(word) { | |
return this.indexOf(word) > -1; | |
}; | |
/** | |
* convert the string to hex value | |
* @memberof! external:String# | |
* @alias toHex | |
* @return {String} | |
*/ | |
String.prototype.toHex = function() { | |
var res = "", c = 0; | |
while(c<this.length){ | |
res += this.charCodeAt(c++).toString(16); | |
} | |
return res; | |
}; | |
/** | |
* The built in Number Object | |
* @external Number | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number|Number} | |
*/ | |
/** | |
* add a clamp fn to the Number object | |
* @memberof! external:Number# | |
* @alias clamp | |
* @param {Number} low lower limit | |
* @param {Number} high higher limit | |
* @return {Number} clamped value | |
*/ | |
Number.prototype.clamp = function(low, high) { | |
return this < low ? low : this > high ? high : +this; | |
}; | |
/** | |
* return a random between min, max | |
* @memberof! external:Number# | |
* @alias random | |
* @param {Number} min minimum value. | |
* @param {Number} max maximum value. | |
* @return {Number} random value | |
*/ | |
Number.prototype.random = function(min, max) { | |
return (~~(Math.random() * (max - min + 1)) + min); | |
}; | |
/** | |
* round a value to the specified number of digit | |
* @memberof! external:Number# | |
* @alias round | |
* @param {Number} [num="Object value"] value to be rounded. | |
* @param {Number} dec number of decimal digit to be rounded to. | |
* @return {Number} rounded value | |
* @example | |
* // round a specific value to 2 digits | |
* Number.prototype.round (10.33333, 2); // return 10.33 | |
* // round a float value to 4 digits | |
* num = 10.3333333 | |
* num.round(4); // return 10.3333 | |
*/ | |
Number.prototype.round = function() { | |
// if only one argument use the object value | |
var num = (arguments.length < 2) ? this : arguments[0]; | |
var powres = Math.pow(10, arguments[1] || arguments[0] || 0); | |
return (Math.round(num * powres) / powres); | |
}; | |
/** | |
* a quick toHex function<br> | |
* given number <b>must</b> be an int, with a value between 0 and 255 | |
* @memberof! external:Number# | |
* @alias toHex | |
* @return {String} converted hexadecimal value | |
*/ | |
Number.prototype.toHex = function() { | |
return "0123456789ABCDEF".charAt((this - this % 16) >> 4) + "0123456789ABCDEF".charAt(this % 16); | |
}; | |
/** | |
* Returns a value indicating the sign of a number<br> | |
* @memberof! external:Number# | |
* @alias sign | |
* @return {Number} sign of a the number | |
*/ | |
Number.prototype.sign = function() { | |
return this < 0 ? -1 : (this > 0 ? 1 : 0); | |
}; | |
/** | |
* Converts an angle in degrees to an angle in radians | |
* @memberof! external:Number# | |
* @alias degToRad | |
* @param {Number} [angle="angle"] angle in degrees | |
* @return {Number} corresponding angle in radians | |
* @example | |
* // convert a specific angle | |
* Number.prototype.degToRad (60); // return 1.0471... | |
* // convert object value | |
* var num = 60 | |
* num.degToRad(); // return 1.0471... | |
*/ | |
Number.prototype.degToRad = function (angle) { | |
return (angle||this) / 180.0 * Math.PI; | |
}; | |
/** | |
* Converts an angle in radians to an angle in degrees. | |
* @memberof! external:Number# | |
* @alias radToDeg | |
* @param {Number} [angle="angle"] angle in radians | |
* @return {Number} corresponding angle in degrees | |
* @example | |
* // convert a specific angle | |
* Number.prototype.radToDeg (1.0471975511965976); // return 59.9999... | |
* // convert object value | |
* num = 1.0471975511965976 | |
* Math.ceil(num.radToDeg()); // return 60 | |
*/ | |
Number.prototype.radToDeg = function (angle) { | |
return (angle||this) * (180.0 / Math.PI); | |
}; | |
/** | |
* The built in Array Object | |
* @external Array | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array|Array} | |
*/ | |
/** | |
* Remove the specified object from the Array<br> | |
* @memberof! external:Array# | |
* @alias remove | |
* @param {Object} object to be removed | |
*/ | |
Array.prototype.remove = function(obj) { | |
var i = Array.prototype.indexOf.call(this, obj); | |
if( i !== -1 ) { | |
Array.prototype.splice.call(this, i, 1); | |
} | |
return this; | |
}; | |
if (!Array.prototype.forEach) { | |
/** | |
* provide a replacement for browsers that don't | |
* support Array.prototype.forEach (JS 1.6) | |
* @ignore | |
*/ | |
Array.prototype.forEach = function (callback, scope) { | |
for (var i = 0, j = this.length; j--; i++) { | |
callback.call(scope || this, this[i], i, this); | |
} | |
}; | |
} | |
Object.defineProperty(me, "initialized", { | |
get : function get() { | |
return me_initialized; | |
} | |
}); | |
/* | |
* me init stuff | |
*/ | |
function _init_ME() { | |
// don't do anything if already initialized (should not happen anyway) | |
if (me_initialized) { | |
return; | |
} | |
// check the device capabilites | |
me.device._check(); | |
// initialize me.save | |
me.save._init(); | |
// enable/disable the cache | |
me.loader.setNocache(document.location.href.match(/\?nocache/)||false); | |
// init the FPS counter if needed | |
me.timer.init(); | |
// create a new map reader instance | |
me.mapReader = new me.TMXMapReader(); | |
// init the App Manager | |
me.state.init(); | |
// init the Entity Pool | |
me.entityPool.init(); | |
// init the level Director | |
me.levelDirector.reset(); | |
me_initialized = true; | |
} | |
/** | |
* me.game represents your current game, it contains all the objects, tilemap layers,<br> | |
* current viewport, collision map, etc...<br> | |
* me.game is also responsible for updating (each frame) the object status and draw them<br> | |
* @namespace me.game | |
* @memberOf me | |
*/ | |
me.game = (function() { | |
// hold public stuff in our singletong | |
var api = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
// ref to the "system" context | |
var frameBuffer = null; | |
// flag to redraw the sprites | |
var initialized = false; | |
// to keep track of deferred stuff | |
var pendingRemove = null; | |
// to know when we have to refresh the display | |
var isDirty = true; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* a reference to the game viewport. | |
* @public | |
* @type me.Viewport | |
* @name viewport | |
* @memberOf me.game | |
*/ | |
api.viewport = null; | |
/** | |
* a reference to the game collision Map | |
* @public | |
* @type me.TMXLayer | |
* @name collisionMap | |
* @memberOf me.game | |
*/ | |
api.collisionMap = null; | |
/** | |
* a reference to the game current level | |
* @public | |
* @type me.TMXTileMap | |
* @name currentLevel | |
* @memberOf me.game | |
*/ | |
api.currentLevel = null; | |
/** | |
* a reference to the game world <br> | |
* a world is a virtual environment containing all the game objects | |
* @public | |
* @type me.ObjectContainer | |
* @name world | |
* @memberOf me.game | |
*/ | |
api.world = null; | |
/** | |
* when true, all objects will be added under the root world container <br> | |
* when false, a `me.ObjectContainer` object will be created for each corresponding `TMXObjectGroup` | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name mergeGroup | |
* @memberOf me.game | |
*/ | |
api.mergeGroup = true; | |
/** | |
* The property of should be used when sorting entities <br> | |
* value : "x", "y", "z" (default: "z") | |
* @public | |
* @type String | |
* @name sortOn | |
* @memberOf me.game | |
*/ | |
api.sortOn = "z"; | |
/** | |
* default layer renderer | |
* @private | |
* @ignore | |
* @type me.TMXRenderer | |
* @name renderer | |
* @memberOf me.game | |
*/ | |
api.renderer = null; | |
// FIX ME : put this somewhere else | |
api.NO_OBJECT = 0; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name ENEMY_OBJECT | |
* @memberOf me.game | |
*/ | |
api.ENEMY_OBJECT = 1; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name COLLECTABLE_OBJECT | |
* @memberOf me.game | |
*/ | |
api.COLLECTABLE_OBJECT = 2; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name ACTION_OBJECT | |
* @memberOf me.game | |
*/ | |
api.ACTION_OBJECT = 3; // door, etc... | |
/** | |
* Fired when a level is fully loaded and <br> | |
* and all entities instantiated. <br> | |
* Additionnaly the level id will also be passed | |
* to the called function. | |
* @public | |
* @callback | |
* @name onLevelLoaded | |
* @memberOf me.game | |
* @example | |
* // call myFunction() everytime a level is loaded | |
* me.game.onLevelLoaded = this.myFunction.bind(this); | |
*/ | |
api.onLevelLoaded = null; | |
/** | |
* Initialize the game manager | |
* @name init | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
* @param {Number} [width="full size of the created canvas"] width of the canvas | |
* @param {Number} [height="full size of the created canvas"] width of the canvas | |
* init function. | |
*/ | |
api.init = function(width, height) { | |
if (!initialized) { | |
// if no parameter specified use the system size | |
width = width || me.video.getWidth(); | |
height = height || me.video.getHeight(); | |
// create a defaut viewport of the same size | |
api.viewport = new me.Viewport(0, 0, width, height); | |
//the root object of our world is an entity container | |
api.world = new me.ObjectContainer(0,0, width, height); | |
// give it a name | |
api.world.name = 'rootContainer'; | |
// get a ref to the screen buffer | |
frameBuffer = me.video.getSystemContext(); | |
// publish init notification | |
me.event.publish(me.event.GAME_INIT); | |
// make display dirty by default | |
isDirty = true; | |
// set as initialized | |
initialized = true; | |
} | |
}; | |
/** | |
* reset the game Object manager<p> | |
* destroy all current objects | |
* @name reset | |
* @memberOf me.game | |
* @public | |
* @function | |
*/ | |
api.reset = function() { | |
// remove all objects | |
api.removeAll(); | |
// reset the viewport to zero ? | |
if (api.viewport) { | |
api.viewport.reset(); | |
} | |
// reset the transform matrix to the normal one | |
frameBuffer.setTransform(1, 0, 0, 1, 0, 0); | |
// dummy current level | |
api.currentLevel = {pos:{x:0,y:0}}; | |
}; | |
/** | |
* Load a TMX level | |
* @name loadTMXLevel | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.loadTMXLevel = function(level) { | |
// disable auto-sort | |
api.world.autoSort = false; | |
// load our map | |
api.currentLevel = level; | |
// get the collision map | |
api.collisionMap = api.currentLevel.getLayerByName("collision"); | |
if (!api.collisionMap || !api.collisionMap.isCollisionMap) { | |
console.error("WARNING : no collision map detected"); | |
} | |
// add all defined layers | |
var layers = api.currentLevel.getLayers(); | |
for ( var i = layers.length; i--;) { | |
if (layers[i].visible) { | |
// only if visible | |
api.add(layers[i]); | |
} | |
} | |
// change the viewport limit | |
api.viewport.setBounds(Math.max(api.currentLevel.width, api.viewport.width), | |
Math.max(api.currentLevel.height, api.viewport.height)); | |
// game world as default container | |
var targetContainer = api.world; | |
// load all ObjectGroup and Object definition | |
var objectGroups = api.currentLevel.getObjectGroups(); | |
for ( var g = 0; g < objectGroups.length; g++) { | |
var group = objectGroups[g]; | |
if (api.mergeGroup === false) { | |
// create a new container with Infinite size (?) | |
// note: initial position and size seems to be meaningless in Tiled | |
// https://github.com/bjorn/tiled/wiki/TMX-Map-Format : | |
// x: Defaults to 0 and can no longer be changed in Tiled Qt. | |
// y: Defaults to 0 and can no longer be changed in Tiled Qt. | |
// width: The width of the object group in tiles. Meaningless. | |
// height: The height of the object group in tiles. Meaningless. | |
targetContainer = new me.ObjectContainer(); | |
// set additional properties | |
targetContainer.name = group.name; | |
targetContainer.visible = group.visible; | |
targetContainer.z = group.z; | |
targetContainer.setOpacity(group.opacity); | |
// disable auto-sort | |
targetContainer.autoSort = false; | |
} | |
// iterate through the group and add all object into their | |
// corresponding target Container | |
for ( var o = 0; o < group.objects.length; o++) { | |
// TMX Object | |
var obj = group.objects[o]; | |
// create the corresponding entity | |
var entity = me.entityPool.newInstanceOf(obj.name, obj.x, obj.y, obj); | |
// ignore if the newInstanceOf function does not return a corresponding object | |
if (entity) { | |
// set the entity z order correspondingly to its parent container/group | |
entity.z = group.z; | |
//apply group opacity value to the child objects if group are merged | |
if (api.mergeGroup === true && entity.isRenderable === true) { | |
entity.setOpacity(entity.getOpacity() * group.opacity); | |
// and to child renderables if any | |
if (entity.renderable !== null) { | |
entity.renderable.setOpacity(entity.renderable.getOpacity() * group.opacity); | |
} | |
} | |
// add the entity into the target container | |
targetContainer.addChild(entity); | |
} | |
} | |
// if we created a new container | |
if (api.mergeGroup === false) { | |
// add our container to the world | |
api.world.addChild(targetContainer); | |
// re-enable auto-sort | |
targetContainer.autoSort = true; | |
} | |
} | |
// sort everything (recursively) | |
api.world.sort(true); | |
// re-enable auto-sort | |
api.world.autoSort = true; | |
// check if the map has different default (0,0) screen coordinates | |
if (api.currentLevel.pos.x !== api.currentLevel.pos.y) { | |
// translate the display accordingly | |
frameBuffer.translate( api.currentLevel.pos.x , api.currentLevel.pos.y ); | |
} | |
// fire the callback if defined | |
if (api.onLevelLoaded) { | |
api.onLevelLoaded.call(api.onLevelLoaded, level.name); | |
} | |
//publish the corresponding message | |
me.event.publish(me.event.LEVEL_LOADED, [level.name]); | |
}; | |
/** | |
* Manually add object to the game manager | |
* @deprecated @see me.game.world.addChild() | |
* @name add | |
* @memberOf me.game | |
* @param {me.ObjectEntity} obj Object to be added | |
* @param {Number} [z="obj.z"] z index | |
* @public | |
* @function | |
* @example | |
* // create a new object | |
* var obj = new MyObject(x, y) | |
* // add the object and force the z index of the current object | |
* me.game.add(obj, this.z); | |
*/ | |
api.add = function(object, zOrder) { | |
if (typeof(zOrder) !== 'undefined') { | |
object.z = zOrder; | |
} | |
// add the object in the game obj list | |
api.world.addChild(object); | |
}; | |
/** | |
* returns the list of entities with the specified name<br> | |
* as defined in Tiled (Name field of the Object Properties)<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByName | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} entityName entity name | |
* @return {me.ObjectEntity[]} Array of object entities | |
*/ | |
api.getEntityByName = function(entityName) { | |
return api.world.getEntityByProp("name", entityName); | |
}; | |
/** | |
* return the entity corresponding to the specified GUID<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByGUID | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} GUID entity GUID | |
* @return {me.ObjectEntity} Object Entity (or null if not found) | |
*/ | |
api.getEntityByGUID = function(guid) { | |
var obj = api.world.getEntityByProp("GUID", guid); | |
return (obj.length>0)?obj[0]:null; | |
}; | |
/** | |
* return the entity corresponding to the property and value<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByProp | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} prop Property name | |
* @param {String} value Value of the property | |
* @return {me.ObjectEntity[]} Array of object entities | |
*/ | |
api.getEntityByProp = function(prop, value) { | |
return api.world.getEntityByProp(prop, value); | |
}; | |
/** | |
* Returns the entity container of the specified Child in the game world | |
* @name getEntityContainer | |
* @memberOf me.game | |
* @function | |
* @param {me.ObjectEntity} child | |
* @return {me.ObjectContainer} | |
*/ | |
api.getEntityContainer = function(child) { | |
return child.ancestor; | |
}; | |
/** | |
* remove the specific object from the world<br> | |
* `me.game.remove` will preserve object that defines the `isPersistent` flag | |
* `me.game.remove` will remove object at the end of the current frame | |
* @name remove | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be removed | |
* @param {Boolean} [force=false] Force immediate deletion.<br> | |
* <strong>WARNING</strong>: Not safe to force asynchronously (e.g. onCollision callbacks) | |
*/ | |
api.remove = function(obj, force) { | |
if (obj.ancestor) { | |
// remove the object from the object list | |
if (force===true) { | |
// force immediate object deletion | |
obj.ancestor.removeChild(obj); | |
} else { | |
// make it invisible (this is bad...) | |
obj.visible = obj.inViewport = false; | |
// wait the end of the current loop | |
/** @ignore */ | |
pendingRemove = (function (obj) { | |
// safety check in case the | |
// object was removed meanwhile | |
if (typeof obj.ancestor !== 'undefined') { | |
obj.ancestor.removeChild(obj); | |
} | |
pendingRemove = null; | |
}.defer(obj)); | |
} | |
} | |
}; | |
/** | |
* remove all objects<br> | |
* @name removeAll | |
* @memberOf me.game | |
* @param {Boolean} [force=false] Force immediate deletion.<br> | |
* <strong>WARNING</strong>: Not safe to force asynchronously (e.g. onCollision callbacks) | |
* @public | |
* @function | |
*/ | |
api.removeAll = function() { | |
//cancel any pending tasks | |
if (pendingRemove) { | |
clearTimeout(pendingRemove); | |
pendingRemove = null; | |
} | |
// destroy all objects in the root container | |
api.world.destroy(); | |
}; | |
/** | |
* Manually trigger the sort all the game objects.</p> | |
* Since version 0.9.9, all objects are automatically sorted, <br> | |
* except if a container autoSort property is set to false. | |
* @deprecated use me.game.world.sort(); | |
* @name sort | |
* @memberOf me.game | |
* @public | |
* @function | |
* @example | |
* // change the default sort property | |
* me.game.sortOn = "y"; | |
* // manuallly call me.game.sort with our sorting function | |
* me.game.sort(); | |
*/ | |
api.sort = function() { | |
api.world.sort(); | |
}; | |
/** | |
* Checks if the specified entity collides with others entities. | |
* @deprecated use me.game.world.collide(); | |
* @name collide | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
* @example | |
* // update player movement | |
* this.updateMovement(); | |
* | |
* // check for collision with other objects | |
* res = me.game.collide(this); | |
* | |
* // check if we collide with an enemy : | |
* if (res && (res.obj.type == me.game.ENEMY_OBJECT)) | |
* { | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* } | |
*/ | |
api.collide = function(objA, multiple) { | |
return api.world.collide (objA, multiple); | |
}; | |
/** | |
* Checks if the specified entity collides with others entities of the specified type. | |
* @deprecated use me.game.world.collideType(); | |
* @name collideType | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be tested for collision | |
* @param {String} type Entity type to be tested for collision (null to disable type check) | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
api.collideType = function(objA, type, multiple) { | |
return api.world.collideType (objA, type, multiple); | |
}; | |
/** | |
* force the redraw (not update) of all objects | |
* @name repaint | |
* @memberOf me.game | |
* @public | |
* @function | |
*/ | |
api.repaint = function() { | |
isDirty = true; | |
}; | |
/** | |
* update all objects of the game manager | |
* @name update | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.update = function() { | |
// update all objects | |
isDirty = api.world.update() || isDirty; | |
// update the camera/viewport | |
isDirty = api.viewport.update(isDirty) || isDirty; | |
return isDirty; | |
}; | |
/** | |
* draw all existing objects | |
* @name draw | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.draw = function() { | |
if (isDirty) { | |
// cache the viewport rendering position, so that other object | |
// can access it later (e,g. entityContainer when drawing floating objects) | |
api.viewport.screenX = api.viewport.pos.x + ~~api.viewport.offset.x; | |
api.viewport.screenY = api.viewport.pos.y + ~~api.viewport.offset.y; | |
// save the current context | |
frameBuffer.save(); | |
// translate by default to screen coordinates | |
frameBuffer.translate(-api.viewport.screenX, -api.viewport.screenY); | |
// substract the map offset to current the current pos | |
api.viewport.screenX -= api.currentLevel.pos.x; | |
api.viewport.screenY -= api.currentLevel.pos.y; | |
// update all objects, | |
// specifying the viewport as the rectangle area to redraw | |
api.world.draw(frameBuffer, api.viewport); | |
//restore context | |
frameBuffer.restore(); | |
// draw our camera/viewport | |
api.viewport.draw(frameBuffer); | |
} | |
isDirty = false; | |
}; | |
// return our object | |
return api; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013 melonJS | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* A singleton object representing the device capabilities and specific events | |
* @namespace me.device | |
* @memberOf me | |
*/ | |
me.device = (function() { | |
// defines object for holding public information/functionality. | |
var obj = {}; | |
// private properties | |
var accelInitialized = false; | |
var deviceOrientationInitialized = false; | |
var devicePixelRatio = null; | |
/** | |
* check the device capapbilities | |
* @ignore | |
*/ | |
obj._check = function() { | |
// detect audio capabilities (should be moved here too) | |
me.audio.detectCapabilities(); | |
// future proofing (MS) feature detection | |
me.device.pointerEnabled = navigator.pointerEnabled || navigator.msPointerEnabled; | |
navigator.maxTouchPoints = navigator.maxTouchPoints || navigator.msMaxTouchPoints || 0; | |
window.gesture = window.gesture || window.MSGesture; | |
// detect touch capabilities | |
me.device.touch = ('createTouch' in document) || ('ontouchstart' in window) || | |
(navigator.isCocoonJS) || (navigator.maxTouchPoints > 0); | |
// detect platform | |
me.device.isMobile = me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i) || false; | |
// accelerometer detection | |
me.device.hasAccelerometer = ( | |
(typeof (window.DeviceMotionEvent) !== 'undefined') || ( | |
(typeof (window.Windows) !== 'undefined') && | |
(typeof (Windows.Devices.Sensors.Accelerometer) === 'function') | |
) | |
); | |
if (window.DeviceOrientationEvent) { | |
me.device.hasDeviceOrientation = true; | |
} | |
try { | |
obj.localStorage = !!window.localStorage; | |
} catch (e) { | |
// the above generates an exception when cookies are blocked | |
obj.localStorage = false; | |
} | |
}; | |
// ----- PUBLIC Properties & Functions ----- | |
// Browser capabilities | |
/** | |
* Browser User Agent | |
* @type Boolean | |
* @readonly | |
* @name ua | |
* @memberOf me.device | |
*/ | |
obj.ua = navigator.userAgent; | |
/** | |
* Browser Audio capabilities | |
* @type Boolean | |
* @readonly | |
* @name sound | |
* @memberOf me.device | |
*/ | |
obj.sound = false; | |
/** | |
* Browser Local Storage capabilities <br> | |
* (this flag will be set to false if cookies are blocked) | |
* @type Boolean | |
* @readonly | |
* @name localStorage | |
* @memberOf me.device | |
*/ | |
obj.localStorage = false; | |
/** | |
* Browser accelerometer capabilities | |
* @type Boolean | |
* @readonly | |
* @name hasAccelerometer | |
* @memberOf me.device | |
*/ | |
obj.hasAccelerometer = false; | |
/** | |
* Browser device orientation | |
* @type Boolean | |
* @readonly | |
* @name hasDeviceOrientation | |
* @memberOf me.device | |
*/ | |
obj.hasDeviceOrientation = false; | |
/** | |
* Browser Base64 decoding capability | |
* @type Boolean | |
* @readonly | |
* @name nativeBase64 | |
* @memberOf me.device | |
*/ | |
obj.nativeBase64 = (typeof(window.atob) === 'function'); | |
/** | |
* Touch capabilities | |
* @type Boolean | |
* @readonly | |
* @name touch | |
* @memberOf me.device | |
*/ | |
obj.touch = false; | |
/** | |
* equals to true if a mobile device <br> | |
* (Android | iPhone | iPad | iPod | BlackBerry | Windows Phone) | |
* @type Boolean | |
* @readonly | |
* @name isMobile | |
* @memberOf me.device | |
*/ | |
obj.isMobile = false; | |
/** | |
* The device current orientation status. <br> | |
* 0 : default orientation<br> | |
* 90 : 90 degrees clockwise from default<br> | |
* -90 : 90 degrees anti-clockwise from default<br> | |
* 180 : 180 degrees from default | |
* @type Number | |
* @readonly | |
* @name orientation | |
* @memberOf me.device | |
*/ | |
obj.orientation = 0; | |
/** | |
* contains the g-force acceleration along the x-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationX | |
* @memberOf me.device | |
*/ | |
obj.accelerationX = 0; | |
/** | |
* contains the g-force acceleration along the y-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationY | |
* @memberOf me.device | |
*/ | |
obj.accelerationY = 0; | |
/** | |
* contains the g-force acceleration along the z-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationZ | |
* @memberOf me.device | |
*/ | |
obj.accelerationZ = 0; | |
/** | |
* Device orientation Gamma property. Gives angle on tilting a portrait held phone left or right | |
* @public | |
* @type Number | |
* @readonly | |
* @name gamma | |
* @memberOf me.device | |
*/ | |
obj.gamma = 0; | |
/** | |
* Device orientation Beta property. Gives angle on tilting a portrait held phone forward or backward | |
* @public | |
* @type Number | |
* @readonly | |
* @name beta | |
* @memberOf me.device | |
*/ | |
obj.beta = 0; | |
/** | |
* Device orientation Alpha property. Gives angle based on the rotation of the phone around its z axis. | |
* The z-axis is perpendicular to the phone, facing out from the center of the screen. | |
* @public | |
* @type Number | |
* @readonly | |
* @name alpha | |
* @memberOf me.device | |
*/ | |
obj.alpha = 0; | |
/** | |
* return the device pixel ratio | |
* @name getPixelRatio | |
* @memberOf me.device | |
* @function | |
*/ | |
obj.getPixelRatio = function() { | |
if (devicePixelRatio===null) { | |
var _context = me.video.getScreenContext(); | |
var _devicePixelRatio = window.devicePixelRatio || 1, | |
_backingStoreRatio = _context.webkitBackingStorePixelRatio || | |
_context.mozBackingStorePixelRatio || | |
_context.msBackingStorePixelRatio || | |
_context.oBackingStorePixelRatio || | |
_context.backingStorePixelRatio || 1; | |
devicePixelRatio = _devicePixelRatio / _backingStoreRatio; | |
} | |
return devicePixelRatio; | |
}; | |
/** | |
* return the device storage | |
* @name getStorage | |
* @memberOf me.device | |
* @function | |
* @param {String} [type="local"] | |
* @return me.save object | |
*/ | |
obj.getStorage = function(type) { | |
type = type || "local"; | |
switch (type) { | |
case "local" : | |
return me.save; | |
default : | |
break; | |
} | |
throw "melonJS : storage type " + type + " not supported"; | |
}; | |
/** | |
* event management (Accelerometer) | |
* http://www.mobilexweb.com/samples/ball.html | |
* http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5 | |
* @ignore | |
*/ | |
function onDeviceMotion(e) { | |
if (e.reading) { | |
// For Windows 8 devices | |
obj.accelerationX = e.reading.accelerationX; | |
obj.accelerationY = e.reading.accelerationY; | |
obj.accelerationZ = e.reading.accelerationZ; | |
} else { | |
// Accelerometer information | |
obj.accelerationX = e.accelerationIncludingGravity.x; | |
obj.accelerationY = e.accelerationIncludingGravity.y; | |
obj.accelerationZ = e.accelerationIncludingGravity.z; | |
} | |
} | |
function onDeviceRotate(e) { | |
obj.gamma = e.gamma; | |
obj.beta = e.beta; | |
obj.alpha = e.alpha; | |
} | |
/** | |
* watch Accelerator event | |
* @name watchAccelerometer | |
* @memberOf me.device | |
* @public | |
* @function | |
* @return {Boolean} false if not supported by the device | |
*/ | |
obj.watchAccelerometer = function () { | |
if (me.device.hasAccelerometer) { | |
if (!accelInitialized) { | |
if (typeof Windows === 'undefined') { | |
// add a listener for the devicemotion event | |
window.addEventListener('devicemotion', onDeviceMotion, false); | |
} else { | |
// On Windows 8 Device | |
var accelerometer = Windows.Devices.Sensors.Accelerometer.getDefault(); | |
if (accelerometer) { | |
// Capture event at regular intervals | |
var minInterval = accelerometer.minimumReportInterval; | |
var Interval = minInterval >= 16 ? minInterval : 25; | |
accelerometer.reportInterval = Interval; | |
accelerometer.addEventListener('readingchanged', onDeviceMotion, false); | |
} | |
} | |
accelInitialized = true; | |
} | |
return true; | |
} | |
return false; | |
}; | |
/** | |
* unwatch Accelerometor event | |
* @name unwatchAccelerometer | |
* @memberOf me.device | |
* @public | |
* @function | |
*/ | |
obj.unwatchAccelerometer = function() { | |
if (accelInitialized) { | |
if (typeof Windows === 'undefined') { | |
// add a listener for the mouse | |
window.removeEventListener('devicemotion', onDeviceMotion, false); | |
} else { | |
// On Windows 8 Devices | |
var accelerometer = Windows.Device.Sensors.Accelerometer.getDefault(); | |
accelerometer.removeEventListener('readingchanged', onDeviceMotion, false); | |
} | |
accelInitialized = false; | |
} | |
}; | |
/** | |
* watch the device orientation event | |
* @name watchDeviceOrientation | |
* @memberOf me.device | |
* @public | |
* @function | |
* @return {Boolean} false if not supported by the device | |
*/ | |
obj.watchDeviceOrientation = function() { | |
if(me.device.hasDeviceOrientation && !deviceOrientationInitialized) { | |
window.addEventListener('deviceorientation', onDeviceRotate, false); | |
deviceOrientationInitialized = true; | |
} | |
return false; | |
}; | |
/** | |
* unwatch Device orientation event | |
* @name unwatchDeviceOrientation | |
* @memberOf me.device | |
* @public | |
* @function | |
*/ | |
obj.unwatchDeviceOrientation = function() { | |
if(deviceOrientationInitialized) { | |
window.removeEventListener('deviceorientation', onDeviceRotate, false); | |
deviceOrientationInitialized = false; | |
} | |
}; | |
return obj; | |
})(); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013 melonJS | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* a Timer object to manage time function (FPS, Game Tick, Time...)<p> | |
* There is no constructor function for me.timer | |
* @namespace me.timer | |
* @memberOf me | |
*/ | |
me.timer = (function() { | |
// hold public stuff in our api | |
var api = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
//hold element to display fps | |
var framecount = 0; | |
var framedelta = 0; | |
/* fps count stuff */ | |
var last = 0; | |
var now = 0; | |
var delta = 0; | |
var step = Math.ceil(1000 / me.sys.fps); // ROUND IT ? | |
// define some step with some margin | |
var minstep = (1000 / me.sys.fps) * 1.25; // IS IT NECESSARY? | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* last game tick value | |
* @public | |
* @type Int | |
* @name tick | |
* @memberOf me.timer | |
*/ | |
api.tick = 1.0; | |
/** | |
* last measured fps rate | |
* @public | |
* @type Int | |
* @name fps | |
* @memberOf me.timer | |
*/ | |
api.fps = 0; | |
/** | |
* init the timer | |
* @ignore | |
*/ | |
api.init = function() { | |
// reset variables to initial state | |
api.reset(); | |
}; | |
/** | |
* reset time (e.g. usefull in case of pause) | |
* @name reset | |
* @memberOf me.timer | |
* @ignore | |
* @function | |
*/ | |
api.reset = function() { | |
// set to "now" | |
now = last = Date.now(); | |
// reset delta counting variables | |
framedelta = 0; | |
framecount = 0; | |
}; | |
/** | |
* Return the current time, in milliseconds elapsed between midnight, January 1, 1970, and the current date and time. | |
* @name getTime | |
* @memberOf me.timer | |
* @return {Number} | |
* @function | |
*/ | |
api.getTime = function() { | |
return now; | |
}; | |
/** | |
* compute the actual frame time and fps rate | |
* @name computeFPS | |
* @ignore | |
* @memberOf me.timer | |
* @function | |
*/ | |
api.countFPS = function() { | |
framecount++; | |
framedelta += delta; | |
if (framecount % 10 === 0) { | |
this.fps = (~~((1000 * framecount) / framedelta)).clamp(0, me.sys.fps); | |
framedelta = 0; | |
framecount = 0; | |
} | |
}; | |
/** | |
* update game tick | |
* should be called once a frame | |
* @ignore | |
*/ | |
api.update = function() { | |
last = now; | |
now = Date.now(); | |
delta = (now - last); | |
// get the game tick | |
api.tick = (delta > minstep && me.sys.interpolation) ? delta / step : 1; | |
}; | |
// return our apiect | |
return api; | |
})(); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* a generic 2D Vector Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {Number} [x=0] x value of the vector | |
* @param {Number} [y=0] y value of the vector | |
*/ | |
me.Vector2d = Object.extend( | |
/** @scope me.Vector2d.prototype */ | |
{ | |
/** | |
* x value of the vector | |
* @public | |
* @type Number | |
* @name x | |
* @memberOf me.Vector2d | |
*/ | |
x : 0, | |
/** | |
* y value of the vector | |
* @public | |
* @type Number | |
* @name y | |
* @memberOf me.Vector2d | |
*/ | |
y : 0, | |
/** @ignore */ | |
init : function(x, y) { | |
this.x = x || 0; | |
this.y = y || 0; | |
}, | |
/** | |
* set the Vector x and y properties to the given values<br> | |
* @name set | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} x | |
* @param {Number} y | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
set : function(x, y) { | |
this.x = x; | |
this.y = y; | |
return this; | |
}, | |
/** | |
* set the Vector x and y properties to 0 | |
* @name setZero | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
setZero : function() { | |
return this.set(0, 0); | |
}, | |
/** | |
* set the Vector x and y properties using the passed vector | |
* @name setV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
setV : function(v) { | |
this.x = v.x; | |
this.y = v.y; | |
return this; | |
}, | |
/** | |
* Add the passed vector to this vector | |
* @name add | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
add : function(v) { | |
this.x += v.x; | |
this.y += v.y; | |
return this; | |
}, | |
/** | |
* Substract the passed vector to this vector | |
* @name sub | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
sub : function(v) { | |
this.x -= v.x; | |
this.y -= v.y; | |
return this; | |
}, | |
/** | |
* Multiply this vector values by the passed vector | |
* @name scale | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
scale : function(v) { | |
this.x *= v.x; | |
this.y *= v.y; | |
return this; | |
}, | |
/** | |
* Divide this vector values by the passed value | |
* @name div | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} value | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
div : function(n) { | |
this.x /= n; | |
this.y /= n; | |
return this; | |
}, | |
/** | |
* Update this vector values to absolute values | |
* @name abs | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
abs : function() { | |
if (this.x < 0) | |
this.x = -this.x; | |
if (this.y < 0) | |
this.y = -this.y; | |
return this; | |
}, | |
/** | |
* Clamp the vector value within the specified value range | |
* @name clamp | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} low | |
* @param {Number} high | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
clamp : function(low, high) { | |
return new me.Vector2d(this.x.clamp(low, high), this.y.clamp(low, high)); | |
}, | |
/** | |
* Clamp this vector value within the specified value range | |
* @name clampSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} low | |
* @param {Number} high | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
clampSelf : function(low, high) { | |
this.x = this.x.clamp(low, high); | |
this.y = this.y.clamp(low, high); | |
return this; | |
}, | |
/** | |
* Update this vector with the minimum value between this and the passed vector | |
* @name minV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
minV : function(v) { | |
this.x = this.x < v.x ? this.x : v.x; | |
this.y = this.y < v.y ? this.y : v.y; | |
return this; | |
}, | |
/** | |
* Update this vector with the maximum value between this and the passed vector | |
* @name maxV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
maxV : function(v) { | |
this.x = this.x > v.x ? this.x : v.x; | |
this.y = this.y > v.y ? this.y : v.y; | |
return this; | |
}, | |
/** | |
* Floor the vector values | |
* @name floor | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
floor : function() { | |
return new me.Vector2d(~~this.x, ~~this.y); | |
}, | |
/** | |
* Floor this vector values | |
* @name floorSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
floorSelf : function() { | |
this.x = ~~this.x; | |
this.y = ~~this.y; | |
return this; | |
}, | |
/** | |
* Ceil the vector values | |
* @name ceil | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
ceil : function() { | |
return new me.Vector2d(Math.ceil(this.x), Math.ceil(this.y)); | |
}, | |
/** | |
* Ceil this vector values | |
* @name ceilSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
ceilSelf : function() { | |
this.x = Math.ceil(this.x); | |
this.y = Math.ceil(this.y); | |
return this; | |
}, | |
/** | |
* Negate the vector values | |
* @name negate | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
negate : function() { | |
return new me.Vector2d(-this.x, -this.y); | |
}, | |
/** | |
* Negate this vector values | |
* @name negateSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
negateSelf : function() { | |
this.x = -this.x; | |
this.y = -this.y; | |
return this; | |
}, | |
/** | |
* Copy the x,y values of the passed vector to this one | |
* @name copy | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
copy : function(v) { | |
this.x = v.x; | |
this.y = v.y; | |
return this; | |
}, | |
/** | |
* return true if the two vectors are the same | |
* @name equals | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Boolean} | |
*/ | |
equals : function(v) { | |
return ((this.x === v.x) && (this.y === v.y)); | |
}, | |
/** | |
* return the length (magnitude) of this vector | |
* @name length | |
* @memberOf me.Vector2d | |
* @function | |
* @return {Number} | |
*/ | |
length : function() { | |
return Math.sqrt(this.x * this.x + this.y * this.y); | |
}, | |
/** | |
* normalize this vector (scale the vector so that its magnitude is 1) | |
* @name normalize | |
* @memberOf me.Vector2d | |
* @function | |
* @return {Number} | |
*/ | |
normalize : function() { | |
var len = this.length(); | |
// some limit test | |
if (len < Number.MIN_VALUE) { | |
return 0.0; | |
} | |
var invL = 1.0 / len; | |
this.x *= invL; | |
this.y *= invL; | |
return len; | |
}, | |
/** | |
* return the dot product of this vector and the passed one | |
* @name dotProduct | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} | |
*/ | |
dotProduct : function(/**me.Vector2d*/ v) { | |
return this.x * v.x + this.y * v.y; | |
}, | |
/** | |
* return the distance between this vector and the passed one | |
* @name distance | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} | |
*/ | |
distance : function(v) { | |
return Math.sqrt((this.x - v.x) * (this.x - v.x) + (this.y - v.y) * (this.y - v.y)); | |
}, | |
/** | |
* return the angle between this vector and the passed one | |
* @name angle | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} angle in radians | |
*/ | |
angle : function(v) { | |
return Math.atan2((v.y - this.y), (v.x - this.x)); | |
}, | |
/** | |
* return a clone copy of this vector | |
* @name clone | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
clone : function() { | |
return new me.Vector2d(this.x, this.y); | |
}, | |
/** | |
* convert the object to a string representation | |
* @name toString | |
* @memberOf me.Vector2d | |
* @function | |
* @return {String} | |
*/ | |
toString : function() { | |
return 'x:' + this.x + ',y:' + this.y; | |
} | |
}); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/************************************************************************************/ | |
/* */ | |
/* a rectangle Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a rectangle Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v x,y position of the rectange | |
* @param {Number} w width of the rectangle | |
* @param {Number} h height of the rectangle | |
*/ | |
me.Rect = Object.extend( | |
/** @scope me.Rect.prototype */ { | |
/** | |
* position of the Rectange | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.Rect | |
*/ | |
pos : null, | |
/** | |
* allow to reduce the collision box size<p> | |
* while keeping the original position vector (pos)<p> | |
* corresponding to the entity<p> | |
* colPos is a relative offset to pos | |
* @ignore | |
* @type me.Vector2d | |
* @name colPos | |
* @memberOf me.Rect | |
* @see me.Rect#adjustSize | |
*/ | |
colPos : null, | |
/** | |
* left coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name left | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* right coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name right | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* bottom coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name bottom | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* top coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name top | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* width of the Rectange | |
* @public | |
* @type Int | |
* @name width | |
* @memberOf me.Rect | |
*/ | |
width : 0, | |
/** | |
* height of the Rectange | |
* @public | |
* @type Int | |
* @name height | |
* @memberOf me.Rect | |
*/ | |
height : 0, | |
// half width/height | |
hWidth : 0, | |
hHeight : 0, | |
// the shape type | |
shapeType : "Rectangle", | |
/* | |
* will be replaced by pos and replace colPos in 1.0.0 :) | |
* @ignore | |
*/ | |
offset: null, | |
/** @ignore */ | |
init : function(v, w, h) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
this.pos.setV(v); | |
if (this.offset === null) { | |
this.offset = new me.Vector2d(); | |
} | |
this.offset.set(0, 0); | |
// allow to reduce the hitbox size | |
// while on keeping the original pos vector | |
// corresponding to the entity | |
if (this.colPos === null) { | |
this.colPos = new me.Vector2d(); | |
} | |
this.colPos.setV(0, 0); | |
this.width = w; | |
this.height = h; | |
// half width/height | |
this.hWidth = ~~(w / 2); | |
this.hHeight = ~~(h / 2); | |
// redefine some properties to ease our life when getting the rectangle coordinates | |
Object.defineProperty(this, "left", { | |
get : function() { | |
return this.pos.x; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "right", { | |
get : function() { | |
return this.pos.x + this.width; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "top", { | |
get : function() { | |
return this.pos.y; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "bottom", { | |
get : function() { | |
return this.pos.y + this.height; | |
}, | |
configurable : true | |
}); | |
}, | |
/** | |
* set new value to the rectangle | |
* @name set | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} v x,y position for the rectangle | |
* @param {Number} w width of the rectangle | |
* @param {Number} h height of the rectangle | |
*/ | |
set : function(v, w, h) { | |
this.pos.setV(v); | |
this.width = w; | |
this.height = h; | |
this.hWidth = ~~(w / 2); | |
this.hHeight = ~~(h / 2); | |
//reset offset | |
this.offset.set(0, 0); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.Rect | |
* @function | |
* @return {me.Rect} new rectangle | |
*/ | |
getBounds : function() { | |
return this.clone(); | |
}, | |
/** | |
* clone this rectangle | |
* @name clone | |
* @memberOf me.Rect | |
* @function | |
* @return {me.Rect} new rectangle | |
*/ | |
clone : function() { | |
return new me.Rect(this.pos.clone(), this.width, this.height); | |
}, | |
/** | |
* translate the rect by the specified offset | |
* @name translate | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x offset | |
* @param {Number} y y offset | |
* @return {me.Rect} this rectangle | |
*/ | |
translate : function(x, y) { | |
this.pos.x+=x; | |
this.pos.y+=y; | |
return this; | |
}, | |
/** | |
* translate the rect by the specified vector | |
* @name translateV | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} v vector offset | |
* @return {me.Rect} this rectangle | |
*/ | |
translateV : function(v) { | |
this.pos.add(v); | |
return this; | |
}, | |
/** | |
* merge this rectangle with another one | |
* @name union | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect other rectangle to union with | |
* @return {me.Rect} the union(ed) rectangle | |
*/ | |
union : function(/** {me.Rect} */ r) { | |
var x1 = Math.min(this.pos.x, r.pos.x); | |
var y1 = Math.min(this.pos.y, r.pos.y); | |
this.width = Math.ceil( Math.max(this.pos.x + this.width, r.pos.x + r.width) - x1 ); | |
this.height = Math.ceil( Math.max(this.pos.y + this.height, r.pos.y + r.height) - y1 ); | |
this.hWidth = ~~(this.width / 2); | |
this.hHeight = ~~(this.height / 2); | |
this.pos.x = ~~x1; | |
this.pos.y = ~~y1; | |
return this; | |
}, | |
/** | |
* update the size of the collision rectangle<br> | |
* the colPos Vector is then set as a relative offset to the initial position (pos)<br> | |
* <img src="images/me.Rect.colpos.png"/> | |
* @name adjustSize | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x offset (specify -1 to not change the width) | |
* @param {Number} w width of the hit box | |
* @param {Number} y y offset (specify -1 to not change the height) | |
* @param {Number} h height of the hit box | |
*/ | |
adjustSize : function(x, w, y, h) { | |
if (x !== -1) { | |
this.colPos.x = x; | |
this.width = w; | |
this.hWidth = ~~(this.width / 2); | |
// avoid Property definition if not necessary | |
if (this.left !== this.pos.x + this.colPos.x) { | |
// redefine our properties taking colPos into account | |
Object.defineProperty(this, "left", { | |
get : function() { | |
return this.pos.x + this.colPos.x; | |
}, | |
configurable : true | |
}); | |
} | |
if (this.right !== this.pos.x + this.colPos.x + this.width) { | |
Object.defineProperty(this, "right", { | |
get : function() { | |
return this.pos.x + this.colPos.x + this.width; | |
}, | |
configurable : true | |
}); | |
} | |
} | |
if (y !== -1) { | |
this.colPos.y = y; | |
this.height = h; | |
this.hHeight = ~~(this.height / 2); | |
// avoid Property definition if not necessary | |
if (this.top !== this.pos.y + this.colPos.y) { | |
// redefine our properties taking colPos into account | |
Object.defineProperty(this, "top", { | |
get : function() { | |
return this.pos.y + this.colPos.y; | |
}, | |
configurable : true | |
}); | |
} | |
if (this.bottom !== this.pos.y + this.colPos.y + this.height) { | |
Object.defineProperty(this, "bottom", { | |
get : function() { | |
return this.pos.y + this.colPos.y + this.height; | |
}, | |
configurable : true | |
}); | |
} | |
} | |
}, | |
/** | |
* | |
* flip on X axis | |
* usefull when used as collision box, in a non symetric way | |
* @ignore | |
* @param sw the sprite width | |
*/ | |
flipX : function(sw) { | |
this.colPos.x = sw - this.width - this.colPos.x; | |
this.hWidth = ~~(this.width / 2); | |
}, | |
/** | |
* | |
* flip on Y axis | |
* usefull when used as collision box, in a non symetric way | |
* @ignore | |
* @param sh the height width | |
*/ | |
flipY : function(sh) { | |
this.colPos.y = sh - this.height - this.colPos.y; | |
this.hHeight = ~~(this.height / 2); | |
}, | |
/** | |
* return true if this rectangle is equal to the specified one | |
* @name equals | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} | |
*/ | |
equals : function(r) { | |
return (this.left === r.left && | |
this.right === r.right && | |
this.top === r.top && | |
this.bottom === r.bottom); | |
}, | |
/** | |
* check if this rectangle is intersecting with the specified one | |
* @name overlaps | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if overlaps | |
*/ | |
overlaps : function(r) { | |
return (this.left < r.right && | |
r.left < this.right && | |
this.top < r.bottom && | |
r.top < this.bottom); | |
}, | |
/** | |
* check if this rectangle is within the specified one | |
* @name within | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if within | |
*/ | |
within: function(r) { | |
return (r.left <= this.left && | |
r.right >= this.right && | |
r.top <= this.top && | |
r.bottom >= this.bottom); | |
}, | |
/** | |
* check if this rectangle contains the specified one | |
* @name contains | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if contains | |
*/ | |
contains: function(r) { | |
return (r.left >= this.left && | |
r.right <= this.right && | |
r.top >= this.top && | |
r.bottom <= this.bottom); | |
}, | |
/** | |
* check if this rectangle contains the specified point | |
* @name containsPointV | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} point | |
* @return {Boolean} true if contains | |
*/ | |
containsPointV: function(v) { | |
return this.containsPoint(v.x, v.y); | |
}, | |
/** | |
* check if this rectangle contains the specified point | |
* @name containsPoint | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x coordinate | |
* @param {Number} y y coordinate | |
* @return {Boolean} true if contains | |
*/ | |
containsPoint: function(x, y) { | |
return (x >= this.left && x <= this.right && | |
(y >= this.top) && y <= this.bottom); | |
}, | |
/** | |
* AABB vs AABB collission dectection<p> | |
* If there was a collision, the return vector will contains the following values: | |
* @example | |
* if (v.x != 0 || v.y != 0) | |
* { | |
* if (v.x != 0) | |
* { | |
* // x axis | |
* if (v.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (v.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* | |
* } | |
* @ignore | |
* @param {me.Rect} rect | |
* @return {me.Vector2d} | |
*/ | |
collideWithRectangle : function(/** {me.Rect} */ rect) { | |
// response vector | |
var p = new me.Vector2d(0, 0); | |
// check if both box are overlaping | |
if (this.overlaps(rect)) { | |
// compute delta between this & rect | |
var dx = this.left + this.hWidth - rect.left - rect.hWidth; | |
var dy = this.top + this.hHeight - rect.top - rect.hHeight; | |
// compute penetration depth for both axis | |
p.x = (rect.hWidth + this.hWidth) - (dx < 0 ? -dx : dx); // - Math.abs(dx); | |
p.y = (rect.hHeight + this.hHeight) - (dy < 0 ? -dy : dy); // - Math.abs(dy); | |
// check and "normalize" axis | |
if (p.x < p.y) { | |
p.y = 0; | |
p.x = dx < 0 ? -p.x : p.x; | |
} else { | |
p.x = 0; | |
p.y = dy < 0 ? -p.y : p.y; | |
} | |
} | |
return p; | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
// draw the rectangle | |
context.strokeStyle = color || "red"; | |
context.strokeRect(this.left, this.top, this.width, this.height); | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a Ellipse Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* an ellipse Object | |
* (Tiled specifies top-left coordinates, and width and height of the ellipse) | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v top-left origin position of the Ellipse | |
* @param {Number} w width of the elipse | |
* @param {Number} h height of the elipse | |
*/ | |
me.Ellipse = Object.extend( | |
/** @scope me.Ellipse.prototype */ { | |
/** | |
* center point of the Ellipse | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.Ellipse | |
*/ | |
pos : null, | |
/** | |
* radius (x/y) of the ellipse | |
* @public | |
* @type me.Vector2d | |
* @name radius | |
* @memberOf me.Ellipse | |
*/ | |
radius : null, | |
// the shape type | |
shapeType : "Ellipse", | |
/** @ignore */ | |
init : function(v, w, h) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
if (this.radius === null) { | |
this.radius = new me.Vector2d(); | |
} | |
this.set(v, w, h); | |
}, | |
/** | |
* set new value to the Ellipse | |
* @name set | |
* @memberOf me.Ellipse | |
* @function | |
* @param {me.Vector2d} v top-left origin position of the Ellipse | |
* @param {Number} w width of the Ellipse | |
* @param {Number} h height of the Ellipse | |
*/ | |
set : function(v, w, h) { | |
this.radius.set(w/2, h/2); | |
this.pos.setV(v).add(this.radius); | |
this.offset = new me.Vector2d(); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.Ellipse | |
* @function | |
* @return {me.Rect} the bounding box Rectangle object | |
*/ | |
getBounds : function() { | |
//will return a rect, with pos being the top-left coordinates | |
return new me.Rect( | |
this.pos.clone().sub(this.radius), | |
this.radius.x * 2, | |
this.radius.y * 2 | |
); | |
}, | |
/** | |
* clone this Ellipse | |
* @name clone | |
* @memberOf me.Ellipse | |
* @function | |
* @return {me.Ellipse} new Ellipse | |
*/ | |
clone : function() { | |
return new me.Ellipse(this.pos.clone(), this.radius.x * 2, this.radius.y * 2); | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
// http://tinyurl.com/opnro2r | |
context.save(); | |
context.beginPath(); | |
context.translate(this.pos.x-this.radius.x, this.pos.y-this.radius.y); | |
context.scale(this.radius.x, this.radius.y); | |
context.arc(1, 1, 1, 0, 2 * Math.PI, false); | |
context.restore(); | |
context.strokeStyle = color || "red"; | |
context.stroke(); | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a PolyShape Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a polyshape (polygone/polyline) Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v origin point of the PolyShape | |
* @param {me.Vector2d[]} points array of vector defining the polyshape | |
* @param {Boolean} closed true if a polygone, false if a polyline | |
*/ | |
me.PolyShape = Object.extend( | |
/** @scope me.PolyShape.prototype */ { | |
/** | |
* @ignore | |
*/ | |
offset : null, | |
/** | |
* origin point of the PolyShape | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.PolyShape | |
*/ | |
pos : null, | |
/** | |
* Array of points defining the polyshape | |
* @public | |
* @type me.Vector2d[] | |
* @name points | |
* @memberOf me.PolyShape | |
*/ | |
points : null, | |
/** | |
* Specified if the shape is closed (i.e. polygon) | |
* @public | |
* @type boolean | |
* @name closed | |
* @memberOf me.PolyShape | |
*/ | |
closed : null, | |
// the shape type | |
shapeType : "PolyShape", | |
/** @ignore */ | |
init : function(v, points, closed) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
if (this.offset === null) { | |
this.offset = new me.Vector2d(); | |
} | |
this.set(v, points, closed); | |
}, | |
/** | |
* set new value to the PolyShape | |
* @name set | |
* @memberOf me.PolyShape | |
* @function | |
* @param {me.Vector2d} v origin point of the PolyShape | |
* @param {me.Vector2d[]} points array of vector defining the polyshape | |
* @param {Boolean} closed true if a polygone, false if a polyline | |
*/ | |
set : function(v, points, closed) { | |
this.pos.setV(v); | |
this.points = points; | |
this.closed = (closed === true); | |
this.offset.set(0, 0); | |
this.getBounds(); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.PolyShape | |
* @function | |
* @return {me.Rect} the bounding box Rectangle object | |
*/ | |
getBounds : function() { | |
var pos = this.offset, right = 0, bottom = 0; | |
this.points.forEach(function(point) { | |
pos.x = Math.min(pos.x, point.x); | |
pos.y = Math.min(pos.y, point.y); | |
right = Math.max(right, point.x); | |
bottom = Math.max(bottom, point.y); | |
}); | |
return new me.Rect(pos, right - pos.x, bottom - pos.y); | |
}, | |
/** | |
* clone this PolyShape | |
* @name clone | |
* @memberOf me.PolyShape | |
* @function | |
* @return {me.PolyShape} new PolyShape | |
*/ | |
clone : function() { | |
return new me.PolyShape(this.pos.clone(), this.points, this.closed); | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
context.save(); | |
context.translate(-this.offset.x, -this.offset.y); | |
context.strokeStyle = color || "red"; | |
context.beginPath(); | |
context.moveTo(this.points[0].x, this.points[0].y); | |
this.points.forEach(function(point) { | |
context.lineTo(point.x, point.y); | |
context.moveTo(point.x, point.y); | |
}); | |
if (this.closed===true) { | |
context.lineTo(this.points[0].x, this.points[0].y); | |
} | |
context.stroke(); | |
context.restore(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* debug stuff. | |
* @namespace | |
*/ | |
me.debug = { | |
/** | |
* render Collision Map layer<br> | |
* default value : false | |
* @type Boolean | |
* @memberOf me.debug | |
*/ | |
renderCollisionMap : false | |
}; | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* A base class for renderable objects. | |
* @class | |
* @extends me.Rect | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} pos position of the renderable object | |
* @param {Number} width object width | |
* @param {Number} height object height | |
*/ | |
me.Renderable = me.Rect.extend( | |
/** @scope me.Renderable.prototype */ | |
{ | |
/** | |
* to identify the object as a renderable object | |
* @ignore | |
*/ | |
isRenderable : true, | |
/** | |
* (G)ame (U)nique (Id)entifier" <br> | |
* a GUID will be allocated for any renderable object added <br> | |
* to an object container (including the `me.game.world` container) | |
* @public | |
* @type String | |
* @name GUID | |
* @memberOf me.Renderable | |
*/ | |
GUID : undefined, | |
/** | |
* the visible state of the renderable object<br> | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name visible | |
* @memberOf me.Renderable | |
*/ | |
visible : true, | |
/** | |
* Whether the renderable object is visible and within the viewport<br> | |
* default value : false | |
* @public | |
* @readonly | |
* @type Boolean | |
* @name inViewport | |
* @memberOf me.Renderable | |
*/ | |
inViewport : false, | |
/** | |
* Whether the renderable object will always update, even when outside of the viewport<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name alwaysUpdate | |
* @memberOf me.Renderable | |
*/ | |
alwaysUpdate : false, | |
/** | |
* Whether to update this object when the game is paused. | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name updateWhenPaused | |
* @memberOf me.Renderable | |
*/ | |
updateWhenPaused: false, | |
/** | |
* make the renderable object persistent over level changes<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name isPersistent | |
* @memberOf me.Renderable | |
*/ | |
isPersistent : false, | |
/** | |
* Define if a renderable follows screen coordinates (floating)<br> | |
* or the world coordinates (not floating)<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name floating | |
* @memberOf me.Renderable | |
*/ | |
floating : false, | |
/** | |
* Z-order for object sorting<br> | |
* default value : 0 | |
* @private | |
* @type Number | |
* @name z | |
* @memberOf me.Renderable | |
*/ | |
z : 0, | |
/** | |
* Define the object anchoring point<br> | |
* This is used when positioning, or scaling the object<br> | |
* The anchor point is a value between 0.0 and 1.0 (1.0 being the maximum size of the object) <br> | |
* (0, 0) means the top-left corner, <br> | |
* (1, 1) means the bottom-right corner, <br> | |
* default anchoring point is the center (0.5, 0.5) of the object. | |
* @public | |
* @type me.Vector2d | |
* @name anchorPoint | |
* @memberOf me.Renderable | |
*/ | |
anchorPoint: null, | |
/** | |
* Define the renderable opacity<br> | |
* @see me.Renderable#setOpacity | |
* @see me.Renderable#getOpacity | |
* @public | |
* @type Number | |
* @name me.Renderable#alpha | |
*/ | |
alpha: 1.0, | |
/** | |
* @ignore | |
*/ | |
init : function(pos, width, height) { | |
// call the parent constructor | |
this.parent(pos, width, height); | |
// set the default anchor point (middle of the renderable) | |
if (this.anchorPoint === null) { | |
this.anchorPoint = new me.Vector2d(); | |
} | |
this.anchorPoint.set(0.5, 0.5); | |
// ensure it's fully opaque by default | |
this.setOpacity(1.0); | |
}, | |
/** | |
* get the renderable alpha channel value<br> | |
* @name getOpacity | |
* @memberOf me.Renderable | |
* @function | |
* @return {Number} current opacity value between 0 and 1 | |
*/ | |
getOpacity : function() { | |
return this.alpha; | |
}, | |
/** | |
* set the renderable alpha channel value<br> | |
* @name setOpacity | |
* @memberOf me.Renderable | |
* @function | |
* @param {Number} alpha opacity value between 0 and 1 | |
*/ | |
setOpacity : function(alpha) { | |
if (typeof (alpha) === "number") { | |
this.alpha = alpha.clamp(0.0,1.0); | |
} | |
}, | |
/** | |
* update function | |
* called by the game manager on each game loop | |
* @name update | |
* @memberOf me.Renderable | |
* @function | |
* @protected | |
* @return false | |
**/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* object draw | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.Renderable | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context, color) { | |
// draw the parent rectangle | |
this.parent(context, color); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* A Simple object to display a sprite on screen. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {Image} image reference to the Sprite Image. See {@link me.loader#getImage} | |
* @param {Number} [spritewidth] sprite width | |
* @param {Number} [spriteheigth] sprite height | |
* @example | |
* // create a static Sprite Object | |
* mySprite = new me.SpriteObject (100, 100, me.loader.getImage("mySpriteImage")); | |
*/ | |
me.SpriteObject = me.Renderable.extend( | |
/** @scope me.SpriteObject.prototype */ | |
{ | |
// default scale ratio of the object | |
/** @ignore */ | |
scale : null, | |
// if true, image flipping/scaling is needed | |
scaleFlag : false, | |
// just to keep track of when we flip | |
lastflipX : false, | |
lastflipY : false, | |
// z position (for ordering display) | |
z : 0, | |
// image offset | |
offset : null, | |
/** | |
* Set the angle (in Radians) of a sprite to rotate it <br> | |
* WARNING: rotating sprites decreases performances | |
* @public | |
* @type Number | |
* @name me.SpriteObject#angle | |
*/ | |
angle: 0, | |
/** | |
* Source rotation angle for pre-rotating the source image<br> | |
* Commonly used for TexturePacker | |
* @ignore | |
*/ | |
_sourceAngle: 0, | |
// image reference | |
image : null, | |
// to manage the flickering effect | |
flickering : false, | |
flickerTimer : -1, | |
flickercb : null, | |
flickerState : false, | |
/** | |
* @ignore | |
*/ | |
init : function(x, y, image, spritewidth, spriteheight) { | |
// Used by the game engine to adjust visibility as the | |
// sprite moves in and out of the viewport | |
this.isSprite = true; | |
// call the parent constructor | |
this.parent(new me.Vector2d(x, y), | |
spritewidth || image.width, | |
spriteheight || image.height); | |
// cache image reference | |
this.image = image; | |
// scale factor of the object | |
this.scale = new me.Vector2d(1.0, 1.0); | |
this.lastflipX = this.lastflipY = false; | |
this.scaleFlag = false; | |
// set the default sprite index & offset | |
this.offset = new me.Vector2d(0, 0); | |
// make it visible by default | |
this.visible = true; | |
// non persistent per default | |
this.isPersistent = false; | |
// and not flickering | |
this.flickering = false; | |
}, | |
/** | |
* specify a transparent color | |
* @name setTransparency | |
* @memberOf me.SpriteObject | |
* @function | |
* @deprecated Use PNG or GIF with transparency instead | |
* @param {String} color color key in "#RRGGBB" format | |
*/ | |
setTransparency : function(col) { | |
// remove the # if present | |
col = (col.charAt(0) === "#") ? col.substring(1, 7) : col; | |
// applyRGB Filter (return a context object) | |
this.image = me.video.applyRGBFilter(this.image, "transparent", col.toUpperCase()).canvas; | |
}, | |
/** | |
* return the flickering state of the object | |
* @name isFlickering | |
* @memberOf me.SpriteObject | |
* @function | |
* @return {Boolean} | |
*/ | |
isFlickering : function() { | |
return this.flickering; | |
}, | |
/** | |
* make the object flicker | |
* @name flicker | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Number} duration expressed in frames | |
* @param {Function} callback Function to call when flickering ends | |
* @example | |
* // make the object flicker for 60 frame | |
* // and then remove it | |
* this.flicker(60, function() | |
* { | |
* me.game.remove(this); | |
* }); | |
*/ | |
flicker : function(duration, callback) { | |
this.flickerTimer = duration; | |
if (this.flickerTimer < 0) { | |
this.flickering = false; | |
this.flickercb = null; | |
} else if (!this.flickering) { | |
this.flickercb = callback; | |
this.flickering = true; | |
} | |
}, | |
/** | |
* Flip object on horizontal axis | |
* @name flipX | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipX : function(flip) { | |
if (flip !== this.lastflipX) { | |
this.lastflipX = flip; | |
// invert the scale.x value | |
this.scale.x = -this.scale.x; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* Flip object on vertical axis | |
* @name flipY | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipY : function(flip) { | |
if (flip !== this.lastflipY) { | |
this.lastflipY = flip; | |
// invert the scale.x value | |
this.scale.y = -this.scale.y; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* Resize the sprite around his center<br> | |
* @name resize | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Number} ratio scaling ratio | |
*/ | |
resize : function(ratio) { | |
if (ratio > 0) { | |
this.scale.x = this.scale.x < 0.0 ? -ratio : ratio; | |
this.scale.y = this.scale.y < 0.0 ? -ratio : ratio; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* sprite update<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name update | |
* @memberOf me.SpriteObject | |
* @function | |
* @protected | |
* @return false | |
**/ | |
update : function() { | |
//update the "flickering" state if necessary | |
if (this.flickering) { | |
this.flickerTimer -= me.timer.tick; | |
if (this.flickerTimer < 0) { | |
if (this.flickercb) | |
this.flickercb(); | |
this.flicker(-1); | |
} | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* object draw<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.SpriteObject | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context) { | |
// do nothing if we are flickering | |
if (this.flickering) { | |
this.flickerState = !this.flickerState; | |
if (!this.flickerState) return; | |
} | |
// save the current the context | |
context.save(); | |
// sprite alpha value | |
context.globalAlpha *= this.getOpacity(); | |
// clamp position vector to pixel grid | |
var xpos = ~~this.pos.x, ypos = ~~this.pos.y; | |
var w = this.width, h = this.height; | |
var angle = this.angle + this._sourceAngle; | |
if ((this.scaleFlag) || (angle!==0)) { | |
// calculate pixel pos of the anchor point | |
var ax = w * this.anchorPoint.x, ay = h * this.anchorPoint.y; | |
// translate to the defined anchor point | |
context.translate(xpos + ax, ypos + ay); | |
// scale | |
if (this.scaleFlag) | |
context.scale(this.scale.x, this.scale.y); | |
if (angle!==0) | |
context.rotate(angle); | |
if (this._sourceAngle!==0) { | |
// swap w and h for rotated source images | |
w = this.height; | |
h = this.width; | |
xpos = -ay; | |
ypos = -ax; | |
} | |
else { | |
// reset coordinates back to upper left coordinates | |
xpos = -ax; | |
ypos = -ay; | |
} | |
} | |
context.drawImage(this.image, | |
this.offset.x, this.offset.y, | |
w, h, | |
xpos, ypos, | |
w, h); | |
// restore the context | |
context.restore(); | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
this.onDestroyEvent.apply(this, arguments); | |
}, | |
/** | |
* OnDestroy Notification function<br> | |
* Called by engine before deleting the object | |
* @name onDestroyEvent | |
* @memberOf me.SpriteObject | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended ! | |
} | |
}); | |
/** | |
* an object to manage animation | |
* @class | |
* @extends me.SpriteObject | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {Image} image reference of the animation sheet | |
* @param {Number} spritewidth width of a single sprite within the spritesheet | |
* @param {Number} [spriteheight=image.height] height of a single sprite within the spritesheet | |
*/ | |
me.AnimationSheet = me.SpriteObject.extend( | |
/** @scope me.AnimationSheet.prototype */ | |
{ | |
// Spacing and margin | |
/** @ignore */ | |
spacing: 0, | |
/** @ignore */ | |
margin: 0, | |
/** | |
* pause and resume animation<br> | |
* default value : false; | |
* @public | |
* @type Boolean | |
* @name me.AnimationSheet#animationpause | |
*/ | |
animationpause : false, | |
/** | |
* animation cycling speed (delay between frame in ms)<br> | |
* default value : 100ms; | |
* @public | |
* @type Number | |
* @name me.AnimationSheet#animationspeed | |
*/ | |
animationspeed : 100, | |
/** @ignore */ | |
init : function(x, y, image, spritewidth, spriteheight, spacing, margin, atlas, atlasIndices) { | |
// hold all defined animation | |
this.anim = {}; | |
// a flag to reset animation | |
this.resetAnim = null; | |
// default animation sequence | |
this.current = null; | |
// default animation speed (ms) | |
this.animationspeed = 100; | |
// Spacing and margin | |
this.spacing = spacing || 0; | |
this.margin = margin || 0; | |
// call the constructor | |
this.parent(x, y, image, spritewidth, spriteheight, spacing, margin); | |
// store the current atlas information | |
this.textureAtlas = null; | |
this.atlasIndices = null; | |
// build the local textureAtlas | |
this.buildLocalAtlas(atlas || undefined, atlasIndices || undefined); | |
// create a default animation sequence with all sprites | |
this.addAnimation("default", null); | |
// set as default | |
this.setCurrentAnimation("default"); | |
}, | |
/** | |
* build the local (private) atlas | |
* @ignore | |
*/ | |
buildLocalAtlas : function (atlas, indices) { | |
// reinitialze the atlas | |
if (atlas !== undefined) { | |
this.textureAtlas = atlas; | |
this.atlasIndices = indices; | |
} else { | |
// regular spritesheet | |
this.textureAtlas = []; | |
// calculate the sprite count (line, col) | |
var spritecount = new me.Vector2d( | |
~~((this.image.width - this.margin) / (this.width + this.spacing)), | |
~~((this.image.height - this.margin) / (this.height + this.spacing)) | |
); | |
// build the local atlas | |
for ( var frame = 0, count = spritecount.x * spritecount.y; frame < count ; frame++) { | |
this.textureAtlas[frame] = { | |
name: ''+frame, | |
offset: new me.Vector2d( | |
this.margin + (this.spacing + this.width) * (frame % spritecount.x), | |
this.margin + (this.spacing + this.height) * ~~(frame / spritecount.x) | |
), | |
width: this.width, | |
height: this.height, | |
hWidth: this.width / 2, | |
hHeight: this.height / 2, | |
angle: 0 | |
}; | |
} | |
} | |
}, | |
/** | |
* add an animation <br> | |
* For fixed-sized cell sprite sheet, the index list must follow the logic as per the following example :<br> | |
* <img src="images/spritesheet_grid.png"/> | |
* @name addAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @param {Number[]|String[]} index list of sprite index or name defining the animation | |
* @param {Number} [animationspeed] cycling speed for animation in ms (delay between each frame). | |
* @see me.AnimationSheet#animationspeed | |
* @example | |
* // walking animation | |
* this.addAnimation("walk", [ 0, 1, 2, 3, 4, 5 ]); | |
* // eating animation | |
* this.addAnimation("eat", [ 6, 6 ]); | |
* // rolling animation | |
* this.addAnimation("roll", [ 7, 8, 9, 10 ]); | |
* // slower animation | |
* this.addAnimation("roll", [ 7, 8, 9, 10 ], 200); | |
*/ | |
addAnimation : function(name, index, animationspeed) { | |
this.anim[name] = { | |
name : name, | |
frame : [], | |
idx : 0, | |
length : 0, | |
animationspeed: animationspeed || this.animationspeed, | |
nextFrame : 0 | |
}; | |
if (index == null) { | |
index = []; | |
var j = 0; | |
// create a default animation with all frame | |
this.textureAtlas.forEach(function() { | |
index[j] = j++; | |
}); | |
} | |
// set each frame configuration (offset, size, etc..) | |
for ( var i = 0 , len = index.length ; i < len; i++) { | |
if (typeof(index[i]) === "number") { | |
this.anim[name].frame[i] = this.textureAtlas[index[i]]; | |
} else { // string | |
if (this.atlasIndices === null) { | |
throw "melonjs: string parameters for addAnimation are only allowed for TextureAtlas "; | |
} else { | |
this.anim[name].frame[i] = this.textureAtlas[this.atlasIndices[index[i]]]; | |
} | |
} | |
} | |
this.anim[name].length = this.anim[name].frame.length; | |
}, | |
/** | |
* set the current animation | |
* @name setCurrentAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @param {String|Function} [onComplete] animation id to switch to when complete, or callback | |
* @example | |
* // set "walk" animation | |
* this.setCurrentAnimation("walk"); | |
* | |
* // set "eat" animation, and switch to "walk" when complete | |
* this.setCurrentAnimation("eat", "walk"); | |
* | |
* // set "die" animation, and remove the object when finished | |
* this.setCurrentAnimation("die", (function () { | |
* me.game.remove(this); | |
* return false; // do not reset to first frame | |
* }).bind(this)); | |
* | |
* // set "attack" animation, and pause for a short duration | |
* this.setCurrentAnimation("die", (function () { | |
* this.animationpause = true; | |
* | |
* // back to "standing" animation after 1 second | |
* setTimeout(function () { | |
* this.setCurrentAnimation("standing"); | |
* }, 1000); | |
* | |
* return false; // do not reset to first frame | |
* }).bind(this)); | |
**/ | |
setCurrentAnimation : function(name, resetAnim) { | |
if (this.anim[name]) { | |
this.current = this.anim[name]; | |
this.resetAnim = resetAnim || null; | |
this.setAnimationFrame(this.current.idx); // or 0 ? | |
this.current.nextFrame = me.timer.getTime() + this.current.animationspeed; | |
} else { | |
throw "melonJS: animation id '" + name + "' not defined"; | |
} | |
}, | |
/** | |
* return true if the specified animation is the current one. | |
* @name isCurrentAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @return {Boolean} | |
* @example | |
* if (!this.isCurrentAnimation("walk")) { | |
* // do something funny... | |
* } | |
*/ | |
isCurrentAnimation : function(name) { | |
return this.current.name === name; | |
}, | |
/** | |
* force the current animation frame index. | |
* @name setAnimationFrame | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {Number} [index=0] animation frame index | |
* @example | |
* //reset the current animation to the first frame | |
* this.setAnimationFrame(); | |
*/ | |
setAnimationFrame : function(idx) { | |
this.current.idx = (idx || 0) % this.current.length; | |
var frame = this.current.frame[this.current.idx]; | |
this.offset = frame.offset; | |
this.width = frame.width; | |
this.height = frame.height; | |
this.hWidth = frame.hWidth; | |
this.hHeight = frame.hHeight; | |
this._sourceAngle = frame.angle; | |
}, | |
/** | |
* return the current animation frame index. | |
* @name getCurrentAnimationFrame | |
* @memberOf me.AnimationSheet | |
* @function | |
* @return {Number} current animation frame index | |
*/ | |
getCurrentAnimationFrame : function() { | |
return this.current.idx; | |
}, | |
/** | |
* update the animation<br> | |
* this is automatically called by the game manager {@link me.game} | |
* @name update | |
* @memberOf me.AnimationSheet | |
* @function | |
* @protected | |
*/ | |
update : function() { | |
// update animation if necessary | |
if (!this.animationpause && (me.timer.getTime() >= this.current.nextFrame)) { | |
this.setAnimationFrame(++this.current.idx); | |
// switch animation if we reach the end of the strip | |
// and a callback is defined | |
if (this.current.idx === 0 && this.resetAnim) { | |
// if string, change to the corresponding animation | |
if (typeof this.resetAnim === "string") | |
this.setCurrentAnimation(this.resetAnim); | |
// if function (callback) call it | |
else if (typeof this.resetAnim === "function" && this.resetAnim() === false) { | |
this.current.idx = this.current.length - 1; | |
this.setAnimationFrame(this.current.idx); | |
this.parent(); | |
return false; | |
} | |
} | |
// set next frame timestamp | |
this.current.nextFrame = me.timer.getTime() + this.current.animationspeed; | |
return this.parent() || true; | |
} | |
return this.parent(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* a local constant for the -(Math.PI / 2) value | |
* @ignore | |
*/ | |
var nhPI = -(Math.PI / 2); | |
/** | |
* A Texture atlas object<br> | |
* Currently support : <br> | |
* - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export <br> | |
* - [ShoeBox]{@link http://renderhjs.net/shoebox/} : through JSON export using the melonJS setting [file]{@link https://github.com/melonjs/melonJS/raw/master/media/shoebox_JSON_export.sbx} | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {Object} atlas atlas information. See {@link me.loader#getJSON} | |
* @param {Image} [texture=atlas.meta.image] texture name | |
* @example | |
* // create a texture atlas | |
* texture = new me.TextureAtlas ( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
*/ | |
me.TextureAtlas = Object.extend( | |
/** @scope me.TextureAtlas.prototype */ | |
{ | |
/** | |
* to identify the atlas format (e.g. texture packer) | |
* @ignore | |
*/ | |
format: null, | |
/** | |
* the image texture itself | |
* @ignore | |
*/ | |
texture : null, | |
/** | |
* the atlas dictionnary | |
* @ignore | |
*/ | |
atlas: null, | |
/** | |
* @ignore | |
*/ | |
init : function(atlas, texture) { | |
if (atlas && atlas.meta) { | |
// Texture Packer | |
if (atlas.meta.app.contains("texturepacker")) { | |
this.format = "texturepacker"; | |
// set the texture | |
if (texture===undefined) { | |
var name = me.utils.getBasename(atlas.meta.image); | |
this.texture = me.loader.getImage(name); | |
if (this.texture === null) { | |
throw "melonjs: Atlas texture '" + name + "' not found"; | |
} | |
} else { | |
this.texture = texture; | |
} | |
} | |
// ShoeBox | |
if (atlas.meta.app.contains("ShoeBox")) { | |
if (!atlas.meta.exporter || !atlas.meta.exporter.contains("melonJS")) { | |
throw "melonjs: ShoeBox requires the JSON exporter : https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx"; | |
} | |
this.format = "ShoeBox"; | |
// set the texture | |
this.texture = texture; | |
} | |
// initialize the atlas | |
this.atlas = this.initFromTexturePacker(atlas); | |
} | |
// if format not recognized | |
if (this.atlas === null) { | |
throw "melonjs: texture atlas format not supported"; | |
} | |
}, | |
/** | |
* @ignore | |
*/ | |
initFromTexturePacker : function (data) { | |
var atlas = {}; | |
data.frames.forEach(function(frame) { | |
// fix wrongly formatted JSON (e.g. last dummy object in ShoeBox) | |
if (frame.hasOwnProperty("filename")) { | |
atlas[frame.filename] = { | |
frame: new me.Rect( | |
new me.Vector2d(frame.frame.x, frame.frame.y), | |
frame.frame.w, frame.frame.h | |
), | |
source: new me.Rect( | |
new me.Vector2d(frame.spriteSourceSize.x, frame.spriteSourceSize.y), | |
frame.spriteSourceSize.w, frame.spriteSourceSize.h | |
), | |
// non trimmed size, but since we don't support trimming both value are the same | |
//sourceSize: new me.Vector2d(frame.sourceSize.w,frame.sourceSize.h), | |
rotated : frame.rotated===true, | |
trimmed : frame.trimmed===true | |
}; | |
} | |
}); | |
return atlas; | |
}, | |
/** | |
* return the Atlas texture | |
* @name getTexture | |
* @memberOf me.TextureAtlas | |
* @function | |
* @return {Image} | |
*/ | |
getTexture : function() { | |
return this.texture; | |
}, | |
/** | |
* return a normalized region/frame information for the specified sprite name | |
* @name getRegion | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String} name name of the sprite | |
* @return {Object} | |
*/ | |
getRegion : function(name) { | |
var region = this.atlas[name]; | |
if (region) { | |
return { | |
name: name, // frame name | |
pos: region.source.pos.clone(), // unused for now | |
offset: region.frame.pos.clone(), | |
width: region.frame.width, | |
height: region.frame.height, | |
hWidth: region.frame.width / 2, | |
hHeight: region.frame.height / 2, | |
angle : (region.rotated===true) ? nhPI : 0 | |
}; | |
} | |
return null; | |
}, | |
/** | |
* Create a sprite object using the first region found using the specified name | |
* @name createSpriteFromName | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String} name name of the sprite | |
* @return {me.SpriteObject} | |
* @example | |
* // create a new texture atlas object under the `game` namespace | |
* game.texture = new me.TextureAtlas( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
* ... | |
* ... | |
* // add the coin sprite as renderable for the entity | |
* this.renderable = game.texture.createSpriteFromName("coin.png"); | |
* // set the renderable position to bottom center | |
* this.anchorPoint.set(0.5, 1.0); | |
*/ | |
createSpriteFromName : function(name) { | |
var region = this.getRegion(name); | |
if (region) { | |
// instantiate a new sprite object | |
var sprite = new me.SpriteObject(0,0, this.getTexture(), region.width, region.height); | |
// set the sprite offset within the texture | |
sprite.offset.setV(region.offset); | |
// set angle if defined | |
sprite._sourceAngle = region.angle; | |
/* -> when using anchor positioning, this is not required | |
-> and makes final position wrong... | |
if (tex.trimmed===true) { | |
// adjust default position | |
sprite.pos.add(tex.source.pos); | |
} | |
*/ | |
// return our object | |
return sprite; | |
} | |
// throw an error | |
throw "melonjs: TextureAtlas - region for " + name + " not found"; | |
}, | |
/** | |
* Create an animation object using the first region found using all specified names | |
* @name createAnimationFromName | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String[]} names list of names for each sprite | |
* @return {me.AnimationSheet} | |
* @example | |
* // create a new texture atlas object under the `game` namespace | |
* game.texture = new me.TextureAtlas( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
* ... | |
* ... | |
* // create a new animationSheet as renderable for the entity | |
* this.renderable = game.texture.createAnimationFromName([ | |
* "walk0001.png", "walk0002.png", "walk0003.png", | |
* "walk0004.png", "walk0005.png", "walk0006.png", | |
* "walk0007.png", "walk0008.png", "walk0009.png", | |
* "walk0010.png", "walk0011.png" | |
* ]); | |
* | |
* // define an additional basic walking animatin | |
* this.renderable.addAnimation ("simple_walk", [0,2,1]); | |
* // you can also use frame name to define your animation | |
* this.renderable.addAnimation ("speed_walk", ["walk0007.png", "walk0008.png", "walk0009.png", "walk0010.png"]); | |
* // set the default animation | |
* this.renderable.setCurrentAnimation("simple_walk"); | |
* // set the renderable position to bottom center | |
* this.anchorPoint.set(0.5, 1.0); | |
*/ | |
createAnimationFromName : function(names) { | |
var tpAtlas = [], indices = {}; | |
// iterate through the given names | |
// and create a "normalized" atlas | |
for (var i = 0; i < names.length;++i) { | |
tpAtlas[i] = this.getRegion(names[i]); | |
indices[names[i]] = i; | |
if (tpAtlas[i] == null) { | |
// throw an error | |
throw "melonjs: TextureAtlas - region for " + names[i] + " not found"; | |
} | |
} | |
// instantiate a new animation sheet object | |
return new me.AnimationSheet(0,0, this.texture, 0, 0, 0, 0, tpAtlas, indices); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* 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.Rect | |
* @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 | |
* @param {Number} [realw] real world width limit | |
* @param {Number} [realh] real world height limit | |
*/ | |
me.Viewport = me.Rect.extend( | |
/** @scope me.Viewport.prototype */ | |
{ | |
/** | |
* 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 | |
*/ | |
AXIS : { | |
NONE : 0, | |
HORIZONTAL : 1, | |
VERTICAL : 2, | |
BOTH : 3 | |
}, | |
// world limit | |
limits : null, | |
// target to follow | |
target : null, | |
// axis to follow | |
follow_axis : 0, | |
// shake parameters | |
shaking : false, | |
_shake : null, | |
// fade parameters | |
_fadeIn : null, | |
_fadeOut : null, | |
// cache some values | |
_deadwidth : 0, | |
_deadheight : 0, | |
_limitwidth : 0, | |
_limitheight : 0, | |
// cache the screen rendering position | |
screenX : 0, | |
screenY : 0, | |
/** @ignore */ | |
init : function(minX, minY, maxX, maxY, realw, realh) { | |
// viewport coordinates | |
this.parent(new me.Vector2d(minX, minY), maxX - minX, maxY - minY); | |
// real worl limits | |
this.limits = new me.Vector2d(realw||this.width, realh||this.height); | |
// 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, | |
start : 0 | |
}; | |
// flash variables | |
this._fadeOut = { | |
color : 0, | |
alpha : 0.0, | |
duration : 0, | |
tween : null | |
}; | |
// fade variables | |
this._fadeIn = { | |
color : 0, | |
alpha : 1.0, | |
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._deadwidth)) { | |
this.pos.x = ~~MIN((target.x) - (this._deadwidth), this._limitwidth); | |
} | |
else if ((target.x - this.pos.x) < (this.deadzone.x)) { | |
this.pos.x = ~~MAX((target.x) - this.deadzone.x, 0); | |
} | |
return (_x !== this.pos.x); | |
}, | |
/** @ignore */ | |
_followV : function(target) { | |
var _y = this.pos.y; | |
if ((target.y - this.pos.y) > (this._deadheight)) { | |
this.pos.y = ~~MIN((target.y) - (this._deadheight), this._limitheight); | |
} | |
else if ((target.y - this.pos.y) < (this.deadzone.y)) { | |
this.pos.y = ~~MAX((target.y) - this.deadzone.y, 0); | |
} | |
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 | |
* @name setDeadzone | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} w deadzone width | |
* @param {Number} h deadzone height | |
*/ | |
setDeadzone : function(w, h) { | |
this.deadzone = new me.Vector2d(~~((this.width - w) / 2), | |
~~((this.height - h) / 2 - h * 0.25)); | |
// cache some value | |
this._deadwidth = this.width - this.deadzone.x; | |
this._deadheight = this.height - this.deadzone.y; | |
// force a camera update | |
this.update(true); | |
}, | |
/** | |
* set the viewport boundaries (world limit) | |
* @name setBounds | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} w world width | |
* @param {Number} h world height | |
*/ | |
setBounds : function(w, h) { | |
this.limits.set(w, h); | |
// cache some value | |
this._limitwidth = this.limits.x - this.width; | |
this._limitheight = this.limits.y - this.height; | |
}, | |
/** | |
* set the viewport to follow the specified entity | |
* @name follow | |
* @memberOf me.Viewport | |
* @function | |
* @param {me.ObjectEntity|me.Vector2d} target ObjectEntity or Position Vector to follow | |
* @param {me.Viewport#AXIS} [axis=AXIS.BOTH] Which axis to follow | |
*/ | |
follow : function(target, axis) { | |
if (target instanceof me.ObjectEntity) | |
this.target = target.pos; | |
else if (target instanceof me.Vector2d) | |
this.target = target; | |
else | |
throw "melonJS: 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.update(true); | |
}, | |
/** | |
* move the viewport to the specified coordinates | |
* @name move | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
move : function(x, y) { | |
var newx = ~~(this.pos.x + x); | |
var newy = ~~(this.pos.y + y); | |
this.pos.x = newx.clamp(0,this._limitwidth); | |
this.pos.y = newy.clamp(0,this._limitheight); | |
//publish the corresponding message | |
me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]); | |
}, | |
/** @ignore */ | |
update : function(updateTarget) { | |
var updated = false; | |
if (this.target && updateTarget) { | |
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; | |
} | |
} | |
if (this.shaking===true) { | |
var delta = me.timer.getTime() - this._shake.start; | |
if (delta >= this._shake.duration) { | |
this.shaking = false; | |
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.shaking) | |
return; | |
this.shaking = true; | |
this._shake = { | |
intensity : intensity, | |
duration : duration, | |
axis : axis || this.AXIS.BOTH, | |
onComplete : onComplete || null, | |
start : me.timer.getTime() | |
}; | |
}, | |
/** | |
* fadeOut(flash) effect<p> | |
* screen is filled with the specified color and slowly goes back to normal | |
* @name fadeOut | |
* @memberOf me.Viewport | |
* @function | |
* @param {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 = color; | |
this._fadeOut.duration = duration || 1000; // convert to ms | |
this._fadeOut.alpha = 1.0; | |
this._fadeOut.tween = me.entityPool.newInstanceOf("me.Tween", this._fadeOut).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 {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 = color; | |
this._fadeIn.duration = duration || 1000; //convert to ms | |
this._fadeIn.alpha = 0.0; | |
this._fadeIn.tween = me.entityPool.newInstanceOf("me.Tween", this._fadeIn).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 around the specified entity<p> | |
* <b>BROKEN !!!!</b> | |
* @deprecated | |
* @ignore | |
* @param {Object} | |
*/ | |
focusOn : function(target) { | |
// BROKEN !! target x and y should be the center point | |
this.pos.x = target.x - this.width * 0.5; | |
this.pos.y = target.y - this.height * 0.5; | |
}, | |
/** | |
* 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 | |
* @return {me.Vector2d} | |
*/ | |
localToWorld : function(x, y) { | |
return (new me.Vector2d(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 | |
* @return {me.Vector2d} | |
*/ | |
worldToLocal : function(x, y) { | |
return (new me.Vector2d(x,y)).sub(this.pos).add(me.game.currentLevel.pos); | |
}, | |
/** | |
* render the camera effects | |
* @ignore | |
*/ | |
draw : function(context) { | |
// fading effect | |
if (this._fadeIn.tween) { | |
context.globalAlpha = this._fadeIn.alpha; | |
me.video.clearSurface(context, me.utils.HexToRGB(this._fadeIn.color)); | |
// set back full opacity | |
context.globalAlpha = 1.0; | |
// remove the tween if over | |
if (this._fadeIn.alpha === 1.0) | |
this._fadeIn.tween = null; | |
} | |
// flashing effect | |
if (this._fadeOut.tween) { | |
context.globalAlpha = this._fadeOut.alpha; | |
me.video.clearSurface(context, me.utils.HexToRGB(this._fadeOut.color)); | |
// set back full opacity | |
context.globalAlpha = 1.0; | |
// remove the tween if over | |
if (this._fadeOut.alpha === 0.0) | |
this._fadeOut.tween = null; | |
} | |
// blit our frame | |
me.video.blitSurface(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* GUI Object<br> | |
* A very basic object to manage GUI elements <br> | |
* The object simply register on the "mousedown" <br> | |
* or "touchstart" event and call the onClick function" | |
* @class | |
* @extends me.SpriteObject | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinate of the GUI Object | |
* @param {Number} y the y coordinate of the GUI Object | |
* @param {me.ObjectSettings} settings Object settings | |
* @example | |
* | |
* // create a basic GUI Object | |
* var myButton = me.GUI_Object.extend( | |
* { | |
* init:function(x, y) | |
* { | |
* settings = {} | |
* settings.image = "button"; | |
* settings.spritewidth = 100; | |
* settings.spriteheight = 50; | |
* // parent constructor | |
* this.parent(x, y, settings); | |
* }, | |
* | |
* // output something in the console | |
* // when the object is clicked | |
* onClick:function(event) | |
* { | |
* console.log("clicked!"); | |
* // don't propagate the event | |
* return false; | |
* } | |
* }); | |
* | |
* // add the object at pos (10,10), z index 4 | |
* me.game.add((new myButton(10,10)),4); | |
* | |
*/ | |
me.GUI_Object = me.SpriteObject.extend({ | |
/** @scope me.GUI_Object.prototype */ | |
/** | |
* object can be clicked or not | |
* @public | |
* @type boolean | |
* @name me.GUI_Object#isClickable | |
*/ | |
isClickable : true, | |
// object has been updated (clicked,etc..) | |
updated : false, | |
/** | |
* @ignore | |
*/ | |
init : function(x, y, settings) { | |
this.parent(x, y, | |
((typeof settings.image === "string") ? me.loader.getImage(settings.image) : settings.image), | |
settings.spritewidth, | |
settings.spriteheight); | |
// GUI items use screen coordinates | |
this.floating = true; | |
// register on mouse event | |
me.input.registerPointerEvent('mousedown', this, this.clicked.bind(this)); | |
}, | |
/** | |
* return true if the object has been clicked | |
* @ignore | |
*/ | |
update : function() { | |
if (this.updated) { | |
// clear the flag | |
this.updated = false; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* function callback for the mousedown event | |
* @ignore | |
*/ | |
clicked : function(event) { | |
if (this.isClickable) { | |
this.updated = true; | |
return this.onClick(event); | |
} | |
}, | |
/** | |
* function called when the object is clicked <br> | |
* to be extended <br> | |
* return false if we need to stop propagating the event | |
* @name onClick | |
* @memberOf me.GUI_Object | |
* @public | |
* @function | |
* @param {Event} event the event object | |
*/ | |
onClick : function(event) { | |
return false; | |
}, | |
/** | |
* OnDestroy notification function<br> | |
* Called by engine before deleting the object<br> | |
* be sure to call the parent function if overwritten | |
* @name onDestroyEvent | |
* @memberOf me.GUI_Object | |
* @public | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
me.input.releasePointerEvent('mousedown', this); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* A global "translation context" for nested ObjectContainers | |
* @ignore | |
*/ | |
var globalTranslation = new me.Rect(new me.Vector2d(), 0, 0); | |
/** | |
* A global "floating entity" reference counter for nested ObjectContainers | |
* @ignore | |
*/ | |
var globalFloatingCounter = 0; | |
/** | |
* EntityContainer represents a collection of child objects | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} [x=0] position of the container | |
* @param {Number} [y=0] position of the container | |
* @param {Number} [w=me.game.viewport.width] width of the container | |
* @param {number} [h=me.game.viewport.height] height of the container | |
*/ | |
me.ObjectContainer = me.Renderable.extend( | |
/** @scope me.ObjectContainer.prototype */ { | |
/** | |
* The property of entity that should be used to sort on <br> | |
* value : "x", "y", "z" (default: me.game.sortOn) | |
* @public | |
* @type String | |
* @name sortOn | |
* @memberOf me.ObjectContainer | |
*/ | |
sortOn : "z", | |
/** | |
* Specify if the entity list should be automatically sorted when adding a new child | |
* @public | |
* @type Boolean | |
* @name autoSort | |
* @memberOf me.ObjectContainer | |
*/ | |
autoSort : true, | |
/** | |
* keep track of pending sort | |
* @ignore | |
*/ | |
pendingSort : null, | |
/** | |
* The array of children of this container. | |
* @ignore | |
*/ | |
children : null, | |
/** | |
* Enable collision detection for this container (default true)<br> | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectContainer | |
*/ | |
collidable : true, | |
/** | |
* constructor | |
* @ignore | |
*/ | |
init : function(x, y, width, height) { | |
// call the parent constructor | |
this.parent( | |
new me.Vector2d(x || 0, y || 0), | |
width || Infinity, | |
height || Infinity | |
); | |
this.children = []; | |
// by default reuse the global me.game.setting | |
this.sortOn = me.game.sortOn; | |
this.autoSort = true; | |
}, | |
/** | |
* Add a child to the container <br> | |
* if auto-sort is disable, the object will be appended at the bottom of the list | |
* @name addChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
addChild : function(child) { | |
if(typeof(child.ancestor) !== 'undefined') { | |
child.ancestor.removeChild(child); | |
} else { | |
// only allocate a GUID if the object has no previous ancestor | |
// (e.g. move one child from one container to another) | |
if (child.isRenderable) { | |
// allocated a GUID value | |
child.GUID = me.utils.createGUID(); | |
} | |
} | |
// specify a z property to infinity if not defined | |
if (typeof child.z === 'undefined') { | |
child.z = Infinity; | |
} | |
child.ancestor = this; | |
this.children.push(child); | |
if (this.autoSort === true) { | |
this.sort(); | |
} | |
}, | |
/** | |
* Add a child to the container at the specified index<br> | |
* (the list won't be sorted after insertion) | |
* @name addChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {Number} index | |
*/ | |
addChildAt : function(child, index) { | |
if((index >= 0) && (index < this.children.length)) { | |
if(typeof(child.ancestor) !== 'undefined') { | |
child.ancestor.removeChild(child); | |
} else { | |
// only allocate a GUID if the object has no previous ancestor | |
// (e.g. move one child from one container to another) | |
if (child.isRenderable) { | |
// allocated a GUID value | |
child.GUID = me.utils.createGUID(); | |
} | |
} | |
child.ancestor = this; | |
this.children.splice(index, 0, child); | |
} else { | |
throw "melonJS (me.ObjectContainer): Index (" + index + ") Out Of Bounds for addChildAt()"; | |
} | |
}, | |
/** | |
* Swaps the position (z depth) of 2 childs | |
* @name swapChildren | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {me.Renderable} child | |
*/ | |
swapChildren : function(child, child2) { | |
var index = this.getChildIndex( child ); | |
var index2 = this.getChildIndex( child2 ); | |
if ((index !== -1) && (index2 !== -1)) { | |
// swap z index | |
var _z = child.z; | |
child.z = child2.z; | |
child2.z = _z; | |
// swap the positions.. | |
this.children[index] = child2; | |
this.children[index2] = child; | |
} else { | |
throw "melonJS (me.ObjectContainer): " + child + " Both the supplied entities must be a child of the caller " + this; | |
} | |
}, | |
/** | |
* Returns the Child at the specified index | |
* @name getChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {Number} index | |
*/ | |
getChildAt : function(index) { | |
if((index >= 0) && (index < this.children.length)) { | |
return this.children[index]; | |
} else { | |
throw "melonJS (me.ObjectContainer): Index (" + index + ") Out Of Bounds for getChildAt()"; | |
} | |
}, | |
/** | |
* Returns the index of the Child | |
* @name getChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
getChildIndex : function(child) { | |
return this.children.indexOf( child ); | |
}, | |
/** | |
* Returns true if contains the specified Child | |
* @name hasChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @return {Boolean} | |
*/ | |
hasChild : function(child) { | |
return this === child.ancestor; | |
}, | |
/** | |
* return the child corresponding to the given property and value.<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object tree each time | |
* @name getEntityByProp | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {String} prop Property name | |
* @param {String} value Value of the property | |
* @return {me.Renderable[]} Array of childs | |
* @example | |
* // get the first entity called "mainPlayer" in a specific container : | |
* ent = myContainer.getEntityByProp("name", "mainPlayer"); | |
* // or query the whole world : | |
* ent = me.game.world.getEntityByProp("name", "mainPlayer"); | |
*/ | |
getEntityByProp : function(prop, value) { | |
var objList = []; | |
// for string comparaisons | |
var _regExp = new RegExp(value, "i"); | |
function compare(obj, prop) { | |
if (typeof (obj[prop]) === 'string') { | |
if (obj[prop].match(_regExp)) { | |
objList.push(obj); | |
} | |
} else if (obj[prop] === value) { | |
objList.push(obj); | |
} | |
} | |
for (var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (obj instanceof me.ObjectContainer) { | |
compare(obj, prop); | |
objList = objList.concat(obj.getEntityByProp(prop, value)); | |
} else if (obj.isEntity) { | |
compare(obj, prop); | |
} | |
} | |
return objList; | |
}, | |
/** | |
* Removes (and optionally destroys) a child from the container.<br> | |
* (removal is immediate and unconditional)<br> | |
* Never use keepalive=true with objects from {@link me.entityPool}. Doing so will create a memory leak. | |
* @name removeChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {Boolean} [keepalive=False] True to prevent calling child.destroy() | |
*/ | |
removeChild : function(child, keepalive) { | |
if (this.hasChild(child)) { | |
child.ancestor = undefined; | |
if (!keepalive) { | |
if (typeof (child.destroy) === 'function') { | |
child.destroy(); | |
} | |
me.entityPool.freeInstance(child); | |
} | |
this.children.splice( this.getChildIndex(child), 1 ); | |
} else { | |
throw "melonJS (me.ObjectContainer): " + child + " The supplied entity must be a child of the caller " + this; | |
} | |
}, | |
/** | |
* Automatically set the specified property of all childs to the given value | |
* @name setChildsProperty | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {String} property property name | |
* @param {Object} value property value | |
* @param {Boolean} [recursive=false] recursively apply the value to child containers if true | |
*/ | |
setChildsProperty : function(prop, val, recursive) { | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if ((recursive === true) && (obj instanceof me.ObjectContainer)) { | |
obj.setChildsProperty(prop, val, recursive); | |
} | |
obj[prop] = val; | |
} | |
}, | |
/** | |
* Move the child in the group one step forward (z depth). | |
* @name moveUp | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveUp : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex -1 >= 0) { | |
// note : we use an inverted loop | |
this.swapChildren(child, this.getChildAt(childIndex-1)); | |
} | |
}, | |
/** | |
* Move the child in the group one step backward (z depth). | |
* @name moveDown | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveDown : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex+1 < this.children.length) { | |
// note : we use an inverted loop | |
this.swapChildren(child, this.getChildAt(childIndex+1)); | |
} | |
}, | |
/** | |
* Move the specified child to the top(z depth). | |
* @name moveToTop | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveToTop : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex > 0) { | |
// note : we use an inverted loop | |
this.splice(0, 0, this.splice(childIndex, 1)[0]); | |
// increment our child z value based on the previous child depth | |
child.z = this.children[1].z + 1; | |
} | |
}, | |
/** | |
* Move the specified child the bottom (z depth). | |
* @name moveToBottom | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveToBottom : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex < (this.children.length -1)) { | |
// note : we use an inverted loop | |
this.splice((this.children.length -1), 0, this.splice(childIndex, 1)[0]); | |
// increment our child z value based on the next child depth | |
child.z = this.children[(this.children.length -2)].z - 1; | |
} | |
}, | |
/** | |
* Checks if the specified entity collides with others entities in this container | |
* @name collide | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {me.Renderable} obj Object to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collide : function(objA, multiple) { | |
return this.collideType(objA, null, multiple); | |
}, | |
/** | |
* Checks if the specified entity collides with others entities in this container | |
* @name collideType | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {me.Renderable} obj Object to be tested for collision | |
* @param {String} [type=undefined] Entity type to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collideType : function(objA, type, multiple) { | |
var res, mres; | |
// make sure we have a boolean | |
multiple = multiple===true ? true : false; | |
if (multiple===true) { | |
mres = []; | |
} | |
// this should be replace by a list of the 4 adjacent cell around the object requesting collision | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if ( (obj.inViewport || obj.alwaysUpdate ) && obj.collidable ) { | |
// recursivly check through | |
if (obj instanceof me.ObjectContainer) { | |
res = obj.collideType(objA, type, multiple); | |
if (multiple) { | |
mres.concat(res); | |
} else if (res) { | |
// the child container returned collision information | |
return res; | |
} | |
} else if ( (obj !== objA) && (!type || (obj.type === type)) ) { | |
res = obj.collisionBox["collideWith"+objA.shapeType].call(obj.collisionBox, objA.collisionBox); | |
if (res.x !== 0 || res.y !== 0) { | |
// notify the object | |
obj.onCollision.call(obj, res, objA); | |
// return the type (deprecated) | |
res.type = obj.type; | |
// return a reference of the colliding object | |
res.obj = obj; | |
// stop here if we don't look for multiple collision detection | |
if (!multiple) { | |
return res; | |
} | |
mres.push(res); | |
} | |
} | |
} | |
} | |
return multiple?mres:null; | |
}, | |
/** | |
* Manually trigger the sort of all the childs in the container</p> | |
* @name sort | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {Boolean} [recursive=false] recursively sort all containers if true | |
*/ | |
sort : function(recursive) { | |
// do nothing if there is already a pending sort | |
if (this.pendingSort === null) { | |
if (recursive === true) { | |
// trigger other child container sort function (if any) | |
for (var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (obj instanceof me.ObjectContainer) { | |
// note : this will generate one defered sorting function | |
// for each existing containe | |
obj.sort(recursive); | |
} | |
} | |
} | |
/** @ignore */ | |
this.pendingSort = (function (self) { | |
// sort everything in this container | |
self.children.sort(self["_sort"+self.sortOn.toUpperCase()]); | |
// clear the defer id | |
self.pendingSort = null; | |
// make sure we redraw everything | |
me.game.repaint(); | |
}.defer(this)); | |
} | |
}, | |
/** | |
* Z Sorting function | |
* @ignore | |
*/ | |
_sortZ : function (a,b) { | |
return (b.z) - (a.z); | |
}, | |
/** | |
* X Sorting function | |
* @ignore | |
*/ | |
_sortX : function(a,b) { | |
/* ? */ | |
var result = (b.z - a.z); | |
return (result ? result : ((b.pos && b.pos.x) - (a.pos && a.pos.x)) || 0); | |
}, | |
/** | |
* Y Sorting function | |
* @ignore | |
*/ | |
_sortY : function(a,b) { | |
var result = (b.z - a.z); | |
return (result ? result : ((b.pos && b.pos.y) - (a.pos && a.pos.y)) || 0); | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
// cancel any sort operation | |
if (this.pendingSort) { | |
clearTimeout(this.pendingSort); | |
this.pendingSort = null; | |
} | |
// delete all children | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
// don't remove it if a persistent object | |
if ( !obj.isPersistent ) { | |
this.removeChild(obj); | |
} | |
} | |
}, | |
/** | |
* @ignore | |
*/ | |
update : function() { | |
var isDirty = false; | |
var isFloating = false; | |
var isPaused = me.state.isPaused(); | |
var isTranslated; | |
var x; | |
var y; | |
var viewport = me.game.viewport; | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (isPaused && (!obj.updateWhenPaused)) { | |
// skip this object | |
continue; | |
} | |
if ( obj.isRenderable ) { | |
isFloating = (globalFloatingCounter > 0 || obj.floating); | |
if (isFloating) { | |
globalFloatingCounter++; | |
} | |
// Translate global context | |
isTranslated = (obj.visible && !isFloating); | |
if (isTranslated) { | |
x = obj.pos.x; | |
y = obj.pos.y; | |
globalTranslation.translateV(obj.pos); | |
globalTranslation.set(globalTranslation.pos, obj.width, obj.height); | |
} | |
// check if object is visible | |
obj.inViewport = obj.visible && ( | |
isFloating || viewport.isVisible(globalTranslation) | |
); | |
// update our object | |
isDirty |= (obj.inViewport || obj.alwaysUpdate) && obj.update(); | |
// Undo global context translation | |
if (isTranslated) { | |
globalTranslation.translate(-x, -y); | |
} | |
if (globalFloatingCounter > 0) { | |
globalFloatingCounter--; | |
} | |
} else { | |
// just directly call update() for non renderable object | |
isDirty |= obj.update(); | |
} | |
} | |
return isDirty; | |
}, | |
/** | |
* @ignore | |
*/ | |
draw : function(context, rect) { | |
var viewport = me.game.viewport; | |
var isFloating = false; | |
this.drawCount = 0; | |
// save the current context | |
context.save(); | |
// apply the group opacity | |
context.globalAlpha *= this.getOpacity(); | |
// translate to the container position | |
context.translate(this.pos.x, this.pos.y); | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
isFloating = obj.floating; | |
if (obj.isRenderable && (obj.inViewport || (isFloating && obj.visible))) { | |
if (isFloating === true) { | |
context.save(); | |
// translate back object | |
context.translate( | |
viewport.screenX -this.pos.x, | |
viewport.screenY -this.pos.y | |
); | |
} | |
// draw the object | |
obj.draw(context, rect); | |
if (isFloating === true) { | |
context.restore(); | |
} | |
this.drawCount++; | |
} | |
} | |
// restore the context | |
context.restore(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* me.ObjectSettings contains the object attributes defined in Tiled<br> | |
* and is created by the engine and passed as parameter to the corresponding object when loading a level<br> | |
* the field marked Mandatory are to be defined either in Tiled, or in the before calling the parent constructor<br> | |
* <img src="images/object_properties.png"/><br> | |
* @class | |
* @protected | |
* @memberOf me | |
*/ | |
me.ObjectSettings = { | |
/** | |
* object entity name<br> | |
* as defined in the Tiled Object Properties | |
* @public | |
* @type String | |
* @name name | |
* @memberOf me.ObjectSettings | |
*/ | |
name : null, | |
/** | |
* image ressource name to be loaded<br> | |
* MANDATORY<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type String | |
* @name image | |
* @memberOf me.ObjectSettings | |
*/ | |
image : null, | |
/** | |
* specify a transparent color for the image in rgb format (#rrggbb)<br> | |
* OPTIONAL<br> | |
* (using this option will imply processing time on the image) | |
* @public | |
* @deprecated Use PNG or GIF with transparency instead | |
* @type String | |
* @name transparent_color | |
* @memberOf me.ObjectSettings | |
*/ | |
transparent_color : null, | |
/** | |
* width of a single sprite in the spritesheet<br> | |
* MANDATORY<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type Int | |
* @name spritewidth | |
* @memberOf me.ObjectSettings | |
*/ | |
spritewidth : null, | |
/** | |
* height of a single sprite in the spritesheet<br> | |
* OPTIONAL<br> | |
* if not specified the value will be set to the corresponding image height<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type Int | |
* @name spriteheight | |
* @memberOf me.ObjectSettings | |
*/ | |
spriteheight : null, | |
/** | |
* custom type for collision detection<br> | |
* OPTIONAL | |
* @public | |
* @type String | |
* @name type | |
* @memberOf me.ObjectSettings | |
*/ | |
type : 0, | |
/** | |
* Enable collision detection for this object<br> | |
* OPTIONAL | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectSettings | |
*/ | |
collidable : true | |
}; | |
/** | |
* A pool of Object entity <br> | |
* This object is used for object pooling - a technique that might speed up your game | |
* if used properly. <br> | |
* If some of your classes will be instantiated and removed a lot at a time, it is a | |
* good idea to add the class to this entity pool. A separate pool for that class | |
* will be created, which will reuse objects of the class. That way they won't be instantiated | |
* each time you need a new one (slowing your game), but stored into that pool and taking one | |
* already instantiated when you need it.<br><br> | |
* This object is also used by the engine to instantiate objects defined in the map, | |
* which means, that on level loading the engine will try to instantiate every object | |
* found in the map, based on the user defined name in each Object Properties<br> | |
* <img src="images/object_properties.png"/><br> | |
* There is no constructor function for me.entityPool, this is a static object | |
* @namespace me.entityPool | |
* @memberOf me | |
*/ | |
me.entityPool = (function() { | |
// hold public stuff in our singletong | |
var obj = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
var entityClass = {}; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/*--- | |
init | |
---*/ | |
obj.init = function() { | |
// add default entity object | |
obj.add("me.ObjectEntity", me.ObjectEntity); | |
obj.add("me.CollectableEntity", me.CollectableEntity); | |
obj.add("me.LevelEntity", me.LevelEntity); | |
obj.add("me.Tween", me.Tween, true); | |
}; | |
/** | |
* Add an object to the pool. <br> | |
* Pooling must be set to true if more than one such objects will be created. <br> | |
* (note) If pooling is enabled, you shouldn't instantiate objects with `new`. | |
* See examples in {@link me.entityPool#newInstanceOf} | |
* @name add | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {String} className as defined in the Name field of the Object Properties (in Tiled) | |
* @param {Object} class corresponding Class to be instantiated | |
* @param {Boolean} [objectPooling=false] enables object pooling for the specified class | |
* - speeds up the game by reusing existing objects | |
* @example | |
* // add our users defined entities in the entity pool | |
* me.entityPool.add("playerspawnpoint", PlayerEntity); | |
* me.entityPool.add("cherryentity", CherryEntity, true); | |
* me.entityPool.add("heartentity", HeartEntity, true); | |
* me.entityPool.add("starentity", StarEntity, true); | |
*/ | |
obj.add = function(className, entityObj, pooling) { | |
if (!pooling) { | |
entityClass[className.toLowerCase()] = { | |
"class" : entityObj, | |
"pool" : undefined | |
}; | |
return; | |
} | |
entityClass[className.toLowerCase()] = { | |
"class" : entityObj, | |
"pool" : [], | |
"active" : [] | |
}; | |
}; | |
/** | |
* Return a new instance of the requested object (if added into the object pool) | |
* @name newInstanceOf | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {String} className as used in {@link me.entityPool#add} | |
* @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object | |
* @example | |
* me.entityPool.add("player", PlayerEntity); | |
* var player = me.entityPool.newInstanceOf("player"); | |
* @example | |
* me.entityPool.add("bullet", BulletEntity, true); | |
* me.entityPool.add("enemy", EnemyEntity, true); | |
* // ... | |
* // when we need to manually create a new bullet: | |
* var bullet = me.entityPool.newInstanceOf("bullet", x, y, direction); | |
* // ... | |
* // params aren't a fixed number | |
* // when we need new enemy we can add more params, that the object construct requires: | |
* var enemy = me.entityPool.newInstanceOf("enemy", x, y, direction, speed, power, life); | |
* // ... | |
* // when we want to destroy existing object, the remove | |
* // function will ensure the object can then be reallocated later | |
* me.game.remove(enemy); | |
* me.game.remove(bullet); | |
*/ | |
obj.newInstanceOf = function(data) { | |
var name = typeof data === 'string' ? data.toLowerCase() : undefined; | |
var args = Array.prototype.slice.call(arguments); | |
if (name && entityClass[name]) { | |
var proto; | |
if (!entityClass[name]['pool']) { | |
proto = entityClass[name]["class"]; | |
args[0] = proto; | |
return new (proto.bind.apply(proto, args))(); | |
} | |
var obj, entity = entityClass[name]; | |
proto = entity["class"]; | |
if (entity["pool"].length > 0) { | |
obj = entity["pool"].pop(); | |
// call the object init function if defined (JR's Inheritance) | |
if (typeof obj.init === "function") { | |
obj.init.apply(obj, args.slice(1)); | |
} | |
// call the object onResetEvent function if defined | |
if (typeof obj.onResetEvent === "function") { | |
obj.onResetEvent.apply(obj, args.slice(1)); | |
} | |
} else { | |
args[0] = proto; | |
obj = new (proto.bind.apply(proto, args))(); | |
obj.className = name; | |
} | |
entity["active"].push(obj); | |
return obj; | |
} | |
// Tile objects can be created with a GID attribute; | |
// The TMX parser will use it to create the image property. | |
var settings = arguments[3]; | |
if (settings && settings.gid && settings.image) { | |
return new me.SpriteObject(settings.x, settings.y, settings.image); | |
} | |
if (name) { | |
console.error("Cannot instantiate entity of type '" + data + "': Class not found!"); | |
} | |
return null; | |
}; | |
/** | |
* purge the entity pool from any inactive object <br> | |
* Object pooling must be enabled for this function to work<br> | |
* note: this will trigger the garbage collector | |
* @name purge | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
*/ | |
obj.purge = function() { | |
for (var className in entityClass) { | |
entityClass[className]["pool"] = []; | |
} | |
}; | |
/** | |
* Remove object from the entity pool <br> | |
* Object pooling for the object class must be enabled, | |
* and object must have been instantiated using {@link me.entityPool#newInstanceOf}, | |
* otherwise this function won't work | |
* @name freeInstance | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {Object} instance to be removed | |
*/ | |
obj.freeInstance = function(obj) { | |
var name = obj.className; | |
if (!name || !entityClass[name]) { | |
return; | |
} | |
var notFound = true; | |
for (var i = 0, len = entityClass[name]["active"].length; i < len; i++) { | |
if (entityClass[name]["active"][i] === obj) { | |
notFound = false; | |
entityClass[name]["active"].splice(i, 1); | |
break; | |
} | |
} | |
if (notFound) { | |
return; | |
} | |
entityClass[name]["pool"].push(obj); | |
}; | |
// return our object | |
return obj; | |
})(); | |
/************************************************************************************/ | |
/* */ | |
/* a generic object entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a Generic Object Entity<br> | |
* Object Properties (settings) are to be defined in Tiled, <br> | |
* or when calling the parent constructor | |
* | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {me.ObjectSettings} settings Object Properties as defined in Tiled <br> <img src="images/object_properties.png"/> | |
*/ | |
me.ObjectEntity = me.Renderable.extend( | |
/** @scope me.ObjectEntity.prototype */ { | |
/** | |
* define the type of the object<br> | |
* default value : none<br> | |
* @public | |
* @type String | |
* @name type | |
* @memberOf me.ObjectEntity | |
*/ | |
type : 0, | |
/** | |
* flag to enable collision detection for this object<br> | |
* default value : true<br> | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectEntity | |
*/ | |
collidable : true, | |
/** | |
* Entity collision Box<br> | |
* (reference to me.ObjectEntity.shapes[0].getBounds) | |
* @public | |
* @deprecated | |
* @type me.Rect | |
* @name collisionBox | |
* @memberOf me.ObjectEntity | |
*/ | |
collisionBox : null, | |
/** | |
* Entity collision shapes<br> | |
* (RFU - Reserved for Future Usage) | |
* @protected | |
* @type Object[] | |
* @name shapes | |
* @memberOf me.ObjectEntity | |
*/ | |
shapes : null, | |
/** | |
* The entity renderable object (if defined) | |
* @public | |
* @type me.Renderable | |
* @name renderable | |
* @memberOf me.ObjectEntity | |
*/ | |
renderable : null, | |
// just to keep track of when we flip | |
lastflipX : false, | |
lastflipY : false, | |
/** @ignore */ | |
init : function(x, y, settings) { | |
// instantiate pos here to avoid | |
// later re-instantiation | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
// call the parent constructor | |
this.parent(this.pos.set(x,y), | |
~~settings.spritewidth || ~~settings.width, | |
~~settings.spriteheight || ~~settings.height); | |
if (settings.image) { | |
var image = typeof settings.image === "string" ? me.loader.getImage(settings.image) : settings.image; | |
this.renderable = new me.AnimationSheet(0, 0, image, | |
~~settings.spritewidth, | |
~~settings.spriteheight, | |
~~settings.spacing, | |
~~settings.margin); | |
// check for user defined transparent color | |
if (settings.transparent_color) { | |
this.renderable.setTransparency(settings.transparent_color); | |
} | |
} | |
// set the object entity name | |
this.name = settings.name?settings.name.toLowerCase():""; | |
/** | |
* entity current velocity<br> | |
* @public | |
* @type me.Vector2d | |
* @name vel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.vel === undefined) { | |
this.vel = new me.Vector2d(); | |
} | |
this.vel.set(0,0); | |
/** | |
* entity current acceleration<br> | |
* @public | |
* @type me.Vector2d | |
* @name accel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.accel === undefined) { | |
this.accel = new me.Vector2d(); | |
} | |
this.accel.set(0,0); | |
/** | |
* entity current friction<br> | |
* @public | |
* @name friction | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.friction === undefined) { | |
this.friction = new me.Vector2d(); | |
} | |
this.friction.set(0,0); | |
/** | |
* max velocity (to limit entity velocity)<br> | |
* @public | |
* @type me.Vector2d | |
* @name maxVel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.maxVel === undefined) { | |
this.maxVel = new me.Vector2d(); | |
} | |
this.maxVel.set(1000, 1000); | |
// some default contants | |
/** | |
* Default gravity value of the entity<br> | |
* default value : 0.98 (earth gravity)<br> | |
* to be set to 0 for RPG, shooter, etc...<br> | |
* Note: Gravity can also globally be defined through me.sys.gravity | |
* @public | |
* @see me.sys.gravity | |
* @type Number | |
* @name gravity | |
* @memberOf me.ObjectEntity | |
*/ | |
this.gravity = me.sys.gravity!==undefined ? me.sys.gravity : 0.98; | |
// just to identify our object | |
this.isEntity = true; | |
/** | |
* dead/living state of the entity<br> | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name alive | |
* @memberOf me.ObjectEntity | |
*/ | |
this.alive = true; | |
// make sure it's visible by default | |
this.visible = true; | |
// and also non floating by default | |
this.floating = false; | |
// and non persistent per default | |
this.isPersistent = false; | |
/** | |
* falling state of the object<br> | |
* true if the object is falling<br> | |
* false if the object is standing on something<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name falling | |
* @memberOf me.ObjectEntity | |
*/ | |
this.falling = false; | |
/** | |
* jumping state of the object<br> | |
* equal true if the entity is jumping<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name jumping | |
* @memberOf me.ObjectEntity | |
*/ | |
this.jumping = true; | |
// some usefull slope variable | |
this.slopeY = 0; | |
/** | |
* equal true if the entity is standing on a slope<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name onslope | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onslope = false; | |
/** | |
* equal true if the entity is on a ladder<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name onladder | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onladder = false; | |
/** | |
* equal true if the entity can go down on a ladder<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name disableTopLadderCollision | |
* @memberOf me.ObjectEntity | |
*/ | |
this.disableTopLadderCollision = false; | |
// to enable collision detection | |
this.collidable = typeof(settings.collidable) !== "undefined" ? settings.collidable : true; | |
// default objec type | |
this.type = settings.type || 0; | |
// default flip value | |
this.lastflipX = this.lastflipY = false; | |
// ref to the collision map | |
this.collisionMap = me.game.collisionMap; | |
/** | |
* Define if an entity can go through breakable tiles<br> | |
* default value : false<br> | |
* @public | |
* @type Boolean | |
* @name canBreakTile | |
* @memberOf me.ObjectEntity | |
*/ | |
this.canBreakTile = false; | |
/** | |
* a callback when an entity break a tile<br> | |
* @public | |
* @callback | |
* @name onTileBreak | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onTileBreak = null; | |
// add a default shape | |
if (settings.isEllipse===true) { | |
// ellipse | |
this.addShape(new me.Ellipse(new me.Vector2d(0,0), this.width, this.height)); | |
} | |
else if ((settings.isPolygon===true) || (settings.isPolyline===true)) { | |
// add a polyshape | |
this.addShape(new me.PolyShape(new me.Vector2d(0,0), settings.points, settings.isPolygon)); | |
// set the entity object based on the bounding box size ? | |
this.width = this.collisionBox.width; | |
this.height = this.collisionBox.height; | |
} | |
else { | |
// add a rectangle | |
this.addShape(new me.Rect(new me.Vector2d(0,0), this.width, this.height)); | |
} | |
}, | |
/** | |
* specify the size of the hit box for collision detection<br> | |
* (allow to have a specific size for each object)<br> | |
* e.g. : object with resized collision box :<br> | |
* <img src="images/me.Rect.colpos.png"/> | |
* @name updateColRect | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x x offset (specify -1 to not change the width) | |
* @param {Number} w width of the hit box | |
* @param {Number} y y offset (specify -1 to not change the height) | |
* @param {Number} h height of the hit box | |
*/ | |
updateColRect : function(x, w, y, h) { | |
this.collisionBox.adjustSize(x, w, y, h); | |
}, | |
/** | |
* add a collision shape to this entity< | |
* @name addShape | |
* @memberOf me.ObjectEntity | |
* @public | |
* @function | |
* @param {me.objet} shape a shape object | |
*/ | |
addShape : function(shape) { | |
if (this.shapes === null) { | |
this.shapes = []; | |
} | |
this.shapes.push(shape); | |
// some hack to get the collisionBox working in this branch | |
// to be removed once the ticket #103 will be done | |
if (this.shapes.length === 1) { | |
this.collisionBox = this.shapes[0].getBounds(); | |
// collisionBox pos vector is a reference to this pos vector | |
this.collisionBox.pos = this.pos; | |
// offset position vector | |
this.pos.add(this.shapes[0].offset); | |
} | |
}, | |
/** | |
* onCollision Event function<br> | |
* called by the game manager when the object collide with shtg<br> | |
* by default, if the object type is Collectable, the destroy function is called | |
* @name onCollision | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} res collision vector | |
* @param {me.ObjectEntity} obj the other object that hit this object | |
* @protected | |
*/ | |
onCollision : function(res, obj) { | |
// destroy the object if collectable | |
if (this.collidable && (this.type === me.game.COLLECTABLE_OBJECT)) | |
me.game.remove(this); | |
}, | |
/** | |
* set the entity default velocity<br> | |
* note : velocity is by default limited to the same value, see setMaxVelocity if needed<br> | |
* @name setVelocity | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x velocity on x axis | |
* @param {Number} y velocity on y axis | |
* @protected | |
*/ | |
setVelocity : function(x, y) { | |
this.accel.x = x !== 0 ? x : this.accel.x; | |
this.accel.y = y !== 0 ? y : this.accel.y; | |
// limit by default to the same max value | |
this.setMaxVelocity(x,y); | |
}, | |
/** | |
* cap the entity velocity to the specified value<br> | |
* @name setMaxVelocity | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x max velocity on x axis | |
* @param {Number} y max velocity on y axis | |
* @protected | |
*/ | |
setMaxVelocity : function(x, y) { | |
this.maxVel.x = x; | |
this.maxVel.y = y; | |
}, | |
/** | |
* set the entity default friction<br> | |
* @name setFriction | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x horizontal friction | |
* @param {Number} y vertical friction | |
* @protected | |
*/ | |
setFriction : function(x, y) { | |
this.friction.x = x || 0; | |
this.friction.y = y || 0; | |
}, | |
/** | |
* Flip object on horizontal axis | |
* @name flipX | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipX : function(flip) { | |
if (flip !== this.lastflipX) { | |
this.lastflipX = flip; | |
if (this.renderable && this.renderable.flipX) { | |
// flip the animation | |
this.renderable.flipX(flip); | |
} | |
// flip the collision box | |
this.collisionBox.flipX(this.width); | |
} | |
}, | |
/** | |
* Flip object on vertical axis | |
* @name flipY | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipY : function(flip) { | |
if (flip !== this.lastflipY) { | |
this.lastflipY = flip; | |
if (this.renderable && this.renderable.flipY) { | |
// flip the animation | |
this.renderable.flipY(flip); | |
} | |
// flip the collision box | |
this.collisionBox.flipY(this.height); | |
} | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity move left of right<br> | |
* @name doWalk | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} left will automatically flip horizontally the entity sprite | |
* @protected | |
* @deprecated | |
* @example | |
* if (me.input.isKeyPressed('left')) | |
* { | |
* this.doWalk(true); | |
* } | |
* else if (me.input.isKeyPressed('right')) | |
* { | |
* this.doWalk(false); | |
* } | |
*/ | |
doWalk : function(left) { | |
this.flipX(left); | |
this.vel.x += (left) ? -this.accel.x * me.timer.tick : this.accel.x * me.timer.tick; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity move up and down<br> | |
* only valid is the player is on a ladder | |
* @name doClimb | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} up will automatically flip vertically the entity sprite | |
* @protected | |
* @deprecated | |
* @example | |
* if (me.input.isKeyPressed('up')) | |
* { | |
* this.doClimb(true); | |
* } | |
* else if (me.input.isKeyPressed('down')) | |
* { | |
* this.doClimb(false); | |
* } | |
*/ | |
doClimb : function(up) { | |
// add the player x acceleration to the y velocity | |
if (this.onladder) { | |
this.vel.y = (up) ? -this.accel.x * me.timer.tick | |
: this.accel.x * me.timer.tick; | |
this.disableTopLadderCollision = !up; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity jump<br> | |
* @name doJump | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @deprecated | |
*/ | |
doJump : function() { | |
// only jump if standing | |
if (!this.jumping && !this.falling) { | |
this.vel.y = -this.maxVel.y * me.timer.tick; | |
this.jumping = true; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* force to the entity to jump (for double jump)<br> | |
* @name forceJump | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @deprecated | |
*/ | |
forceJump : function() { | |
this.jumping = this.falling = false; | |
this.doJump(); | |
}, | |
/** | |
* return the distance to the specified entity | |
* @name distanceTo | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.ObjectEntity} entity Entity | |
* @return {Number} distance | |
*/ | |
distanceTo: function(e) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var dx = (this.pos.x + this.hWidth) - (e.pos.x + e.hWidth); | |
var dy = (this.pos.y + this.hHeight) - (e.pos.y + e.hHeight); | |
return Math.sqrt(dx*dx+dy*dy); | |
}, | |
/** | |
* return the distance to the specified point | |
* @name distanceToPoint | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} vector vector | |
* @return {Number} distance | |
*/ | |
distanceToPoint: function(v) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var dx = (this.pos.x + this.hWidth) - (v.x); | |
var dy = (this.pos.y + this.hHeight) - (v.y); | |
return Math.sqrt(dx*dx+dy*dy); | |
}, | |
/** | |
* return the angle to the specified entity | |
* @name angleTo | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.ObjectEntity} entity Entity | |
* @return {Number} angle in radians | |
*/ | |
angleTo: function(e) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var ax = (e.pos.x + e.hWidth) - (this.pos.x + this.hWidth); | |
var ay = (e.pos.y + e.hHeight) - (this.pos.y + this.hHeight); | |
return Math.atan2(ay, ax); | |
}, | |
/** | |
* return the angle to the specified point | |
* @name angleToPoint | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} vector vector | |
* @return {Number} angle in radians | |
*/ | |
angleToPoint: function(v) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var ax = (v.x) - (this.pos.x + this.hWidth); | |
var ay = (v.y) - (this.pos.y + this.hHeight); | |
return Math.atan2(ay, ax); | |
}, | |
/** | |
* handle the player movement on a slope | |
* and update vel value | |
* @ignore | |
*/ | |
checkSlope : function(tile, left) { | |
// first make the object stick to the tile | |
this.pos.y = tile.pos.y - this.height; | |
// normally the check should be on the object center point, | |
// but since the collision check is done on corner, we must do the same thing here | |
if (left) | |
this.slopeY = tile.height - (this.collisionBox.right + this.vel.x - tile.pos.x); | |
else | |
this.slopeY = (this.collisionBox.left + this.vel.x - tile.pos.x); | |
// cancel y vel | |
this.vel.y = 0; | |
// set player position (+ workaround when entering/exiting slopes tile) | |
this.pos.y += this.slopeY.clamp(0, tile.height); | |
}, | |
/** | |
* compute the new velocity value | |
* @ignore | |
*/ | |
computeVelocity : function(vel) { | |
// apply gravity (if any) | |
if (this.gravity) { | |
// apply a constant gravity (if not on a ladder) | |
vel.y += !this.onladder?(this.gravity * me.timer.tick):0; | |
// check if falling / jumping | |
this.falling = (vel.y > 0); | |
this.jumping = this.falling?false:this.jumping; | |
} | |
// apply friction | |
if (this.friction.x) | |
vel.x = me.utils.applyFriction(vel.x,this.friction.x); | |
if (this.friction.y) | |
vel.y = me.utils.applyFriction(vel.y,this.friction.y); | |
// cap velocity | |
if (vel.y !== 0) | |
vel.y = vel.y.clamp(-this.maxVel.y,this.maxVel.y); | |
if (vel.x !== 0) | |
vel.x = vel.x.clamp(-this.maxVel.x,this.maxVel.x); | |
}, | |
/** | |
* handle the player movement, "trying" to update his position<br> | |
* @name updateMovement | |
* @memberOf me.ObjectEntity | |
* @function | |
* @return {me.Vector2d} a collision vector | |
* @example | |
* // make the player move | |
* if (me.input.isKeyPressed('left')) | |
* { | |
* this.vel.x -= this.accel.x * me.timer.tick; | |
* } | |
* else if (me.input.isKeyPressed('right')) | |
* { | |
* this.vel.x += this.accel.x * me.timer.tick; | |
* } | |
* // update player position | |
* var res = this.updateMovement(); | |
* | |
* // check for collision result with the environment | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else if(res.y != 0) | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* | |
* // display the tile type | |
* console.log(res.yprop.type) | |
* } | |
* | |
* // check player status after collision check | |
* var updated = (this.vel.x!=0 || this.vel.y!=0); | |
*/ | |
updateMovement : function() { | |
this.computeVelocity(this.vel); | |
// Adjust position only on collidable object | |
var collision; | |
if (this.collidable) { | |
// check for collision | |
collision = this.collisionMap.checkCollision(this.collisionBox, this.vel); | |
// update some flags | |
this.onslope = collision.yprop.isSlope || collision.xprop.isSlope; | |
// clear the ladder flag | |
this.onladder = false; | |
// y collision | |
if (collision.y) { | |
// going down, collision with the floor | |
this.onladder = collision.yprop.isLadder || collision.yprop.isTopLadder; | |
if (collision.y > 0) { | |
if (collision.yprop.isSolid || | |
(collision.yprop.isPlatform && (this.collisionBox.bottom - 1 <= collision.ytile.pos.y)) || | |
(collision.yprop.isTopLadder && !this.disableTopLadderCollision)) { | |
// adjust position to the corresponding tile | |
this.pos.y = ~~this.pos.y; | |
this.vel.y = (this.falling) ?collision.ytile.pos.y - this.collisionBox.bottom: 0 ; | |
this.falling = false; | |
} | |
else if (collision.yprop.isSlope && !this.jumping) { | |
// we stop falling | |
this.checkSlope(collision.ytile, collision.yprop.isLeftSlope); | |
this.falling = false; | |
} | |
else if (collision.yprop.isBreakable) { | |
if (this.canBreakTile) { | |
// remove the tile | |
me.game.currentLevel.clearTile(collision.ytile.col, collision.ytile.row); | |
if (this.onTileBreak) | |
this.onTileBreak(); | |
} | |
else { | |
// adjust position to the corresponding tile | |
this.pos.y = ~~this.pos.y; | |
this.vel.y = (this.falling) ?collision.ytile.pos.y - this.collisionBox.bottom: 0; | |
this.falling = false; | |
} | |
} | |
} | |
// going up, collision with ceiling | |
else if (collision.y < 0) { | |
if (!collision.yprop.isPlatform && !collision.yprop.isLadder && !collision.yprop.isTopLadder) { | |
this.falling = true; | |
// cancel the y velocity | |
this.vel.y = 0; | |
} | |
} | |
} | |
// x collision | |
if (collision.x) { | |
this.onladder = collision.xprop.isLadder || collision.yprop.isTopLadder; | |
if (collision.xprop.isSlope && !this.jumping) { | |
this.checkSlope(collision.xtile, collision.xprop.isLeftSlope); | |
this.falling = false; | |
} else { | |
// can walk through the platform & ladder | |
if (!collision.xprop.isPlatform && !collision.xprop.isLadder && !collision.xprop.isTopLadder) { | |
if (collision.xprop.isBreakable && this.canBreakTile) { | |
// remove the tile | |
me.game.currentLevel.clearTile(collision.xtile.col, collision.xtile.row); | |
if (this.onTileBreak) { | |
this.onTileBreak(); | |
} | |
} else { | |
this.vel.x = 0; | |
} | |
} | |
} | |
} | |
} | |
// update player position | |
this.pos.add(this.vel); | |
// returns the collision "vector" | |
return collision; | |
}, | |
/** | |
* Checks if this entity collides with others entities. | |
* @public | |
* @name collide | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (if multiple collision){@link me.Rect#collideVsAABB} | |
* @example | |
* // update player movement | |
* this.updateMovement(); | |
* | |
* // check for collision with other objects | |
* res = this.collide(); | |
* | |
* // check if we collide with an enemy : | |
* if (res && (res.obj.type == me.game.ENEMY_OBJECT)) | |
* { | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* } | |
*/ | |
collide : function(multiple) { | |
return me.game.collide(this, multiple || false); | |
}, | |
/** | |
* Checks if the specified entity collides with others entities of the specified type. | |
* @public | |
* @name collideType | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {String} type Entity type to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collideType : function(type, multiple) { | |
return me.game.collideType(this, type, multiple || false); | |
}, | |
/** @ignore */ | |
update : function() { | |
if (this.renderable) { | |
return this.renderable.update(); | |
} | |
return false; | |
}, | |
/** | |
* @ignore | |
*/ | |
getBounds : function() { | |
if (this.renderable) { | |
// translate the renderable position since its | |
// position is relative to this entity | |
return this.renderable.getBounds().translateV(this.pos); | |
} | |
return null; | |
}, | |
/** | |
* object draw<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context) { | |
// draw the sprite if defined | |
if (this.renderable) { | |
// translate the renderable position (relative to the entity) | |
// and keeps it in the entity defined bounds | |
// anyway to optimize this ? | |
var x = ~~(this.pos.x + (this.anchorPoint.x * (this.width - this.renderable.width))); | |
var y = ~~(this.pos.y + (this.anchorPoint.y * (this.height - this.renderable.height))); | |
context.translate(x, y); | |
this.renderable.draw(context); | |
context.translate(-x, -y); | |
} | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
// free some property objects | |
if (this.renderable) { | |
this.renderable.destroy.apply(this.renderable, arguments); | |
this.renderable = null; | |
} | |
this.onDestroyEvent.apply(this, arguments); | |
this.collisionBox = null; | |
this.shapes = []; | |
}, | |
/** | |
* OnDestroy Notification function<br> | |
* Called by engine before deleting the object | |
* @name onDestroyEvent | |
* @memberOf me.ObjectEntity | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended ! | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a Collectable entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* @class | |
* @extends me.ObjectEntity | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {me.ObjectSettings} settings object settings | |
*/ | |
me.CollectableEntity = me.ObjectEntity.extend( | |
/** @scope me.CollectableEntity.prototype */ | |
{ | |
/** @ignore */ | |
init : function(x, y, settings) { | |
// call the parent constructor | |
this.parent(x, y, settings); | |
this.type = me.game.COLLECTABLE_OBJECT; | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a level entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* @class | |
* @extends me.ObjectEntity | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the object | |
* @param {Number} y the y coordinates of the object | |
* @param {me.ObjectSettings} settings object settings | |
*/ | |
me.LevelEntity = me.ObjectEntity.extend( | |
/** @scope me.LevelEntity.prototype */ | |
{ | |
/** @ignore */ | |
init : function(x, y, settings) { | |
this.parent(x, y, settings); | |
this.nextlevel = settings.to; | |
this.fade = settings.fade; | |
this.duration = settings.duration; | |
this.fading = false; | |
// a temp variable | |
this.gotolevel = settings.to; | |
}, | |
/** | |
* @ignore | |
*/ | |
onFadeComplete : function() { | |
me.levelDirector.loadLevel(this.gotolevel); | |
me.game.viewport.fadeOut(this.fade, this.duration); | |
}, | |
/** | |
* go to the specified level | |
* @name goTo | |
* @memberOf me.LevelEntity | |
* @function | |
* @param {String} [level=this.nextlevel] name of the level to load | |
* @protected | |
*/ | |
goTo : function(level) { | |
this.gotolevel = level || this.nextlevel; | |
// load a level | |
//console.log("going to : ", to); | |
if (this.fade && this.duration) { | |
if (!this.fading) { | |
this.fading = true; | |
me.game.viewport.fadeIn(this.fade, this.duration, | |
this.onFadeComplete.bind(this)); | |
} | |
} else { | |
me.levelDirector.loadLevel(this.gotolevel); | |
} | |
}, | |
/** @ignore */ | |
onCollision : function() { | |
this.goTo(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Screens objects & State machine | |
* | |
*/ | |
(function($) { | |
/** | |
* A class skeleton for "Screen" Object <br> | |
* every "screen" object (title screen, credits, ingame, etc...) to be managed <br> | |
* through the state manager must inherit from this base class. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Boolean} [addAsObject] add the object in the game manager object pool<br> | |
* @param {Boolean} [isPersistent] make the screen persistent over level changes; requires addAsObject=true<br> | |
* @see me.state | |
* @example | |
* // create a custom loading screen | |
* var CustomLoadingScreen = me.ScreenObject.extend( | |
* { | |
* // constructor | |
* init: function() | |
* { | |
* // pass true to the parent constructor | |
* // as we draw our progress bar in the draw function | |
* this.parent(true); | |
* // a font logo | |
* this.logo = new me.Font('century gothic', 32, 'white'); | |
* // flag to know if we need to refresh the display | |
* this.invalidate = false; | |
* // load progress in percent | |
* this.loadPercent = 0; | |
* // setup a callback | |
* me.loader.onProgress = this.onProgressUpdate.bind(this); | |
* | |
* }, | |
* | |
* // will be fired by the loader each time a resource is loaded | |
* onProgressUpdate: function(progress) | |
* { | |
* this.loadPercent = progress; | |
* this.invalidate = true; | |
* }, | |
* | |
* | |
* // make sure the screen is only refreshed on load progress | |
* update: function() | |
* { | |
* if (this.invalidate===true) | |
* { | |
* // clear the flag | |
* this.invalidate = false; | |
* // and return true | |
* return true; | |
* } | |
* // else return false | |
* return false; | |
* }, | |
* | |
* // on destroy event | |
* onDestroyEvent : function () | |
* { | |
* // "nullify" all fonts | |
* this.logo = null; | |
* }, | |
* | |
* // draw function | |
* draw : function(context) | |
* { | |
* // clear the screen | |
* me.video.clearSurface (context, "black"); | |
* | |
* // measure the logo size | |
* logo_width = this.logo.measureText(context,"awesome loading screen").width; | |
* | |
* // draw our text somewhere in the middle | |
* this.logo.draw(context, | |
* "awesome loading screen", | |
* ((me.video.getWidth() - logo_width) / 2), | |
* (me.video.getHeight() + 60) / 2); | |
* | |
* // display a progressive loading bar | |
* var width = Math.floor(this.loadPercent * me.video.getWidth()); | |
* | |
* // draw the progress bar | |
* context.strokeStyle = "silver"; | |
* context.strokeRect(0, (me.video.getHeight() / 2) + 40, me.video.getWidth(), 6); | |
* context.fillStyle = "#89b002"; | |
* context.fillRect(2, (me.video.getHeight() / 2) + 42, width-4, 2); | |
* }, | |
* }); | |
* | |
*/ | |
me.ScreenObject = me.Renderable.extend( | |
/** @scope me.ScreenObject.prototype */ | |
{ | |
/** @ignore */ | |
addAsObject : false, | |
/** @ignore */ | |
visible : false, | |
/** @ignore */ | |
frame : 0, | |
/** | |
* Z-order for object sorting<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* default value : 999 | |
* @private | |
* @type Number | |
* @name z | |
* @memberOf me.ScreenObject | |
*/ | |
z : 999, | |
/** | |
* initialization function | |
* @ignore | |
*/ | |
init : function(addAsObject, isPersistent) { | |
this.parent(new me.Vector2d(0, 0), 0, 0); | |
this.addAsObject = this.visible = (addAsObject === true) || false; | |
this.isPersistent = (this.visible && (isPersistent === true)) || false; | |
}, | |
/** | |
* Object reset function | |
* @ignore | |
*/ | |
reset : function() { | |
// reset the game manager | |
me.game.reset(); | |
// reset the frame counter | |
this.frame = 0; | |
this.frameRate = Math.round(60/me.sys.fps); | |
// call the onReset Function | |
this.onResetEvent.apply(this, arguments); | |
// add our object to the GameObject Manager | |
// allowing to benefit from the keyboard event stuff | |
if (this.addAsObject) { | |
// make sure we are visible upon reset | |
this.visible = true; | |
// Always use screen coordinates | |
this.floating = true; | |
// update the screen size if added as an object | |
this.set(new me.Vector2d(), me.game.viewport.width, me.game.viewport.height); | |
// add ourself ! | |
me.game.add(this, this.z); | |
} | |
// sort the object pool | |
me.game.sort(); | |
}, | |
/** | |
* destroy function | |
* @ignore | |
*/ | |
destroy : function() { | |
// notify the object | |
this.onDestroyEvent.apply(this, arguments); | |
}, | |
/** | |
* update function<br> | |
* optional empty function<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* @name update | |
* @memberOf me.ScreenObject | |
* @function | |
* @example | |
* // define a Title Screen | |
* var TitleScreen = me.ScreenObject.extend( | |
* { | |
* // override the default constructor | |
* init : function() | |
* { | |
* //call the parent constructor giving true | |
* //as parameter, so that we use the update & draw functions | |
* this.parent(true); | |
* // ... | |
* }, | |
* // ... | |
* }); | |
*/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* frame update function function | |
* @ignore | |
*/ | |
onUpdateFrame : function() { | |
// handle frame skipping if required | |
if ((++this.frame%this.frameRate)===0) { | |
// reset the frame counter | |
this.frame = 0; | |
// update the timer | |
me.timer.update(); | |
// update all games object | |
me.game.update(); | |
} | |
// draw the game objects | |
me.game.draw(); | |
}, | |
/** | |
* draw function<br> | |
* optional empty function<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* @name draw | |
* @memberOf me.ScreenObject | |
* @function | |
* @example | |
* // define a Title Screen | |
* var TitleScreen = me.ScreenObject.extend( | |
* { | |
* // override the default constructor | |
* init : function() | |
* { | |
* //call the parent constructor giving true | |
* //as parameter, so that we use the update & draw functions | |
* this.parent(true); | |
* // ... | |
* }, | |
* // ... | |
* }); | |
*/ | |
draw : function() { | |
// to be extended | |
}, | |
/** | |
* onResetEvent function<br> | |
* called by the state manager when reseting the object<br> | |
* this is typically where you will load a level, etc... | |
* to be extended | |
* @name onResetEvent | |
* @memberOf me.ScreenObject | |
* @function | |
* @param {} [arguments...] optional arguments passed when switching state | |
* @see me.state#change | |
*/ | |
onResetEvent : function() { | |
// to be extended | |
}, | |
/** | |
* onDestroyEvent function<br> | |
* called by the state manager before switching to another state<br> | |
* @name onDestroyEvent | |
* @memberOf me.ScreenObject | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended | |
} | |
}); | |
// based on the requestAnimationFrame polyfill by Erik Möller | |
(function() { | |
var lastTime = 0; | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
// get unprefixed rAF and cAF, if present | |
var requestAnimationFrame = window.requestAnimationFrame; | |
var cancelAnimationFrame = window.cancelAnimationFrame; | |
for(var x = 0; x < vendors.length; ++x) { | |
if ( requestAnimationFrame && cancelAnimationFrame ) { | |
break; | |
} | |
requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; | |
cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || | |
window[vendors[x]+'CancelRequestAnimationFrame']; | |
} | |
if (!requestAnimationFrame || !cancelAnimationFrame) { | |
requestAnimationFrame = function(callback, element) { | |
var currTime = Date.now(); | |
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
var id = window.setTimeout(function() { | |
callback(currTime + timeToCall); | |
}, timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
cancelAnimationFrame = function(id) { | |
window.clearTimeout(id); | |
}; | |
} | |
// put back in global namespace | |
window.requestAnimationFrame = requestAnimationFrame; | |
window.cancelAnimationFrame = cancelAnimationFrame; | |
}()); | |
/** | |
* a State Manager (state machine)<p> | |
* There is no constructor function for me.state. | |
* @namespace me.state | |
* @memberOf me | |
*/ | |
me.state = (function() { | |
// hold public stuff in our singleton | |
var obj = {}; | |
/*------------------------------------------- | |
PRIVATE STUFF | |
--------------------------------------------*/ | |
// current state | |
var _state = -1; | |
// requestAnimeFrame Id | |
var _animFrameId = -1; | |
// whether the game state is "paused" | |
var _isPaused = false; | |
// list of screenObject | |
var _screenObject = {}; | |
// fading transition parameters between screen | |
var _fade = { | |
color : "", | |
duration : 0 | |
}; | |
// callback when state switch is done | |
/** @ignore */ | |
var _onSwitchComplete = null; | |
// just to keep track of possible extra arguments | |
var _extraArgs = null; | |
// cache reference to the active screen update frame | |
var _activeUpdateFrame = null; | |
/** | |
* @ignore | |
*/ | |
function _startRunLoop() { | |
// ensure nothing is running first and in valid state | |
if ((_animFrameId === -1) && (_state !== -1)) { | |
// reset the timer | |
me.timer.reset(); | |
// start the main loop | |
_animFrameId = window.requestAnimationFrame(_renderFrame); | |
} | |
} | |
/** | |
* Resume the game loop after a pause. | |
* @ignore | |
*/ | |
function _resumeRunLoop() { | |
// ensure game is actually paused and in valid state | |
if (_isPaused && (_state !== -1)) { | |
// reset the timer | |
me.timer.reset(); | |
_isPaused = false; | |
} | |
} | |
/** | |
* Pause the loop for most screen objects. | |
* @ignore | |
*/ | |
function _pauseRunLoop() { | |
// Set the paused boolean to stop updates on (most) entities | |
_isPaused = true; | |
} | |
/** | |
* this is only called when using requestAnimFrame stuff | |
* @ignore | |
*/ | |
function _renderFrame() { | |
_activeUpdateFrame(); | |
if (_animFrameId !== -1) { | |
_animFrameId = window.requestAnimationFrame(_renderFrame); | |
} | |
} | |
/** | |
* stop the SO main loop | |
* @ignore | |
*/ | |
function _stopRunLoop() { | |
// cancel any previous animationRequestFrame | |
window.cancelAnimationFrame(_animFrameId); | |
_animFrameId = -1; | |
} | |
/** | |
* start the SO main loop | |
* @ignore | |
*/ | |
function _switchState(state) { | |
// clear previous interval if any | |
_stopRunLoop(); | |
// call the screen object destroy method | |
if (_screenObject[_state]) { | |
if (_screenObject[_state].screen.visible) { | |
// persistent or not, make sure we remove it | |
// from the current object list | |
me.game.remove.call(me.game, _screenObject[_state].screen, true); | |
} else { | |
// just notify the object | |
_screenObject[_state].screen.destroy(); | |
} | |
} | |
if (_screenObject[state]) | |
{ | |
// set the global variable | |
_state = state; | |
// call the reset function with _extraArgs as arguments | |
_screenObject[_state].screen.reset.apply(_screenObject[_state].screen, _extraArgs); | |
// cache the new screen object update function | |
_activeUpdateFrame = _screenObject[_state].screen.onUpdateFrame.bind(_screenObject[_state].screen); | |
// and start the main loop of the | |
// new requested state | |
_startRunLoop(); | |
// execute callback if defined | |
if (_onSwitchComplete) { | |
_onSwitchComplete(); | |
} | |
// force repaint | |
me.game.repaint(); | |
} | |
} | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* default state value for Loading Screen | |
* @constant | |
* @name LOADING | |
* @memberOf me.state | |
*/ | |
obj.LOADING = 0; | |
/** | |
* default state value for Menu Screen | |
* @constant | |
* @name MENU | |
* @memberOf me.state | |
*/ | |
obj.MENU = 1; | |
/** | |
* default state value for "Ready" Screen | |
* @constant | |
* @name READY | |
* @memberOf me.state | |
*/ | |
obj.READY = 2; | |
/** | |
* default state value for Play Screen | |
* @constant | |
* @name PLAY | |
* @memberOf me.state | |
*/ | |
obj.PLAY = 3; | |
/** | |
* default state value for Game Over Screen | |
* @constant | |
* @name GAMEOVER | |
* @memberOf me.state | |
*/ | |
obj.GAMEOVER = 4; | |
/** | |
* default state value for Game End Screen | |
* @constant | |
* @name GAME_END | |
* @memberOf me.state | |
*/ | |
obj.GAME_END = 5; | |
/** | |
* default state value for High Score Screen | |
* @constant | |
* @name SCORE | |
* @memberOf me.state | |
*/ | |
obj.SCORE = 6; | |
/** | |
* default state value for Credits Screen | |
* @constant | |
* @name CREDITS | |
* @memberOf me.state | |
*/ | |
obj.CREDITS = 7; | |
/** | |
* default state value for Settings Screen | |
* @constant | |
* @name SETTINGS | |
* @memberOf me.state | |
*/ | |
obj.SETTINGS = 8; | |
/** | |
* default state value for user defined constants<br> | |
* @constant | |
* @name USER | |
* @memberOf me.state | |
* @example | |
* var STATE_INFO = me.state.USER + 0; | |
* var STATE_WARN = me.state.USER + 1; | |
* var STATE_ERROR = me.state.USER + 2; | |
* var STATE_CUTSCENE = me.state.USER + 3; | |
*/ | |
obj.USER = 100; | |
/** | |
* onPause callback | |
* @callback | |
* @name onPause | |
* @memberOf me.state | |
*/ | |
obj.onPause = null; | |
/** | |
* onResume callback | |
* @callback | |
* @name onResume | |
* @memberOf me.state | |
*/ | |
obj.onResume = null; | |
/** | |
* onStop callback | |
* @callback | |
* @name onStop | |
* @memberOf me.state | |
*/ | |
obj.onStop = null; | |
/** | |
* onRestart callback | |
* @callback | |
* @name onRestart | |
* @memberOf me.state | |
*/ | |
obj.onRestart = null; | |
/** | |
* @ignore | |
*/ | |
obj.init = function() { | |
// set the embedded loading screen | |
obj.set(obj.LOADING, new me.DefaultLoadingScreen()); | |
// set pause/stop action on losing focus | |
$.addEventListener("blur", function() { | |
// only in case we are not loading stuff | |
if (_state !== obj.LOADING) { | |
if (me.sys.stopOnBlur) { | |
obj.stop(true); | |
// callback? | |
if (obj.onStop) | |
obj.onStop(); | |
// publish the pause notification | |
me.event.publish(me.event.STATE_STOP); | |
} | |
if (me.sys.pauseOnBlur) { | |
obj.pause(true); | |
// callback? | |
if (obj.onPause) | |
obj.onPause(); | |
// publish the pause notification | |
me.event.publish(me.event.STATE_PAUSE); | |
} | |
} | |
}, false); | |
// set restart/resume action on gaining focus | |
$.addEventListener("focus", function() { | |
// only in case we are not loading stuff | |
if (_state !== obj.LOADING) { | |
// note: separate boolean so we can stay paused if user prefers | |
if (me.sys.resumeOnFocus) { | |
obj.resume(true); | |
// callback? | |
if (obj.onResume) | |
obj.onResume(); | |
// publish the resume notification | |
me.event.publish(me.event.STATE_RESUME); | |
} | |
if (me.sys.stopOnBlur) { | |
obj.restart(true); | |
// force repaint | |
me.game.repaint(); | |
// callback? | |
if (obj.onRestart) | |
obj.onRestart(); | |
// publish the resume notification | |
me.event.publish(me.event.STATE_RESTART); | |
} | |
} | |
}, false); | |
}; | |
/** | |
* Stop the current screen object. | |
* @name stop | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} pauseTrack pause current track on screen stop. | |
*/ | |
obj.stop = function(music) { | |
// stop the main loop | |
_stopRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.pauseTrack(); | |
}; | |
/** | |
* pause the current screen object | |
* @name pause | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} pauseTrack pause current track on screen pause | |
*/ | |
obj.pause = function(music) { | |
// stop the main loop | |
_pauseRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.pauseTrack(); | |
}; | |
/** | |
* Restart the screen object from a full stop. | |
* @name restart | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} resumeTrack resume current track on screen resume | |
*/ | |
obj.restart = function(music) { | |
// restart the main loop | |
_startRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.resumeTrack(); | |
}; | |
/** | |
* resume the screen object | |
* @name resume | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} resumeTrack resume current track on screen resume | |
*/ | |
obj.resume = function(music) { | |
// resume the main loop | |
_resumeRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.resumeTrack(); | |
}; | |
/** | |
* return the running state of the state manager | |
* @name isRunning | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} true if a "process is running" | |
*/ | |
obj.isRunning = function() { | |
return _animFrameId !== -1; | |
}; | |
/** | |
* Return the pause state of the state manager | |
* @name isPaused | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} true if the game is paused | |
*/ | |
obj.isPaused = function() { | |
return _isPaused; | |
}; | |
/** | |
* associate the specified state with a screen object | |
* @name set | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
* @param {me.ScreenObject} | |
*/ | |
obj.set = function(state, so) { | |
_screenObject[state] = {}; | |
_screenObject[state].screen = so; | |
_screenObject[state].transition = true; | |
}; | |
/** | |
* return a reference to the current screen object<br> | |
* useful to call a object specific method | |
* @name current | |
* @memberOf me.state | |
* @public | |
* @function | |
* @return {me.ScreenObject} | |
*/ | |
obj.current = function() { | |
return _screenObject[_state].screen; | |
}; | |
/** | |
* specify a global transition effect | |
* @name transition | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {String} effect (only "fade" is supported for now) | |
* @param {String} color a CSS color value | |
* @param {Number} [duration=1000] expressed in milliseconds | |
*/ | |
obj.transition = function(effect, color, duration) { | |
if (effect === "fade") { | |
_fade.color = color; | |
_fade.duration = duration; | |
} | |
}; | |
/** | |
* enable/disable transition for a specific state (by default enabled for all) | |
* @name setTransition | |
* @memberOf me.state | |
* @public | |
* @function | |
*/ | |
obj.setTransition = function(state, enable) { | |
_screenObject[state].transition = enable; | |
}; | |
/** | |
* change the game/app state | |
* @name change | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
* @param {} [arguments...] extra arguments to be passed to the reset functions | |
* @example | |
* // The onResetEvent method on the play screen will receive two args: | |
* // "level_1" and the number 3 | |
* me.state.change(me.state.PLAY, "level_1", 3); | |
*/ | |
obj.change = function(state) { | |
// Protect against undefined ScreenObject | |
if (typeof(_screenObject[state]) === "undefined") { | |
throw "melonJS : Undefined ScreenObject for state '" + state + "'"; | |
} | |
_extraArgs = null; | |
if (arguments.length > 1) { | |
// store extra arguments if any | |
_extraArgs = Array.prototype.slice.call(arguments, 1); | |
} | |
// if fading effect | |
if (_fade.duration && _screenObject[state].transition) { | |
/** @ignore */ | |
_onSwitchComplete = function() { | |
me.game.viewport.fadeOut(_fade.color, _fade.duration); | |
}; | |
me.game.viewport.fadeIn(_fade.color, _fade.duration, | |
function() { | |
_switchState.defer(state); | |
}); | |
} | |
// else just switch without any effects | |
else { | |
// wait for the last frame to be | |
// "finished" before switching | |
_switchState.defer(state); | |
} | |
}; | |
/** | |
* return true if the specified state is the current one | |
* @name isCurrent | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
*/ | |
obj.isCurrent = function(state) { | |
return _state === state; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* a default loading screen | |
* @memberOf me | |
* @ignore | |
* @constructor | |
*/ | |
me.DefaultLoadingScreen = me.ScreenObject.extend({ | |
// constructor | |
init : function() { | |
this.parent(true); | |
// flag to know if we need to refresh the display | |
this.invalidate = false; | |
// handle for the susbcribe function | |
this.handle = null; | |
}, | |
// call when the loader is resetted | |
onResetEvent : function() { | |
// melonJS logo | |
this.logo1 = new me.Font('century gothic', 32, 'white', 'middle'); | |
this.logo2 = new me.Font('century gothic', 32, '#55aa00', 'middle'); | |
this.logo2.bold(); | |
this.logo1.textBaseline = this.logo2.textBaseline = "alphabetic"; | |
// default progress bar height | |
this.barHeight = 4; | |
// setup a callback | |
this.handle = me.event.subscribe(me.event.LOADER_PROGRESS, this.onProgressUpdate.bind(this)); | |
// load progress in percent | |
this.loadPercent = 0; | |
}, | |
// destroy object at end of loading | |
onDestroyEvent : function() { | |
// "nullify" all fonts | |
this.logo1 = this.logo2 = null; | |
// cancel the callback | |
if (this.handle) { | |
me.event.unsubscribe(this.handle); | |
this.handle = null; | |
} | |
}, | |
// make sure the screen is refreshed every frame | |
onProgressUpdate : function(progress) { | |
this.loadPercent = progress; | |
this.invalidate = true; | |
}, | |
// make sure the screen is refreshed every frame | |
update : function() { | |
if (this.invalidate === true) { | |
// clear the flag | |
this.invalidate = false; | |
// and return true | |
return true; | |
} | |
// else return false | |
return false; | |
}, | |
// draw the melonJS logo | |
drawLogo : function (context, x, y) { | |
context.save(); | |
// translate to destination point | |
context.translate(x,y); | |
// generated using Illustrator and the Ai2Canvas plugin | |
context.beginPath(); | |
context.moveTo(0.7, 48.9); | |
context.bezierCurveTo(10.8, 68.9, 38.4, 75.8, 62.2, 64.5); | |
context.bezierCurveTo(86.1, 53.1, 97.2, 27.7, 87.0, 7.7); | |
context.lineTo(87.0, 7.7); | |
context.bezierCurveTo(89.9, 15.4, 73.9, 30.2, 50.5, 41.4); | |
context.bezierCurveTo(27.1, 52.5, 5.2, 55.8, 0.7, 48.9); | |
context.lineTo(0.7, 48.9); | |
context.lineTo(0.7, 48.9); | |
context.closePath(); | |
context.fillStyle = "rgb(255, 255, 255)"; | |
context.fill(); | |
context.beginPath(); | |
context.moveTo(84.0, 7.0); | |
context.bezierCurveTo(87.6, 14.7, 72.5, 30.2, 50.2, 41.6); | |
context.bezierCurveTo(27.9, 53.0, 6.9, 55.9, 3.2, 48.2); | |
context.bezierCurveTo(-0.5, 40.4, 14.6, 24.9, 36.9, 13.5); | |
context.bezierCurveTo(59.2, 2.2, 80.3, -0.8, 84.0, 7.0); | |
context.lineTo(84.0, 7.0); | |
context.closePath(); | |
context.lineWidth = 5.3; | |
context.strokeStyle = "rgb(255, 255, 255)"; | |
context.lineJoin = "miter"; | |
context.miterLimit = 4.0; | |
context.stroke(); | |
context.restore(); | |
}, | |
// draw function | |
draw : function(context) { | |
// measure the logo size | |
var logo1_width = this.logo1.measureText(context, "melon").width; | |
var xpos = (me.video.getWidth() - logo1_width - this.logo2.measureText(context, "JS").width) / 2; | |
var ypos = (me.video.getHeight() / 2) + (this.logo2.measureText(context, "melon").height); | |
// clear surface | |
me.video.clearSurface(context, "#202020"); | |
// logo 100x85 | |
this.drawLogo( | |
context, | |
(me.video.getWidth() - 100) /2 , | |
(me.video.getHeight()/2) - (this.barHeight/2) - 90 | |
); | |
// draw the melonJS string | |
this.logo1.draw(context, 'melon', xpos , ypos); | |
xpos += logo1_width; | |
this.logo2.draw(context, 'JS', xpos, ypos); | |
// display a progressive loading bar | |
var progress = Math.floor(this.loadPercent * me.video.getWidth()); | |
// draw the progress bar | |
context.fillStyle = "black"; | |
context.fillRect(0, (me.video.getHeight()/2)-(this.barHeight/2), me.video.getWidth(), this.barHeight); | |
context.fillStyle = "#55aa00"; | |
context.fillRect(2, (me.video.getHeight()/2)-(this.barHeight/2), progress, this.barHeight); | |
} | |
}); | |
// --- END --- | |
})(window); | |
/* | |
* 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 singletong | |
var obj = {}; | |
// 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 texture atlas files | |
var atlasList = {}; | |
// 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 (obj.onload) { | |
// make sure we clear the timer | |
clearTimeout(timerId); | |
// trigger the onload callback | |
setTimeout(function () { | |
obj.onload(); | |
me.event.publish(me.event.LOADER_COMPLETE); | |
}, 300); | |
} else | |
console.error("no load callback defined"); | |
} else { | |
timerId = setTimeout(checkLoadStatus, 100); | |
} | |
} | |
/** | |
* load Images | |
* | |
* call 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 + obj.nocache; | |
} | |
/** | |
* preload TMX files | |
* @ignore | |
*/ | |
function preloadTMX(tmxData, onload, onerror) { | |
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 + obj.nocache, true); | |
// add the tmx to the levelDirector | |
if (tmxData.type === "tmx") { | |
me.levelDirector.addTMXLevel(tmxData.name); | |
} | |
// 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': | |
// 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; | |
} | |
// change the data format | |
format = 'xml'; | |
break; | |
case 'json': | |
result = JSON.parse(xmlhttp.responseText); | |
break; | |
default: | |
throw "melonJS: TMX file format " + format + "not supported !"; | |
} | |
// get the TMX content | |
tmxList[tmxData.name] = { | |
data: result, | |
isTMX: (tmxData.type === "tmx"), | |
format : 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 + obj.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 + obj.nocache, true); | |
httpReq.responseType = "arraybuffer"; | |
httpReq.onerror = onerror; | |
httpReq.onload = function(event){ | |
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 | |
*/ | |
obj.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); | |
*/ | |
obj.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. | |
* @public | |
* @callback | |
* @name onProgress | |
* @memberOf me.loader | |
* @example | |
* | |
* // set a callback for progress notification | |
* me.loader.onProgress = this.updateProgress.bind(this); | |
*/ | |
obj.onProgress = undefined; | |
/** | |
* just increment the number of already loaded resources | |
* @ignore | |
*/ | |
obj.onResourceLoaded = function(e) { | |
// increment the loading counter | |
loadCount++; | |
// callback ? | |
var progress = obj.getLoadProgress(); | |
if (obj.onProgress) { | |
// pass the load progress in percent, as parameter | |
obj.onProgress(progress); | |
} | |
me.event.publish(me.event.LOADER_PROGRESS, [progress]); | |
}; | |
/** | |
* on error callback for image loading | |
* @ignore | |
*/ | |
obj.onLoadingError = function(res) { | |
throw "melonJS: Failed loading resource " + res.src; | |
}; | |
/** | |
* enable the nocache mechanism | |
* @ignore | |
*/ | |
obj.setNocache = function(enable) { | |
obj.nocache = enable ? "?" + parseInt(Math.random() * 10000000, 10) : ''; | |
}; | |
/** | |
* 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> | |
* - src : path and file name of the resource<br> | |
* (!) for audio :<br> | |
* - src : path (only) where resources are located<br> | |
* - channel : optional number of channels to be created<br> | |
* - stream : optional boolean to enable audio streaming<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"}, | |
* // audio ressources | |
* {name: "bgmusic", type: "audio", src: "data/audio/", channel: 1, stream: true}, | |
* {name: "cling", type: "audio", src: "data/audio/", channel: 2}, | |
* // 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); | |
*/ | |
obj.preload = function(res) { | |
// parse the resources | |
for ( var i = 0; i < res.length; i++) { | |
resourceCount += obj.load(res[i], obj.onResourceLoaded.bind(obj), obj.onLoadingError.bind(obj, 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 parmeter must contain the following fields :<br> | |
* - name : internal name of the resource<br> | |
* - type : "audio", binary", "image", "json", "tmx", "tsx" | |
* - src : path and file name of the resource<br> | |
* (!) for audio :<br> | |
* - src : path (only) where resources are located<br> | |
* - channel : optional number of channels to be created<br> | |
* - stream : optional boolean to enable audio streaming<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 streaming music | |
* me.loader.load({ | |
* name : "bgmusic", | |
* type : "audio", | |
* src : "data/audio/", | |
* stream : true | |
* }, function() { | |
* me.audio.play("bgmusic"); | |
* }); | |
*/ | |
obj.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": | |
// only load is sound is enable | |
if (me.audio.isAudioEnable()) { | |
me.audio.load(res, onload, onerror); | |
return 1; | |
} | |
break; | |
default: | |
throw "melonJS: me.loader.load : unknown or invalid resource type : " + res.type; | |
} | |
return 0; | |
}; | |
/** | |
* 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"}); | |
*/ | |
obj.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 "melonJS: me.loader.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(); | |
*/ | |
obj.unloadAll = function() { | |
var name; | |
// unload all binary resources | |
for (name in binList) | |
obj.unload(name); | |
// unload all image resources | |
for (name in imgList) | |
obj.unload(name); | |
// unload all tmx resources | |
for (name in tmxList) | |
obj.unload(name); | |
// unload all atlas resources | |
for (name in atlasList) | |
obj.unload(name); | |
// unload all in json resources | |
for (name in jsonList) | |
obj.unload(name); | |
// unload all audio resources | |
me.audio.unloadAll(); | |
}; | |
/** | |
* return the specified TMX object storing type | |
* @name getTMXFormat | |
* @memberOf me.loader | |
* @public | |
* @function | |
* @param {String} tmx name of the tmx/tsx element ("map1"); | |
* @return {String} 'xml' or 'json' | |
*/ | |
obj.getTMXFormat = function(elt) { | |
// avoid case issue | |
elt = elt.toLowerCase(); | |
if (elt in tmxList) | |
return tmxList[elt].format; | |
else { | |
//console.log ("warning %s resource not yet loaded!",name); | |
return null; | |
} | |
}; | |
/** | |
* 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} | |
*/ | |
obj.getTMX = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getBinary = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getImage = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getJSON = function(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} | |
*/ | |
obj.getLoadProgress = function() { | |
return loadCount / resourceCount; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Font / Bitmap font | |
* | |
* ASCII Table | |
* http://www.asciitable.com/ | |
* [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz] | |
* | |
* -> first char " " 32d (0x20); | |
*/ | |
(function($) { | |
/** | |
* a generic system font object. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {String} font a CSS font name | |
* @param {Number|String} size size, or size + suffix (px, em, pt) | |
* @param {String} fillStyle a CSS color value | |
* @param {String} [textAlign="left"] horizontal alignment | |
*/ | |
me.Font = me.Renderable.extend( | |
/** @scope me.Font.prototype */ { | |
// private font properties | |
/** @ignore */ | |
font : null, | |
fontSize : null, | |
/** | |
* defines the color used to draw the font.<br> | |
* Default value : "#000000" | |
* @public | |
* @type String | |
* @name me.Font#fillStyle | |
*/ | |
fillStyle : "#000000", | |
/** | |
* defines the color used to draw the font stroke.<br> | |
* Default value : "#000000" | |
* @public | |
* @type String | |
* @name me.Font#strokeStyle | |
*/ | |
strokeStyle : "#000000", | |
/** | |
* sets the current line width, in pixels, when drawing stroke | |
* Default value : 1 | |
* @public | |
* @type Number | |
* @name me.Font#lineWidth | |
*/ | |
lineWidth : 1, | |
/** | |
* Set the default text alignment (or justification),<br> | |
* possible values are "left", "right", and "center".<br> | |
* Default value : "left" | |
* @public | |
* @type String | |
* @name me.Font#textAlign | |
*/ | |
textAlign : "left", | |
/** | |
* Set the text baseline (e.g. the Y-coordinate for the draw operation), <br> | |
* possible values are "top", "hanging, "middle, "alphabetic, "ideographic, "bottom"<br> | |
* Default value : "top" | |
* @public | |
* @type String | |
* @name me.Font#textBaseline | |
*/ | |
textBaseline : "top", | |
/** | |
* Set the line spacing height (when displaying multi-line strings). <br> | |
* Current font height will be multiplied with this value to set the line height. | |
* Default value : 1.0 | |
* @public | |
* @type Number | |
* @name me.Font#lineHeight | |
*/ | |
lineHeight : 1.0, | |
/** @ignore */ | |
init : function(font, size, fillStyle, textAlign) { | |
this.pos = new me.Vector2d(); | |
this.fontSize = new me.Vector2d(); | |
// font name and type | |
this.set(font, size, fillStyle, textAlign); | |
// parent constructor | |
this.parent(this.pos, 0, this.fontSize.y); | |
}, | |
/** | |
* make the font bold | |
* @name bold | |
* @memberOf me.Font | |
* @function | |
*/ | |
bold : function() { | |
this.font = "bold " + this.font; | |
}, | |
/** | |
* make the font italic | |
* @name italic | |
* @memberOf me.Font | |
* @function | |
*/ | |
italic : function() { | |
this.font = "italic " + this.font; | |
}, | |
/** | |
* Change the font settings | |
* @name set | |
* @memberOf me.Font | |
* @function | |
* @param {String} font a CSS font name | |
* @param {Number|String} size size, or size + suffix (px, em, pt) | |
* @param {String} fillStyle a CSS color value | |
* @param {String} [textAlign="left"] horizontal alignment | |
* @example | |
* font.set("Arial", 20, "white"); | |
* font.set("Arial", "1.5em", "white"); | |
*/ | |
set : function(font, size, fillStyle, textAlign) { | |
// font name and type | |
var font_names = font.split(",").map(function (value) { | |
value = value.trim(); | |
return ( | |
!/(^".*"$)|(^'.*'$)/.test(value) | |
) ? '"' + value + '"' : value; | |
}); | |
this.fontSize.y = parseInt(size, 10); | |
this.height = this.fontSize.y; | |
if (typeof size === "number") { | |
size += "px"; | |
} | |
this.font = size + " " + font_names.join(","); | |
this.fillStyle = fillStyle; | |
if (textAlign) { | |
this.textAlign = textAlign; | |
} | |
}, | |
/** | |
* measure the given text size in pixels | |
* @name measureText | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @return {Object} returns an object, with two attributes: width (the width of the text) and height (the height of the text). | |
*/ | |
measureText : function(context, text) { | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
this.height = this.width = 0; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
this.width = Math.max(context.measureText(strings[i].trimRight()).width, this.width); | |
this.height += this.fontSize.y * this.lineHeight; | |
} | |
return {width: this.width, height: this.height}; | |
}, | |
/** | |
* draw a text at the specified coord | |
* @name draw | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
draw : function(context, text, x, y) { | |
// update initial position | |
this.pos.set(x,y); | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
// draw the string | |
context.fillText(strings[i].trimRight(), ~~x, ~~y); | |
// add leading space | |
y += this.fontSize.y * this.lineHeight; | |
} | |
}, | |
/** | |
* draw a stroke text at the specified coord, as defined <br> | |
* by the `lineWidth` and `fillStroke` properties. <br> | |
* Note : using drawStroke is not recommended for performance reasons | |
* @name drawStroke | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
drawStroke : function(context, text, x, y) { | |
// update initial position | |
this.pos.set(x,y); | |
// save the context, as we are modifying | |
// too much parameter in this function | |
context.save(); | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.strokeStyle = this.strokeStyle; | |
context.lineWidth = this.lineWidth; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
var _string = strings[i].trimRight(); | |
// draw the border | |
context.strokeText(_string, ~~x, ~~y); | |
// draw the string | |
context.fillText(_string, ~~x, ~~y); | |
// add leading space | |
y += this.fontSize.y * this.lineHeight; | |
} | |
// restore the context | |
context.restore(); | |
} | |
}); | |
/** | |
* a bitpmap font object | |
* @class | |
* @extends me.Font | |
* @memberOf me | |
* @constructor | |
* @param {String} font | |
* @param {Number|Object} size either an int value, or an object like {x:16,y:16} | |
* @param {Number} [scale="1.0"] | |
* @param {String} [firstChar="0x20"] | |
*/ | |
me.BitmapFont = me.Font.extend( | |
/** @scope me.BitmapFont.prototype */ { | |
/** @ignore */ | |
// font scale; | |
sSize : null, | |
// first char in the ascii table | |
firstChar : 0x20, | |
// #char per row | |
charCount : 0, | |
/** @ignore */ | |
init : function(font, size, scale, firstChar) { | |
// font name and type | |
this.parent(font, null, null); | |
// font scaled size; | |
this.sSize = new me.Vector2d(); | |
// first char in the ascii table | |
this.firstChar = firstChar || 0x20; | |
// load the font metrics | |
this.loadFontMetrics(font, size); | |
// set a default alignement | |
this.textAlign = "left"; | |
this.textBaseline = "top"; | |
// resize if necessary | |
if (scale) { | |
this.resize(scale); | |
} | |
}, | |
/** | |
* Load the font metrics | |
* @ignore | |
*/ | |
loadFontMetrics : function(font, size) { | |
this.font = me.loader.getImage(font); | |
// some cheap metrics | |
this.fontSize.x = size.x || size; | |
this.fontSize.y = size.y || this.font.height; | |
this.sSize.copy(this.fontSize); | |
this.height = this.sSize.y; | |
// #char per row | |
this.charCount = ~~(this.font.width / this.fontSize.x); | |
}, | |
/** | |
* change the font settings | |
* @name set | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {String} textAlign ("left", "center", "right") | |
* @param {Number} [scale] | |
*/ | |
set : function(textAlign, scale) { | |
this.textAlign = textAlign; | |
// updated scaled Size | |
if (scale) { | |
this.resize(scale); | |
} | |
}, | |
/** | |
* change the font display size | |
* @name resize | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Number} scale ratio | |
*/ | |
resize : function(scale) { | |
// updated scaled Size | |
this.sSize.setV(this.fontSize); | |
this.sSize.x *= scale; | |
this.sSize.y *= scale; | |
this.height = this.sSize.y; | |
}, | |
/** | |
* measure the given text size in pixels | |
* @name measureText | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @return {Object} returns an object, with two attributes: width (the width of the text) and height (the height of the text). | |
*/ | |
measureText : function(context, text) { | |
var strings = (""+text).split("\n"); | |
this.height = this.width = 0; | |
for (var i = 0; i < strings.length; i++) { | |
this.width = Math.max((strings[i].trimRight().length * this.sSize.x), this.width); | |
this.height += this.sSize.y * this.lineHeight; | |
} | |
return {width: this.width, height: this.height}; | |
}, | |
/** | |
* draw a text at the specified coord | |
* @name draw | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
draw : function(context, text, x, y) { | |
var strings = (""+text).split("\n"); | |
var lX = x; | |
var height = this.sSize.y * this.lineHeight; | |
// update initial position | |
this.pos.set(x,y); | |
for (var i = 0; i < strings.length; i++) { | |
x = lX; | |
var string = strings[i].trimRight(); | |
// adjust x pos based on alignment value | |
var width = string.length * this.sSize.x; | |
switch(this.textAlign) { | |
case "right": | |
x -= width; | |
break; | |
case "center": | |
x -= width * 0.5; | |
break; | |
default : | |
break; | |
} | |
// adjust y pos based on alignment value | |
switch(this.textBaseline) { | |
case "middle": | |
y -= height * 0.5; | |
break; | |
case "ideographic": | |
case "alphabetic": | |
case "bottom": | |
y -= height; | |
break; | |
default : | |
break; | |
} | |
// draw the string | |
for ( var c = 0,len = string.length; c < len; c++) { | |
// calculate the char index | |
var idx = string.charCodeAt(c) - this.firstChar; | |
if (idx >= 0) { | |
// draw it | |
context.drawImage(this.font, | |
this.fontSize.x * (idx % this.charCount), | |
this.fontSize.y * ~~(idx / this.charCount), | |
this.fontSize.x, this.fontSize.y, | |
~~x, ~~y, | |
this.sSize.x, this.sSize.y); | |
} | |
x += this.sSize.x; | |
} | |
// increment line | |
y += height; | |
} | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Audio Mngt Objects | |
* | |
* | |
*/ | |
(function($) { | |
/** | |
* There is no constructor function for me.audio. | |
* @namespace me.audio | |
* @memberOf me | |
*/ | |
me.audio = (function() { | |
/* | |
* --------------------------------------------- | |
* PRIVATE STUFF | |
* --------------------------------------------- | |
*/ | |
// hold public stuff in our singleton | |
var obj = {}; | |
// audio channel list | |
var audio_channels = {}; | |
// Active (supported) audio extension | |
var activeAudioExt = -1; | |
// current music | |
var current_track_id = null; | |
var current_track = null; | |
// enable/disable flag | |
var sound_enable = true; | |
// defaut reset value | |
var reset_val = 0;// .01; | |
// a retry counter | |
var retry_counter = 0; | |
// global volume setting | |
var settings = { | |
volume : 1.0, | |
muted : false | |
}; | |
// synchronous loader for mobile user agents | |
var sync_loading = false; | |
var sync_loader = []; | |
/** | |
* return the first audio format extension supported by the browser | |
* @ignore | |
*/ | |
function getSupportedAudioFormat(requestedFormat) { | |
var result = ""; | |
var len = requestedFormat.length; | |
// check for sound support by the browser | |
if (me.device.sound) { | |
var ext = ""; | |
for (var i = 0; i < len; i++) { | |
ext = requestedFormat[i].toLowerCase().trim(); | |
// check extension against detected capabilities | |
if (obj.capabilities[ext] && | |
obj.capabilities[ext].canPlay && | |
// get only the first valid OR first 'probably' playable codec | |
(result === "" || obj.capabilities[ext].canPlayType === 'probably') | |
) { | |
result = ext; | |
if (obj.capabilities[ext].canPlayType === 'probably') { | |
break; | |
} | |
} | |
} | |
} | |
if (result === "") { | |
// deactivate sound | |
sound_enable = false; | |
} | |
return result; | |
} | |
/** | |
* return the specified sound | |
* @ignore | |
*/ | |
function get(sound_id) { | |
var channels = audio_channels[sound_id]; | |
// find which channel is available | |
for ( var i = 0, soundclip; soundclip = channels[i++];) { | |
if (soundclip.ended || !soundclip.currentTime)// soundclip.paused) | |
{ | |
// console.log ("requested %s on channel %d",sound_id, i); | |
soundclip.currentTime = reset_val; | |
return soundclip; | |
} | |
} | |
// else force on channel 0 | |
channels[0].pause(); | |
channels[0].currentTime = reset_val; | |
return channels[0]; | |
} | |
/** | |
* event listener callback on load error | |
* @ignore | |
*/ | |
function soundLoadError(sound_id, onerror_cb) { | |
// check the retry counter | |
if (retry_counter++ > 3) { | |
// something went wrong | |
var errmsg = "melonJS: failed loading " + sound_id + "." + activeAudioExt; | |
if (me.sys.stopOnAudioError===false) { | |
// disable audio | |
me.audio.disable(); | |
// call error callback if defined | |
if (onerror_cb) { | |
onerror_cb(); | |
} | |
// warning | |
console.log(errmsg + ", disabling audio"); | |
} else { | |
// throw an exception and stop everything ! | |
throw errmsg; | |
} | |
// else try loading again ! | |
} else { | |
audio_channels[sound_id][0].load(); | |
} | |
} | |
/** | |
* event listener callback when a sound is loaded | |
* @ignore | |
*/ | |
function soundLoaded(sound_id, sound_channel, onload_cb) { | |
// reset the retry counter | |
retry_counter = 0; | |
// create other "copy" channels if necessary | |
if (sound_channel > 1) { | |
var soundclip = audio_channels[sound_id][0]; | |
// clone copy to create multiple channel version | |
for (var channel = 1; channel < sound_channel; channel++) { | |
// allocate the new additional channels | |
audio_channels[sound_id][channel] = new Audio( soundclip.src ); | |
audio_channels[sound_id][channel].preload = 'auto'; | |
audio_channels[sound_id][channel].load(); | |
} | |
} | |
// callback if defined | |
if (onload_cb) { | |
onload_cb(); | |
} | |
} | |
/** | |
* play the specified sound | |
* @name play | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} | |
* sound_id audio clip id | |
* @param {Boolean} | |
* [loop=false] loop audio | |
* @param {Function} | |
* [callback] callback function | |
* @param {Number} | |
* [volume=default] Float specifying volume (0.0 - 1.0 values accepted). | |
* @example | |
* // play the "cling" audio clip | |
* me.audio.play("cling"); | |
* // play & repeat the "engine" audio clip | |
* me.audio.play("engine", true); | |
* // play the "gameover_sfx" audio clip and call myFunc when finished | |
* me.audio.play("gameover_sfx", false, myFunc); | |
* // play the "gameover_sfx" audio clip with a lower volume level | |
* me.audio.play("gameover_sfx", false, null, 0.5); | |
*/ | |
function _play_audio_enable(sound_id, loop, callback, volume) { | |
var soundclip = get(sound_id.toLowerCase()); | |
soundclip.loop = loop || false; | |
soundclip.volume = volume ? parseFloat(volume).clamp(0.0,1.0) : settings.volume; | |
soundclip.muted = settings.muted; | |
soundclip.play(); | |
// set a callback if defined | |
if (callback && !loop) { | |
soundclip.addEventListener('ended', function callbackFn(event) { | |
soundclip.removeEventListener('ended', callbackFn, | |
false); | |
// soundclip.pause(); | |
// soundclip.currentTime = reset_val; | |
// execute a callback if required | |
callback(); | |
}, false); | |
} | |
return soundclip; | |
} | |
/** | |
* play_audio with simulated callback | |
* @ignore | |
*/ | |
function _play_audio_disable(sound_id, loop, callback) { | |
// check if a callback need to be called | |
if (callback && !loop) { | |
// SoundMngr._play_cb = callback; | |
setTimeout(callback, 2000); // 2 sec as default timer ? | |
} | |
return null; | |
} | |
/* | |
*--------------------------------------------- | |
* PUBLIC STUFF | |
*--------------------------------------------- | |
*/ | |
// audio capabilities | |
obj.capabilities = { | |
mp3: { | |
codec: 'audio/mpeg', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
ogg: { | |
codec: 'audio/ogg; codecs="vorbis"', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
m4a: { | |
codec: 'audio/mp4; codecs="mp4a.40.2"', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
wav: { | |
codec: 'audio/wav; codecs="1"', | |
canPlay: false, | |
canPlayType: 'no' | |
} | |
}; | |
/** | |
* @ignore | |
*/ | |
obj.detectCapabilities = function () { | |
// init some audio variables | |
var a = document.createElement('audio'); | |
if (a.canPlayType) { | |
for (var c in obj.capabilities) { | |
var canPlayType = a.canPlayType(obj.capabilities[c].codec); | |
// convert the string to a boolean | |
if (canPlayType !== "" && canPlayType !== "no") { | |
obj.capabilities[c].canPlay = true; | |
obj.capabilities[c].canPlayType = canPlayType; | |
} | |
// enable sound if any of the audio format is supported | |
me.device.sound |= obj.capabilities[c].canPlay; | |
} | |
} | |
}; | |
/** | |
* initialize the audio engine<br> | |
* the melonJS loader will try to load audio files corresponding to the | |
* browser supported audio format<br> | |
* if no compatible audio codecs are found, audio will be disabled | |
* @name init | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} | |
* audioFormat audio format provided ("mp3, ogg, m4a, wav") | |
* @example | |
* // initialize the "sound engine", giving "mp3" and "ogg" as desired audio format | |
* // i.e. on Safari, the loader will load all audio.mp3 files, | |
* // on Opera the loader will however load audio.ogg files | |
* me.audio.init("mp3,ogg"); | |
*/ | |
obj.init = function(audioFormat) { | |
if (!me.initialized) { | |
throw "melonJS: me.audio.init() called before engine initialization."; | |
} | |
// if no param is given to init we use mp3 by default | |
audioFormat = typeof audioFormat === "string" ? audioFormat : "mp3"; | |
// convert it into an array | |
audioFormat = audioFormat.split(','); | |
// detect the prefered audio format | |
activeAudioExt = getSupportedAudioFormat(audioFormat); | |
// Disable audio on Mobile devices for now. (ARGH!) | |
if (me.device.isMobile && !navigator.isCocoonJS) { | |
sound_enable = false; | |
} | |
// enable/disable sound | |
obj.play = obj.isAudioEnable() ? _play_audio_enable : _play_audio_disable; | |
return obj.isAudioEnable(); | |
}; | |
/** | |
* return true if audio is enable | |
* | |
* @see me.audio#enable | |
* @name isAudioEnable | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @return {Boolean} | |
*/ | |
obj.isAudioEnable = function() { | |
return sound_enable; | |
}; | |
/** | |
* enable audio output <br> | |
* only useful if audio supported and previously disabled through | |
* audio.disable() | |
* | |
* @see me.audio#disable | |
* @name enable | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.enable = function() { | |
sound_enable = me.device.sound; | |
if (sound_enable) | |
obj.play = _play_audio_enable; | |
else | |
obj.play = _play_audio_disable; | |
}; | |
/** | |
* disable audio output | |
* | |
* @name disable | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.disable = function() { | |
// stop the current track | |
me.audio.stopTrack(); | |
// disable sound | |
obj.play = _play_audio_disable; | |
sound_enable = false; | |
}; | |
/** | |
* Load an audio file.<br> | |
* <br> | |
* sound item must contain the following fields :<br> | |
* - name : id of the sound<br> | |
* - src : source path<br> | |
* - channel : [Optional] number of channels to allocate<br> | |
* - stream : [Optional] boolean to enable streaming<br> | |
* @ignore | |
*/ | |
obj.load = function(sound, onload_cb, onerror_cb) { | |
// do nothing if no compatible format is found | |
if (activeAudioExt === -1) | |
return 0; | |
// check for specific platform | |
if (me.device.isMobile && !navigator.isCocoonJS) { | |
if (sync_loading) { | |
sync_loader.push([ sound, onload_cb, onerror_cb ]); | |
return; | |
} | |
sync_loading = true; | |
} | |
var channels = sound.channel || 1; | |
var eventname = "canplaythrough"; | |
if (sound.stream === true && !me.device.isMobile) { | |
channels = 1; | |
eventname = "canplay"; | |
} | |
var soundclip = new Audio(sound.src + sound.name + "." + activeAudioExt + me.loader.nocache); | |
soundclip.preload = 'auto'; | |
soundclip.addEventListener(eventname, function callbackFn(e) { | |
soundclip.removeEventListener(eventname, callbackFn, false); | |
sync_loading = false; | |
soundLoaded.call( | |
me.audio, | |
sound.name, | |
channels, | |
onload_cb | |
); | |
// Load next audio clip synchronously | |
var next = sync_loader.shift(); | |
if (next) { | |
obj.load.apply(obj, next); | |
} | |
}, false); | |
soundclip.addEventListener("error", function(e) { | |
soundLoadError.call(me.audio, sound.name, onerror_cb); | |
}, false); | |
// load it | |
soundclip.load(); | |
audio_channels[sound.name] = [ soundclip ]; | |
return 1; | |
}; | |
/** | |
* stop the specified sound on all channels | |
* | |
* @name stop | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
* @example | |
* me.audio.stop("cling"); | |
*/ | |
obj.stop = function(sound_id) { | |
if (sound_enable) { | |
var sound = audio_channels[sound_id.toLowerCase()]; | |
for (var channel_id = sound.length; channel_id--;) { | |
sound[channel_id].pause(); | |
// force rewind to beginning | |
sound[channel_id].currentTime = reset_val; | |
} | |
} | |
}; | |
/** | |
* pause the specified sound on all channels<br> | |
* this function does not reset the currentTime property | |
* | |
* @name pause | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
* @example | |
* me.audio.pause("cling"); | |
*/ | |
obj.pause = function(sound_id) { | |
if (sound_enable) { | |
var sound = audio_channels[sound_id.toLowerCase()]; | |
for (var channel_id = sound.length; channel_id--;) { | |
sound[channel_id].pause(); | |
} | |
} | |
}; | |
/** | |
* play the specified audio track<br> | |
* this function automatically set the loop property to true<br> | |
* and keep track of the current sound being played. | |
* | |
* @name playTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio track id | |
* @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted). | |
* @example | |
* me.audio.playTrack("awesome_music"); | |
*/ | |
obj.playTrack = function(sound_id, volume) { | |
current_track = me.audio.play(sound_id, true, null, volume); | |
current_track_id = sound_id.toLowerCase(); | |
}; | |
/** | |
* stop the current audio track | |
* | |
* @see me.audio#playTrack | |
* @name stopTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @example | |
* // play a awesome music | |
* me.audio.playTrack("awesome_music"); | |
* // stop the current music | |
* me.audio.stopTrack(); | |
*/ | |
obj.stopTrack = function() { | |
if (sound_enable && current_track) { | |
current_track.pause(); | |
current_track_id = null; | |
current_track = null; | |
} | |
}; | |
/** | |
* set the default global volume | |
* @name setVolume | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {Number} volume Float specifying volume (0.0 - 1.0 values accepted). | |
*/ | |
obj.setVolume = function(volume) { | |
if (typeof(volume) === "number") { | |
settings.volume = volume.clamp(0.0,1.0); | |
} | |
}; | |
/** | |
* get the default global volume | |
* @name getVolume | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @returns {Number} current volume value in Float [0.0 - 1.0] . | |
*/ | |
obj.getVolume = function() { | |
return settings.volume; | |
}; | |
/** | |
* mute the specified sound | |
* @name mute | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
*/ | |
obj.mute = function(sound_id, mute) { | |
// if not defined : true | |
mute = (mute === undefined)?true:!!mute; | |
var channels = audio_channels[sound_id.toLowerCase()]; | |
for ( var i = 0, soundclip; soundclip = channels[i++];) { | |
soundclip.muted = mute; | |
} | |
}; | |
/** | |
* unmute the specified sound | |
* @name unmute | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
*/ | |
obj.unmute = function(sound_id) { | |
obj.mute(sound_id, false); | |
}; | |
/** | |
* mute all audio | |
* @name muteAll | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.muteAll = function() { | |
settings.muted = true; | |
for (var sound_id in audio_channels) { | |
obj.mute(sound_id, settings.muted); | |
} | |
}; | |
/** | |
* unmute all audio | |
* @name unmuteAll | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.unmuteAll = function() { | |
settings.muted = false; | |
for (var sound_id in audio_channels) { | |
obj.mute(sound_id, settings.muted); | |
} | |
}; | |
/** | |
* returns the current track Id | |
* @name getCurrentTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @return {String} audio track id | |
*/ | |
obj.getCurrentTrack = function() { | |
return current_track_id; | |
}; | |
/** | |
* pause the current audio track | |
* | |
* @name pauseTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @example | |
* me.audio.pauseTrack(); | |
*/ | |
obj.pauseTrack = function() { | |
if (sound_enable && current_track) { | |
current_track.pause(); | |
} | |
}; | |
/** | |
* resume the previously paused audio track | |
* | |
* @name resumeTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio track id | |
* @example | |
* // play an awesome music | |
* me.audio.playTrack("awesome_music"); | |
* // pause the audio track | |
* me.audio.pauseTrack(); | |
* // resume the music | |
* me.audio.resumeTrack(); | |
*/ | |
obj.resumeTrack = function() { | |
if (sound_enable && current_track) { | |
current_track.play(); | |
} | |
}; | |
/** | |
* unload specified audio track to free memory | |
* | |
* @name unload | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio track id | |
* @return {Boolean} true if unloaded | |
* @example | |
* me.audio.unload("awesome_music"); | |
*/ | |
obj.unload = function(sound_id) { | |
sound_id = sound_id.toLowerCase(); | |
if (!(sound_id in audio_channels)) | |
return false; | |
if (current_track_id === sound_id) { | |
obj.stopTrack(); | |
} | |
else { | |
obj.stop(sound_id); | |
} | |
delete audio_channels[sound_id]; | |
return true; | |
}; | |
/** | |
* unload all audio to free memory | |
* | |
* @name unloadAll | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @example | |
* me.audio.unloadAll(); | |
*/ | |
obj.unloadAll = function() { | |
for (var sound_id in audio_channels) { | |
obj.unload(sound_id); | |
} | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* video functions | |
* There is no constructor function for me.video | |
* @namespace me.video | |
* @memberOf me | |
*/ | |
me.video = (function() { | |
// hold public stuff in our apig | |
var api = {}; | |
// internal variables | |
var canvas = null; | |
var context2D = null; | |
var backBufferCanvas = null; | |
var backBufferContext2D = null; | |
var wrapper = null; | |
var deferResizeId = -1; | |
var double_buffering = false; | |
var game_width_zoom = 0; | |
var game_height_zoom = 0; | |
var auto_scale = false; | |
var maintainAspectRatio = true; | |
// max display size | |
var maxWidth = Infinity; | |
var maxHeight = Infinity; | |
/** | |
* return a vendor specific canvas type | |
* @ignore | |
*/ | |
function getCanvasType() { | |
// cocoonJS specific canvas extension | |
if (navigator.isCocoonJS) { | |
return 'screencanvas'; | |
} | |
return 'canvas'; | |
} | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* init the "video" part<p> | |
* return false if initialization failed (canvas not supported) | |
* @name init | |
* @memberOf me.video | |
* @function | |
* @param {String} wrapper the "div" element id to hold the canvas in the HTML file (if null document.body will be used) | |
* @param {Number} width game width | |
* @param {Number} height game height | |
* @param {Boolean} [double_buffering] enable/disable double buffering | |
* @param {Number} [scale] enable scaling of the canvas ('auto' for automatic scaling) | |
* @param {Boolean} [maintainAspectRatio] maintainAspectRatio when scaling the display | |
* @return {Boolean} | |
* @example | |
* // init the video with a 480x320 canvas | |
* if (!me.video.init('jsapp', 480, 320)) { | |
* alert("Sorry but your browser does not support html 5 canvas !"); | |
* return; | |
* } | |
*/ | |
api.init = function(wrapperid, game_width, game_height, doublebuffering, scale, aspectRatio) { | |
// ensure melonjs has been properly initialized | |
if (!me.initialized) { | |
throw "melonJS: me.video.init() called before engine initialization."; | |
} | |
// check given parameters | |
double_buffering = doublebuffering || false; | |
auto_scale = (scale==='auto') || false; | |
maintainAspectRatio = (aspectRatio !== undefined) ? aspectRatio : true; | |
// normalize scale | |
scale = (scale!=='auto') ? parseFloat(scale || 1.0) : 1.0; | |
me.sys.scale = new me.Vector2d(scale, scale); | |
// force double buffering if scaling is required | |
if (auto_scale || (scale !== 1.0)) { | |
double_buffering = true; | |
} | |
// default scaled size value | |
game_width_zoom = game_width * me.sys.scale.x; | |
game_height_zoom = game_height * me.sys.scale.y; | |
//add a channel for the onresize/onorientationchange event | |
window.addEventListener('resize', throttle(100, false, function (event) {me.event.publish(me.event.WINDOW_ONRESIZE, [event]);}), false); | |
window.addEventListener('orientationchange', function (event) {me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE, [event]);}, false); | |
// register to the channel | |
me.event.subscribe(me.event.WINDOW_ONRESIZE, me.video.onresize.bind(me.video)); | |
me.event.subscribe(me.event.WINDOW_ONORIENTATION_CHANGE, me.video.onresize.bind(me.video)); | |
// create the main canvas | |
canvas = api.createCanvas(game_width_zoom, game_height_zoom, true); | |
// add our canvas | |
if (wrapperid) { | |
wrapper = document.getElementById(wrapperid); | |
} | |
// if wrapperid is not defined (null) | |
if (!wrapper) { | |
// add the canvas to document.body | |
wrapper = document.body; | |
} | |
wrapper.appendChild(canvas); | |
// stop here if not supported | |
if (!canvas.getContext) | |
return false; | |
// get the 2D context | |
context2D = api.getContext2d(canvas); | |
// adjust CSS style for High-DPI devices | |
if (me.device.getPixelRatio()>1) { | |
canvas.style.width = (canvas.width / me.device.getPixelRatio()) + 'px'; | |
canvas.style.height = (canvas.height / me.device.getPixelRatio()) + 'px'; | |
} | |
// create the back buffer if we use double buffering | |
if (double_buffering) { | |
backBufferCanvas = api.createCanvas(game_width, game_height, false); | |
backBufferContext2D = api.getContext2d(backBufferCanvas); | |
} else { | |
backBufferCanvas = canvas; | |
backBufferContext2D = context2D; | |
} | |
// set max the canvas max size if CSS values are defined | |
if (window.getComputedStyle) { | |
var style = window.getComputedStyle(canvas, null); | |
me.video.setMaxSize(parseInt(style.maxWidth, 10), parseInt(style.maxHeight, 10)); | |
} | |
// trigger an initial resize(); | |
me.video.onresize(null); | |
me.game.init(); | |
return true; | |
}; | |
/** | |
* return a reference to the wrapper | |
* @name getWrapper | |
* @memberOf me.video | |
* @function | |
* @return {Document} | |
*/ | |
api.getWrapper = function() { | |
return wrapper; | |
}; | |
/** | |
* return the width of the display canvas (before scaling) | |
* @name getWidth | |
* @memberOf me.video | |
* @function | |
* @return {Number} | |
*/ | |
api.getWidth = function() { | |
return backBufferCanvas.width; | |
}; | |
/** | |
* return the relative (to the page) position of the specified Canvas | |
* @name getPos | |
* @memberOf me.video | |
* @function | |
* @param {Canvas} [canvas] system one if none specified | |
* @return {me.Vector2d} | |
*/ | |
api.getPos = function(c) { | |
c = c || canvas; | |
return c.getBoundingClientRect?c.getBoundingClientRect():{left:0,top:0}; | |
}; | |
/** | |
* return the height of the display canvas (before scaling) | |
* @name getHeight | |
* @memberOf me.video | |
* @function | |
* @return {Number} | |
*/ | |
api.getHeight = function() { | |
return backBufferCanvas.height; | |
}; | |
/** | |
* set the max canvas display size (when scaling) | |
* @name setMaxSize | |
* @memberOf me.video | |
* @function | |
* @param {Number} width width | |
* @param {Number} height height | |
*/ | |
api.setMaxSize = function(w, h) { | |
// max display size | |
maxWidth = w || Infinity; | |
maxHeight = h || Infinity; | |
}; | |
/** | |
* Create and return a new Canvas | |
* @name createCanvas | |
* @memberOf me.video | |
* @function | |
* @param {Number} width width | |
* @param {Number} height height | |
* @return {Canvas} | |
*/ | |
api.createCanvas = function(width, height, vendorExt) { | |
if (width === 0 || height === 0) { | |
throw new Error("melonJS: width or height was zero, Canvas could not be initialized !"); | |
} | |
var canvasType = (vendorExt === true) ? getCanvasType() : 'canvas'; | |
var _canvas = document.createElement(canvasType); | |
_canvas.width = width || backBufferCanvas.width; | |
_canvas.height = height || backBufferCanvas.height; | |
return _canvas; | |
}; | |
/** | |
* Returns the 2D Context object of the given Canvas | |
* `getContext2d` will also enable/disable antialiasing features based on global settings. | |
* @name getContext2D | |
* @memberOf me.video | |
* @function | |
* @param {Canvas} | |
* @return {Context2d} | |
*/ | |
api.getContext2d = function(canvas) { | |
var _context; | |
if (navigator.isCocoonJS) { | |
// cocoonJS specific extension | |
_context = canvas.getContext('2d', { "antialias" : me.sys.scalingInterpolation }); | |
} else { | |
_context = canvas.getContext('2d'); | |
} | |
if (!_context.canvas) { | |
_context.canvas = canvas; | |
} | |
me.video.setImageSmoothing(_context, me.sys.scalingInterpolation); | |
return _context; | |
}; | |
/** | |
* return a reference to the screen canvas <br> | |
* (will return buffered canvas if double buffering is enabled, or a reference to Screen Canvas) <br> | |
* use this when checking for display size, event <br> | |
* or if you need to apply any special "effect" to <br> | |
* the corresponding context (ie. imageSmoothingEnabled) | |
* @name getScreenCanvas | |
* @memberOf me.video | |
* @function | |
* @return {Canvas} | |
*/ | |
api.getScreenCanvas = function() { | |
return canvas; | |
}; | |
/** | |
* return a reference to the screen canvas corresponding 2d Context<br> | |
* (will return buffered context if double buffering is enabled, or a reference to the Screen Context) | |
* @name getScreenContext | |
* @memberOf me.video | |
* @function | |
* @return {Context2d} | |
*/ | |
api.getScreenContext = function() { | |
return context2D; | |
}; | |
/** | |
* return a reference to the system canvas | |
* @name getSystemCanvas | |
* @memberOf me.video | |
* @function | |
* @return {Canvas} | |
*/ | |
api.getSystemCanvas = function() { | |
return backBufferCanvas; | |
}; | |
/** | |
* return a reference to the system 2d Context | |
* @name getSystemContext | |
* @memberOf me.video | |
* @function | |
* @return {Context2d} | |
*/ | |
api.getSystemContext = function() { | |
return backBufferContext2D; | |
}; | |
/** | |
* callback for window resize event | |
* @ignore | |
*/ | |
api.onresize = function(event){ | |
// default (no scaling) | |
var scaleX = 1, scaleY = 1; | |
// check for orientation information | |
if (typeof window.orientation !== 'undefined') { | |
me.device.orientation = window.orientation; | |
} else { | |
// is this actually not the best option since default "portrait" | |
// orientation might vary between for example an ipad and and android tab | |
me.device.orientation = (window.outerWidth > window.outerHeight) ? 90 : 0; | |
} | |
if (auto_scale) { | |
// get the parent container max size | |
var parent = me.video.getScreenCanvas().parentNode; | |
var _max_width = Math.min(maxWidth, parent.width || window.innerWidth); | |
var _max_height = Math.min(maxHeight, parent.height || window.innerHeight); | |
if (maintainAspectRatio) { | |
// make sure we maintain the original aspect ratio | |
var designRatio = me.video.getWidth() / me.video.getHeight(); | |
var screenRatio = _max_width / _max_height; | |
if (screenRatio < designRatio) | |
scaleX = scaleY = _max_width / me.video.getWidth(); | |
else | |
scaleX = scaleY = _max_height / me.video.getHeight(); | |
} else { | |
// scale the display canvas to fit with the parent container | |
scaleX = _max_width / me.video.getWidth(); | |
scaleY = _max_height / me.video.getHeight(); | |
} | |
// adjust scaling ratio based on the device pixel ratio | |
scaleX *= me.device.getPixelRatio(); | |
scaleY *= me.device.getPixelRatio(); | |
// scale if required | |
if (scaleX!==1 || scaleY !==1) { | |
if (deferResizeId >= 0) { | |
// cancel any previous pending resize | |
clearTimeout(deferResizeId); | |
} | |
deferResizeId = me.video.updateDisplaySize.defer(scaleX , scaleY); | |
return; | |
} | |
} | |
// make sure we have the correct relative canvas position cached | |
me.input.offset = me.video.getPos(); | |
}; | |
/** | |
* Modify the "displayed" canvas size | |
* @name updateDisplaySize | |
* @memberOf me.video | |
* @function | |
* @param {Number} scaleX X scaling multiplier | |
* @param {Number} scaleY Y scaling multiplier | |
*/ | |
api.updateDisplaySize = function(scaleX, scaleY) { | |
// update the global scale variable | |
me.sys.scale.set(scaleX,scaleY); | |
// apply the new value | |
canvas.width = game_width_zoom = backBufferCanvas.width * scaleX; | |
canvas.height = game_height_zoom = backBufferCanvas.height * scaleY; | |
// adjust CSS style for High-DPI devices | |
if (me.device.getPixelRatio()>1) { | |
canvas.style.width = (canvas.width / me.device.getPixelRatio()) + 'px'; | |
canvas.style.height = (canvas.height / me.device.getPixelRatio()) + 'px'; | |
} | |
me.video.setImageSmoothing(context2D, me.sys.scalingInterpolation); | |
// make sure we have the correct relative canvas position cached | |
me.input.offset = me.video.getPos(); | |
// force a canvas repaint | |
api.blitSurface(); | |
// clear the timeout id | |
deferResizeId = -1; | |
}; | |
/** | |
* Clear the specified context with the given color | |
* @name clearSurface | |
* @memberOf me.video | |
* @function | |
* @param {Context2d} context Canvas context | |
* @param {String} color a CSS color string | |
*/ | |
api.clearSurface = function(context, col) { | |
var w = context.canvas.width; | |
var h = context.canvas.height; | |
context.save(); | |
context.setTransform(1, 0, 0, 1, 0, 0); | |
if (col.substr(0, 4) === "rgba") { | |
context.clearRect(0, 0, w, h); | |
} | |
context.fillStyle = col; | |
context.fillRect(0, 0, w, h); | |
context.restore(); | |
}; | |
/** | |
* enable/disable image smoothing (scaling interpolation) for the specified 2d Context<br> | |
* (!) this might not be supported by all browsers <br> | |
* @name setImageSmoothing | |
* @memberOf me.video | |
* @function | |
* @param {Context2d} context | |
* @param {Boolean} [enable=false] | |
*/ | |
api.setImageSmoothing = function(context, enable) { | |
// a quick polyfill for the `imageSmoothingEnabled` property | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
for(var x = 0; x < vendors.length; ++x) { | |
if (context[vendors[x]+'ImageSmoothingEnabled'] !== undefined) { | |
context[vendors[x]+'ImageSmoothingEnabled'] = (enable===true); | |
} | |
} | |
// generic one (if implemented) | |
context.imageSmoothingEnabled = (enable===true); | |
}; | |
/** | |
* enable/disable Alpha for the specified context | |
* @name setAlpha | |
* @memberOf me.video | |
* @function | |
* @param {Context2d} context | |
* @param {Boolean} enable | |
*/ | |
api.setAlpha = function(context, enable) { | |
context.globalCompositeOperation = enable ? "source-over" : "copy"; | |
}; | |
/** | |
* render the main framebuffer on screen | |
* @name blitSurface | |
* @memberOf me.video | |
* @function | |
*/ | |
api.blitSurface = function() { | |
if (double_buffering) { | |
/** @ignore */ | |
api.blitSurface = function() { | |
//FPS.update(); | |
context2D.drawImage(backBufferCanvas, 0, 0, | |
backBufferCanvas.width, backBufferCanvas.height, 0, | |
0, game_width_zoom, game_height_zoom); | |
}; | |
} else { | |
// "empty" function, as we directly render stuff on "context2D" | |
/** @ignore */ | |
api.blitSurface = function() { | |
}; | |
} | |
api.blitSurface(); | |
}; | |
/** | |
* apply the specified filter to the main canvas | |
* and return a new canvas object with the modified output<br> | |
* (!) Due to the internal usage of getImageData to manipulate pixels, | |
* this function will throw a Security Exception with FF if used locally | |
* @name applyRGBFilter | |
* @memberOf me.video | |
* @function | |
* @param {Object} object Canvas or Image Object on which to apply the filter | |
* @param {String} effect "b&w", "brightness", "transparent" | |
* @param {String} option For "brightness" effect : level [0...1] <br> For "transparent" effect : color to be replaced in "#RRGGBB" format | |
* @return {Context2d} context object | |
*/ | |
api.applyRGBFilter = function(object, effect, option) { | |
//create a output canvas using the given canvas or image size | |
var _context = api.getContext2d(api.createCanvas(object.width, object.height, false)); | |
// get the pixels array of the give parameter | |
var imgpix = me.utils.getPixels(object); | |
// pointer to the pixels data | |
var pix = imgpix.data; | |
// apply selected effect | |
var i, n; | |
switch (effect) { | |
case "b&w": | |
for (i = 0, n = pix.length; i < n; i += 4) { | |
var grayscale = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) >>> 3; | |
pix[i] = grayscale; // red | |
pix[i + 1] = grayscale; // green | |
pix[i + 2] = grayscale; // blue | |
} | |
break; | |
case "brightness": | |
// make sure it's between 0.0 and 1.0 | |
var brightness = Math.abs(option).clamp(0.0, 1.0); | |
for (i = 0, n = pix.length; i < n; i += 4) { | |
pix[i] *= brightness; // red | |
pix[i + 1] *= brightness; // green | |
pix[i + 2] *= brightness; // blue | |
} | |
break; | |
case "transparent": | |
for (i = 0, n = pix.length; i < n; i += 4) { | |
if (me.utils.RGBToHex(pix[i], pix[i + 1], pix[i + 2]) === option) { | |
pix[i + 3] = 0; | |
} | |
} | |
break; | |
default: | |
return null; | |
} | |
// put our modified image back in the new filtered canvas | |
_context.putImageData(imgpix, 0, 0); | |
// return it | |
return _context; | |
}; | |
// return our api | |
return api; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* The built in Event Object | |
* @external Event | |
* @see {@link https://developer.mozilla.org/en/docs/Web/API/Event|Event} | |
*/ | |
/** | |
* Event normalized X coordinate within the game canvas itself<br> | |
* <img src="images/event_coord.png"/> | |
* @memberof! external:Event# | |
* @name external:Event#gameX | |
* @type {Number} | |
*/ | |
/** | |
* Event normalized Y coordinate within the game canvas itself<br> | |
* <img src="images/event_coord.png"/> | |
* @memberof! external:Event# | |
* @name external:Event#gameY | |
* @type {Number} | |
*/ | |
/** | |
* Event X coordinate relative to the viewport<br> | |
* @memberof! external:Event# | |
* @name external:Event#gameScreenX | |
* @type {Number} | |
*/ | |
/** | |
* Event Y coordinate relative to the viewport<br> | |
* @memberof! external:Event# | |
* @name external:Event#gameScreenY | |
* @type {Number} | |
*/ | |
/** | |
* Event X coordinate relative to the map<br> | |
* @memberof! external:Event# | |
* @name external:Event#gameWorldX | |
* @type {Number} | |
*/ | |
/** | |
* Event Y coordinate relative to the map<br> | |
* @memberof! external:Event# | |
* @name external:Event#gameWorldY | |
* @type {Number} | |
*/ | |
/** | |
* The unique identifier of the contact for a touch, mouse or pen <br> | |
* (This id is also defined on non Pointer Event Compatible platform like pure mouse or iOS-like touch event) | |
* @memberof! external:Event# | |
* @name external:Event#pointerId | |
* @type {Number} | |
* @see http://msdn.microsoft.com/en-us/library/windows/apps/hh466123.aspx | |
*/ | |
/** | |
* There is no constructor function for me.input. | |
* @namespace me.input | |
* @memberOf me | |
*/ | |
me.input = (function() { | |
// hold public stuff in our singleton | |
var obj = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
// list of binded keys | |
var KeyBinding = {}; | |
// corresponding actions | |
var keyStatus = {}; | |
// lock enable flag for keys | |
var keyLock = {}; | |
// actual lock status of each key | |
var keyLocked = {}; | |
// List of binded keys being held | |
var keyRefs = {}; | |
// list of registered Event handlers | |
var evtHandlers = {}; | |
// some usefull flags | |
var keyboardInitialized = false; | |
var pointerInitialized = false; | |
// to keep track of the supported wheel event | |
var wheeltype = 'mousewheel'; | |
// Track last event timestamp to prevent firing events out of order | |
var lastTimeStamp = 0; | |
// list of supported mouse & touch events | |
var activeEventList = null; | |
var mouseEventList = ['mousewheel', 'mousemove', 'mousedown', 'mouseup', undefined, 'click', 'dblclick']; | |
var touchEventList = [undefined, 'touchmove', 'touchstart', 'touchend', 'touchcancel', 'tap', 'dbltap']; | |
// (a polyfill will probably be required at some stage, once this will be fully standardized | |
var pointerEventList = ['mousewheel', 'pointermove', 'pointerdown', 'pointerup', 'pointercancel', undefined, undefined ]; | |
var MSPointerEventList = ['mousewheel', 'MSPointerMove', 'MSPointerDown', 'MSPointerUp', 'MSPointerCancel', undefined, undefined ]; | |
// internal constants | |
var MOUSE_WHEEL = 0; | |
var POINTER_MOVE = 1; | |
var POINTER_DOWN = 2; | |
var POINTER_UP = 3; | |
var POINTER_CANCEL = 4; | |
/** | |
* enable keyboard event | |
* @ignore | |
*/ | |
function enableKeyboardEvent() { | |
if (!keyboardInitialized) { | |
window.addEventListener('keydown', keydown, false); | |
window.addEventListener('keyup', keyup, false); | |
keyboardInitialized = true; | |
} | |
} | |
/** | |
* addEventListerner for the specified event list and callback | |
* @private | |
*/ | |
function registerEventListener(eventList, callback) { | |
for (var x = 2; x < eventList.length; ++x) { | |
if (eventList[x] !== undefined) { | |
me.video.getScreenCanvas().addEventListener(eventList[x], callback, false); | |
} | |
} | |
} | |
/** | |
* enable pointer event (MSPointer/Mouse/Touch) | |
* @ignore | |
*/ | |
function enablePointerEvent() { | |
if (!pointerInitialized) { | |
// initialize mouse pos (0,0) | |
obj.changedTouches.push({ x: 0, y: 0 }); | |
obj.mouse.pos = new me.Vector2d(0,0); | |
// get relative canvas position in the page | |
obj.offset = me.video.getPos(); | |
// Automatically update relative canvas position on scroll | |
window.addEventListener("scroll", throttle(100, false, | |
function (e) { | |
obj.offset = me.video.getPos(); | |
me.event.publish(me.event.WINDOW_ONSCROLL, [ e ]); | |
} | |
), false); | |
// check standard | |
if(window.navigator.pointerEnabled) { | |
activeEventList = pointerEventList; | |
} | |
else if(window.navigator.msPointerEnabled) { // check for backward compatibility with the 'MS' prefix | |
activeEventList = MSPointerEventList; | |
} | |
else if (me.device.touch) { // `touch****` events for iOS/Android devices | |
activeEventList = touchEventList; | |
} | |
else { // Regular Mouse events | |
activeEventList = mouseEventList; | |
} | |
registerEventListener(activeEventList, onPointerEvent); | |
// detect wheel event support | |
// Modern browsers support "wheel", Webkit and IE support at least "mousewheel | |
wheeltype = "onwheel" in document.createElement("div") ? "wheel" : "mousewheel"; | |
window.addEventListener(wheeltype, onMouseWheel, false); | |
// set the PointerMove/touchMove/MouseMove event | |
if (obj.throttlingInterval === undefined) { | |
// set the default value | |
obj.throttlingInterval = Math.floor(1000/me.sys.fps); | |
} | |
// if time interval <= 16, disable the feature | |
if (obj.throttlingInterval < 17) { | |
me.video.getScreenCanvas().addEventListener(activeEventList[POINTER_MOVE], onMoveEvent, false); | |
} | |
else { | |
me.video.getScreenCanvas().addEventListener(activeEventList[POINTER_MOVE], throttle(obj.throttlingInterval, false, function(e){onMoveEvent(e);}), false); | |
} | |
pointerInitialized = true; | |
} | |
} | |
/** | |
* prevent event propagation | |
* @ignore | |
*/ | |
function preventDefault(e) { | |
// stop event propagation | |
if (e.stopPropagation) { | |
e.stopPropagation(); | |
} | |
else { | |
e.cancelBubble = true; | |
} | |
// stop event default processing | |
if (e.preventDefault) { | |
e.preventDefault(); | |
} | |
else { | |
e.returnValue = false; | |
} | |
return false; | |
} | |
/** | |
* key down event | |
* @ignore | |
*/ | |
function keydown(e, keyCode, mouseButton) { | |
keyCode = keyCode || e.keyCode || e.which; | |
var action = KeyBinding[keyCode]; | |
// publish a message for keydown event | |
me.event.publish(me.event.KEYDOWN, [ | |
action, | |
keyCode, | |
action ? !keyLocked[action] : true | |
]); | |
if (action) { | |
if (!keyLocked[action]) { | |
var trigger = mouseButton ? mouseButton : keyCode; | |
if (!keyRefs[action][trigger]) { | |
keyStatus[action]++; | |
keyRefs[action][trigger] = true; | |
} | |
} | |
// prevent event propagation | |
return preventDefault(e); | |
} | |
return true; | |
} | |
/** | |
* key up event | |
* @ignore | |
*/ | |
function keyup(e, keyCode, mouseButton) { | |
keyCode = keyCode || e.keyCode || e.which; | |
var action = KeyBinding[keyCode]; | |
// publish a message for keydown event | |
me.event.publish(me.event.KEYUP, [ action, keyCode ]); | |
if (action) { | |
var trigger = mouseButton ? mouseButton : keyCode; | |
keyRefs[action][trigger] = undefined; | |
if (keyStatus[action] > 0) | |
keyStatus[action]--; | |
keyLocked[action] = false; | |
// prevent the event propagation | |
return preventDefault(e); | |
} | |
return true; | |
} | |
/** | |
* propagate events to registered objects | |
* @ignore | |
*/ | |
function dispatchEvent(e) { | |
var handled = false; | |
var handlers = evtHandlers[e.type]; | |
// Convert touchcancel -> touchend, and pointercancel -> pointerup | |
if (!handlers) { | |
if (activeEventList.indexOf(e.type) === POINTER_CANCEL) { | |
handlers = evtHandlers[activeEventList[POINTER_UP]]; | |
} else { | |
handlers = evtHandlers[e.type]; | |
} | |
} | |
if (handlers) { | |
// get the current screen to world offset | |
var offset = me.game.viewport.localToWorld(0,0); | |
for(var t=0, l=obj.changedTouches.length; t<l; t++) { | |
// Do not fire older events | |
if (typeof(e.timeStamp) !== "undefined") { | |
if (e.timeStamp < lastTimeStamp) continue; | |
lastTimeStamp = e.timeStamp; | |
} | |
// if PointerEvent is not supported | |
if (!me.device.pointerEnabled) { | |
// -> define pointerId to simulate the PointerEvent standard | |
e.pointerId = obj.changedTouches[t].id; | |
} | |
/* Initialize the two coordinate space properties. */ | |
e.gameScreenX = obj.changedTouches[t].x; | |
e.gameScreenY = obj.changedTouches[t].y; | |
e.gameWorldX = e.gameScreenX + offset.x; | |
e.gameWorldY = e.gameScreenY + offset.y; | |
// parse all handlers | |
for (var i = handlers.length, handler; i--, handler = handlers[i];) { | |
/* Set gameX and gameY depending on floating. */ | |
if (handler.floating === true) { | |
e.gameX = e.gameScreenX; | |
e.gameY = e.gameScreenY; | |
} else { | |
e.gameX = e.gameWorldX; | |
e.gameY = e.gameWorldY; | |
} | |
// call the defined handler | |
if ((handler.rect === null) || handler.rect.containsPoint(e.gameX, e.gameY)) { | |
// trigger the corresponding callback | |
if (handler.cb(e) === false) { | |
// stop propagating the event if return false | |
handled = true; | |
break; | |
} | |
} | |
} | |
} | |
} | |
return handled; | |
} | |
/** | |
* translate event coordinates | |
* @ignore | |
*/ | |
function updateCoordFromEvent(e) { | |
var local; | |
// reset the touch array cache | |
obj.changedTouches.length=0; | |
// PointerEvent or standard Mouse event | |
if (!e.touches) { | |
local = obj.globalToLocal(e.clientX, e.clientY); | |
local.id = e.pointerId || 1; | |
obj.changedTouches.push(local); | |
} | |
// iOS/Android like touch event | |
else { | |
for(var i=0, l=e.changedTouches.length; i<l; i++) { | |
var t = e.changedTouches[i]; | |
local = obj.globalToLocal(t.clientX, t.clientY); | |
local.id = t.identifier; | |
obj.changedTouches.push(local); | |
} | |
} | |
// if event.isPrimary is defined and false, return | |
if (e.isPrimary === false) { | |
return; | |
} | |
// Else use the first entry to simulate mouse event | |
obj.mouse.pos.set(obj.changedTouches[0].x,obj.changedTouches[0].y); | |
} | |
/** | |
* mouse event management (mousewheel) | |
* @ignore | |
*/ | |
function onMouseWheel(e) { | |
/* jshint expr:true */ | |
if (e.target === me.video.getScreenCanvas()) { | |
// create a (fake) normalized event object | |
var _event = { | |
deltaMode : 1, | |
type : "mousewheel", | |
deltaX: e.deltaX, | |
deltaY: e.deltaY, | |
deltaZ: e.deltaZ | |
}; | |
if ( wheeltype === "mousewheel" ) { | |
_event.deltaY = - 1/40 * e.wheelDelta; | |
// Webkit also support wheelDeltaX | |
e.wheelDeltaX && ( _event.deltaX = - 1/40 * e.wheelDeltaX ); | |
} | |
// dispatch mouse event to registered object | |
if (dispatchEvent(_event)) { | |
// prevent default action | |
return preventDefault(e); | |
} | |
} | |
return true; | |
} | |
/** | |
* mouse/touch/pointer event management (move) | |
* @ignore | |
*/ | |
function onMoveEvent(e) { | |
// update position | |
updateCoordFromEvent(e); | |
// dispatch mouse event to registered object | |
if (dispatchEvent(e)) { | |
// prevent default action | |
return preventDefault(e); | |
} | |
return true; | |
} | |
/** | |
* mouse/touch/pointer event management (start/down, end/up) | |
* @ignore | |
*/ | |
function onPointerEvent(e) { | |
// update the pointer position | |
updateCoordFromEvent(e); | |
// dispatch event to registered objects | |
if (dispatchEvent(e)) { | |
// prevent default action | |
return preventDefault(e); | |
} | |
// in case of touch event button is undefined | |
var button = e.button || 0; | |
var keycode = obj.mouse.bind[button]; | |
// check if mapped to a key | |
if (keycode) { | |
if (e.type === activeEventList[POINTER_DOWN]) | |
return keydown(e, keycode, button + 1); | |
else // 'mouseup' or 'touchend' | |
return keyup(e, keycode, button + 1); | |
} | |
return true; | |
} | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* Mouse information<br> | |
* properties : <br> | |
* pos (me.Vector2d) : pointer position (in screen coordinates) <br> | |
* LEFT : constant for left button <br> | |
* MIDDLE : constant for middle button <br> | |
* RIGHT : constant for right button <br> | |
* @public | |
* @enum {number} | |
* @name mouse | |
* @memberOf me.input | |
*/ | |
obj.mouse = { | |
// mouse position | |
pos : null, | |
// button constants (W3C) | |
LEFT: 0, | |
MIDDLE: 1, | |
RIGHT: 2, | |
// bind list for mouse buttons | |
bind: [ 0, 0, 0 ] | |
}; | |
/** | |
* cache value for the offset of the canvas position within the page | |
* @private | |
*/ | |
obj.offset = null; | |
/** | |
* time interval for event throttling in milliseconds<br> | |
* default value : "1000/me.sys.fps" ms<br> | |
* set to 0 ms to disable the feature | |
* @public | |
* @type Number | |
* @name throttlingInterval | |
* @memberOf me.input | |
*/ | |
obj.throttlingInterval = undefined; | |
/** | |
* Array of object containing changed touch information (iOS event model)<br> | |
* properties : <br> | |
* x : x position of the touch event in the canvas (screen coordinates)<br> | |
* y : y position of the touch event in the canvas (screen coordinates)<br> | |
* id : unique finger identifier<br> | |
* @public | |
* @type Array | |
* @name touches | |
* @memberOf me.input | |
*/ | |
obj.changedTouches = []; | |
/** | |
* list of mappable keys : | |
* LEFT, UP, RIGHT, DOWN, ENTER, SHIFT, CTRL, ALT, PAUSE, ESC, ESCAPE, [0..9], [A..Z] | |
* @public | |
* @enum {number} | |
* @name KEY | |
* @memberOf me.input | |
*/ | |
obj.KEY = { | |
'LEFT' : 37, | |
'UP' : 38, | |
'RIGHT' : 39, | |
'DOWN' : 40, | |
'ENTER' : 13, | |
'TAB' : 9, | |
'SHIFT' : 16, | |
'CTRL' : 17, | |
'ALT' : 18, | |
'PAUSE' : 19, | |
'ESC' : 27, | |
'SPACE' : 32, | |
'NUM0' : 48, | |
'NUM1' : 49, | |
'NUM2' : 50, | |
'NUM3' : 51, | |
'NUM4' : 52, | |
'NUM5' : 53, | |
'NUM6' : 54, | |
'NUM7' : 55, | |
'NUM8' : 56, | |
'NUM9' : 57, | |
'A' : 65, | |
'B' : 66, | |
'C' : 67, | |
'D' : 68, | |
'E' : 69, | |
'F' : 70, | |
'G' : 71, | |
'H' : 72, | |
'I' : 73, | |
'J' : 74, | |
'K' : 75, | |
'L' : 76, | |
'M' : 77, | |
'N' : 78, | |
'O' : 79, | |
'P' : 80, | |
'Q' : 81, | |
'R' : 82, | |
'S' : 83, | |
'T' : 84, | |
'U' : 85, | |
'V' : 86, | |
'W' : 87, | |
'X' : 88, | |
'Y' : 89, | |
'Z' : 90 | |
}; | |
/** | |
* return the key press status of the specified action | |
* @name isKeyPressed | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {String} action user defined corresponding action | |
* @return {Boolean} true if pressed | |
* @example | |
* if (me.input.isKeyPressed('left')) | |
* { | |
* //do something | |
* } | |
* else if (me.input.isKeyPressed('right')) | |
* { | |
* //do something else... | |
* } | |
* | |
*/ | |
obj.isKeyPressed = function(action) { | |
if (keyStatus[action] && !keyLocked[action]) { | |
if (keyLock[action]) { | |
keyLocked[action] = true; | |
} | |
return true; | |
} | |
return false; | |
}; | |
/** | |
* return the key status of the specified action | |
* @name keyStatus | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {String} action user defined corresponding action | |
* @return {Boolean} down (true) or up(false) | |
*/ | |
obj.keyStatus = function(action) { | |
return (keyStatus[action] > 0); | |
}; | |
/** | |
* trigger the specified key (simulated) event <br> | |
* @name triggerKeyEvent | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {me.input#KEY} keycode | |
* @param {Boolean} true to trigger a key press, or false for key release | |
* @example | |
* // trigger a key press | |
* me.input.triggerKeyEvent(me.input.KEY.LEFT, true); | |
*/ | |
obj.triggerKeyEvent = function(keycode, status) { | |
if (status) { | |
keydown({}, keycode); | |
} | |
else { | |
keyup({}, keycode); | |
} | |
}; | |
/** | |
* associate a user defined action to a keycode | |
* @name bindKey | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {me.input#KEY} keycode | |
* @param {String} action user defined corresponding action | |
* @param {Boolean} lock cancel the keypress event once read | |
* @example | |
* // enable the keyboard | |
* me.input.bindKey(me.input.KEY.LEFT, "left"); | |
* me.input.bindKey(me.input.KEY.RIGHT, "right"); | |
* me.input.bindKey(me.input.KEY.X, "jump", true); | |
*/ | |
obj.bindKey = function(keycode, action, lock) { | |
// make sure the keyboard is enable | |
enableKeyboardEvent(); | |
KeyBinding[keycode] = action; | |
keyStatus[action] = 0; | |
keyLock[action] = lock ? lock : false; | |
keyLocked[action] = false; | |
keyRefs[action] = {}; | |
}; | |
/** | |
* unlock a key manually | |
* @name unlockKey | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {String} action user defined corresponding action | |
* @example | |
* // Unlock jump when touching the ground | |
* if(!this.falling && !this.jumping) { | |
* me.input.unlockKey("jump"); | |
* } | |
*/ | |
obj.unlockKey = function(action) { | |
keyLocked[action] = false; | |
}; | |
/** | |
* unbind the defined keycode | |
* @name unbindKey | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {me.input#KEY} keycode | |
* @example | |
* me.input.unbindKey(me.input.KEY.LEFT); | |
*/ | |
obj.unbindKey = function(keycode) { | |
// clear the event status | |
keyStatus[KeyBinding[keycode]] = 0; | |
keyLock[KeyBinding[keycode]] = false; | |
keyRefs[KeyBinding[keycode]] = {}; | |
// remove the key binding | |
KeyBinding[keycode] = null; | |
}; | |
/** | |
* Translate the specified x and y values from the global (absolute) | |
* coordinate to local (viewport) relative coordinate. | |
* @name globalToLocal | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {Number} x the global x coordinate to be translated. | |
* @param {Number} y the global y coordinate to be translated. | |
* @return {me.Vector2d} A vector object with the corresponding translated coordinates. | |
* @example | |
* onMouseEvent : function(e) { | |
* // convert the given into local (viewport) relative coordinates | |
* var pos = me.input.globalToLocal(e.clientX, e,clientY); | |
* // do something with pos ! | |
* }; | |
*/ | |
obj.globalToLocal = function (x, y) { | |
var offset = obj.offset; | |
var pixelRatio = me.device.getPixelRatio(); | |
x -= offset.left; | |
y -= offset.top; | |
var scale = me.sys.scale; | |
if (scale.x !== 1.0 || scale.y !== 1.0) { | |
x/= scale.x; | |
y/= scale.y; | |
} | |
return new me.Vector2d(x * pixelRatio, y * pixelRatio); | |
}; | |
/** | |
* Associate a mouse (button) action to a keycode | |
* Left button – 0 | |
* Middle button – 1 | |
* Right button – 2 | |
* @name bindMouse | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {Number} button (accordingly to W3C values : 0,1,2 for left, middle and right buttons) | |
* @param {me.input#KEY} keyCode | |
* @example | |
* // enable the keyboard | |
* me.input.bindKey(me.input.KEY.X, "shoot"); | |
* // map the left button click on the X key | |
* me.input.bindMouse(me.input.mouse.LEFT, me.input.KEY.X); | |
*/ | |
obj.bindMouse = function (button, keyCode) | |
{ | |
// make sure the mouse is initialized | |
enablePointerEvent(); | |
// throw an exception if no action is defined for the specified keycode | |
if (!KeyBinding[keyCode]) | |
throw "melonJS : no action defined for keycode " + keyCode; | |
// map the mouse button to the keycode | |
obj.mouse.bind[button] = keyCode; | |
}; | |
/** | |
* unbind the defined keycode | |
* @name unbindMouse | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {Number} button (accordingly to W3C values : 0,1,2 for left, middle and right buttons) | |
* @example | |
* me.input.unbindMouse(me.input.mouse.LEFT); | |
*/ | |
obj.unbindMouse = function(button) { | |
// clear the event status | |
obj.mouse.bind[button] = null; | |
}; | |
/** | |
* Associate a touch action to a keycode | |
* @name bindTouch | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {me.input#KEY} keyCode | |
* @example | |
* // enable the keyboard | |
* me.input.bindKey(me.input.KEY.X, "shoot"); | |
* // map the touch event on the X key | |
* me.input.bindTouch(me.input.KEY.X); | |
*/ | |
obj.bindTouch = function (keyCode) | |
{ | |
// reuse the mouse emulation stuff | |
// where left mouse button is map to touch event | |
obj.bindMouse(me.input.mouse.LEFT, keyCode); | |
}; | |
/** | |
* unbind the defined touch binding | |
* @name unbindTouch | |
* @memberOf me.input | |
* @public | |
* @function | |
* @example | |
* me.input.unbindTouch(); | |
*/ | |
obj.unbindTouch = function() { | |
// clear the key binding | |
obj.unbindMouse(me.input.mouse.LEFT); | |
}; | |
/** | |
* allows registration of event listeners on the object target. <br> | |
* (on a touch enabled device mouse event will automatically be converted to touch event)<br> | |
* <br> | |
* melonJS defines the additional `gameX` and `gameY` properties when passing the Event object <br> | |
* to the defined callback (see below)<br> | |
* @see external:Event | |
* @name registerPointerEvent | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {String} eventType The event type for which the object is registering ('mousemove','mousedown','mouseup','mousewheel','touchstart','touchmove','touchend') | |
* @param {me.Rect} rect object target (or corresponding region defined through me.Rect) | |
* @param {Function} callback methods to be called when the event occurs. | |
* @param {Boolean} [floating="floating property of the given object"] specify if the object is a floating object (if yes, screen coordinates are used, if not mouse/touch coordinates will be converted to world coordinates) | |
* @example | |
* // register on the 'mousemove' event | |
* me.input.registerPointerEvent('mousemove', this.collisionBox, this.mouseMove.bind(this)); | |
*/ | |
obj.registerPointerEvent = function (eventType, rect, callback, floating) { | |
// make sure the mouse/touch events are initialized | |
enablePointerEvent(); | |
// convert mouse events to iOS/PointerEvent equivalent | |
if ((mouseEventList.indexOf(eventType) !== -1) && (me.device.touch || me.device.pointerEnabled)) { | |
eventType = activeEventList[mouseEventList.indexOf(eventType)]; | |
} | |
// >>>TODO<<< change iOS touch event to their PointerEvent equivalent & vice-versa | |
// check if this is supported event | |
if (eventType && (activeEventList.indexOf(eventType) !== -1)) { | |
// register the event | |
if (!evtHandlers[eventType]) { | |
evtHandlers[eventType] = []; | |
} | |
// check if this is a floating object or not | |
var _float = rect.floating === true ? true : false; | |
// check if there is a given parameter | |
if (floating) { | |
// ovveride the previous value | |
_float = floating === true ? true : false; | |
} | |
// initialize the handler | |
evtHandlers[eventType].push({ rect: rect || null, cb: callback, floating: _float }); | |
return; | |
} | |
throw "melonJS : invalid event type : " + eventType; | |
}; | |
/** | |
* allows the removal of event listeners from the object target. | |
* note : on a touch enabled device mouse event will automatically be converted to touch event | |
* @name releasePointerEvent | |
* @memberOf me.input | |
* @public | |
* @function | |
* @param {String} eventType The event type for which the object is registering ('mousemove', 'mousedown', 'mouseup', 'mousewheel', 'click', 'dblclick', 'touchstart', 'touchmove', 'touchend', 'tap', 'dbltap') | |
* @param {me.Rect} region object target (or corresponding region defined through me.Rect) | |
* @example | |
* // release the registered object/region on the 'mousemove' event | |
* me.input.releasePointerEvent('mousemove', this.collisionBox); | |
*/ | |
obj.releasePointerEvent = function(eventType, rect) { | |
// convert mouse events to iOS/MSPointer equivalent | |
if ((mouseEventList.indexOf(eventType) !== -1) && (me.device.touch || me.device.pointerEnabled)) { | |
eventType = activeEventList[mouseEventList.indexOf(eventType)]; | |
} | |
// >>>TODO<<< change iOS touch event to their PointerEvent equivalent & vice-versa | |
// check if this is supported event | |
if (eventType && (activeEventList.indexOf(eventType) !== -1)) { | |
// unregister the event | |
if (!evtHandlers[eventType]) { | |
evtHandlers[eventType] = []; | |
} | |
var handlers = evtHandlers[eventType]; | |
if (handlers) { | |
for (var i = handlers.length, handler; i--, handler = handlers[i];) { | |
if (handler.rect === rect) { | |
// make sure all references are null | |
handler.rect = handler.cb = handler.floating = null; | |
evtHandlers[eventType].splice(i, 1); | |
} | |
} | |
} | |
return; | |
} | |
throw "melonJS : invalid event type : " + eventType; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* Base64 decoding | |
* @see <a href="http://www.webtoolkit.info/">http://www.webtoolkit.info/</A> | |
* @ignore | |
*/ | |
var Base64 = (function() { | |
// hold public stuff in our singleton | |
var singleton = {}; | |
// private property | |
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |
// public method for decoding | |
singleton.decode = function(input) { | |
// make sure our input string has the right format | |
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); | |
if (me.device.nativeBase64) { | |
// use native decoder | |
return $.atob(input); | |
} | |
else { | |
// use cross-browser decoding | |
var output = [], chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0; | |
while (i < input.length) { | |
enc1 = _keyStr.indexOf(input.charAt(i++)); | |
enc2 = _keyStr.indexOf(input.charAt(i++)); | |
enc3 = _keyStr.indexOf(input.charAt(i++)); | |
enc4 = _keyStr.indexOf(input.charAt(i++)); | |
chr1 = (enc1 << 2) | (enc2 >> 4); | |
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); | |
chr3 = ((enc3 & 3) << 6) | enc4; | |
output.push(String.fromCharCode(chr1)); | |
if (enc3 !== 64) { | |
output.push(String.fromCharCode(chr2)); | |
} | |
if (enc4 !== 64) { | |
output.push(String.fromCharCode(chr3)); | |
} | |
} | |
output = output.join(''); | |
return output; | |
} | |
}; | |
return singleton; | |
})(); | |
/** | |
* a collection of utility functions<br> | |
* there is no constructor function for me.utils | |
* @namespace me.utils | |
* @memberOf me | |
*/ | |
me.utils = (function() { | |
// hold public stuff in our singleton | |
var api = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
// cache rgb converted value | |
var rgbCache = {}; | |
// guid default value | |
var GUID_base = ""; | |
var GUID_index = 0; | |
// regexp to deal with file name & path | |
var removepath = /^.*(\\|\/|\:)/; | |
var removeext = /\.[^\.]*$/; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* Decode a base64 encoded string into a binary string | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name decodeBase64 | |
* @param {String} input Base64 encoded data | |
* @return {String} Binary string | |
*/ | |
api.decodeBase64 = function(input) { | |
return Base64.decode(input); | |
}; | |
/** | |
* Decode a base64 encoded string into a byte array | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name decodeBase64AsArray | |
* @param {String} input Base64 encoded data | |
* @param {Number} [bytes] number of bytes per array entry | |
* @return {Number[]} Array of bytes | |
*/ | |
api.decodeBase64AsArray = function(input, bytes) { | |
bytes = bytes || 1; | |
var dec = Base64.decode(input), i, j, len; | |
// use a typed array if supported | |
var ar; | |
if (typeof window.Uint32Array === 'function') { | |
ar = new Uint32Array(dec.length / bytes); | |
} else { | |
ar = []; | |
} | |
for (i = 0, len = dec.length / bytes; i < len; i++) { | |
ar[i] = 0; | |
for (j = bytes - 1; j >= 0; --j) { | |
ar[i] += dec.charCodeAt((i * bytes) + j) << (j << 3); | |
} | |
} | |
return ar; | |
}; | |
/** | |
* decompress zlib/gzip data (NOT IMPLEMENTED) | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name decompress | |
* @param {Number[]} data Array of bytes | |
* @param {String} format compressed data format ("gzip","zlib") | |
* @return {Number[]} Array of bytes | |
*/ | |
api.decompress = function(data, format) { | |
throw "melonJS: GZIP/ZLIB compressed TMX Tile Map not supported!"; | |
}; | |
/** | |
* Decode a CSV encoded array into a binary array | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name decodeCSV | |
* @param {String} input CSV formatted data | |
* @param {Number} limit row split limit | |
* @return {Number[]} Int Array | |
*/ | |
api.decodeCSV = function(input, limit) { | |
input = input.trim().split("\n"); | |
var result = []; | |
for ( var i = 0; i < input.length; i++) { | |
var entries = input[i].split(",", limit); | |
for ( var e = 0; e < entries.length; e++) { | |
result.push(+entries[e]); | |
} | |
} | |
return result; | |
}; | |
/** | |
* return the base name of the file without path info.<br> | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name getBasename | |
* @param {String} path path containing the filename | |
* @return {String} the base name without path information. | |
*/ | |
api.getBasename = function(path) { | |
return path.replace(removepath, '').replace(removeext, ''); | |
}; | |
/** | |
* return the extension of the file in the given path <br> | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name getFileExtension | |
* @param {String} path path containing the filename | |
* @return {String} filename extension. | |
*/ | |
api.getFileExtension = function(path) { | |
return path.substring(path.lastIndexOf(".") + 1, path.length); | |
}; | |
/** | |
* a Hex to RGB color function | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name HexTORGB | |
* @param {String} h Hex color code in "#rgb" or "#RRGGBB" format | |
* @param {Number} [a] Alpha to be appended to decoded color (0 to 255) | |
* @return {String} CSS color string in rgb() or rgba() format | |
*/ | |
api.HexToRGB = function(h, a) { | |
if (h.charAt(0) !== "#") { | |
// this is not a hexadecimal string | |
return h; | |
} | |
// remove the # | |
h = h.substring(1, h.length); | |
// check if we already have the converted value cached | |
if (rgbCache[h] == null) { | |
// else add it (format : "r,g,b") | |
var h1, h2, h3; | |
if (h.length < 6) { | |
// 3 char shortcut is used, double each char | |
h1 = h.charAt(0)+h.charAt(0); | |
h2 = h.charAt(1)+h.charAt(1); | |
h3 = h.charAt(2)+h.charAt(2); | |
} | |
else { | |
h1 = h.substring(0, 2); | |
h2 = h.substring(2, 4); | |
h3 = h.substring(4, 6); | |
} | |
// set the value in our cache | |
rgbCache[h] = parseInt(h1, 16) + "," + parseInt(h2, 16) + "," + parseInt(h3, 16); | |
} | |
return (a ? "rgba(" : "rgb(") + rgbCache[h] + (a ? "," + a + ")" : ")"); | |
}; | |
/** | |
* an RGB to Hex color function | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name RGBToHex | |
* @param {Number} r Value for red component (0 to 255) | |
* @param {Number} g Value for green component (0 to 255) | |
* @param {Number} b Value for blue component (0 to 255) | |
* @return {String} Hex color code in "RRGGBB" format | |
*/ | |
api.RGBToHex = function(r, g, b) { | |
return r.toHex() + g.toHex() + b.toHex(); | |
}; | |
/** | |
* Get image pixels | |
* @public | |
* @function | |
* @memberOf me.utils | |
* @name getPixels | |
* @param {Image|Canvas} image Image to read | |
* @return {ImageData} Canvas ImageData object | |
*/ | |
api.getPixels = function(arg) { | |
if (arg instanceof HTMLImageElement) { | |
var _context = me.video.getContext2d( | |
me.video.createCanvas(arg.width, arg.height) | |
); | |
_context.drawImage(arg, 0, 0); | |
return _context.getImageData(0, 0, arg.width, arg.height); | |
} else { | |
// canvas ! | |
return arg.getContext('2d').getImageData(0, 0, arg.width, arg.height); | |
} | |
}; | |
/** | |
* reset the GUID Base Name | |
* the idea here being to have a unique ID | |
* per level / object | |
* @ignore | |
*/ | |
api.resetGUID = function(base) { | |
// also ensure it's only 8bit ASCII characters | |
GUID_base = base.toString().toUpperCase().toHex(); | |
GUID_index = 0; | |
}; | |
/** | |
* create and return a very simple GUID | |
* Game Unique ID | |
* @ignore | |
*/ | |
api.createGUID = function() { | |
return GUID_base + "-" + (GUID_index++); | |
}; | |
/** | |
* apply friction to a force | |
* @ignore | |
* @TODO Move this somewhere else | |
*/ | |
api.applyFriction = function(v, f) { | |
return (v+f<0)?v+(f*me.timer.tick):(v-f>0)?v-(f*me.timer.tick):0; | |
}; | |
// return our object | |
return api; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013 melonJS | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* A singleton object to access the device localStorage area | |
* @example | |
* // Initialize "score" and "lives" with default values | |
* me.save.add({ score : 0, lives : 3 }); | |
* | |
* // Save score | |
* me.save.score = 31337; | |
* | |
* // Load lives | |
* console.log(me.save.lives); | |
* | |
* // Also supports complex objects thanks to JSON backend | |
* me.save.complexObject = { a : "b", c : [ 1, 2, 3, "d" ], e : { f : [{}] } }; | |
* // DO NOT set any child properties of me.save.complexObject directly! | |
* // Changes made that way will not save. Always set the entire object value at once. | |
* | |
* // Print all | |
* console.log(JSON.stringify(me.save)); | |
* | |
* // Delete "score" from localStorage | |
* me.save.delete('score'); | |
* @namespace me.save | |
* @memberOf me | |
*/ | |
me.save = (function () { | |
// Variable to hold the object data | |
var data = {}; | |
// a fucntion to check if the given key is a reserved word | |
function isReserved (key) { | |
return (key === "add" || key === "delete"); | |
} | |
// Public API | |
var api = { | |
/** | |
* @ignore | |
*/ | |
_init: function() { | |
// Load previous data if local Storage is supported | |
if (me.device.localStorage === true) { | |
var keys = JSON.parse(localStorage.getItem("me.save")) || []; | |
keys.forEach(function (key) { | |
data[key] = JSON.parse(localStorage.getItem("me.save." + key)); | |
}); | |
} | |
}, | |
/** | |
* Add new keys to localStorage and set them to the given default values if they do not exist | |
* @name add | |
* @memberOf me.save | |
* @function | |
* @param {Object} props key and corresponding values | |
* @example | |
* // Initialize "score" and "lives" with default values | |
* me.save.add({ score : 0, lives : 3 }); | |
*/ | |
add : function (props) { | |
Object.keys(props).forEach(function (key) { | |
if (isReserved(key)) return; | |
(function (prop) { | |
Object.defineProperty(api, prop, { | |
configurable : true, | |
enumerable : true, | |
get : function () { | |
return data[prop]; | |
}, | |
set : function (value) { | |
data[prop] = value; | |
if (me.device.localStorage === true) { | |
localStorage.setItem("me.save." + prop, JSON.stringify(value)); | |
} | |
} | |
}); | |
})(key); | |
// Set default value for key | |
if (!(key in data)) { | |
api[key] = props[key]; | |
} | |
}); | |
// Save keys | |
if (me.device.localStorage === true) { | |
localStorage.setItem("me.save", JSON.stringify(Object.keys(data))); | |
} | |
}, | |
/** | |
* Remove a key from localStorage | |
* @name delete | |
* @memberOf me.save | |
* @function | |
* @param {String} key key to be removed | |
* @example | |
* // Remove the "score" key from localStorage | |
* me.save.delete("score"); | |
*/ | |
delete : function (key) { | |
if (!isReserved(key)) { | |
if (typeof data[key] !== 'undefined') { | |
delete data[key]; | |
if (me.device.localStorage === true) { | |
localStorage.removeItem("me.save." + key); | |
localStorage.setItem("me.save", JSON.stringify(Object.keys(data))); | |
} | |
} | |
} | |
} | |
}; | |
return api; | |
})(); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
// some custom constants | |
me.COLLISION_LAYER = "collision"; | |
// some TMX constants | |
me.TMX_TAG_MAP = "map"; | |
me.TMX_TAG_NAME = "name"; | |
me.TMX_TAG_VALUE = "value"; | |
me.TMX_TAG_VERSION = "version"; | |
me.TMX_TAG_ORIENTATION = "orientation"; | |
me.TMX_TAG_WIDTH = "width"; | |
me.TMX_TAG_HEIGHT = "height"; | |
me.TMX_TAG_OPACITY = "opacity"; | |
me.TMX_TAG_TRANS = "trans"; | |
me.TMX_TAG_TILEWIDTH = "tilewidth"; | |
me.TMX_TAG_TILEHEIGHT = "tileheight"; | |
me.TMX_TAG_TILEOFFSET = "tileoffset"; | |
me.TMX_TAG_FIRSTGID = "firstgid"; | |
me.TMX_TAG_GID = "gid"; | |
me.TMX_TAG_TILE = "tile"; | |
me.TMX_TAG_ID = "id"; | |
me.TMX_TAG_DATA = "data"; | |
me.TMX_TAG_COMPRESSION = "compression"; | |
me.TMX_TAG_GZIP = "gzip"; | |
me.TMX_TAG_ZLIB = "zlib"; | |
me.TMX_TAG_ENCODING = "encoding"; | |
me.TMX_TAG_ATTR_BASE64 = "base64"; | |
me.TMX_TAG_CSV = "csv"; | |
me.TMX_TAG_SPACING = "spacing"; | |
me.TMX_TAG_MARGIN = "margin"; | |
me.TMX_TAG_PROPERTIES = "properties"; | |
me.TMX_TAG_PROPERTY = "property"; | |
me.TMX_TAG_IMAGE = "image"; | |
me.TMX_TAG_SOURCE = "source"; | |
me.TMX_TAG_VISIBLE = "visible"; | |
me.TMX_TAG_TILESET = "tileset"; | |
me.TMX_TAG_LAYER = "layer"; | |
me.TMX_TAG_TILE_LAYER = "tilelayer"; | |
me.TMX_TAG_IMAGE_LAYER = "imagelayer"; | |
me.TMX_TAG_OBJECTGROUP = "objectgroup"; | |
me.TMX_TAG_OBJECT = "object"; | |
me.TMX_TAG_X = "x"; | |
me.TMX_TAG_Y = "y"; | |
me.TMX_TAG_WIDTH = "width"; | |
me.TMX_TAG_HEIGHT = "height"; | |
me.TMX_TAG_POLYGON = "polygon"; | |
me.TMX_TAG_POLYLINE = "polyline"; | |
me.TMX_TAG_ELLIPSE = "ellipse"; | |
me.TMX_TAG_POINTS = "points"; | |
me.TMX_BACKGROUND_COLOR = "backgroundcolor"; | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
/** | |
* a collection of TMX utility Function | |
* @final | |
* @memberOf me | |
* @ignore | |
*/ | |
me.TMXUtils = (function() { | |
/** | |
* set and interpret a TMX property value | |
* @ignore | |
*/ | |
function setTMXValue(value) { | |
if (!value || value.isBoolean()) { | |
// if value not defined or boolean | |
value = value ? (value === "true") : true; | |
} else if (value.isNumeric()) { | |
// check if numeric | |
value = Number(value); | |
} else if (value.match(/^json:/i)) { | |
// try to parse it | |
var match = value.split(/^json:/i)[1]; | |
try { | |
value = JSON.parse(match); | |
} | |
catch (e) { | |
throw "Unable to parse JSON: " + match; | |
} | |
} | |
// return the interpreted value | |
return value; | |
} | |
// hold public stuff in our singleton | |
var api = {}; | |
/** | |
* Apply TMX Properties to the give object | |
* @ignore | |
*/ | |
api.applyTMXPropertiesFromXML = function(obj, xmldata) { | |
var properties = xmldata.getElementsByTagName(me.TMX_TAG_PROPERTIES)[0]; | |
if (properties) { | |
var oProp = properties.getElementsByTagName(me.TMX_TAG_PROPERTY); | |
for ( var i = 0; i < oProp.length; i++) { | |
var propname = me.mapReader.TMXParser.getStringAttribute(oProp[i], me.TMX_TAG_NAME); | |
var value = me.mapReader.TMXParser.getStringAttribute(oProp[i], me.TMX_TAG_VALUE); | |
// set the value | |
obj[propname] = setTMXValue(value); | |
} | |
} | |
}; | |
/** | |
* Apply TMX Properties to the give object | |
* @ignore | |
*/ | |
api.applyTMXPropertiesFromJSON = function(obj, data) { | |
var properties = data[me.TMX_TAG_PROPERTIES]; | |
if (properties) { | |
for(var name in properties){ | |
if (properties.hasOwnProperty(name)) { | |
// set the value | |
obj[name] = setTMXValue(properties[name]); | |
} | |
} | |
} | |
}; | |
/** | |
* basic function to merge object properties | |
* @ignore | |
*/ | |
api.mergeProperties = function(dest, src, overwrite) { | |
for(var p in src){ | |
if(overwrite || dest[p]===undefined) dest[p]= src[p]; | |
} | |
return dest; | |
}; | |
// return our object | |
return api; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
/** | |
* TMX Object Group <br> | |
* contains the object group definition as defined in Tiled. <br> | |
* note : object group definition is translated into the virtual `me.game.world` using `me.ObjectContainer`. | |
* @see me.ObjectContainer | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
*/ | |
me.TMXObjectGroup = Object.extend({ | |
/** | |
* group name | |
* @public | |
* @type String | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
name : null, | |
/** | |
* group width | |
* @public | |
* @type Number | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
width : 0, | |
/** | |
* group height | |
* @public | |
* @type Number | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
height : 0, | |
/** | |
* group visibility state | |
* @public | |
* @type Boolean | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
visible : false, | |
/** | |
* group z order | |
* @public | |
* @type Number | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
z : 0, | |
/** | |
* group objects list definition | |
* @see me.TMXObject | |
* @public | |
* @type Array | |
* @name name | |
* @memberOf me.TMXObjectGroup | |
*/ | |
objects : [], | |
/** | |
* constructor from XML content | |
* @ignore | |
* @function | |
*/ | |
initFromXML : function(name, tmxObjGroup, tilesets, z) { | |
this.name = name; | |
this.width = me.mapReader.TMXParser.getIntAttribute(tmxObjGroup, me.TMX_TAG_WIDTH); | |
this.height = me.mapReader.TMXParser.getIntAttribute(tmxObjGroup, me.TMX_TAG_HEIGHT); | |
this.visible = (me.mapReader.TMXParser.getIntAttribute(tmxObjGroup, me.TMX_TAG_VISIBLE, 1) === 1); | |
this.opacity = me.mapReader.TMXParser.getFloatAttribute(tmxObjGroup, me.TMX_TAG_OPACITY, 1.0).clamp(0.0, 1.0); | |
this.z = z; | |
this.objects = []; | |
// check if we have any user-defined properties | |
if (tmxObjGroup.firstChild && (tmxObjGroup.firstChild.nextSibling.nodeName === me.TMX_TAG_PROPERTIES)) { | |
me.TMXUtils.applyTMXPropertiesFromXML(this, tmxObjGroup); | |
} | |
var data = tmxObjGroup.getElementsByTagName(me.TMX_TAG_OBJECT); | |
for ( var i = 0; i < data.length; i++) { | |
var object = new me.TMXObject(); | |
object.initFromXML(data[i], tilesets, z); | |
this.objects.push(object); | |
} | |
}, | |
/** | |
* constructor from JSON content | |
* @ignore | |
* @function | |
*/ | |
initFromJSON : function(name, tmxObjGroup, tilesets, z) { | |
var self = this; | |
this.name = name; | |
this.width = tmxObjGroup[me.TMX_TAG_WIDTH]; | |
this.height = tmxObjGroup[me.TMX_TAG_HEIGHT]; | |
this.visible = tmxObjGroup[me.TMX_TAG_VISIBLE]; | |
this.opacity = parseFloat(tmxObjGroup[me.TMX_TAG_OPACITY] || 1.0).clamp(0.0, 1.0); | |
this.z = z; | |
this.objects = []; | |
// check if we have any user-defined properties | |
me.TMXUtils.applyTMXPropertiesFromJSON(this, tmxObjGroup); | |
// parse all TMX objects | |
tmxObjGroup["objects"].forEach(function(tmxObj) { | |
var object = new me.TMXObject(); | |
object.initFromJSON(tmxObj, tilesets, z); | |
self.objects.push(object); | |
}); | |
}, | |
/** | |
* reset function | |
* @ignore | |
* @function | |
*/ | |
reset : function() { | |
// clear all allocated objects | |
this.objects = null; | |
}, | |
/** | |
* return the object count | |
* @ignore | |
* @function | |
*/ | |
getObjectCount : function() { | |
return this.objects.length; | |
}, | |
/** | |
* returns the object at the specified index | |
* @ignore | |
* @function | |
*/ | |
getObjectByIndex : function(idx) { | |
return this.objects[idx]; | |
} | |
}); | |
/** | |
* a TMX Object defintion, as defined in Tiled. <br> | |
* note : object definition are translated into the virtual `me.game.world` using `me.ObjectEntity`. | |
* @see me.ObjectEntity | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
*/ | |
me.TMXObject = Object.extend({ | |
/** | |
* object name | |
* @public | |
* @type String | |
* @name name | |
* @memberOf me.TMXObject | |
*/ | |
name : null, | |
/** | |
* object x position | |
* @public | |
* @type Number | |
* @name x | |
* @memberOf me.TMXObject | |
*/ | |
x : 0, | |
/** | |
* object y position | |
* @public | |
* @type Number | |
* @name y | |
* @memberOf me.TMXObject | |
*/ | |
y : 0, | |
/** | |
* object width | |
* @public | |
* @type Number | |
* @name width | |
* @memberOf me.TMXObject | |
*/ | |
width : 0, | |
/** | |
* object height | |
* @public | |
* @type Number | |
* @name height | |
* @memberOf me.TMXObject | |
*/ | |
height : 0, | |
/** | |
* object z order | |
* @public | |
* @type Number | |
* @name z | |
* @memberOf me.TMXObject | |
*/ | |
z : 0, | |
/** | |
* object gid value | |
* when defined the object is a tiled object | |
* @public | |
* @type Number | |
* @name gid | |
* @memberOf me.TMXObject | |
*/ | |
gid : undefined, | |
/** | |
* if true, the object is a polygone | |
* @public | |
* @type Boolean | |
* @name isPolygon | |
* @memberOf me.TMXObject | |
*/ | |
isPolygon : false, | |
/** | |
* f true, the object is a polygone | |
* @public | |
* @type Boolean | |
* @name isPolyline | |
* @memberOf me.TMXObject | |
*/ | |
isPolyline : false, | |
/** | |
* object point list (for polygone and polyline) | |
* @public | |
* @type Vector2d[] | |
* @name points | |
* @memberOf me.TMXObject | |
*/ | |
points : undefined, | |
/** | |
* constructor from XML content | |
* @ignore | |
* @function | |
*/ | |
initFromXML : function(tmxObj, tilesets, z) { | |
this.name = me.mapReader.TMXParser.getStringAttribute(tmxObj, me.TMX_TAG_NAME); | |
this.x = me.mapReader.TMXParser.getIntAttribute(tmxObj, me.TMX_TAG_X); | |
this.y = me.mapReader.TMXParser.getIntAttribute(tmxObj, me.TMX_TAG_Y); | |
this.z = z; | |
this.width = me.mapReader.TMXParser.getIntAttribute(tmxObj, me.TMX_TAG_WIDTH, 0); | |
this.height = me.mapReader.TMXParser.getIntAttribute(tmxObj, me.TMX_TAG_HEIGHT, 0); | |
this.gid = me.mapReader.TMXParser.getIntAttribute(tmxObj, me.TMX_TAG_GID, null); | |
this.isEllipse = false; | |
this.isPolygon = false; | |
this.isPolyline = false; | |
// check if the object has an associated gid | |
if (this.gid) { | |
this.setImage(this.gid, tilesets); | |
} else { | |
// check if this is an ellipse | |
if (tmxObj.getElementsByTagName(me.TMX_TAG_ELLIPSE).length) { | |
this.isEllipse = true; | |
} else { | |
// polygone || polyline | |
var points = tmxObj.getElementsByTagName(me.TMX_TAG_POLYGON); | |
if (points.length) { | |
this.isPolygon = true; | |
} else { | |
points = tmxObj.getElementsByTagName(me.TMX_TAG_POLYLINE); | |
if (points.length) { | |
this.isPolyline = true; | |
} | |
} | |
if (points.length) { | |
this.points = []; | |
// get a point array | |
var point = me.mapReader.TMXParser.getStringAttribute( | |
points[0], | |
me.TMX_TAG_POINTS | |
).split(" "); | |
// and normalize them into an array of vectors | |
for (var i = 0, v; i < point.length; i++) { | |
v = point[i].split(","); | |
this.points.push(new me.Vector2d(+v[0], +v[1])); | |
} | |
} | |
} | |
} | |
// Adjust the Position to match Tiled | |
me.game.renderer.adjustPosition(this); | |
// set the object properties | |
me.TMXUtils.applyTMXPropertiesFromXML(this, tmxObj); | |
}, | |
/** | |
* constructor from JSON content | |
* @ignore | |
* @function | |
*/ | |
initFromJSON : function(tmxObj, tilesets, z) { | |
this.name = tmxObj[me.TMX_TAG_NAME]; | |
this.x = parseInt(tmxObj[me.TMX_TAG_X], 10); | |
this.y = parseInt(tmxObj[me.TMX_TAG_Y], 10); | |
this.z = parseInt(z, 10); | |
this.width = parseInt(tmxObj[me.TMX_TAG_WIDTH] || 0, 10); | |
this.height = parseInt(tmxObj[me.TMX_TAG_HEIGHT] || 0, 10); | |
this.gid = parseInt(tmxObj[me.TMX_TAG_GID], 10) || null; | |
this.isEllipse = false; | |
this.isPolygon = false; | |
this.isPolyline = false; | |
// check if the object has an associated gid | |
if (this.gid) { | |
this.setImage(this.gid, tilesets); | |
} | |
else { | |
if (tmxObj[me.TMX_TAG_ELLIPSE]!==undefined) { | |
this.isEllipse = true; | |
} | |
else { | |
var points = tmxObj[me.TMX_TAG_POLYGON]; | |
if (points !== undefined) { | |
this.isPolygon = true; | |
} else { | |
points = tmxObj[me.TMX_TAG_POLYLINE]; | |
if (points !== undefined) { | |
this.isPolyline = true; | |
} | |
} | |
if (points !== undefined) { | |
this.points = []; | |
var self = this; | |
points.forEach(function(point) { | |
self.points.push(new me.Vector2d(parseInt(point.x, 10), parseInt(point.y, 10))); | |
}); | |
} | |
} | |
} | |
// Adjust the Position to match Tiled | |
me.game.renderer.adjustPosition(this); | |
// set the object properties | |
me.TMXUtils.applyTMXPropertiesFromJSON(this, tmxObj); | |
}, | |
/** | |
* set the object image (for Tiled Object) | |
* @ignore | |
* @function | |
*/ | |
setImage : function(gid, tilesets) { | |
// get the corresponding tileset | |
var tileset = tilesets.getTilesetByGid(this.gid); | |
// set width and height equal to tile size | |
this.width = tileset.tilewidth; | |
this.height = tileset.tileheight; | |
// force spritewidth size | |
this.spritewidth = this.width; | |
// the object corresponding tile | |
var tmxTile = new me.Tile(this.x, this.y, tileset.tilewidth, tileset.tileheight, this.gid); | |
// get the corresponding tile into our object | |
this.image = tileset.getTileImage(tmxTile); | |
}, | |
/** | |
* getObjectPropertyByName | |
* @ignore | |
* @function | |
*/ | |
getObjectPropertyByName : function(name) { | |
return this[name]; | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
/**************************************************/ | |
/* */ | |
/* 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({ | |
/** | |
* tileId | |
* @public | |
* @type int | |
* @name me.Tile#tileId | |
*/ | |
tileId : null, | |
/** | |
* tileset | |
* @public | |
* @type me.TMXTileset | |
* @name me.Tile#tileset | |
*/ | |
tileset : null, | |
/** @ignore */ | |
init : function(x, y, w, h, gid) { | |
this.parent(new me.Vector2d(x * w, y * h), w, h); | |
// Tile col / row pos | |
this.col = x; | |
this.row = y; | |
this.tileId = gid; | |
/** | |
* True if the tile is flipped horizontally<br> | |
* @public | |
* @type Boolean | |
* @name me.Tile#flipX | |
*/ | |
this.flipX = (this.tileId & FlippedHorizontallyFlag) !== 0; | |
/** | |
* True if the tile is flipped vertically<br> | |
* @public | |
* @type Boolean | |
* @name me.Tile#flipY | |
*/ | |
this.flipY = (this.tileId & FlippedVerticallyFlag) !== 0; | |
/** | |
* True if the tile is flipped anti-diagonally<br> | |
* @public | |
* @type Boolean | |
* @name me.Tile#flipAD | |
*/ | |
this.flipAD = (this.tileId & FlippedAntiDiagonallyFlag) !== 0; | |
/** | |
* Global flag that indicates if the tile is flipped<br> | |
* @public | |
* @type Boolean | |
* @name me.Tile#flipped | |
*/ | |
this.flipped = this.flipX || this.flipY || this.flipAD; | |
// clear out the flags and set the tileId | |
this.tileId &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag | FlippedAntiDiagonallyFlag); | |
} | |
}); | |
/** | |
* a TMX Tile Set Object | |
* @class | |
* @memberOf me | |
* @constructor | |
*/ | |
me.TMXTileset = Object.extend({ | |
// tile types | |
type : { | |
SOLID : "solid", | |
PLATFORM : "platform", | |
L_SLOPE : "lslope", | |
R_SLOPE : "rslope", | |
LADDER : "ladder", | |
TOPLADDER : "topladder", | |
BREAKABLE : "breakable" | |
}, | |
init: function() { | |
// tile properties (collidable, etc..) | |
this.TileProperties = []; | |
// a cache for offset value | |
this.tileXOffset = []; | |
this.tileYOffset = []; | |
}, | |
// constructor | |
initFromXML: function (xmltileset) { | |
// first gid | |
this.firstgid = me.mapReader.TMXParser.getIntAttribute(xmltileset, me.TMX_TAG_FIRSTGID); | |
var src = me.mapReader.TMXParser.getStringAttribute(xmltileset, me.TMX_TAG_SOURCE); | |
if (src) { | |
// load TSX | |
src = me.utils.getBasename(src); | |
xmltileset = me.loader.getTMX(src); | |
if (!xmltileset) { | |
throw "melonJS:" + src + " TSX tileset not found"; | |
} | |
// FIXME: This is ok for now, but it wipes out the | |
// XML currently loaded into the global `me.mapReader.TMXParser` | |
me.mapReader.TMXParser.parseFromString(xmltileset); | |
xmltileset = me.mapReader.TMXParser.getFirstElementByTagName("tileset"); | |
} | |
this.name = me.mapReader.TMXParser.getStringAttribute(xmltileset, me.TMX_TAG_NAME); | |
this.tilewidth = me.mapReader.TMXParser.getIntAttribute(xmltileset, me.TMX_TAG_TILEWIDTH); | |
this.tileheight = me.mapReader.TMXParser.getIntAttribute(xmltileset, me.TMX_TAG_TILEHEIGHT); | |
this.spacing = me.mapReader.TMXParser.getIntAttribute(xmltileset, me.TMX_TAG_SPACING, 0); | |
this.margin = me.mapReader.TMXParser.getIntAttribute(xmltileset, me.TMX_TAG_MARGIN, 0); | |
// set tile offset properties (if any) | |
this.tileoffset = new me.Vector2d(0,0); | |
var offset = xmltileset.getElementsByTagName(me.TMX_TAG_TILEOFFSET); | |
if (offset.length>0) { | |
this.tileoffset.x = me.mapReader.TMXParser.getIntAttribute(offset[0], me.TMX_TAG_X); | |
this.tileoffset.y = me.mapReader.TMXParser.getIntAttribute(offset[0], me.TMX_TAG_Y); | |
} | |
// set tile properties, if any | |
var tileInfo = xmltileset.getElementsByTagName(me.TMX_TAG_TILE); | |
for ( var i = 0; i < tileInfo.length; i++) { | |
var tileID = me.mapReader.TMXParser.getIntAttribute(tileInfo[i], me.TMX_TAG_ID) + this.firstgid; | |
// apply tiled defined properties | |
var prop = {}; | |
me.TMXUtils.applyTMXPropertiesFromXML(prop, tileInfo[i]); | |
this.setTileProperty(tileID, prop); | |
} | |
// check for the texture corresponding image | |
var imagesrc = xmltileset.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE); | |
var image = (imagesrc) ? me.loader.getImage(me.utils.getBasename(imagesrc)):null; | |
if (!image) { | |
console.log("melonJS: '" + imagesrc + "' file for tileset '" + this.name + "' not found!"); | |
} | |
// check if transparency is defined for a specific color | |
var trans = xmltileset.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_TRANS); | |
this.initFromImage(image, trans); | |
}, | |
// constructor | |
initFromJSON: function (tileset) { | |
// first gid | |
this.firstgid = tileset[me.TMX_TAG_FIRSTGID]; | |
var src = tileset[me.TMX_TAG_SOURCE]; | |
if (src) { | |
// load TSX | |
src = me.utils.getBasename(src); | |
// replace tiletset with a local variable | |
tileset = me.loader.getTMX(src); | |
if (!tileset) { | |
throw "melonJS:" + 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[me.TMX_TAG_NAME]; | |
this.tilewidth = parseInt(tileset[me.TMX_TAG_TILEWIDTH], 10); | |
this.tileheight = parseInt(tileset[me.TMX_TAG_TILEHEIGHT], 10); | |
this.spacing = parseInt(tileset[me.TMX_TAG_SPACING] || 0, 10); | |
this.margin = parseInt(tileset[me.TMX_TAG_MARGIN] ||0, 10); | |
// set tile offset properties (if any) | |
this.tileoffset = new me.Vector2d(0,0); | |
var offset = tileset[me.TMX_TAG_TILEOFFSET]; | |
if (offset) { | |
this.tileoffset.x = parseInt(offset[me.TMX_TAG_X], 10); | |
this.tileoffset.y = parseInt(offset[me.TMX_TAG_Y], 10); | |
} | |
var tileInfo = tileset["tileproperties"]; | |
// set tile properties, if any | |
for(var i in tileInfo) { | |
var prop = {}; | |
me.TMXUtils.mergeProperties(prop, tileInfo[i]); | |
this.setTileProperty(parseInt(i, 10) + this.firstgid, prop); | |
} | |
// check for the texture corresponding image | |
var imagesrc = me.utils.getBasename(tileset[me.TMX_TAG_IMAGE]); | |
var image = imagesrc ? me.loader.getImage(imagesrc) : null; | |
if (!image) { | |
console.log("melonJS: '" + imagesrc + "' file for tileset '" + this.name + "' not found!"); | |
} | |
// check if transparency is defined for a specific color | |
var trans = tileset[me.TMX_TAG_TRANS] || null; | |
this.initFromImage(image, trans); | |
}, | |
// constructor | |
initFromImage: function (image, transparency) { | |
if (image) { | |
this.image = image; | |
// 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); | |
// set Color Key for transparency if needed | |
if (transparency !== null && this.image) { | |
// applyRGB Filter (return a context object) | |
this.image = me.video.applyRGBFilter(this.image, "transparent", transparency.toUpperCase()).canvas; | |
} | |
}, | |
/** | |
* set the tile properties | |
* @ignore | |
* @function | |
*/ | |
setTileProperty : function(gid, prop) { | |
// check what we found and adjust property | |
prop.isSolid = prop.type ? prop.type.toLowerCase() === this.type.SOLID : false; | |
prop.isPlatform = prop.type ? prop.type.toLowerCase() === this.type.PLATFORM : false; | |
prop.isLeftSlope = prop.type ? prop.type.toLowerCase() === this.type.L_SLOPE : false; | |
prop.isRightSlope = prop.type ? prop.type.toLowerCase() === this.type.R_SLOPE : false; | |
prop.isBreakable = prop.type ? prop.type.toLowerCase() === this.type.BREAKABLE : false; | |
prop.isLadder = prop.type ? prop.type.toLowerCase() === this.type.LADDER : false; | |
prop.isTopLadder = prop.type ? prop.type.toLowerCase() === this.type.TOPLADDER : false; | |
prop.isSlope = prop.isLeftSlope || prop.isRightSlope; | |
// ensure the collidable flag is correct | |
prop.isCollidable = !! (prop.type); | |
// 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.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 <br> | |
* the function will return an object with the following boolean value :<br> | |
* - isCollidable<br> | |
* - isSolid<br> | |
* - isPlatform<br> | |
* - isSlope <br> | |
* - isLeftSlope<br> | |
* - isRightSlope<br> | |
* - isLadder<br> | |
* - isBreakable<br> | |
* @name me.TMXTileset#getTileProperties | |
* @public | |
* @function | |
* @param {Number} tileId | |
* @return {Object} | |
*/ | |
getTileProperties: function(tileId) { | |
return this.TileProperties[tileId]; | |
}, | |
//return collidable status of the specifiled tile | |
isTileCollidable : function(tileId) { | |
return this.TileProperties[tileId].isCollidable; | |
}, | |
/* | |
//return collectable status of the specifiled tile | |
isTileCollectable : function (tileId) { | |
return this.TileProperties[tileId].isCollectable; | |
}, | |
*/ | |
/** | |
* 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; | |
}, | |
// draw the x,y tile | |
drawTile : function(context, dx, dy, tmxTile) { | |
// check if any transformation is required | |
if (tmxTile.flipped) { | |
var m11 = 1; // Horizontal scaling factor | |
var m12 = 0; // Vertical shearing factor | |
var m21 = 0; // Horizontal shearing factor | |
var m22 = 1; // Vertical scaling factor | |
var mx = dx; | |
var my = dy; | |
// set initial value to zero since we use a transform matrix | |
dx = dy = 0; | |
context.save(); | |
if (tmxTile.flipAD){ | |
// Use shearing to swap the X/Y axis | |
m11=0; | |
m12=1; | |
m21=1; | |
m22=0; | |
// Compensate for the swap of image dimensions | |
my += this.tileheight - this.tilewidth; | |
} | |
if (tmxTile.flipX){ | |
m11 = -m11; | |
m21 = -m21; | |
mx += tmxTile.flipAD ? this.tileheight : this.tilewidth; | |
} | |
if (tmxTile.flipY){ | |
m12 = -m12; | |
m22 = -m22; | |
my += tmxTile.flipAD ? this.tilewidth : this.tileheight; | |
} | |
// set the transform matrix | |
context.transform(m11, m12, m21, m22, mx, my); | |
} | |
// get the local tileset id | |
var tileid = tmxTile.tileId - this.firstgid; | |
// draw the tile | |
context.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 | |
context.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) { | |
if (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 "no matching tileset found for gid " + gid; | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
/** | |
* an Orthogonal Map Renderder | |
* Tiled QT 0.7.x format | |
* @memberOf me | |
* @ignore | |
* @constructor | |
*/ | |
me.TMXOrthogonalRenderer = Object.extend({ | |
// constructor | |
init: function(cols, rows, tilewidth, tileheight) { | |
this.cols = cols; | |
this.rows = rows; | |
this.tilewidth = tilewidth; | |
this.tileheight = tileheight; | |
}, | |
/** | |
* return true if the renderer can render the specified layer | |
* @ignore | |
*/ | |
canRender : function(layer) { | |
return ((layer.orientation === 'orthogonal') && | |
(this.cols === layer.cols) && | |
(this.rows === layer.rows) && | |
(this.tilewidth === layer.tilewidth) && | |
(this.tileheight === layer.tileheight)); | |
}, | |
/** | |
* return the tile position corresponding to the specified pixel | |
* @ignore | |
*/ | |
pixelToTileCoords : function(x, y) { | |
return new me.Vector2d(x / this.tilewidth, | |
y / this.tileheight); | |
}, | |
/** | |
* return the pixel position corresponding of the specified tile | |
* @ignore | |
*/ | |
tileToPixelCoords : function(x, y) { | |
return new me.Vector2d(x * this.tilewidth, | |
y * this.tileheight); | |
}, | |
/** | |
* fix the position of Objects to match | |
* the way Tiled places them | |
* @ignore | |
*/ | |
adjustPosition: function(obj) { | |
// only adjust position if obj.gid is defined | |
if (typeof(obj.gid) === 'number') { | |
// Tiled objects origin point is "bottom-left" in Tiled, | |
// "top-left" in melonJS) | |
obj.y -= obj.height; | |
} | |
}, | |
/** | |
* draw the tile map | |
* @ignore | |
*/ | |
drawTile : function(context, x, y, tmxTile, tileset) { | |
// draw the tile | |
tileset.drawTile(context, | |
tileset.tileoffset.x + x * this.tilewidth, | |
tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight, | |
tmxTile); | |
}, | |
/** | |
* draw the tile map | |
* @ignore | |
*/ | |
drawTileLayer : function(context, layer, rect) { | |
// get top-left and bottom-right tile position | |
var start = this.pixelToTileCoords(rect.pos.x, | |
rect.pos.y).floorSelf(); | |
var end = this.pixelToTileCoords(rect.pos.x + rect.width + this.tilewidth, | |
rect.pos.y + rect.height + this.tileheight).ceilSelf(); | |
//ensure we are in the valid tile range | |
end.x = end.x > this.cols ? this.cols : end.x; | |
end.y = end.y > this.rows ? this.rows : end.y; | |
// main drawing loop | |
for ( var y = start.y ; y < end.y; y++) { | |
for ( var x = start.x; x < end.x; x++) { | |
var tmxTile = layer.layerData[x][y]; | |
if (tmxTile) { | |
this.drawTile(context, x, y, tmxTile, tmxTile.tileset); | |
} | |
} | |
} | |
} | |
}); | |
/** | |
* an Isometric Map Renderder | |
* Tiled QT 0.7.x format | |
* @memberOf me | |
* @ignore | |
* @constructor | |
*/ | |
me.TMXIsometricRenderer = Object.extend({ | |
// constructor | |
init: function(cols, rows, tilewidth, tileheight) { | |
this.cols = cols; | |
this.rows = rows; | |
this.tilewidth = tilewidth; | |
this.tileheight = tileheight; | |
this.hTilewidth = tilewidth / 2; | |
this.hTileheight = tileheight / 2; | |
this.originX = this.rows * this.hTilewidth; | |
}, | |
/** | |
* return true if the renderer can render the specified layer | |
* @ignore | |
*/ | |
canRender : function(layer) { | |
return ((layer.orientation === 'isometric') && | |
(this.cols === layer.cols) && | |
(this.rows === layer.rows) && | |
(this.tilewidth === layer.tilewidth) && | |
(this.tileheight === layer.tileheight)); | |
}, | |
/** | |
* return the tile position corresponding to the specified pixel | |
* @ignore | |
*/ | |
pixelToTileCoords : function(x, y) { | |
x -= this.originX; | |
var tileY = y / this.tileheight; | |
var tileX = x / this.tilewidth; | |
return new me.Vector2d(tileY + tileX, tileY - tileX); | |
}, | |
/** | |
* return the pixel position corresponding of the specified tile | |
* @ignore | |
*/ | |
tileToPixelCoords : function(x, y) { | |
return new me.Vector2d((x - y) * this.hTilewidth + this.originX, | |
(x + y) * this.hTileheight); | |
}, | |
/** | |
* fix the position of Objects to match | |
* the way Tiled places them | |
* @ignore | |
*/ | |
adjustPosition: function(obj){ | |
var tilex = obj.x/this.hTilewidth; | |
var tiley = obj.y/this.tileheight; | |
var isoPos = this.tileToPixelCoords(tilex, tiley); | |
isoPos.x -= obj.width/2; | |
isoPos.y -= obj.height; | |
obj.x = isoPos.x; | |
obj.y = isoPos.y; | |
//return isoPos; | |
}, | |
/** | |
* draw the tile map | |
* @ignore | |
*/ | |
drawTile : function(context, x, y, tmxTile, tileset) { | |
// draw the tile | |
tileset.drawTile(context, | |
((this.cols-1) * tileset.tilewidth + (x-y) * tileset.tilewidth>>1), | |
(-tileset.tilewidth + (x+y) * tileset.tileheight>>2), | |
tmxTile); | |
}, | |
/** | |
* draw the tile map | |
* @ignore | |
*/ | |
drawTileLayer : function(context, layer, rect) { | |
// cache a couple of useful references | |
var tileset = layer.tileset; | |
var offset = tileset.tileoffset; | |
// get top-left and bottom-right tile position | |
var rowItr = this.pixelToTileCoords(rect.pos.x - tileset.tilewidth, | |
rect.pos.y - tileset.tileheight).floorSelf(); | |
var TileEnd = this.pixelToTileCoords(rect.pos.x + rect.width + tileset.tilewidth, | |
rect.pos.y + rect.height + tileset.tileheight).ceilSelf(); | |
var rectEnd = this.tileToPixelCoords(TileEnd.x, TileEnd.y); | |
// Determine the tile and pixel coordinates to start at | |
var startPos = this.tileToPixelCoords(rowItr.x, rowItr.y); | |
startPos.x -= this.hTilewidth; | |
startPos.y += this.tileheight; | |
/* Determine in which half of the tile the top-left corner of the area we | |
* need to draw is. If we're in the upper half, we need to start one row | |
* up due to those tiles being visible as well. How we go up one row | |
* depends on whether we're in the left or right half of the tile. | |
*/ | |
var inUpperHalf = startPos.y - rect.pos.y > this.hTileheight; | |
var inLeftHalf = rect.pos.x - startPos.x < this.hTilewidth; | |
if (inUpperHalf) { | |
if (inLeftHalf) { | |
rowItr.x--; | |
startPos.x -= this.hTilewidth; | |
} else { | |
rowItr.y--; | |
startPos.x += this.hTilewidth; | |
} | |
startPos.y -= this.hTileheight; | |
} | |
// Determine whether the current row is shifted half a tile to the right | |
var shifted = inUpperHalf ^ inLeftHalf; | |
// initialize the columItr vector | |
var columnItr = rowItr.clone(); | |
// main drawing loop | |
for (var y = startPos.y; y - this.tileheight < rectEnd.y; y += this.hTileheight) { | |
columnItr.setV(rowItr); | |
for (var x = startPos.x; x < rectEnd.x; x += this.tilewidth) { | |
//check if it's valid tile, if so render | |
if ((columnItr.x >= 0) && (columnItr.y >= 0) && (columnItr.x < this.cols) && (columnItr.y < this.rows)) | |
{ | |
var tmxTile = layer.layerData[columnItr.x][columnItr.y]; | |
if (tmxTile) { | |
tileset = tmxTile.tileset; | |
// offset could be different per tileset | |
offset = tileset.tileoffset; | |
// draw our tile | |
tileset.drawTile(context, offset.x + x, offset.y + y - tileset.tileheight, tmxTile); | |
} | |
} | |
// Advance to the next column | |
columnItr.x++; | |
columnItr.y--; | |
} | |
// Advance to the next row | |
if (!shifted) { | |
rowItr.x++; | |
startPos.x += this.hTilewidth; | |
shifted = true; | |
} else { | |
rowItr.y++; | |
startPos.x -= this.hTilewidth; | |
shifted = false; | |
} | |
} | |
} | |
}); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* a generic Color Layer Object | |
* @class | |
* @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.parent(new me.Vector2d(0, 0), Infinity, Infinity); | |
// apply given parameters | |
this.name = name; | |
this.color = me.utils.HexToRGB(color); | |
this.z = z; | |
}, | |
/** | |
* reset function | |
* @ignore | |
* @function | |
*/ | |
reset : function() { | |
// nothing to do here | |
}, | |
/** | |
* update function | |
* @ignore | |
* @function | |
*/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* draw the color layer | |
* @ignore | |
*/ | |
draw : function(context, rect) { | |
// set layer opacity | |
var _alpha = context.globalAlpha; | |
context.globalAlpha *= this.getOpacity(); | |
// set layer color | |
context.fillStyle = this.color; | |
// clear the specified rect | |
context.fillRect(rect.left, rect.top, rect.width, rect.height); | |
// restore context alpha value | |
context.globalAlpha = _alpha; | |
} | |
}); | |
/** | |
* a generic Image Layer Object | |
* @class | |
* @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({ | |
/** | |
* 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 | |
*/ | |
//repeat: 'repeat', (define through getter/setter | |
/** | |
* 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 | |
*/ | |
//ratio: new me.Vector2d(1.0, 1.0), | |
/** | |
* 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 "melonJS: '" + 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.parent(new me.Vector2d(0, 0), width, height); | |
// displaying order | |
this.z = z; | |
// default ratio for parallax | |
this.ratio = new me.Vector2d(1.0, 1.0); | |
if (ratio) { | |
// 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; | |
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)); | |
}, | |
/** | |
* reset function | |
* @ignore | |
* @function | |
*/ | |
reset : 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; | |
}, | |
/** | |
* updateLayer function | |
* @ignore | |
* @function | |
*/ | |
updateLayer : function(vpos) { | |
if (0 === this.ratio.x && 0 === this.ratio.y) { | |
// static image | |
return; | |
} else { | |
// 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; | |
this.lastpos.setV(vpos); | |
} | |
}, | |
/** | |
* update function | |
* @ignore | |
* @function | |
*/ | |
update : function() { | |
// this one will be repainted anyway | |
// if the viewport change | |
// note : this will not work later if | |
// we re-introduce a dirty rect algorithm ? | |
return false; | |
}, | |
/** | |
* draw the image layer | |
* @ignore | |
*/ | |
draw : function(context, rect) { | |
// save current context state | |
context.save(); | |
// translate default position using the anchorPoint value | |
if (this.anchorPoint.y !==0 || this.anchorPoint.x !==0) { | |
var viewport = me.game.viewport; | |
context.translate ( | |
~~(this.anchorPoint.x * (viewport.width - this.imagewidth)), | |
~~(this.anchorPoint.y * (viewport.height - this.imageheight)) | |
); | |
} | |
// set the layer alpha value | |
context.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); | |
context.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 | |
// todo ; broken with dirtyRect enabled | |
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 { | |
context.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 ); | |
} | |
// restore context state | |
context.restore(); | |
}, | |
// called when the layer is destroyed | |
destroy : function() { | |
this.reset(); | |
}, | |
}); | |
/** | |
* a generic collision tile based layer object | |
* @memberOf me | |
* @ignore | |
* @constructor | |
*/ | |
me.CollisionTiledLayer = me.Renderable.extend({ | |
// constructor | |
init: function(width, height) { | |
this.parent(new me.Vector2d(0, 0), width, height); | |
this.isCollisionMap = true; | |
}, | |
/** | |
* reset function | |
* @ignore | |
* @function | |
*/ | |
reset : function() { | |
// nothing to do here | |
}, | |
/** | |
* only test for the world limit | |
* @ignore | |
**/ | |
checkCollision : function(obj, pv) { | |
var x = (pv.x < 0) ? obj.left + pv.x : obj.right + pv.x; | |
var y = (pv.y < 0) ? obj.top + pv.y : obj.bottom + pv.y; | |
//to return tile collision detection | |
var res = { | |
x : 0, // !=0 if collision on x axis | |
y : 0, // !=0 if collision on y axis | |
xprop : {}, | |
yprop : {} | |
}; | |
// test x limits | |
if (x <= 0 || x >= this.width) { | |
res.x = pv.x; | |
} | |
// test y limits | |
if (y <= 0 || y >= this.height) { | |
res.y = pv.y; | |
} | |
// return the collide object if collision | |
return res; | |
} | |
}); | |
/** | |
* a TMX Tile Layer Object | |
* Tiled QT 0.7.x format | |
* @class | |
* @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({ | |
// the layer data array | |
layerData : null, | |
/** @ignore */ | |
init: function(tilewidth, tileheight, orientation, tilesets, zOrder) { | |
// parent constructor | |
this.parent(new me.Vector2d(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; | |
// for displaying order | |
this.z = zOrder; | |
}, | |
/** @ignore */ | |
initFromXML: function(layer) { | |
// additional TMX flags | |
this.name = me.mapReader.TMXParser.getStringAttribute(layer, me.TMX_TAG_NAME); | |
this.visible = (me.mapReader.TMXParser.getIntAttribute(layer, me.TMX_TAG_VISIBLE, 1) === 1); | |
this.cols = me.mapReader.TMXParser.getIntAttribute(layer, me.TMX_TAG_WIDTH); | |
this.rows = me.mapReader.TMXParser.getIntAttribute(layer, me.TMX_TAG_HEIGHT); | |
// layer opacity | |
this.setOpacity(me.mapReader.TMXParser.getFloatAttribute(layer, me.TMX_TAG_OPACITY, 1.0)); | |
// layer "real" size | |
this.width = this.cols * this.tilewidth; | |
this.height = this.rows * this.tileheight; | |
// check if we have any user-defined properties | |
me.TMXUtils.applyTMXPropertiesFromXML(this, layer); | |
// check for the correct rendering method | |
if (typeof (this.preRender) === 'undefined') { | |
this.preRender = me.sys.preRender; | |
} | |
// detect if the layer is a collision map | |
this.isCollisionMap = (this.name.toLowerCase().contains(me.COLLISION_LAYER)); | |
if (this.isCollisionMap && !me.debug.renderCollisionMap) { | |
// force the layer as invisible | |
this.visible = false; | |
} | |
// if pre-rendering method is use, create the offline canvas | |
if (this.preRender === true) { | |
this.layerCanvas = me.video.createCanvas(this.cols * this.tilewidth, this.rows * this.tileheight); | |
this.layerSurface = me.video.getContext2d(this.layerCanvas); | |
} | |
}, | |
/** @ignore */ | |
initFromJSON: function(layer) { | |
// additional TMX flags | |
this.name = layer[me.TMX_TAG_NAME]; | |
this.visible = layer[me.TMX_TAG_VISIBLE]; | |
this.cols = parseInt(layer[me.TMX_TAG_WIDTH], 10); | |
this.rows = parseInt(layer[me.TMX_TAG_HEIGHT], 10); | |
// layer opacity | |
this.setOpacity(parseFloat(layer[me.TMX_TAG_OPACITY])); | |
// layer "real" size | |
this.width = this.cols * this.tilewidth; | |
this.height = this.rows * this.tileheight; | |
// check if we have any user-defined properties | |
me.TMXUtils.applyTMXPropertiesFromJSON(this, layer); | |
// check for the correct rendering method | |
if (typeof (this.preRender) === 'undefined') { | |
this.preRender = me.sys.preRender; | |
} | |
// detect if the layer is a collision map | |
this.isCollisionMap = (this.name.toLowerCase().contains(me.COLLISION_LAYER)); | |
if (this.isCollisionMap && !me.debug.renderCollisionMap) { | |
// force the layer as invisible | |
this.visible = false; | |
} | |
// if pre-rendering method is use, create the offline canvas | |
if (this.preRender === true) { | |
this.layerCanvas = me.video.createCanvas(this.cols * this.tilewidth, this.rows * this.tileheight); | |
this.layerSurface = me.video.getContext2d(this.layerCanvas); | |
} | |
}, | |
/** | |
* reset function | |
* @ignore | |
* @function | |
*/ | |
reset : 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; | |
}, | |
/** | |
* 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[~~(x / this.tilewidth)][~~(y / this.tileheight)]; | |
}, | |
/** | |
* 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.visible && this.preRender) { | |
this.layerSurface.clearRect(x * this.tilewidth, y * this.tileheight, this.tilewidth, this.tileheight); | |
} | |
}, | |
/** | |
* check for collision | |
* obj - obj | |
* pv - projection vector | |
* res : result collision object | |
* @ignore | |
*/ | |
checkCollision : function(obj, pv) { | |
var x = (pv.x < 0) ? ~~(obj.left + pv.x) : Math.ceil(obj.right - 1 + pv.x); | |
var y = (pv.y < 0) ? ~~(obj.top + pv.y) : Math.ceil(obj.bottom - 1 + pv.y); | |
//to return tile collision detection | |
var res = { | |
x : 0, // !=0 if collision on x axis | |
xtile : undefined, | |
xprop : {}, | |
y : 0, // !=0 if collision on y axis | |
ytile : undefined, | |
yprop : {} | |
}; | |
//var tile; | |
if (x <= 0 || x >= this.width) { | |
res.x = pv.x; | |
} else if (pv.x !== 0 ) { | |
// x, bottom corner | |
res.xtile = this.getTile(x, Math.ceil(obj.bottom - 1)); | |
if (res.xtile && this.tileset.isTileCollidable(res.xtile.tileId)) { | |
res.x = pv.x; // reuse pv.x to get a | |
res.xprop = this.tileset.getTileProperties(res.xtile.tileId); | |
} else { | |
// x, top corner | |
res.xtile = this.getTile(x, ~~obj.top); | |
if (res.xtile && this.tileset.isTileCollidable(res.xtile.tileId)) { | |
res.x = pv.x; | |
res.xprop = this.tileset.getTileProperties(res.xtile.tileId); | |
} | |
} | |
} | |
// check for y movement | |
// left, y corner | |
res.ytile = this.getTile((pv.x < 0) ? ~~obj.left : Math.ceil(obj.right - 1), y); | |
if (res.ytile && this.tileset.isTileCollidable(res.ytile.tileId)) { | |
res.y = pv.y || 1; | |
res.yprop = this.tileset.getTileProperties(res.ytile.tileId); | |
} else { // right, y corner | |
res.ytile = this.getTile((pv.x < 0) ? Math.ceil(obj.right - 1) : ~~obj.left, y); | |
if (res.ytile && this.tileset.isTileCollidable(res.ytile.tileId)) { | |
res.y = pv.y || 1; | |
res.yprop = this.tileset.getTileProperties(res.ytile.tileId); | |
} | |
} | |
// return the collide object | |
return res; | |
}, | |
/** | |
* a dummy update function | |
* @ignore | |
*/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* draw a tileset layer | |
* @ignore | |
*/ | |
draw : function(context, 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 = context.globalAlpha * this.getOpacity(); | |
// draw using the cached canvas | |
context.drawImage(this.layerCanvas, | |
rect.pos.x, //sx | |
rect.pos.y, //sy | |
width, height, //sw, sh | |
rect.pos.x, //dx | |
rect.pos.y, //dy | |
width, height); //dw, dh | |
} | |
// dynamically render the layer | |
else { | |
// set the layer alpha value | |
var _alpha = context.globalAlpha; | |
context.globalAlpha *= this.getOpacity(); | |
// draw the layer | |
this.renderer.drawTileLayer(context, this, rect); | |
// restore context to initial state | |
context.globalAlpha = _alpha; | |
} | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function($) { | |
/** | |
* a TMX Tile Map Object | |
* Tiled QT 0.7.x format | |
* @class | |
* @memberOf me | |
* @constructor | |
* @param {String} levelId name of TMX map | |
*/ | |
me.TMXTileMap = me.Renderable.extend({ | |
// constructor | |
init: function(levelId) { | |
// map id | |
this.levelId = levelId; | |
// map default z order | |
this.z = 0; | |
/** | |
* name of the tilemap | |
* @public | |
* @type String | |
* @name me.TMXTileMap#name | |
*/ | |
this.name = null; | |
/** | |
* width of the tilemap in tiles | |
* @public | |
* @type Int | |
* @name me.TMXTileMap#cols | |
*/ | |
this.cols = 0; | |
/** | |
* height of the tilemap in tiles | |
* @public | |
* @type Int | |
* @name me.TMXTileMap#rows | |
*/ | |
this.rows = 0; | |
/** | |
* Tile width | |
* @public | |
* @type Int | |
* @name me.TMXTileMap#tilewidth | |
*/ | |
this.tilewidth = 0; | |
/** | |
* Tile height | |
* @public | |
* @type Int | |
* @name me.TMXTileMap#tileheight | |
*/ | |
this.tileheight = 0; | |
// corresponding tileset for this map | |
this.tilesets = null; | |
// map layers | |
this.mapLayers = []; | |
// map Object | |
this.objectGroups = []; | |
// loading flag | |
this.initialized = false; | |
// tilemap version | |
this.version = ""; | |
// map type (only orthogonal format supported) | |
this.orientation = ""; | |
// tileset(s) | |
this.tilesets = null; | |
this.parent(new me.Vector2d(), 0, 0); | |
}, | |
/** | |
* a dummy update function | |
* @ignore | |
*/ | |
reset : function() { | |
if (this.initialized === true) { | |
var i; | |
// reset/clear all layers | |
for (i = this.mapLayers.length; i--;) { | |
this.mapLayers[i].reset(); | |
this.mapLayers[i] = null; | |
} | |
// reset object groups | |
for (i = this.objectGroups.length; i--;) { | |
this.objectGroups[i].reset(); | |
this.objectGroups[i] = null; | |
} | |
// call parent reset function | |
this.tilesets = null; | |
this.mapLayers.length = 0; | |
this.objectGroups.length = 0; | |
this.pos.set(0,0); | |
// set back as not initialized | |
this.initialized = false; | |
} | |
}, | |
/** | |
* return the corresponding object group definition | |
* @name me.TMXTileMap#getObjectGroupByName | |
* @public | |
* @function | |
* @return {me.TMXObjectGroup} group | |
*/ | |
getObjectGroupByName : function(name) { | |
var objectGroup = null; | |
// normalize name | |
name = name.trim().toLowerCase(); | |
for ( var i = this.objectGroups.length; i--;) { | |
if (this.objectGroups[i].name.toLowerCase().contains(name)) { | |
objectGroup = this.objectGroups[i]; | |
break; | |
} | |
} | |
return objectGroup; | |
}, | |
/** | |
* return all the existing object group definition | |
* @name me.TMXTileMap#getObjectGroups | |
* @public | |
* @function | |
* @return {me.TMXObjectGroup[]} Array of Groups | |
*/ | |
getObjectGroups : function() { | |
return this.objectGroups; | |
}, | |
/** | |
* return all the existing layers | |
* @name me.TMXTileMap#getLayers | |
* @public | |
* @function | |
* @return {me.TMXLayer[]} Array of Layers | |
*/ | |
getLayers : function() { | |
return this.mapLayers; | |
}, | |
/** | |
* return the specified layer object | |
* @name me.TMXTileMap#getLayerByName | |
* @public | |
* @function | |
* @param {String} name Layer Name | |
* @return {me.TMXLayer} Layer Object | |
*/ | |
getLayerByName : function(name) { | |
var layer = null; | |
// normalize name | |
name = name.trim().toLowerCase(); | |
for ( var i = this.mapLayers.length; i--;) { | |
if (this.mapLayers[i].name.toLowerCase().contains(name)) { | |
layer = this.mapLayers[i]; | |
break; | |
} | |
} | |
// return a fake collision layer if not found | |
if ((name.toLowerCase().contains(me.COLLISION_LAYER)) && (layer == null)) { | |
layer = new me.CollisionTiledLayer( | |
me.game.currentLevel.width, | |
me.game.currentLevel.height | |
); | |
} | |
return layer; | |
}, | |
/** | |
* clear the tile at the specified position from all layers | |
* @name me.TMXTileMap#clearTile | |
* @public | |
* @function | |
* @param {Number} x x position | |
* @param {Number} y y position | |
*/ | |
clearTile : function(x, y) { | |
// add all layers | |
for ( var i = this.mapLayers.length; i--;) { | |
// that are visible | |
if (this.mapLayers[i] instanceof me.TMXLayer) { | |
this.mapLayers[i].clearTile(x, y); | |
} | |
} | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Tile QT 0.7.x format | |
* http://www.mapeditor.org/ | |
* | |
*/ | |
(function(window) { | |
/** | |
* a TMX Map Reader | |
* Tiled QT 0.7.x format | |
* @class | |
* @memberOf me | |
* @constructor | |
* @ignore | |
*/ | |
me.TMXMapReader = Object.extend({ | |
XMLReader : null, | |
JSONReader : null, | |
// temporary, the time to | |
// rewrite the rest properly | |
TMXParser: null, | |
readMap: function (map) { | |
// if already loaded, do nothing | |
if (map.initialized) { | |
return; | |
} | |
if (me.loader.getTMXFormat(map.levelId) === 'xml') { | |
// create an instance of the XML Reader | |
if (this.XMLReader === null) { | |
this.XMLReader = new XMLMapReader(); | |
} | |
this.TMXParser = this.XMLReader.TMXParser; | |
// load the map | |
this.XMLReader.readXMLMap(map, me.loader.getTMX(map.levelId)); | |
} | |
else /*JSON*/ { | |
// create an instance of the JSON Reader | |
if (this.JSONReader === null) { | |
this.JSONReader = new JSONMapReader(); | |
} | |
this.JSONReader.readJSONMap(map, me.loader.getTMX(map.levelId)); | |
} | |
// center the map if smaller than the current viewport | |
if ((map.width < me.game.viewport.width) || | |
(map.height < me.game.viewport.height)) { | |
var shiftX = ~~( (me.game.viewport.width - map.width) / 2); | |
var shiftY = ~~( (me.game.viewport.height - map.height) / 2); | |
// update the map default screen position | |
map.pos.add({x:shiftX > 0 ? shiftX : 0 , y:shiftY > 0 ? shiftY : 0} ); | |
} | |
// flag as loaded | |
map.initialized = true; | |
}, | |
/** | |
* set a compatible renderer object | |
* for the specified map | |
* TODO : put this somewhere else | |
* @ignore | |
*/ | |
getNewDefaultRenderer: function (obj) { | |
switch (obj.orientation) { | |
case "orthogonal": | |
return new me.TMXOrthogonalRenderer(obj.cols, obj.rows, obj.tilewidth, obj.tileheight); | |
case "isometric": | |
return new me.TMXIsometricRenderer(obj.cols, obj.rows , obj.tilewidth, obj.tileheight); | |
// if none found, throw an exception | |
default: | |
throw "melonJS: " + obj.orientation + " type TMX Tile Map not supported!"; | |
} | |
}, | |
/** | |
* Set tiled layer Data | |
* @ignore | |
*/ | |
setLayerData : function(layer, data, encoding, compression) { | |
// initialize the layer data array | |
layer.initArray(layer.cols, layer.rows); | |
// decode data based on encoding type | |
switch (encoding) { | |
// XML encoding | |
case null: | |
data = data.getElementsByTagName(me.TMX_TAG_TILE); | |
break; | |
// json encoding | |
case 'json': | |
// do nothing as data can be directly reused | |
break; | |
// CSV encoding | |
case me.TMX_TAG_CSV: | |
// Base 64 encoding | |
case me.TMX_TAG_ATTR_BASE64: | |
// Merge all childNodes[].nodeValue into a single one | |
var nodeValue = ''; | |
for ( var i = 0, len = data.childNodes.length; i < len; i++) { | |
nodeValue += data.childNodes[i].nodeValue; | |
} | |
// and then decode them | |
if (encoding === me.TMX_TAG_CSV) { | |
// CSV decode | |
data = me.utils.decodeCSV(nodeValue, layer.cols); | |
} else { | |
// Base 64 decode | |
data = me.utils.decodeBase64AsArray(nodeValue, 4); | |
// check if data is compressed | |
if (compression !== null) { | |
data = me.utils.decompress(data, compression); | |
} | |
} | |
// ensure nodeValue is deallocated | |
nodeValue = null; | |
break; | |
default: | |
throw "melonJS: TMX Tile Map " + encoding + " encoding not supported!"; | |
} | |
var idx = 0; | |
// set everything | |
for ( var y = 0 ; y <layer.rows; y++) { | |
for ( var x = 0; x <layer.cols; x++) { | |
// get the value of the gid | |
var gid = (encoding == null) ? this.TMXParser.getIntAttribute(data[idx++], me.TMX_TAG_GID) : data[idx++]; | |
// fill the array | |
if (gid !== 0) { | |
// add a new tile to the layer | |
var tile = layer.setTile(x, y, gid); | |
// draw the corresponding tile | |
if (layer.visible && layer.preRender) { | |
layer.renderer.drawTile(layer.layerSurface, x, y, tile, tile.tileset); | |
} | |
} | |
} | |
} | |
} | |
}); | |
/** | |
* a basic TMX/TSX Parser | |
* @class | |
* @constructor | |
* @ignore | |
**/ | |
function _TinyTMXParser() { | |
var parserObj = { | |
tmxDoc : null, | |
// parse a TMX XML file | |
setData : function(data) { | |
this.tmxDoc = data; | |
}, | |
getFirstElementByTagName : function(name) { | |
return this.tmxDoc ? this.tmxDoc.getElementsByTagName(name)[0] : null; | |
}, | |
getAllTagElements : function() { | |
return this.tmxDoc ? this.tmxDoc.getElementsByTagName('*') : null; | |
}, | |
getStringAttribute : function(elt, str, val) { | |
var ret = elt.getAttribute(str); | |
return ret ? ret.trim() : val; | |
}, | |
getIntAttribute : function(elt, str, val) { | |
var ret = this.getStringAttribute(elt, str, val); | |
return ret ? parseInt(ret, 10) : val; | |
}, | |
getFloatAttribute : function(elt, str, val) { | |
var ret = this.getStringAttribute(elt, str, val); | |
return ret ? parseFloat(ret) : val; | |
}, | |
getBooleanAttribute : function(elt, str, val) { | |
var ret = this.getStringAttribute(elt, str, val); | |
return ret ? (ret === "true") : val; | |
}, | |
// free the allocated parser | |
free : function() { | |
this.tmxDoc = null; | |
} | |
}; | |
return parserObj; | |
} | |
/** | |
* a XML Map Reader | |
* Tiled QT 0.7.x format | |
* @class | |
* @memberOf me | |
* @constructor | |
* @ignore | |
*/ | |
var XMLMapReader = me.TMXMapReader.extend({ | |
TMXParser : null, | |
init: function(){ | |
if (!this.TMXParser) { | |
this.TMXParser = new _TinyTMXParser(); | |
} | |
}, | |
/** | |
* initialize a map using XML data | |
* @ignore | |
*/ | |
readXMLMap : function(map, data) { | |
if (!data) { | |
throw "melonJS:" + map.levelId + " TMX map not found"; | |
} | |
// to automatically increment z index | |
var zOrder = 0; | |
// init the parser | |
this.TMXParser.setData(data); | |
// retreive all the elements of the XML file | |
var xmlElements = this.TMXParser.getAllTagElements(); | |
// parse all tags | |
for ( var i = 0; i < xmlElements.length; i++) { | |
// check each Tag | |
switch (xmlElements.item(i).nodeName) { | |
// get the map information | |
case me.TMX_TAG_MAP: | |
var elements = xmlElements.item(i); | |
map.version = this.TMXParser.getStringAttribute(elements, me.TMX_TAG_VERSION); | |
map.orientation = this.TMXParser.getStringAttribute(elements, me.TMX_TAG_ORIENTATION); | |
map.cols = this.TMXParser.getIntAttribute(elements, me.TMX_TAG_WIDTH); | |
map.rows = this.TMXParser.getIntAttribute(elements, me.TMX_TAG_HEIGHT); | |
map.tilewidth = this.TMXParser.getIntAttribute(elements, me.TMX_TAG_TILEWIDTH); | |
map.tileheight = this.TMXParser.getIntAttribute(elements, me.TMX_TAG_TILEHEIGHT); | |
map.width = map.cols * map.tilewidth; | |
map.height = map.rows * map.tileheight; | |
map.backgroundcolor = this.TMXParser.getStringAttribute(elements, me.TMX_BACKGROUND_COLOR); | |
map.z = zOrder++; | |
// set the map properties (if any) | |
me.TMXUtils.applyTMXPropertiesFromXML(map, elements); | |
// check if a user-defined background color is defined | |
map.background_color = map.backgroundcolor ? map.backgroundcolor : map.background_color; | |
if (map.background_color) { | |
map.mapLayers.push(new me.ColorLayer("background_color", | |
map.background_color, | |
zOrder++)); | |
} | |
// check if a background image is defined | |
if (map.background_image) { | |
// add a new image layer | |
map.mapLayers.push(new me.ImageLayer("background_image", | |
map.width, map.height, | |
map.background_image, | |
zOrder++)); | |
} | |
// initialize a default renderer | |
if ((me.game.renderer === null) || !me.game.renderer.canRender(map)) { | |
me.game.renderer = this.getNewDefaultRenderer(map); | |
} | |
break; | |
// get the tileset information | |
case me.TMX_TAG_TILESET: | |
// Initialize our object if not yet done | |
if (!map.tilesets) { | |
map.tilesets = new me.TMXTilesetGroup(); | |
} | |
// add the new tileset | |
map.tilesets.add(this.readTileset(xmlElements.item(i))); | |
break; | |
// get image layer information | |
case me.TMX_TAG_IMAGE_LAYER: | |
map.mapLayers.push(this.readImageLayer(map, xmlElements.item(i), zOrder++)); | |
break; | |
// get the layer(s) information | |
case me.TMX_TAG_LAYER: | |
// regular layer or collision layer | |
map.mapLayers.push(this.readLayer(map, xmlElements.item(i), zOrder++)); | |
break; | |
// get the object groups information | |
case me.TMX_TAG_OBJECTGROUP: | |
map.objectGroups.push(this.readObjectGroup(map, xmlElements.item(i), zOrder++)); | |
break; | |
default: | |
// ignore unrecognized tags | |
break; | |
} // end switch | |
} // end for | |
// free the TMXParser ressource | |
this.TMXParser.free(); | |
}, | |
readLayer: function (map, data, z) { | |
var layer = new me.TMXLayer(map.tilewidth, map.tileheight, map.orientation, map.tilesets, z); | |
// init the layer properly | |
layer.initFromXML(data); | |
// check data encoding/compression type | |
var layerData = data.getElementsByTagName(me.TMX_TAG_DATA)[0]; | |
var encoding = this.TMXParser.getStringAttribute(layerData, me.TMX_TAG_ENCODING, null); | |
var compression = this.TMXParser.getStringAttribute(layerData, me.TMX_TAG_COMPRESSION, null); | |
// make sure this is not happening | |
if (encoding === '') { | |
encoding = null; | |
} | |
if (compression === '') { | |
compression = null; | |
} | |
// associate a renderer to the layer (if not a collision layer) | |
if (!layer.isCollisionMap || me.debug.renderCollisionMap) { | |
if (!me.game.renderer.canRender(layer)) { | |
layer.setRenderer(me.mapReader.getNewDefaultRenderer(layer)); | |
} else { | |
// use the default one | |
layer.setRenderer(me.game.renderer); | |
} | |
} | |
// parse the layer data | |
this.setLayerData(layer, layerData, encoding, compression); | |
// free layerData | |
layerData = null; | |
return layer; | |
}, | |
readImageLayer: function(map, data, z) { | |
// extract layer information | |
var iln = this.TMXParser.getStringAttribute(data, me.TMX_TAG_NAME); | |
var ilw = this.TMXParser.getIntAttribute(data, me.TMX_TAG_WIDTH); | |
var ilh = this.TMXParser.getIntAttribute(data, me.TMX_TAG_HEIGHT); | |
var ilsrc = data.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE); | |
// create the layer | |
var imageLayer = new me.ImageLayer(iln, ilw * map.tilewidth, ilh * map.tileheight, ilsrc, z); | |
// set some additional flags | |
imageLayer.visible = (this.TMXParser.getIntAttribute(data, me.TMX_TAG_VISIBLE, 1) === 1); | |
imageLayer.setOpacity(this.TMXParser.getFloatAttribute(data, me.TMX_TAG_OPACITY, 1.0)); | |
// check if we have any properties | |
me.TMXUtils.applyTMXPropertiesFromXML(imageLayer, data); | |
// make sure ratio is a vector (backward compatibility) | |
if (typeof(imageLayer.ratio) === "number") { | |
imageLayer.ratio = new me.Vector2d(parseFloat(imageLayer.ratio), parseFloat(imageLayer.ratio)); | |
} | |
// add the new layer | |
return imageLayer; | |
}, | |
readTileset : function (data) { | |
var tileset = new me.TMXTileset(); | |
tileset.initFromXML(data); | |
return tileset; | |
}, | |
readObjectGroup: function(map, data, z) { | |
var name = this.TMXParser.getStringAttribute(data, me.TMX_TAG_NAME); | |
var group = new me.TMXObjectGroup(); | |
group.initFromXML(name, data, map.tilesets, z); | |
return group; | |
} | |
}); | |
/** | |
* a JSON Map Reader | |
* Tiled QT 0.7.x format | |
* @class | |
* @memberOf me | |
* @constructor | |
* @ignore | |
*/ | |
var JSONMapReader = me.TMXMapReader.extend({ | |
readJSONMap: function (map, data) { | |
if (!data) { | |
throw "melonJS:" + map.levelId + " TMX map not found"; | |
} | |
// to automatically increment z index | |
var zOrder = 0; | |
// keep a reference to our scope | |
var self = this; | |
// map information | |
map.version = data[me.TMX_TAG_VERSION]; | |
map.orientation = data[me.TMX_TAG_ORIENTATION]; | |
map.cols = parseInt(data[me.TMX_TAG_WIDTH], 10); | |
map.rows = parseInt(data[me.TMX_TAG_HEIGHT], 10); | |
map.tilewidth = parseInt(data[me.TMX_TAG_TILEWIDTH], 10); | |
map.tileheight = parseInt(data[me.TMX_TAG_TILEHEIGHT], 10); | |
map.width = map.cols * map.tilewidth; | |
map.height = map.rows * map.tileheight; | |
map.backgroundcolor = data[me.TMX_BACKGROUND_COLOR]; | |
map.z = zOrder++; | |
// set the map properties (if any) | |
me.TMXUtils.applyTMXPropertiesFromJSON(map, data); | |
// check if a user-defined background color is defined | |
map.background_color = map.backgroundcolor ? map.backgroundcolor : map.background_color; | |
if (map.background_color) { | |
map.mapLayers.push(new me.ColorLayer("background_color", | |
map.background_color, | |
zOrder++)); | |
} | |
// check if a background image is defined | |
if (map.background_image) { | |
// add a new image layer | |
map.mapLayers.push(new me.ImageLayer("background_image", | |
map.width, map.height, | |
map.background_image, | |
zOrder++)); | |
} | |
// initialize a default renderer | |
if ((me.game.renderer === null) || !me.game.renderer.canRender(map)) { | |
me.game.renderer = this.getNewDefaultRenderer(map); | |
} | |
// Tileset information | |
if (!map.tilesets) { | |
// make sure we have a TilesetGroup Object | |
map.tilesets = new me.TMXTilesetGroup(); | |
} | |
// parse all tileset objects | |
data["tilesets"].forEach(function(tileset) { | |
// add the new tileset | |
map.tilesets.add(self.readTileset(tileset)); | |
}); | |
// get layers information | |
data["layers"].forEach(function(layer) { | |
switch (layer.type) { | |
case me.TMX_TAG_IMAGE_LAYER : | |
map.mapLayers.push(self.readImageLayer(map, layer, zOrder++)); | |
break; | |
case me.TMX_TAG_TILE_LAYER : | |
map.mapLayers.push(self.readLayer(map, layer, zOrder++)); | |
break; | |
// get the object groups information | |
case me.TMX_TAG_OBJECTGROUP: | |
map.objectGroups.push(self.readObjectGroup(map, layer, zOrder++)); | |
break; | |
default: break; | |
} | |
}); | |
// FINISH ! | |
}, | |
readLayer: function (map, data, z) { | |
var layer = new me.TMXLayer(map.tilewidth, map.tileheight, map.orientation, map.tilesets, z); | |
// init the layer properly | |
layer.initFromJSON(data); | |
// associate a renderer to the layer (if not a collision layer) | |
if (!layer.isCollisionMap) { | |
if (!me.game.renderer.canRender(layer)) { | |
layer.setRenderer(me.mapReader.getNewDefaultRenderer(layer)); | |
} else { | |
// use the default one | |
layer.setRenderer(me.game.renderer); | |
} | |
} | |
// parse the layer data | |
this.setLayerData(layer, data[me.TMX_TAG_DATA], 'json', null); | |
return layer; | |
}, | |
readImageLayer: function(map, data, z) { | |
// extract layer information | |
var iln = data[me.TMX_TAG_NAME]; | |
var ilw = parseInt(data[me.TMX_TAG_WIDTH], 10); | |
var ilh = parseInt(data[me.TMX_TAG_HEIGHT], 10); | |
var ilsrc = data[me.TMX_TAG_IMAGE]; | |
// create the layer | |
var imageLayer = new me.ImageLayer(iln, ilw * map.tilewidth, ilh * map.tileheight, ilsrc, z); | |
// set some additional flags | |
imageLayer.visible = data[me.TMX_TAG_VISIBLE]; | |
imageLayer.setOpacity(parseFloat(data[me.TMX_TAG_OPACITY])); | |
// check if we have any additional properties | |
me.TMXUtils.applyTMXPropertiesFromJSON(imageLayer, data); | |
// make sure ratio is a vector (backward compatibility) | |
if (typeof(imageLayer.ratio) === "number") { | |
imageLayer.ratio = new me.Vector2d(parseFloat(imageLayer.ratio), parseFloat(imageLayer.ratio)); | |
} | |
return imageLayer; | |
}, | |
readTileset : function (data) { | |
var tileset = new me.TMXTileset(); | |
tileset.initFromJSON(data); | |
return tileset; | |
}, | |
readObjectGroup: function(map, data, z) { | |
var group = new me.TMXObjectGroup(); | |
group.initFromJSON(data[me.TMX_TAG_NAME], data, map.tilesets, z); | |
return group; | |
} | |
}); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* a level manager object <br> | |
* once ressources loaded, the level director contains all references of defined levels<br> | |
* There is no constructor function for me.levelDirector, this is a static object | |
* @namespace me.levelDirector | |
* @memberOf me | |
*/ | |
me.levelDirector = (function() { | |
// hold public stuff in our singletong | |
var obj = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
// our levels | |
var levels = {}; | |
// level index table | |
var levelIdx = []; | |
// current level index | |
var currentLevelIdx = 0; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* reset the level director | |
* @ignore | |
*/ | |
obj.reset = function() { | |
}; | |
/** | |
* add a level | |
* @ignore | |
*/ | |
obj.addLevel = function(level) { | |
throw "melonJS: no level loader defined"; | |
}; | |
/** | |
* | |
* add a TMX level | |
* @ignore | |
*/ | |
obj.addTMXLevel = function(levelId, callback) { | |
// just load the level with the XML stuff | |
if (levels[levelId] == null) { | |
//console.log("loading "+ levelId); | |
levels[levelId] = new me.TMXTileMap(levelId); | |
// set the name of the level | |
levels[levelId].name = levelId; | |
// level index | |
levelIdx.push(levelId); | |
} | |
else { | |
//console.log("level %s already loaded", levelId); | |
return false; | |
} | |
// call the callback if defined | |
if (callback) { | |
callback(); | |
} | |
// true if level loaded | |
return true; | |
}; | |
/** | |
* load a level into the game manager<br> | |
* (will also create all level defined entities, etc..) | |
* @name loadLevel | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
* @param {String} level level id | |
* @example | |
* // the game defined ressources | |
* // to be preloaded by the loader | |
* // TMX maps | |
* ... | |
* {name: "a4_level1", type: "tmx", src: "data/level/a4_level1.tmx"}, | |
* {name: "a4_level2", type: "tmx", src: "data/level/a4_level2.tmx"}, | |
* {name: "a4_level3", type: "tmx", src: "data/level/a4_level3.tmx"}, | |
* ... | |
* ... | |
* // load a level | |
* me.levelDirector.loadLevel("a4_level1"); | |
*/ | |
obj.loadLevel = function(levelId) { | |
// make sure it's a string | |
levelId = levelId.toString().toLowerCase(); | |
// throw an exception if not existing | |
if (levels[levelId] === undefined) { | |
throw ("melonJS: level " + levelId + " not found"); | |
} | |
if (levels[levelId] instanceof me.TMXTileMap) { | |
// check the status of the state mngr | |
var wasRunning = me.state.isRunning(); | |
if (wasRunning) { | |
// stop the game loop to avoid | |
// some silly side effects | |
me.state.stop(); | |
} | |
// reset the gameObject Manager (just in case!) | |
me.game.reset(); | |
// reset the GUID generator | |
// and pass the level id as parameter | |
me.utils.resetGUID(levelId); | |
// reset the current (previous) level | |
if (levels[obj.getCurrentLevelId()]) { | |
levels[obj.getCurrentLevelId()].reset(); | |
} | |
// read the map data | |
me.mapReader.readMap(levels[levelId]); | |
// update current level index | |
currentLevelIdx = levelIdx.indexOf(levelId); | |
// add the specified level to the game manager | |
me.game.loadTMXLevel(levels[levelId]); | |
if (wasRunning) { | |
// resume the game loop if it was | |
// previously running | |
me.state.restart.defer(); | |
} | |
} else { | |
throw "melonJS: no level loader defined"; | |
} | |
return true; | |
}; | |
/** | |
* return the current level id<br> | |
* @name getCurrentLevelId | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
* @return {String} | |
*/ | |
obj.getCurrentLevelId = function() { | |
return levelIdx[currentLevelIdx]; | |
}; | |
/** | |
* reload the current level<br> | |
* @name reloadLevel | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
*/ | |
obj.reloadLevel = function() { | |
// reset the level to initial state | |
//levels[currentLevel].reset(); | |
return obj.loadLevel(obj.getCurrentLevelId()); | |
}; | |
/** | |
* load the next level<br> | |
* @name nextLevel | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
*/ | |
obj.nextLevel = function() { | |
//go to the next level | |
if (currentLevelIdx + 1 < levelIdx.length) { | |
return obj.loadLevel(levelIdx[currentLevelIdx + 1]); | |
} else { | |
return false; | |
} | |
}; | |
/** | |
* load the previous level<br> | |
* @name previousLevel | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
*/ | |
obj.previousLevel = function() { | |
// go to previous level | |
if (currentLevelIdx - 1 >= 0) { | |
return obj.loadLevel(levelIdx[currentLevelIdx - 1]); | |
} else { | |
return false; | |
} | |
}; | |
/** | |
* return the amount of level preloaded<br> | |
* @name levelCount | |
* @memberOf me.levelDirector | |
* @public | |
* @function | |
*/ | |
obj.levelCount = function() { | |
return levelIdx.length; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/** | |
* @preserve Tween JS | |
* https://github.com/sole/Tween.js | |
*/ | |
(function() { | |
/** | |
* Javascript Tweening Engine<p> | |
* Super simple, fast and easy to use tweening engine which incorporates optimised Robert Penner's equation<p> | |
* <a href="https://github.com/sole/Tween.js">https://github.com/sole/Tween.js</a><p> | |
* author sole / http://soledadpenades.com<br> | |
* author mr.doob / http://mrdoob.com<br> | |
* author Robert Eisele / http://www.xarg.org<br> | |
* author Philippe / http://philippe.elsass.me<br> | |
* author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html<br> | |
* author Paul Lewis / http://www.aerotwist.com/<br> | |
* author lechecacharro<br> | |
* author Josh Faul / http://jocafa.com/ | |
* @class | |
* @memberOf me | |
* @constructor | |
* @param {Object} object object on which to apply the tween | |
* @example | |
* // add a tween to change the object pos.y variable to 200 in 3 seconds | |
* tween = new me.Tween(myObject.pos).to({y: 200}, 3000).onComplete(myFunc); | |
* tween.easing(me.Tween.Easing.Bounce.Out); | |
* tween.start(); | |
*/ | |
me.Tween = function ( object ) { | |
var _object = object; | |
var _valuesStart = {}; | |
var _valuesEnd = {}; | |
var _valuesStartRepeat = {}; | |
var _duration = 1000; | |
var _repeat = 0; | |
var _yoyo = false; | |
var _reversed = false; | |
var _delayTime = 0; | |
var _startTime = null; | |
var _pauseTime = 0; | |
var _easingFunction = me.Tween.Easing.Linear.None; | |
var _interpolationFunction = me.Tween.Interpolation.Linear; | |
var _chainedTweens = []; | |
var _onStartCallback = null; | |
var _onStartCallbackFired = false; | |
var _onUpdateCallback = null; | |
var _onCompleteCallback = null; | |
// Set all starting values present on the target object | |
for ( var field in object ) { | |
_valuesStart[ field ] = parseFloat(object[field], 10); | |
} | |
/** | |
* reset the tween object to default value | |
* @ignore | |
*/ | |
this.onResetEvent = function ( object ) { | |
_object = object; | |
_valuesStart = {}; | |
_valuesEnd = {}; | |
_valuesStartRepeat = {}; | |
_easingFunction = me.Tween.Easing.Linear.None; | |
_interpolationFunction = me.Tween.Interpolation.Linear; | |
_yoyo = false; | |
_reversed = false; | |
_duration = 1000; | |
_delayTime = 0; | |
_onStartCallback = null; | |
_onStartCallbackFired = false; | |
_onUpdateCallback = null; | |
_onCompleteCallback = null; | |
}; | |
/** | |
* object properties to be updated and duration | |
* @name me.Tween#to | |
* @public | |
* @function | |
* @param {Object} properties hash of properties | |
* @param {Number} [duration=1000] tween duration | |
*/ | |
this.to = function ( properties, duration ) { | |
if ( duration !== undefined ) { | |
_duration = duration; | |
} | |
_valuesEnd = properties; | |
return this; | |
}; | |
/** | |
* start the tween | |
* @name me.Tween#start | |
* @public | |
* @function | |
*/ | |
this.start = function () { | |
_onStartCallbackFired = false; | |
// add the tween to the object pool on start | |
me.game.world.addChild(this); | |
_startTime = me.timer.getTime() + _delayTime; | |
_pauseTime = 0; | |
for ( var property in _valuesEnd ) { | |
// check if an Array was provided as property value | |
if ( _valuesEnd[ property ] instanceof Array ) { | |
if ( _valuesEnd[ property ].length === 0 ) { | |
continue; | |
} | |
// create a local copy of the Array with the start value at the front | |
_valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] ); | |
} | |
_valuesStart[ property ] = _object[ property ]; | |
if( ( _valuesStart[ property ] instanceof Array ) === false ) { | |
_valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings | |
} | |
_valuesStartRepeat[ property ] = _valuesStart[ property ] || 0; | |
} | |
return this; | |
}; | |
/** | |
* stop the tween | |
* @name me.Tween#stop | |
* @public | |
* @function | |
*/ | |
this.stop = function () { | |
me.game.world.removeChild(this); | |
return this; | |
}; | |
/** | |
* delay the tween | |
* @name me.Tween#delay | |
* @public | |
* @function | |
* @param {Number} amount delay amount expressed in milliseconds | |
*/ | |
this.delay = function ( amount ) { | |
_delayTime = amount; | |
return this; | |
}; | |
/** | |
* Calculate delta to pause the tween | |
* @ignore | |
*/ | |
me.event.subscribe(me.event.STATE_PAUSE, function onPause() { | |
if (_startTime) { | |
_pauseTime = me.timer.getTime(); | |
} | |
}); | |
/** | |
* Calculate delta to resume the tween | |
* @ignore | |
*/ | |
me.event.subscribe(me.event.STATE_RESUME, function onResume() { | |
if (_startTime && _pauseTime) { | |
_startTime += me.timer.getTime() - _pauseTime; | |
} | |
}); | |
/** | |
* Repeat the tween | |
* @name me.Tween#repeat | |
* @public | |
* @function | |
* @param {Number} times amount of times the tween should be repeated | |
*/ | |
this.repeat = function ( times ) { | |
_repeat = times; | |
return this; | |
}; | |
/** | |
* allows the tween to bounce back to their original value when finished | |
* @name me.Tween#yoyo | |
* @public | |
* @function | |
* @param {Boolean} yoyo | |
*/ | |
this.yoyo = function( yoyo ) { | |
_yoyo = yoyo; | |
return this; | |
}; | |
/** | |
* set the easing function | |
* @name me.Tween#easing | |
* @public | |
* @function | |
* @param {me.Tween#Easing} easing easing function | |
*/ | |
this.easing = function ( easing ) { | |
if (typeof easing !== 'function') { | |
throw "melonJS: invalid easing function for me.Tween.easing()"; | |
} | |
_easingFunction = easing; | |
return this; | |
}; | |
/** | |
* set the interpolation function | |
* @name me.Tween#interpolation | |
* @public | |
* @function | |
* @param {me.Tween#Interpolation} easing easing function | |
*/ | |
this.interpolation = function ( interpolation ) { | |
_interpolationFunction = interpolation; | |
return this; | |
}; | |
/** | |
* chain the tween | |
* @name me.Tween#chain | |
* @public | |
* @function | |
* @param {me.Tween} chainedTween Tween to be chained | |
*/ | |
this.chain = function () { | |
_chainedTweens = arguments; | |
return this; | |
}; | |
/** | |
* onStart callback | |
* @name me.Tween#onStart | |
* @public | |
* @function | |
* @param {Function} onStartCallback callback | |
*/ | |
this.onStart = function ( callback ) { | |
_onStartCallback = callback; | |
return this; | |
}; | |
/** | |
* onUpdate callback | |
* @name me.Tween#onUpdate | |
* @public | |
* @function | |
* @param {Function} onUpdateCallback callback | |
*/ | |
this.onUpdate = function ( callback ) { | |
_onUpdateCallback = callback; | |
return this; | |
}; | |
/** | |
* onComplete callback | |
* @name me.Tween#onComplete | |
* @public | |
* @function | |
* @param {Function} onCompleteCallback callback | |
*/ | |
this.onComplete = function ( callback ) { | |
_onCompleteCallback = callback; | |
return this; | |
}; | |
/** @ignore*/ | |
this.update = function ( /*time*/ ) { | |
var property; | |
var time = me.timer.getTime(); | |
if ( time < _startTime ) { | |
return true; | |
} | |
if ( _onStartCallbackFired === false ) { | |
if ( _onStartCallback !== null ) { | |
_onStartCallback.call( _object ); | |
} | |
_onStartCallbackFired = true; | |
} | |
var elapsed = ( time - _startTime ) / _duration; | |
elapsed = elapsed > 1 ? 1 : elapsed; | |
var value = _easingFunction( elapsed ); | |
for ( property in _valuesEnd ) { | |
var start = _valuesStart[ property ] || 0; | |
var end = _valuesEnd[ property ]; | |
if ( end instanceof Array ) { | |
_object[ property ] = _interpolationFunction( end, value ); | |
} else { | |
// Parses relative end values with start as base (e.g.: +10, -3) | |
if ( typeof(end) === "string" ) { | |
end = start + parseFloat(end, 10); | |
} | |
// protect against non numeric properties. | |
if ( typeof(end) === "number" ) { | |
_object[ property ] = start + ( end - start ) * value; | |
} | |
} | |
} | |
if ( _onUpdateCallback !== null ) { | |
_onUpdateCallback.call( _object, value ); | |
} | |
if ( elapsed === 1 ) { | |
if ( _repeat > 0 ) { | |
if( isFinite( _repeat ) ) { | |
_repeat--; | |
} | |
// reassign starting values, restart by making startTime = now | |
for( property in _valuesStartRepeat ) { | |
if ( typeof( _valuesEnd[ property ] ) === "string" ) { | |
_valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10); | |
} | |
if (_yoyo) { | |
var tmp = _valuesStartRepeat[ property ]; | |
_valuesStartRepeat[ property ] = _valuesEnd[ property ]; | |
_valuesEnd[ property ] = tmp; | |
_reversed = !_reversed; | |
} | |
_valuesStart[ property ] = _valuesStartRepeat[ property ]; | |
} | |
_startTime = time + _delayTime; | |
return true; | |
} else { | |
// remove the tween from the object pool | |
me.game.world.removeChild(this); | |
if ( _onCompleteCallback !== null ) { | |
_onCompleteCallback.call( _object ); | |
} | |
for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i ++ ) { | |
_chainedTweens[ i ].start( time ); | |
} | |
return false; | |
} | |
} | |
return true; | |
}; | |
}; | |
/** | |
* Easing Function :<br> | |
* <p> | |
* me.Tween.Easing.Linear.None<br> | |
* me.Tween.Easing.Quadratic.In<br> | |
* me.Tween.Easing.Quadratic.Out<br> | |
* me.Tween.Easing.Quadratic.InOut<br> | |
* me.Tween.Easing.Cubic.In<br> | |
* me.Tween.Easing.Cubic.Out<br> | |
* me.Tween.Easing.Cubic.InOut<br> | |
* me.Tween.Easing.Quartic.In<br> | |
* me.Tween.Easing.Quartic.Out<br> | |
* me.Tween.Easing.Quartic.InOut<br> | |
* me.Tween.Easing.Quintic.In<br> | |
* me.Tween.Easing.Quintic.Out<br> | |
* me.Tween.Easing.Quintic.InOut<br> | |
* me.Tween.Easing.Sinusoidal.In<br> | |
* me.Tween.Easing.Sinusoidal.Out<br> | |
* me.Tween.Easing.Sinusoidal.InOut<br> | |
* me.Tween.Easing.Exponential.In<br> | |
* me.Tween.Easing.Exponential.Out<br> | |
* me.Tween.Easing.Exponential.InOut<br> | |
* me.Tween.Easing.Circular.In<br> | |
* me.Tween.Easing.Circular.Out<br> | |
* me.Tween.Easing.Circular.InOut<br> | |
* me.Tween.Easing.Elastic.In<br> | |
* me.Tween.Easing.Elastic.Out<br> | |
* me.Tween.Easing.Elastic.InOut<br> | |
* me.Tween.Easing.Back.In<br> | |
* me.Tween.Easing.Back.Out<br> | |
* me.Tween.Easing.Back.InOut<br> | |
* me.Tween.Easing.Bounce.In<br> | |
* me.Tween.Easing.Bounce.Out<br> | |
* me.Tween.Easing.Bounce.InOut | |
* </p> | |
* @public | |
* @constant | |
* @type enum | |
* @name me.Tween#Easing | |
*/ | |
me.Tween.Easing = { | |
Linear: { | |
/** @ignore */ | |
None: function ( k ) { | |
return k; | |
} | |
}, | |
Quadratic: { | |
/** @ignore */ | |
In: function ( k ) { | |
return k * k; | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return k * ( 2 - k ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; | |
return - 0.5 * ( --k * ( k - 2 ) - 1 ); | |
} | |
}, | |
Cubic: { | |
/** @ignore */ | |
In: function ( k ) { | |
return k * k * k; | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return --k * k * k + 1; | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k; | |
return 0.5 * ( ( k -= 2 ) * k * k + 2 ); | |
} | |
}, | |
Quartic: { | |
/** @ignore */ | |
In: function ( k ) { | |
return k * k * k * k; | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return 1 - ( --k * k * k * k ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k; | |
return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 ); | |
} | |
}, | |
Quintic: { | |
/** @ignore */ | |
In: function ( k ) { | |
return k * k * k * k * k; | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return --k * k * k * k * k + 1; | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k; | |
return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 ); | |
} | |
}, | |
Sinusoidal: { | |
/** @ignore */ | |
In: function ( k ) { | |
return 1 - Math.cos( k * Math.PI / 2 ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return Math.sin( k * Math.PI / 2 ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
return 0.5 * ( 1 - Math.cos( Math.PI * k ) ); | |
} | |
}, | |
Exponential: { | |
/** @ignore */ | |
In: function ( k ) { | |
return k === 0 ? 0 : Math.pow( 1024, k - 1 ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( k === 0 ) return 0; | |
if ( k === 1 ) return 1; | |
if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 ); | |
return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 ); | |
} | |
}, | |
Circular: { | |
/** @ignore */ | |
In: function ( k ) { | |
return 1 - Math.sqrt( 1 - k * k ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
return Math.sqrt( 1 - ( --k * k ) ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1); | |
return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1); | |
} | |
}, | |
Elastic: { | |
/** @ignore */ | |
In: function ( k ) { | |
var s, a = 0.1, p = 0.4; | |
if ( k === 0 ) return 0; | |
if ( k === 1 ) return 1; | |
if ( !a || a < 1 ) { a = 1; s = p / 4; } | |
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); | |
return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
var s, a = 0.1, p = 0.4; | |
if ( k === 0 ) return 0; | |
if ( k === 1 ) return 1; | |
if ( !a || a < 1 ) { a = 1; s = p / 4; } | |
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); | |
return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 ); | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
var s, a = 0.1, p = 0.4; | |
if ( k === 0 ) return 0; | |
if ( k === 1 ) return 1; | |
if ( !a || a < 1 ) { a = 1; s = p / 4; } | |
else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); | |
if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); | |
return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1; | |
} | |
}, | |
Back: { | |
/** @ignore */ | |
In: function ( k ) { | |
var s = 1.70158; | |
return k * k * ( ( s + 1 ) * k - s ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
var s = 1.70158; | |
return --k * k * ( ( s + 1 ) * k + s ) + 1; | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
var s = 1.70158 * 1.525; | |
if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) ); | |
return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 ); | |
} | |
}, | |
Bounce: { | |
/** @ignore */ | |
In: function ( k ) { | |
return 1 - me.Tween.Easing.Bounce.Out( 1 - k ); | |
}, | |
/** @ignore */ | |
Out: function ( k ) { | |
if ( k < ( 1 / 2.75 ) ) { | |
return 7.5625 * k * k; | |
} else if ( k < ( 2 / 2.75 ) ) { | |
return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; | |
} else if ( k < ( 2.5 / 2.75 ) ) { | |
return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; | |
} else { | |
return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; | |
} | |
}, | |
/** @ignore */ | |
InOut: function ( k ) { | |
if ( k < 0.5 ) return me.Tween.Easing.Bounce.In( k * 2 ) * 0.5; | |
return me.Tween.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5; | |
} | |
} | |
}; | |
/* Interpolation Function :<br> | |
* <p> | |
* me.Tween.Interpolation.Linear<br> | |
* me.Tween.Interpolation.Bezier<br> | |
* me.Tween.Interpolation.CatmullRom<br> | |
* </p> | |
* @public | |
* @constant | |
* @type enum | |
* @name me.Tween#Interpolation | |
*/ | |
me.Tween.Interpolation = { | |
/** @ignore */ | |
Linear: function ( v, k ) { | |
var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = me.Tween.Interpolation.Utils.Linear; | |
if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f ); | |
if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f ); | |
return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i ); | |
}, | |
/** @ignore */ | |
Bezier: function ( v, k ) { | |
var b = 0, n = v.length - 1, pw = Math.pow, bn = me.Tween.Interpolation.Utils.Bernstein, i; | |
for ( i = 0; i <= n; i++ ) { | |
b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i ); | |
} | |
return b; | |
}, | |
/** @ignore */ | |
CatmullRom: function ( v, k ) { | |
var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = me.Tween.Interpolation.Utils.CatmullRom; | |
if ( v[ 0 ] === v[ m ] ) { | |
if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) ); | |
return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i ); | |
} else { | |
if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] ); | |
if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] ); | |
return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i ); | |
} | |
}, | |
Utils: { | |
/** @ignore */ | |
Linear: function ( p0, p1, t ) { | |
return ( p1 - p0 ) * t + p0; | |
}, | |
/** @ignore */ | |
Bernstein: function ( n , i ) { | |
var fc = me.Tween.Interpolation.Utils.Factorial; | |
return fc( n ) / fc( i ) / fc( n - i ); | |
}, | |
/** @ignore */ | |
Factorial: ( function () { | |
var a = [ 1 ]; | |
return function ( n ) { | |
var s = 1, i; | |
if ( a[ n ] ) return a[ n ]; | |
for ( i = n; i > 1; i-- ) s *= i; | |
return a[ n ] = s; | |
}; | |
} )(), | |
/** @ignore */ | |
CatmullRom: function ( p0, p1, p2, p3, t ) { | |
var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2; | |
return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; | |
} | |
} | |
}; | |
})(); | |
/** | |
* @preserve MinPubSub | |
* a micro publish/subscribe messaging framework | |
* @see https://github.com/daniellmb/MinPubSub | |
* @author Daniel Lamb <daniellmb.com> | |
* | |
* Released under the MIT License | |
*/ | |
(function() { | |
/** | |
* There is no constructor function for me.event | |
* @namespace me.event | |
* @memberOf me | |
*/ | |
me.event = (function() { | |
// hold public stuff inside the singleton | |
var obj = {}; | |
/** | |
* the channel/subscription hash | |
* @ignore | |
*/ | |
var cache = {}; | |
/*-------------- | |
PUBLIC | |
--------------*/ | |
/** | |
* Channel Constant when the game is paused <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#STATE_PAUSE | |
*/ | |
obj.STATE_PAUSE = "me.state.onPause"; | |
/** | |
* Channel Constant for when the game is resumed <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#STATE_RESUME | |
*/ | |
obj.STATE_RESUME = "me.state.onResume"; | |
/** | |
* Channel Constant when the game is stopped <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#STATE_STOP | |
*/ | |
obj.STATE_STOP = "me.state.onStop"; | |
/** | |
* Channel Constant for when the game is restarted <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#STATE_RESTART | |
*/ | |
obj.STATE_RESTART = "me.state.onRestart"; | |
/** | |
* Channel Constant for when the game manager is initialized <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#GAME_INIT | |
*/ | |
obj.GAME_INIT = "me.game.onInit"; | |
/** | |
* Channel Constant for when a level is loaded <br> | |
* Data passed : {String} Level Name | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#LEVEL_LOADED | |
*/ | |
obj.LEVEL_LOADED = "me.game.onLevelLoaded"; | |
/** | |
* Channel Constant for when everything has loaded <br> | |
* Data passed : none <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#LOADER_COMPLETE | |
*/ | |
obj.LOADER_COMPLETE = "me.loader.onload"; | |
/** | |
* Channel Constant for displaying a load progress indicator <br> | |
* Data passed : {Number} [0 .. 1] <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#LOADER_PROGRESS | |
*/ | |
obj.LOADER_PROGRESS = "me.loader.onProgress"; | |
/** | |
* Channel Constant for pressing a binded key <br> | |
* Data passed : {String} user-defined action, {Number} keyCode, | |
* {Boolean} edge state <br> | |
* Edge-state is for detecting "locked" key bindings. When a locked key | |
* is pressed and held, the first event will have have the third | |
* argument set true. subsequent events will continue firing with the | |
* third argument set false. | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#KEYDOWN | |
* @example | |
* me.input.bindKey("jump", me.input.KEY.X, true); // Edge-triggered | |
* me.input.bindKey("shoot", me.input.KEY.Z); // Level-triggered | |
* me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge)) { | |
* // Checking bound keys | |
* if (action === "jump") { | |
* if (edge) { | |
* this.doJump(); | |
* } | |
* | |
* // Make character fall slower when holding the jump key | |
* this.vel.y = this.gravity; | |
* } | |
* }); | |
*/ | |
obj.KEYDOWN = "me.input.keydown"; | |
/** | |
* Channel Constant for releasing a binded key <br> | |
* Data passed : {String} user-defined action, {Number} keyCode <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#KEYUP | |
* @example | |
* me.event.subscribe(me.event.KEYUP, function (action, keyCode)) { | |
* // Checking unbound keys | |
* if (keyCode == me.input.KEY.ESC) { | |
* if (me.state.isPaused()) { | |
* me.state.resume(); | |
* } | |
* else { | |
* me.state.pause(); | |
* } | |
* } | |
* }); | |
*/ | |
obj.KEYUP = "me.input.keyup"; | |
/** | |
* Channel Constant for when the (browser) window is resized <br> | |
* Data passed : {Event} Event object <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#WINDOW_ONRESIZE | |
*/ | |
obj.WINDOW_ONRESIZE = "window.onresize"; | |
/** | |
* Channel Constant for when the device is rotated <br> | |
* Data passed : {Event} Event object <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#WINDOW_ONORIENTATION_CHANGE | |
*/ | |
obj.WINDOW_ONORIENTATION_CHANGE = "window.orientationchange"; | |
/** | |
* Channel Constant for when the (browser) window is scrolled <br> | |
* Data passed : {Event} Event object <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#WINDOW_ONSCROLL | |
*/ | |
obj.WINDOW_ONSCROLL = "window.onscroll"; | |
/** | |
* Channel Constant for when the viewport position is updated <br> | |
* Data passed : {me.Vector2d} viewport position vector <br> | |
* @public | |
* @constant | |
* @type String | |
* @name me.event#VIEWPORT_ONCHANGE | |
*/ | |
obj.VIEWPORT_ONCHANGE = "viewport.onchange"; | |
/** | |
* Publish some data on a channel | |
* @name me.event#publish | |
* @public | |
* @function | |
* @param {String} channel The channel to publish on | |
* @param {Array} arguments The data to publish | |
* | |
* @example Publish stuff on '/some/channel'. | |
* Anything subscribed will be called with a function | |
* signature like: function(a,b,c){ ... } | |
* | |
* me.event.publish("/some/channel", ["a","b","c"]); | |
* | |
*/ | |
obj.publish = function(channel, args){ | |
var subs = cache[channel], | |
len = subs ? subs.length : 0; | |
//can change loop or reverse array if the order matters | |
while(len--){ | |
subs[len].apply(window, args || []); // is window correct here? | |
} | |
}; | |
/** | |
* Register a callback on a named channel. | |
* @name me.event#subscribe | |
* @public | |
* @function | |
* @param {String} channel The channel to subscribe to | |
* @param {Function} callback The event handler, any time something is | |
* published on a subscribed channel, the callback will be called | |
* with the published array as ordered arguments | |
* @return {handle} A handle which can be used to unsubscribe this | |
* particular subscription | |
* @example | |
* me.event.subscribe("/some/channel", function(a, b, c){ doSomething(); }); | |
*/ | |
obj.subscribe = function(channel, callback){ | |
if(!cache[channel]){ | |
cache[channel] = []; | |
} | |
cache[channel].push(callback); | |
return [channel, callback]; // Array | |
}; | |
/** | |
* Disconnect a subscribed function for a channel. | |
* @name me.event#unsubscribe | |
* @public | |
* @function | |
* @param {Array|String} handle The return value from a subscribe call or the | |
* name of a channel as a String | |
* @param {Function} [callback] The callback to be unsubscribed. | |
* @example | |
* var handle = me.event.subscribe("/some/channel", function(){}); | |
* me.event.unsubscribe(handle); | |
* | |
* // Or alternatively ... | |
* | |
* var callback = function(){}; | |
* me.event.subscribe("/some/channel", callback); | |
* me.event.unsubscribe("/some/channel", callback); | |
*/ | |
obj.unsubscribe = function(handle, callback){ | |
var subs = cache[callback ? handle : handle[0]], | |
len = subs ? subs.length : 0; | |
callback = callback || handle[1]; | |
while(len--){ | |
if(subs[len] === callback){ | |
subs.splice(len, 1); | |
} | |
} | |
}; | |
// return our object | |
return obj; | |
})(); | |
})(); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
*/ | |
(function() { | |
/** | |
* There is no constructor function for me.plugin | |
* @namespace me.plugin | |
* @memberOf me | |
*/ | |
me.plugin = (function() { | |
// hold public stuff inside the singleton | |
var singleton = {}; | |
/*-------------- | |
PUBLIC | |
--------------*/ | |
/** | |
* a base Object for plugin <br> | |
* plugin must be installed using the register function | |
* @see me.plugin | |
* @class | |
* @extends Object | |
* @name plugin.Base | |
* @memberOf me | |
* @constructor | |
*/ | |
singleton.Base = Object.extend( | |
/** @scope me.plugin.Base.prototype */ | |
{ | |
/** | |
* define the minimum required <br> | |
* version of melonJS <br> | |
* this need to be defined by the plugin | |
* @public | |
* @type String | |
* @name me.plugin.Base#version | |
*/ | |
version : undefined, | |
/** @ignore */ | |
init : function() { | |
//empty for now ! | |
} | |
}); | |
/** | |
* patch a melonJS function | |
* @name patch | |
* @memberOf me.plugin | |
* @public | |
* @function | |
* @param {Object} proto target object | |
* @param {String} name target function | |
* @param {Function} fn replacement function | |
* @example | |
* // redefine the me.game.update function with a new one | |
* me.plugin.patch(me.game, "update", function () { | |
* // display something in the console | |
* console.log("duh"); | |
* // call the original me.game.update function | |
* this.parent(); | |
* }); | |
*/ | |
singleton.patch = function(proto, name, fn){ | |
// use the object prototype if possible | |
if (proto.prototype!==undefined) { | |
proto = proto.prototype; | |
} | |
// reuse the logic behind Object.extend | |
if (typeof(proto[name]) === "function") { | |
// save the original function | |
var _parent = proto[name]; | |
// override the function with the new one | |
proto[name] = (function(name, fn){ | |
return function() { | |
var tmp = this.parent; | |
this.parent = _parent; | |
var ret = fn.apply(this, arguments); | |
this.parent = tmp; | |
return ret; | |
}; | |
})( name, fn ); | |
} | |
else { | |
console.error(name + " is not an existing function"); | |
} | |
}; | |
/** | |
* Register a plugin. | |
* @name register | |
* @memberOf me.plugin | |
* @see me.plugin.Base | |
* @public | |
* @function | |
* @param {me.plugin.Base} plugin Plugin to instiantiate and register | |
* @param {String} name | |
* @param {} [arguments...] all extra parameters will be passed to the plugin constructor | |
* @example | |
* // register a new plugin | |
* me.plugin.register(TestPlugin, "testPlugin"); | |
* // the plugin then also become available | |
* // under then me.plugin namespace | |
* me.plugin.testPlugin.myFunction(); | |
*/ | |
singleton.register = function(plugin, name){ | |
// ensure me.plugin[name] is not already "used" | |
if (me.plugin[name]) { | |
console.error ("plugin " + name + " already registered"); | |
} | |
// compatibility testing | |
if (plugin.prototype.version === undefined) { | |
throw "melonJS: Plugin version not defined !"; | |
} else if (me.sys.checkVersion(plugin.prototype.version) > 0) { | |
throw ("melonJS: Plugin version mismatch, expected: "+ plugin.prototype.version +", got: " + me.version); | |
} | |
// get extra arguments | |
var _args = []; | |
if (arguments.length > 2) { | |
// store extra arguments if any | |
_args = Array.prototype.slice.call(arguments, 1); | |
} | |
// try to instantiate the plugin | |
_args[0] = plugin; | |
me.plugin[name] = new (plugin.bind.apply(plugin, _args))(); | |
// inheritance check | |
if (!(me.plugin[name] instanceof me.plugin.Base)) { | |
throw "melonJS: Plugin should extend the me.plugin.Base Class !"; | |
} | |
}; | |
// return our singleton | |
return singleton; | |
})(); | |
})(); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* a simple debug panel plugin | |
* usage : me.plugin.register(debugPanel, "debug"); | |
* | |
* you can then use me.plugin.debug.show() or me.plugin.debug.hide() | |
* to show or hide the panel, or press respectively the "S" and "H" keys. | |
* | |
* note : | |
* Heap Memory information is available under Chrome when using | |
* the "--enable-memory-info" parameter to launch Chrome | |
*/ | |
(function($) { | |
// ensure that me.debug is defined | |
me.debug = me.debug || {}; | |
/** | |
* @class | |
* @public | |
* @extends me.plugin.Base | |
* @memberOf me | |
* @constructor | |
*/ | |
debugPanel = me.plugin.Base.extend( | |
/** @scope me.debug.Panel.prototype */ | |
{ | |
// Object "Game Unique Identifier" | |
GUID : null, | |
// to hold the debug options | |
// clickable rect area | |
area : {}, | |
// panel position and size | |
rect : null, | |
// for z ordering | |
// make it ridiculously high | |
z : Infinity, | |
// visibility flag | |
visible : false, | |
// minimum melonJS version expected | |
version : "0.9.9", | |
/** @private */ | |
init : function(showKey, hideKey) { | |
// call the parent constructor | |
this.parent(); | |
this.rect = new me.Rect(new me.Vector2d(0, 0), me.video.getWidth(), 35); | |
// set the object GUID value | |
this.GUID = "debug-" + me.utils.createGUID(); | |
// set the object entity name | |
this.name = "me.debugPanel"; | |
// persistent | |
this.isPersistent = true; | |
// a floating object | |
this.floating = true; | |
// renderable | |
this.isRenderable = true; | |
// always update, even when not visible | |
this.alwaysUpdate = true; | |
// create a default font, with fixed char width | |
this.font = new me.Font('courier', 10, 'white'); | |
// clickable areas | |
this.area.renderHitBox = new me.Rect(new me.Vector2d(160,5),15,15); | |
this.area.renderVelocity = new me.Rect(new me.Vector2d(165,18),15,15); | |
this.area.renderDirty = new me.Rect(new me.Vector2d(270,5),15,15); | |
this.area.renderCollisionMap = new me.Rect(new me.Vector2d(270,18),15,15); | |
// some internal string/length | |
this.help_str = "(s)how/(h)ide"; | |
this.help_str_len = this.font.measureText(me.video.getSystemContext(), this.help_str).width; | |
this.fps_str_len = this.font.measureText(me.video.getSystemContext(), "00/00 fps").width; | |
// enable the FPS counter | |
me.debug.displayFPS = true; | |
// bind the "S" and "H" keys | |
me.input.bindKey(showKey || me.input.KEY.S, "show"); | |
me.input.bindKey(hideKey || me.input.KEY.H, "hide"); | |
// memory heap sample points | |
this.samples = []; | |
//patch patch patch ! | |
this.patchSystemFn(); | |
// make it visible | |
this.show(); | |
}, | |
/** | |
* patch system fn to draw debug information | |
*/ | |
patchSystemFn : function() { | |
// add a few new debug flag (if not yet defined) | |
me.debug.renderHitBox = me.debug.renderHitBox || false; | |
me.debug.renderVelocity = me.debug.renderVelocity || false; | |
// patch video.js | |
me.plugin.patch(me.timer, "update", function (context) { | |
// call the original me.game.draw function | |
this.parent(); | |
// call the FPS counter | |
me.timer.countFPS(); | |
}); | |
// patch sprite.js | |
me.plugin.patch(me.SpriteObject, "draw", function (context) { | |
// call the original me.game.draw function | |
this.parent(context); | |
// draw the sprite rectangle | |
if (me.debug.renderHitBox) { | |
context.strokeStyle = "green"; | |
context.strokeRect(this.left, this.top, this.width, this.height); | |
} | |
}); | |
// patch entities.js | |
me.plugin.patch(me.ObjectEntity, "draw", function (context) { | |
// call the original me.game.draw function | |
this.parent(context); | |
// check if debug mode is enabled | |
if (me.debug.renderHitBox && this.shapes.length) { | |
// draw the original collisionBox | |
this.collisionBox.draw(context, "red"); | |
// draw the original shape if not a rectangle | |
if (this.shapes[0].shapeType!=="Rectangle") { | |
// draw the original shape as well | |
context.translate(this.pos.x, this.pos.y); | |
this.shapes[0].draw(context, "red"); | |
context.translate(-this.pos.x, -this.pos.y); | |
} | |
} | |
if (me.debug.renderVelocity) { | |
// draw entity current velocity | |
var x = ~~(this.pos.x + this.hWidth); | |
var y = ~~(this.pos.y + this.hHeight); | |
context.strokeStyle = "blue"; | |
context.lineWidth = 1; | |
context.beginPath(); | |
context.moveTo(x, y); | |
context.lineTo( | |
x + ~~(this.vel.x * this.hWidth), | |
y + ~~(this.vel.y * this.hHeight) | |
); | |
context.stroke(); | |
} | |
}); | |
}, | |
/** | |
* show the debug panel | |
*/ | |
show : function() { | |
if (!this.visible) { | |
// add the panel to the object pool if required | |
if (!me.game.getEntityByName("me.debugPanel")[0]) { | |
me.game.add(this, this.z); | |
me.game.sort(); | |
} | |
// register a mouse event for the checkboxes | |
me.input.registerPointerEvent('mousedown', this.rect, this.onClick.bind(this), true); | |
// make it visible | |
this.visible = true; | |
// force repaint | |
me.game.repaint(); | |
} | |
}, | |
/** | |
* hide the debug panel | |
*/ | |
hide : function() { | |
if (this.visible) { | |
// release the mouse event for the checkboxes | |
me.input.releasePointerEvent('mousedown', this.rect); | |
// make it visible | |
this.visible = false; | |
// force repaint | |
me.game.repaint(); | |
} | |
}, | |
/** @private */ | |
update : function() { | |
if (me.input.isKeyPressed('show')) { | |
this.show(); | |
} | |
else if (me.input.isKeyPressed('hide')) { | |
this.hide(); | |
} | |
return true; | |
}, | |
/** | |
* @private | |
*/ | |
getRect : function() { | |
return this.rect; | |
}, | |
/** @private */ | |
onClick : function(e) { | |
// check the clickable areas | |
if (this.area.renderHitBox.containsPoint(e.gameX, e.gameY)) { | |
me.debug.renderHitBox = !me.debug.renderHitBox; | |
} | |
else if (this.area.renderCollisionMap.containsPoint(e.gameX, e.gameY)) { | |
me.debug.renderCollisionMap = !me.debug.renderCollisionMap; | |
/* | |
// not working with dynamic rendering since | |
// collision layer does not have allocated renderers | |
var layer = me.game.currentLevel.getLayerByName("collision"); | |
if (layer && me.debug.renderCollisionMap === false) { | |
layer.visible = true; | |
me.game.add(layer); | |
me.debug.renderCollisionMap = true; | |
me.game.sort(); | |
} else if (layer) { | |
layer.visible = false; | |
me.game.remove(layer); | |
me.debug.renderCollisionMap = false; | |
} | |
*/ | |
} else if (this.area.renderVelocity.containsPoint(e.gameX, e.gameY)) { | |
// does nothing for now, since velocity is | |
// rendered together with hitboxes (is a global debug flag required?) | |
me.debug.renderVelocity = !me.debug.renderVelocity; | |
} | |
// force repaint | |
me.game.repaint(); | |
}, | |
/** @private */ | |
drawMemoryGraph : function (context, startX, endX) { | |
if (window.performance && window.performance.memory) { | |
var usedHeap = Number.prototype.round(window.performance.memory.usedJSHeapSize/1048576, 2); | |
var totalHeap = Number.prototype.round(window.performance.memory.totalJSHeapSize/1048576, 2); | |
var len = endX - startX; | |
// remove the first item | |
this.samples.shift(); | |
// add a new sample (25 is the height of the graph) | |
this.samples[len] = (usedHeap / totalHeap) * 25; | |
// draw the graph | |
for (var x = len;x--;) { | |
var where = endX - (len - x); | |
context.beginPath(); | |
context.strokeStyle = "lightgreen"; | |
context.moveTo(where, 30); | |
context.lineTo(where, 30 - (this.samples[x] || 0)); | |
context.stroke(); | |
} | |
// display the current value | |
this.font.draw(context, usedHeap + '/' + totalHeap + ' MB', startX, 18); | |
} else { | |
// Heap Memory information not available | |
this.font.draw(context, "??/?? MB", startX, 18); | |
} | |
}, | |
/** @private */ | |
draw : function(context) { | |
context.save(); | |
// draw the panel | |
context.globalAlpha = 0.5; | |
context.fillStyle = "black"; | |
context.fillRect(this.rect.left, this.rect.top, | |
this.rect.width, this.rect.height); | |
context.globalAlpha = 1.0; | |
// # entities / draw | |
this.font.draw(context, "#objects : " + me.game.world.children.length, 5, 5); | |
this.font.draw(context, "#draws : " + me.game.world.drawCount, 5, 18); | |
// debug checkboxes | |
this.font.draw(context, "?hitbox ["+ (me.debug.renderHitBox?"x":" ") +"]", 100, 5); | |
this.font.draw(context, "?velocity ["+ (me.debug.renderVelocity?"x":" ") +"]", 100, 18); | |
this.font.draw(context, "?dirtyRect [ ]", 200, 5); | |
this.font.draw(context, "?col. layer ["+ (me.debug.renderCollisionMap?"x":" ") +"]", 200, 18); | |
// draw the memory heap usage | |
this.drawMemoryGraph(context, 300, this.rect.width - this.help_str_len - 5); | |
// some help string | |
this.font.draw(context, this.help_str, this.rect.width - this.help_str_len - 5, 18); | |
//fps counter | |
var fps_str = "" + me.timer.fps + "/" + me.sys.fps + " fps"; | |
this.font.draw(context, fps_str, this.rect.width - this.fps_str_len - 5, 5); | |
context.restore(); | |
}, | |
/** @private */ | |
onDestroyEvent : function() { | |
// hide the panel | |
this.hide(); | |
// unbind "S" & "H" | |
me.input.unbindKey(me.input.KEY.S); | |
me.input.unbindKey(me.input.KEY.H); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
// Make this false to to turn on the debugging panel manually | |
var enableDebugging = true; | |
/* Game namespace */ | |
var game = { | |
// an object where to store game information | |
data : { | |
// score | |
score : 0 | |
}, | |
// Run on page load. | |
"onload" : function () { | |
// Initialize the video. | |
if (!me.video.init("screen", 640, 480, true, 'auto')) { | |
alert("Your browser does not support HTML5 canvas."); | |
return; | |
} | |
// add "#debug" to the URL to enable the debug Panel | |
if (enableDebugging || document.location.hash === "#debug") { | |
window.onReady(function () { | |
me.plugin.register.defer(debugPanel, "debug"); | |
}); | |
} | |
// Initialize the audio. | |
me.audio.init("mp3,ogg"); | |
// Set a callback to run when loading is complete. | |
me.loader.onload = this.loaded.bind(this); | |
// Load the resources. | |
me.loader.preload(game.resources); | |
// Initialize melonJS and display a loading screen. | |
me.state.change(me.state.LOADING); | |
}, | |
// Run on game resources loaded. | |
"loaded" : function () { | |
me.state.set(me.state.MENU, new game.TitleScreen()); | |
me.state.set(me.state.PLAY, new game.PlayScreen()); | |
// add our player entity in the entity pool | |
me.entityPool.add("mainPlayer", game.PlayerEntity); | |
me.entityPool.add("CoinEntity", game.CoinEntity); | |
me.entityPool.add("EnemyEntity", game.EnemyEntity); | |
// enable the keyboard | |
me.input.bindKey(me.input.KEY.LEFT, "left"); | |
me.input.bindKey(me.input.KEY.RIGHT, "right"); | |
me.input.bindKey(me.input.KEY.X, "jump", true); | |
// Start the game. | |
// me.state.change(me.state.MENU); | |
me.state.change(me.state.PLAY); | |
} | |
}; | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
game.resources = [ | |
/* Graphics. | |
* @example | |
* {name: "example", type:"image", src: "data/img/example.png"}, | |
*/ | |
// our level tileset | |
{name: "area01_level_tiles", type:"image", src: "data/img/map/area01_level_tiles.png"}, | |
// the main player spritesheet | |
{name: "gripe_run_right", type:"image", src: "data/img/sprite/gripe_run_right.png"}, | |
// the parallax background | |
{name: "area01_bkg0", type:"image", src: "data/img/area01_bkg0.png"}, | |
{name: "area01_bkg1", type:"image", src: "data/img/area01_bkg1.png"}, | |
// the spinning coin spritesheet | |
{name: "spinning_coin_gold", type:"image", src: "data/img/sprite/spinning_coin_gold.png"}, | |
// our enemty entity | |
{name: "wheelie_right", type:"image", src: "data/img/sprite/wheelie_right.png"}, | |
// game font | |
{name: "32x32_font", type:"image", src: "data/img/font/32x32_font.png"}, | |
// title screen | |
{name: "title_screen", type:"image", src: "data/img/gui/title_screen.png"}, | |
/* Atlases | |
* @example | |
* {name: "example_tps", type: "tps", src: "data/img/example_tps.json"}, | |
*/ | |
/* Maps. | |
* @example | |
* {name: "example01", type: "tmx", src: "data/map/example01.tmx"}, | |
* {name: "example01", type: "tmx", src: "data/map/example01.json"}, | |
*/ | |
{name: "area01", type: "tmx", src: "data/map/area01.tmx"}, | |
/* Background music. | |
* @example | |
* {name: "example_bgm", type: "audio", src: "data/bgm/", channel : 1}, | |
*/ | |
{name: "dst-inertexponent", type: "audio", src: "data/bgm/", channel : 1}, | |
/* Sound effects. | |
* @example | |
* {name: "example_sfx", type: "audio", src: "data/sfx/", channel : 2} | |
*/ | |
{name: "cling", type: "audio", src: "data/sfx/", channel : 2}, | |
{name: "stomp", type: "audio", src: "data/sfx/", channel : 1}, | |
{name: "jump", type: "audio", src: "data/sfx/", channel : 1} | |
]; | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
/** | |
* a HUD container and child items | |
*/ | |
game.HUD = game.HUD || {}; | |
game.HUD.Container = me.ObjectContainer.extend({ | |
init: function() { | |
// call the constructor | |
this.parent(); | |
// persistent across level change | |
this.isPersistent = true; | |
// non collidable | |
this.collidable = false; | |
// make sure our object is always draw first | |
this.z = Infinity; | |
// give a name | |
this.name = "HUD"; | |
// add our child score object at the right-bottom position | |
this.addChild(new game.HUD.ScoreItem(630, 440)); | |
} | |
}); | |
/** | |
* a basic HUD item to display score | |
*/ | |
game.HUD.ScoreItem = me.Renderable.extend({ | |
/** | |
* constructor | |
*/ | |
init: function(x, y) { | |
// call the parent constructor | |
// (size does not matter here) | |
this.parent(new me.Vector2d(x, y), 10, 10); | |
// create a font | |
this.font = new me.BitmapFont("32x32_font", 32); | |
this.font.set("right"); | |
// local copy of the global score | |
this.score = -1; | |
// make sure we use screen coordinates | |
this.floating = true; | |
}, | |
/** | |
* update function | |
*/ | |
update : function () { | |
// we don't do anything fancy here, so just | |
// return true if the score has been updated | |
if (this.score !== game.data.score) { | |
this.score = game.data.score; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* draw the score | |
*/ | |
draw : function (context) { | |
// draw it baby ! | |
this.font.draw(context, game.data.score, this.pos.x, this.pos.y); | |
} | |
}); | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
/*------------------- | |
a player entity | |
-------------------------------- */ | |
game.PlayerEntity = me.ObjectEntity.extend({ | |
/* ----- | |
constructor | |
------ */ | |
init: function(x, y, settings) { | |
// call the constructor | |
this.parent(x, y, settings); | |
// set the default horizontal & vertical speed (accel vector) | |
this.setVelocity(3, 15); | |
// adjust the bounding box | |
this.updateColRect(8, 48, 8, 54); | |
// set the display to follow our position on both axis | |
me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH); | |
}, | |
/* ----- | |
update the player pos | |
------ */ | |
update: function() { | |
if (me.input.isKeyPressed('left')) { | |
// flip the sprite on horizontal axis | |
this.flipX(true); | |
// update the entity velocity | |
this.vel.x -= this.accel.x * me.timer.tick; | |
} else if (me.input.isKeyPressed('right')) { | |
// unflip the sprite | |
this.flipX(false); | |
// update the entity velocity | |
this.vel.x += this.accel.x * me.timer.tick; | |
} else { | |
this.vel.x = 0; | |
} | |
if (me.input.isKeyPressed('jump')) { | |
// make sure we are not already jumping or falling | |
if (!this.jumping && !this.falling) { | |
// set current vel to the maximum defined value | |
// gravity will then do the rest | |
this.vel.y = -this.maxVel.y * me.timer.tick; | |
// set the jumping flag | |
this.jumping = true; | |
// play some audio | |
me.audio.play("jump"); | |
} | |
} | |
// check & update player movement | |
this.updateMovement(); | |
// check for collision | |
var res = me.game.world.collide(this); | |
if (res) { | |
// if we collide with an enemy | |
if (res.obj.type == me.game.ENEMY_OBJECT) { | |
// check if we jumped on it | |
if ((res.y > 0) && ! this.jumping) { | |
// bounce (force jump) | |
this.falling = false; | |
this.vel.y = -this.maxVel.y * me.timer.tick; | |
// set the jumping flag | |
this.jumping = true; | |
// play some audio | |
me.audio.play("stomp"); | |
} else { | |
// let's flicker in case we touched an enemy | |
this.renderable.flicker(45); | |
me.game.viewport.shake(10, 500, me.game.viewport.AXIS.BOTH); | |
} | |
} | |
} | |
// update animation if necessary | |
if (this.vel.x!=0 || this.vel.y!=0) { | |
// update object animation | |
this.parent(); | |
return true; | |
} | |
// else inform the engine we did not perform | |
// any update (e.g. position, animation) | |
return false; | |
} | |
}); | |
/*---------------- | |
a Coin entity | |
------------------------ */ | |
game.CoinEntity = me.CollectableEntity.extend({ | |
// extending the init function is not mandatory | |
// unless you need to add some extra initialization | |
init: function(x, y, settings) { | |
// define this here instead of tiled | |
settings.image = "spinning_coin_gold"; | |
settings.spritewidth = 32; | |
// call the parent constructor | |
this.parent(x, y, settings); | |
}, | |
// this function is called by the engine, when | |
// an object is touched by something (here collected) | |
onCollision: function() { | |
// do something when collected | |
// play a "coin collected" sound | |
me.audio.play("cling"); | |
// give some score | |
game.data.score += 250; | |
// make sure it cannot be collected "again" | |
this.collidable = false; | |
// remove it | |
me.game.remove(this); | |
} | |
}); | |
/* -------------------------- | |
an enemy Entity | |
------------------------ */ | |
game.EnemyEntity = me.ObjectEntity.extend({ | |
init: function(x, y, settings) { | |
// define this here instead of tiled | |
settings.image = "wheelie_right"; | |
settings.spritewidth = 64; | |
// call the parent constructor | |
this.parent(x, y, settings); | |
this.startX = x; | |
this.endX = x + settings.width - settings.spritewidth; | |
// size of sprite | |
// make him start from the right | |
this.pos.x = x + settings.width - settings.spritewidth; | |
this.walkLeft = true; | |
// walking & jumping speed | |
this.setVelocity(1, 6); | |
// set collision rectangle | |
this.updateColRect(4, 56, 8, 56); | |
// make it collidable | |
this.collidable = true; | |
// make it a enemy object | |
this.type = me.game.ENEMY_OBJECT; | |
}, | |
// call by the engine when colliding with another object | |
// obj parameter corresponds to the other object (typically the player) touching this one | |
onCollision: function(res, obj) { | |
// res.y >0 means touched by something on the bottom | |
// which mean at top position for this one | |
if (this.alive && (res.y > 0) && obj.falling) { | |
this.renderable.flicker(45); | |
} | |
}, | |
// manage the enemy movement | |
update: function() { | |
// do nothing if not in viewport | |
if (!this.inViewport) { | |
return false; | |
} | |
if (this.alive) { | |
if (this.walkLeft && this.pos.x <= this.startX) { | |
this.walkLeft = false; | |
} else if (!this.walkLeft && this.pos.x >= this.endX) { | |
this.walkLeft = true; | |
} | |
// make it walk | |
this.flipX(this.walkLeft); | |
this.vel.x += (this.walkLeft) ? -this.accel.x * me.timer.tick : this.accel.x * me.timer.tick; | |
} else { | |
this.vel.x = 0; | |
} | |
// check and update movement | |
this.updateMovement(); | |
// update animation if necessary | |
if (this.vel.x!=0 || this.vel.y!=0) { | |
// update object animation | |
this.parent(); | |
return true; | |
} | |
return false; | |
} | |
}); | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
game.PlayScreen = me.ScreenObject.extend({ | |
/** | |
* action to perform on state change | |
*/ | |
onResetEvent: function() { | |
// load a level | |
me.levelDirector.loadLevel("area01"); | |
// reset the score | |
game.data.score = 0; | |
// add our HUD to the game world | |
this.HUD = new game.HUD.Container(); | |
me.game.world.addChild(this.HUD); | |
// play the audio track | |
//me.audio.playTrack("DST-InertExponent"); | |
}, | |
/** | |
* action to perform when leaving this screen (state change) | |
*/ | |
onDestroyEvent: function() { | |
// stop the current audio track | |
//me.audio.stopTrack(); | |
// remove the HUD from the game world | |
me.game.world.removeChild(this.HUD); | |
} | |
}); | |
/*jslint sloppy:true, browser: true, devel: true, eqeq: true, vars: true, white: true*/ | |
/*global game: true, debugPanel:true, me:true*/ | |
game.TitleScreen = me.ScreenObject.extend({ | |
// constructor | |
init: function() { | |
this.parent(true); | |
// title screen image | |
this.title = null; | |
this.font = null; | |
this.scrollerfont = null; | |
this.scrollertween = null; | |
this.scroller = "A SMALL STEP BY STEP TUTORIAL FOR GAME CREATION WITH MELONJS "; | |
this.scrollerpos = 600; | |
}, | |
// reset function | |
onResetEvent: function() { | |
if (this.title == null) { | |
// init stuff if not yet done | |
this.title = me.loader.getImage("title_screen"); | |
// font to display the menu items | |
this.font = new me.BitmapFont("32x32_font", 32); | |
// set the scroller | |
this.scrollerfont = new me.BitmapFont("32x32_font", 32); | |
} | |
// reset to default value | |
this.scrollerpos = 640; | |
// a tween to animate the arrow | |
this.scrollertween = new me.Tween(this).to({ | |
scrollerpos: -2200 | |
}, 10000).onComplete(this.scrollover.bind(this)).start(); | |
// enable the keyboard | |
me.input.bindKey(me.input.KEY.ENTER, "enter", true); | |
// play something | |
me.audio.play("cling"); | |
}, | |
// some callback for the tween objects | |
scrollover: function() { | |
// reset to default value | |
this.scrollerpos = 640; | |
this.scrollertween.to({ | |
scrollerpos: -2200 | |
}, 10000).onComplete(this.scrollover.bind(this)).start(); | |
}, | |
// update function | |
update: function() { | |
// enter pressed ? | |
if (me.input.isKeyPressed('enter')) { | |
me.state.change(me.state.PLAY); | |
} | |
return true; | |
}, | |
// draw function | |
draw: function(context) { | |
context.drawImage(this.title, 0, 0); | |
this.font.draw(context, "PRESS ENTER TO PLAY", 20, 240); | |
this.scrollerfont.draw(context, this.scroller, this.scrollerpos, 440); | |
}, | |
// destroy function | |
onDestroyEvent: function() { | |
me.input.unbindKey(me.input.KEY.ENTER); | |
//just in case | |
this.scrollertween.stop(); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license MelonJS Game Engine | |
* @copyright (C) 2011 - 2013 Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
* melonJS is licensed under the MIT License. | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
*/ | |
window.me=window.me||{},function(a){function b(){if(!h){if(!e.body)return setTimeout(b,13);e.removeEventListener?e.removeEventListener("DOMContentLoaded",b,!1):a.removeEventListener("load",b,!1),h=!0;for(var c=0;c<i.length;c++)i[c].call(a,[]);i.length=0}}function c(){return g?void 0:(g=!0,"complete"===e.readyState?b():(e.addEventListener&&e.addEventListener("DOMContentLoaded",b,!1),void a.addEventListener("load",b,!1)))}function d(){f||(me.device._check(),me.save._init(),me.loader.setNocache(e.location.href.match(/\?nocache/)||!1),me.timer.init(),me.mapReader=new me.TMXMapReader,me.state.init(),me.entityPool.init(),me.levelDirector.reset(),f=!0)}var e=a.document;me={mod:"melonJS",version:"0.9.11"},me.sys={fps:60,interpolation:!1,scale:null,scalingInterpolation:!1,gravity:void 0,stopOnAudioError:!0,pauseOnBlur:!0,resumeOnFocus:!0,stopOnBlur:!1,preRender:!1,checkVersion:function(a,b){b=b||me.version;for(var c=a.split("."),d=b.split("."),e=Math.min(c.length,d.length),f=0,g=0;e>g&&!(f=+c[g]-+d[g]);g++);return f?f:c.length-d.length}};var f=!1,g=!1,h=!1,i=[];a.onReady=function(b){return c(),h?b.call(a,[]):i.push(function(){return b.call(a,[])}),this},a.onReady(function(){d()});var j=!1,k=/var xyz/.test(function(){})?/\bparent\b/:/[\D|\d]*/,l=function(a){if(null==a||"object"!=typeof a)return a;var b;if(a instanceof Array){b=[],Object.setPrototypeOf(b,Object.getPrototypeOf(a));for(var c=0,d=a.length;d>c;c++)b[c]=l(a[c]);return b}if(a instanceof Date)return b=new Date,b.setTime(a.getTime()),b;b={},Object.setPrototypeOf(b,Object.getPrototypeOf(a));for(var e in a)a.hasOwnProperty(e)&&(b[e]=l(a[e]));return b};if(Object.extend=function(a){function b(a,b){return function(){var c=this.parent;this.parent=d[a];var e=b.apply(this,arguments);return this.parent=c,e}}function c(){if(!j){for(var a in this)"object"==typeof this[a]&&(this[a]=l(this[a]));this.init&&this.init.apply(this,arguments)}return this}var d=this.prototype;j=!0;var e=new this;j=!1;for(var f in a)e[f]="function"==typeof a[f]&&"function"==typeof d[f]&&k.test(a[f])?b(f,a[f]):a[f];return c.prototype=e,c.constructor=c,c.extend=Object.extend,c},"function"!=typeof Object.create&&(Object.create=function(a){function b(){}return b.prototype=a,new b}),!Function.prototype.bind){var m=function(){};Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError("Function.prototype.bind called on incompatible "+b);var c=Array.prototype.slice.call(arguments,1),d=function(){if(this instanceof d){var e=b.apply(this,c.concat(Array.prototype.slice.call(arguments)));return Object(e)===e?e:this}return b.apply(a,c.concat(Array.prototype.slice.call(arguments)))};return b.prototype&&(m.prototype=b.prototype,d.prototype=new m,m.prototype=null),d}}window.throttle||(window.throttle=function(a,b,c){var d,e=Date.now();return"boolean"!=typeof b&&(b=!1),function(){var f=Date.now(),g=f-e,h=arguments;return a>g?void(b===!1&&(clearTimeout(d),d=setTimeout(function(){return e=f,c.apply(null,h)},g))):(e=f,c.apply(null,h))}}),"undefined"==typeof Date.now&&(Date.now=function(){return(new Date).getTime()}),"undefined"==typeof console&&(console={log:function(){},info:function(){},error:function(){alert(Array.prototype.slice.call(arguments).join(", "))}}),Function.prototype.defer=function(){var a=this,b=Array.prototype.slice.call(arguments);return window.setTimeout(function(){return a.apply(a,b)},.01)},Object.defineProperty||(Object.defineProperty=function(a,b,c){if(!a.__defineGetter__)throw"melonJS: Object.defineProperty not supported";c.get&&a.__defineGetter__(b,c.get),c.set&&a.__defineSetter__(b,c.set)}),Object.getPrototypeOf=Object.getPrototypeOf||function(a){return a.__proto__},Object.setPrototypeOf=Object.setPrototypeOf||function(a,b){return a.__proto__=b,a},String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")}),String.prototype.trimRight||(String.prototype.trimRight=function(){return this.replace(/\s+$/,"")}),String.prototype.isNumeric=function(){return null!==this&&!isNaN(this)&&""!==this.trim()},String.prototype.isBoolean=function(){return null!==this&&("true"===this.trim()||"false"===this.trim())},String.prototype.contains=function(a){return this.indexOf(a)>-1},String.prototype.toHex=function(){for(var a="",b=0;b<this.length;)a+=this.charCodeAt(b++).toString(16);return a},Number.prototype.clamp=function(a,b){return a>this?a:this>b?b:+this},Number.prototype.random=function(a,b){return~~(Math.random()*(b-a+1))+a},Number.prototype.round=function(){var a=arguments.length<2?this:arguments[0],b=Math.pow(10,arguments[1]||arguments[0]||0);return Math.round(a*b)/b},Number.prototype.toHex=function(){return"0123456789ABCDEF".charAt(this-this%16>>4)+"0123456789ABCDEF".charAt(this%16)},Number.prototype.sign=function(){return 0>this?-1:this>0?1:0},Number.prototype.degToRad=function(a){return(a||this)/180*Math.PI},Number.prototype.radToDeg=function(a){return(a||this)*(180/Math.PI)},Array.prototype.remove=function(a){var b=Array.prototype.indexOf.call(this,a);return-1!==b&&Array.prototype.splice.call(this,b,1),this},Array.prototype.forEach||(Array.prototype.forEach=function(a,b){for(var c=0,d=this.length;d--;c++)a.call(b||this,this[c],c,this)}),Object.defineProperty(me,"initialized",{get:function(){return f}}),me.game=function(){var a={},b=null,c=!1,d=null,e=!0;return a.viewport=null,a.collisionMap=null,a.currentLevel=null,a.world=null,a.mergeGroup=!0,a.sortOn="z",a.renderer=null,a.NO_OBJECT=0,a.ENEMY_OBJECT=1,a.COLLECTABLE_OBJECT=2,a.ACTION_OBJECT=3,a.onLevelLoaded=null,a.init=function(d,f){c||(d=d||me.video.getWidth(),f=f||me.video.getHeight(),a.viewport=new me.Viewport(0,0,d,f),a.world=new me.ObjectContainer(0,0,d,f),a.world.name="rootContainer",b=me.video.getSystemContext(),me.event.publish(me.event.GAME_INIT),e=!0,c=!0)},a.reset=function(){a.removeAll(),a.viewport&&a.viewport.reset(),b.setTransform(1,0,0,1,0,0),a.currentLevel={pos:{x:0,y:0}}},a.loadTMXLevel=function(c){a.world.autoSort=!1,a.currentLevel=c,a.collisionMap=a.currentLevel.getLayerByName("collision"),a.collisionMap&&a.collisionMap.isCollisionMap||console.error("WARNING : no collision map detected");for(var d=a.currentLevel.getLayers(),e=d.length;e--;)d[e].visible&&a.add(d[e]);a.viewport.setBounds(Math.max(a.currentLevel.width,a.viewport.width),Math.max(a.currentLevel.height,a.viewport.height));for(var f=a.world,g=a.currentLevel.getObjectGroups(),h=0;h<g.length;h++){var i=g[h];a.mergeGroup===!1&&(f=new me.ObjectContainer,f.name=i.name,f.visible=i.visible,f.z=i.z,f.setOpacity(i.opacity),f.autoSort=!1);for(var j=0;j<i.objects.length;j++){var k=i.objects[j],l=me.entityPool.newInstanceOf(k.name,k.x,k.y,k);l&&(l.z=i.z,a.mergeGroup===!0&&l.isRenderable===!0&&(l.setOpacity(l.getOpacity()*i.opacity),null!==l.renderable&&l.renderable.setOpacity(l.renderable.getOpacity()*i.opacity)),f.addChild(l))}a.mergeGroup===!1&&(a.world.addChild(f),f.autoSort=!0)}a.world.sort(!0),a.world.autoSort=!0,a.currentLevel.pos.x!==a.currentLevel.pos.y&&b.translate(a.currentLevel.pos.x,a.currentLevel.pos.y),a.onLevelLoaded&&a.onLevelLoaded.call(a.onLevelLoaded,c.name),me.event.publish(me.event.LEVEL_LOADED,[c.name])},a.add=function(b,c){"undefined"!=typeof c&&(b.z=c),a.world.addChild(b)},a.getEntityByName=function(b){return a.world.getEntityByProp("name",b)},a.getEntityByGUID=function(b){var c=a.world.getEntityByProp("GUID",b);return c.length>0?c[0]:null},a.getEntityByProp=function(b,c){return a.world.getEntityByProp(b,c)},a.getEntityContainer=function(a){return a.ancestor},a.remove=function(a,b){a.ancestor&&(b===!0?a.ancestor.removeChild(a):(a.visible=a.inViewport=!1,d=function(a){"undefined"!=typeof a.ancestor&&a.ancestor.removeChild(a),d=null}.defer(a)))},a.removeAll=function(){d&&(clearTimeout(d),d=null),a.world.destroy()},a.sort=function(){a.world.sort()},a.collide=function(b,c){return a.world.collide(b,c)},a.collideType=function(b,c,d){return a.world.collideType(b,c,d)},a.repaint=function(){e=!0},a.update=function(){return e=a.world.update()||e,e=a.viewport.update(e)||e},a.draw=function(){e&&(a.viewport.screenX=a.viewport.pos.x+~~a.viewport.offset.x,a.viewport.screenY=a.viewport.pos.y+~~a.viewport.offset.y,b.save(),b.translate(-a.viewport.screenX,-a.viewport.screenY),a.viewport.screenX-=a.currentLevel.pos.x,a.viewport.screenY-=a.currentLevel.pos.y,a.world.draw(b,a.viewport),b.restore(),a.viewport.draw(b)),e=!1},a}()}(window),function(a){me.device=function(){function b(a){a.reading?(d.accelerationX=a.reading.accelerationX,d.accelerationY=a.reading.accelerationY,d.accelerationZ=a.reading.accelerationZ):(d.accelerationX=a.accelerationIncludingGravity.x,d.accelerationY=a.accelerationIncludingGravity.y,d.accelerationZ=a.accelerationIncludingGravity.z)}function c(a){d.gamma=a.gamma,d.beta=a.beta,d.alpha=a.alpha}var d={},e=!1,f=!1,g=null;return d._check=function(){me.audio.detectCapabilities(),me.device.pointerEnabled=navigator.pointerEnabled||navigator.msPointerEnabled,navigator.maxTouchPoints=navigator.maxTouchPoints||navigator.msMaxTouchPoints||0,a.gesture=a.gesture||a.MSGesture,me.device.touch="createTouch"in document||"ontouchstart"in a||navigator.isCocoonJS||navigator.maxTouchPoints>0,me.device.isMobile=me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i)||!1,me.device.hasAccelerometer="undefined"!=typeof a.DeviceMotionEvent||"undefined"!=typeof a.Windows&&"function"==typeof Windows.Devices.Sensors.Accelerometer,a.DeviceOrientationEvent&&(me.device.hasDeviceOrientation=!0);try{d.localStorage=!!a.localStorage}catch(b){d.localStorage=!1}},d.ua=navigator.userAgent,d.sound=!1,d.localStorage=!1,d.hasAccelerometer=!1,d.hasDeviceOrientation=!1,d.nativeBase64="function"==typeof a.atob,d.touch=!1,d.isMobile=!1,d.orientation=0,d.accelerationX=0,d.accelerationY=0,d.accelerationZ=0,d.gamma=0,d.beta=0,d.alpha=0,d.getPixelRatio=function(){if(null===g){var b=me.video.getScreenContext(),c=a.devicePixelRatio||1,d=b.webkitBackingStorePixelRatio||b.mozBackingStorePixelRatio||b.msBackingStorePixelRatio||b.oBackingStorePixelRatio||b.backingStorePixelRatio||1;g=c/d}return g},d.getStorage=function(a){switch(a=a||"local"){case"local":return me.save}throw"melonJS : storage type "+a+" not supported"},d.watchAccelerometer=function(){if(me.device.hasAccelerometer){if(!e){if("undefined"==typeof Windows)a.addEventListener("devicemotion",b,!1);else{var c=Windows.Devices.Sensors.Accelerometer.getDefault();if(c){var d=c.minimumReportInterval,f=d>=16?d:25;c.reportInterval=f,c.addEventListener("readingchanged",b,!1)}}e=!0}return!0}return!1},d.unwatchAccelerometer=function(){if(e){if("undefined"==typeof Windows)a.removeEventListener("devicemotion",b,!1);else{var c=Windows.Device.Sensors.Accelerometer.getDefault();c.removeEventListener("readingchanged",b,!1)}e=!1}},d.watchDeviceOrientation=function(){return me.device.hasDeviceOrientation&&!f&&(a.addEventListener("deviceorientation",c,!1),f=!0),!1},d.unwatchDeviceOrientation=function(){f&&(a.removeEventListener("deviceorientation",c,!1),f=!1)},d}()}(window),function(){me.timer=function(){var a={},b=0,c=0,d=0,e=0,f=0,g=Math.ceil(1e3/me.sys.fps),h=1e3/me.sys.fps*1.25;return a.tick=1,a.fps=0,a.init=function(){a.reset()},a.reset=function(){e=d=Date.now(),c=0,b=0},a.getTime=function(){return e},a.countFPS=function(){b++,c+=f,b%10===0&&(this.fps=(~~(1e3*b/c)).clamp(0,me.sys.fps),c=0,b=0)},a.update=function(){d=e,e=Date.now(),f=e-d,a.tick=f>h&&me.sys.interpolation?f/g:1},a}()}(window),function(){me.Vector2d=Object.extend({x:0,y:0,init:function(a,b){this.x=a||0,this.y=b||0},set:function(a,b){return this.x=a,this.y=b,this},setZero:function(){return this.set(0,0)},setV:function(a){return this.x=a.x,this.y=a.y,this},add:function(a){return this.x+=a.x,this.y+=a.y,this},sub:function(a){return this.x-=a.x,this.y-=a.y,this},scale:function(a){return this.x*=a.x,this.y*=a.y,this},div:function(a){return this.x/=a,this.y/=a,this},abs:function(){return this.x<0&&(this.x=-this.x),this.y<0&&(this.y=-this.y),this},clamp:function(a,b){return new me.Vector2d(this.x.clamp(a,b),this.y.clamp(a,b))},clampSelf:function(a,b){return this.x=this.x.clamp(a,b),this.y=this.y.clamp(a,b),this},minV:function(a){return this.x=this.x<a.x?this.x:a.x,this.y=this.y<a.y?this.y:a.y,this},maxV:function(a){return this.x=this.x>a.x?this.x:a.x,this.y=this.y>a.y?this.y:a.y,this},floor:function(){return new me.Vector2d(~~this.x,~~this.y)},floorSelf:function(){return this.x=~~this.x,this.y=~~this.y,this},ceil:function(){return new me.Vector2d(Math.ceil(this.x),Math.ceil(this.y))},ceilSelf:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},negate:function(){return new me.Vector2d(-this.x,-this.y)},negateSelf:function(){return this.x=-this.x,this.y=-this.y,this},copy:function(a){return this.x=a.x,this.y=a.y,this},equals:function(a){return this.x===a.x&&this.y===a.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},normalize:function(){var a=this.length();if(a<Number.MIN_VALUE)return 0;var b=1/a;return this.x*=b,this.y*=b,a},dotProduct:function(a){return this.x*a.x+this.y*a.y},distance:function(a){return Math.sqrt((this.x-a.x)*(this.x-a.x)+(this.y-a.y)*(this.y-a.y))},angle:function(a){return Math.atan2(a.y-this.y,a.x-this.x)},clone:function(){return new me.Vector2d(this.x,this.y)},toString:function(){return"x:"+this.x+",y:"+this.y}})}(window),function(){me.Rect=Object.extend({pos:null,colPos:null,width:0,height:0,hWidth:0,hHeight:0,shapeType:"Rectangle",offset:null,init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),this.pos.setV(a),null===this.offset&&(this.offset=new me.Vector2d),this.offset.set(0,0),null===this.colPos&&(this.colPos=new me.Vector2d),this.colPos.setV(0,0),this.width=b,this.height=c,this.hWidth=~~(b/2),this.hHeight=~~(c/2),Object.defineProperty(this,"left",{get:function(){return this.pos.x},configurable:!0}),Object.defineProperty(this,"right",{get:function(){return this.pos.x+this.width},configurable:!0}),Object.defineProperty(this,"top",{get:function(){return this.pos.y},configurable:!0}),Object.defineProperty(this,"bottom",{get:function(){return this.pos.y+this.height},configurable:!0})},set:function(a,b,c){this.pos.setV(a),this.width=b,this.height=c,this.hWidth=~~(b/2),this.hHeight=~~(c/2),this.offset.set(0,0)},getBounds:function(){return this.clone()},clone:function(){return new me.Rect(this.pos.clone(),this.width,this.height)},translate:function(a,b){return this.pos.x+=a,this.pos.y+=b,this},translateV:function(a){return this.pos.add(a),this},union:function(a){var b=Math.min(this.pos.x,a.pos.x),c=Math.min(this.pos.y,a.pos.y);return this.width=Math.ceil(Math.max(this.pos.x+this.width,a.pos.x+a.width)-b),this.height=Math.ceil(Math.max(this.pos.y+this.height,a.pos.y+a.height)-c),this.hWidth=~~(this.width/2),this.hHeight=~~(this.height/2),this.pos.x=~~b,this.pos.y=~~c,this},adjustSize:function(a,b,c,d){-1!==a&&(this.colPos.x=a,this.width=b,this.hWidth=~~(this.width/2),this.left!==this.pos.x+this.colPos.x&&Object.defineProperty(this,"left",{get:function(){return this.pos.x+this.colPos.x},configurable:!0}),this.right!==this.pos.x+this.colPos.x+this.width&&Object.defineProperty(this,"right",{get:function(){return this.pos.x+this.colPos.x+this.width},configurable:!0})),-1!==c&&(this.colPos.y=c,this.height=d,this.hHeight=~~(this.height/2),this.top!==this.pos.y+this.colPos.y&&Object.defineProperty(this,"top",{get:function(){return this.pos.y+this.colPos.y},configurable:!0}),this.bottom!==this.pos.y+this.colPos.y+this.height&&Object.defineProperty(this,"bottom",{get:function(){return this.pos.y+this.colPos.y+this.height},configurable:!0}))},flipX:function(a){this.colPos.x=a-this.width-this.colPos.x,this.hWidth=~~(this.width/2)},flipY:function(a){this.colPos.y=a-this.height-this.colPos.y,this.hHeight=~~(this.height/2)},equals:function(a){return this.left===a.left&&this.right===a.right&&this.top===a.top&&this.bottom===a.bottom},overlaps:function(a){return this.left<a.right&&a.left<this.right&&this.top<a.bottom&&a.top<this.bottom},within:function(a){return a.left<=this.left&&a.right>=this.right&&a.top<=this.top&&a.bottom>=this.bottom},contains:function(a){return a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom},containsPointV:function(a){return this.containsPoint(a.x,a.y)},containsPoint:function(a,b){return a>=this.left&&a<=this.right&&b>=this.top&&b<=this.bottom},collideWithRectangle:function(a){var b=new me.Vector2d(0,0);if(this.overlaps(a)){var c=this.left+this.hWidth-a.left-a.hWidth,d=this.top+this.hHeight-a.top-a.hHeight;b.x=a.hWidth+this.hWidth-(0>c?-c:c),b.y=a.hHeight+this.hHeight-(0>d?-d:d),b.x<b.y?(b.y=0,b.x=0>c?-b.x:b.x):(b.x=0,b.y=0>d?-b.y:b.y)}return b},draw:function(a,b){a.strokeStyle=b||"red",a.strokeRect(this.left,this.top,this.width,this.height)}}),me.Ellipse=Object.extend({pos:null,radius:null,shapeType:"Ellipse",init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),null===this.radius&&(this.radius=new me.Vector2d),this.set(a,b,c)},set:function(a,b,c){this.radius.set(b/2,c/2),this.pos.setV(a).add(this.radius),this.offset=new me.Vector2d},getBounds:function(){return new me.Rect(this.pos.clone().sub(this.radius),2*this.radius.x,2*this.radius.y)},clone:function(){return new me.Ellipse(this.pos.clone(),2*this.radius.x,2*this.radius.y)},draw:function(a,b){a.save(),a.beginPath(),a.translate(this.pos.x-this.radius.x,this.pos.y-this.radius.y),a.scale(this.radius.x,this.radius.y),a.arc(1,1,1,0,2*Math.PI,!1),a.restore(),a.strokeStyle=b||"red",a.stroke()}}),me.PolyShape=Object.extend({offset:null,pos:null,points:null,closed:null,shapeType:"PolyShape",init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),null===this.offset&&(this.offset=new me.Vector2d),this.set(a,b,c)},set:function(a,b,c){this.pos.setV(a),this.points=b,this.closed=c===!0,this.offset.set(0,0),this.getBounds()},getBounds:function(){var a=this.offset,b=0,c=0;return this.points.forEach(function(d){a.x=Math.min(a.x,d.x),a.y=Math.min(a.y,d.y),b=Math.max(b,d.x),c=Math.max(c,d.y)}),new me.Rect(a,b-a.x,c-a.y)},clone:function(){return new me.PolyShape(this.pos.clone(),this.points,this.closed)},draw:function(a,b){a.save(),a.translate(-this.offset.x,-this.offset.y),a.strokeStyle=b||"red",a.beginPath(),a.moveTo(this.points[0].x,this.points[0].y),this.points.forEach(function(b){a.lineTo(b.x,b.y),a.moveTo(b.x,b.y)}),this.closed===!0&&a.lineTo(this.points[0].x,this.points[0].y),a.stroke(),a.restore()}})}(window),function(){me.debug={renderCollisionMap:!1}}(window),function(){me.Renderable=me.Rect.extend({isRenderable:!0,GUID:void 0,visible:!0,inViewport:!1,alwaysUpdate:!1,updateWhenPaused:!1,isPersistent:!1,floating:!1,z:0,anchorPoint:null,alpha:1,init:function(a,b,c){this.parent(a,b,c),null===this.anchorPoint&&(this.anchorPoint=new me.Vector2d),this.anchorPoint.set(.5,.5),this.setOpacity(1)},getOpacity:function(){return this.alpha},setOpacity:function(a){"number"==typeof a&&(this.alpha=a.clamp(0,1))},update:function(){return!1},draw:function(a,b){this.parent(a,b)}})}(window),function(){me.SpriteObject=me.Renderable.extend({scale:null,scaleFlag:!1,lastflipX:!1,lastflipY:!1,z:0,offset:null,angle:0,_sourceAngle:0,image:null,flickering:!1,flickerTimer:-1,flickercb:null,flickerState:!1,init:function(a,b,c,d,e){this.isSprite=!0,this.parent(new me.Vector2d(a,b),d||c.width,e||c.height),this.image=c,this.scale=new me.Vector2d(1,1),this.lastflipX=this.lastflipY=!1,this.scaleFlag=!1,this.offset=new me.Vector2d(0,0),this.visible=!0,this.isPersistent=!1,this.flickering=!1},setTransparency:function(a){a="#"===a.charAt(0)?a.substring(1,7):a,this.image=me.video.applyRGBFilter(this.image,"transparent",a.toUpperCase()).canvas},isFlickering:function(){return this.flickering},flicker:function(a,b){this.flickerTimer=a,this.flickerTimer<0?(this.flickering=!1,this.flickercb=null):this.flickering||(this.flickercb=b,this.flickering=!0)},flipX:function(a){a!==this.lastflipX&&(this.lastflipX=a,this.scale.x=-this.scale.x,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},flipY:function(a){a!==this.lastflipY&&(this.lastflipY=a,this.scale.y=-this.scale.y,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},resize:function(a){a>0&&(this.scale.x=this.scale.x<0?-a:a,this.scale.y=this.scale.y<0?-a:a,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},update:function(){return this.flickering?(this.flickerTimer-=me.timer.tick,this.flickerTimer<0&&(this.flickercb&&this.flickercb(),this.flicker(-1)),!0):!1},draw:function(a){if(!this.flickering||(this.flickerState=!this.flickerState,this.flickerState)){a.save(),a.globalAlpha*=this.getOpacity();var b=~~this.pos.x,c=~~this.pos.y,d=this.width,e=this.height,f=this.angle+this._sourceAngle;if(this.scaleFlag||0!==f){var g=d*this.anchorPoint.x,h=e*this.anchorPoint.y;a.translate(b+g,c+h),this.scaleFlag&&a.scale(this.scale.x,this.scale.y),0!==f&&a.rotate(f),0!==this._sourceAngle?(d=this.height,e=this.width,b=-h,c=-g):(b=-g,c=-h)}a.drawImage(this.image,this.offset.x,this.offset.y,d,e,b,c,d,e),a.restore()}},destroy:function(){this.onDestroyEvent.apply(this,arguments)},onDestroyEvent:function(){}}),me.AnimationSheet=me.SpriteObject.extend({spacing:0,margin:0,animationpause:!1,animationspeed:100,init:function(a,b,c,d,e,f,g,h,i){this.anim={},this.resetAnim=null,this.current=null,this.animationspeed=100,this.spacing=f||0,this.margin=g||0,this.parent(a,b,c,d,e,f,g),this.textureAtlas=null,this.atlasIndices=null,this.buildLocalAtlas(h||void 0,i||void 0),this.addAnimation("default",null),this.setCurrentAnimation("default")},buildLocalAtlas:function(a,b){if(void 0!==a)this.textureAtlas=a,this.atlasIndices=b;else{this.textureAtlas=[];for(var c=new me.Vector2d(~~((this.image.width-this.margin)/(this.width+this.spacing)),~~((this.image.height-this.margin)/(this.height+this.spacing))),d=0,e=c.x*c.y;e>d;d++)this.textureAtlas[d]={name:""+d,offset:new me.Vector2d(this.margin+(this.spacing+this.width)*(d%c.x),this.margin+(this.spacing+this.height)*~~(d/c.x)),width:this.width,height:this.height,hWidth:this.width/2,hHeight:this.height/2,angle:0}}},addAnimation:function(a,b,c){if(this.anim[a]={name:a,frame:[],idx:0,length:0,animationspeed:c||this.animationspeed,nextFrame:0},null==b){b=[];var d=0;this.textureAtlas.forEach(function(){b[d]=d++})}for(var e=0,f=b.length;f>e;e++)if("number"==typeof b[e])this.anim[a].frame[e]=this.textureAtlas[b[e]];else{if(null===this.atlasIndices)throw"melonjs: string parameters for addAnimation are only allowed for TextureAtlas ";this.anim[a].frame[e]=this.textureAtlas[this.atlasIndices[b[e]]]}this.anim[a].length=this.anim[a].frame.length},setCurrentAnimation:function(a,b){if(!this.anim[a])throw"melonJS: animation id '"+a+"' not defined";this.current=this.anim[a],this.resetAnim=b||null,this.setAnimationFrame(this.current.idx),this.current.nextFrame=me.timer.getTime()+this.current.animationspeed},isCurrentAnimation:function(a){return this.current.name===a},setAnimationFrame:function(a){this.current.idx=(a||0)%this.current.length;var b=this.current.frame[this.current.idx];this.offset=b.offset,this.width=b.width,this.height=b.height,this.hWidth=b.hWidth,this.hHeight=b.hHeight,this._sourceAngle=b.angle},getCurrentAnimationFrame:function(){return this.current.idx},update:function(){if(!this.animationpause&&me.timer.getTime()>=this.current.nextFrame){if(this.setAnimationFrame(++this.current.idx),0===this.current.idx&&this.resetAnim)if("string"==typeof this.resetAnim)this.setCurrentAnimation(this.resetAnim);else if("function"==typeof this.resetAnim&&this.resetAnim()===!1)return this.current.idx=this.current.length-1,this.setAnimationFrame(this.current.idx),this.parent(),!1;return this.current.nextFrame=me.timer.getTime()+this.current.animationspeed,this.parent()||!0}return this.parent()}})}(window),function(){var a=-(Math.PI/2);me.TextureAtlas=Object.extend({format:null,texture:null,atlas:null,init:function(a,b){if(a&&a.meta){if(a.meta.app.contains("texturepacker"))if(this.format="texturepacker",void 0===b){var c=me.utils.getBasename(a.meta.image);if(this.texture=me.loader.getImage(c),null===this.texture)throw"melonjs: Atlas texture '"+c+"' not found"}else this.texture=b;if(a.meta.app.contains("ShoeBox")){if(!a.meta.exporter||!a.meta.exporter.contains("melonJS"))throw"melonjs: ShoeBox requires the JSON exporter : https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx";this.format="ShoeBox",this.texture=b}this.atlas=this.initFromTexturePacker(a)}if(null===this.atlas)throw"melonjs: texture atlas format not supported"},initFromTexturePacker:function(a){var b={};return a.frames.forEach(function(a){a.hasOwnProperty("filename")&&(b[a.filename]={frame:new me.Rect(new me.Vector2d(a.frame.x,a.frame.y),a.frame.w,a.frame.h),source:new me.Rect(new me.Vector2d(a.spriteSourceSize.x,a.spriteSourceSize.y),a.spriteSourceSize.w,a.spriteSourceSize.h),rotated:a.rotated===!0,trimmed:a.trimmed===!0})}),b},getTexture:function(){return this.texture},getRegion:function(b){var c=this.atlas[b];return c?{name:b,pos:c.source.pos.clone(),offset:c.frame.pos.clone(),width:c.frame.width,height:c.frame.height,hWidth:c.frame.width/2,hHeight:c.frame.height/2,angle:c.rotated===!0?a:0}:null},createSpriteFromName:function(a){var b=this.getRegion(a);if(b){var c=new me.SpriteObject(0,0,this.getTexture(),b.width,b.height);return c.offset.setV(b.offset),c._sourceAngle=b.angle,c}throw"melonjs: TextureAtlas - region for "+a+" not found"},createAnimationFromName:function(a){for(var b=[],c={},d=0;d<a.length;++d)if(b[d]=this.getRegion(a[d]),c[a[d]]=d,null==b[d])throw"melonjs: TextureAtlas - region for "+a[d]+" not found";return new me.AnimationSheet(0,0,this.texture,0,0,0,0,b,c)}})}(window),function(){var a=Math.min,b=Math.max;me.Viewport=me.Rect.extend({AXIS:{NONE:0,HORIZONTAL:1,VERTICAL:2,BOTH:3},limits:null,target:null,follow_axis:0,shaking:!1,_shake:null,_fadeIn:null,_fadeOut:null,_deadwidth:0,_deadheight:0,_limitwidth:0,_limitheight:0,screenX:0,screenY:0,init:function(a,b,c,d,e,f){this.parent(new me.Vector2d(a,b),c-a,d-b),this.limits=new me.Vector2d(e||this.width,f||this.height),this.offset=new me.Vector2d,this.target=null,this.follow_axis=this.AXIS.NONE,this._shake={intensity:0,duration:0,axis:this.AXIS.BOTH,onComplete:null,start:0},this._fadeOut={color:0,alpha:0,duration:0,tween:null},this._fadeIn={color:0,alpha:1,duration:0,tween:null},this.setDeadzone(this.width/6,this.height/6)},_followH:function(c){var d=this.pos.x;return c.x-this.pos.x>this._deadwidth?this.pos.x=~~a(c.x-this._deadwidth,this._limitwidth):c.x-this.pos.x<this.deadzone.x&&(this.pos.x=~~b(c.x-this.deadzone.x,0)),d!==this.pos.x},_followV:function(c){var d=this.pos.y;return c.y-this.pos.y>this._deadheight?this.pos.y=~~a(c.y-this._deadheight,this._limitheight):c.y-this.pos.y<this.deadzone.y&&(this.pos.y=~~b(c.y-this.deadzone.y,0)),d!==this.pos.y},reset:function(a,b){this.pos.x=a||0,this.pos.y=b||0,this.target=null,this.follow_axis=null},setDeadzone:function(a,b){this.deadzone=new me.Vector2d(~~((this.width-a)/2),~~((this.height-b)/2-.25*b)),this._deadwidth=this.width-this.deadzone.x,this._deadheight=this.height-this.deadzone.y,this.update(!0)},setBounds:function(a,b){this.limits.set(a,b),this._limitwidth=this.limits.x-this.width,this._limitheight=this.limits.y-this.height},follow:function(a,b){if(a instanceof me.ObjectEntity)this.target=a.pos;else{if(!(a instanceof me.Vector2d))throw"melonJS: invalid target for viewport.follow";this.target=a}this.follow_axis="undefined"==typeof b?this.AXIS.BOTH:b,this.update(!0)},move:function(a,b){var c=~~(this.pos.x+a),d=~~(this.pos.y+b);this.pos.x=c.clamp(0,this._limitwidth),this.pos.y=d.clamp(0,this._limitheight),me.event.publish(me.event.VIEWPORT_ONCHANGE,[this.pos])},update:function(a){var b=!1;if(this.target&&a)switch(this.follow_axis){case this.AXIS.NONE:break;case this.AXIS.HORIZONTAL:b=this._followH(this.target);break;case this.AXIS.VERTICAL:b=this._followV(this.target);break;case this.AXIS.BOTH:b=this._followH(this.target),b=this._followV(this.target)||b}if(this.shaking===!0){var c=me.timer.getTime()-this._shake.start;c>=this._shake.duration?(this.shaking=!1,this.offset.setZero(),"function"==typeof this._shake.onComplete&&this._shake.onComplete()):((this._shake.axis===this.AXIS.BOTH||this._shake.axis===this.AXIS.HORIZONTAL)&&(this.offset.x=(Math.random()-.5)*this._shake.intensity),(this._shake.axis===this.AXIS.BOTH||this._shake.axis===this.AXIS.VERTICAL)&&(this.offset.y=(Math.random()-.5)*this._shake.intensity)),b=!0}return b===!0&&me.event.publish(me.event.VIEWPORT_ONCHANGE,[this.pos]),(null!=this._fadeIn.tween||null!=this._fadeOut.tween)&&(b=!0),b},shake:function(a,b,c,d){this.shaking||(this.shaking=!0,this._shake={intensity:a,duration:b,axis:c||this.AXIS.BOTH,onComplete:d||null,start:me.timer.getTime()})},fadeOut:function(a,b,c){this._fadeOut.color=a,this._fadeOut.duration=b||1e3,this._fadeOut.alpha=1,this._fadeOut.tween=me.entityPool.newInstanceOf("me.Tween",this._fadeOut).to({alpha:0},this._fadeOut.duration).onComplete(c||null),this._fadeOut.tween.start()},fadeIn:function(a,b,c){this._fadeIn.color=a,this._fadeIn.duration=b||1e3,this._fadeIn.alpha=0,this._fadeIn.tween=me.entityPool.newInstanceOf("me.Tween",this._fadeIn).to({alpha:1},this._fadeIn.duration).onComplete(c||null),this._fadeIn.tween.start()},getWidth:function(){return this.width},getHeight:function(){return this.height},focusOn:function(a){this.pos.x=a.x-.5*this.width,this.pos.y=a.y-.5*this.height},isVisible:function(a){return a.overlaps(this)},localToWorld:function(a,b){return new me.Vector2d(a,b).add(this.pos).sub(me.game.currentLevel.pos)},worldToLocal:function(a,b){return new me.Vector2d(a,b).sub(this.pos).add(me.game.currentLevel.pos)},draw:function(a){this._fadeIn.tween&&(a.globalAlpha=this._fadeIn.alpha,me.video.clearSurface(a,me.utils.HexToRGB(this._fadeIn.color)),a.globalAlpha=1,1===this._fadeIn.alpha&&(this._fadeIn.tween=null)),this._fadeOut.tween&&(a.globalAlpha=this._fadeOut.alpha,me.video.clearSurface(a,me.utils.HexToRGB(this._fadeOut.color)),a.globalAlpha=1,0===this._fadeOut.alpha&&(this._fadeOut.tween=null)),me.video.blitSurface()}})}(window),function(){me.GUI_Object=me.SpriteObject.extend({isClickable:!0,updated:!1,init:function(a,b,c){this.parent(a,b,"string"==typeof c.image?me.loader.getImage(c.image):c.image,c.spritewidth,c.spriteheight),this.floating=!0,me.input.registerPointerEvent("mousedown",this,this.clicked.bind(this))},update:function(){return this.updated?(this.updated=!1,!0):!1},clicked:function(a){return this.isClickable?(this.updated=!0,this.onClick(a)):void 0},onClick:function(){return!1},onDestroyEvent:function(){me.input.releasePointerEvent("mousedown",this)}})}(window),function(){var a=new me.Rect(new me.Vector2d,0,0),b=0;me.ObjectContainer=me.Renderable.extend({sortOn:"z",autoSort:!0,pendingSort:null,children:null,collidable:!0,init:function(a,b,c,d){this.parent(new me.Vector2d(a||0,b||0),c||1/0,d||1/0),this.children=[],this.sortOn=me.game.sortOn,this.autoSort=!0},addChild:function(a){"undefined"!=typeof a.ancestor?a.ancestor.removeChild(a):a.isRenderable&&(a.GUID=me.utils.createGUID()),"undefined"==typeof a.z&&(a.z=1/0),a.ancestor=this,this.children.push(a),this.autoSort===!0&&this.sort()},addChildAt:function(a,b){if(!(b>=0&&b<this.children.length))throw"melonJS (me.ObjectContainer): Index ("+b+") Out Of Bounds for addChildAt()";"undefined"!=typeof a.ancestor?a.ancestor.removeChild(a):a.isRenderable&&(a.GUID=me.utils.createGUID()),a.ancestor=this,this.children.splice(b,0,a)},swapChildren:function(a,b){var c=this.getChildIndex(a),d=this.getChildIndex(b);if(-1===c||-1===d)throw"melonJS (me.ObjectContainer): "+a+" Both the supplied entities must be a child of the caller "+this;var e=a.z;a.z=b.z,b.z=e,this.children[c]=b,this.children[d]=a},getChildAt:function(a){if(a>=0&&a<this.children.length)return this.children[a];throw"melonJS (me.ObjectContainer): Index ("+a+") Out Of Bounds for getChildAt()"},getChildIndex:function(a){return this.children.indexOf(a)},hasChild:function(a){return this===a.ancestor},getEntityByProp:function(a,b){function c(a,c){"string"==typeof a[c]?a[c].match(f)&&e.push(a):a[c]===b&&e.push(a) | |
}for(var d,e=[],f=new RegExp(b,"i"),g=this.children.length;g--,d=this.children[g];)d instanceof me.ObjectContainer?(c(d,a),e=e.concat(d.getEntityByProp(a,b))):d.isEntity&&c(d,a);return e},removeChild:function(a,b){if(!this.hasChild(a))throw"melonJS (me.ObjectContainer): "+a+" The supplied entity must be a child of the caller "+this;a.ancestor=void 0,b||("function"==typeof a.destroy&&a.destroy(),me.entityPool.freeInstance(a)),this.children.splice(this.getChildIndex(a),1)},setChildsProperty:function(a,b,c){for(var d,e=this.children.length;e--,d=this.children[e];)c===!0&&d instanceof me.ObjectContainer&&d.setChildsProperty(a,b,c),d[a]=b},moveUp:function(a){var b=this.getChildIndex(a);b-1>=0&&this.swapChildren(a,this.getChildAt(b-1))},moveDown:function(a){var b=this.getChildIndex(a);b+1<this.children.length&&this.swapChildren(a,this.getChildAt(b+1))},moveToTop:function(a){var b=this.getChildIndex(a);b>0&&(this.splice(0,0,this.splice(b,1)[0]),a.z=this.children[1].z+1)},moveToBottom:function(a){var b=this.getChildIndex(a);b<this.children.length-1&&(this.splice(this.children.length-1,0,this.splice(b,1)[0]),a.z=this.children[this.children.length-2].z-1)},collide:function(a,b){return this.collideType(a,null,b)},collideType:function(a,b,c){var d,e;c=c===!0?!0:!1,c===!0&&(e=[]);for(var f,g=this.children.length;g--,f=this.children[g];)if((f.inViewport||f.alwaysUpdate)&&f.collidable)if(f instanceof me.ObjectContainer){if(d=f.collideType(a,b,c),c)e.concat(d);else if(d)return d}else if(f!==a&&(!b||f.type===b)&&(d=f.collisionBox["collideWith"+a.shapeType].call(f.collisionBox,a.collisionBox),0!==d.x||0!==d.y)){if(f.onCollision.call(f,d,a),d.type=f.type,d.obj=f,!c)return d;e.push(d)}return c?e:null},sort:function(a){if(null===this.pendingSort){if(a===!0)for(var b,c=this.children.length;c--,b=this.children[c];)b instanceof me.ObjectContainer&&b.sort(a);this.pendingSort=function(a){a.children.sort(a["_sort"+a.sortOn.toUpperCase()]),a.pendingSort=null,me.game.repaint()}.defer(this)}},_sortZ:function(a,b){return b.z-a.z},_sortX:function(a,b){var c=b.z-a.z;return c?c:(b.pos&&b.pos.x)-(a.pos&&a.pos.x)||0},_sortY:function(a,b){var c=b.z-a.z;return c?c:(b.pos&&b.pos.y)-(a.pos&&a.pos.y)||0},destroy:function(){this.pendingSort&&(clearTimeout(this.pendingSort),this.pendingSort=null);for(var a,b=this.children.length;b--,a=this.children[b];)a.isPersistent||this.removeChild(a)},update:function(){for(var c,d,e,f,g=!1,h=!1,i=me.state.isPaused(),j=me.game.viewport,k=this.children.length;k--,f=this.children[k];)(!i||f.updateWhenPaused)&&(f.isRenderable?(h=b>0||f.floating,h&&b++,c=f.visible&&!h,c&&(d=f.pos.x,e=f.pos.y,a.translateV(f.pos),a.set(a.pos,f.width,f.height)),f.inViewport=f.visible&&(h||j.isVisible(a)),g|=(f.inViewport||f.alwaysUpdate)&&f.update(),c&&a.translate(-d,-e),b>0&&b--):g|=f.update());return g},draw:function(a,b){var c=me.game.viewport,d=!1;this.drawCount=0,a.save(),a.globalAlpha*=this.getOpacity(),a.translate(this.pos.x,this.pos.y);for(var e,f=this.children.length;f--,e=this.children[f];)d=e.floating,e.isRenderable&&(e.inViewport||d&&e.visible)&&(d===!0&&(a.save(),a.translate(c.screenX-this.pos.x,c.screenY-this.pos.y)),e.draw(a,b),d===!0&&a.restore(),this.drawCount++);a.restore()}})}(window),function(){me.ObjectSettings={name:null,image:null,transparent_color:null,spritewidth:null,spriteheight:null,type:0,collidable:!0},me.entityPool=function(){var a={},b={};return a.init=function(){a.add("me.ObjectEntity",me.ObjectEntity),a.add("me.CollectableEntity",me.CollectableEntity),a.add("me.LevelEntity",me.LevelEntity),a.add("me.Tween",me.Tween,!0)},a.add=function(a,c,d){return d?void(b[a.toLowerCase()]={"class":c,pool:[],active:[]}):void(b[a.toLowerCase()]={"class":c,pool:void 0})},a.newInstanceOf=function(a){var c="string"==typeof a?a.toLowerCase():void 0,d=Array.prototype.slice.call(arguments);if(c&&b[c]){var e;if(!b[c].pool)return e=b[c]["class"],d[0]=e,new(e.bind.apply(e,d));var f,g=b[c];return e=g["class"],g.pool.length>0?(f=g.pool.pop(),"function"==typeof f.init&&f.init.apply(f,d.slice(1)),"function"==typeof f.onResetEvent&&f.onResetEvent.apply(f,d.slice(1))):(d[0]=e,f=new(e.bind.apply(e,d)),f.className=c),g.active.push(f),f}var h=arguments[3];return h&&h.gid&&h.image?new me.SpriteObject(h.x,h.y,h.image):(c&&console.error("Cannot instantiate entity of type '"+a+"': Class not found!"),null)},a.purge=function(){for(var a in b)b[a].pool=[]},a.freeInstance=function(a){var c=a.className;if(c&&b[c]){for(var d=!0,e=0,f=b[c].active.length;f>e;e++)if(b[c].active[e]===a){d=!1,b[c].active.splice(e,1);break}d||b[c].pool.push(a)}},a}(),me.ObjectEntity=me.Renderable.extend({type:0,collidable:!0,collisionBox:null,shapes:null,renderable:null,lastflipX:!1,lastflipY:!1,init:function(a,b,c){if(null===this.pos&&(this.pos=new me.Vector2d),this.parent(this.pos.set(a,b),~~c.spritewidth||~~c.width,~~c.spriteheight||~~c.height),c.image){var d="string"==typeof c.image?me.loader.getImage(c.image):c.image;this.renderable=new me.AnimationSheet(0,0,d,~~c.spritewidth,~~c.spriteheight,~~c.spacing,~~c.margin),c.transparent_color&&this.renderable.setTransparency(c.transparent_color)}this.name=c.name?c.name.toLowerCase():"",void 0===this.vel&&(this.vel=new me.Vector2d),this.vel.set(0,0),void 0===this.accel&&(this.accel=new me.Vector2d),this.accel.set(0,0),void 0===this.friction&&(this.friction=new me.Vector2d),this.friction.set(0,0),void 0===this.maxVel&&(this.maxVel=new me.Vector2d),this.maxVel.set(1e3,1e3),this.gravity=void 0!==me.sys.gravity?me.sys.gravity:.98,this.isEntity=!0,this.alive=!0,this.visible=!0,this.floating=!1,this.isPersistent=!1,this.falling=!1,this.jumping=!0,this.slopeY=0,this.onslope=!1,this.onladder=!1,this.disableTopLadderCollision=!1,this.collidable="undefined"!=typeof c.collidable?c.collidable:!0,this.type=c.type||0,this.lastflipX=this.lastflipY=!1,this.collisionMap=me.game.collisionMap,this.canBreakTile=!1,this.onTileBreak=null,c.isEllipse===!0?this.addShape(new me.Ellipse(new me.Vector2d(0,0),this.width,this.height)):c.isPolygon===!0||c.isPolyline===!0?(this.addShape(new me.PolyShape(new me.Vector2d(0,0),c.points,c.isPolygon)),this.width=this.collisionBox.width,this.height=this.collisionBox.height):this.addShape(new me.Rect(new me.Vector2d(0,0),this.width,this.height))},updateColRect:function(a,b,c,d){this.collisionBox.adjustSize(a,b,c,d)},addShape:function(a){null===this.shapes&&(this.shapes=[]),this.shapes.push(a),1===this.shapes.length&&(this.collisionBox=this.shapes[0].getBounds(),this.collisionBox.pos=this.pos,this.pos.add(this.shapes[0].offset))},onCollision:function(){this.collidable&&this.type===me.game.COLLECTABLE_OBJECT&&me.game.remove(this)},setVelocity:function(a,b){this.accel.x=0!==a?a:this.accel.x,this.accel.y=0!==b?b:this.accel.y,this.setMaxVelocity(a,b)},setMaxVelocity:function(a,b){this.maxVel.x=a,this.maxVel.y=b},setFriction:function(a,b){this.friction.x=a||0,this.friction.y=b||0},flipX:function(a){a!==this.lastflipX&&(this.lastflipX=a,this.renderable&&this.renderable.flipX&&this.renderable.flipX(a),this.collisionBox.flipX(this.width))},flipY:function(a){a!==this.lastflipY&&(this.lastflipY=a,this.renderable&&this.renderable.flipY&&this.renderable.flipY(a),this.collisionBox.flipY(this.height))},doWalk:function(a){this.flipX(a),this.vel.x+=a?-this.accel.x*me.timer.tick:this.accel.x*me.timer.tick},doClimb:function(a){return this.onladder?(this.vel.y=a?-this.accel.x*me.timer.tick:this.accel.x*me.timer.tick,this.disableTopLadderCollision=!a,!0):!1},doJump:function(){return this.jumping||this.falling?!1:(this.vel.y=-this.maxVel.y*me.timer.tick,this.jumping=!0,!0)},forceJump:function(){this.jumping=this.falling=!1,this.doJump()},distanceTo:function(a){var b=this.pos.x+this.hWidth-(a.pos.x+a.hWidth),c=this.pos.y+this.hHeight-(a.pos.y+a.hHeight);return Math.sqrt(b*b+c*c)},distanceToPoint:function(a){var b=this.pos.x+this.hWidth-a.x,c=this.pos.y+this.hHeight-a.y;return Math.sqrt(b*b+c*c)},angleTo:function(a){var b=a.pos.x+a.hWidth-(this.pos.x+this.hWidth),c=a.pos.y+a.hHeight-(this.pos.y+this.hHeight);return Math.atan2(c,b)},angleToPoint:function(a){var b=a.x-(this.pos.x+this.hWidth),c=a.y-(this.pos.y+this.hHeight);return Math.atan2(c,b)},checkSlope:function(a,b){this.pos.y=a.pos.y-this.height,this.slopeY=b?a.height-(this.collisionBox.right+this.vel.x-a.pos.x):this.collisionBox.left+this.vel.x-a.pos.x,this.vel.y=0,this.pos.y+=this.slopeY.clamp(0,a.height)},computeVelocity:function(a){this.gravity&&(a.y+=this.onladder?0:this.gravity*me.timer.tick,this.falling=a.y>0,this.jumping=this.falling?!1:this.jumping),this.friction.x&&(a.x=me.utils.applyFriction(a.x,this.friction.x)),this.friction.y&&(a.y=me.utils.applyFriction(a.y,this.friction.y)),0!==a.y&&(a.y=a.y.clamp(-this.maxVel.y,this.maxVel.y)),0!==a.x&&(a.x=a.x.clamp(-this.maxVel.x,this.maxVel.x))},updateMovement:function(){this.computeVelocity(this.vel);var a;return this.collidable&&(a=this.collisionMap.checkCollision(this.collisionBox,this.vel),this.onslope=a.yprop.isSlope||a.xprop.isSlope,this.onladder=!1,a.y&&(this.onladder=a.yprop.isLadder||a.yprop.isTopLadder,a.y>0?a.yprop.isSolid||a.yprop.isPlatform&&this.collisionBox.bottom-1<=a.ytile.pos.y||a.yprop.isTopLadder&&!this.disableTopLadderCollision?(this.pos.y=~~this.pos.y,this.vel.y=this.falling?a.ytile.pos.y-this.collisionBox.bottom:0,this.falling=!1):a.yprop.isSlope&&!this.jumping?(this.checkSlope(a.ytile,a.yprop.isLeftSlope),this.falling=!1):a.yprop.isBreakable&&(this.canBreakTile?(me.game.currentLevel.clearTile(a.ytile.col,a.ytile.row),this.onTileBreak&&this.onTileBreak()):(this.pos.y=~~this.pos.y,this.vel.y=this.falling?a.ytile.pos.y-this.collisionBox.bottom:0,this.falling=!1)):a.y<0&&(a.yprop.isPlatform||a.yprop.isLadder||a.yprop.isTopLadder||(this.falling=!0,this.vel.y=0))),a.x&&(this.onladder=a.xprop.isLadder||a.yprop.isTopLadder,a.xprop.isSlope&&!this.jumping?(this.checkSlope(a.xtile,a.xprop.isLeftSlope),this.falling=!1):a.xprop.isPlatform||a.xprop.isLadder||a.xprop.isTopLadder||(a.xprop.isBreakable&&this.canBreakTile?(me.game.currentLevel.clearTile(a.xtile.col,a.xtile.row),this.onTileBreak&&this.onTileBreak()):this.vel.x=0))),this.pos.add(this.vel),a},collide:function(a){return me.game.collide(this,a||!1)},collideType:function(a,b){return me.game.collideType(this,a,b||!1)},update:function(){return this.renderable?this.renderable.update():!1},getBounds:function(){return this.renderable?this.renderable.getBounds().translateV(this.pos):null},draw:function(a){if(this.renderable){var b=~~(this.pos.x+this.anchorPoint.x*(this.width-this.renderable.width)),c=~~(this.pos.y+this.anchorPoint.y*(this.height-this.renderable.height));a.translate(b,c),this.renderable.draw(a),a.translate(-b,-c)}},destroy:function(){this.renderable&&(this.renderable.destroy.apply(this.renderable,arguments),this.renderable=null),this.onDestroyEvent.apply(this,arguments),this.collisionBox=null,this.shapes=[]},onDestroyEvent:function(){}}),me.CollectableEntity=me.ObjectEntity.extend({init:function(a,b,c){this.parent(a,b,c),this.type=me.game.COLLECTABLE_OBJECT}}),me.LevelEntity=me.ObjectEntity.extend({init:function(a,b,c){this.parent(a,b,c),this.nextlevel=c.to,this.fade=c.fade,this.duration=c.duration,this.fading=!1,this.gotolevel=c.to},onFadeComplete:function(){me.levelDirector.loadLevel(this.gotolevel),me.game.viewport.fadeOut(this.fade,this.duration)},goTo:function(a){this.gotolevel=a||this.nextlevel,this.fade&&this.duration?this.fading||(this.fading=!0,me.game.viewport.fadeIn(this.fade,this.duration,this.onFadeComplete.bind(this))):me.levelDirector.loadLevel(this.gotolevel)},onCollision:function(){this.goTo()}})}(window),function(a){me.ScreenObject=me.Renderable.extend({addAsObject:!1,visible:!1,frame:0,z:999,init:function(a,b){this.parent(new me.Vector2d(0,0),0,0),this.addAsObject=this.visible=a===!0||!1,this.isPersistent=this.visible&&b===!0||!1},reset:function(){me.game.reset(),this.frame=0,this.frameRate=Math.round(60/me.sys.fps),this.onResetEvent.apply(this,arguments),this.addAsObject&&(this.visible=!0,this.floating=!0,this.set(new me.Vector2d,me.game.viewport.width,me.game.viewport.height),me.game.add(this,this.z)),me.game.sort()},destroy:function(){this.onDestroyEvent.apply(this,arguments)},update:function(){return!1},onUpdateFrame:function(){++this.frame%this.frameRate===0&&(this.frame=0,me.timer.update(),me.game.update()),me.game.draw()},draw:function(){},onResetEvent:function(){},onDestroyEvent:function(){}}),function(){for(var a=0,b=["ms","moz","webkit","o"],c=window.requestAnimationFrame,d=window.cancelAnimationFrame,e=0;e<b.length&&(!c||!d);++e)c=window[b[e]+"RequestAnimationFrame"],d=window[b[e]+"CancelAnimationFrame"]||window[b[e]+"CancelRequestAnimationFrame"];c&&d||(c=function(b){var c=Date.now(),d=Math.max(0,16-(c-a)),e=window.setTimeout(function(){b(c+d)},d);return a=c+d,e},d=function(a){window.clearTimeout(a)}),window.requestAnimationFrame=c,window.cancelAnimationFrame=d}(),me.state=function(){function b(){-1===j&&-1!==i&&(me.timer.reset(),j=window.requestAnimationFrame(e))}function c(){k&&-1!==i&&(me.timer.reset(),k=!1)}function d(){k=!0}function e(){p(),-1!==j&&(j=window.requestAnimationFrame(e))}function f(){window.cancelAnimationFrame(j),j=-1}function g(a){f(),l[i]&&(l[i].screen.visible?me.game.remove.call(me.game,l[i].screen,!0):l[i].screen.destroy()),l[a]&&(i=a,l[i].screen.reset.apply(l[i].screen,o),p=l[i].screen.onUpdateFrame.bind(l[i].screen),b(),n&&n(),me.game.repaint())}var h={},i=-1,j=-1,k=!1,l={},m={color:"",duration:0},n=null,o=null,p=null;return h.LOADING=0,h.MENU=1,h.READY=2,h.PLAY=3,h.GAMEOVER=4,h.GAME_END=5,h.SCORE=6,h.CREDITS=7,h.SETTINGS=8,h.USER=100,h.onPause=null,h.onResume=null,h.onStop=null,h.onRestart=null,h.init=function(){h.set(h.LOADING,new me.DefaultLoadingScreen),a.addEventListener("blur",function(){i!==h.LOADING&&(me.sys.stopOnBlur&&(h.stop(!0),h.onStop&&h.onStop(),me.event.publish(me.event.STATE_STOP)),me.sys.pauseOnBlur&&(h.pause(!0),h.onPause&&h.onPause(),me.event.publish(me.event.STATE_PAUSE)))},!1),a.addEventListener("focus",function(){i!==h.LOADING&&(me.sys.resumeOnFocus&&(h.resume(!0),h.onResume&&h.onResume(),me.event.publish(me.event.STATE_RESUME)),me.sys.stopOnBlur&&(h.restart(!0),me.game.repaint(),h.onRestart&&h.onRestart(),me.event.publish(me.event.STATE_RESTART)))},!1)},h.stop=function(a){f(),a&&me.audio.pauseTrack()},h.pause=function(a){d(),a&&me.audio.pauseTrack()},h.restart=function(a){b(),a&&me.audio.resumeTrack()},h.resume=function(a){c(),a&&me.audio.resumeTrack()},h.isRunning=function(){return-1!==j},h.isPaused=function(){return k},h.set=function(a,b){l[a]={},l[a].screen=b,l[a].transition=!0},h.current=function(){return l[i].screen},h.transition=function(a,b,c){"fade"===a&&(m.color=b,m.duration=c)},h.setTransition=function(a,b){l[a].transition=b},h.change=function(a){if("undefined"==typeof l[a])throw"melonJS : Undefined ScreenObject for state '"+a+"'";o=null,arguments.length>1&&(o=Array.prototype.slice.call(arguments,1)),m.duration&&l[a].transition?(n=function(){me.game.viewport.fadeOut(m.color,m.duration)},me.game.viewport.fadeIn(m.color,m.duration,function(){g.defer(a)})):g.defer(a)},h.isCurrent=function(a){return i===a},h}()}(window),function(){me.DefaultLoadingScreen=me.ScreenObject.extend({init:function(){this.parent(!0),this.invalidate=!1,this.handle=null},onResetEvent:function(){this.logo1=new me.Font("century gothic",32,"white","middle"),this.logo2=new me.Font("century gothic",32,"#55aa00","middle"),this.logo2.bold(),this.logo1.textBaseline=this.logo2.textBaseline="alphabetic",this.barHeight=4,this.handle=me.event.subscribe(me.event.LOADER_PROGRESS,this.onProgressUpdate.bind(this)),this.loadPercent=0},onDestroyEvent:function(){this.logo1=this.logo2=null,this.handle&&(me.event.unsubscribe(this.handle),this.handle=null)},onProgressUpdate:function(a){this.loadPercent=a,this.invalidate=!0},update:function(){return this.invalidate===!0?(this.invalidate=!1,!0):!1},drawLogo:function(a,b,c){a.save(),a.translate(b,c),a.beginPath(),a.moveTo(.7,48.9),a.bezierCurveTo(10.8,68.9,38.4,75.8,62.2,64.5),a.bezierCurveTo(86.1,53.1,97.2,27.7,87,7.7),a.lineTo(87,7.7),a.bezierCurveTo(89.9,15.4,73.9,30.2,50.5,41.4),a.bezierCurveTo(27.1,52.5,5.2,55.8,.7,48.9),a.lineTo(.7,48.9),a.lineTo(.7,48.9),a.closePath(),a.fillStyle="rgb(255, 255, 255)",a.fill(),a.beginPath(),a.moveTo(84,7),a.bezierCurveTo(87.6,14.7,72.5,30.2,50.2,41.6),a.bezierCurveTo(27.9,53,6.9,55.9,3.2,48.2),a.bezierCurveTo(-.5,40.4,14.6,24.9,36.9,13.5),a.bezierCurveTo(59.2,2.2,80.3,-.8,84,7),a.lineTo(84,7),a.closePath(),a.lineWidth=5.3,a.strokeStyle="rgb(255, 255, 255)",a.lineJoin="miter",a.miterLimit=4,a.stroke(),a.restore()},draw:function(a){var b=this.logo1.measureText(a,"melon").width,c=(me.video.getWidth()-b-this.logo2.measureText(a,"JS").width)/2,d=me.video.getHeight()/2+this.logo2.measureText(a,"melon").height;me.video.clearSurface(a,"#202020"),this.drawLogo(a,(me.video.getWidth()-100)/2,me.video.getHeight()/2-this.barHeight/2-90),this.logo1.draw(a,"melon",c,d),c+=b,this.logo2.draw(a,"JS",c,d);var e=Math.floor(this.loadPercent*me.video.getWidth());a.fillStyle="black",a.fillRect(0,me.video.getHeight()/2-this.barHeight/2,me.video.getWidth(),this.barHeight),a.fillStyle="#55aa00",a.fillRect(2,me.video.getHeight()/2-this.barHeight/2,e,this.barHeight)}})}(window),function(){me.loader=function(){function a(){m===l?f.onload?(clearTimeout(n),setTimeout(function(){f.onload(),me.event.publish(me.event.LOADER_COMPLETE)},300)):console.error("no load callback defined"):n=setTimeout(a,100)}function b(a,b,c){g[a.name]=new Image,g[a.name].onload=b,g[a.name].onerror=c,g[a.name].src=a.src+f.nocache}function c(a,b,c){var d=new XMLHttpRequest,e=me.utils.getFileExtension(a.src).toLowerCase();d.overrideMimeType&&d.overrideMimeType("json"===e?"application/json":"text/xml"),d.open("GET",a.src+f.nocache,!0),"tmx"===a.type&&me.levelDirector.addTMXLevel(a.name),d.ontimeout=c,d.onreadystatechange=function(){if(4===d.readyState)if(200===d.status||0===d.status&&d.responseText){var f=null;switch(e){case"xml":case"tmx":f=me.device.ua.match(/msie/i)||!d.responseXML?(new DOMParser).parseFromString(d.responseText,"text/xml"):d.responseXML,e="xml";break;case"json":f=JSON.parse(d.responseText);break;default:throw"melonJS: TMX file format "+e+"not supported !"}h[a.name]={data:f,isTMX:"tmx"===a.type,format:e},b()}else c()},d.send(null)}function d(a,b,c){var d=new XMLHttpRequest;d.overrideMimeType&&d.overrideMimeType("application/json"),d.open("GET",a.src+f.nocache,!0),d.ontimeout=c,d.onreadystatechange=function(){4===d.readyState&&(200===d.status||0===d.status&&d.responseText?(k[a.name]=JSON.parse(d.responseText),b()):c())},d.send(null)}function e(a,b,c){var d=new XMLHttpRequest;d.open("GET",a.src+f.nocache,!0),d.responseType="arraybuffer",d.onerror=c,d.onload=function(){var c=d.response;if(c){for(var e=new Uint8Array(c),f=[],g=0;g<e.byteLength;g++)f[g]=String.fromCharCode(e[g]);i[a.name]=f.join(""),b()}},d.send()}var f={},g={},h={},i={},j={},k={},l=0,m=0,n=0;return f.nocache="",f.onload=void 0,f.onProgress=void 0,f.onResourceLoaded=function(){m++;var a=f.getLoadProgress();f.onProgress&&f.onProgress(a),me.event.publish(me.event.LOADER_PROGRESS,[a])},f.onLoadingError=function(a){throw"melonJS: Failed loading resource "+a.src},f.setNocache=function(a){f.nocache=a?"?"+parseInt(1e7*Math.random(),10):""},f.preload=function(b){for(var c=0;c<b.length;c++)l+=f.load(b[c],f.onResourceLoaded.bind(f),f.onLoadingError.bind(f,b[c]));a()},f.load=function(a,f,g){switch(a.name=a.name.toLowerCase(),a.type){case"binary":return e.call(this,a,f,g),1;case"image":return b.call(this,a,f,g),1;case"json":return d.call(this,a,f,g),1;case"tmx":case"tsx":return c.call(this,a,f,g),1;case"audio":if(me.audio.isAudioEnable())return me.audio.load(a,f,g),1;break;default:throw"melonJS: me.loader.load : unknown or invalid resource type : "+a.type}return 0},f.unload=function(a){switch(a.name=a.name.toLowerCase(),a.type){case"binary":return a.name in i?(delete i[a.name],!0):!1;case"image":return a.name in g?("function"==typeof g[a.name].dispose&&g[a.name].dispose(),delete g[a.name],!0):!1;case"json":return a.name in k?(delete k[a.name],!0):!1;case"tmx":case"tsx":return a.name in h?(delete h[a.name],!0):!1;case"audio":return me.audio.unload(a.name);default:throw"melonJS: me.loader.unload : unknown or invalid resource type : "+a.type}},f.unloadAll=function(){var a;for(a in i)f.unload(a);for(a in g)f.unload(a);for(a in h)f.unload(a);for(a in j)f.unload(a);for(a in k)f.unload(a);me.audio.unloadAll()},f.getTMXFormat=function(a){return a=a.toLowerCase(),a in h?h[a].format:null},f.getTMX=function(a){return a=a.toLowerCase(),a in h?h[a].data:null},f.getBinary=function(a){return a=a.toLowerCase(),a in i?i[a]:null},f.getImage=function(a){return a=a.toLowerCase(),a in g?g[a]:null},f.getJSON=function(a){return a=a.toLowerCase(),a in k?k[a]:null},f.getLoadProgress=function(){return m/l},f}()}(window),function(){me.Font=me.Renderable.extend({font:null,fontSize:null,fillStyle:"#000000",strokeStyle:"#000000",lineWidth:1,textAlign:"left",textBaseline:"top",lineHeight:1,init:function(a,b,c,d){this.pos=new me.Vector2d,this.fontSize=new me.Vector2d,this.set(a,b,c,d),this.parent(this.pos,0,this.fontSize.y)},bold:function(){this.font="bold "+this.font},italic:function(){this.font="italic "+this.font},set:function(a,b,c,d){var e=a.split(",").map(function(a){return a=a.trim(),/(^".*"$)|(^'.*'$)/.test(a)?a:'"'+a+'"'});this.fontSize.y=parseInt(b,10),this.height=this.fontSize.y,"number"==typeof b&&(b+="px"),this.font=b+" "+e.join(","),this.fillStyle=c,d&&(this.textAlign=d)},measureText:function(a,b){a.font=this.font,a.fillStyle=this.fillStyle,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline,this.height=this.width=0;for(var c=(""+b).split("\n"),d=0;d<c.length;d++)this.width=Math.max(a.measureText(c[d].trimRight()).width,this.width),this.height+=this.fontSize.y*this.lineHeight;return{width:this.width,height:this.height}},draw:function(a,b,c,d){this.pos.set(c,d),a.font=this.font,a.fillStyle=this.fillStyle,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline;for(var e=(""+b).split("\n"),f=0;f<e.length;f++)a.fillText(e[f].trimRight(),~~c,~~d),d+=this.fontSize.y*this.lineHeight},drawStroke:function(a,b,c,d){this.pos.set(c,d),a.save(),a.font=this.font,a.fillStyle=this.fillStyle,a.strokeStyle=this.strokeStyle,a.lineWidth=this.lineWidth,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline;for(var e=(""+b).split("\n"),f=0;f<e.length;f++){var g=e[f].trimRight();a.strokeText(g,~~c,~~d),a.fillText(g,~~c,~~d),d+=this.fontSize.y*this.lineHeight}a.restore()}}),me.BitmapFont=me.Font.extend({sSize:null,firstChar:32,charCount:0,init:function(a,b,c,d){this.parent(a,null,null),this.sSize=new me.Vector2d,this.firstChar=d||32,this.loadFontMetrics(a,b),this.textAlign="left",this.textBaseline="top",c&&this.resize(c)},loadFontMetrics:function(a,b){this.font=me.loader.getImage(a),this.fontSize.x=b.x||b,this.fontSize.y=b.y||this.font.height,this.sSize.copy(this.fontSize),this.height=this.sSize.y,this.charCount=~~(this.font.width/this.fontSize.x)},set:function(a,b){this.textAlign=a,b&&this.resize(b)},resize:function(a){this.sSize.setV(this.fontSize),this.sSize.x*=a,this.sSize.y*=a,this.height=this.sSize.y},measureText:function(a,b){var c=(""+b).split("\n");this.height=this.width=0;for(var d=0;d<c.length;d++)this.width=Math.max(c[d].trimRight().length*this.sSize.x,this.width),this.height+=this.sSize.y*this.lineHeight;return{width:this.width,height:this.height}},draw:function(a,b,c,d){var e=(""+b).split("\n"),f=c,g=this.sSize.y*this.lineHeight;this.pos.set(c,d);for(var h=0;h<e.length;h++){c=f;var i=e[h].trimRight(),j=i.length*this.sSize.x;switch(this.textAlign){case"right":c-=j;break;case"center":c-=.5*j}switch(this.textBaseline){case"middle":d-=.5*g;break;case"ideographic":case"alphabetic":case"bottom":d-=g}for(var k=0,l=i.length;l>k;k++){var m=i.charCodeAt(k)-this.firstChar;m>=0&&a.drawImage(this.font,this.fontSize.x*(m%this.charCount),this.fontSize.y*~~(m/this.charCount),this.fontSize.x,this.fontSize.y,~~c,~~d,this.sSize.x,this.sSize.y),c+=this.sSize.x}d+=g}}})}(window),function(){me.audio=function(){function a(a){var b="",c=a.length;if(me.device.sound)for(var d="",e=0;c>e&&(d=a[e].toLowerCase().trim(),!g.capabilities[d]||!g.capabilities[d].canPlay||""!==b&&"probably"!==g.capabilities[d].canPlayType||(b=d,"probably"!==g.capabilities[d].canPlayType));e++);return""===b&&(l=!1),b}function b(a){for(var b,c=h[a],d=0;b=c[d++];)if(b.ended||!b.currentTime)return b.currentTime=m,b;return c[0].pause(),c[0].currentTime=m,c[0]}function c(a,b){if(n++>3){var c="melonJS: failed loading "+a+"."+i;if(me.sys.stopOnAudioError!==!1)throw c;me.audio.disable(),b&&b(),console.log(c+", disabling audio")}else h[a][0].load()}function d(a,b,c){if(n=0,b>1)for(var d=h[a][0],e=1;b>e;e++)h[a][e]=new Audio(d.src),h[a][e].preload="auto",h[a][e].load();c&&c()}function e(a,c,d,e){var f=b(a.toLowerCase());return f.loop=c||!1,f.volume=e?parseFloat(e).clamp(0,1):o.volume,f.muted=o.muted,f.play(),d&&!c&&f.addEventListener("ended",function g(){f.removeEventListener("ended",g,!1),d()},!1),f}function f(a,b,c){return c&&!b&&setTimeout(c,2e3),null}var g={},h={},i=-1,j=null,k=null,l=!0,m=0,n=0,o={volume:1,muted:!1},p=!1,q=[];return g.capabilities={mp3:{codec:"audio/mpeg",canPlay:!1,canPlayType:"no"},ogg:{codec:'audio/ogg; codecs="vorbis"',canPlay:!1,canPlayType:"no"},m4a:{codec:'audio/mp4; codecs="mp4a.40.2"',canPlay:!1,canPlayType:"no"},wav:{codec:'audio/wav; codecs="1"',canPlay:!1,canPlayType:"no"}},g.detectCapabilities=function(){var a=document.createElement("audio");if(a.canPlayType)for(var b in g.capabilities){var c=a.canPlayType(g.capabilities[b].codec);""!==c&&"no"!==c&&(g.capabilities[b].canPlay=!0,g.capabilities[b].canPlayType=c),me.device.sound|=g.capabilities[b].canPlay}},g.init=function(b){if(!me.initialized)throw"melonJS: me.audio.init() called before engine initialization.";return b="string"==typeof b?b:"mp3",b=b.split(","),i=a(b),me.device.isMobile&&!navigator.isCocoonJS&&(l=!1),g.play=g.isAudioEnable()?e:f,g.isAudioEnable()},g.isAudioEnable=function(){return l},g.enable=function(){l=me.device.sound,g.play=l?e:f},g.disable=function(){me.audio.stopTrack(),g.play=f,l=!1},g.load=function(a,b,e){if(-1===i)return 0;if(me.device.isMobile&&!navigator.isCocoonJS){if(p)return void q.push([a,b,e]);p=!0}var f=a.channel||1,j="canplaythrough";a.stream!==!0||me.device.isMobile||(f=1,j="canplay");var k=new Audio(a.src+a.name+"."+i+me.loader.nocache);return k.preload="auto",k.addEventListener(j,function l(){k.removeEventListener(j,l,!1),p=!1,d.call(me.audio,a.name,f,b);var c=q.shift();c&&g.load.apply(g,c)},!1),k.addEventListener("error",function(){c.call(me.audio,a.name,e)},!1),k.load(),h[a.name]=[k],1},g.stop=function(a){if(l)for(var b=h[a.toLowerCase()],c=b.length;c--;)b[c].pause(),b[c].currentTime=m},g.pause=function(a){if(l)for(var b=h[a.toLowerCase()],c=b.length;c--;)b[c].pause()},g.playTrack=function(a,b){k=me.audio.play(a,!0,null,b),j=a.toLowerCase()},g.stopTrack=function(){l&&k&&(k.pause(),j=null,k=null)},g.setVolume=function(a){"number"==typeof a&&(o.volume=a.clamp(0,1))},g.getVolume=function(){return o.volume},g.mute=function(a,b){b=void 0===b?!0:!!b;for(var c,d=h[a.toLowerCase()],e=0;c=d[e++];)c.muted=b},g.unmute=function(a){g.mute(a,!1)},g.muteAll=function(){o.muted=!0;for(var a in h)g.mute(a,o.muted)},g.unmuteAll=function(){o.muted=!1;for(var a in h)g.mute(a,o.muted)},g.getCurrentTrack=function(){return j},g.pauseTrack=function(){l&&k&&k.pause()},g.resumeTrack=function(){l&&k&&k.play()},g.unload=function(a){return a=a.toLowerCase(),a in h?(j===a?g.stopTrack():g.stop(a),delete h[a],!0):!1},g.unloadAll=function(){for(var a in h)g.unload(a)},g}()}(window),function(){me.video=function(){function a(){return navigator.isCocoonJS?"screencanvas":"canvas"}var b={},c=null,d=null,e=null,f=null,g=null,h=-1,i=!1,j=0,k=0,l=!1,m=!0,n=1/0,o=1/0;return b.init=function(a,h,n,o,p,q){if(!me.initialized)throw"melonJS: me.video.init() called before engine initialization.";if(i=o||!1,l="auto"===p||!1,m=void 0!==q?q:!0,p="auto"!==p?parseFloat(p||1):1,me.sys.scale=new me.Vector2d(p,p),(l||1!==p)&&(i=!0),j=h*me.sys.scale.x,k=n*me.sys.scale.y,window.addEventListener("resize",throttle(100,!1,function(a){me.event.publish(me.event.WINDOW_ONRESIZE,[a])}),!1),window.addEventListener("orientationchange",function(a){me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE,[a])},!1),me.event.subscribe(me.event.WINDOW_ONRESIZE,me.video.onresize.bind(me.video)),me.event.subscribe(me.event.WINDOW_ONORIENTATION_CHANGE,me.video.onresize.bind(me.video)),c=b.createCanvas(j,k,!0),a&&(g=document.getElementById(a)),g||(g=document.body),g.appendChild(c),!c.getContext)return!1;if(d=b.getContext2d(c),me.device.getPixelRatio()>1&&(c.style.width=c.width/me.device.getPixelRatio()+"px",c.style.height=c.height/me.device.getPixelRatio()+"px"),i?(e=b.createCanvas(h,n,!1),f=b.getContext2d(e)):(e=c,f=d),window.getComputedStyle){var r=window.getComputedStyle(c,null);me.video.setMaxSize(parseInt(r.maxWidth,10),parseInt(r.maxHeight,10))}return me.video.onresize(null),me.game.init(),!0},b.getWrapper=function(){return g},b.getWidth=function(){return e.width},b.getPos=function(a){return a=a||c,a.getBoundingClientRect?a.getBoundingClientRect():{left:0,top:0}},b.getHeight=function(){return e.height},b.setMaxSize=function(a,b){n=a||1/0,o=b||1/0},b.createCanvas=function(b,c,d){if(0===b||0===c)throw new Error("melonJS: width or height was zero, Canvas could not be initialized !");var f=d===!0?a():"canvas",g=document.createElement(f);return g.width=b||e.width,g.height=c||e.height,g},b.getContext2d=function(a){var b;return b=navigator.isCocoonJS?a.getContext("2d",{antialias:me.sys.scalingInterpolation}):a.getContext("2d"),b.canvas||(b.canvas=a),me.video.setImageSmoothing(b,me.sys.scalingInterpolation),b},b.getScreenCanvas=function(){return c},b.getScreenContext=function(){return d},b.getSystemCanvas=function(){return e},b.getSystemContext=function(){return f},b.onresize=function(){var a=1,b=1;if(me.device.orientation="undefined"!=typeof window.orientation?window.orientation:window.outerWidth>window.outerHeight?90:0,l){var c=me.video.getScreenCanvas().parentNode,d=Math.min(n,c.width||window.innerWidth),e=Math.min(o,c.height||window.innerHeight);if(m){var f=me.video.getWidth()/me.video.getHeight(),g=d/e;a=b=f>g?d/me.video.getWidth():e/me.video.getHeight()}else a=d/me.video.getWidth(),b=e/me.video.getHeight();if(a*=me.device.getPixelRatio(),b*=me.device.getPixelRatio(),1!==a||1!==b)return h>=0&&clearTimeout(h),void(h=me.video.updateDisplaySize.defer(a,b))}me.input.offset=me.video.getPos()},b.updateDisplaySize=function(a,f){me.sys.scale.set(a,f),c.width=j=e.width*a,c.height=k=e.height*f,me.device.getPixelRatio()>1&&(c.style.width=c.width/me.device.getPixelRatio()+"px",c.style.height=c.height/me.device.getPixelRatio()+"px"),me.video.setImageSmoothing(d,me.sys.scalingInterpolation),me.input.offset=me.video.getPos(),b.blitSurface(),h=-1},b.clearSurface=function(a,b){var c=a.canvas.width,d=a.canvas.height;a.save(),a.setTransform(1,0,0,1,0,0),"rgba"===b.substr(0,4)&&a.clearRect(0,0,c,d),a.fillStyle=b,a.fillRect(0,0,c,d),a.restore()},b.setImageSmoothing=function(a,b){for(var c=["ms","moz","webkit","o"],d=0;d<c.length;++d)void 0!==a[c[d]+"ImageSmoothingEnabled"]&&(a[c[d]+"ImageSmoothingEnabled"]=b===!0);a.imageSmoothingEnabled=b===!0},b.setAlpha=function(a,b){a.globalCompositeOperation=b?"source-over":"copy"},b.blitSurface=function(){b.blitSurface=i?function(){d.drawImage(e,0,0,e.width,e.height,0,0,j,k)}:function(){},b.blitSurface()},b.applyRGBFilter=function(a,c,d){var e,f,g=b.getContext2d(b.createCanvas(a.width,a.height,!1)),h=me.utils.getPixels(a),i=h.data;switch(c){case"b&w":for(e=0,f=i.length;f>e;e+=4){var j=3*i[e]+4*i[e+1]+i[e+2]>>>3;i[e]=j,i[e+1]=j,i[e+2]=j}break;case"brightness":var k=Math.abs(d).clamp(0,1);for(e=0,f=i.length;f>e;e+=4)i[e]*=k,i[e+1]*=k,i[e+2]*=k;break;case"transparent":for(e=0,f=i.length;f>e;e+=4)me.utils.RGBToHex(i[e],i[e+1],i[e+2])===d&&(i[e+3]=0); | |
break;default:return null}return g.putImageData(h,0,0),g},b}()}(window),function(a){me.input=function(){function b(){t||(a.addEventListener("keydown",f,!1),a.addEventListener("keyup",g,!1),t=!0)}function c(a,b){for(var c=2;c<a.length;++c)void 0!==a[c]&&me.video.getScreenCanvas().addEventListener(a[c],b,!1)}function d(){u||(m.changedTouches.push({x:0,y:0}),m.mouse.pos=new me.Vector2d(0,0),m.offset=me.video.getPos(),a.addEventListener("scroll",throttle(100,!1,function(a){m.offset=me.video.getPos(),me.event.publish(me.event.WINDOW_ONSCROLL,[a])}),!1),x=a.navigator.pointerEnabled?A:a.navigator.msPointerEnabled?B:me.device.touch?z:y,c(x,l),v="onwheel"in document.createElement("div")?"wheel":"mousewheel",a.addEventListener(v,j,!1),void 0===m.throttlingInterval&&(m.throttlingInterval=Math.floor(1e3/me.sys.fps)),m.throttlingInterval<17?me.video.getScreenCanvas().addEventListener(x[C],k,!1):me.video.getScreenCanvas().addEventListener(x[C],throttle(m.throttlingInterval,!1,function(a){k(a)}),!1),u=!0)}function e(a){return a.stopPropagation?a.stopPropagation():a.cancelBubble=!0,a.preventDefault?a.preventDefault():a.returnValue=!1,!1}function f(a,b,c){b=b||a.keyCode||a.which;var d=n[b];if(me.event.publish(me.event.KEYDOWN,[d,b,d?!q[d]:!0]),d){if(!q[d]){var f=c?c:b;r[d][f]||(o[d]++,r[d][f]=!0)}return e(a)}return!0}function g(a,b,c){b=b||a.keyCode||a.which;var d=n[b];if(me.event.publish(me.event.KEYUP,[d,b]),d){var f=c?c:b;return r[d][f]=void 0,o[d]>0&&o[d]--,q[d]=!1,e(a)}return!0}function h(a){var b=!1,c=s[a.type];if(c||(c=x.indexOf(a.type)===F?s[x[E]]:s[a.type]),c)for(var d=me.game.viewport.localToWorld(0,0),e=0,f=m.changedTouches.length;f>e;e++){if("undefined"!=typeof a.timeStamp){if(a.timeStamp<w)continue;w=a.timeStamp}me.device.pointerEnabled||(a.pointerId=m.changedTouches[e].id),a.gameScreenX=m.changedTouches[e].x,a.gameScreenY=m.changedTouches[e].y,a.gameWorldX=a.gameScreenX+d.x,a.gameWorldY=a.gameScreenY+d.y;for(var g,h=c.length;h--,g=c[h];)if(g.floating===!0?(a.gameX=a.gameScreenX,a.gameY=a.gameScreenY):(a.gameX=a.gameWorldX,a.gameY=a.gameWorldY),(null===g.rect||g.rect.containsPoint(a.gameX,a.gameY))&&g.cb(a)===!1){b=!0;break}}return b}function i(a){var b;if(m.changedTouches.length=0,a.touches)for(var c=0,d=a.changedTouches.length;d>c;c++){var e=a.changedTouches[c];b=m.globalToLocal(e.clientX,e.clientY),b.id=e.identifier,m.changedTouches.push(b)}else b=m.globalToLocal(a.clientX,a.clientY),b.id=a.pointerId||1,m.changedTouches.push(b);a.isPrimary!==!1&&m.mouse.pos.set(m.changedTouches[0].x,m.changedTouches[0].y)}function j(a){if(a.target===me.video.getScreenCanvas()){var b={deltaMode:1,type:"mousewheel",deltaX:a.deltaX,deltaY:a.deltaY,deltaZ:a.deltaZ};if("mousewheel"===v&&(b.deltaY=-1/40*a.wheelDelta,a.wheelDeltaX&&(b.deltaX=-1/40*a.wheelDeltaX)),h(b))return e(a)}return!0}function k(a){return i(a),h(a)?e(a):!0}function l(a){if(i(a),h(a))return e(a);var b=a.button||0,c=m.mouse.bind[b];return c?a.type===x[D]?f(a,c,b+1):g(a,c,b+1):!0}var m={},n={},o={},p={},q={},r={},s={},t=!1,u=!1,v="mousewheel",w=0,x=null,y=["mousewheel","mousemove","mousedown","mouseup",void 0,"click","dblclick"],z=[void 0,"touchmove","touchstart","touchend","touchcancel","tap","dbltap"],A=["mousewheel","pointermove","pointerdown","pointerup","pointercancel",void 0,void 0],B=["mousewheel","MSPointerMove","MSPointerDown","MSPointerUp","MSPointerCancel",void 0,void 0],C=1,D=2,E=3,F=4;return m.mouse={pos:null,LEFT:0,MIDDLE:1,RIGHT:2,bind:[0,0,0]},m.offset=null,m.throttlingInterval=void 0,m.changedTouches=[],m.KEY={LEFT:37,UP:38,RIGHT:39,DOWN:40,ENTER:13,TAB:9,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,ESC:27,SPACE:32,NUM0:48,NUM1:49,NUM2:50,NUM3:51,NUM4:52,NUM5:53,NUM6:54,NUM7:55,NUM8:56,NUM9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90},m.isKeyPressed=function(a){return o[a]&&!q[a]?(p[a]&&(q[a]=!0),!0):!1},m.keyStatus=function(a){return o[a]>0},m.triggerKeyEvent=function(a,b){b?f({},a):g({},a)},m.bindKey=function(a,c,d){b(),n[a]=c,o[c]=0,p[c]=d?d:!1,q[c]=!1,r[c]={}},m.unlockKey=function(a){q[a]=!1},m.unbindKey=function(a){o[n[a]]=0,p[n[a]]=!1,r[n[a]]={},n[a]=null},m.globalToLocal=function(a,b){var c=m.offset,d=me.device.getPixelRatio();a-=c.left,b-=c.top;var e=me.sys.scale;return(1!==e.x||1!==e.y)&&(a/=e.x,b/=e.y),new me.Vector2d(a*d,b*d)},m.bindMouse=function(a,b){if(d(),!n[b])throw"melonJS : no action defined for keycode "+b;m.mouse.bind[a]=b},m.unbindMouse=function(a){m.mouse.bind[a]=null},m.bindTouch=function(a){m.bindMouse(me.input.mouse.LEFT,a)},m.unbindTouch=function(){m.unbindMouse(me.input.mouse.LEFT)},m.registerPointerEvent=function(a,b,c,e){if(d(),-1!==y.indexOf(a)&&(me.device.touch||me.device.pointerEnabled)&&(a=x[y.indexOf(a)]),a&&-1!==x.indexOf(a)){s[a]||(s[a]=[]);var f=b.floating===!0?!0:!1;return e&&(f=e===!0?!0:!1),void s[a].push({rect:b||null,cb:c,floating:f})}throw"melonJS : invalid event type : "+a},m.releasePointerEvent=function(a,b){-1!==y.indexOf(a)&&(me.device.touch||me.device.pointerEnabled)&&(a=x[y.indexOf(a)]);{if(!a||-1===x.indexOf(a))throw"melonJS : invalid event type : "+a;s[a]||(s[a]=[]);var c=s[a];if(c)for(var d,e=c.length;e--,d=c[e];)d.rect===b&&(d.rect=d.cb=d.floating=null,s[a].splice(e,1))}},m}()}(window),function(a){var b=function(){var b={},c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return b.decode=function(b){if(b=b.replace(/[^A-Za-z0-9\+\/\=]/g,""),me.device.nativeBase64)return a.atob(b);for(var d,e,f,g,h,i,j,k=[],l=0;l<b.length;)g=c.indexOf(b.charAt(l++)),h=c.indexOf(b.charAt(l++)),i=c.indexOf(b.charAt(l++)),j=c.indexOf(b.charAt(l++)),d=g<<2|h>>4,e=(15&h)<<4|i>>2,f=(3&i)<<6|j,k.push(String.fromCharCode(d)),64!==i&&k.push(String.fromCharCode(e)),64!==j&&k.push(String.fromCharCode(f));return k=k.join("")},b}();me.utils=function(){var a={},c={},d="",e=0,f=/^.*(\\|\/|\:)/,g=/\.[^\.]*$/;return a.decodeBase64=function(a){return b.decode(a)},a.decodeBase64AsArray=function(a,c){c=c||1;var d,e,f,g,h=b.decode(a);for(g="function"==typeof window.Uint32Array?new Uint32Array(h.length/c):[],d=0,f=h.length/c;f>d;d++)for(g[d]=0,e=c-1;e>=0;--e)g[d]+=h.charCodeAt(d*c+e)<<(e<<3);return g},a.decompress=function(){throw"melonJS: GZIP/ZLIB compressed TMX Tile Map not supported!"},a.decodeCSV=function(a,b){a=a.trim().split("\n");for(var c=[],d=0;d<a.length;d++)for(var e=a[d].split(",",b),f=0;f<e.length;f++)c.push(+e[f]);return c},a.getBasename=function(a){return a.replace(f,"").replace(g,"")},a.getFileExtension=function(a){return a.substring(a.lastIndexOf(".")+1,a.length)},a.HexToRGB=function(a,b){if("#"!==a.charAt(0))return a;if(a=a.substring(1,a.length),null==c[a]){var d,e,f;a.length<6?(d=a.charAt(0)+a.charAt(0),e=a.charAt(1)+a.charAt(1),f=a.charAt(2)+a.charAt(2)):(d=a.substring(0,2),e=a.substring(2,4),f=a.substring(4,6)),c[a]=parseInt(d,16)+","+parseInt(e,16)+","+parseInt(f,16)}return(b?"rgba(":"rgb(")+c[a]+(b?","+b+")":")")},a.RGBToHex=function(a,b,c){return a.toHex()+b.toHex()+c.toHex()},a.getPixels=function(a){if(a instanceof HTMLImageElement){var b=me.video.getContext2d(me.video.createCanvas(a.width,a.height));return b.drawImage(a,0,0),b.getImageData(0,0,a.width,a.height)}return a.getContext("2d").getImageData(0,0,a.width,a.height)},a.resetGUID=function(a){d=a.toString().toUpperCase().toHex(),e=0},a.createGUID=function(){return d+"-"+e++},a.applyFriction=function(a,b){return 0>a+b?a+b*me.timer.tick:a-b>0?a-b*me.timer.tick:0},a}()}(window),function(){me.save=function(){function a(a){return"add"===a||"delete"===a}var b={},c={_init:function(){if(me.device.localStorage===!0){var a=JSON.parse(localStorage.getItem("me.save"))||[];a.forEach(function(a){b[a]=JSON.parse(localStorage.getItem("me.save."+a))})}},add:function(d){Object.keys(d).forEach(function(e){a(e)||(!function(a){Object.defineProperty(c,a,{configurable:!0,enumerable:!0,get:function(){return b[a]},set:function(c){b[a]=c,me.device.localStorage===!0&&localStorage.setItem("me.save."+a,JSON.stringify(c))}})}(e),e in b||(c[e]=d[e]))}),me.device.localStorage===!0&&localStorage.setItem("me.save",JSON.stringify(Object.keys(b)))},"delete":function(c){a(c)||"undefined"!=typeof b[c]&&(delete b[c],me.device.localStorage===!0&&(localStorage.removeItem("me.save."+c),localStorage.setItem("me.save",JSON.stringify(Object.keys(b)))))}};return c}()}(window),function(){me.COLLISION_LAYER="collision",me.TMX_TAG_MAP="map",me.TMX_TAG_NAME="name",me.TMX_TAG_VALUE="value",me.TMX_TAG_VERSION="version",me.TMX_TAG_ORIENTATION="orientation",me.TMX_TAG_WIDTH="width",me.TMX_TAG_HEIGHT="height",me.TMX_TAG_OPACITY="opacity",me.TMX_TAG_TRANS="trans",me.TMX_TAG_TILEWIDTH="tilewidth",me.TMX_TAG_TILEHEIGHT="tileheight",me.TMX_TAG_TILEOFFSET="tileoffset",me.TMX_TAG_FIRSTGID="firstgid",me.TMX_TAG_GID="gid",me.TMX_TAG_TILE="tile",me.TMX_TAG_ID="id",me.TMX_TAG_DATA="data",me.TMX_TAG_COMPRESSION="compression",me.TMX_TAG_GZIP="gzip",me.TMX_TAG_ZLIB="zlib",me.TMX_TAG_ENCODING="encoding",me.TMX_TAG_ATTR_BASE64="base64",me.TMX_TAG_CSV="csv",me.TMX_TAG_SPACING="spacing",me.TMX_TAG_MARGIN="margin",me.TMX_TAG_PROPERTIES="properties",me.TMX_TAG_PROPERTY="property",me.TMX_TAG_IMAGE="image",me.TMX_TAG_SOURCE="source",me.TMX_TAG_VISIBLE="visible",me.TMX_TAG_TILESET="tileset",me.TMX_TAG_LAYER="layer",me.TMX_TAG_TILE_LAYER="tilelayer",me.TMX_TAG_IMAGE_LAYER="imagelayer",me.TMX_TAG_OBJECTGROUP="objectgroup",me.TMX_TAG_OBJECT="object",me.TMX_TAG_X="x",me.TMX_TAG_Y="y",me.TMX_TAG_WIDTH="width",me.TMX_TAG_HEIGHT="height",me.TMX_TAG_POLYGON="polygon",me.TMX_TAG_POLYLINE="polyline",me.TMX_TAG_ELLIPSE="ellipse",me.TMX_TAG_POINTS="points",me.TMX_BACKGROUND_COLOR="backgroundcolor"}(window),function(){me.TMXUtils=function(){function a(a){if(!a||a.isBoolean())a=a?"true"===a:!0;else if(a.isNumeric())a=Number(a);else if(a.match(/^json:/i)){var b=a.split(/^json:/i)[1];try{a=JSON.parse(b)}catch(c){throw"Unable to parse JSON: "+b}}return a}var b={};return b.applyTMXPropertiesFromXML=function(b,c){var d=c.getElementsByTagName(me.TMX_TAG_PROPERTIES)[0];if(d)for(var e=d.getElementsByTagName(me.TMX_TAG_PROPERTY),f=0;f<e.length;f++){var g=me.mapReader.TMXParser.getStringAttribute(e[f],me.TMX_TAG_NAME),h=me.mapReader.TMXParser.getStringAttribute(e[f],me.TMX_TAG_VALUE);b[g]=a(h)}},b.applyTMXPropertiesFromJSON=function(b,c){var d=c[me.TMX_TAG_PROPERTIES];if(d)for(var e in d)d.hasOwnProperty(e)&&(b[e]=a(d[e]))},b.mergeProperties=function(a,b,c){for(var d in b)(c||void 0===a[d])&&(a[d]=b[d]);return a},b}()}(window),function(){me.TMXObjectGroup=Object.extend({name:null,width:0,height:0,visible:!1,z:0,objects:[],initFromXML:function(a,b,c,d){this.name=a,this.width=me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_WIDTH),this.height=me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_HEIGHT),this.visible=1===me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_VISIBLE,1),this.opacity=me.mapReader.TMXParser.getFloatAttribute(b,me.TMX_TAG_OPACITY,1).clamp(0,1),this.z=d,this.objects=[],b.firstChild&&b.firstChild.nextSibling.nodeName===me.TMX_TAG_PROPERTIES&&me.TMXUtils.applyTMXPropertiesFromXML(this,b);for(var e=b.getElementsByTagName(me.TMX_TAG_OBJECT),f=0;f<e.length;f++){var g=new me.TMXObject;g.initFromXML(e[f],c,d),this.objects.push(g)}},initFromJSON:function(a,b,c,d){var e=this;this.name=a,this.width=b[me.TMX_TAG_WIDTH],this.height=b[me.TMX_TAG_HEIGHT],this.visible=b[me.TMX_TAG_VISIBLE],this.opacity=parseFloat(b[me.TMX_TAG_OPACITY]||1).clamp(0,1),this.z=d,this.objects=[],me.TMXUtils.applyTMXPropertiesFromJSON(this,b),b.objects.forEach(function(a){var b=new me.TMXObject;b.initFromJSON(a,c,d),e.objects.push(b)})},reset:function(){this.objects=null},getObjectCount:function(){return this.objects.length},getObjectByIndex:function(a){return this.objects[a]}}),me.TMXObject=Object.extend({name:null,x:0,y:0,width:0,height:0,z:0,gid:void 0,isPolygon:!1,isPolyline:!1,points:void 0,initFromXML:function(a,b,c){if(this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.x=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_X),this.y=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_Y),this.z=c,this.width=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_WIDTH,0),this.height=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_HEIGHT,0),this.gid=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_GID,null),this.isEllipse=!1,this.isPolygon=!1,this.isPolyline=!1,this.gid)this.setImage(this.gid,b);else if(a.getElementsByTagName(me.TMX_TAG_ELLIPSE).length)this.isEllipse=!0;else{var d=a.getElementsByTagName(me.TMX_TAG_POLYGON);if(d.length?this.isPolygon=!0:(d=a.getElementsByTagName(me.TMX_TAG_POLYLINE),d.length&&(this.isPolyline=!0)),d.length){this.points=[];for(var e,f=me.mapReader.TMXParser.getStringAttribute(d[0],me.TMX_TAG_POINTS).split(" "),g=0;g<f.length;g++)e=f[g].split(","),this.points.push(new me.Vector2d(+e[0],+e[1]))}}me.game.renderer.adjustPosition(this),me.TMXUtils.applyTMXPropertiesFromXML(this,a)},initFromJSON:function(a,b,c){if(this.name=a[me.TMX_TAG_NAME],this.x=parseInt(a[me.TMX_TAG_X],10),this.y=parseInt(a[me.TMX_TAG_Y],10),this.z=parseInt(c,10),this.width=parseInt(a[me.TMX_TAG_WIDTH]||0,10),this.height=parseInt(a[me.TMX_TAG_HEIGHT]||0,10),this.gid=parseInt(a[me.TMX_TAG_GID],10)||null,this.isEllipse=!1,this.isPolygon=!1,this.isPolyline=!1,this.gid)this.setImage(this.gid,b);else if(void 0!==a[me.TMX_TAG_ELLIPSE])this.isEllipse=!0;else{var d=a[me.TMX_TAG_POLYGON];if(void 0!==d?this.isPolygon=!0:(d=a[me.TMX_TAG_POLYLINE],void 0!==d&&(this.isPolyline=!0)),void 0!==d){this.points=[];var e=this;d.forEach(function(a){e.points.push(new me.Vector2d(parseInt(a.x,10),parseInt(a.y,10)))})}}me.game.renderer.adjustPosition(this),me.TMXUtils.applyTMXPropertiesFromJSON(this,a)},setImage:function(a,b){var c=b.getTilesetByGid(this.gid);this.width=c.tilewidth,this.height=c.tileheight,this.spritewidth=this.width;var d=new me.Tile(this.x,this.y,c.tilewidth,c.tileheight,this.gid);this.image=c.getTileImage(d)},getObjectPropertyByName:function(a){return this[a]}})}(window),function(){var a=2147483648,b=1073741824,c=536870912;me.Tile=me.Rect.extend({tileId:null,tileset:null,init:function(d,e,f,g,h){this.parent(new me.Vector2d(d*f,e*g),f,g),this.col=d,this.row=e,this.tileId=h,this.flipX=0!==(this.tileId&a),this.flipY=0!==(this.tileId&b),this.flipAD=0!==(this.tileId&c),this.flipped=this.flipX||this.flipY||this.flipAD,this.tileId&=~(a|b|c)}}),me.TMXTileset=Object.extend({type:{SOLID:"solid",PLATFORM:"platform",L_SLOPE:"lslope",R_SLOPE:"rslope",LADDER:"ladder",TOPLADDER:"topladder",BREAKABLE:"breakable"},init:function(){this.TileProperties=[],this.tileXOffset=[],this.tileYOffset=[]},initFromXML:function(a){this.firstgid=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_FIRSTGID);var b=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_SOURCE);if(b){if(b=me.utils.getBasename(b),a=me.loader.getTMX(b),!a)throw"melonJS:"+b+" TSX tileset not found";me.mapReader.TMXParser.parseFromString(a),a=me.mapReader.TMXParser.getFirstElementByTagName("tileset")}this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.tilewidth=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_TILEWIDTH),this.tileheight=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_TILEHEIGHT),this.spacing=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_SPACING,0),this.margin=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_MARGIN,0),this.tileoffset=new me.Vector2d(0,0);var c=a.getElementsByTagName(me.TMX_TAG_TILEOFFSET);c.length>0&&(this.tileoffset.x=me.mapReader.TMXParser.getIntAttribute(c[0],me.TMX_TAG_X),this.tileoffset.y=me.mapReader.TMXParser.getIntAttribute(c[0],me.TMX_TAG_Y));for(var d=a.getElementsByTagName(me.TMX_TAG_TILE),e=0;e<d.length;e++){var f=me.mapReader.TMXParser.getIntAttribute(d[e],me.TMX_TAG_ID)+this.firstgid,g={};me.TMXUtils.applyTMXPropertiesFromXML(g,d[e]),this.setTileProperty(f,g)}var h=a.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE),i=h?me.loader.getImage(me.utils.getBasename(h)):null;i||console.log("melonJS: '"+h+"' file for tileset '"+this.name+"' not found!");var j=a.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_TRANS);this.initFromImage(i,j)},initFromJSON:function(a){this.firstgid=a[me.TMX_TAG_FIRSTGID];var b=a[me.TMX_TAG_SOURCE];if(b&&(b=me.utils.getBasename(b),a=me.loader.getTMX(b),!a))throw"melonJS:"+b+" TSX tileset not found";this.name=a[me.TMX_TAG_NAME],this.tilewidth=parseInt(a[me.TMX_TAG_TILEWIDTH],10),this.tileheight=parseInt(a[me.TMX_TAG_TILEHEIGHT],10),this.spacing=parseInt(a[me.TMX_TAG_SPACING]||0,10),this.margin=parseInt(a[me.TMX_TAG_MARGIN]||0,10),this.tileoffset=new me.Vector2d(0,0);var c=a[me.TMX_TAG_TILEOFFSET];c&&(this.tileoffset.x=parseInt(c[me.TMX_TAG_X],10),this.tileoffset.y=parseInt(c[me.TMX_TAG_Y],10));var d=a.tileproperties;for(var e in d){var f={};me.TMXUtils.mergeProperties(f,d[e]),this.setTileProperty(parseInt(e,10)+this.firstgid,f)}var g=me.utils.getBasename(a[me.TMX_TAG_IMAGE]),h=g?me.loader.getImage(g):null;h||console.log("melonJS: '"+g+"' file for tileset '"+this.name+"' not found!");var i=a[me.TMX_TAG_TRANS]||null;this.initFromImage(h,i)},initFromImage:function(a,b){a&&(this.image=a,this.hTileCount=~~((this.image.width-this.margin)/(this.tilewidth+this.spacing)),this.vTileCount=~~((this.image.height-this.margin)/(this.tileheight+this.spacing))),this.lastgid=this.firstgid+(this.hTileCount*this.vTileCount-1||0),null!==b&&this.image&&(this.image=me.video.applyRGBFilter(this.image,"transparent",b.toUpperCase()).canvas)},setTileProperty:function(a,b){b.isSolid=b.type?b.type.toLowerCase()===this.type.SOLID:!1,b.isPlatform=b.type?b.type.toLowerCase()===this.type.PLATFORM:!1,b.isLeftSlope=b.type?b.type.toLowerCase()===this.type.L_SLOPE:!1,b.isRightSlope=b.type?b.type.toLowerCase()===this.type.R_SLOPE:!1,b.isBreakable=b.type?b.type.toLowerCase()===this.type.BREAKABLE:!1,b.isLadder=b.type?b.type.toLowerCase()===this.type.LADDER:!1,b.isTopLadder=b.type?b.type.toLowerCase()===this.type.TOPLADDER:!1,b.isSlope=b.isLeftSlope||b.isRightSlope,b.isCollidable=!!b.type,this.TileProperties[a]=b},contains:function(a){return a>=this.firstgid&&a<=this.lastgid},getTileImage:function(a){var b=me.video.getContext2d(me.video.createCanvas(this.tilewidth,this.tileheight));return this.drawTile(b,0,0,a),b.canvas},getTileProperties:function(a){return this.TileProperties[a]},isTileCollidable:function(a){return this.TileProperties[a].isCollidable},getTileOffsetX:function(a){var b=this.tileXOffset[a];return"undefined"==typeof b&&(b=this.tileXOffset[a]=this.margin+(this.spacing+this.tilewidth)*(a%this.hTileCount)),b},getTileOffsetY:function(a){var b=this.tileYOffset[a];return"undefined"==typeof b&&(b=this.tileYOffset[a]=this.margin+(this.spacing+this.tileheight)*~~(a/this.hTileCount)),b},drawTile:function(a,b,c,d){if(d.flipped){var e=1,f=0,g=0,h=1,i=b,j=c;b=c=0,a.save(),d.flipAD&&(e=0,f=1,g=1,h=0,j+=this.tileheight-this.tilewidth),d.flipX&&(e=-e,g=-g,i+=d.flipAD?this.tileheight:this.tilewidth),d.flipY&&(f=-f,h=-h,j+=d.flipAD?this.tilewidth:this.tileheight),a.transform(e,f,g,h,i,j)}var k=d.tileId-this.firstgid;a.drawImage(this.image,this.getTileOffsetX(k),this.getTileOffsetY(k),this.tilewidth,this.tileheight,b,c,this.tilewidth,this.tileheight),d.flipped&&a.restore()}}),me.TMXTilesetGroup=Object.extend({init:function(){this.tilesets=[]},add:function(a){this.tilesets.push(a)},getTilesetByIndex:function(a){return this.tilesets[a]},getTilesetByGid:function(a){for(var b=-1,c=0,d=this.tilesets.length;d>c;c++){if(this.tilesets[c].contains(a))return this.tilesets[c];this.tilesets[c].firstgid===this.tilesets[c].lastgid&&a>=this.tilesets[c].firstgid&&(b=c)}if(-1!==b)return this.tilesets[b];throw"no matching tileset found for gid "+a}})}(window),function(){me.TMXOrthogonalRenderer=Object.extend({init:function(a,b,c,d){this.cols=a,this.rows=b,this.tilewidth=c,this.tileheight=d},canRender:function(a){return"orthogonal"===a.orientation&&this.cols===a.cols&&this.rows===a.rows&&this.tilewidth===a.tilewidth&&this.tileheight===a.tileheight},pixelToTileCoords:function(a,b){return new me.Vector2d(a/this.tilewidth,b/this.tileheight)},tileToPixelCoords:function(a,b){return new me.Vector2d(a*this.tilewidth,b*this.tileheight)},adjustPosition:function(a){"number"==typeof a.gid&&(a.y-=a.height)},drawTile:function(a,b,c,d,e){e.drawTile(a,e.tileoffset.x+b*this.tilewidth,e.tileoffset.y+(c+1)*this.tileheight-e.tileheight,d)},drawTileLayer:function(a,b,c){var d=this.pixelToTileCoords(c.pos.x,c.pos.y).floorSelf(),e=this.pixelToTileCoords(c.pos.x+c.width+this.tilewidth,c.pos.y+c.height+this.tileheight).ceilSelf();e.x=e.x>this.cols?this.cols:e.x,e.y=e.y>this.rows?this.rows:e.y;for(var f=d.y;f<e.y;f++)for(var g=d.x;g<e.x;g++){var h=b.layerData[g][f];h&&this.drawTile(a,g,f,h,h.tileset)}}}),me.TMXIsometricRenderer=Object.extend({init:function(a,b,c,d){this.cols=a,this.rows=b,this.tilewidth=c,this.tileheight=d,this.hTilewidth=c/2,this.hTileheight=d/2,this.originX=this.rows*this.hTilewidth},canRender:function(a){return"isometric"===a.orientation&&this.cols===a.cols&&this.rows===a.rows&&this.tilewidth===a.tilewidth&&this.tileheight===a.tileheight},pixelToTileCoords:function(a,b){a-=this.originX;var c=b/this.tileheight,d=a/this.tilewidth;return new me.Vector2d(c+d,c-d)},tileToPixelCoords:function(a,b){return new me.Vector2d((a-b)*this.hTilewidth+this.originX,(a+b)*this.hTileheight)},adjustPosition:function(a){var b=a.x/this.hTilewidth,c=a.y/this.tileheight,d=this.tileToPixelCoords(b,c);d.x-=a.width/2,d.y-=a.height,a.x=d.x,a.y=d.y},drawTile:function(a,b,c,d,e){e.drawTile(a,(this.cols-1)*e.tilewidth+(b-c)*e.tilewidth>>1,-e.tilewidth+(b+c)*e.tileheight>>2,d)},drawTileLayer:function(a,b,c){var d=b.tileset,e=d.tileoffset,f=this.pixelToTileCoords(c.pos.x-d.tilewidth,c.pos.y-d.tileheight).floorSelf(),g=this.pixelToTileCoords(c.pos.x+c.width+d.tilewidth,c.pos.y+c.height+d.tileheight).ceilSelf(),h=this.tileToPixelCoords(g.x,g.y),i=this.tileToPixelCoords(f.x,f.y);i.x-=this.hTilewidth,i.y+=this.tileheight;var j=i.y-c.pos.y>this.hTileheight,k=c.pos.x-i.x<this.hTilewidth;j&&(k?(f.x--,i.x-=this.hTilewidth):(f.y--,i.x+=this.hTilewidth),i.y-=this.hTileheight);for(var l=j^k,m=f.clone(),n=i.y;n-this.tileheight<h.y;n+=this.hTileheight){m.setV(f);for(var o=i.x;o<h.x;o+=this.tilewidth){if(m.x>=0&&m.y>=0&&m.x<this.cols&&m.y<this.rows){var p=b.layerData[m.x][m.y];p&&(d=p.tileset,e=d.tileoffset,d.drawTile(a,e.x+o,e.y+n-d.tileheight,p))}m.x++,m.y--}l?(f.y++,i.x-=this.hTilewidth,l=!1):(f.x++,i.x+=this.hTilewidth,l=!0)}}})}(window),function(){me.ColorLayer=me.Renderable.extend({init:function(a,b,c){this.parent(new me.Vector2d(0,0),1/0,1/0),this.name=a,this.color=me.utils.HexToRGB(b),this.z=c},reset:function(){},update:function(){return!1},draw:function(a,b){var c=a.globalAlpha;a.globalAlpha*=this.getOpacity(),a.fillStyle=this.color,a.fillRect(b.left,b.top,b.width,b.height),a.globalAlpha=c}}),me.ImageLayer=me.Renderable.extend({init:function(a,b,c,d,e,f){if(this.name=a,this.image=d?me.loader.getImage(me.utils.getBasename(d)):null,!this.image)throw"melonJS: '"+d+"' file for Image Layer '"+this.name+"' not found!";this.imagewidth=this.image.width,this.imageheight=this.image.height;var g=me.game.viewport;b=b?Math.min(g.width,b):g.width,c=c?Math.min(g.height,c):g.height,this.parent(new me.Vector2d(0,0),b,c),this.z=e,this.ratio=new me.Vector2d(1,1),f&&("number"==typeof f?this.ratio.set(f,f):this.ratio.setV(f)),this.lastpos=g.pos.clone(),this.floating=!0,this._repeat="repeat",this.repeatX=!0,this.repeatY=!0,Object.defineProperty(this,"repeat",{get:function(){return this._repeat},set:function(a){switch(this._repeat=a,this._repeat){case"no-repeat":this.repeatX=!1,this.repeatY=!1;break;case"repeat-x":this.repeatX=!0,this.repeatY=!1;break;case"repeat-y":this.repeatX=!1,this.repeatY=!0;break;default:this.repeatX=!0,this.repeatY=!0}}}),this.anchorPoint.set(0,0),this.handle=me.event.subscribe(me.event.VIEWPORT_ONCHANGE,this.updateLayer.bind(this))},reset:function(){this.handle&&(me.event.unsubscribe(this.handle),this.handle=null),this.image=null,this.lastpos=null},updateLayer:function(a){(0!==this.ratio.x||0!==this.ratio.y)&&(this.pos.x+=(a.x-this.lastpos.x)*this.ratio.x%this.imagewidth,this.pos.x=(this.imagewidth+this.pos.x)%this.imagewidth,this.pos.y+=(a.y-this.lastpos.y)*this.ratio.y%this.imageheight,this.pos.y=(this.imageheight+this.pos.y)%this.imageheight,this.lastpos.setV(a))},update:function(){return!1},draw:function(a,b){if(a.save(),0!==this.anchorPoint.y||0!==this.anchorPoint.x){var c=me.game.viewport;a.translate(~~(this.anchorPoint.x*(c.width-this.imagewidth)),~~(this.anchorPoint.y*(c.height-this.imageheight)))}a.globalAlpha*=this.getOpacity();var d,e;if(0===this.ratio.x&&0===this.ratio.y)d=Math.min(b.width,this.imagewidth),e=Math.min(b.height,this.imageheight),a.drawImage(this.image,b.left,b.top,d,e,b.left,b.top,d,e);else{var f=~~this.pos.x,g=~~this.pos.y,h=0,i=0;for(d=Math.min(this.imagewidth-f,this.width),e=Math.min(this.imageheight-g,this.height);;){do a.drawImage(this.image,f,g,d,e,h,i,d,e),g=0,i+=e,e=Math.min(this.imageheight,this.height-i);while(this.repeatY&&i<this.height);if(h+=d,!this.repeatX||h>=this.width)break;f=0,d=Math.min(this.imagewidth,this.width-h),g=~~this.pos.y,i=0,e=Math.min(this.imageheight-~~this.pos.y,this.height)}}a.restore()},destroy:function(){this.reset()}}),me.CollisionTiledLayer=me.Renderable.extend({init:function(a,b){this.parent(new me.Vector2d(0,0),a,b),this.isCollisionMap=!0},reset:function(){},checkCollision:function(a,b){var c=b.x<0?a.left+b.x:a.right+b.x,d=b.y<0?a.top+b.y:a.bottom+b.y,e={x:0,y:0,xprop:{},yprop:{}};return(0>=c||c>=this.width)&&(e.x=b.x),(0>=d||d>=this.height)&&(e.y=b.y),e}}),me.TMXLayer=me.Renderable.extend({layerData:null,init:function(a,b,c,d,e){this.parent(new me.Vector2d(0,0),0,0),this.tilewidth=a,this.tileheight=b,this.orientation=c,this.tilesets=d,this.tileset=this.tilesets?this.tilesets.getTilesetByIndex(0):null,this.z=e},initFromXML:function(a){this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.visible=1===me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_VISIBLE,1),this.cols=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_WIDTH),this.rows=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_HEIGHT),this.setOpacity(me.mapReader.TMXParser.getFloatAttribute(a,me.TMX_TAG_OPACITY,1)),this.width=this.cols*this.tilewidth,this.height=this.rows*this.tileheight,me.TMXUtils.applyTMXPropertiesFromXML(this,a),"undefined"==typeof this.preRender&&(this.preRender=me.sys.preRender),this.isCollisionMap=this.name.toLowerCase().contains(me.COLLISION_LAYER),this.isCollisionMap&&!me.debug.renderCollisionMap&&(this.visible=!1),this.preRender===!0&&(this.layerCanvas=me.video.createCanvas(this.cols*this.tilewidth,this.rows*this.tileheight),this.layerSurface=me.video.getContext2d(this.layerCanvas))},initFromJSON:function(a){this.name=a[me.TMX_TAG_NAME],this.visible=a[me.TMX_TAG_VISIBLE],this.cols=parseInt(a[me.TMX_TAG_WIDTH],10),this.rows=parseInt(a[me.TMX_TAG_HEIGHT],10),this.setOpacity(parseFloat(a[me.TMX_TAG_OPACITY])),this.width=this.cols*this.tilewidth,this.height=this.rows*this.tileheight,me.TMXUtils.applyTMXPropertiesFromJSON(this,a),"undefined"==typeof this.preRender&&(this.preRender=me.sys.preRender),this.isCollisionMap=this.name.toLowerCase().contains(me.COLLISION_LAYER),this.isCollisionMap&&!me.debug.renderCollisionMap&&(this.visible=!1),this.preRender===!0&&(this.layerCanvas=me.video.createCanvas(this.cols*this.tilewidth,this.rows*this.tileheight),this.layerSurface=me.video.getContext2d(this.layerCanvas))},reset:function(){this.preRender&&(this.layerCanvas=null,this.layerSurface=null),this.renderer=null,this.layerData=null,this.tileset=null,this.tilesets=null},setRenderer:function(a){this.renderer=a},initArray:function(a,b){this.layerData=[];for(var c=0;a>c;c++){this.layerData[c]=[];for(var d=0;b>d;d++)this.layerData[c][d]=null}},getTileId:function(a,b){var c=this.getTile(a,b);return c?c.tileId:null},getTile:function(a,b){return this.layerData[~~(a/this.tilewidth)][~~(b/this.tileheight)]},setTile:function(a,b,c){var d=new me.Tile(a,b,this.tilewidth,this.tileheight,c);return d.tileset=this.tileset.contains(d.tileId)?this.tileset:this.tileset=this.tilesets.getTilesetByGid(d.tileId),this.layerData[a][b]=d,d},clearTile:function(a,b){this.layerData[a][b]=null,this.visible&&this.preRender&&this.layerSurface.clearRect(a*this.tilewidth,b*this.tileheight,this.tilewidth,this.tileheight)},checkCollision:function(a,b){var c=b.x<0?~~(a.left+b.x):Math.ceil(a.right-1+b.x),d=b.y<0?~~(a.top+b.y):Math.ceil(a.bottom-1+b.y),e={x:0,xtile:void 0,xprop:{},y:0,ytile:void 0,yprop:{}};return 0>=c||c>=this.width?e.x=b.x:0!==b.x&&(e.xtile=this.getTile(c,Math.ceil(a.bottom-1)),e.xtile&&this.tileset.isTileCollidable(e.xtile.tileId)?(e.x=b.x,e.xprop=this.tileset.getTileProperties(e.xtile.tileId)):(e.xtile=this.getTile(c,~~a.top),e.xtile&&this.tileset.isTileCollidable(e.xtile.tileId)&&(e.x=b.x,e.xprop=this.tileset.getTileProperties(e.xtile.tileId)))),e.ytile=this.getTile(b.x<0?~~a.left:Math.ceil(a.right-1),d),e.ytile&&this.tileset.isTileCollidable(e.ytile.tileId)?(e.y=b.y||1,e.yprop=this.tileset.getTileProperties(e.ytile.tileId)):(e.ytile=this.getTile(b.x<0?Math.ceil(a.right-1):~~a.left,d),e.ytile&&this.tileset.isTileCollidable(e.ytile.tileId)&&(e.y=b.y||1,e.yprop=this.tileset.getTileProperties(e.ytile.tileId))),e},update:function(){return!1},draw:function(a,b){if(this.preRender){var c=Math.min(b.width,this.width),d=Math.min(b.height,this.height);this.layerSurface.globalAlpha=a.globalAlpha*this.getOpacity(),a.drawImage(this.layerCanvas,b.pos.x,b.pos.y,c,d,b.pos.x,b.pos.y,c,d)}else{var e=a.globalAlpha;a.globalAlpha*=this.getOpacity(),this.renderer.drawTileLayer(a,this,b),a.globalAlpha=e}}})}(window),function(){me.TMXTileMap=me.Renderable.extend({init:function(a){this.levelId=a,this.z=0,this.name=null,this.cols=0,this.rows=0,this.tilewidth=0,this.tileheight=0,this.tilesets=null,this.mapLayers=[],this.objectGroups=[],this.initialized=!1,this.version="",this.orientation="",this.tilesets=null,this.parent(new me.Vector2d,0,0)},reset:function(){if(this.initialized===!0){var a;for(a=this.mapLayers.length;a--;)this.mapLayers[a].reset(),this.mapLayers[a]=null;for(a=this.objectGroups.length;a--;)this.objectGroups[a].reset(),this.objectGroups[a]=null;this.tilesets=null,this.mapLayers.length=0,this.objectGroups.length=0,this.pos.set(0,0),this.initialized=!1}},getObjectGroupByName:function(a){var b=null;a=a.trim().toLowerCase();for(var c=this.objectGroups.length;c--;)if(this.objectGroups[c].name.toLowerCase().contains(a)){b=this.objectGroups[c];break}return b},getObjectGroups:function(){return this.objectGroups},getLayers:function(){return this.mapLayers},getLayerByName:function(a){var b=null;a=a.trim().toLowerCase();for(var c=this.mapLayers.length;c--;)if(this.mapLayers[c].name.toLowerCase().contains(a)){b=this.mapLayers[c];break}return a.toLowerCase().contains(me.COLLISION_LAYER)&&null==b&&(b=new me.CollisionTiledLayer(me.game.currentLevel.width,me.game.currentLevel.height)),b},clearTile:function(a,b){for(var c=this.mapLayers.length;c--;)this.mapLayers[c]instanceof me.TMXLayer&&this.mapLayers[c].clearTile(a,b)}})}(window),function(){function a(){var a={tmxDoc:null,setData:function(a){this.tmxDoc=a},getFirstElementByTagName:function(a){return this.tmxDoc?this.tmxDoc.getElementsByTagName(a)[0]:null},getAllTagElements:function(){return this.tmxDoc?this.tmxDoc.getElementsByTagName("*"):null},getStringAttribute:function(a,b,c){var d=a.getAttribute(b);return d?d.trim():c},getIntAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?parseInt(d,10):c},getFloatAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?parseFloat(d):c},getBooleanAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?"true"===d:c},free:function(){this.tmxDoc=null}};return a}me.TMXMapReader=Object.extend({XMLReader:null,JSONReader:null,TMXParser:null,readMap:function(a){if(!a.initialized){if("xml"===me.loader.getTMXFormat(a.levelId)?(null===this.XMLReader&&(this.XMLReader=new b),this.TMXParser=this.XMLReader.TMXParser,this.XMLReader.readXMLMap(a,me.loader.getTMX(a.levelId))):(null===this.JSONReader&&(this.JSONReader=new c),this.JSONReader.readJSONMap(a,me.loader.getTMX(a.levelId))),a.width<me.game.viewport.width||a.height<me.game.viewport.height){var d=~~((me.game.viewport.width-a.width)/2),e=~~((me.game.viewport.height-a.height)/2); | |
a.pos.add({x:d>0?d:0,y:e>0?e:0})}a.initialized=!0}},getNewDefaultRenderer:function(a){switch(a.orientation){case"orthogonal":return new me.TMXOrthogonalRenderer(a.cols,a.rows,a.tilewidth,a.tileheight);case"isometric":return new me.TMXIsometricRenderer(a.cols,a.rows,a.tilewidth,a.tileheight);default:throw"melonJS: "+a.orientation+" type TMX Tile Map not supported!"}},setLayerData:function(a,b,c,d){switch(a.initArray(a.cols,a.rows),c){case null:b=b.getElementsByTagName(me.TMX_TAG_TILE);break;case"json":break;case me.TMX_TAG_CSV:case me.TMX_TAG_ATTR_BASE64:for(var e="",f=0,g=b.childNodes.length;g>f;f++)e+=b.childNodes[f].nodeValue;c===me.TMX_TAG_CSV?b=me.utils.decodeCSV(e,a.cols):(b=me.utils.decodeBase64AsArray(e,4),null!==d&&(b=me.utils.decompress(b,d))),e=null;break;default:throw"melonJS: TMX Tile Map "+c+" encoding not supported!"}for(var h=0,i=0;i<a.rows;i++)for(var j=0;j<a.cols;j++){var k=null==c?this.TMXParser.getIntAttribute(b[h++],me.TMX_TAG_GID):b[h++];if(0!==k){var l=a.setTile(j,i,k);a.visible&&a.preRender&&a.renderer.drawTile(a.layerSurface,j,i,l,l.tileset)}}}});var b=me.TMXMapReader.extend({TMXParser:null,init:function(){this.TMXParser||(this.TMXParser=new a)},readXMLMap:function(a,b){if(!b)throw"melonJS:"+a.levelId+" TMX map not found";var c=0;this.TMXParser.setData(b);for(var d=this.TMXParser.getAllTagElements(),e=0;e<d.length;e++)switch(d.item(e).nodeName){case me.TMX_TAG_MAP:var f=d.item(e);a.version=this.TMXParser.getStringAttribute(f,me.TMX_TAG_VERSION),a.orientation=this.TMXParser.getStringAttribute(f,me.TMX_TAG_ORIENTATION),a.cols=this.TMXParser.getIntAttribute(f,me.TMX_TAG_WIDTH),a.rows=this.TMXParser.getIntAttribute(f,me.TMX_TAG_HEIGHT),a.tilewidth=this.TMXParser.getIntAttribute(f,me.TMX_TAG_TILEWIDTH),a.tileheight=this.TMXParser.getIntAttribute(f,me.TMX_TAG_TILEHEIGHT),a.width=a.cols*a.tilewidth,a.height=a.rows*a.tileheight,a.backgroundcolor=this.TMXParser.getStringAttribute(f,me.TMX_BACKGROUND_COLOR),a.z=c++,me.TMXUtils.applyTMXPropertiesFromXML(a,f),a.background_color=a.backgroundcolor?a.backgroundcolor:a.background_color,a.background_color&&a.mapLayers.push(new me.ColorLayer("background_color",a.background_color,c++)),a.background_image&&a.mapLayers.push(new me.ImageLayer("background_image",a.width,a.height,a.background_image,c++)),null!==me.game.renderer&&me.game.renderer.canRender(a)||(me.game.renderer=this.getNewDefaultRenderer(a));break;case me.TMX_TAG_TILESET:a.tilesets||(a.tilesets=new me.TMXTilesetGroup),a.tilesets.add(this.readTileset(d.item(e)));break;case me.TMX_TAG_IMAGE_LAYER:a.mapLayers.push(this.readImageLayer(a,d.item(e),c++));break;case me.TMX_TAG_LAYER:a.mapLayers.push(this.readLayer(a,d.item(e),c++));break;case me.TMX_TAG_OBJECTGROUP:a.objectGroups.push(this.readObjectGroup(a,d.item(e),c++))}this.TMXParser.free()},readLayer:function(a,b,c){var d=new me.TMXLayer(a.tilewidth,a.tileheight,a.orientation,a.tilesets,c);d.initFromXML(b);var e=b.getElementsByTagName(me.TMX_TAG_DATA)[0],f=this.TMXParser.getStringAttribute(e,me.TMX_TAG_ENCODING,null),g=this.TMXParser.getStringAttribute(e,me.TMX_TAG_COMPRESSION,null);return""===f&&(f=null),""===g&&(g=null),(!d.isCollisionMap||me.debug.renderCollisionMap)&&d.setRenderer(me.game.renderer.canRender(d)?me.game.renderer:me.mapReader.getNewDefaultRenderer(d)),this.setLayerData(d,e,f,g),e=null,d},readImageLayer:function(a,b,c){var d=this.TMXParser.getStringAttribute(b,me.TMX_TAG_NAME),e=this.TMXParser.getIntAttribute(b,me.TMX_TAG_WIDTH),f=this.TMXParser.getIntAttribute(b,me.TMX_TAG_HEIGHT),g=b.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE),h=new me.ImageLayer(d,e*a.tilewidth,f*a.tileheight,g,c);return h.visible=1===this.TMXParser.getIntAttribute(b,me.TMX_TAG_VISIBLE,1),h.setOpacity(this.TMXParser.getFloatAttribute(b,me.TMX_TAG_OPACITY,1)),me.TMXUtils.applyTMXPropertiesFromXML(h,b),"number"==typeof h.ratio&&(h.ratio=new me.Vector2d(parseFloat(h.ratio),parseFloat(h.ratio))),h},readTileset:function(a){var b=new me.TMXTileset;return b.initFromXML(a),b},readObjectGroup:function(a,b,c){var d=this.TMXParser.getStringAttribute(b,me.TMX_TAG_NAME),e=new me.TMXObjectGroup;return e.initFromXML(d,b,a.tilesets,c),e}}),c=me.TMXMapReader.extend({readJSONMap:function(a,b){if(!b)throw"melonJS:"+a.levelId+" TMX map not found";var c=0,d=this;a.version=b[me.TMX_TAG_VERSION],a.orientation=b[me.TMX_TAG_ORIENTATION],a.cols=parseInt(b[me.TMX_TAG_WIDTH],10),a.rows=parseInt(b[me.TMX_TAG_HEIGHT],10),a.tilewidth=parseInt(b[me.TMX_TAG_TILEWIDTH],10),a.tileheight=parseInt(b[me.TMX_TAG_TILEHEIGHT],10),a.width=a.cols*a.tilewidth,a.height=a.rows*a.tileheight,a.backgroundcolor=b[me.TMX_BACKGROUND_COLOR],a.z=c++,me.TMXUtils.applyTMXPropertiesFromJSON(a,b),a.background_color=a.backgroundcolor?a.backgroundcolor:a.background_color,a.background_color&&a.mapLayers.push(new me.ColorLayer("background_color",a.background_color,c++)),a.background_image&&a.mapLayers.push(new me.ImageLayer("background_image",a.width,a.height,a.background_image,c++)),null!==me.game.renderer&&me.game.renderer.canRender(a)||(me.game.renderer=this.getNewDefaultRenderer(a)),a.tilesets||(a.tilesets=new me.TMXTilesetGroup),b.tilesets.forEach(function(b){a.tilesets.add(d.readTileset(b))}),b.layers.forEach(function(b){switch(b.type){case me.TMX_TAG_IMAGE_LAYER:a.mapLayers.push(d.readImageLayer(a,b,c++));break;case me.TMX_TAG_TILE_LAYER:a.mapLayers.push(d.readLayer(a,b,c++));break;case me.TMX_TAG_OBJECTGROUP:a.objectGroups.push(d.readObjectGroup(a,b,c++))}})},readLayer:function(a,b,c){var d=new me.TMXLayer(a.tilewidth,a.tileheight,a.orientation,a.tilesets,c);return d.initFromJSON(b),d.isCollisionMap||d.setRenderer(me.game.renderer.canRender(d)?me.game.renderer:me.mapReader.getNewDefaultRenderer(d)),this.setLayerData(d,b[me.TMX_TAG_DATA],"json",null),d},readImageLayer:function(a,b,c){var d=b[me.TMX_TAG_NAME],e=parseInt(b[me.TMX_TAG_WIDTH],10),f=parseInt(b[me.TMX_TAG_HEIGHT],10),g=b[me.TMX_TAG_IMAGE],h=new me.ImageLayer(d,e*a.tilewidth,f*a.tileheight,g,c);return h.visible=b[me.TMX_TAG_VISIBLE],h.setOpacity(parseFloat(b[me.TMX_TAG_OPACITY])),me.TMXUtils.applyTMXPropertiesFromJSON(h,b),"number"==typeof h.ratio&&(h.ratio=new me.Vector2d(parseFloat(h.ratio),parseFloat(h.ratio))),h},readTileset:function(a){var b=new me.TMXTileset;return b.initFromJSON(a),b},readObjectGroup:function(a,b,c){var d=new me.TMXObjectGroup;return d.initFromJSON(b[me.TMX_TAG_NAME],b,a.tilesets,c),d}})}(window),function(){me.levelDirector=function(){var a={},b={},c=[],d=0;return a.reset=function(){},a.addLevel=function(){throw"melonJS: no level loader defined"},a.addTMXLevel=function(a,d){return null!=b[a]?!1:(b[a]=new me.TMXTileMap(a),b[a].name=a,c.push(a),d&&d(),!0)},a.loadLevel=function(e){if(e=e.toString().toLowerCase(),void 0===b[e])throw"melonJS: level "+e+" not found";if(!(b[e]instanceof me.TMXTileMap))throw"melonJS: no level loader defined";var f=me.state.isRunning();return f&&me.state.stop(),me.game.reset(),me.utils.resetGUID(e),b[a.getCurrentLevelId()]&&b[a.getCurrentLevelId()].reset(),me.mapReader.readMap(b[e]),d=c.indexOf(e),me.game.loadTMXLevel(b[e]),f&&me.state.restart.defer(),!0},a.getCurrentLevelId=function(){return c[d]},a.reloadLevel=function(){return a.loadLevel(a.getCurrentLevelId())},a.nextLevel=function(){return d+1<c.length?a.loadLevel(c[d+1]):!1},a.previousLevel=function(){return d-1>=0?a.loadLevel(c[d-1]):!1},a.levelCount=function(){return c.length},a}()}(window),/** | |
* @preserve Tween JS | |
* https://github.com/sole/Tween.js | |
*/ | |
function(){me.Tween=function(a){var b=a,c={},d={},e={},f=1e3,g=0,h=!1,i=!1,j=0,k=null,l=0,m=me.Tween.Easing.Linear.None,n=me.Tween.Interpolation.Linear,o=[],p=null,q=!1,r=null,s=null;for(var t in a)c[t]=parseFloat(a[t],10);this.onResetEvent=function(a){b=a,c={},d={},e={},m=me.Tween.Easing.Linear.None,n=me.Tween.Interpolation.Linear,h=!1,i=!1,f=1e3,j=0,p=null,q=!1,r=null,s=null},this.to=function(a,b){return void 0!==b&&(f=b),d=a,this},this.start=function(){q=!1,me.game.world.addChild(this),k=me.timer.getTime()+j,l=0;for(var a in d){if(d[a]instanceof Array){if(0===d[a].length)continue;d[a]=[b[a]].concat(d[a])}c[a]=b[a],c[a]instanceof Array==!1&&(c[a]*=1),e[a]=c[a]||0}return this},this.stop=function(){return me.game.world.removeChild(this),this},this.delay=function(a){return j=a,this},me.event.subscribe(me.event.STATE_PAUSE,function(){k&&(l=me.timer.getTime())}),me.event.subscribe(me.event.STATE_RESUME,function(){k&&l&&(k+=me.timer.getTime()-l)}),this.repeat=function(a){return g=a,this},this.yoyo=function(a){return h=a,this},this.easing=function(a){if("function"!=typeof a)throw"melonJS: invalid easing function for me.Tween.easing()";return m=a,this},this.interpolation=function(a){return n=a,this},this.chain=function(){return o=arguments,this},this.onStart=function(a){return p=a,this},this.onUpdate=function(a){return r=a,this},this.onComplete=function(a){return s=a,this},this.update=function(){var a,l=me.timer.getTime();if(k>l)return!0;q===!1&&(null!==p&&p.call(b),q=!0);var t=(l-k)/f;t=t>1?1:t;var u=m(t);for(a in d){var v=c[a]||0,w=d[a];w instanceof Array?b[a]=n(w,u):("string"==typeof w&&(w=v+parseFloat(w,10)),"number"==typeof w&&(b[a]=v+(w-v)*u))}if(null!==r&&r.call(b,u),1===t){if(g>0){isFinite(g)&&g--;for(a in e){if("string"==typeof d[a]&&(e[a]=e[a]+parseFloat(d[a],10)),h){var x=e[a];e[a]=d[a],d[a]=x,i=!i}c[a]=e[a]}return k=l+j,!0}me.game.world.removeChild(this),null!==s&&s.call(b);for(var y=0,z=o.length;z>y;y++)o[y].start(l);return!1}return!0}},me.Tween.Easing={Linear:{None:function(a){return a}},Quadratic:{In:function(a){return a*a},Out:function(a){return a*(2-a)},InOut:function(a){return(a*=2)<1?.5*a*a:-.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return(a*=2)<1?.5*a*a*a:.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return(a*=2)<1?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a*a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return(a*=2)<1?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:(a*=2)<1?.5*Math.pow(1024,a-1):.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1-Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return(a*=2)<1?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),-(c*Math.pow(2,10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d)))},Out:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),c*Math.pow(2,-10*a)*Math.sin(2*(a-b)*Math.PI/d)+1)},InOut:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),(a*=2)<1?-.5*c*Math.pow(2,10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d):c*Math.pow(2,-10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d)*.5+1)}},Back:{In:function(a){var b=1.70158;return a*a*((b+1)*a-b)},Out:function(a){var b=1.70158;return--a*a*((b+1)*a+b)+1},InOut:function(a){var b=2.5949095;return(a*=2)<1?.5*a*a*((b+1)*a-b):.5*((a-=2)*a*((b+1)*a+b)+2)}},Bounce:{In:function(a){return 1-me.Tween.Easing.Bounce.Out(1-a)},Out:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},InOut:function(a){return.5>a?.5*me.Tween.Easing.Bounce.In(2*a):.5*me.Tween.Easing.Bounce.Out(2*a-1)+.5}}},me.Tween.Interpolation={Linear:function(a,b){var c=a.length-1,d=c*b,e=Math.floor(d),f=me.Tween.Interpolation.Utils.Linear;return 0>b?f(a[0],a[1],d):b>1?f(a[c],a[c-1],c-d):f(a[e],a[e+1>c?c:e+1],d-e)},Bezier:function(a,b){var c,d=0,e=a.length-1,f=Math.pow,g=me.Tween.Interpolation.Utils.Bernstein;for(c=0;e>=c;c++)d+=f(1-b,e-c)*f(b,c)*a[c]*g(e,c);return d},CatmullRom:function(a,b){var c=a.length-1,d=c*b,e=Math.floor(d),f=me.Tween.Interpolation.Utils.CatmullRom;return a[0]===a[c]?(0>b&&(e=Math.floor(d=c*(1+b))),f(a[(e-1+c)%c],a[e],a[(e+1)%c],a[(e+2)%c],d-e)):0>b?a[0]-(f(a[0],a[0],a[1],a[1],-d)-a[0]):b>1?a[c]-(f(a[c],a[c],a[c-1],a[c-1],d-c)-a[c]):f(a[e?e-1:0],a[e],a[e+1>c?c:e+1],a[e+2>c?c:e+2],d-e)},Utils:{Linear:function(a,b,c){return(b-a)*c+a},Bernstein:function(a,b){var c=me.Tween.Interpolation.Utils.Factorial;return c(a)/c(b)/c(a-b)},Factorial:function(){var a=[1];return function(b){var c,d=1;if(a[b])return a[b];for(c=b;c>1;c--)d*=c;return a[b]=d}}(),CatmullRom:function(a,b,c,d,e){var f=.5*(c-a),g=.5*(d-b),h=e*e,i=e*h;return(2*b-2*c+f+g)*i+(-3*b+3*c-2*f-g)*h+f*e+b}}}}(),/** | |
* @preserve MinPubSub | |
* a micro publish/subscribe messaging framework | |
* @see https://github.com/daniellmb/MinPubSub | |
* @author Daniel Lamb <daniellmb.com> | |
* | |
* Released under the MIT License | |
*/ | |
function(){me.event=function(){var a={},b={};return a.STATE_PAUSE="me.state.onPause",a.STATE_RESUME="me.state.onResume",a.STATE_STOP="me.state.onStop",a.STATE_RESTART="me.state.onRestart",a.GAME_INIT="me.game.onInit",a.LEVEL_LOADED="me.game.onLevelLoaded",a.LOADER_COMPLETE="me.loader.onload",a.LOADER_PROGRESS="me.loader.onProgress",a.KEYDOWN="me.input.keydown",a.KEYUP="me.input.keyup",a.WINDOW_ONRESIZE="window.onresize",a.WINDOW_ONORIENTATION_CHANGE="window.orientationchange",a.WINDOW_ONSCROLL="window.onscroll",a.VIEWPORT_ONCHANGE="viewport.onchange",a.publish=function(a,c){for(var d=b[a],e=d?d.length:0;e--;)d[e].apply(window,c||[])},a.subscribe=function(a,c){return b[a]||(b[a]=[]),b[a].push(c),[a,c]},a.unsubscribe=function(a,c){var d=b[c?a:a[0]],e=d?d.length:0;for(c=c||a[1];e--;)d[e]===c&&d.splice(e,1)},a}()}(),function(){me.plugin=function(){var a={};return a.Base=Object.extend({version:void 0,init:function(){}}),a.patch=function(a,b,c){if(void 0!==a.prototype&&(a=a.prototype),"function"==typeof a[b]){var d=a[b];a[b]=function(a,b){return function(){var a=this.parent;this.parent=d;var c=b.apply(this,arguments);return this.parent=a,c}}(b,c)}else console.error(b+" is not an existing function")},a.register=function(a,b){if(me.plugin[b]&&console.error("plugin "+b+" already registered"),void 0===a.prototype.version)throw"melonJS: Plugin version not defined !";if(me.sys.checkVersion(a.prototype.version)>0)throw"melonJS: Plugin version mismatch, expected: "+a.prototype.version+", got: "+me.version;var c=[];if(arguments.length>2&&(c=Array.prototype.slice.call(arguments,1)),c[0]=a,me.plugin[b]=new(a.bind.apply(a,c)),!(me.plugin[b]instanceof me.plugin.Base))throw"melonJS: Plugin should extend the me.plugin.Base Class !"},a}()}(),function(){me.debug=me.debug||{},debugPanel=me.plugin.Base.extend({GUID:null,area:{},rect:null,z:1/0,visible:!1,version:"0.9.9",init:function(a,b){this.parent(),this.rect=new me.Rect(new me.Vector2d(0,0),me.video.getWidth(),35),this.GUID="debug-"+me.utils.createGUID(),this.name="me.debugPanel",this.isPersistent=!0,this.floating=!0,this.isRenderable=!0,this.alwaysUpdate=!0,this.font=new me.Font("courier",10,"white"),this.area.renderHitBox=new me.Rect(new me.Vector2d(160,5),15,15),this.area.renderVelocity=new me.Rect(new me.Vector2d(165,18),15,15),this.area.renderDirty=new me.Rect(new me.Vector2d(270,5),15,15),this.area.renderCollisionMap=new me.Rect(new me.Vector2d(270,18),15,15),this.help_str="(s)how/(h)ide",this.help_str_len=this.font.measureText(me.video.getSystemContext(),this.help_str).width,this.fps_str_len=this.font.measureText(me.video.getSystemContext(),"00/00 fps").width,me.debug.displayFPS=!0,me.input.bindKey(a||me.input.KEY.S,"show"),me.input.bindKey(b||me.input.KEY.H,"hide"),this.samples=[],this.patchSystemFn(),this.show()},patchSystemFn:function(){me.debug.renderHitBox=me.debug.renderHitBox||!1,me.debug.renderVelocity=me.debug.renderVelocity||!1,me.plugin.patch(me.timer,"update",function(){this.parent(),me.timer.countFPS()}),me.plugin.patch(me.SpriteObject,"draw",function(a){this.parent(a),me.debug.renderHitBox&&(a.strokeStyle="green",a.strokeRect(this.left,this.top,this.width,this.height))}),me.plugin.patch(me.ObjectEntity,"draw",function(a){if(this.parent(a),me.debug.renderHitBox&&this.shapes.length&&(this.collisionBox.draw(a,"red"),"Rectangle"!==this.shapes[0].shapeType&&(a.translate(this.pos.x,this.pos.y),this.shapes[0].draw(a,"red"),a.translate(-this.pos.x,-this.pos.y))),me.debug.renderVelocity){var b=~~(this.pos.x+this.hWidth),c=~~(this.pos.y+this.hHeight);a.strokeStyle="blue",a.lineWidth=1,a.beginPath(),a.moveTo(b,c),a.lineTo(b+~~(this.vel.x*this.hWidth),c+~~(this.vel.y*this.hHeight)),a.stroke()}})},show:function(){this.visible||(me.game.getEntityByName("me.debugPanel")[0]||(me.game.add(this,this.z),me.game.sort()),me.input.registerPointerEvent("mousedown",this.rect,this.onClick.bind(this),!0),this.visible=!0,me.game.repaint())},hide:function(){this.visible&&(me.input.releasePointerEvent("mousedown",this.rect),this.visible=!1,me.game.repaint())},update:function(){return me.input.isKeyPressed("show")?this.show():me.input.isKeyPressed("hide")&&this.hide(),!0},getRect:function(){return this.rect},onClick:function(a){this.area.renderHitBox.containsPoint(a.gameX,a.gameY)?me.debug.renderHitBox=!me.debug.renderHitBox:this.area.renderCollisionMap.containsPoint(a.gameX,a.gameY)?me.debug.renderCollisionMap=!me.debug.renderCollisionMap:this.area.renderVelocity.containsPoint(a.gameX,a.gameY)&&(me.debug.renderVelocity=!me.debug.renderVelocity),me.game.repaint()},drawMemoryGraph:function(a,b,c){if(window.performance&&window.performance.memory){var d=Number.prototype.round(window.performance.memory.usedJSHeapSize/1048576,2),e=Number.prototype.round(window.performance.memory.totalJSHeapSize/1048576,2),f=c-b;this.samples.shift(),this.samples[f]=d/e*25;for(var g=f;g--;){var h=c-(f-g);a.beginPath(),a.strokeStyle="lightgreen",a.moveTo(h,30),a.lineTo(h,30-(this.samples[g]||0)),a.stroke()}this.font.draw(a,d+"/"+e+" MB",b,18)}else this.font.draw(a,"??/?? MB",b,18)},draw:function(a){a.save(),a.globalAlpha=.5,a.fillStyle="black",a.fillRect(this.rect.left,this.rect.top,this.rect.width,this.rect.height),a.globalAlpha=1,this.font.draw(a,"#objects : "+me.game.world.children.length,5,5),this.font.draw(a,"#draws : "+me.game.world.drawCount,5,18),this.font.draw(a,"?hitbox ["+(me.debug.renderHitBox?"x":" ")+"]",100,5),this.font.draw(a,"?velocity ["+(me.debug.renderVelocity?"x":" ")+"]",100,18),this.font.draw(a,"?dirtyRect [ ]",200,5),this.font.draw(a,"?col. layer ["+(me.debug.renderCollisionMap?"x":" ")+"]",200,18),this.drawMemoryGraph(a,300,this.rect.width-this.help_str_len-5),this.font.draw(a,this.help_str,this.rect.width-this.help_str_len-5,18);var b=""+me.timer.fps+"/"+me.sys.fps+" fps";this.font.draw(a,b,this.rect.width-this.fps_str_len-5,5),a.restore()},onDestroyEvent:function(){this.hide(),me.input.unbindKey(me.input.KEY.S),me.input.unbindKey(me.input.KEY.H)}})}(window);var enableDebugging=!0,game={data:{score:0},onload:function(){return me.video.init("screen",640,480,!0,"auto")?((enableDebugging||"#debug"===document.location.hash)&&window.onReady(function(){me.plugin.register.defer(debugPanel,"debug")}),me.audio.init("mp3,ogg"),me.loader.onload=this.loaded.bind(this),me.loader.preload(game.resources),void me.state.change(me.state.LOADING)):void alert("Your browser does not support HTML5 canvas.")},loaded:function(){me.state.set(me.state.MENU,new game.TitleScreen),me.state.set(me.state.PLAY,new game.PlayScreen),me.entityPool.add("mainPlayer",game.PlayerEntity),me.entityPool.add("CoinEntity",game.CoinEntity),me.entityPool.add("EnemyEntity",game.EnemyEntity),me.input.bindKey(me.input.KEY.LEFT,"left"),me.input.bindKey(me.input.KEY.RIGHT,"right"),me.input.bindKey(me.input.KEY.X,"jump",!0),me.state.change(me.state.PLAY)}};game.resources=[{name:"area01_level_tiles",type:"image",src:"data/img/map/area01_level_tiles.png"},{name:"gripe_run_right",type:"image",src:"data/img/sprite/gripe_run_right.png"},{name:"area01_bkg0",type:"image",src:"data/img/area01_bkg0.png"},{name:"area01_bkg1",type:"image",src:"data/img/area01_bkg1.png"},{name:"spinning_coin_gold",type:"image",src:"data/img/sprite/spinning_coin_gold.png"},{name:"wheelie_right",type:"image",src:"data/img/sprite/wheelie_right.png"},{name:"32x32_font",type:"image",src:"data/img/font/32x32_font.png"},{name:"title_screen",type:"image",src:"data/img/gui/title_screen.png"},{name:"area01",type:"tmx",src:"data/map/area01.tmx"},{name:"dst-inertexponent",type:"audio",src:"data/bgm/",channel:1},{name:"cling",type:"audio",src:"data/sfx/",channel:2},{name:"stomp",type:"audio",src:"data/sfx/",channel:1},{name:"jump",type:"audio",src:"data/sfx/",channel:1}],game.HUD=game.HUD||{},game.HUD.Container=me.ObjectContainer.extend({init:function(){this.parent(),this.isPersistent=!0,this.collidable=!1,this.z=1/0,this.name="HUD",this.addChild(new game.HUD.ScoreItem(630,440))}}),game.HUD.ScoreItem=me.Renderable.extend({init:function(a,b){this.parent(new me.Vector2d(a,b),10,10),this.font=new me.BitmapFont("32x32_font",32),this.font.set("right"),this.score=-1,this.floating=!0},update:function(){return this.score!==game.data.score?(this.score=game.data.score,!0):!1},draw:function(a){this.font.draw(a,game.data.score,this.pos.x,this.pos.y)}}),game.PlayerEntity=me.ObjectEntity.extend({init:function(a,b,c){this.parent(a,b,c),this.setVelocity(3,15),this.updateColRect(8,48,8,54),me.game.viewport.follow(this.pos,me.game.viewport.AXIS.BOTH)},update:function(){me.input.isKeyPressed("left")?(this.flipX(!0),this.vel.x-=this.accel.x*me.timer.tick):me.input.isKeyPressed("right")?(this.flipX(!1),this.vel.x+=this.accel.x*me.timer.tick):this.vel.x=0,me.input.isKeyPressed("jump")&&(this.jumping||this.falling||(this.vel.y=-this.maxVel.y*me.timer.tick,this.jumping=!0,me.audio.play("jump"))),this.updateMovement();var a=me.game.world.collide(this);return a&&a.obj.type==me.game.ENEMY_OBJECT&&(a.y>0&&!this.jumping?(this.falling=!1,this.vel.y=-this.maxVel.y*me.timer.tick,this.jumping=!0,me.audio.play("stomp")):(this.renderable.flicker(45),me.game.viewport.shake(10,500,me.game.viewport.AXIS.BOTH))),0!=this.vel.x||0!=this.vel.y?(this.parent(),!0):!1}}),game.CoinEntity=me.CollectableEntity.extend({init:function(a,b,c){c.image="spinning_coin_gold",c.spritewidth=32,this.parent(a,b,c)},onCollision:function(){me.audio.play("cling"),game.data.score+=250,this.collidable=!1,me.game.remove(this)}}),game.EnemyEntity=me.ObjectEntity.extend({init:function(a,b,c){c.image="wheelie_right",c.spritewidth=64,this.parent(a,b,c),this.startX=a,this.endX=a+c.width-c.spritewidth,this.pos.x=a+c.width-c.spritewidth,this.walkLeft=!0,this.setVelocity(1,6),this.updateColRect(4,56,8,56),this.collidable=!0,this.type=me.game.ENEMY_OBJECT},onCollision:function(a,b){this.alive&&a.y>0&&b.falling&&this.renderable.flicker(45)},update:function(){return this.inViewport?(this.alive?(this.walkLeft&&this.pos.x<=this.startX?this.walkLeft=!1:!this.walkLeft&&this.pos.x>=this.endX&&(this.walkLeft=!0),this.flipX(this.walkLeft),this.vel.x+=this.walkLeft?-this.accel.x*me.timer.tick:this.accel.x*me.timer.tick):this.vel.x=0,this.updateMovement(),0!=this.vel.x||0!=this.vel.y?(this.parent(),!0):!1):!1}}),game.PlayScreen=me.ScreenObject.extend({onResetEvent:function(){me.levelDirector.loadLevel("area01"),game.data.score=0,this.HUD=new game.HUD.Container,me.game.world.addChild(this.HUD)},onDestroyEvent:function(){me.game.world.removeChild(this.HUD)}}),game.TitleScreen=me.ScreenObject.extend({init:function(){this.parent(!0),this.title=null,this.font=null,this.scrollerfont=null,this.scrollertween=null,this.scroller="A SMALL STEP BY STEP TUTORIAL FOR GAME CREATION WITH MELONJS ",this.scrollerpos=600},onResetEvent:function(){null==this.title&&(this.title=me.loader.getImage("title_screen"),this.font=new me.BitmapFont("32x32_font",32),this.scrollerfont=new me.BitmapFont("32x32_font",32)),this.scrollerpos=640,this.scrollertween=new me.Tween(this).to({scrollerpos:-2200},1e4).onComplete(this.scrollover.bind(this)).start(),me.input.bindKey(me.input.KEY.ENTER,"enter",!0),me.audio.play("cling")},scrollover:function(){this.scrollerpos=640,this.scrollertween.to({scrollerpos:-2200},1e4).onComplete(this.scrollover.bind(this)).start()},update:function(){return me.input.isKeyPressed("enter")&&me.state.change(me.state.PLAY),!0},draw:function(a){a.drawImage(this.title,0,0),this.font.draw(a,"PRESS ENTER TO PLAY",20,240),this.scrollerfont.draw(a,this.scroller,this.scrollerpos,440)},onDestroyEvent:function(){me.input.unbindKey(me.input.KEY.ENTER),this.scrollertween.stop()}}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license MelonJS Game Engine | |
* @copyright (C) 2011 - 2013 Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
* melonJS is licensed under the MIT License. | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
*/ | |
window.me=window.me||{},function(a){function b(){if(!h){if(!e.body)return setTimeout(b,13);e.removeEventListener?e.removeEventListener("DOMContentLoaded",b,!1):a.removeEventListener("load",b,!1),h=!0;for(var c=0;c<i.length;c++)i[c].call(a,[]);i.length=0}}function c(){return g?void 0:(g=!0,"complete"===e.readyState?b():(e.addEventListener&&e.addEventListener("DOMContentLoaded",b,!1),a.addEventListener("load",b,!1),void 0))}function d(){f||(me.device._check(),me.save._init(),me.loader.setNocache(e.location.href.match(/\?nocache/)||!1),me.timer.init(),me.mapReader=new me.TMXMapReader,me.state.init(),me.entityPool.init(),me.levelDirector.reset(),f=!0)}var e=a.document;me={mod:"melonJS",version:"0.9.11"},me.sys={fps:60,interpolation:!1,scale:null,scalingInterpolation:!1,gravity:void 0,stopOnAudioError:!0,pauseOnBlur:!0,resumeOnFocus:!0,stopOnBlur:!1,preRender:!1,checkVersion:function(a,b){b=b||me.version;for(var c=a.split("."),d=b.split("."),e=Math.min(c.length,d.length),f=0,g=0;e>g&&!(f=+c[g]-+d[g]);g++);return f?f:c.length-d.length}};var f=!1,g=!1,h=!1,i=[];a.onReady=function(b){return c(),h?b.call(a,[]):i.push(function(){return b.call(a,[])}),this},a.onReady(function(){d()});var j=!1,k=/var xyz/.test(function(){})?/\bparent\b/:/[\D|\d]*/,l=function(a){if(null==a||"object"!=typeof a)return a;var b;if(a instanceof Array){b=[],Object.setPrototypeOf(b,Object.getPrototypeOf(a));for(var c=0,d=a.length;d>c;c++)b[c]=l(a[c]);return b}if(a instanceof Date)return b=new Date,b.setTime(a.getTime()),b;b={},Object.setPrototypeOf(b,Object.getPrototypeOf(a));for(var e in a)a.hasOwnProperty(e)&&(b[e]=l(a[e]));return b};if(Object.extend=function(a){function b(a,b){return function(){var c=this.parent;this.parent=d[a];var e=b.apply(this,arguments);return this.parent=c,e}}function c(){if(!j){for(var a in this)"object"==typeof this[a]&&(this[a]=l(this[a]));this.init&&this.init.apply(this,arguments)}return this}var d=this.prototype;j=!0;var e=new this;j=!1;for(var f in a)e[f]="function"==typeof a[f]&&"function"==typeof d[f]&&k.test(a[f])?b(f,a[f]):a[f];return c.prototype=e,c.constructor=c,c.extend=Object.extend,c},"function"!=typeof Object.create&&(Object.create=function(a){function b(){}return b.prototype=a,new b}),!Function.prototype.bind){var m=function(){};Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError("Function.prototype.bind called on incompatible "+b);var c=Array.prototype.slice.call(arguments,1),d=function(){if(this instanceof d){var e=b.apply(this,c.concat(Array.prototype.slice.call(arguments)));return Object(e)===e?e:this}return b.apply(a,c.concat(Array.prototype.slice.call(arguments)))};return b.prototype&&(m.prototype=b.prototype,d.prototype=new m,m.prototype=null),d}}window.throttle||(window.throttle=function(a,b,c){var d,e=Date.now();return"boolean"!=typeof b&&(b=!1),function(){var f=Date.now(),g=f-e,h=arguments;return a>g?(b===!1&&(clearTimeout(d),d=setTimeout(function(){return e=f,c.apply(null,h)},g)),void 0):(e=f,c.apply(null,h))}}),"undefined"==typeof Date.now&&(Date.now=function(){return(new Date).getTime()}),"undefined"==typeof console&&(console={log:function(){},info:function(){},error:function(){alert(Array.prototype.slice.call(arguments).join(", "))}}),Function.prototype.defer=function(){var a=this,b=Array.prototype.slice.call(arguments);return window.setTimeout(function(){return a.apply(a,b)},.01)},Object.defineProperty||(Object.defineProperty=function(a,b,c){if(!a.__defineGetter__)throw"melonJS: Object.defineProperty not supported";c.get&&a.__defineGetter__(b,c.get),c.set&&a.__defineSetter__(b,c.set)}),Object.getPrototypeOf=Object.getPrototypeOf||function(a){return a.__proto__},Object.setPrototypeOf=Object.setPrototypeOf||function(a,b){return a.__proto__=b,a},String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+/,"").replace(/\s+$/,"")}),String.prototype.trimRight||(String.prototype.trimRight=function(){return this.replace(/\s+$/,"")}),String.prototype.isNumeric=function(){return null!==this&&!isNaN(this)&&""!==this.trim()},String.prototype.isBoolean=function(){return null!==this&&("true"===this.trim()||"false"===this.trim())},String.prototype.contains=function(a){return this.indexOf(a)>-1},String.prototype.toHex=function(){for(var a="",b=0;b<this.length;)a+=this.charCodeAt(b++).toString(16);return a},Number.prototype.clamp=function(a,b){return a>this?a:this>b?b:+this},Number.prototype.random=function(a,b){return~~(Math.random()*(b-a+1))+a},Number.prototype.round=function(){var a=arguments.length<2?this:arguments[0],b=Math.pow(10,arguments[1]||arguments[0]||0);return Math.round(a*b)/b},Number.prototype.toHex=function(){return"0123456789ABCDEF".charAt(this-this%16>>4)+"0123456789ABCDEF".charAt(this%16)},Number.prototype.sign=function(){return 0>this?-1:this>0?1:0},Number.prototype.degToRad=function(a){return(a||this)/180*Math.PI},Number.prototype.radToDeg=function(a){return(a||this)*(180/Math.PI)},Array.prototype.remove=function(a){var b=Array.prototype.indexOf.call(this,a);return-1!==b&&Array.prototype.splice.call(this,b,1),this},Array.prototype.forEach||(Array.prototype.forEach=function(a,b){for(var c=0,d=this.length;d--;c++)a.call(b||this,this[c],c,this)}),Object.defineProperty(me,"initialized",{get:function(){return f}}),me.game=function(){var a={},b=null,c=!1,d=null,e=!0;return a.viewport=null,a.collisionMap=null,a.currentLevel=null,a.world=null,a.mergeGroup=!0,a.sortOn="z",a.renderer=null,a.NO_OBJECT=0,a.ENEMY_OBJECT=1,a.COLLECTABLE_OBJECT=2,a.ACTION_OBJECT=3,a.onLevelLoaded=null,a.init=function(d,f){c||(d=d||me.video.getWidth(),f=f||me.video.getHeight(),a.viewport=new me.Viewport(0,0,d,f),a.world=new me.ObjectContainer(0,0,d,f),a.world.name="rootContainer",b=me.video.getSystemContext(),me.event.publish(me.event.GAME_INIT),e=!0,c=!0)},a.reset=function(){a.removeAll(),a.viewport&&a.viewport.reset(),b.setTransform(1,0,0,1,0,0),a.currentLevel={pos:{x:0,y:0}}},a.loadTMXLevel=function(c){a.world.autoSort=!1,a.currentLevel=c,a.collisionMap=a.currentLevel.getLayerByName("collision"),a.collisionMap&&a.collisionMap.isCollisionMap||console.error("WARNING : no collision map detected");for(var d=a.currentLevel.getLayers(),e=d.length;e--;)d[e].visible&&a.add(d[e]);a.viewport.setBounds(Math.max(a.currentLevel.width,a.viewport.width),Math.max(a.currentLevel.height,a.viewport.height));for(var f=a.world,g=a.currentLevel.getObjectGroups(),h=0;h<g.length;h++){var i=g[h];a.mergeGroup===!1&&(f=new me.ObjectContainer,f.name=i.name,f.visible=i.visible,f.z=i.z,f.setOpacity(i.opacity),f.autoSort=!1);for(var j=0;j<i.objects.length;j++){var k=i.objects[j],l=me.entityPool.newInstanceOf(k.name,k.x,k.y,k);l&&(l.z=i.z,a.mergeGroup===!0&&l.isRenderable===!0&&(l.setOpacity(l.getOpacity()*i.opacity),null!==l.renderable&&l.renderable.setOpacity(l.renderable.getOpacity()*i.opacity)),f.addChild(l))}a.mergeGroup===!1&&(a.world.addChild(f),f.autoSort=!0)}a.world.sort(!0),a.world.autoSort=!0,a.currentLevel.pos.x!==a.currentLevel.pos.y&&b.translate(a.currentLevel.pos.x,a.currentLevel.pos.y),a.onLevelLoaded&&a.onLevelLoaded.call(a.onLevelLoaded,c.name),me.event.publish(me.event.LEVEL_LOADED,[c.name])},a.add=function(b,c){"undefined"!=typeof c&&(b.z=c),a.world.addChild(b)},a.getEntityByName=function(b){return a.world.getEntityByProp("name",b)},a.getEntityByGUID=function(b){var c=a.world.getEntityByProp("GUID",b);return c.length>0?c[0]:null},a.getEntityByProp=function(b,c){return a.world.getEntityByProp(b,c)},a.getEntityContainer=function(a){return a.ancestor},a.remove=function(a,b){a.ancestor&&(b===!0?a.ancestor.removeChild(a):(a.visible=a.inViewport=!1,d=function(a){"undefined"!=typeof a.ancestor&&a.ancestor.removeChild(a),d=null}.defer(a)))},a.removeAll=function(){d&&(clearTimeout(d),d=null),a.world.destroy()},a.sort=function(){a.world.sort()},a.collide=function(b,c){return a.world.collide(b,c)},a.collideType=function(b,c,d){return a.world.collideType(b,c,d)},a.repaint=function(){e=!0},a.update=function(){return e=a.world.update()||e,e=a.viewport.update(e)||e},a.draw=function(){e&&(a.viewport.screenX=a.viewport.pos.x+~~a.viewport.offset.x,a.viewport.screenY=a.viewport.pos.y+~~a.viewport.offset.y,b.save(),b.translate(-a.viewport.screenX,-a.viewport.screenY),a.viewport.screenX-=a.currentLevel.pos.x,a.viewport.screenY-=a.currentLevel.pos.y,a.world.draw(b,a.viewport),b.restore(),a.viewport.draw(b)),e=!1},a}()}(window),function(a){me.device=function(){function b(a){a.reading?(d.accelerationX=a.reading.accelerationX,d.accelerationY=a.reading.accelerationY,d.accelerationZ=a.reading.accelerationZ):(d.accelerationX=a.accelerationIncludingGravity.x,d.accelerationY=a.accelerationIncludingGravity.y,d.accelerationZ=a.accelerationIncludingGravity.z)}function c(a){d.gamma=a.gamma,d.beta=a.beta,d.alpha=a.alpha}var d={},e=!1,f=!1,g=null;return d._check=function(){me.audio.detectCapabilities(),me.device.pointerEnabled=navigator.pointerEnabled||navigator.msPointerEnabled,navigator.maxTouchPoints=navigator.maxTouchPoints||navigator.msMaxTouchPoints||0,a.gesture=a.gesture||a.MSGesture,me.device.touch="createTouch"in document||"ontouchstart"in a||navigator.isCocoonJS||navigator.maxTouchPoints>0,me.device.isMobile=me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i)||!1,me.device.hasAccelerometer="undefined"!=typeof a.DeviceMotionEvent||"undefined"!=typeof a.Windows&&"function"==typeof Windows.Devices.Sensors.Accelerometer,a.DeviceOrientationEvent&&(me.device.hasDeviceOrientation=!0);try{d.localStorage=!!a.localStorage}catch(b){d.localStorage=!1}},d.ua=navigator.userAgent,d.sound=!1,d.localStorage=!1,d.hasAccelerometer=!1,d.hasDeviceOrientation=!1,d.nativeBase64="function"==typeof a.atob,d.touch=!1,d.isMobile=!1,d.orientation=0,d.accelerationX=0,d.accelerationY=0,d.accelerationZ=0,d.gamma=0,d.beta=0,d.alpha=0,d.getPixelRatio=function(){if(null===g){var b=me.video.getScreenContext(),c=a.devicePixelRatio||1,d=b.webkitBackingStorePixelRatio||b.mozBackingStorePixelRatio||b.msBackingStorePixelRatio||b.oBackingStorePixelRatio||b.backingStorePixelRatio||1;g=c/d}return g},d.getStorage=function(a){switch(a=a||"local"){case"local":return me.save}throw"melonJS : storage type "+a+" not supported"},d.watchAccelerometer=function(){if(me.device.hasAccelerometer){if(!e){if("undefined"==typeof Windows)a.addEventListener("devicemotion",b,!1);else{var c=Windows.Devices.Sensors.Accelerometer.getDefault();if(c){var d=c.minimumReportInterval,f=d>=16?d:25;c.reportInterval=f,c.addEventListener("readingchanged",b,!1)}}e=!0}return!0}return!1},d.unwatchAccelerometer=function(){if(e){if("undefined"==typeof Windows)a.removeEventListener("devicemotion",b,!1);else{var c=Windows.Device.Sensors.Accelerometer.getDefault();c.removeEventListener("readingchanged",b,!1)}e=!1}},d.watchDeviceOrientation=function(){return me.device.hasDeviceOrientation&&!f&&(a.addEventListener("deviceorientation",c,!1),f=!0),!1},d.unwatchDeviceOrientation=function(){f&&(a.removeEventListener("deviceorientation",c,!1),f=!1)},d}()}(window),function(){me.timer=function(){var a={},b=0,c=0,d=0,e=0,f=0,g=Math.ceil(1e3/me.sys.fps),h=1e3/me.sys.fps*1.25;return a.tick=1,a.fps=0,a.init=function(){a.reset()},a.reset=function(){e=d=Date.now(),c=0,b=0},a.getTime=function(){return e},a.countFPS=function(){b++,c+=f,b%10===0&&(this.fps=(~~(1e3*b/c)).clamp(0,me.sys.fps),c=0,b=0)},a.update=function(){d=e,e=Date.now(),f=e-d,a.tick=f>h&&me.sys.interpolation?f/g:1},a}()}(window),function(){me.Vector2d=Object.extend({x:0,y:0,init:function(a,b){this.x=a||0,this.y=b||0},set:function(a,b){return this.x=a,this.y=b,this},setZero:function(){return this.set(0,0)},setV:function(a){return this.x=a.x,this.y=a.y,this},add:function(a){return this.x+=a.x,this.y+=a.y,this},sub:function(a){return this.x-=a.x,this.y-=a.y,this},scale:function(a){return this.x*=a.x,this.y*=a.y,this},div:function(a){return this.x/=a,this.y/=a,this},abs:function(){return this.x<0&&(this.x=-this.x),this.y<0&&(this.y=-this.y),this},clamp:function(a,b){return new me.Vector2d(this.x.clamp(a,b),this.y.clamp(a,b))},clampSelf:function(a,b){return this.x=this.x.clamp(a,b),this.y=this.y.clamp(a,b),this},minV:function(a){return this.x=this.x<a.x?this.x:a.x,this.y=this.y<a.y?this.y:a.y,this},maxV:function(a){return this.x=this.x>a.x?this.x:a.x,this.y=this.y>a.y?this.y:a.y,this},floor:function(){return new me.Vector2d(~~this.x,~~this.y)},floorSelf:function(){return this.x=~~this.x,this.y=~~this.y,this},ceil:function(){return new me.Vector2d(Math.ceil(this.x),Math.ceil(this.y))},ceilSelf:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},negate:function(){return new me.Vector2d(-this.x,-this.y)},negateSelf:function(){return this.x=-this.x,this.y=-this.y,this},copy:function(a){return this.x=a.x,this.y=a.y,this},equals:function(a){return this.x===a.x&&this.y===a.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},normalize:function(){var a=this.length();if(a<Number.MIN_VALUE)return 0;var b=1/a;return this.x*=b,this.y*=b,a},dotProduct:function(a){return this.x*a.x+this.y*a.y},distance:function(a){return Math.sqrt((this.x-a.x)*(this.x-a.x)+(this.y-a.y)*(this.y-a.y))},angle:function(a){return Math.atan2(a.y-this.y,a.x-this.x)},clone:function(){return new me.Vector2d(this.x,this.y)},toString:function(){return"x:"+this.x+",y:"+this.y}})}(window),function(){me.Rect=Object.extend({pos:null,colPos:null,width:0,height:0,hWidth:0,hHeight:0,shapeType:"Rectangle",offset:null,init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),this.pos.setV(a),null===this.offset&&(this.offset=new me.Vector2d),this.offset.set(0,0),null===this.colPos&&(this.colPos=new me.Vector2d),this.colPos.setV(0,0),this.width=b,this.height=c,this.hWidth=~~(b/2),this.hHeight=~~(c/2),Object.defineProperty(this,"left",{get:function(){return this.pos.x},configurable:!0}),Object.defineProperty(this,"right",{get:function(){return this.pos.x+this.width},configurable:!0}),Object.defineProperty(this,"top",{get:function(){return this.pos.y},configurable:!0}),Object.defineProperty(this,"bottom",{get:function(){return this.pos.y+this.height},configurable:!0})},set:function(a,b,c){this.pos.setV(a),this.width=b,this.height=c,this.hWidth=~~(b/2),this.hHeight=~~(c/2),this.offset.set(0,0)},getBounds:function(){return this.clone()},clone:function(){return new me.Rect(this.pos.clone(),this.width,this.height)},translate:function(a,b){return this.pos.x+=a,this.pos.y+=b,this},translateV:function(a){return this.pos.add(a),this},union:function(a){var b=Math.min(this.pos.x,a.pos.x),c=Math.min(this.pos.y,a.pos.y);return this.width=Math.ceil(Math.max(this.pos.x+this.width,a.pos.x+a.width)-b),this.height=Math.ceil(Math.max(this.pos.y+this.height,a.pos.y+a.height)-c),this.hWidth=~~(this.width/2),this.hHeight=~~(this.height/2),this.pos.x=~~b,this.pos.y=~~c,this},adjustSize:function(a,b,c,d){-1!==a&&(this.colPos.x=a,this.width=b,this.hWidth=~~(this.width/2),this.left!==this.pos.x+this.colPos.x&&Object.defineProperty(this,"left",{get:function(){return this.pos.x+this.colPos.x},configurable:!0}),this.right!==this.pos.x+this.colPos.x+this.width&&Object.defineProperty(this,"right",{get:function(){return this.pos.x+this.colPos.x+this.width},configurable:!0})),-1!==c&&(this.colPos.y=c,this.height=d,this.hHeight=~~(this.height/2),this.top!==this.pos.y+this.colPos.y&&Object.defineProperty(this,"top",{get:function(){return this.pos.y+this.colPos.y},configurable:!0}),this.bottom!==this.pos.y+this.colPos.y+this.height&&Object.defineProperty(this,"bottom",{get:function(){return this.pos.y+this.colPos.y+this.height},configurable:!0}))},flipX:function(a){this.colPos.x=a-this.width-this.colPos.x,this.hWidth=~~(this.width/2)},flipY:function(a){this.colPos.y=a-this.height-this.colPos.y,this.hHeight=~~(this.height/2)},equals:function(a){return this.left===a.left&&this.right===a.right&&this.top===a.top&&this.bottom===a.bottom},overlaps:function(a){return this.left<a.right&&a.left<this.right&&this.top<a.bottom&&a.top<this.bottom},within:function(a){return a.left<=this.left&&a.right>=this.right&&a.top<=this.top&&a.bottom>=this.bottom},contains:function(a){return a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom},containsPointV:function(a){return this.containsPoint(a.x,a.y)},containsPoint:function(a,b){return a>=this.left&&a<=this.right&&b>=this.top&&b<=this.bottom},collideWithRectangle:function(a){var b=new me.Vector2d(0,0);if(this.overlaps(a)){var c=this.left+this.hWidth-a.left-a.hWidth,d=this.top+this.hHeight-a.top-a.hHeight;b.x=a.hWidth+this.hWidth-(0>c?-c:c),b.y=a.hHeight+this.hHeight-(0>d?-d:d),b.x<b.y?(b.y=0,b.x=0>c?-b.x:b.x):(b.x=0,b.y=0>d?-b.y:b.y)}return b},draw:function(a,b){a.strokeStyle=b||"red",a.strokeRect(this.left,this.top,this.width,this.height)}}),me.Ellipse=Object.extend({pos:null,radius:null,shapeType:"Ellipse",init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),null===this.radius&&(this.radius=new me.Vector2d),this.set(a,b,c)},set:function(a,b,c){this.radius.set(b/2,c/2),this.pos.setV(a).add(this.radius),this.offset=new me.Vector2d},getBounds:function(){return new me.Rect(this.pos.clone().sub(this.radius),2*this.radius.x,2*this.radius.y)},clone:function(){return new me.Ellipse(this.pos.clone(),2*this.radius.x,2*this.radius.y)},draw:function(a,b){a.save(),a.beginPath(),a.translate(this.pos.x-this.radius.x,this.pos.y-this.radius.y),a.scale(this.radius.x,this.radius.y),a.arc(1,1,1,0,2*Math.PI,!1),a.restore(),a.strokeStyle=b||"red",a.stroke()}}),me.PolyShape=Object.extend({offset:null,pos:null,points:null,closed:null,shapeType:"PolyShape",init:function(a,b,c){null===this.pos&&(this.pos=new me.Vector2d),null===this.offset&&(this.offset=new me.Vector2d),this.set(a,b,c)},set:function(a,b,c){this.pos.setV(a),this.points=b,this.closed=c===!0,this.offset.set(0,0),this.getBounds()},getBounds:function(){var a=this.offset,b=0,c=0;return this.points.forEach(function(d){a.x=Math.min(a.x,d.x),a.y=Math.min(a.y,d.y),b=Math.max(b,d.x),c=Math.max(c,d.y)}),new me.Rect(a,b-a.x,c-a.y)},clone:function(){return new me.PolyShape(this.pos.clone(),this.points,this.closed)},draw:function(a,b){a.save(),a.translate(-this.offset.x,-this.offset.y),a.strokeStyle=b||"red",a.beginPath(),a.moveTo(this.points[0].x,this.points[0].y),this.points.forEach(function(b){a.lineTo(b.x,b.y),a.moveTo(b.x,b.y)}),this.closed===!0&&a.lineTo(this.points[0].x,this.points[0].y),a.stroke(),a.restore()}})}(window),function(){me.debug={renderCollisionMap:!1}}(window),function(){me.Renderable=me.Rect.extend({isRenderable:!0,GUID:void 0,visible:!0,inViewport:!1,alwaysUpdate:!1,updateWhenPaused:!1,isPersistent:!1,floating:!1,z:0,anchorPoint:null,alpha:1,init:function(a,b,c){this.parent(a,b,c),null===this.anchorPoint&&(this.anchorPoint=new me.Vector2d),this.anchorPoint.set(.5,.5),this.setOpacity(1)},getOpacity:function(){return this.alpha},setOpacity:function(a){"number"==typeof a&&(this.alpha=a.clamp(0,1))},update:function(){return!1},draw:function(a,b){this.parent(a,b)}})}(window),function(){me.SpriteObject=me.Renderable.extend({scale:null,scaleFlag:!1,lastflipX:!1,lastflipY:!1,z:0,offset:null,angle:0,_sourceAngle:0,image:null,flickering:!1,flickerTimer:-1,flickercb:null,flickerState:!1,init:function(a,b,c,d,e){this.isSprite=!0,this.parent(new me.Vector2d(a,b),d||c.width,e||c.height),this.image=c,this.scale=new me.Vector2d(1,1),this.lastflipX=this.lastflipY=!1,this.scaleFlag=!1,this.offset=new me.Vector2d(0,0),this.visible=!0,this.isPersistent=!1,this.flickering=!1},setTransparency:function(a){a="#"===a.charAt(0)?a.substring(1,7):a,this.image=me.video.applyRGBFilter(this.image,"transparent",a.toUpperCase()).canvas},isFlickering:function(){return this.flickering},flicker:function(a,b){this.flickerTimer=a,this.flickerTimer<0?(this.flickering=!1,this.flickercb=null):this.flickering||(this.flickercb=b,this.flickering=!0)},flipX:function(a){a!==this.lastflipX&&(this.lastflipX=a,this.scale.x=-this.scale.x,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},flipY:function(a){a!==this.lastflipY&&(this.lastflipY=a,this.scale.y=-this.scale.y,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},resize:function(a){a>0&&(this.scale.x=this.scale.x<0?-a:a,this.scale.y=this.scale.y<0?-a:a,this.scaleFlag=1!==this.scale.x||1!==this.scale.y)},update:function(){return this.flickering?(this.flickerTimer-=me.timer.tick,this.flickerTimer<0&&(this.flickercb&&this.flickercb(),this.flicker(-1)),!0):!1},draw:function(a){if(!this.flickering||(this.flickerState=!this.flickerState,this.flickerState)){a.save(),a.globalAlpha*=this.getOpacity();var b=~~this.pos.x,c=~~this.pos.y,d=this.width,e=this.height,f=this.angle+this._sourceAngle;if(this.scaleFlag||0!==f){var g=d*this.anchorPoint.x,h=e*this.anchorPoint.y;a.translate(b+g,c+h),this.scaleFlag&&a.scale(this.scale.x,this.scale.y),0!==f&&a.rotate(f),0!==this._sourceAngle?(d=this.height,e=this.width,b=-h,c=-g):(b=-g,c=-h)}a.drawImage(this.image,this.offset.x,this.offset.y,d,e,b,c,d,e),a.restore()}},destroy:function(){this.onDestroyEvent.apply(this,arguments)},onDestroyEvent:function(){}}),me.AnimationSheet=me.SpriteObject.extend({spacing:0,margin:0,animationpause:!1,animationspeed:100,init:function(a,b,c,d,e,f,g,h,i){this.anim={},this.resetAnim=null,this.current=null,this.animationspeed=100,this.spacing=f||0,this.margin=g||0,this.parent(a,b,c,d,e,f,g),this.textureAtlas=null,this.atlasIndices=null,this.buildLocalAtlas(h||void 0,i||void 0),this.addAnimation("default",null),this.setCurrentAnimation("default")},buildLocalAtlas:function(a,b){if(void 0!==a)this.textureAtlas=a,this.atlasIndices=b;else{this.textureAtlas=[];for(var c=new me.Vector2d(~~((this.image.width-this.margin)/(this.width+this.spacing)),~~((this.image.height-this.margin)/(this.height+this.spacing))),d=0,e=c.x*c.y;e>d;d++)this.textureAtlas[d]={name:""+d,offset:new me.Vector2d(this.margin+(this.spacing+this.width)*(d%c.x),this.margin+(this.spacing+this.height)*~~(d/c.x)),width:this.width,height:this.height,hWidth:this.width/2,hHeight:this.height/2,angle:0}}},addAnimation:function(a,b,c){if(this.anim[a]={name:a,frame:[],idx:0,length:0,animationspeed:c||this.animationspeed,nextFrame:0},null==b){b=[];var d=0;this.textureAtlas.forEach(function(){b[d]=d++})}for(var e=0,f=b.length;f>e;e++)if("number"==typeof b[e])this.anim[a].frame[e]=this.textureAtlas[b[e]];else{if(null===this.atlasIndices)throw"melonjs: string parameters for addAnimation are only allowed for TextureAtlas ";this.anim[a].frame[e]=this.textureAtlas[this.atlasIndices[b[e]]]}this.anim[a].length=this.anim[a].frame.length},setCurrentAnimation:function(a,b){if(!this.anim[a])throw"melonJS: animation id '"+a+"' not defined";this.current=this.anim[a],this.resetAnim=b||null,this.setAnimationFrame(this.current.idx),this.current.nextFrame=me.timer.getTime()+this.current.animationspeed},isCurrentAnimation:function(a){return this.current.name===a},setAnimationFrame:function(a){this.current.idx=(a||0)%this.current.length;var b=this.current.frame[this.current.idx];this.offset=b.offset,this.width=b.width,this.height=b.height,this.hWidth=b.hWidth,this.hHeight=b.hHeight,this._sourceAngle=b.angle},getCurrentAnimationFrame:function(){return this.current.idx},update:function(){if(!this.animationpause&&me.timer.getTime()>=this.current.nextFrame){if(this.setAnimationFrame(++this.current.idx),0===this.current.idx&&this.resetAnim)if("string"==typeof this.resetAnim)this.setCurrentAnimation(this.resetAnim);else if("function"==typeof this.resetAnim&&this.resetAnim()===!1)return this.current.idx=this.current.length-1,this.setAnimationFrame(this.current.idx),this.parent(),!1;return this.current.nextFrame=me.timer.getTime()+this.current.animationspeed,this.parent()||!0}return this.parent()}})}(window),function(){var a=-(Math.PI/2);me.TextureAtlas=Object.extend({format:null,texture:null,atlas:null,init:function(a,b){if(a&&a.meta){if(a.meta.app.contains("texturepacker"))if(this.format="texturepacker",void 0===b){var c=me.utils.getBasename(a.meta.image);if(this.texture=me.loader.getImage(c),null===this.texture)throw"melonjs: Atlas texture '"+c+"' not found"}else this.texture=b;if(a.meta.app.contains("ShoeBox")){if(!a.meta.exporter||!a.meta.exporter.contains("melonJS"))throw"melonjs: ShoeBox requires the JSON exporter : https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx";this.format="ShoeBox",this.texture=b}this.atlas=this.initFromTexturePacker(a)}if(null===this.atlas)throw"melonjs: texture atlas format not supported"},initFromTexturePacker:function(a){var b={};return a.frames.forEach(function(a){a.hasOwnProperty("filename")&&(b[a.filename]={frame:new me.Rect(new me.Vector2d(a.frame.x,a.frame.y),a.frame.w,a.frame.h),source:new me.Rect(new me.Vector2d(a.spriteSourceSize.x,a.spriteSourceSize.y),a.spriteSourceSize.w,a.spriteSourceSize.h),rotated:a.rotated===!0,trimmed:a.trimmed===!0})}),b},getTexture:function(){return this.texture},getRegion:function(b){var c=this.atlas[b];return c?{name:b,pos:c.source.pos.clone(),offset:c.frame.pos.clone(),width:c.frame.width,height:c.frame.height,hWidth:c.frame.width/2,hHeight:c.frame.height/2,angle:c.rotated===!0?a:0}:null},createSpriteFromName:function(a){var b=this.getRegion(a);if(b){var c=new me.SpriteObject(0,0,this.getTexture(),b.width,b.height);return c.offset.setV(b.offset),c._sourceAngle=b.angle,c}throw"melonjs: TextureAtlas - region for "+a+" not found"},createAnimationFromName:function(a){for(var b=[],c={},d=0;d<a.length;++d)if(b[d]=this.getRegion(a[d]),c[a[d]]=d,null==b[d])throw"melonjs: TextureAtlas - region for "+a[d]+" not found";return new me.AnimationSheet(0,0,this.texture,0,0,0,0,b,c)}})}(window),function(){var a=Math.min,b=Math.max;me.Viewport=me.Rect.extend({AXIS:{NONE:0,HORIZONTAL:1,VERTICAL:2,BOTH:3},limits:null,target:null,follow_axis:0,shaking:!1,_shake:null,_fadeIn:null,_fadeOut:null,_deadwidth:0,_deadheight:0,_limitwidth:0,_limitheight:0,screenX:0,screenY:0,init:function(a,b,c,d,e,f){this.parent(new me.Vector2d(a,b),c-a,d-b),this.limits=new me.Vector2d(e||this.width,f||this.height),this.offset=new me.Vector2d,this.target=null,this.follow_axis=this.AXIS.NONE,this._shake={intensity:0,duration:0,axis:this.AXIS.BOTH,onComplete:null,start:0},this._fadeOut={color:0,alpha:0,duration:0,tween:null},this._fadeIn={color:0,alpha:1,duration:0,tween:null},this.setDeadzone(this.width/6,this.height/6)},_followH:function(c){var d=this.pos.x;return c.x-this.pos.x>this._deadwidth?this.pos.x=~~a(c.x-this._deadwidth,this._limitwidth):c.x-this.pos.x<this.deadzone.x&&(this.pos.x=~~b(c.x-this.deadzone.x,0)),d!==this.pos.x},_followV:function(c){var d=this.pos.y;return c.y-this.pos.y>this._deadheight?this.pos.y=~~a(c.y-this._deadheight,this._limitheight):c.y-this.pos.y<this.deadzone.y&&(this.pos.y=~~b(c.y-this.deadzone.y,0)),d!==this.pos.y},reset:function(a,b){this.pos.x=a||0,this.pos.y=b||0,this.target=null,this.follow_axis=null},setDeadzone:function(a,b){this.deadzone=new me.Vector2d(~~((this.width-a)/2),~~((this.height-b)/2-.25*b)),this._deadwidth=this.width-this.deadzone.x,this._deadheight=this.height-this.deadzone.y,this.update(!0)},setBounds:function(a,b){this.limits.set(a,b),this._limitwidth=this.limits.x-this.width,this._limitheight=this.limits.y-this.height},follow:function(a,b){if(a instanceof me.ObjectEntity)this.target=a.pos;else{if(!(a instanceof me.Vector2d))throw"melonJS: invalid target for viewport.follow";this.target=a}this.follow_axis="undefined"==typeof b?this.AXIS.BOTH:b,this.update(!0)},move:function(a,b){var c=~~(this.pos.x+a),d=~~(this.pos.y+b);this.pos.x=c.clamp(0,this._limitwidth),this.pos.y=d.clamp(0,this._limitheight),me.event.publish(me.event.VIEWPORT_ONCHANGE,[this.pos])},update:function(a){var b=!1;if(this.target&&a)switch(this.follow_axis){case this.AXIS.NONE:break;case this.AXIS.HORIZONTAL:b=this._followH(this.target);break;case this.AXIS.VERTICAL:b=this._followV(this.target);break;case this.AXIS.BOTH:b=this._followH(this.target),b=this._followV(this.target)||b}if(this.shaking===!0){var c=me.timer.getTime()-this._shake.start;c>=this._shake.duration?(this.shaking=!1,this.offset.setZero(),"function"==typeof this._shake.onComplete&&this._shake.onComplete()):((this._shake.axis===this.AXIS.BOTH||this._shake.axis===this.AXIS.HORIZONTAL)&&(this.offset.x=(Math.random()-.5)*this._shake.intensity),(this._shake.axis===this.AXIS.BOTH||this._shake.axis===this.AXIS.VERTICAL)&&(this.offset.y=(Math.random()-.5)*this._shake.intensity)),b=!0}return b===!0&&me.event.publish(me.event.VIEWPORT_ONCHANGE,[this.pos]),(null!=this._fadeIn.tween||null!=this._fadeOut.tween)&&(b=!0),b},shake:function(a,b,c,d){this.shaking||(this.shaking=!0,this._shake={intensity:a,duration:b,axis:c||this.AXIS.BOTH,onComplete:d||null,start:me.timer.getTime()})},fadeOut:function(a,b,c){this._fadeOut.color=a,this._fadeOut.duration=b||1e3,this._fadeOut.alpha=1,this._fadeOut.tween=me.entityPool.newInstanceOf("me.Tween",this._fadeOut).to({alpha:0},this._fadeOut.duration).onComplete(c||null),this._fadeOut.tween.start()},fadeIn:function(a,b,c){this._fadeIn.color=a,this._fadeIn.duration=b||1e3,this._fadeIn.alpha=0,this._fadeIn.tween=me.entityPool.newInstanceOf("me.Tween",this._fadeIn).to({alpha:1},this._fadeIn.duration).onComplete(c||null),this._fadeIn.tween.start()},getWidth:function(){return this.width},getHeight:function(){return this.height},focusOn:function(a){this.pos.x=a.x-.5*this.width,this.pos.y=a.y-.5*this.height},isVisible:function(a){return a.overlaps(this)},localToWorld:function(a,b){return new me.Vector2d(a,b).add(this.pos).sub(me.game.currentLevel.pos)},worldToLocal:function(a,b){return new me.Vector2d(a,b).sub(this.pos).add(me.game.currentLevel.pos)},draw:function(a){this._fadeIn.tween&&(a.globalAlpha=this._fadeIn.alpha,me.video.clearSurface(a,me.utils.HexToRGB(this._fadeIn.color)),a.globalAlpha=1,1===this._fadeIn.alpha&&(this._fadeIn.tween=null)),this._fadeOut.tween&&(a.globalAlpha=this._fadeOut.alpha,me.video.clearSurface(a,me.utils.HexToRGB(this._fadeOut.color)),a.globalAlpha=1,0===this._fadeOut.alpha&&(this._fadeOut.tween=null)),me.video.blitSurface()}})}(window),function(){me.GUI_Object=me.SpriteObject.extend({isClickable:!0,updated:!1,init:function(a,b,c){this.parent(a,b,"string"==typeof c.image?me.loader.getImage(c.image):c.image,c.spritewidth,c.spriteheight),this.floating=!0,me.input.registerPointerEvent("mousedown",this,this.clicked.bind(this))},update:function(){return this.updated?(this.updated=!1,!0):!1},clicked:function(a){return this.isClickable?(this.updated=!0,this.onClick(a)):void 0},onClick:function(){return!1},onDestroyEvent:function(){me.input.releasePointerEvent("mousedown",this)}})}(window),function(){var a=new me.Rect(new me.Vector2d,0,0),b=0;me.ObjectContainer=me.Renderable.extend({sortOn:"z",autoSort:!0,pendingSort:null,children:null,collidable:!0,init:function(a,b,c,d){this.parent(new me.Vector2d(a||0,b||0),c||1/0,d||1/0),this.children=[],this.sortOn=me.game.sortOn,this.autoSort=!0},addChild:function(a){"undefined"!=typeof a.ancestor?a.ancestor.removeChild(a):a.isRenderable&&(a.GUID=me.utils.createGUID()),"undefined"==typeof a.z&&(a.z=1/0),a.ancestor=this,this.children.push(a),this.autoSort===!0&&this.sort()},addChildAt:function(a,b){if(!(b>=0&&b<this.children.length))throw"melonJS (me.ObjectContainer): Index ("+b+") Out Of Bounds for addChildAt()";"undefined"!=typeof a.ancestor?a.ancestor.removeChild(a):a.isRenderable&&(a.GUID=me.utils.createGUID()),a.ancestor=this,this.children.splice(b,0,a)},swapChildren:function(a,b){var c=this.getChildIndex(a),d=this.getChildIndex(b);if(-1===c||-1===d)throw"melonJS (me.ObjectContainer): "+a+" Both the supplied entities must be a child of the caller "+this;var e=a.z;a.z=b.z,b.z=e,this.children[c]=b,this.children[d]=a},getChildAt:function(a){if(a>=0&&a<this.children.length)return this.children[a];throw"melonJS (me.ObjectContainer): Index ("+a+") Out Of Bounds for getChildAt()"},getChildIndex:function(a){return this.children.indexOf(a)},hasChild:function(a){return this===a.ancestor},getEntityByProp:function(a,b){function c(a,c){"string"==typeof a[c]?a[c].match(f)&&e.push(a):a[c]===b&&e.push(a) | |
}for(var d,e=[],f=new RegExp(b,"i"),g=this.children.length;g--,d=this.children[g];)d instanceof me.ObjectContainer?(c(d,a),e=e.concat(d.getEntityByProp(a,b))):d.isEntity&&c(d,a);return e},removeChild:function(a,b){if(!this.hasChild(a))throw"melonJS (me.ObjectContainer): "+a+" The supplied entity must be a child of the caller "+this;a.ancestor=void 0,b||("function"==typeof a.destroy&&a.destroy(),me.entityPool.freeInstance(a)),this.children.splice(this.getChildIndex(a),1)},setChildsProperty:function(a,b,c){for(var d,e=this.children.length;e--,d=this.children[e];)c===!0&&d instanceof me.ObjectContainer&&d.setChildsProperty(a,b,c),d[a]=b},moveUp:function(a){var b=this.getChildIndex(a);b-1>=0&&this.swapChildren(a,this.getChildAt(b-1))},moveDown:function(a){var b=this.getChildIndex(a);b+1<this.children.length&&this.swapChildren(a,this.getChildAt(b+1))},moveToTop:function(a){var b=this.getChildIndex(a);b>0&&(this.splice(0,0,this.splice(b,1)[0]),a.z=this.children[1].z+1)},moveToBottom:function(a){var b=this.getChildIndex(a);b<this.children.length-1&&(this.splice(this.children.length-1,0,this.splice(b,1)[0]),a.z=this.children[this.children.length-2].z-1)},collide:function(a,b){return this.collideType(a,null,b)},collideType:function(a,b,c){var d,e;c=c===!0?!0:!1,c===!0&&(e=[]);for(var f,g=this.children.length;g--,f=this.children[g];)if((f.inViewport||f.alwaysUpdate)&&f.collidable)if(f instanceof me.ObjectContainer){if(d=f.collideType(a,b,c),c)e.concat(d);else if(d)return d}else if(f!==a&&(!b||f.type===b)&&(d=f.collisionBox["collideWith"+a.shapeType].call(f.collisionBox,a.collisionBox),0!==d.x||0!==d.y)){if(f.onCollision.call(f,d,a),d.type=f.type,d.obj=f,!c)return d;e.push(d)}return c?e:null},sort:function(a){if(null===this.pendingSort){if(a===!0)for(var b,c=this.children.length;c--,b=this.children[c];)b instanceof me.ObjectContainer&&b.sort(a);this.pendingSort=function(a){a.children.sort(a["_sort"+a.sortOn.toUpperCase()]),a.pendingSort=null,me.game.repaint()}.defer(this)}},_sortZ:function(a,b){return b.z-a.z},_sortX:function(a,b){var c=b.z-a.z;return c?c:(b.pos&&b.pos.x)-(a.pos&&a.pos.x)||0},_sortY:function(a,b){var c=b.z-a.z;return c?c:(b.pos&&b.pos.y)-(a.pos&&a.pos.y)||0},destroy:function(){this.pendingSort&&(clearTimeout(this.pendingSort),this.pendingSort=null);for(var a,b=this.children.length;b--,a=this.children[b];)a.isPersistent||this.removeChild(a)},update:function(){for(var c,d,e,f,g=!1,h=!1,i=me.state.isPaused(),j=me.game.viewport,k=this.children.length;k--,f=this.children[k];)(!i||f.updateWhenPaused)&&(f.isRenderable?(h=b>0||f.floating,h&&b++,c=f.visible&&!h,c&&(d=f.pos.x,e=f.pos.y,a.translateV(f.pos),a.set(a.pos,f.width,f.height)),f.inViewport=f.visible&&(h||j.isVisible(a)),g|=(f.inViewport||f.alwaysUpdate)&&f.update(),c&&a.translate(-d,-e),b>0&&b--):g|=f.update());return g},draw:function(a,b){var c=me.game.viewport,d=!1;this.drawCount=0,a.save(),a.globalAlpha*=this.getOpacity(),a.translate(this.pos.x,this.pos.y);for(var e,f=this.children.length;f--,e=this.children[f];)d=e.floating,e.isRenderable&&(e.inViewport||d&&e.visible)&&(d===!0&&(a.save(),a.translate(c.screenX-this.pos.x,c.screenY-this.pos.y)),e.draw(a,b),d===!0&&a.restore(),this.drawCount++);a.restore()}})}(window),function(){me.ObjectSettings={name:null,image:null,transparent_color:null,spritewidth:null,spriteheight:null,type:0,collidable:!0},me.entityPool=function(){var a={},b={};return a.init=function(){a.add("me.ObjectEntity",me.ObjectEntity),a.add("me.CollectableEntity",me.CollectableEntity),a.add("me.LevelEntity",me.LevelEntity),a.add("me.Tween",me.Tween,!0)},a.add=function(a,c,d){return d?(b[a.toLowerCase()]={"class":c,pool:[],active:[]},void 0):(b[a.toLowerCase()]={"class":c,pool:void 0},void 0)},a.newInstanceOf=function(a){var c="string"==typeof a?a.toLowerCase():void 0,d=Array.prototype.slice.call(arguments);if(c&&b[c]){var e;if(!b[c].pool)return e=b[c]["class"],d[0]=e,new(e.bind.apply(e,d));var f,g=b[c];return e=g["class"],g.pool.length>0?(f=g.pool.pop(),"function"==typeof f.init&&f.init.apply(f,d.slice(1)),"function"==typeof f.onResetEvent&&f.onResetEvent.apply(f,d.slice(1))):(d[0]=e,f=new(e.bind.apply(e,d)),f.className=c),g.active.push(f),f}var h=arguments[3];return h&&h.gid&&h.image?new me.SpriteObject(h.x,h.y,h.image):(c&&console.error("Cannot instantiate entity of type '"+a+"': Class not found!"),null)},a.purge=function(){for(var a in b)b[a].pool=[]},a.freeInstance=function(a){var c=a.className;if(c&&b[c]){for(var d=!0,e=0,f=b[c].active.length;f>e;e++)if(b[c].active[e]===a){d=!1,b[c].active.splice(e,1);break}d||b[c].pool.push(a)}},a}(),me.ObjectEntity=me.Renderable.extend({type:0,collidable:!0,collisionBox:null,shapes:null,renderable:null,lastflipX:!1,lastflipY:!1,init:function(a,b,c){if(null===this.pos&&(this.pos=new me.Vector2d),this.parent(this.pos.set(a,b),~~c.spritewidth||~~c.width,~~c.spriteheight||~~c.height),c.image){var d="string"==typeof c.image?me.loader.getImage(c.image):c.image;this.renderable=new me.AnimationSheet(0,0,d,~~c.spritewidth,~~c.spriteheight,~~c.spacing,~~c.margin),c.transparent_color&&this.renderable.setTransparency(c.transparent_color)}this.name=c.name?c.name.toLowerCase():"",void 0===this.vel&&(this.vel=new me.Vector2d),this.vel.set(0,0),void 0===this.accel&&(this.accel=new me.Vector2d),this.accel.set(0,0),void 0===this.friction&&(this.friction=new me.Vector2d),this.friction.set(0,0),void 0===this.maxVel&&(this.maxVel=new me.Vector2d),this.maxVel.set(1e3,1e3),this.gravity=void 0!==me.sys.gravity?me.sys.gravity:.98,this.isEntity=!0,this.alive=!0,this.visible=!0,this.floating=!1,this.isPersistent=!1,this.falling=!1,this.jumping=!0,this.slopeY=0,this.onslope=!1,this.onladder=!1,this.disableTopLadderCollision=!1,this.collidable="undefined"!=typeof c.collidable?c.collidable:!0,this.type=c.type||0,this.lastflipX=this.lastflipY=!1,this.collisionMap=me.game.collisionMap,this.canBreakTile=!1,this.onTileBreak=null,c.isEllipse===!0?this.addShape(new me.Ellipse(new me.Vector2d(0,0),this.width,this.height)):c.isPolygon===!0||c.isPolyline===!0?(this.addShape(new me.PolyShape(new me.Vector2d(0,0),c.points,c.isPolygon)),this.width=this.collisionBox.width,this.height=this.collisionBox.height):this.addShape(new me.Rect(new me.Vector2d(0,0),this.width,this.height))},updateColRect:function(a,b,c,d){this.collisionBox.adjustSize(a,b,c,d)},addShape:function(a){null===this.shapes&&(this.shapes=[]),this.shapes.push(a),1===this.shapes.length&&(this.collisionBox=this.shapes[0].getBounds(),this.collisionBox.pos=this.pos,this.pos.add(this.shapes[0].offset))},onCollision:function(){this.collidable&&this.type===me.game.COLLECTABLE_OBJECT&&me.game.remove(this)},setVelocity:function(a,b){this.accel.x=0!==a?a:this.accel.x,this.accel.y=0!==b?b:this.accel.y,this.setMaxVelocity(a,b)},setMaxVelocity:function(a,b){this.maxVel.x=a,this.maxVel.y=b},setFriction:function(a,b){this.friction.x=a||0,this.friction.y=b||0},flipX:function(a){a!==this.lastflipX&&(this.lastflipX=a,this.renderable&&this.renderable.flipX&&this.renderable.flipX(a),this.collisionBox.flipX(this.width))},flipY:function(a){a!==this.lastflipY&&(this.lastflipY=a,this.renderable&&this.renderable.flipY&&this.renderable.flipY(a),this.collisionBox.flipY(this.height))},doWalk:function(a){this.flipX(a),this.vel.x+=a?-this.accel.x*me.timer.tick:this.accel.x*me.timer.tick},doClimb:function(a){return this.onladder?(this.vel.y=a?-this.accel.x*me.timer.tick:this.accel.x*me.timer.tick,this.disableTopLadderCollision=!a,!0):!1},doJump:function(){return this.jumping||this.falling?!1:(this.vel.y=-this.maxVel.y*me.timer.tick,this.jumping=!0,!0)},forceJump:function(){this.jumping=this.falling=!1,this.doJump()},distanceTo:function(a){var b=this.pos.x+this.hWidth-(a.pos.x+a.hWidth),c=this.pos.y+this.hHeight-(a.pos.y+a.hHeight);return Math.sqrt(b*b+c*c)},distanceToPoint:function(a){var b=this.pos.x+this.hWidth-a.x,c=this.pos.y+this.hHeight-a.y;return Math.sqrt(b*b+c*c)},angleTo:function(a){var b=a.pos.x+a.hWidth-(this.pos.x+this.hWidth),c=a.pos.y+a.hHeight-(this.pos.y+this.hHeight);return Math.atan2(c,b)},angleToPoint:function(a){var b=a.x-(this.pos.x+this.hWidth),c=a.y-(this.pos.y+this.hHeight);return Math.atan2(c,b)},checkSlope:function(a,b){this.pos.y=a.pos.y-this.height,this.slopeY=b?a.height-(this.collisionBox.right+this.vel.x-a.pos.x):this.collisionBox.left+this.vel.x-a.pos.x,this.vel.y=0,this.pos.y+=this.slopeY.clamp(0,a.height)},computeVelocity:function(a){this.gravity&&(a.y+=this.onladder?0:this.gravity*me.timer.tick,this.falling=a.y>0,this.jumping=this.falling?!1:this.jumping),this.friction.x&&(a.x=me.utils.applyFriction(a.x,this.friction.x)),this.friction.y&&(a.y=me.utils.applyFriction(a.y,this.friction.y)),0!==a.y&&(a.y=a.y.clamp(-this.maxVel.y,this.maxVel.y)),0!==a.x&&(a.x=a.x.clamp(-this.maxVel.x,this.maxVel.x))},updateMovement:function(){this.computeVelocity(this.vel);var a;return this.collidable&&(a=this.collisionMap.checkCollision(this.collisionBox,this.vel),this.onslope=a.yprop.isSlope||a.xprop.isSlope,this.onladder=!1,a.y&&(this.onladder=a.yprop.isLadder||a.yprop.isTopLadder,a.y>0?a.yprop.isSolid||a.yprop.isPlatform&&this.collisionBox.bottom-1<=a.ytile.pos.y||a.yprop.isTopLadder&&!this.disableTopLadderCollision?(this.pos.y=~~this.pos.y,this.vel.y=this.falling?a.ytile.pos.y-this.collisionBox.bottom:0,this.falling=!1):a.yprop.isSlope&&!this.jumping?(this.checkSlope(a.ytile,a.yprop.isLeftSlope),this.falling=!1):a.yprop.isBreakable&&(this.canBreakTile?(me.game.currentLevel.clearTile(a.ytile.col,a.ytile.row),this.onTileBreak&&this.onTileBreak()):(this.pos.y=~~this.pos.y,this.vel.y=this.falling?a.ytile.pos.y-this.collisionBox.bottom:0,this.falling=!1)):a.y<0&&(a.yprop.isPlatform||a.yprop.isLadder||a.yprop.isTopLadder||(this.falling=!0,this.vel.y=0))),a.x&&(this.onladder=a.xprop.isLadder||a.yprop.isTopLadder,a.xprop.isSlope&&!this.jumping?(this.checkSlope(a.xtile,a.xprop.isLeftSlope),this.falling=!1):a.xprop.isPlatform||a.xprop.isLadder||a.xprop.isTopLadder||(a.xprop.isBreakable&&this.canBreakTile?(me.game.currentLevel.clearTile(a.xtile.col,a.xtile.row),this.onTileBreak&&this.onTileBreak()):this.vel.x=0))),this.pos.add(this.vel),a},collide:function(a){return me.game.collide(this,a||!1)},collideType:function(a,b){return me.game.collideType(this,a,b||!1)},update:function(){return this.renderable?this.renderable.update():!1},getBounds:function(){return this.renderable?this.renderable.getBounds().translateV(this.pos):null},draw:function(a){if(this.renderable){var b=~~(this.pos.x+this.anchorPoint.x*(this.width-this.renderable.width)),c=~~(this.pos.y+this.anchorPoint.y*(this.height-this.renderable.height));a.translate(b,c),this.renderable.draw(a),a.translate(-b,-c)}},destroy:function(){this.renderable&&(this.renderable.destroy.apply(this.renderable,arguments),this.renderable=null),this.onDestroyEvent.apply(this,arguments),this.collisionBox=null,this.shapes=[]},onDestroyEvent:function(){}}),me.CollectableEntity=me.ObjectEntity.extend({init:function(a,b,c){this.parent(a,b,c),this.type=me.game.COLLECTABLE_OBJECT}}),me.LevelEntity=me.ObjectEntity.extend({init:function(a,b,c){this.parent(a,b,c),this.nextlevel=c.to,this.fade=c.fade,this.duration=c.duration,this.fading=!1,this.gotolevel=c.to},onFadeComplete:function(){me.levelDirector.loadLevel(this.gotolevel),me.game.viewport.fadeOut(this.fade,this.duration)},goTo:function(a){this.gotolevel=a||this.nextlevel,this.fade&&this.duration?this.fading||(this.fading=!0,me.game.viewport.fadeIn(this.fade,this.duration,this.onFadeComplete.bind(this))):me.levelDirector.loadLevel(this.gotolevel)},onCollision:function(){this.goTo()}})}(window),function(a){me.ScreenObject=me.Renderable.extend({addAsObject:!1,visible:!1,frame:0,z:999,init:function(a,b){this.parent(new me.Vector2d(0,0),0,0),this.addAsObject=this.visible=a===!0||!1,this.isPersistent=this.visible&&b===!0||!1},reset:function(){me.game.reset(),this.frame=0,this.frameRate=Math.round(60/me.sys.fps),this.onResetEvent.apply(this,arguments),this.addAsObject&&(this.visible=!0,this.floating=!0,this.set(new me.Vector2d,me.game.viewport.width,me.game.viewport.height),me.game.add(this,this.z)),me.game.sort()},destroy:function(){this.onDestroyEvent.apply(this,arguments)},update:function(){return!1},onUpdateFrame:function(){++this.frame%this.frameRate===0&&(this.frame=0,me.timer.update(),me.game.update()),me.game.draw()},draw:function(){},onResetEvent:function(){},onDestroyEvent:function(){}}),function(){for(var a=0,b=["ms","moz","webkit","o"],c=window.requestAnimationFrame,d=window.cancelAnimationFrame,e=0;e<b.length&&(!c||!d);++e)c=window[b[e]+"RequestAnimationFrame"],d=window[b[e]+"CancelAnimationFrame"]||window[b[e]+"CancelRequestAnimationFrame"];c&&d||(c=function(b){var c=Date.now(),d=Math.max(0,16-(c-a)),e=window.setTimeout(function(){b(c+d)},d);return a=c+d,e},d=function(a){window.clearTimeout(a)}),window.requestAnimationFrame=c,window.cancelAnimationFrame=d}(),me.state=function(){function b(){-1===j&&-1!==i&&(me.timer.reset(),j=window.requestAnimationFrame(e))}function c(){k&&-1!==i&&(me.timer.reset(),k=!1)}function d(){k=!0}function e(){p(),-1!==j&&(j=window.requestAnimationFrame(e))}function f(){window.cancelAnimationFrame(j),j=-1}function g(a){f(),l[i]&&(l[i].screen.visible?me.game.remove.call(me.game,l[i].screen,!0):l[i].screen.destroy()),l[a]&&(i=a,l[i].screen.reset.apply(l[i].screen,o),p=l[i].screen.onUpdateFrame.bind(l[i].screen),b(),n&&n(),me.game.repaint())}var h={},i=-1,j=-1,k=!1,l={},m={color:"",duration:0},n=null,o=null,p=null;return h.LOADING=0,h.MENU=1,h.READY=2,h.PLAY=3,h.GAMEOVER=4,h.GAME_END=5,h.SCORE=6,h.CREDITS=7,h.SETTINGS=8,h.USER=100,h.onPause=null,h.onResume=null,h.onStop=null,h.onRestart=null,h.init=function(){h.set(h.LOADING,new me.DefaultLoadingScreen),a.addEventListener("blur",function(){i!==h.LOADING&&(me.sys.stopOnBlur&&(h.stop(!0),h.onStop&&h.onStop(),me.event.publish(me.event.STATE_STOP)),me.sys.pauseOnBlur&&(h.pause(!0),h.onPause&&h.onPause(),me.event.publish(me.event.STATE_PAUSE)))},!1),a.addEventListener("focus",function(){i!==h.LOADING&&(me.sys.resumeOnFocus&&(h.resume(!0),h.onResume&&h.onResume(),me.event.publish(me.event.STATE_RESUME)),me.sys.stopOnBlur&&(h.restart(!0),me.game.repaint(),h.onRestart&&h.onRestart(),me.event.publish(me.event.STATE_RESTART)))},!1)},h.stop=function(a){f(),a&&me.audio.pauseTrack()},h.pause=function(a){d(),a&&me.audio.pauseTrack()},h.restart=function(a){b(),a&&me.audio.resumeTrack()},h.resume=function(a){c(),a&&me.audio.resumeTrack()},h.isRunning=function(){return-1!==j},h.isPaused=function(){return k},h.set=function(a,b){l[a]={},l[a].screen=b,l[a].transition=!0},h.current=function(){return l[i].screen},h.transition=function(a,b,c){"fade"===a&&(m.color=b,m.duration=c)},h.setTransition=function(a,b){l[a].transition=b},h.change=function(a){if("undefined"==typeof l[a])throw"melonJS : Undefined ScreenObject for state '"+a+"'";o=null,arguments.length>1&&(o=Array.prototype.slice.call(arguments,1)),m.duration&&l[a].transition?(n=function(){me.game.viewport.fadeOut(m.color,m.duration)},me.game.viewport.fadeIn(m.color,m.duration,function(){g.defer(a)})):g.defer(a)},h.isCurrent=function(a){return i===a},h}()}(window),function(){me.DefaultLoadingScreen=me.ScreenObject.extend({init:function(){this.parent(!0),this.invalidate=!1,this.handle=null},onResetEvent:function(){this.logo1=new me.Font("century gothic",32,"white","middle"),this.logo2=new me.Font("century gothic",32,"#55aa00","middle"),this.logo2.bold(),this.logo1.textBaseline=this.logo2.textBaseline="alphabetic",this.barHeight=4,this.handle=me.event.subscribe(me.event.LOADER_PROGRESS,this.onProgressUpdate.bind(this)),this.loadPercent=0},onDestroyEvent:function(){this.logo1=this.logo2=null,this.handle&&(me.event.unsubscribe(this.handle),this.handle=null)},onProgressUpdate:function(a){this.loadPercent=a,this.invalidate=!0},update:function(){return this.invalidate===!0?(this.invalidate=!1,!0):!1},drawLogo:function(a,b,c){a.save(),a.translate(b,c),a.beginPath(),a.moveTo(.7,48.9),a.bezierCurveTo(10.8,68.9,38.4,75.8,62.2,64.5),a.bezierCurveTo(86.1,53.1,97.2,27.7,87,7.7),a.lineTo(87,7.7),a.bezierCurveTo(89.9,15.4,73.9,30.2,50.5,41.4),a.bezierCurveTo(27.1,52.5,5.2,55.8,.7,48.9),a.lineTo(.7,48.9),a.lineTo(.7,48.9),a.closePath(),a.fillStyle="rgb(255, 255, 255)",a.fill(),a.beginPath(),a.moveTo(84,7),a.bezierCurveTo(87.6,14.7,72.5,30.2,50.2,41.6),a.bezierCurveTo(27.9,53,6.9,55.9,3.2,48.2),a.bezierCurveTo(-.5,40.4,14.6,24.9,36.9,13.5),a.bezierCurveTo(59.2,2.2,80.3,-.8,84,7),a.lineTo(84,7),a.closePath(),a.lineWidth=5.3,a.strokeStyle="rgb(255, 255, 255)",a.lineJoin="miter",a.miterLimit=4,a.stroke(),a.restore()},draw:function(a){var b=this.logo1.measureText(a,"melon").width,c=(me.video.getWidth()-b-this.logo2.measureText(a,"JS").width)/2,d=me.video.getHeight()/2+this.logo2.measureText(a,"melon").height;me.video.clearSurface(a,"#202020"),this.drawLogo(a,(me.video.getWidth()-100)/2,me.video.getHeight()/2-this.barHeight/2-90),this.logo1.draw(a,"melon",c,d),c+=b,this.logo2.draw(a,"JS",c,d);var e=Math.floor(this.loadPercent*me.video.getWidth());a.fillStyle="black",a.fillRect(0,me.video.getHeight()/2-this.barHeight/2,me.video.getWidth(),this.barHeight),a.fillStyle="#55aa00",a.fillRect(2,me.video.getHeight()/2-this.barHeight/2,e,this.barHeight)}})}(window),function(){me.loader=function(){function a(){m===l?f.onload?(clearTimeout(n),setTimeout(function(){f.onload(),me.event.publish(me.event.LOADER_COMPLETE)},300)):console.error("no load callback defined"):n=setTimeout(a,100)}function b(a,b,c){g[a.name]=new Image,g[a.name].onload=b,g[a.name].onerror=c,g[a.name].src=a.src+f.nocache}function c(a,b,c){var d=new XMLHttpRequest,e=me.utils.getFileExtension(a.src).toLowerCase();d.overrideMimeType&&("json"===e?d.overrideMimeType("application/json"):d.overrideMimeType("text/xml")),d.open("GET",a.src+f.nocache,!0),"tmx"===a.type&&me.levelDirector.addTMXLevel(a.name),d.ontimeout=c,d.onreadystatechange=function(){if(4===d.readyState)if(200===d.status||0===d.status&&d.responseText){var f=null;switch(e){case"xml":case"tmx":f=me.device.ua.match(/msie/i)||!d.responseXML?(new DOMParser).parseFromString(d.responseText,"text/xml"):d.responseXML,e="xml";break;case"json":f=JSON.parse(d.responseText);break;default:throw"melonJS: TMX file format "+e+"not supported !"}h[a.name]={data:f,isTMX:"tmx"===a.type,format:e},b()}else c()},d.send(null)}function d(a,b,c){var d=new XMLHttpRequest;d.overrideMimeType&&d.overrideMimeType("application/json"),d.open("GET",a.src+f.nocache,!0),d.ontimeout=c,d.onreadystatechange=function(){4===d.readyState&&(200===d.status||0===d.status&&d.responseText?(k[a.name]=JSON.parse(d.responseText),b()):c())},d.send(null)}function e(a,b,c){var d=new XMLHttpRequest;d.open("GET",a.src+f.nocache,!0),d.responseType="arraybuffer",d.onerror=c,d.onload=function(){var c=d.response;if(c){for(var e=new Uint8Array(c),f=[],g=0;g<e.byteLength;g++)f[g]=String.fromCharCode(e[g]);i[a.name]=f.join(""),b()}},d.send()}var f={},g={},h={},i={},j={},k={},l=0,m=0,n=0;return f.nocache="",f.onload=void 0,f.onProgress=void 0,f.onResourceLoaded=function(){m++;var a=f.getLoadProgress();f.onProgress&&f.onProgress(a),me.event.publish(me.event.LOADER_PROGRESS,[a])},f.onLoadingError=function(a){throw"melonJS: Failed loading resource "+a.src},f.setNocache=function(a){f.nocache=a?"?"+parseInt(1e7*Math.random(),10):""},f.preload=function(b){for(var c=0;c<b.length;c++)l+=f.load(b[c],f.onResourceLoaded.bind(f),f.onLoadingError.bind(f,b[c]));a()},f.load=function(a,f,g){switch(a.name=a.name.toLowerCase(),a.type){case"binary":return e.call(this,a,f,g),1;case"image":return b.call(this,a,f,g),1;case"json":return d.call(this,a,f,g),1;case"tmx":case"tsx":return c.call(this,a,f,g),1;case"audio":if(me.audio.isAudioEnable())return me.audio.load(a,f,g),1;break;default:throw"melonJS: me.loader.load : unknown or invalid resource type : "+a.type}return 0},f.unload=function(a){switch(a.name=a.name.toLowerCase(),a.type){case"binary":return a.name in i?(delete i[a.name],!0):!1;case"image":return a.name in g?("function"==typeof g[a.name].dispose&&g[a.name].dispose(),delete g[a.name],!0):!1;case"json":return a.name in k?(delete k[a.name],!0):!1;case"tmx":case"tsx":return a.name in h?(delete h[a.name],!0):!1;case"audio":return me.audio.unload(a.name);default:throw"melonJS: me.loader.unload : unknown or invalid resource type : "+a.type}},f.unloadAll=function(){var a;for(a in i)f.unload(a);for(a in g)f.unload(a);for(a in h)f.unload(a);for(a in j)f.unload(a);for(a in k)f.unload(a);me.audio.unloadAll()},f.getTMXFormat=function(a){return a=a.toLowerCase(),a in h?h[a].format:null},f.getTMX=function(a){return a=a.toLowerCase(),a in h?h[a].data:null},f.getBinary=function(a){return a=a.toLowerCase(),a in i?i[a]:null},f.getImage=function(a){return a=a.toLowerCase(),a in g?g[a]:null},f.getJSON=function(a){return a=a.toLowerCase(),a in k?k[a]:null},f.getLoadProgress=function(){return m/l},f}()}(window),function(){me.Font=me.Renderable.extend({font:null,fontSize:null,fillStyle:"#000000",strokeStyle:"#000000",lineWidth:1,textAlign:"left",textBaseline:"top",lineHeight:1,init:function(a,b,c,d){this.pos=new me.Vector2d,this.fontSize=new me.Vector2d,this.set(a,b,c,d),this.parent(this.pos,0,this.fontSize.y)},bold:function(){this.font="bold "+this.font},italic:function(){this.font="italic "+this.font},set:function(a,b,c,d){var e=a.split(",").map(function(a){return a=a.trim(),/(^".*"$)|(^'.*'$)/.test(a)?a:'"'+a+'"'});this.fontSize.y=parseInt(b,10),this.height=this.fontSize.y,"number"==typeof b&&(b+="px"),this.font=b+" "+e.join(","),this.fillStyle=c,d&&(this.textAlign=d)},measureText:function(a,b){a.font=this.font,a.fillStyle=this.fillStyle,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline,this.height=this.width=0;for(var c=(""+b).split("\n"),d=0;d<c.length;d++)this.width=Math.max(a.measureText(c[d].trimRight()).width,this.width),this.height+=this.fontSize.y*this.lineHeight;return{width:this.width,height:this.height}},draw:function(a,b,c,d){this.pos.set(c,d),a.font=this.font,a.fillStyle=this.fillStyle,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline;for(var e=(""+b).split("\n"),f=0;f<e.length;f++)a.fillText(e[f].trimRight(),~~c,~~d),d+=this.fontSize.y*this.lineHeight},drawStroke:function(a,b,c,d){this.pos.set(c,d),a.save(),a.font=this.font,a.fillStyle=this.fillStyle,a.strokeStyle=this.strokeStyle,a.lineWidth=this.lineWidth,a.textAlign=this.textAlign,a.textBaseline=this.textBaseline;for(var e=(""+b).split("\n"),f=0;f<e.length;f++){var g=e[f].trimRight();a.strokeText(g,~~c,~~d),a.fillText(g,~~c,~~d),d+=this.fontSize.y*this.lineHeight}a.restore()}}),me.BitmapFont=me.Font.extend({sSize:null,firstChar:32,charCount:0,init:function(a,b,c,d){this.parent(a,null,null),this.sSize=new me.Vector2d,this.firstChar=d||32,this.loadFontMetrics(a,b),this.textAlign="left",this.textBaseline="top",c&&this.resize(c)},loadFontMetrics:function(a,b){this.font=me.loader.getImage(a),this.fontSize.x=b.x||b,this.fontSize.y=b.y||this.font.height,this.sSize.copy(this.fontSize),this.height=this.sSize.y,this.charCount=~~(this.font.width/this.fontSize.x)},set:function(a,b){this.textAlign=a,b&&this.resize(b)},resize:function(a){this.sSize.setV(this.fontSize),this.sSize.x*=a,this.sSize.y*=a,this.height=this.sSize.y},measureText:function(a,b){var c=(""+b).split("\n");this.height=this.width=0;for(var d=0;d<c.length;d++)this.width=Math.max(c[d].trimRight().length*this.sSize.x,this.width),this.height+=this.sSize.y*this.lineHeight;return{width:this.width,height:this.height}},draw:function(a,b,c,d){var e=(""+b).split("\n"),f=c,g=this.sSize.y*this.lineHeight;this.pos.set(c,d);for(var h=0;h<e.length;h++){c=f;var i=e[h].trimRight(),j=i.length*this.sSize.x;switch(this.textAlign){case"right":c-=j;break;case"center":c-=.5*j}switch(this.textBaseline){case"middle":d-=.5*g;break;case"ideographic":case"alphabetic":case"bottom":d-=g}for(var k=0,l=i.length;l>k;k++){var m=i.charCodeAt(k)-this.firstChar;m>=0&&a.drawImage(this.font,this.fontSize.x*(m%this.charCount),this.fontSize.y*~~(m/this.charCount),this.fontSize.x,this.fontSize.y,~~c,~~d,this.sSize.x,this.sSize.y),c+=this.sSize.x}d+=g}}})}(window),function(){me.audio=function(){function a(a){var b="",c=a.length;if(me.device.sound)for(var d="",e=0;c>e&&(d=a[e].toLowerCase().trim(),!g.capabilities[d]||!g.capabilities[d].canPlay||""!==b&&"probably"!==g.capabilities[d].canPlayType||(b=d,"probably"!==g.capabilities[d].canPlayType));e++);return""===b&&(l=!1),b}function b(a){for(var b,c=h[a],d=0;b=c[d++];)if(b.ended||!b.currentTime)return b.currentTime=m,b;return c[0].pause(),c[0].currentTime=m,c[0]}function c(a,b){if(n++>3){var c="melonJS: failed loading "+a+"."+i;if(me.sys.stopOnAudioError!==!1)throw c;me.audio.disable(),b&&b(),console.log(c+", disabling audio")}else h[a][0].load()}function d(a,b,c){if(n=0,b>1)for(var d=h[a][0],e=1;b>e;e++)h[a][e]=new Audio(d.src),h[a][e].preload="auto",h[a][e].load();c&&c()}function e(a,c,d,e){var f=b(a.toLowerCase());return f.loop=c||!1,f.volume=e?parseFloat(e).clamp(0,1):o.volume,f.muted=o.muted,f.play(),d&&!c&&f.addEventListener("ended",function g(){f.removeEventListener("ended",g,!1),d()},!1),f}function f(a,b,c){return c&&!b&&setTimeout(c,2e3),null}var g={},h={},i=-1,j=null,k=null,l=!0,m=0,n=0,o={volume:1,muted:!1},p=!1,q=[];return g.capabilities={mp3:{codec:"audio/mpeg",canPlay:!1,canPlayType:"no"},ogg:{codec:'audio/ogg; codecs="vorbis"',canPlay:!1,canPlayType:"no"},m4a:{codec:'audio/mp4; codecs="mp4a.40.2"',canPlay:!1,canPlayType:"no"},wav:{codec:'audio/wav; codecs="1"',canPlay:!1,canPlayType:"no"}},g.detectCapabilities=function(){var a=document.createElement("audio");if(a.canPlayType)for(var b in g.capabilities){var c=a.canPlayType(g.capabilities[b].codec);""!==c&&"no"!==c&&(g.capabilities[b].canPlay=!0,g.capabilities[b].canPlayType=c),me.device.sound|=g.capabilities[b].canPlay}},g.init=function(b){if(!me.initialized)throw"melonJS: me.audio.init() called before engine initialization.";return b="string"==typeof b?b:"mp3",b=b.split(","),i=a(b),me.device.isMobile&&!navigator.isCocoonJS&&(l=!1),g.play=g.isAudioEnable()?e:f,g.isAudioEnable()},g.isAudioEnable=function(){return l},g.enable=function(){l=me.device.sound,g.play=l?e:f},g.disable=function(){me.audio.stopTrack(),g.play=f,l=!1},g.load=function(a,b,e){if(-1===i)return 0;if(me.device.isMobile&&!navigator.isCocoonJS){if(p)return q.push([a,b,e]),void 0;p=!0}var f=a.channel||1,j="canplaythrough";a.stream!==!0||me.device.isMobile||(f=1,j="canplay");var k=new Audio(a.src+a.name+"."+i+me.loader.nocache);return k.preload="auto",k.addEventListener(j,function l(){k.removeEventListener(j,l,!1),p=!1,d.call(me.audio,a.name,f,b);var c=q.shift();c&&g.load.apply(g,c)},!1),k.addEventListener("error",function(){c.call(me.audio,a.name,e)},!1),k.load(),h[a.name]=[k],1},g.stop=function(a){if(l)for(var b=h[a.toLowerCase()],c=b.length;c--;)b[c].pause(),b[c].currentTime=m},g.pause=function(a){if(l)for(var b=h[a.toLowerCase()],c=b.length;c--;)b[c].pause()},g.playTrack=function(a,b){k=me.audio.play(a,!0,null,b),j=a.toLowerCase()},g.stopTrack=function(){l&&k&&(k.pause(),j=null,k=null)},g.setVolume=function(a){"number"==typeof a&&(o.volume=a.clamp(0,1))},g.getVolume=function(){return o.volume},g.mute=function(a,b){b=void 0===b?!0:!!b;for(var c,d=h[a.toLowerCase()],e=0;c=d[e++];)c.muted=b},g.unmute=function(a){g.mute(a,!1)},g.muteAll=function(){o.muted=!0;for(var a in h)g.mute(a,o.muted)},g.unmuteAll=function(){o.muted=!1;for(var a in h)g.mute(a,o.muted)},g.getCurrentTrack=function(){return j},g.pauseTrack=function(){l&&k&&k.pause()},g.resumeTrack=function(){l&&k&&k.play()},g.unload=function(a){return a=a.toLowerCase(),a in h?(j===a?g.stopTrack():g.stop(a),delete h[a],!0):!1},g.unloadAll=function(){for(var a in h)g.unload(a)},g}()}(window),function(){me.video=function(){function a(){return navigator.isCocoonJS?"screencanvas":"canvas"}var b={},c=null,d=null,e=null,f=null,g=null,h=-1,i=!1,j=0,k=0,l=!1,m=!0,n=1/0,o=1/0;return b.init=function(a,h,n,o,p,q){if(!me.initialized)throw"melonJS: me.video.init() called before engine initialization.";if(i=o||!1,l="auto"===p||!1,m=void 0!==q?q:!0,p="auto"!==p?parseFloat(p||1):1,me.sys.scale=new me.Vector2d(p,p),(l||1!==p)&&(i=!0),j=h*me.sys.scale.x,k=n*me.sys.scale.y,window.addEventListener("resize",throttle(100,!1,function(a){me.event.publish(me.event.WINDOW_ONRESIZE,[a])}),!1),window.addEventListener("orientationchange",function(a){me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE,[a])},!1),me.event.subscribe(me.event.WINDOW_ONRESIZE,me.video.onresize.bind(me.video)),me.event.subscribe(me.event.WINDOW_ONORIENTATION_CHANGE,me.video.onresize.bind(me.video)),c=b.createCanvas(j,k,!0),a&&(g=document.getElementById(a)),g||(g=document.body),g.appendChild(c),!c.getContext)return!1;if(d=b.getContext2d(c),me.device.getPixelRatio()>1&&(c.style.width=c.width/me.device.getPixelRatio()+"px",c.style.height=c.height/me.device.getPixelRatio()+"px"),i?(e=b.createCanvas(h,n,!1),f=b.getContext2d(e)):(e=c,f=d),window.getComputedStyle){var r=window.getComputedStyle(c,null);me.video.setMaxSize(parseInt(r.maxWidth,10),parseInt(r.maxHeight,10))}return me.video.onresize(null),me.game.init(),!0},b.getWrapper=function(){return g},b.getWidth=function(){return e.width},b.getPos=function(a){return a=a||c,a.getBoundingClientRect?a.getBoundingClientRect():{left:0,top:0}},b.getHeight=function(){return e.height},b.setMaxSize=function(a,b){n=a||1/0,o=b||1/0},b.createCanvas=function(b,c,d){if(0===b||0===c)throw new Error("melonJS: width or height was zero, Canvas could not be initialized !");var f=d===!0?a():"canvas",g=document.createElement(f);return g.width=b||e.width,g.height=c||e.height,g},b.getContext2d=function(a){var b;return b=navigator.isCocoonJS?a.getContext("2d",{antialias:me.sys.scalingInterpolation}):a.getContext("2d"),b.canvas||(b.canvas=a),me.video.setImageSmoothing(b,me.sys.scalingInterpolation),b},b.getScreenCanvas=function(){return c},b.getScreenContext=function(){return d},b.getSystemCanvas=function(){return e},b.getSystemContext=function(){return f},b.onresize=function(){var a=1,b=1;if(me.device.orientation="undefined"!=typeof window.orientation?window.orientation:window.outerWidth>window.outerHeight?90:0,l){var c=me.video.getScreenCanvas().parentNode,d=Math.min(n,c.width||window.innerWidth),e=Math.min(o,c.height||window.innerHeight);if(m){var f=me.video.getWidth()/me.video.getHeight(),g=d/e;a=b=f>g?d/me.video.getWidth():e/me.video.getHeight()}else a=d/me.video.getWidth(),b=e/me.video.getHeight();if(a*=me.device.getPixelRatio(),b*=me.device.getPixelRatio(),1!==a||1!==b)return h>=0&&clearTimeout(h),h=me.video.updateDisplaySize.defer(a,b),void 0}me.input.offset=me.video.getPos()},b.updateDisplaySize=function(a,f){me.sys.scale.set(a,f),c.width=j=e.width*a,c.height=k=e.height*f,me.device.getPixelRatio()>1&&(c.style.width=c.width/me.device.getPixelRatio()+"px",c.style.height=c.height/me.device.getPixelRatio()+"px"),me.video.setImageSmoothing(d,me.sys.scalingInterpolation),me.input.offset=me.video.getPos(),b.blitSurface(),h=-1},b.clearSurface=function(a,b){var c=a.canvas.width,d=a.canvas.height;a.save(),a.setTransform(1,0,0,1,0,0),"rgba"===b.substr(0,4)&&a.clearRect(0,0,c,d),a.fillStyle=b,a.fillRect(0,0,c,d),a.restore()},b.setImageSmoothing=function(a,b){for(var c=["ms","moz","webkit","o"],d=0;d<c.length;++d)void 0!==a[c[d]+"ImageSmoothingEnabled"]&&(a[c[d]+"ImageSmoothingEnabled"]=b===!0);a.imageSmoothingEnabled=b===!0},b.setAlpha=function(a,b){a.globalCompositeOperation=b?"source-over":"copy"},b.blitSurface=function(){b.blitSurface=i?function(){d.drawImage(e,0,0,e.width,e.height,0,0,j,k)}:function(){},b.blitSurface()},b.applyRGBFilter=function(a,c,d){var e,f,g=b.getContext2d(b.createCanvas(a.width,a.height,!1)),h=me.utils.getPixels(a),i=h.data;switch(c){case"b&w":for(e=0,f=i.length;f>e;e+=4){var j=3*i[e]+4*i[e+1]+i[e+2]>>>3;i[e]=j,i[e+1]=j,i[e+2]=j}break;case"brightness":var k=Math.abs(d).clamp(0,1);for(e=0,f=i.length;f>e;e+=4)i[e]*=k,i[e+1]*=k,i[e+2]*=k;break;case"transparent":for(e=0,f=i.length;f>e;e+=4)me.utils.RGBToHex(i[e],i[e+1],i[e+2])===d&&(i[e+3]=0); | |
break;default:return null}return g.putImageData(h,0,0),g},b}()}(window),function(a){me.input=function(){function b(){t||(a.addEventListener("keydown",f,!1),a.addEventListener("keyup",g,!1),t=!0)}function c(a,b){for(var c=2;c<a.length;++c)void 0!==a[c]&&me.video.getScreenCanvas().addEventListener(a[c],b,!1)}function d(){u||(m.changedTouches.push({x:0,y:0}),m.mouse.pos=new me.Vector2d(0,0),m.offset=me.video.getPos(),a.addEventListener("scroll",throttle(100,!1,function(a){m.offset=me.video.getPos(),me.event.publish(me.event.WINDOW_ONSCROLL,[a])}),!1),x=a.navigator.pointerEnabled?A:a.navigator.msPointerEnabled?B:me.device.touch?z:y,c(x,l),v="onwheel"in document.createElement("div")?"wheel":"mousewheel",a.addEventListener(v,j,!1),void 0===m.throttlingInterval&&(m.throttlingInterval=Math.floor(1e3/me.sys.fps)),m.throttlingInterval<17?me.video.getScreenCanvas().addEventListener(x[C],k,!1):me.video.getScreenCanvas().addEventListener(x[C],throttle(m.throttlingInterval,!1,function(a){k(a)}),!1),u=!0)}function e(a){return a.stopPropagation?a.stopPropagation():a.cancelBubble=!0,a.preventDefault?a.preventDefault():a.returnValue=!1,!1}function f(a,b,c){b=b||a.keyCode||a.which;var d=n[b];if(me.event.publish(me.event.KEYDOWN,[d,b,d?!q[d]:!0]),d){if(!q[d]){var f=c?c:b;r[d][f]||(o[d]++,r[d][f]=!0)}return e(a)}return!0}function g(a,b,c){b=b||a.keyCode||a.which;var d=n[b];if(me.event.publish(me.event.KEYUP,[d,b]),d){var f=c?c:b;return r[d][f]=void 0,o[d]>0&&o[d]--,q[d]=!1,e(a)}return!0}function h(a){var b=!1,c=s[a.type];if(c||(c=x.indexOf(a.type)===F?s[x[E]]:s[a.type]),c)for(var d=me.game.viewport.localToWorld(0,0),e=0,f=m.changedTouches.length;f>e;e++){if("undefined"!=typeof a.timeStamp){if(a.timeStamp<w)continue;w=a.timeStamp}me.device.pointerEnabled||(a.pointerId=m.changedTouches[e].id),a.gameScreenX=m.changedTouches[e].x,a.gameScreenY=m.changedTouches[e].y,a.gameWorldX=a.gameScreenX+d.x,a.gameWorldY=a.gameScreenY+d.y;for(var g,h=c.length;h--,g=c[h];)if(g.floating===!0?(a.gameX=a.gameScreenX,a.gameY=a.gameScreenY):(a.gameX=a.gameWorldX,a.gameY=a.gameWorldY),(null===g.rect||g.rect.containsPoint(a.gameX,a.gameY))&&g.cb(a)===!1){b=!0;break}}return b}function i(a){var b;if(m.changedTouches.length=0,a.touches)for(var c=0,d=a.changedTouches.length;d>c;c++){var e=a.changedTouches[c];b=m.globalToLocal(e.clientX,e.clientY),b.id=e.identifier,m.changedTouches.push(b)}else b=m.globalToLocal(a.clientX,a.clientY),b.id=a.pointerId||1,m.changedTouches.push(b);a.isPrimary!==!1&&m.mouse.pos.set(m.changedTouches[0].x,m.changedTouches[0].y)}function j(a){if(a.target===me.video.getScreenCanvas()){var b={deltaMode:1,type:"mousewheel",deltaX:a.deltaX,deltaY:a.deltaY,deltaZ:a.deltaZ};if("mousewheel"===v&&(b.deltaY=-1/40*a.wheelDelta,a.wheelDeltaX&&(b.deltaX=-1/40*a.wheelDeltaX)),h(b))return e(a)}return!0}function k(a){return i(a),h(a)?e(a):!0}function l(a){if(i(a),h(a))return e(a);var b=a.button||0,c=m.mouse.bind[b];return c?a.type===x[D]?f(a,c,b+1):g(a,c,b+1):!0}var m={},n={},o={},p={},q={},r={},s={},t=!1,u=!1,v="mousewheel",w=0,x=null,y=["mousewheel","mousemove","mousedown","mouseup",void 0,"click","dblclick"],z=[void 0,"touchmove","touchstart","touchend","touchcancel","tap","dbltap"],A=["mousewheel","pointermove","pointerdown","pointerup","pointercancel",void 0,void 0],B=["mousewheel","MSPointerMove","MSPointerDown","MSPointerUp","MSPointerCancel",void 0,void 0],C=1,D=2,E=3,F=4;return m.mouse={pos:null,LEFT:0,MIDDLE:1,RIGHT:2,bind:[0,0,0]},m.offset=null,m.throttlingInterval=void 0,m.changedTouches=[],m.KEY={LEFT:37,UP:38,RIGHT:39,DOWN:40,ENTER:13,TAB:9,SHIFT:16,CTRL:17,ALT:18,PAUSE:19,ESC:27,SPACE:32,NUM0:48,NUM1:49,NUM2:50,NUM3:51,NUM4:52,NUM5:53,NUM6:54,NUM7:55,NUM8:56,NUM9:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90},m.isKeyPressed=function(a){return o[a]&&!q[a]?(p[a]&&(q[a]=!0),!0):!1},m.keyStatus=function(a){return o[a]>0},m.triggerKeyEvent=function(a,b){b?f({},a):g({},a)},m.bindKey=function(a,c,d){b(),n[a]=c,o[c]=0,p[c]=d?d:!1,q[c]=!1,r[c]={}},m.unlockKey=function(a){q[a]=!1},m.unbindKey=function(a){o[n[a]]=0,p[n[a]]=!1,r[n[a]]={},n[a]=null},m.globalToLocal=function(a,b){var c=m.offset,d=me.device.getPixelRatio();a-=c.left,b-=c.top;var e=me.sys.scale;return(1!==e.x||1!==e.y)&&(a/=e.x,b/=e.y),new me.Vector2d(a*d,b*d)},m.bindMouse=function(a,b){if(d(),!n[b])throw"melonJS : no action defined for keycode "+b;m.mouse.bind[a]=b},m.unbindMouse=function(a){m.mouse.bind[a]=null},m.bindTouch=function(a){m.bindMouse(me.input.mouse.LEFT,a)},m.unbindTouch=function(){m.unbindMouse(me.input.mouse.LEFT)},m.registerPointerEvent=function(a,b,c,e){if(d(),-1!==y.indexOf(a)&&(me.device.touch||me.device.pointerEnabled)&&(a=x[y.indexOf(a)]),a&&-1!==x.indexOf(a)){s[a]||(s[a]=[]);var f=b.floating===!0?!0:!1;return e&&(f=e===!0?!0:!1),s[a].push({rect:b||null,cb:c,floating:f}),void 0}throw"melonJS : invalid event type : "+a},m.releasePointerEvent=function(a,b){-1!==y.indexOf(a)&&(me.device.touch||me.device.pointerEnabled)&&(a=x[y.indexOf(a)]);{if(!a||-1===x.indexOf(a))throw"melonJS : invalid event type : "+a;s[a]||(s[a]=[]);var c=s[a];if(c)for(var d,e=c.length;e--,d=c[e];)d.rect===b&&(d.rect=d.cb=d.floating=null,s[a].splice(e,1))}},m}()}(window),function(a){var b=function(){var b={},c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";return b.decode=function(b){if(b=b.replace(/[^A-Za-z0-9\+\/\=]/g,""),me.device.nativeBase64)return a.atob(b);for(var d,e,f,g,h,i,j,k=[],l=0;l<b.length;)g=c.indexOf(b.charAt(l++)),h=c.indexOf(b.charAt(l++)),i=c.indexOf(b.charAt(l++)),j=c.indexOf(b.charAt(l++)),d=g<<2|h>>4,e=(15&h)<<4|i>>2,f=(3&i)<<6|j,k.push(String.fromCharCode(d)),64!==i&&k.push(String.fromCharCode(e)),64!==j&&k.push(String.fromCharCode(f));return k=k.join("")},b}();me.utils=function(){var a={},c={},d="",e=0,f=/^.*(\\|\/|\:)/,g=/\.[^\.]*$/;return a.decodeBase64=function(a){return b.decode(a)},a.decodeBase64AsArray=function(a,c){c=c||1;var d,e,f,g,h=b.decode(a);for(g="function"==typeof window.Uint32Array?new Uint32Array(h.length/c):[],d=0,f=h.length/c;f>d;d++)for(g[d]=0,e=c-1;e>=0;--e)g[d]+=h.charCodeAt(d*c+e)<<(e<<3);return g},a.decompress=function(){throw"melonJS: GZIP/ZLIB compressed TMX Tile Map not supported!"},a.decodeCSV=function(a,b){a=a.trim().split("\n");for(var c=[],d=0;d<a.length;d++)for(var e=a[d].split(",",b),f=0;f<e.length;f++)c.push(+e[f]);return c},a.getBasename=function(a){return a.replace(f,"").replace(g,"")},a.getFileExtension=function(a){return a.substring(a.lastIndexOf(".")+1,a.length)},a.HexToRGB=function(a,b){if("#"!==a.charAt(0))return a;if(a=a.substring(1,a.length),null==c[a]){var d,e,f;a.length<6?(d=a.charAt(0)+a.charAt(0),e=a.charAt(1)+a.charAt(1),f=a.charAt(2)+a.charAt(2)):(d=a.substring(0,2),e=a.substring(2,4),f=a.substring(4,6)),c[a]=parseInt(d,16)+","+parseInt(e,16)+","+parseInt(f,16)}return(b?"rgba(":"rgb(")+c[a]+(b?","+b+")":")")},a.RGBToHex=function(a,b,c){return a.toHex()+b.toHex()+c.toHex()},a.getPixels=function(a){if(a instanceof HTMLImageElement){var b=me.video.getContext2d(me.video.createCanvas(a.width,a.height));return b.drawImage(a,0,0),b.getImageData(0,0,a.width,a.height)}return a.getContext("2d").getImageData(0,0,a.width,a.height)},a.resetGUID=function(a){d=a.toString().toUpperCase().toHex(),e=0},a.createGUID=function(){return d+"-"+e++},a.applyFriction=function(a,b){return 0>a+b?a+b*me.timer.tick:a-b>0?a-b*me.timer.tick:0},a}()}(window),function(){me.save=function(){function a(a){return"add"===a||"delete"===a}var b={},c={_init:function(){if(me.device.localStorage===!0){var a=JSON.parse(localStorage.getItem("me.save"))||[];a.forEach(function(a){b[a]=JSON.parse(localStorage.getItem("me.save."+a))})}},add:function(d){Object.keys(d).forEach(function(e){a(e)||(!function(a){Object.defineProperty(c,a,{configurable:!0,enumerable:!0,get:function(){return b[a]},set:function(c){b[a]=c,me.device.localStorage===!0&&localStorage.setItem("me.save."+a,JSON.stringify(c))}})}(e),e in b||(c[e]=d[e]))}),me.device.localStorage===!0&&localStorage.setItem("me.save",JSON.stringify(Object.keys(b)))},"delete":function(c){a(c)||"undefined"!=typeof b[c]&&(delete b[c],me.device.localStorage===!0&&(localStorage.removeItem("me.save."+c),localStorage.setItem("me.save",JSON.stringify(Object.keys(b)))))}};return c}()}(window),function(){me.COLLISION_LAYER="collision",me.TMX_TAG_MAP="map",me.TMX_TAG_NAME="name",me.TMX_TAG_VALUE="value",me.TMX_TAG_VERSION="version",me.TMX_TAG_ORIENTATION="orientation",me.TMX_TAG_WIDTH="width",me.TMX_TAG_HEIGHT="height",me.TMX_TAG_OPACITY="opacity",me.TMX_TAG_TRANS="trans",me.TMX_TAG_TILEWIDTH="tilewidth",me.TMX_TAG_TILEHEIGHT="tileheight",me.TMX_TAG_TILEOFFSET="tileoffset",me.TMX_TAG_FIRSTGID="firstgid",me.TMX_TAG_GID="gid",me.TMX_TAG_TILE="tile",me.TMX_TAG_ID="id",me.TMX_TAG_DATA="data",me.TMX_TAG_COMPRESSION="compression",me.TMX_TAG_GZIP="gzip",me.TMX_TAG_ZLIB="zlib",me.TMX_TAG_ENCODING="encoding",me.TMX_TAG_ATTR_BASE64="base64",me.TMX_TAG_CSV="csv",me.TMX_TAG_SPACING="spacing",me.TMX_TAG_MARGIN="margin",me.TMX_TAG_PROPERTIES="properties",me.TMX_TAG_PROPERTY="property",me.TMX_TAG_IMAGE="image",me.TMX_TAG_SOURCE="source",me.TMX_TAG_VISIBLE="visible",me.TMX_TAG_TILESET="tileset",me.TMX_TAG_LAYER="layer",me.TMX_TAG_TILE_LAYER="tilelayer",me.TMX_TAG_IMAGE_LAYER="imagelayer",me.TMX_TAG_OBJECTGROUP="objectgroup",me.TMX_TAG_OBJECT="object",me.TMX_TAG_X="x",me.TMX_TAG_Y="y",me.TMX_TAG_WIDTH="width",me.TMX_TAG_HEIGHT="height",me.TMX_TAG_POLYGON="polygon",me.TMX_TAG_POLYLINE="polyline",me.TMX_TAG_ELLIPSE="ellipse",me.TMX_TAG_POINTS="points",me.TMX_BACKGROUND_COLOR="backgroundcolor"}(window),function(){me.TMXUtils=function(){function a(a){if(!a||a.isBoolean())a=a?"true"===a:!0;else if(a.isNumeric())a=Number(a);else if(a.match(/^json:/i)){var b=a.split(/^json:/i)[1];try{a=JSON.parse(b)}catch(c){throw"Unable to parse JSON: "+b}}return a}var b={};return b.applyTMXPropertiesFromXML=function(b,c){var d=c.getElementsByTagName(me.TMX_TAG_PROPERTIES)[0];if(d)for(var e=d.getElementsByTagName(me.TMX_TAG_PROPERTY),f=0;f<e.length;f++){var g=me.mapReader.TMXParser.getStringAttribute(e[f],me.TMX_TAG_NAME),h=me.mapReader.TMXParser.getStringAttribute(e[f],me.TMX_TAG_VALUE);b[g]=a(h)}},b.applyTMXPropertiesFromJSON=function(b,c){var d=c[me.TMX_TAG_PROPERTIES];if(d)for(var e in d)d.hasOwnProperty(e)&&(b[e]=a(d[e]))},b.mergeProperties=function(a,b,c){for(var d in b)(c||void 0===a[d])&&(a[d]=b[d]);return a},b}()}(window),function(){me.TMXObjectGroup=Object.extend({name:null,width:0,height:0,visible:!1,z:0,objects:[],initFromXML:function(a,b,c,d){this.name=a,this.width=me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_WIDTH),this.height=me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_HEIGHT),this.visible=1===me.mapReader.TMXParser.getIntAttribute(b,me.TMX_TAG_VISIBLE,1),this.opacity=me.mapReader.TMXParser.getFloatAttribute(b,me.TMX_TAG_OPACITY,1).clamp(0,1),this.z=d,this.objects=[],b.firstChild&&b.firstChild.nextSibling.nodeName===me.TMX_TAG_PROPERTIES&&me.TMXUtils.applyTMXPropertiesFromXML(this,b);for(var e=b.getElementsByTagName(me.TMX_TAG_OBJECT),f=0;f<e.length;f++){var g=new me.TMXObject;g.initFromXML(e[f],c,d),this.objects.push(g)}},initFromJSON:function(a,b,c,d){var e=this;this.name=a,this.width=b[me.TMX_TAG_WIDTH],this.height=b[me.TMX_TAG_HEIGHT],this.visible=b[me.TMX_TAG_VISIBLE],this.opacity=parseFloat(b[me.TMX_TAG_OPACITY]||1).clamp(0,1),this.z=d,this.objects=[],me.TMXUtils.applyTMXPropertiesFromJSON(this,b),b.objects.forEach(function(a){var b=new me.TMXObject;b.initFromJSON(a,c,d),e.objects.push(b)})},reset:function(){this.objects=null},getObjectCount:function(){return this.objects.length},getObjectByIndex:function(a){return this.objects[a]}}),me.TMXObject=Object.extend({name:null,x:0,y:0,width:0,height:0,z:0,gid:void 0,isPolygon:!1,isPolyline:!1,points:void 0,initFromXML:function(a,b,c){if(this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.x=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_X),this.y=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_Y),this.z=c,this.width=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_WIDTH,0),this.height=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_HEIGHT,0),this.gid=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_GID,null),this.isEllipse=!1,this.isPolygon=!1,this.isPolyline=!1,this.gid)this.setImage(this.gid,b);else if(a.getElementsByTagName(me.TMX_TAG_ELLIPSE).length)this.isEllipse=!0;else{var d=a.getElementsByTagName(me.TMX_TAG_POLYGON);if(d.length?this.isPolygon=!0:(d=a.getElementsByTagName(me.TMX_TAG_POLYLINE),d.length&&(this.isPolyline=!0)),d.length){this.points=[];for(var e,f=me.mapReader.TMXParser.getStringAttribute(d[0],me.TMX_TAG_POINTS).split(" "),g=0;g<f.length;g++)e=f[g].split(","),this.points.push(new me.Vector2d(+e[0],+e[1]))}}me.game.renderer.adjustPosition(this),me.TMXUtils.applyTMXPropertiesFromXML(this,a)},initFromJSON:function(a,b,c){if(this.name=a[me.TMX_TAG_NAME],this.x=parseInt(a[me.TMX_TAG_X],10),this.y=parseInt(a[me.TMX_TAG_Y],10),this.z=parseInt(c,10),this.width=parseInt(a[me.TMX_TAG_WIDTH]||0,10),this.height=parseInt(a[me.TMX_TAG_HEIGHT]||0,10),this.gid=parseInt(a[me.TMX_TAG_GID],10)||null,this.isEllipse=!1,this.isPolygon=!1,this.isPolyline=!1,this.gid)this.setImage(this.gid,b);else if(void 0!==a[me.TMX_TAG_ELLIPSE])this.isEllipse=!0;else{var d=a[me.TMX_TAG_POLYGON];if(void 0!==d?this.isPolygon=!0:(d=a[me.TMX_TAG_POLYLINE],void 0!==d&&(this.isPolyline=!0)),void 0!==d){this.points=[];var e=this;d.forEach(function(a){e.points.push(new me.Vector2d(parseInt(a.x,10),parseInt(a.y,10)))})}}me.game.renderer.adjustPosition(this),me.TMXUtils.applyTMXPropertiesFromJSON(this,a)},setImage:function(a,b){var c=b.getTilesetByGid(this.gid);this.width=c.tilewidth,this.height=c.tileheight,this.spritewidth=this.width;var d=new me.Tile(this.x,this.y,c.tilewidth,c.tileheight,this.gid);this.image=c.getTileImage(d)},getObjectPropertyByName:function(a){return this[a]}})}(window),function(){var a=2147483648,b=1073741824,c=536870912;me.Tile=me.Rect.extend({tileId:null,tileset:null,init:function(d,e,f,g,h){this.parent(new me.Vector2d(d*f,e*g),f,g),this.col=d,this.row=e,this.tileId=h,this.flipX=0!==(this.tileId&a),this.flipY=0!==(this.tileId&b),this.flipAD=0!==(this.tileId&c),this.flipped=this.flipX||this.flipY||this.flipAD,this.tileId&=~(a|b|c)}}),me.TMXTileset=Object.extend({type:{SOLID:"solid",PLATFORM:"platform",L_SLOPE:"lslope",R_SLOPE:"rslope",LADDER:"ladder",TOPLADDER:"topladder",BREAKABLE:"breakable"},init:function(){this.TileProperties=[],this.tileXOffset=[],this.tileYOffset=[]},initFromXML:function(a){this.firstgid=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_FIRSTGID);var b=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_SOURCE);if(b){if(b=me.utils.getBasename(b),a=me.loader.getTMX(b),!a)throw"melonJS:"+b+" TSX tileset not found";me.mapReader.TMXParser.parseFromString(a),a=me.mapReader.TMXParser.getFirstElementByTagName("tileset")}this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.tilewidth=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_TILEWIDTH),this.tileheight=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_TILEHEIGHT),this.spacing=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_SPACING,0),this.margin=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_MARGIN,0),this.tileoffset=new me.Vector2d(0,0);var c=a.getElementsByTagName(me.TMX_TAG_TILEOFFSET);c.length>0&&(this.tileoffset.x=me.mapReader.TMXParser.getIntAttribute(c[0],me.TMX_TAG_X),this.tileoffset.y=me.mapReader.TMXParser.getIntAttribute(c[0],me.TMX_TAG_Y));for(var d=a.getElementsByTagName(me.TMX_TAG_TILE),e=0;e<d.length;e++){var f=me.mapReader.TMXParser.getIntAttribute(d[e],me.TMX_TAG_ID)+this.firstgid,g={};me.TMXUtils.applyTMXPropertiesFromXML(g,d[e]),this.setTileProperty(f,g)}var h=a.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE),i=h?me.loader.getImage(me.utils.getBasename(h)):null;i||console.log("melonJS: '"+h+"' file for tileset '"+this.name+"' not found!");var j=a.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_TRANS);this.initFromImage(i,j)},initFromJSON:function(a){this.firstgid=a[me.TMX_TAG_FIRSTGID];var b=a[me.TMX_TAG_SOURCE];if(b&&(b=me.utils.getBasename(b),a=me.loader.getTMX(b),!a))throw"melonJS:"+b+" TSX tileset not found";this.name=a[me.TMX_TAG_NAME],this.tilewidth=parseInt(a[me.TMX_TAG_TILEWIDTH],10),this.tileheight=parseInt(a[me.TMX_TAG_TILEHEIGHT],10),this.spacing=parseInt(a[me.TMX_TAG_SPACING]||0,10),this.margin=parseInt(a[me.TMX_TAG_MARGIN]||0,10),this.tileoffset=new me.Vector2d(0,0);var c=a[me.TMX_TAG_TILEOFFSET];c&&(this.tileoffset.x=parseInt(c[me.TMX_TAG_X],10),this.tileoffset.y=parseInt(c[me.TMX_TAG_Y],10));var d=a.tileproperties;for(var e in d){var f={};me.TMXUtils.mergeProperties(f,d[e]),this.setTileProperty(parseInt(e,10)+this.firstgid,f)}var g=me.utils.getBasename(a[me.TMX_TAG_IMAGE]),h=g?me.loader.getImage(g):null;h||console.log("melonJS: '"+g+"' file for tileset '"+this.name+"' not found!");var i=a[me.TMX_TAG_TRANS]||null;this.initFromImage(h,i)},initFromImage:function(a,b){a&&(this.image=a,this.hTileCount=~~((this.image.width-this.margin)/(this.tilewidth+this.spacing)),this.vTileCount=~~((this.image.height-this.margin)/(this.tileheight+this.spacing))),this.lastgid=this.firstgid+(this.hTileCount*this.vTileCount-1||0),null!==b&&this.image&&(this.image=me.video.applyRGBFilter(this.image,"transparent",b.toUpperCase()).canvas)},setTileProperty:function(a,b){b.isSolid=b.type?b.type.toLowerCase()===this.type.SOLID:!1,b.isPlatform=b.type?b.type.toLowerCase()===this.type.PLATFORM:!1,b.isLeftSlope=b.type?b.type.toLowerCase()===this.type.L_SLOPE:!1,b.isRightSlope=b.type?b.type.toLowerCase()===this.type.R_SLOPE:!1,b.isBreakable=b.type?b.type.toLowerCase()===this.type.BREAKABLE:!1,b.isLadder=b.type?b.type.toLowerCase()===this.type.LADDER:!1,b.isTopLadder=b.type?b.type.toLowerCase()===this.type.TOPLADDER:!1,b.isSlope=b.isLeftSlope||b.isRightSlope,b.isCollidable=!!b.type,this.TileProperties[a]=b},contains:function(a){return a>=this.firstgid&&a<=this.lastgid},getTileImage:function(a){var b=me.video.getContext2d(me.video.createCanvas(this.tilewidth,this.tileheight));return this.drawTile(b,0,0,a),b.canvas},getTileProperties:function(a){return this.TileProperties[a]},isTileCollidable:function(a){return this.TileProperties[a].isCollidable},getTileOffsetX:function(a){var b=this.tileXOffset[a];return"undefined"==typeof b&&(b=this.tileXOffset[a]=this.margin+(this.spacing+this.tilewidth)*(a%this.hTileCount)),b},getTileOffsetY:function(a){var b=this.tileYOffset[a];return"undefined"==typeof b&&(b=this.tileYOffset[a]=this.margin+(this.spacing+this.tileheight)*~~(a/this.hTileCount)),b},drawTile:function(a,b,c,d){if(d.flipped){var e=1,f=0,g=0,h=1,i=b,j=c;b=c=0,a.save(),d.flipAD&&(e=0,f=1,g=1,h=0,j+=this.tileheight-this.tilewidth),d.flipX&&(e=-e,g=-g,i+=d.flipAD?this.tileheight:this.tilewidth),d.flipY&&(f=-f,h=-h,j+=d.flipAD?this.tilewidth:this.tileheight),a.transform(e,f,g,h,i,j)}var k=d.tileId-this.firstgid;a.drawImage(this.image,this.getTileOffsetX(k),this.getTileOffsetY(k),this.tilewidth,this.tileheight,b,c,this.tilewidth,this.tileheight),d.flipped&&a.restore()}}),me.TMXTilesetGroup=Object.extend({init:function(){this.tilesets=[]},add:function(a){this.tilesets.push(a)},getTilesetByIndex:function(a){return this.tilesets[a]},getTilesetByGid:function(a){for(var b=-1,c=0,d=this.tilesets.length;d>c;c++){if(this.tilesets[c].contains(a))return this.tilesets[c];this.tilesets[c].firstgid===this.tilesets[c].lastgid&&a>=this.tilesets[c].firstgid&&(b=c)}if(-1!==b)return this.tilesets[b];throw"no matching tileset found for gid "+a}})}(window),function(){me.TMXOrthogonalRenderer=Object.extend({init:function(a,b,c,d){this.cols=a,this.rows=b,this.tilewidth=c,this.tileheight=d},canRender:function(a){return"orthogonal"===a.orientation&&this.cols===a.cols&&this.rows===a.rows&&this.tilewidth===a.tilewidth&&this.tileheight===a.tileheight},pixelToTileCoords:function(a,b){return new me.Vector2d(a/this.tilewidth,b/this.tileheight)},tileToPixelCoords:function(a,b){return new me.Vector2d(a*this.tilewidth,b*this.tileheight)},adjustPosition:function(a){"number"==typeof a.gid&&(a.y-=a.height)},drawTile:function(a,b,c,d,e){e.drawTile(a,e.tileoffset.x+b*this.tilewidth,e.tileoffset.y+(c+1)*this.tileheight-e.tileheight,d)},drawTileLayer:function(a,b,c){var d=this.pixelToTileCoords(c.pos.x,c.pos.y).floorSelf(),e=this.pixelToTileCoords(c.pos.x+c.width+this.tilewidth,c.pos.y+c.height+this.tileheight).ceilSelf();e.x=e.x>this.cols?this.cols:e.x,e.y=e.y>this.rows?this.rows:e.y;for(var f=d.y;f<e.y;f++)for(var g=d.x;g<e.x;g++){var h=b.layerData[g][f];h&&this.drawTile(a,g,f,h,h.tileset)}}}),me.TMXIsometricRenderer=Object.extend({init:function(a,b,c,d){this.cols=a,this.rows=b,this.tilewidth=c,this.tileheight=d,this.hTilewidth=c/2,this.hTileheight=d/2,this.originX=this.rows*this.hTilewidth},canRender:function(a){return"isometric"===a.orientation&&this.cols===a.cols&&this.rows===a.rows&&this.tilewidth===a.tilewidth&&this.tileheight===a.tileheight},pixelToTileCoords:function(a,b){a-=this.originX;var c=b/this.tileheight,d=a/this.tilewidth;return new me.Vector2d(c+d,c-d)},tileToPixelCoords:function(a,b){return new me.Vector2d((a-b)*this.hTilewidth+this.originX,(a+b)*this.hTileheight)},adjustPosition:function(a){var b=a.x/this.hTilewidth,c=a.y/this.tileheight,d=this.tileToPixelCoords(b,c);d.x-=a.width/2,d.y-=a.height,a.x=d.x,a.y=d.y},drawTile:function(a,b,c,d,e){e.drawTile(a,(this.cols-1)*e.tilewidth+(b-c)*e.tilewidth>>1,-e.tilewidth+(b+c)*e.tileheight>>2,d)},drawTileLayer:function(a,b,c){var d=b.tileset,e=d.tileoffset,f=this.pixelToTileCoords(c.pos.x-d.tilewidth,c.pos.y-d.tileheight).floorSelf(),g=this.pixelToTileCoords(c.pos.x+c.width+d.tilewidth,c.pos.y+c.height+d.tileheight).ceilSelf(),h=this.tileToPixelCoords(g.x,g.y),i=this.tileToPixelCoords(f.x,f.y);i.x-=this.hTilewidth,i.y+=this.tileheight;var j=i.y-c.pos.y>this.hTileheight,k=c.pos.x-i.x<this.hTilewidth;j&&(k?(f.x--,i.x-=this.hTilewidth):(f.y--,i.x+=this.hTilewidth),i.y-=this.hTileheight);for(var l=j^k,m=f.clone(),n=i.y;n-this.tileheight<h.y;n+=this.hTileheight){m.setV(f);for(var o=i.x;o<h.x;o+=this.tilewidth){if(m.x>=0&&m.y>=0&&m.x<this.cols&&m.y<this.rows){var p=b.layerData[m.x][m.y];p&&(d=p.tileset,e=d.tileoffset,d.drawTile(a,e.x+o,e.y+n-d.tileheight,p))}m.x++,m.y--}l?(f.y++,i.x-=this.hTilewidth,l=!1):(f.x++,i.x+=this.hTilewidth,l=!0)}}})}(window),function(){me.ColorLayer=me.Renderable.extend({init:function(a,b,c){this.parent(new me.Vector2d(0,0),1/0,1/0),this.name=a,this.color=me.utils.HexToRGB(b),this.z=c},reset:function(){},update:function(){return!1},draw:function(a,b){var c=a.globalAlpha;a.globalAlpha*=this.getOpacity(),a.fillStyle=this.color,a.fillRect(b.left,b.top,b.width,b.height),a.globalAlpha=c}}),me.ImageLayer=me.Renderable.extend({init:function(a,b,c,d,e,f){if(this.name=a,this.image=d?me.loader.getImage(me.utils.getBasename(d)):null,!this.image)throw"melonJS: '"+d+"' file for Image Layer '"+this.name+"' not found!";this.imagewidth=this.image.width,this.imageheight=this.image.height;var g=me.game.viewport;b=b?Math.min(g.width,b):g.width,c=c?Math.min(g.height,c):g.height,this.parent(new me.Vector2d(0,0),b,c),this.z=e,this.ratio=new me.Vector2d(1,1),f&&("number"==typeof f?this.ratio.set(f,f):this.ratio.setV(f)),this.lastpos=g.pos.clone(),this.floating=!0,this._repeat="repeat",this.repeatX=!0,this.repeatY=!0,Object.defineProperty(this,"repeat",{get:function(){return this._repeat},set:function(a){switch(this._repeat=a,this._repeat){case"no-repeat":this.repeatX=!1,this.repeatY=!1;break;case"repeat-x":this.repeatX=!0,this.repeatY=!1;break;case"repeat-y":this.repeatX=!1,this.repeatY=!0;break;default:this.repeatX=!0,this.repeatY=!0}}}),this.anchorPoint.set(0,0),this.handle=me.event.subscribe(me.event.VIEWPORT_ONCHANGE,this.updateLayer.bind(this))},reset:function(){this.handle&&(me.event.unsubscribe(this.handle),this.handle=null),this.image=null,this.lastpos=null},updateLayer:function(a){(0!==this.ratio.x||0!==this.ratio.y)&&(this.pos.x+=(a.x-this.lastpos.x)*this.ratio.x%this.imagewidth,this.pos.x=(this.imagewidth+this.pos.x)%this.imagewidth,this.pos.y+=(a.y-this.lastpos.y)*this.ratio.y%this.imageheight,this.pos.y=(this.imageheight+this.pos.y)%this.imageheight,this.lastpos.setV(a))},update:function(){return!1},draw:function(a,b){if(a.save(),0!==this.anchorPoint.y||0!==this.anchorPoint.x){var c=me.game.viewport;a.translate(~~(this.anchorPoint.x*(c.width-this.imagewidth)),~~(this.anchorPoint.y*(c.height-this.imageheight)))}a.globalAlpha*=this.getOpacity();var d,e;if(0===this.ratio.x&&0===this.ratio.y)d=Math.min(b.width,this.imagewidth),e=Math.min(b.height,this.imageheight),a.drawImage(this.image,b.left,b.top,d,e,b.left,b.top,d,e);else{var f=~~this.pos.x,g=~~this.pos.y,h=0,i=0;for(d=Math.min(this.imagewidth-f,this.width),e=Math.min(this.imageheight-g,this.height);;){do a.drawImage(this.image,f,g,d,e,h,i,d,e),g=0,i+=e,e=Math.min(this.imageheight,this.height-i);while(this.repeatY&&i<this.height);if(h+=d,!this.repeatX||h>=this.width)break;f=0,d=Math.min(this.imagewidth,this.width-h),g=~~this.pos.y,i=0,e=Math.min(this.imageheight-~~this.pos.y,this.height)}}a.restore()},destroy:function(){this.reset()}}),me.CollisionTiledLayer=me.Renderable.extend({init:function(a,b){this.parent(new me.Vector2d(0,0),a,b),this.isCollisionMap=!0},reset:function(){},checkCollision:function(a,b){var c=b.x<0?a.left+b.x:a.right+b.x,d=b.y<0?a.top+b.y:a.bottom+b.y,e={x:0,y:0,xprop:{},yprop:{}};return(0>=c||c>=this.width)&&(e.x=b.x),(0>=d||d>=this.height)&&(e.y=b.y),e}}),me.TMXLayer=me.Renderable.extend({layerData:null,init:function(a,b,c,d,e){this.parent(new me.Vector2d(0,0),0,0),this.tilewidth=a,this.tileheight=b,this.orientation=c,this.tilesets=d,this.tileset=this.tilesets?this.tilesets.getTilesetByIndex(0):null,this.z=e},initFromXML:function(a){this.name=me.mapReader.TMXParser.getStringAttribute(a,me.TMX_TAG_NAME),this.visible=1===me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_VISIBLE,1),this.cols=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_WIDTH),this.rows=me.mapReader.TMXParser.getIntAttribute(a,me.TMX_TAG_HEIGHT),this.setOpacity(me.mapReader.TMXParser.getFloatAttribute(a,me.TMX_TAG_OPACITY,1)),this.width=this.cols*this.tilewidth,this.height=this.rows*this.tileheight,me.TMXUtils.applyTMXPropertiesFromXML(this,a),"undefined"==typeof this.preRender&&(this.preRender=me.sys.preRender),this.isCollisionMap=this.name.toLowerCase().contains(me.COLLISION_LAYER),this.isCollisionMap&&!me.debug.renderCollisionMap&&(this.visible=!1),this.preRender===!0&&(this.layerCanvas=me.video.createCanvas(this.cols*this.tilewidth,this.rows*this.tileheight),this.layerSurface=me.video.getContext2d(this.layerCanvas))},initFromJSON:function(a){this.name=a[me.TMX_TAG_NAME],this.visible=a[me.TMX_TAG_VISIBLE],this.cols=parseInt(a[me.TMX_TAG_WIDTH],10),this.rows=parseInt(a[me.TMX_TAG_HEIGHT],10),this.setOpacity(parseFloat(a[me.TMX_TAG_OPACITY])),this.width=this.cols*this.tilewidth,this.height=this.rows*this.tileheight,me.TMXUtils.applyTMXPropertiesFromJSON(this,a),"undefined"==typeof this.preRender&&(this.preRender=me.sys.preRender),this.isCollisionMap=this.name.toLowerCase().contains(me.COLLISION_LAYER),this.isCollisionMap&&!me.debug.renderCollisionMap&&(this.visible=!1),this.preRender===!0&&(this.layerCanvas=me.video.createCanvas(this.cols*this.tilewidth,this.rows*this.tileheight),this.layerSurface=me.video.getContext2d(this.layerCanvas))},reset:function(){this.preRender&&(this.layerCanvas=null,this.layerSurface=null),this.renderer=null,this.layerData=null,this.tileset=null,this.tilesets=null},setRenderer:function(a){this.renderer=a},initArray:function(a,b){this.layerData=[];for(var c=0;a>c;c++){this.layerData[c]=[];for(var d=0;b>d;d++)this.layerData[c][d]=null}},getTileId:function(a,b){var c=this.getTile(a,b);return c?c.tileId:null},getTile:function(a,b){return this.layerData[~~(a/this.tilewidth)][~~(b/this.tileheight)]},setTile:function(a,b,c){var d=new me.Tile(a,b,this.tilewidth,this.tileheight,c);return d.tileset=this.tileset.contains(d.tileId)?this.tileset:this.tileset=this.tilesets.getTilesetByGid(d.tileId),this.layerData[a][b]=d,d},clearTile:function(a,b){this.layerData[a][b]=null,this.visible&&this.preRender&&this.layerSurface.clearRect(a*this.tilewidth,b*this.tileheight,this.tilewidth,this.tileheight)},checkCollision:function(a,b){var c=b.x<0?~~(a.left+b.x):Math.ceil(a.right-1+b.x),d=b.y<0?~~(a.top+b.y):Math.ceil(a.bottom-1+b.y),e={x:0,xtile:void 0,xprop:{},y:0,ytile:void 0,yprop:{}};return 0>=c||c>=this.width?e.x=b.x:0!==b.x&&(e.xtile=this.getTile(c,Math.ceil(a.bottom-1)),e.xtile&&this.tileset.isTileCollidable(e.xtile.tileId)?(e.x=b.x,e.xprop=this.tileset.getTileProperties(e.xtile.tileId)):(e.xtile=this.getTile(c,~~a.top),e.xtile&&this.tileset.isTileCollidable(e.xtile.tileId)&&(e.x=b.x,e.xprop=this.tileset.getTileProperties(e.xtile.tileId)))),e.ytile=this.getTile(b.x<0?~~a.left:Math.ceil(a.right-1),d),e.ytile&&this.tileset.isTileCollidable(e.ytile.tileId)?(e.y=b.y||1,e.yprop=this.tileset.getTileProperties(e.ytile.tileId)):(e.ytile=this.getTile(b.x<0?Math.ceil(a.right-1):~~a.left,d),e.ytile&&this.tileset.isTileCollidable(e.ytile.tileId)&&(e.y=b.y||1,e.yprop=this.tileset.getTileProperties(e.ytile.tileId))),e},update:function(){return!1},draw:function(a,b){if(this.preRender){var c=Math.min(b.width,this.width),d=Math.min(b.height,this.height);this.layerSurface.globalAlpha=a.globalAlpha*this.getOpacity(),a.drawImage(this.layerCanvas,b.pos.x,b.pos.y,c,d,b.pos.x,b.pos.y,c,d)}else{var e=a.globalAlpha;a.globalAlpha*=this.getOpacity(),this.renderer.drawTileLayer(a,this,b),a.globalAlpha=e}}})}(window),function(){me.TMXTileMap=me.Renderable.extend({init:function(a){this.levelId=a,this.z=0,this.name=null,this.cols=0,this.rows=0,this.tilewidth=0,this.tileheight=0,this.tilesets=null,this.mapLayers=[],this.objectGroups=[],this.initialized=!1,this.version="",this.orientation="",this.tilesets=null,this.parent(new me.Vector2d,0,0)},reset:function(){if(this.initialized===!0){var a;for(a=this.mapLayers.length;a--;)this.mapLayers[a].reset(),this.mapLayers[a]=null;for(a=this.objectGroups.length;a--;)this.objectGroups[a].reset(),this.objectGroups[a]=null;this.tilesets=null,this.mapLayers.length=0,this.objectGroups.length=0,this.pos.set(0,0),this.initialized=!1}},getObjectGroupByName:function(a){var b=null;a=a.trim().toLowerCase();for(var c=this.objectGroups.length;c--;)if(this.objectGroups[c].name.toLowerCase().contains(a)){b=this.objectGroups[c];break}return b},getObjectGroups:function(){return this.objectGroups},getLayers:function(){return this.mapLayers},getLayerByName:function(a){var b=null;a=a.trim().toLowerCase();for(var c=this.mapLayers.length;c--;)if(this.mapLayers[c].name.toLowerCase().contains(a)){b=this.mapLayers[c];break}return a.toLowerCase().contains(me.COLLISION_LAYER)&&null==b&&(b=new me.CollisionTiledLayer(me.game.currentLevel.width,me.game.currentLevel.height)),b},clearTile:function(a,b){for(var c=this.mapLayers.length;c--;)this.mapLayers[c]instanceof me.TMXLayer&&this.mapLayers[c].clearTile(a,b)}})}(window),function(){function a(){var a={tmxDoc:null,setData:function(a){this.tmxDoc=a},getFirstElementByTagName:function(a){return this.tmxDoc?this.tmxDoc.getElementsByTagName(a)[0]:null},getAllTagElements:function(){return this.tmxDoc?this.tmxDoc.getElementsByTagName("*"):null},getStringAttribute:function(a,b,c){var d=a.getAttribute(b);return d?d.trim():c},getIntAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?parseInt(d,10):c},getFloatAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?parseFloat(d):c},getBooleanAttribute:function(a,b,c){var d=this.getStringAttribute(a,b,c);return d?"true"===d:c},free:function(){this.tmxDoc=null}};return a}me.TMXMapReader=Object.extend({XMLReader:null,JSONReader:null,TMXParser:null,readMap:function(a){if(!a.initialized){if("xml"===me.loader.getTMXFormat(a.levelId)?(null===this.XMLReader&&(this.XMLReader=new b),this.TMXParser=this.XMLReader.TMXParser,this.XMLReader.readXMLMap(a,me.loader.getTMX(a.levelId))):(null===this.JSONReader&&(this.JSONReader=new c),this.JSONReader.readJSONMap(a,me.loader.getTMX(a.levelId))),a.width<me.game.viewport.width||a.height<me.game.viewport.height){var d=~~((me.game.viewport.width-a.width)/2),e=~~((me.game.viewport.height-a.height)/2); | |
a.pos.add({x:d>0?d:0,y:e>0?e:0})}a.initialized=!0}},getNewDefaultRenderer:function(a){switch(a.orientation){case"orthogonal":return new me.TMXOrthogonalRenderer(a.cols,a.rows,a.tilewidth,a.tileheight);case"isometric":return new me.TMXIsometricRenderer(a.cols,a.rows,a.tilewidth,a.tileheight);default:throw"melonJS: "+a.orientation+" type TMX Tile Map not supported!"}},setLayerData:function(a,b,c,d){switch(a.initArray(a.cols,a.rows),c){case null:b=b.getElementsByTagName(me.TMX_TAG_TILE);break;case"json":break;case me.TMX_TAG_CSV:case me.TMX_TAG_ATTR_BASE64:for(var e="",f=0,g=b.childNodes.length;g>f;f++)e+=b.childNodes[f].nodeValue;c===me.TMX_TAG_CSV?b=me.utils.decodeCSV(e,a.cols):(b=me.utils.decodeBase64AsArray(e,4),null!==d&&(b=me.utils.decompress(b,d))),e=null;break;default:throw"melonJS: TMX Tile Map "+c+" encoding not supported!"}for(var h=0,i=0;i<a.rows;i++)for(var j=0;j<a.cols;j++){var k=null==c?this.TMXParser.getIntAttribute(b[h++],me.TMX_TAG_GID):b[h++];if(0!==k){var l=a.setTile(j,i,k);a.visible&&a.preRender&&a.renderer.drawTile(a.layerSurface,j,i,l,l.tileset)}}}});var b=me.TMXMapReader.extend({TMXParser:null,init:function(){this.TMXParser||(this.TMXParser=new a)},readXMLMap:function(a,b){if(!b)throw"melonJS:"+a.levelId+" TMX map not found";var c=0;this.TMXParser.setData(b);for(var d=this.TMXParser.getAllTagElements(),e=0;e<d.length;e++)switch(d.item(e).nodeName){case me.TMX_TAG_MAP:var f=d.item(e);a.version=this.TMXParser.getStringAttribute(f,me.TMX_TAG_VERSION),a.orientation=this.TMXParser.getStringAttribute(f,me.TMX_TAG_ORIENTATION),a.cols=this.TMXParser.getIntAttribute(f,me.TMX_TAG_WIDTH),a.rows=this.TMXParser.getIntAttribute(f,me.TMX_TAG_HEIGHT),a.tilewidth=this.TMXParser.getIntAttribute(f,me.TMX_TAG_TILEWIDTH),a.tileheight=this.TMXParser.getIntAttribute(f,me.TMX_TAG_TILEHEIGHT),a.width=a.cols*a.tilewidth,a.height=a.rows*a.tileheight,a.backgroundcolor=this.TMXParser.getStringAttribute(f,me.TMX_BACKGROUND_COLOR),a.z=c++,me.TMXUtils.applyTMXPropertiesFromXML(a,f),a.background_color=a.backgroundcolor?a.backgroundcolor:a.background_color,a.background_color&&a.mapLayers.push(new me.ColorLayer("background_color",a.background_color,c++)),a.background_image&&a.mapLayers.push(new me.ImageLayer("background_image",a.width,a.height,a.background_image,c++)),null!==me.game.renderer&&me.game.renderer.canRender(a)||(me.game.renderer=this.getNewDefaultRenderer(a));break;case me.TMX_TAG_TILESET:a.tilesets||(a.tilesets=new me.TMXTilesetGroup),a.tilesets.add(this.readTileset(d.item(e)));break;case me.TMX_TAG_IMAGE_LAYER:a.mapLayers.push(this.readImageLayer(a,d.item(e),c++));break;case me.TMX_TAG_LAYER:a.mapLayers.push(this.readLayer(a,d.item(e),c++));break;case me.TMX_TAG_OBJECTGROUP:a.objectGroups.push(this.readObjectGroup(a,d.item(e),c++))}this.TMXParser.free()},readLayer:function(a,b,c){var d=new me.TMXLayer(a.tilewidth,a.tileheight,a.orientation,a.tilesets,c);d.initFromXML(b);var e=b.getElementsByTagName(me.TMX_TAG_DATA)[0],f=this.TMXParser.getStringAttribute(e,me.TMX_TAG_ENCODING,null),g=this.TMXParser.getStringAttribute(e,me.TMX_TAG_COMPRESSION,null);return""===f&&(f=null),""===g&&(g=null),(!d.isCollisionMap||me.debug.renderCollisionMap)&&(me.game.renderer.canRender(d)?d.setRenderer(me.game.renderer):d.setRenderer(me.mapReader.getNewDefaultRenderer(d))),this.setLayerData(d,e,f,g),e=null,d},readImageLayer:function(a,b,c){var d=this.TMXParser.getStringAttribute(b,me.TMX_TAG_NAME),e=this.TMXParser.getIntAttribute(b,me.TMX_TAG_WIDTH),f=this.TMXParser.getIntAttribute(b,me.TMX_TAG_HEIGHT),g=b.getElementsByTagName(me.TMX_TAG_IMAGE)[0].getAttribute(me.TMX_TAG_SOURCE),h=new me.ImageLayer(d,e*a.tilewidth,f*a.tileheight,g,c);return h.visible=1===this.TMXParser.getIntAttribute(b,me.TMX_TAG_VISIBLE,1),h.setOpacity(this.TMXParser.getFloatAttribute(b,me.TMX_TAG_OPACITY,1)),me.TMXUtils.applyTMXPropertiesFromXML(h,b),"number"==typeof h.ratio&&(h.ratio=new me.Vector2d(parseFloat(h.ratio),parseFloat(h.ratio))),h},readTileset:function(a){var b=new me.TMXTileset;return b.initFromXML(a),b},readObjectGroup:function(a,b,c){var d=this.TMXParser.getStringAttribute(b,me.TMX_TAG_NAME),e=new me.TMXObjectGroup;return e.initFromXML(d,b,a.tilesets,c),e}}),c=me.TMXMapReader.extend({readJSONMap:function(a,b){if(!b)throw"melonJS:"+a.levelId+" TMX map not found";var c=0,d=this;a.version=b[me.TMX_TAG_VERSION],a.orientation=b[me.TMX_TAG_ORIENTATION],a.cols=parseInt(b[me.TMX_TAG_WIDTH],10),a.rows=parseInt(b[me.TMX_TAG_HEIGHT],10),a.tilewidth=parseInt(b[me.TMX_TAG_TILEWIDTH],10),a.tileheight=parseInt(b[me.TMX_TAG_TILEHEIGHT],10),a.width=a.cols*a.tilewidth,a.height=a.rows*a.tileheight,a.backgroundcolor=b[me.TMX_BACKGROUND_COLOR],a.z=c++,me.TMXUtils.applyTMXPropertiesFromJSON(a,b),a.background_color=a.backgroundcolor?a.backgroundcolor:a.background_color,a.background_color&&a.mapLayers.push(new me.ColorLayer("background_color",a.background_color,c++)),a.background_image&&a.mapLayers.push(new me.ImageLayer("background_image",a.width,a.height,a.background_image,c++)),null!==me.game.renderer&&me.game.renderer.canRender(a)||(me.game.renderer=this.getNewDefaultRenderer(a)),a.tilesets||(a.tilesets=new me.TMXTilesetGroup),b.tilesets.forEach(function(b){a.tilesets.add(d.readTileset(b))}),b.layers.forEach(function(b){switch(b.type){case me.TMX_TAG_IMAGE_LAYER:a.mapLayers.push(d.readImageLayer(a,b,c++));break;case me.TMX_TAG_TILE_LAYER:a.mapLayers.push(d.readLayer(a,b,c++));break;case me.TMX_TAG_OBJECTGROUP:a.objectGroups.push(d.readObjectGroup(a,b,c++))}})},readLayer:function(a,b,c){var d=new me.TMXLayer(a.tilewidth,a.tileheight,a.orientation,a.tilesets,c);return d.initFromJSON(b),d.isCollisionMap||(me.game.renderer.canRender(d)?d.setRenderer(me.game.renderer):d.setRenderer(me.mapReader.getNewDefaultRenderer(d))),this.setLayerData(d,b[me.TMX_TAG_DATA],"json",null),d},readImageLayer:function(a,b,c){var d=b[me.TMX_TAG_NAME],e=parseInt(b[me.TMX_TAG_WIDTH],10),f=parseInt(b[me.TMX_TAG_HEIGHT],10),g=b[me.TMX_TAG_IMAGE],h=new me.ImageLayer(d,e*a.tilewidth,f*a.tileheight,g,c);return h.visible=b[me.TMX_TAG_VISIBLE],h.setOpacity(parseFloat(b[me.TMX_TAG_OPACITY])),me.TMXUtils.applyTMXPropertiesFromJSON(h,b),"number"==typeof h.ratio&&(h.ratio=new me.Vector2d(parseFloat(h.ratio),parseFloat(h.ratio))),h},readTileset:function(a){var b=new me.TMXTileset;return b.initFromJSON(a),b},readObjectGroup:function(a,b,c){var d=new me.TMXObjectGroup;return d.initFromJSON(b[me.TMX_TAG_NAME],b,a.tilesets,c),d}})}(window),function(){me.levelDirector=function(){var a={},b={},c=[],d=0;return a.reset=function(){},a.addLevel=function(){throw"melonJS: no level loader defined"},a.addTMXLevel=function(a,d){return null!=b[a]?!1:(b[a]=new me.TMXTileMap(a),b[a].name=a,c.push(a),d&&d(),!0)},a.loadLevel=function(e){if(e=e.toString().toLowerCase(),void 0===b[e])throw"melonJS: level "+e+" not found";if(!(b[e]instanceof me.TMXTileMap))throw"melonJS: no level loader defined";var f=me.state.isRunning();return f&&me.state.stop(),me.game.reset(),me.utils.resetGUID(e),b[a.getCurrentLevelId()]&&b[a.getCurrentLevelId()].reset(),me.mapReader.readMap(b[e]),d=c.indexOf(e),me.game.loadTMXLevel(b[e]),f&&me.state.restart.defer(),!0},a.getCurrentLevelId=function(){return c[d]},a.reloadLevel=function(){return a.loadLevel(a.getCurrentLevelId())},a.nextLevel=function(){return d+1<c.length?a.loadLevel(c[d+1]):!1},a.previousLevel=function(){return d-1>=0?a.loadLevel(c[d-1]):!1},a.levelCount=function(){return c.length},a}()}(window),/** | |
* @preserve Tween JS | |
* https://github.com/sole/Tween.js | |
*/ | |
function(){me.Tween=function(a){var b=a,c={},d={},e={},f=1e3,g=0,h=!1,i=!1,j=0,k=null,l=0,m=me.Tween.Easing.Linear.None,n=me.Tween.Interpolation.Linear,o=[],p=null,q=!1,r=null,s=null;for(var t in a)c[t]=parseFloat(a[t],10);this.onResetEvent=function(a){b=a,c={},d={},e={},m=me.Tween.Easing.Linear.None,n=me.Tween.Interpolation.Linear,h=!1,i=!1,f=1e3,j=0,p=null,q=!1,r=null,s=null},this.to=function(a,b){return void 0!==b&&(f=b),d=a,this},this.start=function(){q=!1,me.game.world.addChild(this),k=me.timer.getTime()+j,l=0;for(var a in d){if(d[a]instanceof Array){if(0===d[a].length)continue;d[a]=[b[a]].concat(d[a])}c[a]=b[a],c[a]instanceof Array==!1&&(c[a]*=1),e[a]=c[a]||0}return this},this.stop=function(){return me.game.world.removeChild(this),this},this.delay=function(a){return j=a,this},me.event.subscribe(me.event.STATE_PAUSE,function(){k&&(l=me.timer.getTime())}),me.event.subscribe(me.event.STATE_RESUME,function(){k&&l&&(k+=me.timer.getTime()-l)}),this.repeat=function(a){return g=a,this},this.yoyo=function(a){return h=a,this},this.easing=function(a){if("function"!=typeof a)throw"melonJS: invalid easing function for me.Tween.easing()";return m=a,this},this.interpolation=function(a){return n=a,this},this.chain=function(){return o=arguments,this},this.onStart=function(a){return p=a,this},this.onUpdate=function(a){return r=a,this},this.onComplete=function(a){return s=a,this},this.update=function(){var a,l=me.timer.getTime();if(k>l)return!0;q===!1&&(null!==p&&p.call(b),q=!0);var t=(l-k)/f;t=t>1?1:t;var u=m(t);for(a in d){var v=c[a]||0,w=d[a];w instanceof Array?b[a]=n(w,u):("string"==typeof w&&(w=v+parseFloat(w,10)),"number"==typeof w&&(b[a]=v+(w-v)*u))}if(null!==r&&r.call(b,u),1===t){if(g>0){isFinite(g)&&g--;for(a in e){if("string"==typeof d[a]&&(e[a]=e[a]+parseFloat(d[a],10)),h){var x=e[a];e[a]=d[a],d[a]=x,i=!i}c[a]=e[a]}return k=l+j,!0}me.game.world.removeChild(this),null!==s&&s.call(b);for(var y=0,z=o.length;z>y;y++)o[y].start(l);return!1}return!0}},me.Tween.Easing={Linear:{None:function(a){return a}},Quadratic:{In:function(a){return a*a},Out:function(a){return a*(2-a)},InOut:function(a){return(a*=2)<1?.5*a*a:-.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return(a*=2)<1?.5*a*a*a:.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return(a*=2)<1?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a*a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return(a*=2)<1?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:(a*=2)<1?.5*Math.pow(1024,a-1):.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1-Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return(a*=2)<1?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),-(c*Math.pow(2,10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d)))},Out:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),c*Math.pow(2,-10*a)*Math.sin(2*(a-b)*Math.PI/d)+1)},InOut:function(a){var b,c=.1,d=.4;return 0===a?0:1===a?1:(!c||1>c?(c=1,b=d/4):b=d*Math.asin(1/c)/(2*Math.PI),(a*=2)<1?-.5*c*Math.pow(2,10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d):c*Math.pow(2,-10*(a-=1))*Math.sin(2*(a-b)*Math.PI/d)*.5+1)}},Back:{In:function(a){var b=1.70158;return a*a*((b+1)*a-b)},Out:function(a){var b=1.70158;return--a*a*((b+1)*a+b)+1},InOut:function(a){var b=2.5949095;return(a*=2)<1?.5*a*a*((b+1)*a-b):.5*((a-=2)*a*((b+1)*a+b)+2)}},Bounce:{In:function(a){return 1-me.Tween.Easing.Bounce.Out(1-a)},Out:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},InOut:function(a){return.5>a?.5*me.Tween.Easing.Bounce.In(2*a):.5*me.Tween.Easing.Bounce.Out(2*a-1)+.5}}},me.Tween.Interpolation={Linear:function(a,b){var c=a.length-1,d=c*b,e=Math.floor(d),f=me.Tween.Interpolation.Utils.Linear;return 0>b?f(a[0],a[1],d):b>1?f(a[c],a[c-1],c-d):f(a[e],a[e+1>c?c:e+1],d-e)},Bezier:function(a,b){var c,d=0,e=a.length-1,f=Math.pow,g=me.Tween.Interpolation.Utils.Bernstein;for(c=0;e>=c;c++)d+=f(1-b,e-c)*f(b,c)*a[c]*g(e,c);return d},CatmullRom:function(a,b){var c=a.length-1,d=c*b,e=Math.floor(d),f=me.Tween.Interpolation.Utils.CatmullRom;return a[0]===a[c]?(0>b&&(e=Math.floor(d=c*(1+b))),f(a[(e-1+c)%c],a[e],a[(e+1)%c],a[(e+2)%c],d-e)):0>b?a[0]-(f(a[0],a[0],a[1],a[1],-d)-a[0]):b>1?a[c]-(f(a[c],a[c],a[c-1],a[c-1],d-c)-a[c]):f(a[e?e-1:0],a[e],a[e+1>c?c:e+1],a[e+2>c?c:e+2],d-e)},Utils:{Linear:function(a,b,c){return(b-a)*c+a},Bernstein:function(a,b){var c=me.Tween.Interpolation.Utils.Factorial;return c(a)/c(b)/c(a-b)},Factorial:function(){var a=[1];return function(b){var c,d=1;if(a[b])return a[b];for(c=b;c>1;c--)d*=c;return a[b]=d}}(),CatmullRom:function(a,b,c,d,e){var f=.5*(c-a),g=.5*(d-b),h=e*e,i=e*h;return(2*b-2*c+f+g)*i+(-3*b+3*c-2*f-g)*h+f*e+b}}}}(),/** | |
* @preserve MinPubSub | |
* a micro publish/subscribe messaging framework | |
* @see https://github.com/daniellmb/MinPubSub | |
* @author Daniel Lamb <daniellmb.com> | |
* | |
* Released under the MIT License | |
*/ | |
function(){me.event=function(){var a={},b={};return a.STATE_PAUSE="me.state.onPause",a.STATE_RESUME="me.state.onResume",a.STATE_STOP="me.state.onStop",a.STATE_RESTART="me.state.onRestart",a.GAME_INIT="me.game.onInit",a.LEVEL_LOADED="me.game.onLevelLoaded",a.LOADER_COMPLETE="me.loader.onload",a.LOADER_PROGRESS="me.loader.onProgress",a.KEYDOWN="me.input.keydown",a.KEYUP="me.input.keyup",a.WINDOW_ONRESIZE="window.onresize",a.WINDOW_ONORIENTATION_CHANGE="window.orientationchange",a.WINDOW_ONSCROLL="window.onscroll",a.VIEWPORT_ONCHANGE="viewport.onchange",a.publish=function(a,c){for(var d=b[a],e=d?d.length:0;e--;)d[e].apply(window,c||[])},a.subscribe=function(a,c){return b[a]||(b[a]=[]),b[a].push(c),[a,c]},a.unsubscribe=function(a,c){var d=b[c?a:a[0]],e=d?d.length:0;for(c=c||a[1];e--;)d[e]===c&&d.splice(e,1)},a}()}(),function(){me.plugin=function(){var a={};return a.Base=Object.extend({version:void 0,init:function(){}}),a.patch=function(a,b,c){if(void 0!==a.prototype&&(a=a.prototype),"function"==typeof a[b]){var d=a[b];a[b]=function(a,b){return function(){var a=this.parent;this.parent=d;var c=b.apply(this,arguments);return this.parent=a,c}}(b,c)}else console.error(b+" is not an existing function")},a.register=function(a,b){if(me.plugin[b]&&console.error("plugin "+b+" already registered"),void 0===a.prototype.version)throw"melonJS: Plugin version not defined !";if(me.sys.checkVersion(a.prototype.version)>0)throw"melonJS: Plugin version mismatch, expected: "+a.prototype.version+", got: "+me.version;var c=[];if(arguments.length>2&&(c=Array.prototype.slice.call(arguments,1)),c[0]=a,me.plugin[b]=new(a.bind.apply(a,c)),!(me.plugin[b]instanceof me.plugin.Base))throw"melonJS: Plugin should extend the me.plugin.Base Class !"},a}()}(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license MelonJS Game Engine | |
* @copyright (C) 2011 - 2013 Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
* melonJS is licensed under the MIT License. | |
* http://www.opensource.org/licenses/mit-license.php | |
* | |
*/ | |
/** | |
* (<b>m</b>)elonJS (<b>e</b>)ngine : All melonJS functions are defined inside of this namespace.<p> | |
* You generally should not add new properties to this namespace as it may be overwritten in future versions. | |
* @namespace | |
*/ | |
window.me = window.me || {}; | |
(function($) { | |
// Use the correct document accordingly to window argument | |
var document = $.document; | |
/** | |
* me global references | |
* @ignore | |
*/ | |
me = { | |
// library name & version | |
mod : "melonJS", | |
version : "0.9.11" | |
}; | |
/** | |
* global system settings and browser capabilities | |
* @namespace | |
*/ | |
me.sys = { | |
// Global settings | |
/** | |
* Game FPS (default 60) | |
* @type Int | |
* @memberOf me.sys | |
*/ | |
fps : 60, | |
/** | |
* enable/disable frame interpolation (default disable)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
interpolation : false, | |
/** | |
* Global scaling factor(default 1.0) | |
* @type me.Vector2d | |
* @memberOf me.sys | |
*/ | |
scale : null, //initialized by me.video.init | |
/** | |
* enable/disable video scaling interpolation (default disable)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
scalingInterpolation : false, | |
/** | |
* Global gravity settings <br> | |
* will override entities init value if defined<br> | |
* default value : undefined | |
* @type Number | |
* @memberOf me.sys | |
*/ | |
gravity : undefined, | |
/** | |
* Specify either to stop on audio loading error or not<br> | |
* if me.debug.stopOnAudioLoad is true, melonJS will throw an exception and stop loading<br> | |
* if me.debug.stopOnAudioLoad is false, melonJS will disable sounds and output a warning message in the console <br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
stopOnAudioError : true, | |
/** | |
* Specify whether to pause the game when losing focus.<br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
pauseOnBlur : true, | |
/** | |
* Specify whether to unpause the game when gaining focus.<br> | |
* default value : true<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
resumeOnFocus : true, | |
/** | |
* Specify whether to stop the game when losing focus or not<br> | |
* The engine restarts on focus if this is enabled. | |
* default value : false<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
stopOnBlur : false, | |
/** | |
* Specify the rendering method for layers <br> | |
* if false, visible part of the layers are rendered dynamically (default)<br> | |
* if true, the entire layers are first rendered into an offscreen canvas<br> | |
* the "best" rendering method depends of your game<br> | |
* (amount of layer, layer size, amount of tiles per layer, etc…)<br> | |
* note : rendering method is also configurable per layer by adding this property to your layer (in Tiled)<br> | |
* @type Boolean | |
* @memberOf me.sys | |
*/ | |
preRender : false, | |
// System methods | |
/** | |
* Compare two version strings | |
* @public | |
* @function | |
* @param {String} first First version string to compare | |
* @param {String} [second="0.9.11"] Second version string to compare | |
* @return {Number} comparison result <br>< 0 : first < second <br>0 : first == second <br>> 0 : first > second | |
* @example | |
* if (me.sys.checkVersion("0.9.5") > 0) { | |
* console.error("melonJS is too old. Expected: 0.9.5, Got: " + me.version); | |
* } | |
*/ | |
checkVersion : function (first, second) { | |
second = second || me.version; | |
var a = first.split("."); | |
var b = second.split("."); | |
var len = Math.min(a.length, b.length); | |
var result = 0; | |
for (var i = 0; i < len; i++) { | |
if (result = +a[i] - +b[i]) { | |
break; | |
} | |
} | |
return result ? result : a.length - b.length; | |
} | |
}; | |
// a flag to know if melonJS | |
// is initialized | |
var me_initialized = false; | |
/*--- | |
DOM loading stuff | |
---*/ | |
var readyBound = false, isReady = false, readyList = []; | |
// Handle when the DOM is ready | |
function domReady() { | |
// Make sure that the DOM is not already loaded | |
if (!isReady) { | |
// be sure document.body is there | |
if (!document.body) { | |
return setTimeout(domReady, 13); | |
} | |
// clean up loading event | |
if (document.removeEventListener) { | |
document.removeEventListener("DOMContentLoaded", domReady, false); | |
} else { | |
$.removeEventListener("load", domReady, false); | |
} | |
// Remember that the DOM is ready | |
isReady = true; | |
// execute the defined callback | |
for ( var fn = 0; fn < readyList.length; fn++) { | |
readyList[fn].call($, []); | |
} | |
readyList.length = 0; | |
} | |
} | |
// bind ready | |
function bindReady() { | |
if (readyBound) { | |
return; | |
} | |
readyBound = true; | |
// directly call domReady if document is already "ready" | |
if (document.readyState === "complete") { | |
return domReady(); | |
} else { | |
if (document.addEventListener) { | |
// Use the handy event callback | |
document.addEventListener("DOMContentLoaded", domReady, false); | |
} | |
// A fallback to window.onload, that will always work | |
$.addEventListener("load", domReady, false); | |
} | |
} | |
/** | |
* The built in window Object | |
* @external window | |
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window.window} | |
*/ | |
/** | |
* Specify a function to execute when the DOM is fully loaded | |
* @memberOf external:window# | |
* @alias onReady | |
* @param {Function} handler A function to execute after the DOM is ready. | |
* @example | |
* // small main skeleton | |
* var game = { | |
* // Initialize the game | |
* // called by the window.onReady function | |
* onload: function() { | |
* | |
* // init video | |
* if (!me.video.init('screen', 640, 480, true)) { | |
* alert("Sorry but your browser does not support html 5 canvas. "); | |
* return; | |
* } | |
* | |
* // initialize the "audio" | |
* me.audio.init("mp3,ogg"); | |
* | |
* // set callback for ressources loaded event | |
* me.loader.onload = this.loaded.bind(this); | |
* | |
* // set all ressources to be loaded | |
* me.loader.preload(game.resources); | |
* | |
* // load everything & display a loading screen | |
* me.state.change(me.state.LOADING); | |
* }, | |
* | |
* // callback when everything is loaded | |
* loaded: function () { | |
* // define stuff | |
* // .... | |
* | |
* // change to the menu screen | |
* me.state.change(me.state.MENU); | |
* } | |
* }; // game | |
* | |
* // "bootstrap" | |
* window.onReady(function() { | |
* game.onload(); | |
* }); | |
*/ | |
$.onReady = function(fn) { | |
// Attach the listeners | |
bindReady(); | |
// If the DOM is already ready | |
if (isReady) { | |
// Execute the function immediately | |
fn.call($, []); | |
} else { | |
// Add the function to the wait list | |
readyList.push(function() { | |
return fn.call($, []); | |
}); | |
} | |
return this; | |
}; | |
// call the library init function when ready | |
$.onReady(function() { | |
_init_ME(); | |
}); | |
/************************************************************************************/ | |
/* | |
* some "Javascript API" patch & enhancement | |
*/ | |
var initializing = false, fnTest = /var xyz/.test(function() {/**@nosideeffects*/var xyz;}) ? /\bparent\b/ : /[\D|\d]*/; | |
/** | |
* a deep copy function | |
* @ignore | |
*/ | |
var deepcopy = function (obj) { | |
if (null == obj || "object" !== typeof obj) { | |
return obj; | |
} | |
// hold the copied object | |
var copy; | |
// Array copy | |
if( obj instanceof Array ) { | |
copy = []; | |
Object.setPrototypeOf(copy, Object.getPrototypeOf(obj)); | |
for( var i = 0, l = obj.length; i < l; i++) { | |
copy[i] = deepcopy(obj[i]); | |
} | |
return copy; | |
} | |
// Date copy | |
if (obj instanceof Date) { | |
copy = new Date(); | |
copy.setTime(obj.getTime()); | |
return copy; | |
} | |
// else instanceof Object | |
copy = {}; | |
Object.setPrototypeOf(copy, Object.getPrototypeOf(obj)); | |
for( var prop in obj ) { | |
if (obj.hasOwnProperty(prop)) copy[prop] = deepcopy(obj[prop]); | |
} | |
return copy; | |
}; | |
/** | |
* JavaScript Inheritance Helper <br> | |
* Based on <a href="http://ejohn.org/">John Resig</a> Simple Inheritance<br> | |
* MIT Licensed.<br> | |
* Inspired by <a href="http://code.google.com/p/base2/">base2</a> and <a href="http://www.prototypejs.org/">Prototype</a><br> | |
* @param {Object} object Object (or Properties) to inherit from | |
* @example | |
* var Person = Object.extend( | |
* { | |
* init: function(isDancing) | |
* { | |
* this.dancing = isDancing; | |
* }, | |
* dance: function() | |
* { | |
* return this.dancing; | |
* } | |
* }); | |
* | |
* var Ninja = Person.extend( | |
* { | |
* init: function() | |
* { | |
* this.parent( false ); | |
* }, | |
* | |
* dance: function() | |
* { | |
* // Call the inherited version of dance() | |
* return this.parent(); | |
* }, | |
* | |
* swingSword: function() | |
* { | |
* return true; | |
* } | |
* }); | |
* | |
* var p = new Person(true); | |
* p.dance(); // => true | |
* | |
* var n = new Ninja(); | |
* n.dance(); // => false | |
* n.swingSword(); // => true | |
* | |
* // Should all be true | |
* p instanceof Person && p instanceof Class && | |
* n instanceof Ninja && n instanceof Person && n instanceof Class | |
*/ | |
Object.extend = function(prop) { | |
// _super rename to parent to ease code reading | |
var parent = this.prototype; | |
// Instantiate a base class (but only create the instance, | |
// don't run the init constructor) | |
initializing = true; | |
var proto = new this(); | |
initializing = false; | |
function addSuper(name, fn) { | |
return function() { | |
var tmp = this.parent; | |
// Add a new ._super() method that is the same method | |
// but on the super-class | |
this.parent = parent[name]; | |
// The method only need to be bound temporarily, so we | |
// remove it when we're done executing | |
var ret = fn.apply(this, arguments); | |
this.parent = tmp; | |
return ret; | |
}; | |
} | |
// Copy the properties over onto the new prototype | |
for ( var name in prop) { | |
// Check if we're overwriting an existing function | |
proto[name] = typeof prop[name] === "function" && | |
typeof parent[name] === "function" && | |
fnTest.test(prop[name]) ? addSuper(name, prop[name]) : prop[name]; | |
} | |
// The dummy class constructor | |
function Class() { | |
if (!initializing) { | |
for( var prop in this ) { | |
// deepcopy properties if required | |
if( typeof(this[prop]) === 'object' ) { | |
this[prop] = deepcopy(this[prop]); | |
} | |
} | |
if (this.init) { | |
this.init.apply(this, arguments); | |
} | |
} | |
return this; | |
} | |
// Populate our constructed prototype object | |
Class.prototype = proto; | |
// Enforce the constructor to be what we expect | |
Class.constructor = Class; | |
// And make this class extendable | |
Class.extend = Object.extend;//arguments.callee; | |
return Class; | |
}; | |
if (typeof Object.create !== 'function') { | |
/** | |
* Prototypal Inheritance Create Helper | |
* @param {Object} Object | |
* @example | |
* // declare oldObject | |
* oldObject = new Object(); | |
* // make some crazy stuff with oldObject (adding functions, etc...) | |
* ... | |
* ... | |
* | |
* // make newObject inherits from oldObject | |
* newObject = Object.create(oldObject); | |
*/ | |
Object.create = function(o) { | |
function _fn() {} | |
_fn.prototype = o; | |
return new _fn(); | |
}; | |
} | |
/** | |
* The built in Function Object | |
* @external Function | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function|Function} | |
*/ | |
if (!Function.prototype.bind) { | |
/** @ignore */ | |
var Empty = function () {}; | |
/** | |
* Binds this function to the given context by wrapping it in another function and returning the wrapper.<p> | |
* Whenever the resulting "bound" function is called, it will call the original ensuring that this is set to context. <p> | |
* Also optionally curries arguments for the function. | |
* @memberof! external:Function# | |
* @alias bind | |
* @param {Object} context the object to bind to. | |
* @param {} [arguments...] Optional additional arguments to curry for the function. | |
* @example | |
* // Ensure that our callback is triggered with the right object context (this): | |
* myObject.onComplete(this.callback.bind(this)); | |
*/ | |
Function.prototype.bind = function bind(that) { | |
// ECMAScript 5 compliant implementation | |
// http://es5.github.com/#x15.3.4.5 | |
// from https://github.com/kriskowal/es5-shim | |
var target = this; | |
if (typeof target !== "function") { | |
throw new TypeError("Function.prototype.bind called on incompatible " + target); | |
} | |
var args = Array.prototype.slice.call(arguments, 1); | |
var bound = function () { | |
if (this instanceof bound) { | |
var result = target.apply( this, args.concat(Array.prototype.slice.call(arguments))); | |
if (Object(result) === result) { | |
return result; | |
} | |
return this; | |
} else { | |
return target.apply(that, args.concat(Array.prototype.slice.call(arguments))); | |
} | |
}; | |
if(target.prototype) { | |
Empty.prototype = target.prototype; | |
bound.prototype = new Empty(); | |
Empty.prototype = null; | |
} | |
return bound; | |
}; | |
} | |
if (!window.throttle) { | |
/** | |
* a simple throttle function | |
* use same fct signature as the one in prototype | |
* in case it's already defined before | |
* @ignore | |
*/ | |
window.throttle = function( delay, no_trailing, callback, debounce_mode ) { | |
var last = Date.now(), deferTimer; | |
// `no_trailing` defaults to false. | |
if ( typeof no_trailing !== 'boolean' ) { | |
no_trailing = false; | |
} | |
return function () { | |
var now = Date.now(); | |
var elasped = now - last; | |
var args = arguments; | |
if (elasped < delay) { | |
if (no_trailing === false) { | |
// hold on to it | |
clearTimeout(deferTimer); | |
deferTimer = setTimeout(function () { | |
last = now; | |
return callback.apply(null, args); | |
}, elasped); | |
} | |
} else { | |
last = now; | |
return callback.apply(null, args); | |
} | |
}; | |
}; | |
} | |
if (typeof Date.now === "undefined") { | |
/** | |
* provide a replacement for browser not | |
* supporting Date.now (JS 1.5) | |
* @ignore | |
*/ | |
Date.now = function(){return new Date().getTime();}; | |
} | |
if(typeof console === "undefined") { | |
/** | |
* Dummy console.log to avoid crash | |
* in case the browser does not support it | |
* @ignore | |
*/ | |
console = { | |
log: function() {}, | |
info: function() {}, | |
error: function() {alert(Array.prototype.slice.call(arguments).join(", "));} | |
}; | |
} | |
/** | |
* Executes a function as soon as the interpreter is idle (stack empty). | |
* @memberof! external:Function# | |
* @alias defer | |
* @param {} [arguments...] Optional additional arguments to curry for the function. | |
* @return {Number} id that can be used to clear the deferred function using clearTimeout | |
* @example | |
* // execute myFunc() when the stack is empty, with 'myArgument' as parameter | |
* myFunc.defer('myArgument'); | |
*/ | |
Function.prototype.defer = function() { | |
var fn = this, args = Array.prototype.slice.call(arguments); | |
return window.setTimeout(function() { | |
return fn.apply(fn, args); | |
}, 0.01); | |
}; | |
if (!Object.defineProperty) { | |
/** | |
* simple defineProperty function definition (if not supported by the browser)<br> | |
* if defineProperty is redefined, internally use __defineGetter__/__defineSetter__ as fallback | |
* @param {Object} obj The object on which to define the property. | |
* @param {String} prop The name of the property to be defined or modified. | |
* @param {Object} desc The descriptor for the property being defined or modified. | |
*/ | |
Object.defineProperty = function(obj, prop, desc) { | |
// check if Object support __defineGetter function | |
if (obj.__defineGetter__) { | |
if (desc.get) { | |
obj.__defineGetter__(prop, desc.get); | |
} | |
if (desc.set) { | |
obj.__defineSetter__(prop, desc.set); | |
} | |
} else { | |
// we should never reach this point.... | |
throw "melonJS: Object.defineProperty not supported"; | |
} | |
}; | |
} | |
/** | |
* Get the prototype of an Object. | |
* @memberOf external:Object# | |
* @alias getPrototypeOf | |
* @param {Object} obj Target object to inspect. | |
* @return {Prototype} Prototype of the target object. | |
*/ | |
Object.getPrototypeOf = Object.getPrototypeOf || function (obj) { | |
return obj.__proto__; | |
}; | |
/** | |
* Set the prototype of an Object. | |
* @memberOf external:Object# | |
* @alias setPrototypeOf | |
* @param {Object} obj Target object to modify. | |
* @param {Prototype} prototype New prototype for the target object. | |
* @return {Object} Modified target object. | |
*/ | |
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, prototype) { | |
obj.__proto__ = prototype; | |
return obj; | |
}; | |
/** | |
* The built in String Object | |
* @external String | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String|String} | |
*/ | |
if(!String.prototype.trim) { | |
/** | |
* returns the string stripped of whitespace from both ends | |
* @memberof! external:String# | |
* @alias trim | |
* @return {String} trimmed string | |
*/ | |
String.prototype.trim = function () { | |
return (this.replace(/^\s+/, '')).replace(/\s+$/, ''); | |
}; | |
} | |
if(!String.prototype.trimRight) { | |
/** | |
* returns the string stripped of whitespace from the right end of the string. | |
* @memberof! external:String# | |
* @alias trimRight | |
* @return {String} trimmed string | |
*/ | |
String.prototype.trimRight = function () { | |
return this.replace(/\s+$/, ''); | |
}; | |
} | |
/** | |
* add isNumeric fn to the string object | |
* @memberof! external:String# | |
* @alias isNumeric | |
* @return {Boolean} true if string contains only digits | |
*/ | |
String.prototype.isNumeric = function() { | |
return (this !== null && !isNaN(this) && this.trim() !== ""); | |
}; | |
/** | |
* add a isBoolean fn to the string object | |
* @memberof! external:String# | |
* @alias isBoolean | |
* @return {Boolean} true if the string is either true or false | |
*/ | |
String.prototype.isBoolean = function() { | |
return (this !== null && ("true" === this.trim() || "false" === this.trim())); | |
}; | |
/** | |
* add a contains fn to the string object | |
* @memberof! external:String# | |
* @alias contains | |
* @param {String} string to test for | |
* @return {Boolean} true if contains the specified string | |
*/ | |
String.prototype.contains = function(word) { | |
return this.indexOf(word) > -1; | |
}; | |
/** | |
* convert the string to hex value | |
* @memberof! external:String# | |
* @alias toHex | |
* @return {String} | |
*/ | |
String.prototype.toHex = function() { | |
var res = "", c = 0; | |
while(c<this.length){ | |
res += this.charCodeAt(c++).toString(16); | |
} | |
return res; | |
}; | |
/** | |
* The built in Number Object | |
* @external Number | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number|Number} | |
*/ | |
/** | |
* add a clamp fn to the Number object | |
* @memberof! external:Number# | |
* @alias clamp | |
* @param {Number} low lower limit | |
* @param {Number} high higher limit | |
* @return {Number} clamped value | |
*/ | |
Number.prototype.clamp = function(low, high) { | |
return this < low ? low : this > high ? high : +this; | |
}; | |
/** | |
* return a random between min, max | |
* @memberof! external:Number# | |
* @alias random | |
* @param {Number} min minimum value. | |
* @param {Number} max maximum value. | |
* @return {Number} random value | |
*/ | |
Number.prototype.random = function(min, max) { | |
return (~~(Math.random() * (max - min + 1)) + min); | |
}; | |
/** | |
* round a value to the specified number of digit | |
* @memberof! external:Number# | |
* @alias round | |
* @param {Number} [num="Object value"] value to be rounded. | |
* @param {Number} dec number of decimal digit to be rounded to. | |
* @return {Number} rounded value | |
* @example | |
* // round a specific value to 2 digits | |
* Number.prototype.round (10.33333, 2); // return 10.33 | |
* // round a float value to 4 digits | |
* num = 10.3333333 | |
* num.round(4); // return 10.3333 | |
*/ | |
Number.prototype.round = function() { | |
// if only one argument use the object value | |
var num = (arguments.length < 2) ? this : arguments[0]; | |
var powres = Math.pow(10, arguments[1] || arguments[0] || 0); | |
return (Math.round(num * powres) / powres); | |
}; | |
/** | |
* a quick toHex function<br> | |
* given number <b>must</b> be an int, with a value between 0 and 255 | |
* @memberof! external:Number# | |
* @alias toHex | |
* @return {String} converted hexadecimal value | |
*/ | |
Number.prototype.toHex = function() { | |
return "0123456789ABCDEF".charAt((this - this % 16) >> 4) + "0123456789ABCDEF".charAt(this % 16); | |
}; | |
/** | |
* Returns a value indicating the sign of a number<br> | |
* @memberof! external:Number# | |
* @alias sign | |
* @return {Number} sign of a the number | |
*/ | |
Number.prototype.sign = function() { | |
return this < 0 ? -1 : (this > 0 ? 1 : 0); | |
}; | |
/** | |
* Converts an angle in degrees to an angle in radians | |
* @memberof! external:Number# | |
* @alias degToRad | |
* @param {Number} [angle="angle"] angle in degrees | |
* @return {Number} corresponding angle in radians | |
* @example | |
* // convert a specific angle | |
* Number.prototype.degToRad (60); // return 1.0471... | |
* // convert object value | |
* var num = 60 | |
* num.degToRad(); // return 1.0471... | |
*/ | |
Number.prototype.degToRad = function (angle) { | |
return (angle||this) / 180.0 * Math.PI; | |
}; | |
/** | |
* Converts an angle in radians to an angle in degrees. | |
* @memberof! external:Number# | |
* @alias radToDeg | |
* @param {Number} [angle="angle"] angle in radians | |
* @return {Number} corresponding angle in degrees | |
* @example | |
* // convert a specific angle | |
* Number.prototype.radToDeg (1.0471975511965976); // return 59.9999... | |
* // convert object value | |
* num = 1.0471975511965976 | |
* Math.ceil(num.radToDeg()); // return 60 | |
*/ | |
Number.prototype.radToDeg = function (angle) { | |
return (angle||this) * (180.0 / Math.PI); | |
}; | |
/** | |
* The built in Array Object | |
* @external Array | |
* @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array|Array} | |
*/ | |
/** | |
* Remove the specified object from the Array<br> | |
* @memberof! external:Array# | |
* @alias remove | |
* @param {Object} object to be removed | |
*/ | |
Array.prototype.remove = function(obj) { | |
var i = Array.prototype.indexOf.call(this, obj); | |
if( i !== -1 ) { | |
Array.prototype.splice.call(this, i, 1); | |
} | |
return this; | |
}; | |
if (!Array.prototype.forEach) { | |
/** | |
* provide a replacement for browsers that don't | |
* support Array.prototype.forEach (JS 1.6) | |
* @ignore | |
*/ | |
Array.prototype.forEach = function (callback, scope) { | |
for (var i = 0, j = this.length; j--; i++) { | |
callback.call(scope || this, this[i], i, this); | |
} | |
}; | |
} | |
Object.defineProperty(me, "initialized", { | |
get : function get() { | |
return me_initialized; | |
} | |
}); | |
/* | |
* me init stuff | |
*/ | |
function _init_ME() { | |
// don't do anything if already initialized (should not happen anyway) | |
if (me_initialized) { | |
return; | |
} | |
// check the device capabilites | |
me.device._check(); | |
// initialize me.save | |
me.save._init(); | |
// enable/disable the cache | |
me.loader.setNocache(document.location.href.match(/\?nocache/)||false); | |
// init the FPS counter if needed | |
me.timer.init(); | |
// create a new map reader instance | |
me.mapReader = new me.TMXMapReader(); | |
// init the App Manager | |
me.state.init(); | |
// init the Entity Pool | |
me.entityPool.init(); | |
// init the level Director | |
me.levelDirector.reset(); | |
me_initialized = true; | |
} | |
/** | |
* me.game represents your current game, it contains all the objects, tilemap layers,<br> | |
* current viewport, collision map, etc...<br> | |
* me.game is also responsible for updating (each frame) the object status and draw them<br> | |
* @namespace me.game | |
* @memberOf me | |
*/ | |
me.game = (function() { | |
// hold public stuff in our singletong | |
var api = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
// ref to the "system" context | |
var frameBuffer = null; | |
// flag to redraw the sprites | |
var initialized = false; | |
// to keep track of deferred stuff | |
var pendingRemove = null; | |
// to know when we have to refresh the display | |
var isDirty = true; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* a reference to the game viewport. | |
* @public | |
* @type me.Viewport | |
* @name viewport | |
* @memberOf me.game | |
*/ | |
api.viewport = null; | |
/** | |
* a reference to the game collision Map | |
* @public | |
* @type me.TMXLayer | |
* @name collisionMap | |
* @memberOf me.game | |
*/ | |
api.collisionMap = null; | |
/** | |
* a reference to the game current level | |
* @public | |
* @type me.TMXTileMap | |
* @name currentLevel | |
* @memberOf me.game | |
*/ | |
api.currentLevel = null; | |
/** | |
* a reference to the game world <br> | |
* a world is a virtual environment containing all the game objects | |
* @public | |
* @type me.ObjectContainer | |
* @name world | |
* @memberOf me.game | |
*/ | |
api.world = null; | |
/** | |
* when true, all objects will be added under the root world container <br> | |
* when false, a `me.ObjectContainer` object will be created for each corresponding `TMXObjectGroup` | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name mergeGroup | |
* @memberOf me.game | |
*/ | |
api.mergeGroup = true; | |
/** | |
* The property of should be used when sorting entities <br> | |
* value : "x", "y", "z" (default: "z") | |
* @public | |
* @type String | |
* @name sortOn | |
* @memberOf me.game | |
*/ | |
api.sortOn = "z"; | |
/** | |
* default layer renderer | |
* @private | |
* @ignore | |
* @type me.TMXRenderer | |
* @name renderer | |
* @memberOf me.game | |
*/ | |
api.renderer = null; | |
// FIX ME : put this somewhere else | |
api.NO_OBJECT = 0; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name ENEMY_OBJECT | |
* @memberOf me.game | |
*/ | |
api.ENEMY_OBJECT = 1; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name COLLECTABLE_OBJECT | |
* @memberOf me.game | |
*/ | |
api.COLLECTABLE_OBJECT = 2; | |
/** | |
* Default object type constant.<br> | |
* See type property of the returned collision vector. | |
* @constant | |
* @name ACTION_OBJECT | |
* @memberOf me.game | |
*/ | |
api.ACTION_OBJECT = 3; // door, etc... | |
/** | |
* Fired when a level is fully loaded and <br> | |
* and all entities instantiated. <br> | |
* Additionnaly the level id will also be passed | |
* to the called function. | |
* @public | |
* @callback | |
* @name onLevelLoaded | |
* @memberOf me.game | |
* @example | |
* // call myFunction() everytime a level is loaded | |
* me.game.onLevelLoaded = this.myFunction.bind(this); | |
*/ | |
api.onLevelLoaded = null; | |
/** | |
* Initialize the game manager | |
* @name init | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
* @param {Number} [width="full size of the created canvas"] width of the canvas | |
* @param {Number} [height="full size of the created canvas"] width of the canvas | |
* init function. | |
*/ | |
api.init = function(width, height) { | |
if (!initialized) { | |
// if no parameter specified use the system size | |
width = width || me.video.getWidth(); | |
height = height || me.video.getHeight(); | |
// create a defaut viewport of the same size | |
api.viewport = new me.Viewport(0, 0, width, height); | |
//the root object of our world is an entity container | |
api.world = new me.ObjectContainer(0,0, width, height); | |
// give it a name | |
api.world.name = 'rootContainer'; | |
// get a ref to the screen buffer | |
frameBuffer = me.video.getSystemContext(); | |
// publish init notification | |
me.event.publish(me.event.GAME_INIT); | |
// make display dirty by default | |
isDirty = true; | |
// set as initialized | |
initialized = true; | |
} | |
}; | |
/** | |
* reset the game Object manager<p> | |
* destroy all current objects | |
* @name reset | |
* @memberOf me.game | |
* @public | |
* @function | |
*/ | |
api.reset = function() { | |
// remove all objects | |
api.removeAll(); | |
// reset the viewport to zero ? | |
if (api.viewport) { | |
api.viewport.reset(); | |
} | |
// reset the transform matrix to the normal one | |
frameBuffer.setTransform(1, 0, 0, 1, 0, 0); | |
// dummy current level | |
api.currentLevel = {pos:{x:0,y:0}}; | |
}; | |
/** | |
* Load a TMX level | |
* @name loadTMXLevel | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.loadTMXLevel = function(level) { | |
// disable auto-sort | |
api.world.autoSort = false; | |
// load our map | |
api.currentLevel = level; | |
// get the collision map | |
api.collisionMap = api.currentLevel.getLayerByName("collision"); | |
if (!api.collisionMap || !api.collisionMap.isCollisionMap) { | |
console.error("WARNING : no collision map detected"); | |
} | |
// add all defined layers | |
var layers = api.currentLevel.getLayers(); | |
for ( var i = layers.length; i--;) { | |
if (layers[i].visible) { | |
// only if visible | |
api.add(layers[i]); | |
} | |
} | |
// change the viewport limit | |
api.viewport.setBounds(Math.max(api.currentLevel.width, api.viewport.width), | |
Math.max(api.currentLevel.height, api.viewport.height)); | |
// game world as default container | |
var targetContainer = api.world; | |
// load all ObjectGroup and Object definition | |
var objectGroups = api.currentLevel.getObjectGroups(); | |
for ( var g = 0; g < objectGroups.length; g++) { | |
var group = objectGroups[g]; | |
if (api.mergeGroup === false) { | |
// create a new container with Infinite size (?) | |
// note: initial position and size seems to be meaningless in Tiled | |
// https://github.com/bjorn/tiled/wiki/TMX-Map-Format : | |
// x: Defaults to 0 and can no longer be changed in Tiled Qt. | |
// y: Defaults to 0 and can no longer be changed in Tiled Qt. | |
// width: The width of the object group in tiles. Meaningless. | |
// height: The height of the object group in tiles. Meaningless. | |
targetContainer = new me.ObjectContainer(); | |
// set additional properties | |
targetContainer.name = group.name; | |
targetContainer.visible = group.visible; | |
targetContainer.z = group.z; | |
targetContainer.setOpacity(group.opacity); | |
// disable auto-sort | |
targetContainer.autoSort = false; | |
} | |
// iterate through the group and add all object into their | |
// corresponding target Container | |
for ( var o = 0; o < group.objects.length; o++) { | |
// TMX Object | |
var obj = group.objects[o]; | |
// create the corresponding entity | |
var entity = me.entityPool.newInstanceOf(obj.name, obj.x, obj.y, obj); | |
// ignore if the newInstanceOf function does not return a corresponding object | |
if (entity) { | |
// set the entity z order correspondingly to its parent container/group | |
entity.z = group.z; | |
//apply group opacity value to the child objects if group are merged | |
if (api.mergeGroup === true && entity.isRenderable === true) { | |
entity.setOpacity(entity.getOpacity() * group.opacity); | |
// and to child renderables if any | |
if (entity.renderable !== null) { | |
entity.renderable.setOpacity(entity.renderable.getOpacity() * group.opacity); | |
} | |
} | |
// add the entity into the target container | |
targetContainer.addChild(entity); | |
} | |
} | |
// if we created a new container | |
if (api.mergeGroup === false) { | |
// add our container to the world | |
api.world.addChild(targetContainer); | |
// re-enable auto-sort | |
targetContainer.autoSort = true; | |
} | |
} | |
// sort everything (recursively) | |
api.world.sort(true); | |
// re-enable auto-sort | |
api.world.autoSort = true; | |
// check if the map has different default (0,0) screen coordinates | |
if (api.currentLevel.pos.x !== api.currentLevel.pos.y) { | |
// translate the display accordingly | |
frameBuffer.translate( api.currentLevel.pos.x , api.currentLevel.pos.y ); | |
} | |
// fire the callback if defined | |
if (api.onLevelLoaded) { | |
api.onLevelLoaded.call(api.onLevelLoaded, level.name); | |
} | |
//publish the corresponding message | |
me.event.publish(me.event.LEVEL_LOADED, [level.name]); | |
}; | |
/** | |
* Manually add object to the game manager | |
* @deprecated @see me.game.world.addChild() | |
* @name add | |
* @memberOf me.game | |
* @param {me.ObjectEntity} obj Object to be added | |
* @param {Number} [z="obj.z"] z index | |
* @public | |
* @function | |
* @example | |
* // create a new object | |
* var obj = new MyObject(x, y) | |
* // add the object and force the z index of the current object | |
* me.game.add(obj, this.z); | |
*/ | |
api.add = function(object, zOrder) { | |
if (typeof(zOrder) !== 'undefined') { | |
object.z = zOrder; | |
} | |
// add the object in the game obj list | |
api.world.addChild(object); | |
}; | |
/** | |
* returns the list of entities with the specified name<br> | |
* as defined in Tiled (Name field of the Object Properties)<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByName | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} entityName entity name | |
* @return {me.ObjectEntity[]} Array of object entities | |
*/ | |
api.getEntityByName = function(entityName) { | |
return api.world.getEntityByProp("name", entityName); | |
}; | |
/** | |
* return the entity corresponding to the specified GUID<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByGUID | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} GUID entity GUID | |
* @return {me.ObjectEntity} Object Entity (or null if not found) | |
*/ | |
api.getEntityByGUID = function(guid) { | |
var obj = api.world.getEntityByProp("GUID", guid); | |
return (obj.length>0)?obj[0]:null; | |
}; | |
/** | |
* return the entity corresponding to the property and value<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object list each time | |
* @deprecated use me.game.world.getEntityByProp(); | |
* @name getEntityByProp | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {String} prop Property name | |
* @param {String} value Value of the property | |
* @return {me.ObjectEntity[]} Array of object entities | |
*/ | |
api.getEntityByProp = function(prop, value) { | |
return api.world.getEntityByProp(prop, value); | |
}; | |
/** | |
* Returns the entity container of the specified Child in the game world | |
* @name getEntityContainer | |
* @memberOf me.game | |
* @function | |
* @param {me.ObjectEntity} child | |
* @return {me.ObjectContainer} | |
*/ | |
api.getEntityContainer = function(child) { | |
return child.ancestor; | |
}; | |
/** | |
* remove the specific object from the world<br> | |
* `me.game.remove` will preserve object that defines the `isPersistent` flag | |
* `me.game.remove` will remove object at the end of the current frame | |
* @name remove | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be removed | |
* @param {Boolean} [force=false] Force immediate deletion.<br> | |
* <strong>WARNING</strong>: Not safe to force asynchronously (e.g. onCollision callbacks) | |
*/ | |
api.remove = function(obj, force) { | |
if (obj.ancestor) { | |
// remove the object from the object list | |
if (force===true) { | |
// force immediate object deletion | |
obj.ancestor.removeChild(obj); | |
} else { | |
// make it invisible (this is bad...) | |
obj.visible = obj.inViewport = false; | |
// wait the end of the current loop | |
/** @ignore */ | |
pendingRemove = (function (obj) { | |
// safety check in case the | |
// object was removed meanwhile | |
if (typeof obj.ancestor !== 'undefined') { | |
obj.ancestor.removeChild(obj); | |
} | |
pendingRemove = null; | |
}.defer(obj)); | |
} | |
} | |
}; | |
/** | |
* remove all objects<br> | |
* @name removeAll | |
* @memberOf me.game | |
* @param {Boolean} [force=false] Force immediate deletion.<br> | |
* <strong>WARNING</strong>: Not safe to force asynchronously (e.g. onCollision callbacks) | |
* @public | |
* @function | |
*/ | |
api.removeAll = function() { | |
//cancel any pending tasks | |
if (pendingRemove) { | |
clearTimeout(pendingRemove); | |
pendingRemove = null; | |
} | |
// destroy all objects in the root container | |
api.world.destroy(); | |
}; | |
/** | |
* Manually trigger the sort all the game objects.</p> | |
* Since version 0.9.9, all objects are automatically sorted, <br> | |
* except if a container autoSort property is set to false. | |
* @deprecated use me.game.world.sort(); | |
* @name sort | |
* @memberOf me.game | |
* @public | |
* @function | |
* @example | |
* // change the default sort property | |
* me.game.sortOn = "y"; | |
* // manuallly call me.game.sort with our sorting function | |
* me.game.sort(); | |
*/ | |
api.sort = function() { | |
api.world.sort(); | |
}; | |
/** | |
* Checks if the specified entity collides with others entities. | |
* @deprecated use me.game.world.collide(); | |
* @name collide | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
* @example | |
* // update player movement | |
* this.updateMovement(); | |
* | |
* // check for collision with other objects | |
* res = me.game.collide(this); | |
* | |
* // check if we collide with an enemy : | |
* if (res && (res.obj.type == me.game.ENEMY_OBJECT)) | |
* { | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* } | |
*/ | |
api.collide = function(objA, multiple) { | |
return api.world.collide (objA, multiple); | |
}; | |
/** | |
* Checks if the specified entity collides with others entities of the specified type. | |
* @deprecated use me.game.world.collideType(); | |
* @name collideType | |
* @memberOf me.game | |
* @public | |
* @function | |
* @param {me.ObjectEntity} obj Object to be tested for collision | |
* @param {String} type Entity type to be tested for collision (null to disable type check) | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
api.collideType = function(objA, type, multiple) { | |
return api.world.collideType (objA, type, multiple); | |
}; | |
/** | |
* force the redraw (not update) of all objects | |
* @name repaint | |
* @memberOf me.game | |
* @public | |
* @function | |
*/ | |
api.repaint = function() { | |
isDirty = true; | |
}; | |
/** | |
* update all objects of the game manager | |
* @name update | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.update = function() { | |
// update all objects | |
isDirty = api.world.update() || isDirty; | |
// update the camera/viewport | |
isDirty = api.viewport.update(isDirty) || isDirty; | |
return isDirty; | |
}; | |
/** | |
* draw all existing objects | |
* @name draw | |
* @memberOf me.game | |
* @private | |
* @ignore | |
* @function | |
*/ | |
api.draw = function() { | |
if (isDirty) { | |
// cache the viewport rendering position, so that other object | |
// can access it later (e,g. entityContainer when drawing floating objects) | |
api.viewport.screenX = api.viewport.pos.x + ~~api.viewport.offset.x; | |
api.viewport.screenY = api.viewport.pos.y + ~~api.viewport.offset.y; | |
// save the current context | |
frameBuffer.save(); | |
// translate by default to screen coordinates | |
frameBuffer.translate(-api.viewport.screenX, -api.viewport.screenY); | |
// substract the map offset to current the current pos | |
api.viewport.screenX -= api.currentLevel.pos.x; | |
api.viewport.screenY -= api.currentLevel.pos.y; | |
// update all objects, | |
// specifying the viewport as the rectangle area to redraw | |
api.world.draw(frameBuffer, api.viewport); | |
//restore context | |
frameBuffer.restore(); | |
// draw our camera/viewport | |
api.viewport.draw(frameBuffer); | |
} | |
isDirty = false; | |
}; | |
// return our object | |
return api; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013 melonJS | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* A singleton object representing the device capabilities and specific events | |
* @namespace me.device | |
* @memberOf me | |
*/ | |
me.device = (function() { | |
// defines object for holding public information/functionality. | |
var obj = {}; | |
// private properties | |
var accelInitialized = false; | |
var deviceOrientationInitialized = false; | |
var devicePixelRatio = null; | |
/** | |
* check the device capapbilities | |
* @ignore | |
*/ | |
obj._check = function() { | |
// detect audio capabilities (should be moved here too) | |
me.audio.detectCapabilities(); | |
// future proofing (MS) feature detection | |
me.device.pointerEnabled = navigator.pointerEnabled || navigator.msPointerEnabled; | |
navigator.maxTouchPoints = navigator.maxTouchPoints || navigator.msMaxTouchPoints || 0; | |
window.gesture = window.gesture || window.MSGesture; | |
// detect touch capabilities | |
me.device.touch = ('createTouch' in document) || ('ontouchstart' in window) || | |
(navigator.isCocoonJS) || (navigator.maxTouchPoints > 0); | |
// detect platform | |
me.device.isMobile = me.device.ua.match(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone|Mobi/i) || false; | |
// accelerometer detection | |
me.device.hasAccelerometer = ( | |
(typeof (window.DeviceMotionEvent) !== 'undefined') || ( | |
(typeof (window.Windows) !== 'undefined') && | |
(typeof (Windows.Devices.Sensors.Accelerometer) === 'function') | |
) | |
); | |
if (window.DeviceOrientationEvent) { | |
me.device.hasDeviceOrientation = true; | |
} | |
try { | |
obj.localStorage = !!window.localStorage; | |
} catch (e) { | |
// the above generates an exception when cookies are blocked | |
obj.localStorage = false; | |
} | |
}; | |
// ----- PUBLIC Properties & Functions ----- | |
// Browser capabilities | |
/** | |
* Browser User Agent | |
* @type Boolean | |
* @readonly | |
* @name ua | |
* @memberOf me.device | |
*/ | |
obj.ua = navigator.userAgent; | |
/** | |
* Browser Audio capabilities | |
* @type Boolean | |
* @readonly | |
* @name sound | |
* @memberOf me.device | |
*/ | |
obj.sound = false; | |
/** | |
* Browser Local Storage capabilities <br> | |
* (this flag will be set to false if cookies are blocked) | |
* @type Boolean | |
* @readonly | |
* @name localStorage | |
* @memberOf me.device | |
*/ | |
obj.localStorage = false; | |
/** | |
* Browser accelerometer capabilities | |
* @type Boolean | |
* @readonly | |
* @name hasAccelerometer | |
* @memberOf me.device | |
*/ | |
obj.hasAccelerometer = false; | |
/** | |
* Browser device orientation | |
* @type Boolean | |
* @readonly | |
* @name hasDeviceOrientation | |
* @memberOf me.device | |
*/ | |
obj.hasDeviceOrientation = false; | |
/** | |
* Browser Base64 decoding capability | |
* @type Boolean | |
* @readonly | |
* @name nativeBase64 | |
* @memberOf me.device | |
*/ | |
obj.nativeBase64 = (typeof(window.atob) === 'function'); | |
/** | |
* Touch capabilities | |
* @type Boolean | |
* @readonly | |
* @name touch | |
* @memberOf me.device | |
*/ | |
obj.touch = false; | |
/** | |
* equals to true if a mobile device <br> | |
* (Android | iPhone | iPad | iPod | BlackBerry | Windows Phone) | |
* @type Boolean | |
* @readonly | |
* @name isMobile | |
* @memberOf me.device | |
*/ | |
obj.isMobile = false; | |
/** | |
* The device current orientation status. <br> | |
* 0 : default orientation<br> | |
* 90 : 90 degrees clockwise from default<br> | |
* -90 : 90 degrees anti-clockwise from default<br> | |
* 180 : 180 degrees from default | |
* @type Number | |
* @readonly | |
* @name orientation | |
* @memberOf me.device | |
*/ | |
obj.orientation = 0; | |
/** | |
* contains the g-force acceleration along the x-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationX | |
* @memberOf me.device | |
*/ | |
obj.accelerationX = 0; | |
/** | |
* contains the g-force acceleration along the y-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationY | |
* @memberOf me.device | |
*/ | |
obj.accelerationY = 0; | |
/** | |
* contains the g-force acceleration along the z-axis. | |
* @public | |
* @type Number | |
* @readonly | |
* @name accelerationZ | |
* @memberOf me.device | |
*/ | |
obj.accelerationZ = 0; | |
/** | |
* Device orientation Gamma property. Gives angle on tilting a portrait held phone left or right | |
* @public | |
* @type Number | |
* @readonly | |
* @name gamma | |
* @memberOf me.device | |
*/ | |
obj.gamma = 0; | |
/** | |
* Device orientation Beta property. Gives angle on tilting a portrait held phone forward or backward | |
* @public | |
* @type Number | |
* @readonly | |
* @name beta | |
* @memberOf me.device | |
*/ | |
obj.beta = 0; | |
/** | |
* Device orientation Alpha property. Gives angle based on the rotation of the phone around its z axis. | |
* The z-axis is perpendicular to the phone, facing out from the center of the screen. | |
* @public | |
* @type Number | |
* @readonly | |
* @name alpha | |
* @memberOf me.device | |
*/ | |
obj.alpha = 0; | |
/** | |
* return the device pixel ratio | |
* @name getPixelRatio | |
* @memberOf me.device | |
* @function | |
*/ | |
obj.getPixelRatio = function() { | |
if (devicePixelRatio===null) { | |
var _context = me.video.getScreenContext(); | |
var _devicePixelRatio = window.devicePixelRatio || 1, | |
_backingStoreRatio = _context.webkitBackingStorePixelRatio || | |
_context.mozBackingStorePixelRatio || | |
_context.msBackingStorePixelRatio || | |
_context.oBackingStorePixelRatio || | |
_context.backingStorePixelRatio || 1; | |
devicePixelRatio = _devicePixelRatio / _backingStoreRatio; | |
} | |
return devicePixelRatio; | |
}; | |
/** | |
* return the device storage | |
* @name getStorage | |
* @memberOf me.device | |
* @function | |
* @param {String} [type="local"] | |
* @return me.save object | |
*/ | |
obj.getStorage = function(type) { | |
type = type || "local"; | |
switch (type) { | |
case "local" : | |
return me.save; | |
default : | |
break; | |
} | |
throw "melonJS : storage type " + type + " not supported"; | |
}; | |
/** | |
* event management (Accelerometer) | |
* http://www.mobilexweb.com/samples/ball.html | |
* http://www.mobilexweb.com/blog/safari-ios-accelerometer-websockets-html5 | |
* @ignore | |
*/ | |
function onDeviceMotion(e) { | |
if (e.reading) { | |
// For Windows 8 devices | |
obj.accelerationX = e.reading.accelerationX; | |
obj.accelerationY = e.reading.accelerationY; | |
obj.accelerationZ = e.reading.accelerationZ; | |
} else { | |
// Accelerometer information | |
obj.accelerationX = e.accelerationIncludingGravity.x; | |
obj.accelerationY = e.accelerationIncludingGravity.y; | |
obj.accelerationZ = e.accelerationIncludingGravity.z; | |
} | |
} | |
function onDeviceRotate(e) { | |
obj.gamma = e.gamma; | |
obj.beta = e.beta; | |
obj.alpha = e.alpha; | |
} | |
/** | |
* watch Accelerator event | |
* @name watchAccelerometer | |
* @memberOf me.device | |
* @public | |
* @function | |
* @return {Boolean} false if not supported by the device | |
*/ | |
obj.watchAccelerometer = function () { | |
if (me.device.hasAccelerometer) { | |
if (!accelInitialized) { | |
if (typeof Windows === 'undefined') { | |
// add a listener for the devicemotion event | |
window.addEventListener('devicemotion', onDeviceMotion, false); | |
} else { | |
// On Windows 8 Device | |
var accelerometer = Windows.Devices.Sensors.Accelerometer.getDefault(); | |
if (accelerometer) { | |
// Capture event at regular intervals | |
var minInterval = accelerometer.minimumReportInterval; | |
var Interval = minInterval >= 16 ? minInterval : 25; | |
accelerometer.reportInterval = Interval; | |
accelerometer.addEventListener('readingchanged', onDeviceMotion, false); | |
} | |
} | |
accelInitialized = true; | |
} | |
return true; | |
} | |
return false; | |
}; | |
/** | |
* unwatch Accelerometor event | |
* @name unwatchAccelerometer | |
* @memberOf me.device | |
* @public | |
* @function | |
*/ | |
obj.unwatchAccelerometer = function() { | |
if (accelInitialized) { | |
if (typeof Windows === 'undefined') { | |
// add a listener for the mouse | |
window.removeEventListener('devicemotion', onDeviceMotion, false); | |
} else { | |
// On Windows 8 Devices | |
var accelerometer = Windows.Device.Sensors.Accelerometer.getDefault(); | |
accelerometer.removeEventListener('readingchanged', onDeviceMotion, false); | |
} | |
accelInitialized = false; | |
} | |
}; | |
/** | |
* watch the device orientation event | |
* @name watchDeviceOrientation | |
* @memberOf me.device | |
* @public | |
* @function | |
* @return {Boolean} false if not supported by the device | |
*/ | |
obj.watchDeviceOrientation = function() { | |
if(me.device.hasDeviceOrientation && !deviceOrientationInitialized) { | |
window.addEventListener('deviceorientation', onDeviceRotate, false); | |
deviceOrientationInitialized = true; | |
} | |
return false; | |
}; | |
/** | |
* unwatch Device orientation event | |
* @name unwatchDeviceOrientation | |
* @memberOf me.device | |
* @public | |
* @function | |
*/ | |
obj.unwatchDeviceOrientation = function() { | |
if(deviceOrientationInitialized) { | |
window.removeEventListener('deviceorientation', onDeviceRotate, false); | |
deviceOrientationInitialized = false; | |
} | |
}; | |
return obj; | |
})(); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013 melonJS | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* a Timer object to manage time function (FPS, Game Tick, Time...)<p> | |
* There is no constructor function for me.timer | |
* @namespace me.timer | |
* @memberOf me | |
*/ | |
me.timer = (function() { | |
// hold public stuff in our api | |
var api = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
//hold element to display fps | |
var framecount = 0; | |
var framedelta = 0; | |
/* fps count stuff */ | |
var last = 0; | |
var now = 0; | |
var delta = 0; | |
var step = Math.ceil(1000 / me.sys.fps); // ROUND IT ? | |
// define some step with some margin | |
var minstep = (1000 / me.sys.fps) * 1.25; // IS IT NECESSARY? | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* last game tick value | |
* @public | |
* @type Int | |
* @name tick | |
* @memberOf me.timer | |
*/ | |
api.tick = 1.0; | |
/** | |
* last measured fps rate | |
* @public | |
* @type Int | |
* @name fps | |
* @memberOf me.timer | |
*/ | |
api.fps = 0; | |
/** | |
* init the timer | |
* @ignore | |
*/ | |
api.init = function() { | |
// reset variables to initial state | |
api.reset(); | |
}; | |
/** | |
* reset time (e.g. usefull in case of pause) | |
* @name reset | |
* @memberOf me.timer | |
* @ignore | |
* @function | |
*/ | |
api.reset = function() { | |
// set to "now" | |
now = last = Date.now(); | |
// reset delta counting variables | |
framedelta = 0; | |
framecount = 0; | |
}; | |
/** | |
* Return the current time, in milliseconds elapsed between midnight, January 1, 1970, and the current date and time. | |
* @name getTime | |
* @memberOf me.timer | |
* @return {Number} | |
* @function | |
*/ | |
api.getTime = function() { | |
return now; | |
}; | |
/** | |
* compute the actual frame time and fps rate | |
* @name computeFPS | |
* @ignore | |
* @memberOf me.timer | |
* @function | |
*/ | |
api.countFPS = function() { | |
framecount++; | |
framedelta += delta; | |
if (framecount % 10 === 0) { | |
this.fps = (~~((1000 * framecount) / framedelta)).clamp(0, me.sys.fps); | |
framedelta = 0; | |
framecount = 0; | |
} | |
}; | |
/** | |
* update game tick | |
* should be called once a frame | |
* @ignore | |
*/ | |
api.update = function() { | |
last = now; | |
now = Date.now(); | |
delta = (now - last); | |
// get the game tick | |
api.tick = (delta > minstep && me.sys.interpolation) ? delta / step : 1; | |
}; | |
// return our apiect | |
return api; | |
})(); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* a generic 2D Vector Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {Number} [x=0] x value of the vector | |
* @param {Number} [y=0] y value of the vector | |
*/ | |
me.Vector2d = Object.extend( | |
/** @scope me.Vector2d.prototype */ | |
{ | |
/** | |
* x value of the vector | |
* @public | |
* @type Number | |
* @name x | |
* @memberOf me.Vector2d | |
*/ | |
x : 0, | |
/** | |
* y value of the vector | |
* @public | |
* @type Number | |
* @name y | |
* @memberOf me.Vector2d | |
*/ | |
y : 0, | |
/** @ignore */ | |
init : function(x, y) { | |
this.x = x || 0; | |
this.y = y || 0; | |
}, | |
/** | |
* set the Vector x and y properties to the given values<br> | |
* @name set | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} x | |
* @param {Number} y | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
set : function(x, y) { | |
this.x = x; | |
this.y = y; | |
return this; | |
}, | |
/** | |
* set the Vector x and y properties to 0 | |
* @name setZero | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
setZero : function() { | |
return this.set(0, 0); | |
}, | |
/** | |
* set the Vector x and y properties using the passed vector | |
* @name setV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
setV : function(v) { | |
this.x = v.x; | |
this.y = v.y; | |
return this; | |
}, | |
/** | |
* Add the passed vector to this vector | |
* @name add | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
add : function(v) { | |
this.x += v.x; | |
this.y += v.y; | |
return this; | |
}, | |
/** | |
* Substract the passed vector to this vector | |
* @name sub | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
sub : function(v) { | |
this.x -= v.x; | |
this.y -= v.y; | |
return this; | |
}, | |
/** | |
* Multiply this vector values by the passed vector | |
* @name scale | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
scale : function(v) { | |
this.x *= v.x; | |
this.y *= v.y; | |
return this; | |
}, | |
/** | |
* Divide this vector values by the passed value | |
* @name div | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} value | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
div : function(n) { | |
this.x /= n; | |
this.y /= n; | |
return this; | |
}, | |
/** | |
* Update this vector values to absolute values | |
* @name abs | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
abs : function() { | |
if (this.x < 0) | |
this.x = -this.x; | |
if (this.y < 0) | |
this.y = -this.y; | |
return this; | |
}, | |
/** | |
* Clamp the vector value within the specified value range | |
* @name clamp | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} low | |
* @param {Number} high | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
clamp : function(low, high) { | |
return new me.Vector2d(this.x.clamp(low, high), this.y.clamp(low, high)); | |
}, | |
/** | |
* Clamp this vector value within the specified value range | |
* @name clampSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @param {Number} low | |
* @param {Number} high | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
clampSelf : function(low, high) { | |
this.x = this.x.clamp(low, high); | |
this.y = this.y.clamp(low, high); | |
return this; | |
}, | |
/** | |
* Update this vector with the minimum value between this and the passed vector | |
* @name minV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
minV : function(v) { | |
this.x = this.x < v.x ? this.x : v.x; | |
this.y = this.y < v.y ? this.y : v.y; | |
return this; | |
}, | |
/** | |
* Update this vector with the maximum value between this and the passed vector | |
* @name maxV | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
maxV : function(v) { | |
this.x = this.x > v.x ? this.x : v.x; | |
this.y = this.y > v.y ? this.y : v.y; | |
return this; | |
}, | |
/** | |
* Floor the vector values | |
* @name floor | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
floor : function() { | |
return new me.Vector2d(~~this.x, ~~this.y); | |
}, | |
/** | |
* Floor this vector values | |
* @name floorSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
floorSelf : function() { | |
this.x = ~~this.x; | |
this.y = ~~this.y; | |
return this; | |
}, | |
/** | |
* Ceil the vector values | |
* @name ceil | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
ceil : function() { | |
return new me.Vector2d(Math.ceil(this.x), Math.ceil(this.y)); | |
}, | |
/** | |
* Ceil this vector values | |
* @name ceilSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
ceilSelf : function() { | |
this.x = Math.ceil(this.x); | |
this.y = Math.ceil(this.y); | |
return this; | |
}, | |
/** | |
* Negate the vector values | |
* @name negate | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
negate : function() { | |
return new me.Vector2d(-this.x, -this.y); | |
}, | |
/** | |
* Negate this vector values | |
* @name negateSelf | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
negateSelf : function() { | |
this.x = -this.x; | |
this.y = -this.y; | |
return this; | |
}, | |
/** | |
* Copy the x,y values of the passed vector to this one | |
* @name copy | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {me.Vector2d} Reference to this object for method chaining | |
*/ | |
copy : function(v) { | |
this.x = v.x; | |
this.y = v.y; | |
return this; | |
}, | |
/** | |
* return true if the two vectors are the same | |
* @name equals | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Boolean} | |
*/ | |
equals : function(v) { | |
return ((this.x === v.x) && (this.y === v.y)); | |
}, | |
/** | |
* return the length (magnitude) of this vector | |
* @name length | |
* @memberOf me.Vector2d | |
* @function | |
* @return {Number} | |
*/ | |
length : function() { | |
return Math.sqrt(this.x * this.x + this.y * this.y); | |
}, | |
/** | |
* normalize this vector (scale the vector so that its magnitude is 1) | |
* @name normalize | |
* @memberOf me.Vector2d | |
* @function | |
* @return {Number} | |
*/ | |
normalize : function() { | |
var len = this.length(); | |
// some limit test | |
if (len < Number.MIN_VALUE) { | |
return 0.0; | |
} | |
var invL = 1.0 / len; | |
this.x *= invL; | |
this.y *= invL; | |
return len; | |
}, | |
/** | |
* return the dot product of this vector and the passed one | |
* @name dotProduct | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} | |
*/ | |
dotProduct : function(/**me.Vector2d*/ v) { | |
return this.x * v.x + this.y * v.y; | |
}, | |
/** | |
* return the distance between this vector and the passed one | |
* @name distance | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} | |
*/ | |
distance : function(v) { | |
return Math.sqrt((this.x - v.x) * (this.x - v.x) + (this.y - v.y) * (this.y - v.y)); | |
}, | |
/** | |
* return the angle between this vector and the passed one | |
* @name angle | |
* @memberOf me.Vector2d | |
* @function | |
* @param {me.Vector2d} v | |
* @return {Number} angle in radians | |
*/ | |
angle : function(v) { | |
return Math.atan2((v.y - this.y), (v.x - this.x)); | |
}, | |
/** | |
* return a clone copy of this vector | |
* @name clone | |
* @memberOf me.Vector2d | |
* @function | |
* @return {me.Vector2d} new me.Vector2d | |
*/ | |
clone : function() { | |
return new me.Vector2d(this.x, this.y); | |
}, | |
/** | |
* convert the object to a string representation | |
* @name toString | |
* @memberOf me.Vector2d | |
* @function | |
* @return {String} | |
*/ | |
toString : function() { | |
return 'x:' + this.x + ',y:' + this.y; | |
} | |
}); | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/************************************************************************************/ | |
/* */ | |
/* a rectangle Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a rectangle Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v x,y position of the rectange | |
* @param {Number} w width of the rectangle | |
* @param {Number} h height of the rectangle | |
*/ | |
me.Rect = Object.extend( | |
/** @scope me.Rect.prototype */ { | |
/** | |
* position of the Rectange | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.Rect | |
*/ | |
pos : null, | |
/** | |
* allow to reduce the collision box size<p> | |
* while keeping the original position vector (pos)<p> | |
* corresponding to the entity<p> | |
* colPos is a relative offset to pos | |
* @ignore | |
* @type me.Vector2d | |
* @name colPos | |
* @memberOf me.Rect | |
* @see me.Rect#adjustSize | |
*/ | |
colPos : null, | |
/** | |
* left coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name left | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* right coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name right | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* bottom coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name bottom | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* top coordinate of the Rectange<br> | |
* takes in account the adjusted size of the rectangle (if set) | |
* @public | |
* @type Int | |
* @name top | |
* @memberOf me.Rect | |
*/ | |
// define later in the constructor | |
/** | |
* width of the Rectange | |
* @public | |
* @type Int | |
* @name width | |
* @memberOf me.Rect | |
*/ | |
width : 0, | |
/** | |
* height of the Rectange | |
* @public | |
* @type Int | |
* @name height | |
* @memberOf me.Rect | |
*/ | |
height : 0, | |
// half width/height | |
hWidth : 0, | |
hHeight : 0, | |
// the shape type | |
shapeType : "Rectangle", | |
/* | |
* will be replaced by pos and replace colPos in 1.0.0 :) | |
* @ignore | |
*/ | |
offset: null, | |
/** @ignore */ | |
init : function(v, w, h) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
this.pos.setV(v); | |
if (this.offset === null) { | |
this.offset = new me.Vector2d(); | |
} | |
this.offset.set(0, 0); | |
// allow to reduce the hitbox size | |
// while on keeping the original pos vector | |
// corresponding to the entity | |
if (this.colPos === null) { | |
this.colPos = new me.Vector2d(); | |
} | |
this.colPos.setV(0, 0); | |
this.width = w; | |
this.height = h; | |
// half width/height | |
this.hWidth = ~~(w / 2); | |
this.hHeight = ~~(h / 2); | |
// redefine some properties to ease our life when getting the rectangle coordinates | |
Object.defineProperty(this, "left", { | |
get : function() { | |
return this.pos.x; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "right", { | |
get : function() { | |
return this.pos.x + this.width; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "top", { | |
get : function() { | |
return this.pos.y; | |
}, | |
configurable : true | |
}); | |
Object.defineProperty(this, "bottom", { | |
get : function() { | |
return this.pos.y + this.height; | |
}, | |
configurable : true | |
}); | |
}, | |
/** | |
* set new value to the rectangle | |
* @name set | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} v x,y position for the rectangle | |
* @param {Number} w width of the rectangle | |
* @param {Number} h height of the rectangle | |
*/ | |
set : function(v, w, h) { | |
this.pos.setV(v); | |
this.width = w; | |
this.height = h; | |
this.hWidth = ~~(w / 2); | |
this.hHeight = ~~(h / 2); | |
//reset offset | |
this.offset.set(0, 0); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.Rect | |
* @function | |
* @return {me.Rect} new rectangle | |
*/ | |
getBounds : function() { | |
return this.clone(); | |
}, | |
/** | |
* clone this rectangle | |
* @name clone | |
* @memberOf me.Rect | |
* @function | |
* @return {me.Rect} new rectangle | |
*/ | |
clone : function() { | |
return new me.Rect(this.pos.clone(), this.width, this.height); | |
}, | |
/** | |
* translate the rect by the specified offset | |
* @name translate | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x offset | |
* @param {Number} y y offset | |
* @return {me.Rect} this rectangle | |
*/ | |
translate : function(x, y) { | |
this.pos.x+=x; | |
this.pos.y+=y; | |
return this; | |
}, | |
/** | |
* translate the rect by the specified vector | |
* @name translateV | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} v vector offset | |
* @return {me.Rect} this rectangle | |
*/ | |
translateV : function(v) { | |
this.pos.add(v); | |
return this; | |
}, | |
/** | |
* merge this rectangle with another one | |
* @name union | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect other rectangle to union with | |
* @return {me.Rect} the union(ed) rectangle | |
*/ | |
union : function(/** {me.Rect} */ r) { | |
var x1 = Math.min(this.pos.x, r.pos.x); | |
var y1 = Math.min(this.pos.y, r.pos.y); | |
this.width = Math.ceil( Math.max(this.pos.x + this.width, r.pos.x + r.width) - x1 ); | |
this.height = Math.ceil( Math.max(this.pos.y + this.height, r.pos.y + r.height) - y1 ); | |
this.hWidth = ~~(this.width / 2); | |
this.hHeight = ~~(this.height / 2); | |
this.pos.x = ~~x1; | |
this.pos.y = ~~y1; | |
return this; | |
}, | |
/** | |
* update the size of the collision rectangle<br> | |
* the colPos Vector is then set as a relative offset to the initial position (pos)<br> | |
* <img src="images/me.Rect.colpos.png"/> | |
* @name adjustSize | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x offset (specify -1 to not change the width) | |
* @param {Number} w width of the hit box | |
* @param {Number} y y offset (specify -1 to not change the height) | |
* @param {Number} h height of the hit box | |
*/ | |
adjustSize : function(x, w, y, h) { | |
if (x !== -1) { | |
this.colPos.x = x; | |
this.width = w; | |
this.hWidth = ~~(this.width / 2); | |
// avoid Property definition if not necessary | |
if (this.left !== this.pos.x + this.colPos.x) { | |
// redefine our properties taking colPos into account | |
Object.defineProperty(this, "left", { | |
get : function() { | |
return this.pos.x + this.colPos.x; | |
}, | |
configurable : true | |
}); | |
} | |
if (this.right !== this.pos.x + this.colPos.x + this.width) { | |
Object.defineProperty(this, "right", { | |
get : function() { | |
return this.pos.x + this.colPos.x + this.width; | |
}, | |
configurable : true | |
}); | |
} | |
} | |
if (y !== -1) { | |
this.colPos.y = y; | |
this.height = h; | |
this.hHeight = ~~(this.height / 2); | |
// avoid Property definition if not necessary | |
if (this.top !== this.pos.y + this.colPos.y) { | |
// redefine our properties taking colPos into account | |
Object.defineProperty(this, "top", { | |
get : function() { | |
return this.pos.y + this.colPos.y; | |
}, | |
configurable : true | |
}); | |
} | |
if (this.bottom !== this.pos.y + this.colPos.y + this.height) { | |
Object.defineProperty(this, "bottom", { | |
get : function() { | |
return this.pos.y + this.colPos.y + this.height; | |
}, | |
configurable : true | |
}); | |
} | |
} | |
}, | |
/** | |
* | |
* flip on X axis | |
* usefull when used as collision box, in a non symetric way | |
* @ignore | |
* @param sw the sprite width | |
*/ | |
flipX : function(sw) { | |
this.colPos.x = sw - this.width - this.colPos.x; | |
this.hWidth = ~~(this.width / 2); | |
}, | |
/** | |
* | |
* flip on Y axis | |
* usefull when used as collision box, in a non symetric way | |
* @ignore | |
* @param sh the height width | |
*/ | |
flipY : function(sh) { | |
this.colPos.y = sh - this.height - this.colPos.y; | |
this.hHeight = ~~(this.height / 2); | |
}, | |
/** | |
* return true if this rectangle is equal to the specified one | |
* @name equals | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} | |
*/ | |
equals : function(r) { | |
return (this.left === r.left && | |
this.right === r.right && | |
this.top === r.top && | |
this.bottom === r.bottom); | |
}, | |
/** | |
* check if this rectangle is intersecting with the specified one | |
* @name overlaps | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if overlaps | |
*/ | |
overlaps : function(r) { | |
return (this.left < r.right && | |
r.left < this.right && | |
this.top < r.bottom && | |
r.top < this.bottom); | |
}, | |
/** | |
* check if this rectangle is within the specified one | |
* @name within | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if within | |
*/ | |
within: function(r) { | |
return (r.left <= this.left && | |
r.right >= this.right && | |
r.top <= this.top && | |
r.bottom >= this.bottom); | |
}, | |
/** | |
* check if this rectangle contains the specified one | |
* @name contains | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Rect} rect | |
* @return {Boolean} true if contains | |
*/ | |
contains: function(r) { | |
return (r.left >= this.left && | |
r.right <= this.right && | |
r.top >= this.top && | |
r.bottom <= this.bottom); | |
}, | |
/** | |
* check if this rectangle contains the specified point | |
* @name containsPointV | |
* @memberOf me.Rect | |
* @function | |
* @param {me.Vector2d} point | |
* @return {Boolean} true if contains | |
*/ | |
containsPointV: function(v) { | |
return this.containsPoint(v.x, v.y); | |
}, | |
/** | |
* check if this rectangle contains the specified point | |
* @name containsPoint | |
* @memberOf me.Rect | |
* @function | |
* @param {Number} x x coordinate | |
* @param {Number} y y coordinate | |
* @return {Boolean} true if contains | |
*/ | |
containsPoint: function(x, y) { | |
return (x >= this.left && x <= this.right && | |
(y >= this.top) && y <= this.bottom); | |
}, | |
/** | |
* AABB vs AABB collission dectection<p> | |
* If there was a collision, the return vector will contains the following values: | |
* @example | |
* if (v.x != 0 || v.y != 0) | |
* { | |
* if (v.x != 0) | |
* { | |
* // x axis | |
* if (v.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (v.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* | |
* } | |
* @ignore | |
* @param {me.Rect} rect | |
* @return {me.Vector2d} | |
*/ | |
collideWithRectangle : function(/** {me.Rect} */ rect) { | |
// response vector | |
var p = new me.Vector2d(0, 0); | |
// check if both box are overlaping | |
if (this.overlaps(rect)) { | |
// compute delta between this & rect | |
var dx = this.left + this.hWidth - rect.left - rect.hWidth; | |
var dy = this.top + this.hHeight - rect.top - rect.hHeight; | |
// compute penetration depth for both axis | |
p.x = (rect.hWidth + this.hWidth) - (dx < 0 ? -dx : dx); // - Math.abs(dx); | |
p.y = (rect.hHeight + this.hHeight) - (dy < 0 ? -dy : dy); // - Math.abs(dy); | |
// check and "normalize" axis | |
if (p.x < p.y) { | |
p.y = 0; | |
p.x = dx < 0 ? -p.x : p.x; | |
} else { | |
p.x = 0; | |
p.y = dy < 0 ? -p.y : p.y; | |
} | |
} | |
return p; | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
// draw the rectangle | |
context.strokeStyle = color || "red"; | |
context.strokeRect(this.left, this.top, this.width, this.height); | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a Ellipse Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* an ellipse Object | |
* (Tiled specifies top-left coordinates, and width and height of the ellipse) | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v top-left origin position of the Ellipse | |
* @param {Number} w width of the elipse | |
* @param {Number} h height of the elipse | |
*/ | |
me.Ellipse = Object.extend( | |
/** @scope me.Ellipse.prototype */ { | |
/** | |
* center point of the Ellipse | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.Ellipse | |
*/ | |
pos : null, | |
/** | |
* radius (x/y) of the ellipse | |
* @public | |
* @type me.Vector2d | |
* @name radius | |
* @memberOf me.Ellipse | |
*/ | |
radius : null, | |
// the shape type | |
shapeType : "Ellipse", | |
/** @ignore */ | |
init : function(v, w, h) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
if (this.radius === null) { | |
this.radius = new me.Vector2d(); | |
} | |
this.set(v, w, h); | |
}, | |
/** | |
* set new value to the Ellipse | |
* @name set | |
* @memberOf me.Ellipse | |
* @function | |
* @param {me.Vector2d} v top-left origin position of the Ellipse | |
* @param {Number} w width of the Ellipse | |
* @param {Number} h height of the Ellipse | |
*/ | |
set : function(v, w, h) { | |
this.radius.set(w/2, h/2); | |
this.pos.setV(v).add(this.radius); | |
this.offset = new me.Vector2d(); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.Ellipse | |
* @function | |
* @return {me.Rect} the bounding box Rectangle object | |
*/ | |
getBounds : function() { | |
//will return a rect, with pos being the top-left coordinates | |
return new me.Rect( | |
this.pos.clone().sub(this.radius), | |
this.radius.x * 2, | |
this.radius.y * 2 | |
); | |
}, | |
/** | |
* clone this Ellipse | |
* @name clone | |
* @memberOf me.Ellipse | |
* @function | |
* @return {me.Ellipse} new Ellipse | |
*/ | |
clone : function() { | |
return new me.Ellipse(this.pos.clone(), this.radius.x * 2, this.radius.y * 2); | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
// http://tinyurl.com/opnro2r | |
context.save(); | |
context.beginPath(); | |
context.translate(this.pos.x-this.radius.x, this.pos.y-this.radius.y); | |
context.scale(this.radius.x, this.radius.y); | |
context.arc(1, 1, 1, 0, 2 * Math.PI, false); | |
context.restore(); | |
context.strokeStyle = color || "red"; | |
context.stroke(); | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a PolyShape Class Object */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a polyshape (polygone/polyline) Object | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} v origin point of the PolyShape | |
* @param {me.Vector2d[]} points array of vector defining the polyshape | |
* @param {Boolean} closed true if a polygone, false if a polyline | |
*/ | |
me.PolyShape = Object.extend( | |
/** @scope me.PolyShape.prototype */ { | |
/** | |
* @ignore | |
*/ | |
offset : null, | |
/** | |
* origin point of the PolyShape | |
* @public | |
* @type me.Vector2d | |
* @name pos | |
* @memberOf me.PolyShape | |
*/ | |
pos : null, | |
/** | |
* Array of points defining the polyshape | |
* @public | |
* @type me.Vector2d[] | |
* @name points | |
* @memberOf me.PolyShape | |
*/ | |
points : null, | |
/** | |
* Specified if the shape is closed (i.e. polygon) | |
* @public | |
* @type boolean | |
* @name closed | |
* @memberOf me.PolyShape | |
*/ | |
closed : null, | |
// the shape type | |
shapeType : "PolyShape", | |
/** @ignore */ | |
init : function(v, points, closed) { | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
if (this.offset === null) { | |
this.offset = new me.Vector2d(); | |
} | |
this.set(v, points, closed); | |
}, | |
/** | |
* set new value to the PolyShape | |
* @name set | |
* @memberOf me.PolyShape | |
* @function | |
* @param {me.Vector2d} v origin point of the PolyShape | |
* @param {me.Vector2d[]} points array of vector defining the polyshape | |
* @param {Boolean} closed true if a polygone, false if a polyline | |
*/ | |
set : function(v, points, closed) { | |
this.pos.setV(v); | |
this.points = points; | |
this.closed = (closed === true); | |
this.offset.set(0, 0); | |
this.getBounds(); | |
}, | |
/** | |
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape. | |
* @name getBounds | |
* @memberOf me.PolyShape | |
* @function | |
* @return {me.Rect} the bounding box Rectangle object | |
*/ | |
getBounds : function() { | |
var pos = this.offset, right = 0, bottom = 0; | |
this.points.forEach(function(point) { | |
pos.x = Math.min(pos.x, point.x); | |
pos.y = Math.min(pos.y, point.y); | |
right = Math.max(right, point.x); | |
bottom = Math.max(bottom, point.y); | |
}); | |
return new me.Rect(pos, right - pos.x, bottom - pos.y); | |
}, | |
/** | |
* clone this PolyShape | |
* @name clone | |
* @memberOf me.PolyShape | |
* @function | |
* @return {me.PolyShape} new PolyShape | |
*/ | |
clone : function() { | |
return new me.PolyShape(this.pos.clone(), this.points, this.closed); | |
}, | |
/** | |
* debug purpose | |
* @ignore | |
*/ | |
draw : function(context, color) { | |
context.save(); | |
context.translate(-this.offset.x, -this.offset.y); | |
context.strokeStyle = color || "red"; | |
context.beginPath(); | |
context.moveTo(this.points[0].x, this.points[0].y); | |
this.points.forEach(function(point) { | |
context.lineTo(point.x, point.y); | |
context.moveTo(point.x, point.y); | |
}); | |
if (this.closed===true) { | |
context.lineTo(this.points[0].x, this.points[0].y); | |
} | |
context.stroke(); | |
context.restore(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* debug stuff. | |
* @namespace | |
*/ | |
me.debug = { | |
/** | |
* render Collision Map layer<br> | |
* default value : false | |
* @type Boolean | |
* @memberOf me.debug | |
*/ | |
renderCollisionMap : false | |
}; | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* A base class for renderable objects. | |
* @class | |
* @extends me.Rect | |
* @memberOf me | |
* @constructor | |
* @param {me.Vector2d} pos position of the renderable object | |
* @param {Number} width object width | |
* @param {Number} height object height | |
*/ | |
me.Renderable = me.Rect.extend( | |
/** @scope me.Renderable.prototype */ | |
{ | |
/** | |
* to identify the object as a renderable object | |
* @ignore | |
*/ | |
isRenderable : true, | |
/** | |
* (G)ame (U)nique (Id)entifier" <br> | |
* a GUID will be allocated for any renderable object added <br> | |
* to an object container (including the `me.game.world` container) | |
* @public | |
* @type String | |
* @name GUID | |
* @memberOf me.Renderable | |
*/ | |
GUID : undefined, | |
/** | |
* the visible state of the renderable object<br> | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name visible | |
* @memberOf me.Renderable | |
*/ | |
visible : true, | |
/** | |
* Whether the renderable object is visible and within the viewport<br> | |
* default value : false | |
* @public | |
* @readonly | |
* @type Boolean | |
* @name inViewport | |
* @memberOf me.Renderable | |
*/ | |
inViewport : false, | |
/** | |
* Whether the renderable object will always update, even when outside of the viewport<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name alwaysUpdate | |
* @memberOf me.Renderable | |
*/ | |
alwaysUpdate : false, | |
/** | |
* Whether to update this object when the game is paused. | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name updateWhenPaused | |
* @memberOf me.Renderable | |
*/ | |
updateWhenPaused: false, | |
/** | |
* make the renderable object persistent over level changes<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name isPersistent | |
* @memberOf me.Renderable | |
*/ | |
isPersistent : false, | |
/** | |
* Define if a renderable follows screen coordinates (floating)<br> | |
* or the world coordinates (not floating)<br> | |
* default value : false | |
* @public | |
* @type Boolean | |
* @name floating | |
* @memberOf me.Renderable | |
*/ | |
floating : false, | |
/** | |
* Z-order for object sorting<br> | |
* default value : 0 | |
* @private | |
* @type Number | |
* @name z | |
* @memberOf me.Renderable | |
*/ | |
z : 0, | |
/** | |
* Define the object anchoring point<br> | |
* This is used when positioning, or scaling the object<br> | |
* The anchor point is a value between 0.0 and 1.0 (1.0 being the maximum size of the object) <br> | |
* (0, 0) means the top-left corner, <br> | |
* (1, 1) means the bottom-right corner, <br> | |
* default anchoring point is the center (0.5, 0.5) of the object. | |
* @public | |
* @type me.Vector2d | |
* @name anchorPoint | |
* @memberOf me.Renderable | |
*/ | |
anchorPoint: null, | |
/** | |
* Define the renderable opacity<br> | |
* @see me.Renderable#setOpacity | |
* @see me.Renderable#getOpacity | |
* @public | |
* @type Number | |
* @name me.Renderable#alpha | |
*/ | |
alpha: 1.0, | |
/** | |
* @ignore | |
*/ | |
init : function(pos, width, height) { | |
// call the parent constructor | |
this.parent(pos, width, height); | |
// set the default anchor point (middle of the renderable) | |
if (this.anchorPoint === null) { | |
this.anchorPoint = new me.Vector2d(); | |
} | |
this.anchorPoint.set(0.5, 0.5); | |
// ensure it's fully opaque by default | |
this.setOpacity(1.0); | |
}, | |
/** | |
* get the renderable alpha channel value<br> | |
* @name getOpacity | |
* @memberOf me.Renderable | |
* @function | |
* @return {Number} current opacity value between 0 and 1 | |
*/ | |
getOpacity : function() { | |
return this.alpha; | |
}, | |
/** | |
* set the renderable alpha channel value<br> | |
* @name setOpacity | |
* @memberOf me.Renderable | |
* @function | |
* @param {Number} alpha opacity value between 0 and 1 | |
*/ | |
setOpacity : function(alpha) { | |
if (typeof (alpha) === "number") { | |
this.alpha = alpha.clamp(0.0,1.0); | |
} | |
}, | |
/** | |
* update function | |
* called by the game manager on each game loop | |
* @name update | |
* @memberOf me.Renderable | |
* @function | |
* @protected | |
* @return false | |
**/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* object draw | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.Renderable | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context, color) { | |
// draw the parent rectangle | |
this.parent(context, color); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* A Simple object to display a sprite on screen. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {Image} image reference to the Sprite Image. See {@link me.loader#getImage} | |
* @param {Number} [spritewidth] sprite width | |
* @param {Number} [spriteheigth] sprite height | |
* @example | |
* // create a static Sprite Object | |
* mySprite = new me.SpriteObject (100, 100, me.loader.getImage("mySpriteImage")); | |
*/ | |
me.SpriteObject = me.Renderable.extend( | |
/** @scope me.SpriteObject.prototype */ | |
{ | |
// default scale ratio of the object | |
/** @ignore */ | |
scale : null, | |
// if true, image flipping/scaling is needed | |
scaleFlag : false, | |
// just to keep track of when we flip | |
lastflipX : false, | |
lastflipY : false, | |
// z position (for ordering display) | |
z : 0, | |
// image offset | |
offset : null, | |
/** | |
* Set the angle (in Radians) of a sprite to rotate it <br> | |
* WARNING: rotating sprites decreases performances | |
* @public | |
* @type Number | |
* @name me.SpriteObject#angle | |
*/ | |
angle: 0, | |
/** | |
* Source rotation angle for pre-rotating the source image<br> | |
* Commonly used for TexturePacker | |
* @ignore | |
*/ | |
_sourceAngle: 0, | |
// image reference | |
image : null, | |
// to manage the flickering effect | |
flickering : false, | |
flickerTimer : -1, | |
flickercb : null, | |
flickerState : false, | |
/** | |
* @ignore | |
*/ | |
init : function(x, y, image, spritewidth, spriteheight) { | |
// Used by the game engine to adjust visibility as the | |
// sprite moves in and out of the viewport | |
this.isSprite = true; | |
// call the parent constructor | |
this.parent(new me.Vector2d(x, y), | |
spritewidth || image.width, | |
spriteheight || image.height); | |
// cache image reference | |
this.image = image; | |
// scale factor of the object | |
this.scale = new me.Vector2d(1.0, 1.0); | |
this.lastflipX = this.lastflipY = false; | |
this.scaleFlag = false; | |
// set the default sprite index & offset | |
this.offset = new me.Vector2d(0, 0); | |
// make it visible by default | |
this.visible = true; | |
// non persistent per default | |
this.isPersistent = false; | |
// and not flickering | |
this.flickering = false; | |
}, | |
/** | |
* specify a transparent color | |
* @name setTransparency | |
* @memberOf me.SpriteObject | |
* @function | |
* @deprecated Use PNG or GIF with transparency instead | |
* @param {String} color color key in "#RRGGBB" format | |
*/ | |
setTransparency : function(col) { | |
// remove the # if present | |
col = (col.charAt(0) === "#") ? col.substring(1, 7) : col; | |
// applyRGB Filter (return a context object) | |
this.image = me.video.applyRGBFilter(this.image, "transparent", col.toUpperCase()).canvas; | |
}, | |
/** | |
* return the flickering state of the object | |
* @name isFlickering | |
* @memberOf me.SpriteObject | |
* @function | |
* @return {Boolean} | |
*/ | |
isFlickering : function() { | |
return this.flickering; | |
}, | |
/** | |
* make the object flicker | |
* @name flicker | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Number} duration expressed in frames | |
* @param {Function} callback Function to call when flickering ends | |
* @example | |
* // make the object flicker for 60 frame | |
* // and then remove it | |
* this.flicker(60, function() | |
* { | |
* me.game.remove(this); | |
* }); | |
*/ | |
flicker : function(duration, callback) { | |
this.flickerTimer = duration; | |
if (this.flickerTimer < 0) { | |
this.flickering = false; | |
this.flickercb = null; | |
} else if (!this.flickering) { | |
this.flickercb = callback; | |
this.flickering = true; | |
} | |
}, | |
/** | |
* Flip object on horizontal axis | |
* @name flipX | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipX : function(flip) { | |
if (flip !== this.lastflipX) { | |
this.lastflipX = flip; | |
// invert the scale.x value | |
this.scale.x = -this.scale.x; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* Flip object on vertical axis | |
* @name flipY | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipY : function(flip) { | |
if (flip !== this.lastflipY) { | |
this.lastflipY = flip; | |
// invert the scale.x value | |
this.scale.y = -this.scale.y; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* Resize the sprite around his center<br> | |
* @name resize | |
* @memberOf me.SpriteObject | |
* @function | |
* @param {Number} ratio scaling ratio | |
*/ | |
resize : function(ratio) { | |
if (ratio > 0) { | |
this.scale.x = this.scale.x < 0.0 ? -ratio : ratio; | |
this.scale.y = this.scale.y < 0.0 ? -ratio : ratio; | |
// set the scaleFlag | |
this.scaleFlag = this.scale.x !== 1.0 || this.scale.y !== 1.0; | |
} | |
}, | |
/** | |
* sprite update<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name update | |
* @memberOf me.SpriteObject | |
* @function | |
* @protected | |
* @return false | |
**/ | |
update : function() { | |
//update the "flickering" state if necessary | |
if (this.flickering) { | |
this.flickerTimer -= me.timer.tick; | |
if (this.flickerTimer < 0) { | |
if (this.flickercb) | |
this.flickercb(); | |
this.flicker(-1); | |
} | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* object draw<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.SpriteObject | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context) { | |
// do nothing if we are flickering | |
if (this.flickering) { | |
this.flickerState = !this.flickerState; | |
if (!this.flickerState) return; | |
} | |
// save the current the context | |
context.save(); | |
// sprite alpha value | |
context.globalAlpha *= this.getOpacity(); | |
// clamp position vector to pixel grid | |
var xpos = ~~this.pos.x, ypos = ~~this.pos.y; | |
var w = this.width, h = this.height; | |
var angle = this.angle + this._sourceAngle; | |
if ((this.scaleFlag) || (angle!==0)) { | |
// calculate pixel pos of the anchor point | |
var ax = w * this.anchorPoint.x, ay = h * this.anchorPoint.y; | |
// translate to the defined anchor point | |
context.translate(xpos + ax, ypos + ay); | |
// scale | |
if (this.scaleFlag) | |
context.scale(this.scale.x, this.scale.y); | |
if (angle!==0) | |
context.rotate(angle); | |
if (this._sourceAngle!==0) { | |
// swap w and h for rotated source images | |
w = this.height; | |
h = this.width; | |
xpos = -ay; | |
ypos = -ax; | |
} | |
else { | |
// reset coordinates back to upper left coordinates | |
xpos = -ax; | |
ypos = -ay; | |
} | |
} | |
context.drawImage(this.image, | |
this.offset.x, this.offset.y, | |
w, h, | |
xpos, ypos, | |
w, h); | |
// restore the context | |
context.restore(); | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
this.onDestroyEvent.apply(this, arguments); | |
}, | |
/** | |
* OnDestroy Notification function<br> | |
* Called by engine before deleting the object | |
* @name onDestroyEvent | |
* @memberOf me.SpriteObject | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended ! | |
} | |
}); | |
/** | |
* an object to manage animation | |
* @class | |
* @extends me.SpriteObject | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {Image} image reference of the animation sheet | |
* @param {Number} spritewidth width of a single sprite within the spritesheet | |
* @param {Number} [spriteheight=image.height] height of a single sprite within the spritesheet | |
*/ | |
me.AnimationSheet = me.SpriteObject.extend( | |
/** @scope me.AnimationSheet.prototype */ | |
{ | |
// Spacing and margin | |
/** @ignore */ | |
spacing: 0, | |
/** @ignore */ | |
margin: 0, | |
/** | |
* pause and resume animation<br> | |
* default value : false; | |
* @public | |
* @type Boolean | |
* @name me.AnimationSheet#animationpause | |
*/ | |
animationpause : false, | |
/** | |
* animation cycling speed (delay between frame in ms)<br> | |
* default value : 100ms; | |
* @public | |
* @type Number | |
* @name me.AnimationSheet#animationspeed | |
*/ | |
animationspeed : 100, | |
/** @ignore */ | |
init : function(x, y, image, spritewidth, spriteheight, spacing, margin, atlas, atlasIndices) { | |
// hold all defined animation | |
this.anim = {}; | |
// a flag to reset animation | |
this.resetAnim = null; | |
// default animation sequence | |
this.current = null; | |
// default animation speed (ms) | |
this.animationspeed = 100; | |
// Spacing and margin | |
this.spacing = spacing || 0; | |
this.margin = margin || 0; | |
// call the constructor | |
this.parent(x, y, image, spritewidth, spriteheight, spacing, margin); | |
// store the current atlas information | |
this.textureAtlas = null; | |
this.atlasIndices = null; | |
// build the local textureAtlas | |
this.buildLocalAtlas(atlas || undefined, atlasIndices || undefined); | |
// create a default animation sequence with all sprites | |
this.addAnimation("default", null); | |
// set as default | |
this.setCurrentAnimation("default"); | |
}, | |
/** | |
* build the local (private) atlas | |
* @ignore | |
*/ | |
buildLocalAtlas : function (atlas, indices) { | |
// reinitialze the atlas | |
if (atlas !== undefined) { | |
this.textureAtlas = atlas; | |
this.atlasIndices = indices; | |
} else { | |
// regular spritesheet | |
this.textureAtlas = []; | |
// calculate the sprite count (line, col) | |
var spritecount = new me.Vector2d( | |
~~((this.image.width - this.margin) / (this.width + this.spacing)), | |
~~((this.image.height - this.margin) / (this.height + this.spacing)) | |
); | |
// build the local atlas | |
for ( var frame = 0, count = spritecount.x * spritecount.y; frame < count ; frame++) { | |
this.textureAtlas[frame] = { | |
name: ''+frame, | |
offset: new me.Vector2d( | |
this.margin + (this.spacing + this.width) * (frame % spritecount.x), | |
this.margin + (this.spacing + this.height) * ~~(frame / spritecount.x) | |
), | |
width: this.width, | |
height: this.height, | |
hWidth: this.width / 2, | |
hHeight: this.height / 2, | |
angle: 0 | |
}; | |
} | |
} | |
}, | |
/** | |
* add an animation <br> | |
* For fixed-sized cell sprite sheet, the index list must follow the logic as per the following example :<br> | |
* <img src="images/spritesheet_grid.png"/> | |
* @name addAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @param {Number[]|String[]} index list of sprite index or name defining the animation | |
* @param {Number} [animationspeed] cycling speed for animation in ms (delay between each frame). | |
* @see me.AnimationSheet#animationspeed | |
* @example | |
* // walking animation | |
* this.addAnimation("walk", [ 0, 1, 2, 3, 4, 5 ]); | |
* // eating animation | |
* this.addAnimation("eat", [ 6, 6 ]); | |
* // rolling animation | |
* this.addAnimation("roll", [ 7, 8, 9, 10 ]); | |
* // slower animation | |
* this.addAnimation("roll", [ 7, 8, 9, 10 ], 200); | |
*/ | |
addAnimation : function(name, index, animationspeed) { | |
this.anim[name] = { | |
name : name, | |
frame : [], | |
idx : 0, | |
length : 0, | |
animationspeed: animationspeed || this.animationspeed, | |
nextFrame : 0 | |
}; | |
if (index == null) { | |
index = []; | |
var j = 0; | |
// create a default animation with all frame | |
this.textureAtlas.forEach(function() { | |
index[j] = j++; | |
}); | |
} | |
// set each frame configuration (offset, size, etc..) | |
for ( var i = 0 , len = index.length ; i < len; i++) { | |
if (typeof(index[i]) === "number") { | |
this.anim[name].frame[i] = this.textureAtlas[index[i]]; | |
} else { // string | |
if (this.atlasIndices === null) { | |
throw "melonjs: string parameters for addAnimation are only allowed for TextureAtlas "; | |
} else { | |
this.anim[name].frame[i] = this.textureAtlas[this.atlasIndices[index[i]]]; | |
} | |
} | |
} | |
this.anim[name].length = this.anim[name].frame.length; | |
}, | |
/** | |
* set the current animation | |
* @name setCurrentAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @param {String|Function} [onComplete] animation id to switch to when complete, or callback | |
* @example | |
* // set "walk" animation | |
* this.setCurrentAnimation("walk"); | |
* | |
* // set "eat" animation, and switch to "walk" when complete | |
* this.setCurrentAnimation("eat", "walk"); | |
* | |
* // set "die" animation, and remove the object when finished | |
* this.setCurrentAnimation("die", (function () { | |
* me.game.remove(this); | |
* return false; // do not reset to first frame | |
* }).bind(this)); | |
* | |
* // set "attack" animation, and pause for a short duration | |
* this.setCurrentAnimation("die", (function () { | |
* this.animationpause = true; | |
* | |
* // back to "standing" animation after 1 second | |
* setTimeout(function () { | |
* this.setCurrentAnimation("standing"); | |
* }, 1000); | |
* | |
* return false; // do not reset to first frame | |
* }).bind(this)); | |
**/ | |
setCurrentAnimation : function(name, resetAnim) { | |
if (this.anim[name]) { | |
this.current = this.anim[name]; | |
this.resetAnim = resetAnim || null; | |
this.setAnimationFrame(this.current.idx); // or 0 ? | |
this.current.nextFrame = me.timer.getTime() + this.current.animationspeed; | |
} else { | |
throw "melonJS: animation id '" + name + "' not defined"; | |
} | |
}, | |
/** | |
* return true if the specified animation is the current one. | |
* @name isCurrentAnimation | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {String} name animation id | |
* @return {Boolean} | |
* @example | |
* if (!this.isCurrentAnimation("walk")) { | |
* // do something funny... | |
* } | |
*/ | |
isCurrentAnimation : function(name) { | |
return this.current.name === name; | |
}, | |
/** | |
* force the current animation frame index. | |
* @name setAnimationFrame | |
* @memberOf me.AnimationSheet | |
* @function | |
* @param {Number} [index=0] animation frame index | |
* @example | |
* //reset the current animation to the first frame | |
* this.setAnimationFrame(); | |
*/ | |
setAnimationFrame : function(idx) { | |
this.current.idx = (idx || 0) % this.current.length; | |
var frame = this.current.frame[this.current.idx]; | |
this.offset = frame.offset; | |
this.width = frame.width; | |
this.height = frame.height; | |
this.hWidth = frame.hWidth; | |
this.hHeight = frame.hHeight; | |
this._sourceAngle = frame.angle; | |
}, | |
/** | |
* return the current animation frame index. | |
* @name getCurrentAnimationFrame | |
* @memberOf me.AnimationSheet | |
* @function | |
* @return {Number} current animation frame index | |
*/ | |
getCurrentAnimationFrame : function() { | |
return this.current.idx; | |
}, | |
/** | |
* update the animation<br> | |
* this is automatically called by the game manager {@link me.game} | |
* @name update | |
* @memberOf me.AnimationSheet | |
* @function | |
* @protected | |
*/ | |
update : function() { | |
// update animation if necessary | |
if (!this.animationpause && (me.timer.getTime() >= this.current.nextFrame)) { | |
this.setAnimationFrame(++this.current.idx); | |
// switch animation if we reach the end of the strip | |
// and a callback is defined | |
if (this.current.idx === 0 && this.resetAnim) { | |
// if string, change to the corresponding animation | |
if (typeof this.resetAnim === "string") | |
this.setCurrentAnimation(this.resetAnim); | |
// if function (callback) call it | |
else if (typeof this.resetAnim === "function" && this.resetAnim() === false) { | |
this.current.idx = this.current.length - 1; | |
this.setAnimationFrame(this.current.idx); | |
this.parent(); | |
return false; | |
} | |
} | |
// set next frame timestamp | |
this.current.nextFrame = me.timer.getTime() + this.current.animationspeed; | |
return this.parent() || true; | |
} | |
return this.parent(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* a local constant for the -(Math.PI / 2) value | |
* @ignore | |
*/ | |
var nhPI = -(Math.PI / 2); | |
/** | |
* A Texture atlas object<br> | |
* Currently support : <br> | |
* - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export <br> | |
* - [ShoeBox]{@link http://renderhjs.net/shoebox/} : through JSON export using the melonJS setting [file]{@link https://github.com/melonjs/melonJS/raw/master/media/shoebox_JSON_export.sbx} | |
* @class | |
* @extends Object | |
* @memberOf me | |
* @constructor | |
* @param {Object} atlas atlas information. See {@link me.loader#getJSON} | |
* @param {Image} [texture=atlas.meta.image] texture name | |
* @example | |
* // create a texture atlas | |
* texture = new me.TextureAtlas ( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
*/ | |
me.TextureAtlas = Object.extend( | |
/** @scope me.TextureAtlas.prototype */ | |
{ | |
/** | |
* to identify the atlas format (e.g. texture packer) | |
* @ignore | |
*/ | |
format: null, | |
/** | |
* the image texture itself | |
* @ignore | |
*/ | |
texture : null, | |
/** | |
* the atlas dictionnary | |
* @ignore | |
*/ | |
atlas: null, | |
/** | |
* @ignore | |
*/ | |
init : function(atlas, texture) { | |
if (atlas && atlas.meta) { | |
// Texture Packer | |
if (atlas.meta.app.contains("texturepacker")) { | |
this.format = "texturepacker"; | |
// set the texture | |
if (texture===undefined) { | |
var name = me.utils.getBasename(atlas.meta.image); | |
this.texture = me.loader.getImage(name); | |
if (this.texture === null) { | |
throw "melonjs: Atlas texture '" + name + "' not found"; | |
} | |
} else { | |
this.texture = texture; | |
} | |
} | |
// ShoeBox | |
if (atlas.meta.app.contains("ShoeBox")) { | |
if (!atlas.meta.exporter || !atlas.meta.exporter.contains("melonJS")) { | |
throw "melonjs: ShoeBox requires the JSON exporter : https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx"; | |
} | |
this.format = "ShoeBox"; | |
// set the texture | |
this.texture = texture; | |
} | |
// initialize the atlas | |
this.atlas = this.initFromTexturePacker(atlas); | |
} | |
// if format not recognized | |
if (this.atlas === null) { | |
throw "melonjs: texture atlas format not supported"; | |
} | |
}, | |
/** | |
* @ignore | |
*/ | |
initFromTexturePacker : function (data) { | |
var atlas = {}; | |
data.frames.forEach(function(frame) { | |
// fix wrongly formatted JSON (e.g. last dummy object in ShoeBox) | |
if (frame.hasOwnProperty("filename")) { | |
atlas[frame.filename] = { | |
frame: new me.Rect( | |
new me.Vector2d(frame.frame.x, frame.frame.y), | |
frame.frame.w, frame.frame.h | |
), | |
source: new me.Rect( | |
new me.Vector2d(frame.spriteSourceSize.x, frame.spriteSourceSize.y), | |
frame.spriteSourceSize.w, frame.spriteSourceSize.h | |
), | |
// non trimmed size, but since we don't support trimming both value are the same | |
//sourceSize: new me.Vector2d(frame.sourceSize.w,frame.sourceSize.h), | |
rotated : frame.rotated===true, | |
trimmed : frame.trimmed===true | |
}; | |
} | |
}); | |
return atlas; | |
}, | |
/** | |
* return the Atlas texture | |
* @name getTexture | |
* @memberOf me.TextureAtlas | |
* @function | |
* @return {Image} | |
*/ | |
getTexture : function() { | |
return this.texture; | |
}, | |
/** | |
* return a normalized region/frame information for the specified sprite name | |
* @name getRegion | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String} name name of the sprite | |
* @return {Object} | |
*/ | |
getRegion : function(name) { | |
var region = this.atlas[name]; | |
if (region) { | |
return { | |
name: name, // frame name | |
pos: region.source.pos.clone(), // unused for now | |
offset: region.frame.pos.clone(), | |
width: region.frame.width, | |
height: region.frame.height, | |
hWidth: region.frame.width / 2, | |
hHeight: region.frame.height / 2, | |
angle : (region.rotated===true) ? nhPI : 0 | |
}; | |
} | |
return null; | |
}, | |
/** | |
* Create a sprite object using the first region found using the specified name | |
* @name createSpriteFromName | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String} name name of the sprite | |
* @return {me.SpriteObject} | |
* @example | |
* // create a new texture atlas object under the `game` namespace | |
* game.texture = new me.TextureAtlas( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
* ... | |
* ... | |
* // add the coin sprite as renderable for the entity | |
* this.renderable = game.texture.createSpriteFromName("coin.png"); | |
* // set the renderable position to bottom center | |
* this.anchorPoint.set(0.5, 1.0); | |
*/ | |
createSpriteFromName : function(name) { | |
var region = this.getRegion(name); | |
if (region) { | |
// instantiate a new sprite object | |
var sprite = new me.SpriteObject(0,0, this.getTexture(), region.width, region.height); | |
// set the sprite offset within the texture | |
sprite.offset.setV(region.offset); | |
// set angle if defined | |
sprite._sourceAngle = region.angle; | |
/* -> when using anchor positioning, this is not required | |
-> and makes final position wrong... | |
if (tex.trimmed===true) { | |
// adjust default position | |
sprite.pos.add(tex.source.pos); | |
} | |
*/ | |
// return our object | |
return sprite; | |
} | |
// throw an error | |
throw "melonjs: TextureAtlas - region for " + name + " not found"; | |
}, | |
/** | |
* Create an animation object using the first region found using all specified names | |
* @name createAnimationFromName | |
* @memberOf me.TextureAtlas | |
* @function | |
* @param {String[]} names list of names for each sprite | |
* @return {me.AnimationSheet} | |
* @example | |
* // create a new texture atlas object under the `game` namespace | |
* game.texture = new me.TextureAtlas( | |
* me.loader.getJSON("texture"), | |
* me.loader.getImage("texture") | |
* ); | |
* ... | |
* ... | |
* // create a new animationSheet as renderable for the entity | |
* this.renderable = game.texture.createAnimationFromName([ | |
* "walk0001.png", "walk0002.png", "walk0003.png", | |
* "walk0004.png", "walk0005.png", "walk0006.png", | |
* "walk0007.png", "walk0008.png", "walk0009.png", | |
* "walk0010.png", "walk0011.png" | |
* ]); | |
* | |
* // define an additional basic walking animatin | |
* this.renderable.addAnimation ("simple_walk", [0,2,1]); | |
* // you can also use frame name to define your animation | |
* this.renderable.addAnimation ("speed_walk", ["walk0007.png", "walk0008.png", "walk0009.png", "walk0010.png"]); | |
* // set the default animation | |
* this.renderable.setCurrentAnimation("simple_walk"); | |
* // set the renderable position to bottom center | |
* this.anchorPoint.set(0.5, 1.0); | |
*/ | |
createAnimationFromName : function(names) { | |
var tpAtlas = [], indices = {}; | |
// iterate through the given names | |
// and create a "normalized" atlas | |
for (var i = 0; i < names.length;++i) { | |
tpAtlas[i] = this.getRegion(names[i]); | |
indices[names[i]] = i; | |
if (tpAtlas[i] == null) { | |
// throw an error | |
throw "melonjs: TextureAtlas - region for " + names[i] + " not found"; | |
} | |
} | |
// instantiate a new animation sheet object | |
return new me.AnimationSheet(0,0, this.texture, 0, 0, 0, 0, tpAtlas, indices); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* 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.Rect | |
* @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 | |
* @param {Number} [realw] real world width limit | |
* @param {Number} [realh] real world height limit | |
*/ | |
me.Viewport = me.Rect.extend( | |
/** @scope me.Viewport.prototype */ | |
{ | |
/** | |
* 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 | |
*/ | |
AXIS : { | |
NONE : 0, | |
HORIZONTAL : 1, | |
VERTICAL : 2, | |
BOTH : 3 | |
}, | |
// world limit | |
limits : null, | |
// target to follow | |
target : null, | |
// axis to follow | |
follow_axis : 0, | |
// shake parameters | |
shaking : false, | |
_shake : null, | |
// fade parameters | |
_fadeIn : null, | |
_fadeOut : null, | |
// cache some values | |
_deadwidth : 0, | |
_deadheight : 0, | |
_limitwidth : 0, | |
_limitheight : 0, | |
// cache the screen rendering position | |
screenX : 0, | |
screenY : 0, | |
/** @ignore */ | |
init : function(minX, minY, maxX, maxY, realw, realh) { | |
// viewport coordinates | |
this.parent(new me.Vector2d(minX, minY), maxX - minX, maxY - minY); | |
// real worl limits | |
this.limits = new me.Vector2d(realw||this.width, realh||this.height); | |
// 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, | |
start : 0 | |
}; | |
// flash variables | |
this._fadeOut = { | |
color : 0, | |
alpha : 0.0, | |
duration : 0, | |
tween : null | |
}; | |
// fade variables | |
this._fadeIn = { | |
color : 0, | |
alpha : 1.0, | |
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._deadwidth)) { | |
this.pos.x = ~~MIN((target.x) - (this._deadwidth), this._limitwidth); | |
} | |
else if ((target.x - this.pos.x) < (this.deadzone.x)) { | |
this.pos.x = ~~MAX((target.x) - this.deadzone.x, 0); | |
} | |
return (_x !== this.pos.x); | |
}, | |
/** @ignore */ | |
_followV : function(target) { | |
var _y = this.pos.y; | |
if ((target.y - this.pos.y) > (this._deadheight)) { | |
this.pos.y = ~~MIN((target.y) - (this._deadheight), this._limitheight); | |
} | |
else if ((target.y - this.pos.y) < (this.deadzone.y)) { | |
this.pos.y = ~~MAX((target.y) - this.deadzone.y, 0); | |
} | |
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 | |
* @name setDeadzone | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} w deadzone width | |
* @param {Number} h deadzone height | |
*/ | |
setDeadzone : function(w, h) { | |
this.deadzone = new me.Vector2d(~~((this.width - w) / 2), | |
~~((this.height - h) / 2 - h * 0.25)); | |
// cache some value | |
this._deadwidth = this.width - this.deadzone.x; | |
this._deadheight = this.height - this.deadzone.y; | |
// force a camera update | |
this.update(true); | |
}, | |
/** | |
* set the viewport boundaries (world limit) | |
* @name setBounds | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} w world width | |
* @param {Number} h world height | |
*/ | |
setBounds : function(w, h) { | |
this.limits.set(w, h); | |
// cache some value | |
this._limitwidth = this.limits.x - this.width; | |
this._limitheight = this.limits.y - this.height; | |
}, | |
/** | |
* set the viewport to follow the specified entity | |
* @name follow | |
* @memberOf me.Viewport | |
* @function | |
* @param {me.ObjectEntity|me.Vector2d} target ObjectEntity or Position Vector to follow | |
* @param {me.Viewport#AXIS} [axis=AXIS.BOTH] Which axis to follow | |
*/ | |
follow : function(target, axis) { | |
if (target instanceof me.ObjectEntity) | |
this.target = target.pos; | |
else if (target instanceof me.Vector2d) | |
this.target = target; | |
else | |
throw "melonJS: 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.update(true); | |
}, | |
/** | |
* move the viewport to the specified coordinates | |
* @name move | |
* @memberOf me.Viewport | |
* @function | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
move : function(x, y) { | |
var newx = ~~(this.pos.x + x); | |
var newy = ~~(this.pos.y + y); | |
this.pos.x = newx.clamp(0,this._limitwidth); | |
this.pos.y = newy.clamp(0,this._limitheight); | |
//publish the corresponding message | |
me.event.publish(me.event.VIEWPORT_ONCHANGE, [this.pos]); | |
}, | |
/** @ignore */ | |
update : function(updateTarget) { | |
var updated = false; | |
if (this.target && updateTarget) { | |
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; | |
} | |
} | |
if (this.shaking===true) { | |
var delta = me.timer.getTime() - this._shake.start; | |
if (delta >= this._shake.duration) { | |
this.shaking = false; | |
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.shaking) | |
return; | |
this.shaking = true; | |
this._shake = { | |
intensity : intensity, | |
duration : duration, | |
axis : axis || this.AXIS.BOTH, | |
onComplete : onComplete || null, | |
start : me.timer.getTime() | |
}; | |
}, | |
/** | |
* fadeOut(flash) effect<p> | |
* screen is filled with the specified color and slowly goes back to normal | |
* @name fadeOut | |
* @memberOf me.Viewport | |
* @function | |
* @param {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 = color; | |
this._fadeOut.duration = duration || 1000; // convert to ms | |
this._fadeOut.alpha = 1.0; | |
this._fadeOut.tween = me.entityPool.newInstanceOf("me.Tween", this._fadeOut).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 {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 = color; | |
this._fadeIn.duration = duration || 1000; //convert to ms | |
this._fadeIn.alpha = 0.0; | |
this._fadeIn.tween = me.entityPool.newInstanceOf("me.Tween", this._fadeIn).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 around the specified entity<p> | |
* <b>BROKEN !!!!</b> | |
* @deprecated | |
* @ignore | |
* @param {Object} | |
*/ | |
focusOn : function(target) { | |
// BROKEN !! target x and y should be the center point | |
this.pos.x = target.x - this.width * 0.5; | |
this.pos.y = target.y - this.height * 0.5; | |
}, | |
/** | |
* 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 | |
* @return {me.Vector2d} | |
*/ | |
localToWorld : function(x, y) { | |
return (new me.Vector2d(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 | |
* @return {me.Vector2d} | |
*/ | |
worldToLocal : function(x, y) { | |
return (new me.Vector2d(x,y)).sub(this.pos).add(me.game.currentLevel.pos); | |
}, | |
/** | |
* render the camera effects | |
* @ignore | |
*/ | |
draw : function(context) { | |
// fading effect | |
if (this._fadeIn.tween) { | |
context.globalAlpha = this._fadeIn.alpha; | |
me.video.clearSurface(context, me.utils.HexToRGB(this._fadeIn.color)); | |
// set back full opacity | |
context.globalAlpha = 1.0; | |
// remove the tween if over | |
if (this._fadeIn.alpha === 1.0) | |
this._fadeIn.tween = null; | |
} | |
// flashing effect | |
if (this._fadeOut.tween) { | |
context.globalAlpha = this._fadeOut.alpha; | |
me.video.clearSurface(context, me.utils.HexToRGB(this._fadeOut.color)); | |
// set back full opacity | |
context.globalAlpha = 1.0; | |
// remove the tween if over | |
if (this._fadeOut.alpha === 0.0) | |
this._fadeOut.tween = null; | |
} | |
// blit our frame | |
me.video.blitSurface(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* GUI Object<br> | |
* A very basic object to manage GUI elements <br> | |
* The object simply register on the "mousedown" <br> | |
* or "touchstart" event and call the onClick function" | |
* @class | |
* @extends me.SpriteObject | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinate of the GUI Object | |
* @param {Number} y the y coordinate of the GUI Object | |
* @param {me.ObjectSettings} settings Object settings | |
* @example | |
* | |
* // create a basic GUI Object | |
* var myButton = me.GUI_Object.extend( | |
* { | |
* init:function(x, y) | |
* { | |
* settings = {} | |
* settings.image = "button"; | |
* settings.spritewidth = 100; | |
* settings.spriteheight = 50; | |
* // parent constructor | |
* this.parent(x, y, settings); | |
* }, | |
* | |
* // output something in the console | |
* // when the object is clicked | |
* onClick:function(event) | |
* { | |
* console.log("clicked!"); | |
* // don't propagate the event | |
* return false; | |
* } | |
* }); | |
* | |
* // add the object at pos (10,10), z index 4 | |
* me.game.add((new myButton(10,10)),4); | |
* | |
*/ | |
me.GUI_Object = me.SpriteObject.extend({ | |
/** @scope me.GUI_Object.prototype */ | |
/** | |
* object can be clicked or not | |
* @public | |
* @type boolean | |
* @name me.GUI_Object#isClickable | |
*/ | |
isClickable : true, | |
// object has been updated (clicked,etc..) | |
updated : false, | |
/** | |
* @ignore | |
*/ | |
init : function(x, y, settings) { | |
this.parent(x, y, | |
((typeof settings.image === "string") ? me.loader.getImage(settings.image) : settings.image), | |
settings.spritewidth, | |
settings.spriteheight); | |
// GUI items use screen coordinates | |
this.floating = true; | |
// register on mouse event | |
me.input.registerPointerEvent('mousedown', this, this.clicked.bind(this)); | |
}, | |
/** | |
* return true if the object has been clicked | |
* @ignore | |
*/ | |
update : function() { | |
if (this.updated) { | |
// clear the flag | |
this.updated = false; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* function callback for the mousedown event | |
* @ignore | |
*/ | |
clicked : function(event) { | |
if (this.isClickable) { | |
this.updated = true; | |
return this.onClick(event); | |
} | |
}, | |
/** | |
* function called when the object is clicked <br> | |
* to be extended <br> | |
* return false if we need to stop propagating the event | |
* @name onClick | |
* @memberOf me.GUI_Object | |
* @public | |
* @function | |
* @param {Event} event the event object | |
*/ | |
onClick : function(event) { | |
return false; | |
}, | |
/** | |
* OnDestroy notification function<br> | |
* Called by engine before deleting the object<br> | |
* be sure to call the parent function if overwritten | |
* @name onDestroyEvent | |
* @memberOf me.GUI_Object | |
* @public | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
me.input.releasePointerEvent('mousedown', this); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier Biot, Jason Oster | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* A global "translation context" for nested ObjectContainers | |
* @ignore | |
*/ | |
var globalTranslation = new me.Rect(new me.Vector2d(), 0, 0); | |
/** | |
* A global "floating entity" reference counter for nested ObjectContainers | |
* @ignore | |
*/ | |
var globalFloatingCounter = 0; | |
/** | |
* EntityContainer represents a collection of child objects | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} [x=0] position of the container | |
* @param {Number} [y=0] position of the container | |
* @param {Number} [w=me.game.viewport.width] width of the container | |
* @param {number} [h=me.game.viewport.height] height of the container | |
*/ | |
me.ObjectContainer = me.Renderable.extend( | |
/** @scope me.ObjectContainer.prototype */ { | |
/** | |
* The property of entity that should be used to sort on <br> | |
* value : "x", "y", "z" (default: me.game.sortOn) | |
* @public | |
* @type String | |
* @name sortOn | |
* @memberOf me.ObjectContainer | |
*/ | |
sortOn : "z", | |
/** | |
* Specify if the entity list should be automatically sorted when adding a new child | |
* @public | |
* @type Boolean | |
* @name autoSort | |
* @memberOf me.ObjectContainer | |
*/ | |
autoSort : true, | |
/** | |
* keep track of pending sort | |
* @ignore | |
*/ | |
pendingSort : null, | |
/** | |
* The array of children of this container. | |
* @ignore | |
*/ | |
children : null, | |
/** | |
* Enable collision detection for this container (default true)<br> | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectContainer | |
*/ | |
collidable : true, | |
/** | |
* constructor | |
* @ignore | |
*/ | |
init : function(x, y, width, height) { | |
// call the parent constructor | |
this.parent( | |
new me.Vector2d(x || 0, y || 0), | |
width || Infinity, | |
height || Infinity | |
); | |
this.children = []; | |
// by default reuse the global me.game.setting | |
this.sortOn = me.game.sortOn; | |
this.autoSort = true; | |
}, | |
/** | |
* Add a child to the container <br> | |
* if auto-sort is disable, the object will be appended at the bottom of the list | |
* @name addChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
addChild : function(child) { | |
if(typeof(child.ancestor) !== 'undefined') { | |
child.ancestor.removeChild(child); | |
} else { | |
// only allocate a GUID if the object has no previous ancestor | |
// (e.g. move one child from one container to another) | |
if (child.isRenderable) { | |
// allocated a GUID value | |
child.GUID = me.utils.createGUID(); | |
} | |
} | |
// specify a z property to infinity if not defined | |
if (typeof child.z === 'undefined') { | |
child.z = Infinity; | |
} | |
child.ancestor = this; | |
this.children.push(child); | |
if (this.autoSort === true) { | |
this.sort(); | |
} | |
}, | |
/** | |
* Add a child to the container at the specified index<br> | |
* (the list won't be sorted after insertion) | |
* @name addChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {Number} index | |
*/ | |
addChildAt : function(child, index) { | |
if((index >= 0) && (index < this.children.length)) { | |
if(typeof(child.ancestor) !== 'undefined') { | |
child.ancestor.removeChild(child); | |
} else { | |
// only allocate a GUID if the object has no previous ancestor | |
// (e.g. move one child from one container to another) | |
if (child.isRenderable) { | |
// allocated a GUID value | |
child.GUID = me.utils.createGUID(); | |
} | |
} | |
child.ancestor = this; | |
this.children.splice(index, 0, child); | |
} else { | |
throw "melonJS (me.ObjectContainer): Index (" + index + ") Out Of Bounds for addChildAt()"; | |
} | |
}, | |
/** | |
* Swaps the position (z depth) of 2 childs | |
* @name swapChildren | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {me.Renderable} child | |
*/ | |
swapChildren : function(child, child2) { | |
var index = this.getChildIndex( child ); | |
var index2 = this.getChildIndex( child2 ); | |
if ((index !== -1) && (index2 !== -1)) { | |
// swap z index | |
var _z = child.z; | |
child.z = child2.z; | |
child2.z = _z; | |
// swap the positions.. | |
this.children[index] = child2; | |
this.children[index2] = child; | |
} else { | |
throw "melonJS (me.ObjectContainer): " + child + " Both the supplied entities must be a child of the caller " + this; | |
} | |
}, | |
/** | |
* Returns the Child at the specified index | |
* @name getChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {Number} index | |
*/ | |
getChildAt : function(index) { | |
if((index >= 0) && (index < this.children.length)) { | |
return this.children[index]; | |
} else { | |
throw "melonJS (me.ObjectContainer): Index (" + index + ") Out Of Bounds for getChildAt()"; | |
} | |
}, | |
/** | |
* Returns the index of the Child | |
* @name getChildAt | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
getChildIndex : function(child) { | |
return this.children.indexOf( child ); | |
}, | |
/** | |
* Returns true if contains the specified Child | |
* @name hasChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @return {Boolean} | |
*/ | |
hasChild : function(child) { | |
return this === child.ancestor; | |
}, | |
/** | |
* return the child corresponding to the given property and value.<br> | |
* note : avoid calling this function every frame since | |
* it parses the whole object tree each time | |
* @name getEntityByProp | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {String} prop Property name | |
* @param {String} value Value of the property | |
* @return {me.Renderable[]} Array of childs | |
* @example | |
* // get the first entity called "mainPlayer" in a specific container : | |
* ent = myContainer.getEntityByProp("name", "mainPlayer"); | |
* // or query the whole world : | |
* ent = me.game.world.getEntityByProp("name", "mainPlayer"); | |
*/ | |
getEntityByProp : function(prop, value) { | |
var objList = []; | |
// for string comparaisons | |
var _regExp = new RegExp(value, "i"); | |
function compare(obj, prop) { | |
if (typeof (obj[prop]) === 'string') { | |
if (obj[prop].match(_regExp)) { | |
objList.push(obj); | |
} | |
} else if (obj[prop] === value) { | |
objList.push(obj); | |
} | |
} | |
for (var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (obj instanceof me.ObjectContainer) { | |
compare(obj, prop); | |
objList = objList.concat(obj.getEntityByProp(prop, value)); | |
} else if (obj.isEntity) { | |
compare(obj, prop); | |
} | |
} | |
return objList; | |
}, | |
/** | |
* Removes (and optionally destroys) a child from the container.<br> | |
* (removal is immediate and unconditional)<br> | |
* Never use keepalive=true with objects from {@link me.entityPool}. Doing so will create a memory leak. | |
* @name removeChild | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
* @param {Boolean} [keepalive=False] True to prevent calling child.destroy() | |
*/ | |
removeChild : function(child, keepalive) { | |
if (this.hasChild(child)) { | |
child.ancestor = undefined; | |
if (!keepalive) { | |
if (typeof (child.destroy) === 'function') { | |
child.destroy(); | |
} | |
me.entityPool.freeInstance(child); | |
} | |
this.children.splice( this.getChildIndex(child), 1 ); | |
} else { | |
throw "melonJS (me.ObjectContainer): " + child + " The supplied entity must be a child of the caller " + this; | |
} | |
}, | |
/** | |
* Automatically set the specified property of all childs to the given value | |
* @name setChildsProperty | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {String} property property name | |
* @param {Object} value property value | |
* @param {Boolean} [recursive=false] recursively apply the value to child containers if true | |
*/ | |
setChildsProperty : function(prop, val, recursive) { | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if ((recursive === true) && (obj instanceof me.ObjectContainer)) { | |
obj.setChildsProperty(prop, val, recursive); | |
} | |
obj[prop] = val; | |
} | |
}, | |
/** | |
* Move the child in the group one step forward (z depth). | |
* @name moveUp | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveUp : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex -1 >= 0) { | |
// note : we use an inverted loop | |
this.swapChildren(child, this.getChildAt(childIndex-1)); | |
} | |
}, | |
/** | |
* Move the child in the group one step backward (z depth). | |
* @name moveDown | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveDown : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex+1 < this.children.length) { | |
// note : we use an inverted loop | |
this.swapChildren(child, this.getChildAt(childIndex+1)); | |
} | |
}, | |
/** | |
* Move the specified child to the top(z depth). | |
* @name moveToTop | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveToTop : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex > 0) { | |
// note : we use an inverted loop | |
this.splice(0, 0, this.splice(childIndex, 1)[0]); | |
// increment our child z value based on the previous child depth | |
child.z = this.children[1].z + 1; | |
} | |
}, | |
/** | |
* Move the specified child the bottom (z depth). | |
* @name moveToBottom | |
* @memberOf me.ObjectContainer | |
* @function | |
* @param {me.Renderable} child | |
*/ | |
moveToBottom : function(child) { | |
var childIndex = this.getChildIndex(child); | |
if (childIndex < (this.children.length -1)) { | |
// note : we use an inverted loop | |
this.splice((this.children.length -1), 0, this.splice(childIndex, 1)[0]); | |
// increment our child z value based on the next child depth | |
child.z = this.children[(this.children.length -2)].z - 1; | |
} | |
}, | |
/** | |
* Checks if the specified entity collides with others entities in this container | |
* @name collide | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {me.Renderable} obj Object to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collide : function(objA, multiple) { | |
return this.collideType(objA, null, multiple); | |
}, | |
/** | |
* Checks if the specified entity collides with others entities in this container | |
* @name collideType | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {me.Renderable} obj Object to be tested for collision | |
* @param {String} [type=undefined] Entity type to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collideType : function(objA, type, multiple) { | |
var res, mres; | |
// make sure we have a boolean | |
multiple = multiple===true ? true : false; | |
if (multiple===true) { | |
mres = []; | |
} | |
// this should be replace by a list of the 4 adjacent cell around the object requesting collision | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if ( (obj.inViewport || obj.alwaysUpdate ) && obj.collidable ) { | |
// recursivly check through | |
if (obj instanceof me.ObjectContainer) { | |
res = obj.collideType(objA, type, multiple); | |
if (multiple) { | |
mres.concat(res); | |
} else if (res) { | |
// the child container returned collision information | |
return res; | |
} | |
} else if ( (obj !== objA) && (!type || (obj.type === type)) ) { | |
res = obj.collisionBox["collideWith"+objA.shapeType].call(obj.collisionBox, objA.collisionBox); | |
if (res.x !== 0 || res.y !== 0) { | |
// notify the object | |
obj.onCollision.call(obj, res, objA); | |
// return the type (deprecated) | |
res.type = obj.type; | |
// return a reference of the colliding object | |
res.obj = obj; | |
// stop here if we don't look for multiple collision detection | |
if (!multiple) { | |
return res; | |
} | |
mres.push(res); | |
} | |
} | |
} | |
} | |
return multiple?mres:null; | |
}, | |
/** | |
* Manually trigger the sort of all the childs in the container</p> | |
* @name sort | |
* @memberOf me.ObjectContainer | |
* @public | |
* @function | |
* @param {Boolean} [recursive=false] recursively sort all containers if true | |
*/ | |
sort : function(recursive) { | |
// do nothing if there is already a pending sort | |
if (this.pendingSort === null) { | |
if (recursive === true) { | |
// trigger other child container sort function (if any) | |
for (var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (obj instanceof me.ObjectContainer) { | |
// note : this will generate one defered sorting function | |
// for each existing containe | |
obj.sort(recursive); | |
} | |
} | |
} | |
/** @ignore */ | |
this.pendingSort = (function (self) { | |
// sort everything in this container | |
self.children.sort(self["_sort"+self.sortOn.toUpperCase()]); | |
// clear the defer id | |
self.pendingSort = null; | |
// make sure we redraw everything | |
me.game.repaint(); | |
}.defer(this)); | |
} | |
}, | |
/** | |
* Z Sorting function | |
* @ignore | |
*/ | |
_sortZ : function (a,b) { | |
return (b.z) - (a.z); | |
}, | |
/** | |
* X Sorting function | |
* @ignore | |
*/ | |
_sortX : function(a,b) { | |
/* ? */ | |
var result = (b.z - a.z); | |
return (result ? result : ((b.pos && b.pos.x) - (a.pos && a.pos.x)) || 0); | |
}, | |
/** | |
* Y Sorting function | |
* @ignore | |
*/ | |
_sortY : function(a,b) { | |
var result = (b.z - a.z); | |
return (result ? result : ((b.pos && b.pos.y) - (a.pos && a.pos.y)) || 0); | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
// cancel any sort operation | |
if (this.pendingSort) { | |
clearTimeout(this.pendingSort); | |
this.pendingSort = null; | |
} | |
// delete all children | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
// don't remove it if a persistent object | |
if ( !obj.isPersistent ) { | |
this.removeChild(obj); | |
} | |
} | |
}, | |
/** | |
* @ignore | |
*/ | |
update : function() { | |
var isDirty = false; | |
var isFloating = false; | |
var isPaused = me.state.isPaused(); | |
var isTranslated; | |
var x; | |
var y; | |
var viewport = me.game.viewport; | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
if (isPaused && (!obj.updateWhenPaused)) { | |
// skip this object | |
continue; | |
} | |
if ( obj.isRenderable ) { | |
isFloating = (globalFloatingCounter > 0 || obj.floating); | |
if (isFloating) { | |
globalFloatingCounter++; | |
} | |
// Translate global context | |
isTranslated = (obj.visible && !isFloating); | |
if (isTranslated) { | |
x = obj.pos.x; | |
y = obj.pos.y; | |
globalTranslation.translateV(obj.pos); | |
globalTranslation.set(globalTranslation.pos, obj.width, obj.height); | |
} | |
// check if object is visible | |
obj.inViewport = obj.visible && ( | |
isFloating || viewport.isVisible(globalTranslation) | |
); | |
// update our object | |
isDirty |= (obj.inViewport || obj.alwaysUpdate) && obj.update(); | |
// Undo global context translation | |
if (isTranslated) { | |
globalTranslation.translate(-x, -y); | |
} | |
if (globalFloatingCounter > 0) { | |
globalFloatingCounter--; | |
} | |
} else { | |
// just directly call update() for non renderable object | |
isDirty |= obj.update(); | |
} | |
} | |
return isDirty; | |
}, | |
/** | |
* @ignore | |
*/ | |
draw : function(context, rect) { | |
var viewport = me.game.viewport; | |
var isFloating = false; | |
this.drawCount = 0; | |
// save the current context | |
context.save(); | |
// apply the group opacity | |
context.globalAlpha *= this.getOpacity(); | |
// translate to the container position | |
context.translate(this.pos.x, this.pos.y); | |
for ( var i = this.children.length, obj; i--, obj = this.children[i];) { | |
isFloating = obj.floating; | |
if (obj.isRenderable && (obj.inViewport || (isFloating && obj.visible))) { | |
if (isFloating === true) { | |
context.save(); | |
// translate back object | |
context.translate( | |
viewport.screenX -this.pos.x, | |
viewport.screenY -this.pos.y | |
); | |
} | |
// draw the object | |
obj.draw(context, rect); | |
if (isFloating === true) { | |
context.restore(); | |
} | |
this.drawCount++; | |
} | |
} | |
// restore the context | |
context.restore(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function($) { | |
/** | |
* me.ObjectSettings contains the object attributes defined in Tiled<br> | |
* and is created by the engine and passed as parameter to the corresponding object when loading a level<br> | |
* the field marked Mandatory are to be defined either in Tiled, or in the before calling the parent constructor<br> | |
* <img src="images/object_properties.png"/><br> | |
* @class | |
* @protected | |
* @memberOf me | |
*/ | |
me.ObjectSettings = { | |
/** | |
* object entity name<br> | |
* as defined in the Tiled Object Properties | |
* @public | |
* @type String | |
* @name name | |
* @memberOf me.ObjectSettings | |
*/ | |
name : null, | |
/** | |
* image ressource name to be loaded<br> | |
* MANDATORY<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type String | |
* @name image | |
* @memberOf me.ObjectSettings | |
*/ | |
image : null, | |
/** | |
* specify a transparent color for the image in rgb format (#rrggbb)<br> | |
* OPTIONAL<br> | |
* (using this option will imply processing time on the image) | |
* @public | |
* @deprecated Use PNG or GIF with transparency instead | |
* @type String | |
* @name transparent_color | |
* @memberOf me.ObjectSettings | |
*/ | |
transparent_color : null, | |
/** | |
* width of a single sprite in the spritesheet<br> | |
* MANDATORY<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type Int | |
* @name spritewidth | |
* @memberOf me.ObjectSettings | |
*/ | |
spritewidth : null, | |
/** | |
* height of a single sprite in the spritesheet<br> | |
* OPTIONAL<br> | |
* if not specified the value will be set to the corresponding image height<br> | |
* (in case of TiledObject, this field is automatically set) | |
* @public | |
* @type Int | |
* @name spriteheight | |
* @memberOf me.ObjectSettings | |
*/ | |
spriteheight : null, | |
/** | |
* custom type for collision detection<br> | |
* OPTIONAL | |
* @public | |
* @type String | |
* @name type | |
* @memberOf me.ObjectSettings | |
*/ | |
type : 0, | |
/** | |
* Enable collision detection for this object<br> | |
* OPTIONAL | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectSettings | |
*/ | |
collidable : true | |
}; | |
/** | |
* A pool of Object entity <br> | |
* This object is used for object pooling - a technique that might speed up your game | |
* if used properly. <br> | |
* If some of your classes will be instantiated and removed a lot at a time, it is a | |
* good idea to add the class to this entity pool. A separate pool for that class | |
* will be created, which will reuse objects of the class. That way they won't be instantiated | |
* each time you need a new one (slowing your game), but stored into that pool and taking one | |
* already instantiated when you need it.<br><br> | |
* This object is also used by the engine to instantiate objects defined in the map, | |
* which means, that on level loading the engine will try to instantiate every object | |
* found in the map, based on the user defined name in each Object Properties<br> | |
* <img src="images/object_properties.png"/><br> | |
* There is no constructor function for me.entityPool, this is a static object | |
* @namespace me.entityPool | |
* @memberOf me | |
*/ | |
me.entityPool = (function() { | |
// hold public stuff in our singletong | |
var obj = {}; | |
/*--------------------------------------------- | |
PRIVATE STUFF | |
---------------------------------------------*/ | |
var entityClass = {}; | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/*--- | |
init | |
---*/ | |
obj.init = function() { | |
// add default entity object | |
obj.add("me.ObjectEntity", me.ObjectEntity); | |
obj.add("me.CollectableEntity", me.CollectableEntity); | |
obj.add("me.LevelEntity", me.LevelEntity); | |
obj.add("me.Tween", me.Tween, true); | |
}; | |
/** | |
* Add an object to the pool. <br> | |
* Pooling must be set to true if more than one such objects will be created. <br> | |
* (note) If pooling is enabled, you shouldn't instantiate objects with `new`. | |
* See examples in {@link me.entityPool#newInstanceOf} | |
* @name add | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {String} className as defined in the Name field of the Object Properties (in Tiled) | |
* @param {Object} class corresponding Class to be instantiated | |
* @param {Boolean} [objectPooling=false] enables object pooling for the specified class | |
* - speeds up the game by reusing existing objects | |
* @example | |
* // add our users defined entities in the entity pool | |
* me.entityPool.add("playerspawnpoint", PlayerEntity); | |
* me.entityPool.add("cherryentity", CherryEntity, true); | |
* me.entityPool.add("heartentity", HeartEntity, true); | |
* me.entityPool.add("starentity", StarEntity, true); | |
*/ | |
obj.add = function(className, entityObj, pooling) { | |
if (!pooling) { | |
entityClass[className.toLowerCase()] = { | |
"class" : entityObj, | |
"pool" : undefined | |
}; | |
return; | |
} | |
entityClass[className.toLowerCase()] = { | |
"class" : entityObj, | |
"pool" : [], | |
"active" : [] | |
}; | |
}; | |
/** | |
* Return a new instance of the requested object (if added into the object pool) | |
* @name newInstanceOf | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {String} className as used in {@link me.entityPool#add} | |
* @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object | |
* @example | |
* me.entityPool.add("player", PlayerEntity); | |
* var player = me.entityPool.newInstanceOf("player"); | |
* @example | |
* me.entityPool.add("bullet", BulletEntity, true); | |
* me.entityPool.add("enemy", EnemyEntity, true); | |
* // ... | |
* // when we need to manually create a new bullet: | |
* var bullet = me.entityPool.newInstanceOf("bullet", x, y, direction); | |
* // ... | |
* // params aren't a fixed number | |
* // when we need new enemy we can add more params, that the object construct requires: | |
* var enemy = me.entityPool.newInstanceOf("enemy", x, y, direction, speed, power, life); | |
* // ... | |
* // when we want to destroy existing object, the remove | |
* // function will ensure the object can then be reallocated later | |
* me.game.remove(enemy); | |
* me.game.remove(bullet); | |
*/ | |
obj.newInstanceOf = function(data) { | |
var name = typeof data === 'string' ? data.toLowerCase() : undefined; | |
var args = Array.prototype.slice.call(arguments); | |
if (name && entityClass[name]) { | |
var proto; | |
if (!entityClass[name]['pool']) { | |
proto = entityClass[name]["class"]; | |
args[0] = proto; | |
return new (proto.bind.apply(proto, args))(); | |
} | |
var obj, entity = entityClass[name]; | |
proto = entity["class"]; | |
if (entity["pool"].length > 0) { | |
obj = entity["pool"].pop(); | |
// call the object init function if defined (JR's Inheritance) | |
if (typeof obj.init === "function") { | |
obj.init.apply(obj, args.slice(1)); | |
} | |
// call the object onResetEvent function if defined | |
if (typeof obj.onResetEvent === "function") { | |
obj.onResetEvent.apply(obj, args.slice(1)); | |
} | |
} else { | |
args[0] = proto; | |
obj = new (proto.bind.apply(proto, args))(); | |
obj.className = name; | |
} | |
entity["active"].push(obj); | |
return obj; | |
} | |
// Tile objects can be created with a GID attribute; | |
// The TMX parser will use it to create the image property. | |
var settings = arguments[3]; | |
if (settings && settings.gid && settings.image) { | |
return new me.SpriteObject(settings.x, settings.y, settings.image); | |
} | |
if (name) { | |
console.error("Cannot instantiate entity of type '" + data + "': Class not found!"); | |
} | |
return null; | |
}; | |
/** | |
* purge the entity pool from any inactive object <br> | |
* Object pooling must be enabled for this function to work<br> | |
* note: this will trigger the garbage collector | |
* @name purge | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
*/ | |
obj.purge = function() { | |
for (var className in entityClass) { | |
entityClass[className]["pool"] = []; | |
} | |
}; | |
/** | |
* Remove object from the entity pool <br> | |
* Object pooling for the object class must be enabled, | |
* and object must have been instantiated using {@link me.entityPool#newInstanceOf}, | |
* otherwise this function won't work | |
* @name freeInstance | |
* @memberOf me.entityPool | |
* @public | |
* @function | |
* @param {Object} instance to be removed | |
*/ | |
obj.freeInstance = function(obj) { | |
var name = obj.className; | |
if (!name || !entityClass[name]) { | |
return; | |
} | |
var notFound = true; | |
for (var i = 0, len = entityClass[name]["active"].length; i < len; i++) { | |
if (entityClass[name]["active"][i] === obj) { | |
notFound = false; | |
entityClass[name]["active"].splice(i, 1); | |
break; | |
} | |
} | |
if (notFound) { | |
return; | |
} | |
entityClass[name]["pool"].push(obj); | |
}; | |
// return our object | |
return obj; | |
})(); | |
/************************************************************************************/ | |
/* */ | |
/* a generic object entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* a Generic Object Entity<br> | |
* Object Properties (settings) are to be defined in Tiled, <br> | |
* or when calling the parent constructor | |
* | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {me.ObjectSettings} settings Object Properties as defined in Tiled <br> <img src="images/object_properties.png"/> | |
*/ | |
me.ObjectEntity = me.Renderable.extend( | |
/** @scope me.ObjectEntity.prototype */ { | |
/** | |
* define the type of the object<br> | |
* default value : none<br> | |
* @public | |
* @type String | |
* @name type | |
* @memberOf me.ObjectEntity | |
*/ | |
type : 0, | |
/** | |
* flag to enable collision detection for this object<br> | |
* default value : true<br> | |
* @public | |
* @type Boolean | |
* @name collidable | |
* @memberOf me.ObjectEntity | |
*/ | |
collidable : true, | |
/** | |
* Entity collision Box<br> | |
* (reference to me.ObjectEntity.shapes[0].getBounds) | |
* @public | |
* @deprecated | |
* @type me.Rect | |
* @name collisionBox | |
* @memberOf me.ObjectEntity | |
*/ | |
collisionBox : null, | |
/** | |
* Entity collision shapes<br> | |
* (RFU - Reserved for Future Usage) | |
* @protected | |
* @type Object[] | |
* @name shapes | |
* @memberOf me.ObjectEntity | |
*/ | |
shapes : null, | |
/** | |
* The entity renderable object (if defined) | |
* @public | |
* @type me.Renderable | |
* @name renderable | |
* @memberOf me.ObjectEntity | |
*/ | |
renderable : null, | |
// just to keep track of when we flip | |
lastflipX : false, | |
lastflipY : false, | |
/** @ignore */ | |
init : function(x, y, settings) { | |
// instantiate pos here to avoid | |
// later re-instantiation | |
if (this.pos === null) { | |
this.pos = new me.Vector2d(); | |
} | |
// call the parent constructor | |
this.parent(this.pos.set(x,y), | |
~~settings.spritewidth || ~~settings.width, | |
~~settings.spriteheight || ~~settings.height); | |
if (settings.image) { | |
var image = typeof settings.image === "string" ? me.loader.getImage(settings.image) : settings.image; | |
this.renderable = new me.AnimationSheet(0, 0, image, | |
~~settings.spritewidth, | |
~~settings.spriteheight, | |
~~settings.spacing, | |
~~settings.margin); | |
// check for user defined transparent color | |
if (settings.transparent_color) { | |
this.renderable.setTransparency(settings.transparent_color); | |
} | |
} | |
// set the object entity name | |
this.name = settings.name?settings.name.toLowerCase():""; | |
/** | |
* entity current velocity<br> | |
* @public | |
* @type me.Vector2d | |
* @name vel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.vel === undefined) { | |
this.vel = new me.Vector2d(); | |
} | |
this.vel.set(0,0); | |
/** | |
* entity current acceleration<br> | |
* @public | |
* @type me.Vector2d | |
* @name accel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.accel === undefined) { | |
this.accel = new me.Vector2d(); | |
} | |
this.accel.set(0,0); | |
/** | |
* entity current friction<br> | |
* @public | |
* @name friction | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.friction === undefined) { | |
this.friction = new me.Vector2d(); | |
} | |
this.friction.set(0,0); | |
/** | |
* max velocity (to limit entity velocity)<br> | |
* @public | |
* @type me.Vector2d | |
* @name maxVel | |
* @memberOf me.ObjectEntity | |
*/ | |
if (this.maxVel === undefined) { | |
this.maxVel = new me.Vector2d(); | |
} | |
this.maxVel.set(1000, 1000); | |
// some default contants | |
/** | |
* Default gravity value of the entity<br> | |
* default value : 0.98 (earth gravity)<br> | |
* to be set to 0 for RPG, shooter, etc...<br> | |
* Note: Gravity can also globally be defined through me.sys.gravity | |
* @public | |
* @see me.sys.gravity | |
* @type Number | |
* @name gravity | |
* @memberOf me.ObjectEntity | |
*/ | |
this.gravity = me.sys.gravity!==undefined ? me.sys.gravity : 0.98; | |
// just to identify our object | |
this.isEntity = true; | |
/** | |
* dead/living state of the entity<br> | |
* default value : true | |
* @public | |
* @type Boolean | |
* @name alive | |
* @memberOf me.ObjectEntity | |
*/ | |
this.alive = true; | |
// make sure it's visible by default | |
this.visible = true; | |
// and also non floating by default | |
this.floating = false; | |
// and non persistent per default | |
this.isPersistent = false; | |
/** | |
* falling state of the object<br> | |
* true if the object is falling<br> | |
* false if the object is standing on something<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name falling | |
* @memberOf me.ObjectEntity | |
*/ | |
this.falling = false; | |
/** | |
* jumping state of the object<br> | |
* equal true if the entity is jumping<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name jumping | |
* @memberOf me.ObjectEntity | |
*/ | |
this.jumping = true; | |
// some usefull slope variable | |
this.slopeY = 0; | |
/** | |
* equal true if the entity is standing on a slope<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name onslope | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onslope = false; | |
/** | |
* equal true if the entity is on a ladder<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name onladder | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onladder = false; | |
/** | |
* equal true if the entity can go down on a ladder<br> | |
* @readonly | |
* @public | |
* @type Boolean | |
* @name disableTopLadderCollision | |
* @memberOf me.ObjectEntity | |
*/ | |
this.disableTopLadderCollision = false; | |
// to enable collision detection | |
this.collidable = typeof(settings.collidable) !== "undefined" ? settings.collidable : true; | |
// default objec type | |
this.type = settings.type || 0; | |
// default flip value | |
this.lastflipX = this.lastflipY = false; | |
// ref to the collision map | |
this.collisionMap = me.game.collisionMap; | |
/** | |
* Define if an entity can go through breakable tiles<br> | |
* default value : false<br> | |
* @public | |
* @type Boolean | |
* @name canBreakTile | |
* @memberOf me.ObjectEntity | |
*/ | |
this.canBreakTile = false; | |
/** | |
* a callback when an entity break a tile<br> | |
* @public | |
* @callback | |
* @name onTileBreak | |
* @memberOf me.ObjectEntity | |
*/ | |
this.onTileBreak = null; | |
// add a default shape | |
if (settings.isEllipse===true) { | |
// ellipse | |
this.addShape(new me.Ellipse(new me.Vector2d(0,0), this.width, this.height)); | |
} | |
else if ((settings.isPolygon===true) || (settings.isPolyline===true)) { | |
// add a polyshape | |
this.addShape(new me.PolyShape(new me.Vector2d(0,0), settings.points, settings.isPolygon)); | |
// set the entity object based on the bounding box size ? | |
this.width = this.collisionBox.width; | |
this.height = this.collisionBox.height; | |
} | |
else { | |
// add a rectangle | |
this.addShape(new me.Rect(new me.Vector2d(0,0), this.width, this.height)); | |
} | |
}, | |
/** | |
* specify the size of the hit box for collision detection<br> | |
* (allow to have a specific size for each object)<br> | |
* e.g. : object with resized collision box :<br> | |
* <img src="images/me.Rect.colpos.png"/> | |
* @name updateColRect | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x x offset (specify -1 to not change the width) | |
* @param {Number} w width of the hit box | |
* @param {Number} y y offset (specify -1 to not change the height) | |
* @param {Number} h height of the hit box | |
*/ | |
updateColRect : function(x, w, y, h) { | |
this.collisionBox.adjustSize(x, w, y, h); | |
}, | |
/** | |
* add a collision shape to this entity< | |
* @name addShape | |
* @memberOf me.ObjectEntity | |
* @public | |
* @function | |
* @param {me.objet} shape a shape object | |
*/ | |
addShape : function(shape) { | |
if (this.shapes === null) { | |
this.shapes = []; | |
} | |
this.shapes.push(shape); | |
// some hack to get the collisionBox working in this branch | |
// to be removed once the ticket #103 will be done | |
if (this.shapes.length === 1) { | |
this.collisionBox = this.shapes[0].getBounds(); | |
// collisionBox pos vector is a reference to this pos vector | |
this.collisionBox.pos = this.pos; | |
// offset position vector | |
this.pos.add(this.shapes[0].offset); | |
} | |
}, | |
/** | |
* onCollision Event function<br> | |
* called by the game manager when the object collide with shtg<br> | |
* by default, if the object type is Collectable, the destroy function is called | |
* @name onCollision | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} res collision vector | |
* @param {me.ObjectEntity} obj the other object that hit this object | |
* @protected | |
*/ | |
onCollision : function(res, obj) { | |
// destroy the object if collectable | |
if (this.collidable && (this.type === me.game.COLLECTABLE_OBJECT)) | |
me.game.remove(this); | |
}, | |
/** | |
* set the entity default velocity<br> | |
* note : velocity is by default limited to the same value, see setMaxVelocity if needed<br> | |
* @name setVelocity | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x velocity on x axis | |
* @param {Number} y velocity on y axis | |
* @protected | |
*/ | |
setVelocity : function(x, y) { | |
this.accel.x = x !== 0 ? x : this.accel.x; | |
this.accel.y = y !== 0 ? y : this.accel.y; | |
// limit by default to the same max value | |
this.setMaxVelocity(x,y); | |
}, | |
/** | |
* cap the entity velocity to the specified value<br> | |
* @name setMaxVelocity | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x max velocity on x axis | |
* @param {Number} y max velocity on y axis | |
* @protected | |
*/ | |
setMaxVelocity : function(x, y) { | |
this.maxVel.x = x; | |
this.maxVel.y = y; | |
}, | |
/** | |
* set the entity default friction<br> | |
* @name setFriction | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Number} x horizontal friction | |
* @param {Number} y vertical friction | |
* @protected | |
*/ | |
setFriction : function(x, y) { | |
this.friction.x = x || 0; | |
this.friction.y = y || 0; | |
}, | |
/** | |
* Flip object on horizontal axis | |
* @name flipX | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipX : function(flip) { | |
if (flip !== this.lastflipX) { | |
this.lastflipX = flip; | |
if (this.renderable && this.renderable.flipX) { | |
// flip the animation | |
this.renderable.flipX(flip); | |
} | |
// flip the collision box | |
this.collisionBox.flipX(this.width); | |
} | |
}, | |
/** | |
* Flip object on vertical axis | |
* @name flipY | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} flip enable/disable flip | |
*/ | |
flipY : function(flip) { | |
if (flip !== this.lastflipY) { | |
this.lastflipY = flip; | |
if (this.renderable && this.renderable.flipY) { | |
// flip the animation | |
this.renderable.flipY(flip); | |
} | |
// flip the collision box | |
this.collisionBox.flipY(this.height); | |
} | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity move left of right<br> | |
* @name doWalk | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} left will automatically flip horizontally the entity sprite | |
* @protected | |
* @deprecated | |
* @example | |
* if (me.input.isKeyPressed('left')) | |
* { | |
* this.doWalk(true); | |
* } | |
* else if (me.input.isKeyPressed('right')) | |
* { | |
* this.doWalk(false); | |
* } | |
*/ | |
doWalk : function(left) { | |
this.flipX(left); | |
this.vel.x += (left) ? -this.accel.x * me.timer.tick : this.accel.x * me.timer.tick; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity move up and down<br> | |
* only valid is the player is on a ladder | |
* @name doClimb | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} up will automatically flip vertically the entity sprite | |
* @protected | |
* @deprecated | |
* @example | |
* if (me.input.isKeyPressed('up')) | |
* { | |
* this.doClimb(true); | |
* } | |
* else if (me.input.isKeyPressed('down')) | |
* { | |
* this.doClimb(false); | |
* } | |
*/ | |
doClimb : function(up) { | |
// add the player x acceleration to the y velocity | |
if (this.onladder) { | |
this.vel.y = (up) ? -this.accel.x * me.timer.tick | |
: this.accel.x * me.timer.tick; | |
this.disableTopLadderCollision = !up; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* make the entity jump<br> | |
* @name doJump | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @deprecated | |
*/ | |
doJump : function() { | |
// only jump if standing | |
if (!this.jumping && !this.falling) { | |
this.vel.y = -this.maxVel.y * me.timer.tick; | |
this.jumping = true; | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* helper function for platform games: <br> | |
* force to the entity to jump (for double jump)<br> | |
* @name forceJump | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @deprecated | |
*/ | |
forceJump : function() { | |
this.jumping = this.falling = false; | |
this.doJump(); | |
}, | |
/** | |
* return the distance to the specified entity | |
* @name distanceTo | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.ObjectEntity} entity Entity | |
* @return {Number} distance | |
*/ | |
distanceTo: function(e) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var dx = (this.pos.x + this.hWidth) - (e.pos.x + e.hWidth); | |
var dy = (this.pos.y + this.hHeight) - (e.pos.y + e.hHeight); | |
return Math.sqrt(dx*dx+dy*dy); | |
}, | |
/** | |
* return the distance to the specified point | |
* @name distanceToPoint | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} vector vector | |
* @return {Number} distance | |
*/ | |
distanceToPoint: function(v) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var dx = (this.pos.x + this.hWidth) - (v.x); | |
var dy = (this.pos.y + this.hHeight) - (v.y); | |
return Math.sqrt(dx*dx+dy*dy); | |
}, | |
/** | |
* return the angle to the specified entity | |
* @name angleTo | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.ObjectEntity} entity Entity | |
* @return {Number} angle in radians | |
*/ | |
angleTo: function(e) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var ax = (e.pos.x + e.hWidth) - (this.pos.x + this.hWidth); | |
var ay = (e.pos.y + e.hHeight) - (this.pos.y + this.hHeight); | |
return Math.atan2(ay, ax); | |
}, | |
/** | |
* return the angle to the specified point | |
* @name angleToPoint | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {me.Vector2d} vector vector | |
* @return {Number} angle in radians | |
*/ | |
angleToPoint: function(v) | |
{ | |
// the me.Vector2d object also implements the same function, but | |
// we have to use here the center of both entities | |
var ax = (v.x) - (this.pos.x + this.hWidth); | |
var ay = (v.y) - (this.pos.y + this.hHeight); | |
return Math.atan2(ay, ax); | |
}, | |
/** | |
* handle the player movement on a slope | |
* and update vel value | |
* @ignore | |
*/ | |
checkSlope : function(tile, left) { | |
// first make the object stick to the tile | |
this.pos.y = tile.pos.y - this.height; | |
// normally the check should be on the object center point, | |
// but since the collision check is done on corner, we must do the same thing here | |
if (left) | |
this.slopeY = tile.height - (this.collisionBox.right + this.vel.x - tile.pos.x); | |
else | |
this.slopeY = (this.collisionBox.left + this.vel.x - tile.pos.x); | |
// cancel y vel | |
this.vel.y = 0; | |
// set player position (+ workaround when entering/exiting slopes tile) | |
this.pos.y += this.slopeY.clamp(0, tile.height); | |
}, | |
/** | |
* compute the new velocity value | |
* @ignore | |
*/ | |
computeVelocity : function(vel) { | |
// apply gravity (if any) | |
if (this.gravity) { | |
// apply a constant gravity (if not on a ladder) | |
vel.y += !this.onladder?(this.gravity * me.timer.tick):0; | |
// check if falling / jumping | |
this.falling = (vel.y > 0); | |
this.jumping = this.falling?false:this.jumping; | |
} | |
// apply friction | |
if (this.friction.x) | |
vel.x = me.utils.applyFriction(vel.x,this.friction.x); | |
if (this.friction.y) | |
vel.y = me.utils.applyFriction(vel.y,this.friction.y); | |
// cap velocity | |
if (vel.y !== 0) | |
vel.y = vel.y.clamp(-this.maxVel.y,this.maxVel.y); | |
if (vel.x !== 0) | |
vel.x = vel.x.clamp(-this.maxVel.x,this.maxVel.x); | |
}, | |
/** | |
* handle the player movement, "trying" to update his position<br> | |
* @name updateMovement | |
* @memberOf me.ObjectEntity | |
* @function | |
* @return {me.Vector2d} a collision vector | |
* @example | |
* // make the player move | |
* if (me.input.isKeyPressed('left')) | |
* { | |
* this.vel.x -= this.accel.x * me.timer.tick; | |
* } | |
* else if (me.input.isKeyPressed('right')) | |
* { | |
* this.vel.x += this.accel.x * me.timer.tick; | |
* } | |
* // update player position | |
* var res = this.updateMovement(); | |
* | |
* // check for collision result with the environment | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else if(res.y != 0) | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* | |
* // display the tile type | |
* console.log(res.yprop.type) | |
* } | |
* | |
* // check player status after collision check | |
* var updated = (this.vel.x!=0 || this.vel.y!=0); | |
*/ | |
updateMovement : function() { | |
this.computeVelocity(this.vel); | |
// Adjust position only on collidable object | |
var collision; | |
if (this.collidable) { | |
// check for collision | |
collision = this.collisionMap.checkCollision(this.collisionBox, this.vel); | |
// update some flags | |
this.onslope = collision.yprop.isSlope || collision.xprop.isSlope; | |
// clear the ladder flag | |
this.onladder = false; | |
// y collision | |
if (collision.y) { | |
// going down, collision with the floor | |
this.onladder = collision.yprop.isLadder || collision.yprop.isTopLadder; | |
if (collision.y > 0) { | |
if (collision.yprop.isSolid || | |
(collision.yprop.isPlatform && (this.collisionBox.bottom - 1 <= collision.ytile.pos.y)) || | |
(collision.yprop.isTopLadder && !this.disableTopLadderCollision)) { | |
// adjust position to the corresponding tile | |
this.pos.y = ~~this.pos.y; | |
this.vel.y = (this.falling) ?collision.ytile.pos.y - this.collisionBox.bottom: 0 ; | |
this.falling = false; | |
} | |
else if (collision.yprop.isSlope && !this.jumping) { | |
// we stop falling | |
this.checkSlope(collision.ytile, collision.yprop.isLeftSlope); | |
this.falling = false; | |
} | |
else if (collision.yprop.isBreakable) { | |
if (this.canBreakTile) { | |
// remove the tile | |
me.game.currentLevel.clearTile(collision.ytile.col, collision.ytile.row); | |
if (this.onTileBreak) | |
this.onTileBreak(); | |
} | |
else { | |
// adjust position to the corresponding tile | |
this.pos.y = ~~this.pos.y; | |
this.vel.y = (this.falling) ?collision.ytile.pos.y - this.collisionBox.bottom: 0; | |
this.falling = false; | |
} | |
} | |
} | |
// going up, collision with ceiling | |
else if (collision.y < 0) { | |
if (!collision.yprop.isPlatform && !collision.yprop.isLadder && !collision.yprop.isTopLadder) { | |
this.falling = true; | |
// cancel the y velocity | |
this.vel.y = 0; | |
} | |
} | |
} | |
// x collision | |
if (collision.x) { | |
this.onladder = collision.xprop.isLadder || collision.yprop.isTopLadder; | |
if (collision.xprop.isSlope && !this.jumping) { | |
this.checkSlope(collision.xtile, collision.xprop.isLeftSlope); | |
this.falling = false; | |
} else { | |
// can walk through the platform & ladder | |
if (!collision.xprop.isPlatform && !collision.xprop.isLadder && !collision.xprop.isTopLadder) { | |
if (collision.xprop.isBreakable && this.canBreakTile) { | |
// remove the tile | |
me.game.currentLevel.clearTile(collision.xtile.col, collision.xtile.row); | |
if (this.onTileBreak) { | |
this.onTileBreak(); | |
} | |
} else { | |
this.vel.x = 0; | |
} | |
} | |
} | |
} | |
} | |
// update player position | |
this.pos.add(this.vel); | |
// returns the collision "vector" | |
return collision; | |
}, | |
/** | |
* Checks if this entity collides with others entities. | |
* @public | |
* @name collide | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (if multiple collision){@link me.Rect#collideVsAABB} | |
* @example | |
* // update player movement | |
* this.updateMovement(); | |
* | |
* // check for collision with other objects | |
* res = this.collide(); | |
* | |
* // check if we collide with an enemy : | |
* if (res && (res.obj.type == me.game.ENEMY_OBJECT)) | |
* { | |
* if (res.x != 0) | |
* { | |
* // x axis | |
* if (res.x<0) | |
* console.log("x axis : left side !"); | |
* else | |
* console.log("x axis : right side !"); | |
* } | |
* else | |
* { | |
* // y axis | |
* if (res.y<0) | |
* console.log("y axis : top side !"); | |
* else | |
* console.log("y axis : bottom side !"); | |
* } | |
* } | |
*/ | |
collide : function(multiple) { | |
return me.game.collide(this, multiple || false); | |
}, | |
/** | |
* Checks if the specified entity collides with others entities of the specified type. | |
* @public | |
* @name collideType | |
* @memberOf me.ObjectEntity | |
* @function | |
* @param {String} type Entity type to be tested for collision | |
* @param {Boolean} [multiple=false] check for multiple collision | |
* @return {me.Vector2d} collision vector or an array of collision vector (multiple collision){@link me.Rect#collideVsAABB} | |
*/ | |
collideType : function(type, multiple) { | |
return me.game.collideType(this, type, multiple || false); | |
}, | |
/** @ignore */ | |
update : function() { | |
if (this.renderable) { | |
return this.renderable.update(); | |
} | |
return false; | |
}, | |
/** | |
* @ignore | |
*/ | |
getBounds : function() { | |
if (this.renderable) { | |
// translate the renderable position since its | |
// position is relative to this entity | |
return this.renderable.getBounds().translateV(this.pos); | |
} | |
return null; | |
}, | |
/** | |
* object draw<br> | |
* not to be called by the end user<br> | |
* called by the game manager on each game loop | |
* @name draw | |
* @memberOf me.ObjectEntity | |
* @function | |
* @protected | |
* @param {Context2d} context 2d Context on which draw our object | |
**/ | |
draw : function(context) { | |
// draw the sprite if defined | |
if (this.renderable) { | |
// translate the renderable position (relative to the entity) | |
// and keeps it in the entity defined bounds | |
// anyway to optimize this ? | |
var x = ~~(this.pos.x + (this.anchorPoint.x * (this.width - this.renderable.width))); | |
var y = ~~(this.pos.y + (this.anchorPoint.y * (this.height - this.renderable.height))); | |
context.translate(x, y); | |
this.renderable.draw(context); | |
context.translate(-x, -y); | |
} | |
}, | |
/** | |
* Destroy function<br> | |
* @ignore | |
*/ | |
destroy : function() { | |
// free some property objects | |
if (this.renderable) { | |
this.renderable.destroy.apply(this.renderable, arguments); | |
this.renderable = null; | |
} | |
this.onDestroyEvent.apply(this, arguments); | |
this.collisionBox = null; | |
this.shapes = []; | |
}, | |
/** | |
* OnDestroy Notification function<br> | |
* Called by engine before deleting the object | |
* @name onDestroyEvent | |
* @memberOf me.ObjectEntity | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended ! | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a Collectable entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* @class | |
* @extends me.ObjectEntity | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the sprite object | |
* @param {Number} y the y coordinates of the sprite object | |
* @param {me.ObjectSettings} settings object settings | |
*/ | |
me.CollectableEntity = me.ObjectEntity.extend( | |
/** @scope me.CollectableEntity.prototype */ | |
{ | |
/** @ignore */ | |
init : function(x, y, settings) { | |
// call the parent constructor | |
this.parent(x, y, settings); | |
this.type = me.game.COLLECTABLE_OBJECT; | |
} | |
}); | |
/************************************************************************************/ | |
/* */ | |
/* a level entity */ | |
/* */ | |
/************************************************************************************/ | |
/** | |
* @class | |
* @extends me.ObjectEntity | |
* @memberOf me | |
* @constructor | |
* @param {Number} x the x coordinates of the object | |
* @param {Number} y the y coordinates of the object | |
* @param {me.ObjectSettings} settings object settings | |
*/ | |
me.LevelEntity = me.ObjectEntity.extend( | |
/** @scope me.LevelEntity.prototype */ | |
{ | |
/** @ignore */ | |
init : function(x, y, settings) { | |
this.parent(x, y, settings); | |
this.nextlevel = settings.to; | |
this.fade = settings.fade; | |
this.duration = settings.duration; | |
this.fading = false; | |
// a temp variable | |
this.gotolevel = settings.to; | |
}, | |
/** | |
* @ignore | |
*/ | |
onFadeComplete : function() { | |
me.levelDirector.loadLevel(this.gotolevel); | |
me.game.viewport.fadeOut(this.fade, this.duration); | |
}, | |
/** | |
* go to the specified level | |
* @name goTo | |
* @memberOf me.LevelEntity | |
* @function | |
* @param {String} [level=this.nextlevel] name of the level to load | |
* @protected | |
*/ | |
goTo : function(level) { | |
this.gotolevel = level || this.nextlevel; | |
// load a level | |
//console.log("going to : ", to); | |
if (this.fade && this.duration) { | |
if (!this.fading) { | |
this.fading = true; | |
me.game.viewport.fadeIn(this.fade, this.duration, | |
this.onFadeComplete.bind(this)); | |
} | |
} else { | |
me.levelDirector.loadLevel(this.gotolevel); | |
} | |
}, | |
/** @ignore */ | |
onCollision : function() { | |
this.goTo(); | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Screens objects & State machine | |
* | |
*/ | |
(function($) { | |
/** | |
* A class skeleton for "Screen" Object <br> | |
* every "screen" object (title screen, credits, ingame, etc...) to be managed <br> | |
* through the state manager must inherit from this base class. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {Boolean} [addAsObject] add the object in the game manager object pool<br> | |
* @param {Boolean} [isPersistent] make the screen persistent over level changes; requires addAsObject=true<br> | |
* @see me.state | |
* @example | |
* // create a custom loading screen | |
* var CustomLoadingScreen = me.ScreenObject.extend( | |
* { | |
* // constructor | |
* init: function() | |
* { | |
* // pass true to the parent constructor | |
* // as we draw our progress bar in the draw function | |
* this.parent(true); | |
* // a font logo | |
* this.logo = new me.Font('century gothic', 32, 'white'); | |
* // flag to know if we need to refresh the display | |
* this.invalidate = false; | |
* // load progress in percent | |
* this.loadPercent = 0; | |
* // setup a callback | |
* me.loader.onProgress = this.onProgressUpdate.bind(this); | |
* | |
* }, | |
* | |
* // will be fired by the loader each time a resource is loaded | |
* onProgressUpdate: function(progress) | |
* { | |
* this.loadPercent = progress; | |
* this.invalidate = true; | |
* }, | |
* | |
* | |
* // make sure the screen is only refreshed on load progress | |
* update: function() | |
* { | |
* if (this.invalidate===true) | |
* { | |
* // clear the flag | |
* this.invalidate = false; | |
* // and return true | |
* return true; | |
* } | |
* // else return false | |
* return false; | |
* }, | |
* | |
* // on destroy event | |
* onDestroyEvent : function () | |
* { | |
* // "nullify" all fonts | |
* this.logo = null; | |
* }, | |
* | |
* // draw function | |
* draw : function(context) | |
* { | |
* // clear the screen | |
* me.video.clearSurface (context, "black"); | |
* | |
* // measure the logo size | |
* logo_width = this.logo.measureText(context,"awesome loading screen").width; | |
* | |
* // draw our text somewhere in the middle | |
* this.logo.draw(context, | |
* "awesome loading screen", | |
* ((me.video.getWidth() - logo_width) / 2), | |
* (me.video.getHeight() + 60) / 2); | |
* | |
* // display a progressive loading bar | |
* var width = Math.floor(this.loadPercent * me.video.getWidth()); | |
* | |
* // draw the progress bar | |
* context.strokeStyle = "silver"; | |
* context.strokeRect(0, (me.video.getHeight() / 2) + 40, me.video.getWidth(), 6); | |
* context.fillStyle = "#89b002"; | |
* context.fillRect(2, (me.video.getHeight() / 2) + 42, width-4, 2); | |
* }, | |
* }); | |
* | |
*/ | |
me.ScreenObject = me.Renderable.extend( | |
/** @scope me.ScreenObject.prototype */ | |
{ | |
/** @ignore */ | |
addAsObject : false, | |
/** @ignore */ | |
visible : false, | |
/** @ignore */ | |
frame : 0, | |
/** | |
* Z-order for object sorting<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* default value : 999 | |
* @private | |
* @type Number | |
* @name z | |
* @memberOf me.ScreenObject | |
*/ | |
z : 999, | |
/** | |
* initialization function | |
* @ignore | |
*/ | |
init : function(addAsObject, isPersistent) { | |
this.parent(new me.Vector2d(0, 0), 0, 0); | |
this.addAsObject = this.visible = (addAsObject === true) || false; | |
this.isPersistent = (this.visible && (isPersistent === true)) || false; | |
}, | |
/** | |
* Object reset function | |
* @ignore | |
*/ | |
reset : function() { | |
// reset the game manager | |
me.game.reset(); | |
// reset the frame counter | |
this.frame = 0; | |
this.frameRate = Math.round(60/me.sys.fps); | |
// call the onReset Function | |
this.onResetEvent.apply(this, arguments); | |
// add our object to the GameObject Manager | |
// allowing to benefit from the keyboard event stuff | |
if (this.addAsObject) { | |
// make sure we are visible upon reset | |
this.visible = true; | |
// Always use screen coordinates | |
this.floating = true; | |
// update the screen size if added as an object | |
this.set(new me.Vector2d(), me.game.viewport.width, me.game.viewport.height); | |
// add ourself ! | |
me.game.add(this, this.z); | |
} | |
// sort the object pool | |
me.game.sort(); | |
}, | |
/** | |
* destroy function | |
* @ignore | |
*/ | |
destroy : function() { | |
// notify the object | |
this.onDestroyEvent.apply(this, arguments); | |
}, | |
/** | |
* update function<br> | |
* optional empty function<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* @name update | |
* @memberOf me.ScreenObject | |
* @function | |
* @example | |
* // define a Title Screen | |
* var TitleScreen = me.ScreenObject.extend( | |
* { | |
* // override the default constructor | |
* init : function() | |
* { | |
* //call the parent constructor giving true | |
* //as parameter, so that we use the update & draw functions | |
* this.parent(true); | |
* // ... | |
* }, | |
* // ... | |
* }); | |
*/ | |
update : function() { | |
return false; | |
}, | |
/** | |
* frame update function function | |
* @ignore | |
*/ | |
onUpdateFrame : function() { | |
// handle frame skipping if required | |
if ((++this.frame%this.frameRate)===0) { | |
// reset the frame counter | |
this.frame = 0; | |
// update the timer | |
me.timer.update(); | |
// update all games object | |
me.game.update(); | |
} | |
// draw the game objects | |
me.game.draw(); | |
}, | |
/** | |
* draw function<br> | |
* optional empty function<br> | |
* only used by the engine if the object has been initialized using addAsObject=true<br> | |
* @name draw | |
* @memberOf me.ScreenObject | |
* @function | |
* @example | |
* // define a Title Screen | |
* var TitleScreen = me.ScreenObject.extend( | |
* { | |
* // override the default constructor | |
* init : function() | |
* { | |
* //call the parent constructor giving true | |
* //as parameter, so that we use the update & draw functions | |
* this.parent(true); | |
* // ... | |
* }, | |
* // ... | |
* }); | |
*/ | |
draw : function() { | |
// to be extended | |
}, | |
/** | |
* onResetEvent function<br> | |
* called by the state manager when reseting the object<br> | |
* this is typically where you will load a level, etc... | |
* to be extended | |
* @name onResetEvent | |
* @memberOf me.ScreenObject | |
* @function | |
* @param {} [arguments...] optional arguments passed when switching state | |
* @see me.state#change | |
*/ | |
onResetEvent : function() { | |
// to be extended | |
}, | |
/** | |
* onDestroyEvent function<br> | |
* called by the state manager before switching to another state<br> | |
* @name onDestroyEvent | |
* @memberOf me.ScreenObject | |
* @function | |
*/ | |
onDestroyEvent : function() { | |
// to be extended | |
} | |
}); | |
// based on the requestAnimationFrame polyfill by Erik Möller | |
(function() { | |
var lastTime = 0; | |
var vendors = ['ms', 'moz', 'webkit', 'o']; | |
// get unprefixed rAF and cAF, if present | |
var requestAnimationFrame = window.requestAnimationFrame; | |
var cancelAnimationFrame = window.cancelAnimationFrame; | |
for(var x = 0; x < vendors.length; ++x) { | |
if ( requestAnimationFrame && cancelAnimationFrame ) { | |
break; | |
} | |
requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; | |
cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || | |
window[vendors[x]+'CancelRequestAnimationFrame']; | |
} | |
if (!requestAnimationFrame || !cancelAnimationFrame) { | |
requestAnimationFrame = function(callback, element) { | |
var currTime = Date.now(); | |
var timeToCall = Math.max(0, 16 - (currTime - lastTime)); | |
var id = window.setTimeout(function() { | |
callback(currTime + timeToCall); | |
}, timeToCall); | |
lastTime = currTime + timeToCall; | |
return id; | |
}; | |
cancelAnimationFrame = function(id) { | |
window.clearTimeout(id); | |
}; | |
} | |
// put back in global namespace | |
window.requestAnimationFrame = requestAnimationFrame; | |
window.cancelAnimationFrame = cancelAnimationFrame; | |
}()); | |
/** | |
* a State Manager (state machine)<p> | |
* There is no constructor function for me.state. | |
* @namespace me.state | |
* @memberOf me | |
*/ | |
me.state = (function() { | |
// hold public stuff in our singleton | |
var obj = {}; | |
/*------------------------------------------- | |
PRIVATE STUFF | |
--------------------------------------------*/ | |
// current state | |
var _state = -1; | |
// requestAnimeFrame Id | |
var _animFrameId = -1; | |
// whether the game state is "paused" | |
var _isPaused = false; | |
// list of screenObject | |
var _screenObject = {}; | |
// fading transition parameters between screen | |
var _fade = { | |
color : "", | |
duration : 0 | |
}; | |
// callback when state switch is done | |
/** @ignore */ | |
var _onSwitchComplete = null; | |
// just to keep track of possible extra arguments | |
var _extraArgs = null; | |
// cache reference to the active screen update frame | |
var _activeUpdateFrame = null; | |
/** | |
* @ignore | |
*/ | |
function _startRunLoop() { | |
// ensure nothing is running first and in valid state | |
if ((_animFrameId === -1) && (_state !== -1)) { | |
// reset the timer | |
me.timer.reset(); | |
// start the main loop | |
_animFrameId = window.requestAnimationFrame(_renderFrame); | |
} | |
} | |
/** | |
* Resume the game loop after a pause. | |
* @ignore | |
*/ | |
function _resumeRunLoop() { | |
// ensure game is actually paused and in valid state | |
if (_isPaused && (_state !== -1)) { | |
// reset the timer | |
me.timer.reset(); | |
_isPaused = false; | |
} | |
} | |
/** | |
* Pause the loop for most screen objects. | |
* @ignore | |
*/ | |
function _pauseRunLoop() { | |
// Set the paused boolean to stop updates on (most) entities | |
_isPaused = true; | |
} | |
/** | |
* this is only called when using requestAnimFrame stuff | |
* @ignore | |
*/ | |
function _renderFrame() { | |
_activeUpdateFrame(); | |
if (_animFrameId !== -1) { | |
_animFrameId = window.requestAnimationFrame(_renderFrame); | |
} | |
} | |
/** | |
* stop the SO main loop | |
* @ignore | |
*/ | |
function _stopRunLoop() { | |
// cancel any previous animationRequestFrame | |
window.cancelAnimationFrame(_animFrameId); | |
_animFrameId = -1; | |
} | |
/** | |
* start the SO main loop | |
* @ignore | |
*/ | |
function _switchState(state) { | |
// clear previous interval if any | |
_stopRunLoop(); | |
// call the screen object destroy method | |
if (_screenObject[_state]) { | |
if (_screenObject[_state].screen.visible) { | |
// persistent or not, make sure we remove it | |
// from the current object list | |
me.game.remove.call(me.game, _screenObject[_state].screen, true); | |
} else { | |
// just notify the object | |
_screenObject[_state].screen.destroy(); | |
} | |
} | |
if (_screenObject[state]) | |
{ | |
// set the global variable | |
_state = state; | |
// call the reset function with _extraArgs as arguments | |
_screenObject[_state].screen.reset.apply(_screenObject[_state].screen, _extraArgs); | |
// cache the new screen object update function | |
_activeUpdateFrame = _screenObject[_state].screen.onUpdateFrame.bind(_screenObject[_state].screen); | |
// and start the main loop of the | |
// new requested state | |
_startRunLoop(); | |
// execute callback if defined | |
if (_onSwitchComplete) { | |
_onSwitchComplete(); | |
} | |
// force repaint | |
me.game.repaint(); | |
} | |
} | |
/*--------------------------------------------- | |
PUBLIC STUFF | |
---------------------------------------------*/ | |
/** | |
* default state value for Loading Screen | |
* @constant | |
* @name LOADING | |
* @memberOf me.state | |
*/ | |
obj.LOADING = 0; | |
/** | |
* default state value for Menu Screen | |
* @constant | |
* @name MENU | |
* @memberOf me.state | |
*/ | |
obj.MENU = 1; | |
/** | |
* default state value for "Ready" Screen | |
* @constant | |
* @name READY | |
* @memberOf me.state | |
*/ | |
obj.READY = 2; | |
/** | |
* default state value for Play Screen | |
* @constant | |
* @name PLAY | |
* @memberOf me.state | |
*/ | |
obj.PLAY = 3; | |
/** | |
* default state value for Game Over Screen | |
* @constant | |
* @name GAMEOVER | |
* @memberOf me.state | |
*/ | |
obj.GAMEOVER = 4; | |
/** | |
* default state value for Game End Screen | |
* @constant | |
* @name GAME_END | |
* @memberOf me.state | |
*/ | |
obj.GAME_END = 5; | |
/** | |
* default state value for High Score Screen | |
* @constant | |
* @name SCORE | |
* @memberOf me.state | |
*/ | |
obj.SCORE = 6; | |
/** | |
* default state value for Credits Screen | |
* @constant | |
* @name CREDITS | |
* @memberOf me.state | |
*/ | |
obj.CREDITS = 7; | |
/** | |
* default state value for Settings Screen | |
* @constant | |
* @name SETTINGS | |
* @memberOf me.state | |
*/ | |
obj.SETTINGS = 8; | |
/** | |
* default state value for user defined constants<br> | |
* @constant | |
* @name USER | |
* @memberOf me.state | |
* @example | |
* var STATE_INFO = me.state.USER + 0; | |
* var STATE_WARN = me.state.USER + 1; | |
* var STATE_ERROR = me.state.USER + 2; | |
* var STATE_CUTSCENE = me.state.USER + 3; | |
*/ | |
obj.USER = 100; | |
/** | |
* onPause callback | |
* @callback | |
* @name onPause | |
* @memberOf me.state | |
*/ | |
obj.onPause = null; | |
/** | |
* onResume callback | |
* @callback | |
* @name onResume | |
* @memberOf me.state | |
*/ | |
obj.onResume = null; | |
/** | |
* onStop callback | |
* @callback | |
* @name onStop | |
* @memberOf me.state | |
*/ | |
obj.onStop = null; | |
/** | |
* onRestart callback | |
* @callback | |
* @name onRestart | |
* @memberOf me.state | |
*/ | |
obj.onRestart = null; | |
/** | |
* @ignore | |
*/ | |
obj.init = function() { | |
// set the embedded loading screen | |
obj.set(obj.LOADING, new me.DefaultLoadingScreen()); | |
// set pause/stop action on losing focus | |
$.addEventListener("blur", function() { | |
// only in case we are not loading stuff | |
if (_state !== obj.LOADING) { | |
if (me.sys.stopOnBlur) { | |
obj.stop(true); | |
// callback? | |
if (obj.onStop) | |
obj.onStop(); | |
// publish the pause notification | |
me.event.publish(me.event.STATE_STOP); | |
} | |
if (me.sys.pauseOnBlur) { | |
obj.pause(true); | |
// callback? | |
if (obj.onPause) | |
obj.onPause(); | |
// publish the pause notification | |
me.event.publish(me.event.STATE_PAUSE); | |
} | |
} | |
}, false); | |
// set restart/resume action on gaining focus | |
$.addEventListener("focus", function() { | |
// only in case we are not loading stuff | |
if (_state !== obj.LOADING) { | |
// note: separate boolean so we can stay paused if user prefers | |
if (me.sys.resumeOnFocus) { | |
obj.resume(true); | |
// callback? | |
if (obj.onResume) | |
obj.onResume(); | |
// publish the resume notification | |
me.event.publish(me.event.STATE_RESUME); | |
} | |
if (me.sys.stopOnBlur) { | |
obj.restart(true); | |
// force repaint | |
me.game.repaint(); | |
// callback? | |
if (obj.onRestart) | |
obj.onRestart(); | |
// publish the resume notification | |
me.event.publish(me.event.STATE_RESTART); | |
} | |
} | |
}, false); | |
}; | |
/** | |
* Stop the current screen object. | |
* @name stop | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} pauseTrack pause current track on screen stop. | |
*/ | |
obj.stop = function(music) { | |
// stop the main loop | |
_stopRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.pauseTrack(); | |
}; | |
/** | |
* pause the current screen object | |
* @name pause | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} pauseTrack pause current track on screen pause | |
*/ | |
obj.pause = function(music) { | |
// stop the main loop | |
_pauseRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.pauseTrack(); | |
}; | |
/** | |
* Restart the screen object from a full stop. | |
* @name restart | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} resumeTrack resume current track on screen resume | |
*/ | |
obj.restart = function(music) { | |
// restart the main loop | |
_startRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.resumeTrack(); | |
}; | |
/** | |
* resume the screen object | |
* @name resume | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} resumeTrack resume current track on screen resume | |
*/ | |
obj.resume = function(music) { | |
// resume the main loop | |
_resumeRunLoop(); | |
// current music stop | |
if (music) | |
me.audio.resumeTrack(); | |
}; | |
/** | |
* return the running state of the state manager | |
* @name isRunning | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} true if a "process is running" | |
*/ | |
obj.isRunning = function() { | |
return _animFrameId !== -1; | |
}; | |
/** | |
* Return the pause state of the state manager | |
* @name isPaused | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Boolean} true if the game is paused | |
*/ | |
obj.isPaused = function() { | |
return _isPaused; | |
}; | |
/** | |
* associate the specified state with a screen object | |
* @name set | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
* @param {me.ScreenObject} | |
*/ | |
obj.set = function(state, so) { | |
_screenObject[state] = {}; | |
_screenObject[state].screen = so; | |
_screenObject[state].transition = true; | |
}; | |
/** | |
* return a reference to the current screen object<br> | |
* useful to call a object specific method | |
* @name current | |
* @memberOf me.state | |
* @public | |
* @function | |
* @return {me.ScreenObject} | |
*/ | |
obj.current = function() { | |
return _screenObject[_state].screen; | |
}; | |
/** | |
* specify a global transition effect | |
* @name transition | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {String} effect (only "fade" is supported for now) | |
* @param {String} color a CSS color value | |
* @param {Number} [duration=1000] expressed in milliseconds | |
*/ | |
obj.transition = function(effect, color, duration) { | |
if (effect === "fade") { | |
_fade.color = color; | |
_fade.duration = duration; | |
} | |
}; | |
/** | |
* enable/disable transition for a specific state (by default enabled for all) | |
* @name setTransition | |
* @memberOf me.state | |
* @public | |
* @function | |
*/ | |
obj.setTransition = function(state, enable) { | |
_screenObject[state].transition = enable; | |
}; | |
/** | |
* change the game/app state | |
* @name change | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
* @param {} [arguments...] extra arguments to be passed to the reset functions | |
* @example | |
* // The onResetEvent method on the play screen will receive two args: | |
* // "level_1" and the number 3 | |
* me.state.change(me.state.PLAY, "level_1", 3); | |
*/ | |
obj.change = function(state) { | |
// Protect against undefined ScreenObject | |
if (typeof(_screenObject[state]) === "undefined") { | |
throw "melonJS : Undefined ScreenObject for state '" + state + "'"; | |
} | |
_extraArgs = null; | |
if (arguments.length > 1) { | |
// store extra arguments if any | |
_extraArgs = Array.prototype.slice.call(arguments, 1); | |
} | |
// if fading effect | |
if (_fade.duration && _screenObject[state].transition) { | |
/** @ignore */ | |
_onSwitchComplete = function() { | |
me.game.viewport.fadeOut(_fade.color, _fade.duration); | |
}; | |
me.game.viewport.fadeIn(_fade.color, _fade.duration, | |
function() { | |
_switchState.defer(state); | |
}); | |
} | |
// else just switch without any effects | |
else { | |
// wait for the last frame to be | |
// "finished" before switching | |
_switchState.defer(state); | |
} | |
}; | |
/** | |
* return true if the specified state is the current one | |
* @name isCurrent | |
* @memberOf me.state | |
* @public | |
* @function | |
* @param {Number} state @see me.state#Constant | |
*/ | |
obj.isCurrent = function(state) { | |
return _state === state; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
*/ | |
(function(window) { | |
/** | |
* a default loading screen | |
* @memberOf me | |
* @ignore | |
* @constructor | |
*/ | |
me.DefaultLoadingScreen = me.ScreenObject.extend({ | |
// constructor | |
init : function() { | |
this.parent(true); | |
// flag to know if we need to refresh the display | |
this.invalidate = false; | |
// handle for the susbcribe function | |
this.handle = null; | |
}, | |
// call when the loader is resetted | |
onResetEvent : function() { | |
// melonJS logo | |
this.logo1 = new me.Font('century gothic', 32, 'white', 'middle'); | |
this.logo2 = new me.Font('century gothic', 32, '#55aa00', 'middle'); | |
this.logo2.bold(); | |
this.logo1.textBaseline = this.logo2.textBaseline = "alphabetic"; | |
// default progress bar height | |
this.barHeight = 4; | |
// setup a callback | |
this.handle = me.event.subscribe(me.event.LOADER_PROGRESS, this.onProgressUpdate.bind(this)); | |
// load progress in percent | |
this.loadPercent = 0; | |
}, | |
// destroy object at end of loading | |
onDestroyEvent : function() { | |
// "nullify" all fonts | |
this.logo1 = this.logo2 = null; | |
// cancel the callback | |
if (this.handle) { | |
me.event.unsubscribe(this.handle); | |
this.handle = null; | |
} | |
}, | |
// make sure the screen is refreshed every frame | |
onProgressUpdate : function(progress) { | |
this.loadPercent = progress; | |
this.invalidate = true; | |
}, | |
// make sure the screen is refreshed every frame | |
update : function() { | |
if (this.invalidate === true) { | |
// clear the flag | |
this.invalidate = false; | |
// and return true | |
return true; | |
} | |
// else return false | |
return false; | |
}, | |
// draw the melonJS logo | |
drawLogo : function (context, x, y) { | |
context.save(); | |
// translate to destination point | |
context.translate(x,y); | |
// generated using Illustrator and the Ai2Canvas plugin | |
context.beginPath(); | |
context.moveTo(0.7, 48.9); | |
context.bezierCurveTo(10.8, 68.9, 38.4, 75.8, 62.2, 64.5); | |
context.bezierCurveTo(86.1, 53.1, 97.2, 27.7, 87.0, 7.7); | |
context.lineTo(87.0, 7.7); | |
context.bezierCurveTo(89.9, 15.4, 73.9, 30.2, 50.5, 41.4); | |
context.bezierCurveTo(27.1, 52.5, 5.2, 55.8, 0.7, 48.9); | |
context.lineTo(0.7, 48.9); | |
context.lineTo(0.7, 48.9); | |
context.closePath(); | |
context.fillStyle = "rgb(255, 255, 255)"; | |
context.fill(); | |
context.beginPath(); | |
context.moveTo(84.0, 7.0); | |
context.bezierCurveTo(87.6, 14.7, 72.5, 30.2, 50.2, 41.6); | |
context.bezierCurveTo(27.9, 53.0, 6.9, 55.9, 3.2, 48.2); | |
context.bezierCurveTo(-0.5, 40.4, 14.6, 24.9, 36.9, 13.5); | |
context.bezierCurveTo(59.2, 2.2, 80.3, -0.8, 84.0, 7.0); | |
context.lineTo(84.0, 7.0); | |
context.closePath(); | |
context.lineWidth = 5.3; | |
context.strokeStyle = "rgb(255, 255, 255)"; | |
context.lineJoin = "miter"; | |
context.miterLimit = 4.0; | |
context.stroke(); | |
context.restore(); | |
}, | |
// draw function | |
draw : function(context) { | |
// measure the logo size | |
var logo1_width = this.logo1.measureText(context, "melon").width; | |
var xpos = (me.video.getWidth() - logo1_width - this.logo2.measureText(context, "JS").width) / 2; | |
var ypos = (me.video.getHeight() / 2) + (this.logo2.measureText(context, "melon").height); | |
// clear surface | |
me.video.clearSurface(context, "#202020"); | |
// logo 100x85 | |
this.drawLogo( | |
context, | |
(me.video.getWidth() - 100) /2 , | |
(me.video.getHeight()/2) - (this.barHeight/2) - 90 | |
); | |
// draw the melonJS string | |
this.logo1.draw(context, 'melon', xpos , ypos); | |
xpos += logo1_width; | |
this.logo2.draw(context, 'JS', xpos, ypos); | |
// display a progressive loading bar | |
var progress = Math.floor(this.loadPercent * me.video.getWidth()); | |
// draw the progress bar | |
context.fillStyle = "black"; | |
context.fillRect(0, (me.video.getHeight()/2)-(this.barHeight/2), me.video.getWidth(), this.barHeight); | |
context.fillStyle = "#55aa00"; | |
context.fillRect(2, (me.video.getHeight()/2)-(this.barHeight/2), progress, this.barHeight); | |
} | |
}); | |
// --- END --- | |
})(window); | |
/* | |
* 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 singletong | |
var obj = {}; | |
// 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 texture atlas files | |
var atlasList = {}; | |
// 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 (obj.onload) { | |
// make sure we clear the timer | |
clearTimeout(timerId); | |
// trigger the onload callback | |
setTimeout(function () { | |
obj.onload(); | |
me.event.publish(me.event.LOADER_COMPLETE); | |
}, 300); | |
} else | |
console.error("no load callback defined"); | |
} else { | |
timerId = setTimeout(checkLoadStatus, 100); | |
} | |
} | |
/** | |
* load Images | |
* | |
* call 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 + obj.nocache; | |
} | |
/** | |
* preload TMX files | |
* @ignore | |
*/ | |
function preloadTMX(tmxData, onload, onerror) { | |
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 + obj.nocache, true); | |
// add the tmx to the levelDirector | |
if (tmxData.type === "tmx") { | |
me.levelDirector.addTMXLevel(tmxData.name); | |
} | |
// 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': | |
// 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; | |
} | |
// change the data format | |
format = 'xml'; | |
break; | |
case 'json': | |
result = JSON.parse(xmlhttp.responseText); | |
break; | |
default: | |
throw "melonJS: TMX file format " + format + "not supported !"; | |
} | |
// get the TMX content | |
tmxList[tmxData.name] = { | |
data: result, | |
isTMX: (tmxData.type === "tmx"), | |
format : 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 + obj.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 + obj.nocache, true); | |
httpReq.responseType = "arraybuffer"; | |
httpReq.onerror = onerror; | |
httpReq.onload = function(event){ | |
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 | |
*/ | |
obj.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); | |
*/ | |
obj.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. | |
* @public | |
* @callback | |
* @name onProgress | |
* @memberOf me.loader | |
* @example | |
* | |
* // set a callback for progress notification | |
* me.loader.onProgress = this.updateProgress.bind(this); | |
*/ | |
obj.onProgress = undefined; | |
/** | |
* just increment the number of already loaded resources | |
* @ignore | |
*/ | |
obj.onResourceLoaded = function(e) { | |
// increment the loading counter | |
loadCount++; | |
// callback ? | |
var progress = obj.getLoadProgress(); | |
if (obj.onProgress) { | |
// pass the load progress in percent, as parameter | |
obj.onProgress(progress); | |
} | |
me.event.publish(me.event.LOADER_PROGRESS, [progress]); | |
}; | |
/** | |
* on error callback for image loading | |
* @ignore | |
*/ | |
obj.onLoadingError = function(res) { | |
throw "melonJS: Failed loading resource " + res.src; | |
}; | |
/** | |
* enable the nocache mechanism | |
* @ignore | |
*/ | |
obj.setNocache = function(enable) { | |
obj.nocache = enable ? "?" + parseInt(Math.random() * 10000000, 10) : ''; | |
}; | |
/** | |
* 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> | |
* - src : path and file name of the resource<br> | |
* (!) for audio :<br> | |
* - src : path (only) where resources are located<br> | |
* - channel : optional number of channels to be created<br> | |
* - stream : optional boolean to enable audio streaming<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"}, | |
* // audio ressources | |
* {name: "bgmusic", type: "audio", src: "data/audio/", channel: 1, stream: true}, | |
* {name: "cling", type: "audio", src: "data/audio/", channel: 2}, | |
* // 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); | |
*/ | |
obj.preload = function(res) { | |
// parse the resources | |
for ( var i = 0; i < res.length; i++) { | |
resourceCount += obj.load(res[i], obj.onResourceLoaded.bind(obj), obj.onLoadingError.bind(obj, 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 parmeter must contain the following fields :<br> | |
* - name : internal name of the resource<br> | |
* - type : "audio", binary", "image", "json", "tmx", "tsx" | |
* - src : path and file name of the resource<br> | |
* (!) for audio :<br> | |
* - src : path (only) where resources are located<br> | |
* - channel : optional number of channels to be created<br> | |
* - stream : optional boolean to enable audio streaming<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 streaming music | |
* me.loader.load({ | |
* name : "bgmusic", | |
* type : "audio", | |
* src : "data/audio/", | |
* stream : true | |
* }, function() { | |
* me.audio.play("bgmusic"); | |
* }); | |
*/ | |
obj.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": | |
// only load is sound is enable | |
if (me.audio.isAudioEnable()) { | |
me.audio.load(res, onload, onerror); | |
return 1; | |
} | |
break; | |
default: | |
throw "melonJS: me.loader.load : unknown or invalid resource type : " + res.type; | |
} | |
return 0; | |
}; | |
/** | |
* 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"}); | |
*/ | |
obj.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 "melonJS: me.loader.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(); | |
*/ | |
obj.unloadAll = function() { | |
var name; | |
// unload all binary resources | |
for (name in binList) | |
obj.unload(name); | |
// unload all image resources | |
for (name in imgList) | |
obj.unload(name); | |
// unload all tmx resources | |
for (name in tmxList) | |
obj.unload(name); | |
// unload all atlas resources | |
for (name in atlasList) | |
obj.unload(name); | |
// unload all in json resources | |
for (name in jsonList) | |
obj.unload(name); | |
// unload all audio resources | |
me.audio.unloadAll(); | |
}; | |
/** | |
* return the specified TMX object storing type | |
* @name getTMXFormat | |
* @memberOf me.loader | |
* @public | |
* @function | |
* @param {String} tmx name of the tmx/tsx element ("map1"); | |
* @return {String} 'xml' or 'json' | |
*/ | |
obj.getTMXFormat = function(elt) { | |
// avoid case issue | |
elt = elt.toLowerCase(); | |
if (elt in tmxList) | |
return tmxList[elt].format; | |
else { | |
//console.log ("warning %s resource not yet loaded!",name); | |
return null; | |
} | |
}; | |
/** | |
* 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} | |
*/ | |
obj.getTMX = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getBinary = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getImage = function(elt) { | |
// avoid case issue | |
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} | |
*/ | |
obj.getJSON = function(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} | |
*/ | |
obj.getLoadProgress = function() { | |
return loadCount / resourceCount; | |
}; | |
// return our object | |
return obj; | |
})(); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Font / Bitmap font | |
* | |
* ASCII Table | |
* http://www.asciitable.com/ | |
* [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz] | |
* | |
* -> first char " " 32d (0x20); | |
*/ | |
(function($) { | |
/** | |
* a generic system font object. | |
* @class | |
* @extends me.Renderable | |
* @memberOf me | |
* @constructor | |
* @param {String} font a CSS font name | |
* @param {Number|String} size size, or size + suffix (px, em, pt) | |
* @param {String} fillStyle a CSS color value | |
* @param {String} [textAlign="left"] horizontal alignment | |
*/ | |
me.Font = me.Renderable.extend( | |
/** @scope me.Font.prototype */ { | |
// private font properties | |
/** @ignore */ | |
font : null, | |
fontSize : null, | |
/** | |
* defines the color used to draw the font.<br> | |
* Default value : "#000000" | |
* @public | |
* @type String | |
* @name me.Font#fillStyle | |
*/ | |
fillStyle : "#000000", | |
/** | |
* defines the color used to draw the font stroke.<br> | |
* Default value : "#000000" | |
* @public | |
* @type String | |
* @name me.Font#strokeStyle | |
*/ | |
strokeStyle : "#000000", | |
/** | |
* sets the current line width, in pixels, when drawing stroke | |
* Default value : 1 | |
* @public | |
* @type Number | |
* @name me.Font#lineWidth | |
*/ | |
lineWidth : 1, | |
/** | |
* Set the default text alignment (or justification),<br> | |
* possible values are "left", "right", and "center".<br> | |
* Default value : "left" | |
* @public | |
* @type String | |
* @name me.Font#textAlign | |
*/ | |
textAlign : "left", | |
/** | |
* Set the text baseline (e.g. the Y-coordinate for the draw operation), <br> | |
* possible values are "top", "hanging, "middle, "alphabetic, "ideographic, "bottom"<br> | |
* Default value : "top" | |
* @public | |
* @type String | |
* @name me.Font#textBaseline | |
*/ | |
textBaseline : "top", | |
/** | |
* Set the line spacing height (when displaying multi-line strings). <br> | |
* Current font height will be multiplied with this value to set the line height. | |
* Default value : 1.0 | |
* @public | |
* @type Number | |
* @name me.Font#lineHeight | |
*/ | |
lineHeight : 1.0, | |
/** @ignore */ | |
init : function(font, size, fillStyle, textAlign) { | |
this.pos = new me.Vector2d(); | |
this.fontSize = new me.Vector2d(); | |
// font name and type | |
this.set(font, size, fillStyle, textAlign); | |
// parent constructor | |
this.parent(this.pos, 0, this.fontSize.y); | |
}, | |
/** | |
* make the font bold | |
* @name bold | |
* @memberOf me.Font | |
* @function | |
*/ | |
bold : function() { | |
this.font = "bold " + this.font; | |
}, | |
/** | |
* make the font italic | |
* @name italic | |
* @memberOf me.Font | |
* @function | |
*/ | |
italic : function() { | |
this.font = "italic " + this.font; | |
}, | |
/** | |
* Change the font settings | |
* @name set | |
* @memberOf me.Font | |
* @function | |
* @param {String} font a CSS font name | |
* @param {Number|String} size size, or size + suffix (px, em, pt) | |
* @param {String} fillStyle a CSS color value | |
* @param {String} [textAlign="left"] horizontal alignment | |
* @example | |
* font.set("Arial", 20, "white"); | |
* font.set("Arial", "1.5em", "white"); | |
*/ | |
set : function(font, size, fillStyle, textAlign) { | |
// font name and type | |
var font_names = font.split(",").map(function (value) { | |
value = value.trim(); | |
return ( | |
!/(^".*"$)|(^'.*'$)/.test(value) | |
) ? '"' + value + '"' : value; | |
}); | |
this.fontSize.y = parseInt(size, 10); | |
this.height = this.fontSize.y; | |
if (typeof size === "number") { | |
size += "px"; | |
} | |
this.font = size + " " + font_names.join(","); | |
this.fillStyle = fillStyle; | |
if (textAlign) { | |
this.textAlign = textAlign; | |
} | |
}, | |
/** | |
* measure the given text size in pixels | |
* @name measureText | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @return {Object} returns an object, with two attributes: width (the width of the text) and height (the height of the text). | |
*/ | |
measureText : function(context, text) { | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
this.height = this.width = 0; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
this.width = Math.max(context.measureText(strings[i].trimRight()).width, this.width); | |
this.height += this.fontSize.y * this.lineHeight; | |
} | |
return {width: this.width, height: this.height}; | |
}, | |
/** | |
* draw a text at the specified coord | |
* @name draw | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
draw : function(context, text, x, y) { | |
// update initial position | |
this.pos.set(x,y); | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
// draw the string | |
context.fillText(strings[i].trimRight(), ~~x, ~~y); | |
// add leading space | |
y += this.fontSize.y * this.lineHeight; | |
} | |
}, | |
/** | |
* draw a stroke text at the specified coord, as defined <br> | |
* by the `lineWidth` and `fillStroke` properties. <br> | |
* Note : using drawStroke is not recommended for performance reasons | |
* @name drawStroke | |
* @memberOf me.Font | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
drawStroke : function(context, text, x, y) { | |
// update initial position | |
this.pos.set(x,y); | |
// save the context, as we are modifying | |
// too much parameter in this function | |
context.save(); | |
// draw the text | |
context.font = this.font; | |
context.fillStyle = this.fillStyle; | |
context.strokeStyle = this.strokeStyle; | |
context.lineWidth = this.lineWidth; | |
context.textAlign = this.textAlign; | |
context.textBaseline = this.textBaseline; | |
var strings = (""+text).split("\n"); | |
for (var i = 0; i < strings.length; i++) { | |
var _string = strings[i].trimRight(); | |
// draw the border | |
context.strokeText(_string, ~~x, ~~y); | |
// draw the string | |
context.fillText(_string, ~~x, ~~y); | |
// add leading space | |
y += this.fontSize.y * this.lineHeight; | |
} | |
// restore the context | |
context.restore(); | |
} | |
}); | |
/** | |
* a bitpmap font object | |
* @class | |
* @extends me.Font | |
* @memberOf me | |
* @constructor | |
* @param {String} font | |
* @param {Number|Object} size either an int value, or an object like {x:16,y:16} | |
* @param {Number} [scale="1.0"] | |
* @param {String} [firstChar="0x20"] | |
*/ | |
me.BitmapFont = me.Font.extend( | |
/** @scope me.BitmapFont.prototype */ { | |
/** @ignore */ | |
// font scale; | |
sSize : null, | |
// first char in the ascii table | |
firstChar : 0x20, | |
// #char per row | |
charCount : 0, | |
/** @ignore */ | |
init : function(font, size, scale, firstChar) { | |
// font name and type | |
this.parent(font, null, null); | |
// font scaled size; | |
this.sSize = new me.Vector2d(); | |
// first char in the ascii table | |
this.firstChar = firstChar || 0x20; | |
// load the font metrics | |
this.loadFontMetrics(font, size); | |
// set a default alignement | |
this.textAlign = "left"; | |
this.textBaseline = "top"; | |
// resize if necessary | |
if (scale) { | |
this.resize(scale); | |
} | |
}, | |
/** | |
* Load the font metrics | |
* @ignore | |
*/ | |
loadFontMetrics : function(font, size) { | |
this.font = me.loader.getImage(font); | |
// some cheap metrics | |
this.fontSize.x = size.x || size; | |
this.fontSize.y = size.y || this.font.height; | |
this.sSize.copy(this.fontSize); | |
this.height = this.sSize.y; | |
// #char per row | |
this.charCount = ~~(this.font.width / this.fontSize.x); | |
}, | |
/** | |
* change the font settings | |
* @name set | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {String} textAlign ("left", "center", "right") | |
* @param {Number} [scale] | |
*/ | |
set : function(textAlign, scale) { | |
this.textAlign = textAlign; | |
// updated scaled Size | |
if (scale) { | |
this.resize(scale); | |
} | |
}, | |
/** | |
* change the font display size | |
* @name resize | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Number} scale ratio | |
*/ | |
resize : function(scale) { | |
// updated scaled Size | |
this.sSize.setV(this.fontSize); | |
this.sSize.x *= scale; | |
this.sSize.y *= scale; | |
this.height = this.sSize.y; | |
}, | |
/** | |
* measure the given text size in pixels | |
* @name measureText | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @return {Object} returns an object, with two attributes: width (the width of the text) and height (the height of the text). | |
*/ | |
measureText : function(context, text) { | |
var strings = (""+text).split("\n"); | |
this.height = this.width = 0; | |
for (var i = 0; i < strings.length; i++) { | |
this.width = Math.max((strings[i].trimRight().length * this.sSize.x), this.width); | |
this.height += this.sSize.y * this.lineHeight; | |
} | |
return {width: this.width, height: this.height}; | |
}, | |
/** | |
* draw a text at the specified coord | |
* @name draw | |
* @memberOf me.BitmapFont | |
* @function | |
* @param {Context} context 2D Context | |
* @param {String} text | |
* @param {Number} x | |
* @param {Number} y | |
*/ | |
draw : function(context, text, x, y) { | |
var strings = (""+text).split("\n"); | |
var lX = x; | |
var height = this.sSize.y * this.lineHeight; | |
// update initial position | |
this.pos.set(x,y); | |
for (var i = 0; i < strings.length; i++) { | |
x = lX; | |
var string = strings[i].trimRight(); | |
// adjust x pos based on alignment value | |
var width = string.length * this.sSize.x; | |
switch(this.textAlign) { | |
case "right": | |
x -= width; | |
break; | |
case "center": | |
x -= width * 0.5; | |
break; | |
default : | |
break; | |
} | |
// adjust y pos based on alignment value | |
switch(this.textBaseline) { | |
case "middle": | |
y -= height * 0.5; | |
break; | |
case "ideographic": | |
case "alphabetic": | |
case "bottom": | |
y -= height; | |
break; | |
default : | |
break; | |
} | |
// draw the string | |
for ( var c = 0,len = string.length; c < len; c++) { | |
// calculate the char index | |
var idx = string.charCodeAt(c) - this.firstChar; | |
if (idx >= 0) { | |
// draw it | |
context.drawImage(this.font, | |
this.fontSize.x * (idx % this.charCount), | |
this.fontSize.y * ~~(idx / this.charCount), | |
this.fontSize.x, this.fontSize.y, | |
~~x, ~~y, | |
this.sSize.x, this.sSize.y); | |
} | |
x += this.sSize.x; | |
} | |
// increment line | |
y += height; | |
} | |
} | |
}); | |
/*---------------------------------------------------------*/ | |
// END END END | |
/*---------------------------------------------------------*/ | |
})(window); | |
/* | |
* MelonJS Game Engine | |
* Copyright (C) 2011 - 2013, Olivier BIOT | |
* http://www.melonjs.org | |
* | |
* Audio Mngt Objects | |
* | |
* | |
*/ | |
(function($) { | |
/** | |
* There is no constructor function for me.audio. | |
* @namespace me.audio | |
* @memberOf me | |
*/ | |
me.audio = (function() { | |
/* | |
* --------------------------------------------- | |
* PRIVATE STUFF | |
* --------------------------------------------- | |
*/ | |
// hold public stuff in our singleton | |
var obj = {}; | |
// audio channel list | |
var audio_channels = {}; | |
// Active (supported) audio extension | |
var activeAudioExt = -1; | |
// current music | |
var current_track_id = null; | |
var current_track = null; | |
// enable/disable flag | |
var sound_enable = true; | |
// defaut reset value | |
var reset_val = 0;// .01; | |
// a retry counter | |
var retry_counter = 0; | |
// global volume setting | |
var settings = { | |
volume : 1.0, | |
muted : false | |
}; | |
// synchronous loader for mobile user agents | |
var sync_loading = false; | |
var sync_loader = []; | |
/** | |
* return the first audio format extension supported by the browser | |
* @ignore | |
*/ | |
function getSupportedAudioFormat(requestedFormat) { | |
var result = ""; | |
var len = requestedFormat.length; | |
// check for sound support by the browser | |
if (me.device.sound) { | |
var ext = ""; | |
for (var i = 0; i < len; i++) { | |
ext = requestedFormat[i].toLowerCase().trim(); | |
// check extension against detected capabilities | |
if (obj.capabilities[ext] && | |
obj.capabilities[ext].canPlay && | |
// get only the first valid OR first 'probably' playable codec | |
(result === "" || obj.capabilities[ext].canPlayType === 'probably') | |
) { | |
result = ext; | |
if (obj.capabilities[ext].canPlayType === 'probably') { | |
break; | |
} | |
} | |
} | |
} | |
if (result === "") { | |
// deactivate sound | |
sound_enable = false; | |
} | |
return result; | |
} | |
/** | |
* return the specified sound | |
* @ignore | |
*/ | |
function get(sound_id) { | |
var channels = audio_channels[sound_id]; | |
// find which channel is available | |
for ( var i = 0, soundclip; soundclip = channels[i++];) { | |
if (soundclip.ended || !soundclip.currentTime)// soundclip.paused) | |
{ | |
// console.log ("requested %s on channel %d",sound_id, i); | |
soundclip.currentTime = reset_val; | |
return soundclip; | |
} | |
} | |
// else force on channel 0 | |
channels[0].pause(); | |
channels[0].currentTime = reset_val; | |
return channels[0]; | |
} | |
/** | |
* event listener callback on load error | |
* @ignore | |
*/ | |
function soundLoadError(sound_id, onerror_cb) { | |
// check the retry counter | |
if (retry_counter++ > 3) { | |
// something went wrong | |
var errmsg = "melonJS: failed loading " + sound_id + "." + activeAudioExt; | |
if (me.sys.stopOnAudioError===false) { | |
// disable audio | |
me.audio.disable(); | |
// call error callback if defined | |
if (onerror_cb) { | |
onerror_cb(); | |
} | |
// warning | |
console.log(errmsg + ", disabling audio"); | |
} else { | |
// throw an exception and stop everything ! | |
throw errmsg; | |
} | |
// else try loading again ! | |
} else { | |
audio_channels[sound_id][0].load(); | |
} | |
} | |
/** | |
* event listener callback when a sound is loaded | |
* @ignore | |
*/ | |
function soundLoaded(sound_id, sound_channel, onload_cb) { | |
// reset the retry counter | |
retry_counter = 0; | |
// create other "copy" channels if necessary | |
if (sound_channel > 1) { | |
var soundclip = audio_channels[sound_id][0]; | |
// clone copy to create multiple channel version | |
for (var channel = 1; channel < sound_channel; channel++) { | |
// allocate the new additional channels | |
audio_channels[sound_id][channel] = new Audio( soundclip.src ); | |
audio_channels[sound_id][channel].preload = 'auto'; | |
audio_channels[sound_id][channel].load(); | |
} | |
} | |
// callback if defined | |
if (onload_cb) { | |
onload_cb(); | |
} | |
} | |
/** | |
* play the specified sound | |
* @name play | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} | |
* sound_id audio clip id | |
* @param {Boolean} | |
* [loop=false] loop audio | |
* @param {Function} | |
* [callback] callback function | |
* @param {Number} | |
* [volume=default] Float specifying volume (0.0 - 1.0 values accepted). | |
* @example | |
* // play the "cling" audio clip | |
* me.audio.play("cling"); | |
* // play & repeat the "engine" audio clip | |
* me.audio.play("engine", true); | |
* // play the "gameover_sfx" audio clip and call myFunc when finished | |
* me.audio.play("gameover_sfx", false, myFunc); | |
* // play the "gameover_sfx" audio clip with a lower volume level | |
* me.audio.play("gameover_sfx", false, null, 0.5); | |
*/ | |
function _play_audio_enable(sound_id, loop, callback, volume) { | |
var soundclip = get(sound_id.toLowerCase()); | |
soundclip.loop = loop || false; | |
soundclip.volume = volume ? parseFloat(volume).clamp(0.0,1.0) : settings.volume; | |
soundclip.muted = settings.muted; | |
soundclip.play(); | |
// set a callback if defined | |
if (callback && !loop) { | |
soundclip.addEventListener('ended', function callbackFn(event) { | |
soundclip.removeEventListener('ended', callbackFn, | |
false); | |
// soundclip.pause(); | |
// soundclip.currentTime = reset_val; | |
// execute a callback if required | |
callback(); | |
}, false); | |
} | |
return soundclip; | |
} | |
/** | |
* play_audio with simulated callback | |
* @ignore | |
*/ | |
function _play_audio_disable(sound_id, loop, callback) { | |
// check if a callback need to be called | |
if (callback && !loop) { | |
// SoundMngr._play_cb = callback; | |
setTimeout(callback, 2000); // 2 sec as default timer ? | |
} | |
return null; | |
} | |
/* | |
*--------------------------------------------- | |
* PUBLIC STUFF | |
*--------------------------------------------- | |
*/ | |
// audio capabilities | |
obj.capabilities = { | |
mp3: { | |
codec: 'audio/mpeg', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
ogg: { | |
codec: 'audio/ogg; codecs="vorbis"', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
m4a: { | |
codec: 'audio/mp4; codecs="mp4a.40.2"', | |
canPlay: false, | |
canPlayType: 'no' | |
}, | |
wav: { | |
codec: 'audio/wav; codecs="1"', | |
canPlay: false, | |
canPlayType: 'no' | |
} | |
}; | |
/** | |
* @ignore | |
*/ | |
obj.detectCapabilities = function () { | |
// init some audio variables | |
var a = document.createElement('audio'); | |
if (a.canPlayType) { | |
for (var c in obj.capabilities) { | |
var canPlayType = a.canPlayType(obj.capabilities[c].codec); | |
// convert the string to a boolean | |
if (canPlayType !== "" && canPlayType !== "no") { | |
obj.capabilities[c].canPlay = true; | |
obj.capabilities[c].canPlayType = canPlayType; | |
} | |
// enable sound if any of the audio format is supported | |
me.device.sound |= obj.capabilities[c].canPlay; | |
} | |
} | |
}; | |
/** | |
* initialize the audio engine<br> | |
* the melonJS loader will try to load audio files corresponding to the | |
* browser supported audio format<br> | |
* if no compatible audio codecs are found, audio will be disabled | |
* @name init | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} | |
* audioFormat audio format provided ("mp3, ogg, m4a, wav") | |
* @example | |
* // initialize the "sound engine", giving "mp3" and "ogg" as desired audio format | |
* // i.e. on Safari, the loader will load all audio.mp3 files, | |
* // on Opera the loader will however load audio.ogg files | |
* me.audio.init("mp3,ogg"); | |
*/ | |
obj.init = function(audioFormat) { | |
if (!me.initialized) { | |
throw "melonJS: me.audio.init() called before engine initialization."; | |
} | |
// if no param is given to init we use mp3 by default | |
audioFormat = typeof audioFormat === "string" ? audioFormat : "mp3"; | |
// convert it into an array | |
audioFormat = audioFormat.split(','); | |
// detect the prefered audio format | |
activeAudioExt = getSupportedAudioFormat(audioFormat); | |
// Disable audio on Mobile devices for now. (ARGH!) | |
if (me.device.isMobile && !navigator.isCocoonJS) { | |
sound_enable = false; | |
} | |
// enable/disable sound | |
obj.play = obj.isAudioEnable() ? _play_audio_enable : _play_audio_disable; | |
return obj.isAudioEnable(); | |
}; | |
/** | |
* return true if audio is enable | |
* | |
* @see me.audio#enable | |
* @name isAudioEnable | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @return {Boolean} | |
*/ | |
obj.isAudioEnable = function() { | |
return sound_enable; | |
}; | |
/** | |
* enable audio output <br> | |
* only useful if audio supported and previously disabled through | |
* audio.disable() | |
* | |
* @see me.audio#disable | |
* @name enable | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.enable = function() { | |
sound_enable = me.device.sound; | |
if (sound_enable) | |
obj.play = _play_audio_enable; | |
else | |
obj.play = _play_audio_disable; | |
}; | |
/** | |
* disable audio output | |
* | |
* @name disable | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.disable = function() { | |
// stop the current track | |
me.audio.stopTrack(); | |
// disable sound | |
obj.play = _play_audio_disable; | |
sound_enable = false; | |
}; | |
/** | |
* Load an audio file.<br> | |
* <br> | |
* sound item must contain the following fields :<br> | |
* - name : id of the sound<br> | |
* - src : source path<br> | |
* - channel : [Optional] number of channels to allocate<br> | |
* - stream : [Optional] boolean to enable streaming<br> | |
* @ignore | |
*/ | |
obj.load = function(sound, onload_cb, onerror_cb) { | |
// do nothing if no compatible format is found | |
if (activeAudioExt === -1) | |
return 0; | |
// check for specific platform | |
if (me.device.isMobile && !navigator.isCocoonJS) { | |
if (sync_loading) { | |
sync_loader.push([ sound, onload_cb, onerror_cb ]); | |
return; | |
} | |
sync_loading = true; | |
} | |
var channels = sound.channel || 1; | |
var eventname = "canplaythrough"; | |
if (sound.stream === true && !me.device.isMobile) { | |
channels = 1; | |
eventname = "canplay"; | |
} | |
var soundclip = new Audio(sound.src + sound.name + "." + activeAudioExt + me.loader.nocache); | |
soundclip.preload = 'auto'; | |
soundclip.addEventListener(eventname, function callbackFn(e) { | |
soundclip.removeEventListener(eventname, callbackFn, false); | |
sync_loading = false; | |
soundLoaded.call( | |
me.audio, | |
sound.name, | |
channels, | |
onload_cb | |
); | |
// Load next audio clip synchronously | |
var next = sync_loader.shift(); | |
if (next) { | |
obj.load.apply(obj, next); | |
} | |
}, false); | |
soundclip.addEventListener("error", function(e) { | |
soundLoadError.call(me.audio, sound.name, onerror_cb); | |
}, false); | |
// load it | |
soundclip.load(); | |
audio_channels[sound.name] = [ soundclip ]; | |
return 1; | |
}; | |
/** | |
* stop the specified sound on all channels | |
* | |
* @name stop | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
* @example | |
* me.audio.stop("cling"); | |
*/ | |
obj.stop = function(sound_id) { | |
if (sound_enable) { | |
var sound = audio_channels[sound_id.toLowerCase()]; | |
for (var channel_id = sound.length; channel_id--;) { | |
sound[channel_id].pause(); | |
// force rewind to beginning | |
sound[channel_id].currentTime = reset_val; | |
} | |
} | |
}; | |
/** | |
* pause the specified sound on all channels<br> | |
* this function does not reset the currentTime property | |
* | |
* @name pause | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
* @example | |
* me.audio.pause("cling"); | |
*/ | |
obj.pause = function(sound_id) { | |
if (sound_enable) { | |
var sound = audio_channels[sound_id.toLowerCase()]; | |
for (var channel_id = sound.length; channel_id--;) { | |
sound[channel_id].pause(); | |
} | |
} | |
}; | |
/** | |
* play the specified audio track<br> | |
* this function automatically set the loop property to true<br> | |
* and keep track of the current sound being played. | |
* | |
* @name playTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio track id | |
* @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted). | |
* @example | |
* me.audio.playTrack("awesome_music"); | |
*/ | |
obj.playTrack = function(sound_id, volume) { | |
current_track = me.audio.play(sound_id, true, null, volume); | |
current_track_id = sound_id.toLowerCase(); | |
}; | |
/** | |
* stop the current audio track | |
* | |
* @see me.audio#playTrack | |
* @name stopTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @example | |
* // play a awesome music | |
* me.audio.playTrack("awesome_music"); | |
* // stop the current music | |
* me.audio.stopTrack(); | |
*/ | |
obj.stopTrack = function() { | |
if (sound_enable && current_track) { | |
current_track.pause(); | |
current_track_id = null; | |
current_track = null; | |
} | |
}; | |
/** | |
* set the default global volume | |
* @name setVolume | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {Number} volume Float specifying volume (0.0 - 1.0 values accepted). | |
*/ | |
obj.setVolume = function(volume) { | |
if (typeof(volume) === "number") { | |
settings.volume = volume.clamp(0.0,1.0); | |
} | |
}; | |
/** | |
* get the default global volume | |
* @name getVolume | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @returns {Number} current volume value in Float [0.0 - 1.0] . | |
*/ | |
obj.getVolume = function() { | |
return settings.volume; | |
}; | |
/** | |
* mute the specified sound | |
* @name mute | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
*/ | |
obj.mute = function(sound_id, mute) { | |
// if not defined : true | |
mute = (mute === undefined)?true:!!mute; | |
var channels = audio_channels[sound_id.toLowerCase()]; | |
for ( var i = 0, soundclip; soundclip = channels[i++];) { | |
soundclip.muted = mute; | |
} | |
}; | |
/** | |
* unmute the specified sound | |
* @name unmute | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio clip id | |
*/ | |
obj.unmute = function(sound_id) { | |
obj.mute(sound_id, false); | |
}; | |
/** | |
* mute all audio | |
* @name muteAll | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.muteAll = function() { | |
settings.muted = true; | |
for (var sound_id in audio_channels) { | |
obj.mute(sound_id, settings.muted); | |
} | |
}; | |
/** | |
* unmute all audio | |
* @name unmuteAll | |
* @memberOf me.audio | |
* @public | |
* @function | |
*/ | |
obj.unmuteAll = function() { | |
settings.muted = false; | |
for (var sound_id in audio_channels) { | |
obj.mute(sound_id, settings.muted); | |
} | |
}; | |
/** | |
* returns the current track Id | |
* @name getCurrentTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @return {String} audio track id | |
*/ | |
obj.getCurrentTrack = function() { | |
return current_track_id; | |
}; | |
/** | |
* pause the current audio track | |
* | |
* @name pauseTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @example | |
* me.audio.pauseTrack(); | |
*/ | |
obj.pauseTrack = function() { | |
if (sound_enable && current_track) { | |
current_track.pause(); | |
} | |
}; | |
/** | |
* resume the previously paused audio track | |
* | |
* @name resumeTrack | |
* @memberOf me.audio | |
* @public | |
* @function | |
* @param {String} sound_id audio track id | |
* @example | |
* // play an awesome music | |
* me.audio.playTrack("awesome_music"); | |
* // pause the audio track | |
* me.audio. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment