topical media & game development
mobile-query-game-jquery.gamequery.js / js
/*
* gameQuery rev. Revision
*
* Copyright (c) 2012 Selim Arsever (http://gamequeryjs.com)
* licensed under the MIT-License
*/
// This allows use of the convenient
(function() {
// CSS Feature detection from: Craig Buckler (http://www.sitepoint.com/detect-css3-property-browser-support/)
var cssTransform = false;
var detectElement = document.createElement("detect"),
CSSprefix = "Webkit,Moz,O,ms,Khtml".split(","),
All = ("transform," + CSSprefix.join("Transform,") + "Transform").split(",");
for (var i = 0, l = All.length; i < l; i++) {
if (detectElement.style[All[i]] === "") {
cssTransform = All[i];
}
}
// This prefix can be use whenever needed to namespace CSS classes, .data() inputs aso.
var gQprefix = "gQ_";
// Those are the possible states of the engine
var STATE_NEW = 0; // Not yet started for the first time
var STATE_RUNNING = 1; // Started and running
var STATE_PAUSED = 2; // Paused
Utility function that returns the radius for a geometry.
parameter: {object} elem DOM element
parameter: {float} angle the angle in degrees
returns: {object} .x, .y radius of geometry
var proj = function (elem, angle) {
switch (elem.geometry){
case
Utility function that checks for collision between two elements.
parameter: {object} elem1 DOM for the first element
parameter: {float} offset1 offset of the first element
parameter: {object} elem2 DOM for the second element
parameter: {float} offset2 offset of the second element
returns: {boolean} if the two elements collide or not
var collide = function(elem1, offset1, elem2, offset2) {
// test real collision (only for two rectangles...)
if((elem1.geometry == .gameQuery.GEOMETRY_RECTANGLE && elem2.geometry ==
Utility function computes the offset relative to the playground of a gameQuery element without using DOM's position.
This should be faster than the standand .offset() function.
Warning: No non-gameQuery elements should be present between this element and the playground div!
parameter: {jQuery} element the jQuery wrapped DOM element representing the gameQuery object.
returns: {object} an object {x:, y: } containing the x and y offset. (Not top and left like jQuery's .offset())
var offset = function(element) {
// Get the tileSet offset (relative to the playground)
var offset = {x: 0, y: 0};
var parent = element[0];
while(parent !== .gameQuery.playground[0] && parent.gameQuery !== undefined) {
offset.x += parent.gameQuery.posx;
offset.y += parent.gameQuery.posy;
parent = parent.parentNode;
}
return offset
}
Utility function computes the index range of the tiles for a tilemap.
parameter: {jQuery} element the jQuery wrapped DOM element representing the tilemap.
parameter: {object} offset an object holding the x and y offset of the tilemap, this is optional and will be computed if not provided.
returns: {object} an object {firstColumn: , lastColumn: , fristRow: , lastRow: }
var visibleTilemapIndexes = function (element, elementOffset) {
if (elementOffset === undefined) {
elementOffset = offset(element);
}
var gameQuery = element[0].gameQuery;
// Activate the visible tiles
return {
firstRow: Math.max(Math.min(Math.floor(-elementOffset.y/gameQuery.height), gameQuery.sizey), 0),
lastRow: Math.max(Math.min(Math.ceil((.gameQuery.playground[0].width-elementOffset.x)/gameQuery.width), gameQuery.sizex), 0)
}
}
Utility function thast computes the buffered zone of a tilemap
parameter: {jQuery} element the jQuery wrapped DOM element representing the tilemap.
parameter: {object} visible an object describing the visible zone
returns: {object} an object {firstColumn: , lastColumn: , fristRow: , lastRow: }
var bufferedTilemapIndexes = function (element, visible) {
var gameQuery = element[0].gameQuery;
return {
firstRow: Math.max(Math.min(visible.firstRow - gameQuery.buffer, gameQuery.sizey), 0),
lastRow: Math.max(Math.min(visible.lastRow + gameQuery.buffer, gameQuery.sizey), 0),
firstColumn: Math.max(Math.min(visible.firstColumn - gameQuery.buffer, gameQuery.sizex), 0),
lastColumn: Math.max(Math.min(visible.lastColumn + gameQuery.buffer, gameQuery.sizex), 0)
}
}
Utility function that creates a tile in the given tilemap
parameter: {jQuery} tileSet the jQuery element representing the tile map
parameter: {integer} row the row index of the tile in the tile map
parameter: {integer} column the column index of the tile in the tile map
var addTile = function(tileSet, row, column) {
var gameQuery = tileSet[0].gameQuery;
var name = tileSet.attr("id");
var tileDescription;
if(gameQuery.func) {
tileDescription = gameQuery.tiles(row,column)-1;
} else {
tileDescription = gameQuery.tiles[row][column]-1;
}
var animation;
if(gameQuery.multi) {
animation = gameQuery.animations;
} else {
animation = gameQuery.animations[tileDescription];
}
if(tileDescription >= 0){
tileSet.addSprite(.gameQuery.tileIdPrefix+name+"_"+row+"_"+column);
if (gameQuery.multi) {
newTile.setAnimation(tileDescription);
} else {
newTile[0].gameQuery.animationNumber = tileDescription;
}
newTile.removeClass(.gameQuery.tileCssClass);
newTile.addClass(.
This is the Animation Object
Animation: function (options, imediateCallback) {
// private default values
var defaults = {
imageURL: "",
numberOfFrame: 1,
delta: 0,
rate: 30,
type: 0,
distance: 0,
offsetx: 0,
offsety: 0
};
// options extends defaults
options = .extend(defaults, options);
// "public" attributes:
this.imageURL = options.imageURL; // The url of the image to be used as an animation or sprite
this.numberOfFrame = options.numberOfFrame; // The number of frames to be displayed when playing the animation
this.delta = options.delta; // The distance in pixels between two frames
this.rate = options.rate; // The rate at which the frames change in miliseconds
this.type = options.type; // The type of the animation.This is a bitwise OR of the properties.
this.distance = options.distance; // The distance in pixels between two animations
this.offsetx = options.offsetx; // The x coordinate where the first sprite begins
this.offsety = options.offsety; // The y coordinate where the first sprite begins
// Whenever a new animation is created we add it to the ResourceManager animation list
"constants" for the different types of an animation
ANIMATION_VERTICAL: 1, // Generated by a vertical offset of the background
ANIMATION_HORIZONTAL: 2, // Generated by a horizontal offset of the background
ANIMATION_ONCE: 4, // Played only once (else looping indefinitely)
ANIMATION_CALLBACK: 8, // A callback is exectued at the end of a cycle
ANIMATION_MULTI: 16, // The image file contains many animations
ANIMATION_PINGPONG: 32, // At the last frame of the animation it reverses (if used in conjunction with ONCE it will have no effect)
// "constants" for the different type of geometry for a sprite
GEOMETRY_RECTANGLE: 1,
GEOMETRY_DISC: 2,
// basic values
refreshRate: 30,
An object to manage resource loading
resourceManager: {
animations: [], // List of animations / images used in the game
sounds: [], // List of sounds used in the game
callbacks: [], // List of the functions called at each refresh
loadedAnimationsPointer: 0, // Keep track of the last loaded animation
loadedSoundsPointer: 0, // Keep track of the last loaded sound
Load resources before starting the game.
preload: function() {
// Start loading the images
for (var i = this.animations.length-1 ; i >= this.loadedAnimationsPointer; i --){
this.animations[i].domO = new Image();
this.animations[i].domO.src = this.animations[i].imageURL;
}
// Start loading the sounds
for (var i = this.sounds.length-1 ; i >= this.loadedSoundsPointer; i --){
this.sounds[i].load();
}
.gameQuery.resourceManager.waitForResources();
},
Wait for all the resources called for in preload() to finish loading.
waitForResources: function() {
// Check the images
var imageCount = 0;
for(var i=this.loadedAnimationsPointer; i < this.animations.length; i++){
if(this.animations[i].domO.complete){
imageCount++;
}
}
// Check the sounds
var soundCount = 0;
for(var i=this.loadedSoundsPointer; i < this.sounds.length; i++){
var temp = this.sounds[i].ready();
if(temp){
soundCount++;
}
}
// Call the load callback with the current progress
if(.gameQuery.resourceManager.loadCallback(percent);
}
if(imageCount + soundCount < (this.animations.length + this.sounds.length - this.loadedAnimationsPointer - this.loadedSoundsPointer)){
imgWait=setTimeout(function () {
.gameQuery.scenegraph.children().each(function(){
// recursive call on the children:
this.children().each(arguments.callee);
// add the image as a background
if(this.gameQuery && this.gameQuery.animation){
this.css("background-image", "url("+this.gameQuery.animation.imageURL+")");
// we set the correct kind of repeat
if(this.gameQuery.animation.type & .gameQuery.ANIMATION_HORIZONTAL) {
this.css("background-repeat", "repeat-y");
} else {
this.css("background-repeat", "no-repeat");
}
}
});
// Launch the refresh loop
if(.gameQuery.resourceManager.refresh();
},(.gameQuery.state = STATE_RUNNING;
if(.gameQuery.startCallback();
}
// Make the scenegraph visible
This function refresh a unique sprite here 'this' represent a dom object
refreshSprite: function() {
// Check if 'this' is a gameQuery element
if(this.gameQuery != undefined){
var gameQuery = this.gameQuery;
// Does 'this' has an animation ?
if(gameQuery.animation){
// Do we have anything to do?
if ( (gameQuery.idleCounter == gameQuery.animation.rate-1) && gameQuery.playing){
// Does 'this' loops?
if(gameQuery.animation.type & .gameQuery.ANIMATION_ONCE){
if(gameQuery.currentFrame < gameQuery.animation.numberOfFrame-1){
gameQuery.currentFrame += gameQuery.frameIncrement;
} else if(gameQuery.currentFrame == gameQuery.animation.numberOfFrame-1) {
// Does 'this' has a callback ?
if(gameQuery.animation.type & .isFunction(gameQuery.callback)){
gameQuery.callback(this);
gameQuery.callback = false;
}
}
}
} else {
if(gameQuery.animation.type & .gameQuery.ANIMATION_CALLBACK){
if(.gameQuery.ANIMATION_VERTICAL) && (gameQuery.animation.numberOfFrame > 1)){
if(gameQuery.multi){
this.css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.multi)+"px "+(-gameQuery.animation.offsety-gameQuery.animation.delta*gameQuery.currentFrame)+"px");
} else {
this.css("background-position",""+(-gameQuery.animation.offsetx)+"px "+(-gameQuery.animation.offsety-gameQuery.animation.delta*gameQuery.currentFrame)+"px");
}
} else if((gameQuery.animation.type &
This function refresh a unique tile-map, here 'this' represent a dom object
refreshTilemap: function() {
// Check if 'this' is a gameQuery element
if(this.gameQuery != undefined){
var gameQuery = this.gameQuery;
if(.isArray(gameQuery.frameTracker)){
for(var i=0; i<gameQuery.frameTracker.length; i++){
// Do we have anything to do?
if(gameQuery.idleCounter[i] == gameQuery.animations[i].rate-1){
// Does 'this' loops?
if(gameQuery.animations[i].type & .gameQuery.ANIMATION_PINGPONG){
if(gameQuery.frameTracker[i] == gameQuery.animations[i].numberOfFrame-1 && gameQuery.frameIncrement[i] == 1) {
gameQuery.frameIncrement[i] = -1;
} else if (gameQuery.frameTracker[i] == 0 && gameQuery.frameIncrement[i] == -1) {
gameQuery.frameIncrement[i] = 1;
}
}
gameQuery.frameTracker[i] = (gameQuery.frameTracker[i]+gameQuery.frameIncrement[i])\%gameQuery.animations[i].numberOfFrame;
}
}
gameQuery.idleCounter[i] = (gameQuery.idleCounter[i]+1)\%gameQuery.animations[i].rate;
}
} else {
// Do we have anything to do?
if(gameQuery.idleCounter == gameQuery.animations.rate-1){
// Does 'this' loops?
if(gameQuery.animations.type & .gameQuery.ANIMATION_PINGPONG){
if(gameQuery.frameTracker == gameQuery.animations.numberOfFrame-1 && gameQuery.frameIncrement == 1) {
gameQuery.frameIncrement = -1;
} else if (gameQuery.frameTracker == 0 && gameQuery.frameIncrement == -1) {
gameQuery.frameIncrement = 1;
}
}
gameQuery.frameTracker = (gameQuery.frameTracker+gameQuery.frameIncrement)\%gameQuery.animations.numberOfFrame;
}
}
gameQuery.idleCounter = (gameQuery.idleCounter+1)\%gameQuery.animations.rate;
}
// Update the background of all active tiles
this.find("."+.isArray(gameQuery.frameTracker)){
var animationNumber = this.gameQuery.animationNumber
if((gameQuery.animations[animationNumber].type & .gameQuery.ANIMATION_HORIZONTAL) && (gameQuery.animations[animationNumber].numberOfFrame > 1)) {
this.css("background-position",""+(-gameQuery.animations[animationNumber].offsetx-gameQuery.animations[animationNumber].delta*gameQuery.frameTracker[animationNumber])+"px "+(-gameQuery.animations[animationNumber].offsety)+"px");
}
} else {
if((gameQuery.animations.type & .gameQuery.ANIMATION_HORIZONTAL) && (gameQuery.animations.numberOfFrame > 1)) {
this.css("background-position",""+(-gameQuery.animations.offsetx-gameQuery.animations.delta*gameQuery.frameTracker)+"px "+(-gameQuery.animations.offsety-this.gameQuery.multi)+"px");
}
}
});
}
return true;
},
Called periodically to refresh the state of the game.
refresh: function() {
if(.gameQuery.playground.find("."+.gameQuery.playground.find("."+.gameQuery.refreshRate);
this.callbacks[i].idleCounter = 0;
}
}
this.callbacks[i].idleCounter = (this.callbacks[i].idleCounter+1)\%this.callbacks[i].rate;
}
for(var i = deadCallback.length-1; i >= 0; i--){
this.callbacks.splice(deadCallback[i],1);
}
}
},
Add an animation to the resource Manager
addAnimation: function(animation, callback) {
if(.gameQuery.refreshRate);
if(animation.rate==0){
animation.rate = 1;
}
this.animations.push(animation);
switch (
Add a sound to the resource Manager
addSound: function(sound, callback){
if(.inArray(sound,this.sounds)<0){
this.sounds.push(sound);
switch (
Register a callback
parameter: {function} fn the callback
parameter: {integer} rate the rate in ms at which the callback should be called (should be a multiple of the playground rate or will be rounded)
registerCallback: function(fn, rate){
rate = Math.round(rate/.gameQuery.refreshRate);
if(rate==0){
rate = 1;
}
this.callbacks.push({fn: fn, rate: rate, idleCounter: 0});
},
Clear the animations and sounds
clear: function(callbacksToo){
this.animations = [];
this.loadedAnimationsPointer = 0;
this.sounds = [];
this.loadedSoundsPointer = 0;
if(callbacksToo) {
this.callbacks = [];
}
}
},
This is a single place to update the underlying data of sprites/groups/tiles after a position or dimesion modification.
update: function(descriptor, transformation) {
// Did we really receive a descriptor or a jQuery object instead?
if(!.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
}
// And add the newly visible tiles
for(var j = Math.max(gameQuery.buffered.lastColumn,newBuffered.firstColumn); j < newBuffered.lastColumn ; j++) {
addTile(tilemap,i,j);
}
}
gameQuery.buffered.firstColumn = newBuffered.firstColumn;
gameQuery.buffered.lastColumn = newBuffered.lastColumn;
// Attach the tilemap back
tilemap.appendTo(parent);
}
if(visible.firstColumn < buffered.firstColumn) {
// Detach the tilemap
var parent = descriptor[0].parentNode;
var tilemap = descriptor.detach();
var newBuffered = bufferedTilemapIndexes(descriptor, visible);
for(var i = gameQuery.buffered.firstRow; i < gameQuery.buffered.lastRow; i++){
// Remove the newly invisible tiles
for(var j = Math.max(newBuffered.lastColumn,gameQuery.buffered.firstColumn); j < gameQuery.buffered.lastColumn ; j++) {
tilemap.find("#"+.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
}
// And add the newly visible tiles
for(var i = Math.max(gameQuery.buffered.lastRow, newBuffered.firstRow); i < newBuffered.lastRow; i++){
addTile(tilemap,i,j);
}
}
gameQuery.buffered.firstRow = newBuffered.firstRow;
gameQuery.buffered.lastRow = newBuffered.lastRow;
// Attach the tilemap back
tilemap.appendTo(parent);
}
if(visible.firstRow < buffered.firstRow) {
// Detach the tilemap
var parent = descriptor[0].parentNode;
var tilemap = descriptor.detach();
var newBuffered = bufferedTilemapIndexes(descriptor, visible);
for(var j = gameQuery.buffered.firstColumn; j < gameQuery.buffered.lastColumn ; j++) {
// Remove the newly invisible tiles
for(var i = Math.max(newBuffered.lastRow, gameQuery.buffered.firstRow); i < gameQuery.buffered.lastRow; i++){
tilemap.find("#"+.gameQuery.playground && !
Mute (or unmute) all the sounds.
muteSound: function(muted){
for (var i = .gameQuery.resourceManager.sounds.length-1 ; i >= 0; i --) {
Accessor for the currently defined playground as a jQuery object
playground: function() {
return .gameQuery.playground
},
Define a callback called during the loading of the game's resources.
The function will recieve as unique parameter
a number representing the progess percentage.
loadCallback: function(callback){
// fragments used to create DOM element
var spriteFragment = $("<div class='"+.gameQuery.groupCssClass+"' style='position: absolute; display: block; overflow: hidden' />");
var tilemapFragment = $("<div class='"+.fn.extend({
Defines the currently selected div to which contains the game and initialize it.
This is a non-destructive call
playground: function(options) {
if(this.length == 1){
if(this[0] == document){
// Old usage detected, this is not supported anymore
throw "Old playground usage, use .extend({
height: 320,
width: 480,
refreshRate: 30,
position: "absolute",
keyTracker: false,
mouseTracker: false,
disableCollision: false
}, options);
// We save the playground node and set some variable for this node:
.gameQuery.refreshRate = options.refreshRate;
.gameQuery.playground[0].width = options.width;
// We initialize the display of the div
.gameQuery.scenegraph = $("#"+gQprefix+"scenegraph");
// Add the keyTracker to the gameQuery object:
.gameQuery.keyTracker[event.keyCode] = true;
});
document.keyup(function(event){
.gameQuery.mouseTracker = {
x: 0,
y: 0};
// We only enable the real tracking if the users wants it
var scenegraphOffset = .gameQuery.playground).mousemove(function(event){
.gameQuery.mouseTracker.y = event.pageY - scenegraphOffset.top;
});
document.mousedown(function(event){
.gameQuery.mouseTracker[event.which] = false;
});
}
}
return this;
},
Starts the game.
Resources from the resource manager are preloaded if necesary
Works only for the playground node.
This is a non-destructive call
startGame: function(callback) {
.gameQuery.resourceManager.preload();
return this;
},
TODO
pauseGame: function() {
.gameQuery.scenegraph.css("visibility","hidden");
return this;
},
Resume the game if it was paused and call the callback passed in argument once the newly added ressources are loaded.
resumeGame: function(callback) {
if(.gameQuery.startCallback = callback;
Removes all the sprites, groups and tilemaps present in the scenegraph
clearScenegraph: function() {
.gameQuery.scenegraph.empty()
return this;
},
Removes all the sprites, groups and tilemaps present in the scenegraph as well as all loaded animations and sounds
clearAll: function(callbackToo) {
.gameQuery.resourceManager.clear(callbackToo)
return this;
},
Add a group to the scene graph. Works only on the scenegraph root or on another group
This IS a destructive call and should be terminated with end()
to go back one level up in the chaining
addGroup: function(group, options) {
options = .gameQuery.GEOMETRY_RECTANGLE,
angle: 0,
factor: 1,
factorh: 1,
factorv: 1
}, options);
var newGroupElement = groupFragment.clone().attr("id",group).css({
overflow: options.overflow,
height: options.height,
width: options.width
});
if(this == .gameQuery.scenegraph.append(newGroupElement);
} else if ((this == .gameQuery.groupCssClass))){
this.append(newGroupElement);
}
newGroupElement[0].gameQuery = options;
newGroupElement[0].gameQuery.boundingCircle = {x: options.posx + options.width/2,
y: options.posy + options.height/0,
originalRadius: Math.sqrt(Math.pow(options.width,2) + Math.pow(options.height,2))/2};
newGroupElement[0].gameQuery.boundingCircle.radius = newGroupElement[0].gameQuery.boundingCircle.originalRadius;
newGroupElement[0].gameQuery.group = true;
newGroupElement.transform();
return this.pushStack(newGroupElement);
},
Add a sprite to the current node. Works only on the playground or any of its sub-nodes
This is a non-destructive call
addSprite: function(sprite, options) {
options = .gameQuery.GEOMETRY_RECTANGLE,
angle: 0,
factor: 1,
playing: true,
factorh: 1,
factorv: 1
}, options);
var newSpriteElem = spriteFragment.clone().attr("id",sprite).css({
height: options.height,
width: options.width,
backgroundPosition: ((options.animation)? -options.animation.offsetx : 0)+"px "+((options.animation)? -options.animation.offsety : 0)+"px"
});
if(this == .gameQuery.scenegraph.append(newSpriteElem);
} else {
this.append(newSpriteElem);
}
// If the game has already started we want to add the animation's image as a background now
if(options.animation){
// The second test is a fix for default background (github.com/onaluf/gameQuery/issues/3)
if(.gameQuery.ANIMATION_VERTICAL) {
newSpriteElem.css("background-repeat", "repeat-x");
} else if(options.animation.type &
Add a Tile Map to the selected element.
This is a non-destructive call. The added sprite is NOT selected after a call to this function!
addTilemap: function(name, tileDescription, animationList, options){
options = .extend({
width: 32,
height: 32,
sizex: 32,
sizey: 32,
posx: 0,
posy: 0,
posz: 0,
posOffsetX: 0,
posOffsetY: 0,
angle: 0,
factor: 1,
factorh: 1,
factorv: 1,
buffer: 1
}, options);
var tileSet = tilemapFragment.clone().attr("id",name).css({
height: options.height*options.sizey,
width: options.width*options.sizex
});
if(this == .gameQuery.scenegraph.append(tileSet);
} else {
this.append(tileSet);
}
tileSet[0].gameQuery = options;
var gameQuery = tileSet[0].gameQuery;
gameQuery.tileSet = true;
gameQuery.tiles = tileDescription;
gameQuery.func = (typeof tileDescription === "function");
if(
This function imports a JSON file generated by Tiled (http://www.mapeditor.org/).
All the created tilemaps will be directly under the currently selected element.
Their name will be made of the provided prefix followed by a number starting at 0.
Only layer of type "tilelayer" are supported for now. Only one single tileset
per layer is supported.
After the call to this function the second argument will hold two new arrays:
- tiles: an arrays of tilemaps wraped in jQuery.
- animations: an arrays of animations
This is a non-destructive call
importTilemaps: function(url, prefix, generatedElements){
var animations = [];
var tilemaps = [];
var that = this;
var tilemapJsonLoaded = function(json){
var tilesetGID = [];
for (var i = 0; i < json.tilesets.length; i++) {
tilesetGID[i] = json.tilesets[i].firstgid;
}
var getTilesetIndex = function(index){
var i = 0;
while(index >= tilesetGID[i] && i < tilesetGID.length){
i++;
}
return i-1;
}
var height = json.height;
var width = json.width;
var tileHeight = json.tileheight;
var tileWidth = json.tilewidth;
var layers = json.layers;
var usedTiles = [];
var animationCounter = 0;
var tilemapArrays = [];
// Detect which animations we need to generate
// and convert the tiles array indexes to the new ones
for (var i=0; i < layers.length; i++){
if(layers[i].type === "tilelayer"){
var tilemapArray = new Array(height);
for (var j=0; j<height; j++){
tilemapArray[j] = new Array(width);
}
for (var j=0; j < layers[i].data.length; j++){
var tile = layers[i].data[j];
if(tile === 0){
tilemapArray[Math.floor(j / width)][j % width] = 0;
} else {
if(!usedTiles[tile]){
animationCounter++;
usedTiles[tile] = animationCounter;
animations.push(new .gameQuery.Animation({
imageURL: json.tilesets[getTilesetIndex(tile)].image,
offsetx: ((tile-1) % Math.floor(json.tilesets[getTilesetIndex(tile)].imagewidth / tileWidth)) * tileWidth,
offsety: Math.floor((tile-1) / Math.floor(json.tilesets[getTilesetIndex(tile)].imagewidth / tileWidth)) * tileHeight
}));
}
tilemapArray[Math.floor(j / width)][j % width] = usedTiles[tile];
}
}
tilemapArrays.push(tilemapArray);
}
}
// adding the tilemaps
for (var i=0; i<tilemapArrays.length; i++){
tilemaps.push(that.addTilemap(
prefix+i,
tilemapArrays[i],
animations,
{
sizex: width,
sizey: height,
width: tileWidth,
height: tileHeight
}));
}
};
Stop the animation at the current frame
This is a non-destructive call.
pauseAnimation: function() {
this[0].gameQuery.playing = false;
return this;
},
Resume the animation (if paused)
This is a non-destructive call.
resumeAnimation: function() {
this[0].gameQuery.playing = true;
return this;
},
Changes the animation associated with a sprite.
WARNING: no checks are made to ensure that the object is really a sprite
This is a non-destructive call.
setAnimation: function(animation, callback) {
var gameQuery = this[0].gameQuery;
if(typeof animation == "number"){
if(gameQuery.animation.type & .gameQuery.ANIMATION_MULTI){
var distance = gameQuery.animation.distance * animation;
gameQuery.multi = distance;
gameQuery.frameIncrement = 1;
gameQuery.currentFrame = 0;
if(gameQuery.animation.type & .gameQuery.ANIMATION_HORIZONTAL) {
this.css("background-position",""+(-gameQuery.animation.offsetx)+"px "+(-distance-gameQuery.animation.offsety)+"px");
}
}
} else {
if(animation){
gameQuery.animation = animation;
gameQuery.currentFrame = 0;
gameQuery.frameIncrement = 1;
if (animation.imageURL !== '') {this.css("backgroundImage", "url('"+animation.imageURL+"')");}
this.css({"background-position": ""+(-animation.offsetx)+"px "+(-animation.offsety)+"px"});
if(gameQuery.animation.type & .gameQuery.ANIMATION_HORIZONTAL) {
this.css("background-repeat", "repeat-y");
} else {
this.css("background-repeat", "no-repeat");
}
} else {
this.css("background-image", "");
}
}
if(callback != undefined){
this[0].gameQuery.callback = callback;
}
return this;
},
Register a callback funnction
This is a non-destructive call
parameter: {Function} fn the callback function.
parameter: {Number} rate time in milliseconds between calls.
registerCallback: function(fn, rate) {
Retrieve a list of objects in collision with the subject.
If 'this' is a sprite or a group, the function will retrieve the list of sprites (not groups!!!) that touch it. For now all abject are considered to be boxes.
This IS a destructive call and should be terminated with end() to go back one level up in the chaining.
collision: function(arg1, arg2){
var filter, override;
if (.isPlainObject(arg1)){
override = arg1;
} else if (typeof arg1 === "string") {
filter = arg1;
}
if (.gameQuery.playground[0]){
if(itsParent.gameQuery){
offsetX += itsParent.gameQuery.posx;
offsetY += itsParent.gameQuery.posy;
}
itsParent = itsParent.parentNode;
}
// Retrieve the playground's absolute position and size information
var pgdGeom = {top: 0, left: 0, bottom: .playground().width()};
// Retrieve the gameQuery object and correct it with the override
var gameQuery = jQuery.extend(true, {}, this[0].gameQuery);
// Retrieve the BoundingCircle and correct it with the override
var boundingCircle = jQuery.extend(true, {}, gameQuery.boundingCircle);
if(override && override.w){
gameQuery.width = override.w;
}
if(override && override.h){
gameQuery.height = override.h;
}
boundingCircle.originalRadius = Math.sqrt(Math.pow(gameQuery.width,2) + Math.pow(gameQuery.height,2))/2
boundingCircle.radius = gameQuery.factor*boundingCircle.originalRadius;
if(override && override.x){
boundingCircle.x = override.x + gameQuery.width/2.0;
}
if(override && override.y){
boundingCircle.y = override.y + gameQuery.height/2.0;
}
gameQuery.boundingCircle = boundingCircle;
// Is 'this' inside the playground ?
if( (gameQuery.boundingCircle.y + gameQuery.boundingCircle.radius + offsetY < pgdGeom.top) ||
(gameQuery.boundingCircle.x + gameQuery.boundingCircle.radius + offsetX < pgdGeom.left) ||
(gameQuery.boundingCircle.y - gameQuery.boundingCircle.radius + offsetY > pgdGeom.bottom) ||
(gameQuery.boundingCircle.x - gameQuery.boundingCircle.radius + offsetX > pgdGeom.right)){
return this.pushStack(new $([]));
}
if(this !== .gameQuery.scenegraph.children(filter).get());
elementsToCheck[0].offsetX = 0;
elementsToCheck[0].offsetY = 0;
for(var i = 0, len = elementsToCheck.length; i < len; i++) {
var subLen = elementsToCheck[i].length;
while(subLen--){
var elementToCheck = elementsToCheck[i][subLen];
// Is it a gameQuery generated element?
if(elementToCheck.gameQuery){
// We don't want to check groups
if(!elementToCheck.gameQuery.group && !elementToCheck.gameQuery.tileSet){
// Does it touche the selection?
if(this[0]!=elementToCheck){
// Check bounding circle collision
var distance = Math.sqrt(Math.pow(offsetY + gameQuery.boundingCircle.y - elementsToCheck[i].offsetY - elementToCheck.gameQuery.boundingCircle.y, 2) + Math.pow(offsetX + gameQuery.boundingCircle.x - elementsToCheck[i].offsetX - elementToCheck.gameQuery.boundingCircle.x, 2));
if(distance - gameQuery.boundingCircle.radius - elementToCheck.gameQuery.boundingCircle.radius <= 0){
// Check real collision
if(collide(gameQuery, {x: offsetX, y: offsetY}, elementToCheck.gameQuery, {x: elementsToCheck[i].offsetX, y: elementsToCheck[i].offsetY})) {
// Add to the result list if collision detected
resultList.push(elementsToCheck[i][subLen]);
}
}
}
}
// Add the children nodes to the list
var eleChildren = elementToCheck.children(filter);
if(eleChildren.length){
elementsToCheck.push(eleChildren.get());
elementsToCheck[len].offsetX = elementToCheck.gameQuery.posx + elementsToCheck[i].offsetX;
elementsToCheck[len].offsetY = elementToCheck.gameQuery.posy + elementsToCheck[i].offsetY;
len++;
}
}
}
}
return this.pushStack(resultList);
}
},
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
-- Sound related functions ------------------------------------------------------------------------------------------------------------------ *
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
Add the sound to the resourceManager for later use and
associates it to the selected dom element(s).
This is a non-destructive call
addSound: function(sound, add) {
// Does a SoundWrapper exist?
if(
Play the sound(s) associated with the selected dom element(s).
This is a non-destructive call.
playSound: function() {
this.each(function(){
var gameQuery = this.gameQuery;
if(gameQuery.sounds) {
for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
gameQuery.sounds[i].play();
}
}
});
return this;
},
Stops the sound(s) associated with the selected dom element(s) and rewinds them.
This is a non-destructive call.
stopSound: function() {
this.each(function(){
var gameQuery = this.gameQuery;
if(gameQuery.sounds) {
for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
gameQuery.sounds[i].stop();
}
}
});
return this;
},
Pauses the sound(s) associated with the selected dom element(s).
This is a non-destructive call.
pauseSound: function() {
this.each(function(){
var gameQuery = this.gameQuery;
if(gameQuery.sounds) {
for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
gameQuery.sounds[i].pause();
}
}
});
return this;
},
Mute or unmute the selected sound or all the sounds if none is specified.
This is a non-destructive call.
muteSound: function(muted) {
this.each(function(){
var gameQuery = this.gameQuery;
if(gameQuery.sounds) {
for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
gameQuery.sounds[i].muted(muted);
}
}
});
return this;
},
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
-- Transformation functions ----------------------------------------------------------------------------------------------------------------- *
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
Internal function doing the combined actions of rotate and scale.
Please use .rotate() or .scale() instead since they are part of the supported API!
This is a non-destructive call.
transform: function() {
var gameQuery = this[0].gameQuery;
if(cssTransform){
var transform = "translate("+gameQuery.posx+"px, "+gameQuery.posy+"px) rotate("+gameQuery.angle+"deg) scale("+(gameQuery.factor*gameQuery.factorh)+","+(gameQuery.factor*gameQuery.factorv)+")";
this.css(cssTransform,transform);
} else {
var angle_rad = Math.PI * 2 / 360 * gameQuery.angle;
// try filter for IE
// For ie from 5.5
var cos = Math.cos(angle_rad) * gameQuery.factor;
var sin = Math.sin(angle_rad) * gameQuery.factor;
this.css("filter","progid:DXImageTransform.Microsoft.Matrix(M11="+(cos*gameQuery.factorh)+",M12="+(-sin*gameQuery.factorv)+",M21="+(sin*gameQuery.factorh)+",M22="+(cos*gameQuery.factorv)+",SizingMethod='auto expand',FilterType='nearest neighbor')");
var newWidth = this.width();
var newHeight = this.height();
gameQuery.posOffsetX = (newWidth-gameQuery.width)/2;
gameQuery.posOffsetY = (newHeight-gameQuery.height)/2;
this.css("left", ""+(gameQuery.posx-gameQuery.posOffsetX)+"px");
this.css("top", ""+(gameQuery.posy-gameQuery.posOffsetY)+"px");
}
return this;
},
Rotate the element(s) clock-wise.
parameter: {Number} angle the angle in degrees
parameter: {Boolean} relative or not
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current rotation angle!
rotate: function(angle, relative){
var gameQuery = this[0].gameQuery;
if(angle !== undefined) {
if(relative === true){
angle += gameQuery.angle;
angle %= 360;
}
.gameQuery.update(gameQuery,{angle: angle});
return this.transform();
} else {
var ang = gameQuery.angle;
return ang;
}
},
Change the scale of the selected element(s). The passed argument is a ratio:
parameter: {Number} factor a ratio: 1.0 = original size, 0.5 = half the original size etc.
parameter: {Boolean} relative or not
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current scale factor!
scale: function(factor, relative){
var gameQuery = this[0].gameQuery;
if(factor !== undefined) {
if(relative === true){
factor *= gameQuery.factor;
}
Flips the element(s) horizontally.
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current horizontal flipping status!
fliph: function(flip){
var gameQuery = this[0].gameQuery;
if (flip === undefined) {
return (gameQuery.factorh !== undefined) ? (gameQuery.factorh === -1) : false;
} else if (flip) {
gameQuery.factorh = -1;
} else {
gameQuery.factorh = 1;
}
return this.transform();
},
Flips the element(s) vertically.
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current vertical flipping status!
flipv: function(flip){
var gameQuery = this[0].gameQuery;
if (flip === undefined) {
return (gameQuery.factorv !== undefined) ? (gameQuery.factorv === -1) : false;;
} else if (flip) {
gameQuery.factorv = -1;
} else {
gameQuery.factorv = 1;
}
return this.transform();
},
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
-- Position getter/setter functions --------------------------------------------------------------------------------------------------------- *
---------------------------------------------------------------------------------------------------------------------------------------------------------------- *
Main function to change the sprite/group/tilemap position on screen.
The three first agruments are the coordiate (double) and the last one is a flag
to specify if the coordinate given are absolute or relative.
If no argument is specified then the functions act as a getter and return a
object {x,y,z}
Please note that the z coordinate is just the z-index.
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
xyz: function(x, y, z, relative) {
if (x === undefined) {
return this.getxyz();
} else {
return this.setxyz({x: x, y: y, z: z}, relative);
}
},
The following functions are all all shortcuts for the .xyz(...) function.
see: xyz for detailed documentation.
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
x: function(value, relative) {
if (value === undefined) {
return this.getxyz().x;
} else {
return this.setxyz({x: value}, relative);
}
},
y: function(value, relative) {
if (value === undefined) {
return this.getxyz().y;
} else {
return this.setxyz({y: value}, relative);
}
},
z: function(value, relative) {
if (value === undefined) {
return this.getxyz().z;
} else {
return this.setxyz({z: value}, relative);
}
},
xy: function(x, y, relative) {
if (x === undefined) {
// we return the z too since it doesn't cost anything
return this.getxyz();
} else {
return this.setxyz({x: x, y: y}, relative);
}
},
Main function to change the sprite/group/tilemap dimension on screen.
The two first arguments are the width and height (double) and the last one is a
flag to specify if the dimensions given are absolute or relative.
If no argument is specified then the functions act as a getter and
return an object {w,h}
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
wh: function(w, h, relative) {
if (w === undefined) {
return this.getwh();
} else {
return this.setwh({w: w, h: h}, relative);
}
},
The following functions are all all shortcuts for the .wh(...) function.
see: wh for detailed documentation.
This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
w: function(value, relative) {
if (value === undefined) {
return this.getwh().w;
} else {
return this.setwh({w: value}, relative);
}
},
h: function(value, relative) {
if (value === undefined) {
return this.getwh().h;
} else {
return this.setwh({h: value}, relative);
}
},
The following four functions are 'private', and are not supposed to
be used outside of the library.
They are NOT part of the API and so are not guaranteed to remain unchanged.
You should really use .xyz() and .wh() instead.
getxyz: function() {
var gameQuery = this[0].gameQuery;
return {x: gameQuery.posx, y: gameQuery.posy, z: gameQuery.posz};
},
setxyz: function(option, relative) {
var gameQuery = this[0].gameQuery;
for (coordinate in option) {
// Update the gameQuery object
switch (coordinate) {
case 'x':
if(relative) {
option.x += gameQuery.posx;
}
gameQuery.posx = option.x;
this.transform();
//update the sub tile maps (if any), this forces to recompute which tiles are visible
this.find("."+.gameQuery.tilemapCssClass).each(function(){
this.x(0, true);
});
break;
case 'y':
if(relative) {
option.y += gameQuery.posy;
}
gameQuery.posy = option.y;
this.transform();
//update the sub tile maps (if any), this forces to recompute which tiles are visible
this.find("."+.gameQuery.update(this, option);
return this;
},
getwh: function() {
var gameQuery = this[0].gameQuery;
return {w: gameQuery.width, h: gameQuery.height};
},
setwh: function(option, relative) {
var gameQuery = this[0].gameQuery;
for (coordinate in option) {
// Update the gameQuery object
switch (coordinate) {
case 'w':
if(relative) {
option.w += gameQuery.width;
}
gameQuery.width = option.w;
this.css("width","" + gameQuery.width + "px");
break;
case 'h':
if(relative) {
option.h += gameQuery.height;
}
gameQuery.height = option.h;
this.css("height","" + gameQuery.height + "px");
break;
}
}
.fn
// alias gameQuery to gQ for easier access
.gameQuery});
})(jQuery);
(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.