1 /** 2 * The Render Engine 3 * Level 4 * 5 * @fileoverview A class for working with loaded levels. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * @author: $Author: bfattori $ 9 * @version: $Revision: 1555 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 // The class this file defines and its required classes 34 R.Engine.define({ 35 "class":"R.resources.types.Level", 36 "requires":[ 37 "R.engine.BaseObject", 38 "R.resources.loaders.SpriteLoader", 39 "R.resources.loaders.TileLoader", 40 "R.resources.types.Sound", 41 "R.resources.types.TileMap" 42 ] 43 }); 44 45 /** 46 * @class Creates an instance of a Level object. 47 * 48 * @constructor 49 * @param name {String} The name of the object 50 * @param levelResource {Object} The level resource loaded by the LevelLoader 51 * @extends R.engine.BaseObject 52 */ 53 R.resources.types.Level = function () { 54 return R.engine.BaseObject.extend(/** @scope R.resources.types.Level.prototype */{ 55 56 editing:false, 57 actors:null, 58 fixtures:null, 59 tilemaps:null, 60 backgroundMusic:null, 61 width:0, 62 height:0, 63 renderContext:null, 64 notAdded:null, 65 version:0, 66 resourceLoaders:null, 67 68 /** @private */ 69 constructor:function (name, width, height) { 70 this.base(name); 71 this.actors = R.struct.Container.create("Actors"); 72 this.fixtures = R.struct.Container.create("Fixtures"); 73 this.tilemaps = R.struct.HashContainer.create("Tilemaps"); 74 this.backgroundMusic = null; 75 this.width = width; 76 this.height = height; 77 this.renderContext = null; 78 this.notAdded = []; 79 this.version = 0; 80 this.resourceLoaders = { 81 sprite:[R.resources.loaders.SpriteLoader.create("LevelSpriteLoader")], 82 tile:[R.resources.loaders.TileLoader.create("LevelTileLoader")], 83 sound:[] 84 }; 85 86 // Add the three tile maps 87 this.tilemaps.add("background", R.resources.types.TileMap.create("background", width, height)); 88 this.tilemaps.add("playfield", R.resources.types.TileMap.create("playfield", width, height)); 89 this.tilemaps.add("foreground", R.resources.types.TileMap.create("foreground", width, height)); 90 91 // Set the z-index for each tile map 92 this.getTileMap("background").setZIndex(0); 93 this.getTileMap("playfield").setZIndex(1); 94 this.getTileMap("foreground").setZIndex(2); 95 }, 96 97 destroy:function () { 98 if (this.renderContext) { 99 // Remove actors and tile maps from the render context 100 var itr; 101 for (itr = this.actors.iterator(); itr.hasNext();) { 102 this.renderContext.remove(itr.next()); 103 } 104 105 for (itr = this.tilemaps.iterator(); itr.hasNext();) { 106 this.renderContext.remove(itr.next()); 107 } 108 } 109 110 // Clean up and destroy the actors, fixtures, and tile maps 111 this.actors.cleanUp(); 112 this.actors.destroy(); 113 this.fixtures.cleanUp(); 114 this.fixtures.destroy(); 115 this.tilemaps.cleanUp(); 116 this.tilemaps.destroy(); 117 118 // If there's background music, get rid of that too 119 if (this.backgroundMusic) { 120 this.backgroundMusic.destroy(); 121 } 122 123 this.base(); 124 }, 125 126 /** 127 * Release the level back into the pool for reuse 128 */ 129 release:function () { 130 this.base(); 131 this.actors = null; 132 this.fixtures = null; 133 this.tilemaps = null; 134 this.backgroundMusic = null; 135 this.renderContext = null; 136 this.width = 0; 137 this.height = 0; 138 this.version = 0; 139 }, 140 141 /** 142 * Add a new resource loader to the set of resource loaders that the 143 * level has access to. By default, there is already a sprite and 144 * tile loader when a level object is created. 145 * 146 * @param resourceLoader {R.resources.loaders.AbstractResourceLoader} The resource loader to add 147 */ 148 addResourceLoader:function (resourceLoader) { 149 var loaderCache; 150 if (resourceLoader instanceof R.resources.loaders.TileLoader) { 151 loaderCache = this.resourceLoaders.tile; 152 } else if (resourceLoader instanceof R.resources.loaders.SpriteLoader) { 153 loaderCache = this.resourceLoaders.sprite; 154 } else { 155 loaderCache = this.resourceLoaders.sound; 156 } 157 loaderCache.push(resourceLoader); 158 }, 159 160 /** 161 * Set a version number for the level. 162 * @param version {Number} A version number 163 */ 164 setVersion:function (version) { 165 this.version = version; 166 }, 167 168 /** 169 * Get the version number associated with the level 170 * @return {Number} 171 */ 172 getVersion:function () { 173 return this.version; 174 }, 175 176 /** 177 * Associate the level with its render context so the tile maps can 178 * be rendered properly. 179 * @param renderContext {R.rendercontexts.AbstractRenderContext} 180 */ 181 setRenderContext:function (renderContext) { 182 if (!this.renderContext) { 183 this.renderContext = renderContext; 184 renderContext.add(this.getTileMap("background")); 185 renderContext.add(this.getTileMap("playfield")); 186 renderContext.add(this.getTileMap("foreground")); 187 188 while (this.notAdded.length > 0) { 189 // Add objects which aren't a part of the render context yet 190 this.renderContext.add(this.notAdded.shift()); 191 } 192 } 193 }, 194 195 /** 196 * Get the width of the level, in tiles. 197 * @return {Number} The width of the level in tiles 198 */ 199 getWidth:function () { 200 return this.width; 201 }, 202 203 /** 204 * Get the height of the level, in tiles. 205 * @return {Number} The height of the level in tiles 206 */ 207 getHeight:function () { 208 return this.height; 209 }, 210 211 addActor:function (actor) { 212 this.actors.add(actor); 213 actor.setTileMap(this.getTileMap("playfield")); 214 215 if (this.renderContext) { 216 this.renderContext.add(actor); 217 } else { 218 this.notAdded.push(actor); 219 } 220 }, 221 222 removeActor:function (actor) { 223 this.actors.remove(actor); 224 }, 225 226 getActors:function () { 227 return this.actors; 228 }, 229 230 addFixture:function (fixture) { 231 this.fixtures.add(fixture); 232 }, 233 234 removeFixture:function (fixture) { 235 this.fixtures.remove(fixture); 236 }, 237 238 getFixtures:function (type) { 239 if (!type) { 240 return this.fixtures; 241 } else { 242 return this.fixtures.filter(function (fixture) { 243 return (fixture.getType() == type); 244 }); 245 } 246 }, 247 248 getTileMap:function (name) { 249 return this.tilemaps.get(name); 250 }, 251 252 setBackgroundMusic:function (sound) { 253 this.backgroundMusic = sound; 254 }, 255 256 /** 257 * Set the editing mode of the level, used by the LevelEditor 258 * @private 259 */ 260 setEditing:function (state) { 261 this.editing = state; 262 } 263 264 }, /** @scope R.resources.types.Level.prototype */ { 265 /** 266 * Gets the class name of this object. 267 * @return {String} The string "R.resources.types.Level" 268 */ 269 getClassName:function () { 270 return "R.resources.types.Level"; 271 }, 272 273 /** 274 * Generate an object which represents the level as a complete 275 * entity, including resource locations. 276 * @param level {R.resources.types.Level} 277 * @return {Object} 278 */ 279 serialize:function (level) { 280 function getCanonicalName(obj) { 281 return obj.getSpriteResource().resourceName + ":" + obj.getName(); 282 } 283 284 var lvl = { 285 name:level.getName(), 286 version:level.getVersion(), 287 width:level.getWidth(), 288 height:level.getHeight(), 289 resourceURLs:{ 290 sprites:[], 291 tiles:[], 292 sounds:[] 293 }, 294 actors:[], 295 fixtures:[], 296 tilemaps:{ 297 background:null, 298 playfield:null, 299 foreground:null 300 } 301 }; 302 303 // Get all of the resource URLs 304 var resourceName, resourceURL, obj, t, itr; 305 306 // SPRITES & ACTORS 307 for (itr = level.getActors().iterator(); itr.hasNext();) { 308 obj = itr.next(); 309 resourceName = obj.getSprite().getSpriteResource().resourceName; 310 resourceURL = obj.getSprite().getSpriteLoader().getPathUrl(resourceName); 311 if (R.engine.Support.filter(lvl.resourceURLs.sprites,function (e) { 312 return (e && e[resourceName]); 313 }).length == 0) { 314 var o = {}; 315 o[resourceName] = resourceURL; 316 lvl.resourceURLs.sprites.push(o); 317 } 318 319 // Do the actors at the same time 320 lvl.actors.push(R.objects.SpriteActor.serialize(obj)); 321 } 322 itr.destroy(); 323 324 // FIXTURES 325 for (itr = level.getFixtures().iterator(); itr.hasNext();) { 326 lvl.fixtures.push(R.objects.Object2D.serialize(itr.next())); 327 } 328 329 // TILES & TILEMAPS 330 $.each(["background", "playfield", "foreground"], function (i, e) { 331 var tile; 332 obj = level.getTileMap(e); 333 for (t = 0; t < obj.getTileMap().length; t++) { 334 tile = obj.getTileMap()[t]; 335 if (tile) { 336 resourceName = tile.getTileResource().resourceName; 337 resourceURL = tile.getTileLoader().getPathUrl(resourceName); 338 if (R.engine.Support.filter(lvl.resourceURLs.tiles,function (e) { 339 return (e && e[resourceName]); 340 }).length == 0) { 341 var o = {}; 342 o[resourceName] = resourceURL; 343 lvl.resourceURLs.tiles.push(o); 344 } 345 } 346 } 347 348 // Do the tile map at the same time 349 lvl.tilemaps[e] = R.resources.types.TileMap.serialize(obj); 350 }); 351 352 // SOUNDS 353 // ... 354 355 return lvl; 356 }, 357 358 deserialize:function (obj) { 359 // Create the level 360 var level = R.resources.types.Level.create(obj.name, obj.width, obj.height); 361 362 // Pull together all of the resources 363 var loader; 364 for (var resType in obj.resourceURLs) { 365 if (resType === "sprites") { 366 loader = level.resourceLoaders.sprite[0]; 367 } else if (resType === "tiles") { 368 loader = level.resourceLoaders.tile[0]; 369 } else { 370 loader = level.resourceLoaders.sound[0]; 371 } 372 for (var res in obj.resourceURLs[resType]) { 373 for (var rName in obj.resourceURLs[resType][res]) { 374 loader.load(rName, R.Engine.getGame().getFilePath(obj.resourceURLs[resType][res][rName])); 375 } 376 } 377 } 378 379 // Now that we've started the resource loading, we need to wait until 380 // all resources are loaded before we can finish loading the level 381 R.lang.Timeout.create("lvlResourceWait", 250, function () { 382 if (level.resourceLoaders.sprite[0].isReady() && 383 level.resourceLoaders.tile[0].isReady()) { 384 this.destroy(); 385 R.resources.types.Level.finishDeserialize(level, obj); 386 } else { 387 this.restart(); 388 } 389 }); 390 391 return level; 392 }, 393 394 /** 395 * @private 396 */ 397 finishDeserialize:function (level, obj) { 398 // Deserialize the tile maps 399 for (var tilemap in obj.tilemaps) { 400 R.resources.types.TileMap.deserialize(obj.tilemaps[tilemap], level.resourceLoaders.tile, 401 level.getTileMap(tilemap)); 402 } 403 404 // Deserialize the actors 405 for (var actor in obj.actors) { 406 var spriteActor = R.objects.SpriteActor.deserialize(obj.actors[actor], level.resourceLoaders.sprite); 407 level.addActor(spriteActor); 408 } 409 410 // Deserialize the fixtures 411 412 413 // Done 414 level.triggerEvent("loaded"); 415 } 416 }); 417 418 };