/* * gameQuery rev. 0.5.1 * * Copyright (c) 2008 Selim Arsever (gamequery.onaluf.org) * licensed under the MIT (MIT-LICENSE.txt) */ // this allow to used the convenient $ notation in a plugins (function($) { $.extend({ gameQuery: { /** * This is the Animation Object */ Animation: function (options) { // 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 frame to be displayed when playing the animation this.delta = options.delta; // The the distance in pixels between two frame this.rate = options.rate; // The rate at which the frame must be played in miliseconds this.type = options.type; // The type of the animation.This is bitwise OR of the properties. this.distance = options.distance; // The the distance in pixels between two animation this.offsetx = options.offsetx; // The x coordinate where the first sprite begin this.offsety = options.offsety; // The y coordinate where the first sprite begin //Whenever a new animation is created we add it to the ResourceManager animation list $.gameQuery.resourceManager.addAnimation(this); return true; }, // "constants" for the different type of an animation ANIMATION_VERTICAL: 1, // genertated by a verical offset of the background ANIMATION_HORIZONTAL: 2, // genertated by a horizontal offset of the background ANIMATION_ONCE: 4, // played only once (else looping indefinitly) ANIMATION_CALLBACK: 8, // A callack is exectued at the end of a cycle ANIMATION_MULTI: 16, // The image file contains many animations // "constants" for the different type of geometry for a sprite GEOMETRY_RECTANGLE: 1, GEOMETRY_DISC: 2, // basic values refreshRate: 30, /** * An object to manages the resources loading **/ resourceManager: { animations: [], // List of animation / images used in the game sounds: [], // List of sounds used in the game callbacks: [], // List of the functions called at each refresh running: false, // State of the game, /** * This function the covers things to load befor to start the game. **/ preload: function() { //Start loading the images for (var i = this.animations.length-1 ; i >= 0; 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 >= 0; i --){ this.sounds[i].load(); } $.gameQuery.resourceManager.waitForResources(); }, /** * This function the waits for all the resources called for in preload() to finish loading. **/ waitForResources: function() { var loadbarEnabled = ($.gameQuery.loadbar != undefined); if(loadbarEnabled){ $($.gameQuery.loadbar.id).width(0); var loadBarIncremant = $.gameQuery.loadbar.width / (this.animations.length + this.sounds.length); } //check the images var imageCount = 0; for(var i=0; i < this.animations.length; i++){ if(this.animations[i].domO.complete){ imageCount++; } } //check the sounds var soundCount = 0; for(var i=0; i < this.sounds.length; i++){ var temp = this.sounds[i].ready(); if(temp){ soundCount++; } } //update the loading bar if(loadbarEnabled){ $("#"+$.gameQuery.loadbar.id).width((imageCount+soundCount)*loadBarIncremant); if($.gameQuery.loadbar.callback){ $.gameQuery.loadbar.callback((imageCount+soundCount)/(this.animations.length + this.sounds.length)*100); } } if($.gameQuery.resourceManager.loadCallback){ var percent = (imageCount+soundCount)/(this.animations.length + this.sounds.length)*100; $.gameQuery.resourceManager.loadCallback(percent); } if(imageCount + soundCount < (this.animations.length + this.sounds.length)){ imgWait=setTimeout(function () { $.gameQuery.resourceManager.waitForResources(); }, 100); } else { // all the resources are loaded! // We can associate the animation's images to their coresponding sprites $.gameQuery.sceengraph.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_VERTICAL) { $(this).css("background-repeat", "repeat-x"); } else if(this.gameQuery.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) { $(this).css("background-repeat", "repeat-y"); } else { $(this).css("background-repeat", "no-repeat"); } } }); // And launch the refresh loop $.gameQuery.resourceManager.running = true; setInterval(function () { $.gameQuery.resourceManager.refresh(); },($.gameQuery.refreshRate)); if($.gameQuery.startCallback){ $.gameQuery.startCallback(); } //make the sceengraph visible $.gameQuery.sceengraph.css("visibility","visible"); } }, /** * This function refresh a unique sprite here 'this' represent a dom object **/ refreshSprite: function() { //Call this function on all the children: // is 'this' a sprite ? 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){ // does 'this' loops? if(gameQuery.animation.type & $.gameQuery.ANIMATION_ONCE){ if(gameQuery.currentFrame < gameQuery.animation.numberOfFrame-2){ gameQuery.currentFrame++; } else if(gameQuery.currentFrame == gameQuery.animation.numberOfFrame-2) { gameQuery.currentFrame++; // does 'this' has a callback ? if(gameQuery.animation.type & $.gameQuery.ANIMATION_CALLBACK){ if($.isFunction(gameQuery.callback)){ gameQuery.callback(this); } } } } else { gameQuery.currentFrame = (gameQuery.currentFrame+1)%gameQuery.animation.numberOfFrame; if(gameQuery.currentFrame == 0){ // does 'this' has a callback ? if(gameQuery.animation.type & $.gameQuery.ANIMATION_CALLBACK){ if($.isFunction(gameQuery.callback)){ gameQuery.callback(this); } } } } // update the background: if(gameQuery.animation.type & $.gameQuery.ANIMATION_VERTICAL){ 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 & $.gameQuery.ANIMATION_HORIZONTAL) { if(gameQuery.multi){ $(this).css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.animation.delta*gameQuery.currentFrame)+"px "+(-gameQuery.animation.offsety-gameQuery.multi)+"px"); } else { $(this).css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.animation.delta*gameQuery.currentFrame)+"px "+(-gameQuery.animation.offsety)+"px"); } } } gameQuery.idleCounter = (gameQuery.idleCounter+1)%gameQuery.animation.rate; } } return true; }, /** * This function refresh a unique tile-map here 'this' represent a dom object **/ refreshTilemap: function() { //Call this function on all the children: // is 'this' a sprite ? if(this.gameQuery != undefined){ var gameQuery = this.gameQuery; if($.isArray(gameQuery.frameTracker)){ for(var i=0; i= 0; i--){ if(this.callbacks[i].idleCounter == this.callbacks[i].rate-1){ var returnedValue = this.callbacks[i].fn(); if(typeof returnedValue == 'boolean'){ // if we have a boolean: 'true' means 'no more execution', 'false' means 'execute once more' if(returnedValue){ deadCallback.push(i); } } else if(typeof returnedValue == 'number') { // if we have a number it re-defines the time to the nex call this.callbacks[i].rate = Math.round(returnedValue/$.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); } }, addAnimation: function(animation) { if($.inArray(animation,this.animations)<0){ //normalize the animationRate: animation.rate = Math.round(animation.rate/$.gameQuery.refreshRate); if(animation.rate==0){ animation.rate = 1; } this.animations.push(animation); } }, addSound: function(sound){ if($.inArray(sound,this.sounds)<0){ this.sounds.push(sound); } }, registerCallback: function(fn, rate){ rate = Math.round(rate/$.gameQuery.refreshRate); if(rate==0){ rate = 1; } this.callbacks.push({fn: fn, rate: rate, idleCounter: 0}); } }, // This is a single place to update the underlying data of sprites/groups/tiles update: function(descriptor, transformation) { // Did we really recieve a descriptor or a jQuery object instead? if(!$.isPlainObject(descriptor)){ // Then we must get real descriptor if(descriptor.length > 0){ var gameQuery = descriptor[0].gameQuery; } else { var gameQuery = descriptor.gameQuery; } } else { var gameQuery = descriptor; } // If we couldn't find one we return if(!gameQuery) return; if(gameQuery.tileSet === true){ //then we have a tilemap! descriptor = $(descriptor); // find the tilemap offset relatif to the playground: var playgroundOffset = $.gameQuery.playground.offset(); var tileSetOffset = descriptor.offset(); tileSetOffset = {top: tileSetOffset.top - playgroundOffset.top, left: tileSetOffset.left - playgroundOffset.left}; // test what kind of transformation we have and react accordingly: // Update the descriptor for(property in transformation){ switch(property){ case "left": //Do we need to activate/desactive the first/last column var left = parseFloat(transformation.left); //Get the tileSet offset (relatif to the playground) var playgroundOffset = $.gameQuery.playground.offset(); var tileSetOffset = descriptor.parent().offset(); tileSetOffset = {top: tileSetOffset.top - playgroundOffset.top, left: tileSetOffset.left + left - playgroundOffset.left}; //actvates the visible tiles var firstColumn = Math.max(Math.min(Math.floor(-tileSetOffset.left/gameQuery.width), gameQuery.sizex),0); var lastColumn = Math.max(Math.min(Math.ceil(($.gameQuery.playground[0].width-tileSetOffset.left)/gameQuery.width), gameQuery.sizex),0); for(var i = gameQuery.firstRow; i < gameQuery.lastRow; i++){ // if old first col < new first col // deactivate the newly invisible tiles for(var j = gameQuery.firstColumn; j < firstColumn ; j++) { $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).removeClass("active"); } //and activate the newly visible tiles for(var j = gameQuery.lastColumn; j < lastColumn ; j++) { $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).addClass("active"); } // if old first col > new first col // deactivate the newly invisible tiles for(var j = lastColumn; j < gameQuery.lastColumn ; j++) { $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).removeClass("active"); } //activate the newly visible tiles for(var j = firstColumn; j < gameQuery.firstColumn ; j++) { $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).addClass("active"); } } gameQuery.firstColumn = firstColumn; gameQuery.lastColumn = lastColumn; break; case "top": //Do we need to activate/desactive the first/last row var top = parseFloat(transformation.top); //Get the tileSet offset (relatif to the playground) var playgroundOffset = $.gameQuery.playground.offset(); var tileSetOffset = descriptor.parent().offset(); tileSetOffset = {top: tileSetOffset.top + top - playgroundOffset.top, left: tileSetOffset.left - playgroundOffset.left}; //actvates the visible tiles var firstRow = Math.max(Math.min(Math.floor(-tileSetOffset.top/gameQuery.height), gameQuery.sizey), 0); var lastRow = Math.max(Math.min(Math.ceil(($.gameQuery.playground[0].height-tileSetOffset.top)/gameQuery.height), gameQuery.sizey), 0); for(var j = gameQuery.firstColumn; j < gameQuery.lastColumn ; j++) { // if old first row < new first row // deactivate the newly invisible tiles for(var i = gameQuery.firstRow; i < firstRow; i++){ $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).removeClass("active"); } //and activate the newly visible tiles for(var i = gameQuery.lastRow; i < lastRow; i++){ $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).addClass("active"); } // if old first row < new first row // deactivate the newly invisible tiles for(var i = lastRow; i < gameQuery.lastRow; i++){ $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).removeClass("active"); } //and activate the newly visible tiles for(var i = firstRow; i < gameQuery.firstRow; i++){ $("#tile_"+descriptor.attr("id")+"_"+i+"_"+j).addClass("active"); } } gameQuery.firstRow = firstRow; gameQuery.lastRow = lastRow; break; case "angle": //(in degree) //TODO break; case "factor": //TODO break; } } } else { var refreshBoundingCircle = $.gameQuery.playground && !$.gameQuery.playground.disableCollision; // Update the descriptor for(property in transformation){ switch(property){ case "left": gameQuery.posx = parseFloat(transformation.left); if(refreshBoundingCircle){ gameQuery.boundingCircle.x = gameQuery.posx+gameQuery.width/2; } break; case "top": gameQuery.posy = parseFloat(transformation.top); if(refreshBoundingCircle){ gameQuery.boundingCircle.y = gameQuery.posy+gameQuery.height/2; } break; case "width": gameQuery.width = parseFloat(transformation.width); break; case "height": gameQuery.height = parseFloat(transformation.height); break; case "angle": //(in degree) gameQuery.angle = parseFloat(transformation.angle); break; case "factor": gameQuery.factor = parseFloat(transformation.factor); if(refreshBoundingCircle){ gameQuery.boundingCircle.radius = gameQuery.factor*gameQuery.boundingCircle.originalRadius; } break; } } } }, // This is a utility function that returns the radius for a geometry proj: function (elem, angle) { switch (elem.geometry){ case $.gameQuery.GEOMETRY_RECTANGLE : var b = angle*Math.PI*2/360; var Rx = Math.abs(Math.cos(b)*elem.width/2*elem.factor)+Math.abs(Math.sin(b)*elem.height/2*elem.factor); var Ry = Math.abs(Math.cos(b)*elem.height/2*elem.factor)+Math.abs(Math.sin(b)*elem.width/2*elem.factor); return {x: Rx, y: Ry}; } }, // This is a utility function for collision of two object collide: function(elem1, offset1, elem2, offset2) { // test real collision (only for two rectangle...) if((elem1.geometry == $.gameQuery.GEOMETRY_RECTANGLE && elem2.geometry == $.gameQuery.GEOMETRY_RECTANGLE)){ var dx = offset2.x + elem2.boundingCircle.x - elem1.boundingCircle.x - offset1.x; var dy = offset2.y + elem2.boundingCircle.y - elem1.boundingCircle.y - offset1.y; var a = Math.atan(dy/dx); var Dx = Math.abs(Math.cos(a-elem1.angle*Math.PI*2/360)/Math.cos(a)*dx); var Dy = Math.abs(Math.sin(a-elem1.angle*Math.PI*2/360)/Math.sin(a)*dy); var R = $.gameQuery.proj(elem2, elem2.angle-elem1.angle); if((elem1.width/2*elem1.factor+R.x <= Dx) || (elem1.height/2*elem1.factor+R.y <= Dy)) { return false; } else { var Dx = Math.abs(Math.cos(a-elem2.angle*Math.PI*2/360)/Math.cos(a)*-dx); var Dy = Math.abs(Math.sin(a-elem2.angle*Math.PI*2/360)/Math.sin(a)*-dy); var R = $.gameQuery.proj(elem1, elem1.angle-elem2.angle); if((elem2.width/2*elem2.factor+R.x <= Dx) || (elem2.height/2*elem2.factor+R.y <= Dy)) { return false; } else { return true; } } } else { return false; } } // This function mute (or unmute) all the sounds. }, muteSound: function(muted){ for (var i = $.gameQuery.resourceManager.sounds.length-1 ; i >= 0; i --) { $.gameQuery.resourceManager.sounds[i].muted(muted); } }, playground: function() { return $.gameQuery.playground // This function define a callback that will be called upon during the // loading of the game's resources. The function will recieve as unique // parameter a number representing the progess percentage. }, loadCallback: function(callback){ $.gameQuery.resourceManager.loadCallback = callback; }}); $.fn.extend({ /** * Define the div to use for the display the game and initailize it. * This could be called on any node it doesn't matter. * The returned node is the playground node. * This IS a desrtuctive call **/ playground: function(options) { if(this.length == 1){ if(this[0] == document){ // Old usage check throw "Old playground usage, use $.playground() to retreive the playground and $('mydiv').playground(options) to set the div!"; } options = $.extend({ height: 320, width: 480, refreshRate: 30, position: "absolute", keyTracker: false, disableCollision: false }, options); //We save the playground node and set some variable for this node: $.gameQuery.playground = this; $.gameQuery.refreshRate = options.refreshRate; $.gameQuery.playground[0].height = options.height; $.gameQuery.playground[0].width = options.width; // We initialize the apearance of the div $.gameQuery.playground.css({ position: options.position, display: "block", overflow: "hidden", height: options.height+"px", width: options.width+"px" }) .append("