ig.module( 'impact.game' ) .requires( 'impact.impact', 'impact.entity', 'impact.collision-map', 'impact.background-map' ) .defines(function(){ "use strict"; ig.Game = ig.Class.extend({ clearColor: '#000000', gravity: 0, screen: {x: 0, y: 0}, _rscreen: {x: 0, y: 0}, entities: [], namedEntities: {}, collisionMap: ig.CollisionMap.staticNoCollision, backgroundMaps: [], backgroundAnims: {}, autoSort: false, sortBy: null, cellSize: 64, _deferredKill: [], _levelToLoad: null, _doSortEntities: false, staticInstantiate: function() { this.sortBy = this.sortBy || ig.Game.SORT.Z_INDEX; ig.game = this; return null; }, loadLevel: function( data ) { this.screen = {x: 0, y: 0}; // Entities this.entities = []; this.namedEntities = {}; for( var i = 0; i < data.entities.length; i++ ) { var ent = data.entities[i]; this.spawnEntity( ent.type, ent.x, ent.y, ent.settings ); } this.sortEntities(); // Map Layer this.collisionMap = ig.CollisionMap.staticNoCollision; this.backgroundMaps = []; for( var i = 0; i < data.layer.length; i++ ) { var ld = data.layer[i]; if( ld.name == 'collision' ) { this.collisionMap = new ig.CollisionMap(ld.tilesize, ld.data ); } else { var newMap = new ig.BackgroundMap(ld.tilesize, ld.data, ld.tilesetName); newMap.anims = this.backgroundAnims[ld.tilesetName] || {}; newMap.repeat = ld.repeat; newMap.distance = ld.distance; newMap.foreground = !!ld.foreground; newMap.preRender = !!ld.preRender; newMap.name = ld.name; this.backgroundMaps.push( newMap ); } } // Call post-init ready function on all entities for( var i = 0; i < this.entities.length; i++ ) { this.entities[i].ready(); } }, loadLevelDeferred: function( data ) { this._levelToLoad = data; }, getMapByName: function( name ) { if( name == 'collision' ) { return this.collisionMap; } for( var i = 0; i < this.backgroundMaps.length; i++ ) { if( this.backgroundMaps[i].name == name ) { return this.backgroundMaps[i]; } } return null; }, getEntityByName: function( name ) { return this.namedEntities[name]; }, getEntitiesByType: function( type ) { var entityClass = typeof(type) === 'string' ? ig.global[type] : type; var a = []; for( var i = 0; i < this.entities.length; i++ ) { var ent = this.entities[i]; if( ent instanceof entityClass && !ent._killed ) { a.push( ent ); } } return a; }, spawnEntity: function( type, x, y, settings ) { var entityClass = typeof(type) === 'string' ? ig.global[type] : type; if( !entityClass ) { throw("Can't spawn entity of type " + type); } var ent = new (entityClass)( x, y, settings || {} ); this.entities.push( ent ); if( ent.name ) { this.namedEntities[ent.name] = ent; } return ent; }, sortEntities: function() { this.entities.sort( this.sortBy ); }, sortEntitiesDeferred: function() { this._doSortEntities = true; }, removeEntity: function( ent ) { // Remove this entity from the named entities if( ent.name ) { delete this.namedEntities[ent.name]; } // We can not remove the entity from the entities[] array in the midst // of an update cycle, so remember all killed entities and remove // them later. // Also make sure this entity doesn't collide anymore and won't get // updated or checked ent._killed = true; ent.type = ig.Entity.TYPE.NONE; ent.checkAgainst = ig.Entity.TYPE.NONE; ent.collides = ig.Entity.COLLIDES.NEVER; this._deferredKill.push( ent ); }, run: function() { this.update(); this.draw(); }, update: function(){ // load new level? if( this._levelToLoad ) { this.loadLevel( this._levelToLoad ); this._levelToLoad = null; } // sort entities? if( this._doSortEntities || this.autoSort ) { this.sortEntities(); this._doSortEntities = false; } // update entities this.updateEntities(); this.checkEntities(); // remove all killed entities for( var i = 0; i < this._deferredKill.length; i++ ) { this.entities.erase( this._deferredKill[i] ); } this._deferredKill = []; // update background animations for( var tileset in this.backgroundAnims ) { var anims = this.backgroundAnims[tileset]; for( var a in anims ) { anims[a].update(); } } }, updateEntities: function() { for( var i = 0; i < this.entities.length; i++ ) { var ent = this.entities[i]; if( !ent._killed ) { ent.update(); } } }, draw: function(){ if( this.clearColor ) { ig.system.clear( this.clearColor ); } // This is a bit of a circle jerk. Entities reference game._rscreen // instead of game.screen when drawing themselfs in order to be // "synchronized" to the rounded(?) screen position this._rscreen.x = ig.system.getDrawPos(this.screen.x)/ig.system.scale; this._rscreen.y = ig.system.getDrawPos(this.screen.y)/ig.system.scale; var mapIndex; for( mapIndex = 0; mapIndex < this.backgroundMaps.length; mapIndex++ ) { var map = this.backgroundMaps[mapIndex]; if( map.foreground ) { // All foreground layers are drawn after the entities break; } map.setScreenPos( this.screen.x, this.screen.y ); map.draw(); } this.drawEntities(); for( mapIndex; mapIndex < this.backgroundMaps.length; mapIndex++ ) { var map = this.backgroundMaps[mapIndex]; map.setScreenPos( this.screen.x, this.screen.y ); map.draw(); } }, drawEntities: function() { for( var i = 0; i < this.entities.length; i++ ) { this.entities[i].draw(); } }, checkEntities: function() { // Insert all entities into a spatial hash and check them against any // other entity that already resides in the same cell. Entities that are // bigger than a single cell, are inserted into each one they intersect // with. // A list of entities, which the current one was already checked with, // is maintained for each entity. var hash = {}; for( var e = 0; e < this.entities.length; e++ ) { var entity = this.entities[e]; // Skip entities that don't check, don't get checked and don't collide if( entity.type == ig.Entity.TYPE.NONE && entity.checkAgainst == ig.Entity.TYPE.NONE && entity.collides == ig.Entity.COLLIDES.NEVER ) { continue; } var checked = {}, xmin = Math.floor( entity.pos.x/this.cellSize ), ymin = Math.floor( entity.pos.y/this.cellSize ), xmax = Math.floor( (entity.pos.x+entity.size.x)/this.cellSize ) + 1, ymax = Math.floor( (entity.pos.y+entity.size.y)/this.cellSize ) + 1; for( var x = xmin; x < xmax; x++ ) { for( var y = ymin; y < ymax; y++ ) { // Current cell is empty - create it and insert! if( !hash[x] ) { hash[x] = {}; hash[x][y] = [entity]; } else if( !hash[x][y] ) { hash[x][y] = [entity]; } // Check against each entity in this cell, then insert else { var cell = hash[x][y]; for( var c = 0; c < cell.length; c++ ) { // Intersects and wasn't already checkd? if( entity.touches(cell[c]) && !checked[cell[c].id] ) { checked[cell[c].id] = true; ig.Entity.checkPair( entity, cell[c] ); } } cell.push(entity); } } // end for y size } // end for x size } // end for entities } }); ig.Game.SORT = { Z_INDEX: function( a, b ){ return a.zIndex - b.zIndex; }, POS_X: function( a, b ){ return (a.pos.x+a.size.x) - (b.pos.x+b.size.x); }, POS_Y: function( a, b ){ return (a.pos.y+a.size.y) - (b.pos.y+b.size.y); } }; });