topical media & game development
mobile-game-present-lib-quintus.js / js
// Quintus Game Engine
// (c) 2012 Pascal Rettig, Cykod LLC
// Quintus may be freely distributed under the MIT license or GPLv2 License.
// For all details and documentation:
// http://html5quintus.com
//
// Quintus HTML5 Game Engine
// =========================
//
// The code in `quintus.js` defines the base `Quintus()` method
// which create an instance of the engine. The basic engine doesn't
// do a whole lot - it provides an architecture for extension, a
// game loop, and a method for creating or binding to an exsiting
// canvas context. The engine has dependencies on Underscore.js and jQuery,
// although the jQuery dependency will be removed in the future.
//
// Most of the game-specific functionality is in the
// various other modules:
//
// * `quintus_input.js` - `Input` module, which allows for user input via keyboard and touchscreen
// * `quintus_sprites.js` - `Sprites` module, which defines a basic `Q.Sprite` class along with spritesheet support in `Q.SpriteSheet`.
// * `quintus_scenes.js` - `Scenes` module. It defines the `Q.Scene` class, which allows creation of reusable scenes, and the `Q.Stage` class, which handles managing a number of sprites at once.
// * `quintus_anim.js` - `Anim` module, which adds in support for animations on sprites along with a `viewport` component to follow the player around and a `Q.Repeater` class that can create a repeating, scrolling background.
// Engine Bootstrapping
// ====================
// Top-level Quintus engine factory wrapper,
// creates new instances of the engine by calling:
//
// var Q = Quintus({ ... });
//
// Any initial setup methods also all return the `Q` object, allowing any initial
// setup calls to be chained together.
//
// var Q = Quintus()
// .include("Input, Sprites, Scenes")
// .setup('quintus', { maximize: true })
// .controls();
//
// `Q` is used internally as the object name, and is used in most of the examples,
// but multiple instances of the engine on the same page can have different names.
//
// var Game1 = Quintus(), Game2 = Quintus();
//
var Quintus = function Quintus(opts) {
// A la jQuery - the returned `Q` object is actually
// a method that calls `Q.select`. `Q.select` doesn't do anything
// initially, but can be overridden by a module to allow
// selection of game objects. The `Scenes` module adds in
// the select method which selects from the default stage.
//
// var Q = Quintus().include("Sprites, Scenes");
// ... Game Code ...
// // Set the angry property on all Enemy1 class objects to true
// Q("Enemy1").p({ angry: true });
//
//
var Q = function(selector,scope,options) {
return Q.select(selector,scope,options);
};
Q.select = function() { /* No-op */ };
// Syntax for including other modules into quintus, can accept a comma-separated
// list of strings, an array of strings, or an array of actual objects. Example:
//
// Q.include("Input, Sprites, Scenes")
//
Q.include = function(mod) {
Q._each(Q._normalizeArg(mod),function(name) {
var m = Quintus[name] || name;
if(!Q._isFunction(m)) { throw "Invalid Module:" + name; }
m(Q);
});
return Q;
};
// Utility Methods
// ===============
//
// Most of these utility methods are a subset of Underscore.js,
// Most are pulled directly from underscore and some are
// occasionally optimized for speed and memory usage in lieu of flexibility.
// Underscore.js is (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// http://underscorejs.org
// An internal utility method (utility methods are prefixed with underscores)
// It's used to take a string of comma separated names and turn it into an `Array`
// of names. If an array of names is passed in, it's left as is. Example usage:
//
// Q._normalizeArg("Sprites, Scenes, Physics ");
// // returns [ "Sprites", "Scenes", "Physics" ]
//
// Used by `Q.include` and `Q.Sprite.add` to add modules and components, respectively.
Q._normalizeArg = function(arg) {
if(Q._isString(arg)) {
arg = arg.replace(/\s+/g,'').split(",");
}
if(!Q._isArray(arg)) {
arg = [ arg ];
}
return arg;
};
// Extends a destination object
// with a source object
Q._extend = function(dest,source) {
if(!source) { return dest; }
for (var prop in source) {
dest[prop] = source[prop];
}
return dest;
};
// Return a shallow copy of an object. Sub-objects (and sub-arrays) are not cloned.
Q._clone = function(obj) {
return Q._extend({},obj);
};
// Method that adds default properties onto
// an object only if the key is undefined
Q._defaults = function(dest,source) {
if(!source) { return dest; }
for (var prop in source) {
if(dest[prop] === void 0) {
dest[prop] = source[prop];
}
}
return dest;
};
// Shortcut for hasOwnProperty
Q._has = function(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
};
// Check if something is a string
// NOTE: this fails for non-primitives
Q._isString = function(obj) {
return typeof obj === "string";
};
Q._isNumber = function(obj) {
return Object.prototype.toString.call(obj) === '[object Number]';
};
// Check if something is a function
Q._isFunction = function(obj) {
return Object.prototype.toString.call(obj) === '[object Function]';
};
// Check if something is a function
Q._isObject = function(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
};
// Check if something is a function
Q._isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// Check if something is undefined
Q._isUndefined = function(obj) {
return obj === void 0;
};
// Removes a property from an object and returns it
Q._popProperty = function(obj,property) {
var val = obj[property];
delete obj[property];
return val;
};
// Basic iteration method. This can often be a performance
// handicap when the callback iterator is created inline,
// as this leads to lots of functions that need to be GC'd.
// Better is to define the iterator as a private method so
// it is only created once.
Q._each = function(obj,iterator,context) {
if (obj == null) { return; }
if (obj.forEach) {
obj.forEach(iterator,context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
iterator.call(context, obj[i], i, obj);
}
} else {
for (var key in obj) {
iterator.call(context, obj[key], key, obj);
}
}
};
// Basic detection method, returns the first instance where the
// iterator returns truthy.
Q._detect = function(obj,iterator,context,arg1,arg2) {
var result;
if (obj == null) { return; }
if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
result = iterator.call(context, obj[i], i, arg1,arg2);
if(result) { return result; }
}
return false;
} else {
for (var key in obj) {
result = iterator.call(context, obj[key], key, arg1,arg2);
if(result) { return result; }
}
return false;
}
};
// Returns a new Array with entries set to the return value of the iterator.
Q._map = function(obj, iterator, context) {
var results = [];
if (obj == null) { return results; }
if (obj.map) { return obj.map(iterator, context); }
Q._each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) { results.length = obj.length; }
return results;
};
// Returns a sorted copy of unique array elements with null remove
Q._uniq = function(arr) {
arr = arr.slice().sort();
var output = [];
var last = null;
for(var i=0;i<arr.length;i++) {
if(arr[i] != void 0 && last != arr[i]) {
output.push(arr[i]);
}
last = arr[i];
}
return output;
};
// returns a new array with the same entries as the source but in a random order.
Q._shuffle = function(obj) {
var shuffled = [], rand;
Q._each(obj, function(value, index, list) {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
});
return shuffled;
};
// Return an object's keys
Q._keys = Object.keys || function(obj) {
if(Q._isObject(obj)) { throw new TypeError('Invalid object'); }
var keys = [];
for (var key in obj) { if (Q._has(obj, key)) { keys[keys.length] = key; } }
return keys;
};
Q._range = function(start,stop,step) {
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
var idIndex = 0;
// Return a unique identifier
Q._uniqueId = function() {
return idIndex++;
};
// Options
// ========
// Default engine options defining the paths
// where images, audio and other data files should be found
// relative to the base HTML file. As well as a couple of other
// options.
//
// These can be overriden by passing in options to the `Quintus()`
// factory method, for example:
//
// // Override the imagePath to default to /assets/images/
// var Q = Quintus({ imagePath: "/assets/images/" });
//
// If you follow the default convention from the examples, however,
// you should be able to call `Quintus()` without any options.
Q.options = {
imagePath: "mobile-game-present-images-",
audioPath: "mobile-game-present-audio-",
dataPath: "mobile-game-present-data-",
audioSupported: [ 'mp3','ogg' ],
sound: true,
frameTimeLimit: 100
};
if(opts) { Q._extend(Q.options,opts); }
// Game Loop support
// =================
// By default the engine doesn't start a game loop until you actually tell it to.
// Usually the loop is started the first time you call `Q.stageScene`, but if you
// aren't using the `Scenes` module you can explicitly start the game loop yourself
// and control **exactly** what the engine does each cycle. For example:
//
// var Q = Quintus().setup();
//
// var ball = new Q.Sprite({ .. });
//
// Q.gameLoop(function(dt) {
// Q.clear();
// ball.step(dt);
// ball.draw(Q.ctx);
// });
//
// The callback will be called with fraction of a second that has elapsed since
// the last call to the loop method.
Q.gameLoop = function(callback) {
Q.lastGameLoopFrame = new Date().getTime();
// Short circuit the loop check in case multiple scenes
// are staged immediately
Q.loop = true;
// Keep track of the frame we are on (so that animations can be synced
// to the next frame)
Q._loopFrame = 0;
// Wrap the callback to save it and standardize the passed
// in time.
Q.gameLoopCallbackWrapper = function(now) {
Q._loopFrame++;
Q.loop = window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
var dt = now - Q.lastGameLoopFrame;
/* Prevent fast-forwarding by limiting the length of a single frame. */
if(dt > Q.options.frameTimeLimit) { dt = Q.options.frameTimeLimit; }
callback.apply(Q,[dt / 1000]);
Q.lastGameLoopFrame = now;
};
window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
return Q;
};
// Pause the entire game by canceling the requestAnimationFrame call. If you use setTimeout or
// setInterval in your game, those will, of course, keep on rolling...
Q.pauseGame = function() {
if(Q.loop) {
window.cancelAnimationFrame(Q.loop);
}
Q.loop = null;
};
// Unpause the game by restarting the requestAnimationFrame-based loop.
Q.unpauseGame = function() {
if(!Q.loop) {
Q.lastGameLoopFrame = new Date().getTime();
Q.loop = window.requestAnimationFrame(Q.gameLoopCallbackWrapper);
}
};
// The base Class object
// ===============
//
// Quintus uses the Simple JavaScript inheritance Class object, created by
// John Resig and described on his blog:
//
// [http://ejohn.org/blog/simple-javascript-inheritance/](http://ejohn.org/blog/simple-javascript-inheritance/)
//
// The class is used wholesale, with the only differences being that instead
// of appearing in a top-level namespace, the `Class` object is available as
// `Q.Class` and a second argument on the `extend` method allows for adding
// class level methods and the class name is passed in a parameter for introspection
// purposes.
//
// Classes can be created by calling `Q.Class.extend(name,{ .. })`, although most of the time
// you'll want to use one of the derivitive classes, `Q.Evented` or `Q.GameObject` which
// have a little bit of functionality built-in. `Q.Evented` adds event binding and
// triggering support and `Q.GameObject` adds support for components and a destroy method.
//
// The main things Q.Class get you are easy inheritance, a constructor method called `init()`,
// dynamic addition of a this._super method when a method is overloaded (be careful with
// this as it adds some overhead to method calls.) Calls to `instanceof` also all
// work as you'd hope.
//
// By convention, classes should be added onto to the `Q` object and capitalized, so if
// you wanted to create a new class for your game, you'd write:
//
// Q.Class.extend("MyClass",{ ... });
//
// Examples:
//
// Q.Class.extend("Bird",{
// init: function(name) { this.name = name; },
// speak: function() { console.log(this.name); },
// fly: function() { console.log("Flying"); }
// });
//
// Q.Bird.extend("Penguin",{
// speak: function() { console.log(this.name + " the penguin"); },
// fly: function() { console.log("Can't fly, sorry..."); }
// });
//
// var randomBird = new Q.Bird("Frank"),
// pengy = new Q.Penguin("Pengy");
//
// randomBird.fly(); // Logs "Flying"
// pengy.fly(); // Logs "Can't fly,sorry..."
//
// randomBird.speak(); // Logs "Frank"
// pengy.speak(); // Logs "Pengy the penguin"
//
// console.log(randomBird instanceof Q.Bird); // true
// console.log(randomBird instanceof Q.Penguin); // false
// console.log(pengy instanceof Q.Bird); // true
// console.log(pengy instanceof Q.Penguin); // true
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*
* Inspired by base2 and Prototype
*/
(function(){
var initializing = false,
fnTest = /xyz/.test(function(){ var xyz;}) ? /\b_super\b/ : /.*/;
/* The base Class implementation (does nothing) */
Q.Class = function(){};
/* Create a new Class that inherits from this class */
Q.Class.extend = function(className, prop, classMethods) {
/* No name, don't add onto Q */
if(!Q._isString(className)) {
classMethods = prop;
prop = className;
className = null;
}
var _super = this.prototype,
ThisClass = this;
/* Instantiate a base class (but only create the instance, */
/* don't run the init constructor) */
initializing = true;
var prototype = new ThisClass();
initializing = false;
function _superFactory(name,fn) {
return function() {
var tmp = this._super;
/* Add a new ._super() method that is the same method */
/* but on the super-class */
this._super = _super[name];
/* The method only need to be bound temporarily, so we */
/* remove it when we're done executing */
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
}
/* Copy the properties over onto the new prototype */
for (var name in prop) {
/* Check if we're overwriting an existing function */
prototype[name] = typeof prop[name] === "function" &&
typeof _super[name] === "function" &&
fnTest.test(prop[name]) ?
_superFactory(name,prop[name]) :
prop[name];
}
/* The dummy class constructor */
function Class() {
/* All construction is actually done in the init method */
if ( !initializing && this.init ) {
this.init.apply(this, arguments);
}
}
/* Populate our constructed prototype object */
Class.prototype = prototype;
/* Enforce the constructor to be what we expect */
Class.prototype.constructor = Class;
/* And make this class extendable */
Class.extend = Q.Class.extend;
/* If there are class-level Methods, add them to the class */
if(classMethods) {
Q._extend(Class,classMethods);
}
if(className) {
/* Save the class onto Q */
Q[className] = Class;
/* Let the class know its name */
Class.prototype.className = className;
Class.className = className;
}
return Class;
};
}());
// Event Handling
// ==============
// The `Q.Evented` class adds event handling onto the base `Q.Class`
// class. Evented objects can trigger events and other objects can
// bind to those events.
Q.Class.extend("Evented",{
// Binds a callback to an event on this object. If you provide a
// `target` object, that object will add this event to it's list of
// binds, allowing it to automatically remove it when it is destroyed.
on: function(event,target,callback) {
// Handle the case where there is no target provided,
// swapping the target and callback parameters.
if(!callback) {
callback = target;
target = null;
}
// If there's still no callback, default to the event name
if(!callback) {
callback = event;
}
// Handle case for callback that is a string, this will
// pull the callback from the target object or from this
// object.
if(Q._isString(callback)) {
callback = (target || this)[callback];
}
// To keep `Q.Evented` objects from needing a constructor,
// the `listeners` object is created on the fly as needed.
// `listeners` keeps a list of callbacks indexed by event name
// for quick lookup.
this.listeners = this.listeners || {};
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push([ target || this, callback]);
// With a provided target, the target object keeps track of
// the events it is bound to, which allows for automatic
// unbinding on destroy.
if(target) {
if(!target.binds) { target.binds = []; }
target.binds.push([this,event,callback]);
}
},
// Triggers an event, passing in some optional additional data about
// the event.
trigger: function(event,data) {
// First make sure there are any listeners, then check for any listeners
// on this specific event, if not, early out.
if(this.listeners && this.listeners[event]) {
// Call each listener in the context of either the target passed into
// `on` or the object itself.
for(var i=0,len = this.listeners[event].length;i<len;i++) {
var listener = this.listeners[event][i];
listener[1].call(listener[0],data);
}
}
},
// Unbinds an event. Can be called with 1, 2, or 3 parameters, each
// of which unbinds a more specific listener.
off: function(event,target,callback) {
// Without a target, remove all teh listeners.
if(!target) {
if(this.listeners[event]) {
delete this.listeners[event];
}
} else {
// If the callback is a string, find a method of the
// same name on the target.
if(Q._isString(callback) && target[callback]) {
callback = target[callback];
}
var l = this.listeners && this.listeners[event];
if(l) {
// Loop from the end to the beginning, which allows us
// to remove elements without having to affect the loop.
for(var i = l.length-1;i>=0;i--) {
if(l[i][0] === target) {
if(!callback || callback === l[i][1]) {
this.listeners[event].splice(i,1);
}
}
}
}
}
},
// `debind` is called to remove any listeners an object had
// on other objects. The most common case is when an object is
// destroyed you'll want all the event listeners to be removed
// for you.
debind: function() {
if(this.binds) {
for(var i=0,len=this.binds.length;i<len;i++) {
var boundEvent = this.binds[i],
source = boundEvent[0],
event = boundEvent[1];
source.off(event,this);
}
}
}
});
// Components
// ==============
//
// Components are self-contained pieces of functionality that can be added onto and removed
// from objects. The allow for a more dynamic functionality tree than using inheritance (i.e.
// by favoring composition over inheritance) and are added and removed on the fly at runtime.
// (yes, I know everything in JS is at runtime, but you know what I mean, geez)
//
// Combining components with events makes it easy to create reusable pieces of
// functionality that can be decoupled from each other.
// The master list of registered components, indexed in an object by name.
Q.components = {};
// The base class for components. These are usually not derived directly but are instead
// created by calling `Q.register` to register a new component given a set of methods the
// component supports. Components are created automatically when they are added to a
// `Q.GameObject` with the `add` method.
//
// Many components also define an `added` method, which is called automatically by the
// `init` constructor after a component has been added to an object. This is a good time
// to add event listeners on the object.
Q.Evented.extend("Component",{
// Components are created when they are added onto a `Q.GameObject` entity. The entity
// is directly extended with any methods inside of an `extend` property and then the
// component itself is added onto the entity as well.
init: function(entity) {
this.entity = entity;
if(this.extend) { Q._extend(entity,this.extend); }
entity[this.name] = this;
entity.activeComponents.push(this.componentName);
if(entity.parent && entity.parent.addToList) {
entity.parent.addToList(this.componentName,entity);
}
if(this.added) { this.added(); }
},
// `destroy` is called automatically when a component is removed from an entity. It is
// not called, however, when an entity is destroyed (for performance reasons).
//
// It's job is to remove any methods that were added with `extend` and then remove and
// debind itself from the entity. It will also call `destroyed` if the component has
// a method by that name.
destroy: function() {
if(this.extend) {
var extensions = Q._keys(this.extend);
for(var i=0,len=extensions.length;i<len;i++) {
delete this.entity[extensions[i]];
}
}
delete this.entity[this.name];
var idx = this.entity.activeComponents.indexOf(this.componentName);
if(idx !== -1) {
this.entity.activeComponents.splice(idx,1);
if(this.entity.parent && this.entity.parent.addToList) {
this.entity.parent.addToLists(this.componentName,this.entity);
}
}
this.debind();
if(this.destroyed) { this.destroyed(); }
}
});
// This is the base class most Quintus objects are derived from, it extends
// `Q.Evented` and adds component support to an object, allowing components to
// be added and removed from an object. It also defines a destroyed method
// which will debind the object, remove it from it's parent (usually a scene)
// if it has one, and trigger a destroyed event.
Q.Evented.extend("GameObject",{
// Simple check to see if a component already exists
// on an object by searching for a property of the same name.
has: function(component) {
return this[component] ? true : false;
},
// See if a object is a specific class
isA: function(className) {
return this.className === className;
},
// Adds one or more components to an object. Accepts either
// a comma separated string or an array of strings that map
// to component names.
//
// Instantiates a new component object of the correct type
// (if the component exists) and then triggers an addComponent
// event.
//
// Returns the object to allow chaining.
add: function(components) {
components = Q._normalizeArg(components);
if(!this.activeComponents) { this.activeComponents = []; }
for(var i=0,len=components.length;i<len;i++) {
var name = components[i],
Comp = Q.components[name];
if(!this.has(name) && Comp) {
var c = new Comp(this);
this.trigger('addComponent',c);
}
}
return this;
},
// Removes one or more components from an object. Accepts the
// same style of parameters as `add`. Triggers a delComponent event
// and and calls destroy on the component.
//
// Returns the element to allow chaining.
del: function(components) {
components = Q._normalizeArg(components);
for(var i=0,len=components.length;i<len;i++) {
var name = components[i];
if(name && this.has(name)) {
this.trigger('delComponent',this[name]);
this[name].destroy();
}
}
return this;
},
// Destroys the object by calling debind and removing the
// object from it's parent. Will trigger a destroyed event
// callback.
destroy: function() {
if(this.isDestroyed) { return; }
this.trigger('destroyed');
this.debind();
if(this.parent && this.parent.remove) {
this.parent.remove(this);
}
this.isDestroyed = true;
}
});
// This registers a component with the engine, making it available to `Q.GameObject`'s
// This creates a new descendent class of `Q.Component` with new methods added in.
Q.component = function(name,methods) {
if(!methods) { return Q.components[name]; }
methods.name = name;
methods.componentName = "." + name;
return (Q.components[name] = Q.Component.extend(name + "Component",methods));
};
// Canvas Methods
// ==============
//
// The `setup` and `clear` method are the only two canvas-specific methods in
// the core of Quintus. `imageData` also uses canvas but it can be used in
// any type of game.
// Setup will either create a new canvas element and append it
// to the body of the document or use an existing one. It will then
// pull out the width and height of the canvas for engine use.
//
// It also adds a wrapper container around the element.
//
// If the `maximize` is set to true, the canvas element is maximized
// on the page and the scroll trick is used to try to get the address bar away.
//
// The engine will also resample the game to CSS dimensions at twice pixel
// dimensions if the `resampleWidth` or `resampleHeight` options are set.
//
// TODO: add support for auto-resize w/ engine event notifications, remove
// jQuery.
Q.touchDevice = ('ontouchstart' in document);
Q.setup = function(id, options) {
if(Q._isObject(id)) {
options = id;
id = null;
}
options = options || {};
id = id || "quintus";
if(Q._isString(id)) {
Q.el = document.getElementById(id);
} else {
Q.el = id;
}
if(!Q.el) {
Q.el = document.createElement("canvas");
Q.el.width = options.width || 320;
Q.el.height = options.height || 420;
Q.el.id = id;
document.body.appendChild(Q.el);
}
var w = parseInt(Q.el.width,10),
h = parseInt(Q.el.height,10),
cssW = w,
cssH = h;
var maxWidth = options.maxWidth || 5000,
maxHeight = options.maxHeight || 5000,
resampleWidth = options.resampleWidth,
resampleHeight = options.resampleHeight,
upsampleWidth = options.upsampleWidth,
upsampleHeight = options.upsampleHeight;
if(options.maximize === 'resize') {
document.body.style.padding = 0;
document.body.style.margin = 0;
var cssCalculatedW = Math.min(window.innerWidth,maxWidth);
var cssCalculatedH = Math.min(window.innerHeight - 5,maxHeight);
if(Q.touchDevice) {
Q.el.style.height = (h*2) + "px";
window.scrollTo(0,1);
cssCalculatedW = Math.min(window.innerWidth,maxWidth);
cssCalculatedH = Math.min(window.innerHeight,maxHeight);
}
if(cssCalculatedW + 50 < w || cssCalculatedH + 50 < h) {
var wRatio = cssCalculatedW / w,
hRatio = cssCalculatedH / h;
var ratio = wRatio < hRatio ? wRatio : hRatio;
cssW = w * ratio;
cssH = h * ratio;
}
}
else if(options.maximize === true || (Q.touchDevice && options.maximize === 'touch')) {
document.body.style.padding = 0;
document.body.style.margin = 0;
w = Math.min(window.innerWidth,maxWidth);
h = Math.min(window.innerHeight - 5,maxHeight);
if(Q.touchDevice) {
Q.el.style.height = (h*2) + "px";
window.scrollTo(0,1);
w = Math.min(window.innerWidth,maxWidth);
h = Math.min(window.innerHeight,maxHeight);
}
}
if((upsampleWidth && w <= upsampleWidth) ||
(upsampleHeight && h <= upsampleHeight)) {
w *= 2;
h *= 2;
}
else if(((resampleWidth && w > resampleWidth) ||
(resampleHeight && h > resampleHeight)) &&
Q.touchDevice) {
w /= 2;
h /= 2;
}
Q.el.style.height = cssH + "px";
Q.el.style.width = cssW + "px";
Q.el.width = w;
Q.el.height = h;
var elParent = Q.el.parentNode;
if(elParent) {
Q.wrapper = document.createElement("div");
Q.wrapper.id = id + '_container';
Q.wrapper.style.width = cssW + "px";
Q.wrapper.style.margin = "0 auto";
Q.wrapper.style.position = "relative";
elParent.insertBefore(Q.wrapper,Q.el);
Q.wrapper.appendChild(Q.el);
}
Q.el.style.position = 'relative';
Q.ctx = Q.el.getContext &&
Q.el.getContext("2d");
Q.width = parseInt(Q.el.width,10);
Q.height = parseInt(Q.el.height,10);
Q.cssWidth = cssW;
Q.cssHeight = cssH;
window.addEventListener('orientationchange',function() {
setTimeout(function() { window.scrollTo(0,1); }, 0);
});
return Q;
};
// Clear the canvas completely.
Q.clear = function() {
if(Q.clearColor) {
Q.ctx.globalAlpha = 1;
Q.ctx.fillStyle = Q.clearColor;
Q.ctx.fillRect(0,0,Q.width,Q.height);
} else {
Q.ctx.clearRect(0,0,Q.width,Q.height);
}
};
// Return canvas image data given an Image object.
Q.imageData = function(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
return ctx.getImageData(0,0,img.width,img.height);
};
// Asset Loading Support
// =====================
//
// The engine supports loading assets of different types using
// `load` or `preload`. Assets are stored by their name so the
// same asset won't be loaded twice if it already exists.
// Augmentable list of asset types, loads a specific asset
// type if the file type matches, otherwise defaults to a Ajax
// load of the data.
//
// You can new types of assets based on file extension by
// adding to `assetTypes` and adding a method called
// loadAssetTYPENAME where TYPENAME is the name of the
// type you added in.
Q.assetTypes = {
png: 'Image', jpg: 'Image', gif: 'Image', jpeg: 'Image',
ogg: 'Audio', wav: 'Audio', m4a: 'Audio', mp3: 'Audio'
};
// Determine the type of asset based on the lookup table above
Q.assetType = function(asset) {
/* Determine the lowercase extension of the file */
var fileParts = asset.split("."),
fileExt = fileParts[fileParts.length-1].toLowerCase();
/* Lookup the asset in the assetTypes hash, or return other */
return Q.assetTypes[fileExt] || 'Other';
};
// Loader for Images, creates a new `Image` object and uses the
// load callback to determine the image has been loaded
Q.loadAssetImage = function(key,src,callback,errorCallback) {
var img = new Image();
img.onload = function() { callback(key,img); };
img.onerror = errorCallback;
if(src.match(/https?:\/\//)) {
img.src = src;
} else {
img.src = Q.options.imagePath + src;
}
};
// List of mime types given an audio file extension, used to
// determine what sound types the browser can play using the
// built-in `Sound.canPlayType`
Q.audioMimeTypes = { mp3: 'audio/mpeg',
ogg: 'audio/ogg; codecs="vorbis"',
m4a: 'audio/m4a',
wav: 'audio/wav' };
// Loader for Audio assets. By default chops off the extension and
// will automatically determine which of the supported types is
// playable by the browser and load that type.
//
// Which types are available are determined by the file extensions
// listed in the Quintus `options.audioSupported`
Q.loadAssetAudio = function(key,src,callback,errorCallback) {
if(!document.createElement("audio").play || !Q.options.sound) {
callback(key,null);
return;
}
var snd = new Audio(),
baseName = Q._removeExtension(src),
extension = null,
filename = null;
/* Find a supported type */
extension =
Q._detect(Q.options.audioSupported,
function(extension) {
return snd.canPlayType(Q.audioMimeTypes[extension]) ?
extension : null;
});
/* No supported audio = trigger ok callback anyway */
if(!extension) {
callback(key,null);
return;
}
snd.addEventListener("error",errorCallback);
snd.addEventListener('canplaythrough',function() {
callback(key,snd);
});
snd.src = Q.options.audioPath + baseName + "." + extension;
snd.load();
return snd;
};
// Loader for other file types, just store the data
// returned from an Ajax call.
Q.loadAssetOther = function(key,src,callback,errorCallback) {
var request = new XMLHttpRequest();
var fileParts = src.split("."),
fileExt = fileParts[fileParts.length-1].toLowerCase();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
if(fileExt === 'json') {
callback(key,JSON.parse(request.responseText));
} else {
callback(key,request.responseText);
}
} else {
errorCallback();
}
}
};
request.open("GET",Q.options.dataPath + src, true);
request.send(null);
};
// Helper method to return a name without an extension
Q._removeExtension = function(filename) {
return filename.replace(/\.(\w{3,4})
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.