Update Translations

This commit is contained in:
Lee Stott
2025-08-23 20:47:28 +01:00
committed by GitHub
parent eff26d962b
commit 4c689557b5
497 changed files with 0 additions and 50930 deletions

View File

@@ -1,385 +0,0 @@
# Build a Space Game Part III: Adding Motion
![video](video-url)
## [Pre-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33)
¡Los juegos no son muy divertidos hasta que tienes extraterrestres corriendo por la pantalla! En este juego haremos uso de dos tipos de movimientos:
- **Keyboard/Mouse movement** (Movimiento del teclado / mouse): cuando el usuario interactúa con el teclado o el mouse para mover un objeto en la pantalla.
- **Game induced movement** (Movimiento inducido por el juego): cuando el juego mueve un objeto con un intervalo de tiempo determinado.
Entonces, ¿cómo movemos las cosas en una pantalla? Se trata de coordenadas cartesianas: cambiamos la ubicación (x, y) del objeto y luego redibujamos la pantalla.
Normalmente, necesita los siguientes pasos para lograr *movimiento* en una pantalla:
1. **Set a new location** (Establecer una nueva ubicación) para un objeto; esto es necesario para percibir que el objeto se ha movido.
2. **Clear the screen** (Limpiar la pantalla), la pantalla debe limpiarse entre sorteos. Podemos borrarlo dibujando un rectángulo que llenamos con un color de fondo.
3. **Redraw object** (Redibujar objeto) en una nueva ubicación. Al hacer esto, finalmente logramos mover el objeto de un lugar a otro.
Así es como puede verse en el código:
```javascript
//establecer la ubicación del héroe
hero.x += 5;
// limpia el rectángulo que alberga al héroe
ctx.clearRect(0, 0, canvas.width, canvas.height);
// vuelve a dibujar el fondo del juego y el héroe
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ ¿Puedes pensar en una razón por la que volver a dibujar a tu héroe muchos fotogramas por segundo podría generar costos de rendimiento? Lea acerca de [alternativas a este patrón](https://www.html5rocks.com/en/tutorials/canvas/performance/).
## Manejar eventos de teclado
Maneja eventos adjuntando eventos específicos al código. Los eventos del teclado se activan en toda la ventana, mientras que los eventos del mouse como un `click` se pueden conectar a hacer clic en un elemento específico. Usaremos eventos de teclado a lo largo de este proyecto.
Para manejar un evento, debe usar el método `addEventListener()` de la ventana y proporcionarle dos parámetros de entrada. El primer parámetro es el nombre del evento, por ejemplo, `keyup`. El segundo parámetro es la función que debe invocarse como resultado del evento que tiene lugar.
He aquí un ejemplo:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = representación de cadena de la clave
if (evt.key === 'ArrowUp') {
// hacer algo
}
})
```
Para los eventos clave, hay dos propiedades en el evento que puede usar para ver qué tecla se presionó:
- `key`, esta es una representación de cadena de la tecla presionada, por ejemplo `ArrowUp`
- `keyCode`, esta es una representación numérica, por ejemplo, `37`, corresponde a `ArrowLeft`.
✅ La manipulación de eventos clave es útil fuera del desarrollo del juego. ¿Qué otros usos se le ocurren para esta técnica?
### Teclas especiales: una advertencia
Hay algunas teclas * especiales * que afectan la ventana. Eso significa que si estás escuchando un evento `keyup` y usas estas teclas especiales para mover a tu héroe, también realizará un desplazamiento horizontal. Por esa razón, es posible que desee *apagar* este comportamiento integrado del navegador a medida que desarrolla su juego. Necesitas un código como este:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Teclas de flecha
case 32:
e.preventDefault();
break; // Space
default:
break; // no bloquee otras llaves
}
};
window.addEventListener('keydown', onKeyDown);
```
El código anterior asegurará que las teclas de flecha y la tecla de espacio tengan su comportamiento *predeterminado* apagado. El mecanismo de *apagado* ocurre cuando llamamos a `e.preventDefault()`.
## Movimiento inducido por el juego
Podemos hacer que las cosas se muevan por sí mismas usando temporizadores como la función `setTimeout()` o `setInterval()` que actualizan la ubicación del objeto en cada tic o intervalo de tiempo. Esto es lo que puede parecer:
```javascript
let id = setInterval(() => {
//mover al enemigo en el eje y
enemy.y += 10;
})
```
## El bucle del juego
El bucle de juego es un concepto que es esencialmente una función que se invoca a intervalos regulares. Se llama bucle de juego, ya que todo lo que debería ser visible para el usuario se incluye en el bucle. El bucle del juego hace uso de todos los objetos del juego que son parte del juego, dibujándolos todos a menos que por alguna razón ya no deban ser parte del juego. Por ejemplo, si un objeto es un enemigo que fue golpeado por un láser y explota, ya no forma parte del ciclo del juego actual (aprenderás más sobre esto en lecciones posteriores).
Así es como suele verse un bucle de juego, expresado en código:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
El ciclo anterior se invoca cada `200` milisegundos para volver a dibujar el lienzo. Tiene la capacidad de elegir el mejor intervalo que tenga sentido para su juego.
## Continuando con el juego espacial
Tomarás el código existente y lo extenderás. Empiece con el código que completó durante la parte I o use el código en [Part II-starter](your-work).
- **Moving the hero** (Mover al héroe): agregará código para asegurarse de que puede mover al héroe con las teclas de flecha.
- **Move enemies** (Mover enemigos): también necesitarás agregar código para asegurarte de que los enemigos se muevan de arriba hacia abajo a un ritmo determinado.
## 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
-| 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, ahora mismo debería representar al héroe y a todos los enemigos; nada se mueve, ¡todavía!
### Agregar código
1. **Agrega objetos dedicados** para `hero` y `enemy` y `game object`, deben tener propiedades `x` e `y`. (Recuerde la parte sobre [Herencia o composición](../README.md)).
* SUGERENCIA* `game object` debe ser el que tenga `x` e `y` y la capacidad de dibujarse a sí mismo en un lienzo.
> consejo: comience agregando una nueva clase GameObject con su constructor delineado como se muestra a continuación, y luego dibuje en el lienzo:
```javascript
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);
}
}
```
Ahora, amplíe este GameObject para crear el héroe y el enemigo.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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)
}
}
```
2. **Agregue controladores de eventos clave** para manejar la navegación de teclas (mueva el héroe hacia arriba / abajo a la izquierda / derecha)
* RECUERDE* es un sistema cartesiano, la parte superior izquierda es `0,0`. También recuerde agregar código para detener *comportamiento predeterminado*.
> consejo: cree su función onKeyDown y adjúntela a la ventana:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...//agregue el código de la lección anterior para detener el comportamiento predeterminado
}
};
window.addEventListener("keydown", onKeyDown);
```
Compruebe la consola de su navegador en este punto y observe cómo se registran las pulsaciones de teclas.
3. **Implemente** el [subpatrón Pub](../README.md), esto mantendrá su código limpio mientras sigue las partes restantes.
Para hacer esta última parte, puede:
1. **Agregue un detector de eventos** en la ventana:
```javascript
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);
}
});
```
1. **Cree una clase EventEmitter** para publicar y suscribirse a mensajes:
```javascript
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));
}
}
}
```
1. **Agregue constantes** y configure el EventEmitter:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Inicializa el juego**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **Configura el bucle del juego**
Refactorice la función window.onload para inicializar el juego y configurar un bucle de juego en un buen intervalo. También agregará un rayo láser:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. **Agregar código** para mover enemigos en un cierto intervalo
Refactorice la función `createEnemies()` para crear los enemigos y empujarlos a la nueva clase gameObjects:
```javascript
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);
}
}
}
```
y agregue una función `createHero()` para hacer un proceso similar para el héroe.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
y finalmente, agregue una función `drawGameObjects()` para comenzar el dibujo:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
¡Tus enemigos deberían comenzar a avanzar en tu nave espacial heroica!
🚀 Desafío: como puede ver, su código puede convertirse en 'código espagueti' cuando comienza a agregar funciones, variables y clases. ¿Cómo puede organizar mejor su código para que sea más legible? Esboce un sistema para organizar su código, incluso si todavía reside en un archivo.
## [Post-lecture prueba](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34)
## Revisión y autoestudio
Mientras escribimos nuestro juego sin usar marcos, existen muchos marcos de lienzo basados en JavaScript para el desarrollo de juegos. Tómate un tiempo para hacer algo [leer sobre estos](https://github.com/collections/javascript-game-engines).
**Tarea**: [Comenta tu código](assignment.es.md)

View File

@@ -1,388 +0,0 @@
# Construisez un jeu spatial, partie 3: Ajout de mouvement
## Quiz préalable
[Quiz préalable](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=fr)
Les jeux ne sont pas très amusants tant que vous n'avez pas des extraterrestres qui courent à l'écran! Dans ce jeu, nous utiliserons deux types de mouvements:
- **Mouvement clavier/souris**: lorsque l'utilisateur interagit avec le clavier ou la souris pour déplacer un objet à l'écran.
- **Mouvement induit par le jeu**: lorsque le jeu déplace un objet avec un certain intervalle de temps.
Alors, comment déplacer les choses sur un écran? Tout est question de coordonnées cartésiennes: nous changeons l'emplacement (x,y) de l'objet puis redessinons l'écran.
Généralement, vous devez suivre les étapes suivantes pour accomplir un *mouvement* sur un écran:
1. **Définir un nouvel emplacement** pour un objet; cela est nécessaire pour percevoir l'objet comme ayant bougé.
2. **Effacer l'écran**, l'écran doit être dégagé entre les tirages. Nous pouvons l'effacer en dessinant un rectangle que nous remplissons avec une couleur de fond.
3. **Redessiner l'objet** au nouvel emplacement. En faisant cela, nous accomplissons finalement le déplacement de l'objet d'un endroit à l'autre.
Voici à quoi cela peut ressembler dans le code:
```javascript
//définir l'emplacement du héros
hero.x += 5;
// effacer le rectangle qui accueille le héros
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redessiner le fond du jeu et le héros
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ Pouvez-vous penser à une raison pour laquelle redessiner votre héros plusieurs images par seconde pourrait entraîner des coûts de performance? Apprenez en plus sur les [alternatives à ce modèle](https://www.html5rocks.com/en/tutorials/canvas/performance/).
## Gérer les événements du clavier
Vous gérez les événements en attachant des événements spécifiques au code. Les événements de clavier sont déclenchés sur toute la fenêtre tandis que les événements de souris comme un `clic` peuvent être liés au clic sur un élément spécifique. Nous utiliserons des événements de clavier tout au long de ce projet.
Pour gérer un événement, vous devez utiliser la méthode `addEventListener()` de la fenêtre et lui fournir deux paramètres d'entrée. Le premier paramètre est le nom de l'événement, par exemple `keyup`. Le deuxième paramètre est la fonction qui doit être invoquée à la suite de l'événement en cours.
Voici un exemple:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = représentation sous forme de chaîne de la clé
if (evt.key === 'ArrowUp') {
// faire quelque chose
}
})
```
Pour les événements clés, il existe deux propriétés sur l'événement que vous pouvez utiliser pour voir quelle touche a été pressée:
- `key`, il s'agit d'une représentation sous forme de chaîne de la touche enfoncée, par exemple `ArrowUp`
- `keyCode`, c'est une représentation numérique, par exemple `37`, correspond à `ArrowLeft`.
✅ La manipulation des événements clés est utile en dehors du développement de jeux. A quelles autres utilisations pensez-vous pour cette technique?
### Touches spéciales: une mise en garde
Il y a des touches *spéciales* qui affectent la fenêtre. Cela signifie que si vous écoutez un événement `keyup` et que vous utilisez ces touches spéciales pour déplacer votre héros, il effectuera également un défilement horizontal. Pour cette raison, vous voudrez peut-être *désactiver* ce comportement de navigateur intégré lorsque vous créez votre jeu. Vous avez besoin d'un code comme celui-ci:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Touches directionnelles
case 32:
e.preventDefault();
break; // Espace
default:
break; // ne pas bloquer d'autres clés
}
};
window.addEventListener('keydown', onKeyDown);
```
Le code ci-dessus garantira que les touches fléchées et la touche espace ont leur comportement *par défaut* désactivé. Le mécanisme *shut-off* se produit lorsque nous appelons `e.preventDefault()`.
## Mouvement induit par le jeu
Nous pouvons faire bouger les choses d'elles-mêmes en utilisant des minuteries telles que la fonction `setTimeout()` ou `setInterval()` qui met à jour l'emplacement de l'objet à chaque tick ou intervalle de temps. Voici à quoi cela peut ressembler:
```javascript
let id = setInterval(() => {
//déplacer l'ennemi sur l'axe y
enemy.y += 10;
})
```
## La boucle du jeu
La boucle de jeu est un concept qui est essentiellement une fonction invoquée à intervalles réguliers. C'est ce qu'on appelle la boucle de jeu car tout ce qui doit être visible pour l'utilisateur est attiré dans la boucle. La boucle de jeu utilise tous les objets de jeu qui font partie du jeu, les dessinant tous à moins que, pour une raison quelconque, ils ne fassent plus partie du jeu. Par exemple, si un objet est un ennemi qui a été touché par un laser et qui explose, il ne fait plus partie de la boucle de jeu en cours (vous en apprendrez plus à ce sujet dans les leçons suivantes).
Voici à quoi ressemble généralement une boucle de jeu, exprimée en code:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
La boucle ci-dessus est invoquée toutes les `200` millisecondes pour redessiner le canevas. Vous avez la possibilité de choisir le meilleur intervalle qui a du sens pour votre jeu.
## Continuer le jeu spatial
Vous allez prendre le code existant et l'étendre. Soit commencez par le code que vous avez complété au cours de la partie I, soit utilisez le code de la [Partie II - starter](../your-work).
- **Déplacer le héros**: vous ajouterez du code pour vous assurer de pouvoir déplacer le héros à l'aide des touches fléchées.
- **Déplacer les ennemis**: vous devrez également ajouter du code pour vous assurer que les ennemis se déplacent de haut en bas à un rythme donné.
## Étapes recommandées
Localisez les fichiers qui ont été créés pour vous dans le sous-dossier `your-work`. Il doit contenir les éléments suivants:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Vous démarrez votre projet dans le dossier `your_work` en tapant:
```bash
cd your-work
npm start
```
Ce qui précède démarrera un serveur HTTP à l'adresse `http://localhost:5000`. Ouvrez un navigateur et entrez cette adresse, pour le moment, cela devrait rendre le héros et tous les ennemis; rien ne bouge - pour l'instant!
### Ajouter un code
1. **Ajouter des objets dédiés** pour `hero` et `enemy` et `game object`, ils doivent avoir les propriétés `x` et `y`. (Rappelez-vous la partie sur l'[héritage ou la composition](../../translations/README.fr.md)).
*CONSEIL* `game object` (l'objet de jeu) doit être celui avec `x` et `y` et la possibilité de se dessiner sur un canevas.
>astuce: commencez par ajouter une nouvelle classe GameObject avec son constructeur délimité comme ci-dessous, puis dessinez-la sur le canevas:
```javascript
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);
}
}
```
Maintenant, étendez ce GameObject pour créer le héros et l'ennemi.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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)
}
}
```
2. **Ajoutez des gestionnaires d'événements de touche** pour gérer la navigation des touches (déplacez le héros vers le haut/bas vers la gauche/la droite)
*RAPPELEZ-VOUS* que c'est un système cartésien, le haut à gauche est `0,0`. N'oubliez pas également d'ajouter du code pour arrêter *le comportement par défaut*
>astuce: créez votre fonction onKeyDown et attachez-la à la fenêtre:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
Vérifiez la console de votre navigateur à ce stade et regardez les frappes enregistrées.
3. **Implémentez** le [modèle Pub Sub](../../translations/README.fr.md), cela gardera votre code propre pendant que vous suivez les parties restantes.
Pour faire cette dernière partie, vous pouvez:
1. **Ajouter un écouteur d'événement** sur la fenêtre:
```javascript
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);
}
});
```
1. **Créez une classe EventEmitter** pour publier et vous abonner aux messages:
```javascript
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));
}
}
}
```
1. **Ajoutez des constantes** et configurez l'EventEmitter:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Initialiser le jeu**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **Configurer la boucle de jeu**
Refactorisez la fonction window.onload pour initialiser le jeu et mettre en place une boucle de jeu sur un bon intervalle. Vous ajouterez également un faisceau laser:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. **Ajoutez du code** pour déplacer les ennemis à un certain intervalle
Refactoriser la fonction `createEnemies()` pour créer les ennemis et les pousser dans la nouvelle classe gameObjects:
```javascript
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);
}
}
}
```
et ajoutez une fonction `createHero()` pour faire un processus similaire pour le héros.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
et enfin, ajoutez une fonction `drawGameObjects()` pour démarrer le dessin:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
Vos ennemis devraient commencer à avancer sur votre vaisseau spatial héros!
---
## 🚀 Challenge
Comme vous pouvez le constater, votre code peut se transformer en 'code spaghetti' lorsque vous commencez à ajouter des fonctions, des variables et des classes. Comment pouvez-vous mieux organiser votre code pour qu'il soit plus lisible? Esquissez un système pour organiser votre code, même s'il réside toujours dans un seul fichier.
## Quiz de validation des connaissances
[Quiz de validation des connaissances](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=fr)
## Révision et étude personnelle
Pendant que nous écrivons notre jeu sans utiliser de frameworks, il existe de nombreux frameworks de canevas basés sur JavaScript pour le développement de jeux. Prenez le temps de faire quelques [lectures à ce sujet](https://github.com/collections/javascript-game-engines).
## Affectation
[Commentez votre code](assignment.fr.md)

View File

@@ -1,387 +0,0 @@
# एक अंतरिक्ष खेल भाग 3 बनाएँ: गति जोड़ना
## प्री-लेक्चर क्विज
[प्री-लेक्चर क्विज](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=hi)
जब तक आप परदे पर चारों ओर एलियंस चल रहे हैं तब तक गेम्स बहुत मज़ेदार नहीं हैं! इस खेल में, हम दो प्रकार के आंदोलनों का उपयोग करेंगे:
- **Keyboard/Mouse movement**: जब उपयोगकर्ता स्क्रीन पर किसी ऑब्जेक्ट को स्थानांतरित करने के लिए कीबोर्ड या माउस के साथ इंटरैक्ट करता है.
- **Game induced movement**: जब खेल एक निश्चित समय अंतराल के साथ एक वस्तु को स्थानांतरित करता है.
तो हम चीजों को स्क्रीन पर कैसे स्थानांतरित करते हैं? यह सब कार्तीय निर्देशांक के बारे में है:हम ऑब्जेक्ट का स्थान (x, y) बदलते हैं और फिर स्क्रीन को फिर से खोलते हैं।
आमतौर पर आपको स्क्रीन पर _गति_ को पूरा करने के लिए निम्न चरणों की आवश्यकता होती है:
1. एक वस्तु के लिए **एक नया स्थान निर्धारित करें**; यह वस्तु को स्थानांतरित करने के रूप में अनुभव करने के लिए आवश्यक है.
2. **स्क्रीन को साफ़ करें**, ड्रॉ के बीच स्क्रीन को साफ़ करना होगा. We can clear it by drawing a rectangle that we fill with a background color.
3. नए स्थान पर **Redraw ऑब्जेक्ट**। ऐसा करने से हम अंत में वस्तु को एक स्थान से दूसरे स्थान तक ले जाते हैं.
यहाँ यह कोड में कैसा दिख सकता है:
```javascript
//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ क्या आप एक कारण के बारे में सोच सकते हैं कि अपने नायक को प्रति सेकंड कई फ़्रेमों को फिर से तैयार करने से प्रदर्शन की लागत बढ़ सकती है? [इस पैटर्न के विकल्प](https://www.html5rocks.com/en/tutorials/canvas/performance/) के बारे में पढ़ें.
## कीबोर्ड घटनाओं को संभालें
आप विशिष्ट घटनाओं को कोड में संलग्न करके घटनाओं को संभालते हैं. कीबोर्ड की घटनाओं को पूरी विंडो पर ट्रिगर किया जाता है जबकि एक `click` जैसे माउस इवेंट को एक विशिष्ट तत्व को क्लिक करने के लिए जोड़ा जा सकता है. हम इस पूरे प्रोजेक्ट में कीबोर्ड इवेंट का उपयोग करेंगे.
एक घटना को संभालने के लिए आपको विंडो के `addEventListener()` विधि का उपयोग करना होगा और इसे दो इनपुट मापदंडों के साथ प्रदान करना होगा. पहला पैरामीटर घटना का नाम है, उदाहरण के लिए `keyup`. दूसरा पैरामीटर वह फ़ंक्शन होता है, जिसे ईवेंट होने के परिणामस्वरूप लागू किया जाना चाहिए.
यहाँ एक उदाहरण है:
```javascript
window.addEventListener("keyup", (evt) => {
// `evt.key` = string representation of the key
if (evt.key === "ArrowUp") {
// do something
}
});
```
प्रमुख घटनाओं के लिए, इस घटना पर दो गुण हैं जिन्हें आप यह देखने के लिए उपयोग कर सकते हैं कि किस कुंजी को दबाया गया था:
- `key`, यह दबाए गए कुंजी का एक स्ट्रिंग प्रतिनिधित्व है, उदाहरण के लिए` ArrowUp`
- `KeyCode`, यह एक संख्या प्रतिनिधित्व है, उदाहरण के लिए` 37`, `ArrowLeft` से मेल खाती है.
✅ की इवेंट हेरफेर खेल के विकास के बाहर उपयोगी है। इस तकनीक के लिए आप और क्या उपयोग कर सकते हैं?
### विशेष कुंजी: एक चेतावनी
कुछ _special_ किइस हैं जो विंडोज को प्रभावित करती हैं. इसका मतलब है कि यदि आप एक `keyup` घटना सुन रहे हैं और आप अपने नायक को स्थानांतरित करने के लिए इन विशेष किइस का उपयोग करते हैं तो यह क्षैतिज स्क्रॉलिंग भी करेगा.इस कारण से आप अपने गेम को बनाते समय इस बिल्ट-इन ब्राउज़र व्यवहार को बंद कर सकते हैं। आपको इस तरह कोड की आवश्यकता है:
```javascript
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);
```
उपरोक्त कोड यह सुनिश्चित करेगा कि एरो किइस और स्पेस की का डिफ़ॉल्ट व्यवहार बंद हो। शट-ऑफ तंत्र तब होता है जब हम `e.preventDefault ()` कहते हैं.
## खेल प्रेरित चाल
हम टाइम सेट का उपयोग करके चीजों को खुद से आगे बढ़ा सकते हैं जैसे `setTimeout()` या `setInterval()` फ़ंक्शन जो प्रत्येक टिक पर वस्तु के स्थान को अपडेट करते हैं, या समय अंतराल।. यहां ऐसा है जो दिख सकता है:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
});
```
## गेम लूप
गेम लूप एक अवधारणा है जो अनिवार्य रूप से एक फ़ंक्शन है जिसे नियमित अंतराल पर लागू किया जाता है. इसे गेम लूप कहा जाता है क्योंकि उपयोगकर्ता को दिखाई देने वाली सभी चीज़ों को लूप में खींचा जाना चाहिए. गेम लूप उन सभी गेम ऑब्जेक्ट्स का उपयोग करता है जो गेम का हिस्सा हैं, उन सभी को ड्रॉ करना जब तक कि किसी कारण से गेम का हिस्सा नहीं होना चाहिए. उदाहरण के लिए यदि कोई वस्तु एक शत्रु है जो लेजर से टकराती है और उड़ती है, तो यह वर्तमान गेम लूप का हिस्सा नहीं है (बाद के पाठों में आप इस पर और अधिक सीखेंगे).
यहाँ एक गेम लूप आमतौर पर कैसा दिख सकता है, कोड में व्यक्त किया गया है:
```javascript
let gameLoopId = setInterval(
() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
},
200
);
```
उपरोक्त लूप को कैनवास को फिर से बनाने के लिए प्रत्येक `200` मिलीसेकेंड पर लगाया जाता है. आपके पास सबसे अच्छा अंतराल चुनने की क्षमता है जो आपके खेल के लिए समझ में आता है.
## अंतरिक्ष खेल को जारी रखना
आप मौजूदा कोड लेंगे और उसे विस्तारित करेंगे। या तो उस कोड से शुरू करें जो आपने भाग I के दौरान पूरा किया था या [भाग II- स्टार्टर](../your-work) में कोड का उपयोग करें.
- **Moving the hero**: आप यह सुनिश्चित करने के लिए कोड जोड़ेंगे कि आप तीर किइस का उपयोग करके नायक को स्थानांतरित कर सकते हैं.
- **Move enemies**: दुश्मनों को किसी दिए गए दर पर ऊपर से नीचे ले जाने के लिए आपको कोड जोड़ने की भी आवश्यकता होगी.
## अनुशंसित कदम
उन फ़ाइलों का पता लगाएँ जो आपके लिए `your-work` सब फ़ोल्डर में बनाई गई हैं. इसमें निम्नलिखित शामिल होना चाहिए:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
आप टाइप करके अपना प्रोजेक्ट `your_work` फ़ोल्डर शुरू करें:
```bash
cd your-work
npm start
```
उपरोक्त पते पर एक HTTP सर्वर शुरू होगा `http: // localhost: 5000`. एक ब्राउज़र खोलें और उस पते को इनपुट करें, अभी उसे नायक और सभी दुश्मनों को प्रस्तुत करना चाहिए; कुछ भी नहीं चल रहा है - फिर भी!
### कोड जोड़ें
1. `हीरो` और `दुश्मन` और `गेम ऑब्जेक्ट` के लिए **समर्पित ऑब्जेक्ट्स जोड़ें**, उनके पास` x` और `y` गुण होने चाहिए ([इन्हेरिटेंस या कम्पोजीशन](../../README.md) पर भाग याद रखें ).
_संकेत_ `गेम ऑब्जेक्ट` `x` और `y` के साथ एक होना चाहिए और एक कैनवास पर खुद को आकर्षित करने की क्षमता होनी चाहिए
> टिप: नीचे के रूप में देलिनेटेड कंस्ट्रक्टर के साथ एक नया ग़मओब्जेक्ट वर्ग जोड़कर शुरू करें, और फिर इसे कैनवास पर ड्रा करें:
```javascript
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);
}
}
```
अब, इस गेमओब्जेक्ट को हीरो और दुश्मन बनाने के लिए विस्तारित करें.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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);
}
}
```
2. **की ईवेंट हैंडलर जोड़ें** की नेविगेशन को संभालने के लिए (बाएं / दाएं हीरो ऊपर ले जाएं)
_REMEMBER_ यह कार्टेशियन सिस्टम है, टॉप-लेफ्ट `0,0` है. यह भी याद रखें कि _डिफ़ॉल्ट व्यवहार_ को रोकने के लिए कोड जोड़ना
> टिप: अपने onKeyDown फ़ंक्शन को बनाएं और इसे विंडो में अटैच करें:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
इस बिंदु पर अपने ब्राउज़र कंसोल की जाँच करें, और लॉग किए जा रहे कीस्ट्रोक्स को देखें.
3. \*\* लागू करें [[पब उप पैटर्न](../../ README.md), शेष भागों का पालन करते हुए यह आपके कोड को साफ रखेगा.
यह अंतिम भाग करने के लिए, आप कर सकते हैं:
1. विंडो पर **एक घटना श्रोता जोड़ें**:
```javascript
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);
}
});
```
2. संदेशों को प्रकाशित करने और सदस्यता लेने के लिए **एक EventEmitter वर्ग बनाएं**:
```javascript
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));
}
}
}
```
3. **स्थिरांक जोड़ें** और EventEmitter सेट करें:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas,
ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
4. **खेल को प्रारंभ करें**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
4. **गेम लूप सेट करें**
गेम को प्रारंभ करने के लिए window.onload फ़ंक्शन को रिफ्लेक्टर करें और एक अच्छे अंतराल पर गेम लूप सेट करें। आप एक लेजर बीम भी जोड़ जोड़ सकते है:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100);
};
```
5. एक निश्चित अंतराल पर दुश्मनों को स्थानांतरित करने के लिए **कोड जोड़ें**
शत्रुओं को बनाने और उन्हें नए गेमऑब्जेक्ट्स क्लास में धकेलने के लिए `createEnemies()` फ़ंक्शन को रिफलेक्‍टर करें:
```javascript
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);
}
}
}
```
और हीरो के लिए एक समान प्रक्रिया करने के लिए एक `createHero()` फ़ंक्शन जोड़ें.
```javascript
function createHero() {
hero = new Hero(canvas.width / 2 - 45, canvas.height - canvas.height / 4);
hero.img = heroImg;
gameObjects.push(hero);
}
```
और अंत में, ड्राइंग शुरू करने के लिए एक `drawGameObjects()` फ़ंक्शन जोड़ें:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach((go) => go.draw(ctx));
}
```
अपने दुश्मनों को अपने नायक अंतरिक्ष यान पर आगे बढ़ना शुरू कर देना चाहिए!
---
## 🚀 चुनौती
जैसा कि आप देख सकते हैं, जब आप फ़ंक्शन और चर और कक्षाएं जोड़ना शुरू करते हैं तो आपका कोड 'स्पेगेटी कोड' में बदल सकता है. आप अपने कोड को बेहतर तरीके से कैसे व्यवस्थित कर सकते हैं ताकि यह अधिक पठनीय हो? अपने कोड को व्यवस्थित करने के लिए एक सिस्टम स्केच करें, भले ही वह अभी भी एक फ़ाइल में रहता हो.
## व्याख्यान के बाद की क्विज
[व्याख्यान के बाद की क्विज](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=hi)
## समीक्षा और स्व अध्ययन
जब हम फ्रेमवर्क का उपयोग किए बिना अपना गेम लिख रहे हैं, तो गेम के विकास के लिए कई जावास्क्रिप्ट-आधारित कैनवास फ्रेमवर्क हैं. कुछ करने के लिए कुछ समय ले लो [इन के बारे में पढ़ना](https://github.com/collections/javascript-game-engines).
## असाइनमेंट
[अपना कोड कमेंट करें](assignment.hi.md)

View File

@@ -1,388 +0,0 @@
# Costruire un Gioco Spaziale parte 3: Aggiungere il Movimento
## Quiz Pre-Lezione
[Quiz Pre-Lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=it)
I giochi non sono molto divertenti finché non si hanno alieni che scorazzano per lo schermo! In questo gioco, si utilizzeranno due tipi di movimenti:
- **Movimento tastiera/mouse**: quando l'utente interagisce con la tastiera o il mouse per spostare un oggetto sullo schermo.
- **Movimento indotto dal gioco**: quando il gioco sposta un oggetto con un certo intervallo di tempo.
Quindi come si spostano le cose su uno schermo? Dipende tutto dalle coordinate cartesiane: si cambia la posizione (x, y) di un oggetto, poi si ridisegna lo schermo.
In genere sono necessari i seguenti passaggi per eseguire il *movimento* su uno schermo:
1. **Impostare una nuova posizione** per un oggetto; questo è necessario per percepire l'oggetto come se si fosse spostato.
2. **Cancellare lo schermo, lo** schermo deve essere cancellato tra un disegno e un altro. Si può cancellarlo disegnando un rettangolo che viene riempito con un colore di sfondo.
3. **Ridisegnare l'oggetto** in una nuova posizione. In questo modo si può finalmente spostare l'oggetto da una posizione all'altra.
Ecco come può apparire nel codice:
```javascript
//imposta la posizione dell'eroe
hero.x += 5;
// pulisce il rettangolo che ospita l'eroe
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ridisegna lo sfondo del gioco e l'eroe
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ Si riesce a pensare a un motivo per cui ridisegnare il proprio eroe con molti fotogrammi al secondo potrebbe far aumentare i costi delle prestazioni? Leggere le [alternative a questo modello](https://www.html5rocks.com/en/tutorials/canvas/performance/).
## Gestire eventi da tastiera
Gli eventi si gestiscono allegando eventi specifici al codice. Gli eventi della tastiera vengono attivati sull'intera finestra mentre gli eventi del mouse come un `clic` possono essere collegati al clic su un elemento specifico. Si useranno gli eventi della tastiera durante questo progetto.
Per gestire un evento è necessario utilizzare il metodo `addEventListener()` dell'oggetto window e fornirgli due parametri di input. Il primo parametro è il nome dell'evento, ad esempio `keyup`. Il secondo parametro è la funzione che dovrebbe essere invocata come risultato dell'evento in corso.
Ecco un esempio:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = rappresentazione stringa del tasto
if (evt.key === 'ArrowUp') {
// fa qualcosa
}
})
```
Per gli eventi da tastiera ci sono due proprietà sull'evento che si possono usare usare per vedere quale tasto è stato premuto:
- `key`, questa è una rappresentazione di stringa del tasto premuto, ad esempio `ArrowUp`
- `keyCode`, questa è una rappresentazione numerica, ad esempio `37`, corrisponde a `ArrowLeft`.
✅ La manipolazione degli eventi da tastiera è utile al di fuori dello sviluppo del gioco. Quali altri usi possono venire in mente per questa tecnica?
### Tasti speciali: un avvertimento
Ci sono alcuni tasti *speciali* che influenzano la finestra. Ciò significa che se si sta ascoltando un evento `keyup` e si usano questi tasti speciali per muovere l'eroe, verrà eseguito anche lo scorrimento orizzontale. Per questo motivo si potrebbe voler *disattivare* questo comportamento del browser integrato mentre si sviluppa il gioco. Serve un codice come questo:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Tasti freccia
case 32:
e.preventDefault();
break; // Barra spazio
default:
break; // non bloccare altri tasti
}
};
window.addEventListener('keydown', onKeyDown);
```
Il codice precedente assicurerà che i tasti freccia e la barra spaziatrice abbiano il loro comportamento *predefinito* disattivato. Il meccanismo *di disattivazione* si verifica quando si chiama `e.preventDefault()`.
## Movimento indotto dal gioco
E' possibile far muovere le cose da sole utilizzando timer come la funzione `setTimeout()` o `setInterval()` che aggiornano la posizione dell'oggetto a ogni tick o intervallo di tempo. Ecco come può apparire:
```javascript
let id = setInterval(() => {
//sposta il nemico sull'asse y
enemy.y += 10;
})
```
## Il ciclo di gioco
Il ciclo di gioco è un concetto che è essenzialmente una funzione che viene invocata a intervalli regolari. Si chiama ciclo di gioco poiché tutto ciò che dovrebbe essere visibile all'utente viene disegnato nel ciclo. Il ciclo di gioco utilizza tutti gli oggetti che fanno parte del gioco, disegnandoli tutti a meno che per qualche motivo non debbano più far parte del gioco. Ad esempio, se un oggetto è un nemico che è stato colpito da un laser ed esplode, non fa più parte del ciclo di gioco corrente (maggiori informazioni nelle lezioni successive).
Ecco come può apparire tipicamente un ciclo di gioco, espresso in codice:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
Il ciclo precedente viene richiamato ogni `200` millisecondi per ridisegnare il canvas. Si ha la possibilità di scegliere l'intervallo migliore che abbia senso per il proprio gioco.
## Continuare il Gioco Spaziale
Si prenderà il codice esistente per estenderlo. Si inizia con il codice che si è completato durante la parte I o si usa il codice nella [parte II-starter](../your-work).
- **Muovere l'eroe**: si aggiungerà un codice per assicurarsi di poter muovere l'eroe usando i tasti freccia.
- **Muovere i nemici**: si dovrà anche aggiungere del codice per assicurarsi che i nemici si muovano dall'alto verso il basso a una determinata velocità.
## Passaggi consigliati
Individuare i file che già sono stati creati nella sottocartella `your-work` Dovrebbe contenere quanto segue:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Si fa partire il progetto nella cartella `your_work` digitando:
```bash
cd your-work
npm start
```
Quanto sopra avvierà un server HTTP all'indirizzo `http://localhost:5000`. Aprire un browser e inserire quell'indirizzo, in questo momento dovrebbe rendere l'eroe e tutti i nemici; niente si muove - ancora!
### Aggiungere codice
1. **Aggiungere oggetti dedicati** per `eroe`, `nemico` e `oggetto di gioco`, dovrebbero avere proprietà `x` e `y` . (Ricorda la parte su [ereditarietà o composizione](../../1-introduction/translations/README.it.md).
*SUGGERIMENTO* l'`oggetto di gioco` (GameObject) dovrebbe essere quello con `x` e `y` e la capacità di disegnare se stesso sul canvas.
> suggerimento: iniziare aggiungendo una nuova classe GameObject con il suo costruttore delineato come di seguito, quindi disegnarlo sul canvas:
```javascript
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);
}
}
```
Ora, si estende questo GameObject per creare eroe (classe Hero) e nemico (clsse Enemy).
```javascript
class Hero extends GameObject {
constructor(x, y) {
...servono x, y, tipo, e velocità
}
}
```
```javascript
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)
}
}
```
2. **Aggiungere gestori di eventi di tastiera** per gestire la navigazione con i tasti (spostare l'eroe su/giù, sinistra/destra)
*RICORDARE* che è un sistema cartesiano, la posizione in alto a sinistra è `0,0`. Ricordare anche di aggiungere il codice per interrompere *il comportamento predefinito*
> suggerimento: creare la funzione onKeyDown e attaccarla all'oggetto window:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...aggiungere il codice dalla lezione più sopra per fermare il comportamento predefinito
}
};
window.addEventListener("keydown", onKeyDown);
```
Controllare la console del browser a questo punto e osservare le sequenze di tasti che vengono registrate.
3. **Implementare** il [modello Pub/Sub](../../1-introduction/translations/README.it.md), questo manterrà il codice pulito mentre si seguono le parti rimanenti.
Per fare quest'ultima parte, si può:
1. **Aggiungere un event listener** all'oggetto window:
```javascript
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);
}
});
```
1. **Creare una classe EventEmitter** per pubblicare e sottoscrivere i messaggi:
```javascript
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));
}
}
}
```
1. **Aggiungere costanti** e impostare EventEmitter:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Inizializzare il gioco**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **Impostare il ciclo di gioco**
Rifattorizzare la funzione window.onload per inizializzare il gioco e impostare un ciclo di gioco su un buon intervallo. Aggiungere anche un raggio laser:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. **Aggiungere il codice** per spostare i nemici a un certo intervallo
Rifattorizzare la funzione `createEnemies()` per creare i nemici e inserirli nella nuova classe gameObjects:
```javascript
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);
}
}
}
```
e aggiungere una `funzione createHero()` per eseguire un processo simile per l'eroe.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
infine, aggiungere una funzione `drawGameObjects()` per avviare il disegno:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
I nemici dovrebbero iniziare ad avanzare verso l'astronave dell'eroe!
---
## 🚀 Sfida
Come si può vedere, il proprio codice può trasformarsi in ["spaghetti code"](https://it.wikipedia.org/wiki/Spaghetti_code) quando si inizia ad aggiungere funzioni, variabili e classi. Come si puo organizzare meglio il codice in modo che sia più leggibile? Disegnare un sistema per organizzare il proprio codice, anche se risiede ancora in un file.
## Quiz Post-Lezione
[Quiz post-lezione](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=it)
## Revisione e Auto Apprendimento
Mentre questo gioco viene scritto senza utilizzare infrastutture Javascript (framework), ci sono molti framework canvas basati su JavaScript per lo sviluppo di giochi. Ci si prenda un po' di tempo per [leggere qualcosa su questi](https://github.com/collections/javascript-game-engines).
## Compito
[Commentare il proprio codice](assignment.it.md)

View File

@@ -1,387 +0,0 @@
# スペースゲーム構築プロジェクト その 3: モーションの追加
## レッスン前の小テスト
[レッスン前の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=ja)
ゲームは、あなたが画面上を走り回るエイリアンを持っているまでは、あまり楽しいものではありません! このゲームでは、2種類の動きを使用しています。このゲームでは、2種類の動きを利用していきます。
- **キーボード/マウスの動き**: ユーザーがキーボードやマウスを操作して画面上のオブジェクトを移動させたとき
- **ゲームで誘導された動き**: ゲームが一定の時間間隔でオブジェクトを移動させたとき
では、どのようにして画面上で物を動かすのでしょうか? それはすべて直交座標に基づいています。オブジェクトの位置 (x,y) を変更してから、画面を再描画します。
通常、画面上で *移動* を行うには、以下の手順が必要です。
1. オブジェクトの**新しい位置を設定します**。これはオブジェクトが移動したと認識するために必要です
2. **画面をクリアします**が、これは描画の合間に画面をクリアする必要があります。背景色で塗りつぶす矩形を描くことでクリアできます
3. 新しい場所にオブジェクトを**再描画します**。これにより、ある場所から別の場所にオブジェクトを移動させることができます
コードではこんな感じになります。
```javascript
// hero の場所を決めます。
hero.x += 5;
// hero がいる長方形をクリアします。
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ゲームの背景と hero 描画し直します。
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ ヒーローを毎秒何フレームも描き直すとパフォーマンスコストが発生する理由が思いつきますか? [このパターンの代替案](https://www.html5rocks.com/ja/tutorials/canvas/performance/)を読んでみてください。
## キーボードイベントの処理
コードに特定のイベントをアタッチすることでイベントを処理します。キーボードイベントはウィンドウ全体でトリガーされますが、`click` のようなマウスイベントは特定の要素をクリックすることに接続することができます。このプロジェクトではキーボードイベントを使用します。
イベントを処理するには、ウィンドウの `addEventListener()` メソッドを使用し、2つの入力パラメータを指定する必要があります。最初のパラメータはイベントの名前で、例えば `keyup` のようなものです。2 番目のパラメータは、イベントの結果として呼び出される関数です。
以下に例を示します。
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = キーの文字列表現
if (evt.key === 'ArrowUp') {
// 何か処理をします。
}
})
```
キーイベントには、どのキーが押されたかを確認するために使用できる2つのプロパティがあります。
- `key`、これは押されたキーの文字列表現で、例えば `ArrowUp` のようなものです
- `keyCode`、これは数値表現であり、例えば `37``ArrowLeft` に対応します
✅ キーイベントの操作はゲーム開発以外でも有用です。他にはどのような用途が考えられますか?
### 特殊なキー: 注意事項
ウィンドウに影響を与える *特殊な* キーがあります。つまり、`keyup` イベントを聞いているときに、これらの特別なキーを使ってヒーローを動かした場合、水平スクロールも行われるということです。そのため、ゲームを構築する際には、このビルトインブラウザの動作を *shut-off* した方が良いかもしれません。このようなコードが必要です。
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // 矢印キー
case 32:
e.preventDefault();
break; // スペース
default:
break; // 他のキーをブロックしないでください。
}
};
window.addEventListener('keydown', onKeyDown);
```
上記のコードでは、矢印キーとスペースキーの *デフォルト* の動作が確実にシャットオフされます。*shut-off* メカニズムは `e.preventDefault()` を呼び出すときに発生します。
## ゲームで誘導された動き
`setTimeout()``setInterval()` 関数のようなタイマーを使うことで、オブジェクトの位置を目盛りや時間間隔ごとに更新することができます。これは次のようなものです。
```javascript
let id = setInterval(() => {
//敵を Y 軸で動かす
enemy.y += 10;
})
```
## ゲームループ
ゲームループとは、基本的には一定の間隔で呼び出される関数の概念です。ユーザーに見えるべきものはすべてループに描画されるので、ゲームループと呼ばれています。ゲームループはゲームの一部であるすべてのゲームオブジェクトを利用し、何らかの理由でゲームの一部ではない場合を除いて、すべてのオブジェクトを描画します。例えば、あるオブジェクトがレーザーで撃たれて吹き飛んでしまった場合、そのオブジェクトは現在のゲームループの一部ではなくなります (これについては後のレッスンで詳しく説明します)。
ゲームループがどのようなものか、コードで表現すると次のようになります。
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
上記のループは `200` ミリ秒ごとに呼び出され、キャンバスを再描画します。あなたのゲームに合った最適な間隔を選択することができます。
## スペースゲームの続き
既存のコードを使って、それを拡張していきます。パート で完成させたコードから始めるか、[パートⅡのスターター](../your-work)のコードを使います。
- **ヒーローの移動**: 矢印キーを使ってヒーローを移動できるようにコードを追加します
- **敵を移動する**: また、敵が与えられたレートで上から下に移動することを確認するためにコードを追加する必要があります
## 推奨される手順
あなたのために作成されたファイルを `your-work` サブフォルダ内で探します。以下のファイルが含まれているはずです。
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
次のコマンドをタイピングして、あなたのプロジェクトを `your_work` フォルダから開始します。
```bash
cd your-work
npm start
```
上記は、アドレス `http://localhost:5000` の HTTP サーバーを起動します。ブラウザを開いてそのアドレスを入力すると、今はヒーローと全ての敵が表示されるはずです。ただしまだ何も動いていません。
### コードの追加
1. `hero``enemy``game object`のための**オブジェクトを追加し**、それらは `x``y` のプロパティを持っている必要があります。([継承や合成](../../translations/README.ja.md)の部分を覚えておいてください)
*ヒント* `game object``x``y` を持ち、それ自身をキャンバスに描画する機能を持つものでなければなりません。
> tip: 以下のようにコンストラクタを定義した新しい GameObject クラスを追加してから、キャンバスに描画します。
```javascript
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);
}
}
```
次に、この GameObject を拡張して、ヒーローと敵を作成します。
```javascript
class Hero extends GameObject {
constructor(x, y) {
...x, y, type, speedが必要です。
}
}
```
```javascript
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)
}
}
```
2. (ヒーローを上下左右に動かす) キーナビゲーションを処理するための**キーイベントハンドラを追加します**
*REMEMBER* これは直交座標系で、左上は `0,0` です。また、*デフォルトの動作*を止めるコードを追加することも忘れないでください*。
> tip: onKeyDown 関数を作成して、それをウィンドウにアタッチします。
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
この時点でブラウザのコンソールを確認し、キー入力がログに記録されているかどうかを確認します。
3. [Pub Sub パターン](../../translations/README.ja.md)を**実装する**と、残りの部分に続くようにコードをきれいに保つことができます
この最後の部分を行うには
1. ウィンドウに**イベントリスナーを追加します**
```javascript
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);
}
});
```
1. メッセージを発行して購読するための **EventEmitter クラスを作成します**
```javascript
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));
}
}
}
```
1. **定数を追加**して EventEmitter を設定します
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **ゲームを初期化します**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **ゲームのループを設定します**
window.onload 関数をリファクタリングしてゲームを初期化し、良い間隔でゲームループを設定します。レーザービームも追加します。
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. 一定間隔で敵を移動させる**コードを追加します**
関数 `createEnemies()` をリファクタリングして敵を作成し、それを新しい gameObjects クラスにプッシュします。
```javascript
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);
}
}
}
```
そして `createHero()` 関数を追加して hero にも同様の処理を行います。
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
最後に `drawGameObjects()` 関数を追加して描画を開始します。
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
あなたの敵はあなたのヒーローの宇宙船で前進を開始する必要があります!
---
## 🚀 チャレンジ
ご覧のように、関数や変数、クラスを追加し始めると、あなたのコードは「スパゲッティコード」になってしまうことがあります。コードをより読みやすく整理するにはどうしたらいいでしょうか? 1つのファイルに存在していても、あなたのコードを整理するためのシステムをスケッチしてみましょう。
## レッスン後の小テスト
[レッスン後の小テスト](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=ja)
## 復習と自己学習
フレームワークを使わずにゲームを書いているうちに、JavaScript を使ったゲーム開発用の canvas フレームワークがたくさん出てきました。時間をかけて[これらについて読む](https://github.com/collections/javascript-game-engines)。
## 課題
[コードをコメントする](assignment.ja.md)

View File

@@ -1,389 +0,0 @@
# Space 게임 제작하기 파트 3: 모션 추가하기
## 강의 전 퀴즈
[Pre-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=ko)
외계인이 화면을 돌아다니기 전까지는 게임이 재미 없습니다! 이 게임에서는, 두 가지 타입의 동작을 씁니다:
- **키보드/마우스 동작**: 사용자가 키보드 또는 마우스와 상호작용하여 화면에서 개체를 움질일 때.
- **게임으로 움직이는 동작**: 게임이 일정 시간 간격으로 객체를 움직일 때.
그러면 화면에서 물건을 어떻게 움직일까요? 그것은 모두 직교 좌표에 관한 것입니다: 객체의 위치 (x, y)를 변경 한 뒤에 화면을 다시 그립니다.
일반적으로 화면에서 *이동*을 하려면 다음 단계가 필요합니다:
1. 객체의 **새로운 위치 설정하기**; 이는 객체가 움직인 것으로 인식하는 데 필요합니다.
2. **화면 비우기**, 화면은 그려지는 사이에 비워져야 합니다. 배경색으로 채운 사각형을 그려서 지울 수 있습니다.
3. 새로운 위치에서 **개체를 다시 그리기**. 최종적으로 한 위치에서 다른 위치로 객체를 이동합니다.
코드에서 다음과 같이 보일 수 있습니다:
```javascript
//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ 영웅을 초당 수 많은 프레임으로 다시 그리게 될 때 성능 비용이 발생하는 이유를 알 수 있나요? [alternatives to this pattern](https://www.html5rocks.com/en/tutorials/canvas/performance/)에 대하여 읽어보세요.
## 키보드 이벤트 제어하기
특정 이벤트를 코드에 연결하여 이벤트를 처리합니다. 키보드 이벤트는 전체 윈도우에서 연결되는 반면에 `click`과 같은 마우스 이벤트는 클릭하는 특정 요소에 연결할 수 있습니다. 이 프로젝트에서는 키보드 이벤트를 사용합니다.
이벤트를 처리하려면 윈도우의 `addEventListener ()` 메소드를 사용하고 두 개의 입력 파라미터를 제공해야 합니다. 첫 번째 파라미터는 이벤트의 이름입니다, 예시를 들자면 `keyup`과 같습니다. 두 번째 파라미터는 이벤트가 발생함에 따라 호출될 함수입니다.
여기는 예시입니다:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
```
키 이벤트의 경우에는 어떤 키를 눌렀는지 확인할 때 쓸 수 있는 이벤트로 두 가지 속성이 있습니다:
- `key`, 눌린 키의 문자열 표현입니다, 예를 들어 `ArrowUp` 입니다.
- `keyCode`, 숫자 표현입니다. 예를 들어 `37``ArrowLeft`에 해당합니다.
✅ 키 이벤트 조작은 게임 개발 외부에서 유용합니다. 이 기술의 다른 사용법은 무엇일까요?
### 특별한 키: a caveat
윈도우에 영향을 주는 몇 가지 *특별한* 키가 있습니다. 즉 `keyup` 이벤트를 듣고 이 특별한 키를 사용하면 영웅을 이동하여 가로 스크롤도 할 수 있다는 것을 의미합니다. 따라서 게임을 제작할 때는 이 내장 브라우저 동작을 *차단* 할 수 있습니다. 다음과 같은 코드가 필요합니다:
```javascript
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);
```
위 코드는 화살표-키와 스페이스 키의 *기본* 동작을 막습니다. *차단* 메커니즘은 `e.preventDefault()`를 호출할 때 발생합니다.
## 게임의 움직임
각 틱 또는 시간 간격에서 객체의 위치를 업데이트하는 `setTimeout()` 또는 `setInterval()` 함수 같은 타이머를 사용하여 스스로 움직일 수 있습니다. 다음과 같이 보입니다:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
})
```
## 게임 루프
게임 루프는 기본적으로 일정한 간격마다 호출되는 함수인 개념입니다. 사용자에게 보여줄 모든 것이 루프에 그려지므로 이것을 게임 루프라고 합니다. 게임 루프는 게임의 일부인 모든 게임 객체를 사용하여, 모종의 이유로 더 이상 게임의 일부가 아니지 않는 이상 다 그립니다. 예를 들면 객체가 레이저에 맞아 폭발한 적이 있다면 더 이상 현재 게임 루프의 일부가 아닙니다 (다음 단원에서 자세히 알아 볼 것입니다).
다음은 일반적으로 코드로 표현된 게임 루프의 모습입니다:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
캔버스를 다시 그리기 위해 위의 루프가 `200` milliseconds 마다 호출됩니다. 게임에 가장 적합한 간격을 고를 수 있습니다.
## Space 게임 계속하기
기존 코드를 가져와 확장합니다. 파트 I 에서 작성한 코드로 시작하거나 [Part II- starter](../your-work)의 코드를 사용합니다.
- **영웅을 움직이기**: 화살표 키를 사용하여 영웅을 이동할 수 있도록 코드를 추가합니다.
- **적을 움직이기**: 적들이 주어진 속도로 상단에서 하단으로 이동할 수 있도록 코드를 추가합니다.
## 권장 단계
`your-work` 하위 폴더에 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
타이핑하여 `your_work` 폴더에서 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하면 영웅과 모든 적을 렌더링 해야하지만; 아무것도 움직이지 않습니다 - 아직!
### 코드 추가하기
1. `영웅``적` 그리고 `게임 객체`에 대한 **전용 객체를 추가합니다**. `x` 혹은 `y` 속성이 필요합니다. ([Inheritance or composition](../README.md) 파트를 기억하세요).
*힌트* `game object``x``y`가 있으면서 canvas에 그릴 수 있는 능력이 되어야 합니다.
>tip: 생성자가 아래와 같이 이루어진 새로운 GameObject 클래스를 추가하여, 시작한 뒤에 canvas로 그립니다:
```javascript
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);
}
}
```
이제, GameObject를 확장하여 영웅과 적을 생성합니다.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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)
}
}
```
2. **키-이벤트 핸들러를 추가**하여 키 탐색을 처리합니다 (Hero를 상/하 좌/우로 이동).
*기억합시다* 데카르트 시스템이고, 좌측 상단은 `0,0`입니다. 또한 *기본 동작*을 중지하는 코드를 추가해야 합니다
>tip: onKeyDown 함수를 만들고 윈도우에 붙입니다.
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
이 지점에서 브라우저 콘솔을 확인해봅니다, 그리고 로깅되는 키 입력을 봅니다.
3. [Pub sub pattern](../README.md)으로 **구현합니다**, 이는 남은 파트를 따라가면서 코드를 깨끗하게 유지할 수 있습니다.
이 마지막 파트를 진행하면, 다음을 할 수 있습니다:
1. 윈도우에 **Add an 이벤트 리스너를 추가합니다**:
```javascript
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);
}
});
```
1. publish하고 메시지를 subscribe할 **EventEmitter 클래스를 생성합니다**:
```javascript
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));
}
}
}
```
1. EventEmitter를 설정하고 **constants를 추가합니다**:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **게임을 초기화합니다**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **게임 루프를 설정합니다**
window.onload 함수를 리팩터링하여 게임을 초기화하고 적절한 간격으로 게임 루프를 설정합니다. 레이저 빔도 추가합니다:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. 일정 간격으로 움직이는 적에 대한 **코드를 작성합니다**
`createEnemies()`함수를 리팩터링하여 적을 생성하고 새로운 gameObjects 클래스로 푸시합니다:
```javascript
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);
}
}
}
```
그리고 비슷한 과정으로 영웅에 대한 `createHero()` 함수를 추가합니다.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
그리고 마지막으로, 그리기를 시작할 `drawGameObjects()` 함수를 추가합니다:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
적들이 영웅 spaceship의 앞으로 나아가려고 합니다!
---
## 🚀 도전
보다가, 함수와 변수 및 클래스를 추가하기 시작하면 코드가 '스파게티 코드'로 변할 수 있습니다. 코드를 더 읽기 쉽게 구성하려면 어떻게 해야 될까요? 코드가 여전히 하나의 파일에 있어도, 어울리는 시스템을 기획하시기 바랍니다.
## 강의 후 퀴즈
[Post-lecture quiz](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=ko)
## 리뷰 & 자기주도 학습
프레임워크를 사용하지 않고 게임을 작성하는 동안, 게임 개발을 위한 JavaScript-기반 canvas 프레임워크가 많이 존재하고 있습니다. 시간을 내어 [about these](https://github.com/collections/javascript-game-engines)를 보시기 바랍니다.
## 과제
[Comment your code](../assignment.md)

View File

@@ -1,388 +0,0 @@
# Bina Permainan Angkasa Bahagian 3: Menambah Gerakan
## Kuiz Pra Kuliah
[Kuiz Pra Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33)
Permainan tidak begitu menyeronokkan sehingga anda mempunyai makhluk asing di layar! Dalam permainan ini, kami akan menggunakan dua jenis pergerakan:
- **Pergerakan papan kekunci / Tetikus**: ketika pengguna berinteraksi dengan papan kekunci atau tetikus untuk menggerakkan objek di layar.
- **Pergerakan yang disebabkan oleh permainan**: ketika permainan menggerakkan objek dengan selang waktu tertentu.
Jadi bagaimana kita memindahkan sesuatu di skrin? Ini semua mengenai koordinat kartesian: kami menukar lokasi (x, y) objek dan kemudian melukis semula skrin.
Biasanya anda memerlukan langkah-langkah berikut untuk menyelesaikan *pergerakan* di skrin:
1. **Tetapkan lokasi baru** untuk objek; ini diperlukan untuk melihat objek sebagai bergerak.
2. **Kosongkan skrin**, skrin perlu dibersihkan di antara undian. Kita dapat membersihkannya dengan melukis sebuah segi empat tepat yang kita isi dengan warna latar belakang.
3. **Lukis semula objek** di lokasi baru. Dengan melakukan ini kita akhirnya dapat memindahkan objek dari satu lokasi ke lokasi lain.
Inilah rupa bentuknya dalam kod:
```javascript
//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ Bolehkah anda memikirkan sebab mengapa menggambar semula pahlawan anda dengan banyak bingkai sesaat mungkin menambah kos prestasi? Baca mengenai [alternatif untuk corak ini](https://www.html5rocks.com/en/tutorials/canvas/performance/).
## Mengendalikan acara papan kekunci
Anda mengendalikan acara dengan melampirkan acara tertentu ke kod. Peristiwa papan kekunci dipicu di seluruh tetingkap sedangkan peristiwa tetikus seperti `click` dapat dihubungkan dengan mengklik elemen tertentu. Kami akan menggunakan acara papan kekunci sepanjang projek ini.
Untuk menangani suatu peristiwa, anda perlu menggunakan kaedah ``addEventListener()` tetingkap dan memberikannya dua parameter input. Parameter pertama adalah nama acara, misalnya `keyup`. Parameter kedua adalah fungsi yang harus dipanggil sebagai akibat dari peristiwa yang berlaku.
Inilah contohnya:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
```
Untuk acara utama terdapat dua sifat pada acara yang boleh anda gunakan untuk melihat kunci apa yang ditekan:
- `key`, ini adalah representasi rentetan dari kekunci yang ditekan, misalnya `ArrowUp`
- `keyCode`, ini adalah representasi angka, misalnya `37`, sesuai dengan `ArrowLeft`.
✅ Manipulasi acara utama berguna di luar pengembangan permainan. Apa kegunaan lain yang dapat anda fikirkan untuk teknik ini?
### Kekunci khas: peringatan
Terdapat beberapa *kunci* khas yang mempengaruhi tetingkap. Ini bermaksud bahawa jika anda sedang mendengar acara `keyup` dan anda menggunakan kekunci khas ini untuk menggerakkan wira anda, ia juga akan melakukan tatal mendatar. Untuk itu anda mungkin mahu *mematikan* tingkah laku penyemak imbas terbina dalam ini semasa anda membina permainan anda. Anda memerlukan kod seperti ini:
```javascript
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);
```
Kod di atas akan memastikan bahawa anak panah dan kekunci spasi mempunyai tingkah laku *lalai* mereka. Mekanisme *shut-off* berlaku apabila kita memanggil `e.preventDefault()`.
## Pergerakan yang disebabkan oleh permainan
Kita dapat membuat sesuatu bergerak dengan menggunakan pemasa seperti fungsi `setTimeout()` atau `setInterval()` yang mengemas kini lokasi objek pada setiap centang, atau selang waktu. Begini rupa:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
})
```
## Gelung permainan
Gelung permainan adalah konsep yang pada dasarnya adalah fungsi yang dipanggil pada selang waktu yang tetap. Ia dipanggil gelung permainan kerana segala sesuatu yang dapat dilihat oleh pengguna ditarik ke dalam gelung. Gelung permainan menggunakan semua objek permainan yang menjadi bagian dari permainan, menggambar semuanya kecuali untuk beberapa alasan tidak seharusnya menjadi bagian dari permainan lagi. Contohnya jika objek adalah musuh yang terkena laser dan meletup, ia bukan lagi bahagian dari gelung permainan semasa (anda akan mengetahui lebih lanjut mengenai perkara ini dalam pelajaran berikutnya).
Inilah rupa gelung permainan, yang dinyatakan dalam kod:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
Gelung di atas dipanggil setiap `200` milisaat untuk menggambar semula kanvas. Anda mempunyai kemampuan untuk memilih selang terbaik yang sesuai untuk permainan anda.
## Meneruskan Permainan Angkasa
Anda akan mengambil kod yang ada dan memanjangkannya. Mulakan dengan kod yang anda lengkapkan semasa bahagian I atau gunakan kod di [Bahagian II- starter] (karya anda).
- **Memindahkan pahlawan**: anda akan menambah kod untuk memastikan anda dapat memindahkan pahlawan menggunakan kekunci anak panah.
- **Pindahkan musuh**: anda juga perlu menambahkan kod untuk memastikan musuh bergerak dari atas ke bawah pada kadar tertentu.
## Langkah yang disyorkan
Cari fail yang telah dibuat untuk anda dalam sub folder `your-work`. Ia harus mengandungi yang berikut:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Anda memulakan projek anda folder `your_work` dengan mengetik:
```bash
cd your-work
npm start
```
Perkara di atas akan memulakan Pelayan HTTP pada alamat `http: // localhost: 5000`. Buka penyemak imbas dan masukkan alamat itu, sekarang ia harus menjadikan pahlawan dan semua musuh; tidak ada yang bergerak - namun!
### Tambah kod
1. **Tambahkan objek khusus** untuk `hero` dan `musuh` dan `objek permainan`, mereka harus mempunyai sifat `x` dan `y`. ( Ingat bahagian pada [Warisan atau komposisi](../../translations/README.ms.md) ).
*HINT* `objek permainan` harus menjadi objek dengan `x` dan `y` dan kemampuan untuk menarik dirinya ke kanvas.
> tip: mulakan dengan menambahkan kelas GameObject baru dengan konstruktornya digambarkan seperti di bawah, dan kemudian lukiskannya ke kanvas:
```javascript
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);
}
}
```
Sekarang, panjangkan GameObject ini untuk membuat Wira dan Musuh.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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)
}
}
```
2. **Tambahkan pengendali acara utama** untuk mengendalikan navigasi utama (pusing pahlawan ke atas / bawah kiri / kanan)
*INGAT* ini adalah sistem kartesian, kiri atas adalah `0,0`. Juga ingat untuk menambah kod untuk menghentikan *tingkah laku lalai*
> tip: buat fungsi onKeyDown anda dan pasangkannya ke tetingkap:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
Periksa konsol penyemak imbas anda pada ketika ini, dan perhatikan penekanan kekunci dicatat.
3. **Terapkan** the [Pub sub pattern](../../translations/README.ms.md), ini akan memastikan kod anda tetap bersih semasa anda mengikuti bahagian yang tinggal.
Untuk melakukan bahagian terakhir ini, anda boleh:
1. **Tambahkan pendengar acara** di tetingkap:
```javascript
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);
}
});
```
1. **Buat kelas EventEmitter** untuk menerbitkan dan melanggan mesej:
```javascript
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));
}
}
}
```
1. **Tambah pemalar** dan sediakan EventEmitter:
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **Memulakan permainan**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **Siapkan gelung permainan**
Memfaktorkan semula fungsi window.onload untuk memulakan permainan dan mengatur gelung permainan pada selang waktu yang baik. Anda juga akan menambah sinar laser:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. **Tambahkan kod** untuk memindahkan musuh pada selang waktu tertentu
Refactor fungsi `createEnemies()` untuk membuat musuh dan mendorong mereka ke kelas gameObjects yang baru:
```javascript
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);
}
}
}
```
dan tambahkan fungsi `createHero()` untuk melakukan proses yang serupa untuk wira.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
dan akhirnya, tambahkan fungsi `drawGameObjects()` untuk memulakan lukisan:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
Musuh anda harus mula maju di kapal angkasa wira anda!
---
## 🚀 Cabaran
Seperti yang anda lihat, kod anda boleh berubah menjadi 'spaghetti code' apabila anda mula menambahkan fungsi dan pemboleh ubah dan kelas. Bagaimana anda dapat mengatur kod anda dengan lebih baik agar lebih mudah dibaca? Lakarkan sistem untuk mengatur kod anda, walaupun masih terdapat dalam satu fail.
## Kuiz Pasca Kuliah
[Kuiz Pasca Kuliah](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34)
## Mengkaji & Belajar Sendiri
Semasa kami menulis permainan kami tanpa menggunakan kerangka kerja, ada banyak kerangka kanvas berdasarkan JavaScript untuk pengembangan permainan. Luangkan sedikit masa untuk melakukan [membaca mengenai perkara ini](https://github.com/collections/javascript-game-engines).
## Tugasan
[Komen kod anda](assignment.ms.md)

View File

@@ -1,388 +0,0 @@
# 建立太空遊戲 Part 3加入動作
## 課前測驗
[課前測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/33?loc=zh_tw)
有外星人在移動的遊戲才會好玩!在這款遊戲中,我們會建立兩種移動模式:
- **鍵盤滑鼠的移動**:當使用者控制鍵盤或滑鼠時,能移動畫面上的物件。
- **遊戲內建的移動**:遊戲能自動地在一定時間內,移動其中的物件。
那我們該如何移動畫面上的物件呢?這都取決於笛卡爾座標系:我們改變物件的座標 (x,y),並在畫面上重新繪製出來。
通常你需要下列流程來*移動*畫面上的物件:
1. **設定物件的新地點**,你才能察覺到物件有所移動。
2. **清除畫面**,每一次的繪製間都需要將畫面清除乾淨。我們可以繪製一張背景色的矩形來覆蓋畫面。
3. **在新地點重新繪製物件**,我們就能移動物件,從 A 點移動到 B 點。
合理的程式碼如下所示:
```javascript
// 設定英雄位置
hero.x += 5;
// 利用矩形清除英雄
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 重新繪製背景與英雄
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ 你能了解為什麼在同一秒內多次重新繪製英雄會影響效能的原因嗎?閱讀[其他種同目的之設計模式](https://www.html5rocks.com/en/tutorials/canvas/performance/)。
## 處理鍵盤事件
連接特定事件到程式中,你就能處理遊戲事件。鍵盤事件可以在視窗被選擇時觸發,而滑鼠事件如 `click`,則要點擊特定的物件。我們會在這個專案中,使用鍵盤物件。
要處理一種事件,需要使用視窗的 `addEventListener()` 方法,並提供給它兩個參數。第一個參數是事件的名稱,例如: `keyup`。第二個參數是回應事件結果的被呼叫函式。
下列是一種例子:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = 按鍵字串
if (evt.key === 'ArrowUp') {
// 做某事
}
})
```
鍵盤事件有兩個屬性來判別被按壓的按鍵:
- `key`,使用字串名稱表達該按鍵,例如: `ArrowUp`
- `keyCode`,使用數字呈現,例如 `37` 會對應到 `ArrowLeft`
✅ 除了遊戲開發以外,鍵盤事件也是十分實用的功能。你能想到其他使用相同技術的應用嗎?
### 特殊按鍵之限制
有許多*特殊*按鍵會影響視窗。這代表若我們正監聽著 `keyup` 事件,這個按鍵同時也會執行視窗的滾動行為。某些時候你會需要*關閉*這些瀏覽器中預設的行為,好比是建立這款遊戲時。你需要下列的程式:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // 方向鍵
case 32:
e.preventDefault();
break; // 空白鍵
default:
break; // 不阻止其他按鍵
}
};
window.addEventListener('keydown', onKeyDown);
```
上述的程式碼能確保方向鍵與空白鍵關閉*預設*的行為。這個*關閉*機制會在我們呼叫 `e.preventDefault()` 時觸發。
## 遊戲內建的移動
我們可以讓物件自己移動,利用計時器如 `setTimeout()` 或是 `setInterval()` 這兩個函式,隨著秒數間隔更新物件的位置。如下方呈現:
```javascript
let id = setInterval(() => {
// 在 y 軸上移動敵人
enemy.y += 10;
})
```
## 遊戲迴圈
遊戲迴圈是個重要概念,定期地呼叫必須執行的函式。之所以被稱作遊戲迴圈也是基於所有東西會在一個迴圈中呈現給玩家。遊戲迴圈會利用到所有的遊戲物件,並依據各個情況與理由決定是否要繪製出它們。舉例來說,當一個敵人被雷射擊中,爆炸了。他就不應該存在於現在的遊戲迴圈中。你會在後續的課程學到更多此概念。
這是一個遊戲迴圈的基本格式,以程式碼表達如下:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
上述的迴圈會每 `200` 毫秒重新繪製 Canvas。你能自由地判斷哪種時長更適合套用在你的遊戲中。
## 繼續我們的太空遊戲
你會利用現有的程式碼來擴增我們的專案。你可以使用你在 Part I 完成的程式,或是使用 [Part II - Starter](../your-work) 這包程式。
- **移動英雄**:你需要加入程式,確保你可以使用方向鍵來移動主角。
- **移動敵人**:你也需要加入程式,確保敵人能定期地由上往下移動。
## 建議步驟
在你的 `your-work` 子資料夾中,確認檔案是否建立完成。它應該包括:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
開始 `your_work` 資料夾中的專案,輸入:
```bash
cd your-work
npm start
```
這會啟動 HTTP 伺服器並發布網址 `http://localhost:5000`。開啟瀏覽器並輸入該網址,現在它能呈現英雄以及所有的敵人,但它們還沒辦法移動!
### 加入程式碼
1. **加入特定物件** `hero``enemy``game object`,它們皆有 `x``y` 位置屬性。(記得課程[繼承與組合](../../translations/README.zh-tw.md)中的片段)。
*提示* `game object` 要有 `x``y`,以及繪製到畫布上的能力。
>要點:開始建立 GameObject class ,結構如下所示,再繪製到畫布上:
```javascript
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);
}
}
```
現在,延伸 GameObject 來建立英雄與敵人。
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
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)
}
}
```
2. **加入鍵盤事件處理器**以處理鍵盤輸入(移動英雄的上下左右)
*記住* 這是笛卡爾座標系,左上方為 `0,0`。也請記得關閉鍵盤的*預設行為*
>要點:建立函式 onKeyDown 並連接到視窗中:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
這時候檢查你的瀏覽器命令欄,看看是否能偵測到鍵盤輸入。
3. **建立**[發布訂閱模式](../../translations/README.zh-tw.md),這能讓剩下的程式段落保持乾淨。
要做到此步驟,你可以:
1. **建立視窗的事件監聽者**
```javascript
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);
}
});
```
1. **建立 EventEmitter class** 以發布及訂閱訊息:
```javascript
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));
}
}
}
```
1. **建立常數**並設定 EventEmitter
```javascript
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",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **初始化遊戲**
```javascript
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 -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **設定遊戲迴圈**
重構函式 window.onload 來初始化遊戲,設定遊戲迴圈的定時間隔。你還需要加入雷射光:
```javascript
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");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. **加入程式**來定期地移動敵人
重構函式 `createEnemies()` 以建立敵人們,接到 gameObjects 中:
```javascript
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);
}
}
}
```
新增函式 `createHero()` 來為英雄做相同的事情。
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
最後,建立函式 `drawGameObjects()` 以開始繪製:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
你的敵人開始會朝你的英雄艦艇前進!
---
## 🚀 挑戰
如你所見,在加入零零總總的函式、變數與 class 後,你的程式變成了「麵條式代碼(spaghetti code)」。你能有效的編排你的程式,讓它更容易被閱讀?勾劃出一個系統來組織你的程式碼,即使所有東西都在一個檔案中。
## 課後測驗
[課後測驗](https://ashy-river-0debb7803.1.azurestaticapps.net/quiz/34?loc=zh_tw)
## 複習與自學
我們並沒有使用框架(frameworks)來編寫我們的遊戲,現在有許多 JavaScript 基底的 Canvas 框架,提供給遊戲開發使用。花點時間[閱讀這些框架](https://github.com/collections/javascript-game-engines)。
## 作業
[為你的程式做註解](assignment.zh-tw.md)

View File

@@ -1,11 +0,0 @@
# Comente su código
## Instrucciones
Revisa tu archivo /app.js actual en la carpeta de tu juego y busca formas de comentarlo y ordenarlo. Es muy fácil que el código se salga de control y ahora es una buena oportunidad para agregar comentarios para asegurarse de que tiene un código legible para que pueda usarlo más tarde.
## Rúbrica
| Criterios | Ejemplar | Adecuado | Necesita mejorar |
| -------- | ---------------- | ------------------------------------- | ------------ |
| | El código `app.js` está completamente comentado y organizado en bloques lógicos | El código `app.js` está adecuadamente comentado | El código `app.js` está algo desorganizado y carece de buenos comentarios |

View File

@@ -1,11 +0,0 @@
# Commentez votre code
## Instructions
Parcourez votre fichier /app.js actuel dans votre dossier de jeu et trouvez des moyens de le commenter et de l'organiser. Il est très facile pour le code de devenir incontrôlable, et c'est maintenant une bonne occasion d'ajouter des commentaires pour s'assurer que vous disposez d'un code lisible afin que vous puissiez l'utiliser plus tard.
## Rubrique
| Critères | Exemplaire | Adéquat | Besoin d'amélioration |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | Le code `app.js` est entièrement commenté et organisé en blocs logiques | Le code `app.js` est correctement commenté | Le code `app.js` est quelque peu désorganisé et manque de bons commentaires |

View File

@@ -1,11 +0,0 @@
# अपना कोड कमेंट करें
## अनुदेश
अपने गेम फ़ोल्डर में अपने वर्तमान /app.js फ़ाइल पर जाएं, और इसे टिप्पणी करने और इसे साफ करने के तरीके खोजें. कोड को नियंत्रण से बाहर करना बहुत आसान है, और अब यह सुनिश्चित करने के लिए टिप्पणियां जोड़ने का एक अच्छा मौका है कि आपके पास पठनीय कोड है ताकि आप इसे बाद में उपयोग कर सकें.
## शीर्ष
| मानदंड | उदाहरणात्मक | पर्याप्त | सुधार की जरूरत |
| ------ | -------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------- |
| | `app.js` कोड पूरी तरह से टिप्पणी की है और तार्किक ब्लॉकों में व्यवस्थित है | `app.js` कोड पर्याप्त रूप से टिप्पणी की है | `app.js` कोड कुछ हद तक अव्यवस्थित है और अच्छी टिप्पणियों का अभाव है |

View File

@@ -1,11 +0,0 @@
# Commentare il proprio codice
## Istruzioni
Eseminare il file /app.js corrente nella cartella del gioco e trovare i modi per commentarlo e riordinarlo. È molto facile che il codice sfugga al controllo e ora è una buona occasione per aggiungere commenti per assicurarsi di avere codice leggibile in modo da poterlo utilizzare in seguito.
## Rubrica
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | Il codice in `app.js` è completamente commentato e organizzato in blocchi logici | Il codice in `app.js` è adeguatamente commentato | Il codice in `app.js` è in qualche modo disorganizzato e manca di buoni commenti |

View File

@@ -1,11 +0,0 @@
# コードをコメントする
## 説明書
ゲームフォルダ内の現在の /app.js ファイルに目を通し、コメントを付けて片付ける方法を見つけてください。コードはいとも簡単に制御不能になります。今はコメントを追加して、後で使えるように読みやすいコードにする良い機会です。
## ルーブリック
| 基準 | 模範的な例 | 適切な | 改善が必要 |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | `app.js` のコードは完全にコメントされ、論理的なブロックに整理されています。 | `app.js` のコードは適切にコメントされています。 | `app.js` のコードはやや乱れていて、良いコメントがありません。 |

View File

@@ -1,11 +0,0 @@
# 코드 주석 달기
## 설명
게임 폴더에 있는 /app.js 파일을 살펴보고, 주석을 달고 정리해 봅시다. 코드가 많아지면 통제하기가 어려워질 수 있습니다. 지금 주석을 추가해서 나중에도 코드를 쉽게 파악할 수 있도록 조치해 봅시다.
## 평가 기준
기준 | 모범 답안 | 적당한 답안 | 개선이 필요한 답안
--- | --- | --- | ---
| `app.js` 코드가 주석으로 충분히 설명되어 있고 논리적으로 설계된 경우 | `app.js` 코드가 주석으로 적절히 설명된 경우 | `app.js` 코드가 주석으로 충분히 설명되지 않고 복잡하게 설계된 경우

View File

@@ -1,11 +0,0 @@
# Komen Kod Anda
## Arahan
Periksa fail /app.js semasa anda di folder permainan anda, dan cari cara untuk mengomentarinya dan rapi. Sangat mudah bagi kod untuk tidak terkawal, dan sekarang adalah peluang yang baik untuk menambahkan komen untuk memastikan bahawa anda mempunyai kod yang dapat dibaca sehingga anda dapat menggunakannya kemudian.
## Rubrik
| Kriteria | Contoh | Mencukupi | Usaha Lagi |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | Kod `app.js` dikomentari sepenuhnya dan disusun menjadi blok logik | Kod `app.js` diberi komen yang mencukupi | Kod `app.js` agak tidak teratur dan tidak mempunyai komen yang baik |

View File

@@ -1,11 +0,0 @@
# Becommentarieer uw code
## Instructies
Bekijk uw huidige /app.js-bestand in uw gamemap en zoek manieren om er commentaar op te geven en het op te ruimen. Het is heel gemakkelijk dat code uit de hand loopt, en dit is een goede kans om opmerkingen toe te voegen om ervoor te zorgen dat u leesbare code heeft, zodat u deze later kunt gebruiken.
## Rubriek
| Criteria | Voorbeeldig | Voldoende | Moet worden verbeterd |
| -------- | ------------------------------------------------------------------ | ------------------------------------- | -------------------------------------------------------------- |
| | `app.js` code is volledig becommentarieerd en georganiseerd in logische blokken | `app.js` code is voldoende becommentarieerd | De code `app.js` is enigszins ongeorganiseerd en er ontbreken goede commentaren |

View File

@@ -1,11 +0,0 @@
# 為你的程式做註解
## 簡介
打開遊戲資料夾中目前的 /app.js 檔案,試著幫它做上註解並整理乾淨。程式碼很容易脫離掌控,現在是個好機會來確保你的程式是容易去閱讀的,在未來還可以被使用。
## 學習評量
| 作業內容 | 優良 | 普通 | 待改進 |
| -------- | ----------------------------- | ----------------------- | ----------------------- |
| | `app.js` 完整地註解且分塊整理 | `app.js` 有做充分的註解 | `app.js` 凌亂且缺乏註解 |