├── LICENSE
├── README.md
├── assets
├── angler1.png
├── angler2.png
├── bulbwhale.png
├── drone.png
├── explosion.wav
├── fireExplosion.png
├── fireball.png
├── gears.png
├── hit.wav
├── hivewhale.png
├── layer1.png
├── layer2.png
├── layer3.png
├── layer4.png
├── logo.png
├── lucky.png
├── moonfish.png
├── player.png
├── powerdown.wav
├── powerup.wav
├── projectile.png
├── razorfin.png
├── shield.png
├── shield.wav
├── shot.wav
├── smokeExplosion.png
└── stalker.png
├── index.html
├── script.js
└── style.css
/LICENSE:
--------------------------------------------------------------------------------
1 | ## Copyright 2023, [Julio Quezada]
2 |
3 | ###### The Warrior
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this The Warrior and associated documentation files, to deal in the The Warrior without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of The Warrior, and to permit persons to whom The Warrior is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the The Warrior.
8 |
9 | THE WARRIOR IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE PORTFOLIO OR THE USE OR OTHER DEALINGS IN THE WARRIOR.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Warrior
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
The Warrior
10 |
11 |
12 |
13 |
14 |
15 | # 📗 Table of Contents
16 |
17 | - [The Warrior](#the-warrior)
18 | - [📗 Table of Contents](#-table-of-contents)
19 | - [📖 The Warrior ](#-the-warrior-)
20 | - [🛠 Built With ](#-built-with-)
21 | - [Tech Stack ](#tech-stack-)
22 | - [Key Features ](#key-features-)
23 | - [🚀 Live Demo ](#-live-demo-)
24 | - [💻 Getting Started ](#-getting-started-)
25 | - [Prerequisites](#prerequisites)
26 | - [Setup](#setup)
27 | - [Install](#install)
28 | - [Usage](#usage)
29 | - [Run tests](#run-tests)
30 | - [Deployment](#deployment)
31 | - [👥 Authors ](#-authors-)
32 | - [🔭 Future Features ](#-future-features-)
33 | - [🤝 Contributing ](#-contributing-)
34 | - [⭐️ Show your support ](#️-show-your-support-)
35 | - [🙏 Acknowledgments ](#-acknowledgments-)
36 | - [❓ FAQ (OPTIONAL) ](#-faq-optional-)
37 | - [📝 License ](#-license-)
38 |
39 |
40 |
41 | # 📖 The Warrior
42 |
43 | **The Warrior:** is an interactive, browser-based 2D game developed using pure JavaScript. It features a dynamic environment, unique characters, various props, and intriguing sound effects. The player can control characters via keyboard inputs and utilize various power-ups and weapons to interact with the environment. The game shows a high degree of interactivity and is enriched with visually attractive assets and good sounds, providing a captivating user experience.
44 |
45 | ## 🛠 Built With
46 |
47 | ### Tech Stack
48 |
49 |
50 | Client
51 |
56 |
57 |
58 |
59 |
60 | ### Key Features
61 |
62 | - **Interactive game.**
63 | - **2D game.**
64 |
65 | (back to top )
66 |
67 |
68 |
69 | ## 🚀 Live Demo
70 |
71 | - [Live Demo Link](https://alejandroq12.github.io/javascript-game/)
72 |
73 | (back to top )
74 |
75 |
76 |
77 | ## 💻 Getting Started
78 |
79 | To get a local copy up and running, follow these steps.
80 |
81 | ### Prerequisites
82 |
83 | To execute this project, all you require is a web browser.
84 | Note: If you wish to modify the code, you will also need a code editor such as Visual Studio Code.
85 |
86 |
87 | ### Setup
88 |
89 | Clone this repository to your desired folder:
90 |
91 | ```sh
92 | cd my-folder
93 | git clone https://github.com/Alejandroq12/javascript-game.git
94 | ```
95 |
96 | ### Install
97 |
98 | You can install necessary software or dependencies for this project using the following command:
99 |
100 | No additional installations are necessary due to the nature of this project.
101 |
102 | ### Usage
103 |
104 | To run the project just open the `index.html` file with you web browser.
105 |
106 | ### Run tests
107 |
108 | Test will be available in the future. I am working on it.
109 |
110 | ### Deployment
111 |
112 | You can deploy this project using GitHub pages:
113 |
114 | 1. Log in to your GitHub account and navigate to the repository that contains your website files.
115 | 2. Make sure that your website files are located in the main branch and in the root directory of the repository.
116 | 3. If your website is not already live, make sure that the index.html file is the main page of your website.
117 | 4. Click on the "Settings" tab in your repository.
118 | 5. Scroll down to the "GitHub Pages" section.
119 | 6. In the "Source" dropdown menu, select the branch where your website files are located. For a simple website with only HTML and CSS, this is typically the main branch.
120 | 7. In the "Path" field, make sure that the root directory is specified (i.e., "/").
121 | 8. Click "Save" to generate your website.
122 | 9. Wait a few minutes for GitHub to build and deploy your website.
123 | 10. Once the website is deployed, visit the GitHub Pages URL to view your site.
124 |
125 | (back to top )
126 |
127 |
128 |
129 | ## 👥 Authors
130 |
131 | 👤 **Julio Quezada**
132 |
133 | - GitHub: [Alejandroq12](https://github.com/Alejandroq12)
134 | - Twitter: [@JulioAle54](https://twitter.com/JulioAle54)
135 | - LinkedIn: [Julio Quezada](https://www.linkedin.com/in/quezadajulio/)
136 |
137 | (back to top )
138 |
139 |
140 |
141 | ## 🔭 Future Features
142 |
143 | - [ ] **I will make it responsive.**
144 | - [ ] **I will add more levels.**
145 |
146 | (back to top )
147 |
148 |
149 |
150 | ## 🤝 Contributing
151 |
152 | Contributions, issues, and feature requests are welcome!
153 |
154 | Feel free to check the [issues page](../../issues/).
155 |
156 | (back to top )
157 |
158 |
159 |
160 | ## ⭐️ Show your support
161 |
162 | If you like this project please give a star.
163 | Thanks in advance.
164 |
165 | (back to top )
166 |
167 |
168 |
169 | ## 🙏 Acknowledgments
170 |
171 | I would like to thank Frank Dvorack for creating the course to teach these concepts.
172 |
173 | (back to top )
174 |
175 |
176 |
177 | ## ❓ FAQ (OPTIONAL)
178 |
179 | > Add at least 2 questions new developers would ask when they decide to use your project.
180 |
181 | - **How much time did you invested learning/coding?**
182 |
183 | - I invested 5 hours.
184 |
185 | (back to top )
186 |
187 |
188 |
189 | ## 📝 License
190 |
191 | This project is [MIT](./LICENSE) licensed.
192 |
193 | (back to top )
194 |
195 |
--------------------------------------------------------------------------------
/assets/angler1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/angler1.png
--------------------------------------------------------------------------------
/assets/angler2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/angler2.png
--------------------------------------------------------------------------------
/assets/bulbwhale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/bulbwhale.png
--------------------------------------------------------------------------------
/assets/drone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/drone.png
--------------------------------------------------------------------------------
/assets/explosion.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/explosion.wav
--------------------------------------------------------------------------------
/assets/fireExplosion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/fireExplosion.png
--------------------------------------------------------------------------------
/assets/fireball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/fireball.png
--------------------------------------------------------------------------------
/assets/gears.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/gears.png
--------------------------------------------------------------------------------
/assets/hit.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/hit.wav
--------------------------------------------------------------------------------
/assets/hivewhale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/hivewhale.png
--------------------------------------------------------------------------------
/assets/layer1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/layer1.png
--------------------------------------------------------------------------------
/assets/layer2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/layer2.png
--------------------------------------------------------------------------------
/assets/layer3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/layer3.png
--------------------------------------------------------------------------------
/assets/layer4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/layer4.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/logo.png
--------------------------------------------------------------------------------
/assets/lucky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/lucky.png
--------------------------------------------------------------------------------
/assets/moonfish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/moonfish.png
--------------------------------------------------------------------------------
/assets/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/player.png
--------------------------------------------------------------------------------
/assets/powerdown.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/powerdown.wav
--------------------------------------------------------------------------------
/assets/powerup.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/powerup.wav
--------------------------------------------------------------------------------
/assets/projectile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/projectile.png
--------------------------------------------------------------------------------
/assets/razorfin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/razorfin.png
--------------------------------------------------------------------------------
/assets/shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/shield.png
--------------------------------------------------------------------------------
/assets/shield.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/shield.wav
--------------------------------------------------------------------------------
/assets/shot.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/shot.wav
--------------------------------------------------------------------------------
/assets/smokeExplosion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/smokeExplosion.png
--------------------------------------------------------------------------------
/assets/stalker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Alejandroq12/javascript-game/8a535236801358e059b365a238cce6e2c8db0fd6/assets/stalker.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The Warrior
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', function(){
2 | //canvas setup
3 | const canvas = document.getElementById('canvas1');
4 | const ctx = canvas.getContext('2d');
5 | canvas.width = 1000;
6 | canvas.height = 500;
7 |
8 | class InputHandler {
9 | constructor(game){
10 | this.game = game;
11 | window.addEventListener('keydown', e => {
12 | if (( (e.key === 'ArrowUp') ||
13 | (e.key === 'ArrowDown')
14 | ) && this.game.keys.indexOf(e.key) === -1){
15 | this.game.keys.push(e.key);
16 | } else if ( e.key === ' '){
17 | this.game.player.shootTop();
18 | } else if ( e.key === 'd'){
19 | this.game.debug = !this.game.debug;
20 | }
21 | });
22 | window.addEventListener('keyup', e =>{
23 | if(this.game.keys.indexOf(e.key) > -1){
24 | this.game.keys.splice(this.game.keys.indexOf(e.key), 1);
25 | }
26 | });
27 | }
28 | }
29 | class SoundController {
30 | constructor(){
31 | this.powerUpSound = document.getElementById('powerup');
32 | this.powerDownSound = document.getElementById('powerdown');
33 | this.explosionSound = document.getElementById('explosion');
34 | this.shotSound = document.getElementById('shot');
35 | this.hitSound = document.getElementById('hit');
36 | this.shieldSound = document.getElementById('shieldSound');
37 |
38 | }
39 | powerUp(){
40 | this.powerUpSound.currentTime = 0;
41 | this.powerUpSound.play();
42 | }
43 | powerDown(){
44 | this.powerDownSound.currentTime = 0;
45 | this.powerDownSound.play();
46 | }
47 | explosion(){
48 | this.explosionSound.currentTime = 0;
49 | this.explosionSound.play();
50 | }
51 | shot(){
52 | this.shotSound.currentTime = 0;
53 | this.shotSound.play();
54 | }
55 | hit(){
56 | this.hitSound.currentTime = 0;
57 | this.hitSound.play();
58 | }
59 | shield(){
60 | this.shieldSound.currentTime = 0;
61 | this.shieldSound.play();
62 | }
63 | }
64 | class Shield {
65 | constructor(game){
66 | this.game = game;
67 | this.width = this.game.player.width;
68 | this.height = this.game.player.height;
69 | this.frameX = 0;
70 | this.maxFrame = 24;
71 | this.image = document.getElementById('shield');
72 | this.fps = 30;
73 | this.timer = 0;
74 | this.interval = 1000/this.fps;
75 | }
76 | update(deltaTime){
77 | if (this.frameX <= this.maxFrame){
78 | if (this.timer > this.interval){
79 | this.frameX++;
80 | this.timer = 0;
81 | } else {
82 | this.timer += deltaTime;
83 | }
84 | }
85 | }
86 | draw(context){
87 | context.drawImage(this.image, this.frameX * this.width, 0,
88 | this.width, this.height, this.game.player.x, this.game.player.y, this.width, this.height);
89 | }
90 | reset(){
91 | this.frameX = 0;
92 | this.game.sound.shield();
93 | }
94 | }
95 | class Projectile {
96 | constructor(game, x, y){
97 | this.game = game;
98 | this.x = x;
99 | this.y = y;
100 | this.width = 36.25;
101 | this.height = 20;
102 | this.speed = Math.random() * 0.2 + 2.8;
103 | this.markedForDeletion = false;
104 | this.image = document.getElementById('fireball');
105 | this.frameX = 0;
106 | this.maxFrame = 3;
107 | this.fps = 10;
108 | this.timer = 0;
109 | this.interval = 1000/this.fps;
110 | }
111 | update(deltaTime){
112 | this.x += this.speed;
113 | if (this.timer > this.interval){
114 | if (this.frameX < this.maxframe) this.frameX++;
115 | else this.frameX = 0;
116 | this.timer = 0;
117 | } else {
118 | this.timer += deltaTime;
119 | }
120 | if (this.x > this.game.width * 0.8) this.markedForDeletion = true;
121 | }
122 | draw(context){
123 | context.drawImage(this.image, this.frameX * this.width, 0, this.width, this.height, this.x, this.y, this.width, this.height);
124 | }
125 | }
126 | class Particle {
127 | constructor(game, x, y,){
128 | this.game = game;
129 | this.x = x;
130 | this.y = y;
131 | this.image = document.getElementById('gears');
132 | this.frameX = Math.floor(Math.random() * 3);
133 | this.frameY = Math.floor(Math.random() * 3);
134 | this.spriteSize = 50;
135 | this.sizeModifier = (Math.random() * 0.5 + 0.5).toFixed(1);
136 | this.size = this.spriteSize * this.sizeModifier;
137 | this.speedX = Math.random() * 6 - 3;
138 | this.speedY = Math.random() * -15;
139 | this.gravity = 0.5;
140 | this.markedForDeletion = false;
141 | this.angle = 0;
142 | this.va = Math.random() * 0.2 - 0.1;
143 | this.bounced = 0;
144 | this.bottomBounceBoundary = Math.random() * 80 + 60;
145 | }
146 | update(){
147 | this.angle += this.va;
148 | this.speedY += this.gravity;
149 | this.x -= this.speedX + this.game.speed;
150 | this.y += this.speedY;
151 | if(this.y > this.game.height + this.size || this.x < 0 - this.size) this.markedForDeletion = true;
152 | if (this.y > this.game.height - this.bottomBounceBoundary && !this.bounced < 5){
153 | this.bounced++;
154 | this.speedY *= -0.5;
155 | }
156 | }
157 | draw(context){
158 | context.save();
159 | context.translate(this.x, this.y);
160 | context.rotate(this.angle);
161 | context.drawImage(this.image, this.frameX * this.spriteSize, this.frameY * this.spriteSize, this.spriteSize, this.spriteSize, this.size * -0.5, this.size * -0.5, this.size, this.size);
162 | context.restore();
163 | }
164 | }
165 | class Player {
166 | constructor(game){
167 | this.game = game;
168 | this.width = 120;
169 | this.height = 190;
170 | this.x = 20;
171 | this.y = 100;
172 | this.frameX = 0;
173 | this.frameY = 0;
174 | this.maxFrame = 37;
175 | this.speedY = 0;
176 | this.maxSpeed = 3;
177 | this.projectiles = [];
178 | this.image = document.getElementById('player');
179 | this.powerUp = false;
180 | this.PowerUpTimer = 0;
181 | this.powerUpLimit = 10000;
182 |
183 | }
184 | update(deltaTime){
185 | if (this.game.keys.includes('ArrowUp')) this.speedY = -this.maxSpeed;
186 | else if (this.game.keys.includes('ArrowDown')) this.speedY = this.maxSpeed;
187 | else this.speedY = 0;
188 | this.y += this.speedY;
189 | // vertical boundaries
190 | if(this.y > this.game.height - this.height * 0.5) this.y = this.game.height - this.height * 0.5;
191 | else if (this.y < -this.height * 0.5) this.y = -this.height * 0.5;
192 | // handle projectiles
193 | this.projectiles.forEach(projectile => {
194 | projectile.update(deltaTime);
195 | });
196 | this.projectiles = this.projectiles.filter(projectile => !projectile.markedForDeletion)
197 | // sprite animation
198 | if (this.frameX < this.maxFrame){
199 | this.frameX++;
200 | } else {
201 | this.frameX = 0;
202 | }
203 | // power up
204 | if (this.powerUp){
205 | if(this.powerUpTimer > this.powerUpLimit){
206 | this.powerUpTimer = 0;
207 | this.powerUp = false;
208 | this.frameY = 0;
209 | this.game.sound.powerDown();
210 | } else {
211 | this.powerUpTimer += deltaTime;
212 | this.frameY = 1;
213 | this.game.ammo += 0.1;
214 | }
215 | }
216 | }
217 | draw(context){
218 | if (this.game.debug)context.strokeRect(this.x, this.y, this.width, this.height);
219 | this.projectiles.forEach(projectile => {
220 | projectile.draw(context);
221 | });
222 | context.drawImage(this.image, this.frameX * this.width, this.frameY * this.height, this.width, this.height, this.x, this.y, this.width, this.height);
223 |
224 | }
225 | shootTop(){
226 | if (this.game.ammo > 0){
227 | this.projectiles.push(new Projectile(this.game, this.x + 80, this.y + 30));
228 | this.game.ammo--;
229 | }
230 | this.game.sound.shot();
231 | if (this.powerUp) this.shootBottom();
232 | }
233 | shootBottom(){
234 | if (this.game.ammo > 0){
235 | this.projectiles.push(new Projectile(this.game, this.x + 80, this.y + 175));
236 | }
237 | }
238 | enterPowerUp(){
239 | this.powerUpTimer = 0;
240 | this.powerUp = true;
241 | if (this.game.ammo < this.game.maxAmmo) this.game.ammo = this.game.maxAmmo;
242 | this.game.sound.powerUp();
243 | }
244 | }
245 |
246 | class Enemy {
247 | constructor(game){
248 | this.game = game;
249 | this.x = this.game.width;
250 | this.speedX = Math.random() * -1.5 - 0.5;
251 | this.markedForDeletion = false;
252 | this.frameX = 0;
253 | this.frameY = 0;
254 | this.maxFrame = 37;
255 | }
256 | update(){
257 | this.x += this.speedX - this.game.speed;
258 | if (this.x + this.width < 0) this.markedForDeletion = true;
259 | // sprite animation
260 | if (this.frameX < this.maxFrame){
261 | this.frameX++;
262 | } else this.frameX = 0;
263 | }
264 | draw(context){
265 | if (this.game.debug) context.strokeRect(this.x, this.y, this.width, this.height);
266 | context.drawImage(this.image, this.frameX * this.width, this.frameY * this.height, this.width, this.height, this.x, this.y, this.width, this.height);
267 | if (this.game.debug){
268 | context.font = '20px Helvetica';
269 | context.fillText(this.lives, this.x, this.y);
270 | }
271 | }
272 | }
273 | class Angler1 extends Enemy {
274 | constructor(game){
275 | super(game);
276 | this.width = 228;
277 | this.height = 169;
278 | this.y = Math.random() * (this.game.height * 0.95 - this.height);
279 | this.image = document.getElementById('angler1');
280 | this.frameY = Math.floor(Math.random() * 3);
281 | this.lives = 5;
282 | this.score = this.lives;
283 | }
284 | }
285 | class Angler2 extends Enemy {
286 | constructor(game){
287 | super(game);
288 | this.width = 213;
289 | this.height = 165;
290 | this.y = Math.random() * (this.game.height * 0.95 - this.height);
291 | this.image = document.getElementById('angler2');
292 | this.frameY = Math.floor(Math.random() * 2);
293 | this.lives = 6;
294 | this.score = this.lives;
295 | }
296 | }
297 | class LuckyFish extends Enemy {
298 | constructor(game){
299 | super(game);
300 | this.width = 99;
301 | this.height = 95;
302 | this.y = Math.random() * (this.game.height * 0.95 - this.height);
303 | this.image = document.getElementById('lucky');
304 | this.frameY = Math.floor(Math.random() * 2);
305 | this.lives = 5;
306 | this.score = 15;
307 | this.type = 'lucky';
308 | }
309 | }
310 | class HiveWhale extends Enemy {
311 | constructor(game){
312 | super(game);
313 | this.width = 400;
314 | this.height = 227;
315 | this.y = Math.random() * (this.game.height * 0.9 - this.height);
316 | this.image = document.getElementById('hivewhale');
317 | this.frameY = 0;
318 | this.lives = 20;
319 | this.score = this.lives;
320 | this.type = 'hive';
321 | this.speedX = Math.random() * -1.2 - 0.2;
322 | }
323 | }
324 | class Drone extends Enemy {
325 | constructor(game, x, y){
326 | super(game);
327 | this.width = 115;
328 | this.height = 95;
329 | this.x = x;
330 | this.y = y;
331 | this.image = document.getElementById('drone');
332 | this.frameY = Math.floor(Math.random() * 2);
333 | this.lives = 3;
334 | this.score = this.lives;
335 | this.type = 'drone';
336 | this.speedX = Math.random() * -4.2 - 0.5;
337 | }
338 | }
339 | class BulbWhale extends Enemy {
340 | constructor(game){
341 | super(game);
342 | this.width = 270;
343 | this.height = 219;
344 | this.y = Math.random() * (this.game.height * 0.9 - this.height);
345 | this.image = document.getElementById('bulbwhale');
346 | this.frameY = Math.floor(Math.random() * 2);
347 | this.lives = 20;
348 | this.score = this.lives;
349 | this.speedX = Math.random() * -1.2 - 0.2;
350 | }
351 | }
352 | class MoonFish extends Enemy {
353 | constructor(game){
354 | super(game);
355 | this.width = 227;
356 | this.height = 240;
357 | this.y = Math.random() * (this.game.height * 0.9 - this.height);
358 | this.image = document.getElementById('moonfish');
359 | this.frameY = 0;
360 | this.lives = 10;
361 | this.score = this.lives;
362 | this.speedX = Math.random() * -1.2 - 2;
363 | this.type = 'moon';
364 | }
365 | }
366 | class Stalker extends Enemy {
367 | constructor(game){
368 | super(game);
369 | this.width = 243;
370 | this.height = 123;
371 | this.y = Math.random() * (this.game.height * 0.95 - this.height);
372 | this.image = document.getElementById('stalker');
373 | this.frameY = 0;
374 | this.lives = 5;
375 | this.score = this.lives;
376 | this.speedX = Math.random() * -1 - 1;
377 | }
378 | }
379 | class RazorFin extends Enemy {
380 | constructor(game){
381 | super(game);
382 | this.width = 187;
383 | this.height = 149;
384 | this.y = Math.random() * (this.game.height * 0.95 - this.height);
385 | this.image = document.getElementById('razorfin');
386 | this.frameY = 0;
387 | this.lives = 7;
388 | this.score = this.lives;
389 | this.speedX = Math.random() * -1 - 1;
390 | }
391 | }
392 | class Layer {
393 | constructor(game, image, speedModifier){
394 | this.game = game;
395 | this.image = image;
396 | this.speedModifier = speedModifier;
397 | this.width = 1768;
398 | this.height = 500;
399 | this.x = 0;
400 | this.y = 0;
401 | }
402 | update(){
403 | if (this.x <= -this.width) this.x = 0;
404 | this.x -= this.game.speed * this.speedModifier;
405 | }
406 | draw(context){
407 | context.drawImage(this.image, this.x, this.y);
408 | context.drawImage(this.image, this.x + this.width, this.y);
409 |
410 | }
411 | }
412 | class Background {
413 | constructor(game){
414 | this.game = game;
415 | this.image1 = document.getElementById('layer1');
416 | this.image2 = document.getElementById('layer2');
417 | this.image3 = document.getElementById('layer3');
418 | this.image4 = document.getElementById('layer4');
419 | this.layer1 = new Layer(this.game, this.image1, 0.2);
420 | this.layer2 = new Layer(this.game, this.image2, 0.4);
421 | this.layer3 = new Layer(this.game, this.image3, 1);
422 | this.layer4 = new Layer(this.game, this.image4, 1.5);
423 | this.layers = [this.layer1, this.layer2, this.layer3];
424 | }
425 | update(){
426 | this.layers.forEach(layer => layer.update());
427 | }
428 | draw(context){
429 | this.layers.forEach(layer => layer.draw(context));
430 |
431 | }
432 | }
433 |
434 | class Explosion {
435 | constructor(game, x, y){
436 | this.game = game;
437 | this.frameX = 0;
438 | this.spriteWidth = 200;
439 | this.spriteHeight = 200;
440 | this.width = this.spriteWidth;
441 | this.height = this.spriteHeight;
442 | this.x = x - this.width * 0.5;
443 | this.y = y - this.height* 0.5;
444 | this.fps = 30;
445 | this.timer = 0;
446 | this.interval = 1000/this.fps;
447 | this.markedForDeletion = false;
448 | this.maxFrame = 8;
449 | }
450 | update(deltaTime){
451 | this.x -= this.game.speed;
452 | if (this.timer > this.interval){
453 | this.frameX++;
454 | this.timer = 0;
455 | } else {
456 | this.timer += deltaTime;
457 | }
458 | if (this.frameX > this.maxFrame) this.markedForDeletion = true;
459 | }
460 | draw(context){
461 | context.drawImage(this.image, this.frameX * this.spriteWidth, 0, this.spriteWidth, this.spriteHeight, this.x, this.y, this.width, this.height);
462 | }
463 | }
464 |
465 | class SmokeExplosion extends Explosion {
466 | constructor(game, x, y){
467 | super(game, x, y);
468 | this.image = document.getElementById('smokeExplosion');
469 | }
470 | }
471 |
472 | class FireExplosion extends Explosion {
473 | constructor(game, x, y){
474 | super(game, x, y);
475 | this.image = document.getElementById('fireExplosion');
476 | }
477 | }
478 |
479 | class UI {
480 | constructor(game){
481 | this.game = game;
482 | this.fontSize = 25;
483 | this.fontFamily = 'Bangers';
484 | this.color = 'white';
485 | }
486 | draw(context){
487 | context.save();
488 | context.fillStyle = this.color;
489 | context.shadowOffsetX = 2;
490 | context.shadowOffsetY = 2;
491 | context.shadowColor = 'black';
492 | context.font = this.fontSize + 'px ' + this.fontFamily;
493 | // score
494 | context.fillText('Score: ' + this.game.score, 20, 40);
495 | // timer
496 | const formattedTime = (this.game.gameTime * 0.001).toFixed(1);
497 | context.fillText('Timer: ' + formattedTime, 20, 100);
498 | // game over messages
499 | if (this.game.gameOver){
500 | context.textAlign = 'center';
501 | let message1;
502 | let message2;
503 | if (this.game.score > this.game.winningScore){
504 | message1 = 'Most Wondrous!';
505 | message2 = 'Well done explorer!';
506 | } else {
507 | message1 = 'Blazes!';
508 | message2 = 'Get my repair kit and try again!';
509 | }
510 | context.font = '70px ' + this.fontFamily;
511 | context.fillText(message1, this.game.width * 0.5, this.game.height * 0.5 - 20);
512 | context.font = '25px ' + this.fontFamily;
513 | context.fillText(message2, this.game.width * 0.5, this.game.height * 0.5 + 20);
514 | }
515 | // ammo
516 | if (this.game.player.powerUp) context.fillStyle = '#ffffbd';
517 | for (let i = 0; i < this.game.ammo; i++){
518 | context.fillRect(20 + 5 * i, 50, 3, 20);
519 | }
520 | context.restore();
521 | }
522 | }
523 | class Game {
524 | constructor(width, height){
525 | this.width = width;
526 | this.height = height;
527 | this.background = new Background(this);
528 | this.player = new Player(this);
529 | this.input = new InputHandler(this);
530 | this.ui = new UI(this);
531 | this.sound = new SoundController();
532 | this.shield = new Shield(this);
533 | this.keys = [];
534 | this.enemies = [];
535 | this.particles = [];
536 | this.explosions = [];
537 | this.enemyTimer = 0;
538 | this.enemyInterval = 2000;
539 | this.ammo = 20;
540 | this.maxAmmo = 50;
541 | this.ammoTimer = 0;
542 | this.ammoInterval = 350;
543 | this.gameOver = false;
544 | this.score = 0;
545 | this.winningScore = 80;
546 | this.gameTime = 0;
547 | this.timeLimit = 30000;
548 | this.speed = 1;
549 | this.debug = false;
550 | }
551 | update(deltaTime){
552 | if (!this.gameOver) this.gameTime += deltaTime;
553 | if (this.gameTime > this.timeLimit) this.gameOver = true;
554 | this.background.update();
555 | this.background.layer4.update();
556 | this.player.update(deltaTime);
557 | if (this.ammoTimer > this.ammoInterval){
558 | if (this.ammo < this.maxAmmo) this.ammo++;
559 | this.ammoTimer = 0;
560 | } else {
561 | this.ammoTimer += deltaTime;
562 | }
563 | this.shield.update(deltaTime);
564 | this.particles.forEach(particle => particle.update());
565 | this.particles = this.particles.filter(particle => !particle.markedForDeletion);
566 | this.explosions.forEach(explosion => explosion.update(deltaTime));
567 | this.explosions = this.explosions.filter(explosion => !explosion.markedForDeletion);
568 | this.enemies.forEach(enemy => {
569 | enemy.update();
570 | if (this.checkCollision(this.player, enemy)){
571 | enemy.markedForDeletion = true;
572 | this.addExplosion(enemy);
573 | this.sound.hit();
574 | this.shield.reset();
575 | for (let i = 0; i < enemy.score; i++){
576 | this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5));
577 | }
578 | if (enemy.type === 'lucky') this.player.enterPowerUp();
579 | else if (!this.gameOver) this.score--;
580 | }
581 | this.player.projectiles.forEach(projectile => {
582 | if(this.checkCollision(projectile, enemy)){
583 | enemy.lives--;
584 | projectile.markedForDeletion = true;
585 | this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5));
586 | if (enemy.lives <= 0){
587 | for (let i = 0; i < enemy.score; i++){
588 | this.particles.push(new Particle(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5));
589 | }
590 | enemy.markedForDeletion = true;
591 | this.addExplosion(enemy);
592 | this.sound.explosion();
593 | if (enemy.type === 'moon' ) this.player.enterPowerUp();
594 | if (enemy.type === 'hive'){
595 | for (let i = 0; i < 5; i++){
596 | this.enemies.push(new Drone(this, enemy.x + Math.random() * enemy.width, enemy.y + Math.random() * enemy.height * 0.5));
597 | }
598 | }
599 | if (!this.gameOver) this.score += enemy.score;
600 | if (this.score > this.winningScore) this.gameOver = true;
601 | }
602 | }
603 | })
604 | });
605 | this.enemies = this.enemies.filter(enemy => !enemy.markedForDeletion);
606 | if (this.enemyTimer > this.enemyInterval && !this.gameOver ){
607 | this.addEnemy();
608 | this.enemyTimer = 0;
609 | } else {
610 | this.enemyTimer += deltaTime;
611 | }
612 | }
613 | draw(context){
614 | this.background.draw(context);
615 | this.ui.draw(context);
616 | this.player.draw(context);
617 | this.shield.draw(context);
618 | this.particles.forEach(particle => particle.draw(context));
619 | this.enemies.forEach(enemy => {
620 | enemy.draw(context);
621 | });
622 | this.explosions.forEach(explosion => {
623 | explosion.draw(context);
624 | });
625 | this.background.layer4.draw(context);
626 | }
627 | addEnemy(){
628 | const randomize = Math.random();
629 | if (randomize < 0.1) this.enemies.push(new Angler1(this));
630 | else if (randomize < 0.3) this.enemies.push(new Stalker(this));
631 | else if (randomize < 0.5) this.enemies.push(new RazorFin(this));
632 | else if (randomize < 0.6) this.enemies.push(new Angler2(this));
633 | else if (randomize < 0.7) this.enemies.push(new HiveWhale(this));
634 | else if (randomize < 0.8) this.enemies.push(new BulbWhale(this));
635 | else if (randomize < 0.9) this.enemies.push(new MoonFish(this));
636 | else this.enemies.push(new LuckyFish(this));
637 | }
638 | addExplosion(enemy){
639 | const randomize = Math.random();
640 | if (randomize < 0.5) {
641 | this.explosions.push(new SmokeExplosion(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5));
642 | } else {
643 | this.explosions.push(new FireExplosion(this, enemy.x + enemy.width * 0.5, enemy.y + enemy.height * 0.5));
644 | }
645 | }
646 | checkCollision(rect1, rect2){
647 | return ( rect1.x < rect2.x + rect2.width &&
648 | rect1.x + rect1.width > rect2.x &&
649 | rect1.y < rect2.y + rect2.height &&
650 | rect1.height + rect1.y > rect2.y)
651 | }
652 | }
653 | const game = new Game(canvas.width, canvas.height);
654 | let lastTime = 0;
655 | //animation loop
656 | function animate(timeStamp){
657 | const deltaTime = timeStamp - lastTime;
658 | lastTime = timeStamp;
659 | ctx.clearRect(0, 0, canvas.width, canvas.height);
660 | game.draw(ctx);
661 | game.update(deltaTime);
662 | requestAnimationFrame(animate);
663 | }
664 | animate(0);
665 | });
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 | #canvas1 {
7 | border: 5px solid black;
8 | position: absolute;
9 | top: 50%;
10 | left: 50%;
11 | transform: translate(-50%, -50%);
12 | background: #4d79bc;
13 | max-width: 100%;
14 | max-height: 100%;
15 | font-family: 'Bangers', cursive;
16 | }
17 |
18 | #layer1,
19 | #layer2,
20 | #layer3,
21 | #layer4,
22 | #player,
23 | #angler1,
24 | #angler2,
25 | #lucky,
26 | #projectile,
27 | #gears,
28 | #hivewhale,
29 | #drone,
30 | #smokeExplosion,
31 | #fireExplosion,
32 | #bulbwhale,
33 | #moonfish,
34 | #shield,
35 | #fireball,
36 | #stalker,
37 | #razorfin {
38 | display: none;
39 | }
40 |
--------------------------------------------------------------------------------