diff --git a/data/images/gfx64/button.png b/data/images/gfx64/button.png index df5c23d..5fbb734 100644 Binary files a/data/images/gfx64/button.png and b/data/images/gfx64/button.png differ diff --git a/index.html b/index.html index 4b6dd7e..2ded6e7 100644 --- a/index.html +++ b/index.html @@ -9,388 +9,29 @@ + + diff --git a/js/app/game-state.js b/js/app/game-state.js new file mode 100644 index 0000000..5b3ade9 --- /dev/null +++ b/js/app/game-state.js @@ -0,0 +1,366 @@ +var state = new Kiwi.State('state'); + + +state.preload = function() { + this.addJSON('level1', 'data/levels/level-01.json'); + this.addJSON('level2', 'data/levels/level-02.json'); + this.addJSON('level3', 'data/levels/level-03.json'); + this.addJSON('level4', 'data/levels/level-04.json'); + this.addJSON('level5', 'data/levels/level-05.json'); + this.addJSON('level6', 'data/levels/level-06.json'); + this.addJSON('level7', 'data/levels/level-07.json'); + this.addJSON('level8', 'data/levels/level-08.json'); + this.addJSON('level9', 'data/levels/level-09.json'); + this.addJSON('level10', 'data/levels/level-10.json'); + this.addJSON('level11', 'data/levels/level-11.json'); + this.addJSON('level12', 'data/levels/level-12.json'); + this.addSpriteSheet('base', './data/images/gfx64/tiles.png', 64, 64); + this.addSpriteSheet('character', './data/images/gfx64/marble_black.png', 80, 80 ); + this.addSpriteSheet('oneWay', './data/images/gfx64/st_oneway.png', 64, 64 ); + this.addSpriteSheet('finishMarker', './data/images/gfx64/finish_marker.png', 64, 64 ); + this.addSpriteSheet('button', './data/images/gfx64/button.png', 128, 64 ); + this.addSpriteSheet('teleport', './data/images/gfx64/st_spitter_idle.png', 64, 64); + this.addSpriteSheet('box', './data/images/gfx64/st_box_wood.png', 64, 64); + this.addSpriteSheet('switch', './data/images/gfx64/st_switch.png', 64, 64); + this.addSpriteSheet('laserBeam', './data/images/gfx64/it_laser.png', 64, 64); + this.addSpriteSheet('laser', './data/images/gfx64/st_laser.png', 64, 64); +} + +state.velocityX = 64; +state.velocityY = 64; + +state.create = function() { + var level = levels[this.game.levelIndex - 1]; + this.tilemap = new Kiwi.GameObjects.Tilemap.TileMap(this, 'level' + this.game.levelIndex.toString(), this.textures.base); + + this.character = new Kiwi.GameObjects.Sprite(this, this.textures.character, 0, 0); + this.character.initialX = 2*64; + this.character.initialY = 64; + this.character.initialVelocityX = 0; + this.character.initialVelocityY = this.velocityY; + // Hitbox is detecting collision in future step. + // This is little bit counter intuitive. + // You have to make collision box smaller at least one step. + this.character.box.hitbox = new Kiwi.Geom.Rectangle( 12, 12, 58, 58 ); + this.character.physics = this.character.components.add( new Kiwi.Components.ArcadePhysics( this.character, this.character.box ) ); + + this.character.animation.add('walking', [ 0, 1 ], 0.2, true); + this.character.animation.add('idle', [ 2, 3, 4, 5, 6, 5, 4, 3 ], 0.2, true); + this.character.animation.add('failed', [ 11, 10, 9, 8, 7, 8, 9, 10], 0.2, true); + + this.redirectorGroup = new Kiwi.Group( this ); + + this.finishMarker = new Kiwi.GameObjects.Sprite(this, this.textures.finishMarker, 6*64, 4*64); + this.finishMarker.animation.add('idle', [ 0, 1, 2, 3, 2, 1 ], 0.3, true); + this.finishMarker.animation.play('idle', true); + + // Ground layer + this.addChild(this.tilemap.layers[0]); + + // Load level specific data + level.create(this); + + // Fix character coordinates and speed + this.resetCharacter(); + + this.addChild(this.finishMarker); + + // Walls layer + this.addChild(this.tilemap.layers[1]); + + // Add action objects + this.addChild(this.redirectorGroup); + + this.addChild(this.character); + + // Sky layer + this.addChild(this.tilemap.layers[2]); + + // Create collision layer + for(var i = 21; i < this.tilemap.tileTypes.length; i++) { + this.tilemap.tileTypes[i].allowCollisions = Kiwi.Components.ArcadePhysics.ANY; + } + + this.keyboard = this.game.input.keyboard; + + this.leftKey = this.keyboard.addKey(Kiwi.Input.Keycodes.LEFT, true); + this.rightKey = this.keyboard.addKey(Kiwi.Input.Keycodes.RIGHT, true); + //Prevent the down key from scrolling the page + this.keyboard.addKey(Kiwi.Input.Keycodes.DOWN, true); + + this.stageState = 'init'; + + this.myButton = new Kiwi.GameObjects.Textfield( this, "Start", 6*64+16, 60, "#000", 32, 'normal', 'Impact' ); + this.myButtonSprite = new Kiwi.GameObjects.Sprite(this, this.textures.button, 6*64, 50); + this.myButtonSprite.input.onUp.add( this.buttonReleased, this ); + + this.character.input.onUp.add( this.buttonReleased, this ); +} + +state.buttonReleased = function(sprite) { + if (this.stageState == 'init') { + this.myButton.text = '...'; + this.activateScene(); + } else if (this.stageState == 'stop') { + this.resetStage(); + this.myButton.text = 'Start' + this.stageState = 'init'; + } else if (this.stageState == 'complete') { + game.levelStatus = 'complete'; + game.states.switchState('levelSelector'); + } +} + +state.resetCharacter = function () { + this.character.physics.velocity.x = 0; + this.character.physics.velocity.y = 0; + this.character.x = this.character.initialX - 8; + this.character.y = this.character.initialY - 8; +} + +/** + * Reset all objects that should be back in the original position. + * Dragable redirectors are left on original place. + */ +state.resetStage = function() { + this.resetCharacter(); + var redirectors = this.redirectorGroup.members; + for ( var i = 0; i < redirectors.length; i++ ) { + var redirector = redirectors[i]; + if (redirector.type == 'box') { + redirector.x = redirector.initialX; + redirector.y = redirector.initialY; + } else if (redirector.type == 'switch') { + redirector.cellIndex = redirector.initialCellIndex; + } else if (redirector.type == 'laserBeam') { + redirector.visible = redirector.initialVisible; + } else if (redirector.type == 'laser') { + redirector.animation.play(redirector.initialAnimation, true); + } + } +} + +state.startedDrag = function(sprite) { + sprite.formerX = sprite.x; + sprite.formerY = sprite.y; +} + +/** + * Reverse effect of drag operaion. Set original coordinates. + */ +state.resetDrag = function(sprite) { + sprite.x = sprite.formerX; + sprite.y = sprite.formerY; +} + +/** + * Get tile index from specified coordinates. + */ +state.getTileIndex = function(x, y) { + var tile = this.tilemap.layers[0].getTileFromXY(x / 64 , y / 64); + if (tile == null) { + return 0; + } + return tile.index; +} + +/** + * Perform adjustments of coordinates for drop operation. + * Make sure that object is dropped on valid place and check + * collision with other objects. In case of failure revert + * to former coordinates. + */ +state.stoppedDrag = function(sprite) { + if (sprite.x % 64 > 32) { + sprite.x += 64; + } + if (sprite.y % 64 > 32) { + sprite.y += 64; + } + sprite.x = sprite.x - sprite.x % 64; + sprite.y = sprite.y - sprite.y % 64; + + // Make sure that we drop tile only on valid ground + if (this.getTileIndex(sprite.x, sprite.y) == 0) { + this.resetDrag(sprite); + } else { + // Make sure that we are not dropping on another redirector object + var redirectors = this.redirectorGroup.members; + for ( var i = 0; i < redirectors.length; i++ ) { + var redirector = redirectors[i]; + + // Skip comparision of self + if (sprite === redirector) { + continue; + } + + if ((sprite.x == redirector.x) && (sprite.y == redirector.y)) { + this.resetDrag(sprite); + break; + } + } + } +} + +state.activateScene = function () { + this.character.physics.velocity.x = this.character.initialVelocityX; + this.character.physics.velocity.y = this.character.initialVelocityY; + this.stageState = 'running'; +} + +state.update = function () { + //Update all the gameobjects + Kiwi.State.prototype.update.call(this); + + //Update physics + this.checkCollision(); + + this.updateCharacterAnimation(); +} + +state.updateCharacterAnimation = function () { + + if(( this.character.physics.velocity.y != 0 ) || (this.character.physics.velocity.x != 0)) { + this.character.animation.play('walking', false); + } else { + if (this.stageState == 'stop') { + this.character.animation.play('failed', false); + } else { + this.character.animation.play('idle', false); + } + } +} + +state.turnOff = function(items) { + for (var i=0; i!=items.length; i++) { + var item = items[i]; + if (item.type == 'laserBeam') { + item.visible = false; + } else if (item.type == 'switch') { + item.cellIndex = 2; + } else { + item.animation.play('idle'); + } + } +} + +state.turnOn = function(items) { + for (var i=0; i!=items.length; i++) { + var item = items[i]; + if (item.type == 'laserBeam') { + item.visible = true; + } else if (item.type == 'switch') { + item.cellIndex = 0; + } else { + item.animation.play('burning'); + } + } +} + +/** + * Create effect based on type of redirector. + */ +state.applyRedirectorMechanics = function(tileX, tileY, redirector) { + var isRecalculationRequired = false; + var redirectorTileX = Math.round(redirector.x/64); + var redirectorTileY = Math.round(redirector.y/64); + + // Box has hit regions around. Compute based on vector of player + if (redirector.type == 'box') { + tileX+=this.character.physics.velocity.x/64; + tileY+=this.character.physics.velocity.y/64; + } + + // Check for hit + if ((tileX != redirectorTileX ) || (tileY != redirectorTileY)) { + return; + } + + if (redirector.type == 'vector') { + this.character.physics.velocity.x = redirector.affectVelocityX; + this.character.physics.velocity.y = redirector.affectVelocityY; + } else if (redirector.type == 'teleport') { + this.character.x = redirector.affectedX - 8; + this.character.y = redirector.affectedY - 8; + } else if (redirector.type == 'box') { + // Move tile when it is possible + if (this.getTileIndex(redirector.x + this.character.physics.velocity.x, redirector.y + this.character.physics.velocity.y) != 0) { + redirector.x += this.character.physics.velocity.x; + redirector.y += this.character.physics.velocity.y; + } + // Bounce + this.character.physics.velocity.x = -this.character.physics.velocity.x; + this.character.physics.velocity.y = -this.character.physics.velocity.y; + + // Bounce from box should relaunch direction calculation + // This is for the case when box lies close to redirector + isRecalculationRequired = true; + } else if (redirector.type == 'switch') { + // Flip the switch + if (redirector.cellIndex == 0) { + redirector.cellIndex = 2; + this.turnOff(redirector.linkedItems); + } else { + redirector.cellIndex = 0; + this.turnOn(redirector.linkedItems); + } + } else if (redirector.type == 'laserBeam') { + if (redirector.visible) { + this.character.physics.velocity.x = 0; + this.character.physics.velocity.y = 0; + } + } + + // Correction of coordinates + this.character.y = Math.round(this.character.y); + this.character.x = Math.round(this.character.x); + return isRecalculationRequired; +} + +/** + * Resolve collisions between the character and the first layer. + */ +state.checkCollision = function () { + this.tilemap.layers[1].physics.overlapsTiles( this.character, true ); + + if ((this.character.physics.velocity.x == 0) && (this.character.physics.velocity.y == 0)) { + if (this.stageState == 'running') { + this.stageState = 'stop'; + this.myButton.text = 'Restart' + } + return; + } + + // Make collision detection only when ball is fully on the same tile + var positionX = Math.round(this.character.x) + 8; + var positionY = Math.round(this.character.y) + 8; + if ((Math.round(positionX) % 64 != 0) || (Math.round(positionY)% 64 != 0)) { + return; + } + + var isRecalculationRequired = false; + var redirectors = this.redirectorGroup.members; + for ( var i = 0; i < redirectors.length; i++ ) { + if (this.applyRedirectorMechanics(Math.round(positionX/64), Math.round(positionY/64), redirectors[i])) { + isRecalculationRequired = true; + } + } + + // Make the second round of calculation in case of hit of box + if (isRecalculationRequired) { + for ( var i = 0; i < redirectors.length; i++ ) { + this.applyRedirectorMechanics(Math.round(positionX/64), Math.round(positionY/64), redirectors[i]); + } + } + + if (((Math.round(positionX/64) == Math.round(this.finishMarker.x/64) )) && (Math.round(positionY/64) == Math.round(this.finishMarker.y/64))) { + this.stageState = 'complete'; + this.myButton.text = 'Next->' + this.addChild( this.myButtonSprite ); + this.addChild( this.myButton ); + + this.character.physics.velocity.x = 0; + this.character.physics.velocity.y = 0; + } + +} diff --git a/js/app/level-selector.js b/js/app/level-selector.js index bd2cc4d..1d6ca67 100644 --- a/js/app/level-selector.js +++ b/js/app/level-selector.js @@ -1,23 +1,55 @@ var levelSelectorState = new Kiwi.State('levelSelector'); +/** + * Save status of locks. + */ +levelSelectorState.save = function() { + this.game.saveManager.add('levelStatus', this.levelStatus); + this.game.saveManager.save(); +} + levelSelectorState.preload = function() { this.addSpriteSheet('button', './data/images/gfx64/button.png', 128, 64 ); + + if (this.game.saveManager.exists('levelStatus')) { + this.levelStatus = this.game.saveManager.getData('levelStatus'); + } else { + this.levelStatus = ['unlocked', 'locked', 'locked', 'locked', 'locked', 'locked', 'locked', 'locked', 'locked', + 'locked', 'locked', 'locked' + ]; + this.save(); + } } levelSelectorState.create = function() { var counterX = 0; var counterY = 0; + + // Unlock next level when previous was completed + if (game.levelStatus == 'complete') { + if (game.levelIndex < 12) { + this.levelStatus[game.levelIndex] = 'unlocked'; + game.levelStatus = 'none'; + this.save(); + } + } + for (var i=1; i 4) { + if (counterX > 3) { counterX = 0; counterY++; } diff --git a/js/save-manager-1.0.2/save-manager.js b/js/save-manager-1.0.2/save-manager.js new file mode 100644 index 0000000..b1ef8c3 --- /dev/null +++ b/js/save-manager-1.0.2/save-manager.js @@ -0,0 +1 @@ +Kiwi.Plugins.SaveGame={name:"SaveGame",version:"1.0.2",minimumKiwiVersion:"1.0.0"};Kiwi.PluginManager.register(Kiwi.Plugins.SaveGame);Kiwi.Plugins.SaveGame.create=function(e){var t=new Kiwi.Plugins.SaveGame.SaveManager(e);e.saveManager=t;return e.saveManager};Kiwi.Plugins.SaveGame.SaveManager=function(e){this.game=e};Kiwi.Plugins.SaveGame.SaveManager.prototype.objType=function(){return"SaveManager"};Kiwi.Plugins.SaveGame.SaveManager.prototype.boot=function(){this.localStorage=new Kiwi.Plugins.SaveGame.LocalStorage(this.game);this.current=this.localStorage};Kiwi.Plugins.SaveGame.SaveManager.prototype.switchCurrent=function(e){if(typeof e==="undefined")return;switch(e){case Kiwi.Plugins.SaveGame.SaveManager.LOCAL_STORAGE:this.current=this.localStorage;break}};Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"add",{get:function(){return this.current.add.bind(this.current)},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"edit",{get:function(){return this.current.edit.bind(this.current)},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"remove",{get:function(){return this.current.remove.bind(this.current)},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"getData",{get:function(){return this.current.getData.bind(this.current)},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"save",{get:function(){return this.current.save.bind(this.current)},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.SaveManager.prototype,"exists",{get:function(){return this.current.exists.bind(this.current)},enumerable:true,configurable:true});Kiwi.Plugins.SaveGame.SaveManager.LOCAL_STORAGE=1;Kiwi.Plugins.SaveGame.LocalStorage=function(e){this.game=e;this._data={};this._supported=Kiwi.DEVICE.localStorage;if(this.supported)this._retrieveData()};Object.defineProperty(Kiwi.Plugins.SaveGame.LocalStorage.prototype,"data",{get:function(){return this._data},enumerable:true,configurable:true});Object.defineProperty(Kiwi.Plugins.SaveGame.LocalStorage.prototype,"supported",{get:function(){return this._supported},enumerable:true,configurable:true});Kiwi.Plugins.SaveGame.LocalStorage.prototype.objType=function(){return"LocalStorage"};Kiwi.Plugins.SaveGame.LocalStorage.prototype._retrieveData=function(){if(localStorage.getItem(this.game.stage.name)!==null){var e=localStorage.getItem(this.game.stage.name);this._data=JSON.parse(e)}else{this._create()}};Kiwi.Plugins.SaveGame.LocalStorage.prototype._create=function(){try{this._data={name:this.game.stage.name};localStorage.setItem(this.game.stage.name,JSON.stringify(this._data))}catch(e){console.log("Can not use localstorage due to memory limitations.");this._supported=false}};Kiwi.Plugins.SaveGame.LocalStorage.prototype.add=function(e,t,n){if(typeof n=="undefined")n=false;if(this.supported===true){this._data[e]=t;if(n===true){return this.save()}else{return true}}return false};Kiwi.Plugins.SaveGame.LocalStorage.prototype.edit=function(e,t,n){if(typeof n=="undefined")n=false;if(this.supported===true){if(this._data[e]!==null){this._data[e]=t;if(n===true){this.save()}else{return true}}}return false};Kiwi.Plugins.SaveGame.LocalStorage.prototype.getData=function(e){if(this.supported===true){if(typeof this._data[e]!=="undefined"){return this._data[e]}}return null};Kiwi.Plugins.SaveGame.LocalStorage.prototype.exists=function(e){return this._data[e]!==undefined};Kiwi.Plugins.SaveGame.LocalStorage.prototype.remove=function(e,t){if(typeof t=="undefined")t=false;if(this.supported===true){if(this._data[e]!==null){delete this._data[e];if(t===true){this.save()}else{return true}}}};Kiwi.Plugins.SaveGame.LocalStorage.prototype.clear=function(e){if(typeof e=="undefined")e=false;if(this.supported===true){this._data={};if(e===true)this.save()}};Kiwi.Plugins.SaveGame.LocalStorage.prototype.save=function(){try{localStorage.setItem([this.game.stage.name],JSON.stringify(this._data));return true}catch(e){console.log("Localstorage is full. Could not update.");return false}};Kiwi.Plugins.SaveGame.LocalStorage.prototype.load=function(){if(this.supported===true){this._data=JSON.parse(localStorage.getItem(this.game.stage.name));return true}return false} \ No newline at end of file