/** * melonJS Game Engine v5.1.0 * http://www.melonjs.org * @license {@link http://www.opensource.org/licenses/mit-license.php|MIT} * @copyright (C) 2011 - 2018 Olivier Biot */ /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /* eslint-disable no-undef */ (function (global) { "use strict"; /** * (m)elonJS (e)ngine : All melonJS functions are defined inside * of this namespace. *
You generally should not add new properties to this namespace as it may be * overwritten in future versions.
* @name me * @namespace */ var me = {}; // support for AMD (Asynchronous Module Definition) libraries if (typeof define === "function" && define.amd) { define(function () { return me; }); } // CommonJS and Node.js module support. else if (typeof exports !== "undefined") { // Support Node.js specific `module.exports` (which can be a function) if (typeof module !== "undefined" && module.exports) { exports = module.exports = me; } // CommonJS module 1.1.1 spec (`exports` cannot be a function) exports.me = me; } // declare me globally global.me = me; }(this)); /* eslint-enable no-undef */ /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /* eslint-disable space-before-blocks, no-global-assign, no-native-reassign */ (function () { /** * The built in window Object * @external window * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window.window|window} */ /** * Specify a function to execute when the DOM is fully loaded * @memberOf external:window# * @alias onReady * @deprecated * @see me.device.onReady * @param {Function} fn A function to execute after the DOM is ready. */ window.onReady = function (fn) { console.warn("Deprecated: window.onReady, use me.device.onReady instead"); me.device.onReady.call(this, fn); }; 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) { var last = window.performance.now(), deferTimer; // `no_trailing` defaults to false. if (typeof no_trailing !== "boolean") { no_trailing = false; } return function () { var now = window.performance.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 console === "undefined") { /** * Dummy console.log to avoid crash * in case the browser does not support it * @ignore */ console = { // jshint ignore:line log : function () {}, info : function () {}, error : function () { alert(Array.prototype.slice.call(arguments).join(", ")); } }; } // based on the requestAnimationFrame polyfill by Erik Möller (function () { var lastTime = 0; var frameDuration = 1000 / 60; var vendors = ["ms", "moz", "webkit", "o"]; var x; // standardized functions // https://developer.mozilla.org/fr/docs/Web/API/Window/requestAnimationFrame var requestAnimationFrame = window.requestAnimationFrame; var cancelAnimationFrame = window.cancelAnimationFrame; // get prefixed rAF and cAF is standard one not supported for (x = 0; x < vendors.length && !requestAnimationFrame; ++x) { requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; } for (x = 0; x < vendors.length && !cancelAnimationFrame; ++x) { cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] || window[vendors[x] + "CancelRequestAnimationFrame"]; } if (!requestAnimationFrame || !cancelAnimationFrame) { requestAnimationFrame = function (callback) { var currTime = window.performance.now(); var timeToCall = Math.max(0, frameDuration - (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; } }()); })(); /* eslint-enable space-before-blocks, no-global-assign, no-native-reassign */ /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /* eslint-disable no-extend-native */ /** * The built in Function Object * @external Function * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function|Function} */ /** * Executes a function as soon as the interpreter is idle (stack empty). * @memberof! external:Function# * @alias defer * @param {Object} context The execution context of the deferred function. * @param {} [arguments...] Optional additional arguments to carry 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 the current context and 'myArgument' as parameter * myFunc.defer(this, 'myArgument'); */ Function.prototype.defer = function () { return setTimeout(this.bind.apply(this, arguments), 0.01); }; /* eslint-enable no-extend-native */ /** * Jay Extend 1.0.1 * https://github.com/parasyte/jay-extend * A super fast prototypal inheritance microlib for the modern web. * Copyright (c) 2014-2015 Jay Oster * MIT License */ /* eslint-disable no-undef */ /** * Extend a class prototype with the provided mixin descriptors. * Designed as a faster replacement for John Resig's Simple Inheritance. * @name extend * @memberOf Jay * @function * @param {Object[]} mixins... Each mixin is a dictionary of functions, or a * previously extended class whose methods will be applied to the target class * prototype. * @return {Object} * @example * var Person = Jay.extend({ * "init" : function (isDancing) { * this.dancing = isDancing; * }, * "dance" : function () { * return this.dancing; * } * }); * * var Ninja = Person.extend({ * "init" : function () { * // Call the super constructor, passing a single argument * Person.prototype.init.apply(this, [false]); * }, * "dance" : function () { * // Call the overridden dance() method * return Person.prototype.dance.apply(this); * }, * "swingSword" : function () { * return true; * } * }); * * var Pirate = Person.extend(Ninja, { * "init" : function () { * // Call the super constructor, passing a single argument * Person.prototype.init.apply(this, [true]); * } * }); * * var p = new Person(true); * console.log(p.dance()); // => true * * var n = new Ninja(); * console.log(n.dance()); // => false * console.log(n.swingSword()); // => true * * var r = new Pirate(); * console.log(r.dance()); // => true * console.log(r.swingSword()); // => true * * console.log( * p instanceof Person && * n instanceof Ninja && * n instanceof Person && * r instanceof Pirate && * r instanceof Person * ); // => true * * console.log(r instanceof Ninja); // => false */ (function () { function extend() { var methods = {}; var mixins = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { mixins.push(arguments[i]); } /** * The class constructor which calls the user `init` constructor. * @ignore */ function Class() { // Call the user constructor this.init.apply(this, arguments); return this; } // Apply superClass Class.prototype = Object.create(this.prototype); // Apply all mixin methods to the class prototype mixins.forEach(function (mixin) { apply_methods(Class, methods, mixin.__methods__ || mixin); }); // Verify constructor exists if (!("init" in Class.prototype)) { throw new TypeError( "extend: Class is missing a constructor named `init`" ); } // Apply syntactic sugar for accessing methods on super classes Object.defineProperty(Class.prototype, "_super", { "value" : _super }); // Create a hidden property on the class itself // List of methods, used for applying classes as mixins Object.defineProperty(Class, "__methods__", { "value" : methods }); // Make this class extendable Class.extend = extend; return Class; } /** * Apply methods to the class prototype. * @ignore */ function apply_methods(Class, methods, descriptor) { Object.keys(descriptor).forEach(function (method) { methods[method] = descriptor[method]; if (typeof(descriptor[method]) !== "function") { throw new TypeError( "extend: Method `" + method + "` is not a function" ); } Object.defineProperty(Class.prototype, method, { "configurable" : true, "value" : descriptor[method] }); }); } /** * Special method that acts as a proxy to the super class. * @name _super * @ignore */ function _super(superClass, method, args) { return superClass.prototype[method].apply(this, args); } /** * The base class from which all jay-extend classes inherit. * @ignore */ var Jay = function () { Object.apply(this, arguments); }; Jay.prototype = Object.create(Object.prototype); Jay.prototype.constructor = Jay; Object.defineProperty(Jay, "extend", { "value" : extend }); /** * Export the extend method. * @ignore */ if (typeof(window) !== "undefined") { window.Jay = Jay; } else { module.exports = Jay; } })(); /* eslint-enable no-undef */ /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /** * The built in Object object. * @external Object * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object|Object} */ /** * The base class from which all melonJS objects inherit. * See: {@link https://github.com/parasyte/jay-extend} * @class * @extends external:Object# * @memberOf me */ me.Object = window.Jay; /* eslint-disable no-self-compare */ if (!Object.is) { /** * The Object.is() method determines whether two values are the same value. * @name is * @memberOf external:Object# * @function * @param {Object} a The first value to compare * @param {Object} b The second value to compare * @return {Boolean} * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is|Object.is} * @example * // Strings are equal * var s = "foo"; * Object.is(s, "foo"); //> true * * // 0 and -0 are not equal * Object.is(0, -0); //>false * * // NaN and NaN are equal * Object.is(NaN, NaN); //> true * * // Two object references are not equal * Object.is({}, {}); //> false * * // Two vars referencing one object are equal * var a = {}, b = a; * Object.is(a, b); //> true */ Object.is = function(a, b) { // SameValue algorithm if (a === b) { // Steps 1-5, 7-10 // Steps 6.b-6.e: +0 != -0 return a !== 0 || 1 / a === 1 / b; } else { // Step 6.a: NaN == NaN return a !== a && b !== b; } }; } if (!Object.assign) { (function () { /** * The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. * The Object.assign method only copies enumerable and own properties from a source object to a target object. * It uses [[Get]] on the source and [[Put]] on the target, so it will invoke getters and setters. * Therefore it assigns properties versus just copying or defining new properties. * This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. * For copying propertiy definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor and Object.defineProperty should be used instead. * @name assign * @memberOf external:Object# * @function * @param {Object} target The target object. * @param {Object[]} sources The source object(s). * @return {Object} The target object gets returned. * @see {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign} * @example * // Merging objects * var o1 = { a: 1 }; * var o2 = { b: 2 }; * var o3 = { c: 3 }; * * var obj = Object.assign(o1, o2, o3); * console.log(obj); * // { a: 1, b: 2, c: 3 } */ Object.assign = function (target) { "use strict"; // We must check against these specific cases. if (target === undefined || target === null) { throw new TypeError("Cannot convert undefined or null to object"); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; })(); }; /* eslint-enable no-self-compare */ /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /** * The built in Error object. * @external Error * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error|Error} */ /** * melonJS base class for exception handling. * @name Error * @memberOf me * @constructor * @param {String} msg Error message. */ me.Error = me.Object.extend.bind(Error)({ /** * @ignore */ init : function (msg) { this.name = "me.Error"; this.message = msg; } }); /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /** * The built in Math Object * @external Math * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math|Math} */ if (!Math.sign) { /** * The Math.sign() function returns the sign of a number, indicating whether the number is positive, negative or zero. * @memberof! external:Math# * @alias sign * @param {number} x a number. * @return {number} sign of the number */ Math.sign = function(x) { // If x is NaN, the result is NaN. // If x is -0, the result is -0. // If x is +0, the result is +0. // If x is negative and not -0, the result is -1. // If x is positive and not +0, the result is +1. x = +x; // convert to a number if (x === 0 || isNaN(x)) { return Number(x); } return x > 0 ? 1 : -1; }; } /** * MelonJS Game Engine * Copyright (C) 2011 - 2018 Olivier Biot * http://www.melonjs.org */ /** * The built in Number Object * @external Number * @see {@link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Number|Number} */ /* eslint-disable no-extend-native */ /** * 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 integer between min (included) and max (excluded) * @memberof! external:Number# * @alias random * @param {number} [min=this] minimum value. * @param {number} max maximum value. * @return {number} random value * @example * // Print a random number; one of 5, 6, 7, 8, 9 * console.log( (5).random(10) ); * // Select a random array element * var ar = [ "foo", "bar", "baz" ]; * console.log(ar[ (0).random(ar.length) ]); */ Number.prototype.random = function (min, max) { if (!max) { max = min; min = this; } return (~~(Math.random() * (max - min)) + min); }; /** * return a random float between min, max (exclusive) * @memberof! external:Number# * @alias randomFloat * @param {number} [min=this] minimum value. * @param {number} max maximum value. * @return {number} random value * @example * // Print a random number; one of 5, 6, 7, 8, 9 * console.log( (5).random(10) ); * // Select a random array element * var ar = [ "foo", "bar", "baz" ]; * console.log(ar[ (0).random(ar.length) ]); */ Number.prototype.randomFloat = function (min, max) { if (!max) { max = min; min = this; } return (Math.random() * (max - min)) + min; }; /** * return a weighted random between min, max (exclusive) * favoring the lower numbers * @memberof! external:Number# * @alias weightedRandom * @param {number} [min=this] minimum value. * @param {number} max maximum value. * @return {number} random value * @example * // Print a random number; one of 5, 6, 7, 8, 9 * console.log( (5).random(10) ); * // Select a random array element * var ar = [ "foo", "bar", "baz" ]; * console.log(ar[ (0).random(ar.length) ]); */ Number.prototype.weightedRandom = function (min, max) { if (!max) { max = min; min = this; } return (~~(Math.pow(Math.random(), 2) * (max - min)) + min); }; /** * round a value to the specified number of digit * @memberof! external:Number# * @alias round * @param {number} [num=this] 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 (num, dec) { // if only one argument use the object value var powres = Math.pow(10, dec || num || 0); num = (arguments.length < 2) ? this : num; return (~~(0.5 + num * powres) / powres); }; /** * a quick toHex function
* 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?\
// list of defined timer function
var timers = [];
var timerId = 0;
/**
* @ignore
*/
var clearTimer = function (timerId) {
for (var i = 0, len = timers.length; i < len; i++) {
if (timers[i].timerId === timerId) {
timers.splice(i, 1);
break;
}
}
};
/**
* update timers
* @ignore
*/
var updateTimers = function (dt) {
for (var i = 0, len = timers.length; i < len; i++) {
var _timer = timers[i];
if (!(_timer.pauseable && me.state.isPaused())) {
_timer.elapsed += dt;
}
if (_timer.elapsed >= _timer.delay) {
_timer.fn.apply(this);
if (_timer.repeat === true) {
_timer.elapsed -= _timer.delay;
} else {
me.timer.clearTimeout(_timer.timerId);
}
}
}
};
/*
* PUBLIC STUFF
*/
/**
* Last game tick value.
* Use this value to scale velocities during frame drops due to slow
* hardware or when setting an FPS limit. (See {@link me.sys.fps})
* This feature is disabled by default. Enable me.sys.interpolation to
* use it.
* @public
* @see me.sys.interpolation
* @type Number
* @name tick
* @memberOf me.timer
*/
api.tick = 1.0;
/**
* Last measured fps rate.
* This feature is disabled by default. Load and enable the DebugPanel
* plugin to use it.
* @public
* @type Number
* @name fps
* @memberOf me.timer
*/
api.fps = 0;
/**
* Last update time.
* Use this value to implement frame prediction in drawing events,
* for creating smooth motion while running game update logic at
* a lower fps.
* @public
* @type Date
* @name lastUpdate
* @memberOf me.timer
*/
api.lastUpdate = window.performance.now();
/**
* init the timer
* @ignore
*/
api.init = function () {
// reset variables to initial state
api.reset();
now = last = 0;
};
/**
* reset time (e.g. usefull in case of pause)
* @name reset
* @memberOf me.timer
* @ignore
* @function
*/
api.reset = function () {
// set to "now"
last = now = window.performance.now();
delta = 0;
// reset delta counting variables
framedelta = 0;
framecount = 0;
};
/**
* Calls a function once after a specified delay.
* @name setTimeout
* @memberOf me.timer
* @param {Function} fn the function you want to execute after delay milliseconds.
* @param {Number} delay the number of milliseconds (thousandths of a second) that the function call should be delayed by.
* @param {Boolean} [pauseable=true] respects the pause state of the engine.
* @return {Number} The numerical ID of the timeout, which can be used later with me.timer.clearTimeout().
* @function
*/
api.setTimeout = function (fn, delay, pauseable) {
timers.push({
fn : fn,
delay : delay,
elapsed : 0,
repeat : false,
timerId : ++timerId,
pauseable : pauseable === true || true
});
return timerId;
};
/**
* Calls a function at specified interval.
* @name setInterval
* @memberOf me.timer
* @param {Function} fn the function to execute
* @param {Number} delay the number of milliseconds (thousandths of a second) on how often to execute the function
* @param {Boolean} [pauseable=true] respects the pause state of the engine.
* @return {Number} The numerical ID of the timeout, which can be used later with me.timer.clearInterval().
* @function
*/
api.setInterval = function (fn, delay, pauseable) {
timers.push({
fn : fn,
delay : delay,
elapsed : 0,
repeat : true,
timerId : ++timerId,
pauseable : pauseable === true || true
});
return timerId;
};
/**
* Clears the delay set by me.timer.setTimeout().
* @name clearTimeout
* @memberOf me.timer
* @function
* @param {Number} timeoutID ID of the timeout to be cleared
*/
api.clearTimeout = function (timeoutID) {
clearTimer.defer(this, timeoutID);
};
/**
* Clears the Interval set by me.timer.setInterval().
* @name clearInterval
* @memberOf me.timer
* @function
* @param {Number} intervalID ID of the interval to be cleared
*/
api.clearInterval = function (intervalID) {
clearTimer.defer(this, intervalID);
};
/**
* Return the current timestamp in milliseconds
* since the game has started or since linux epoch (based on browser support for High Resolution Timer)
* @name getTime
* @memberOf me.timer
* @return {Number}
* @function
*/
api.getTime = function () {
return now;
};
/**
* Return elapsed time in milliseconds since the last update
* @name getDelta
* @memberOf me.timer
* @return {Number}
* @function
*/
api.getDelta = function () {
return delta;
};
/**
* 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
* @param {Number} time current timestamp as provided by the RAF callback
* @return {Number} time elapsed since the last update
* @ignore
*/
api.update = function (time) {
last = now;
now = time;
delta = (now - last);
// get the game tick
api.tick = (delta > minstep && me.sys.interpolation) ? delta / step : 1;
// update defined timers
updateTimers(delta);
return delta;
};
// return our apiect
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* A pool of Object entity
* This object is used for object pooling - a technique that might speed up your game
* if used properly.
* 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.
* 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
*
* @namespace me.pool
* @memberOf me
*/
me.pool = (function () {
// hold public stuff in our singleton
var api = {};
var entityClass = {};
/*
* PUBLIC STUFF
*/
/**
* Constructor
* @ignore
*/
api.init = function () {
// add default entity object
api.register("me.Entity", me.Entity);
api.register("me.CollectableEntity", me.CollectableEntity);
api.register("me.LevelEntity", me.LevelEntity);
api.register("me.Tween", me.Tween, true);
api.register("me.Color", me.Color, true);
api.register("me.Particle", me.Particle, true);
api.register("me.Sprite", me.Sprite);
api.register("me.Vector2d", me.Vector2d, true);
api.register("me.Glyph", me.Glyph, true);
api.register("me.Matrix2d", me.Matrix2d, true);
};
/**
* register an object to the pool.
* Pooling must be set to true if more than one such objects will be created.
* (note) If pooling is enabled, you shouldn't instantiate objects with `new`.
* See examples in {@link me.pool#pull}
* @name register
* @memberOf me.pool
* @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.pool.register("playerspawnpoint", PlayerEntity);
* me.pool.register("cherryentity", CherryEntity, true);
* me.pool.register("heartentity", HeartEntity, true);
* me.pool.register("starentity", StarEntity, true);
*/
api.register = function (className, classObj, pooling) {
if (typeof (classObj) !== "undefined") {
entityClass[className] = {
"class" : classObj,
"pool" : (pooling ? [] : undefined)
};
} else {
throw new me.Error("Cannot register object '" + className + "', invalid class");
}
};
/**
* Pull a new instance of the requested object (if added into the object pool)
* @name pull
* @memberOf me.pool
* @public
* @function
* @param {String} className as used in {@link me.pool.register}
* @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object
* @return {Object} the instance of the requested object
* @example
* me.pool.register("player", PlayerEntity);
* var player = me.pool.pull("player");
* @example
* me.pool.register("bullet", BulletEntity, true);
* me.pool.register("enemy", EnemyEntity, true);
* // ...
* // when we need to manually create a new bullet:
* var bullet = me.pool.pull("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.pool.pull("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.world.removeChild(enemy);
* me.game.world.removeChild(bullet);
*/
api.pull = function (name) {
var args = new Array(arguments.length);
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
var entity = entityClass[name];
if (entity) {
var proto = entity["class"],
pool = entity.pool,
obj;
if (pool && ((obj = pool.pop()))) {
args.shift();
// call the object onResetEvent function if defined
if (typeof(obj.onResetEvent) === "function") {
obj.onResetEvent.apply(obj, args);
}
else {
obj.init.apply(obj, args);
}
}
else {
args[0] = proto;
obj = new (proto.bind.apply(proto, args))();
if (pool) {
obj.className = name;
}
}
return obj;
}
throw new me.Error("Cannot instantiate entity of type '" + name + "'");
};
/**
* purge the entity pool from any inactive object
* Object pooling must be enabled for this function to work
* note: this will trigger the garbage collector
* @name purge
* @memberOf me.pool
* @public
* @function
*/
api.purge = function () {
for (var className in entityClass) {
if (entityClass[className]) {
entityClass[className].pool = [];
}
}
};
/**
* Push back an object instance into the entity pool
* Object pooling for the object class must be enabled,
* and object must have been instantiated using {@link me.pool#pull},
* otherwise this function won't work
* @name push
* @memberOf me.pool
* @public
* @function
* @param {Object} instance to be recycled
*/
api.push = function (obj) {
var name = obj.className;
if (typeof(name) === "undefined" || !entityClass[name]) {
// object is not registered, don't do anything
return;
}
// store back the object instance for later recycling
entityClass[name].pool.push(obj);
};
/**
* Check if an object with the provided name is registered
* @name exists
* @memberOf me.pool
* @public
* @function
* @param {String} name of the registered object
* @return {Boolean} true if the classname is registered
*/
api.exists = function (name) {
return name in entityClass;
};
// return our object
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a generic 2D Vector Object
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Number} [x=0] x value of the vector
* @param {Number} [y=0] y value of the vector
*/
me.Vector2d = me.Object.extend(
/** @scope me.Vector2d.prototype */
{
/** @ignore */
init : function (x, y) {
return this.set(x || 0, y || 0);
},
/**
* @ignore */
_set : function (x, y) {
this.x = x;
this.y = y;
return this;
},
/**
* set the Vector x and y properties to the given values
* @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) {
if (x !== +x || y !== +y) {
throw new me.Vector2d.Error(
"invalid x,y parameters (not a number)"
);
}
/**
* x value of the vector
* @public
* @type Number
* @name x
* @memberOf me.Vector2d
*/
//this.x = x;
/**
* y value of the vector
* @public
* @type Number
* @name y
* @memberOf me.Vector2d
*/
//this.y = y;
return this._set(x, y);
},
/**
* 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) {
return this._set(v.x, v.y);
},
/**
* 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) {
return this._set(this.x + v.x, this.y + v.y);
},
/**
* 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) {
return this._set(this.x - v.x, this.y - v.y);
},
/**
* Multiply this vector values by the given scalar
* @name scale
* @memberOf me.Vector2d
* @function
* @param {Number} x
* @param {Number} [y=x]
* @return {me.Vector2d} Reference to this object for method chaining
*/
scale : function (x, y) {
return this._set(this.x * x, this.y * (typeof (y) !== "undefined" ? y : x));
},
/**
* Convert this vector into isometric coordinate space
* @name toIso
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} Reference to this object for method chaining
*/
toIso : function () {
return this._set(this.x - this.y, (this.x + this.y) * 0.5);
},
/**
* Convert this vector into 2d coordinate space
* @name to2d
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} Reference to this object for method chaining
*/
to2d : function () {
return this._set(this.y + this.x / 2, this.y - this.x / 2);
},
/**
* Multiply this vector values by the passed vector
* @name scaleV
* @memberOf me.Vector2d
* @function
* @param {me.Vector2d} v
* @return {me.Vector2d} Reference to this object for method chaining
*/
scaleV : function (v) {
return this._set(this.x * v.x, this.y * v.y);
},
/**
* 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) {
return this._set(this.x / n, this.y / n);
},
/**
* 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 () {
return this._set((this.x < 0) ? -this.x : this.x, (this.y < 0) ? -this.y : this.y);
},
/**
* 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) {
return this._set(this.x.clamp(low, high), this.y.clamp(low, high));
},
/**
* 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) {
return this._set((this.x < v.x) ? this.x : v.x, (this.y < v.y) ? this.y : v.y);
},
/**
* 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) {
return this._set((this.x > v.x) ? this.x : v.x, (this.y > v.y) ? this.y : v.y);
},
/**
* Floor the vector values
* @name floor
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} new me.Vector2d
*/
floor : function () {
return new me.Vector2d(Math.floor(this.x), Math.floor(this.y));
},
/**
* Floor this vector values
* @name floorSelf
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} Reference to this object for method chaining
*/
floorSelf : function () {
return this._set(Math.floor(this.x), Math.floor(this.y));
},
/**
* 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 () {
return this._set(Math.ceil(this.x), Math.ceil(this.y));
},
/**
* 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 () {
return this._set(-this.x, -this.y);
},
/**
* 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) {
return this._set(v.x, v.y);
},
/**
* 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));
},
/**
* normalize this vector (scale the vector so that its magnitude is 1)
* @name normalize
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} Reference to this object for method chaining
*/
normalize : function () {
var d = this.length();
if (d > 0) {
return this._set(this.x / d, this.y / d);
}
return this;
},
/**
* change this vector to be perpendicular to what it was before.
* (Effectively rotates it 90 degrees in a clockwise direction)
* @name perp
* @memberOf me.Vector2d
* @function
* @return {me.Vector2d} Reference to this object for method chaining
*/
perp : function () {
return this._set(this.y, -this.x);
},
/**
* Rotate this vector (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.Vector2d
* @function
* @param {number} angle The angle to rotate (in radians)
* @return {me.Vector2d} Reference to this object for method chaining
*/
rotate : function (angle) {
var x = this.x;
var y = this.y;
return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle));
},
/**
* return the dot product of this vector and the passed one
* @name dotProduct
* @memberOf me.Vector2d
* @function
* @param {me.Vector2d} v
* @return {Number} The dot product.
*/
dotProduct : function (v) {
return this.x * v.x + this.y * v.y;
},
/**
* return the square length of this vector
* @name length2
* @memberOf me.Vector2d
* @function
* @return {Number} The length^2 of this vector.
*/
length2 : function () {
return this.dotProduct(this);
},
/**
* return the length (magnitude) of this vector
* @name length
* @memberOf me.Vector2d
* @function
* @return {Number} the length of this vector
*/
length : function () {
return Math.sqrt(this.length2());
},
/**
* 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) {
var dx = this.x - v.x, dy = this.y - v.y;
return Math.sqrt(dx * dx + dy * dy);
},
/**
* 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.acos((this.dotProduct(v) / (this.length() * v.length())).clamp(-1, 1));
},
/**
* project this vector on to another vector.
* @name project
* @memberOf me.Vector2d
* @function
* @param {me.Vector2d} v The vector to project onto.
* @return {me.Vector2d} Reference to this object for method chaining
*/
project : function (v) {
return this.scale(this.dotProduct(v) / v.length2());
},
/**
* Project this vector onto a vector of unit length.
* This is slightly more efficient than `project` when dealing with unit vectors.
* @name projectN
* @memberOf me.Vector2d
* @function
* @param {me.Vector2d} v The unit vector to project onto.
* @return {me.Vector2d} Reference to this object for method chaining
*/
projectN : function (v) {
return this.scale(this.dotProduct(v));
},
/**
* 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;
}
});
/**
* Base class for Vector2d exception handling.
* @name Error
* @class
* @memberOf me.Vector2d
* @constructor
* @param {String} msg Error message.
*/
me.Vector2d.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.Vector2d.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a generic 3D Vector Object
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Number} [x=0] x value of the vector
* @param {Number} [y=0] y value of the vector
* @param {Number} [z=0] z value of the vector
*/
me.Vector3d = me.Object.extend(
/** @scope me.Vector3d.prototype */
{
/** @ignore */
init : function (x, y, z) {
return this.set(x || 0, y || 0, z || 0);
},
/**
* @ignore */
_set : function (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
},
/**
* set the Vector x and y properties to the given values
* @name set
* @memberOf me.Vector3d
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @return {me.Vector3d} Reference to this object for method chaining
*/
set : function (x, y, z) {
if (x !== +x || y !== +y || z !== +z) {
throw new me.Vector3d.Error(
"invalid x, y, z parameters (not a number)"
);
}
/**
* x value of the vector
* @public
* @type Number
* @name x
* @memberOf me.Vector3d
*/
//this.x = x;
/**
* y value of the vector
* @public
* @type Number
* @name y
* @memberOf me.Vector3d
*/
//this.y = y;
/**
* z value of the vector
* @public
* @type Number
* @name z
* @memberOf me.Vector3d
*/
//this.z = z;
return this._set(x, y, z);
},
/**
* set the Vector x and y properties to 0
* @name setZero
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
setZero : function () {
return this.set(0, 0, 0);
},
/**
* set the Vector x and y properties using the passed vector
* @name setV
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
setV : function (v) {
return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this.z);
},
/**
* Add the passed vector to this vector
* @name add
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
add : function (v) {
return this._set(this.x + v.x, this.y + v.y, this.z + (v.z || 0));
},
/**
* Substract the passed vector to this vector
* @name sub
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
sub : function (v) {
return this._set(this.x - v.x, this.y - v.y, this.z - (v.z || 0));
},
/**
* Multiply this vector values by the given scalar
* @name scale
* @memberOf me.Vector3d
* @function
* @param {Number} x
* @param {Number} [y=x]
* @param {Number} [z=x]
* @return {me.Vector3d} Reference to this object for method chaining
*/
scale : function (x, y, z) {
y = (typeof (y) !== "undefined" ? y : x);
z = (typeof (z) !== "undefined" ? z : x);
return this._set(this.x * x, this.y * y, this.z * z);
},
/**
* Multiply this vector values by the passed vector
* @name scaleV
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
scaleV : function (v) {
return this._set(this.x * v.x, this.y * v.y, this.z * (v.z || 1));
},
/**
* Convert this vector into isometric coordinate space
* @name toIso
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
toIso : function () {
return this._set(this.x - this.y, (this.x + this.y) * 0.5, this.z);
},
/**
* Convert this vector into 2d coordinate space
* @name to2d
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
to2d : function () {
return this._set(this.y + this.x / 2, this.y - this.x / 2, this.z);
},
/**
* Divide this vector values by the passed value
* @name div
* @memberOf me.Vector3d
* @function
* @param {Number} value
* @return {me.Vector3d} Reference to this object for method chaining
*/
div : function (n) {
return this._set(this.x / n, this.y / n, this.z / n);
},
/**
* Update this vector values to absolute values
* @name abs
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
abs : function () {
return this._set((this.x < 0) ? -this.x : this.x, (this.y < 0) ? -this.y : this.y, (this.z < 0) ? -this.z : this.z);
},
/**
* Clamp the vector value within the specified value range
* @name clamp
* @memberOf me.Vector3d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.Vector3d} new me.Vector3d
*/
clamp : function (low, high) {
return new me.Vector3d(this.x.clamp(low, high), this.y.clamp(low, high), this.z.clamp(low, high));
},
/**
* Clamp this vector value within the specified value range
* @name clampSelf
* @memberOf me.Vector3d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.Vector3d} Reference to this object for method chaining
*/
clampSelf : function (low, high) {
return this._set(this.x.clamp(low, high), this.y.clamp(low, high), this.z.clamp(low, high));
},
/**
* Update this vector with the minimum value between this and the passed vector
* @name minV
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
minV : function (v) {
var _vz = v.z || 0;
return this._set((this.x < v.x) ? this.x : v.x, (this.y < v.y) ? this.y : v.y, (this.z < _vz) ? this.z : _vz);
},
/**
* Update this vector with the maximum value between this and the passed vector
* @name maxV
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
maxV : function (v) {
var _vz = v.z || 0;
return this._set((this.x > v.x) ? this.x : v.x, (this.y > v.y) ? this.y : v.y, (this.z > _vz) ? this.z : _vz);
},
/**
* Floor the vector values
* @name floor
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} new me.Vector3d
*/
floor : function () {
return new me.Vector3d(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z));
},
/**
* Floor this vector values
* @name floorSelf
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
floorSelf : function () {
return this._set(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z));
},
/**
* Ceil the vector values
* @name ceil
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} new me.Vector3d
*/
ceil : function () {
return new me.Vector3d(Math.ceil(this.x), Math.ceil(this.y), Math.ceil(this.z));
},
/**
* Ceil this vector values
* @name ceilSelf
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
ceilSelf : function () {
return this._set(Math.ceil(this.x), Math.ceil(this.y), Math.ceil(this.z));
},
/**
* Negate the vector values
* @name negate
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} new me.Vector3d
*/
negate : function () {
return new me.Vector3d(-this.x, -this.y, -this.z);
},
/**
* Negate this vector values
* @name negateSelf
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
negateSelf : function () {
return this._set(-this.x, -this.y, -this.z);
},
/**
* Copy the x,y values of the passed vector to this one
* @name copy
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {me.Vector3d} Reference to this object for method chaining
*/
copy : function (v) {
return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this.z);
},
/**
* return true if the two vectors are the same
* @name equals
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {Boolean}
*/
equals : function (v) {
return ((this.x === v.x) && (this.y === v.y) && (this.z === (v.z || this.z)));
},
/**
* normalize this vector (scale the vector so that its magnitude is 1)
* @name normalize
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
normalize : function () {
var d = this.length();
if (d > 0) {
return this._set(this.x / d, this.y / d, this.z / d);
}
return this;
},
/**
* change this vector to be perpendicular to what it was before.
* (Effectively rotates it 90 degrees in a clockwise direction around the z axis)
* @name perp
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} Reference to this object for method chaining
*/
perp : function () {
return this._set(this.y, -this.x, this.z);
},
/**
* Rotate this vector (counter-clockwise) by the specified angle (in radians) around the z axis
* @name rotate
* @memberOf me.Vector3d
* @function
* @param {number} angle The angle to rotate (in radians)
* @return {me.Vector3d} Reference to this object for method chaining
*/
rotate : function (angle) {
var x = this.x;
var y = this.y;
return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle), this.z);
},
/**
* return the dot product of this vector and the passed one
* @name dotProduct
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {Number} The dot product.
*/
dotProduct : function (v) {
return this.x * v.x + this.y * v.y + this.z * (v.z || 1);
},
/**
* return the square length of this vector
* @name length2
* @memberOf me.Vector3d
* @function
* @return {Number} The length^2 of this vector.
*/
length2 : function () {
return this.dotProduct(this);
},
/**
* return the length (magnitude) of this vector
* @name length
* @memberOf me.Vector3d
* @function
* @return {Number} the length of this vector
*/
length : function () {
return Math.sqrt(this.length2());
},
/**
* return the distance between this vector and the passed one
* @name distance
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {Number}
*/
distance : function (v) {
var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - (v.z || 0);
return Math.sqrt(dx * dx + dy * dy + dz * dz);
},
/**
* return the angle between this vector and the passed one
* @name angle
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v
* @return {Number} angle in radians
*/
angle : function (v) {
return Math.acos((this.dotProduct(v) / (this.length() * v.length())).clamp(-1, 1));
},
/**
* project this vector on to another vector.
* @name project
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v The vector to project onto.
* @return {me.Vector3d} Reference to this object for method chaining
*/
project : function (v) {
return this.scale(this.dotProduct(v) / v.length2());
},
/**
* Project this vector onto a vector of unit length.
* This is slightly more efficient than `project` when dealing with unit vectors.
* @name projectN
* @memberOf me.Vector3d
* @function
* @param {me.Vector2d|me.Vector3d} v The unit vector to project onto.
* @return {me.Vector3d} Reference to this object for method chaining
*/
projectN : function (v) {
return this.scale(this.dotProduct(v));
},
/**
* return a clone copy of this vector
* @name clone
* @memberOf me.Vector3d
* @function
* @return {me.Vector3d} new me.Vector3d
*/
clone : function () {
return new me.Vector3d(this.x, this.y, this.z);
},
/**
* convert the object to a string representation
* @name toString
* @memberOf me.Vector3d
* @function
* @return {String}
*/
toString : function () {
return "x:" + this.x + ",y:" + this.y + ",z:" + this.z;
}
});
/**
* Base class for Vector3d exception handling.
* @name Error
* @class
* @memberOf me.Vector3d
* @constructor
* @param {String} msg Error message.
*/
me.Vector3d.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.Vector3d.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* A Vector2d object that provide notification by executing the given callback when the vector is changed.
* @class
* @extends me.Vector2d
* @constructor
* @param {Number} [x=0] x value of the vector
* @param {Number} [y=0] y value of the vector
* @param {Object} settings additional required parameters
* @param {Function} settings.onUpdate the callback to be executed when the vector is changed
*/
me.ObservableVector2d = me.Vector2d.extend({
/** @scope me.ObservableVector2d.prototype */
/** @ignore */
init : function (x, y, settings) {
/**
* x value of the vector
* @public
* @type Number
* @name x
* @memberOf me.ObservableVector2d
*/
Object.defineProperty(this, "x", {
/**
* @ignore
*/
get : function () {
return this._x;
},
/**
* @ignore
*/
set : function (value) {
var ret = this.onUpdate(value, this._y, this._x, this._y);
if (ret && "x" in ret) {
this._x = ret.x;
} else {
this._x = value
}
}
});
/**
* y value of the vector
* @public
* @type Number
* @name y
* @memberOf me.ObservableVector2d
*/
Object.defineProperty(this, "y", {
/**
* @ignore
*/
get : function () {
return this._y;
},
/**
* @ignore
*/
set : function (value) {
var ret = this.onUpdate(this._x, value, this._x, this._y);
if (ret && "y" in ret) {
this._y = ret.y;
} else {
this._y = value
}
}
});
if (typeof(settings) === "undefined") {
throw new me.ObservableVector2d.Error(
"undefined `onUpdate` callback"
);
}
this.setCallback(settings.onUpdate);
this._x = x || 0;
this._y = y || 0;
},
/** @ignore */
_set : function (x, y) {
var ret = this.onUpdate(x, y, this._x, this._y);
if (ret && "x" in ret && "y" in ret) {
this._x = ret.x;
this._y = ret.y;
} else {
this._x = x;
this._y = y;
}
return this;
},
/**
* set the vector value without triggering the callback
* @name setMuted
* @memberOf me.ObservableVector2d
* @function
* @param {Number} x x value of the vector
* @param {Number} y y value of the vector
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
setMuted : function (x, y) {
this._x = x;
this._y = y;
return this;
},
/**
* set the callback to be executed when the vector is changed
* @name setCallback
* @memberOf me.ObservableVector2d
* @function
* @param {function} onUpdate callback
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
setCallback : function (fn) {
if (typeof(fn) !== "function") {
throw new me.ObservableVector2d.Error(
"invalid `onUpdate` callback"
);
}
this.onUpdate = fn;
return this;
},
/**
* Add the passed vector to this vector
* @name add
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
add : function (v) {
return this._set(this._x + v.x, this._y + v.y);
},
/**
* Substract the passed vector to this vector
* @name sub
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
sub : function (v) {
return this._set(this._x - v.x, this._y - v.y);
},
/**
* Multiply this vector values by the given scalar
* @name scale
* @memberOf me.ObservableVector2d
* @function
* @param {Number} x
* @param {Number} [y=x]
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
scale : function (x, y) {
return this._set(this._x * x, this._y * (typeof (y) !== "undefined" ? y : x));
},
/**
* Multiply this vector values by the passed vector
* @name scaleV
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
scaleV : function (v) {
return this._set(this._x * v.x, this._y * v.y);
},
/**
* Divide this vector values by the passed value
* @name div
* @memberOf me.ObservableVector2d
* @function
* @param {Number} value
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
div : function (n) {
return this._set(this._x / n, this._y / n);
},
/**
* Update this vector values to absolute values
* @name abs
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
abs : function () {
return this._set((this._x < 0) ? -this._x : this._x, (this._y < 0) ? -this._y : this._y);
},
/**
* Clamp the vector value within the specified value range
* @name clamp
* @memberOf me.ObservableVector2d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.ObservableVector2d} new me.ObservableVector2d
*/
clamp : function (low, high) {
return new me.ObservableVector2d(this.x.clamp(low, high), this.y.clamp(low, high), {onUpdate: this.onUpdate});
},
/**
* Clamp this vector value within the specified value range
* @name clampSelf
* @memberOf me.ObservableVector2d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
clampSelf : function (low, high) {
return this._set(this._x.clamp(low, high), this._y.clamp(low, high));
},
/**
* Update this vector with the minimum value between this and the passed vector
* @name minV
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
minV : function (v) {
return this._set((this._x < v.x) ? this._x : v.x, (this._y < v.y) ? this._y : v.y);
},
/**
* Update this vector with the maximum value between this and the passed vector
* @name maxV
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
maxV : function (v) {
return this._set((this._x > v.x) ? this._x : v.x, (this._y > v.y) ? this._y : v.y);
},
/**
* Floor the vector values
* @name floor
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} new me.ObservableVector2d
*/
floor : function () {
return new me.ObservableVector2d(Math.floor(this._x), Math.floor(this._y), {onUpdate: this.onUpdate});
},
/**
* Floor this vector values
* @name floorSelf
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
floorSelf : function () {
return this._set(Math.floor(this._x), Math.floor(this._y));
},
/**
* Ceil the vector values
* @name ceil
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} new me.ObservableVector2d
*/
ceil : function () {
return new me.ObservableVector2d(Math.ceil(this._x), Math.ceil(this._y), {onUpdate: this.onUpdate});
},
/**
* Ceil this vector values
* @name ceilSelf
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
ceilSelf : function () {
return this._set(Math.ceil(this._x), Math.ceil(this._y));
},
/**
* Negate the vector values
* @name negate
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} new me.ObservableVector2d
*/
negate : function () {
return new me.ObservableVector2d(-this._x, -this._y, {onUpdate: this.onUpdate});
},
/**
* Negate this vector values
* @name negateSelf
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
negateSelf : function () {
return this._set(-this._x, -this._y);
},
/**
* Copy the x,y values of the passed vector to this one
* @name copy
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
copy : function (v) {
return this._set(v.x, v.y);
},
/**
* return true if the two vectors are the same
* @name equals
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {Boolean}
*/
equals : function (v) {
return ((this._x === v.x) && (this._y === v.y));
},
/**
* normalize this vector (scale the vector so that its magnitude is 1)
* @name normalize
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
normalize : function () {
var d = this.length();
if (d > 0) {
return this._set(this._x / d, this._y / d);
}
return this;
},
/**
* change this vector to be perpendicular to what it was before.
* (Effectively rotates it 90 degrees in a clockwise direction)
* @name perp
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
perp : function () {
return this._set(this._y, -this._x);
},
/**
* Rotate this vector (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.ObservableVector2d
* @function
* @param {number} angle The angle to rotate (in radians)
* @return {me.ObservableVector2d} Reference to this object for method chaining
*/
rotate : function (angle) {
var x = this._x;
var y = this._y;
return this._set(x * Math.cos(angle) - y * Math.sin(angle), x * Math.sin(angle) + y * Math.cos(angle));
},
/**
* return the dot product of this vector and the passed one
* @name dotProduct
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} v
* @return {Number} The dot product.
*/
dotProduct : function (v) {
return this._x * v.x + this._y * v.y;
},
/**
* return the distance between this vector and the passed one
* @name distance
* @memberOf me.ObservableVector2d
* @function
* @param {me.ObservableVector2d} 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 a clone copy of this vector
* @name clone
* @memberOf me.ObservableVector2d
* @function
* @return {me.ObservableVector2d} new me.ObservableVector2d
*/
clone : function () {
// shall we return a cloned me.ObservableVector2d here ?
return new me.ObservableVector2d(this._x, this._y, {onUpdate: this.onUpdate});
},
/**
* return a `me.Vector2d` copy of this `me.ObservableVector2d` object
* @name toVector2d
* @memberOf me.ObservableVector2d
* @function
* @return {me.Vector2d} new me.Vector2d
*/
toVector2d : function () {
return new me.Vector2d(this._x, this._y);
},
/**
* convert the object to a string representation
* @name toString
* @memberOf me.ObservableVector2d
* @function
* @return {String}
*/
toString : function () {
return "x:" + this._x + ",y:" + this._y;
}
});
/**
* Base class for Vector2d exception handling.
* @name Error
* @class
* @memberOf me.ObservableVector2d
* @constructor
* @param {String} msg Error message.
*/
me.ObservableVector2d.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.ObservableVector2d.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* A Vector3d object that provide notification by executing the given callback when the vector is changed.
* @class
* @extends me.Vector3d
* @constructor
* @param {Number} [x=0] x value of the vector
* @param {Number} [y=0] y value of the vector
* @param {Number} [z=0] z value of the vector
* @param {Object} settings additional required parameters
* @param {Function} settings.onUpdate the callback to be executed when the vector is changed
*/
me.ObservableVector3d = me.Vector3d.extend({
/** @scope me.ObservableVector3d.prototype */
/**
* @ignore
*/
init : function (x, y, z, settings) {
/**
* x value of the vector
* @public
* @type Number
* @name x
* @memberOf me.ObservableVector3d
*/
Object.defineProperty(this, "x", {
/**
* @ignore
*/
get : function () {
return this._x;
},
/**
* @ignore
*/
set : function (value) {
var ret = this.onUpdate(value, this._y, this._z, this._x, this._y, this._z);
if (ret && "x" in ret) {
this._x = ret.x;
} else {
this._x = value
}
}
});
/**
* y value of the vector
* @public
* @type Number
* @name y
* @memberOf me.ObservableVector3d
*/
Object.defineProperty(this, "y", {
/**
* @ignore
*/
get : function () {
return this._y;
},
/**
* @ignore
*/
set : function (value) {
var ret = this.onUpdate(this._x, value, this._z, this._x, this._y, this._z);
if (ret && "y" in ret) {
this._y = ret.y;
} else {
this._y = value
}
}
});
/**
* z value of the vector
* @public
* @type Number
* @name z
* @memberOf me.ObservableVector3d
*/
Object.defineProperty(this, "z", {
/**
* @ignore
*/
get : function () {
return this._z;
},
/**
* @ignore
*/
set : function (value) {
var ret = this.onUpdate(this._x, this._y, value, this._x, this._y, this._z);
if (ret && "z" in ret) {
this._z = ret.z;
} else {
this._z = value
}
}
});
if (typeof(settings) === "undefined") {
throw new me.ObservableVector3d.Error(
"undefined `onUpdate` callback"
);
}
this.setCallback(settings.onUpdate);
this._x = x || 0;
this._y = y || 0;
this._z = z || 0;
},
/**
* @ignore */
_set : function (x, y, z) {
var ret = this.onUpdate(x, y, z, this._x, this._y, this._z);
if (ret && "x" in ret && "y" in ret && "z" in ret) {
this._x = ret.x;
this._y = ret.y;
this._z = ret.z;
} else {
this._x = x;
this._y = y;
this._z = z;
}
return this;
},
/**
* set the vector value without triggering the callback
* @name setMuted
* @memberOf me.ObservableVector3d
* @function
* @param {Number} x x value of the vector
* @param {Number} y y value of the vector
* @param {Number} z z value of the vector
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
setMuted : function (x, y, z) {
this._x = x;
this._y = y;
this._z = z;
return this;
},
/**
* set the callback to be executed when the vector is changed
* @name setCallback
* @memberOf me.ObservableVector3d
* @function
* @param {function} onUpdate callback
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
setCallback : function (fn) {
if (typeof(fn) !== "function") {
throw new me.ObservableVector3d.Error(
"invalid `onUpdate` callback"
);
}
this.onUpdate = fn;
return this;
},
/**
* Add the passed vector to this vector
* @name add
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
add : function (v) {
return this._set(this._x + v.x, this._y + v.y, this._z + (v.z || 0));
},
/**
* Substract the passed vector to this vector
* @name sub
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
sub : function (v) {
return this._set(this._x - v.x, this._y - v.y, this._z - (v.z || 0));
},
/**
* Multiply this vector values by the given scalar
* @name scale
* @memberOf me.ObservableVector3d
* @function
* @param {Number} x
* @param {Number} [y=x]
* @param {Number} [z=x]
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
scale : function (x, y, z) {
y = (typeof (y) !== "undefined" ? y : x);
z = (typeof (z) !== "undefined" ? z : x);
return this._set(this._x * x, this._y * y, this._z * z);
},
/**
* Multiply this vector values by the passed vector
* @name scaleV
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
scaleV : function (v) {
return this._set(this._x * v.x, this._y * v.y, this._z * (v.z || 1));
},
/**
* Divide this vector values by the passed value
* @name div
* @memberOf me.ObservableVector3d
* @function
* @param {Number} value
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
div : function (n) {
return this._set(this._x / n, this._y / n, this._z / n);
},
/**
* Update this vector values to absolute values
* @name abs
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
abs : function () {
return this._set(
(this._x < 0) ? -this._x : this._x,
(this._y < 0) ? -this._y : this._y,
(this._Z < 0) ? -this._z : this._z
);
},
/**
* Clamp the vector value within the specified value range
* @name clamp
* @memberOf me.ObservableVector3d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.ObservableVector3d} new me.ObservableVector3d
*/
clamp : function (low, high) {
return new me.ObservableVector3d(
this._x.clamp(low, high),
this._y.clamp(low, high),
this._z.clamp(low, high),
{onUpdate: this.onUpdate}
);
},
/**
* Clamp this vector value within the specified value range
* @name clampSelf
* @memberOf me.ObservableVector3d
* @function
* @param {Number} low
* @param {Number} high
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
clampSelf : function (low, high) {
return this._set(
this._x.clamp(low, high),
this._y.clamp(low, high),
this._z.clamp(low, high)
);
},
/**
* Update this vector with the minimum value between this and the passed vector
* @name minV
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
minV : function (v) {
var _vz = v.z || 0;
return this._set(
(this._x < v.x) ? this._x : v.x,
(this._y < v.y) ? this._y : v.y,
(this._z < _vz) ? this._z : _vz
);
},
/**
* Update this vector with the maximum value between this and the passed vector
* @name maxV
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
maxV : function (v) {
var _vz = v.z || 0;
return this._set(
(this._x > v.x) ? this._x : v.x,
(this._y > v.y) ? this._y : v.y,
(this._z > _vz) ? this._z : _vz
);
},
/**
* Floor the vector values
* @name floor
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} new me.ObservableVector3d
*/
floor : function () {
return new me.ObservableVector3d(
Math.floor(this._x),
Math.floor(this._y),
Math.floor(this._z),
{onUpdate: this.onUpdate}
);
},
/**
* Floor this vector values
* @name floorSelf
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
floorSelf : function () {
return this._set(Math.floor(this._x), Math.floor(this._y), Math.floor(this._z));
},
/**
* Ceil the vector values
* @name ceil
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} new me.ObservableVector3d
*/
ceil : function () {
return new me.ObservableVector3d(
Math.ceil(this._x),
Math.ceil(this._y),
Math.ceil(this._z),
{onUpdate: this.onUpdate}
);
},
/**
* Ceil this vector values
* @name ceilSelf
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
ceilSelf : function () {
return this._set(Math.ceil(this._x), Math.ceil(this._y), Math.ceil(this._z));
},
/**
* Negate the vector values
* @name negate
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} new me.ObservableVector3d
*/
negate : function () {
return new me.ObservableVector3d(
-this._x,
-this._y,
-this._z,
{onUpdate: this.onUpdate}
);
},
/**
* Negate this vector values
* @name negateSelf
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
negateSelf : function () {
return this._set(-this._x, -this._y, -this._z);
},
/**
* Copy the x,y,z values of the passed vector to this one
* @name copy
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
copy : function (v) {
return this._set(v.x, v.y, typeof (v.z) !== "undefined" ? v.z : this._z);
},
/**
* return true if the two vectors are the same
* @name equals
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {Boolean}
*/
equals : function (v) {
return ((this._x === v.x) && (this._y === v.y) && (this._z === (v.z || this._z)));
},
/**
* normalize this vector (scale the vector so that its magnitude is 1)
* @name normalize
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
normalize : function () {
var d = this.length();
if (d > 0) {
return this._set(this._x / d, this._y / d, this._z / d);
}
return this;
},
/**
* change this vector to be perpendicular to what it was before.
* (Effectively rotates it 90 degrees in a clockwise direction)
* @name perp
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
perp : function () {
return this._set(this._y, -this._x, this._z);
},
/**
* Rotate this vector (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.ObservableVector3d
* @function
* @param {number} angle The angle to rotate (in radians)
* @return {me.ObservableVector3d} Reference to this object for method chaining
*/
rotate : function (angle) {
var x = this._x;
var y = this._y;
return this._set(
x * Math.cos(angle) - y * Math.sin(angle),
x * Math.sin(angle) + y * Math.cos(angle),
this._z
);
},
/**
* return the dot product of this vector and the passed one
* @name dotProduct
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {Number} The dot product.
*/
dotProduct : function (v) {
return this._x * v.x + this._y * v.y + this._z * (v.z || 1);
},
/**
* return the distance between this vector and the passed one
* @name distance
* @memberOf me.ObservableVector3d
* @function
* @param {me.Vector2d|me.Vector3d|me.ObservableVector2d|me.ObservableVector3d} v
* @return {Number}
*/
distance : function (v) {
var dx = this._x - v.x, dy = this._y - v.y, dz = this._z - (v.z || 0);
return Math.sqrt(dx * dx + dy * dy + dz * dz);
},
/**
* return a clone copy of this vector
* @name clone
* @memberOf me.ObservableVector3d
* @function
* @return {me.ObservableVector3d} new me.ObservableVector3d
*/
clone : function () {
// shall we return a cloned me.ObservableVector3d here ?
return new me.ObservableVector3d(
this._x,
this._y,
this._z,
{onUpdate: this.onUpdate}
);
},
/**
* return a `me.Vector3d` copy of this `me.ObservableVector3d` object
* @name toVector3d
* @memberOf me.ObservableVector3d
* @function
* @return {me.Vector3d} new me.Vector3d
*/
toVector3d : function () {
return new me.Vector3d(this._x, this._y, this._z);
},
/**
* convert the object to a string representation
* @name toString
* @memberOf me.ObservableVector3d
* @function
* @return {String}
*/
toString : function () {
return "x:" + this._x + ",y:" + this._y + ",z:" + this._z;
}
});
/**
* Base class for Vector3d exception handling.
* @name Error
* @class
* @memberOf me.ObservableVector3d
* @constructor
* @param {String} msg Error message.
*/
me.ObservableVector3d.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.ObservableVector3d.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a Matrix2d Object.
* the identity matrix and parameters position :
*
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {me.Matrix2d} [mat2d] An instance of me.Matrix2d to copy from
* @param {Number[]} [arguments...] Matrix elements. See {@link me.Matrix2d.set}
*/
me.Matrix2d = me.Object.extend(
/** @scope me.Matrix2d.prototype */ {
/** @ignore */
init : function () {
if (typeof(this.val) === "undefined") {
this.val = new Float32Array(9);
}
if (arguments.length && arguments[0] instanceof me.Matrix2d) {
this.copy(arguments[0]);
}
else if (arguments.length >= 6) {
this.setTransform.apply(this, arguments);
}
else {
this.identity();
}
},
/**
* reset the transformation matrix to the identity matrix (no transformation).
* the identity matrix and parameters position :
*
* @name identity
* @memberOf me.Matrix2d
* @function
* @return {me.Matrix2d} Reference to this object for method chaining
*/
identity : function () {
this.setTransform(
1, 0, 0,
0, 1, 0,
0, 0, 1
);
return this;
},
/**
* set the matrix to the specified value
* @name setTransform
* @memberOf me.Matrix2d
* @function
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @param {Number} e
* @param {Number} f
* @param {Number} [g=0]
* @param {Number} [h=0]
* @param {Number} [i=1]
* @return {me.Matrix2d} Reference to this object for method chaining
*/
setTransform : function () {
var a = this.val;
if (arguments.length === 9) {
a[0] = arguments[0]; // a - m00
a[1] = arguments[1]; // b - m10
a[2] = arguments[2]; // c - m20
a[3] = arguments[3]; // d - m01
a[4] = arguments[4]; // e - m11
a[5] = arguments[5]; // f - m21
a[6] = arguments[6]; // g - m02
a[7] = arguments[7]; // h - m12
a[8] = arguments[8]; // i - m22
} else if (arguments.length === 6) {
a[0] = arguments[0]; // a
a[1] = arguments[2]; // c
a[2] = arguments[4]; // e
a[3] = arguments[1]; // b
a[4] = arguments[3]; // d
a[5] = arguments[5]; // f
a[6] = 0; // g
a[7] = 0; // h
a[8] = 1; // i
}
return this;
},
/**
* Copies over the values from another me.Matrix2d.
* @name copy
* @memberOf me.Matrix2d
* @function
* @param {me.Matrix2d} m the matrix object to copy from
* @return {me.Matrix2d} Reference to this object for method chaining
*/
copy : function (b) {
this.val.set(b.val);
return this;
},
/**
* multiply both matrix
* @name multiply
* @memberOf me.Matrix2d
* @function
* @param {me.Matrix2d} b Other matrix
* @return {me.Matrix2d} Reference to this object for method chaining
*/
multiply : function (b) {
b = b.val;
var a = this.val,
a0 = a[0],
a1 = a[1],
a3 = a[3],
a4 = a[4],
b0 = b[0],
b1 = b[1],
b3 = b[3],
b4 = b[4],
b6 = b[6],
b7 = b[7];
a[0] = a0 * b0 + a3 * b1;
a[1] = a1 * b0 + a4 * b1;
a[3] = a0 * b3 + a3 * b4;
a[4] = a1 * b3 + a4 * b4;
a[6] += a0 * b6 + a3 * b7;
a[7] += a1 * b6 + a4 * b7;
return this;
},
/**
* Transpose the value of this matrix.
* @name transpose
* @memberOf me.Matrix2d
* @function
* @return {me.Matrix2d} Reference to this object for method chaining
*/
transpose : function () {
var tmp, a = this.val;
tmp = a[1];
a[1] = a[3];
a[3] = tmp;
tmp = a[2];
a[2] = a[6];
a[6] = tmp;
tmp = a[5];
a[5] = a[7];
a[7] = tmp;
return this;
},
/**
* invert this matrix, causing it to apply the opposite transformation.
* @name invert
* @memberOf me.Matrix2d
* @function
* @return {me.Matrix2d} Reference to this object for method chaining
*/
invert : function () {
var val = this.val;
var a = val[ 0 ], b = val[ 1 ], c = val[ 2 ],
d = val[ 3 ], e = val[ 4 ], f = val[ 5 ],
g = val[ 6 ], h = val[ 7 ], i = val[ 8 ];
var ta = i * e - f * h,
td = f * g - i * d,
tg = h * d - e * g;
var n = a * ta + b * td + c * tg;
val[ 0 ] = ta / n;
val[ 1 ] = ( c * h - i * b ) / n;
val[ 2 ] = ( f * b - c * e ) / n;
val[ 3 ] = td / n;
val[ 4 ] = ( i * a - c * g ) / n;
val[ 5 ] = ( c * d - f * a ) / n;
val[ 6 ] = tg / n;
val[ 7 ] = ( b * g - h * a ) / n;
val[ 8 ] = ( e * a - b * d ) / n;
return this;
},
/**
* Transforms the given vector according to this matrix.
* @name multiplyVector
* @memberOf me.Matrix2d
* @function
* @param {me.Vector2d} vector the vector object to be transformed
* @return {me.Vector2d} result vector object. Useful for chaining method calls.
*/
multiplyVector : function (v) {
var a = this.val,
x = v.x,
y = v.y;
v.x = x * a[0] + y * a[3] + a[6];
v.y = x * a[1] + y * a[4] + a[7];
return v;
},
/**
* Transforms the given vector using the inverted current matrix.
* @name multiplyVector
* @memberOf me.Matrix2d
* @function
* @param {me.Vector2d} vector the vector object to be transformed
* @return {me.Vector2d} result vector object. Useful for chaining method calls.
*/
multiplyVectorInverse : function (v) {
var a = this.val,
x = v.x,
y = v.y;
var invD = 1 / ((a[0] * a[4]) + (a[3] * -a[1]));
v.x = (a[4] * invD * x) + (-a[3] * invD * y) + (((a[7] * a[3]) - (a[6] * a[4])) * invD);
v.y = (a[0] * invD * y) + (-a[1] * invD * x) + (((-a[7] * a[0]) + (a[6] * a[1])) * invD);
return v;
},
/**
* scale the matrix
* @name scale
* @memberOf me.Matrix2d
* @function
* @param {Number} x a number representing the abscissa of the scaling vector.
* @param {Number} [y=x] a number representing the ordinate of the scaling vector.
* @return {me.Matrix2d} Reference to this object for method chaining
*/
scale : function (x, y) {
var a = this.val,
_x = x,
_y = typeof(y) === "undefined" ? _x : y;
a[0] *= _x;
a[1] *= _x;
a[3] *= _y;
a[4] *= _y;
return this;
},
/**
* adds a 2D scaling transformation.
* @name scaleV
* @memberOf me.Matrix2d
* @function
* @param {me.Vector2d} vector scaling vector
* @return {me.Matrix2d} Reference to this object for method chaining
*/
scaleV : function (v) {
return this.scale(v.x, v.y);
},
/**
* specifies a 2D scale operation using the [sx, 1] scaling vector
* @name scaleX
* @memberOf me.Matrix2d
* @function
* @param {Number} x x scaling vector
* @return {me.Matrix2d} Reference to this object for method chaining
*/
scaleX : function (x) {
return this.scale(x, 1);
},
/**
* specifies a 2D scale operation using the [1,sy] scaling vector
* @name scaleY
* @memberOf me.Matrix2d
* @function
* @param {Number} y y scaling vector
* @return {me.Matrix2d} Reference to this object for method chaining
*/
scaleY : function (y) {
return this.scale(1, y);
},
/**
* rotate the matrix (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.Matrix2d
* @function
* @param {Number} angle Rotation angle in radians.
* @return {me.Matrix2d} Reference to this object for method chaining
*/
rotate : function (angle) {
if (angle !== 0) {
var a = this.val,
a0 = a[0],
a1 = a[1],
a3 = a[3],
a4 = a[4],
s = Math.sin(angle),
c = Math.cos(angle);
a[0] = a0 * c + a3 * s;
a[1] = a1 * c + a4 * s;
a[3] = a0 * -s + a3 * c;
a[4] = a1 * -s + a4 * c;
}
return this;
},
/**
* translate the matrix position on the horizontal and vertical axis
* @name translate
* @memberOf me.Matrix2d
* @function
* @param {Number} x the x coordindates to translate the matrix by
* @param {Number} y the y coordindates to translate the matrix by
* @return {me.Matrix2d} Reference to this object for method chaining
*/
translate : function (x, y) {
var a = this.val;
a[6] += a[0] * x + a[3] * y;
a[7] += a[1] * x + a[4] * y;
return this;
},
/**
* translate the matrix by a vector on the horizontal and vertical axis
* @name translateV
* @memberOf me.Matrix2d
* @function
* @param {me.Vector2d} v the vector to translate the matrix by
* @return {me.Matrix2d} Reference to this object for method chaining
*/
translateV : function (v) {
return this.translate(v.x, v.y);
},
/**
* returns true if the matrix is an identity matrix.
* @name isIdentity
* @memberOf me.Matrix2d
* @function
* @return {Boolean}
**/
isIdentity : function () {
var a = this.val;
return (
a[0] === 1 &&
a[1] === 0 &&
a[2] === 0 &&
a[3] === 0 &&
a[4] === 1 &&
a[5] === 0 &&
a[6] === 0 &&
a[7] === 0 &&
a[8] === 1
);
},
/**
* Clone the Matrix
* @name clone
* @memberOf me.Matrix2d
* @function
* @return {me.Matrix2d}
*/
clone : function () {
return me.pool.pull("me.Matrix2d", this);
},
/**
* convert the object to a string representation
* @name toString
* @memberOf me.Matrix2d
* @function
* @return {String}
*/
toString : function () {
var a = this.val;
return "me.Matrix2d(" +
a[0] + ", " + a[1] + ", " + a[2] + ", " +
a[3] + ", " + a[4] + ", " + a[5] + ", " +
a[6] + ", " + a[7] + ", " + a[8] +
")";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* an ellipse Object
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Number} x the center x coordinate of the ellipse
* @param {Number} y the center y coordinate of the ellipse
* @param {Number} w width (diameter) of the ellipse
* @param {Number} h height (diameter) of the ellipse
*/
me.Ellipse = me.Object.extend(
{
/** @scope me.Ellipse.prototype */
/** @ignore */
init : function (x, y, w, h) {
/**
* the center coordinates of the ellipse
* @public
* @type {me.Vector2d}
* @name pos
* @memberOf me.Ellipse
*/
this.pos = new me.Vector2d();
/**
* The bounding rectangle for this shape
* @private
* @type {me.Rect}
* @name _bounds
* @memberOf me.Ellipse
*/
this._bounds = undefined;
/**
* Maximum radius of the ellipse
* @public
* @type {Number}
* @name radius
* @memberOf me.Ellipse
*/
this.radius = NaN;
/**
* Pre-scaled radius vector for ellipse
* @public
* @type {me.Vector2d}
* @name radiusV
* @memberOf me.Ellipse
*/
this.radiusV = new me.Vector2d();
/**
* Radius squared, for pythagorean theorom
* @public
* @type {me.Vector2d}
* @name radiusSq
* @memberOf me.Ellipse
*/
this.radiusSq = new me.Vector2d();
/**
* x/y scaling ratio for ellipse
* @public
* @type {me.Vector2d}
* @name ratio
* @memberOf me.Ellipse
*/
this.ratio = new me.Vector2d();
// the shape type
this.shapeType = "Ellipse";
this.setShape(x, y, w, h);
},
/**
* set new value to the Ellipse shape
* @name setShape
* @memberOf me.Ellipse
* @function
* @param {Number} x position of the ellipse
* @param {Number} y position of the ellipse
* @param {Number} w width (diameter) of the ellipse
* @param {Number} h height (diameter) of the ellipse
*/
setShape : function (x, y, w, h) {
var hW = w / 2;
var hH = h / 2;
this.pos.set(x, y);
this.radius = Math.max(hW, hH);
this.ratio.set(hW / this.radius, hH / this.radius);
this.radiusV.set(this.radius, this.radius).scaleV(this.ratio);
var r = this.radius * this.radius;
this.radiusSq.set(r, r).scaleV(this.ratio);
this.updateBounds();
return this;
},
/**
* Rotate this Ellipse (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.Ellipse
* @function
* @param {Number} angle The angle to rotate (in radians)
* @return {me.Ellipse} Reference to this object for method chaining
*/
rotate : function (/*angle*/) {
// TODO
return this;
},
/**
* Scale this Ellipse by the specified scalar.
* @name scale
* @memberOf me.Ellipse
* @function
* @param {Number} x
* @param {Number} [y=x]
* @return {me.Ellipse} Reference to this object for method chaining
*/
scale : function (x, y) {
y = typeof (y) !== "undefined" ? y : x;
return this.setShape(
this.pos.x,
this.pos.y,
this.radiusV.x * 2 * x,
this.radiusV.y * 2 * y
);
},
/**
* Scale this Ellipse by the specified vector.
* @name scale
* @memberOf me.Ellipse
* @function
* @param {me.Vector2d} v
* @return {me.Ellipse} Reference to this object for method chaining
*/
scaleV : function (v) {
return this.scale(v.x, v.y);
},
/**
* apply the given transformation matrix to this ellipse
* @name transform
* @memberOf me.Ellipse
* @function
* @param {me.Matrix2d} matrix the transformation matrix
* @return {me.Polygon} Reference to this object for method chaining
*/
transform : function (/* m */) {
// TODO
return this;
},
/**
* translate the circle/ellipse by the specified offset
* @name translate
* @memberOf me.Ellipse
* @function
* @param {Number} x x offset
* @param {Number} y y offset
* @return {me.Ellipse} this ellipse
*/
translate : function (x, y) {
this.pos.x += x;
this.pos.y += y;
this._bounds.translate(x, y);
return this;
},
/**
* translate the circle/ellipse by the specified vector
* @name translateV
* @memberOf me.Ellipse
* @function
* @param {me.Vector2d} v vector offset
* @return {me.Rect} this ellipse
*/
translateV : function (v) {
this.pos.add(v);
this._bounds.translateV(v);
return this;
},
/**
* check if this circle/ellipse contains the specified point
* @name containsPointV
* @memberOf me.Ellipse
* @function
* @param {me.Vector2d} point
* @return {boolean} true if contains
*/
containsPointV: function (v) {
return this.containsPoint(v.x, v.y);
},
/**
* check if this circle/ellipse contains the specified point
* @name containsPoint
* @memberOf me.Ellipse
* @function
* @param {Number} x x coordinate
* @param {Number} y y coordinate
* @return {boolean} true if contains
*/
containsPoint: function (x, y) {
// Make position relative to object center point.
x -= this.pos.x;
y -= this.pos.y;
// Pythagorean theorem.
return (
((x * x) / this.radiusSq.x) +
((y * y) / this.radiusSq.y)
) <= 1.0;
},
/**
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape.
* @name getBounds
* @memberOf me.Ellipse
* @function
* @return {me.Rect} this shape bounding box Rectangle object
*/
getBounds : function () {
return this._bounds;
},
/**
* update the bounding box for this shape.
* @name updateBounds
* @memberOf me.Ellipse
* @function
* @return {me.Rect} this shape bounding box Rectangle object
*/
updateBounds : function () {
var rx = this.radiusV.x,
ry = this.radiusV.y,
x = this.pos.x - rx,
y = this.pos.y - ry,
w = rx * 2,
h = ry * 2;
if (!this._bounds) {
this._bounds = new me.Rect(x, y, w, h);
} else {
this._bounds.setShape(x, y, w, h);
}
return this._bounds;
},
/**
* clone this Ellipse
* @name clone
* @memberOf me.Ellipse
* @function
* @return {me.Ellipse} new Ellipse
*/
clone : function () {
return new me.Ellipse(
this.pos.x,
this.pos.y,
this.radiusV.x * 2,
this.radiusV.y * 2
);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a polygon Object.
* Please do note that melonJS implements a simple Axis-Aligned Boxes collision algorithm, which requires all polygons used for collision to be convex with all vertices defined with clockwise winding.
* A polygon is convex when all line segments connecting two points in the interior do not cross any edge of the polygon
* (which means that all angles are less than 180 degrees), as described here below :
*
* | (0) | * (-1) [S]--------------[E] (1) * | (0) | ** * @ignore * @param {Vector} line The line segment. * @param {Vector} point The point. * @return {number} LEFT_VORNOI_REGION (-1) if it is the left region, * MIDDLE_VORNOI_REGION (0) if it is the middle region, * RIGHT_VORNOI_REGION (1) if it is the right region. */ function vornoiRegion(line, point) { var len2 = line.length2(); var dp = point.dotProduct(line); if (dp < 0) { // If the point is beyond the start of the line, it is in the // left vornoi region. return LEFT_VORNOI_REGION; } else if (dp > len2) { // If the point is beyond the end of the line, it is in the // right vornoi region. return RIGHT_VORNOI_REGION; } else { // Otherwise, it's in the middle one. return MIDDLE_VORNOI_REGION; } } /** * A singleton for managing collision detection (and projection-based collision response) of 2D shapes.
* screen is filled with the specified color and slowly goes back to normal * @name fadeOut * @memberOf me.Viewport * @function * @param {me.Color|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 = me.pool.pull("me.Color").copy(color); this._fadeOut.tween = me.pool.pull("me.Tween", this._fadeOut.color) .to({ alpha: 0.0 }, duration || 1000) .onComplete(onComplete || null); this._fadeOut.tween.isPersistent = true; this._fadeOut.tween.start(); }, /** * fadeIn effect
* fade to the specified color
* @name fadeIn
* @memberOf me.Viewport
* @function
* @param {me.Color|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 = me.pool.pull("me.Color").copy(color);
var _alpha = this._fadeIn.color.alpha;
this._fadeIn.color.alpha = 0.0;
this._fadeIn.tween = me.pool.pull("me.Tween", this._fadeIn.color)
.to({ alpha: _alpha }, duration || 1000)
.onComplete(onComplete || null);
this._fadeIn.tween.isPersistent = true;
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 position around the specified object
* @name focusOn
* @memberOf me.Viewport
* @function
* @param {me.Renderable}
*/
focusOn : function (target) {
var bounds = target.getBounds();
this.moveTo(
target.pos.x + bounds.pos.x + (bounds.width / 2),
target.pos.y + bounds.pos.y + (bounds.height / 2)
);
},
/**
* 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
* @param {Number} [v] an optional vector object where to set the
* converted value
* @return {me.Vector2d}
*/
localToWorld : function (x, y, v) {
// TODO memoization for one set of coords (multitouch)
v = v || new me.Vector2d();
v.set(x, y).add(this.pos).sub(me.game.world.pos);
if (!this.currentTransform.isIdentity()) {
this.currentTransform.multiplyVectorInverse(v);
}
return v;
},
/**
* convert the given world coordinates into "local" (screen) coordinates
* @name worldToLocal
* @memberOf me.Viewport
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} [v] an optional vector object where to set the
* converted value
* @return {me.Vector2d}
*/
worldToLocal : function (x, y, v) {
// TODO memoization for one set of coords (multitouch)
v = v || new me.Vector2d();
v.set(x, y)
if (!this.currentTransform.isIdentity()) {
this.currentTransform.multiplyVector(v);
}
return v.sub(this.pos).add(me.game.world.pos);
},
/**
* render the camera effects
* @ignore
*/
draw : function (renderer) {
// fading effect
if (this._fadeIn.tween) {
renderer.clearColor(this._fadeIn.color);
// remove the tween if over
if (this._fadeIn.color.alpha === 1.0) {
this._fadeIn.tween = null;
me.pool.push(this._fadeIn.color);
this._fadeIn.color = null;
}
}
// flashing effect
if (this._fadeOut.tween) {
renderer.clearColor(this._fadeOut.color);
// remove the tween if over
if (this._fadeOut.color.alpha === 0.0) {
this._fadeOut.tween = null;
me.pool.push(this._fadeOut.color);
this._fadeOut.color = null;
}
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* GUI Object
* A very basic object to manage GUI elements
* The object simply register on the "pointerdown"
* or "touchstart" event and call the onClick function"
* @class
* @extends me.Sprite
* @memberOf me
* @constructor
* @param {Number} x the x coordinate of the GUI Object
* @param {Number} y the y coordinate of the GUI Object
* @param {Object} settings See {@link me.Entity}
* @example
*
* // create a basic GUI Object
* var myButton = me.GUI_Object.extend(
* {
* init:function (x, y)
* {
* var settings = {}
* settings.image = "button";
* settings.framewidth = 100;
* settings.frameheight = 50;
* // super constructor
* me.GUI_Object.prototype.init.apply(this, [x, y, settings]);
* // define the object z order
* this.pos.z = 4;
* },
*
* // 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)
* me.game.world.addChild(new myButton(10,10));
*
*/
me.GUI_Object = me.Sprite.extend({
/** @scope me.GUI_Object.prototype */
/**
* @ignore
*/
init : function (x, y, settings) {
/**
* object can be clicked or not
* @public
* @type boolean
* @default true
* @name me.GUI_Object#isClickable
*/
this.isClickable = true;
/**
* Tap and hold threshold timeout in ms
* @type {number}
* @default 250
* @name me.GUI_Object#holdThreshold
*/
this.holdThreshold = 250;
/**
* object can be tap and hold
* @public
* @type boolean
* @default false
* @name me.GUI_Object#isHoldable
*/
this.isHoldable = false;
/**
* true if the pointer is over the object
* @public
* @type boolean
* @default false
* @name me.GUI_Object#hover
*/
this.hover = false;
// object has been updated (clicked,etc..)
this.holdTimeout = null;
this.updated = false;
this.released = true;
// call the parent constructor
me.Sprite.prototype.init.apply(this, [ x, y, settings ]);
// GUI items use screen coordinates
this.floating = true;
// enable event detection
this.isKinematic = false;
},
/**
* return true if the object has been clicked
* @ignore
*/
update : function () {
if (this.updated) {
// clear the flag
if (!this.released) {
this.updated = false;
}
return true;
}
return false;
},
/**
* function callback for the pointerdown event
* @ignore
*/
clicked : function (event) {
// Check if left mouse button is pressed
if (event.button === 0 && this.isClickable) {
this.updated = true;
this.released = false;
if (this.isHoldable) {
if (this.holdTimeout !== null) {
me.timer.clearTimeout(this.holdTimeout);
}
this.holdTimeout = me.timer.setTimeout(this.hold.bind(this), this.holdThreshold, false);
this.released = false;
}
return this.onClick(event);
}
},
/**
* function called when the object is pressed
* to be extended
* 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;
},
/**
* function callback for the pointerEnter event
* @ignore
*/
enter : function (event) {
this.hover = true;
return this.onOver(event);
},
/**
* function called when the pointer is over the object
* @name onOver
* @memberOf me.GUI_Object
* @public
* @function
* @param {Event} event the event object
*/
onOver : function (/* event */) {},
/**
* function callback for the pointerLeave event
* @ignore
*/
leave : function (event) {
this.hover = false;
this.release.call(this, event);
return this.onOut(event);
},
/**
* function called when the pointer is leaving the object area
* @name onOut
* @memberOf me.GUI_Object
* @public
* @function
* @param {Event} event the event object
*/
onOut : function (/* event */) {},
/**
* function callback for the pointerup event
* @ignore
*/
release : function (event) {
if (this.released === false) {
this.released = true;
me.timer.clearTimeout(this.holdTimeout);
return this.onRelease(event);
}
},
/**
* function called when the object is pressed and released
* to be extended
* return false if we need to stop propagating the event
* @name onRelease
* @memberOf me.GUI_Object
* @public
* @function
* @param {Event} event the event object
*/
onRelease : function () {
return false;
},
/**
* function callback for the tap and hold timer event
* @ignore
*/
hold : function () {
me.timer.clearTimeout(this.holdTimeout);
if (!this.released) {
this.onHold();
}
},
/**
* function called when the object is pressed and held
* to be extended
* @name onHold
* @memberOf me.GUI_Object
* @public
* @function
*/
onHold : function () {},
/**
* function called when added to the game world or a container
* @ignore
*/
onActivateEvent : function () {
// register pointer events
me.input.registerPointerEvent("pointerdown", this, this.clicked.bind(this));
me.input.registerPointerEvent("pointerup", this, this.release.bind(this));
me.input.registerPointerEvent("pointercancel", this, this.release.bind(this));
me.input.registerPointerEvent("pointerenter", this, this.enter.bind(this));
me.input.registerPointerEvent("pointerleave", this, this.leave.bind(this));
},
/**
* function called when removed from the game world or a container
* @ignore
*/
onDeactivateEvent : function () {
// release pointer events
me.input.releasePointerEvent("pointerdown", this);
me.input.releasePointerEvent("pointerup", this);
me.input.releasePointerEvent("pointercancel", this);
me.input.releasePointerEvent("pointerenter", this);
me.input.releasePointerEvent("pointerleave", this);
me.timer.clearTimeout(this.holdTimeout);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* Private function to re-use for object removal in a defer
* @ignore
*/
var deferredRemove = function (child, keepalive) {
this.removeChildNow(child, keepalive);
};
var globalFloatingCounter = 0;
/**
* me.Container 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.Container = me.Renderable.extend(
/** @scope me.Container.prototype */
{
/**
* constructor
* @ignore
*/
init : function (x, y, width, height) {
/**
* keep track of pending sort
* @ignore
*/
this.pendingSort = null;
/**
* whether the container is the root of the scene
* @private
* @ignore
*/
this._root = false;
// call the _super constructor
me.Renderable.prototype.init.apply(this,
[x || 0, y || 0,
width || Infinity,
height || Infinity]
);
/**
* The array of children of this container.
* @ignore
*/
this.children = [];
/**
* The property of the child object that should be used to sort on
* value : "x", "y", "z"
* @public
* @type String
* @default me.game.sortOn
* @name sortOn
* @memberOf me.Container
*/
this.sortOn = me.game.sortOn;
/**
* Specify if the children list should be automatically sorted when adding a new child
* @public
* @type Boolean
* @default true
* @name autoSort
* @memberOf me.Container
*/
this.autoSort = true;
/**
* Specify if the children z index should automatically be managed by the parent container
* @public
* @type Boolean
* @default true
* @name autoDepth
* @memberOf me.Container
*/
this.autoDepth = true;
/**
* a callback to be extended, triggered when a child is added or removed
* @name onChildChange
* @memberOf me.Container
* @function
* @param {Number} index added or removed child index
*/
this.onChildChange = function (/* index */) {
// to be extended
};
/**
* Used by the debug panel plugin
* @ignore
*/
this.drawCount = 0;
/**
* The bounds that contains all its children
* @public
* @type me.Rect
* @name childBounds
* @memberOf me.Container
*/
this.childBounds = this.getBounds().clone();
// container self apply any defined transformation
this.autoTransform = true;
// enable collision and event detection
this.isKinematic = false;
},
/**
* Add a child to the container
* if auto-sort is disable, the object will be appended at the bottom of the list
* @name addChild
* @memberOf me.Container
* @function
* @param {me.Renderable} child
* @param {number} [z] forces the z index of the child to the specified value
* @return {me.Renderable} the added child
*/
addChild : function (child, z) {
if (child.ancestor instanceof me.Container) {
child.ancestor.removeChildNow(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 (use child.id as based index if defined)
child.GUID = me.utils.createGUID(child.id);
}
}
child.ancestor = this;
this.children.push(child);
// set the child z value if required
if (typeof(child.pos) !== "undefined") {
if (typeof(z) === "number") {
child.pos.z = z;
} else if (this.autoDepth === true) {
child.pos.z = this.children.length;
}
}
if (this.autoSort === true) {
this.sort();
}
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
child.onActivateEvent();
}
this.onChildChange.call(this, this.children.length - 1);
return child;
},
/**
* Add a child to the container at the specified index
* (the list won't be sorted after insertion)
* @name addChildAt
* @memberOf me.Container
* @function
* @param {me.Renderable} child
* @param {Number} index
* @return {me.Renderable} the added child
*/
addChildAt : function (child, index) {
if (index >= 0 && index < this.children.length) {
if (child.ancestor instanceof me.Container) {
child.ancestor.removeChildNow(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);
if (typeof child.onActivateEvent === "function" && this.isAttachedToRoot()) {
child.onActivateEvent();
}
this.onChildChange.call(this, index);
return child;
}
else {
throw new me.Container.Error("Index (" + index + ") Out Of Bounds for addChildAt()");
}
},
/**
* The forEach() method executes a provided function once per child element.
* callback is invoked with three arguments:
* - the element value
* - the element index
* - the array being traversed
* @name forEach
* @memberOf me.Container
* @function
* @param {Function} callback
* @param {Object} [thisArg] value to use as this(i.e reference Object) when executing callback.
* @example
* // iterate through all children of the root container
* me.game.world.forEach(function (child) {
* // do something with the child
* });
*/
forEach : function (callback, thisArg) {
var context = this, i = 0;
var len = this.children.length;
if (typeof callback !== "function") {
throw new me.Container.Error(callback + " is not a function");
}
if (arguments.length > 1) {
context = thisArg;
}
while (i < len) {
callback.call(context, this.children[i], i, this.children);
i++;
}
},
/**
* Swaps the position (z-index) of 2 children
* @name swapChildren
* @memberOf me.Container
* @function
* @param {me.Renderable} child
* @param {me.Renderable} child2
*/
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.pos.z;
child.pos.z = child2.pos.z;
child2.pos.z = _z;
// swap the positions..
this.children[index] = child2;
this.children[index2] = child;
}
else {
throw new me.Container.Error(child + " Both the supplied childs must be a child of the caller " + this);
}
},
/**
* Returns the Child at the specified index
* @name getChildAt
* @memberOf me.Container
* @function
* @param {Number} index
*/
getChildAt : function (index) {
if (index >= 0 && index < this.children.length) {
return this.children[index];
}
else {
throw new me.Container.Error("Index (" + index + ") Out Of Bounds for getChildAt()");
}
},
/**
* Returns the index of the Child
* @name getChildAt
* @memberOf me.Container
* @function
* @param {me.Renderable} child
*/
getChildIndex : function (child) {
return this.children.indexOf(child);
},
/**
* Returns true if contains the specified Child
* @name hasChild
* @memberOf me.Container
* @function
* @param {me.Renderable} child
* @return {Boolean}
*/
hasChild : function (child) {
return this === child.ancestor;
},
/**
* return the child corresponding to the given property and value.
* note : avoid calling this function every frame since
* it parses the whole object tree each time
* @name getChildByProp
* @memberOf me.Container
* @public
* @function
* @param {String} prop Property name
* @param {String|RegExp|Number|Boolean} value Value of the property
* @return {me.Renderable[]} Array of childs
* @example
* // get the first child object called "mainPlayer" in a specific container :
* var ent = myContainer.getChildByProp("name", "mainPlayer");
*
* // or query the whole world :
* var ent = me.game.world.getChildByProp("name", "mainPlayer");
*
* // partial property matches are also allowed by using a RegExp.
* // the following matches "redCOIN", "bluecoin", "bagOfCoins", etc :
* var allCoins = me.game.world.getChildByProp("name", /coin/i);
*
* // searching for numbers or other data types :
* var zIndex10 = me.game.world.getChildByProp("z", 10);
* var inViewport = me.game.world.getChildByProp("inViewport", true);
*/
getChildByProp : function (prop, value) {
var objList = [];
function compare(obj, prop) {
var v = obj[prop];
if (value instanceof RegExp && typeof(v) === "string") {
if (value.test(v)) {
objList.push(obj);
}
}
else if (v === value) {
objList.push(obj);
}
}
for (var i = this.children.length - 1; i >= 0; i--) {
var obj = this.children[i];
compare(obj, prop);
if (obj instanceof me.Container) {
objList = objList.concat(obj.getChildByProp(prop, value));
}
}
return objList;
},
/**
* returns the list of childs with the specified class type
* @name getChildByType
* @memberOf me.Container
* @public
* @function
* @param {Object} class type
* @return {me.Renderable[]} Array of children
*/
getChildByType : function (_class) {
var objList = [];
for (var i = this.children.length - 1; i >= 0; i--) {
var obj = this.children[i];
if (obj instanceof _class) {
objList.push(obj);
}
if (obj instanceof me.Container) {
objList = objList.concat(obj.getChildByType(_class));
}
}
return objList;
},
/**
* returns the list of childs with the specified name
* as defined in Tiled (Name field of the Object Properties)
* note : avoid calling this function every frame since
* it parses the whole object list each time
* @name getChildByName
* @memberOf me.Container
* @public
* @function
* @param {String|RegExp|Number|Boolean} name entity name
* @return {me.Renderable[]} Array of children
*/
getChildByName : function (name) {
return this.getChildByProp("name", name);
},
/**
* return the child corresponding to the specified GUID
* note : avoid calling this function every frame since
* it parses the whole object list each time
* @name getChildByGUID
* @memberOf me.Container
* @public
* @function
* @param {String|RegExp|Number|Boolean} GUID entity GUID
* @return {me.Renderable} corresponding child or null
*/
getChildByGUID : function (guid) {
var obj = this.getChildByProp("GUID", guid);
return (obj.length > 0) ? obj[0] : null;
},
/**
* resizes the child bounds rectangle, based on children bounds.
* @name updateChildBounds
* @memberOf me.Container
* @function
* @return {me.Rect} updated child bounds
*/
updateChildBounds : function () {
this.childBounds.pos.set(Infinity, Infinity);
this.childBounds.resize(-Infinity, -Infinity);
var childBounds;
for (var i = this.children.length, child; i--, (child = this.children[i]);) {
if (child.isRenderable) {
if (child instanceof me.Container) {
childBounds = child.childBounds;
}
else {
childBounds = child.getBounds();
}
// TODO : returns an "empty" rect instead of null (e.g. EntityObject)
// TODO : getBounds should always return something anyway
if (childBounds !== null) {
this.childBounds.union(childBounds);
}
}
}
return this.childBounds;
},
/**
* Checks if this container is root or if ti's attached to the root container.
* @private
* @name isAttachedToRoot
* @memberOf me.Container
* @function
* @returns Boolean
*/
isAttachedToRoot : function () {
if (this._root) {
return true;
} else {
var ancestor = this.ancestor;
while (ancestor) {
if (ancestor._root === true) {
return true;
}
ancestor = ancestor.ancestor;
}
return false;
}
},
/**
* update the renderable's bounding rect (private)
* @private
* @name updateBoundsPos
* @memberOf me.Container
* @function
*/
updateBoundsPos : function (newX, newY) {
me.Renderable.prototype.updateBoundsPos.apply(this, [ newX, newY ]);
// Update container's absolute position
this._absPos.set(newX, newY);
if (this.ancestor) {
this._absPos.add(this.ancestor._absPos);
}
// Notify children that the parent's position has changed
for (var i = this.children.length, child; i--, (child = this.children[i]);) {
if (child.isRenderable) {
child.updateBoundsPos(child.pos.x, child.pos.y);
}
}
return this.getBounds();
},
/**
* @ignore
*/
onActivateEvent : function () {
for (var i = this.children.length, child; i--, (child = this.children[i]);) {
if (typeof child.onActivateEvent === "function") {
child.onActivateEvent();
}
}
},
/**
* Invokes the removeChildNow in a defer, to ensure the child is removed safely after the update & draw stack has completed
* @name removeChild
* @memberOf me.Container
* @public
* @function
* @param {me.Renderable} child
* @param {Boolean} [keepalive=False] True to prevent calling child.destroy()
*/
removeChild : function (child, keepalive) {
if (this.hasChild(child)) {
deferredRemove.defer(this, child, keepalive);
}
else {
throw new me.Container.Error("Child is not mine.");
}
},
/**
* Removes (and optionally destroys) a child from the container.
* (removal is immediate and unconditional)
* Never use keepalive=true with objects from {@link me.pool}. Doing so will create a memory leak.
* @name removeChildNow
* @memberOf me.Container
* @function
* @param {me.Renderable} child
* @param {Boolean} [keepalive=False] True to prevent calling child.destroy()
*/
removeChildNow : function (child, keepalive) {
if (this.hasChild(child) && (this.getChildIndex(child) >= 0)) {
if (typeof child.onDeactivateEvent === "function") {
child.onDeactivateEvent();
}
if (!keepalive) {
if (typeof (child.destroy) === "function") {
child.destroy();
}
me.pool.push(child);
}
// Don't cache the child index; another element might have been removed
// by the child's `onDeactivateEvent` or `destroy` methods
var childIndex = this.getChildIndex(child);
if (childIndex >= 0) {
this.children.splice(childIndex, 1);
child.ancestor = undefined;
}
this.onChildChange.call(this, childIndex);
}
},
/**
* Automatically set the specified property of all childs to the given value
* @name setChildsProperty
* @memberOf me.Container
* @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; i >= 0; i--) {
var obj = this.children[i];
if ((recursive === true) && (obj instanceof me.Container)) {
obj.setChildsProperty(prop, val, recursive);
}
obj[prop] = val;
}
},
/**
* Move the child in the group one step forward (z depth).
* @name moveUp
* @memberOf me.Container
* @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.Container
* @function
* @param {me.Renderable} child
*/
moveDown : function (child) {
var childIndex = this.getChildIndex(child);
if (childIndex >= 0 && (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.Container
* @function
* @param {me.Renderable} child
*/
moveToTop : function (child) {
var childIndex = this.getChildIndex(child);
if (childIndex > 0) {
// note : we use an inverted loop
this.children.splice(0, 0, this.children.splice(childIndex, 1)[0]);
// increment our child z value based on the previous child depth
child.pos.z = this.children[1].pos.z + 1;
}
},
/**
* Move the specified child the bottom (z depth).
* @name moveToBottom
* @memberOf me.Container
* @function
* @param {me.Renderable} child
*/
moveToBottom : function (child) {
var childIndex = this.getChildIndex(child);
if (childIndex >= 0 && childIndex < (this.children.length - 1)) {
// note : we use an inverted loop
this.children.splice((this.children.length - 1), 0, this.children.splice(childIndex, 1)[0]);
// increment our child z value based on the next child depth
child.pos.z = this.children[(this.children.length - 2)].pos.z - 1;
}
},
/**
* Manually trigger the sort of all the childs in the container
* There is no constructor function for me.state.
* @namespace me.state
* @memberOf me
*/
me.state = (function () {
// hold public stuff in our singleton
var api = {};
/*-------------------------------------------
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;
// store the elapsed time during pause/stop period
var _pauseTime = 0;
/**
* @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
* @param {Number} time current timestamp in milliseconds
* @ignore
*/
function _renderFrame(time) {
// update all game objects
me.game.update(time);
// render all game objects
me.game.draw();
// schedule the next frame update
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]) {
// 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);
// 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 ID for Loading Screen
* @constant
* @name LOADING
* @memberOf me.state
*/
api.LOADING = 0;
/**
* default state ID for Menu Screen
* @constant
* @name MENU
* @memberOf me.state
*/
api.MENU = 1;
/**
* default state ID for "Ready" Screen
* @constant
* @name READY
* @memberOf me.state
*/
api.READY = 2;
/**
* default state ID for Play Screen
* @constant
* @name PLAY
* @memberOf me.state
*/
api.PLAY = 3;
/**
* default state ID for Game Over Screen
* @constant
* @name GAMEOVER
* @memberOf me.state
*/
api.GAMEOVER = 4;
/**
* default state ID for Game End Screen
* @constant
* @name GAME_END
* @memberOf me.state
*/
api.GAME_END = 5;
/**
* default state ID for High Score Screen
* @constant
* @name SCORE
* @memberOf me.state
*/
api.SCORE = 6;
/**
* default state ID for Credits Screen
* @constant
* @name CREDITS
* @memberOf me.state
*/
api.CREDITS = 7;
/**
* default state ID for Settings Screen
* @constant
* @name SETTINGS
* @memberOf me.state
*/
api.SETTINGS = 8;
/**
* default state ID for user defined constants
* Super simple, fast and easy to use tweening engine which incorporates optimised Robert Penner's equation
* https://github.com/sole/Tween.js
* author sole / http://soledadpenades.com
* me.Tween.Easing.Linear.None
* me.Tween.Interpolation.Linear
* @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;
*/
api.USER = 100;
/**
* onPause callback
* @function
* @name onPause
* @memberOf me.state
*/
api.onPause = null;
/**
* onResume callback
* @function
* @name onResume
* @memberOf me.state
*/
api.onResume = null;
/**
* onStop callback
* @function
* @name onStop
* @memberOf me.state
*/
api.onStop = null;
/**
* onRestart callback
* @function
* @name onRestart
* @memberOf me.state
*/
api.onRestart = null;
/**
* @ignore
*/
api.init = function () {
// set the embedded loading screen
api.set(api.LOADING, new me.DefaultLoadingScreen());
};
/**
* Stop the current screen object.
* @name stop
* @memberOf me.state
* @public
* @function
* @param {Boolean} pauseTrack pause current track on screen stop.
*/
api.stop = function (music) {
// only stop when we are not loading stuff
if ((_state !== api.LOADING) && api.isRunning()) {
// stop the main loop
_stopRunLoop();
// current music stop
if (music === true) {
me.audio.pauseTrack();
}
// store time when stopped
_pauseTime = window.performance.now();
// publish the stop notification
me.event.publish(me.event.STATE_STOP);
// any callback defined ?
if (typeof(api.onStop) === "function") {
api.onStop();
}
}
};
/**
* pause the current screen object
* @name pause
* @memberOf me.state
* @public
* @function
* @param {Boolean} pauseTrack pause current track on screen pause
*/
api.pause = function (music) {
// only pause when we are not loading stuff
if ((_state !== api.LOADING) && !api.isPaused()) {
// stop the main loop
_pauseRunLoop();
// current music stop
if (music === true) {
me.audio.pauseTrack();
}
// store time when paused
_pauseTime = window.performance.now();
// publish the pause event
me.event.publish(me.event.STATE_PAUSE);
// any callback defined ?
if (typeof(api.onPause) === "function") {
api.onPause();
}
}
};
/**
* Restart the screen object from a full stop.
* @name restart
* @memberOf me.state
* @public
* @function
* @param {Boolean} resumeTrack resume current track on screen resume
*/
api.restart = function (music) {
if (!api.isRunning()) {
// restart the main loop
_startRunLoop();
// current music stop
if (music === true) {
me.audio.resumeTrack();
}
// calculate the elpased time
_pauseTime = window.performance.now() - _pauseTime;
// force repaint
me.game.repaint();
// publish the restart notification
me.event.publish(me.event.STATE_RESTART, [ _pauseTime ]);
// any callback defined ?
if (typeof(api.onRestart) === "function") {
api.onRestart();
}
}
};
/**
* resume the screen object
* @name resume
* @memberOf me.state
* @public
* @function
* @param {Boolean} resumeTrack resume current track on screen resume
*/
api.resume = function (music) {
if (api.isPaused()) {
// resume the main loop
_resumeRunLoop();
// current music stop
if (music === true) {
me.audio.resumeTrack();
}
// calculate the elpased time
_pauseTime = window.performance.now() - _pauseTime;
// publish the resume event
me.event.publish(me.event.STATE_RESUME, [ _pauseTime ]);
// any callback defined ?
if (typeof(api.onResume) === "function") {
api.onResume();
}
}
};
/**
* return the running state of the state manager
* @name isRunning
* @memberOf me.state
* @public
* @function
* @return {Boolean} true if a "process is running"
*/
api.isRunning = function () {
return _animFrameId !== -1;
};
/**
* Return the pause state of the state manager
* @name isPaused
* @memberOf me.state
* @public
* @function
* @return {Boolean} true if the game is paused
*/
api.isPaused = function () {
return _isPaused;
};
/**
* associate the specified state with a screen object
* @name set
* @memberOf me.state
* @public
* @function
* @param {Number} state State ID (see constants)
* @param {me.ScreenObject} so Instantiated ScreenObject to associate
* with state ID
* @example
* var MenuButton = me.GUI_Object.extend({
* "onClick" : function () {
* // Change to the PLAY state when the button is clicked
* me.state.change(me.state.PLAY);
* return true;
* }
* });
*
* var MenuScreen = me.ScreenObject.extend({
* onResetEvent: function() {
* // Load background image
* me.game.world.addChild(
* new me.ImageLayer(0, 0, {
* image : "bg",
* z: 0 // z-index
* }
* );
*
* // Add a button
* me.game.world.addChild(
* new MenuButton(350, 200, { "image" : "start" }),
* 1 // z-index
* );
*
* // Play music
* me.audio.playTrack("menu");
* },
*
* "onDestroyEvent" : function () {
* // Stop music
* me.audio.stopTrack();
* }
* });
*
* me.state.set(me.state.MENU, new MenuScreen());
*/
api.set = function (state, so) {
if (!(so instanceof me.ScreenObject)) {
throw new me.Error(so + " is not an instance of me.ScreenObject");
}
_screenObject[state] = {};
_screenObject[state].screen = so;
_screenObject[state].transition = true;
};
/**
* return a reference to the current screen object
* useful to call a object specific method
* @name current
* @memberOf me.state
* @public
* @function
* @return {me.ScreenObject}
*/
api.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 {me.Color|String} color a CSS color value
* @param {Number} [duration=1000] expressed in milliseconds
*/
api.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
* @param {Number} state State ID (see constants)
* @param {Boolean} enable
*/
api.setTransition = function (state, enable) {
_screenObject[state].transition = enable;
};
/**
* change the game/app state
* @name change
* @memberOf me.state
* @public
* @function
* @param {Number} state State ID (see constants)
* @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);
*/
api.change = function (state) {
// Protect against undefined ScreenObject
if (typeof(_screenObject[state]) === "undefined") {
throw new me.Error("Undefined ScreenObject for state '" + state + "'");
}
if (api.isCurrent(state)) {
// do nothing if already the current state
return;
}
_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(this, state);
}
);
}
// else just switch without any effects
else {
// wait for the last frame to be
// "finished" before switching
_switchState.defer(this, state);
}
};
/**
* return true if the specified state is the current one
* @name isCurrent
* @memberOf me.state
* @public
* @function
* @param {Number} state State ID (see constants)
*/
api.isCurrent = function (state) {
return _state === state;
};
// return our object
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*/
(function () {
// a basic progress bar object
var ProgressBar = me.Renderable.extend({
/**
* @ignore
*/
init: function (x, y, w, h) {
me.Renderable.prototype.init.apply(this, [x, y, w, h]);
// flag to know if we need to refresh the display
this.invalidate = false;
// current progress
this.progress = 0;
this.anchorPoint.set(0, 0);
},
/**
* make sure the screen is refreshed every frame
* @ignore
*/
onProgressUpdate : function (progress) {
this.progress = ~~(progress * this.width);
this.invalidate = true;
},
/**
* @ignore
*/
update : function () {
if (this.invalidate === true) {
// clear the flag
this.invalidate = false;
// and return true
return true;
}
// else return false
return false;
},
/**
* draw function
* @ignore
*/
draw : function (renderer) {
var color = renderer.getColor();
var height = renderer.getHeight();
// draw the progress bar
renderer.setColor("black");
renderer.fillRect(this.pos.x, height / 2, this.width, this.height / 2);
renderer.setColor("#55aa00");
renderer.fillRect(this.pos.x, height / 2, this.progress, this.height / 2);
renderer.setColor(color);
}
});
// the melonJS Logo
var IconLogo = me.Renderable.extend({
/**
* @ignore
*/
init : function (x, y) {
me.Renderable.prototype.init.apply(this, [x, y, 100, 85]);
this.iconCanvas = me.video.createCanvas(
me.utils.nextPowerOfTwo(this.width),
me.utils.nextPowerOfTwo(this.height),
false);
var context = me.video.renderer.getContext2d(this.iconCanvas);
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.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();
this.anchorPoint.set(0.5, 0.5);
},
/**
* @ignore
*/
draw : function (renderer) {
renderer.drawImage(this.iconCanvas, this.pos.x, this.pos.y);
}
});
// the melonJS Text Logo
var TextLogo = me.Renderable.extend({
/**
* @ignore
*/
init : function (w, h) {
me.Renderable.prototype.init.apply(this, [0, 0, w, h]);
// offscreen cache canvas
this.fontCanvas = me.video.createCanvas(256, 64);
this.drawFont(me.video.renderer.getContext2d(this.fontCanvas));
this.anchorPoint.set(0.0, 0.0);
},
drawFont : function (context) {
var logo1 = new me.Font("century gothic", 32, "white", "middle");
var logo2 = new me.Font("century gothic", 32, "#55aa00", "middle");
var logo1_width = 0;
// configure the font
logo2.bold();
logo1.textBaseline = logo2.textBaseline = "top";
// measure the logo size (using standard 2d context)
context.font = logo1.font;
context.fillStyle = logo1.fillStyle.toRGBA();
context.textAlign = logo1.textAlign;
context.textBaseline = logo1.textBaseline;
logo1_width = context.measureText("melon").width;
// calculate the final rendering position
this.pos.x = Math.round((this.width - logo1_width - context.measureText("JS").width) / 2);
this.pos.y = this.height / 2 + 16;
// use the private _drawFont method to directly draw on the canvas context
logo1._drawFont(context, "melon", 0, 0);
logo2._drawFont(context, "JS", logo1_width, 0);
},
/**
* @ignore
*/
draw : function (renderer) {
renderer.drawImage(this.fontCanvas, this.pos.x, this.pos.y);
}
});
/**
* a default loading screen
* @memberOf me
* @ignore
* @constructor
*/
me.DefaultLoadingScreen = me.ScreenObject.extend({
/**
* call when the loader is resetted
* @ignore
*/
onResetEvent : function () {
// background color
me.game.world.addChild(new me.ColorLayer("background", "#202020", 0), 0);
// progress bar
var progressBar = new ProgressBar(
0,
me.video.renderer.getHeight() / 2,
me.video.renderer.getWidth(),
8 // bar height
);
this.loaderHdlr = me.event.subscribe(
me.event.LOADER_PROGRESS,
progressBar.onProgressUpdate.bind(progressBar)
);
this.resizeHdlr = me.event.subscribe(
me.event.VIEWPORT_ONRESIZE,
progressBar.resize.bind(progressBar)
);
me.game.world.addChild(progressBar, 1);
// melonJS text & logo
var icon = new IconLogo(
me.video.renderer.getWidth() / 2,
(me.video.renderer.getHeight() / 2) - (progressBar.height) - 35
);
me.game.world.addChild(icon, 1);
me.game.world.addChild(new TextLogo(me.video.renderer.getWidth(), me.video.renderer.getHeight()), 1);
},
/**
* destroy object at end of loading
* @ignore
*/
onDestroyEvent : function () {
// cancel the callback
me.event.unsubscribe(this.loaderHdlr);
me.event.unsubscribe(this.resizeHdlr);
this.loaderHdlr = this.resizeHdlr = null;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a small class to manage loading of stuff and manage resources
* There is no constructor function for me.input.
* @namespace me.loader
* @memberOf me
*/
me.loader = (function () {
// hold public stuff in our singleton
var api = {};
// contains all the images loaded
var imgList = {};
// contains all the TMX loaded
var tmxList = {};
// contains all the binary files loaded
var binList = {};
// contains all the JSON files
var jsonList = {};
// baseURL
var baseURL = {};
// flag to check loading status
var resourceCount = 0;
var loadCount = 0;
var timerId = 0;
/**
* check the loading status
* @ignore
*/
function checkLoadStatus(onload) {
if (loadCount === resourceCount) {
// wait 1/2s and execute callback (cheap workaround to ensure everything is loaded)
if (onload || api.onload) {
// make sure we clear the timer
clearTimeout(timerId);
// trigger the onload callback
// we call either the supplied callback (which takes precedence) or the global one
var callback = onload || api.onload;
setTimeout(function () {
callback();
me.event.publish(me.event.LOADER_COMPLETE);
}, 300);
}
else {
console.error("no load callback defined");
}
}
else {
timerId = setTimeout(function() {
checkLoadStatus(onload);
}, 100);
}
}
/**
* load Images
* @example
* preloadImages([
* { name : 'image1', src : 'images/image1.png'},
* { name : 'image2', src : 'images/image2.png'},
* { name : 'image3', src : 'images/image3.png'},
* { name : 'image4', src : 'images/image4.png'}
* ]);
* @ignore
*/
function preloadImage(img, onload, onerror) {
// create new Image object and add to list
imgList[img.name] = new Image();
imgList[img.name].onload = onload;
imgList[img.name].onerror = onerror;
if (typeof (api.crossOrigin) === "string") {
imgList[img.name].crossOrigin = api.crossOrigin;
}
imgList[img.name].src = img.src + api.nocache;
}
/**
* preload TMX files
* @ignore
*/
function preloadTMX(tmxData, onload, onerror) {
function addToTMXList(data) {
// set the TMX content
tmxList[tmxData.name] = data;
// add the tmx to the levelDirector
if (tmxData.type === "tmx") {
me.levelDirector.addTMXLevel(tmxData.name);
}
}
//if the data is in the tmxData object, don't get it via a XMLHTTPRequest
if (tmxData.data) {
addToTMXList(tmxData.data);
onload();
return;
}
var xmlhttp = new XMLHttpRequest();
// check the data format ('tmx', 'json')
var format = me.utils.getFileExtension(tmxData.src);
if (xmlhttp.overrideMimeType) {
if (format === "json") {
xmlhttp.overrideMimeType("application/json");
}
else {
xmlhttp.overrideMimeType("text/xml");
}
}
xmlhttp.open("GET", tmxData.src + api.nocache, true);
xmlhttp.withCredentials = me.loader.withCredentials;
// set the callbacks
xmlhttp.ontimeout = onerror;
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
// status = 0 when file protocol is used, or cross-domain origin,
// (With Chrome use "--allow-file-access-from-files --disable-web-security")
if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) {
var result = null;
// parse response
switch (format) {
case "xml":
case "tmx":
case "tsx":
// ie9 does not fully implement the responseXML
if (me.device.ua.match(/msie/i) || !xmlhttp.responseXML) {
if (window.DOMParser) {
// manually create the XML DOM
result = (new DOMParser()).parseFromString(xmlhttp.responseText, "text/xml");
} else {
throw new api.Error("XML file format loading not supported, use the JSON file format instead");
}
}
else {
result = xmlhttp.responseXML;
}
// converts to a JS object
var data = me.TMXUtils.parse(result);
switch (format) {
case "tmx":
result = data.map;
break;
case "tsx":
result = data.tilesets[0];
break;
}
break;
case "json":
result = JSON.parse(xmlhttp.responseText);
break;
default:
throw new api.Error("TMX file format " + format + "not supported !");
}
//set the TMX content
addToTMXList(result);
// fire the callback
onload();
}
else {
onerror();
}
}
};
// send the request
xmlhttp.send(null);
}
/**
* preload TMX files
* @ignore
*/
function preloadJSON(data, onload, onerror) {
var xmlhttp = new XMLHttpRequest();
if (xmlhttp.overrideMimeType) {
xmlhttp.overrideMimeType("application/json");
}
xmlhttp.open("GET", data.src + api.nocache, true);
xmlhttp.withCredentials = me.loader.withCredentials;
// set the callbacks
xmlhttp.ontimeout = onerror;
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4) {
// status = 0 when file protocol is used, or cross-domain origin,
// (With Chrome use "--allow-file-access-from-files --disable-web-security")
if ((xmlhttp.status === 200) || ((xmlhttp.status === 0) && xmlhttp.responseText)) {
// get the Texture Packer Atlas content
jsonList[data.name] = JSON.parse(xmlhttp.responseText);
// fire the callback
onload();
}
else {
onerror();
}
}
};
// send the request
xmlhttp.send(null);
}
/**
* preload Binary files
* @ignore
*/
function preloadBinary(data, onload, onerror) {
var httpReq = new XMLHttpRequest();
// load our file
httpReq.open("GET", data.src + api.nocache, true);
httpReq.withCredentials = me.loader.withCredentials;
httpReq.responseType = "arraybuffer";
httpReq.onerror = onerror;
httpReq.onload = function () {
var arrayBuffer = httpReq.response;
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
var buffer = [];
for (var i = 0; i < byteArray.byteLength; i++) {
buffer[i] = String.fromCharCode(byteArray[i]);
}
binList[data.name] = buffer.join("");
// callback
onload();
}
};
httpReq.send();
}
/**
* to enable/disable caching
* @ignore
*/
api.nocache = "";
/*
* PUBLIC STUFF
*/
/**
* onload callback
* @public
* @function
* @name onload
* @memberOf me.loader
* @example
* // set a callback when everything is loaded
* me.loader.onload = this.loaded.bind(this);
*/
api.onload = undefined;
/**
* onProgress callback
* each time a resource is loaded, the loader will fire the specified function,
* giving the actual progress [0 ... 1], as argument, and an object describing the resource loaded
* @public
* @function
* @name onProgress
* @memberOf me.loader
* @example
* // set a callback for progress notification
* me.loader.onProgress = this.updateProgress.bind(this);
*/
api.onProgress = undefined;
/**
* crossOrigin attribute to configure the CORS requests for Image data element.
* By default (that is, when the attribute is not specified), CORS is not used at all.
* The "anonymous" keyword means that there will be no exchange of user credentials via cookies,
* client-side SSL certificates or HTTP authentication as described in the Terminology section of the CORS specification.
* @public
* @type String
* @name crossOrigin
* @default undefined
* @memberOf me.loader
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes
* @example
* // allow for cross-origin texture loading in WebGL
* me.loader.crossOrigin = "anonymous";
*
* // set all ressources to be loaded
* me.loader.preload(game.resources, this.loaded.bind(this));
*/
api.crossOrigin = undefined;
/**
* indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies,
* authorization headers or TLS client certificates. Setting withCredentials has no effect on same-site requests.
* @public
* @type Boolean
* @name withCredentials
* @default false
* @memberOf me.loader
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
* @example
* // enable withCredentials
* me.loader.withCredentials = true;
*
* // set all ressources to be loaded
* me.loader.preload(game.resources, this.loaded.bind(this));
*/
api.withCredentials = false;
/**
* Base class for Loader exception handling.
* @name Error
* @class
* @memberOf me.loader
* @constructor
* @param {String} msg Error message.
*/
api.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.loader.Error";
}
});
/**
* just increment the number of already loaded resources
* @ignore
*/
api.onResourceLoaded = function (res) {
// increment the loading counter
loadCount++;
// callback ?
var progress = api.getLoadProgress();
if (api.onProgress) {
// pass the load progress in percent, as parameter
api.onProgress(progress, res);
}
me.event.publish(me.event.LOADER_PROGRESS, [progress, res]);
};
/**
* on error callback for image loading
* @ignore
*/
api.onLoadingError = function (res) {
throw new api.Error("Failed loading resource " + res.src);
};
/**
* enable the nocache mechanism
* @ignore
*/
api.setNocache = function (enable) {
api.nocache = enable ? "?" + ~~(Math.random() * 10000000) : "";
};
/**
* change the default baseURL for the given asset type.
* (this will prepend the asset URL and must finish with a '/')
* @name setBaseURL
* @memberOf me.loader
* @public
* @function
* @param {String} type "*", "audio", binary", "image", "json", "tmx", "tsx"
* @param {String} [url="./"] default base URL
* @example
* // change the base URL relative address
* me.loader.setBaseURL("audio", "data/audio/");
* // change the base URL absolute address for all object types
* me.loader.setBaseURL("*", "http://myurl.com/")
*/
api.setBaseURL = function (type, url) {
if (type !== "*") {
baseURL[type] = url;
} else {
// "wildcards"
baseURL["audio"] = url;
baseURL["binary"] = url;
baseURL["image"] = url;
baseURL["json"] = url;
baseURL["tmx"] = url;
baseURL["tsx"] = url;
}
};
/**
* set all the specified game resources to be preloaded.
* @name preload
* @memberOf me.loader
* @public
* @function
* @param {Object[]} resources
* @param {String} resources.name internal name of the resource
* @param {String} resources.type "audio", binary", "image", "json", "tmx", "tsx"
* @param {String} resources.src path and/or file name of the resource (for audio assets only the path is required)
* @param {Boolean} [resources.stream] Set to true to force HTML5 Audio, which allows not to wait for large file to be downloaded before playing.
* @param {function} [onload=me.loader.onload] function to be called when all resources are loaded
* @param {boolean} [switchToLoadState=true] automatically switch to the loading screen
* @example
* game_resources = [
* // PNG tileset
* {name: "tileset-platformer", type: "image", src: "data/map/tileset.png"},
* // PNG packed texture
* {name: "texture", type:"image", src: "data/gfx/texture.png"}
* // TSX file
* {name: "meta_tiles", type: "tsx", src: "data/map/meta_tiles.tsx"},
* // TMX level (XML & JSON)
* {name: "map1", type: "tmx", src: "data/map/map1.json"},
* {name: "map2", type: "tmx", src: "data/map/map2.tmx"},
* {name: "map3", type: "tmx", format: "json", data: {"height":15,"layers":[...],"tilewidth":32,"version":1,"width":20}},
* {name: "map4", type: "tmx", format: "xml", data: {xml representation of tmx}},
* // audio resources
* {name: "bgmusic", type: "audio", src: "data/audio/"},
* {name: "cling", type: "audio", src: "data/audio/"},
* // binary file
* {name: "ymTrack", type: "binary", src: "data/audio/main.ym"},
* // JSON file (used for texturePacker)
* {name: "texture", type: "json", src: "data/gfx/texture.json"}
* ];
* ...
* // set all resources to be loaded
* me.loader.preload(game.resources, this.loaded.bind(this));
*/
api.preload = function (res, onload, switchToLoadState) {
// parse the resources
for (var i = 0; i < res.length; i++) {
resourceCount += api.load(
res[i],
api.onResourceLoaded.bind(api, res[i]),
api.onLoadingError.bind(api, res[i])
);
}
// set the onload callback if defined
if (typeof(onload) !== "undefined") {
api.onload = onload;
}
if (switchToLoadState !== false) {
// swith to the loading screen
me.state.change(me.state.LOADING);
}
// check load status
checkLoadStatus(onload);
};
/**
* Load a single resource (to be used if you need to load additional resource during the game)
* @name load
* @memberOf me.loader
* @public
* @function
* @param {Object} resource
* @param {String} resource.name internal name of the resource
* @param {String} resource.type "audio", binary", "image", "json", "tmx", "tsx"
* @param {String} resource.src path and/or file name of the resource (for audio assets only the path is required)
* @param {Boolean} [resource.stream] Set to true to force HTML5 Audio, which allows not to wait for large file to be downloaded before playing.
* @param {Function} onload function to be called when the resource is loaded
* @param {Function} onerror function to be called in case of error
* @example
* // load an image asset
* me.loader.load({name: "avatar", type:"image", src: "data/avatar.png"}, this.onload.bind(this), this.onerror.bind(this));
*
* // start loading music
* me.loader.load({
* name : "bgmusic",
* type : "audio",
* src : "data/audio/"
* }, function () {
* me.audio.play("bgmusic");
* });
*/
api.load = function (res, onload, onerror) {
// transform the url if necessary
if (typeof (baseURL[res.type]) !== "undefined") {
res.src = baseURL[res.type] + res.src;
}
// check ressource type
switch (res.type) {
case "binary":
// reuse the preloadImage fn
preloadBinary.call(this, res, onload, onerror);
return 1;
case "image":
// reuse the preloadImage fn
preloadImage.call(this, res, onload, onerror);
return 1;
case "json":
preloadJSON.call(this, res, onload, onerror);
return 1;
case "tmx":
case "tsx":
preloadTMX.call(this, res, onload, onerror);
return 1;
case "audio":
me.audio.load(res, !!res.stream, onload, onerror);
return 1;
default:
throw new api.Error("load : unknown or invalid resource type : " + res.type);
}
};
/**
* unload specified resource to free memory
* @name unload
* @memberOf me.loader
* @public
* @function
* @param {Object} resource
* @return {Boolean} true if unloaded
* @example me.loader.unload({name: "avatar", type:"image", src: "data/avatar.png"});
*/
api.unload = function (res) {
switch (res.type) {
case "binary":
if (!(res.name in binList)) {
return false;
}
delete binList[res.name];
return true;
case "image":
if (!(res.name in imgList)) {
return false;
}
if (typeof(imgList[res.name].dispose) === "function") {
// cocoonJS implements a dispose function to free
// corresponding allocated texture in memory
imgList[res.name].dispose();
}
delete imgList[res.name];
return true;
case "json":
if (!(res.name in jsonList)) {
return false;
}
delete jsonList[res.name];
return true;
case "tmx":
case "tsx":
if (!(res.name in tmxList)) {
return false;
}
delete tmxList[res.name];
return true;
case "audio":
return me.audio.unload(res.name);
default:
throw new api.Error("unload : unknown or invalid resource type : " + res.type);
}
};
/**
* unload all resources to free memory
* @name unloadAll
* @memberOf me.loader
* @public
* @function
* @example me.loader.unloadAll();
*/
api.unloadAll = function () {
var name;
// unload all binary resources
for (name in binList) {
if (binList.hasOwnProperty(name)) {
api.unload({
"name" : name,
"type" : "binary"
});
}
}
// unload all image resources
for (name in imgList) {
if (imgList.hasOwnProperty(name)) {
api.unload({
"name" : name,
"type" : "image"
});
}
}
// unload all tmx resources
for (name in tmxList) {
if (tmxList.hasOwnProperty(name)) {
api.unload({
"name" : name,
"type" : "tmx"
});
}
}
// unload all in json resources
for (name in jsonList) {
if (jsonList.hasOwnProperty(name)) {
api.unload({
"name" : name,
"type" : "json"
});
}
}
// unload all audio resources
me.audio.unloadAll();
};
/**
* return the specified TMX/TSX object
* @name getTMX
* @memberOf me.loader
* @public
* @function
* @param {String} tmx name of the tmx/tsx element ("map1");
* @return {XML|Object}
*/
api.getTMX = function (elt) {
// force as string
elt = "" + elt;
if (elt in tmxList) {
return tmxList[elt];
}
else {
//console.log ("warning %s resource not yet loaded!",name);
return null;
}
};
/**
* return the specified Binary object
* @name getBinary
* @memberOf me.loader
* @public
* @function
* @param {String} name of the binary object ("ymTrack");
* @return {Object}
*/
api.getBinary = function (elt) {
// force as string
elt = "" + elt;
if (elt in binList) {
return binList[elt];
}
else {
//console.log ("warning %s resource not yet loaded!",name);
return null;
}
};
/**
* return the specified Image Object
* @name getImage
* @memberOf me.loader
* @public
* @function
* @param {String} Image name of the Image element ("tileset-platformer");
* @return {Image}
*/
api.getImage = function (elt) {
// force as string
elt = "" + elt;
if (elt in imgList) {
// return the corresponding Image object
return imgList[elt];
}
else {
//console.log ("warning %s resource not yet loaded!",name);
return null;
}
};
/**
* return the specified JSON Object
* @name getJSON
* @memberOf me.loader
* @public
* @function
* @param {String} Name for the json file to load
* @return {Object}
*/
api.getJSON = function (elt) {
// force as string
elt = "" + elt;
if (elt in jsonList) {
return jsonList[elt];
}
else {
return null;
}
};
/**
* Return the loading progress in percent
* @name getLoadProgress
* @memberOf me.loader
* @public
* @function
* @deprecated use callback instead
* @return {Number}
*/
api.getLoadProgress = function () {
return loadCount / resourceCount;
};
// return our object
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Font / Bitmap font
*
* ASCII Table
* http://www.asciitable.com/
* [ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz]
*
* -> first char " " 32d (0x20);
*/
(function () {
var runits = ["ex", "em", "pt", "px"];
var toPX = [12, 24, 0.75, 1];
/**
* 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 {me.Color|String} fillStyle a CSS color value
* @param {String} [textAlign="left"] horizontal alignment
*/
me.Font = me.Renderable.extend(
/** @scope me.Font.prototype */ {
/** @ignore */
init : function (font, size, fillStyle, textAlign) {
// private font properties
/** @ignore */
this.fontSize = new me.Vector2d();
/**
* defines the color used to draw the font.
* @public
* @type me.Color
* @default black
* @name me.Font#fillStyle
*/
this.fillStyle = new me.Color().copy(fillStyle);
/**
* defines the color used to draw the font stroke.
* @public
* @type me.Color
* @default black
* @name me.Font#strokeStyle
*/
this.strokeStyle = new me.Color(0, 0, 0);
/**
* sets the current line width, in pixels, when drawing stroke
* @public
* @type Number
* @default 1
* @name me.Font#lineWidth
*/
this.lineWidth = 1;
/**
* Set the default text alignment (or justification),
* possible values are "left", "right", and "center".
* @public
* @type String
* @default "left"
* @name me.Font#textAlign
*/
this.textAlign = textAlign || "left";
/**
* Set the text baseline (e.g. the Y-coordinate for the draw operation),
* possible values are "top", "hanging, "middle, "alphabetic, "ideographic, "bottom"
* @public
* @type String
* @default "top"
* @name me.Font#textBaseline
*/
this.textBaseline = "top";
/**
* Set the line spacing height (when displaying multi-line strings).
* Current font height will be multiplied with this value to set the line height.
* @public
* @type Number
* @default 1.0
* @name me.Font#lineHeight
*/
this.lineHeight = 1.0;
// super constructor
me.Renderable.prototype.init.apply(this, [0, 0, 0, 0]);
// font name and type
this.setFont(font, size, fillStyle, textAlign);
if (!this.gid) {
this.gid = me.utils.createGUID();
}
},
/**
* 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 setFont
* @memberOf me.Font
* @function
* @param {String} font a CSS font name
* @param {Number|String} size size, or size + suffix (px, em, pt)
* @param {me.Color|String} [fillStyle] a CSS color value
* @param {String} [textAlign="left"] horizontal alignment
* @example
* font.setFont("Arial", 20, "white");
* font.setFont("Arial", "1.5em", "white");
*/
setFont : 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;
});
if (typeof size === "number") {
this.fontSize.y = size;
size += "px";
} else /* string */ {
// extract the units and convert if necessary
var CSSval = size.match(/([-+]?[\d.]*)(.*)/);
this.fontSize.y = parseFloat(CSSval[1]);
if (CSSval[2]) {
this.fontSize.y *= toPX[runits.indexOf(CSSval[2])];
} else {
// no unit define, assume px
size += "px";
}
}
this.height = this.fontSize.y;
this.font = size + " " + font_names.join(",");
if (typeof(fillStyle) !== "undefined") {
this.fillStyle.copy(fillStyle);
}
if (textAlign) {
this.textAlign = textAlign;
}
},
/**
* measure the given text size in pixels
* @name measureText
* @memberOf me.Font
* @function
* @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance
* @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 (renderer, text) {
var context = renderer.getFontContext();
// draw the text
context.font = this.font;
context.fillStyle = this.fillStyle.toRGBA();
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 {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance
* @param {String} text
* @param {Number} x
* @param {Number} y
*/
draw : function (renderer, text, x, y) {
// save the previous global alpha value
var _alpha = renderer.globalAlpha();
renderer.setGlobalAlpha(_alpha * this.getOpacity());
// draw the text
renderer.drawFont(this._drawFont(renderer.getFontContext(), text, ~~x, ~~y, false));
// restore the previous global alpha value
renderer.setGlobalAlpha(_alpha);
},
/**
* draw a stroke text at the specified coord, as defined
* by the `lineWidth` and `fillStroke` properties.
* Note : using drawStroke is not recommended for performance reasons
* @name drawStroke
* @memberOf me.Font
* @function
* @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance
* @param {String} text
* @param {Number} x
* @param {Number} y
*/
drawStroke : function (renderer, text, x, y) {
// save the previous global alpha value
var _alpha = renderer.globalAlpha();
renderer.setGlobalAlpha(_alpha * this.getOpacity());
// draw the text
renderer.drawFont(this._drawFont(renderer.getFontContext(), text, ~~x, ~~y, true));
// restore the previous global alpha value
renderer.setGlobalAlpha(_alpha);
},
/**
* @ignore
*/
_drawFont : function (context, text, x, y, stroke) {
context.font = this.font;
context.fillStyle = this.fillStyle.toRGBA();
if (stroke) {
context.strokeStyle = this.strokeStyle.toRGBA();
context.lineWidth = this.lineWidth;
}
context.textAlign = this.textAlign;
context.textBaseline = this.textBaseline;
var strings = ("" + text).split("\n"), string = "";
var dw = 0;
var dy = y;
var lineHeight = this.fontSize.y * this.lineHeight;
for (var i = 0; i < strings.length; i++) {
string = strings[i].trimRight();
// measure the string
dw = Math.max(dw, context.measureText(string).width);
// draw the string
context[stroke ? "strokeText" : "fillText"](string, x, y);
// add leading space
y += lineHeight;
}
// compute bounds
// TODO : memoize me !
var dx = (this.textAlign === "right" ? x - dw : (
this.textAlign === "center" ? x - ~~(dw / 2) : x
));
dy = (this.textBaseline.search(/^(top|hanging)$/) === 0) ? dy : (
this.textBaseline === "middle" ? dy - ~~(lineHeight / 2) : dy - lineHeight
);
// update the renderable bounds
return this.getBounds().setShape(
~~dx,
~~dy,
~~(dw + 0.5),
~~(strings.length * lineHeight + 0.5)
);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Font / Bitmap font
*
* -> first char " " 32d (0x20);
*/
(function () {
/**
* Measures a single line of text, does not account for \n
* @ignore
*/
var measureTextWidth = function(font, text) {
var characters = text.split("");
var width = 0;
var lastGlyph = null;
for (var i = 0; i < characters.length; i++) {
var ch = characters[i].charCodeAt(0);
var glyph = font.bitmapFontData.glyphs[ch];
var kerning = (lastGlyph && lastGlyph.kerning) ? lastGlyph.getKerning(ch) : 0;
width += (glyph.xadvance + kerning) * font.fontScale.x;
lastGlyph = glyph;
}
return width;
};
/**
* a bitmap font object
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {Object} font the font object data. Should be retrieved from the loader
* @param {Image} image the font image itself Should be retrieved from the loader
* @param {Number} [scale=1.0]
* @param {String} [textAlign="left"]
* @param {String} [textBaseline="top"]
* @example
* // Use me.loader.preload or me.loader.load to load assets
* me.loader.preload([
* { name: "arial", type: "binary" src: "data/font/arial.fnt" },
* { name: "arial", type: "image" src: "data/font/arial.png" },
* ])
* // Then create an instance of your bitmap font:
* var myFont = new me.BitmapFont(me.loader.getBinary("arial"), me.loader.getImage("arial"));
* // And draw it inside your Renderable, just like me.Font
* myFont.draw(renderer, "Hello!", 0, 0);
*/
me.BitmapFont = me.Renderable.extend(
/** @scope me.BitmapFont.prototype */ {
/** @ignore */
init : function (data, fontImage, scale, textAlign, textBaseline) {
/** @ignore */
// scaled font size;
this.sSize = me.pool.pull("me.Vector2d", 0, 0);
this.fontImage = fontImage;
/**
* The instance of me.BitmapFontData
* @type {me.BitmapFontData}
* @name bitmapFontData
* @memberOf me.BitmapFont
*/
this.bitmapFontData = new me.BitmapFontData(data);
this.fontScale = me.pool.pull("me.Vector2d", 1, 1);
this.charCount = 0;
me.Renderable.prototype.init.apply(this, [0, 0, 0, 0, 0, 0]);
// set a default alignement
this.textAlign = textAlign || "left";
this.textBaseline = textBaseline || "top";
this.lineHeight = 1;
// resize if necessary
if (scale) {
this.resize(scale);
}
},
/**
* 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) {
this.fontScale.set(scale, scale);
},
/**
* measure the given text size in pixels
* @name measureText
* @memberOf me.BitmapFont
* @function
* @param {String} text
* @returns {Object} an object with two properties: `width` and `height`, defining the output dimensions
*/
measureText : function (text) {
var strings = ("" + text).split("\n");
var width = 0;
var height = 0;
var stringHeight = this.bitmapFontData.capHeight * this.lineHeight;
for (var i = 0; i < strings.length; i++) {
width = Math.max(measureTextWidth(this, strings[i]), width);
height += stringHeight;
}
return {width: width, height: height * this.fontScale.y};
},
/**
* draw a text at the specified coord
* @name draw
* @memberOf me.BitmapFont
* @function
* @param {me.CanvasRenderer|me.WebGLRenderer} renderer Reference to the destination renderer instance
* @param {String} text
* @param {Number} x
* @param {Number} y
*/
draw : function (renderer, text, x, y) {
var strings = ("" + text).split("\n");
var lX = x;
var stringHeight = this.bitmapFontData.capHeight * this.lineHeight * this.fontScale.y;
// save the previous global alpha value
var _alpha = renderer.globalAlpha();
renderer.setGlobalAlpha(_alpha * this.getOpacity());
// update initial position
this.pos.set(x, y, this.pos.z); // TODO : z ?
for (var i = 0; i < strings.length; i++) {
x = lX;
var string = strings[i].trimRight();
// adjust x pos based on alignment value
var stringWidth = measureTextWidth(this, string);
switch (this.textAlign) {
case "right":
x -= stringWidth;
break;
case "center":
x -= stringWidth * 0.5;
break;
default :
break;
}
// adjust y pos based on alignment value
switch (this.textBaseline) {
case "middle":
y -= stringHeight * 0.5;
break;
case "ideographic":
case "alphabetic":
case "bottom":
y -= stringHeight;
break;
default :
break;
}
// x *= this.fontScale.x;
// y *= this.fontScale.y;
// draw the string
var lastGlyph = null;
for (var c = 0, len = string.length; c < len; c++) {
// calculate the char index
var ch = string.charCodeAt(c);
var glyph = this.bitmapFontData.glyphs[ch];
var kerning = (lastGlyph && lastGlyph.kerning) ? lastGlyph.getKerning(ch) : 0;
// draw it
if (glyph.width !== 0 && glyph.height !== 0) {
// some browser throw an exception when drawing a 0 width or height image
renderer.drawImage(this.fontImage,
glyph.src.x, glyph.src.y,
glyph.width, glyph.height,
x + glyph.offset.x,
y + glyph.offset.y * this.fontScale.y,
glyph.width * this.fontScale.x, glyph.height * this.fontScale.y
);
}
// increment position
x += (glyph.xadvance + kerning) * this.fontScale.x;
lastGlyph = glyph;
}
// increment line
y += stringHeight;
}
// restore the previous global alpha value
renderer.setGlobalAlpha(_alpha);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function() {
/**
* Class for storing relevant data from the font file.
* @class me.BitmapFontData
* @memberOf me
* @param data {String} - The bitmap font data pulled from the resource loader using me.loader.getBinary()
* @constructor
*/
me.BitmapFontData = me.Object.extend({
/**
* @ignore
*/
init: function (data) {
this.padTop = 0;
this.padRight = 0;
this.padBottom = 0;
this.padLeft = 0;
this.lineHeight = 0;
// The distance from the top of most uppercase characters to the baseline. Since the drawing position is the cap height of
// the first line, the cap height can be used to get the location of the baseline.
this.capHeight = 1;
// The distance from the bottom of the glyph that extends the lowest to the baseline. This number is negative.
this.descent = 0;
this.scale = new me.Vector2d();
/**
* The map of glyphs, each key is a char code.
* @name glyphs
* @type {Object}
* @memberOf me.BitmapFontData
*/
this.glyphs = {};
this.xChars = ["x", "e", "a", "o", "n", "s", "r", "c", "u", "m", "v", "w", "z"];
this.capChars = ["M", "N", "B", "D", "C", "E", "F", "K", "A", "G", "H", "I", "J", "L", "O", "P", "Q", "R", "S",
"T", "U", "V", "W", "X", "Y", "Z"];
// parse the data
this.parse(data);
},
/**
* Creates a glyph to use for the space character
* @private
* @name _createSpaceGlyph
* @memberOf me.BitmapFontData
* @function
*/
_createSpaceGlyph: function () {
var spaceCharCode = " ".charCodeAt(0);
var glyph = this.glyphs[spaceCharCode];
if (!glyph) {
glyph = me.pool.pull("me.Glyph");
glyph.id = spaceCharCode;
glyph.xadvance = this._getFirstGlyph().xadvance;
this.glyphs[spaceCharCode] = glyph;
}
},
/**
* Gets the first glyph in the map that is not a space character
* @private
* @name _getFirstGlyph
* @memberOf me.BitmapFontData
* @function
* @returns {me.Glyph}
*/
_getFirstGlyph: function () {
var keys = Object.keys(this.glyphs);
for (var i = 0; i < keys.length; i++) {
if (keys[i] > 32) {
return this.glyphs[keys[i]];
}
}
return null;
},
/**
* Gets the value from a string of pairs. For example: one=1 two=2 something=hi. Can accept the regex of /one={d}/
* and returns the value of d
* @private
* @name _getValueFromPair
* @memberOf me.BitmapFontData
* @function
* @returns {String}
*/
_getValueFromPair: function (string, pattern) {
var value = string.match(pattern);
if (!value) {
throw "Could not find pattern " + pattern + " in string: " + string;
}
return value[0].split("=")[1];
},
/**
* This parses the font data text and builds a map of glyphs containing the data for each character
* @name parse
* @memberOf me.BitmapFontData
* @function
* @param {String} fontData
*/
parse: function (fontData) {
if (!fontData) {
throw "File containing font data was empty, cannot load the bitmap font.";
}
var lines = fontData.split(/\r\n|\n/);
var padding = fontData.match(/padding\=\d+,\d+,\d+,\d+/g);
if (!padding) {
throw "Padding not found in first line";
}
var paddingValues = padding[0].split("=")[1].split(",");
this.padTop = parseFloat(paddingValues[0]);
this.padLeft = parseFloat(paddingValues[1]);
this.padBottom = parseFloat(paddingValues[2]);
this.padRight = parseFloat(paddingValues[3]);
this.lineHeight = parseFloat(this._getValueFromPair(lines[1], /lineHeight\=\d+/g));
var baseLine = parseFloat(this._getValueFromPair(lines[1], /base\=\d+/g));
var padY = this.padTop + this.padBottom;
var glyph = null;
for (var i = 4; i < lines.length; i++) {
var line = lines[i];
var characterValues = line.split(/=|\s+/);
if (!line || /^kernings/.test(line)) {
continue;
}
if (/^kerning\s/.test(line)) {
var first = parseFloat(characterValues[2]);
var second = parseFloat(characterValues[4]);
var amount = parseFloat(characterValues[6]);
glyph = this.glyphs[first];
if (glyph !== null && typeof glyph !== "undefined") {
glyph.setKerning(second, amount);
}
} else {
glyph = me.pool.pull("me.Glyph");
var ch = parseFloat(characterValues[2]);
glyph.id = ch;
glyph.src.set(parseFloat(characterValues[4]), parseFloat(characterValues[6]));
glyph.width = parseFloat(characterValues[8]);
glyph.height = parseFloat(characterValues[10]);
var y = parseFloat(characterValues[14]);
glyph.offset.set(parseFloat(characterValues[12]), y);
glyph.xadvance = parseFloat(characterValues[16]);
if (glyph.width > 0 && glyph.height > 0) {
this.descent = Math.min(baseLine + glyph.yoffset, this.descent);
}
this.glyphs[ch] = glyph;
}
}
this.descent += this.padBottom;
this._createSpaceGlyph();
var xGlyph = null;
for (i = 0; i < this.xChars.length; i++) {
var xChar = this.xChars[i];
xGlyph = this.glyphs[xChar.charCodeAt(0)];
if (xGlyph) {
break;
}
}
if (!xGlyph) {
xGlyph = this._getFirstGlyph();
}
var capGlyph = null;
for (i = 0; i < this.capChars.length; i++) {
var capChar = this.capChars[i];
capGlyph = this.glyphs[capChar.charCodeAt(0)];
if (capGlyph) {
break;
}
}
if (!capGlyph) {
for (var charCode in this.glyphs) {
if (this.glyphs.hasOwnProperty(charCode)) {
glyph = this.glyphs[charCode];
if (glyph.height === 0 || glyph.width === 0) {
continue;
}
this.capHeight = Math.max(this.capHeight, glyph.height);
}
}
} else {
this.capHeight = capGlyph.height;
}
this.capHeight -= padY;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Glyph
*/
(function() {
var LOG2_PAGE_SIZE = 9;
var PAGE_SIZE = 1 << LOG2_PAGE_SIZE;
/**
* a glyph representing a single character in a font
* @class
* @extends me.Object
* @memberOf me
* @constructor
*/
me.Glyph = me.Object.extend({
/**
* @ignore
*/
init: function () {
this.src = new me.Vector2d();
this.offset = new me.Vector2d();
this.onResetEvent();
},
/**
* @ignore
*/
onResetEvent: function () {
this.id = 0;
this.src.set(0, 0);
this.width = 0;
this.height = 0;
this.u = 0;
this.v = 0;
this.u2 = 0;
this.v2 = 0;
this.offset.set(0, 0);
this.xadvance = 0;
this.fixedWidth = false;
},
/**
* @ignore
*/
getKerning: function (ch) {
if (this.kerning) {
var page = this.kerning[ch >>> LOG2_PAGE_SIZE];
if (page) {
return page[ch & PAGE_SIZE - 1] || 0;
}
}
return 0;
},
/**
* @ignore
*/
setKerning: function (ch, value) {
if (!this.kerning) {
this.kerning = {};
}
var page = this.kerning[ch >>> LOG2_PAGE_SIZE];
if (typeof page === "undefined") {
this.kerning[ch >>> LOG2_PAGE_SIZE] = {};
page = this.kerning[ch >>> LOG2_PAGE_SIZE];
}
page[ch & PAGE_SIZE - 1] = value;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 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 api = {};
// audio channel list
var audioTracks = {};
// current music
var current_track_id = null;
// a retry counter
var retry_counter = 0;
/**
* event listener callback on load error
* @ignore
*/
function soundLoadError(sound_name, onerror_cb) {
// check the retry counter
if (retry_counter++ > 3) {
// something went wrong
var errmsg = "melonJS: failed loading " + sound_name;
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 new me.audio.Error(errmsg);
}
// else try loading again !
}
else {
audioTracks[sound_name].load();
}
}
/*
* PUBLIC STUFF
*/
/**
* Initialize and configure the audio support.
* melonJS supports a wide array of audio codecs that have varying browser support :
* ("mp3", "mpeg", opus", "ogg", "oga", "wav", "aac", "caf", "m4a", "mp4", "weba", "webm", "dolby", "flac").
* For a maximum browser coverage the recommendation is to use at least two of them,
* typically default to webm and then fallback to mp3 for the best balance of small filesize and high quality,
* webm has nearly full browser coverage with a great combination of compression and quality, and mp3 will fallback gracefully for other browsers.
* It is important to remember that melonJS selects the first compatible sound based on the list of extensions and given order passed here.
* So if you want webm to be used before mp3, you need to put the audio format in that order.
* @name init
* @memberOf me.audio
* @public
* @function
* @param {String} [audioFormat="mp3"] audio format provided
* @return {Boolean} Indicates whether audio initialization was successful
* @example
* // initialize the "sound engine", giving "webm" as default desired audio format, and "mp3" as a fallback
* if (!me.audio.init("webm,mp3")) {
* alert("Sorry but your browser does not support html 5 audio !");
* return;
* }
*/
api.init = function (audioFormat) {
if (!me.initialized) {
throw new me.audio.Error("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
this.audioFormats = audioFormat.split(",");
return !Howler.noAudio;
};
/**
* enable audio output
* only useful if audio supported and previously disabled through
*
* @see me.audio#disable
* @name enable
* @memberOf me.audio
* @public
* @function
*/
api.enable = function () {
this.unmuteAll();
};
/**
* disable audio output
*
* @name disable
* @memberOf me.audio
* @public
* @function
*/
api.disable = function () {
this.muteAll();
};
/**
* Load an audio file.
*
* sound item must contain the following fields :
* - name : name of the sound
* - src : source path
* @ignore
*/
api.load = function (sound, html5, onload_cb, onerror_cb) {
var urls = [];
if (typeof(this.audioFormats) === "undefined" || this.audioFormats.length === 0) {
throw new me.audio.Error("target audio extension(s) should be set through me.audio.init() before calling the preloader.");
}
for (var i = 0; i < this.audioFormats.length; i++) {
urls.push(sound.src + sound.name + "." + this.audioFormats[i] + me.loader.nocache);
}
audioTracks[sound.name] = new Howl({
src : urls,
volume : Howler.volume(),
html5 : html5 === true,
xhrWithCredentials : me.loader.withCredentials,
/**
* @ignore
*/
onloaderror : function () {
soundLoadError.call(me.audio, sound.name, onerror_cb);
},
/**
* @ignore
*/
onload : function () {
retry_counter = 0;
if (onload_cb) {
onload_cb();
}
}
});
return 1;
};
/**
* play the specified sound
* @name play
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Boolean} [loop=false] loop audio
* @param {Function} [onend] Function to call when sound instance ends playing.
* @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted).
* @return {Number} the sound instance ID.
* @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);
*/
api.play = function (sound_name, loop, onend, volume) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
var id = sound.play();
if (typeof loop === "boolean") {
// arg[0] can take different types in howler 2.0
sound.loop(loop, id);
}
sound.volume(typeof(volume) === "number" ? volume.clamp(0.0, 1.0) : Howler.volume(), id);
if (typeof(onend) === "function") {
if (loop === true) {
sound.on("end", onend, id);
}
else {
sound.once("end", onend, id);
}
}
return id;
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* Fade a currently playing sound between two volumee.
* @name fade
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} from Volume to fade from (0.0 to 1.0).
* @param {Number} to Volume to fade to (0.0 to 1.0).
* @param {Number} duration Time in milliseconds to fade.
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will fade.
*/
api.fade = function (sound_name, from, to, duration, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
sound.fade(from, to, duration, id);
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* get/set the position of playback for a sound.
* @name seek
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [seek] The position to move current playback to (in seconds).
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will changed.
* @return return the current seek position (if no extra parameters were given)
* @example
* // return the current position of the background music
* var current_pos = me.audio.seek("dst-gameforest");
* // set back the position of the background music to the beginning
* me.audio.seek("dst-gameforest", 0);
*/
api.seek = function (sound_name, seek, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
return sound.seek.apply(sound, Array.prototype.slice.call(arguments, 1));
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* get or set the rate of playback for a sound.
* @name rate
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [rate] playback rate : 0.5 to 4.0, with 1.0 being normal speed.
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will be changed.
* @return return the current playback rate (if no extra parameters were given)
* @example
* // get the playback rate of the background music
* var rate = me.audio.rate("dst-gameforest");
* // speed up the playback of the background music
* me.audio.rate("dst-gameforest", 2.0);
*/
api.rate = function (sound_name, rate, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
return sound.rate.apply(sound, Array.prototype.slice.call(arguments, 1));
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* stop the specified sound on all channels
* @name stop
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will stop.
* @example
* me.audio.stop("cling");
*/
api.stop = function (sound_name, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
sound.stop(id);
// remove the defined onend callback (if any defined)
sound.off("end", undefined, id);
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* pause the specified sound on all channels
* this function does not reset the currentTime property
* @name pause
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will pause.
* @example
* me.audio.pause("cling");
*/
api.pause = function (sound_name, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
sound.pause(id);
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* resume the specified sound on all channels
* @name resume
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will resume.
* @example
* // play a audio clip
* var id = me.audio.play("myClip");
* ...
* // pause it
* me.audio.pause("myClip", id);
* ...
* // resume
* me.audio.resume("myClip", id);
*/
api.resume = function (sound_name, id) {
var sound = audioTracks[sound_name];
if (sound && typeof sound !== "undefined") {
sound.play(id);
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* play the specified audio track
* this function automatically set the loop property to true
* and keep track of the current sound being played.
* @name playTrack
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio track name - case sensitive
* @param {Number} [volume=default] Float specifying volume (0.0 - 1.0 values accepted).
* @return {Number} the sound instance ID.
* @example
* me.audio.playTrack("awesome_music");
*/
api.playTrack = function (sound_name, volume) {
current_track_id = sound_name;
return me.audio.play(
current_track_id,
true,
null,
volume
);
};
/**
* 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();
*/
api.stopTrack = function () {
if (current_track_id !== null) {
audioTracks[current_track_id].stop();
current_track_id = null;
}
};
/**
* pause the current audio track
*
* @name pauseTrack
* @memberOf me.audio
* @public
* @function
* @example
* me.audio.pauseTrack();
*/
api.pauseTrack = function () {
if (current_track_id !== null) {
audioTracks[current_track_id].pause();
}
};
/**
* resume the previously paused audio track
*
* @name resumeTrack
* @memberOf me.audio
* @public
* @function
* @example
* // play an awesome music
* me.audio.playTrack("awesome_music");
* // pause the audio track
* me.audio.pauseTrack();
* // resume the music
* me.audio.resumeTrack();
*/
api.resumeTrack = function () {
if (current_track_id !== null) {
audioTracks[current_track_id].play();
}
};
/**
* returns the current track Id
* @name getCurrentTrack
* @memberOf me.audio
* @public
* @function
* @return {String} audio track name
*/
api.getCurrentTrack = function () {
return current_track_id;
};
/**
* set the default global volume
* @name setVolume
* @memberOf me.audio
* @public
* @function
* @param {Number} volume Float specifying volume (0.0 - 1.0 values accepted).
*/
api.setVolume = function (volume) {
Howler.volume(volume);
};
/**
* get the default global volume
* @name getVolume
* @memberOf me.audio
* @public
* @function
* @returns {Number} current volume value in Float [0.0 - 1.0] .
*/
api.getVolume = function () {
return Howler.volume();
};
/**
* mute or unmute the specified sound, but does not pause the playback.
* @name mute
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name - case sensitive
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will mute.
* @param {Boolean} [mute=true] True to mute and false to unmute
* @example
* // mute the background music
* me.audio.mute("awesome_music");
*/
api.mute = function (sound_name, id, mute) {
// if not defined : true
mute = (typeof(mute) === "undefined" ? true : !!mute);
var sound = audioTracks[sound_name];
if (sound && typeof(sound) !== "undefined") {
sound.mute(mute, id);
} else {
throw new me.audio.Error("audio clip " + sound_name + " does not exist");
}
};
/**
* unmute the specified sound
* @name unmute
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio clip name
* @param {Number} [id] the sound instance ID. If none is passed, all sounds in group will unmute.
*/
api.unmute = function (sound_name, id) {
api.mute(sound_name, id, false);
};
/**
* mute all audio
* @name muteAll
* @memberOf me.audio
* @public
* @function
*/
api.muteAll = function () {
Howler.mute(true);
};
/**
* unmute all audio
* @name unmuteAll
* @memberOf me.audio
* @public
* @function
*/
api.unmuteAll = function () {
Howler.mute(false);
};
/**
* Returns true if audio is muted globally.
* @name muted
* @memberOf me.audio
* @public
* @function
* @return {Boolean} true if audio is muted globally
*/
api.muted = function () {
return Howler._muted;
};
/**
* unload specified audio track to free memory
*
* @name unload
* @memberOf me.audio
* @public
* @function
* @param {String} sound_name audio track name - case sensitive
* @return {Boolean} true if unloaded
* @example
* me.audio.unload("awesome_music");
*/
api.unload = function (sound_name) {
if (!(sound_name in audioTracks)) {
return false;
}
// destroy the Howl object
audioTracks[sound_name].unload();
if (typeof(audioTracks[sound_name].dispose) === "function") {
// cocoonJS implements a dispose function to free
// corresponding allocated audio in memory
audioTracks[sound_name].dispose();
}
delete audioTracks[sound_name];
return true;
};
/**
* unload all audio to free memory
*
* @name unloadAll
* @memberOf me.audio
* @public
* @function
* @example
* me.audio.unloadAll();
*/
api.unloadAll = function () {
for (var sound_name in audioTracks) {
if (audioTracks.hasOwnProperty(sound_name)) {
api.unload(sound_name);
}
}
};
// return our object
return api;
})();
/**
* Base class for audio exception handling.
* @name Error
* @class
* @memberOf me.audio
* @constructor
* @param {String} msg Error message.
*/
me.audio.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.audio.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 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 deferResizeId = 0;
var designRatio = 1;
var designWidth = 0;
var designHeight = 0;
// max display size
var maxWidth = Infinity;
var maxHeight = Infinity;
// default video settings
var settings = {
wrapper : undefined,
renderer : 0, // canvas
doubleBuffering : false,
autoScale : false,
scale : 1.0,
scaleMethod : "fit",
transparent : false,
antiAlias : false,
subPixel : false,
verbose : false
};
/**
* Auto-detect the best renderer to use
* @ignore
*/
function autoDetectRenderer(c, width, height, options) {
try {
return new me.WebGLRenderer(c, width, height, options);
}
catch (e) {
return new me.CanvasRenderer(c, width, height, options);
}
}
/*
* PUBLIC STUFF
*/
/**
* Base class for Video exception handling.
* @name Error
* @class
* @constructor
* @memberOf me.video
* @param {String} msg Error message.
*/
api.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.video.Error";
}
});
/**
* cache value for the offset of the canvas position within the page
* @ignore
*/
api._canvasOffset = null;
/**
* Select the HTML5 Canvas renderer
* @public
* @name CANVAS
* @memberOf me.video
* @enum {Number}
*/
api.CANVAS = 0;
/**
* Select the WebGL renderer
* @public
* @name WEBGL
* @memberOf me.video
* @enum {Number}
*/
api.WEBGL = 1;
/**
* Auto-select the renderer (Attempt WebGL first, with fallback to Canvas)
* @public
* @name AUTO
* @memberOf me.video
* @enum {Number}
*/
api.AUTO = 2;
/**
* Initialize the "video" system (create a canvas based on the given arguments, and the related renderer).
* melonJS support various scaling mode :
* - `fit` : Letterboxed; content is scaled to design aspect ratio
* - `fill-max` : Canvas is resized to fit maximum design resolution; content is scaled to design aspect ratio
* - `flex-height` : Canvas height is resized to fit; content is scaled to design aspect ratio
* - `flex-width` : Canvas width is resized to fit; content is scaled to design aspect ratio
* - `stretch` : Canvas is resized to fit; content is scaled to screen aspect ratio
* @name init
* @memberOf me.video
* @function
* @param {Number} width the width of the canvas viewport
* @param {Number} height the height of the canvas viewport
* @param {Object} [options] The optional video/renderer parameters.
(see Renderer(s) documentation for further specific options)
* @param {String} [options.wrapper=document.body] the "div" element name to hold the canvas in the HTML file
* @param {Number} [options.renderer=me.video.CANVAS] renderer to use.
* @param {Boolean} [options.doubleBuffering=false] enable/disable double buffering
* @param {Number|String} [options.scale=1.0] enable scaling of the canvas ('auto' for automatic scaling)
* @param {String} [options.scaleMethod="fit"] ('fit','fill-min','fill-max','flex','flex-width','flex-height','stretch') screen scaling modes
* @param {Boolean} [options.useParentDOMSize=false] on browser devices, limit the canvas width and height to its parent container dimensions as returned by getBoundingClientRect(),
* as opposed to the browser window dimensions
* @param {Boolean} [options.transparent=false] whether to allow transparent pixels in the front buffer (screen)
* @param {Boolean} [options.antiAlias=false] whether to enable or not video scaling interpolation
* @return {Boolean} false if initialization failed (canvas not supported)
* @see me.CanvasRenderer
* @see me.WebGLRenderer
* @example
* // init the video with a 640x480 canvas
* me.video.init(640, 480, {
* wrapper : "screen",
* renderer : me.video.CANVAS,
* scale : "auto",
* scaleMethod : "fit",
* doubleBuffering : true
* });
*/
api.init = function (game_width, game_height, options) {
// ensure melonjs has been properly initialized
if (!me.initialized) {
throw new api.Error("me.video.init() called before engine initialization.");
}
// revert to default options if not defined
settings = Object.assign(settings, options || {});
// sanitize potential given parameters
settings.doubleBuffering = !!(settings.doubleBuffering);
settings.useParentDOMSize = !!(settings.useParentDOMSize);
settings.autoScale = (settings.scale === "auto") || false;
if (settings.scaleMethod.search(/^(fill-(min|max)|fit|flex(-(width|height))?|stretch)$/) !== 0) {
settings.scaleMethod = "fit";
}
settings.transparent = !!(settings.transparent);
// override renderer settings if &webgl is defined in the URL
if (me.game.HASH.webgl === true) {
settings.renderer = api.WEBGL;
}
// normalize scale
settings.scale = (settings.autoScale) ? 1.0 : (+settings.scale || 1.0);
me.sys.scale = new me.Vector2d(settings.scale, settings.scale);
// force double buffering if scaling is required
if (settings.autoScale || (settings.scale !== 1.0)) {
settings.doubleBuffering = true;
}
// hold the requested video size ratio
designRatio = game_width / game_height;
designWidth = game_width;
designHeight = game_height;
// default scaled size value
var game_width_zoom = game_width * me.sys.scale.x;
var game_height_zoom = game_height * me.sys.scale.y;
settings.zoomX = game_width_zoom;
settings.zoomY = game_height_zoom;
//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
);
// Screen Orientation API
window.addEventListener(
"orientationchange",
function (event) {
me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE, [ event ]);
},
false
);
// pre-fixed implementation on mozzila
window.addEventListener(
"onmozorientationchange",
function (event) {
me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE, [ event ]);
},
false
);
if (typeof window.screen !== "undefined") {
// is this one required ?
window.screen.onorientationchange = function (event) {
me.event.publish(me.event.WINDOW_ONORIENTATION_CHANGE, [ event ]);
};
}
// Automatically update relative canvas position on scroll
window.addEventListener("scroll", throttle(100, false,
function (e) {
// invalidate the current canvas position cache so that it
// get recalculated the next time getPos is called
api._canvasOffset = null;
me.event.publish(me.event.WINDOW_ONSCROLL, [ e ]);
}
), 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 screen canvas
if (me.device.ejecta === true) {
// a main canvas is already automatically created by Ejecta
canvas = document.getElementById("canvas");
} else {
canvas = api.createCanvas(game_width_zoom, game_height_zoom, true);
}
// add our canvas
if (options.wrapper) {
settings.wrapper = document.getElementById(options.wrapper);
}
// if wrapperid is not defined (null)
if (!settings.wrapper) {
// add the canvas to document.body
settings.wrapper = document.body;
}
settings.wrapper.appendChild(canvas);
// stop here if not supported
if (!canvas.getContext) {
return false;
}
/**
* A reference to the current video renderer
* @public
* @memberOf me.video
* @name renderer
* @type {me.Renderer|me.CanvasRenderer|me.WebGLRenderer}
*/
switch (settings.renderer) {
case api.WEBGL:
this.renderer = new me.WebGLRenderer(canvas, game_width, game_height, settings);
break;
case api.AUTO:
this.renderer = autoDetectRenderer(canvas, game_width, game_height, settings);
break;
default:
this.renderer = new me.CanvasRenderer(canvas, game_width, game_height, settings);
break;
}
// adjust CSS style for High-DPI devices
var ratio = me.device.getPixelRatio();
if (ratio > 1) {
canvas.style.width = (canvas.width / ratio) + "px";
canvas.style.height = (canvas.height / ratio) + "px";
}
// 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));
}
me.game.init();
// trigger an initial resize();
me.video.onresize();
return true;
};
/**
* 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 {DOMRect}
*/
api.getPos = function (c) {
if (typeof c === "undefined") {
if (api._canvasOffset === null) {
c = this.renderer.getScreenCanvas();
api._canvasOffset = c && c.getBoundingClientRect ? c.getBoundingClientRect() : { left : 0, top : 0 };
}
return api._canvasOffset;
} else {
return c.getBoundingClientRect ? c.getBoundingClientRect() : { left : 0, top : 0 };
}
};
/**
* 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;
// trigger a resize
// defer it to ensure everything is properly intialized
this.onresize.defer(this);
};
/**
* Create and return a new Canvas
* @name createCanvas
* @memberOf me.video
* @function
* @param {Number} width width
* @param {Number} height height
* @param {Boolean} [screencanvas=false] set to true if this canvas renders directly to the screen
* @return {Canvas}
*/
api.createCanvas = function (width, height, screencanvas) {
if (width === 0 || height === 0) {
throw new api.Error("width or height was zero, Canvas could not be initialized !");
}
var _canvas = document.createElement("canvas");
if ((screencanvas === true) && (me.device.cocoon) && (me.device.android2 !== true)) {
// http://docs.cocoon.io/article/screencanvas/
_canvas.screencanvas = true;
}
_canvas.width = width || canvas.width;
_canvas.height = height || canvas.height;
return _canvas;
};
/**
* return a reference to the wrapper
* @name getWrapper
* @memberOf me.video
* @function
* @return {Document}
*/
api.getWrapper = function () {
return settings.wrapper;
};
/**
* callback for window resize event
* @ignore
*/
api.onresize = function () {
// default (no scaling)
var scaleX = 1, scaleY = 1;
// invalidate the current canvas position cache so that it
// get recalculated the next time getPos is called
api._canvasOffset = null;
if (settings.autoScale) {
var parentNodeWidth;
var parentNodeHeight;
var parentNode = me.video.renderer.getScreenCanvas().parentNode;
if (typeof (parentNode) !== "undefined") {
if (settings.useParentDOMSize && typeof parentNode.getBoundingClientRect === "function") {
var rect = parentNode.getBoundingClientRect();
parentNodeWidth = rect.width || (rect.right - rect.left);
parentNodeHeight = rect.height || (rect.bottom - rect.top);
} else {
// for cased where DOM is not implemented and so parentNode (e.g. Ejecta)
parentNodeWidth = parentNode.width;
parentNodeHeight = parentNode.height;
}
}
var _max_width = Math.min(maxWidth, parentNodeWidth || window.innerWidth);
var _max_height = Math.min(maxHeight, parentNodeHeight || window.innerHeight);
var screenRatio = _max_width / _max_height;
var sWidth = Infinity;
var sHeight = Infinity;
if (
(settings.scaleMethod === "fill-min" && screenRatio > designRatio) ||
(settings.scaleMethod === "fill-max" && screenRatio < designRatio) ||
(settings.scaleMethod === "flex-width")
) {
// resize the display canvas to fill the parent container
sWidth = Math.min(maxWidth, designHeight * screenRatio);
scaleX = scaleY = _max_width / sWidth;
sWidth = ~~(sWidth + 0.5);
this.renderer.resize(sWidth, designHeight);
me.game.viewport.resize(sWidth, designHeight);
/*
* XXX: Workaround for not updating container child-bounds
* automatically (it's expensive!)
*/
me.game.world.updateChildBounds();
}
else if (
(settings.scaleMethod === "fill-min" && screenRatio < designRatio) ||
(settings.scaleMethod === "fill-max" && screenRatio > designRatio) ||
(settings.scaleMethod === "flex-height")
) {
// resize the display canvas to fill the parent container
sHeight = Math.min(maxHeight, designWidth * (_max_height / _max_width));
scaleX = scaleY = _max_height / sHeight;
sHeight = ~~(sHeight + 0.5);
this.renderer.resize(designWidth, sHeight);
me.game.viewport.resize(designWidth, sHeight);
/*
* XXX: Workaround for not updating container child-bounds
* automatically (it's expensive!)
*/
me.game.world.updateChildBounds();
}
else if (settings.scaleMethod === "flex") {
// resize the display canvas to fill the parent container
this.renderer.resize(_max_width, _max_height);
me.game.viewport.resize(_max_width, _max_height);
/*
* XXX: Workaround for not updating container child-bounds
* automatically (it's expensive!)
*/
me.game.world.updateChildBounds();
}
else if (settings.scaleMethod === "stretch") {
// scale the display canvas to fit with the parent container
scaleX = _max_width / designWidth;
scaleY = _max_height / designHeight;
}
else {
// scale the display canvas to fit the parent container
// make sure we maintain the original aspect ratio
if (screenRatio < designRatio) {
scaleX = scaleY = _max_width / designWidth;
}
else {
scaleX = scaleY = _max_height / designHeight;
}
}
// adjust scaling ratio based on the device pixel ratio
scaleX *= me.device.getPixelRatio();
scaleY *= me.device.getPixelRatio();
if (deferResizeId) {
// cancel any previous pending resize
clearTimeout(deferResizeId);
}
deferResizeId = me.video.updateDisplaySize.defer(this, scaleX, scaleY);
}
};
/**
* 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) {
// invalidate the current canvas position cache so that it
// get recalculated the next time getPos is called
api._canvasOffset = null;
// update the global scale variable
me.sys.scale.set(scaleX, scaleY);
// renderer resize logic
this.renderer.scaleCanvas(scaleX, scaleY);
me.game.repaint();
// clear the timeout id
deferResizeId = 0;
};
// return our api
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a base renderer object
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Canvas} canvas The html canvas tag to draw to on screen.
* @param {Number} width The width of the canvas without scaling
* @param {Number} height The height of the canvas without scaling
* @param {Object} [options] The renderer parameters
* @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering
* @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing
* @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled)
* @param {Boolean} [options.subPixel=false] Whether to enable subpixel rendering (performance hit when enabled)
* @param {Boolean} [options.verbose=false] Enable the verbose mode that provides additional details as to what the renderer is doing
* @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied
* @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied
*/
me.Renderer = me.Object.extend(
/** @scope me.Renderer.prototype */
{
/**
* @ignore
*/
init : function (c, width, height, options) {
options = options || {};
// rendering options
this.transparent = !!(options.transparent);
this.doubleBuffering = !!(options.doubleBuffering);
this.antiAlias = !!(options.antiAlias);
this.subPixel = !!(options.subPixel);
this.verbose = !!(options.verbose);
this.gameWidthZoom = options.zoomX || width;
this.gameHeightZoom = options.zoomY || height;
// canvas object and context
this.canvas = this.backBufferCanvas = c;
this.context = null;
// global color
this.currentColor = new me.Color(255, 255, 255, 1.0);
// default uvOffset
this.uvOffset = 0;
return this;
},
/**
* @ignore
*/
applyRGBFilter : function (object, effect, option) {
//create a output canvas using the given canvas or image size
var _context = this.getContext2d(me.video.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":
var refColor = me.pool.pull("me.Color").parseCSS(option);
var pixel = me.pool.pull("me.Color");
for (i = 0, n = pix.length; i < n; i += 4) {
pixel.setColor(pix[i], pix[i + 1], pix[i + 2]);
if (pixel.equals(refColor)) {
pix[i + 3] = 0;
}
}
me.pool.push(refColor);
me.pool.push(pixel);
break;
default:
return null;
}
// put our modified image back in the new filtered canvas
_context.putImageData(imgpix, 0, 0);
// return it
return _context;
},
/**
* @ignore
*/
clear : function () {},
/**
* @ignore
*/
reset : function () {
this.resetTransform();
this.cache.reset();
},
/**
* return a reference to the system canvas
* @name getCanvas
* @memberOf me.Renderer
* @function
* @return {Canvas}
*/
getCanvas : function () {
return this.backBufferCanvas;
},
/**
* return a reference to the screen canvas
* @name getScreenCanvas
* @memberOf me.Renderer
* @function
* @return {Canvas}
*/
getScreenCanvas : function () {
return this.canvas;
},
/**
* return a reference to the screen canvas corresponding 2d Context
* (will return buffered context if double buffering is enabled, or a reference to the Screen Context)
* @name getScreenContext
* @memberOf me.Renderer
* @function
* @return {Context2d}
*/
getScreenContext : function () {
return this.context;
},
/**
* Returns the 2D Context object of the given Canvas
* Also configures anti-aliasing based on constructor options.
* @name getContext2d
* @memberOf me.Renderer
* @function
* @param {Canvas} canvas
* @param {Boolean} [opaque=false] True to disable transparency
* @return {Context2d}
*/
getContext2d : function (c, opaque) {
if (typeof c === "undefined" || c === null) {
throw new me.video.Error(
"You must pass a canvas element in order to create " +
"a 2d context"
);
}
if (typeof c.getContext === "undefined") {
throw new me.video.Error(
"Your browser does not support HTML5 canvas."
);
}
var _context;
if (me.device.cocoon) {
// cocoonJS specific extension
_context = c.getContext("2d", {
"antialias" : this.antiAlias,
"alpha" : !opaque
});
}
else {
_context = c.getContext("2d", {
"alpha" : !opaque
});
}
if (!_context.canvas) {
_context.canvas = c;
}
this.setAntiAlias(_context, this.antiAlias);
return _context;
},
/**
* return the width of the system Canvas
* @name getWidth
* @memberOf me.Renderer
* @function
* @return {Number}
*/
getWidth : function () {
return this.backBufferCanvas.width;
},
/**
* return the height of the system Canvas
* @name getHeight
* @memberOf me.Renderer
* @function
* @return {Number}
*/
getHeight : function () {
return this.backBufferCanvas.height;
},
/**
* get the current fill & stroke style color.
* @name getColor
* @memberOf me.Renderer
* @function
* @param {me.Color} current global color
*/
getColor : function () {
return this.currentColor;
},
/**
* return the current global alpha
* @name globalAlpha
* @memberOf me.Renderer
* @function
* @return {Number}
*/
globalAlpha : function () {
return this.currentColor.glArray[3];
},
/**
* resizes the system canvas
* @name resize
* @memberOf me.Renderer
* @function
* @param {Number} width new width of the canvas
* @param {Number} height new height of the canvas
*/
resize : function (width, height) {
this.backBufferCanvas.width = width;
this.backBufferCanvas.height = height;
},
/**
* enable/disable image smoothing (scaling interpolation) for the specified 2d Context
* (!) this might not be supported by all browsers
* @name setAntiAlias
* @memberOf me.Renderer
* @function
* @param {Context2d} context
* @param {Boolean} [enable=false]
*/
setAntiAlias : function (context, enable) {
if (typeof(context) !== "undefined") {
// enable/disable antialis on the given context
me.agent.setPrefixed("imageSmoothingEnabled", enable === true, context);
}
// disable antialias CSS scaling on the main canvas
var cssStyle = context.canvas.style["image-rendering"];
if (enable === false && (cssStyle === "" || cssStyle === "auto")) {
// if a specific value is set through CSS or equal to the standard "auto" one
context.canvas.style["image-rendering"] = "pixelated";
} else if (enable === true && cssStyle === "pixelated") {
// if set to the standard "pixelated"
context.canvas.style["image-rendering"] = "auto";
}
},
/**
* @ignore
*/
drawFont : function (/*bounds*/) {}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a basic texture cache object
* @ignore
*/
me.Renderer.TextureCache = me.Object.extend({
/**
* @ignore
*/
init : function (max_size) {
this.max_size = max_size || Infinity;
this.reset();
},
/**
* @ignore
*/
reset : function () {
this.cache = new Map();
this.units = new Map();
this.length = 0;
},
/**
* @ignore
*/
validate : function () {
if (this.length >= this.max_size) {
// TODO: Merge textures instead of throwing an exception
throw new me.video.Error(
"Texture cache overflow: " + this.max_size +
" texture units available."
);
}
},
/**
* @ignore
*/
get : function (image, atlas) {
if (!this.cache.has(image)) {
if (!atlas) {
atlas = me.video.renderer.Texture.prototype.createAtlas.apply(
me.video.renderer.Texture.prototype,
[image.width, image.height, image.src ? me.utils.getBasename(image.src) : undefined]
);
}
this.put(image, new me.video.renderer.Texture(atlas, image, false));
}
return this.cache.get(image);
},
/**
* @ignore
*/
put : function (image, texture) {
this.validate();
this.cache.set(image, texture);
this.units.set(texture, this.length++);
},
/**
* @ignore
*/
getUnit : function (texture) {
return this.units.get(texture);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a canvas renderer object
* @class
* @extends me.Renderer
* @memberOf me
* @constructor
* @param {Canvas} canvas The html canvas tag to draw to on screen.
* @param {Number} width The width of the canvas without scaling
* @param {Number} height The height of the canvas without scaling
* @param {Object} [options] The renderer parameters
* @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering
* @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing
* @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled)
* @param {Boolean} [options.subPixel=false] Whether to enable subpixel renderering (performance hit when enabled)
* @param {Boolean} [options.textureSeamFix=true] enable the texture seam fix when rendering Tile when antiAlias is off for the canvasRenderer
* @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied
* @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied
*/
me.CanvasRenderer = me.Renderer.extend(
/** @scope me.CanvasRenderer.prototype */
{
/**
* @ignore
*/
init : function (c, width, height, options) {
// parent constructor
me.Renderer.prototype.init.apply(this, [c, width, height, options]);
// defined the 2d context
this.context = this.getContext2d(this.canvas, !this.transparent);
// create the back buffer if we use double buffering
if (this.doubleBuffering) {
this.backBufferCanvas = me.video.createCanvas(width, height, false);
this.backBufferContext2D = this.getContext2d(this.backBufferCanvas);
if (this.transparent) {
// Clears the front buffer for each frame blit
this.context.globalCompositeOperation = "copy";
}
}
else {
this.backBufferCanvas = this.canvas;
this.backBufferContext2D = this.context;
}
// apply the default color to the 2d context
this.setColor(this.currentColor);
// create a texture cache
this.cache = new me.Renderer.TextureCache();
if (options.textureSeamFix !== false && !this.antiAlias) {
// enable the tile texture seam fix with the canvas renderer
this.uvOffset = 1;
}
return this;
},
/**
* prepare the framebuffer for drawing a new frame
* @name clear
* @memberOf me.CanvasRenderer
* @function
*/
clear : function () {
if (this.transparent) {
this.clearColor("rgba(0,0,0,0)", true);
}
},
/**
* render the main framebuffer on screen
* @name flush
* @memberOf me.CanvasRenderer
* @function
*/
flush : function () {
if (this.doubleBuffering) {
this.context.drawImage(
this.backBufferCanvas, 0, 0,
this.backBufferCanvas.width, this.backBufferCanvas.height,
0, 0,
this.gameWidthZoom, this.gameHeightZoom
);
}
},
/**
* Clears the main framebuffer with the given color
* @name clearColor
* @memberOf me.CanvasRenderer
* @function
* @param {me.Color|String} color CSS color.
* @param {Boolean} [opaque=false] Allow transparency [default] or clear the surface completely [true]
*/
clearColor : function (col, opaque) {
var _ctx = this.backBufferContext2D;
var _canvas = _ctx.canvas;
_ctx.save();
_ctx.setTransform(1, 0, 0, 1, 0, 0);
_ctx.globalCompositeOperation = opaque ? "copy" : "source-over";
_ctx.fillStyle = (col instanceof me.Color) ? col.toRGBA() : col;
_ctx.fillRect(0, 0, _canvas.width, _canvas.height);
_ctx.restore();
},
/**
* Sets all pixels in the given rectangle to transparent black,
* erasing any previously drawn content.
* @name clearRect
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x x axis of the coordinate for the rectangle starting point.
* @param {Number} y y axis of the coordinate for the rectangle starting point.
* @param {Number} width The rectangle's width.
* @param {Number} height The rectangle's height.
*/
clearRect : function (x, y, width, height) {
this.backBufferContext2D.clearRect(x, y, width, height);
},
/**
* Create a pattern with the specified repition
* @name createPattern
* @memberOf me.CanvasRenderer
* @function
* @param {image} image Source image
* @param {String} repeat Define how the pattern should be repeated
* @return {CanvasPattern}
* @see me.ImageLayer#repeat
* @example
* var tileable = renderer.createPattern(image, "repeat");
* var horizontal = renderer.createPattern(image, "repeat-x");
* var vertical = renderer.createPattern(image, "repeat-y");
* var basic = renderer.createPattern(image, "no-repeat");
*/
createPattern : function (image, repeat) {
return this.backBufferContext2D.createPattern(image, repeat);
},
/**
* Draw an image using the canvas api
* @name drawImage
* @memberOf me.CanvasRenderer
* @function
* @param {image} image Source image
* @param {Number} sx Source x-coordinate
* @param {Number} sy Source y-coordinate
* @param {Number} sw Source width
* @param {Number} sh Source height
* @param {Number} dx Destination x-coordinate
* @param {Number} dy Destination y-coordinate
* @param {Number} dw Destination width
* @param {Number} dh Destination height
* @example
* // Can be used in three ways:
* renderer.drawImage(image, dx, dy);
* renderer.drawImage(image, dx, dy, dw, dh);
* renderer.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
* // dx, dy, dw, dh being the destination target & dimensions. sx, sy, sw, sh being the position & dimensions to take from the image
*/
drawImage : function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
if (this.subPixel === false) {
if (typeof sw === "undefined") {
sw = dw = image.width;
sh = dh = image.height;
dx = sx;
dy = sy;
sx = 0;
sy = 0;
}
else if (typeof dx === "undefined") {
dx = sx;
dy = sy;
dw = sw;
dh = sh;
sw = image.width;
sh = image.height;
sx = 0;
sy = 0;
}
this.backBufferContext2D.drawImage(image, sx, sy, sw, sh, ~~dx, ~~dy, dw, dh);
} else {
this.backBufferContext2D.drawImage.apply(this.backBufferContext2D, arguments);
}
},
/**
* Draw a pattern within the given rectangle.
* @name drawPattern
* @memberOf me.CanvasRenderer
* @function
* @param {CanvasPattern} pattern Pattern object
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
* @see me.CanvasRenderer#createPattern
*/
drawPattern : function (pattern, x, y, width, height) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
var fillStyle = this.backBufferContext2D.fillStyle;
this.backBufferContext2D.fillStyle = pattern;
this.backBufferContext2D.fillRect(x, y, width, height);
this.backBufferContext2D.fillStyle = fillStyle;
},
/**
* Fill an arc at the specified coordinates with given radius, start and end points
* @name fillArc
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x arc center point x-axis
* @param {Number} y arc center point y-axis
* @param {Number} radius
* @param {Number} start start angle in radians
* @param {Number} end end angle in radians
* @param {Boolean} [antiClockwise=false] draw arc anti-clockwise
*/
fillArc : function (x, y, radius, start, end, antiClockwise) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.translate(x + radius, y + radius);
this.backBufferContext2D.beginPath();
this.backBufferContext2D.arc(0, 0, radius, start, end, antiClockwise || false);
this.backBufferContext2D.fill();
this.backBufferContext2D.closePath();
this.translate(- (x + radius), -(y + radius));
},
/**
* Draw a filled rectangle at the specified coordinates
* @name fillRect
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
*/
fillRect : function (x, y, width, height) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.backBufferContext2D.fillRect(x, y, width, height);
},
/**
* return a reference to the system 2d Context
* @name getContext
* @memberOf me.CanvasRenderer
* @function
* @return {Context2d}
*/
getContext : function () {
return this.backBufferContext2D;
},
/**
* return a reference to the font 2d Context
* @ignore
*/
getFontContext : function () {
// in canvas more we can directly use the 2d context
return this.getContext();
},
/**
* resets the canvas transform to identity
* @name resetTransform
* @memberOf me.CanvasRenderer
* @function
*/
resetTransform : function () {
this.backBufferContext2D.setTransform(1, 0, 0, 1, 0, 0);
},
/**
* scales the canvas & 2d Context
* @name scaleCanvas
* @memberOf me.CanvasRenderer
* @function
*/
scaleCanvas : function (scaleX, scaleY) {
this.canvas.width = this.gameWidthZoom = this.backBufferCanvas.width * scaleX;
this.canvas.height = this.gameHeightZoom = this.backBufferCanvas.height * scaleY;
// adjust CSS style for High-DPI devices
if (me.device.getPixelRatio() > 1) {
this.canvas.style.width = (this.canvas.width / me.device.getPixelRatio()) + "px";
this.canvas.style.height = (this.canvas.height / me.device.getPixelRatio()) + "px";
}
if (this.doubleBuffering && this.transparent) {
// Clears the front buffer for each frame blit
this.context.globalCompositeOperation = "copy";
}
this.setAntiAlias(this.context, this.antiAlias);
this.flush();
},
/**
* save the canvas context
* @name save
* @memberOf me.CanvasRenderer
* @function
*/
save : function () {
this.backBufferContext2D.save();
},
/**
* restores the canvas context
* @name restore
* @memberOf me.CanvasRenderer
* @function
*/
restore : function () {
this.backBufferContext2D.restore();
this.currentColor.glArray[3] = this.backBufferContext2D.globalAlpha;
},
/**
* rotates the canvas context
* @name rotate
* @memberOf me.CanvasRenderer
* @function
* @param {Number} angle in radians
*/
rotate : function (angle) {
this.backBufferContext2D.rotate(angle);
},
/**
* scales the canvas context
* @name scale
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x
* @param {Number} y
*/
scale : function (x, y) {
this.backBufferContext2D.scale(x, y);
},
/**
* Sets the fill & stroke style colors for the context.
* @name setColor
* @memberOf me.CanvasRenderer
* @function
* @param {me.Color|String} color css color value
*/
setColor : function (color) {
this.backBufferContext2D.strokeStyle =
this.backBufferContext2D.fillStyle = (
color instanceof me.Color ?
color.toRGBA() :
color
);
},
/**
* Sets the global alpha on the canvas context
* @name setGlobalAlpha
* @memberOf me.CanvasRenderer
* @function
* @param {Number} alpha 0.0 to 1.0 values accepted.
*/
setGlobalAlpha : function (a) {
this.backBufferContext2D.globalAlpha = this.currentColor.glArray[3] = a;
},
/**
* sets the line width on the context
* @name setLineWidth
* @memberOf me.CanvasRenderer
* @function
* @param {Number} width Line width
*/
setLineWidth : function (width) {
this.backBufferContext2D.lineWidth = width;
},
/**
* Stroke an arc at the specified coordinates with given radius, start and end points
* @name strokeArc
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x arc center point x-axis
* @param {Number} y arc center point y-axis
* @param {Number} radius
* @param {Number} start start angle in radians
* @param {Number} end end angle in radians
* @param {Boolean} [antiClockwise=false] draw arc anti-clockwise
*/
strokeArc : function (x, y, radius, start, end, antiClockwise) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.translate(x + radius, y + radius);
this.backBufferContext2D.beginPath();
this.backBufferContext2D.arc(0, 0, radius, start, end, antiClockwise || false);
this.backBufferContext2D.stroke();
this.backBufferContext2D.closePath();
this.translate(-(x + radius), -(y + radius));
},
/**
* Stroke an ellipse at the specified coordinates with given radius, start and end points
* @name strokeEllipse
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x arc center point x-axis
* @param {Number} y arc center point y-axis
* @param {Number} w horizontal radius of the ellipse
* @param {Number} h vertical radius of the ellipse
*/
strokeEllipse : function (x, y, w, h) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
var hw = w,
hh = h,
lx = x - hw,
rx = x + hw,
ty = y - hh,
by = y + hh;
var xmagic = hw * 0.551784,
ymagic = hh * 0.551784,
xmin = x - xmagic,
xmax = x + xmagic,
ymin = y - ymagic,
ymax = y + ymagic;
this.backBufferContext2D.beginPath();
this.backBufferContext2D.moveTo(x, ty);
this.backBufferContext2D.bezierCurveTo(xmax, ty, rx, ymin, rx, y);
this.backBufferContext2D.bezierCurveTo(rx, ymax, xmax, by, x, by);
this.backBufferContext2D.bezierCurveTo(xmin, by, lx, ymax, lx, y);
this.backBufferContext2D.bezierCurveTo(lx, ymin, xmin, ty, x, ty);
this.backBufferContext2D.stroke();
},
/**
* Stroke a line of the given two points
* @name strokeLine
* @memberOf me.CanvasRenderer
* @function
* @param {Number} startX the start x coordinate
* @param {Number} startY the start y coordinate
* @param {Number} endX the end x coordinate
* @param {Number} endY the end y coordinate
*/
strokeLine : function (startX, startY, endX, endY) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.backBufferContext2D.beginPath();
this.backBufferContext2D.moveTo(startX, startY);
this.backBufferContext2D.lineTo(endX, endY);
this.backBufferContext2D.stroke();
},
/**
* Strokes a me.Polygon on the screen with a specified color
* @name strokePolygon
* @memberOf me.CanvasRenderer
* @function
* @param {me.Polygon} poly the shape to draw
*/
strokePolygon : function (poly) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.translate(poly.pos.x, poly.pos.y);
this.backBufferContext2D.beginPath();
this.backBufferContext2D.moveTo(poly.points[0].x, poly.points[0].y);
var point;
for (var i = 1; i < poly.points.length; i++) {
point = poly.points[i];
this.backBufferContext2D.lineTo(point.x, point.y);
}
this.backBufferContext2D.lineTo(poly.points[0].x, poly.points[0].y);
this.backBufferContext2D.stroke();
this.backBufferContext2D.closePath();
this.translate(-poly.pos.x, -poly.pos.y);
},
/**
* Stroke a rectangle at the specified coordinates with a given color
* @name strokeRect
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
*/
strokeRect : function (x, y, width, height) {
if (this.backBufferContext2D.globalAlpha < 1 / 255) {
// Fast path: don't draw fully transparent
return;
}
this.backBufferContext2D.strokeRect(x, y, width, height);
},
/**
* draw the given shape
* @name drawShape
* @memberOf me.CanvasRenderer
* @function
* @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object
*/
drawShape : function (shape) {
if (shape.shapeType === "Rectangle") {
this.strokeRect(shape.left, shape.top, shape.width, shape.height);
} else if (shape instanceof me.Line || shape instanceof me.Polygon) {
this.strokePolygon(shape);
} else if (shape instanceof me.Ellipse) {
if (shape.radiusV.x === shape.radiusV.y) {
// it's a circle
this.strokeArc(
shape.pos.x - shape.radius,
shape.pos.y - shape.radius,
shape.radius,
0,
2 * Math.PI
);
} else {
// it's an ellipse
this.strokeEllipse(
shape.pos.x,
shape.pos.y,
shape.radiusV.x,
shape.radiusV.y
);
}
}
},
/**
* Resets (overrides) the renderer transformation matrix to the
* identity one, and then apply the given transformation matrix.
* @name setTransform
* @memberOf me.CanvasRenderer
* @function
* @param {me.Matrix2d} mat2d Matrix to transform by
*/
setTransform : function (mat2d) {
this.resetTransform();
this.transform(mat2d);
},
/**
* Multiply given matrix into the renderer tranformation matrix
* @name transform
* @memberOf me.CanvasRenderer
* @function
* @param {me.Matrix2d} mat2d Matrix to transform by
*/
transform : function (mat2d) {
var a = mat2d.val;
var tx = a[6],
ty = a[7];
if (this.subPixel === false) {
tx = ~~tx;
ty = ~~ty;
}
this.backBufferContext2D.transform(
a[0],
a[1],
a[3],
a[4],
tx,
ty
);
},
/**
* Translates the context to the given position
* @name translate
* @memberOf me.CanvasRenderer
* @function
* @param {Number} x
* @param {Number} y
*/
translate : function (x, y) {
if (this.subPixel === false) {
this.backBufferContext2D.translate(~~x, ~~y);
} else {
this.backBufferContext2D.translate(x, y);
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 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
* For portability, a global reference to this class is available through the default renderer: {@link me.video.renderer}.Texture
*
* Currently supports :
* - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export
* - [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}
* - Standard (fixed cell size) spritesheet : through a {framewidth:xx, frameheight:xx, anchorPoint:me.Vector2d} object
* @class
* @extends me.Object
* @memberOf me.CanvasRenderer
* @name Texture
* @constructor
* @param {Object} atlas atlas information. See {@link me.loader.getJSON}
* @param {HTMLImageElement|HTMLCanvasElement} [source=atlas.meta.image] Image source
* @param {Boolean} [cached=false] Use true to skip caching this Texture
* @example
* // create a texture atlas from a JSON Object
* texture = new me.video.renderer.Texture(
* me.loader.getJSON("texture"),
* me.loader.getImage("texture")
* );
*
* // create a texture atlas for a spritesheet, with (optional) an anchorPoint in the center of each frame
* texture = new me.video.renderer.Texture(
* { framewidth : 32, frameheight : 32, anchorPoint : new me.Vector2d(0.5, 0.5) },
* me.loader.getImage("spritesheet")
* );
*/
me.CanvasRenderer.prototype.Texture = me.Object.extend(
/** @scope me.video.renderer.Texture.prototype */
{
/**
* @ignore
*/
init : function (atlas, source, cache) {
/**
* to identify the atlas format (e.g. texture packer)
* @ignore
*/
this.format = null;
/**
* the texture source itself
* @type {HTMLImageElement|HTMLCanvasElement}
* @ignore
*/
this.source = source || null;
/**
* the atlas dictionnary
* @ignore
*/
this.atlas = null;
if (typeof (atlas) !== "undefined") {
if (typeof(atlas.meta) !== "undefined") {
// Texture Packer
if (atlas.meta.app.includes("texturepacker")) {
this.format = "texturepacker";
// set the texture
if (typeof(texture) === "undefined") {
var image = atlas.meta.image;
this.source = me.utils.getImage(image);
if (!this.source) {
throw new me.video.renderer.Texture.Error(
"Atlas texture '" + image + "' not found"
);
}
}
this.repeat = "no-repeat";
}
// ShoeBox
else if (atlas.meta.app.includes("ShoeBox")) {
if (!atlas.meta.exporter || !atlas.meta.exporter.includes("melonJS")) {
throw new me.video.renderer.Texture.Error(
"ShoeBox requires the JSON exporter : " +
"https://github.com/melonjs/melonJS/tree/master/media/shoebox_JSON_export.sbx"
);
}
this.format = "ShoeBox";
this.repeat = "no-repeat";
}
// Internal texture atlas
else if (atlas.meta.app.includes("melonJS")) {
this.format = "melonJS";
this.repeat = atlas.meta.repeat || "no-repeat";
}
// initialize the atlas
this.atlas = this.parse(atlas);
} else {
// a regular spritesheet ?
if (typeof(atlas.framewidth) !== "undefined" &&
typeof(atlas.frameheight) !== "undefined") {
this.format = "Spritesheet (fixed cell size)";
if (typeof(this.source) !== undefined) {
// overwrite if specified
atlas.image = this.source;
}
// initialize the atlas
this.atlas = this.parseFromSpriteSheet(atlas);
this.repeat = "no-repeat";
}
}
}
// if format not recognized
if (!this.atlas) {
throw new me.video.renderer.Texture.Error("texture atlas format not supported");
}
// Add self to TextureCache if cache !== false
if (cache !== false) {
if (cache instanceof me.Renderer.TextureCache) {
cache.put(this.source, this);
} else {
me.video.renderer.cache.put(this.source, this);
}
}
},
/**
* create a simple 1 frame texture atlas based on the given parameters
* @ignore
*/
createAtlas : function (width, height, name, repeat) {
return {
"meta" : {
"app" : "melonJS",
"size" : { "w" : width, "h" : height },
"repeat" : repeat || "no-repeat"
},
"frames" : [{
"filename" : name || "default",
"frame" : { "x" : 0, "y" : 0, "w" : width, "h" : height }
}]
};
},
/**
* build an atlas from the given data
* @ignore
*/
parse : function (data) {
var atlas = {};
data.frames.forEach(function (frame) {
// fix wrongly formatted JSON (e.g. last dummy object in ShoeBox)
if (frame.hasOwnProperty("filename")) {
// Source coordinates
var s = frame.frame;
var originX, originY;
// Pixel-based offset origin from the top-left of the source frame
var hasTextureAnchorPoint = (frame.spriteSourceSize && frame.sourceSize && frame.pivot);
if (hasTextureAnchorPoint) {
originX = (frame.sourceSize.w * frame.pivot.x) - ((frame.trimmed) ? frame.spriteSourceSize.x : 0);
originY = (frame.sourceSize.h * frame.pivot.y) - ((frame.trimmed) ? frame.spriteSourceSize.y : 0);
}
atlas[frame.filename] = {
name : frame.filename, // frame name
offset : new me.Vector2d(s.x, s.y),
anchorPoint : (hasTextureAnchorPoint) ? new me.Vector2d(originX / s.w, originY / s.h) : null,
width : s.w,
height : s.h,
angle : (frame.rotated === true) ? nhPI : 0
};
}
});
return atlas;
},
/**
* build an atlas from the given spritesheet
* @ignore
*/
parseFromSpriteSheet : function (data) {
var atlas = {};
var image = data.image;
var spacing = data.spacing || 0;
var margin = data.margin || 0;
var width = image.width;
var height = image.height;
// calculate the sprite count (line, col)
var spritecount = new me.Vector2d(
~~((width - margin + spacing) / (data.framewidth + spacing)),
~~((height - margin + spacing) / (data.frameheight + spacing))
);
// verifying the texture size
if ((width % (data.framewidth + spacing)) !== 0 ||
(height % (data.frameheight + spacing)) !== 0) {
// "truncate size"
width = spritecount.x * (data.framewidth + spacing);
height = spritecount.y * (data.frameheight + spacing);
// warning message
console.warn(
"Spritesheet Texture for image: " + image.src +
" is not divisible by " + (data.framewidth + spacing) +
"x" + (data.frameheight + spacing) +
", truncating effective size to " + width + "x" + height
);
}
// build the local atlas
for (var frame = 0, count = spritecount.x * spritecount.y; frame < count; frame++) {
atlas["" + frame] = {
name: "" + frame,
offset: new me.Vector2d(
margin + (spacing + data.framewidth) * (frame % spritecount.x),
margin + (spacing + data.frameheight) * ~~(frame / spritecount.x)
),
anchorPoint: (data.anchorPoint || null),
width: data.framewidth,
height: data.frameheight,
angle: 0
};
}
return atlas;
},
/**
* return the Atlas dictionnary
* @name getAtlas
* @memberOf me.CanvasRenderer.Texture
* @function
* @return {Object}
*/
getAtlas : function () {
return this.atlas;
},
/**
* return the Atlas texture
* @name getTexture
* @memberOf me.CanvasRenderer.Texture
* @function
* @return {HTMLImageElement|HTMLCanvasElement}
*/
getTexture : function () {
return this.source;
},
/**
* return a normalized region/frame information for the specified sprite name
* @name getRegion
* @memberOf me.CanvasRenderer.Texture
* @function
* @param {String} name name of the sprite
* @return {Object}
*/
getRegion : function (name) {
return this.atlas[name];
},
/**
* Create a sprite object using the first region found using the specified name
* @name createSpriteFromName
* @memberOf me.CanvasRenderer.Texture
* @function
* @param {String} name name of the sprite
* @param {Object} [settings] Additional settings passed to the {@link me.Sprite} contructor
* @return {me.Sprite}
* @example
* // create a new texture atlas object under the `game` namespace
* game.texture = new me.video.renderer.Texture(
* 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, settings) {
// instantiate a new sprite object
return me.pool.pull(
"me.Sprite",
0, 0,
Object.assign({
image: this,
region : name
}, settings || {})
);
},
/**
* Create an animation object using the first region found using all specified names
* @name createAnimationFromName
* @memberOf me.CanvasRenderer.Texture
* @function
* @param {String[]|Number[]} names list of names for each sprite
* (when manually creating a Texture out of a spritesheet, only numeric values are authorized)
* @param {Object} [settings] Additional settings passed to the {@link me.Sprite} contructor
* @return {me.Sprite}
* @example
* // create a new texture atlas object under the `game` namespace
* game.texture = new me.video.renderer.Texture(
* me.loader.getJSON("texture"),
* me.loader.getImage("texture")
* );
*
* // create a new Sprite 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 animation
* 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, settings) {
var tpAtlas = [], indices = {};
var width = 0, height = 0;
var region;
// iterate through the given names
// and create a "normalized" atlas
for (var i = 0; i < names.length; ++i) {
region = this.getRegion(names[i]);
if (region == null) {
// throw an error
throw new me.video.renderer.Texture.Error("Texture - region for " + names[i] + " not found");
}
tpAtlas[i] = region;
// save the corresponding index
indices[names[i]] = i;
// calculate the max size of a frame
width = Math.max(region.width, width);
height = Math.max(region.height, height);
}
// instantiate a new animation sheet object
return new me.Sprite(0, 0, Object.assign({
image: this,
framewidth: width,
frameheight: height,
margin: 0,
spacing: 0,
atlas: tpAtlas,
atlasIndices: indices
}, settings || {}));
}
});
/**
* Base class for Texture exception handling.
* @name Error
* @class
* @memberOf me.CanvasRenderer.Texture
* @constructor
* @param {String} msg Error message.
*/
me.CanvasRenderer.prototype.Texture.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.CanvasRenderer.Texture.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* The WebGL Shader singleton
* There is no constructor function for me.video.shader
* @namespace me.video.shader
* @memberOf me.video
*/
me.video.shader = (function () {
/**
* Public API
* @ignore
*/
var api = {};
/**
* Compile GLSL into a shader object
* @private
*/
function getShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new me.video.Error(gl.getShaderInfoLog(shader));
}
return shader;
}
/**
* Hash map of GLSL data types to WebGL Uniform methods
* @private
*/
var fnHash = {
"bool" : "1i",
"int" : "1i",
"float" : "1f",
"vec2" : "2fv",
"vec3" : "3fv",
"vec4" : "4fv",
"bvec2" : "2iv",
"bvec3" : "3iv",
"bvec4" : "4iv",
"ivec2" : "2iv",
"ivec3" : "3iv",
"ivec4" : "4iv",
"mat2" : "Matrix2fv",
"mat3" : "Matrix3fv",
"mat4" : "Matrix4fv",
"sampler2D" : "1i"
};
/**
* Create a shader program (with bindings) using the given GLSL sources
* @name createShader
* @memberOf me.video.shader
* @function
* @param {WebGLContext} gl WebGL Context
* @param {String} vertex Vertex shader source
* @param {String} fragment Fragment shader source
* @return {Object} A reference to the WebGL Shader Program
*/
api.createShader = function (gl, vertex, fragment) {
var program = {
"attributes" : {},
"uniforms" : {},
"handle" : null
},
handle = program.handle = gl.createProgram(),
attrRx = /attribute\s+\w+\s+(\w+)/g,
uniRx = /uniform\s+(\w+)\s+(\w+)/g,
attributes = [],
uniforms = {},
match,
descriptor = {},
locations = {};
gl.attachShader(handle, getShader(gl, gl.VERTEX_SHADER, vertex));
gl.attachShader(handle, getShader(gl, gl.FRAGMENT_SHADER, fragment));
gl.linkProgram(handle);
if (!gl.getProgramParameter(handle, gl.LINK_STATUS)) {
throw new me.video.Error(gl.getProgramInfoLog(handle));
}
gl.useProgram(handle);
// Detect all attribute names
while ((match = attrRx.exec(vertex))) {
attributes.push(match[1]);
}
// Detect all uniform names and types
[ vertex, fragment ].forEach(function (shader) {
while ((match = uniRx.exec(shader))) {
uniforms[match[2]] = match[1];
}
});
// Get attribute references
attributes.forEach(function (attr) {
program.attributes[attr] = gl.getAttribLocation(handle, attr);
gl.enableVertexAttribArray(program.attributes[attr]);
});
// Get uniform references
Object.keys(uniforms).forEach(function (name) {
var type = uniforms[name];
locations[name] = gl.getUniformLocation(handle, name);
descriptor[name] = {
"get" : (function (name) {
/**
* A getter for the uniform location
* @ignore
*/
return function () {
return locations[name];
};
})(name),
"set" : (function (name, type, fn) {
if (type.indexOf("mat") === 0) {
/**
* A generic setter for uniform matrices
* @ignore
*/
return function (val) {
gl[fn](locations[name], false, val);
};
}
else {
/**
* A generic setter for uniform vectors
* @ignore
*/
return function (val) {
var fnv = fn;
if (val.length && fn.substr(-1) !== "v") {
fnv += "v";
}
gl[fnv](locations[name], val);
};
}
})(name, type, "uniform" + fnHash[type])
};
});
Object.defineProperties(program.uniforms, descriptor);
return program;
};
/**
* Create a texture from an image
* @name createTexture
* @memberOf me.video.shader
* @function
* @param {WebGLContext} gl WebGL Context
* @param {Number} unit Destination texture unit
* @param {Image|Canvas|ImageData|UInt8Array[]|Float32Array[]} image Source image
* @param {Number} filter gl.LINEAR or gl.NEAREST
* @param {String} [repeat="no-repeat"] Image repeat behavior (see {@link me.ImageLayer#repeat})
* @param {Number} [w] Source image width (Only use with UInt8Array[] or Float32Array[] source image)
* @param {Number} [h] Source image height (Only use with UInt8Array[] or Float32Array[] source image)
* @param {Number} [b] Source image border (Only use with UInt8Array[] or Float32Array[] source image)
* @return {WebGLTexture} A texture object
*/
api.createTexture = function (gl, unit, image, filter, repeat, w, h, b) {
repeat = repeat || "no-repeat";
if (!me.utils.isPowerOfTwo(w || image.width) || !me.utils.isPowerOfTwo(h || image.height)) {
console.warn(
"[WebGL Renderer] " + image + " is not a POT texture " +
"(" + (w || image.width) + "x" + (h || image.height) + ")"
);
}
var texture = gl.createTexture(),
rs = (repeat.search(/^repeat(-x)?$/) === 0) ? gl.REPEAT : gl.CLAMP_TO_EDGE,
rt = (repeat.search(/^repeat(-y)?$/) === 0) ? gl.REPEAT : gl.CLAMP_TO_EDGE;
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, rs);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, rt);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
if (w || h || b) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, b, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
return texture;
};
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a WebGL renderer object
* @extends me.Renderer
* @namespace me.WebGLRenderer
* @memberOf me
* @constructor
* @param {Canvas} canvas The html canvas tag to draw to on screen.
* @param {Number} width The width of the canvas without scaling
* @param {Number} height The height of the canvas without scaling
* @param {Object} [options] The renderer parameters
* @param {Boolean} [options.doubleBuffering=false] Whether to enable double buffering
* @param {Boolean} [options.antiAlias=false] Whether to enable anti-aliasing
* @param {Boolean} [options.transparent=false] Whether to enable transparency on the canvas (performance hit when enabled)
* @param {Boolean} [options.subPixel=false] Whether to enable subpixel renderering (performance hit when enabled)
* @param {Number} [options.zoomX=width] The actual width of the canvas with scaling applied
* @param {Number} [options.zoomY=height] The actual height of the canvas with scaling applied
* @param {me.WebGLRenderer.Compositor} [options.compositor] A class that implements the compositor API
*/
me.WebGLRenderer = me.Renderer.extend(
/** @scope me.WebGLRenderer.prototype */
{
/**
* @ignore
*/
init : function (c, width, height, options) {
me.Renderer.prototype.init.apply(this, [c, width, height, options]);
/**
* The WebGL context
* @name gl
* @memberOf me.WebGLRenderer
*/
this.gl = this.getContextGL(c, !this.transparent);
/**
* @ignore
*/
this._colorStack = [];
/**
* @ignore
*/
this._matrixStack = [];
/**
* @ignore
*/
this._linePoints = [
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d()
];
/**
* The current transformation matrix used for transformations on the overall scene
* @name currentTransform
* @type me.Matrix2d
* @memberOf me.WebGLRenderer
*/
this.currentTransform = new me.Matrix2d();
// Create a compositor
var Compositor = options.compositor || me.WebGLRenderer.Compositor;
this.compositor = new Compositor(this);
// Create a texture cache
this.cache = new me.Renderer.TextureCache(
this.compositor.maxTextures
);
this.createFillTexture(this.cache);
// Configure the WebGL viewport
this.scaleCanvas(1, 1);
return this;
},
/**
* @ignore
*/
createFillTexture : function (cache) {
// Create a 1x1 white texture for fill operations
var image = new Uint8Array([255, 255, 255, 255]);
/**
* @ignore
*/
this.fillTexture = new this.Texture(
this.Texture.prototype.createAtlas.apply(
this.Texture.prototype,
[ 1, 1, "fillTexture"]
),
image,
cache
);
this.compositor.uploadTexture(
this.fillTexture,
1,
1,
0
);
},
/**
* @ignore
*/
createFontTexture : function (cache) {
var image = me.video.createCanvas(
me.utils.nextPowerOfTwo(this.backBufferCanvas.width),
me.utils.nextPowerOfTwo(this.backBufferCanvas.height)
);
/**
* @ignore
*/
this.fontContext2D = this.getContext2d(image);
/**
* @ignore
*/
this.fontTexture = new this.Texture(
this.Texture.prototype.createAtlas.apply(
this.Texture.prototype,
[ this.backBufferCanvas.width, this.backBufferCanvas.height, "fontTexture"]
),
image,
cache
);
this.compositor.uploadTexture(this.fontTexture);
},
/**
* Create a pattern with the specified repetition
* @name createPattern
* @memberOf me.WebGLRenderer
* @function
* @param {image} image Source image
* @param {String} repeat Define how the pattern should be repeated
* @return {me.video.renderer.Texture}
* @see me.ImageLayer#repeat
* @example
* var tileable = renderer.createPattern(image, "repeat");
* var horizontal = renderer.createPattern(image, "repeat-x");
* var vertical = renderer.createPattern(image, "repeat-y");
* var basic = renderer.createPattern(image, "no-repeat");
*/
createPattern : function (image, repeat) {
if (!me.utils.isPowerOfTwo(image.width) || !me.utils.isPowerOfTwo(image.height)) {
throw new me.video.Error(
"[WebGL Renderer] " + image + " is not a POT texture " +
"(" + image.width + "x" + image.height + ")"
);
}
var texture = new this.Texture(
this.Texture.prototype.createAtlas.apply(
this.Texture.prototype,
[ image.width, image.height, "pattern", repeat]
),
image
);
// FIXME: Remove old cache entry and texture when changing the repeat mode
this.compositor.uploadTexture(texture);
return texture;
},
/**
* Flush the compositor to the frame buffer
* @name flush
* @memberOf me.WebGLRenderer
* @function
*/
flush : function () {
this.compositor.flush();
},
/**
* Clears the gl context with the given color.
* @name clearColor
* @memberOf me.WebGLRenderer
* @function
* @param {me.Color|String} color CSS color.
* @param {Boolean} [opaque=false] Allow transparency [default] or clear the surface completely [true]
*/
clearColor : function (col, opaque) {
var color = this.currentColor.clone();
var matrix = this.currentTransform.clone();
this.currentColor.copy(col);
this.currentTransform.identity();
if (opaque) {
this.compositor.clear();
}
else {
this.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
this.currentTransform.copy(matrix);
this.currentColor.copy(color);
me.pool.push(color);
},
/**
* Sets all pixels in the given rectangle to transparent black,
* erasing any previously drawn content.
* @name clearRect
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x x axis of the coordinate for the rectangle starting point.
* @param {Number} y y axis of the coordinate for the rectangle starting point.
* @param {Number} width The rectangle's width.
* @param {Number} height The rectangle's height.
*/
clearRect : function (x, y, width, height) {
var color = this.currentColor.clone();
this.currentColor.copy("#0000");
this.fillRect(x, y, width, height);
this.currentColor.copy(color);
me.pool.push(color);
},
/**
* @ignore
*/
drawFont : function (bounds) {
var fontContext = this.getFontContext();
// Flush the compositor so we can upload a new texture
this.compositor.flush();
// Force-upload the new texture
this.compositor.uploadTexture(this.fontTexture, 0, 0, 0, true);
// Add the new quad
var key = bounds.pos.x + "," + bounds.pos.y + "," + bounds.width + "," + bounds.height;
this.compositor.addQuad(
this.fontTexture,
key,
bounds.pos.x,
bounds.pos.y,
bounds.width,
bounds.height
);
// Clear font context2D
fontContext.clearRect(0, 0, this.backBufferCanvas.width, this.backBufferCanvas.height);
},
/**
* Draw an image to the gl context
* @name drawImage
* @memberOf me.WebGLRenderer
* @function
* @param {Image} image Source image
* @param {Number} sx Source x-coordinate
* @param {Number} sy Source y-coordinate
* @param {Number} sw Source width
* @param {Number} sh Source height
* @param {Number} dx Destination x-coordinate
* @param {Number} dy Destination y-coordinate
* @param {Number} dw Destination width
* @param {Number} dh Destination height
* @example
* // Can be used in three ways:
* renderer.drawImage(image, dx, dy);
* renderer.drawImage(image, dx, dy, dw, dh);
* renderer.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
* // dx, dy, dw, dh being the destination target & dimensions. sx, sy, sw, sh being the position & dimensions to take from the image
*/
drawImage : function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
// TODO: Replace the function signature with:
// drawImage(Image|Object, sx, sy, sw, sh, dx, dy, dw, dh)
if (typeof sw === "undefined") {
sw = dw = image.width;
sh = dh = image.height;
dx = sx;
dy = sy;
sx = 0;
sy = 0;
}
else if (typeof dx === "undefined") {
dx = sx;
dy = sy;
dw = sw;
dh = sh;
sw = image.width;
sh = image.height;
sx = 0;
sy = 0;
}
if (this.subPixel === false) {
// clamp to pixel grid
dx = ~~dx;
dy = ~~dy;
}
var key = sx + "," + sy + "," + sw + "," + sh;
this.compositor.addQuad(this.cache.get(image), key, dx, dy, dw, dh);
},
/**
* Draw a pattern within the given rectangle.
* @name drawPattern
* @memberOf me.WebGLRenderer
* @function
* @param {me.video.renderer.Texture} pattern Pattern object
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
* @see me.WebGLRenderer#createPattern
*/
drawPattern : function (pattern, x, y, width, height) {
var key = "0,0," + width + "," + height;
this.compositor.addQuad(pattern, key, x, y, width, height);
},
/**
* Draw a filled rectangle at the specified coordinates
* @name fillRect
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
*/
fillRect : function (x, y, width, height) {
this.compositor.addQuad(this.fillTexture, "default", x, y, width, height);
},
/**
* return a reference to the screen canvas corresponding WebGL Context
* @name getScreenContext
* @memberOf me.WebGLRenderer
* @function
* @return {WebGLContext}
*/
getScreenContext : function () {
return this.gl;
},
/**
* Returns the WebGL Context object of the given Canvas
* @name getContextGL
* @memberOf me.WebGLRenderer
* @function
* @param {Canvas} canvas
* @param {Boolean} [opaque=false] Use true to disable transparency
* @return {WebGLContext}
*/
getContextGL : function (c, opaque) {
if (typeof c === "undefined" || c === null) {
throw new me.video.Error(
"You must pass a canvas element in order to create " +
"a GL context"
);
}
if (typeof c.getContext === "undefined") {
throw new me.video.Error(
"Your browser does not support WebGL."
);
}
var attr = {
antialias : this.antiAlias,
alpha : !opaque
};
return (
c.getContext("webgl", attr) ||
c.getContext("experimental-webgl", attr)
);
},
/**
* Returns the WebGLContext instance for the renderer
* return a reference to the system 2d Context
* @name getContext
* @memberOf me.WebGLRenderer
* @function
* @return {WebGLContext}
*/
getContext : function () {
return this.gl;
},
/**
* return a reference to the font 2d Context
* @ignore
*/
getFontContext : function () {
if (typeof (this.fontContext2D) === "undefined" ) {
// warn the end user about performance impact
console.warn("[WebGL Renderer] WARNING : Using Standard me.Font with WebGL will severly impact performances !");
// create the font texture if not done yet
this.createFontTexture(this.cache);
}
return this.fontContext2D;
},
/**
* resets the gl transform to identity
* @name resetTransform
* @memberOf me.WebGLRenderer
* @function
*/
resetTransform : function () {
this.currentTransform.identity();
},
/**
* Reset context state
* @name reset
* @memberOf me.WebGLRenderer
* @function
*/
reset : function () {
this.resetTransform();
this.cache.reset();
this.compositor.reset();
this.createFillTexture();
if (typeof (this.fontContext2D) !== "undefined" ) {
this.createFontTexture();
}
},
/**
* scales the canvas & GL Context
* @name scaleCanvas
* @memberOf me.WebGLRenderer
* @function
*/
scaleCanvas : function (scaleX, scaleY) {
var w = this.canvas.width * scaleX;
var h = this.canvas.height * scaleY;
// adjust CSS style for High-DPI devices
if (me.device.getPixelRatio() > 1) {
this.canvas.style.width = (w / me.device.getPixelRatio()) + "px";
this.canvas.style.height = (h / me.device.getPixelRatio()) + "px";
}
else {
this.canvas.style.width = w + "px";
this.canvas.style.height = h + "px";
}
this.compositor.setProjection(this.canvas.width, this.canvas.height);
},
/**
* restores the canvas context
* @name restore
* @memberOf me.WebGLRenderer
* @function
*/
restore : function () {
// do nothing if there is no saved states
if (this._matrixStack.length !== 0) {
var color = this._colorStack.pop();
var matrix = this._matrixStack.pop();
// restore the previous context
this.currentColor.copy(color);
this.currentTransform.copy(matrix);
// recycle objects
me.pool.push(color);
me.pool.push(matrix);
}
},
/**
* saves the canvas context
* @name save
* @memberOf me.WebGLRenderer
* @function
*/
save : function () {
this._colorStack.push(this.currentColor.clone());
this._matrixStack.push(this.currentTransform.clone());
},
/**
* rotates the uniform matrix
* @name rotate
* @memberOf me.WebGLRenderer
* @function
* @param {Number} angle in radians
*/
rotate : function (angle) {
this.currentTransform.rotate(angle);
},
/**
* scales the uniform matrix
* @name scale
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x
* @param {Number} y
*/
scale : function (x, y) {
this.currentTransform.scale(x, y);
},
/**
* not used by this renderer?
* @ignore
*/
setAntiAlias : function (context, enable) {
me.Renderer.prototype.setAntiAlias.apply(this, [context, enable]);
// TODO: perhaps handle GLNEAREST or other options with texture binding
},
/**
* return the current global alpha
* @name globalAlpha
* @memberOf me.WebGLRenderer
* @function
* @return {Number}
*/
setGlobalAlpha : function (a) {
this.currentColor.glArray[3] = a;
},
/**
* Sets the color for further draw calls
* @name setColor
* @memberOf me.WebGLRenderer
* @function
* @param {me.Color|String} color css color string.
*/
setColor : function (color) {
var alpha = this.currentColor.glArray[3];
this.currentColor.copy(color);
this.currentColor.glArray[3] *= alpha;
},
/**
* Set the line width
* @name setLineWidth
* @memberOf me.WebGLRenderer
* @function
* @param {Number} width Line width
*/
setLineWidth : function (width) {
this.compositor.lineWidth(width);
},
/**
* Stroke an arc at the specified coordinates with given radius, start and end points
* @name strokeArc
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x arc center point x-axis
* @param {Number} y arc center point y-axis
* @param {Number} radius
* @param {Number} start start angle in radians
* @param {Number} end end angle in radians
* @param {Boolean} [antiClockwise=false] draw arc anti-clockwise
*/
strokeArc : function (/*x, y, radius, start, end, antiClockwise*/) {
// TODO
},
/**
* Stroke an ellipse at the specified coordinates with given radius, start and end points
* @name strokeEllipse
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x arc center point x-axis
* @param {Number} y arc center point y-axis
* @param {Number} w horizontal radius of the ellipse
* @param {Number} h vertical radius of the ellipse
*/
strokeEllipse : function (/*x, y, w, h*/) {
// TODO
},
/**
* Stroke a line of the given two points
* @name strokeLine
* @memberOf me.WebGLRenderer
* @function
* @param {Number} startX the start x coordinate
* @param {Number} startY the start y coordinate
* @param {Number} endX the end x coordinate
* @param {Number} endY the end y coordinate
*/
strokeLine : function (startX, startY, endX, endY) {
var points = this._linePoints.slice(0, 2);
points[0].x = startX;
points[0].y = startY;
points[1].x = endX;
points[1].y = endY;
this.compositor.drawLine(points, true);
},
/**
* Strokes a me.Polygon on the screen with a specified color
* @name strokePolygon
* @memberOf me.WebGLRenderer
* @function
* @param {me.Polygon} poly the shape to draw
*/
strokePolygon : function (poly) {
var len = poly.points.length,
points,
i;
// Grow internal points buffer if necessary
for (i = this._linePoints.length; i < len; i++) {
this._linePoints.push(new me.Vector2d());
}
points = this._linePoints.slice(0, len);
for (i = 0; i < len; i++) {
points[i].x = poly.pos.x + poly.points[i].x;
points[i].y = poly.pos.y + poly.points[i].y;
}
this.compositor.drawLine(points);
},
/**
* Draw a stroke rectangle at the specified coordinates
* @name strokeRect
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
*/
strokeRect : function (x, y, width, height) {
var points = this._linePoints.slice(0, 4);
points[0].x = x;
points[0].y = y;
points[1].x = x + width;
points[1].y = y;
points[2].x = x + width;
points[2].y = y + height;
points[3].x = x;
points[3].y = y + height;
this.compositor.drawLine(points);
},
/**
* draw the given shape
* @name drawShape
* @memberOf me.WebGLRenderer
* @function
* @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object
*/
drawShape : function (shape) {
if (shape.shapeType === "Rectangle") {
this.strokeRect(shape.left, shape.top, shape.width, shape.height);
} else if (shape instanceof me.Line || shape instanceof me.Polygon) {
this.strokePolygon(shape);
} else if (shape instanceof me.Ellipse) {
if (shape.radiusV.x === shape.radiusV.y) {
// it's a circle
this.strokeArc(
shape.pos.x - shape.radius,
shape.pos.y - shape.radius,
shape.radius,
0,
2 * Math.PI
);
} else {
// it's an ellipse
this.strokeEllipse(
shape.pos.x,
shape.pos.y,
shape.radiusV.x,
shape.radiusV.y
);
}
}
},
/**
* Resets (overrides) the renderer transformation matrix to the
* identity one, and then apply the given transformation matrix.
* @name setTransform
* @memberOf me.WebGLRenderer
* @function
* @param {me.Matrix2d} mat2d Matrix to transform by
*/
setTransform : function (mat2d) {
this.resetTransform();
this.transform(mat2d);
},
/**
* Multiply given matrix into the renderer tranformation matrix
* @name transform
* @memberOf me.WebGLRenderer
* @function
* @param {me.Matrix2d} mat2d Matrix to transform by
*/
transform : function (mat2d) {
this.currentTransform.multiply(mat2d);
if (this.subPixel === false) {
// snap position values to pixel grid
var a = this.currentTransform.val;
a[6] = ~~a[6];
a[7] = ~~a[7];
}
},
/**
* Translates the uniform matrix by the given coordinates
* @name translate
* @memberOf me.WebGLRenderer
* @function
* @param {Number} x
* @param {Number} y
*/
translate : function (x, y) {
if (this.subPixel === false) {
this.currentTransform.translate(~~x, ~~y);
} else {
this.currentTransform.translate(x, y);
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* A Texture atlas object for WebGL
* For portability, a global reference to this class is available through the default renderer: {@link me.video.renderer}.Texture
*
* Currently supports :
* - [TexturePacker]{@link http://www.codeandweb.com/texturepacker/} : through JSON export
* - [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}
* - Standard (fixed cell size) spritesheet : through a {framewidth:xx, frameheight:xx} object
* @class
* @extends me.CanvasRenderer
* @memberOf me.WebGLRenderer
* @name Texture
* @constructor
* @param {Object} atlas atlas information. See {@link me.loader.getJSON}
* @param {Image} [texture=atlas.meta.image] texture name
* @param {Boolean} [cached=false] Use true to skip caching this Texture
* @example
* // create a texture atlas from a JSON Object
* texture = new me.video.renderer.Texture(
* me.loader.getJSON("texture"),
* me.loader.getImage("texture")
* );
*
* // create a texture atlas for a spritesheet
* texture = new me.video.renderer.Texture(
* { framewidth : 32, frameheight : 32 },
* me.loader.getImage("spritesheet")
* );
*/
me.WebGLRenderer.prototype.Texture = me.CanvasRenderer.prototype.Texture.extend(
/** @scope me.video.renderer.Texture.prototype */
{
/**
* @ignore
*/
parse : function (data) {
var w = data.meta.size.w;
var h = data.meta.size.h;
var atlas = me.CanvasRenderer.prototype.Texture.prototype.parse.apply(this, [ data ]);
return this._addStMap(atlas, w, h);
},
/**
* @ignore
*/
parseFromSpriteSheet : function (data) {
var w = data.image.width;
var h = data.image.height;
var atlas = me.CanvasRenderer.prototype.Texture.prototype.parseFromSpriteSheet.apply(this, [ data ]);
return this._addStMap(atlas, w, h);
},
/**
* @ignore
*/
_addStMap : function (atlas, w, h) {
Object.keys(atlas).forEach(function (frame) {
// Source coordinates
var s = atlas[frame].offset;
var sw = atlas[frame].width;
var sh = atlas[frame].height;
// ST texture coordinates
atlas[frame].stMap = new Float32Array([
s.x / w, // Left
s.y / h, // Top
(s.x + sw) / w, // Right
(s.y + sh) / h // Bottom
]);
// Cache source coordinates
// TODO: Remove this when the Batcher only accepts a region name
var key = s.x + "," + s.y + "," + w + "," + h;
atlas[key] = atlas[frame];
});
return atlas;
},
/**
* @ignore
*/
_insertRegion : function (name, x, y, w, h) {
var dw = this.source.width;
var dh = this.source.height;
this.atlas[name] = {
name : name,
offset : new me.Vector2d(x, y),
width : w,
height : h,
angle : 0,
stMap : new Float32Array([
x / dw, // Left
y / dh, // Top
(x + w) / dw, // Right
(y + h) / dh // Bottom
])
};
return this.atlas[name];
}
});
/**
* Base class for Texture exception handling.
* @ignore
*/
me.WebGLRenderer.prototype.Texture.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.WebGLRenderer.Texture.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*/
(function () {
// Handy constants
var VERTEX_SIZE = 2;
var COLOR_SIZE = 4;
var TEXTURE_SIZE = 1;
var REGION_SIZE = 2;
var ELEMENT_SIZE = VERTEX_SIZE + COLOR_SIZE + TEXTURE_SIZE + REGION_SIZE;
var ELEMENT_OFFSET = ELEMENT_SIZE * Float32Array.BYTES_PER_ELEMENT;
var VERTEX_ELEMENT = 0;
var COLOR_ELEMENT = VERTEX_ELEMENT + VERTEX_SIZE;
var TEXTURE_ELEMENT = COLOR_ELEMENT + COLOR_SIZE;
var REGION_ELEMENT = TEXTURE_ELEMENT + TEXTURE_SIZE;
var VERTEX_OFFSET = VERTEX_ELEMENT * Float32Array.BYTES_PER_ELEMENT;
var COLOR_OFFSET = COLOR_ELEMENT * Float32Array.BYTES_PER_ELEMENT;
var TEXTURE_OFFSET = TEXTURE_ELEMENT * Float32Array.BYTES_PER_ELEMENT;
var REGION_OFFSET = REGION_ELEMENT * Float32Array.BYTES_PER_ELEMENT;
var ELEMENTS_PER_QUAD = 4;
var INDICES_PER_QUAD = 6;
var MAX_LENGTH = 16000;
/**
* A WebGL texture Compositor object. This class handles all of the WebGL state
* Pushes texture regions into WebGL buffers, automatically flushes to GPU
* @extends me.Object
* @namespace me.WebGLRenderer.Compositor
* @memberOf me
* @constructor
* @param {me.WebGLRenderer} renderer the current WebGL renderer session
*/
me.WebGLRenderer.Compositor = me.Object.extend(
/** @scope me.WebGLRenderer.Compositor.prototype */
{
/**
* @ignore
*/
init : function (renderer) {
// local reference
var gl = renderer.gl;
/**
* The number of quads held in the batch
* @name length
* @memberOf me.WebGLRenderer.Compositor
* @type Number
* @readonly
*/
this.length = 0;
// Hash map of texture units
this.units = [];
/*
* XXX: The GLSL compiler pukes with "memory exhausted" when it is
* given long if-then-else chains.
*
* See: http://stackoverflow.com/questions/15828966/glsl-compile-error-memory-exhausted
*
* Workaround the problem by limiting the max texture support to 24.
* The magic number was determined by testing under different UAs.
* All Desktop UAs were capable of compiling with 27 fragment shader
* samplers. Using 24 seems like a reasonable compromise;
*
* 24 = 2^4 + 2^3
*
* As of October 2015, approximately 4.2% of all WebGL-enabled UAs
* support more than 24 max textures, according to
* http://webglstats.com/
*/
this.maxTextures = Math.min(24, gl.getParameter(
gl.MAX_TEXTURE_IMAGE_UNITS
));
// Vector pool
this.v = [
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d()
];
// the associated renderer
// TODO : add a set context or whatever function, and split
// the constructor accordingly, so that this is easier to restore
// the GL context when lost
this.renderer = renderer;
// WebGL context
this.gl = renderer.gl;
// Global transformation matrix
this.matrix = renderer.currentTransform;
// Global color
this.color = renderer.currentColor;
// Uniform projection matrix
this.uMatrix = new me.Matrix2d();
// Detect GPU capabilities
var precision = (gl.getShaderPrecisionFormat(
gl.FRAGMENT_SHADER,
gl.HIGH_FLOAT
).precision < 16) ? "mediump" : "highp";
// Load and create shader programs
/* eslint-disable */
this.lineShader = me.video.shader.createShader(
this.gl,
(function anonymous(ctx){var out='precision highp float;attribute vec2 aVertex;uniform mat3 uMatrix;void main(void){gl_Position=vec4((uMatrix*vec3(aVertex,1)).xy,0,1);}';return out;})(),
(function anonymous(ctx){var out='precision '+(ctx.precision)+' float;uniform vec4 uColor;void main(void){gl_FragColor=uColor;}';return out;})({
"precision" : precision
})
);
this.quadShader = me.video.shader.createShader(
this.gl,
(function anonymous(ctx){var out='precision highp float;attribute vec2 aVertex;attribute vec4 aColor;attribute float aTexture;attribute vec2 aRegion;uniform mat3 uMatrix;varying vec4 vColor;varying float vTexture;varying vec2 vRegion;void main(void){gl_Position=vec4((uMatrix*vec3(aVertex,1)).xy,0,1);vColor=vec4(aColor.rgb*aColor.a,aColor.a);vTexture=aTexture;vRegion=aRegion;}';return out;})(),
(function anonymous(ctx){var out='precision '+(ctx.precision)+' float;uniform sampler2D uSampler['+(ctx.maxTextures)+'];varying vec4 vColor;varying float vTexture;varying vec2 vRegion;void main(void){int texture=int(vTexture);if(texture==0){gl_FragColor=texture2D(uSampler[0],vRegion)*vColor;}';for(var i=1;i
* @name triggerKeyEvent
* @memberOf me.input
* @public
* @function
* @param {me.input.KEY} keycode
* @param {Boolean} [status=false] true to trigger a key press, or false for key release
* @example
* // trigger a key press
* me.input.triggerKeyEvent(me.input.KEY.LEFT, true);
*/
api.triggerKeyEvent = function (keycode, status) {
if (status) {
api._keydown({}, keycode);
}
else {
api._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=false] cancel the keypress event once read
* @param {Boolean} [preventDefault=me.input.preventDefault] prevent default browser action
* @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);
* me.input.bindKey(me.input.KEY.F1, "options", true, true);
*/
api.bindKey = function (keycode, action, lock, preventDefault) {
// make sure the keyboard is enable
api._enableKeyboardEvent();
if (typeof preventDefault !== "boolean") {
preventDefault = api.preventDefault;
}
api._KeyBinding[keycode] = action;
preventDefaultForKeys[keycode] = preventDefault;
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");
* }
*/
api.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);
*/
api.unbindKey = function (keycode) {
// clear the event status
var keybinding = api._KeyBinding[keycode];
keyStatus[keybinding] = 0;
keyLock[keybinding] = false;
keyRefs[keybinding] = {};
// remove the key binding
api._KeyBinding[keycode] = null;
preventDefaultForKeys[keycode] = null;
};
})(me.input);
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* cache value for the offset of the canvas position within the page
* @ignore
*/
var viewportOffset = new me.Vector2d();
/**
* a pointer object, representing a single finger on a touch enabled device.
* @class
* @extends me.Rect
* @memberOf me
* @constructor
*/
me.Pointer = me.Rect.extend(
/** @scope me.Pointer.prototype */
{
/**
* @ignore
*/
init : function (x, y, w, h) {
/**
* the originating Event Object
* @public
* @type {PointerEvent|TouchEvent|MouseEvent}
* @name event
* @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
* @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
* @memberOf me.Pointer
*/
this.event = undefined;
/**
* a string containing the event's type.
* @public
* @type {String}
* @name type
* @see https://developer.mozilla.org/en-US/docs/Web/API/Event/type
* @memberOf me.Pointer
*/
this.type = undefined;
/**
* the button property indicates which button was pressed on the mouse to trigger the event.
* @public
* @type {Number}
* @name width
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
* @memberOf me.Pointer
*/
this.button = 0;
/**
* indicates whether or not the pointer device that created the event is the primary pointe.
* @public
* @type {Boolean}
* @name width
* @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
* @memberOf me.Pointer
*/
this.isPrimary = false;
/**
* the horizontal coordinate within the application's client area at which the event occurred
* @public
* @type {Number}
* @name clientX
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX
* @memberOf me.Pointer
*/
this.clientX = undefined;
/**
* the vertical coordinate within the application's client area at which the event occurred
* @public
* @type {Number}
* @name clientY
* @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY
* @memberOf me.Pointer
*/
this.clientY = undefined;
/**
* Event normalized X coordinate within the game canvas itself
*
* @public
* @type {Number}
* @name gameX
* @memberOf me.Pointer
*/
this.gameX = undefined;
/**
* Event normalized Y coordinate within the game canvas itself
*
* @public
* @type {Number}
* @name gameY
* @memberOf me.Pointer
*/
this.gameY = undefined;
/**
* Event X coordinate relative to the viewport
* @public
* @type {Number}
* @name gameScreenX
* @memberOf me.Pointer
*/
this.gameScreenX = undefined;
/**
* Event Y coordinate relative to the viewport
* @public
* @type {Number}
* @name gameScreenY
* @memberOf me.Pointer
*/
this.gameScreenY = undefined;
/**
* Event X coordinate relative to the map
* @public
* @type {Number}
* @name gameWorldX
* @memberOf me.Pointer
*/
this.gameWorldX = undefined;
/**
* Event Y coordinate relative to the map
* @public
* @type {Number}
* @name gameWorldY
* @memberOf me.Pointer
*/
this.gameWorldY = undefined;
/**
* Event X coordinate relative to the holding container
* @public
* @type {Number}
* @name gameLocalX
* @memberOf me.Pointer
*/
this.gameLocalX = undefined;
/**
* Event Y coordinate relative to the holding container
* @public
* @type {Number}
* @name gameLocalY
* @memberOf me.Pointer
*/
this.gameLocalY = undefined;
/**
* The unique identifier of the contact for a touch, mouse or pen
* @public
* @type {Number}
* @name pointerId
* @memberOf me.Pointer
* @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId
*/
this.pointerId = undefined;
// parent constructor
me.Rect.prototype.init.apply(this, [x || 0, y || 0, w || 1, h || 1]);
},
/**
* initialize the Pointer object using the given Event Object
* @name me.Pointer#set
* @private
* @function
* @param {Event} event the original Event object
* @param {Number} clientX the horizontal coordinate within the application's client area at which the event occurred
* @param {Number} clientX the vertical coordinate within the application's client area at which the event occurred
* @param {Number} pointedId the Pointer, Touch or Mouse event Id
*/
setEvent : function (event, clientX, clientY, pointerId) {
var width = 1;
var height = 1;
// the original event object
this.event = event;
this.clientX = clientX || 0;
this.clientY = clientY || 0;
// translate to local coordinates
me.input.globalToLocal(clientX, clientY, this.pos);
// true if not originally a pointer event
this.isNormalized = !me.device.PointerEvent || (me.device.PointerEvent && !(event instanceof window.PointerEvent));
if (event.type === "wheel") {
this.deltaMode = 1;
this.deltaX = event.deltaX;
this.deltaY = - 1 / 40 * event.wheelDelta;
event.wheelDeltaX && (this.deltaX = - 1 / 40 * event.wheelDeltaX);
}
// could be 0, so test if defined
this.pointerId = (typeof pointerId !== "undefined") ? pointerId : 1;
this.isPrimary = (typeof event.isPrimary !== "undefined") ? event.isPrimary : true;
// in case of touch events, button is not defined
this.button = event.button || 0;
this.type = event.type;
this.gameScreenX = this.pos.x;
this.gameScreenY = this.pos.y;
// get the current screen to world offset
me.game.viewport.localToWorld(this.gameScreenX, this.gameScreenY, viewportOffset);
/* Initialize the two coordinate space properties. */
this.gameWorldX = viewportOffset.x;
this.gameWorldY = viewportOffset.y;
// get the pointer size
if (this.isNormalized === false) {
// native PointerEvent
width = event.width;
height = event.height;
} else if (typeof(event.radiusX) === "number") {
// TouchEvent
width = event.radiusX * 2;
height = event.radiusY * 2;
}
// resize the pointer object accordingly
this.resize(width, height);
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org/
*
*/
(function (api) {
/**
* A pool of `Pointer` objects to cache pointer/touch event coordinates.
* @type {Array.
* properties :
* LEFT : constant for left button
* MIDDLE : constant for middle button
* RIGHT : constant for right button
* @public
* @type {me.Rect}
* @name pointer
* @memberOf me.input
*/
api.pointer = new me.Pointer(0, 0, 1, 1);
// bind list for mouse buttons
api.pointer.bind = [ 0, 0, 0 ];
// W3C button constants
api.pointer.LEFT = 0;
api.pointer.MIDDLE = 1;
api.pointer.RIGHT = 2;
/**
* time interval for event throttling in milliseconds
* default value : "1000/me.sys.fps" ms
* set to 0 ms to disable the feature
* @public
* @type Number
* @name throttlingInterval
* @memberOf me.input
*/
api.throttlingInterval = undefined;
/**
* 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.
* @param {Number} [v] an optional vector object where to set the
* @return {me.Vector2d} A vector object with the corresponding translated coordinates.
* @example
* onMouseEvent : function (pointer) {
* // convert the given into local (viewport) relative coordinates
* var pos = me.input.globalToLocal(pointer.clientX, pointer.clientY);
* // do something with pos !
* };
*/
api.globalToLocal = function (x, y, v) {
v = v || new me.Vector2d();
var offset = me.video.getPos();
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 v.set(x * pixelRatio, y * pixelRatio);
};
/**
* enable/disable all gestures on the given element.
* by default melonJS will disable browser handling of all panning and zooming gestures.
* @name setTouchAction
* @memberOf me.input
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action
* @public
* @function
* @param {HTMLCanvasElement} element
* @param {String} [value="none"]
*/
api.setTouchAction = function (element, value) {
element.style["touch-action"] = value || "none";
};
/**
* Associate a pointer event to a keycode
* Left button – 0
* Middle button – 1
* Right button – 2
* @name bindPointer
* @memberOf me.input
* @public
* @function
* @param {Number} [button=me.input.pointer.LEFT] (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 (default if the button is not specified)
* me.input.bindPointer(me.input.KEY.X);
* // map the right button click on the X key
* me.input.bindPointer(me.input.pointer.RIGHT, me.input.KEY.X);
*/
api.bindPointer = function () {
var button = (arguments.length < 2) ? api.pointer.LEFT : arguments[0];
var keyCode = (arguments.length < 2) ? arguments[0] : arguments[1];
// make sure the mouse is initialized
enablePointerEvent();
// throw an exception if no action is defined for the specified keycode
if (!api._KeyBinding[keyCode]) {
throw new me.Error("no action defined for keycode " + keyCode);
}
// map the mouse button to the keycode
api.pointer.bind[button] = keyCode;
};
/**
* unbind the defined keycode
* @name unbindPointer
* @memberOf me.input
* @public
* @function
* @param {Number} [button=me.input.pointer.LEFT] (accordingly to W3C values : 0,1,2 for left, middle and right buttons)
* @example
* me.input.unbindPointer(me.input.pointer.LEFT);
*/
api.unbindPointer = function (button) {
// clear the event status
api.pointer.bind[
typeof(button) === "undefined" ?
api.pointer.LEFT : button
] = null;
};
/**
* allows registration of event listeners on the object target.
* melonJS will pass a me.Pointer object to the defined callback.
* @see me.Pointer
* @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list}
* @name registerPointerEvent
* @memberOf me.input
* @public
* @function
* @param {String} eventType The event type for which the object is registering
* melonJS currently supports:
*
*
* @param {me.Rect|me.Polygon|me.Line|me.Ellipse} region a shape representing the region to register on
* @param {Function} callback methods to be called when the event occurs.
* Returning `false` from the defined callback will prevent the event to be propagated to other objects
* @example
* // onActivate function
* onActivateEvent: function () {
* // register on the 'pointerdown' event
* me.input.registerPointerEvent('pointerdown', this, this.pointerDown.bind(this));
* },
*
* // pointerDown event callback
* pointerDown: function (pointer) {
* // do something
* ....
* // don"t propagate the event to other objects
* return false;
* },
*/
api.registerPointerEvent = function (eventType, region, callback) {
// make sure the mouse/touch events are initialized
enablePointerEvent();
if (pointerEventList.indexOf(eventType) === -1) {
throw new me.Error("invalid event type : " + eventType);
}
var eventTypes = findAllActiveEvents(activeEventList, pointerEventMap[eventType]);
// register the event
if (!eventHandlers.has(region)) {
eventHandlers.set(region, {
region : region,
callbacks : {},
pointerId : null
});
}
// allocate array if not defined
var handlers = eventHandlers.get(region);
for (var i = 0; i < eventTypes.length; i++) {
eventType = eventTypes[i];
if (handlers.callbacks[eventType]) {
handlers.callbacks[eventType].push(callback);
} else {
handlers.callbacks[eventType] = [callback];
}
}
};
/**
* allows the removal of event listeners from the object target.
* @see {@link http://www.w3.org/TR/pointerevents/#list-of-pointer-events|W3C Pointer Event list}
* @name releasePointerEvent
* @memberOf me.input
* @public
* @function
* @param {String} eventType The event type for which the object was registered. See {@link me.input.registerPointerEvent}
* @param {me.Rect|me.Polygon|me.Line|me.Ellipse} region the registered region to release for this event
* @param {Function} [callback="all"] if specified unregister the event only for the specific callback
* @example
* // release the registered region on the 'pointerdown' event
* me.input.releasePointerEvent('pointerdown', this);
*/
api.releasePointerEvent = function (eventType, region, callback) {
if (pointerEventList.indexOf(eventType) === -1) {
throw new me.Error("invalid event type : " + eventType);
}
// convert to supported event type if pointerEvent not natively supported
var eventTypes = findAllActiveEvents(activeEventList, pointerEventMap[eventType]);
var handlers = eventHandlers.get(region);
if (typeof (handlers) !== "undefined") {
for (var i = 0; i < eventTypes.length; i++) {
eventType = eventTypes[i];
if (handlers.callbacks[eventType]) {
if (typeof (callback) !== "undefined") {
handlers.callbacks[eventType].remove(callback);
} else {
while (handlers.callbacks[eventType].length > 0) {
handlers.callbacks[eventType].pop();
}
}
// free the array if empty
if (handlers.callbacks[eventType].length === 0) {
delete handlers.callbacks[eventType];
}
}
}
if (Object.keys(handlers.callbacks).length === 0) {
eventHandlers.delete(region);
}
}
};
/**
* Will translate global (frequently used) pointer events
* which should be catched at root level, into minipubsub system events
* @name _translatePointerEvents
* @memberOf me.input
* @ignore
* @function
*/
api._translatePointerEvents = function () {
// listen to mouse move (and touch move) events on the viewport
// and convert them to a system event by default
api.registerPointerEvent("pointermove", me.game.viewport, function (e) {
me.event.publish(me.event.POINTERMOVE, [e]);
});
};
})(me.input);
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org/
*
*/
(function (api) {
/*
* PRIVATE STUFF
*/
// Analog deadzone
var deadzone = 0.1;
/**
* A function that returns a normalized value in range [-1.0..1.0], or 0.0 if the axis is unknown.
* @callback me.input~normalize_fn
* @param {Number} value The raw value read from the gamepad driver
* @param {Number} axis The axis index from the standard mapping, or -1 if not an axis
* @param {Number} button The button index from the standard mapping, or -1 if not a button
*/
function defaultNormalizeFn(value) {
return value;
}
/**
* Normalize axis values for wired Xbox 360
* @ignore
*/
function wiredXbox360NormalizeFn(value, axis, button) {
if (button === api.GAMEPAD.BUTTONS.L2 || button === api.GAMEPAD.BUTTONS.R2) {
return (value + 1) / 2;
}
return value;
}
/**
* Normalize axis values for OUYA
* @ignore
*/
function ouyaNormalizeFn(value, axis, button) {
if (value > 0) {
if (button === api.GAMEPAD.BUTTONS.L2) {
// L2 is wonky; seems like the deadzone is around 20000
// (That's over 15% of the total range!)
value = Math.max(0, value - 20000) / 111070;
}
else {
// Normalize [1..65536] => [0.0..0.5]
value = (value - 1) / 131070;
}
}
else {
// Normalize [-65536..-1] => [0.5..1.0]
value = (65536 + value) / 131070 + 0.5;
}
return value;
}
// Match vendor and product codes for Firefox
var vendorProductRE = /^([0-9a-f]{1,4})-([0-9a-f]{1,4})-/i;
// Match leading zeros
var leadingZeroRE = /^0+/;
/**
* Firefox reports different ids for gamepads depending on the platform:
* - Windows: vendor and product codes contain leading zeroes
* - Mac: vendor and product codes are sparse (no leading zeroes)
*
* This function normalizes the id to support both formats
* @ignore
*/
function addMapping(id, mapping) {
var expanded_id = id.replace(vendorProductRE, function (_, a, b) {
return (
"000".substr(a.length - 1) + a + "-" +
"000".substr(b.length - 1) + b + "-"
);
});
var sparse_id = id.replace(vendorProductRE, function (_, a, b) {
return (
a.replace(leadingZeroRE, "") + "-" +
b.replace(leadingZeroRE, "") + "-"
);
});
// Normalize optional parameters
mapping.analog = mapping.analog || mapping.buttons.map(function () {
return -1;
});
mapping.normalize_fn = mapping.normalize_fn || defaultNormalizeFn;
remap.set(expanded_id, mapping);
remap.set(sparse_id, mapping);
}
// binding list
var bindings = {};
// mapping list
var remap = new Map();
/**
* Default gamepad mappings
* @ignore
*/
[
// Firefox mappings
[
"45e-28e-Xbox 360 Wired Controller",
{
"axes" : [ 0, 1, 3, 4 ],
"buttons" : [ 11, 12, 13, 14, 8, 9, -1, -1, 5, 4, 6, 7, 0, 1, 2, 3, 10 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : wiredXbox360NormalizeFn
}
],
[
"54c-268-PLAYSTATION(R)3 Controller",
{
"axes" : [ 0, 1, 2, 3 ],
"buttons" : [ 14, 13, 15, 12, 10, 11, 8, 9, 0, 3, 1, 2, 4, 6, 7, 5, 16 ]
}
],
[
"54c-5c4-Wireless Controller", // PS4 Controller
{
"axes" : [ 0, 1, 2, 3 ],
"buttons" : [ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 12, 13 ]
}
],
[
"2836-1-OUYA Game Controller",
{
"axes" : [ 0, 3, 7, 9 ],
"buttons" : [ 3, 6, 4, 5, 7, 8, 15, 16, -1, -1, 9, 10, 11, 12, 13, 14, -1 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : ouyaNormalizeFn
}
],
// Chrome mappings
[
"OUYA Game Controller (Vendor: 2836 Product: 0001)",
{
"axes" : [ 0, 1, 3, 4 ],
"buttons" : [ 0, 3, 1, 2, 4, 5, 12, 13, -1, -1, 6, 7, 8, 9, 10, 11, -1 ],
"analog" : [ -1, -1, -1, -1, -1, -1, 2, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
"normalize_fn" : ouyaNormalizeFn
}
]
].forEach(function (value) {
addMapping(value[0], value[1]);
});
/**
* gamepad connected callback
* @ignore
*/
window.addEventListener("gamepadconnected", function (event) {
me.event.publish(me.event.GAMEPAD_CONNECTED, [ event.gamepad ]);
}, false);
/**
* gamepad disconnected callback
* @ignore
*/
window.addEventListener("gamepaddisconnected", function (event) {
me.event.publish(me.event.GAMEPAD_DISCONNECTED, [ event.gamepad ]);
}, false);
/**
* Update gamepad status
* @ignore
*/
api._updateGamepads = navigator.getGamepads ? function () {
var gamepads = navigator.getGamepads();
var e = {};
// Trigger button bindings
Object.keys(bindings).forEach(function (index) {
var gamepad = gamepads[index];
if (!gamepad) {
return;
}
var mapping = null;
if (gamepad.mapping !== "standard") {
mapping = remap.get(gamepad.id);
}
var binding = bindings[index];
// Iterate all buttons that have active bindings
Object.keys(binding.buttons).forEach(function (button) {
var last = binding.buttons[button];
var mapped_button = button;
var mapped_axis = -1;
// Remap buttons if necessary
if (mapping) {
mapped_button = mapping.buttons[button];
mapped_axis = mapping.analog[button];
if (mapped_button < 0 && mapped_axis < 0) {
// Button is not mapped
return;
}
}
// Get mapped button
var current = gamepad.buttons[mapped_button] || {};
// Remap an axis to an analog button
if (mapping) {
if (mapped_axis >= 0) {
var value = mapping.normalize_fn(gamepad.axes[mapped_axis], -1, +button);
// Create a new object, because GamepadButton is read-only
current = {
"value" : value,
"pressed" : current.pressed || (Math.abs(value) >= deadzone)
};
}
}
me.event.publish(me.event.GAMEPAD_UPDATE, [ index, "buttons", +button, current ]);
// Edge detection
if (!last.pressed && current.pressed) {
api._keydown(e, last.keyCode, mapped_button + 256);
}
else if (last.pressed && !current.pressed) {
api._keyup(e, last.keyCode, mapped_button + 256);
}
// Update last button state
last.value = current.value;
last.pressed = current.pressed;
});
// Iterate all axes that have active bindings
Object.keys(binding.axes).forEach(function (axis) {
var last = binding.axes[axis];
var mapped_axis = axis;
// Remap buttons if necessary
if (mapping) {
mapped_axis = mapping.axes[axis];
if (mapped_axis < 0) {
// axe is not mapped
return;
}
}
// retrieve the current value and normalize if necessary
var value = gamepad.axes[mapped_axis];
if (typeof(value) === "undefined") {
return;
}
if (mapping) {
value = mapping.normalize_fn(value, +axis, -1);
}
// normalize value into a [-1, 1] range value (treat 0 as positive)
var range = Math.sign(value) || 1;
if (last[range].keyCode === 0) {
return;
}
var pressed = (Math.abs(value) >= (deadzone + Math.abs(last[range].threshold)));
me.event.publish(me.event.GAMEPAD_UPDATE, [ index, "axes", +axis, value ]);
// Edge detection
if (!last[range].pressed && pressed) {
// Release the opposite direction, if necessary
if (last[-range].pressed) {
api._keyup(e, last[-range].keyCode, mapped_axis + 256);
last[-range].value = 0;
last[-range].pressed = false;
}
api._keydown(e, last[range].keyCode, mapped_axis + 256);
}
else if ((last[range].pressed || last[-range].pressed) && !pressed) {
range = last[range].pressed ? range : -range;
api._keyup(e, last[range].keyCode, mapped_axis + 256);
}
// Update last axis state
last[range].value = value;
last[range].pressed = pressed;
});
});
} : function () {};
/*
* PUBLIC STUFF
*/
/**
* Namespace for standard gamepad mapping constants
* @public
* @namespace GAMEPAD
* @memberOf me.input
*/
api.GAMEPAD = {
/**
* Standard gamepad mapping information for axes"pointermove"
"pointerdown"
"pointerup"
"pointerenter"
"pointerover"
"pointerleave"
"pointercancel"
"wheel"
*
*
* @public
* @name AXES
* @enum {Number}
* @memberOf me.input.GAMEPAD
* @see https://w3c.github.io/gamepad/#remapping
*/
"AXES" : {
"LX" : 0,
"LY" : 1,
"RX" : 2,
"RY" : 3,
"EXTRA_1" : 4,
"EXTRA_2" : 5,
"EXTRA_3" : 6,
"EXTRA_4" : 7
},
/**
* Standard gamepad mapping information for buttonsLX
(horizontal), LY
(vertical)RX
(horizontal), RY
(vertical)EXTRA_1
, EXTRA_2
, EXTRA_3
, EXTRA_4
*
*
* @public
* @name BUTTONS
* @enum {Number}
* @memberOf me.input.GAMEPAD
* @see https://w3c.github.io/gamepad/#remapping
*/
"BUTTONS" : {
"FACE_1" : 0,
"FACE_2" : 1,
"FACE_3" : 2,
"FACE_4" : 3,
"L1" : 4,
"R1" : 5,
"L2" : 6,
"R2" : 7,
"SELECT" : 8,
"BACK" : 8,
"START" : 9,
"FORWARD" : 9,
"L3" : 10,
"R3" : 11,
"UP" : 12,
"DOWN" : 13,
"LEFT" : 14,
"RIGHT" : 15,
"HOME" : 16,
"EXTRA_1" : 17,
"EXTRA_2" : 18,
"EXTRA_3" : 19,
"EXTRA_4" : 20
}
};
/**
* Associate a gamepad event to a keycode
* @name bindGamepad
* @memberOf me.input
* @public
* @function
* @param {Number} index Gamepad index
* @param {Object} button Button/Axis definition
* @param {String} button.type "buttons" or "axes"
* @param {me.input.GAMEPAD.BUTTONS|me.input.GAMEPAD.AXES} button.code button or axis code id
* @param {Number} [button.threshold=1] value indicating when the axis should trigger the keycode (e.g. -0.5 or 0.5)
* @param {me.input.KEY} keyCode
* @example
* // enable the keyboard
* me.input.bindKey(me.input.KEY.X, "shoot");
* ...
* // map the lower face button on the first gamepad to the X key
* me.input.bindGamepad(0, {type:"buttons", code: me.input.GAMEPAD.BUTTONS.FACE_1}, me.input.KEY.X);
* // map the left axis value on the first gamepad to the LEFT key
* me.input.bindGamepad(0, {type:"axes", code: me.input.GAMEPAD.AXES.LX, threshold: -0.5}, me.input.KEY.LEFT);
*/
api.bindGamepad = function (index, button, keyCode) {
// Throw an exception if no action is defined for the specified keycode
if (!api._KeyBinding[keyCode]) {
throw new me.Error("no action defined for keycode " + keyCode);
}
// Allocate bindings if not defined
if (!bindings[index]) {
bindings[index] = {
"axes" : {},
"buttons" : {}
};
}
var mapping = {
"keyCode" : keyCode,
"value" : 0,
"pressed" : false,
"threshold" : button.threshold // can be undefined
};
var binding = bindings[index][button.type];
// Map the gamepad button or axis to the keycode
if (button.type === "buttons") {
// buttons are defined by a `gamePadButton` object
binding[button.code] = mapping;
} else if (button.type === "axes") {
// normalize threshold into a value that can represent both side of the axis
var range = (Math.sign(button.threshold) || 1);
// axes are defined using two objects; one for negative and one for positive
if (!binding[button.code]) {
binding[button.code] = {};
}
var axes = binding[button.code];
axes[range] = mapping;
// Ensure the opposite axis exists
if (!axes[-range]) {
axes[-range] = {
"keyCode" : 0,
"value" : 0,
"pressed" : false,
"threshold" : -range
};
}
}
};
/**
* unbind the defined keycode
* @name unbindGamepad
* @memberOf me.input
* @public
* @function
* @param {Number} index Gamepad index
* @param {me.input.GAMEPAD.BUTTONS} button
* @example
* me.input.unbindGamepad(0, me.input.GAMEPAD.BUTTONS.FACE_1);
*/
api.unbindGamepad = function (index, button) {
if (!bindings[index]) {
throw new me.Error("no bindings for gamepad " + index);
}
bindings[index].buttons[button] = {};
};
/**
* Set deadzone for analog gamepad inputsFACE_1
, FACE_2
, FACE_3
, FACE_4
UP
, DOWN
, LEFT
, RIGHT
L1
, L2
, R1
, R2
L3
, R3
SELECT
(BACK
), START
(FORWARD
), HOME
EXTRA_1
, EXTRA_2
, EXTRA_3
, EXTRA_4
* The default deadzone is 0.1 (10%) Analog values less than this will be ignored
* @name setGamepadDeadzone
* @memberOf me.input
* @public
* @function
* @param {Number} value Deadzone value
*/
api.setGamepadDeadzone = function (value) {
deadzone = value;
};
/**
* specify a custom mapping for a specific gamepad id
* see below for the default mapping :
*
* @name setGamepadMapping
* @memberOf me.input
* @public
* @function
* @param {String} id Gamepad id string
* @param {Object} mapping A hash table
* @param {Number[]} mapping.axes Standard analog control stick axis locations
* @param {Number[]} mapping.buttons Standard digital button locations
* @param {Number[]} [mapping.analog] Analog axis locations for buttons
* @param {me.input~normalize_fn} [mapping.normalize_fn] Axis normalization function
* @example
* // A weird controller that has its axis mappings reversed
* me.input.setGamepadMapping("Generic USB Controller", {
* "axes" : [ 3, 2, 1, 0 ],
* "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]
* });
*
* // Mapping extra axes to analog buttons
* me.input.setGamepadMapping("Generic Analog Controller", {
* "axes" : [ 0, 1, 2, 3 ],
* "buttons" : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ],
*
* // Raw axis 4 is mapped to GAMEPAD.BUTTONS.FACE_1
* // Raw axis 5 is mapped to GAMEPAD.BUTTONS.FACE_2
* // etc...
* // Also maps left and right triggers
* "analog" : [ 4, 5, 6, 7, -1, -1, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1 ],
*
* // Normalize the value of button L2: [-1.0..1.0] => [0.0..1.0]
* "normalize_fn" : function (value, axis, button) {
* return ((button === me.input.GAMEPAD.BUTTONS.L2) ? ((value + 1) / 2) : value) || 0;
* }
* });
*/
api.setGamepadMapping = addMapping;
})(me.input);
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* Base64 decoding
* @see http://www.webtoolkit.info/
* @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 window.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;
}
};
// public method for encoding
singleton.encode = function (input) {
// make sure our input string has the right format
input = input.replace(/\r\n/g, "\n");
if (me.device.nativeBase64) {
// use native encoder
return window.btoa(input);
}
else {
// use cross-browser encoding
var output = [], chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0;
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output.push(_keyStr.charAt(enc1));
output.push(_keyStr.charAt(enc2));
output.push(_keyStr.charAt(enc3));
output.push(_keyStr.charAt(enc4));
}
output = output.join("");
return output;
}
};
return singleton;
})();
/**
* a collection of utility functions
* 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
*/
// guid default value
var GUID_base = "";
var GUID_index = 0;
// regexp to deal with file name & path
var REMOVE_PATH = /^.*(\\|\/|\:)/;
var REMOVE_EXT = /\.[^\.]*$/;
/*
* 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);
};
/**
* Encode binary string into a base64 string
* @public
* @function
* @memberOf me.utils
* @name encodeBase64
* @param {String} input Binary string
* @return {String} Base64 encoded data
*/
api.encodeBase64 = function (input) {
return Base64.encode(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[]} Decoded data
*/
api.decodeBase64AsArray = function (input, bytes) {
bytes = bytes || 1;
var dec = Base64.decode(input), i, j, len;
var ar = new Uint32Array(dec.length / bytes);
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[]} Decompressed data
*/
api.decompress = function () {
throw new me.Error("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 (only numbers, everything else will be converted to NaN)
* @return {Number[]} Decoded data
*/
api.decodeCSV = function (input) {
var entries = input.replace("\n", "").trim().split(",");
var result = [];
for (var i = 0; i < entries.length; i++) {
result.push(+entries[i]);
}
return result;
};
/**
* return the base name of the file without path info.
* @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(REMOVE_PATH, "").replace(REMOVE_EXT, "");
};
/**
* return the extension of the file in the given path
* @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);
};
/**
* 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.CanvasRenderer.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);
}
};
/**
* Normalize a String or Image to an Image reference
* @public
* @function
* @memberOf me.utils
* @name getImage
* @param {Image|String} image Image name or Image reference
* @return {Image} Image reference
*/
api.getImage = function (image) {
return (
(typeof(image) === "string") ?
me.loader.getImage(me.utils.getBasename(image)) :
image
);
};
/**
* reset the GUID Base Name
* the idea here being to have a unique ID
* per level / object
* @ignore
*/
api.resetGUID = function (base, index) {
// also ensure it's only 8bit ASCII characters
GUID_base = base.toString().toUpperCase().toHex();
GUID_index = index || 0;
};
/**
* create and return a very simple GUID
* Game Unique ID
* @ignore
*/
api.createGUID = function (index) {
// to cover the case of undefined id for groups
GUID_index += index || 1;
return GUID_base + "-" + (index || GUID_index);
};
/**
* returns true if the given value is a power of two
* @public
* @function
* @memberOf me.utils
* @name isPowerOfTwo
* @param {Number} val
* @return {boolean}
*/
api.isPowerOfTwo = function (val) {
return (val & (val - 1)) === 0;
};
/**
* returns the next power of two for the given value
* @public
* @function
* @memberOf me.utils
* @name nextPowerOfTwo
* @param {Number} val
* @return {boolean}
*/
api.nextPowerOfTwo = function (val) {
val --;
val |= val >> 1;
val |= val >> 2;
val |= val >> 4;
val |= val >> 8;
val |= val >> 16;
val ++;
return val;
};
// return our object
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*/
(function () {
var rgbaRx = /^rgba?\((\d+), ?(\d+), ?(\d+)(, ?([\d\.]+))?\)$/;
var hex3Rx = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])$/;
var hex4Rx = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])([\da-fA-F])$/;
var hex6Rx = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/;
var hex8Rx = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/;
var cssToRGB = new Map();
[
// CSS1
[ "black", [ 0, 0, 0 ] ],
[ "silver", [ 192, 192, 129 ] ],
[ "gray", [ 128, 128, 128 ] ],
[ "white", [ 255, 255, 255 ] ],
[ "maroon", [ 128, 0, 0 ] ],
[ "red", [ 255, 0, 0 ] ],
[ "purple", [ 128, 0, 128 ] ],
[ "fuchsia", [ 255, 0, 255 ] ],
[ "green", [ 0, 128, 0 ] ],
[ "lime", [ 0, 255, 0 ] ],
[ "olive", [ 128, 128, 0 ] ],
[ "yellow", [ 255, 255, 0 ] ],
[ "navy", [ 0, 0, 128 ] ],
[ "blue", [ 0, 0, 255 ] ],
[ "teal", [ 0, 128, 128 ] ],
[ "aqua", [ 0, 255, 255 ] ],
// CSS2
[ "orange", [ 255, 165, 0 ] ],
// CSS3
[ "aliceblue", [ 240, 248, 245 ] ],
[ "antiquewhite", [ 250, 235, 215 ] ],
[ "aquamarine", [ 127, 255, 212 ] ],
[ "azure", [ 240, 255, 255 ] ],
[ "beige", [ 245, 245, 220 ] ],
[ "bisque", [ 255, 228, 196 ] ],
[ "blanchedalmond", [ 255, 235, 205 ] ],
[ "blueviolet", [ 138, 43, 226 ] ],
[ "brown", [ 165, 42, 42 ] ],
[ "burlywood", [ 222, 184, 35 ] ],
[ "cadetblue", [ 95, 158, 160 ] ],
[ "chartreuse", [ 127, 255, 0 ] ],
[ "chocolate", [ 210, 105, 30 ] ],
[ "coral", [ 255, 127, 80 ] ],
[ "cornflowerblue", [ 100, 149, 237 ] ],
[ "cornsilk", [ 255, 248, 220 ] ],
[ "crimson", [ 220, 20, 60 ] ],
[ "darkblue", [ 0, 0, 139 ] ],
[ "darkcyan", [ 0, 139, 139 ] ],
[ "darkgoldenrod", [ 184, 134, 11 ] ],
[ "darkgray[*]", [ 169, 169, 169 ] ],
[ "darkgreen", [ 0, 100, 0 ] ],
[ "darkgrey[*]", [ 169, 169, 169 ] ],
[ "darkkhaki", [ 189, 183, 107 ] ],
[ "darkmagenta", [ 139, 0, 139 ] ],
[ "darkolivegreen", [ 85, 107, 47 ] ],
[ "darkorange", [ 255, 140, 0 ] ],
[ "darkorchid", [ 153, 50, 204 ] ],
[ "darkred", [ 139, 0, 0 ] ],
[ "darksalmon", [ 233, 150, 122 ] ],
[ "darkseagreen", [ 143, 188, 143 ] ],
[ "darkslateblue", [ 72, 61, 139 ] ],
[ "darkslategray", [ 47, 79, 79 ] ],
[ "darkslategrey", [ 47, 79, 79 ] ],
[ "darkturquoise", [ 0, 206, 209 ] ],
[ "darkviolet", [ 148, 0, 211 ] ],
[ "deeppink", [ 255, 20, 147 ] ],
[ "deepskyblue", [ 0, 191, 255 ] ],
[ "dimgray", [ 105, 105, 105 ] ],
[ "dimgrey", [ 105, 105, 105 ] ],
[ "dodgerblue", [ 30, 144, 255 ] ],
[ "firebrick", [ 178, 34, 34 ] ],
[ "floralwhite", [ 255, 250, 240 ] ],
[ "forestgreen", [ 34, 139, 34 ] ],
[ "gainsboro", [ 220, 220, 220 ] ],
[ "ghostwhite", [ 248, 248, 255 ] ],
[ "gold", [ 255, 215, 0 ] ],
[ "goldenrod", [ 218, 165, 32 ] ],
[ "greenyellow", [ 173, 255, 47 ] ],
[ "grey", [ 128, 128, 128 ] ],
[ "honeydew", [ 240, 255, 240 ] ],
[ "hotpink", [ 255, 105, 180 ] ],
[ "indianred", [ 205, 92, 92 ] ],
[ "indigo", [ 75, 0, 130 ] ],
[ "ivory", [ 255, 255, 240 ] ],
[ "khaki", [ 240, 230, 140 ] ],
[ "lavender", [ 230, 230, 250 ] ],
[ "lavenderblush", [ 255, 240, 245 ] ],
[ "lawngreen", [ 124, 252, 0 ] ],
[ "lemonchiffon", [ 255, 250, 205 ] ],
[ "lightblue", [ 173, 216, 230 ] ],
[ "lightcoral", [ 240, 128, 128 ] ],
[ "lightcyan", [ 224, 255, 255 ] ],
[ "lightgoldenrodyellow", [ 250, 250, 210 ] ],
[ "lightgray", [ 211, 211, 211 ] ],
[ "lightgreen", [ 144, 238, 144 ] ],
[ "lightgrey", [ 211, 211, 211 ] ],
[ "lightpink", [ 255, 182, 193 ] ],
[ "lightsalmon", [ 255, 160, 122 ] ],
[ "lightseagreen", [ 32, 178, 170 ] ],
[ "lightskyblue", [ 135, 206, 250 ] ],
[ "lightslategray", [ 119, 136, 153 ] ],
[ "lightslategrey", [ 119, 136, 153 ] ],
[ "lightsteelblue", [ 176, 196, 222 ] ],
[ "lightyellow", [ 255, 255, 224 ] ],
[ "limegreen", [ 50, 205, 50 ] ],
[ "linen", [ 250, 240, 230 ] ],
[ "mediumaquamarine", [ 102, 205, 170 ] ],
[ "mediumblue", [ 0, 0, 205 ] ],
[ "mediumorchid", [ 186, 85, 211 ] ],
[ "mediumpurple", [ 147, 112, 219 ] ],
[ "mediumseagreen", [ 60, 179, 113 ] ],
[ "mediumslateblue", [ 123, 104, 238 ] ],
[ "mediumspringgreen", [ 0, 250, 154 ] ],
[ "mediumturquoise", [ 72, 209, 204 ] ],
[ "mediumvioletred", [ 199, 21, 133 ] ],
[ "midnightblue", [ 25, 25, 112 ] ],
[ "mintcream", [ 245, 255, 250 ] ],
[ "mistyrose", [ 255, 228, 225 ] ],
[ "moccasin", [ 255, 228, 181 ] ],
[ "navajowhite", [ 255, 222, 173 ] ],
[ "oldlace", [ 253, 245, 230 ] ],
[ "olivedrab", [ 107, 142, 35 ] ],
[ "orangered", [ 255, 69, 0 ] ],
[ "orchid", [ 218, 112, 214 ] ],
[ "palegoldenrod", [ 238, 232, 170 ] ],
[ "palegreen", [ 152, 251, 152 ] ],
[ "paleturquoise", [ 175, 238, 238 ] ],
[ "palevioletred", [ 219, 112, 147 ] ],
[ "papayawhip", [ 255, 239, 213 ] ],
[ "peachpuff", [ 255, 218, 185 ] ],
[ "peru", [ 205, 133, 63 ] ],
[ "pink", [ 255, 192, 203 ] ],
[ "plum", [ 221, 160, 221 ] ],
[ "powderblue", [ 176, 224, 230 ] ],
[ "rosybrown", [ 188, 143, 143 ] ],
[ "royalblue", [ 65, 105, 225 ] ],
[ "saddlebrown", [ 139, 69, 19 ] ],
[ "salmon", [ 250, 128, 114 ] ],
[ "sandybrown", [ 244, 164, 96 ] ],
[ "seagreen", [ 46, 139, 87 ] ],
[ "seashell", [ 255, 245, 238 ] ],
[ "sienna", [ 160, 82, 45 ] ],
[ "skyblue", [ 135, 206, 235 ] ],
[ "slateblue", [ 106, 90, 205 ] ],
[ "slategray", [ 112, 128, 144 ] ],
[ "slategrey", [ 112, 128, 144 ] ],
[ "snow", [ 255, 250, 250 ] ],
[ "springgreen", [ 0, 255, 127 ] ],
[ "steelblue", [ 70, 130, 180 ] ],
[ "tan", [ 210, 180, 140 ] ],
[ "thistle", [ 216, 191, 216 ] ],
[ "tomato", [ 255, 99, 71 ] ],
[ "turquoise", [ 64, 224, 208 ] ],
[ "violet", [ 238, 130, 238 ] ],
[ "wheat", [ 245, 222, 179 ] ],
[ "whitesmoke", [ 245, 245, 245 ] ],
[ "yellowgreen", [ 154, 205, 50 ] ]
].forEach(function (value) {
cssToRGB.set(value[0], value[1]);
});
/**
* A color manipulation object.
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Float32Array|Number} [r=0] red component or array of color components
* @param {Number} [g=0] green component
* @param {Number} [b=0] blue component
* @param {Number} [alpha=1.0] alpha value
*/
me.Color = me.Object.extend(
/** @scope me.Color.prototype */
{
/**
* @ignore
*/
init : function (r, g, b, alpha) {
/**
* Color components in a Float32Array suitable for WebGL
* @name glArray
* @memberOf me.Color
* @type {Float32Array}
* @readonly
*/
if (typeof (this.glArray) === "undefined") {
this.glArray = new Float32Array([ 0.0, 0.0, 0.0, 1.0 ]);
}
return this.setColor(r, g, b, alpha);
},
/**
* Set this color to the specified value.
* @name setColor
* @memberOf me.Color
* @function
* @param {Number} r red component [0 .. 255]
* @param {Number} g green component [0 .. 255]
* @param {Number} b blue component [0 .. 255]
* @param {Number} [alpha=1.0] alpha value [0.0 .. 1.0]
* @return {me.Color} Reference to this object for method chaining
*/
setColor : function (r, g, b, alpha) {
// Private initialization: copy Color value directly
if (r instanceof me.Color) {
this.glArray.set(r.glArray);
return r;
}
this.r = r;
this.g = g;
this.b = b;
this.alpha = alpha;
return this;
},
/**
* Create a new copy of this color object.
* @name clone
* @memberOf me.Color
* @function
* @return {me.Color} Reference to the newly cloned object
*/
clone : function () {
return me.pool.pull("me.Color", this);
},
/**
* Copy a color object or CSS color into this one.
* @name copy
* @memberOf me.Color
* @function
* @param {me.Color|String} color
* @return {me.Color} Reference to this object for method chaining
*/
copy : function (color) {
if (color instanceof me.Color) {
this.glArray.set(color.glArray);
return this;
}
return this.parseCSS(color);
},
/**
* Blend this color with the given one using addition.
* @name add
* @memberOf me.Color
* @function
* @param {me.Color} color
* @return {me.Color} Reference to this object for method chaining
*/
add : function (color) {
this.glArray[0] = (this.glArray[0] + color.glArray[0]).clamp(0, 1);
this.glArray[1] = (this.glArray[1] + color.glArray[1]).clamp(0, 1);
this.glArray[2] = (this.glArray[2] + color.glArray[2]).clamp(0, 1);
this.glArray[3] = (this.glArray[3] + color.glArray[3]) / 2;
return this;
},
/**
* Darken this color value by 0..1
* @name darken
* @memberOf me.Color
* @function
* @param {Number} scale
* @return {me.Color} Reference to this object for method chaining
*/
darken : function (scale) {
scale = scale.clamp(0, 1);
this.glArray[0] *= scale;
this.glArray[1] *= scale;
this.glArray[2] *= scale;
return this;
},
/**
* Lighten this color value by 0..1
* @name lighten
* @memberOf me.Color
* @function
* @param {Number} scale
* @return {me.Color} Reference to this object for method chaining
*/
lighten : function (scale) {
scale = scale.clamp(0, 1);
this.glArray[0] = (this.glArray[0] + (1 - this.glArray[0]) * scale).clamp(0, 1);
this.glArray[1] = (this.glArray[1] + (1 - this.glArray[1]) * scale).clamp(0, 1);
this.glArray[2] = (this.glArray[2] + (1 - this.glArray[2]) * scale).clamp(0, 1);
return this;
},
/**
* Generate random r,g,b values for this color object
* @name random
* @memberOf me.Color
* @function
* @return {me.Color} Reference to this object for method chaining
*/
random : function () {
return this.setColor(
Math.random() * 256,
Math.random() * 256,
Math.random() * 256,
this.alpha
);
},
/**
* Return true if the r,g,b,a values of this color are equal with the
* given one.
* @name equals
* @memberOf me.Color
* @function
* @param {me.Color} color
* @return {Boolean}
*/
equals : function (color) {
return (
(this.glArray[0] === color.glArray[0]) &&
(this.glArray[1] === color.glArray[1]) &&
(this.glArray[2] === color.glArray[2]) &&
(this.glArray[3] === color.glArray[3])
);
},
/**
* Parse a CSS color string and set this color to the corresponding
* r,g,b values
* @name parseCSS
* @memberOf me.Color
* @function
* @param {String} color
* @return {me.Color} Reference to this object for method chaining
*/
parseCSS : function (cssColor) {
// TODO : Memoize this function by caching its input
if (cssToRGB.has(cssColor)) {
return this.setColor.apply(this, cssToRGB.get(cssColor));
}
return this.parseRGB(cssColor);
},
/**
* Parse an RGB or RGBA CSS color string
* @name parseRGB
* @memberOf me.Color
* @function
* @param {String} color
* @return {me.Color} Reference to this object for method chaining
*/
parseRGB : function (rgbColor) {
// TODO : Memoize this function by caching its input
var match = rgbaRx.exec(rgbColor);
if (match) {
return this.setColor(+match[1], +match[2], +match[3], +match[5]);
}
return this.parseHex(rgbColor);
},
/**
* Parse a Hex color ("#RGB", "#RGBA" or "#RRGGBB", "#RRGGBBAA" format) and set this color to
* the corresponding r,g,b,a values
* @name parseHex
* @memberOf me.Color
* @function
* @param {String} color
* @return {me.Color} Reference to this object for method chaining
*/
parseHex : function (hexColor) {
// TODO : Memoize this function by caching its input
var match;
if ((match = hex8Rx.exec(hexColor))) {
// #AARRGGBB
return this.setColor(
parseInt(match[1], 16),
parseInt(match[2], 16),
parseInt(match[3], 16),
(parseInt(match[4], 16).clamp(0, 255) / 255.0).toFixed(1)
);
}
if ((match = hex6Rx.exec(hexColor))) {
// #RRGGBB
return this.setColor(
parseInt(match[1], 16),
parseInt(match[2], 16),
parseInt(match[3], 16)
);
}
if ((match = hex4Rx.exec(hexColor))) {
// #ARGB
return this.setColor(
parseInt(match[1] + match[1], 16),
parseInt(match[2] + match[2], 16),
parseInt(match[3] + match[3], 16),
(parseInt(match[4] + match[4], 16).clamp(0, 255) / 255.0).toFixed(1)
);
}
if ((match = hex3Rx.exec(hexColor))) {
// #RGB
return this.setColor(
parseInt(match[1] + match[1], 16),
parseInt(match[2] + match[2], 16),
parseInt(match[3] + match[3], 16)
);
}
throw new me.Color.Error(
"invalid parameter: " + hexColor
);
},
/**
* Returns the private glArray
* @ignore
*/
toGL : function () {
return this.glArray;
},
/**
* Get the color in "#RRGGBB" format
* @name toHex
* @memberOf me.Color
* @function
* @return {String}
*/
toHex : function () {
// TODO : Memoize this function by caching its result until any of
// the r,g,b,a values are changed
return "#" + this.r.toHex() + this.g.toHex() + this.b.toHex();
},
/**
* Get the color in "#RRGGBBAA" format
* @name toHex8
* @memberOf me.Color
* @function
* @return {String}
*/
toHex8 : function () {
// TODO : Memoize this function by caching its result until any of
// the r,g,b,a values are changed
return "#" + this.r.toHex() + this.g.toHex() + this.b.toHex() + this.alpha.toHex();
},
/**
* Get the color in "rgb(R,G,B)" format
* @name toRGB
* @memberOf me.Color
* @function
* @return {String}
*/
toRGB : function () {
// TODO : Memoize this function by caching its result until any of
// the r,g,b,a values are changed
return "rgb(" +
this.r + "," +
this.g + "," +
this.b +
")";
},
/**
* Get the color in "rgba(R,G,B,A)" format
* @name toRGBA
* @memberOf me.Color
* @function
* @return {String}
*/
toRGBA : function () {
// TODO : Memoize this function by caching its result until any of
// the r,g,b,a values are changed
return "rgba(" +
this.r + "," +
this.g + "," +
this.b + "," +
this.alpha +
")";
}
});
/**
* Color Red Component
* @type Number
* @name r
* @readonly
* @memberOf me.Color
*/
Object.defineProperty(me.Color.prototype, "r", {
/**
* @ignore
*/
get : function () { return ~~(this.glArray[0] * 255); },
/**
* @ignore
*/
set : function (value) { this.glArray[0] = (~~value || 0).clamp(0, 255) / 255.0; },
enumerable : true,
configurable : true
});
/**
* Color Green Component
* @type Number
* @name g
* @readonly
* @memberOf me.Color
*/
Object.defineProperty(me.Color.prototype, "g", {
/**
* @ignore
*/
get : function () { return ~~(this.glArray[1] * 255); },
/**
* @ignore
*/
set : function (value) { this.glArray[1] = (~~value || 0).clamp(0, 255) / 255.0; },
enumerable : true,
configurable : true
});
/**
* Color Blue Component
* @type Number
* @name b
* @readonly
* @memberOf me.Color
*/
Object.defineProperty(me.Color.prototype, "b", {
/**
* @ignore
*/
get : function () { return ~~(this.glArray[2] * 255); },
/**
* @ignore
*/
set : function (value) { this.glArray[2] = (~~value || 0).clamp(0, 255) / 255.0; },
enumerable : true,
configurable : true
});
/**
* Color Alpha Component
* @type Number
* @name alpha
* @readonly
* @memberOf me.Color
*/
Object.defineProperty(me.Color.prototype, "alpha", {
/**
* @ignore
*/
get : function () { return this.glArray[3]; },
/**
* @ignore
*/
set : function (value) { this.glArray[3] = typeof(value) === "undefined" ? 1.0 : (+value).clamp(0, 1); },
enumerable : true,
configurable : true
});
/**
* Base class for me.Color exception handling.
* @name Error
* @class
* @memberOf me.Color
* @constructor
* @param {String} msg Error message.
*/
me.Color.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.Color.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* A singleton object to access the device localStorage area
* @example
* // Initialize "score" and "lives" with default values
* // This loads the properties from localStorage if they exist, else it sets the given defaults
* me.save.add({ score : 0, lives : 3 });
*
* // Print all
* // On first load, this prints { score : 0, lives : 3 }
* // On further reloads, it prints { score : 31337, lives : 3, complexObject : ... }
* // Because the following changes will be saved to localStorage
* console.log(JSON.stringify(me.save));
*
* // Save score
* me.save.score = 31337;
*
* // Also supports complex objects thanks to the JSON backend
* me.save.add({ complexObject : {} })
* me.save.complexObject = { a : "b", c : [ 1, 2, 3, "d" ], e : { f : [{}] } };
*
* // WARNING: Do not set any child properties of complex objects directly!
* // Changes made that way will not save. Always set the entire object value at once.
* // If you cannot live with this limitation, there's a workaround:
* me.save.complexObject.c.push("foo"); // Modify a child property
* me.save.complexObject = me.save.complexObject; // Save the entire object!
*
* // Remove "lives" from localStorage
* me.save.remove("lives");
* @namespace me.save
* @memberOf me
*/
me.save = (function () {
// Variable to hold the object data
var data = {};
// a function to check if the given key is a reserved word
function isReserved(key) {
return (key === "add" || key === "remove");
}
// 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,
/**
* @ignore
*/
get : function () {
return data[prop];
},
/**
* @ignore
*/
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 remove
* @memberOf me.save
* @function
* @param {String} key key to be removed
* @example
* // Remove the "score" key from localStorage
* me.save.remove("score");
*/
remove : 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;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 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 () {
/*
* PUBLIC
*/
// hold public stuff in our singleton
var api = {};
/**
* set and interpret a TMX property value
* @ignore
*/
function setTMXValue(name, type, value) {
var match;
if (typeof(value) !== "string") {
// Value is already normalized (e.g. with JSON maps)
return value;
}
switch (type) {
case "int" :
case "float" :
value = Number(value);
break;
case "bool" :
value = (value === "true");
break;
default :
// try to parse it anyway
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.search(/^json:/i) === 0) {
// try to parse it
match = value.split(/^json:/i)[1];
try {
value = JSON.parse(match);
}
catch (e) {
throw new me.Error("Unable to parse JSON: " + match);
}
}
else if (value.search(/^eval:/i) === 0) {
// try to evaluate it
match = value.split(/^eval:/i)[1];
try {
// eslint-disable-next-line
value = eval(match);
}
catch (e) {
throw new me.Error("Unable to evaluate: " + match);
}
}
else if (
((match = value.match(/^#([\da-fA-F])([\da-fA-F]{3})$/))) ||
((match = value.match(/^#([\da-fA-F]{2})([\da-fA-F]{6})$/)))
) {
value = "#" + match[2] + match[1];
}
// normalize values
if (name.search(/^(ratio|anchorPoint)$/) === 0) {
// convert number to vector
if (typeof(value) === "number") {
value = {
"x" : value,
"y" : value
};
}
}
}
// return the interpreted value
return value;
}
function parseAttributes(obj, elt) {
// do attributes
if (elt.attributes && elt.attributes.length > 0) {
for (var j = 0; j < elt.attributes.length; j++) {
var attribute = elt.attributes.item(j);
if (typeof(attribute.name) !== "undefined") {
// DOM4 (Attr no longer inherit from Node)
obj[attribute.name] = attribute.value;
} else {
// else use the deprecated ones
obj[attribute.nodeName] = attribute.nodeValue;
}
}
}
}
/**
* Decode the given data
* @ignore
*/
api.decode = function (data, encoding, compression) {
compression = compression || "none";
encoding = encoding || "none";
switch (encoding) {
case "csv":
return me.utils.decodeCSV(data);
case "base64":
var decoded = me.utils.decodeBase64AsArray(data, 4);
return (
(compression === "none") ?
decoded :
me.utils.decompress(decoded, compression)
);
case "none":
return data;
case "xml":
throw new me.Error("XML encoding is deprecated, use base64 instead");
default:
throw new me.Error("Unknown layer encoding: " + encoding);
}
};
/**
* Normalize TMX format to Tiled JSON format
* @ignore
*/
api.normalize = function (obj, item) {
var nodeName = item.nodeName;
switch (nodeName) {
case "data":
var data = api.parse(item);
// When no encoding is given, the tiles are stored as individual XML tile elements.
data.encoding = data.encoding || "xml";
obj.data = api.decode(data.text, data.encoding, data.compression);
obj.encoding = "none";
break;
case "imagelayer":
case "layer":
case "objectgroup":
case "group":
var layer = api.parse(item);
layer.type = (nodeName === "layer" ? "tilelayer" : nodeName);
if (layer.image) {
layer.image = layer.image.source;
}
obj.layers = obj.layers || [];
obj.layers.push(layer);
break;
case "animation":
obj.animation = api.parse(item).frames;
break;
case "frame":
case "object":
var name = nodeName + "s";
obj[name] = obj[name] || [];
obj[name].push(api.parse(item));
break;
case "tile":
var tile = api.parse(item);
if (tile.image) {
tile.imagewidth = tile.image.width;
tile.imageheight = tile.image.height;
tile.image = tile.image.source;
}
obj.tiles = obj.tiles || {};
obj.tiles[tile.id] = tile;
break;
case "tileset":
var tileset = api.parse(item);
if (tileset.image) {
tileset.imagewidth = tileset.image.width;
tileset.imageheight = tileset.image.height;
tileset.image = tileset.image.source;
}
obj.tilesets = obj.tilesets || [];
obj.tilesets.push(tileset);
break;
case "polygon":
case "polyline":
obj[nodeName] = [];
// Get a point array
var points = api.parse(item).points.split(" ");
// And normalize them into an array of vectors
for (var i = 0, v; i < points.length; i++) {
v = points[i].split(",");
obj[nodeName].push({
"x" : +v[0],
"y" : +v[1]
});
}
break;
case "properties":
obj.properties = api.parse(item);
break;
case "property":
var property = api.parse(item);
obj[property.name] = setTMXValue(
property.name,
// in XML type is undefined for "string" values
property.type || "string",
property.value
);
break;
default:
obj[nodeName] = api.parse(item);
break;
}
};
/**
* Parse a XML TMX object and returns the corresponding javascript object
* @ignore
*/
api.parse = function (xml) {
// Create the return object
var obj = {};
var text = "";
if (xml.nodeType === 1) {
// do attributes
parseAttributes(obj, xml);
}
// do children
if (xml.hasChildNodes()) {
for (var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
switch (item.nodeType) {
case 1:
api.normalize(obj, item);
break;
case 3:
text += item.nodeValue.trim();
break;
}
}
}
if (text) {
obj.text = text;
}
return obj;
};
/**
* Apply TMX Properties to the given object
* @ignore
*/
api.applyTMXProperties = function (obj, data) {
var properties = data.properties;
var types = data.propertytypes;
if (typeof(properties) !== "undefined") {
for (var name in properties) {
if (properties.hasOwnProperty(name)) {
var type = "string";
if (typeof(types) !== "undefined") {
type = types[name];
}
// set the value
obj[name] = setTMXValue(name, type, properties[name]);
}
}
}
};
// return our object
return api;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tile QT 0.1.0 format
* http://www.mapeditor.org/
*
*/
(function () {
/**
* TMX Group
* contains an object group definition as defined in Tiled.
* note : object group definition is translated into the virtual `me.game.world` using `me.Container`.
* @see me.Container
* @class
* @extends me.Object
* @memberOf me
* @constructor
*/
me.TMXGroup = me.Object.extend({
/**
* @ignore
*/
init : function (map, data, z) {
/**
* group name
* @public
* @type String
* @name name
* @memberOf me.TMXGroup
*/
this.name = data.name;
/**
* group width
* @public
* @type Number
* @name width
* @memberOf me.TMXGroup
*/
this.width = data.width || 0;
/**
* group height
* @public
* @type Number
* @name height
* @memberOf me.TMXGroup
*/
this.height = data.height || 0;
/**
* group z order
* @public
* @type Number
* @name z
* @memberOf me.TMXGroup
*/
this.z = z;
/**
* group objects list definition
* @see me.TMXObject
* @public
* @type Array
* @name name
* @memberOf me.TMXGroup
*/
this.objects = [];
var visible = typeof(data.visible) !== "undefined" ? data.visible : true;
this.opacity = (visible === true) ? (+data.opacity || 1.0).clamp(0.0, 1.0) : 0;
// check if we have any user-defined properties
me.TMXUtils.applyTMXProperties(this, data);
// parse all child objects/layers
var self = this;
if (data.objects) {
var _objects = data.objects;
_objects.forEach(function (object) {
self.objects.push(new me.TMXObject(map, object, z));
});
}
if (data.layers) {
var _layers = data.layers;
_layers.forEach(function (object) {
var layer = new me.TMXLayer(map.tilewidth, map.tileheight, map.orientation, map.tilesets, z++);
// init the layer properly
layer.initFromJSON(object);
// set a renderer
layer.setRenderer(map.getRenderer(layer));
// resize container accordingly
self.width = Math.max(self.width, layer.width);
self.height = Math.max(self.height, layer.height);
self.objects.push(layer);
});
}
},
/**
* reset function
* @ignore
* @function
*/
destroy : 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];
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tile QT 0.7.x format
* http://www.mapeditor.org/
*
*/
(function () {
/**
* a TMX Object defintion, as defined in Tiled.
* note : object definition are translated into the virtual `me.game.world` using `me.Entity`.
* @see me.Entity
* @class
* @extends Object
* @memberOf me
* @constructor
*/
me.TMXObject = me.Object.extend({
/**
* @ignore
*/
init : function (map, tmxObj, z) {
/**
* object point list (for Polygon and PolyLine)
* @public
* @type Vector2d[]
* @name points
* @memberOf me.TMXObject
*/
this.points = undefined;
/**
* object name
* @public
* @type String
* @name name
* @memberOf me.TMXObject
*/
this.name = tmxObj.name;
/**
* object x position
* @public
* @type Number
* @name x
* @memberOf me.TMXObject
*/
this.x = +tmxObj.x;
/**
* object y position
* @public
* @type Number
* @name y
* @memberOf me.TMXObject
*/
this.y = +tmxObj.y;
/**
* object z order
* @public
* @type Number
* @name z
* @memberOf me.TMXObject
*/
this.z = +z;
/**
* object width
* @public
* @type Number
* @name width
* @memberOf me.TMXObject
*/
this.width = +tmxObj.width || 0;
/**
* object height
* @public
* @type Number
* @name height
* @memberOf me.TMXObject
*/
this.height = +tmxObj.height || 0;
/**
* object gid value
* when defined the object is a tiled object
* @public
* @type Number
* @name gid
* @memberOf me.TMXObject
*/
this.gid = +tmxObj.gid || null;
/**
* object type
* @public
* @type String
* @name type
* @memberOf me.TMXObject
*/
this.type = tmxObj.type;
/**
* The rotation of the object in radians clockwise (defaults to 0)
* @public
* @type Number
* @name rotation
* @memberOf me.TMXObject
*/
this.rotation = Number.prototype.degToRad(+tmxObj.rotation || 0);
/**
* object unique identifier per level (Tiled 0.11.x+)
* @public
* @type Number
* @name id
* @memberOf me.TMXObject
*/
this.id = +tmxObj.id || undefined;
/**
* object orientation (orthogonal or isometric)
* @public
* @type String
* @name orientation
* @memberOf me.TMXObject
*/
this.orientation = map.orientation;
/**
* the collision shapes defined for this object
* @public
* @type Array
* @name shapes
* @memberOf me.TMXObject
*/
this.shapes = undefined;
/**
* if true, the object is an Ellipse
* @public
* @type Boolean
* @name isEllipse
* @memberOf me.TMXObject
*/
this.isEllipse = false;
/**
* if true, the object is a Polygon
* @public
* @type Boolean
* @name isPolygon
* @memberOf me.TMXObject
*/
this.isPolygon = false;
/**
* if true, the object is a PolyLine
* @public
* @type Boolean
* @name isPolyLine
* @memberOf me.TMXObject
*/
this.isPolyLine = false;
// check if the object has an associated gid
if (typeof this.gid === "number") {
this.setTile(map.tilesets);
}
else {
if (typeof(tmxObj.ellipse) !== "undefined") {
this.isEllipse = true;
}
else {
var points = tmxObj.polygon;
if (typeof(points) !== "undefined") {
this.isPolygon = true;
}
else {
points = tmxObj.polyline;
if (typeof(points) !== "undefined") {
this.isPolyLine = true;
}
}
if (typeof(points) !== "undefined") {
this.points = [];
var self = this;
points.forEach(function (point) {
self.points.push(new me.Vector2d(point.x, point.y));
});
}
}
}
// Adjust the Position to match Tiled
map.getRenderer().adjustPosition(this);
// set the object properties
me.TMXUtils.applyTMXProperties(this, tmxObj);
// define the object shapes if required
if (!this.shapes) {
this.shapes = this.parseTMXShapes();
}
},
/**
* set the object image (for Tiled Object)
* @ignore
* @function
*/
setTile : function (tilesets) {
// get the corresponding tileset
var tileset = tilesets.getTilesetByGid(this.gid);
if (tileset.isCollection === false) {
// set width and height equal to tile size
this.width = this.framewidth = tileset.tilewidth;
this.height = this.frameheight = tileset.tileheight;
}
// the object corresponding tile object
this.tile = new me.Tile(this.x, this.y, this.gid, tileset);
},
/**
* parses the TMX shape definition and returns a corresponding array of me.Shape object
* @name parseTMXShapes
* @memberOf me.TMXObject
* @private
* @function
* @return {me.Polygon[]|me.Line[]|me.Ellipse[]} an array of shape objects
*/
parseTMXShapes : function () {
var i = 0;
var shapes = [];
// add an ellipse shape
if (this.isEllipse === true) {
// ellipse coordinates are the center position, so set default to the corresonding radius
shapes.push((new me.Ellipse(
this.width / 2,
this.height / 2,
this.width,
this.height
)).rotate(this.rotation));
}
// add a polygon
else if (this.isPolygon === true) {
shapes.push((new me.Polygon(0, 0, this.points)).rotate(this.rotation));
}
// add a polyline
else if (this.isPolyLine === true) {
var p = this.points;
var p1, p2;
var segments = p.length - 1;
for (i = 0; i < segments; i++) {
// clone the value before, as [i + 1]
// is reused later by the next segment
p1 = p[i];
p2 = p[i + 1].clone();
if (this.rotation !== 0) {
p1 = p1.rotate(this.rotation);
p2 = p2.rotate(this.rotation);
}
shapes.push(new me.Line(0, 0, [ p1, p2 ]));
}
}
// it's a rectangle, returns a polygon object anyway
else {
shapes.push((new me.Polygon(
0, 0, [
new me.Vector2d(), new me.Vector2d(this.width, 0),
new me.Vector2d(this.width, this.height), new me.Vector2d(0, this.height)
]
)).rotate(this.rotation));
}
// Apply isometric projection
if (this.orientation === "isometric") {
for (i = 0; i < shapes.length; i++) {
shapes[i].toIso();
}
}
return shapes;
},
/**
* getObjectPropertyByName
* @ignore
* @function
*/
getObjectPropertyByName : function (name) {
return this[name];
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tile QT 0.7.x format
* http://www.mapeditor.org/
*
*/
(function () {
// bitmask constants to check for flipped & rotated tiles
var TMX_FLIP_H = 0x80000000,
TMX_FLIP_V = 0x40000000,
TMX_FLIP_AD = 0x20000000,
TMX_CLEAR_BIT_MASK = ~(0x80000000 | 0x40000000 | 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} gid tile gid
* @param {me.TMXTileset} tileset the corresponding tileset object
*/
me.Tile = me.Rect.extend({
/** @ignore */
init : function (x, y, gid, tileset) {
var width, height;
// determine the tile size
if (tileset.isCollection) {
var image = tileset.getTileImage(gid & TMX_CLEAR_BIT_MASK);
width = image.width;
height = image.height;
} else {
width = tileset.tilewidth;
height = tileset.tileheight;
}
// call the parent constructor
me.Rect.prototype.init.apply(this, [x * width, y * height, width, height]);
/**
* tileset
* @public
* @type me.TMXTileset
* @name me.Tile#tileset
*/
this.tileset = tileset;
/**
* the tile transformation matrix (if defined)
* @ignore
*/
this.currentTransform = null;
// Tile col / row pos
this.col = x;
this.row = y;
/**
* tileId
* @public
* @type int
* @name me.Tile#tileId
*/
this.tileId = gid;
/**
* True if the tile is flipped horizontally
* @public
* @type Boolean
* @name me.Tile#flipX
*/
this.flippedX = (this.tileId & TMX_FLIP_H) !== 0;
/**
* True if the tile is flipped vertically
* @public
* @type Boolean
* @name me.Tile#flippedY
*/
this.flippedY = (this.tileId & TMX_FLIP_V) !== 0;
/**
* True if the tile is flipped anti-diagonally
* @public
* @type Boolean
* @name me.Tile#flippedAD
*/
this.flippedAD = (this.tileId & TMX_FLIP_AD) !== 0;
/**
* Global flag that indicates if the tile is flipped
* @public
* @type Boolean
* @name me.Tile#flipped
*/
this.flipped = this.flippedX || this.flippedY || this.flippedAD;
// create a transformation matrix if required
if (this.flipped === true) {
this.createTransform();
}
// clear out the flags and set the tileId
this.tileId &= TMX_CLEAR_BIT_MASK;
},
/**
* create a transformation matrix for this tile
* @ignore
*/
createTransform : function () {
if (this.currentTransform === null) {
this.currentTransform = new me.Matrix2d();
} else {
// reset the matrix
this.currentTransform.identity();
}
if (this.flippedAD) {
// Use shearing to swap the X/Y axis
this.currentTransform.setTransform(
0, 1, 0,
1, 0, 0,
0, 0, 1
);
this.currentTransform.translate(0, this.height - this.width);
}
if (this.flippedX) {
this.currentTransform.translate(
(this.flippedAD ? 0 : this.width),
(this.flippedAD ? this.height : 0)
);
this.currentTransform.scaleX(-1);
}
if (this.flippedY) {
this.currentTransform.translate(
(this.flippedAD ? this.width : 0),
(this.flippedAD ? 0 : this.height)
);
this.currentTransform.scaleY(-1);
}
},
/**
* return a renderable object for this Tile object
* @name me.Tile#getRenderable
* @public
* @function
* @param {Object} [settings] see {@link me.Sprite}
* @return {me.Renderable} a me.Sprite object
*/
getRenderable : function (settings) {
var renderable;
var tileset = this.tileset;
if (tileset.animations.has(this.tileId)) {
var frames = [];
var frameId = [];
(tileset.animations.get(this.tileId).frames).forEach(function (frame) {
frameId.push(frame.tileid);
frames.push({
name : "" + frame.tileid,
delay : frame.duration
});
});
renderable = tileset.texture.createAnimationFromName(frameId, settings);
renderable.addAnimation(this.tileId - tileset.firstgid, frames);
renderable.setCurrentAnimation(this.tileId - tileset.firstgid);
} else {
if (tileset.isCollection === true) {
var image = tileset.getTileImage(this.tileId);
renderable = new me.Sprite(0, 0,
Object.assign({
image: image
})//, settings)
);
renderable.anchorPoint.set(0, 0);
renderable.scale((settings.width / this.width), (settings.height / this.height));
if (typeof settings.rotation !== "undefined") {
renderable.anchorPoint.set(0.5, 0.5);
renderable.currentTransform.rotate(settings.rotation);
renderable.currentTransform.translate(settings.width / 2, settings.height / 2);
// TODO : move the rotation related code from TMXTiledMap to here (under)
settings.rotation = undefined;
}
} else {
renderable = tileset.texture.createSpriteFromName(this.tileId - tileset.firstgid, settings);
}
}
// any H/V flipping to apply?
if (this.flippedX) {
renderable.currentTransform.scaleX(-1);
}
if (this.flippedY) {
renderable.currentTransform.scaleY(-1);
}
return renderable;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tile QT 0.7.x format
* http://www.mapeditor.org/
*
*/
(function () {
// bitmask constants to check for flipped & rotated tiles
var TMX_CLEAR_BIT_MASK = ~(0x80000000 | 0x40000000 | 0x20000000);
/**
* a TMX Tile Set Object
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Object} tileset tileset JSON definition
*/
me.TMXTileset = me.Object.extend({
/**
* constructor
* @ignore
*/
init: function (tileset) {
var i = 0;
// first gid
// tile properties (collidable, etc..)
this.TileProperties = [];
// hold reference to each tile image
this.imageCollection = [];
this.firstgid = this.lastgid = +tileset.firstgid;
// check if an external tileset is defined
if (typeof(tileset.source) !== "undefined") {
var src = tileset.source;
var ext = me.utils.getFileExtension(src);
if (ext === "tsx" || ext === "json") {
// load the external tileset (TSX/JSON)
tileset = me.loader.getTMX(me.utils.getBasename(src));
if (!tileset) {
throw new me.Error(src + " external TSX/JSON tileset not found");
}
}
}
this.name = tileset.name;
this.tilewidth = +tileset.tilewidth;
this.tileheight = +tileset.tileheight;
this.spacing = +tileset.spacing || 0;
this.margin = +tileset.margin || 0;
// set tile offset properties (if any)
this.tileoffset = new me.Vector2d();
/**
* Tileset contains animated tiles
* @public
* @type Boolean
* @name me.TMXTileset#isAnimated
*/
this.isAnimated = false;
/**
* true if the tileset is a "Collection of Image" Tileset
* @public
* @type Boolean
* @name me.TMXTileset#isCollection
*/
this.isCollection = false;
/**
* Tileset animations
* @private
* @type Map
* @name me.TMXTileset#animations
*/
this.animations = new Map();
/**
* Remember the last update timestamp to prevent too many animation updates
* @private
* @type Map
* @name me.TMXTileset#_lastUpdate
*/
this._lastUpdate = 0;
var tiles = tileset.tiles;
for (i in tiles) {
if (tiles.hasOwnProperty(i)) {
if ("animation" in tiles[i]) {
this.isAnimated = true;
this.animations.set(+i + this.firstgid, {
dt : 0,
idx : 0,
frames : tiles[i].animation,
cur : tiles[i].animation[0]
});
}
// set tile properties, if any (XML format)
if ("properties" in tiles[i]) {
this.setTileProperty(+i + this.firstgid, tiles[i].properties);
}
if ("image" in tiles[i]) {
var image = me.utils.getImage(tiles[i].image);
if (!image) {
throw new me.TMXTileset.Error("melonJS: '" + tiles[i].image + "' file for tile '" + (+i + this.firstgid) + "' not found!");
}
this.imageCollection[+i + this.firstgid] = image;
}
}
}
this.isCollection = this.imageCollection.length > 0;
var offset = tileset.tileoffset;
if (offset) {
this.tileoffset.x = +offset.x;
this.tileoffset.y = +offset.y;
}
// set tile properties, if any (JSON format)
var tileInfo = tileset.tileproperties;
if (tileInfo) {
for (i in tileInfo) {
if (tileInfo.hasOwnProperty(i)) {
this.setTileProperty(+i + this.firstgid, tileInfo[i]);
}
}
}
// if not a tile image collection
if (this.isCollection === false) {
// get the global tileset texture
this.image = me.utils.getImage(tileset.image);
if (!this.image) {
throw new me.TMXTileset.Error("melonJS: '" + tileset.image + "' file for tileset '" + this.name + "' not found!");
}
// create a texture atlas for the given tileset
this.texture = me.video.renderer.cache.get(this.image, {
framewidth : this.tilewidth,
frameheight : this.tileheight,
margin : this.margin,
spacing : this.spacing
});
this.atlas = this.texture.getAtlas();
// calculate the number of tiles per horizontal line
var hTileCount = +tileset.columns || ~~(this.image.width / (this.tilewidth + this.spacing));
var vTileCount = ~~(this.image.height / (this.tileheight + this.spacing));
// compute the last gid value in the tileset
this.lastgid = this.firstgid + (((hTileCount * vTileCount) - 1) || 0);
if (tileset.tilecount && this.lastgid - this.firstgid + 1 !== +tileset.tilecount) {
console.warn(
"Computed tilecount (" + (this.lastgid - this.firstgid + 1) +
") does not match expected tilecount (" + tileset.tilecount + ")"
);
}
}
},
/**
* return the tile image from a "Collection of Image" tileset
* @name me.TMXTileset#getTileImage
* @public
* @function
* @param {Number} gid
* @return {Image} corresponding image or undefined
*/
getTileImage : function (gid) {
return this.imageCollection[gid];
},
/**
* set the tile properties
* @ignore
* @function
*/
setTileProperty : function (gid, prop) {
// set the given tile id
this.TileProperties[gid] = prop;
},
/**
* return true if the gid belongs to the tileset
* @name me.TMXTileset#contains
* @public
* @function
* @param {Number} gid
* @return {Boolean}
*/
contains : function (gid) {
return gid >= this.firstgid && gid <= this.lastgid;
},
/**
* Get the view (local) tile ID from a GID, with animations applied
* @name me.TMXTileset#getViewTileId
* @public
* @function
* @param {Number} gid Global tile ID
* @return {Number} View tile ID
*/
getViewTileId : function (gid) {
if (this.animations.has(gid)) {
// apply animations
gid = this.animations.get(gid).cur.tileid;
}
else {
// get the local tileset id
gid -= this.firstgid;
}
return gid;
},
/**
* return the properties of the specified tile
* @name me.TMXTileset#getTileProperties
* @public
* @function
* @param {Number} tileId
* @return {Object}
*/
getTileProperties: function (tileId) {
return this.TileProperties[tileId];
},
// update tile animations
update : function (dt) {
var duration = 0,
now = me.timer.getTime(),
result = false;
if (this._lastUpdate !== now) {
this._lastUpdate = now;
this.animations.forEach(function (anim) {
anim.dt += dt;
duration = anim.cur.duration;
while (anim.dt >= duration) {
anim.dt -= duration;
anim.idx = (anim.idx + 1) % anim.frames.length;
anim.cur = anim.frames[anim.idx];
duration = anim.cur.duration;
result = true;
}
});
}
return result;
},
// draw the x,y tile
drawTile : function (renderer, dx, dy, tmxTile) {
// check if any transformation is required
if (tmxTile.flipped) {
renderer.save();
// apply the tile current transform
renderer.translate(dx, dy);
renderer.transform(tmxTile.currentTransform);
// reset both values as managed through transform();
dx = dy = 0;
}
// check if the tile has an associated image
if (this.isCollection === true) {
// draw the tile
renderer.drawImage(
this.imageCollection[tmxTile.tileId],
0, 0,
tmxTile.width, tmxTile.height,
dx, dy,
tmxTile.width, tmxTile.height
);
} else {
// use the tileset texture
var offset = this.atlas[this.getViewTileId(tmxTile.tileId)].offset;
// draw the tile
renderer.drawImage(
this.image,
offset.x, offset.y,
this.tilewidth, this.tileheight,
dx, dy,
this.tilewidth + renderer.uvOffset, this.tileheight + renderer.uvOffset
);
}
if (tmxTile.flipped) {
// restore the context to the previous state
renderer.restore();
}
}
});
/**
* an object containing all tileset
* @class
* @memberOf me
* @constructor
*/
me.TMXTilesetGroup = me.Object.extend({
/**
* constructor
* @ignore
*/
init: function () {
this.tilesets = [];
this.length = 0;
},
/**
* add a tileset to the tileset group
* @name me.TMXTilesetGroup#add
* @public
* @function
* @param {me.TMXTileset} tileset
*/
add : function (tileset) {
this.tilesets.push(tileset);
this.length++;
},
/**
* return the tileset at the specified index
* @name me.TMXTilesetGroup#getTilesetByIndex
* @public
* @function
* @param {Number} i
* @return {me.TMXTileset} corresponding tileset
*/
getTilesetByIndex : function (i) {
return this.tilesets[i];
},
/**
* return the tileset corresponding to the specified id
* 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;
// clear the gid of all flip/rotation flags
gid &= TMX_CLEAR_BIT_MASK;
// cycle through all tilesets
for (var i = 0, len = this.tilesets.length; i < len; i++) {
// return the corresponding tileset if matching
if (this.tilesets[i].contains(gid)) {
return this.tilesets[i];
}
// typically indicates a layer with no asset loaded (collision?)
if (this.tilesets[i].firstgid === this.tilesets[i].lastgid &&
gid >= this.tilesets[i].firstgid) {
// store the id if the [firstgid .. lastgid] is invalid
invalidRange = i;
}
}
// return the tileset with the invalid range
if (invalidRange !== -1) {
return this.tilesets[invalidRange];
}
else {
throw new me.Error("no matching tileset found for gid " + gid);
}
}
});
/**
* Base class for TMXTileset exception handling.
* @name Error
* @class
* @memberOf me.TMXTileset
* @constructor
* @param {String} msg Error message.
*/
me.TMXTileset.Error = me.Error.extend({
/**
* @ignore
*/
init : function (msg) {
me.Error.prototype.init.apply(this, [ msg ]);
this.name = "me.TMXTileset.Error";
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tiled (0.7+) format
* http://www.mapeditor.org/
*
*/
(function () {
// scope global var & constants
var offsetsStaggerX = [
{x: 0, y: 0},
{x: + 1, y: - 1},
{x: + 1, y: 0},
{x: + 2, y: 0}
];
var offsetsStaggerY = [
{x: 0, y: 0},
{x: - 1, y: + 1},
{x: 0, y: + 1},
{x: 0, y: + 2}
];
/**
* The map renderer base class
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {Number} cols width of the tilemap in tiles
* @param {Number} rows height of the tilemap in tiles
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
*/
me.TMXRenderer = me.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
* @name me.TMXRenderer#canRender
* @public
* @function
* @param {me.TMXTileMap|me.TMXLayer} component TMX Map or Layer
* @return {boolean}
*/
canRender : function (component) {
return (
(this.cols === component.cols) &&
(this.rows === component.rows) &&
(this.tilewidth === component.tilewidth) &&
(this.tileheight === component.tileheight)
);
},
/**
* return the tile position corresponding to the specified pixel
* @name me.TMXRenderer#pixelToTileCoords
* @public
* @function
* @param {Number} x X coordinate
* @param {Number} y Y coordinate
* @param {me.Vector2d} [vector] an optional vector object where to put the return values
* @return {me.Vector2d}
*/
pixelToTileCoords : function (x, y, v) {
return v;
},
/**
* return the pixel position corresponding of the specified tile
* @name me.TMXRenderer#tileToPixelCoords
* @public
* @function
* @param {Number} col tile horizontal position
* @param {Number} row tile vertical position
* @param {me.Vector2d} [vector] an optional vector object where to put the return values
* @return {me.Vector2d}
*/
tileToPixelCoords : function (x, y, v) {
return v;
},
/**
* return the tile position corresponding for the given X coordinate
* @name me.TMXRenderer#pixelToTileX
* @public
* @function
* @param {Number} x X coordinate
* @return {Number} tile vertical position
*/
pixelToTileX : function (x) {
},
/**
* return the tile position corresponding for the given Y coordinates
* @name me.TMXRenderer#pixelToTileY
* @public
* @function
* @param {Number} y Y coordinate
* @return {Number} tile horizontal position
*/
pixelToTileY : function (y) {
},
/**
* draw the given tile at the specified layer
* @name me.TMXRenderer#drawTile
* @public
* @function
* @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object
* @param {Number} x X coordinate where to draw the tile
* @param {Number} y Y coordinate where to draw the tile
* @param {me.Tile} tile the tile object to draw
*/
drawTile : function (renderer, x, y, tile) {
},
/**
* draw the given TMX Layer for the given area
* @name me.TMXRenderer#drawTileLayer
* @public
* @function
* @param {me.CanvasRenderer|me.WebGLRenderer} renderer a renderer object
* @param {me.TMXLayer} layer a TMX Layer object
* @param {me.Rect} rect the area of the layer to draw
*/
drawTileLayer : function (renderer, layer, rect) {
}
});
/**
* an Orthogonal Map Renderder
* @memberOf me
* @extends me.TMXRenderer
* @memberOf me
* @constructor
* @param {Number} cols width of the tilemap in tiles
* @param {Number} rows height of the tilemap in tiles
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
*/
me.TMXOrthogonalRenderer = me.TMXRenderer.extend({
/**
* return true if the renderer can render the specified layer
* @ignore
*/
canRender : function (layer) {
return (
(layer.orientation === "orthogonal") &&
me.TMXRenderer.prototype.canRender.apply(this, [ layer ])
);
},
/**
* return the tile position corresponding to the specified pixel
* @ignore
*/
pixelToTileCoords : function (x, y, v) {
var ret = v || new me.Vector2d();
return ret.set(
this.pixelToTileX(x),
this.pixelToTileY(y)
);
},
/**
* return the tile position corresponding for the given X coordinate
* @ignore
*/
pixelToTileX : function (x) {
return x / this.tilewidth;
},
/**
* return the tile position corresponding for the given Y coordinates
* @ignore
*/
pixelToTileY : function (y) {
return y / this.tileheight;
},
/**
* return the pixel position corresponding of the specified tile
* @ignore
*/
tileToPixelCoords : function (x, y, v) {
var ret = v || new me.Vector2d();
return ret.set(
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 (renderer, x, y, tmxTile) {
var tileset = tmxTile.tileset;
// draw the tile
tileset.drawTile(
renderer,
tileset.tileoffset.x + x * this.tilewidth,
tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight,
tmxTile
);
},
/**
* draw the tile map
* @ignore
*/
drawTileLayer : function (renderer, layer, rect) {
// get top-left and bottom-right tile position
var start = this.pixelToTileCoords(
Math.max(rect.pos.x - (layer.maxTileSize.width - layer.tilewidth), 0),
Math.max(rect.pos.y - (layer.maxTileSize.height - layer.tileheight), 0),
me.pool.pull("me.Vector2d")
).floorSelf();
var end = this.pixelToTileCoords(
rect.pos.x + rect.width + this.tilewidth,
rect.pos.y + rect.height + this.tileheight,
me.pool.pull("me.Vector2d")
).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(renderer, x, y, tmxTile);
}
}
}
me.pool.push(start);
me.pool.push(end);
}
});
/**
* an Isometric Map Renderder
* @memberOf me
* @extends me.TMXRenderer
* @memberOf me
* @constructor
* @param {Number} cols width of the tilemap in tiles
* @param {Number} rows height of the tilemap in tiles
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
*/
me.TMXIsometricRenderer = me.TMXRenderer.extend({
// constructor
init: function (cols, rows, tilewidth, tileheight) {
me.TMXRenderer.prototype.init.apply(this, [
cols,
rows,
tilewidth,
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") &&
me.TMXRenderer.prototype.canRender.apply(this, [ layer ])
);
},
/**
* return the tile position corresponding to the specified pixel
* @ignore
*/
pixelToTileCoords : function (x, y, v) {
var ret = v || new me.Vector2d();
return ret.set(
this.pixelToTileX(x, y),
this.pixelToTileY(y, x)
);
},
/**
* return the tile position corresponding for the given X coordinate
* @ignore
*/
pixelToTileX : function (x, y) {
return (y / this.tileheight) + ((x - this.originX) / this.tilewidth);
},
/**
* return the tile position corresponding for the given Y coordinates
* @ignore
*/
pixelToTileY : function (y, x) {
return (y / this.tileheight) - ((x - this.originX) / this.tilewidth);
},
/**
* return the pixel position corresponding of the specified tile
* @ignore
*/
tileToPixelCoords : function (x, y, v) {
var ret = v || new me.Vector2d();
return ret.set(
(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 = me.pool.pull("me.Vector2d");
this.tileToPixelCoords(tileX, tileY, isoPos);
obj.x = isoPos.x;
obj.y = isoPos.y;
me.pool.push(isoPos);
},
/**
* draw the tile map
* @ignore
*/
drawTile : function (renderer, x, y, tmxTile) {
var tileset = tmxTile.tileset;
// draw the tile
tileset.drawTile(
renderer,
((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 (renderer, 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,
me.pool.pull("me.Vector2d")
).floorSelf();
var TileEnd = this.pixelToTileCoords(
rect.pos.x + rect.width + tileset.tilewidth,
rect.pos.y + rect.height + tileset.tileheight,
me.pool.pull("me.Vector2d")
).ceilSelf();
var rectEnd = this.tileToPixelCoords(TileEnd.x, TileEnd.y, me.pool.pull("me.Vector2d"));
// Determine the tile and pixel coordinates to start at
var startPos = this.tileToPixelCoords(rowItr.x, rowItr.y, me.pool.pull("me.Vector2d"));
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 * 2; y - this.tileheight * 2 < rectEnd.y * 2; y += this.tileheight) {
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(
renderer,
offset.x + x,
offset.y + y / 2 - 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;
}
}
me.pool.push(rowItr);
me.pool.push(TileEnd);
me.pool.push(rectEnd);
me.pool.push(startPos);
}
});
/**
* an Hexagonal Map Renderder
* @memberOf me
* @extends me.TMXRenderer
* @memberOf me
* @constructor
* @param {Number} cols width of the tilemap in tiles
* @param {Number} rows height of the tilemap in tiles
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
*/
me.TMXHexagonalRenderer = me.TMXRenderer.extend({
// constructor
init: function (cols, rows, tilewidth, tileheight, hexsidelength, staggeraxis, staggerindex) {
me.TMXRenderer.prototype.init.apply(this, [
cols,
rows,
tilewidth,
tileheight
]);
this.hexsidelength = hexsidelength;
this.staggeraxis = staggeraxis;
this.staggerindex = staggerindex;
this.sidelengthx = 0;
this.sidelengthy = 0;
if (staggeraxis === "x") {
this.sidelengthx = hexsidelength;
}
else {
this.sidelengthy = hexsidelength;
}
this.sideoffsetx = (this.tilewidth - this.sidelengthx) / 2;
this.sideoffsety = (this.tileheight - this.sidelengthy) / 2;
this.columnwidth = this.sideoffsetx + this.sidelengthx;
this.rowheight = this.sideoffsety + this.sidelengthy;
this.centers = [
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d(),
new me.Vector2d()
];
},
/**
* return true if the renderer can render the specified layer
* @ignore
*/
canRender : function (layer) {
return (
(layer.orientation === "hexagonal") &&
me.TMXRenderer.prototype.canRender.apply(this, [ layer ])
);
},
/**
* return the tile position corresponding to the specified pixel
* @ignore
*/
pixelToTileCoords : function (x, y, v) {
var q, r;
var ret = v || new me.Vector2d();
if (this.staggeraxis === "x") { //flat top
x = x - ((this.staggerindex === "odd") ? this.sideoffsetx : this.tilewidth);
}
else { //pointy top
y = y - ((this.staggerindex === "odd") ? this.sideoffsety : this.tileheight);
}
// Start with the coordinates of a grid-aligned tile
var referencePoint = me.pool.pull("me.Vector2d",
Math.floor(x / (this.columnwidth * 2)),
Math.floor((y / (this.rowheight * 2)))
);
// Relative x and y position on the base square of the grid-aligned tile
var rel = me.pool.pull("me.Vector2d",
x - referencePoint.x * (this.columnwidth * 2),
y - referencePoint.y * (this.rowheight * 2)
);
// Adjust the reference point to the correct tile coordinates
if (this.staggeraxis === "x") {
referencePoint.x = referencePoint.x * 2;
if (this.staggerindex === "even") {
++referencePoint.x;
}
}
else {
referencePoint.y = referencePoint.y * 2;
if (this.staggerindex === "even") {
++referencePoint.y;
}
}
// Determine the nearest hexagon tile by the distance to the center
var left, top, centerX, centerY;
if (this.staggeraxis === "x") {
left = this.sidelengthx / 2;
centerX = left + this.columnwidth;
centerY = this.tileheight / 2;
this.centers[0].set(left, centerY);
this.centers[1].set(centerX, centerY - this.rowheight);
this.centers[2].set(centerX, centerY + this.rowheight);
this.centers[3].set(centerX + this.columnwidth, centerY);
}
else {
top = this.sidelengthy / 2;
centerX = this.tilewidth / 2;
centerY = top + this.rowheight;
this.centers[0].set(centerX, top);
this.centers[1].set(centerX - this.columnwidth, centerY);
this.centers[2].set(centerX + this.columnwidth, centerY);
this.centers[3].set(centerX, centerY + this.rowheight);
}
var nearest = 0;
var minDist = Number.MAX_VALUE;
var dc;
for (var i = 0; i < 4; ++i) {
dc = Math.pow(this.centers[i].x - rel.x, 2) + Math.pow(this.centers[i].y - rel.y, 2);
if (dc < minDist) {
minDist = dc;
nearest = i;
}
}
var offsets = (this.staggeraxis === "x") ? offsetsStaggerX : offsetsStaggerY;
q = referencePoint.x + offsets[nearest].x;
r = referencePoint.y + offsets[nearest].y;
me.pool.push(referencePoint);
me.pool.push(rel);
return ret.set(q, r);
},
/**
* return the tile position corresponding for the given X coordinate
* @ignore
*/
pixelToTileX : function (x, y) {
var ret = me.pool.pull("me.Vector2d");
this.pixelToTileCoords(x, y, ret);
me.pool.push(ret);
return ret.x;
},
/**
* return the tile position corresponding for the given Y coordinates
* @ignore
*/
pixelToTileY : function (y, x) {
var ret = me.pool.pull("me.Vector2d");
this.pixelToTileCoords(x, y, ret);
me.pool.push(ret);
return ret.y;
},
/**
* return the pixel position corresponding of the specified tile
* @ignore
*/
tileToPixelCoords : function (q, r, v) {
var x, y;
var ret = v || new me.Vector2d();
if (this.staggeraxis === "x") {
//flat top
x = q * this.columnwidth;
if (this.staggerindex === "odd") {
y = r * (this.tileheight + this.sidelengthy);
y = y + (this.rowheight * (q & 1));
}
else {
y = r * (this.tileheight + this.sidelengthy);
y = y + (this.rowheight * (1 - (q & 1)));
}
}
else {
//pointy top
y = r * this.rowheight;
if (this.staggerindex === "odd") {
x = q * (this.tilewidth + this.sidelengthx);
x = x + (this.columnwidth * (r & 1));
}
else {
x = q * (this.tilewidth + this.sidelengthx);
x = x + (this.columnwidth * (1 - (r & 1)));
}
}
return ret.set(x, y);
},
/**
* 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 (renderer, x, y, tmxTile) {
var tileset = tmxTile.tileset;
var point = this.tileToPixelCoords(x, y, me.pool.pull("me.Vector2d"));
// draw the tile
tileset.drawTile(
renderer,
tileset.tileoffset.x + point.x,
tileset.tileoffset.y + point.y + (this.tileheight - tileset.tileheight),
tmxTile
);
me.pool.push(point);
},
/**
* draw the tile map
* @ignore
*/
drawTileLayer : function (renderer, 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
start.x = start.x < 0 ? 0 : start.x;
start.y = start.y < 0 ? 0 : start.y;
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(renderer, x, y, tmxTile);
}
}
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* Set a tiled layer Data
* @ignore
*/
function setLayerData(layer, data) {
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 = data[idx++];
// fill the array
if (gid !== 0) {
// add a new tile to the layer
layer.setTile(x, y, gid);
}
}
}
}
/**
* a TMX Tile Layer Object
* Tiled QT 0.7.x format
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {Number} tilewidth width of each tile in pixels
* @param {Number} tileheight height of each tile in pixels
* @param {String} orientation "isometric" or "orthogonal"
* @param {me.TMXTilesetGroup} tilesets tileset as defined in Tiled
* @param {Number} z z-index position
*/
me.TMXLayer = me.Renderable.extend({
/**
* @ignore
*/
init: function (tilewidth, tileheight, orientation, tilesets, z) {
// super constructor
me.Renderable.prototype.init.apply(this, [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
// XXX: Is this even used?
this.tileset = (this.tilesets ? this.tilesets.getTilesetByIndex(0) : null);
// Biggest tile size to draw
this.maxTileSize = {
"width" : 0,
"height" : 0
};
for (var i = 0; i < this.tilesets.length; i++) {
var tileset = this.tilesets.getTilesetByIndex(i);
this.maxTileSize.width = Math.max(this.maxTileSize.width, tileset.tilewidth);
this.maxTileSize.height = Math.max(this.maxTileSize.height, tileset.tileheight);
}
/**
* All animated tilesets in this layer
* @ignore
* @type Array
* @name me.TMXLayer#animatedTilesets
*/
this.animatedTilesets = [];
/**
* Layer contains tileset animations
* @public
* @type Boolean
* @name me.TMXLayer#isAnimated
*/
this.isAnimated = false;
// for displaying order
this.pos.z = z;
// tiled default coordinates are top-left
this.anchorPoint.set(0, 0);
},
/** @ignore */
initFromJSON: function (data) {
// additional TMX flags
this.name = data.name;
this.cols = +data.width;
this.rows = +data.height;
// hexagonal maps only
this.hexsidelength = +data.hexsidelength || undefined;
this.staggeraxis = data.staggeraxis;
this.staggerindex = data.staggerindex;
// layer opacity
var visible = typeof(data.visible) !== "undefined" ? +data.visible : 1;
this.setOpacity(visible ? +data.opacity : 0);
// layer "real" size
if (this.orientation === "isometric") {
this.width = (this.cols + this.rows) * (this.tilewidth / 2);
this.height = (this.cols + this.rows) * (this.tileheight / 2);
} else {
this.width = this.cols * this.tilewidth;
this.height = this.rows * this.tileheight;
}
// check if we have any user-defined properties
me.TMXUtils.applyTMXProperties(this, data);
// check for the correct rendering method
if (typeof (this.preRender) === "undefined") {
this.preRender = me.sys.preRender;
}
// if pre-rendering method is use, create an offline canvas/renderer
if (this.preRender === true) {
this.canvasRenderer = new me.CanvasRenderer(
me.video.createCanvas(this.width, this.height),
this.width, this.height,
{ transparent : true }
);
}
//initialize the layer data array
this.initArray(this.cols, this.rows);
// parse the layer data
setLayerData(this,
me.TMXUtils.decode(
data.data,
data.encoding,
data.compression
)
);
},
// called when the layer is added to the game world or a container
onActivateEvent : function () {
// (re)initialize the layer data array
/*if (this.layerData === undefined) {
this.initArray(this.cols, this.rows);
}*/
if (this.animatedTilesets === undefined) {
this.animatedTilesets = [];
}
if (this.tilesets) {
var tileset = this.tilesets.tilesets;
for (var i = 0; i < tileset.length; i++) {
if (tileset[i].isAnimated) {
this.animatedTilesets.push(tileset[i]);
}
}
}
this.isAnimated = this.animatedTilesets.length > 0;
// Force pre-render off when tileset animation is used
if (this.isAnimated) {
this.preRender = false;
}
// Resize the bounding rect
this.getBounds().resize(this.width, this.height);
},
// called when the layer is removed from the game world or a container
onDeactivateEvent : function () {
// clear all allocated objects
//this.layerData = undefined;
this.animatedTilesets = undefined;
},
/**
* Se the TMX renderer for this layer object
* @name setRenderer
* @memberOf me.TMXLayer
* @public
* @function
* @param {me.TMXRenderer} renderer
*/
setRenderer : function (renderer) {
this.renderer = renderer;
},
/**
* Return the layer current renderer object
* @name getRenderer
* @memberOf me.TMXLayer
* @public
* @function
* @return {me.TMXRenderer} renderer
*/
getRenderer : function (renderer) {
return this.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 world/pixels coordinates)
* @param {Number} y Y coordinate (in world/pixels coordinates)
* @return {Number} TileId or null if there is no Tile at the given position
*/
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 world/pixels coordinates)
* @param {Number} y Y coordinate (in world/pixels coordinates)
* @return {me.Tile} corresponding tile or null if outside of the map area
* @example
* // get the TMX Map Layer called "Front layer"
* var layer = me.game.world.getChildByName("Front Layer")[0];
* // get the tile object corresponding to the latest pointer position
* var tile = layer.getTile(me.input.pointer.pos.x, me.input.pointer.pos.y);
*/
getTile : function (x, y) {
if (this.containsPoint(x, y)) {
var renderer = this.renderer;
var col = ~~renderer.pixelToTileX(x, y);
var row = ~~renderer.pixelToTileY(y, x);
if ((col >= 0 && col < renderer.cols) && (row >= 0 && row < renderer.rows)) {
return this.layerData[col][row];
}
}
// return null if no corresponding tile
return null;
},
/**
* Create a new Tile at the specified position
* @name setTile
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x X coordinate (in map coordinates: row/column)
* @param {Number} y Y coordinate (in map coordinates: row/column)
* @param {Number} tileId tileId
* @return {me.Tile} the corresponding newly created tile object
*/
setTile : function (x, y, tileId) {
if (!this.tileset.contains(tileId)) {
// look for the corresponding tileset
this.tileset = this.tilesets.getTilesetByGid(tileId);
}
var tile = this.layerData[x][y] = new me.Tile(x, y, tileId, this.tileset);
// draw the corresponding tile
if (this.preRender) {
this.renderer.drawTile(this.canvasRenderer, x, y, tile);
}
return tile;
},
/**
* clear the tile at the specified position
* @name clearTile
* @memberOf me.TMXLayer
* @public
* @function
* @param {Number} x X coordinate (in map coordinates: row/column)
* @param {Number} y Y coordinate (in map coordinates: row/column)
* @example
* me.game.world.getChildByType(me.TMXLayer).forEach(function(layer) {
* // clear all tiles at the given x,y coordinates
* layer.clearTile(x, y);
* });
*/
clearTile : function (x, y) {
// clearing tile
this.layerData[x][y] = null;
// erase the corresponding area in the canvas
if (this.preRender) {
this.canvasRenderer.clearRect(x * this.tilewidth, y * this.tileheight, this.tilewidth, this.tileheight);
}
},
/**
* update animations in a tileset layer
* @ignore
*/
update : function (dt) {
if (this.isAnimated) {
var result = false;
for (var i = 0; i < this.animatedTilesets.length; i++) {
result = this.animatedTilesets[i].update(dt) || result;
}
return result;
}
return false;
},
/**
* draw a tileset layer
* @ignore
*/
draw : function (renderer, rect) {
// use the offscreen canvas
if (this.preRender) {
var width = Math.min(rect.width, this.width);
var height = Math.min(rect.height, this.height);
// draw using the cached canvas
renderer.drawImage(
this.canvasRenderer.getCanvas(),
rect.pos.x, rect.pos.y, // sx,sy
width, height, // sw,sh
rect.pos.x, rect.pos.y, // dx,dy
width, height // dw,dh
);
}
// dynamically render the layer
else {
// draw the layer
this.renderer.drawTileLayer(renderer, this, rect);
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
* Tile QT 0.1.0 format
* http://www.mapeditor.org/
*
*/
(function () {
// constant to identify the collision object layer
var COLLISION_GROUP = "collision";
/**
* set a compatible renderer object
* for the specified map
* @ignore
*/
function getNewDefaultRenderer(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
);
case "hexagonal":
return new me.TMXHexagonalRenderer(
obj.cols,
obj.rows,
obj.tilewidth,
obj.tileheight,
obj.hexsidelength,
obj.staggeraxis,
obj.staggerindex
);
// if none found, throw an exception
default:
throw new me.Error(obj.orientation + " type TMX Tile Map not supported!");
}
}
/**
* read the layer Data
* @ignore
*/
function readLayer(map, data, z) {
var layer = new me.TMXLayer(map.tilewidth, map.tileheight, map.orientation, map.tilesets, z);
// init the layer properly
layer.initFromJSON(data);
// set a renderer
layer.setRenderer(map.getRenderer(layer));
return layer;
}
/**
* read the Image Layer Data
* @ignore
*/
function readImageLayer(map, data, z) {
// Normalize properties
me.TMXUtils.applyTMXProperties(data.properties, data);
// create the layer
var imageLayer = new me.ImageLayer(
+data.x || 0,
+data.y || 0,
Object.assign({
name: data.name,
image: data.image,
z: z
}, data.properties)
);
// set some additional flags
var visible = typeof(data.visible) !== "undefined" ? data.visible : true;
imageLayer.setOpacity(visible ? +data.opacity : 0);
return imageLayer;
}
/**
* read the tileset Data
* @ignore
*/
function readTileset(data) {
return (new me.TMXTileset(data));
}
/**
* read the object group Data
* @ignore
*/
function readObjectGroup(map, data, z) {
return (new me.TMXGroup(map, data, z));
}
/**
* a TMX Tile Map Object
* Tiled QT +0.7.x format
* @class
* @extends me.Object
* @memberOf me
* @constructor
* @param {String} levelId name of TMX map
* @param {Object} data TMX map in JSON format
* @example
* // create a new level object based on the TMX JSON object
* var level = new me.TMXTileMap(levelId, me.loader.getTMX(levelId));
* // add the level to the game world container
* level.addTo(me.game.world, true);
*/
me.TMXTileMap = me.Object.extend({
// constructor
init: function (levelId, data) {
/**
* name of the tilemap
* @public
* @type String
* @name me.TMXTileMap#name
*/
this.name = levelId;
/**
* the level data (JSON)
* @ignore
*/
this.data = data;
/**
* width of the tilemap in tiles
* @public
* @type Int
* @name me.TMXTileMap#cols
*/
this.cols = +data.width;
/**
* height of the tilemap in tiles
* @public
* @type Int
* @name me.TMXTileMap#rows
*/
this.rows = +data.height;
/**
* Tile width
* @public
* @type Int
* @name me.TMXTileMap#tilewidth
*/
this.tilewidth = +data.tilewidth;
/**
* Tile height
* @public
* @type Int
* @name me.TMXTileMap#tileheight
*/
this.tileheight = +data.tileheight;
// tilesets for this map
this.tilesets = null;
// layers
this.layers = [];
// group objects
this.objectGroups = [];
// tilemap version
this.version = data.version;
// map type (orthogonal or isometric)
this.orientation = data.orientation;
if (this.orientation === "isometric") {
this.width = (this.cols + this.rows) * (this.tilewidth / 2);
this.height = (this.cols + this.rows) * (this.tileheight / 2);
} else {
this.width = this.cols * this.tilewidth;
this.height = this.rows * this.tileheight;
}
// objects minimum z order
this.z = 0;
// object id
this.nextobjectid = +data.nextobjectid || undefined;
// hex/iso properties
this.hexsidelength = +data.hexsidelength || undefined;
this.staggeraxis = data.staggeraxis;
this.staggerindex = data.staggerindex;
// background color
this.backgroundcolor = data.backgroundcolor;
// set additional map properties (if any)
me.TMXUtils.applyTMXProperties(this, data);
// internal flag
this.initialized = false;
},
/**
* Return the map default renderer
* @name getRenderer
* @memberOf me.TMXTileMap
* @public
* @function
* @param {me.TMXLayer} [layer] a layer object
* @return {me.TMXRenderer} a TMX renderer
*/
getRenderer : function (layer) {
// first ensure a renderer is associated to this map
if ((typeof(this.renderer) === "undefined") || (!this.renderer.canRender(this))) {
this.renderer = getNewDefaultRenderer(this);
}
// return a renderer for the given layer (if any)
if ((typeof(layer) !== "undefined") && (!this.renderer.canRender(layer))) {
return getNewDefaultRenderer(layer);
}
// else return this renderer
return this.renderer;
},
/**
* parse the map
* @ignore
*/
readMapObjects: function (data) {
if (this.initialized === true) {
return;
}
// to automatically increment z index
var zOrder = this.z;
var self = this;
// Tileset information
if (!this.tilesets) {
// make sure we have a TilesetGroup Object
this.tilesets = new me.TMXTilesetGroup();
}
// parse all tileset objects
if (typeof (data.tilesets) !== "undefined") {
var tilesets = data.tilesets;
tilesets.forEach(function (tileset) {
// add the new tileset
self.tilesets.add(readTileset(tileset));
});
}
// check if a user-defined background color is defined
if (this.backgroundcolor) {
this.layers.push(
new me.ColorLayer(
"background_color",
this.backgroundcolor,
zOrder++
)
);
}
// check if a background image is defined
if (this.background_image) {
// add a new image layer
this.layers.push(new me.ImageLayer(
0, 0, {
name : "background_image",
image : this.background_image,
z : zOrder++
}
));
}
data.layers.forEach(function (layer) {
switch (layer.type) {
case "imagelayer":
self.layers.push(readImageLayer(self, layer, zOrder++));
break;
case "tilelayer":
self.layers.push(readLayer(self, layer, zOrder++));
break;
// get the object groups information
case "objectgroup":
self.objectGroups.push(readObjectGroup(self, layer, zOrder++));
break;
// get the object groups information
case "group":
self.objectGroups.push(readObjectGroup(self, layer, zOrder++));
break;
default:
break;
}
});
this.initialized = true;
},
/**
* add all the map layers and objects to the given container
* @name me.TMXTileMap#addTo
* @public
* @function
* @param {me.Container} target container
* @param {boolean} flatten if true, flatten all objects into the given container
* @example
* // create a new level object based on the TMX JSON object
* var level = new me.TMXTileMap(levelId, me.loader.getTMX(levelId));
* // add the level to the game world container
* level.addTo(me.game.world, true);
*/
addTo : function (container, flatten) {
var _sort = container.autoSort;
var _depth = container.autoDepth;
// disable auto-sort and auto-depth
container.autoSort = false;
container.autoDepth = false;
// add all layers instances
this.getLayers().forEach(function (layer) {
container.addChild(layer);
});
// add all Object instances
this.getObjects(flatten).forEach(function (object) {
container.addChild(object);
});
// set back auto-sort and auto-depth
container.autoSort = _sort;
container.autoDepth = _depth;
// force a sort
container.sort(true);
},
/**
* return an Array of instantiated objects, based on the map object definition
* @name me.TMXTileMap#getObjects
* @public
* @function
* @param {boolean} flatten if true, flatten all objects into the returned array,
* ignoring all defined groups (no sub containers will be created)
* @return {me.Renderable[]} Array of Objects
*/
getObjects : function (flatten) {
var objects = [];
var isCollisionGroup = false;
var targetContainer;
// parse the map for objects
this.readMapObjects(this.data);
for (var g = 0; g < this.objectGroups.length; g++) {
var group = this.objectGroups[g];
// check if this is the collision shape group
isCollisionGroup = group.name.toLowerCase().includes(COLLISION_GROUP);
if (flatten === false) {
// create a new container
targetContainer = new me.Container(0, 0, this.width, this.height);
// tiled uses 0,0 by default
targetContainer.anchorPoint.set(0, 0);
// set additional properties
targetContainer.name = group.name;
targetContainer.pos.z = group.z;
targetContainer.setOpacity(group.opacity);
// disable auto-sort and auto-depth
targetContainer.autoSort = false;
targetContainer.autoDepth = 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 settings
var settings = group.objects[o];
// reference to the instantiated object
var obj;
// Tiled uses 0,0 by default
if (typeof (settings.anchorPoint) === "undefined") {
settings.anchorPoint = {x : 0, y : 0};
}
// groups can contains either objects or layers
if (settings instanceof me.TMXLayer) {
// layers are alerady instantiated & initialized
obj = settings;
// z value set already
} else {
// pull the corresponding entity from the object pool
obj = me.pool.pull(
settings.name || "me.Entity",
settings.x, settings.y,
settings
);
// set the obj z order
obj.pos.z = settings.z;
}
// check if a me.Tile object is embedded
if (typeof (settings.tile) === "object" && !obj.renderable) {
obj.renderable = settings.tile.getRenderable(settings);
// adjust position if necessary
switch (settings.rotation) {
case Math.PI:
obj.translate(-obj.renderable.width, obj.renderable.height);
break;
case Math.PI / 2 :
obj.translate(0, obj.renderable.height);
break;
case -(Math.PI / 2) :
obj.translate(-obj.renderable.width, 0);
break;
default :
// this should not happen
break;
}
// tile object use use left-bottom coordinates
//obj.anchorPoint.set(0, 1);
}
if (isCollisionGroup && !settings.name) {
// configure the body accordingly
obj.body.collisionType = me.collision.types.WORLD_SHAPE;
}
//apply group opacity value to the child objects if group are merged
if (flatten === true) {
if (obj.isRenderable === true) {
obj.setOpacity(obj.getOpacity() * group.opacity);
// and to child renderables if any
if (obj.renderable instanceof me.Renderable) {
obj.renderable.setOpacity(obj.renderable.getOpacity() * group.opacity);
}
}
// directly add the obj into the objects array
objects.push(obj);
} else /* false*/ {
// add it to the new container
targetContainer.addChild(obj);
}
}
// if we created a new container
if ((flatten === false) && (targetContainer.children.length > 0)) {
// re-enable auto-sort and auto-depth
targetContainer.autoSort = true;
targetContainer.autoDepth = true;
// add our container to the world
objects.push(targetContainer);
}
}
return objects;
},
/**
* return all the existing layers
* @name me.TMXTileMap#getLayers
* @public
* @function
* @return {me.TMXLayer[]} Array of Layers
*/
getLayers : function () {
// parse the map for objects
this.readMapObjects(this.data);
return this.layers;
},
/**
* destroy function, clean all allocated objects
* @name me.TMXTileMap#destroy
* @public
* @function
*/
destroy : function () {
this.tilesets = undefined;
this.layers = [];
this.objectGroups = [];
this.initialized = false;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* a level manager object
* once ressources loaded, the level director contains all references of defined levels
* 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 api = {};
/*
* PRIVATE STUFF
*/
// our levels
var levels = {};
// level index table
var levelIdx = [];
// current level index
var currentLevelIdx = 0;
// onresize handler
var onresize_handler = null;
function safeLoadLevel(levelId, options, restart) {
// clean the destination container
options.container.destroy();
// reset the renderer
me.video.renderer.reset();
// clean the current (previous) level
if (levels[api.getCurrentLevelId()]) {
levels[api.getCurrentLevelId()].destroy();
}
// update current level index
currentLevelIdx = levelIdx.indexOf(levelId);
// add the specified level to the game world
loadTMXLevel(levelId, options.container, options.flatten, options.setViewportBounds);
// publish the corresponding message
me.event.publish(me.event.LEVEL_LOADED, [ levelId ]);
// fire the callback
options.onLoaded(levelId);
if (restart) {
// resume the game loop if it was previously running
me.state.restart();
}
}
/**
* Load a TMX level
* @name loadTMXLevel
* @memberOf me.game
* @private
* @param {String} level level id
* @param {me.Container} target container
* @param {boolean} flatten if true, flatten all objects into the given container
* @param {boolean} setViewportBounds if true, set the viewport bounds to the map size
* @ignore
* @function
*/
function loadTMXLevel(levelId, container, flatten, setViewportBounds) {
var level = levels[levelId];
// disable auto-sort for the given container
var autoSort = container.autoSort;
container.autoSort = false;
if (setViewportBounds) {
// update the viewport bounds
me.game.viewport.setBounds(
0, 0,
Math.max(level.width, me.game.viewport.width),
Math.max(level.height, me.game.viewport.height)
);
}
// reset the GUID generator
// and pass the level id as parameter
me.utils.resetGUID(levelId, level.nextobjectid);
// Tiled use 0,0 anchor coordinates
container.anchorPoint.set(0, 0);
// add all level elements to the target container
level.addTo(container, flatten);
// sort everything (recursively)
container.sort(true);
container.autoSort = autoSort;
container.resize(level.width, level.height);
function resize_container() {
// center the map if smaller than the current viewport
container.pos.set(
Math.max(0, ~~((me.game.viewport.width - level.width) / 2)),
Math.max(0, ~~((me.game.viewport.height - level.height) / 2)),
0
);
}
if (setViewportBounds) {
resize_container();
// Replace the resize handler
if (onresize_handler) {
me.event.unsubscribe(onresize_handler);
}
onresize_handler = me.event.subscribe(me.event.VIEWPORT_ONRESIZE, resize_container);
}
}
/*
* PUBLIC STUFF
*/
/**
* reset the level director
* @ignore
*/
api.reset = function () {};
/**
* add a level
* @ignore
*/
api.addLevel = function () {
throw new me.Error("no level loader defined");
};
/**
* add a TMX level
* @ignore
*/
api.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, me.loader.getTMX(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
* (will also create all level defined entities, etc..)
* @name loadLevel
* @memberOf me.levelDirector
* @public
* @function
* @param {String} level level id
* @param {Object} [options] additional optional parameters
* @param {me.Container} [options.container=me.game.world] container in which to load the specified level
* @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
* @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
* @param {boolean} [options.setViewportBounds=true] if true, set the viewport bounds to the map size
* @example
* // the game assets to be be preloaded
* // TMX maps
* var resources = [
* {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 into the game world
* me.levelDirector.loadLevel("a4_level1");
* ...
* ...
* // load a level into a specific container
* var levelContainer = new me.Container();
* me.levelDirector.loadLevel("a4_level2", {container:levelContainer});
* // add a simple transformation
* levelContainer.currentTransform.translate(levelContainer.width / 2, levelContainer.height / 2 );
* levelContainer.currentTransform.rotate(0.05);
* levelContainer.currentTransform.translate(-levelContainer.width / 2, -levelContainer.height / 2 );
* // add it to the game world
* me.game.world.addChild(levelContainer);
*/
api.loadLevel = function (levelId, options) {
options = Object.assign({
"container" : me.game.world,
"onLoaded" : me.game.onLevelLoaded,
"flatten" : me.game.mergeGroup,
"setViewportBounds" : true
}, options || {});
// throw an exception if not existing
if (typeof(levels[levelId]) === "undefined") {
throw new me.Error("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();
safeLoadLevel.defer(this, levelId, options, true);
}
else {
safeLoadLevel(levelId, options);
}
}
else {
throw new me.Error("no level loader defined");
}
return true;
};
/**
* return the current level id
* @name getCurrentLevelId
* @memberOf me.levelDirector
* @public
* @function
* @return {String}
*/
api.getCurrentLevelId = function () {
return levelIdx[currentLevelIdx];
};
/**
* return the current level definition.
* for a reference to the live instantiated level,
* rather use the container in which it was loaded (e.g. me.game.world)
* @name getCurrentLevel
* @memberOf me.levelDirector
* @public
* @function
* @return {me.TMXTileMap}
*/
api.getCurrentLevel = function () {
return levels[api.getCurrentLevelId()];
};
/**
* reload the current level
* @name reloadLevel
* @memberOf me.levelDirector
* @public
* @function
* @param {Object} [options] additional optional parameters
* @param {me.Container} [options.container=me.game.world] container in which to load the specified level
* @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
* @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
*/
api.reloadLevel = function (options) {
// reset the level to initial state
//levels[currentLevel].reset();
return api.loadLevel(api.getCurrentLevelId(), options);
};
/**
* load the next level
* @name nextLevel
* @memberOf me.levelDirector
* @public
* @function
* @param {Object} [options] additional optional parameters
* @param {me.Container} [options.container=me.game.world] container in which to load the specified level
* @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
* @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
*/
api.nextLevel = function (options) {
//go to the next level
if (currentLevelIdx + 1 < levelIdx.length) {
return api.loadLevel(levelIdx[currentLevelIdx + 1], options);
}
else {
return false;
}
};
/**
* load the previous level
* @name previousLevel
* @memberOf me.levelDirector
* @public
* @function
* @param {Object} [options] additional optional parameters
* @param {me.Container} [options.container=me.game.world] container in which to load the specified level
* @param {function} [options.onLoaded=me.game.onLevelLoaded] callback for when the level is fully loaded
* @param {boolean} [options.flatten=me.game.mergeGroup] if true, flatten all objects into the given container
*/
api.previousLevel = function (options) {
// go to previous level
if (currentLevelIdx - 1 >= 0) {
return api.loadLevel(levelIdx[currentLevelIdx - 1], options);
}
else {
return false;
}
};
/**
* return the amount of level preloaded
* @name levelCount
* @memberOf me.levelDirector
* @public
* @function
*/
api.levelCount = function () {
return levelIdx.length;
};
// return our object
return api;
})();
})();
/**
* @preserve Tween JS
* https://github.com/sole/Tween.js
*/
/* eslint-disable quotes, keyword-spacing, comma-spacing, no-return-assign */
(function() {
/**
* Javascript Tweening Engine
* author mr.doob / http://mrdoob.com
* author Robert Eisele / http://www.xarg.org
* author Philippe / http://philippe.elsass.me
* author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html
* author Paul Lewis / http://www.aerotwist.com/
* author lechecacharro
* 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 = null;
var _valuesStart = null;
var _valuesEnd = null;
var _valuesStartRepeat = null;
var _duration = null;
var _repeat = null;
var _yoyo = null;
var _reversed = null;
var _delayTime = null;
var _startTime = null;
var _easingFunction = null;
var _interpolationFunction = null;
var _chainedTweens = null;
var _onStartCallback = null;
var _onStartCallbackFired = null;
var _onUpdateCallback = null;
var _onCompleteCallback = null;
var _tweenTimeTracker = null;
// comply with the container contract
this.isRenderable = false;
/**
* @ignore
*/
this._resumeCallback = function (elapsed) {
if (_startTime) {
_startTime += elapsed;
}
};
/**
* @ignore
*/
this.setProperties = function (object) {
_object = object;
_valuesStart = {};
_valuesEnd = {};
_valuesStartRepeat = {};
_duration = 1000;
_repeat = 0;
_yoyo = false;
_reversed = false;
_delayTime = 0;
_startTime = null;
_easingFunction = me.Tween.Easing.Linear.None;
_interpolationFunction = me.Tween.Interpolation.Linear;
_chainedTweens = [];
_onStartCallback = null;
_onStartCallbackFired = false;
_onUpdateCallback = null;
_onCompleteCallback = null;
_tweenTimeTracker = me.timer.lastUpdate;
// reset flags to default value
this.isPersistent = false;
// this is not really supported
this.updateWhenPaused = false;
// Set all starting values present on the target object
for ( var field in object ) {
if(typeof object !== 'object') {
_valuesStart[ field ] = parseFloat(object[field], 10);
}
}
};
this.setProperties(object);
/**
* reset the tween object to default value
* @ignore
*/
this.onResetEvent = function ( object ) {
this.setProperties(object);
};
/**
* subscribe to the resume event when added
* @ignore
*/
this.onActivateEvent = function () {
me.event.subscribe(me.event.STATE_RESUME, this._resumeCallback);
};
/**
* Unsubscribe when tween is removed
* @ignore
*/
this.onDeactivateEvent = function () {
me.event.unsubscribe(me.event.STATE_RESUME, this._resumeCallback);
};
/**
* 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 ( _time ) {
_onStartCallbackFired = false;
// add the tween to the object pool on start
me.game.world.addChild(this);
_startTime = (typeof(_time) === 'undefined' ? me.timer.getTime() : _time) + _delayTime;
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 () {
// remove the tween from the world container
me.game.world.removeChildNow(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;
};
/**
* 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} fn easing function
*/
this.easing = function ( easing ) {
if (typeof easing !== 'function') {
throw new me.Tween.Error("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} fn interpolation 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 ( dt ) {
// the original Tween implementation expect
// a timestamp and not a time delta
_tweenTimeTracker = (me.timer.lastUpdate > _tweenTimeTracker) ? me.timer.lastUpdate : _tweenTimeTracker + dt;
var time = _tweenTimeTracker;
var property;
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;
}
_valuesStart[ property ] = _valuesStartRepeat[ property ];
}
if (_yoyo) {
_reversed = !_reversed;
}
_startTime = time + _delayTime;
return true;
} else {
// remove the tween from the world container
me.game.world.removeChildNow(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 :
*
* me.Tween.Easing.Quadratic.In
* me.Tween.Easing.Quadratic.Out
* me.Tween.Easing.Quadratic.InOut
* me.Tween.Easing.Cubic.In
* me.Tween.Easing.Cubic.Out
* me.Tween.Easing.Cubic.InOut
* me.Tween.Easing.Quartic.In
* me.Tween.Easing.Quartic.Out
* me.Tween.Easing.Quartic.InOut
* me.Tween.Easing.Quintic.In
* me.Tween.Easing.Quintic.Out
* me.Tween.Easing.Quintic.InOut
* me.Tween.Easing.Sinusoidal.In
* me.Tween.Easing.Sinusoidal.Out
* me.Tween.Easing.Sinusoidal.InOut
* me.Tween.Easing.Exponential.In
* me.Tween.Easing.Exponential.Out
* me.Tween.Easing.Exponential.InOut
* me.Tween.Easing.Circular.In
* me.Tween.Easing.Circular.Out
* me.Tween.Easing.Circular.InOut
* me.Tween.Easing.Elastic.In
* me.Tween.Easing.Elastic.Out
* me.Tween.Easing.Elastic.InOut
* me.Tween.Easing.Back.In
* me.Tween.Easing.Back.Out
* me.Tween.Easing.Back.InOut
* me.Tween.Easing.Bounce.In
* me.Tween.Easing.Bounce.Out
* me.Tween.Easing.Bounce.InOut
*
*
* me.Tween.Interpolation.Bezier
* me.Tween.Interpolation.CatmullRom
*
* Data passed : none
* @public
* @constant
* @type String
* @name me.event#STATE_PAUSE
*/
api.STATE_PAUSE = "me.state.onPause";
/**
* Channel Constant for when the game is resumed
* Data passed : {Number} time in ms the game was paused
* @public
* @constant
* @type String
* @name me.event#STATE_RESUME
*/
api.STATE_RESUME = "me.state.onResume";
/**
* Channel Constant when the game is stopped
* Data passed : none
* @public
* @constant
* @type String
* @name me.event#STATE_STOP
*/
api.STATE_STOP = "me.state.onStop";
/**
* Channel Constant for when the game is restarted
* Data passed : {Number} time in ms the game was stopped
* @public
* @constant
* @type String
* @name me.event#STATE_RESTART
*/
api.STATE_RESTART = "me.state.onRestart";
/**
* Channel Constant for when the game manager is initialized
* Data passed : none
* @public
* @constant
* @type String
* @name me.event#GAME_INIT
*/
api.GAME_INIT = "me.game.onInit";
/**
* Channel Constant for when the game manager is resetted
* Data passed : none
* @public
* @constant
* @type String
* @name me.event#GAME_RESET
*/
api.GAME_RESET = "me.game.onReset";
/**
* Channel Constant for when a level is loaded
* Data passed : {String} Level Name
* @public
* @constant
* @type String
* @name me.event#LEVEL_LOADED
*/
api.LEVEL_LOADED = "me.game.onLevelLoaded";
/**
* Channel Constant for when everything has loaded
* Data passed : none
* @public
* @constant
* @type String
* @name me.event#LOADER_COMPLETE
*/
api.LOADER_COMPLETE = "me.loader.onload";
/**
* Channel Constant for displaying a load progress indicator
* Data passed : {Number} [0 .. 1], {Resource} resource object
* @public
* @constant
* @type String
* @name me.event#LOADER_PROGRESS
*/
api.LOADER_PROGRESS = "me.loader.onProgress";
/**
* Channel Constant for pressing a binded key
* Data passed : {String} user-defined action, {Number} keyCode,
* {Boolean} edge state
* Edge-state is for detecting "locked" key bindings. When a locked key
* is pressed and held, the first event will 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(me.input.KEY.X, "jump", true); // Edge-triggered
* me.input.bindKey(me.input.KEY.Z, "shoot"); // 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.body.gravity;
* }
* });
*/
api.KEYDOWN = "me.input.keydown";
/**
* Channel Constant for releasing a binded key
* Data passed : {String} user-defined action, {Number} keyCode
* @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();
* }
* }
* });
*/
api.KEYUP = "me.input.keyup";
/**
* Channel Constant for when a gamepad is connected
* Data passed : {Object} gamepad object
* @public
* @constant
* @type String
* @name me.event#GAMEPAD_CONNECTED
*/
api.GAMEPAD_CONNECTED = "gamepad.connected";
/**
* Channel Constant for when a gamepad is disconnected
* Data passed : {Object} gamepad object
* @public
* @constant
* @type String
* @name me.event#GAMEPAD_DISCONNECTED
*/
api.GAMEPAD_DISCONNECTED = "gamepad.disconnected";
/**
* Channel Constant for when gamepad button/axis state is updated
* Data passed : {Number} index
* Data passed : {String} type : "axes" or "buttons"
* Data passed : {Number} button
* Data passed : {Number} current.value
* Data passed : {Boolean} current.pressed
* @public
* @constant
* @type String
* @name me.event#GAMEPAD_UPDATE
*/
api.GAMEPAD_UPDATE = "gamepad.update";
/**
* Channel Constant for pointermove events on the viewport area
* Data passed : {me.Pointer} a Pointer object
* @public
* @constant
* @type String
* @name me.event#POINTERMOVE
*/
api.POINTERMOVE = "me.event.pointermove";
/**
* Channel Constant for dragstart events on a Draggable entity
* Data passed:
* {Object} the drag event
* {Object} the Draggable entity
* @public
* @constant
* @type String
* @name me.event#DRAGSTART
*/
api.DRAGSTART = "me.game.dragstart";
/**
* Channel Constant for dragend events on a Draggable entity
* Data passed:
* {Object} the drag event
* {Object} the Draggable entity
* @public
* @constant
* @type String
* @name me.event#DRAGEND
*/
api.DRAGEND = "me.game.dragend";
/**
* Channel Constant for when the (browser) window is resized
* Data passed : {Event} Event object
* @public
* @constant
* @type String
* @name me.event#WINDOW_ONRESIZE
*/
api.WINDOW_ONRESIZE = "window.onresize";
/**
* Channel Constant for when the viewport is resized
* (this usually follows a WINDOW_ONRESIZE event, when using the `flex` scaling mode is used and after the viewport was updated).
* Data passed : {Number} viewport width
* Data passed : {Number} viewport height
* @public
* @constant
* @type String
* @name me.event#VIEWPORT_ONRESIZE
*/
api.VIEWPORT_ONRESIZE = "viewport.onresize";
/**
* Channel Constant for when the device is rotated
* Data passed : {Event} Event object
* @public
* @constant
* @type String
* @name me.event#WINDOW_ONORIENTATION_CHANGE
*/
api.WINDOW_ONORIENTATION_CHANGE = "window.orientationchange";
/**
* Channel Constant for when the (browser) window is scrolled
* Data passed : {Event} Event object
* @public
* @constant
* @type String
* @name me.event#WINDOW_ONSCROLL
*/
api.WINDOW_ONSCROLL = "window.onscroll";
/**
* Channel Constant for when the viewport position is updated
* Data passed : {me.Vector2d} viewport position vector
* @public
* @constant
* @type String
* @name me.event#VIEWPORT_ONCHANGE
*/
api.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"]);
*
*/
api.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(); });
*/
api.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);
*/
api.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 api;
})();
})();
/*!
* howler.js v2.0.7
* howlerjs.com
*
* (c) 2013-2017, James Simpson of GoldFire Studios
* goldfirestudios.com
*
* MIT License
*/
/* eslint-disable quotes, space-infix-ops, new-cap, keyword-spacing, no-redeclare, no-undef, no-new*/
(function() {
'use strict';
/** Global Methods **/
/***************************************************************************/
/**
* Create the global controller. All contained methods and properties apply
* to all sounds that are currently playing or will be in the future.
*/
var HowlerGlobal = function() {
this.init();
};
HowlerGlobal.prototype = {
/**
* Initialize the global Howler object.
* @return {Howler}
*/
init: function() {
var self = this || Howler;
// Create a global ID counter.
self._counter = 1000;
// Internal properties.
self._codecs = {};
self._howls = [];
self._muted = false;
self._volume = 1;
self._canPlayEvent = 'canplaythrough';
self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null;
// Public properties.
self.masterGain = null;
self.noAudio = false;
self.usingWebAudio = true;
self.autoSuspend = true;
self.ctx = null;
// Set to false to disable the auto iOS enabler.
self.mobileAutoEnable = true;
// Setup the various state values for global tracking.
self._setup();
return self;
},
/**
* Get/set the global volume for all sounds.
* @param {Float} vol Volume from 0.0 to 1.0.
* @return {Howler/Float} Returns self or current volume.
*/
volume: function(vol) {
var self = this || Howler;
vol = parseFloat(vol);
// If we don't have an AudioContext created yet, run the setup.
if (!self.ctx) {
setupAudioContext();
}
if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) {
self._volume = vol;
// Don't update any of the nodes if we are muted.
if (self._muted) {
return self;
}
// When using Web Audio, we just need to adjust the master gain.
if (self.usingWebAudio) {
self.masterGain.gain.setValueAtTime(vol, Howler.ctx.currentTime);
}
// Loop through and change volume for all HTML5 audio nodes.
for (var i=0; i
* This namespace is a container for all registered plugins.
* @see me.plugin.register
* @namespace me.plugins
* @memberOf me
*/
me.plugins = {};
/**
* 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
* plugin must be installed using the register function
* @see me.plugin
* @class
* @extends Object
* @name plugin.Base
* @memberOf me
* @constructor
*/
singleton.Base = me.Object.extend(
/** @scope me.plugin.Base.prototype */
{
/** @ignore */
init : function () {
/**
* define the minimum required version of melonJS
* this can be overridden by the plugin
* @public
* @type String
* @default "5.1.0"
* @name me.plugin.Base#version
*/
this.version = "5.1.0";
}
});
/**
* 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._patched();
* });
*/
singleton.patch = function (proto, name, fn) {
// use the object prototype if possible
if (typeof proto.prototype !== "undefined") {
proto = proto.prototype;
}
// reuse the logic behind me.Object.extend
if (typeof(proto[name]) === "function") {
// save the original function
var _parent = proto[name];
// override the function with the new one
Object.defineProperty(proto, name, {
"configurable" : true,
"value" : (function (name, fn) {
return function () {
this._patched = _parent;
var ret = fn.apply(this, arguments);
this._patched = null;
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.plugins namespace
* me.plugins.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");
}
// 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;
var instance = new (plugin.bind.apply(plugin, _args))();
// inheritance check
if (!instance || !(instance instanceof me.plugin.Base)) {
throw new me.Error("Plugin should extend the me.plugin.Base Class !");
}
// compatibility testing
if (me.sys.checkVersion(instance.version) > 0) {
throw new me.Error("Plugin version mismatch, expected: " + instance.version + ", got: " + me.version);
}
// create a reference to the new plugin
me.plugins[name] = instance;
};
// return our singleton
return singleton;
})();
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*/
/**
* Used to make a game entity draggable
* @class
* @extends me.Entity
* @memberOf me
* @constructor
* @param {Number} x the x coordinates of the entity object
* @param {Number} y the y coordinates of the entity object
* @param {Object} settings Entity properties (see {@link me.Entity})
*/
me.DraggableEntity = (function (Entity, Input, Event, Vector) {
"use strict";
return Entity.extend(
/** @scope me.DraggableEntity.prototype */
{
/**
* Constructor
* @name init
* @memberOf me.DraggableEntity
* @function
* @param {Number} x the x postion of the entity
* @param {Number} y the y postion of the entity
* @param {Object} settings the additional entity settings
*/
init: function (x, y, settings) {
Entity.prototype.init.apply(this, [x, y, settings]);
this.dragging = false;
this.dragId = null;
this.grabOffset = new Vector(0, 0);
this.onPointerEvent = Input.registerPointerEvent;
this.removePointerEvent = Input.releasePointerEvent;
this.initEvents();
},
/**
* Initializes the events the modules needs to listen to
* It translates the pointer events to me.events
* in order to make them pass through the system and to make
* this module testable. Then we subscribe this module to the
* transformed events.
* @name initEvents
* @memberOf me.DraggableEntity
* @function
*/
initEvents: function () {
var self = this;
/**
* @ignore
*/
this.mouseDown = function (e) {
this.translatePointerEvent(e, Event.DRAGSTART);
};
/**
* @ignore
*/
this.mouseUp = function (e) {
this.translatePointerEvent(e, Event.DRAGEND);
};
this.onPointerEvent("pointerdown", this, this.mouseDown.bind(this));
this.onPointerEvent("pointerup", this, this.mouseUp.bind(this));
Event.subscribe(Event.POINTERMOVE, this.dragMove.bind(this));
Event.subscribe(Event.DRAGSTART, function (e, draggable) {
if (draggable === self) {
self.dragStart(e);
}
});
Event.subscribe(Event.DRAGEND, function (e, draggable) {
if (draggable === self) {
self.dragEnd(e);
}
});
},
/**
* Translates a pointer event to a me.event
* @name translatePointerEvent
* @memberOf me.DraggableEntity
* @function
* @param {Object} e the pointer event you want to translate
* @param {String} translation the me.event you want to translate
* the event to
*/
translatePointerEvent: function (e, translation) {
Event.publish(translation, [e, this]);
},
/**
* Gets called when the user starts dragging the entity
* @name dragStart
* @memberOf me.DraggableEntity
* @function
* @param {Object} x the pointer event
*/
dragStart: function (e) {
if (this.dragging === false) {
this.dragging = true;
this.grabOffset.set(e.gameX, e.gameY);
this.grabOffset.sub(this.pos);
return false;
}
},
/**
* Gets called when the user drags this entity around
* @name dragMove
* @memberOf me.DraggableEntity
* @function
* @param {Object} x the pointer event
*/
dragMove: function (e) {
if (this.dragging === true) {
this.pos.set(e.gameX, e.gameY, this.pos.z); //TODO : z ?
this.pos.sub(this.grabOffset);
}
},
/**
* Gets called when the user stops dragging the entity
* @name dragEnd
* @memberOf me.DraggableEntity
* @function
* @param {Object} x the pointer event
*/
dragEnd: function () {
if (this.dragging === true) {
this.dragging = false;
return false;
}
},
/**
* Destructor
* @name destroy
* @memberOf me.DraggableEntity
* @function
*/
destroy: function () {
Event.unsubscribe(Event.POINTERMOVE, this.dragMove);
Event.unsubscribe(Event.DRAGSTART, this.dragStart);
Event.unsubscribe(Event.DRAGEND, this.dragEnd);
this.removePointerEvent("pointerdown", this);
this.removePointerEvent("pointerup", this);
}
});
}(me.Entity, me.input, me.event, me.Vector2d));
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*/
/**
* Used to make a game entity a droptarget
* @class
* @extends me.Entity
* @memberOf me
* @constructor
* @param {Number} x the x coordinates of the entity object
* @param {Number} y the y coordinates of the entity object
* @param {Object} settings Entity properties (see {@link me.Entity})
*/
me.DroptargetEntity = (function (Entity, Event) {
"use strict";
return Entity.extend(
/** @scope me.DroptargetEntity.prototype */
{
/**
* Constructor
* @name init
* @memberOf me.DroptargetEntity
* @function
* @param {Number} x the x postion of the entity
* @param {Number} y the y postion of the entity
* @param {Object} settings the additional entity settings
*/
init: function (x, y, settings) {
/**
* constant for the overlaps method
* @public
* @constant
* @type String
* @name CHECKMETHOD_OVERLAP
* @memberOf me.DroptargetEntity
*/
this.CHECKMETHOD_OVERLAP = "overlaps";
/**
* constant for the contains method
* @public
* @constant
* @type String
* @name CHECKMETHOD_CONTAINS
* @memberOf me.DroptargetEntity
*/
this.CHECKMETHOD_CONTAINS = "contains";
/**
* the checkmethod we want to use
* @public
* @constant
* @type String
* @name checkMethod
* @memberOf me.DroptargetEntity
*/
this.checkMethod = null;
Entity.prototype.init.apply(this, [x, y, settings]);
Event.subscribe(Event.DRAGEND, this.checkOnMe.bind(this));
this.checkMethod = this[this.CHECKMETHOD_OVERLAP];
},
/**
* Sets the collision method which is going to be used to check a valid drop
* @name setCheckMethod
* @memberOf me.DroptargetEntity
* @function
* @param {Constant} checkMethod the checkmethod (defaults to CHECKMETHOD_OVERLAP)
*/
setCheckMethod: function (checkMethod) {
// We can improve this check,
// because now you can use every method in theory
if (typeof(this[checkMethod]) !== "undefined") {
this.checkMethod = this[checkMethod];
}
},
/**
* Checks if a dropped entity is dropped on the current entity
* @name checkOnMe
* @memberOf me.DroptargetEntity
* @function
* @param {Object} draggableEntity the draggable entity that is dropped
*/
checkOnMe: function (e, draggableEntity) {
if (draggableEntity && this.checkMethod(draggableEntity.getBounds())) {
// call the drop method on the current entity
this.drop(draggableEntity);
}
},
/**
* Gets called when a draggable entity is dropped on the current entity
* @name drop
* @memberOf me.DroptargetEntity
* @function
* @param {Object} draggableEntity the draggable entity that is dropped
*/
drop: function () {},
/**
* Destructor
* @name destroy
* @memberOf me.DroptargetEntity
* @function
*/
destroy: function () {
Event.unsubscribe(Event.DRAGEND, this.checkOnMe);
}
});
}(me.Entity, me.event));
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* @class
* @extends me.Entity
* @memberOf me
* @constructor
* @param {Number} x the x coordinates of the entity object
* @param {Number} y the y coordinates of the entity object
* @param {Object} settings See {@link me.Entity}
*/
me.CollectableEntity = me.Entity.extend(
/** @scope me.CollectableEntity.prototype */
{
/** @ignore */
init : function (x, y, settings) {
// call the super constructor
me.Entity.prototype.init.apply(this, [x, y, settings]);
this.body.collisionType = me.collision.types.COLLECTABLE_OBJECT;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* @class
* @extends me.Entity
* @memberOf me
* @constructor
* @param {Number} x the x coordinates of the object
* @param {Number} y the y coordinates of the object
* @param {Object} settings See {@link me.Entity}
* @param {String} [settings.duration] Fade duration (in ms)
* @param {String|me.Color} [settings.color] Fade color
* @param {String} [settings.to] TMX level to load
* @param {String|me.Container} [settings.container] Target container. See {@link me.levelDirector.loadLevel}
* @param {Function} [settings.onLoaded] Level loaded callback. See {@link me.levelDirector.loadLevel}
* @param {Boolean} [settings.flatten] Flatten all objects into the target container. See {@link me.levelDirector.loadLevel}
* @param {Boolean} [settings.setViewportBounds] Resize the viewport to match the level. See {@link me.levelDirector.loadLevel}
* @example
* me.game.world.addChild(new me.LevelEntity(
* x, y, {
* "duration" : 250,
* "color" : "#000",
* "to" : "mymap2"
* }
* ));
*/
me.LevelEntity = me.Entity.extend(
/** @scope me.LevelEntity.prototype */
{
/** @ignore */
init : function (x, y, settings) {
me.Entity.prototype.init.apply(this, [x, y, settings]);
this.nextlevel = settings.to;
this.fade = settings.fade;
this.duration = settings.duration;
this.fading = false;
this.name = "levelEntity";
// a temp variable
this.gotolevel = settings.to;
// Collect the defined level settings
this.loadLevelSettings = {};
[ "container", "onLoaded", "flatten", "setViewportBounds" ].forEach(function (v) {
if (typeof(settings[v]) !== "undefined") {
this.loadLevelSettings[v] = settings[v];
}
}.bind(this));
this.body.collisionType = me.collision.types.ACTION_OBJECT;
},
/**
* @ignore
*/
getlevelSettings : function () {
// Lookup for the container instance
if (typeof(this.loadLevelSettings.container) === "string") {
this.loadLevelSettings.container = me.game.world.getChildByName(this.loadLevelSettings.container)[0];
}
return this.loadLevelSettings;
},
/**
* @ignore
*/
onFadeComplete : function () {
me.levelDirector.loadLevel(this.gotolevel, this.getlevelSettings());
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, this.getlevelSettings());
}
},
/** @ignore */
onCollision : function () {
if (this.name === "levelEntity") {
this.goTo.apply(this);
}
return false;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
// generate a default image for the particles
var pixel = (function () {
var canvas = me.video.createCanvas(1, 1);
var context = canvas.getContext("2d");
context.fillStyle = "#fff";
context.fillRect(0, 0, 1, 1);
return canvas;
})();
/**
* me.ParticleEmitterSettings contains the default settings for me.ParticleEmitter.
*
* @protected
* @class
* @memberOf me
* @see me.ParticleEmitter
*/
me.ParticleEmitterSettings = {
/**
* Width of the particle spawn area.
* @public
* @type Number
* @name width
* @memberOf me.ParticleEmitterSettings
* @default 0
*/
width : 0,
/**
* Height of the particle spawn area.
* @public
* @type Number
* @name height
* @memberOf me.ParticleEmitterSettings
* @default 0
*/
height : 0,
/**
* Image used for particles.
* @public
* @type CanvasImageSource
* @name image
* @memberOf me.ParticleEmitterSettings
* @default 1x1 white pixel
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvasimagesource
*/
image : pixel,
/**
* Total number of particles in the emitter.
* @public
* @type Number
* @name totalParticles
* @default 50
* @memberOf me.ParticleEmitterSettings
*/
totalParticles : 50,
/**
* Start angle for particle launch in Radians.
* @public
* @type Number
* @name angle
* @default Math.PI / 2
* @memberOf me.ParticleEmitterSettings
*/
angle : Math.PI / 2,
/**
* Variation in the start angle for particle launch in Radians.
* @public
* @type Number
* @name angleVariation
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
angleVariation : 0,
/**
* Minimum time each particle lives once it is emitted in ms.
* @public
* @type Number
* @name minLife
* @default 1000
* @memberOf me.ParticleEmitterSettings
*/
minLife : 1000,
/**
* Maximum time each particle lives once it is emitted in ms.
* @public
* @type Number
* @name maxLife
* @default 3000
* @memberOf me.ParticleEmitterSettings
*/
maxLife : 3000,
/**
* Start speed of particles.
* @public
* @type Number
* @name speed
* @default 2
* @memberOf me.ParticleEmitterSettings
*/
speed : 2,
/**
* Variation in the start speed of particles.
* @public
* @type Number
* @name speedVariation
* @default 1
* @memberOf me.ParticleEmitterSettings
*/
speedVariation : 1,
/**
* Minimum start rotation for particles sprites in Radians.
* @public
* @type Number
* @name minRotation
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
minRotation : 0,
/**
* Maximum start rotation for particles sprites in Radians.
* @public
* @type Number
* @name maxRotation
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
maxRotation : 0,
/**
* Minimum start scale ratio for particles (1 = no scaling).
* @public
* @type Number
* @name minStartScale
* @default 1
* @memberOf me.ParticleEmitterSettings
*/
minStartScale : 1,
/**
* Maximum start scale ratio for particles (1 = no scaling).
* @public
* @type Number
* @name maxStartScale
* @default 1
* @memberOf me.ParticleEmitterSettings
*/
maxStartScale : 1,
/**
* Minimum end scale ratio for particles.
* @public
* @type Number
* @name minEndScale
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
minEndScale : 0,
/**
* Maximum end scale ratio for particles.
* @public
* @type Number
* @name maxEndScale
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
maxEndScale : 0,
/**
* Vertical force (Gravity) for each particle.
* @public
* @type Number
* @name gravity
* @default 0
* @memberOf me.ParticleEmitterSettings
* @see me.sys.gravity
*/
gravity : 0,
/**
* Horizontal force (like a Wind) for each particle.
* @public
* @type Number
* @name wind
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
wind : 0,
/**
* Update the rotation of particle in accordance the particle trajectory.
* The particle sprite should aim at zero angle (draw from left to right).
* Override the particle minRotation and maxRotation.
* @public
* @type Boolean
* @name followTrajectory
* @default false
* @memberOf me.ParticleEmitterSettings
*/
followTrajectory : false,
/**
* Enable the Texture Additive by canvas composite operation (lighter).
* WARNING: Composite Operation may decreases performance!.
* @public
* @type Boolean
* @name textureAdditive
* @default false
* @memberOf me.ParticleEmitterSettings
*/
textureAdditive : false,
/**
* Update particles only in the viewport, remove it when out of viewport.
* @public
* @type Boolean
* @name onlyInViewport
* @default true
* @memberOf me.ParticleEmitterSettings
*/
onlyInViewport : true,
/**
* Render particles in screen space.
* @public
* @type Boolean
* @name floating
* @default false
* @memberOf me.ParticleEmitterSettings
*/
floating : false,
/**
* Maximum number of particles launched each time in this emitter (used only if emitter is Stream).
* @public
* @type Number
* @name maxParticles
* @default 10
* @memberOf me.ParticleEmitterSettings
*/
maxParticles : 10,
/**
* How often a particle is emitted in ms (used only if emitter is Stream).
* Necessary that value is greater than zero.
* @public
* @type Number
* @name frequency
* @default 100
* @memberOf me.ParticleEmitterSettings
*/
frequency : 100,
/**
* Duration that the emitter releases particles in ms (used only if emitter is Stream).
* After this period, the emitter stop the launch of particles.
* @public
* @type Number
* @name duration
* @default Infinity
* @memberOf me.ParticleEmitterSettings
*/
duration : Infinity,
/**
* Skip n frames after updating the particle system once.
* This can be used to reduce the performance impact of emitters with many particles.
* @public
* @type Number
* @name framesToSkip
* @default 0
* @memberOf me.ParticleEmitterSettings
*/
framesToSkip : 0
};
/**
* Particle Emitter Object.
* @class
* @extends Rect
* @memberOf me
* @constructor
* @param {Number} x x-position of the particle emitter
* @param {Number} y y-position of the particle emitter
* @param {object} settings An object containing the settings for the particle emitter. See {@link me.ParticleEmitterSettings}
* @example
*
* // Create a basic emitter at position 100, 100
* var emitter = new me.ParticleEmitter(100, 100);
*
* // Adjust the emitter properties
* emitter.totalParticles = 200;
* emitter.minLife = 1000;
* emitter.maxLife = 3000;
* emitter.z = 10;
*
* // Add the emitter to the game world
* me.game.world.addChild(emitter);
*
* // Launch all particles one time and stop, like a explosion
* emitter.burstParticles();
*
* // Launch constantly the particles, like a fountain
* emitter.streamParticles();
*
* // At the end, remove emitter from the game world
* // call this in onDestroyEvent function
* me.game.world.removeChild(emitter);
*
*/
me.ParticleEmitter = me.Rect.extend(
/** @scope me.ParticleEmitter.prototype */
{
/**
* @ignore
*/
init: function (x, y, settings) {
// Emitter is Stream, launch particles constantly
/** @ignore */
this._stream = false;
// Frequency timer (in ms) for emitter launch new particles
// used only in stream emitter
/** @ignore */
this._frequencyTimer = 0;
// Time of live (in ms) for emitter launch new particles
// used only in stream emitter
/** @ignore */
this._durationTimer = 0;
// Emitter is emitting particles
/** @ignore */
this._enabled = false;
// Emitter will always update
this.isRenderable = false;
// call the super constructor
me.Rect.prototype.init.apply(this,
[x, y,
Infinity,
Infinity]
);
// don't sort the particles by z-index
this.autoSort = false;
this.container = new me.ParticleContainer(this);
/**
* @ignore
*/
Object.defineProperty(this.pos, "z", {
/**
* @ignore
*/
get : (function () { return this.container.pos.z; }).bind(this),
/**
* @ignore
*/
set : (function (value) { this.container.pos.z = value; }).bind(this),
enumerable : true,
configurable : true
});
/**
* Floating property for particles, value is forwarded to the particle container
* @type Boolean
* @name floating
* @memberOf me.ParticleEmitter
*/
Object.defineProperty(this, "floating", {
/**
* @ignore
*/
get : function () { return this.container.floating; },
/**
* @ignore
*/
set : function (value) { this.container.floating = value; },
enumerable : true,
configurable : true
});
// Reset the emitter to defaults
this.reset(settings);
},
/**
* @ignore
*/
onActivateEvent: function() {
this.ancestor.addChild(this.container);
this.container.pos.z = this.pos.z;
if (!this.ancestor.autoSort) {
this.ancestor.sort();
}
},
/**
* @ignore
*/
onDeactivateEvent: function() {
if (this.ancestor.hasChild(this.container)) {
this.ancestor.removeChildNow(this.container);
}
},
/**
* @ignore
*/
destroy: function () {
this.reset();
},
/**
* returns a random point inside the bounds x axis of this emitter
* @name getRandomPointX
* @memberOf me.ParticleEmitter
* @function
* @return {Number}
*/
getRandomPointX: function () {
return this.pos.x + (0).randomFloat(this.width);
},
/**
* returns a random point inside the bounds y axis of this emitter
* @name getRandomPointY
* @memberOf me.ParticleEmitter
* @function
* @return {Number}
*/
getRandomPointY: function () {
return this.pos.y + (0).randomFloat(this.height);
},
/**
* Reset the emitter with default values.
* @function
* @param {Object} settings [optional] object with emitter settings. See {@link me.ParticleEmitterSettings}
* @name reset
* @memberOf me.ParticleEmitter
*/
reset: function (settings) {
// check if settings exists and create a dummy object if necessary
settings = settings || {};
var defaults = me.ParticleEmitterSettings;
var width = (typeof settings.width === "number") ? settings.width : defaults.width;
var height = (typeof settings.height === "number") ? settings.height : defaults.height;
this.resize(width, height);
Object.assign(this, defaults, settings);
// reset particle container values
this.container.destroy();
},
// Add count particles in the game world
/** @ignore */
addParticles: function (count) {
for (var i = 0; i < ~~count; i++) {
// Add particle to the container
var particle = me.pool.pull("me.Particle", this);
this.container.addChild(particle);
}
},
/**
* Emitter is of type stream and is launching particles
* @function
* @returns {Boolean} Emitter is Stream and is launching particles
* @name isRunning
* @memberOf me.ParticleEmitter
*/
isRunning: function () {
return this._enabled && this._stream;
},
/**
* Launch particles from emitter constantly
* Particles example: Fountains
* @param {Number} duration [optional] time that the emitter releases particles in ms
* @function
* @name streamParticles
* @memberOf me.ParticleEmitter
*/
streamParticles: function (duration) {
this._enabled = true;
this._stream = true;
this.frequency = Math.max(this.frequency, 1);
this._durationTimer = (typeof duration === "number") ? duration : this.duration;
},
/**
* Stop the emitter from generating new particles (used only if emitter is Stream)
* @function
* @name stopStream
* @memberOf me.ParticleEmitter
*/
stopStream: function () {
this._enabled = false;
},
/**
* Launch all particles from emitter and stop
* Particles example: Explosions
* @param {Number} total [optional] number of particles to launch
* @function
* @name burstParticles
* @memberOf me.ParticleEmitter
*/
burstParticles: function (total) {
this._enabled = true;
this._stream = false;
this.addParticles((typeof total === "number") ? total : this.totalParticles);
this._enabled = false;
},
/**
* @ignore
*/
update: function (dt) {
// Launch new particles, if emitter is Stream
if ((this._enabled) && (this._stream)) {
// Check if the emitter has duration set
if (this._durationTimer !== Infinity) {
this._durationTimer -= dt;
if (this._durationTimer <= 0) {
this.stopStream();
return false;
}
}
// Increase the emitter launcher timer
this._frequencyTimer += dt;
// Check for new particles launch
var particlesCount = this.container.children.length;
if ((particlesCount < this.totalParticles) && (this._frequencyTimer >= this.frequency)) {
if ((particlesCount + this.maxParticles) <= this.totalParticles) {
this.addParticles(this.maxParticles);
}
else {
this.addParticles(this.totalParticles - particlesCount);
}
this._frequencyTimer = 0;
}
}
return true;
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* Particle Container Object.
* @class
* @extends me.Container
* @memberOf me
* @constructor
* @param {me.ParticleEmitter} emitter the emitter which owns this container
*/
me.ParticleContainer = me.Container.extend(
/** @scope ParticleContainer */
{
/**
* @ignore
*/
init: function (emitter) {
// call the super constructor
me.Container.prototype.init.apply(this, [
me.game.viewport.pos.x,
me.game.viewport.pos.y,
me.game.viewport.width,
me.game.viewport.height
]);
// don't sort the particles by z-index
this.autoSort = false;
// count the updates
this._updateCount = 0;
// internally store how much time was skipped when frames are skipped
this._dt = 0;
// cache the emitter for later use
this._emitter = emitter;
this.autoTransform = false;
this.anchorPoint.set(0, 0);
this.isKinematic = true;
},
/**
* @ignore
*/
update: function (dt) {
// skip frames if necessary
if (++this._updateCount > this._emitter.framesToSkip) {
this._updateCount = 0;
}
if (this._updateCount > 0) {
this._dt += dt;
return false;
}
// apply skipped delta time
dt += this._dt;
this._dt = 0;
// Update particles and remove them if they are dead
var viewport = me.game.viewport;
for (var i = this.children.length - 1; i >= 0; --i) {
var particle = this.children[i];
particle.inViewport = this.floating || viewport.isVisible(particle.getBounds());
if (!particle.update(dt)) {
this.removeChildNow(particle);
}
}
return true;
},
/**
* @ignore
*/
draw : function (renderer, rect) {
if (this.children.length > 0) {
var context = renderer.getContext(),
gco;
// Check for additive draw
if (this._emitter.textureAdditive) {
gco = context.globalCompositeOperation;
context.globalCompositeOperation = "lighter";
}
me.Container.prototype.draw.apply(this, [renderer, rect]);
// Restore globalCompositeOperation
if (this._emitter.textureAdditive) {
context.globalCompositeOperation = gco;
}
}
}
});
})();
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2018 Olivier Biot
* http://www.melonjs.org
*
*/
(function () {
/**
* Single Particle Object.
* @class
* @extends me.Renderable
* @memberOf me
* @constructor
* @param {me.ParticleEmitter} particle emitter
*/
me.Particle = me.Renderable.extend(
/** @scope me.Particle.prototype */
{
/**
* @ignore
*/
init : function (emitter) {
// Call the super constructor
me.Renderable.prototype.init.apply(this, [
emitter.getRandomPointX(),
emitter.getRandomPointY(),
emitter.image.width,
emitter.image.height
]);
// Particle will always update
this.alwaysUpdate = true;
// Cache the image reference
this.image = emitter.image;
// Set the start particle Angle and Speed as defined in emitter
var angle = emitter.angle + ((emitter.angleVariation > 0) ? ((0).randomFloat(2) - 1) * emitter.angleVariation : 0);
var speed = emitter.speed + ((emitter.speedVariation > 0) ? ((0).randomFloat(2) - 1) * emitter.speedVariation : 0);
// Set the start particle Velocity
this.vel = new me.Vector2d(speed * Math.cos(angle), -speed * Math.sin(angle));
// Set the start particle Time of Life as defined in emitter
this.life = emitter.minLife.randomFloat(emitter.maxLife);
this.startLife = this.life;
// Set the start and end particle Scale as defined in emitter
// clamp the values as minimum and maximum scales range
this.startScale = emitter.minStartScale.randomFloat(
emitter.maxStartScale
).clamp(emitter.minStartScale, emitter.maxStartScale);
this.endScale = emitter.minEndScale.randomFloat(
emitter.maxEndScale
).clamp(emitter.minEndScale, emitter.maxEndScale);
// Set the particle Gravity and Wind (horizontal gravity) as defined in emitter
this.gravity = emitter.gravity;
this.wind = emitter.wind;
// Set if the particle update the rotation in accordance the trajectory
this.followTrajectory = emitter.followTrajectory;
// Set if the particle update only in Viewport
this.onlyInViewport = emitter.onlyInViewport;
// Set the particle Z Order
this.pos.z = emitter.z;
// cache inverse of the expected delta time
this._deltaInv = me.sys.fps / 1000;
// Set the start particle rotation as defined in emitter
// if the particle not follow trajectory
if (!emitter.followTrajectory) {
this.angle = emitter.minRotation.randomFloat(emitter.maxRotation);
}
},
/**
* Update the Particle
* This is automatically called by the game manager {@link me.game}
* @name update
* @memberOf me.Particle
* @function
* @ignore
* @param {Number} dt time since the last update in milliseconds
*/
update : function (dt) {
// move things forward independent of the current frame rate
var skew = dt * this._deltaInv;
// Decrease particle life
this.life = this.life > dt ? this.life - dt : 0;
// Calculate the particle Age Ratio
var ageRatio = this.life / this.startLife;
// Resize the particle as particle Age Ratio
var scale = this.startScale;
if (this.startScale > this.endScale) {
scale *= ageRatio;
scale = (scale < this.endScale) ? this.endScale : scale;
}
else if (this.startScale < this.endScale) {
scale /= ageRatio;
scale = (scale > this.endScale) ? this.endScale : scale;
}
// Set the particle opacity as Age Ratio
this.alpha = ageRatio;
// Adjust the particle velocity
this.vel.x += this.wind * skew;
this.vel.y += this.gravity * skew;
// If necessary update the rotation of particle in accordance the particle trajectory
var angle = this.followTrajectory ? Math.atan2(this.vel.y, this.vel.x) : this.angle;
this.pos.x += this.vel.x * skew;
this.pos.y += this.vel.y * skew;
// Update particle transform
this.currentTransform.setTransform(
scale, 0, 0,
0, scale, 0,
this.pos.x, this.pos.y, 1
).rotate(angle);
// Return true if the particle is not dead yet
return (this.inViewport || !this.onlyInViewport) && (this.life > 0);
},
/**
* @ignore
*/
preDraw : function (renderer) {
// restore is called in postDraw
renderer.save();
// particle alpha value
renderer.setGlobalAlpha(renderer.globalAlpha() * this.alpha);
// translate to the defined anchor point and scale it
renderer.transform(this.currentTransform);
},
/**
* @ignore
*/
draw : function (renderer) {
var w = this.width, h = this.height;
renderer.drawImage(
this.image,
0, 0,
w, h,
-w / 2, -h / 2,
w, h
);
}
});
/*---------------------------------------------------------*/
// END END END
/*---------------------------------------------------------*/
})(window);