├── .vscode └── settings.json ├── favicon.ico ├── html-2d.PNG ├── resources ├── data │ ├── images │ │ ├── bat.png │ │ ├── canon.png │ │ ├── clock.png │ │ ├── coin.png │ │ ├── door.png │ │ ├── heart.png │ │ ├── key.png │ │ ├── snake.png │ │ ├── spikes.png │ │ ├── spring.png │ │ ├── character.png │ │ ├── platform.png │ │ └── skeleton.png │ ├── music │ │ └── music3.mp3 │ ├── sounds │ │ ├── coin.mp3 │ │ ├── heart.mp3 │ │ ├── jump.mp3 │ │ ├── key.mp3 │ │ ├── spring.mp3 │ │ └── explosion.mp3 │ ├── maps │ │ ├── tilesheet.png │ │ ├── tilesheet.json │ │ └── level1.json │ └── parallax │ │ ├── layer_01_1920 x 1080.png │ │ ├── layer_02_1920 x 1080.png │ │ ├── layer_03_1920 x 1080.png │ │ ├── layer_04_1920 x 1080.png │ │ ├── layer_05_1920 x 1080.png │ │ ├── layer_06_1920 x 1080.png │ │ ├── layer_07_1920 x 1080.png │ │ ├── layer_08_1920 x 1080.png │ │ └── layer_07_1920 x 1080_1.png ├── css │ └── game.css └── js │ ├── game │ ├── events.js │ ├── limit.js │ ├── door.js │ ├── stairs.js │ ├── resources.js │ ├── items.js │ ├── platforms.js │ ├── player.js │ ├── enemies.js │ └── demo-game.js │ ├── core │ ├── sound.js │ ├── timer.js │ ├── particle.js │ ├── game.js │ ├── text.js │ ├── background.js │ ├── animation.js │ ├── utils.js │ ├── vec2.js │ ├── camera.js │ ├── entity.js │ ├── map.js │ ├── joypad.js │ └── world.js │ └── includes.js ├── README.md ├── index.html └── LICENSE /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5502 3 | } -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/favicon.ico -------------------------------------------------------------------------------- /html-2d.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/html-2d.PNG -------------------------------------------------------------------------------- /resources/data/images/bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/bat.png -------------------------------------------------------------------------------- /resources/data/images/canon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/canon.png -------------------------------------------------------------------------------- /resources/data/images/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/clock.png -------------------------------------------------------------------------------- /resources/data/images/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/coin.png -------------------------------------------------------------------------------- /resources/data/images/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/door.png -------------------------------------------------------------------------------- /resources/data/images/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/heart.png -------------------------------------------------------------------------------- /resources/data/images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/key.png -------------------------------------------------------------------------------- /resources/data/images/snake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/snake.png -------------------------------------------------------------------------------- /resources/data/music/music3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/music/music3.mp3 -------------------------------------------------------------------------------- /resources/data/sounds/coin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/coin.mp3 -------------------------------------------------------------------------------- /resources/data/sounds/heart.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/heart.mp3 -------------------------------------------------------------------------------- /resources/data/sounds/jump.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/jump.mp3 -------------------------------------------------------------------------------- /resources/data/sounds/key.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/key.mp3 -------------------------------------------------------------------------------- /resources/data/images/spikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/spikes.png -------------------------------------------------------------------------------- /resources/data/images/spring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/spring.png -------------------------------------------------------------------------------- /resources/data/maps/tilesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/maps/tilesheet.png -------------------------------------------------------------------------------- /resources/data/sounds/spring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/spring.mp3 -------------------------------------------------------------------------------- /resources/data/images/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/character.png -------------------------------------------------------------------------------- /resources/data/images/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/platform.png -------------------------------------------------------------------------------- /resources/data/images/skeleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/images/skeleton.png -------------------------------------------------------------------------------- /resources/data/sounds/explosion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/sounds/explosion.mp3 -------------------------------------------------------------------------------- /resources/data/parallax/layer_01_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_01_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_02_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_02_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_03_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_03_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_04_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_04_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_05_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_05_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_06_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_06_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_07_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_07_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_08_1920 x 1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_08_1920 x 1080.png -------------------------------------------------------------------------------- /resources/data/parallax/layer_07_1920 x 1080_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiss/html-2d/HEAD/resources/data/parallax/layer_07_1920 x 1080_1.png -------------------------------------------------------------------------------- /resources/css/game.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | #game { 10 | width: 100%; 11 | height: 100%; 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html-2d 2 | Little engine for 2D HTML games written in Javascript. 3 | 4 | [Live Demo](https://sergiss.github.io/html-2d/) 5 | 6 | ![HTML 2D Game Engine](https://raw.githubusercontent.com/sergiss/html-2d/master/html-2d.PNG) 7 | 8 | https://sergiosoriano.com/ 9 | -------------------------------------------------------------------------------- /resources/data/maps/tilesheet.json: -------------------------------------------------------------------------------- 1 | { "columns":8, 2 | "image":"tilesheet.png", 3 | "imageheight":160, 4 | "imagewidth":160, 5 | "margin":2, 6 | "name":"tilesheet", 7 | "spacing":4, 8 | "tilecount":64, 9 | "tiledversion":"1.4.3", 10 | "tileheight":16, 11 | "tilewidth":16, 12 | "type":"tileset", 13 | "version":1.4 14 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Game Demo | www.sergiosoriano.com 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Sergio S. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /resources/js/game/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function End() { 31 | Entity.call(this); 32 | 33 | this.setMass(0); 34 | 35 | this.collisionGroup = EVENT_GROUP; 36 | this.collisionMask = PLAYER_GROUP; 37 | 38 | this.onCollision = function(pair) { 39 | let entity = pair.b; 40 | entity.gameOver = true; // TODO : demo end 41 | } 42 | } -------------------------------------------------------------------------------- /resources/js/game/limit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Limit() { 31 | Entity.call(this); 32 | 33 | this.setMass(0); 34 | 35 | this.collisionGroup = LIMIT_GROUP; 36 | this.collisionMask = ENEMY_GROUP | ITEM_GROUP | PLATFORM_GROUP | LIMIT_GROUP; 37 | 38 | this.onCollision = function(pair) { 39 | let entity = pair.b; 40 | entity.direction = pair.normal.x < 0 || pair.normal.y < 0; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /resources/js/core/sound.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | class Sound { 31 | constructor(src) { 32 | this.sound = document.createElement("audio"); 33 | this.sound.src = src; 34 | this.sound.setAttribute("preload", "auto"); 35 | this.sound.setAttribute("controls", "none"); 36 | this.sound.style.display = "none"; 37 | document.body.appendChild(this.sound); 38 | } 39 | 40 | play() { 41 | this.sound.play(); 42 | } 43 | stop() { 44 | this.sound.pause(); 45 | this.sound.currentTime = 0; 46 | } 47 | isPlaying() { 48 | return !this.sound.paused; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /resources/js/core/timer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Timer(listener) { 31 | 32 | this.listener = listener; 33 | 34 | this.setTimeout = function(timeout) { 35 | this.time = 0; 36 | this.timeout = timeout == undefined ? 0 : timeout; 37 | } 38 | 39 | this.restart = function() { 40 | this.setTimeout(this.timeout); 41 | } 42 | 43 | this.step = function(dt) { 44 | this.time += dt; 45 | let diff = this.time - this.timeout; 46 | if (diff > this.timeout) { 47 | this.time = diff; 48 | this.listener.onTimeout(); 49 | } 50 | } 51 | 52 | 53 | } -------------------------------------------------------------------------------- /resources/js/core/particle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Particle(color) { 31 | Entity.call(this); 32 | 33 | this.static = false; 34 | this.hh = 0.5; 35 | this.hw = 0.5; 36 | 37 | this.setMass(1); 38 | 39 | this.time = 0; 40 | 41 | this.collisionGroup = -1; 42 | this.collisionMask = -1; 43 | 44 | this.render = function(camera) { 45 | if (this.isVisible(camera)) { 46 | camera.context.fillStyle = color; 47 | camera.context.fillRect(this.position.x - this.hw, this.position.y - this.hh, this.hw * 2, this.hh * 2); 48 | } 49 | } 50 | 51 | this.update = function(dt) { 52 | 53 | // if (this.position.y > 350) this.position.y = 0; 54 | this.time += dt; 55 | if (this.time > 2) { 56 | this.world.remove(this); 57 | } 58 | 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /resources/js/includes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | var includeJs = function(src) { 31 | let script = document.createElement("script"); 32 | script.src = src; 33 | script.type = "text/javascript"; 34 | script.async = true; 35 | document.head.appendChild(script); 36 | } 37 | 38 | includeJs("./resources/js/core/sound.js"); 39 | includeJs("./resources/js/core/vec2.js"); 40 | includeJs("./resources/js/core/camera.js"); 41 | includeJs("./resources/js/core/map.js"); 42 | includeJs("./resources/js/core/world.js"); 43 | includeJs("./resources/js/core/background.js"); 44 | includeJs("./resources/js/core/game.js"); 45 | includeJs("./resources/js/core/entity.js"); 46 | includeJs("./resources/js/core/animation.js"); 47 | includeJs("./resources/js/core/particle.js"); 48 | includeJs("./resources/js/core/text.js"); 49 | includeJs("./resources/js/core/utils.js"); 50 | includeJs("./resources/js/core/timer.js"); 51 | includeJs("./resources/js/core/joypad.js"); 52 | 53 | includeJs("./resources/js/game/player.js"); 54 | includeJs("./resources/js/game/enemies.js"); 55 | includeJs("./resources/js/game/platforms.js"); 56 | includeJs("./resources/js/game/stairs.js"); 57 | includeJs("./resources/js/game/limit.js"); 58 | includeJs("./resources/js/game/events.js"); 59 | includeJs("./resources/js/game/door.js"); 60 | includeJs("./resources/js/game/items.js"); -------------------------------------------------------------------------------- /resources/js/game/door.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Door() { 31 | Entity.call(this); 32 | 33 | this.open = false; 34 | 35 | this.keyId = ""; 36 | 37 | this.setMass(0); 38 | 39 | this.collisionGroup = PLATFORM_GROUP; 40 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP | ITEM_GROUP | PLATFORM_GROUP | BULLET_GROUP; 41 | 42 | this.render = function(camera) { 43 | if (!this.open && this.isVisible(camera)) { 44 | camera.context.drawImage(doorImg, this.position.x - this.hw, this.position.y - this.hh, this.hw * 2, this.hh * 2); 45 | } 46 | } 47 | 48 | this.onCollision = function(pair) { 49 | if (!this.open && pair.b.inventory) { 50 | // test if entity has the key 51 | let key = removeKey(pair.b, this.keyId); 52 | this.open = key != undefined; 53 | } 54 | if (!this.open) { 55 | this.collisionMask |= BULLET_GROUP; 56 | this.world.solveCollision(pair); 57 | } else { 58 | this.collisionMask &= ~BULLET_GROUP; 59 | } 60 | } 61 | 62 | } 63 | 64 | function removeKey(e, id) { 65 | for (let i = 0; i < e.inventory.length; ++i) { 66 | let obj = e.inventory[i]; 67 | 68 | if (obj instanceof Key) { 69 | if (obj.id == id) { 70 | e.inventory.splice(i, 1); 71 | return obj; 72 | } 73 | } 74 | } 75 | return null; 76 | } -------------------------------------------------------------------------------- /resources/js/core/game.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | class Game { 31 | 32 | constructor(listener) { 33 | this.listener = listener; // game listener 34 | this.sTime = new Date().getTime(); // start time 35 | this.eTime = this.sTime; // end time 36 | 37 | if (listener.onKeyDown !== "undefined") { 38 | window.addEventListener("keydown", (e) => { 39 | e.preventDefault(); 40 | listener.onKeyDown(e); 41 | }); 42 | } 43 | 44 | if (listener.onKeyUp !== "undefined") { 45 | window.addEventListener("keyup", (e) => { 46 | e.preventDefault(); 47 | listener.onKeyUp(e); 48 | }); 49 | } 50 | 51 | this.fps = 60; 52 | this.tick = this.tick.bind(this); 53 | } 54 | 55 | tick() { 56 | if (this.running) { 57 | requestAnimationFrame(this.tick); 58 | const now = Date.now(); 59 | const diff = now - this.startTime; 60 | if (diff > this.tickTime) { 61 | this.startTime = now - (diff % this.tickTime); 62 | this.listener.update(this.tickTime / 1000.0); 63 | } 64 | } 65 | } 66 | 67 | start() { 68 | if(!this.running) { 69 | this.running = true; 70 | this.listener.create(); 71 | this.startTime = Date.now(); 72 | this.tickTime = 1000 / this.fps; 73 | requestAnimationFrame(this.tick); 74 | } 75 | } 76 | 77 | stop() { 78 | this.running = false; 79 | console.log("Game loop stopped."); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /resources/js/core/text.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Text() { 31 | 32 | this.position = new Vec2(); 33 | this.text = "empty"; 34 | this.color = "#FFF"; 35 | 36 | this.font = "Verdana"; 37 | this.size = 5; 38 | 39 | this.offX = 0; 40 | this.offY = 0; 41 | 42 | this.render = function(camera) { 43 | camera.context.font = this.size + "px " + this.font; 44 | if (this.isVisible(camera)) { 45 | camera.context.fillStyle = this.color; 46 | let measure = camera.context.measureText(this.text); 47 | let hw = measure.width * 0.5; 48 | camera.context.fillText(this.text, this.position.x + this.offX - hw, this.position.y + this.offY); 49 | } 50 | } 51 | 52 | this.isVisible = function(camera) { 53 | let viewport = camera.viewport; 54 | let measure = camera.context.measureText(this.text); 55 | let hw = measure.width * 0.5; 56 | return viewport.x1 < this.position.x + hw && 57 | viewport.y1 < this.position.y && 58 | viewport.x2 > this.position.x - hw && 59 | viewport.y2 > this.position.y; 60 | } 61 | 62 | this.update = function(dt) { 63 | 64 | } 65 | 66 | } 67 | 68 | function ScoreText() { 69 | Text.call(this); 70 | this.timer = 0; 71 | this.velocity = 0.25; 72 | this.update = function(dt) { 73 | this.timer += dt; 74 | this.color = brightColor(rndHexColor(), 1.25); 75 | this.offX = Math.sin(this.timer * 8) * 4; 76 | this.velocity -= dt * 1.25; 77 | this.position.y += Math.max(-1.5, this.velocity); 78 | if (this.timer > 5.0) { 79 | this.world.removeText(this); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /resources/js/game/stairs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Stairs() { 31 | Entity.call(this); 32 | 33 | this.hh = 8; 34 | this.hw = 8; 35 | 36 | this.speed = 1; 37 | 38 | this.setMass(0); 39 | 40 | this.collisionGroup = STAIRS_GROUP; 41 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP; 42 | 43 | this.onCollision = function(pair) { 44 | 45 | let entity = pair.b; 46 | 47 | if (!entity.down && !entity.climb && pair.normal.y < 0) { // check onFloor 48 | this.world.solveCollision(pair); 49 | entity.onFloor = true; 50 | } else if (entity.climb) { 51 | 52 | if (entity.down && this.getMaxY() - entity.getMaxY() <= this.speed) { // lower exit 53 | entity.climb = false; 54 | } else if (entity.up && entity.getMaxY() - this.getMinY() <= this.speed) { // upper exit 55 | entity.climb = false; 56 | } else { // climb 57 | this.fixPosition(entity); 58 | entity.velocity.y = 0; 59 | entity.force.y = 0; 60 | if (entity.up) { 61 | entity.position.y -= this.speed; 62 | } else if (entity.down) { 63 | entity.position.y += this.speed; 64 | } 65 | } 66 | 67 | } else if (entity.down && pair.normal.y < 0) { // upper enter 68 | entity.climb = true; 69 | } else if (entity.up) { // lower enter 70 | if (pair.penetration > this.hw * 0.5) { 71 | entity.climb = true; 72 | } 73 | } 74 | 75 | } 76 | 77 | this.fixPosition = function(e) { 78 | e.position.x = interpolate(e.position.x, this.position.x, 0.2); 79 | e.velocity.x = 0; 80 | e.force.x = 0; 81 | } 82 | 83 | this.getPositionY = function() { 84 | return this.position.y - 0.01; // fix stairs upper position 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /resources/js/core/background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | class Background { 31 | 32 | constructor() { 33 | this.layers = {}; 34 | } 35 | 36 | addLayer(id, image, x, y, width, height, scale, ratioX, ratioY, offsetX, offsetY, paddingX, paddingY) { 37 | this.layers[id] = { 38 | image: image, 39 | x: x, 40 | y: y, 41 | width: width, 42 | height: height, 43 | scale: scale, 44 | ratioX: ratioX, 45 | ratioY: ratioY, 46 | offsetX: offsetX, 47 | offsetY: offsetY, 48 | paddingX: paddingX, 49 | paddingY: paddingY 50 | } 51 | } 52 | 53 | removeLayer(id) { 54 | let tmp = this.layers[id]; 55 | delete this.layers[id]; 56 | return tmp; 57 | } 58 | 59 | render(camera) { 60 | 61 | let viewport = camera.viewport; 62 | 63 | let vw = viewport.getWidth(); 64 | let vh = viewport.getHeight(); 65 | 66 | for (var key in this.layers) { 67 | let layer = this.layers[key]; 68 | let width = layer.width * layer.scale; 69 | let height = layer.height * layer.scale; 70 | 71 | let lw = width + layer.paddingX * layer.scale; 72 | let lh = height + layer.paddingY * layer.scale; 73 | 74 | let currentX = -(camera.position.x * layer.ratioX + layer.offsetX) % lw; 75 | let y = -(camera.position.y * layer.ratioY - vh * 0.5 + layer.offsetY + height) % lh; 76 | 77 | do { 78 | let currentY = y; 79 | do { 80 | camera.context.drawImage(layer.image, layer.x, layer.y, layer.width, layer.height, currentX + viewport.x1, currentY + viewport.y1, width, height); 81 | currentY += lh; 82 | } while (currentY < vh); 83 | currentX += lw; 84 | } while (currentX < vw); 85 | 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /resources/js/game/resources.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | var loadImage = function(src) { 31 | let result = document.createElement("img"); 32 | result.src = src; 33 | return result; 34 | } 35 | 36 | var skeletonImg = loadImage("./resources/data/images/skeleton.png"); 37 | var batImg = loadImage("./resources/data/images/bat.png"); 38 | var characterImg = loadImage("./resources/data/images/character.png"); 39 | var platformImg = loadImage("./resources/data/images/platform.png"); 40 | var spikesImg = loadImage("./resources/data/images/spikes.png"); 41 | var springImg = loadImage("./resources/data/images/spring.png") 42 | var snakeImg = loadImage("./resources/data/images/snake.png"); 43 | var doorImg = loadImage("./resources/data/images/door.png"); 44 | var keyImg = loadImage("./resources/data/images/key.png"); 45 | var heartImg = loadImage("./resources/data/images/heart.png"); 46 | var coinImg = loadImage("./resources/data/images/coin.png"); 47 | var clockImg = loadImage("./resources/data/images/clock.png"); 48 | var canonImg = loadImage("./resources/data/images/canon.png"); 49 | 50 | var parallax1 = loadImage("./resources/data/parallax/layer_01_1920 x 1080.png"); 51 | var parallax2 = loadImage("./resources/data/parallax/layer_02_1920 x 1080.png"); 52 | var parallax3 = loadImage("./resources/data/parallax/layer_03_1920 x 1080.png"); 53 | var parallax4 = loadImage("./resources/data/parallax/layer_04_1920 x 1080.png"); 54 | var parallax5 = loadImage("./resources/data/parallax/layer_05_1920 x 1080.png"); 55 | var parallax6 = loadImage("./resources/data/parallax/layer_06_1920 x 1080.png"); 56 | var parallax7 = loadImage("./resources/data/parallax/layer_07_1920 x 1080.png"); 57 | var parallax8 = loadImage("./resources/data/parallax/layer_08_1920 x 1080.png"); 58 | 59 | var jumpSound; 60 | var musicSound; 61 | var keySound; 62 | var springSound; 63 | window.addEventListener("load", function() { 64 | jumpSound = new Sound("./resources/data/sounds/jump.mp3"); 65 | jumpSound.sound.volume = 0.5; 66 | 67 | heartSound = new Sound("./resources/data/sounds/heart.mp3"); 68 | heartSound.sound.volume = 1; 69 | 70 | keySound = new Sound("./resources/data/sounds/key.mp3"); 71 | keySound.sound.volume = 1; 72 | 73 | coinSound = new Sound("./resources/data/sounds/coin.mp3"); 74 | coinSound.sound.volume = 1; 75 | 76 | explosionSound = new Sound("./resources/data/sounds/explosion.mp3"); 77 | explosionSound.sound.volume = 0.5; 78 | 79 | springSound = new Sound("./resources/data/sounds/spring.mp3"); 80 | springSound.sound.volume = 0.5; 81 | 82 | musicSound = new Sound("./resources/data/music/music3.mp3"); 83 | musicSound.sound.volume = 0.5; 84 | }); 85 | -------------------------------------------------------------------------------- /resources/js/core/animation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Animation(img, coords, tw, th, frameStart, frameEnd, offX, offY) { 31 | 32 | this.img = img; 33 | this.coords = coords; 34 | this.tw = tw; 35 | this.th = th; 36 | this.frameStart = frameStart == undefined ? 0 : frameStart; 37 | this.frameIndex = this.frameStart; 38 | this.frameEnd = frameEnd == undefined ? coords.length : frameEnd; 39 | this.offX = offX == undefined ? 0 : offX; 40 | this.offY = offY == undefined ? 0 : offY; 41 | 42 | this.currentTime = 0; 43 | this.frameTime = 0.1; 44 | this.scale = new Vec2(1, 1); 45 | 46 | this.render = function(x, y, ctx) { 47 | if (this.coords) { 48 | let coord = this.coords[this.frameIndex]; 49 | ctx.save(); 50 | if (this.flipHorizontal) { 51 | ctx.translate(this.tw * 0.5 - this.offX, 0); 52 | ctx.scale(-1, 1); 53 | x = -x; 54 | } else { 55 | x -= this.tw * 0.5 - this.offX; 56 | } 57 | if (this.flipVertical) { 58 | ctx.translate(0, this.th * 0.5 - this.offY); 59 | ctx.scale(1, -1); 60 | y = -y; 61 | } else { 62 | y -= this.th * 0.5 - this.offY; 63 | } 64 | ctx.drawImage(this.img, coord.x, coord.y, this.tw, this.th, x, y, this.tw * this.scale.x, this.th * this.scale.y); 65 | ctx.restore(); 66 | } 67 | } 68 | 69 | this.update = function(dt) { 70 | this.currentTime += dt; 71 | if (this.currentTime >= this.frameTime) { 72 | this.currentTime -= this.frameTime; 73 | this.frameIndex++; 74 | if (this.frameIndex > this.frameEnd) { 75 | this.frameIndex = this.frameStart; 76 | } 77 | } 78 | } 79 | 80 | this.restart = function() { 81 | this.frameIndex = this.frameStart; 82 | this.currentTime = 0; 83 | } 84 | 85 | this.getWidth = function() { 86 | return this.tw * this.scale.x; 87 | } 88 | 89 | this.getHeight = function() { 90 | return this.th * this.scale.y; 91 | } 92 | 93 | } 94 | 95 | var createAnimation = function(img, cols, rows, frameStart, frameEnd, offX, offY) { 96 | let coords = []; 97 | let tw = img.width / cols; 98 | let th = img.height / rows; 99 | for (let j, i = 0; i < cols; ++i) { 100 | for (j = 0; j < rows; ++j) { 101 | coords[j * cols + i] = new Vec2(i * tw, j * th); 102 | } 103 | } 104 | return new Animation(img, coords, tw, th, frameStart, frameEnd, offX, offY); 105 | } -------------------------------------------------------------------------------- /resources/js/core/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function load(src, callback) { 31 | // helper for load content 32 | var xhr = new XMLHttpRequest(); 33 | xhr.onload = function () { 34 | if (xhr.status == 200) { 35 | callback(this.responseText); 36 | } 37 | }; 38 | xhr.open("GET", src, true); 39 | xhr.send(); 40 | } 41 | 42 | function map(value, fromMin, fromMax, toMin, toMax) { 43 | if (value < fromMin) value = fromMin; 44 | else if (value > fromMin) value = fromMax; 45 | return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin; 46 | } 47 | 48 | function computeRadians(x1, y1, x2, y2) { 49 | return Math.atan2(x2 - x1, y1 - y2); 50 | } 51 | 52 | function interpolate(a, b, step) { 53 | return a + (b - a) * step; 54 | } 55 | 56 | function rndHexColor() { 57 | let r = Math.random() * 256.0; 58 | let g = Math.random() * 256.0; 59 | let b = Math.random() * 256.0; 60 | return rgbToHex(r, g, b); 61 | } 62 | 63 | function rgbToHex(r, g, b) { 64 | r = Math.floor(r).toString(16).padStart(2, '0'); 65 | g = Math.floor(g).toString(16).padStart(2, '0'); 66 | b = Math.floor(b).toString(16).padStart(2, '0'); 67 | return "#" + r + g + b; 68 | } 69 | 70 | function brightColor(color, factor) { 71 | 72 | if (color.startsWith("#")) { 73 | color = color.substring(1, color.length); 74 | } 75 | 76 | let value = color.match(/.{1,2}/g); 77 | 78 | let r = parseInt(value[0], 16); 79 | r = Math.min(r * factor, 255); 80 | let g = parseInt(value[1], 16); 81 | g = Math.min(g * factor, 255); 82 | let b = parseInt(value[2], 16); 83 | b = Math.min(b * factor, 255); 84 | 85 | return rgbToHex(r, g, b); 86 | } 87 | 88 | if (!String.prototype.padStart) { 89 | String.prototype.padStart = function padStart(targetLength, padString) { 90 | targetLength = targetLength >> 0; //truncate if number or convert non-number to 0; 91 | padString = String(typeof padString !== 'undefined' ? padString : ' '); 92 | if (this.length > targetLength) { 93 | return String(this); 94 | } else { 95 | targetLength = targetLength - this.length; 96 | if (targetLength > padString.length) { 97 | padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed 98 | } 99 | return padString.slice(0, targetLength) + String(this); 100 | } 101 | }; 102 | } 103 | 104 | if (!String.prototype.startsWith) { 105 | Object.defineProperty(String.prototype, 'startsWith', { 106 | value: function(search, rawPos) { 107 | var pos = rawPos > 0 ? rawPos | 0 : 0; 108 | return this.substring(pos, pos + search.length) === search; 109 | } 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /resources/js/game/items.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | function Item() { 32 | Entity.call(this); 33 | this.collisionGroup = ITEM_GROUP; 34 | } 35 | 36 | function Key() { 37 | Item.call(this); 38 | 39 | this.hw = 8; 40 | this.hh = 4; 41 | 42 | this.update = function(dt) { 43 | this.velocity.x *= 0.95; 44 | } 45 | 46 | this.render = function(camera) { 47 | if (this.isVisible(camera)) { 48 | camera.context.drawImage(keyImg, this.position.x - this.hw, this.position.y - this.hh, this.hw * 2, this.hh * 2); 49 | } 50 | } 51 | 52 | this.renderUi = function(x, y, context) { 53 | context.drawImage(keyImg, x, y, this.hw * 2, this.hh * 2); 54 | } 55 | 56 | this.onCollision = function(pair) { 57 | if (!pair.b.removed && pair.b.inventory) { 58 | pair.b.inventory.push(this); 59 | this.world.remove(this); 60 | if (pair.b instanceof Player) 61 | keySound.play(); 62 | } 63 | } 64 | 65 | } 66 | 67 | function Heart() { 68 | Item.call(this); 69 | 70 | this.hw = 6; 71 | this.hh = 6; 72 | 73 | this.heartAnim = createAnimation(heartImg, 2, 2, 0, 3, 2, 2); 74 | this.heartAnim.scale.set(0.75, 0.75); 75 | 76 | this.update = function(dt) { 77 | this.heartAnim.update(dt); 78 | } 79 | 80 | this.render = function(camera) { 81 | if (this.isVisible(camera)) { 82 | this.heartAnim.render(this.position.x, this.position.y, camera.context); 83 | } 84 | } 85 | 86 | this.onCollision = function(pair) { 87 | if (!pair.b.removed) { 88 | if (pair.b instanceof Player) { 89 | pair.b.increaseHealth(1); 90 | this.world.remove(this); 91 | } else if (pair.b.inventory) { 92 | pair.b.inventory.push(this); 93 | this.world.remove(this); 94 | } 95 | } 96 | } 97 | 98 | } 99 | 100 | function Coin() { 101 | Item.call(this); 102 | 103 | this.hw = 6; 104 | this.hh = 6; 105 | 106 | this.points = 25; 107 | 108 | this.coinAnim = createAnimation(coinImg, 3, 3, 0, 6, 0, 0); 109 | this.coinAnim.scale.set(0.75, 0.75); 110 | 111 | this.update = function(dt) { 112 | this.coinAnim.update(dt); 113 | } 114 | 115 | this.render = function(camera) { 116 | if (this.isVisible(camera)) { 117 | this.coinAnim.render(this.position.x, this.position.y, camera.context); 118 | } 119 | } 120 | 121 | this.onCollision = function(pair) { 122 | if (!pair.b.removed) { 123 | if (pair.b instanceof Player) { 124 | pair.b.createText(10, this.position); 125 | pair.b.score += this.points; 126 | coinSound.stop(); 127 | coinSound.play(); 128 | this.world.remove(this); 129 | } else if (pair.b.inventory) { 130 | pair.b.inventory.push(this); 131 | this.world.remove(this); 132 | } 133 | } 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /resources/js/core/vec2.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | class Vec2 { 31 | constructor(x = 0, y = 0) { 32 | this.set(x, y); 33 | } 34 | 35 | set(x, y) { 36 | if (x instanceof Vec2) { 37 | y = x.y; 38 | x = x.x; 39 | } else if(y == undefined){ 40 | y = x; 41 | } 42 | this.x = x; 43 | this.y = y; 44 | return this; 45 | } 46 | 47 | add(x, y) { 48 | if (x instanceof Vec2) { 49 | y = x.y; 50 | x = x.x; 51 | } else if(y == undefined){ 52 | y = x; 53 | } 54 | this.x += x; 55 | this.y += y; 56 | return this; 57 | } 58 | 59 | sub(x, y) { 60 | if (x instanceof Vec2) { 61 | y = x.y; 62 | x = x.x; 63 | } else if(y == undefined){ 64 | y = x; 65 | } 66 | this.x -= x; 67 | this.y -= y; 68 | return this; 69 | } 70 | 71 | div(x, y) { 72 | if (x instanceof Vec2) { 73 | y = x.y; 74 | x = x.x; 75 | } else if(y == undefined){ 76 | y = x; 77 | } 78 | this.x /= x; 79 | this.y /= y; 80 | return this; 81 | } 82 | 83 | scl(x, y) { 84 | if (x instanceof Vec2) { 85 | y = x.y; 86 | x = x.x; 87 | } else if(y == undefined){ 88 | y = x; 89 | } 90 | this.x *= x; 91 | this.y *= y; 92 | return this; 93 | } 94 | 95 | addScl(v, s) { 96 | this.x += v.x * s; 97 | this.y += v.y * s; 98 | return this; 99 | } 100 | 101 | rotate(sin, cos) { 102 | if (cos === undefined) { 103 | cos = Math.cos(sin); 104 | sin = Math.sin(sin); 105 | } 106 | let tmp = this.x; 107 | this.x = this.x * cos - this.y * sin; 108 | this.y = tmp * sin + this.y * cos; 109 | return this; 110 | } 111 | 112 | dst2(x, y) { 113 | if (x instanceof Vec2) { 114 | y = x.y; 115 | x = x.x; 116 | } 117 | let dx = x - this.x; 118 | let dy = y - this.y; 119 | return dx * dx + dy * dy; 120 | } 121 | 122 | dst(x, y) { 123 | if (x instanceof Vec2) { 124 | y = x.y; 125 | x = x.x; 126 | } 127 | return Math.sqrt(this.dst2(x, y)); 128 | } 129 | 130 | len2() { 131 | return this.x * this.x + this.y * this.y; 132 | } 133 | 134 | len() { 135 | return Math.sqrt(this.len2()); 136 | } 137 | 138 | nor() { 139 | let len = this.len2(); 140 | if (len != 0) { 141 | len = Math.sqrt(len); 142 | this.x /= len; 143 | this.y /= len; 144 | } 145 | return this; 146 | } 147 | 148 | dot(x, y) { 149 | if (x instanceof Vec2) { 150 | y = x.y; 151 | x = x.x; 152 | } else if(y == undefined){ 153 | y = x; 154 | } 155 | return this.x * x + this.y * y; 156 | } 157 | 158 | setZero() { 159 | this.x = this.y = 0; 160 | return this; 161 | } 162 | 163 | negate() { 164 | this.x = -this.x; 165 | this.y = -this.y; 166 | return this; 167 | } 168 | 169 | floor() { 170 | this.x = Math.floor(this.x); 171 | this.y = Math.floor(this.y); 172 | return this; 173 | } 174 | 175 | copy() { 176 | return new Vec2(this.x, this.y); 177 | } 178 | 179 | render(camera, color) { 180 | const context = camera.context; 181 | context.beginPath(); 182 | context.arc(this.x, this.y, 5, 0, 2 * Math.PI, false); 183 | if(color) context.fillStyle = color; 184 | context.fill(); 185 | } 186 | 187 | } -------------------------------------------------------------------------------- /resources/js/core/camera.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | const VIRTUAL_WIDTH = 640; 31 | const VIRTUAL_HEIGHT = 640; 32 | 33 | class Camera { 34 | 35 | constructor(canvas, x, y) { 36 | 37 | this.canvas = canvas; 38 | 39 | this.position = new Vec2(x, y); 40 | this.zoom = 1.0; 41 | this.rotation = 0.0; 42 | 43 | this.onresize(); 44 | window.addEventListener("resize", this.onresize.bind(this)); 45 | } 46 | 47 | update() { 48 | 49 | this.context.restore(); 50 | this.context.save(); 51 | 52 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); 53 | let hw = this.canvas.width * 0.5; 54 | let hh = this.canvas.height * 0.5; 55 | // hw = Math.round(hw); 56 | // hh = Math.round(hh); 57 | 58 | this.context.translate(hw, hh); 59 | this.context.rotate(this.rotation); 60 | this.context.translate(-hw, -hh); 61 | 62 | this.context.scale(this.zoom, this.zoom); 63 | let x = hw / this.zoom - this.position.x; 64 | let y = hh / this.zoom - this.position.y; 65 | x = Math.round(x * 10.0) / 10.0; 66 | y = Math.round(y * 10.0) / 10.0; 67 | 68 | this.context.translate(x, y); 69 | 70 | this.viewport = this.computeViewport(); 71 | } 72 | 73 | onresize() { 74 | 75 | let w = this.canvas.clientWidth; 76 | let h = this.canvas.clientHeight; 77 | if (w > h) { 78 | this.canvas.width = VIRTUAL_WIDTH; 79 | this.canvas.height = VIRTUAL_HEIGHT * (h / w); 80 | } else { 81 | this.canvas.width = VIRTUAL_WIDTH * (w / h); 82 | this.canvas.height = VIRTUAL_HEIGHT; 83 | } 84 | this.context = this.canvas.getContext('2d'); 85 | this.context.imageSmoothingEnabled = false; 86 | } 87 | 88 | computeViewport() { 89 | 90 | let w = this.canvas.width / this.zoom; 91 | let h = this.canvas.height / this.zoom; 92 | 93 | let hw = w * 0.5; 94 | let hh = h * 0.5; 95 | 96 | let x, y, r = this.rotation; 97 | if (r != 0) { 98 | let cos = Math.abs(Math.cos(r)); 99 | let sin = Math.abs(Math.sin(r)); 100 | x = cos * hw + sin * hh; 101 | y = cos * hh + sin * hw; 102 | } else { 103 | x = hw; 104 | y = hh; 105 | } 106 | 107 | let x1 = this.position.x - x; 108 | let y1 = this.position.y - y; 109 | let x2 = this.position.x + x; 110 | let y2 = this.position.y + y; 111 | return { 112 | x1: x1, 113 | y1: y1, 114 | x2: x2, 115 | y2: y2, 116 | getWidth: function() { 117 | return x2 - x1; 118 | }, 119 | getHeight: function() { 120 | return y2 - y1; 121 | } 122 | }; 123 | } 124 | 125 | drawViewport() { 126 | let viewport = this.computeViewport(); 127 | this.context.beginPath(); 128 | this.context.rect(viewport.x1, viewport.y1, viewport.getWidth(), viewport.getHeight()); 129 | this.context.stroke(); 130 | } 131 | 132 | follow(position, mapSize, dt) { 133 | let hw = this.canvas.width * 0.5; 134 | let hh = this.canvas.height * 0.5; 135 | if (dt == undefined) { 136 | dt = 1; 137 | } 138 | this.position.x = Math.max(interpolate(this.position.x, position.x, dt), hw / this.zoom); 139 | this.position.x = Math.min(this.position.x, mapSize.width - hw / this.zoom); 140 | this.position.y = Math.max(interpolate(this.position.y, position.y, dt), hh / this.zoom); 141 | this.position.y = Math.min(this.position.y, mapSize.height - hh / this.zoom); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /resources/js/core/entity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Entity() { 31 | 32 | this.hw = 0; 33 | this.hh = 0; 34 | 35 | this.velocity = new Vec2(); 36 | this.linearVelocity = new Vec2(); 37 | this.force = new Vec2(); 38 | this.position = new Vec2(); 39 | 40 | this.static = false; 41 | 42 | this.restitution = 0; 43 | this.invMass = 1.0; 44 | 45 | this.collisionGroup = 0; 46 | this.collisionMask = 0; 47 | 48 | this.render = function(camera) {} 49 | 50 | this.update = function(dt) {} 51 | 52 | this.isVisible = function(camera) { 53 | let viewport = camera.viewport; 54 | return viewport.x1 < this.position.x + this.hw && 55 | viewport.y1 < this.position.y + this.hh && 56 | viewport.x2 > this.position.x - this.hw && 57 | viewport.y2 > this.position.y - this.hh; 58 | } 59 | 60 | this.hit = function(e, hitInfo) { 61 | let nx = e.getPositionX() - this.getPositionX(); 62 | let xOverlap = (this.hw + e.hw) - Math.abs(nx); 63 | if (xOverlap >= 0.0) { 64 | let ny = e.getPositionY() - this.getPositionY(); 65 | let yOverlap = (this.hh + e.hh) - Math.abs(ny); 66 | if (yOverlap >= 0.0) { 67 | if (hitInfo) { 68 | if (yOverlap > xOverlap) { 69 | hitInfo.normal.x = nx > 0 ? 1 : -1; 70 | hitInfo.normal.y = 0; 71 | hitInfo.penetration = xOverlap; 72 | } else { 73 | hitInfo.normal.y = ny > 0 ? 1 : -1; 74 | hitInfo.normal.x = 0; 75 | hitInfo.penetration = yOverlap; 76 | } 77 | } 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | this.contains = function(e) { 85 | return this.getMinX() < e.getMinX() && 86 | this.getMinY() < e.getMinY() && 87 | this.getMaxX() > e.getMaxX() && 88 | this.getMaxY() > e.getMaxY(); 89 | } 90 | 91 | this.getPositionX = function() { 92 | return this.position.x; 93 | } 94 | 95 | this.getPositionY = function() { 96 | return this.position.y; 97 | } 98 | 99 | this.getMinX = function() { 100 | return this.position.x - this.hw; 101 | } 102 | 103 | this.getMaxX = function() { 104 | return this.position.x + this.hw; 105 | } 106 | 107 | this.getMinY = function() { 108 | return this.position.y - this.hh; 109 | } 110 | 111 | this.getMaxY = function() { 112 | return this.position.y + this.hh; 113 | } 114 | 115 | this.setMass = function(mass) { 116 | if (mass <= 0) { 117 | this.invMass = 0; 118 | this.static = true; 119 | } else { 120 | this.invMass = 1.0 / mass; 121 | } 122 | } 123 | 124 | this.onCollision = function(pair) { 125 | 126 | } 127 | 128 | this.explosion = function(particles, position, force, color) { 129 | explosionSound.stop(); 130 | explosionSound.play(); 131 | 132 | for (let i = 0; i < particles; ++i) { 133 | let particle = new Particle(color == undefined ? rndHexColor() : color); 134 | // particle.collisionGroup = 2; 135 | // particle.collisionMask = 2; 136 | particle.position.set(position); 137 | particle.force.x = -force.x + (Math.random() - Math.random()) * 200; 138 | particle.force.y = force.y + (Math.random() - Math.random()) * 200; 139 | // particle.velocity.rotate(Math.random() * Math.PI * 0.5 + Math.PI * 0.25); 140 | particle.restitution = Math.random(); 141 | this.world.add(particle); 142 | } 143 | } 144 | 145 | this.createText = function(text, position) { 146 | let t = new ScoreText(); 147 | // t.color = "#FF2222"; 148 | t.position.set(position); 149 | t.text = text; 150 | this.world.addText(t); 151 | } 152 | 153 | this.onWorldCollision = function(collisionInfo) { 154 | return false; 155 | } 156 | 157 | } 158 | 159 | function Mob() { 160 | Entity.call(this); 161 | this.inventory = []; 162 | } -------------------------------------------------------------------------------- /resources/js/core/map.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | class Map { 31 | 32 | constructor(root, map) { 33 | this.root = root; 34 | this.map = map; 35 | 36 | this.objects = {}; 37 | this.layerMap = {}; 38 | this.layerList = []; 39 | } 40 | 41 | async load(callback) { 42 | 43 | let response = await fetch(this.root + this.map); 44 | let responseText = await response.text(); 45 | 46 | let text = responseText.replace(".tsx", ".json"); 47 | let json = JSON.parse(text); 48 | this.tileSize = json.tilewidth; 49 | this.width = json.width; 50 | this.height = json.height; 51 | let n = json.layers.length; 52 | for (let index = 0, i = 0; i < n; ++i) { 53 | let tmp = json.layers[i]; 54 | if (tmp.type == "tilelayer") { 55 | let layer = { 56 | id: tmp.id, 57 | name: tmp.name, 58 | visible: tmp.visible, 59 | data: tmp.data 60 | }; 61 | for (let j = 0; j < layer.data.length; ++j) { 62 | layer.data[j]--; 63 | } 64 | this.layerList[index++] = this.layerMap[layer.name] = layer; 65 | } else if (tmp.type == "objectgroup") { 66 | let objs = tmp.objects; 67 | for (let i = 0; i < objs.length; ++i) { 68 | let tmp = objs[i]; 69 | let list = this.objects[tmp.name]; 70 | if (list == undefined) { 71 | list = []; 72 | this.objects[tmp.name] = list; 73 | } 74 | let object = this.toObject(tmp); 75 | list.push(object); 76 | } 77 | } 78 | } 79 | 80 | let tmp = json.tilesets[0].source; 81 | 82 | response = await fetch(this.root + tmp); 83 | responseText = await response.text(); 84 | 85 | this.tileset = JSON.parse(responseText); 86 | this.img = document.createElement("img"); 87 | this.img.src = this.root + this.tileset.image; 88 | 89 | if(callback) callback(); 90 | 91 | } 92 | 93 | toObject(o) { 94 | if (o.type == "") { 95 | o.getProperty = function(key) { 96 | if (o.properties == undefined) return null; 97 | for (let i = 0; i < o.properties.length; ++i) { 98 | let property = o.properties[i]; 99 | if (key == property.name) { 100 | return property.value; 101 | } 102 | } 103 | }; 104 | return o; 105 | } else { 106 | console.log("Warning!!!, unmaped object: " + o); 107 | return null; 108 | } 109 | } 110 | 111 | render(camera) { 112 | const ts = this.tileset.tilewidth; 113 | for (let i = 0; i < this.layerList.length; ++i) { 114 | if (this.layerList[i].visible) { 115 | let data = this.layerList[i].data; 116 | for (let x, y = 0; y < this.height; ++y) { 117 | let index = y * this.width; 118 | for (x = 0; x < this.width; ++x) { 119 | let v = data[index + x]; 120 | let wx = x * ts; 121 | let wy = y * ts; 122 | if (v > -1 && 123 | wx + ts > camera.viewport.x1 && 124 | wy + ts > camera.viewport.y1 && 125 | wx < camera.viewport.x2 && 126 | wy < camera.viewport.y2) { 127 | let c = v % this.tileset.columns; 128 | let r = Math.floor(v / this.tileset.columns); 129 | let sx = this.tileset.margin + c * (ts + this.tileset.spacing); 130 | let sy = this.tileset.margin + r * (ts + this.tileset.spacing); 131 | camera.context.drawImage(this.img, 132 | sx, sy, 133 | ts, ts, 134 | wx, wy, 135 | ts + 0.4, ts + 0.4); // + 0.4 Fix firefox float point error 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | getTile(x, y, layer) { 144 | const index = y * this.width + x; 145 | if (layer === undefined) { 146 | for (let i = this.layerList.length - 1; i > -1; --i) { 147 | let value = this.layerList[i].data[index]; 148 | if (value > 0) return value; 149 | } 150 | return -1; 151 | } 152 | return this.layerMap[layer].data[index]; 153 | } 154 | 155 | setTile(tile, x, y, layer) { 156 | this.layers[layer].data[y * this.width + x] = tile; 157 | } 158 | 159 | getSize() { 160 | 161 | const ts = this.tileset.tilewidth; 162 | return { 163 | width : this.width * ts, 164 | height: this.height * ts 165 | }; 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /resources/js/game/platforms.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Platform() { 31 | Entity.call(this); 32 | 33 | this.speed = 1.0; 34 | this.dir = 1; 35 | this.direction = false; 36 | 37 | this.type = 0; // type 0 = platform, type 1 = elevator 38 | 39 | this.setMass(0); 40 | this.static = false; 41 | 42 | this.collisionGroup = PLATFORM_GROUP; 43 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP | ITEM_GROUP | PLATFORM_GROUP | LIMIT_GROUP | BULLET_GROUP; 44 | 45 | this.render = function(camera) { 46 | if (this.isVisible(camera)) { 47 | camera.context.drawImage(platformImg, this.position.x - this.hw, this.position.y - this.hh, this.hw * 2, this.hh * 2); 48 | } 49 | } 50 | 51 | this.update = function(dt) { 52 | 53 | switch (this.type) { 54 | case 1: 55 | { 56 | if (this.direction) { 57 | if (this.dir == 1) this.velocity.y = 0; 58 | this.dir = -1; 59 | this.force.y -= this.speed; 60 | } else { 61 | if (this.dir == -1) this.velocity.y = 0; 62 | this.dir = 1; 63 | this.force.y += this.speed; 64 | } 65 | break; 66 | } 67 | default: 68 | { 69 | if (this.direction) { 70 | if (this.dir == 1) this.velocity.x = 0; 71 | this.dir = -1; 72 | this.force.x -= this.speed; 73 | } else { 74 | if (this.dir == -1) this.velocity.x = 0; 75 | this.dir = 1; 76 | this.force.x += this.speed; 77 | } 78 | break; 79 | } 80 | } 81 | 82 | } 83 | 84 | this.onCollision = function(pair) { 85 | let entity = pair.b; 86 | if (entity instanceof Limit) return; 87 | this.world.solveCollision(pair); 88 | if (pair.normal.y < 0) { 89 | entity.linearVelocity.x += this.velocity.x; 90 | entity.force.y += 2; 91 | entity.onFloor = true; 92 | } 93 | } 94 | 95 | } 96 | 97 | function Spring() { 98 | Entity.call(this); 99 | 100 | this.hw = 8; 101 | this.hh = 8; 102 | 103 | this.dir = 1; 104 | this.direction = false; 105 | 106 | this.static = false; 107 | this.setMass(1.0); 108 | 109 | this.springAnimation = createAnimation(springImg, 2, 1, 0, 1, 0, -8); 110 | 111 | this.animate = false; 112 | this.time = 0; 113 | 114 | this.collisionGroup = PLATFORM_GROUP; 115 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP | ITEM_GROUP | PLATFORM_GROUP; 116 | 117 | this.render = function(camera) { 118 | if (this.isVisible(camera)) { 119 | this.springAnimation.render(this.position.x, this.position.y, camera.context); 120 | } 121 | } 122 | 123 | this.update = function(dt) { 124 | this.velocity.x *= 0.95; 125 | if (this.animate) { 126 | this.time += dt; 127 | this.springAnimation.update(dt); 128 | if (this.time > 0.5) { 129 | this.time = 0; 130 | this.animate = false; 131 | this.springAnimation.restart(); 132 | } 133 | } 134 | } 135 | 136 | this.onCollision = function(pair) { 137 | let entity = pair.b; 138 | if (entity instanceof Limit || 139 | entity instanceof Stairs || 140 | (entity instanceof Coin && entity.static)) return; 141 | this.world.solveCollision(pair); 142 | if (pair.normal.y < 0) { 143 | entity.force.y -= 500; 144 | entity.onFloor = false; 145 | this.animate = true; 146 | this.time = 0; 147 | if (pair.b instanceof Player) { 148 | springSound.stop(); 149 | springSound.play(); 150 | } 151 | } 152 | } 153 | 154 | } 155 | 156 | function Spikes() { 157 | Entity.call(this); 158 | 159 | this.static = true; 160 | this.setMass(0.0); 161 | 162 | this.spikesAnimation = createAnimation(spikesImg, 2, 1, 0, 1, 0, 0); 163 | this.spikesAnimation.scale.x = 0.5; 164 | 165 | let w = this.spikesAnimation.getWidth(); 166 | 167 | this.collisionGroup = BULLET_GROUP; 168 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP; 169 | 170 | this.render = function(camera) { 171 | if (this.isVisible(camera)) { 172 | 173 | let th = (this.hh * 2.0); 174 | this.spikesAnimation.scale.y = th / this.spikesAnimation.th; 175 | 176 | let tmp = this.hw * 2.0; 177 | let n = tmp / w; 178 | 179 | let offX = ((n * 0.5) >> 0) * w - w; 180 | let offY = (this.spikesAnimation.th - th) * 0.5; 181 | 182 | for (let i = 0; i < n; ++i) { 183 | this.spikesAnimation.render(this.position.x + i * w - offX, this.position.y + offY, camera.context); 184 | } 185 | } 186 | } 187 | 188 | this.update = function(dt) { 189 | this.spikesAnimation.update(dt); 190 | } 191 | 192 | this.onCollision = function(pair) { 193 | //this.world.solveCollision(pair); 194 | } 195 | 196 | } -------------------------------------------------------------------------------- /resources/js/core/joypad.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Joypad(container) { // keyboard simulator 31 | 32 | let joyCanvas; 33 | if (!container.joyCanvas) { 34 | joyCanvas = document.createElement("canvas"); 35 | joyCanvas.id = "joy_canvas"; 36 | joyCanvas.style.position = "absolute"; 37 | joyCanvas.style.width = '100%'; 38 | joyCanvas.style.height = '100%'; 39 | joyCanvas.style.top = 0; 40 | joyCanvas.style.left = 0; 41 | container.appendChild(joyCanvas); 42 | container.joyCanvas = joyCanvas; 43 | } else { 44 | joyCanvas = container.joyCanvas; 45 | } 46 | 47 | let context = joyCanvas.getContext("2d"); 48 | 49 | this.control = { x: 0, y: 0, w: 0, h: 0 } 50 | this.action1 = { x: 0, y: 0, w: 0, h: 0 } 51 | 52 | this.color = 'rgba(128, 128, 128, 0.5)'; 53 | 54 | this.touchMap = {}; 55 | 56 | let self = this; 57 | joyCanvas.addEventListener('touchstart', function(e) { 58 | for (let i = 0; i < e.changedTouches.length; i++) { 59 | let touch = e.changedTouches[i]; 60 | self.handleTouch(touch, false); 61 | } 62 | }, false); 63 | 64 | joyCanvas.addEventListener('touchend', function(e) { 65 | for (let i = 0; i < e.changedTouches.length; i++) { 66 | let touch = e.changedTouches[i]; 67 | self.handleTouch(touch, true); 68 | } 69 | }, false); 70 | 71 | joyCanvas.addEventListener('touchmove', function(e) { 72 | for (let i = 0; i < e.changedTouches.length; i++) { 73 | let touch = e.changedTouches[i]; 74 | self.handleTouch(touch, false); 75 | } 76 | }, false); 77 | 78 | this.render = function() { 79 | joyCanvas.width = joyCanvas.clientWidth; 80 | joyCanvas.height = joyCanvas.clientHeight; 81 | // clear 82 | let rw = joyCanvas.width; 83 | let rh = joyCanvas.height; 84 | context.clearRect(0, 0, rw, rh); 85 | 86 | let size = Math.min(rw, rh); 87 | 88 | this.control.w = size * 0.4; 89 | this.control.h = this.control.w; 90 | let offset = this.control.w * 0.2; 91 | this.control.x = offset; 92 | this.control.y = rh - (this.control.h + offset); 93 | 94 | context.fillStyle = this.color; 95 | context.beginPath(); 96 | context.fillRect(this.control.x + this.control.w * 0.5 - this.control.w * 0.1, this.control.y, this.control.w * 0.2, this.control.h); 97 | context.stroke(); 98 | 99 | context.beginPath(); 100 | context.fillRect(this.control.x, this.control.y + this.control.h * 0.5 - this.control.h * 0.1, this.control.w, this.control.h * 0.2); 101 | context.stroke(); 102 | 103 | this.action1.w = size * 0.2; 104 | this.action1.h = this.action1.w; 105 | offset = this.action1.w * 0.6; 106 | this.action1.x = rw - (this.action1.w + offset); 107 | this.action1.y = rh - (this.action1.h + offset); 108 | 109 | context.beginPath(); 110 | context.arc(this.action1.x + this.action1.w * 0.5, this.action1.y + this.action1.h * 0.5, this.action1.w * 0.7, 0, Math.PI * 2); 111 | context.fill(); 112 | 113 | }; 114 | 115 | this.handleTouch = function(touch, end) { 116 | 117 | // console.log(touch); 118 | 119 | if (end) { 120 | let codes = this.touchMap[touch.identifier]; 121 | if (codes) { 122 | delete this.touchMap[touch.identifier]; 123 | for (let i = 0; i < codes.length; ++i) { 124 | if (codes[i]) 125 | this.listener.onKeyUp({ keyCode: codes[i] }); 126 | } 127 | } 128 | return; 129 | } 130 | 131 | let touchX = touch.clientX; 132 | let touchY = touch.clientY; 133 | 134 | let x1 = touchX - this.control.x; 135 | let y1 = touchY - this.control.y; 136 | 137 | if (x1 > 0 && x1 < this.control.w && 138 | y1 > 0 && y1 < this.control.h) { 139 | let codes = this.touchMap[touch.identifier]; 140 | if (codes == undefined) { 141 | codes = []; 142 | this.touchMap[touch.identifier] = codes; 143 | } 144 | let hw = this.control.w * 0.5; 145 | let hh = this.control.h * 0.5; 146 | 147 | if (x1 > hw + hw * 0.5) { 148 | if (!codes[0]) codes[0] = 39; // right 149 | } else if (x1 < hw * 0.5) { 150 | if (!codes[1]) codes[1] = 37; // left 151 | } 152 | 153 | if (y1 > hh + hh * 0.5) { 154 | if (!codes[2]) codes[2] = 40; // down 155 | } else if (y1 < hh * 0.5) { 156 | if (!codes[3]) codes[3] = 38; // up 157 | } 158 | 159 | for (let i = 0; i < codes.length; ++i) { 160 | this.listener.onKeyDown({ keyCode: codes[i] }); 161 | 162 | } 163 | 164 | } else { 165 | 166 | let x1 = touchX - this.action1.x; 167 | let y1 = touchY - this.action1.y; 168 | 169 | if (x1 > 0 && x1 < this.action1.w && 170 | y1 > 0 && y1 < this.action1.h) { 171 | let codes = [32]; 172 | this.touchMap[touch.identifier] = codes; 173 | this.listener.onKeyDown({ keyCode: codes[0] }); 174 | } 175 | 176 | } 177 | 178 | } 179 | } -------------------------------------------------------------------------------- /resources/js/game/player.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Player(color) { 31 | Mob.call(this); 32 | 33 | this.static = false; 34 | this.color = color ? color : '#00FF00'; 35 | this.hh = 10; 36 | this.hw = 4.5; 37 | 38 | this.standAnimation = createAnimation(characterImg, 3, 3, 2, 2, 0, -2); 39 | this.walkAnimation = createAnimation(characterImg, 3, 3, 3, 4, 0, -2); 40 | this.jumpAnimation = createAnimation(characterImg, 3, 3, 5, 5, 0, -2); 41 | this.climb1Animation = createAnimation(characterImg, 3, 3, 6, 7, 0, -2); 42 | this.climb2Animation = createAnimation(characterImg, 3, 3, 6, 6, 0, -2); 43 | 44 | this.clockAnimation = createAnimation(clockImg, 3, 3, 0, 7, 0, 0); 45 | this.clockAnimation.frameTime = 1; 46 | this.heartAnim = createAnimation(heartImg, 2, 2, 0, 3, 2, 2); 47 | //this.heartAnim.scale.set(0.75, 0.75); 48 | 49 | this.collisionGroup = PLAYER_GROUP; 50 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP | BULLET_GROUP | PLATFORM_GROUP | STAIRS_GROUP | ITEM_GROUP | EVENT_GROUP; 51 | 52 | this.xDir = 1; 53 | this.speed = 2.35; 54 | this.jumpForce = 250; 55 | 56 | this.maxHealth = 3; 57 | this.health = this.maxHealth; 58 | 59 | this.score = 0; 60 | 61 | let self = this; 62 | this.timer = new Timer({ 63 | onTimeout: function() { 64 | console.log("Player Position: ") 65 | console.log(self.position); 66 | } 67 | }); 68 | this.timer.setTimeout(2); 69 | 70 | this.clockTime = new Date().getTime(); 71 | 72 | this.renderUi = function(camera) { 73 | 74 | let viewport = camera.viewport; 75 | let x = viewport.x1; 76 | let y = viewport.y1; 77 | 78 | // clock 79 | this.clockAnimation.render(x + this.clockAnimation.tw, y + 16, camera.context); 80 | 81 | let timeDiff = new Date().getTime() - this.clockTime; 82 | 83 | let div = timeDiff / 1000; 84 | let minutes = ("" + Math.min(99, (div / 60) << 0)).padStart(2, "0"); 85 | let seconds = ("" + ((div % 60) << 0)).padStart(2, "0"); 86 | //let millis = ("" + ((((timeDiff - (div << 0) * 1000)) / 10) << 0)).padStart(2, "0"); 87 | let text = minutes + ":" + seconds /* + ":" + millis*/ ; 88 | camera.context.font = "7px Verdana"; 89 | 90 | camera.context.fillStyle = "#FFF"; 91 | camera.context.fillText(text, x + 25, y + 19); 92 | 93 | // health 94 | for (let i = 0; i < this.health; ++i) 95 | this.heartAnim.render(x + this.heartAnim.tw + (this.heartAnim.tw + 2) * i, viewport.y2 - 16, camera.context); 96 | 97 | // lives 98 | 99 | // inventory 100 | for (let i = 0; i < this.inventory.length; ++i) 101 | this.inventory[i].renderUi(x + 12 + (16 + 2) * i, viewport.y2 - 36, camera.context); 102 | } 103 | 104 | this.render = function(camera) { 105 | this.currentAnimation.render(this.position.x, this.position.y, camera.context); 106 | } 107 | 108 | this.update = function(dt) { 109 | 110 | this.clockAnimation.update(dt); 111 | 112 | // this.timer.step(dt); 113 | 114 | let minX = this.getMinX(); 115 | if (minX < 0) { 116 | this.position.x -= minX; 117 | } 118 | 119 | if (this.health <= 0 || 120 | this.position.y > 512) { 121 | this.health = 0; 122 | this.gameOver = true; 123 | this.world.remove(this); 124 | return; 125 | } 126 | 127 | if (this.left) { 128 | this.xDir = -1; 129 | this.currentAnimation = this.walkAnimation; 130 | this.force.x -= this.speed; 131 | /*let tgtForce = -this.speed - this.velocity.x; 132 | let force = tgtForce / dt; 133 | this.force.x += force;*/ 134 | } else if (this.right) { 135 | this.xDir = 1; 136 | this.currentAnimation = this.walkAnimation; 137 | this.force.x += this.speed; 138 | } else { 139 | this.currentAnimation = this.standAnimation; 140 | if (this.onFloor) { 141 | this.velocity.x *= 0.75; 142 | } 143 | } 144 | 145 | if (this.climb) { 146 | if (this.up || this.down) { 147 | this.currentAnimation = this.climb1Animation; 148 | } else { 149 | this.currentAnimation = this.climb2Animation; 150 | } 151 | } else if (!this.onFloor) { 152 | this.currentAnimation = this.jumpAnimation; 153 | } 154 | 155 | this.currentAnimation.flipHorizontal = this.xDir == -1; 156 | 157 | if (this.jump) { 158 | this.jump = false; 159 | 160 | if (this.onFloor || (this.climb && !this.up)) { 161 | this.climb = false; 162 | jumpSound.play(); 163 | this.force.y -= this.jumpForce; 164 | } 165 | } 166 | this.onFloor = false; 167 | this.currentAnimation.update(dt); 168 | 169 | } 170 | 171 | this.hurt = function(fx, fy) { 172 | this.health--; 173 | this.force.x = fx; 174 | this.force.y = fy 175 | this.climb = false; 176 | } 177 | 178 | this.onCollision = function(pair) { 179 | 180 | let entity = pair.b; 181 | if (entity.enemy) { 182 | if (pair.normal.y > 0) { 183 | this.world.remove(entity); 184 | let f = this.jumpForce; 185 | this.explosion(25, entity.position, new Vec2().set(this.velocity).nor().scl(-f, -f)); 186 | this.createText(entity.points, entity.position); 187 | this.score += entity.points; 188 | this.force.y = -f; 189 | } else { 190 | this.world.solveCollision(pair); 191 | this.hurt(-pair.normal.x * this.jumpForce * 0.5, -this.jumpForce * 0.5); 192 | } 193 | } else if (entity instanceof Spikes) { 194 | if (pair.normal.y > 0) { 195 | this.world.solveCollision(pair); 196 | this.health--; 197 | this.force.x = -pair.normal.x * this.jumpForce * 0.5; 198 | this.force.y = -this.jumpForce * 0.5; 199 | this.climb = false; 200 | } 201 | } 202 | } 203 | 204 | this.increaseHealth = function(value) { 205 | this.health = Math.min(this.maxHealth, this.health + value); 206 | heartSound.play(); 207 | } 208 | 209 | } -------------------------------------------------------------------------------- /resources/js/game/enemies.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | function Enemy() { 31 | Mob.call(this); 32 | 33 | this.enemy = true; 34 | this.points = 0; 35 | 36 | this.collisionGroup = ENEMY_GROUP; 37 | this.collisionMask = LIMIT_GROUP | PLAYER_GROUP | BULLET_GROUP | ITEM_GROUP | PLATFORM_GROUP | STAIRS_GROUP; 38 | 39 | } 40 | 41 | function Snake() { 42 | Enemy.call(this); 43 | 44 | this.hh = 4; 45 | this.hw = 12; 46 | 47 | this.walkAnimation = createAnimation(snakeImg, 3, 1, 0, 2, 0, -4); 48 | this.walkAnimation.frameTime = 0.25; 49 | this.walkAnimation.currentTime = Math.random() * this.walkAnimation.frameTime; 50 | 51 | this.speed = 0.5 + Math.random() * 0.3; 52 | this.dir = 1; 53 | this.direction = false; 54 | 55 | this.points = 50; 56 | 57 | this.render = function(camera) { 58 | if (this.isVisible(camera)) { 59 | this.currentAnimation.render(this.position.x, this.position.y, camera.context); 60 | } 61 | } 62 | 63 | this.update = function(dt) { 64 | 65 | if (this.direction) { 66 | if (this.dir == 1) this.velocity.x = 0; 67 | this.dir = -1; 68 | this.currentAnimation = this.walkAnimation; 69 | this.force.x -= this.speed; 70 | } else { 71 | if (this.dir == -1) this.velocity.x = 0; 72 | this.dir = 1; 73 | this.currentAnimation = this.walkAnimation; 74 | this.force.x += this.speed; 75 | } 76 | 77 | if (this.world.getCollisionInfo(this, this.force.x, 0) != null) { 78 | this.direction = !this.direction; 79 | } 80 | 81 | this.currentAnimation.flipHorizontal = this.dir == 1; 82 | this.currentAnimation.update(dt); 83 | if (this.currentAnimation.frameIndex == 0) { 84 | this.velocity.x = 0; 85 | } 86 | 87 | } 88 | 89 | this.onCollision = function(pair) { 90 | // this.world.solveCollision(pair); 91 | } 92 | 93 | } 94 | 95 | function Bat() { 96 | Enemy.call(this); 97 | 98 | this.hh = 8; 99 | this.hw = 8; 100 | 101 | this.flyAnimation = createAnimation(batImg, 2, 1, 0, 1, 0, 0); 102 | this.flyAnimation.frameTime = 0.2; 103 | this.flyAnimation.currentTime = Math.random() * this.flyAnimation.frameTime; 104 | 105 | this.speed = 1.0 + Math.random() * 0.3; 106 | this.dir = 1; 107 | this.direction = false; 108 | 109 | this.points = 75; 110 | 111 | this.setMass(0); 112 | this.static = false; 113 | 114 | this.time = 0; 115 | 116 | this.render = function(camera) { 117 | if (this.isVisible(camera)) { 118 | let offY = Math.sin(this.time) * 3; 119 | this.currentAnimation.render(this.position.x, this.position.y + offY, camera.context); 120 | } 121 | } 122 | 123 | this.update = function(dt) { 124 | 125 | this.time += dt * 17; 126 | 127 | if (this.direction) { 128 | if (this.dir == 1) this.velocity.x = 0; 129 | this.dir = -1; 130 | this.currentAnimation = this.flyAnimation; 131 | this.force.x -= this.speed; 132 | } else { 133 | if (this.dir == -1) this.velocity.x = 0; 134 | this.dir = 1; 135 | this.currentAnimation = this.flyAnimation; 136 | this.force.x += this.speed; 137 | } 138 | 139 | if (this.world.getCollisionInfo(this, this.force.x, 0) != null) { 140 | this.direction = !this.direction; 141 | } 142 | 143 | this.currentAnimation.flipHorizontal = this.dir == 1; 144 | this.currentAnimation.update(dt); 145 | 146 | } 147 | 148 | this.onCollision = function(pair) { 149 | // this.world.solveCollision(pair); 150 | } 151 | 152 | } 153 | 154 | function Skull() { 155 | Enemy.call(this); 156 | 157 | this.hh = 10; 158 | this.hw = 4.5; 159 | 160 | this.walkAnimation = createAnimation(skeletonImg, 4, 9, 4, 7, 0, 0); 161 | 162 | this.speed = 1.0 + Math.random() * 0.15; 163 | this.dir = 1; 164 | this.direction = false; 165 | 166 | this.points = 100; 167 | 168 | this.render = function(camera) { 169 | if (this.isVisible(camera)) { 170 | this.currentAnimation.render(this.position.x, this.position.y, camera.context); 171 | } 172 | } 173 | 174 | this.update = function(dt) { 175 | 176 | if (this.direction) { 177 | if (this.dir == 1) this.velocity.x = 0; 178 | this.dir = -1; 179 | this.currentAnimation = this.walkAnimation; 180 | this.force.x -= this.speed; 181 | } else { 182 | if (this.dir == -1) this.velocity.x = 0; 183 | this.dir = 1; 184 | this.currentAnimation = this.walkAnimation; 185 | this.force.x += this.speed; 186 | } 187 | 188 | if (this.world.getCollisionInfo(this, this.force.x, 0) != null) { 189 | this.direction = !this.direction; 190 | } 191 | 192 | this.currentAnimation.flipHorizontal = this.dir == -1; 193 | this.currentAnimation.update(dt); 194 | 195 | } 196 | 197 | this.onCollision = function(pair) { 198 | // this.world.solveCollision(pair); 199 | } 200 | 201 | } 202 | 203 | function Tank() { 204 | Enemy.call(this); 205 | 206 | this.hh = 14; 207 | this.hw = 13; 208 | 209 | this.moveAnimation = createAnimation(canonImg, 2, 2, 0, 1, 0, 0); 210 | 211 | this.speed = 0.65 + Math.random() * 0.15; 212 | this.dir = 1; 213 | this.direction = false; 214 | 215 | this.points = 150; 216 | this.rotation = 0; 217 | 218 | this.time = 0; 219 | 220 | this.render = function(camera) { 221 | this.visible = this.isVisible(camera); 222 | if (this.visible) { 223 | camera.context.save(); 224 | 225 | let y = this.position.y - 4; 226 | let x = this.position.x; 227 | 228 | camera.context.translate(x, y) 229 | camera.context.rotate(this.rotation); 230 | camera.context.drawImage(canonImg, 3, 45, 20, 10, -2, -5, 20, 10); 231 | camera.context.restore(); 232 | 233 | this.currentAnimation.render(this.position.x, this.position.y, camera.context); 234 | } 235 | } 236 | 237 | this.update = function(dt) { 238 | 239 | if (!this.remove && this.visible) { 240 | let rotation = computeRadians(this.position.x, this.position.y, this.player.position.x, this.player.position.y) - Math.PI / 2; 241 | this.rotation = interpolate(this.rotation, Math.min(0, Math.max(-Math.PI, rotation)), dt * 4.0); 242 | this.time += dt; 243 | if (this.time > 1.0) { 244 | this.time = 0; 245 | let bullet = new Bullet(); 246 | bullet.direction.set(1, 0).rotate(this.rotation); 247 | bullet.position.set(this.position.x + bullet.direction.x * 20, this.position.y - 4.9 + bullet.direction.y * 20); 248 | this.world.add(bullet); 249 | } 250 | } 251 | 252 | if (this.direction) { 253 | if (this.dir == 1) this.velocity.x = 0; 254 | this.dir = -1; 255 | this.currentAnimation = this.moveAnimation; 256 | this.force.x -= this.speed; 257 | } else { 258 | if (this.dir == -1) this.velocity.x = 0; 259 | this.dir = 1; 260 | this.currentAnimation = this.moveAnimation; 261 | this.force.x += this.speed; 262 | } 263 | 264 | if (this.world.getCollisionInfo(this, this.force.x, 0) != null) { 265 | this.direction = !this.direction; 266 | } 267 | 268 | this.currentAnimation.flipHorizontal = this.dir == -1; 269 | this.currentAnimation.update(dt); 270 | 271 | } 272 | 273 | this.onCollision = function(pair) { 274 | // this.world.solveCollision(pair); 275 | } 276 | 277 | } 278 | 279 | function Bullet() { 280 | Entity.call(this); 281 | 282 | this.hh = 2; 283 | this.hw = 2; 284 | 285 | this.speed = 0.65 + Math.random() * 0.15; 286 | this.direction = new Vec2(); 287 | 288 | this.setMass(0); 289 | this.static = false; 290 | 291 | this.speed = 2; 292 | 293 | this.collisionGroup = BULLET_GROUP; 294 | this.collisionMask = PLAYER_GROUP | ENEMY_GROUP | PLATFORM_GROUP; 295 | 296 | this.color = "#fffa4c"; 297 | this.time = 0; 298 | 299 | this.render = function(camera) { 300 | this.visible = this.isVisible(camera); 301 | if (this.visible) { 302 | camera.context.beginPath(); 303 | camera.context.fillStyle = this.color; 304 | camera.context.arc(this.position.x, this.position.y, this.hw, 0, Math.PI * 2.0); 305 | camera.context.fill(); 306 | } 307 | } 308 | 309 | this.update = function(dt) { 310 | 311 | if (this.time > 10) { 312 | this.world.remove(this); 313 | } else { 314 | this.time += dt; 315 | let tgtForce = this.direction.x * this.speed - this.velocity.x; 316 | let force = tgtForce / dt; 317 | this.force.x += force; 318 | 319 | tgtForce = this.direction.y * this.speed - this.velocity.y; 320 | force = tgtForce / dt; 321 | this.force.y += force; 322 | } 323 | 324 | } 325 | 326 | this.onCollision = function(pair) { 327 | this.world.remove(this); 328 | if (pair.b instanceof Player) { 329 | this.velocity.nor(); 330 | pair.b.hurt(this.velocity.x * pair.b.jumpForce * 0.5, -pair.b.jumpForce * 0.5); 331 | } else if (pair.b.enemy) { 332 | this.world.remove(pair.b); 333 | this.explosion(25, pair.b.position, this.force); 334 | } 335 | } 336 | 337 | this.onWorldCollision = function(collisionInfo) { 338 | this.explosion(10, this.position, this.force, this.color); 339 | this.world.remove(this); 340 | return true; 341 | } 342 | 343 | } -------------------------------------------------------------------------------- /resources/js/core/world.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | var PLAYER_GROUP = 1; 31 | var ENEMY_GROUP = 2; 32 | var BULLET_GROUP = 4; 33 | var PLATFORM_GROUP = 8; 34 | var STAIRS_GROUP = 16; 35 | var ITEM_GROUP = 32; 36 | var LIMIT_GROUP = 64; 37 | var EVENT_GROUP = 128; 38 | 39 | class World { 40 | 41 | constructor() { 42 | this.clear(); 43 | } 44 | 45 | handlePromises() { 46 | const promises = []; 47 | promises.push(this.map.load()); 48 | return promises; 49 | } 50 | 51 | clear() { 52 | this.clearVelocity = true; 53 | this.gravity = new Vec2(0, 9.80665); 54 | 55 | this.texts = []; 56 | this.entities = []; 57 | 58 | this.background = null; 59 | this.map = null; 60 | this.collisionListener = null; 61 | } 62 | 63 | step(dt) { 64 | 65 | let j, i, entity; 66 | 67 | // update entities 68 | for (i = 0; i < this.entities.length; ++i) { 69 | entity = this.entities[i]; 70 | entity.update(dt); 71 | if (entity.invMass != 0) { 72 | entity.velocity.addScl(this.gravity, dt); 73 | } 74 | entity.velocity.addScl(entity.force, dt); 75 | entity.force.setZero(); 76 | } 77 | 78 | // test entity collision 79 | for (i = 0; i < this.entities.length; ++i) { 80 | entity = this.entities[i]; 81 | for (j = i + 1; j < this.entities.length; ++j) { 82 | let b = this.entities[j]; 83 | if ((entity.collisionMask & b.collisionGroup) == b.collisionGroup) { 84 | if (!entity.static || !b.static) { 85 | let pair = { 86 | normal: new Vec2(), 87 | penetration: 0, 88 | dt: dt 89 | }; 90 | if (entity.hit(b, pair)) { 91 | pair.a = entity; 92 | pair.b = b; 93 | entity.onCollision(pair); 94 | 95 | pair.a = b; 96 | pair.b = entity; 97 | pair.normal.negate(); 98 | b.onCollision(pair); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | // test world collision 106 | for (i = 0; i < this.entities.length; ++i) { 107 | entity = this.entities[i]; 108 | if (!entity.static) { 109 | entity.velocity.addScl(entity.force, dt); 110 | entity.linearVelocity.add(entity.velocity); 111 | this.tryToMove(entity, entity.linearVelocity.x, entity.linearVelocity.y); 112 | if (this.clearVelocity) 113 | entity.velocity.scl(0.98, 0.98); 114 | } else { 115 | entity.velocity.setZero(); 116 | } 117 | entity.force.setZero(); 118 | entity.linearVelocity.setZero(); 119 | } 120 | 121 | this.updateTexts(dt); 122 | } 123 | 124 | tryToMove(entity, dx, dy) { 125 | 126 | let tmp, collisionInfo; 127 | if (dx != 0.0) { 128 | collisionInfo = this.getCollisionInfo(entity, dx, 0); 129 | if (collisionInfo == null) { 130 | entity.position.x += dx; 131 | } else { 132 | 133 | if (dx > 0) { 134 | tmp = entity.position.x + entity.hw; 135 | let t = Math.ceil(tmp / this.map.tileSize) * this.map.tileSize; 136 | collisionInfo.nx = -1.0; 137 | collisionInfo.ny = 0; 138 | collisionInfo.penetration = tmp - t; 139 | } else { 140 | tmp = entity.position.x - entity.hw; 141 | let t = Math.floor(tmp / this.map.tileSize) * this.map.tileSize; 142 | collisionInfo.nx = 1.0; 143 | collisionInfo.ny = 0; 144 | collisionInfo.penetration = t - tmp; 145 | } 146 | 147 | if (entity.onWorldCollision(collisionInfo) || this.collisionListener.onCollision(entity, collisionInfo)) { 148 | tmp = collisionInfo.penetration * collisionInfo.nx; 149 | // if(this.getCollisionInfo(entity, tmp, 0) == null) { 150 | entity.position.x += collisionInfo.penetration * collisionInfo.nx; 151 | entity.velocity.x = -entity.velocity.x * entity.restitution; 152 | // } 153 | } else { 154 | entity.position.x += dx; 155 | } 156 | 157 | } 158 | } 159 | 160 | if (dy != 0.0) { 161 | collisionInfo = this.getCollisionInfo(entity, 0, dy); 162 | if (collisionInfo == null) { 163 | entity.position.y += dy; 164 | } else { 165 | 166 | if (dy > 0) { 167 | tmp = entity.position.y + entity.hh; 168 | let t = Math.ceil(tmp / this.map.tileSize) * this.map.tileSize; 169 | collisionInfo.nx = 0; 170 | collisionInfo.ny = -1.0; 171 | collisionInfo.penetration = tmp - t; 172 | } else { 173 | tmp = entity.position.y - entity.hh; 174 | let t = Math.floor(tmp / this.map.tileSize) * this.map.tileSize; 175 | collisionInfo.nx = 0; 176 | collisionInfo.ny = 1.0; 177 | collisionInfo.penetration = t - tmp; 178 | } 179 | 180 | if (entity.onWorldCollision(collisionInfo) || this.collisionListener.onCollision(entity, collisionInfo)) { 181 | tmp = collisionInfo.penetration * collisionInfo.ny; 182 | // if(this.getCollisionInfo(entity, 0, tmp) == null) { 183 | entity.position.y += collisionInfo.penetration * collisionInfo.ny; 184 | entity.velocity.y = -entity.velocity.y * entity.restitution; 185 | // } 186 | } else { 187 | entity.position.y += dy; 188 | } 189 | 190 | } 191 | } 192 | } 193 | 194 | solveCollision(pair) { 195 | 196 | let a = pair.a; 197 | let b = pair.b; 198 | 199 | let invMass = a.invMass + b.invMass; 200 | if (invMass > 0.0) { 201 | let normal = pair.normal; 202 | // Velocity Correction 203 | let velocityDiff = new Vec2(); 204 | velocityDiff.set(b.velocity).sub(a.velocity); 205 | let normalVelocity = velocityDiff.dot(normal); 206 | if (normalVelocity < 0) { 207 | let j = -normalVelocity / invMass; 208 | a.velocity.addScl(normal, -j * a.invMass); 209 | b.velocity.addScl(normal, j * b.invMass); 210 | // Position Correction 211 | let correction = pair.penetration / invMass; 212 | let x = normal.x * correction; 213 | let y = normal.y * correction; 214 | this.tryToMove(a, -x * a.invMass, -y * a.invMass); 215 | this.tryToMove(b, x * b.invMass, y * b.invMass); 216 | } 217 | 218 | } 219 | } 220 | 221 | render(camera) { 222 | this.background.render(camera); 223 | if(this.map) 224 | this.map.render(camera); 225 | let i, entity; 226 | for (i = 0; i < this.entities.length; ++i) { 227 | entity = this.entities[i]; 228 | entity.render(camera); 229 | } 230 | this.renderTexts(camera); 231 | } 232 | 233 | getCollisionInfo(entity, dx, dy) { // dx or dy must be zero 234 | let tileSize = this.map.tileSize; 235 | let x1 = Math.max(0, Math.floor((entity.position.x + dx - entity.hw) / tileSize)); 236 | let y1 = Math.max(0, Math.floor((entity.position.y + dy - entity.hh) / tileSize)); 237 | 238 | let x2 = Math.min(this.map.width, Math.ceil((entity.position.x + dx + entity.hw) / tileSize)); 239 | let y2 = Math.min(this.map.height, Math.ceil((entity.position.y + dy + entity.hh) / tileSize)); 240 | 241 | let collisionLayer = this.collisionListener.collisionLayer; 242 | for (let i = x1; i < x2; ++i) { 243 | for (let j = y1; j < y2; ++j) { 244 | let tile = this.map.getTile(i, j, collisionLayer); 245 | if (this.collisionListener.hasCollision(entity, tile)) { 246 | return { 247 | tile: tile, 248 | x: i, 249 | y: j, 250 | getTileBounds: function() { 251 | return { 252 | x1: i * tileSize, 253 | y1: j * tileSize, 254 | x2: x1 + tileSize, 255 | y2: y1 + tileSize 256 | } 257 | } 258 | }; 259 | } 260 | } 261 | } 262 | return null; 263 | } 264 | 265 | overlapsTile(entity, layer, tile) { 266 | return this.overlapsTiles(entity, layer, [tile]); 267 | } 268 | 269 | overlapsTiles(entity, layer, tiles) { 270 | let x1 = Math.max(0, Math.floor((entity.position.x - entity.hw) / this.map.tileSize)); 271 | let y1 = Math.max(0, Math.floor((entity.position.y - entity.hh) / this.map.tileSize)); 272 | 273 | let x2 = Math.min(this.map.width, Math.ceil((entity.position.x + entity.hw) / this.map.tileSize)); 274 | let y2 = Math.min(this.map.height, Math.ceil((entity.position.y + entity.hh) / this.map.tileSize)); 275 | 276 | for (let i = x1; i < x2; ++i) { 277 | for (let j = y1; j < y2; ++j) { 278 | let tile = this.map.getTile(i, j, layer); 279 | for (let index = 0; index < tiles.length; ++index) { 280 | if (tile == tiles[index]) { 281 | return true; 282 | } 283 | } 284 | } 285 | } 286 | return false; 287 | } 288 | 289 | debug(ctx) { 290 | ctx.lineWidth = "2"; 291 | ctx.strokeStyle = "#FF0000"; 292 | for (let i = 0; i < this.entities.length; ++i) { 293 | let entity = this.entities[i]; 294 | ctx.beginPath(); 295 | ctx.rect(entity.position.x - entity.hw, 296 | entity.position.y - entity.hh, 297 | entity.hw * 2.0, 298 | entity.hh * 2.0); 299 | ctx.stroke(); 300 | } 301 | } 302 | 303 | setMap(map, collisionListener) { 304 | this.map = map; 305 | this.collisionListener = collisionListener; 306 | } 307 | 308 | add(e) { 309 | e.world = this; 310 | this.entities.push(e); 311 | return this; 312 | } 313 | 314 | remove(e) { 315 | let index = this.entities.indexOf(e); 316 | if (index > -1) { 317 | this.entities.splice(index, 1); 318 | e.removed = true; 319 | if (e.inventory) { 320 | for (let i = 0; i < e.inventory.length; ++i) { 321 | e.inventory[i].position.set(e.position); 322 | e.inventory[i].force.set(e.force); 323 | this.add(e.inventory[i]); 324 | } 325 | } 326 | return true; 327 | } 328 | return false; 329 | } 330 | 331 | removeText(e) { 332 | let index = this.texts.indexOf(e); 333 | if (index > -1) { 334 | this.texts.splice(index, 1); 335 | return true; 336 | } 337 | return false; 338 | } 339 | 340 | addText(e) { 341 | e.world = this; 342 | this.texts.push(e); 343 | } 344 | 345 | renderTexts(camera) { 346 | for (let i = 0; i < this.texts.length; ++i) { 347 | this.texts[i].render(camera); 348 | } 349 | } 350 | 351 | updateTexts(dt) { 352 | for (let i = 0; i < this.texts.length; ++i) { 353 | this.texts[i].update(dt); 354 | } 355 | } 356 | 357 | } 358 | -------------------------------------------------------------------------------- /resources/js/game/demo-game.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, Sergio S.- sergi.ss4@gmail.com http://sergiosoriano.com 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * 2. Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * 3. Neither the name of the copyright holder nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | window.addEventListener("load", function() { 31 | 32 | const canvas = document.querySelector("#game"); 33 | const camera = new Camera(canvas); 34 | 35 | const promises = []; 36 | 37 | const level1 = new Map("./resources/data/maps/", "level1.json"); 38 | promises.push(level1.load()); 39 | 40 | var debug = false; 41 | 42 | var world = new World(); 43 | 44 | var player; 45 | var joypad; 46 | 47 | let keyMap = []; 48 | let game = new Game({ 49 | 50 | create: function() { 51 | 52 | world.clear(); 53 | 54 | camera.zoom = 2.0; 55 | 56 | const background = new Background(); 57 | background.addLayer("parallax8", parallax8, 0, 0, parallax8.width, parallax8.height, 0.5, 0.03, 0.3, 0, 80, 0, -100); 58 | background.addLayer("parallax7", parallax7, 0, 0, parallax7.width, parallax7.height, 0.5, 0.04, 0.4, 0, 80, 0, -50); 59 | background.addLayer("parallax6", parallax6, 0, 0, parallax6.width, parallax6.height, 0.5, 0.05, 0.5, 0, 80, 0, -100); 60 | background.addLayer("parallax5", parallax5, 0, 0, parallax5.width, parallax5.height, 0.5, 0.1, 0.6, 0, 80, 0, -90); 61 | background.addLayer("parallax4", parallax4, 0, 0, parallax4.width, parallax4.height, 0.5, 0.2, 0.7, 0, 80, 0, -80); 62 | background.addLayer("parallax3", parallax3, 0, 0, parallax3.width, parallax3.height, 0.5, 0.3, 0.8, 0, 80, 0, -70); 63 | background.addLayer("parallax2", parallax2, 0, 0, parallax2.width, parallax2.height, 0.5, 0.4, 0.9, 0, 0, 0, -60); 64 | background.addLayer("parallax1", parallax1, 0, 0, parallax1.width, parallax1.height, 0.5, 0.5, 1.0, 0, 0, 0, -50); 65 | world.background = background; 66 | 67 | // Create Player 68 | player = new Player(); 69 | world.add(player); 70 | 71 | this.map = level1; 72 | 73 | // Load Objects 74 | for (let key in this.map.objects) { 75 | let list = this.map.objects[key]; 76 | let obj; 77 | switch (key) { 78 | case "player": // set player position 79 | obj = list[0]; 80 | player.position.set(obj.x, obj.y); 81 | camera.position.set(player.position); 82 | 83 | break; 84 | case "skull": 85 | for (let i = 0; i < list.length; ++i) { 86 | obj = list[i]; 87 | let skull = new Skull(); 88 | skull.position.set(obj.x, obj.y); 89 | skull.right = true; 90 | addRndItem(skull, this.world); 91 | world.add(skull); 92 | } 93 | break; 94 | case "bat": 95 | for (let i = 0; i < list.length; ++i) { 96 | obj = list[i]; 97 | let bat = new Bat(); 98 | bat.position.set(obj.x, obj.y); 99 | bat.right = true; 100 | addRndItem(bat, this.world); 101 | world.add(bat); 102 | } 103 | break; 104 | case "snake": 105 | for (let i = 0; i < list.length; ++i) { 106 | obj = list[i]; 107 | let snake = new Snake(); 108 | snake.position.set(obj.x, obj.y); 109 | snake.right = true; 110 | addRndItem(snake, world); 111 | world.add(snake); 112 | } 113 | break; 114 | case "tank": 115 | for (let i = 0; i < list.length; ++i) { 116 | obj = list[i]; 117 | let tank = new Tank(); 118 | tank.position.set(obj.x, obj.y); 119 | tank.right = true; 120 | addRndItem(tank, world); 121 | world.add(tank); 122 | tank.player = player; 123 | } 124 | break; 125 | case "limit": 126 | for (let i = 0; i < list.length; ++i) { 127 | obj = list[i]; 128 | let limit = new Limit(); 129 | let hw = obj.width * 0.5; 130 | let hh = obj.height * 0.5; 131 | limit.hw = hw; 132 | limit.hh = hh; 133 | limit.position.set(obj.x + hw, obj.y + hh); 134 | world.add(limit); 135 | } 136 | break; 137 | case "stairs": 138 | for (let i = 0; i < list.length; ++i) { 139 | obj = list[i]; 140 | let stairs = new Stairs(); 141 | let hw = obj.width * 0.5; 142 | let hh = obj.height * 0.5; 143 | stairs.hw = hw; 144 | stairs.hh = hh; 145 | stairs.position.set(obj.x + hw, obj.y + hh); 146 | world.add(stairs); 147 | } 148 | break; 149 | case "platform": 150 | for (let i = 0; i < list.length; ++i) { 151 | obj = list[i]; 152 | let platform = new Platform(); 153 | let hw = obj.width * 0.5; 154 | let hh = obj.height * 0.5; 155 | platform.hw = hw; 156 | platform.hh = hh; 157 | platform.position.set(obj.x + hw, obj.y + hh); 158 | platform.type = obj.getProperty("type"); 159 | world.add(platform); 160 | } 161 | break; 162 | case "spring": 163 | for (let i = 0; i < list.length; ++i) { 164 | obj = list[i]; 165 | let spring = new Spring(); 166 | spring.position.set(obj.x, obj.y); 167 | world.add(spring); 168 | } 169 | break; 170 | case "door": 171 | for (let i = 0; i < list.length; ++i) { 172 | obj = list[i]; 173 | let door = new Door(); 174 | let hw = obj.width * 0.5; 175 | let hh = obj.height * 0.5; 176 | door.hw = hw; 177 | door.hh = hh; 178 | door.position.set(obj.x + hw, obj.y + hh); 179 | door.keyId = obj.getProperty('keyId'); 180 | world.add(door); 181 | } 182 | break; 183 | case "key": 184 | for (let i = 0; i < list.length; ++i) { 185 | obj = list[i]; 186 | let key = new Key(); 187 | key.position.set(obj.x, obj.y); 188 | key.id = obj.id; 189 | world.add(key); 190 | } 191 | break; 192 | case "coin": 193 | for (let i = 0; i < list.length; ++i) { 194 | obj = list[i]; 195 | let coin = new Coin(); 196 | coin.setMass(0); 197 | coin.position.set(obj.x, obj.y); 198 | world.add(coin); 199 | } 200 | break; 201 | case "heart": 202 | for (let i = 0; i < list.length; ++i) { 203 | obj = list[i]; 204 | let heart = new Heart(); 205 | heart.setMass(0); 206 | heart.position.set(obj.x, obj.y); 207 | world.add(heart); 208 | } 209 | break; 210 | case "spikes": 211 | for (let i = 0; i < list.length; ++i) { 212 | obj = list[i]; 213 | let spikes = new Spikes(); 214 | let hw = obj.width * 0.5; 215 | let hh = obj.height * 0.5; 216 | spikes.hw = hw; 217 | spikes.hh = hh; 218 | spikes.position.set(obj.x + hw, obj.y + hh); 219 | world.add(spikes); 220 | } 221 | break; 222 | case "end": 223 | for (let i = 0; i < list.length; ++i) { 224 | obj = list[i]; 225 | let end = new End(); 226 | let hw = obj.width * 0.5; 227 | let hh = obj.height * 0.5; 228 | end.hw = hw; 229 | end.hh = hh; 230 | end.position.set(obj.x + hw, obj.y + hh); 231 | world.add(end); 232 | } 233 | break; 234 | } 235 | 236 | } 237 | 238 | world.setMap(this.map, { 239 | collisionLayer: "Collisions", 240 | hasCollision: function(entity, tile) { 241 | return tile > -1; 242 | }, 243 | onCollision: function(entity, collisionInfo) { 244 | if (collisionInfo.ny < 0) { 245 | entity.onFloor = true; 246 | } 247 | return true; 248 | } 249 | }); 250 | 251 | if (window.location === window.parent.location ) { // Disable music in iframe 252 | musicSound.play(); 253 | } 254 | 255 | }, 256 | update: function(dt) { 257 | 258 | if (player.gameOver) { 259 | musicSound.stop(); 260 | 261 | if (this.gameOver == undefined) { 262 | this.gameOver = new GameOver(); 263 | world.addText(this.gameOver); 264 | } 265 | 266 | this.gameOver.position.set(camera.position); 267 | this.gameOver.ticks++; 268 | if (this.gameOver.ticks % 4 < 2) return; 269 | if (this.gameOver.ticks > this.gameOver.tgtTicks) { 270 | this.gameOver = null; 271 | this.create(); 272 | } 273 | 274 | } 275 | 276 | // Update Camera 277 | let tmp1 = new Vec2(); 278 | let tmp2 = new Vec2(); 279 | let len = player.velocity.len() * 25.0; 280 | tmp2.set(player.velocity).nor().scl(len, 0); 281 | tmp1.set(player.position).add(tmp2); 282 | camera.follow(tmp1, this.map.getSize(), dt * 5); 283 | camera.update(); 284 | 285 | // Update & Render 286 | world.step(dt); 287 | world.render(camera); 288 | 289 | if (debug) { 290 | world.debug(camera.context); 291 | camera.drawViewport(); 292 | } 293 | player.renderUi(camera); 294 | 295 | if (joypad) { 296 | joypad.render(); 297 | } 298 | 299 | }, 300 | onKeyDown: function(e) { 301 | 302 | if (!keyMap[e.keyCode]) { 303 | keyMap[e.keyCode] = true; 304 | switch (e.keyCode) { 305 | case 37: 306 | case 79: 307 | player.left = true; 308 | player.right = false; 309 | break; 310 | case 39: 311 | case 80: 312 | player.right = true; 313 | player.len = false; 314 | break; 315 | case 32: 316 | case 88: 317 | player.jump = true; 318 | break; 319 | case 38: 320 | player.up = true; 321 | player.down = false; 322 | break; 323 | case 40: 324 | player.down = true; 325 | player.up = false; 326 | break; 327 | } 328 | } 329 | }, 330 | onKeyUp: function(e) { 331 | 332 | keyMap[e.keyCode] = false; 333 | switch (e.keyCode) { 334 | case 37: 335 | case 79: 336 | player.left = false; 337 | break; 338 | case 39: 339 | case 80: 340 | player.right = false; 341 | break; 342 | case 32: 343 | case 88: 344 | player.jump = false; 345 | break; 346 | case 38: 347 | player.up = false; 348 | break; 349 | case 40: 350 | player.down = false; 351 | break; 352 | } 353 | } 354 | 355 | }); 356 | 357 | Promise.all(promises).then(()=> { 358 | game.start(); 359 | }); 360 | 361 | if ("ontouchstart" in document.documentElement) { 362 | joypad = new Joypad(document.body); 363 | joypad.listener = game.listener; 364 | } 365 | 366 | }); 367 | 368 | function GameOver() { 369 | Text.call(this); 370 | this.text = "GAME OVER"; 371 | this.size = 40; 372 | this.ticks = 0; 373 | this.tgtTicks = 200; 374 | } 375 | 376 | function addRndItem(e) { 377 | let rnd = Math.random(); 378 | if (rnd < 0.25) { 379 | let heart = new Heart(); 380 | e.inventory.push(heart); 381 | } 382 | 383 | rnd = Math.random(); 384 | if (rnd < 0.5) { 385 | let coin = new Coin(); 386 | e.inventory.push(coin); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /resources/data/maps/level1.json: -------------------------------------------------------------------------------- 1 | { "compressionlevel":-1, 2 | "editorsettings": 3 | { 4 | "export": 5 | { 6 | "format":"json", 7 | "target":"level1.json" 8 | } 9 | }, 10 | "height":32, 11 | "infinite":false, 12 | "layers":[ 13 | { 14 | "draworder":"topdown", 15 | "id":3, 16 | "name":"Objects", 17 | "objects":[ 18 | { 19 | "height":32, 20 | "id":35, 21 | "name":"limit", 22 | "rotation":0, 23 | "type":"", 24 | "visible":true, 25 | "width":16, 26 | "x":-16, 27 | "y":384 28 | }, 29 | { 30 | "height":64, 31 | "id":37, 32 | "name":"limit", 33 | "rotation":0, 34 | "type":"", 35 | "visible":true, 36 | "width":32, 37 | "x":304, 38 | "y":352 39 | }, 40 | { 41 | "height":64, 42 | "id":48, 43 | "name":"limit", 44 | "rotation":0, 45 | "type":"", 46 | "visible":true, 47 | "width":32, 48 | "x":160, 49 | "y":352 50 | }, 51 | { 52 | "height":16, 53 | "id":50, 54 | "name":"limit", 55 | "rotation":0, 56 | "type":"", 57 | "visible":true, 58 | "width":16, 59 | "x":144, 60 | "y":144 61 | }, 62 | { 63 | "height":16, 64 | "id":51, 65 | "name":"limit", 66 | "rotation":0, 67 | "type":"", 68 | "visible":true, 69 | "width":16, 70 | "x":144, 71 | "y":240 72 | }, 73 | { 74 | "height":16, 75 | "id":68, 76 | "name":"limit", 77 | "rotation":0, 78 | "type":"", 79 | "visible":true, 80 | "width":16, 81 | "x":208, 82 | "y":192 83 | }, 84 | { 85 | "height":16, 86 | "id":69, 87 | "name":"limit", 88 | "rotation":0, 89 | "type":"", 90 | "visible":true, 91 | "width":16, 92 | "x":416, 93 | "y":208 94 | }, 95 | { 96 | "height":16, 97 | "id":70, 98 | "name":"limit", 99 | "rotation":0, 100 | "type":"", 101 | "visible":true, 102 | "width":1, 103 | "x":351, 104 | "y":192 105 | }, 106 | { 107 | "height":16, 108 | "id":74, 109 | "name":"limit", 110 | "rotation":0, 111 | "type":"", 112 | "visible":true, 113 | "width":16, 114 | "x":400, 115 | "y":272 116 | }, 117 | { 118 | "height":16, 119 | "id":75, 120 | "name":"limit", 121 | "rotation":0, 122 | "type":"", 123 | "visible":true, 124 | "width":0.75, 125 | "x":575.25, 126 | "y":272 127 | }, 128 | { 129 | "height":16, 130 | "id":83, 131 | "name":"limit", 132 | "rotation":0, 133 | "type":"", 134 | "visible":true, 135 | "width":0.75, 136 | "x":671.3, 137 | "y":272 138 | }, 139 | { 140 | "height":11, 141 | "id":40, 142 | "name":"spikes", 143 | "rotation":0, 144 | "type":"", 145 | "visible":true, 146 | "width":48, 147 | "x":432, 148 | "y":373 149 | }, 150 | { 151 | "height":8, 152 | "id":49, 153 | "name":"platform", 154 | "properties":[ 155 | { 156 | "name":"type", 157 | "type":"int", 158 | "value":1 159 | }], 160 | "rotation":0, 161 | "type":"", 162 | "visible":true, 163 | "width":80, 164 | "x":112, 165 | "y":224 166 | }, 167 | { 168 | "height":8, 169 | "id":76, 170 | "name":"platform", 171 | "properties":[ 172 | { 173 | "name":"type", 174 | "type":"int", 175 | "value":0 176 | }], 177 | "rotation":0, 178 | "type":"", 179 | "visible":true, 180 | "width":80, 181 | "x":464, 182 | "y":272 183 | }, 184 | { 185 | "height":0, 186 | "id":78, 187 | "name":"spring", 188 | "point":true, 189 | "rotation":0, 190 | "type":"", 191 | "visible":true, 192 | "width":0, 193 | "x":664, 194 | "y":399 195 | }, 196 | { 197 | "height":0, 198 | "id":53, 199 | "name":"spring", 200 | "point":true, 201 | "rotation":0, 202 | "type":"", 203 | "visible":true, 204 | "width":0, 205 | "x":40, 206 | "y":158.833 207 | }, 208 | { 209 | "height":160, 210 | "id":58, 211 | "name":"stairs", 212 | "rotation":0, 213 | "type":"", 214 | "visible":true, 215 | "width":16, 216 | "x":256, 217 | "y":208 218 | }, 219 | { 220 | "height":48, 221 | "id":59, 222 | "name":"stairs", 223 | "rotation":0, 224 | "type":"", 225 | "visible":true, 226 | "width":16, 227 | "x":224, 228 | "y":368 229 | }, 230 | { 231 | "height":128, 232 | "id":60, 233 | "name":"stairs", 234 | "rotation":0, 235 | "type":"", 236 | "visible":true, 237 | "width":16, 238 | "x":400, 239 | "y":224 240 | }, 241 | { 242 | "height":112, 243 | "id":77, 244 | "name":"stairs", 245 | "rotation":0, 246 | "type":"", 247 | "visible":true, 248 | "width":16, 249 | "x":640, 250 | "y":288 251 | }, 252 | { 253 | "height":0, 254 | "id":61, 255 | "name":"coin", 256 | "point":true, 257 | "rotation":0, 258 | "type":"", 259 | "visible":true, 260 | "width":0, 261 | "x":136, 262 | "y":153 263 | }, 264 | { 265 | "height":0, 266 | "id":62, 267 | "name":"coin", 268 | "point":true, 269 | "rotation":0, 270 | "type":"", 271 | "visible":true, 272 | "width":0, 273 | "x":152, 274 | "y":153 275 | }, 276 | { 277 | "height":0, 278 | "id":63, 279 | "name":"coin", 280 | "point":true, 281 | "rotation":0, 282 | "type":"", 283 | "visible":true, 284 | "width":0, 285 | "x":168, 286 | "y":153 287 | }, 288 | { 289 | "height":0, 290 | "id":64, 291 | "name":"coin", 292 | "point":true, 293 | "rotation":0, 294 | "type":"", 295 | "visible":true, 296 | "width":0, 297 | "x":440, 298 | "y":328 299 | }, 300 | { 301 | "height":0, 302 | "id":65, 303 | "name":"coin", 304 | "point":true, 305 | "rotation":0, 306 | "type":"", 307 | "visible":true, 308 | "width":0, 309 | "x":456, 310 | "y":328 311 | }, 312 | { 313 | "height":0, 314 | "id":66, 315 | "name":"coin", 316 | "point":true, 317 | "rotation":0, 318 | "type":"", 319 | "visible":true, 320 | "width":0, 321 | "x":472, 322 | "y":328 323 | }, 324 | { 325 | "height":0, 326 | "id":85, 327 | "name":"key", 328 | "point":true, 329 | "rotation":0, 330 | "type":"", 331 | "visible":true, 332 | "width":0, 333 | "x":608, 334 | "y":320 335 | }, 336 | { 337 | "height":0, 338 | "id":71, 339 | "name":"skull", 340 | "point":true, 341 | "rotation":0, 342 | "type":"", 343 | "visible":true, 344 | "width":0, 345 | "x":384, 346 | "y":208 347 | }, 348 | { 349 | "height":0, 350 | "id":72, 351 | "name":"snake", 352 | "point":true, 353 | "rotation":0, 354 | "type":"", 355 | "visible":true, 356 | "width":0, 357 | "x":288, 358 | "y":192 359 | }, 360 | { 361 | "height":32, 362 | "id":73, 363 | "name":"door", 364 | "properties":[ 365 | { 366 | "name":"keyId", 367 | "type":"int", 368 | "value":88 369 | }], 370 | "rotation":0, 371 | "type":"", 372 | "visible":true, 373 | "width":16.5, 374 | "x":624, 375 | "y":256 376 | }, 377 | { 378 | "height":32, 379 | "id":82, 380 | "name":"door", 381 | "properties":[ 382 | { 383 | "name":"keyId", 384 | "type":"int", 385 | "value":85 386 | }], 387 | "rotation":0, 388 | "type":"", 389 | "visible":true, 390 | "width":16.5, 391 | "x":752, 392 | "y":368 393 | }, 394 | { 395 | "height":0, 396 | "id":79, 397 | "name":"bat", 398 | "point":true, 399 | "rotation":0, 400 | "type":"", 401 | "visible":true, 402 | "width":0, 403 | "x":568.75, 404 | "y":248.75 405 | }, 406 | { 407 | "height":0, 408 | "id":80, 409 | "name":"tank", 410 | "point":true, 411 | "rotation":0, 412 | "type":"", 413 | "visible":true, 414 | "width":0, 415 | "x":557.083, 416 | "y":371.917 417 | }, 418 | { 419 | "height":0, 420 | "id":81, 421 | "name":"heart", 422 | "point":true, 423 | "rotation":0, 424 | "type":"", 425 | "visible":true, 426 | "width":0, 427 | "x":557.75, 428 | "y":356.25 429 | }, 430 | { 431 | "height":0, 432 | "id":84, 433 | "name":"tank", 434 | "point":true, 435 | "rotation":0, 436 | "type":"", 437 | "visible":true, 438 | "width":0, 439 | "x":720, 440 | "y":272 441 | }, 442 | { 443 | "height":0, 444 | "id":88, 445 | "name":"key", 446 | "point":true, 447 | "rotation":0, 448 | "type":"", 449 | "visible":true, 450 | "width":0, 451 | "x":152, 452 | "y":80 453 | }, 454 | { 455 | "height":16, 456 | "id":89, 457 | "name":"platform", 458 | "properties":[ 459 | { 460 | "name":"type", 461 | "type":"int", 462 | "value":1 463 | }], 464 | "rotation":0, 465 | "type":"", 466 | "visible":true, 467 | "width":48, 468 | "x":768, 469 | "y":464 470 | }, 471 | { 472 | "height":16, 473 | "id":90, 474 | "name":"limit", 475 | "rotation":0, 476 | "type":"", 477 | "visible":true, 478 | "width":16, 479 | "x":784, 480 | "y":512 481 | }, 482 | { 483 | "height":16, 484 | "id":92, 485 | "name":"limit", 486 | "rotation":0, 487 | "type":"", 488 | "visible":true, 489 | "width":16, 490 | "x":784, 491 | "y":160 492 | }, 493 | { 494 | "height":11, 495 | "id":93, 496 | "name":"spikes", 497 | "rotation":0, 498 | "type":"", 499 | "visible":true, 500 | "width":48, 501 | "x":880, 502 | "y":437 503 | }, 504 | { 505 | "height":0, 506 | "id":94, 507 | "name":"coin", 508 | "point":true, 509 | "rotation":0, 510 | "type":"", 511 | "visible":true, 512 | "width":0, 513 | "x":784, 514 | "y":352 515 | }, 516 | { 517 | "height":0, 518 | "id":95, 519 | "name":"coin", 520 | "point":true, 521 | "rotation":0, 522 | "type":"", 523 | "visible":true, 524 | "width":0, 525 | "x":800, 526 | "y":336 527 | }, 528 | { 529 | "height":0, 530 | "id":96, 531 | "name":"coin", 532 | "point":true, 533 | "rotation":0, 534 | "type":"", 535 | "visible":true, 536 | "width":0, 537 | "x":784, 538 | "y":304 539 | }, 540 | { 541 | "height":0, 542 | "id":97, 543 | "name":"coin", 544 | "point":true, 545 | "rotation":0, 546 | "type":"", 547 | "visible":true, 548 | "width":0, 549 | "x":800, 550 | "y":288 551 | }, 552 | { 553 | "height":0, 554 | "id":98, 555 | "name":"coin", 556 | "point":true, 557 | "rotation":0, 558 | "type":"", 559 | "visible":true, 560 | "width":0, 561 | "x":784, 562 | "y":256 563 | }, 564 | { 565 | "height":0, 566 | "id":99, 567 | "name":"coin", 568 | "point":true, 569 | "rotation":0, 570 | "type":"", 571 | "visible":true, 572 | "width":0, 573 | "x":800, 574 | "y":240 575 | }, 576 | { 577 | "height":0, 578 | "id":100, 579 | "name":"coin", 580 | "point":true, 581 | "rotation":0, 582 | "type":"", 583 | "visible":true, 584 | "width":0, 585 | "x":656, 586 | "y":160 587 | }, 588 | { 589 | "height":0, 590 | "id":101, 591 | "name":"coin", 592 | "point":true, 593 | "rotation":0, 594 | "type":"", 595 | "visible":true, 596 | "width":0, 597 | "x":672, 598 | "y":160 599 | }, 600 | { 601 | "height":0, 602 | "id":102, 603 | "name":"coin", 604 | "point":true, 605 | "rotation":0, 606 | "type":"", 607 | "visible":true, 608 | "width":0, 609 | "x":688, 610 | "y":160 611 | }, 612 | { 613 | "height":0, 614 | "id":103, 615 | "name":"coin", 616 | "point":true, 617 | "rotation":0, 618 | "type":"", 619 | "visible":true, 620 | "width":0, 621 | "x":704, 622 | "y":160 623 | }, 624 | { 625 | "height":0, 626 | "id":104, 627 | "name":"coin", 628 | "point":true, 629 | "rotation":0, 630 | "type":"", 631 | "visible":true, 632 | "width":0, 633 | "x":720, 634 | "y":160 635 | }, 636 | { 637 | "height":0, 638 | "id":105, 639 | "name":"coin", 640 | "point":true, 641 | "rotation":0, 642 | "type":"", 643 | "visible":true, 644 | "width":0, 645 | "x":736, 646 | "y":160 647 | }, 648 | { 649 | "height":0, 650 | "id":106, 651 | "name":"coin", 652 | "point":true, 653 | "rotation":0, 654 | "type":"", 655 | "visible":true, 656 | "width":0, 657 | "x":656, 658 | "y":144 659 | }, 660 | { 661 | "height":0, 662 | "id":107, 663 | "name":"coin", 664 | "point":true, 665 | "rotation":0, 666 | "type":"", 667 | "visible":true, 668 | "width":0, 669 | "x":672, 670 | "y":144 671 | }, 672 | { 673 | "height":0, 674 | "id":108, 675 | "name":"coin", 676 | "point":true, 677 | "rotation":0, 678 | "type":"", 679 | "visible":true, 680 | "width":0, 681 | "x":688, 682 | "y":144 683 | }, 684 | { 685 | "height":0, 686 | "id":109, 687 | "name":"coin", 688 | "point":true, 689 | "rotation":0, 690 | "type":"", 691 | "visible":true, 692 | "width":0, 693 | "x":704, 694 | "y":144 695 | }, 696 | { 697 | "height":0, 698 | "id":110, 699 | "name":"coin", 700 | "point":true, 701 | "rotation":0, 702 | "type":"", 703 | "visible":true, 704 | "width":0, 705 | "x":720, 706 | "y":144 707 | }, 708 | { 709 | "height":0, 710 | "id":111, 711 | "name":"coin", 712 | "point":true, 713 | "rotation":0, 714 | "type":"", 715 | "visible":true, 716 | "width":0, 717 | "x":736, 718 | "y":144 719 | }, 720 | { 721 | "height":0, 722 | "id":112, 723 | "name":"coin", 724 | "point":true, 725 | "rotation":0, 726 | "type":"", 727 | "visible":true, 728 | "width":0, 729 | "x":656, 730 | "y":128 731 | }, 732 | { 733 | "height":0, 734 | "id":113, 735 | "name":"coin", 736 | "point":true, 737 | "rotation":0, 738 | "type":"", 739 | "visible":true, 740 | "width":0, 741 | "x":672, 742 | "y":128 743 | }, 744 | { 745 | "height":0, 746 | "id":114, 747 | "name":"coin", 748 | "point":true, 749 | "rotation":0, 750 | "type":"", 751 | "visible":true, 752 | "width":0, 753 | "x":688, 754 | "y":128 755 | }, 756 | { 757 | "height":0, 758 | "id":115, 759 | "name":"coin", 760 | "point":true, 761 | "rotation":0, 762 | "type":"", 763 | "visible":true, 764 | "width":0, 765 | "x":704, 766 | "y":128 767 | }, 768 | { 769 | "height":0, 770 | "id":116, 771 | "name":"coin", 772 | "point":true, 773 | "rotation":0, 774 | "type":"", 775 | "visible":true, 776 | "width":0, 777 | "x":720, 778 | "y":128 779 | }, 780 | { 781 | "height":0, 782 | "id":117, 783 | "name":"coin", 784 | "point":true, 785 | "rotation":0, 786 | "type":"", 787 | "visible":true, 788 | "width":0, 789 | "x":736, 790 | "y":128 791 | }, 792 | { 793 | "height":0, 794 | "id":118, 795 | "name":"spring", 796 | "point":true, 797 | "properties":[ 798 | { 799 | "name":"force", 800 | "type":"int", 801 | "value":20 802 | }], 803 | "rotation":0, 804 | "type":"", 805 | "visible":true, 806 | "width":0, 807 | "x":792, 808 | "y":442 809 | }, 810 | { 811 | "height":0, 812 | "id":120, 813 | "name":"bat", 814 | "point":true, 815 | "rotation":0, 816 | "type":"", 817 | "visible":true, 818 | "width":0, 819 | "x":1095.67, 820 | "y":392.333 821 | }, 822 | { 823 | "height":16, 824 | "id":121, 825 | "name":"limit", 826 | "rotation":0, 827 | "type":"", 828 | "visible":true, 829 | "width":16, 830 | "x":1088, 831 | "y":336 832 | }, 833 | { 834 | "height":16, 835 | "id":122, 836 | "name":"limit", 837 | "rotation":0, 838 | "type":"", 839 | "visible":true, 840 | "width":16, 841 | "x":992, 842 | "y":336 843 | }, 844 | { 845 | "height":0, 846 | "id":123, 847 | "name":"skull", 848 | "point":true, 849 | "rotation":0, 850 | "type":"", 851 | "visible":true, 852 | "width":0, 853 | "x":1056, 854 | "y":336 855 | }, 856 | { 857 | "height":64, 858 | "id":124, 859 | "name":"stairs", 860 | "rotation":0, 861 | "type":"", 862 | "visible":true, 863 | "width":16, 864 | "x":1040, 865 | "y":352 866 | }, 867 | { 868 | "height":32, 869 | "id":125, 870 | "name":"door", 871 | "properties":[ 872 | { 873 | "name":"keyId", 874 | "type":"int", 875 | "value":133 876 | }], 877 | "rotation":0, 878 | "type":"", 879 | "visible":true, 880 | "width":16.5, 881 | "x":1008, 882 | "y":256 883 | }, 884 | { 885 | "height":0, 886 | "id":128, 887 | "name":"snake", 888 | "point":true, 889 | "rotation":0, 890 | "type":"", 891 | "visible":true, 892 | "width":0, 893 | "x":128, 894 | "y":400 895 | }, 896 | { 897 | "height":31.9545, 898 | "id":130, 899 | "name":"stairs", 900 | "rotation":0, 901 | "type":"", 902 | "visible":true, 903 | "width":16, 904 | "x":960, 905 | "y":304 906 | }, 907 | { 908 | "height":31.9545, 909 | "id":131, 910 | "name":"stairs", 911 | "rotation":0, 912 | "type":"", 913 | "visible":true, 914 | "width":16, 915 | "x":944, 916 | "y":304 917 | }, 918 | { 919 | "height":0, 920 | "id":132, 921 | "name":"bat", 922 | "point":true, 923 | "rotation":0, 924 | "type":"", 925 | "visible":true, 926 | "width":0, 927 | "x":680, 928 | "y":228.5 929 | }, 930 | { 931 | "height":0, 932 | "id":133, 933 | "name":"key", 934 | "point":true, 935 | "rotation":0, 936 | "type":"", 937 | "visible":true, 938 | "width":0, 939 | "x":864, 940 | "y":224 941 | }, 942 | { 943 | "height":16, 944 | "id":134, 945 | "name":"end", 946 | "rotation":0, 947 | "type":"", 948 | "visible":true, 949 | "width":16, 950 | "x":1136, 951 | "y":256 952 | }, 953 | { 954 | "height":0, 955 | "id":135, 956 | "name":"player", 957 | "point":true, 958 | "rotation":0, 959 | "type":"", 960 | "visible":true, 961 | "width":0, 962 | "x":32, 963 | "y":400 964 | }, 965 | { 966 | "height":0, 967 | "id":136, 968 | "name":"skull", 969 | "point":true, 970 | "rotation":0, 971 | "type":"", 972 | "visible":true, 973 | "width":0, 974 | "x":240, 975 | "y":336 976 | }], 977 | "opacity":1, 978 | "type":"objectgroup", 979 | "visible":true, 980 | "x":0, 981 | "y":0 982 | }, 983 | { 984 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 42, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 15, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 50, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 15, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57, 50, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 15, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 985 | "height":32, 986 | "id":2, 987 | "name":"Background", 988 | "opacity":1, 989 | "type":"tilelayer", 990 | "visible":true, 991 | "width":77, 992 | "x":0, 993 | "y":0 994 | }, 995 | { 996 | "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 51, 51, 51, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 55, 55, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 51, 51, 0, 51, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 55, 55, 55, 55, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 55, 55, 55, 55, 55, 55, 55, 55, 0, 0, 0, 55, 55, 55, 55, 55, 55, 55, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 23, 23, 23, 23, 23, 23, 23, 23, 0, 0, 0, 23, 23, 23, 23, 23, 23, 23, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 41, 0, 41, 41, 41, 41, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 41, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 51, 0, 0, 51, 51, 51, 51, 51, 51, 0, 0, 0, 51, 51, 51, 51, 0, 0, 0, 16, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 51, 43, 43, 51, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 51, 43, 43, 51, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 55, 0, 0, 51, 51, 51, 51, 51, 51, 0, 0, 0, 51, 51, 51, 51, 0, 0, 0, 16, 0, 0, 0, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 23, 23, 23, 23, 23, 23, 0, 0, 0, 23, 23, 23, 23, 0, 0, 0, 16, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 16, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 16, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 16, 55, 55, 16, 0, 0, 0, 16, 55, 55, 16, 0, 0, 0, 16, 0, 0, 0, 0, 41, 41, 0, 41, 41, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 41, 0, 41, 41, 41, 41, 0, 0, 0, 0, 41, 10, 10, 41, 0, 0, 0, 41, 41, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 10, 10, 10, 10, 41, 41, 41, 10, 10, 41, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 41, 41, 41, 41, 41, 41, 41, 41, 0, 41, 41, 41, 41, 41, 41, 0, 0, 0, 55, 41, 41, 55, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 57, 57, 57, 57, 57, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 0, 0, 41, 41, 41, 41, 41, 41, 41, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 41, 41, 41, 41, 41, 41, 51, 57, 57, 57, 57, 57, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 16, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 51, 57, 57, 57, 57, 57, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 10, 10, 10, 10, 51, 51, 51, 16, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 51, 57, 57, 57, 57, 57, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 51, 57, 57, 57, 57, 57, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 51, 57, 57, 57, 57, 57, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 51, 57, 57, 57, 57, 57], 997 | "height":32, 998 | "id":1, 999 | "name":"Collisions", 1000 | "opacity":1, 1001 | "type":"tilelayer", 1002 | "visible":true, 1003 | "width":77, 1004 | "x":0, 1005 | "y":0 1006 | }], 1007 | "nextlayerid":4, 1008 | "nextobjectid":137, 1009 | "orientation":"orthogonal", 1010 | "renderorder":"right-down", 1011 | "tiledversion":"1.4.3", 1012 | "tileheight":16, 1013 | "tilesets":[ 1014 | { 1015 | "firstgid":1, 1016 | "source":"tilesheet.tsx" 1017 | }], 1018 | "tilewidth":16, 1019 | "type":"map", 1020 | "version":1.4, 1021 | "width":77 1022 | } --------------------------------------------------------------------------------