├── .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 | 
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 | }
--------------------------------------------------------------------------------