folder names
12
6-space-game/6-end-condition/.github/post-lecture-quiz.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*Complete this quiz after the lesson by checking one answer per question.*
|
||||
|
||||
1. What is a good pattern to use when a game end condition has been met?
|
||||
|
||||
- [ ] Display a suitable message
|
||||
- [ ] Quit the game
|
||||
- [ ] Display a suitable message, offer the player to restart, and display what key to hit for that action
|
||||
|
||||
1. You should offer a restart only when the game has ended
|
||||
|
||||
- [ ] true
|
||||
- [ ] false
|
14
6-space-game/6-end-condition/.github/pre-lecture-quiz.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
*A warm-up quiz about game development*
|
||||
|
||||
Complete this quiz in class
|
||||
|
||||
1. When is a good time to restart a game
|
||||
|
||||
- [ ] when a player wins or loses
|
||||
- [ ] whenever
|
||||
|
||||
2. When should a game end
|
||||
|
||||
- [ ] when an enemy ship is destroyed
|
||||
- [ ] when a hero ship is destroyed
|
||||
- [ ] when points are collected
|
21
6-space-game/6-end-condition/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 WebDev-For-Beginners
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
218
6-space-game/6-end-condition/README.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Build a Space Game Part VI: End and Restart
|
||||
|
||||
## [Pre-lecture quiz](.github/pre-lecture-quiz.md)
|
||||
|
||||
There are different ways to express and *end condition* in a game. It's up to you as the creator of the game to say why the game has ended. Here are some reasons, if we assume we are talking about the space game you have been building so far:
|
||||
|
||||
- **`N` Enemy ships have been destroyed**: It's quite common if you divide up a game into different levels that you need to destroy `N` Enemy ships to complete a level
|
||||
- **Your ship has been destroyed**: There are definitely games where you lose the game if your ship is destroyed. Another common approach is that you have the concept of lives. Every time a your ship is destroyed it deducts a life. Once all lives have been lost then you lose the game.
|
||||
- **You've collected `N` points**: Another common end condition is for you to collect points. How you get points is up to you but it's quite common to assign points to various activities like destroying an enemy ship or maybe collect items that items *drop* when they are destroyed.
|
||||
- **Complete a level**: This might involve several conditions such as `X` enemy ships destroyed, `Y` points collected or maybe that a specific item has been collected.
|
||||
|
||||
## Restarting
|
||||
|
||||
If people enjoy your game they are likely to want to replay it. Once the game ends for whatever reason you should offer an alternative to restart.
|
||||
|
||||
✅ Think a bit about under what conditions you find a game ends, and then how you are prompted to restart
|
||||
|
||||
## What to build
|
||||
|
||||
You will be adding these rules to your game:
|
||||
|
||||
1. **Winning the game**. Once all enemy ships have been destroyed, you win the game. Additionally display some kind of victory message.
|
||||
1. **Restart**. Once all your lives are lost or the game is won, you should offer a way to restart the game. Remember! You will need to reinitialize the game and the previous game state should be cleared.
|
||||
|
||||
## Recommended steps
|
||||
|
||||
Locate the files that have been created for you in the `your-work` sub folder. It should contain the following:
|
||||
|
||||
```bash
|
||||
-| assets
|
||||
-| enemyShip.png
|
||||
-| player.png
|
||||
-| laserRed.png
|
||||
-| life.png
|
||||
-| index.html
|
||||
-| app.js
|
||||
-| package.json
|
||||
```
|
||||
|
||||
You start your project the `your_work` folder by typing:
|
||||
|
||||
```bash
|
||||
cd your-work
|
||||
npm start
|
||||
```
|
||||
|
||||
The above will start a HTTP Server on address `http://localhost:5000`. Open up a browser and input that address. Your game should be in a playable state.
|
||||
|
||||
> tip: to avoid warnings in Visual Studio Code, edit the `window.onload` function to call `gameLoopId` as is (without `let`), and declare the gameLoopId at the top of the file, independently: `let gameLoopId;`
|
||||
|
||||
### Add code
|
||||
|
||||
1. **Track end condition**. Add code that keeps track of the number of enemies, or if the hero ship has been destroyedby adding these two functions:
|
||||
|
||||
```javascript
|
||||
function isHeroDead() {
|
||||
return hero.life <= 0;
|
||||
}
|
||||
|
||||
function isEnemiesDead() {
|
||||
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
|
||||
return enemies.length === 0;
|
||||
}
|
||||
```
|
||||
|
||||
1. **Add logic to message handlers**. Edit the `eventEmitter` to handle these conditions:
|
||||
|
||||
```javascript
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
||||
first.dead = true;
|
||||
second.dead = true;
|
||||
hero.incrementPoints();
|
||||
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
|
||||
enemy.dead = true;
|
||||
hero.decrementLife();
|
||||
if (isHeroDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_LOSS);
|
||||
return; // loss before victory
|
||||
}
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.GAME_END_WIN, () => {
|
||||
endGame(true);
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.GAME_END_LOSS, () => {
|
||||
endGame(false);
|
||||
});
|
||||
```
|
||||
|
||||
1. **Add new message types**. Add these Messages to the constants object:
|
||||
|
||||
```javascript
|
||||
GAME_END_LOSS: "GAME_END_LOSS",
|
||||
GAME_END_WIN: "GAME_END_WIN",
|
||||
```
|
||||
|
||||
2. **Add restart code** code that restarts the game at the press of a selected button.
|
||||
|
||||
1. **Listen to key press `Enter`**. Edit your window's eventListener to listen for this press:
|
||||
|
||||
```javascript
|
||||
else if(evt.key === "Enter") {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
|
||||
}
|
||||
```
|
||||
|
||||
1. **Add restart message**. Add this Message to your Messages constant:
|
||||
|
||||
```javascript
|
||||
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
|
||||
```
|
||||
|
||||
1. **Implement game rules**. Implement the following game rules:
|
||||
|
||||
1. **Player win condition**. When all enemy ships are destroyed, display a victory message.
|
||||
|
||||
1. First, create a `displayMessage()` function:
|
||||
|
||||
```javascript
|
||||
function displayMessage(message, color = "red") {
|
||||
ctx.font = "30px Arial";
|
||||
ctx.fillStyle = color;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
```
|
||||
|
||||
1. Create an `endGame()` function:
|
||||
|
||||
```javascript
|
||||
function endGame(win) {
|
||||
clearInterval(gameLoopId);
|
||||
|
||||
// set a delay so we are sure any paints have finished
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
if (win) {
|
||||
displayMessage(
|
||||
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
|
||||
"green"
|
||||
);
|
||||
} else {
|
||||
displayMessage(
|
||||
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
|
||||
);
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
```
|
||||
|
||||
1. **Restart logic**. When all lives are lost or the player won the game, display that the game can be restarted. Additionally restart the game when the *restart* key is hit (you can decide what key should be mapped to restart).
|
||||
|
||||
1. Create the `resetGame()` function:
|
||||
|
||||
```javascript
|
||||
function resetGame() {
|
||||
if (gameLoopId) {
|
||||
clearInterval(gameLoopId);
|
||||
eventEmitter.clear();
|
||||
initGame();
|
||||
gameLoopId = setInterval(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
drawPoints();
|
||||
drawLife();
|
||||
updateGameObjects();
|
||||
drawGameObjects(ctx);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Add a call to the `eventEmitter` to reset the game in `initGame()`:
|
||||
|
||||
```javascript
|
||||
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
|
||||
resetGame();
|
||||
});
|
||||
```
|
||||
|
||||
1. Add a `clear()` function to the EventEmitter:
|
||||
|
||||
```javascript
|
||||
clear() {
|
||||
this.listeners = {};
|
||||
}
|
||||
```
|
||||
|
||||
👽 💥 🚀 Congratulations, Captain! Your game is complete! Well done! 🚀 💥 👽
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Challenge
|
||||
|
||||
Add a sound! Can you add a sound to enhance your game play, maybe when there's a laser hit, or the hero dies or wins? Have a look at this [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) to learn how to play sound using JavaScript
|
||||
|
||||
## [Post-lecture quiz](.github/post-lecture-quiz.md)
|
||||
|
||||
## Review & Self Study
|
||||
|
||||
Your assignment is to create a fresh sample game, so explore some of the interesting games out there to see what type of game you might build.
|
||||
|
||||
## Assignment
|
||||
|
||||
[Build a Sample Game](assignment.md)
|
19
6-space-game/6-end-condition/assignment.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Build a Sample Game
|
||||
|
||||
## Instructions
|
||||
|
||||
Try building a small game where you practice on different end conditions. Vary between getting a number of points, the hero loses all lives or all monsters are defeated. Build something simple like a console based adventure game. Use the below game flow as inspiration:
|
||||
|
||||
```
|
||||
Hero> Strikes with broadsword - orc takes 3p damage
|
||||
Orc> Hits with club - hero takes 2p damage
|
||||
Hero> Kicks - orc takes 1p damage
|
||||
Game> Orc is defeated - Hero collects 2 coins
|
||||
Game> ****No more monsters, you have conquered the evil fortress****
|
||||
```
|
||||
|
||||
## Rubric
|
||||
|
||||
| Criteria | Exemplary | Adequate | Needs Improvement |
|
||||
| -------- | ---------------------- | --------------------------- | -------------------------- |
|
||||
| | full game is presented | game is partially presented | partial game contains bugs |
|
392
6-space-game/6-end-condition/solution/app.js
Normal file
@@ -0,0 +1,392 @@
|
||||
// @ts-check
|
||||
let gameLoopId;
|
||||
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
on(message, listener) {
|
||||
if (!this.listeners[message]) {
|
||||
this.listeners[message] = [];
|
||||
}
|
||||
this.listeners[message].push(listener);
|
||||
}
|
||||
|
||||
emit(message, payload = null) {
|
||||
if (this.listeners[message]) {
|
||||
this.listeners[message].forEach((l) => l(message, payload));
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
this.listeners = {};
|
||||
}
|
||||
}
|
||||
|
||||
class GameObject {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.dead = false;
|
||||
this.type = '';
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.img = undefined;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
|
||||
}
|
||||
|
||||
rectFromGameObject() {
|
||||
return {
|
||||
top: this.y,
|
||||
left: this.x,
|
||||
bottom: this.y + this.height,
|
||||
right: this.x + this.width,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Hero extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 99), (this.height = 75);
|
||||
this.type = 'Hero';
|
||||
this.speed = { x: 0, y: 0 };
|
||||
this.cooldown = 0;
|
||||
this.life = 3;
|
||||
this.points = 0;
|
||||
}
|
||||
fire() {
|
||||
gameObjects.push(new Laser(this.x + 45, this.y - 10));
|
||||
this.cooldown = 500;
|
||||
|
||||
let id = setInterval(() => {
|
||||
if (this.cooldown > 0) {
|
||||
this.cooldown -= 100;
|
||||
} else {
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
canFire() {
|
||||
return this.cooldown === 0;
|
||||
}
|
||||
decrementLife() {
|
||||
this.life--;
|
||||
if (this.life === 0) {
|
||||
this.dead = true;
|
||||
}
|
||||
}
|
||||
incrementPoints() {
|
||||
this.points += 100;
|
||||
}
|
||||
}
|
||||
|
||||
class Enemy extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 98), (this.height = 50);
|
||||
this.type = 'Enemy';
|
||||
let id = setInterval(() => {
|
||||
if (this.y < canvas.height - this.height) {
|
||||
this.y += 5;
|
||||
} else {
|
||||
console.log('Stopped at', this.y);
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
class Laser extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 9), (this.height = 33);
|
||||
this.type = 'Laser';
|
||||
this.img = laserImg;
|
||||
let id = setInterval(() => {
|
||||
if (this.y > 0) {
|
||||
this.y -= 15;
|
||||
} else {
|
||||
this.dead = true;
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function loadTexture(path) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = path;
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function intersectRect(r1, r2) {
|
||||
return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top);
|
||||
}
|
||||
|
||||
const Messages = {
|
||||
KEY_EVENT_ENTER: 'KEY_EVENT_ENTER',
|
||||
KEY_EVENT_UP: 'KEY_EVENT_UP',
|
||||
KEY_EVENT_DOWN: 'KEY_EVENT_DOWN',
|
||||
KEY_EVENT_LEFT: 'KEY_EVENT_LEFT',
|
||||
KEY_EVENT_RIGHT: 'KEY_EVENT_RIGHT',
|
||||
KEY_EVENT_SPACE: 'KEY_EVENT_SPACE',
|
||||
COLLISION_ENEMY_LASER: 'COLLISION_ENEMY_LASER',
|
||||
COLLISION_ENEMY_HERO: 'COLLISION_ENEMY_HERO',
|
||||
GAME_END_LOSS: 'GAME_END_LOSS',
|
||||
GAME_END_WIN: 'GAME_END_WIN',
|
||||
};
|
||||
|
||||
let heroImg,
|
||||
enemyImg,
|
||||
laserImg,
|
||||
lifeImg,
|
||||
canvas,
|
||||
ctx,
|
||||
gameObjects = [],
|
||||
hero,
|
||||
eventEmitter = new EventEmitter();
|
||||
|
||||
// EVENTS
|
||||
let onKeyDown = function (e) {
|
||||
// console.log(e.keyCode);
|
||||
switch (e.keyCode) {
|
||||
case 37:
|
||||
case 39:
|
||||
case 38:
|
||||
case 40: // Arrow keys
|
||||
case 32:
|
||||
e.preventDefault();
|
||||
break; // Space
|
||||
default:
|
||||
break; // do not block other keys
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
|
||||
// TODO make message driven
|
||||
window.addEventListener('keyup', (evt) => {
|
||||
if (evt.key === 'ArrowUp') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_UP);
|
||||
} else if (evt.key === 'ArrowDown') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
|
||||
} else if (evt.key === 'ArrowLeft') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
|
||||
} else if (evt.key === 'ArrowRight') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
|
||||
} else if (evt.keyCode === 32) {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
|
||||
} else if (evt.key === 'Enter') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
|
||||
}
|
||||
});
|
||||
|
||||
function createEnemies() {
|
||||
const MONSTER_TOTAL = 5;
|
||||
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
|
||||
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
|
||||
const STOP_X = START_X + MONSTER_WIDTH;
|
||||
|
||||
for (let x = START_X; x < STOP_X; x += 98) {
|
||||
for (let y = 0; y < 50 * 5; y += 50) {
|
||||
const enemy = new Enemy(x, y);
|
||||
enemy.img = enemyImg;
|
||||
gameObjects.push(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createHero() {
|
||||
hero = new Hero(canvas.width / 2 - 45, canvas.height - canvas.height / 4);
|
||||
hero.img = heroImg;
|
||||
gameObjects.push(hero);
|
||||
}
|
||||
|
||||
function updateGameObjects() {
|
||||
const enemies = gameObjects.filter((go) => go.type === 'Enemy');
|
||||
const lasers = gameObjects.filter((go) => go.type === 'Laser');
|
||||
|
||||
enemies.forEach((enemy) => {
|
||||
const heroRect = hero.rectFromGameObject();
|
||||
if (intersectRect(heroRect, enemy.rectFromGameObject())) {
|
||||
eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
|
||||
}
|
||||
});
|
||||
// laser hit something
|
||||
lasers.forEach((l) => {
|
||||
enemies.forEach((m) => {
|
||||
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
|
||||
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
|
||||
first: l,
|
||||
second: m,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gameObjects = gameObjects.filter((go) => !go.dead);
|
||||
}
|
||||
|
||||
function drawGameObjects(ctx) {
|
||||
gameObjects.forEach((go) => go.draw(ctx));
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
gameObjects = [];
|
||||
createEnemies();
|
||||
createHero();
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
|
||||
resetGame();
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
|
||||
hero.y -= 5;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
|
||||
hero.y += 5;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
|
||||
hero.x -= 20;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
|
||||
hero.x += 20;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
|
||||
if (hero.canFire()) {
|
||||
hero.fire();
|
||||
}
|
||||
// console.log('cant fire - cooling down')
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
||||
first.dead = true;
|
||||
second.dead = true;
|
||||
hero.incrementPoints();
|
||||
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
|
||||
enemy.dead = true;
|
||||
hero.decrementLife();
|
||||
if (isHeroDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_LOSS);
|
||||
return; // loss before victory
|
||||
}
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.GAME_END_WIN, () => {
|
||||
endGame(true);
|
||||
});
|
||||
eventEmitter.on(Messages.GAME_END_LOSS, () => {
|
||||
endGame(false);
|
||||
});
|
||||
}
|
||||
|
||||
function endGame(win) {
|
||||
clearInterval(gameLoopId);
|
||||
|
||||
// set delay so we are sure any paints have finished
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
if (win) {
|
||||
displayMessage('Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew', 'green');
|
||||
} else {
|
||||
displayMessage('You died !!! Press [Enter] to start a new game Captain Pew Pew');
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function isHeroDead() {
|
||||
return hero.life <= 0;
|
||||
}
|
||||
|
||||
function isEnemiesDead() {
|
||||
const enemies = gameObjects.filter((go) => go.type === 'Enemy' && !go.dead);
|
||||
return enemies.length === 0;
|
||||
}
|
||||
|
||||
function drawLife() {
|
||||
// TODO, 35, 27
|
||||
//
|
||||
|
||||
const START_POS = canvas.width - 180;
|
||||
for (let i = 0; i < hero.life; i++) {
|
||||
ctx.drawImage(lifeImg, START_POS + 45 * (i + 1), canvas.height - 37);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPoints() {
|
||||
ctx.font = '30px Arial';
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.textAlign = 'left';
|
||||
drawText('Points: ' + hero.points, 10, canvas.height - 20);
|
||||
}
|
||||
|
||||
function drawText(message, x, y) {
|
||||
ctx.fillText(message, x, y);
|
||||
}
|
||||
|
||||
function displayMessage(message, color = 'red') {
|
||||
ctx.font = '30px Arial';
|
||||
ctx.fillStyle = color;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
if (gameLoopId) {
|
||||
clearInterval(gameLoopId);
|
||||
eventEmitter.clear();
|
||||
initGame();
|
||||
gameLoopId = setInterval(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
drawPoints();
|
||||
drawLife();
|
||||
updateGameObjects();
|
||||
drawGameObjects(ctx);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = async () => {
|
||||
canvas = document.getElementById('canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
heroImg = await loadTexture('assets/player.png');
|
||||
enemyImg = await loadTexture('assets/enemyShip.png');
|
||||
laserImg = await loadTexture('assets/laserRed.png');
|
||||
lifeImg = await loadTexture('assets/life.png');
|
||||
|
||||
initGame();
|
||||
gameLoopId = setInterval(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
drawPoints();
|
||||
drawLife();
|
||||
updateGameObjects();
|
||||
drawGameObjects(ctx);
|
||||
}, 100);
|
||||
};
|
BIN
6-space-game/6-end-condition/solution/assets/enemyShip.png
Executable file
After Width: | Height: | Size: 4.1 KiB |
BIN
6-space-game/6-end-condition/solution/assets/laserRed.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
6-space-game/6-end-condition/solution/assets/life.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
BIN
6-space-game/6-end-condition/solution/assets/player.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
6
6-space-game/6-end-condition/solution/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<canvas id ="canvas" width="1024" height="768"></canvas>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
13
6-space-game/6-end-condition/solution/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "solution",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "npx http-server -c-1 -p 5000",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
214
6-space-game/6-end-condition/translations/README.es.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Construye un juego espacial Parte VI: Finalizar y reiniciar
|
||||
|
||||

|
||||
|
||||
## [Pre-lecture prueba](.github/pre-lecture-quiz.md)
|
||||
|
||||
Hay diferentes formas de expresar y *condición final* en un juego. Depende de usted, como creador del juego, decir por qué ha terminado. Aquí hay algunas razones, si asumimos que estamos hablando del juego espacial que has estado construyendo hasta ahora:
|
||||
|
||||
- **Las naves `N` enemigas han sido destruidas**: Es bastante común si divides un juego en diferentes niveles que necesites destruir las naves` N` Enemy para completar un nivel
|
||||
- **Tu nave ha sido destruida**: Definitivamente hay juegos en los que pierdes el juego si tu nave es destruida. Otro enfoque común es que tienes el concepto de vidas. Cada vez que un barco es destruido, se descuenta una vida. Una vez que se hayan perdido todas las vidas, perderá el juego.
|
||||
- **Has acumulado `N` puntos**: Otra condición final común es que acumules puntos. La forma de obtener puntos depende de usted, pero es bastante común asignar puntos a diversas actividades, como destruir una nave enemiga o tal vez recolectar elementos que los elementos *sueltan* cuando son destruidos.
|
||||
- **Completa un nivel**: Esto puede implicar varias condiciones como "X" barcos enemigos destruidos, puntos "Y" acumulados o tal vez que se haya recogido un objeto específico.
|
||||
|
||||
## Reiniciando
|
||||
|
||||
Si las personas disfrutan de tu juego, es probable que quieran volver a jugarlo. Una vez que el juego termina por cualquier motivo, debes ofrecer una alternativa para reiniciar.
|
||||
|
||||
✅ Piensa un poco en las condiciones en las que encuentras que termina un juego y luego cómo se te pide que reinicies
|
||||
|
||||
## Qué construir
|
||||
|
||||
Agregará estas reglas a su juego:
|
||||
|
||||
1. **Ganar el juego**. Una vez que todas las naves enemigas hayan sido destruidas, ganas el juego. Además, muestra algún tipo de mensaje de victoria.
|
||||
1. **Reiniciar**. Una vez que hayas perdido todas tus vidas o hayas ganado el juego, debes ofrecer una forma de reiniciar el juego. ¡Recuerda! Deberá reinicializar el juego y se debe borrar el estado anterior del juego.
|
||||
|
||||
## Pasos recomendados
|
||||
|
||||
Busque los archivos que se han creado para usted en la subcarpeta `your-work`. Debe contener lo siguiente:
|
||||
|
||||
```bash
|
||||
-| assets
|
||||
-| enemyShip.png
|
||||
-| player.png
|
||||
-| laserRed.png
|
||||
-| life.png
|
||||
-| index.html
|
||||
-| app.js
|
||||
-| package.json
|
||||
```
|
||||
|
||||
Comienzas tu proyecto en la carpeta `your_work` escribiendo:
|
||||
|
||||
```bash
|
||||
cd your-work
|
||||
npm start
|
||||
```
|
||||
|
||||
Lo anterior iniciará un servidor HTTP en la dirección `http://localhost:5000`. Abra un navegador e ingrese esa dirección. Tu juego debe estar en un estado jugable.
|
||||
|
||||
> consejo: para evitar advertencias en Visual Studio Code, edite la función `window.onload` para llamar a `gameLoopId` tal cual (sin `let`), y declare el gameLoopId en la parte superior del archivo, independientemente: `let gameLoopId;`
|
||||
|
||||
### Agregar código
|
||||
|
||||
1. **Condición de fin de pista**. Agregue un código que realice un seguimiento de la cantidad de enemigos o si el barco héroe ha sido destruido agregando estas dos funciones:
|
||||
|
||||
```javascript
|
||||
function isHeroDead() {
|
||||
return hero.life <= 0;
|
||||
}
|
||||
|
||||
function isEnemiesDead() {
|
||||
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
|
||||
return enemies.length === 0;
|
||||
}
|
||||
```
|
||||
|
||||
1. **Agregar lógica a los controladores de mensajes**. Edite el `eventEmitter` para manejar estas condiciones:
|
||||
|
||||
```javascript
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
||||
first.dead = true;
|
||||
second.dead = true;
|
||||
hero.incrementPoints();
|
||||
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
|
||||
enemy.dead = true;
|
||||
hero.decrementLife();
|
||||
if (isHeroDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_LOSS);
|
||||
return; // loss before victory
|
||||
}
|
||||
if (isEnemiesDead()) {
|
||||
eventEmitter.emit(Messages.GAME_END_WIN);
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.GAME_END_WIN, () => {
|
||||
endGame(true);
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.GAME_END_LOSS, () => {
|
||||
endGame(false);
|
||||
});
|
||||
```
|
||||
|
||||
1. **Agregar nuevos tipos de mensajes**. Agregue estos mensajes al objeto de constantes:
|
||||
|
||||
```javascript
|
||||
GAME_END_LOSS: "GAME_END_LOSS",
|
||||
GAME_END_WIN: "GAME_END_WIN",
|
||||
```
|
||||
|
||||
2. **Agregar código de reinicio** código que reinicia el juego con solo presionar un botón seleccionado.
|
||||
|
||||
1. **Escuche la tecla "Enter"**. Edite el eventListener de su ventana para escuchar esta prensa:
|
||||
|
||||
```javascript
|
||||
else if(evt.key === "Enter") {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
|
||||
}
|
||||
```
|
||||
|
||||
1. **Agregar mensaje de reinicio**. Agregue este mensaje a su constante de Mensajes:
|
||||
|
||||
```javascript
|
||||
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
|
||||
```
|
||||
|
||||
1. **Implementar las reglas del juego**. Implementa las siguientes reglas del juego:
|
||||
|
||||
1. **Condición de victoria del jugador**. Cuando todos los barcos enemigos sean destruidos, muestra un mensaje de victoria.
|
||||
|
||||
1. Primero, cree una función `displayMessage()`:
|
||||
|
||||
```javascript
|
||||
function displayMessage(message, color = "red") {
|
||||
ctx.font = "30px Arial";
|
||||
ctx.fillStyle = color;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
|
||||
}
|
||||
```
|
||||
|
||||
1. Cree una función `endGame()`:
|
||||
|
||||
```javascript
|
||||
function endGame(win) {
|
||||
clearInterval(gameLoopId);
|
||||
|
||||
// establece una demora para estar seguros de que las pinturas han terminado
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
if (win) {
|
||||
displayMessage(
|
||||
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
|
||||
"green"
|
||||
);
|
||||
} else {
|
||||
displayMessage(
|
||||
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
|
||||
);
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
```
|
||||
|
||||
1. **Reiniciar la lógica**. Cuando se pierdan todas las vidas o el jugador haya ganado el juego, muestre que el juego se puede reiniciar. Además, reinicie el juego cuando se presione la tecla *reiniciar* (puede decidir qué tecla debe asignarse para reiniciar).
|
||||
|
||||
1. Cree la función `resetGame()`:
|
||||
|
||||
```javascript
|
||||
function resetGame() {
|
||||
if (gameLoopId) {
|
||||
clearInterval(gameLoopId);
|
||||
eventEmitter.clear();
|
||||
initGame();
|
||||
gameLoopId = setInterval(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
drawPoints();
|
||||
drawLife();
|
||||
updateGameObjects();
|
||||
drawGameObjects(ctx);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Agrega una llamada al `eventEmitter` para reiniciar el juego en `initGame()`:
|
||||
|
||||
```javascript
|
||||
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
|
||||
resetGame();
|
||||
});
|
||||
```
|
||||
|
||||
1. Agregue una función `clear()` al EventEmitter:
|
||||
|
||||
```javascript
|
||||
clear() {
|
||||
this.listeners = {};
|
||||
}
|
||||
```
|
||||
|
||||
👽 💥 🚀 ¡Felicitaciones, Capitán! ¡Tu juego está completo! ¡Bien hecho! 🚀 💥 👽
|
||||
|
||||
🚀 Desafío: ¡Agrega un sonido! ¿Puedes agregar un sonido para mejorar tu juego, tal vez cuando hay un golpe de láser, o el héroe muere o gana? Eche un vistazo a este [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play) para aprender a reproducir sonido con JavaScript.
|
||||
|
||||
## [Post-lecture prueba](.github/post-lecture-quiz.md)
|
||||
|
||||
## Revisión y autoestudio
|
||||
|
||||
Tu tarea es crear un juego de muestra nuevo, así que explora algunos de los juegos interesantes que existen para ver qué tipo de juego podrías construir.
|
||||
|
||||
**Tarea**: [Crear un juego de muestra](assignment.md)
|
19
6-space-game/6-end-condition/translations/assignment.es.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Crea un juego de muestra
|
||||
|
||||
## Instrucciones
|
||||
|
||||
Intente crear un juego pequeño en el que practique en diferentes condiciones finales. Varía entre obtener una cantidad de puntos, el héroe pierde todas las vidas o todos los monstruos son derrotados. Construye algo simple como un juego de aventuras basado en consola. Utilice el siguiente flujo de juego como inspiración:
|
||||
|
||||
''
|
||||
Hero> Golpea con espada ancha: el orco recibe 3p de daño
|
||||
Orc> Golpes con garrote: el héroe recibe 2p de daño
|
||||
Hero> Patadas: el orco recibe 1p de daño
|
||||
Game> Orc es derrotado - Hero recoge 2 monedas
|
||||
Game> ****No más monstruos, has conquistado la fortaleza del mal****
|
||||
''
|
||||
|
||||
## Rúbrica
|
||||
|
||||
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
|
||||
| -------- | ---------------------- | --------------------------- | -------------------------- |
|
||||
| | se presenta el juego completo | se presenta parcialmente el juego | juego parcial contiene errores |
|
311
6-space-game/6-end-condition/your-work/app.js
Normal file
@@ -0,0 +1,311 @@
|
||||
// @ts-check
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
on(message, listener) {
|
||||
if (!this.listeners[message]) {
|
||||
this.listeners[message] = [];
|
||||
}
|
||||
this.listeners[message].push(listener);
|
||||
}
|
||||
|
||||
emit(message, payload = null) {
|
||||
if (this.listeners[message]) {
|
||||
this.listeners[message].forEach((l) => l(message, payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GameObject {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.dead = false;
|
||||
this.type = '';
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.img = undefined;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
|
||||
}
|
||||
|
||||
rectFromGameObject() {
|
||||
return {
|
||||
top: this.y,
|
||||
left: this.x,
|
||||
bottom: this.y + this.height,
|
||||
right: this.x + this.width,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Hero extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 99), (this.height = 75);
|
||||
this.type = 'Hero';
|
||||
this.speed = { x: 0, y: 0 };
|
||||
this.cooldown = 0;
|
||||
this.life = 3;
|
||||
this.points = 0;
|
||||
}
|
||||
fire() {
|
||||
gameObjects.push(new Laser(this.x + 45, this.y - 10));
|
||||
this.cooldown = 500;
|
||||
|
||||
let id = setInterval(() => {
|
||||
if (this.cooldown > 0) {
|
||||
this.cooldown -= 100;
|
||||
} else {
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
canFire() {
|
||||
return this.cooldown === 0;
|
||||
}
|
||||
decrementLife() {
|
||||
this.life--;
|
||||
if (this.life === 0) {
|
||||
this.dead = true;
|
||||
}
|
||||
}
|
||||
incrementPoints() {
|
||||
this.points += 100;
|
||||
}
|
||||
}
|
||||
|
||||
class Enemy extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 98), (this.height = 50);
|
||||
this.type = 'Enemy';
|
||||
let id = setInterval(() => {
|
||||
if (this.y < canvas.height - this.height) {
|
||||
this.y += 5;
|
||||
} else {
|
||||
console.log('Stopped at', this.y);
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
class Laser extends GameObject {
|
||||
constructor(x, y) {
|
||||
super(x, y);
|
||||
(this.width = 9), (this.height = 33);
|
||||
this.type = 'Laser';
|
||||
this.img = laserImg;
|
||||
let id = setInterval(() => {
|
||||
if (this.y > 0) {
|
||||
this.y -= 15;
|
||||
} else {
|
||||
this.dead = true;
|
||||
clearInterval(id);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function loadTexture(path) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = path;
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function intersectRect(r1, r2) {
|
||||
return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top);
|
||||
}
|
||||
|
||||
const Messages = {
|
||||
KEY_EVENT_UP: 'KEY_EVENT_UP',
|
||||
KEY_EVENT_DOWN: 'KEY_EVENT_DOWN',
|
||||
KEY_EVENT_LEFT: 'KEY_EVENT_LEFT',
|
||||
KEY_EVENT_RIGHT: 'KEY_EVENT_RIGHT',
|
||||
KEY_EVENT_SPACE: 'KEY_EVENT_SPACE',
|
||||
COLLISION_ENEMY_LASER: 'COLLISION_ENEMY_LASER',
|
||||
COLLISION_ENEMY_HERO: 'COLLISION_ENEMY_HERO',
|
||||
};
|
||||
|
||||
let heroImg,
|
||||
enemyImg,
|
||||
laserImg,
|
||||
lifeImg,
|
||||
canvas,
|
||||
ctx,
|
||||
gameObjects = [],
|
||||
hero,
|
||||
eventEmitter = new EventEmitter();
|
||||
|
||||
// EVENTS
|
||||
let onKeyDown = function (e) {
|
||||
// console.log(e.keyCode);
|
||||
switch (e.keyCode) {
|
||||
case 37:
|
||||
case 39:
|
||||
case 38:
|
||||
case 40: // Arrow keys
|
||||
case 32:
|
||||
e.preventDefault();
|
||||
break; // Space
|
||||
default:
|
||||
break; // do not block other keys
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
|
||||
// TODO make message driven
|
||||
window.addEventListener('keyup', (evt) => {
|
||||
if (evt.key === 'ArrowUp') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_UP);
|
||||
} else if (evt.key === 'ArrowDown') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
|
||||
} else if (evt.key === 'ArrowLeft') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
|
||||
} else if (evt.key === 'ArrowRight') {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
|
||||
} else if (evt.keyCode === 32) {
|
||||
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
|
||||
}
|
||||
});
|
||||
|
||||
function createEnemies() {
|
||||
const MONSTER_TOTAL = 5;
|
||||
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
|
||||
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
|
||||
const STOP_X = START_X + MONSTER_WIDTH;
|
||||
|
||||
for (let x = START_X; x < STOP_X; x += 98) {
|
||||
for (let y = 0; y < 50 * 5; y += 50) {
|
||||
const enemy = new Enemy(x, y);
|
||||
enemy.img = enemyImg;
|
||||
gameObjects.push(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createHero() {
|
||||
hero = new Hero(canvas.width / 2 - 45, canvas.height - canvas.height / 4);
|
||||
hero.img = heroImg;
|
||||
gameObjects.push(hero);
|
||||
}
|
||||
|
||||
function updateGameObjects() {
|
||||
const enemies = gameObjects.filter((go) => go.type === 'Enemy');
|
||||
const lasers = gameObjects.filter((go) => go.type === 'Laser');
|
||||
|
||||
enemies.forEach((enemy) => {
|
||||
const heroRect = hero.rectFromGameObject();
|
||||
if (intersectRect(heroRect, enemy.rectFromGameObject())) {
|
||||
eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
|
||||
}
|
||||
});
|
||||
// laser hit something
|
||||
lasers.forEach((l) => {
|
||||
enemies.forEach((m) => {
|
||||
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
|
||||
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
|
||||
first: l,
|
||||
second: m,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gameObjects = gameObjects.filter((go) => !go.dead);
|
||||
}
|
||||
|
||||
function drawGameObjects(ctx) {
|
||||
gameObjects.forEach((go) => go.draw(ctx));
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
gameObjects = [];
|
||||
createEnemies();
|
||||
createHero();
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
|
||||
hero.y -= 5;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
|
||||
hero.y += 5;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
|
||||
hero.x -= 20;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
|
||||
hero.x += 20;
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
|
||||
if (hero.canFire()) {
|
||||
hero.fire();
|
||||
}
|
||||
// console.log('cant fire - cooling down')
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
||||
first.dead = true;
|
||||
second.dead = true;
|
||||
hero.incrementPoints();
|
||||
});
|
||||
|
||||
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
|
||||
enemy.dead = true;
|
||||
hero.decrementLife();
|
||||
});
|
||||
}
|
||||
|
||||
function drawLife() {
|
||||
// TODO, 35, 27
|
||||
//
|
||||
|
||||
const START_POS = canvas.width - 180;
|
||||
for (let i = 0; i < hero.life; i++) {
|
||||
ctx.drawImage(lifeImg, START_POS + 45 * (i + 1), canvas.height - 37);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPoints() {
|
||||
ctx.font = '30px Arial';
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.textAlign = 'left';
|
||||
drawText('Points: ' + hero.points, 10, canvas.height - 20);
|
||||
}
|
||||
|
||||
function drawText(message, x, y) {
|
||||
ctx.fillText(message, x, y);
|
||||
}
|
||||
|
||||
window.onload = async () => {
|
||||
canvas = document.getElementById('canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
heroImg = await loadTexture('assets/player.png');
|
||||
enemyImg = await loadTexture('assets/enemyShip.png');
|
||||
laserImg = await loadTexture('assets/laserRed.png');
|
||||
lifeImg = await loadTexture('assets/life.png');
|
||||
|
||||
initGame();
|
||||
let gameLoopId = setInterval(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
drawPoints();
|
||||
drawLife();
|
||||
updateGameObjects();
|
||||
drawGameObjects(ctx);
|
||||
}, 100);
|
||||
};
|
BIN
6-space-game/6-end-condition/your-work/assets/enemyShip.png
Executable file
After Width: | Height: | Size: 4.1 KiB |
BIN
6-space-game/6-end-condition/your-work/assets/laserRed.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
6-space-game/6-end-condition/your-work/assets/life.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
BIN
6-space-game/6-end-condition/your-work/assets/player.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
6
6-space-game/6-end-condition/your-work/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<canvas id ="canvas" width="1024" height="768"></canvas>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
13
6-space-game/6-end-condition/your-work/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "solution",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "npx http-server -c-1 -p 5000",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|