parent
d25580e020
commit
b3a64cf551
@ -0,0 +1,225 @@
|
||||
# Costruire un Gioco Spaziale Parte 1: Introduzione
|
||||
|
||||
![video](../../images/pewpew.gif)
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/29)
|
||||
|
||||
### Ereditarietà e Composizione nello sviluppo del gioco
|
||||
|
||||
Nelle lezioni precedenti, non c'era molto bisogno di preoccuparsi dell'architettura di progettazione delle app che sono state create, poiché i progetti erano di portata molto ridotta. Tuttavia, quando le dimensioni e l'ambito delle applicazioni aumentano, le decisioni sull'architettura diventano una preoccupazione maggiore. Esistono due approcci principali per creare applicazioni più grandi in JavaScript: *composizione* o *ereditarietà*. Ci sono pro e contro in entrambi, che verranno spiegiati dall'interno del contesto di un gioco.
|
||||
|
||||
✅ Uno dei libri di programmazione più famosi mai scritti ha a che fare con [i modelli di progettazione](https://it.wikipedia.org/wiki/Design_Patterns).
|
||||
|
||||
In un gioco ci sono `oggetti di gioco` che esistono su uno schermo. Ciò significa che hanno una posizione su un sistema di coordinate cartesiane, caratterizzati dall'avere una coordinata `x` e una `y` . Man mano che si sviluppa un gioco si noterà che tutti gli oggetti di gioco hanno una proprietà standard, comune a ogni gioco che si crea, ovvero elementi che sono:
|
||||
|
||||
- **basati sulla posizione** La maggior parte, se non tutti, gli elementi del gioco sono basati sulla posizione. Ciò significa che hanno una posizione, una `x` e una `y`.
|
||||
- **mobili** Questi sono oggetti che possono spostarsi in una nuova posizione. Tipicamente un eroe, un mostro o un personaggio non giocante (NPC - Non Player Character), ma non, ad esempio, un oggetto statico come un albero.
|
||||
- **autodistruggenti** Questi oggetti esistono solo per un determinato periodo di tempo prima che vengano contrassegnati per l'eliminazione. Di solito questo è rappresentato da un booleano `morto` o `distrutto` che segnala al motore di gioco che questo oggetto non dovrebbe più essere renderizzato.
|
||||
- **raffreddamento** Il "raffreddamento" è una proprietà tipica tra gli oggetti di breve durata. Un classico esempio è un pezzo di testo o un effetto grafico come un'esplosione che dovrebbe essere visto solo per pochi millisecondi.
|
||||
|
||||
✅ Si pensi a un gioco come Pac-Man. Si riescono a identificare i quattro tipi di oggetti sopra elencati in questo gioco?
|
||||
|
||||
### Esprimere il comportamento
|
||||
|
||||
Quanto descritto sopra è il comportamento che possono avere gli oggetti di gioco. Allora come vanno codificati? Si può esprimere questo comportamento tramite metodi associati a classi o oggetti.
|
||||
|
||||
**Classi**
|
||||
|
||||
L'idea è di usare `classi` insieme all'`ereditarietà` per ottenere l'aggiunta di un determinato comportamento a una classe.
|
||||
|
||||
✅ L'ereditarietà è un concetto importante da comprendere. Ulteriori informazioni sull'[articolo di MDN sull'ereditarietà](https://developer.mozilla.org/it/docs/Web/JavaScript/Inheritance_and_the_prototype_chain).
|
||||
|
||||
Espresso tramite codice, un oggetto di gioco può tipicamente avere questo aspetto:
|
||||
|
||||
```javascript
|
||||
|
||||
//Imposta la classe GameObject
|
||||
class GameObject {
|
||||
constructor(x, y, type) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
//Questa classe estenderà le proprietà di classe di GameObject
|
||||
class Movable extends GameObject {
|
||||
constructor(x,y, type) {
|
||||
super(x,y, type)
|
||||
}
|
||||
|
||||
//questo oggetto movibile può essere spostato nello schermo
|
||||
moveTo(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
//classe specifica che estende la classe Movable, per poter approfittare di tutte le proprietà che eredita
|
||||
class Hero extends Movable {
|
||||
constructor(x,y) {
|
||||
super(x,y, 'Hero')
|
||||
}
|
||||
}
|
||||
|
||||
//questa classe, d'altro canto, eredita solo le proprietà di GameObject
|
||||
class Tree extends GameObject {
|
||||
constructor(x,y) {
|
||||
super(x,y, 'Tree')
|
||||
}
|
||||
}
|
||||
|
||||
//un eroe può spostarsi...
|
||||
const hero = new Hero();
|
||||
hero.moveTo(5,5);
|
||||
|
||||
//ma un albero no
|
||||
const tree = new Tree();
|
||||
```
|
||||
|
||||
✅ Ci si prenda qualche minuto per rivedere un eroe di Pac-Man (Inky, Pinky o Blinky, per esempio) e come sarebbe scritto in JavaScript.
|
||||
|
||||
**Composizione**
|
||||
|
||||
Un modo diverso di gestire l'ereditarietà degli oggetti consiste nell'usare la *composizione*. Con questo sistema gli oggetti esprimono il loro comportamento in questo modo:
|
||||
|
||||
```javascript
|
||||
//crea una costante gameObject
|
||||
const gameObject = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
type: ''
|
||||
};
|
||||
|
||||
//...e una costante movable
|
||||
const movable = {
|
||||
moveTo(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
//poi la costante movableObject viene composta dalle costanti gameObject e movable
|
||||
const movableObject = {...gameObject, ...movable};
|
||||
|
||||
//quindi si scrive una funzione per crear un nuovo eroe che eredita le proprietà di movableObject
|
||||
function createHero(x, y) {
|
||||
return {
|
||||
...movableObject,
|
||||
x,
|
||||
y,
|
||||
type: 'Hero'
|
||||
}
|
||||
}
|
||||
//...e un oggetto statico che eredita solo le proprietà di gameObject
|
||||
function createStatic(x, y, type) {
|
||||
return {
|
||||
...gameObject
|
||||
x,
|
||||
y,
|
||||
type
|
||||
}
|
||||
}
|
||||
//crea l'eroe e lo muove
|
||||
const hero = createHero(10,10);
|
||||
hero.moveTo(5,5);
|
||||
//e crea un albero statico immobile
|
||||
const tree = createStatic(0,0, 'Tree');
|
||||
```
|
||||
|
||||
**Quale modello si dovrebbe usare?**
|
||||
|
||||
Dipende dallo sviluppatore quale modello scegliere. JavaScript supporta entrambi questi paradigmi.
|
||||
|
||||
--
|
||||
|
||||
Un altro modello comune nello sviluppo di giochi affronta il problema della gestione dell'esperienza utente e delle prestazioni del gioco.
|
||||
|
||||
## Modello pub/sub
|
||||
|
||||
✅ Pub/Sub sta per pubblica/sottoscrivi ('publish-subscribe')
|
||||
|
||||
Questo modello indirizza l'idea che parti disparate della propria applicazione non dovrebbero sapere l'una dell'altra. Perché? Rende molto più facile vedere cosa sta succedendo in generale se le varie parti sono separate. Inoltre, è più facile cambiare improvvisamente un comportamento se necessario. Come si realizza? Si fa stabilendo alcuni concetti:
|
||||
|
||||
- **messaggio**: un messaggio è solitamente una stringa di testo accompagnata da un payload opzionale (un dato che chiarisce di cosa tratta il messaggio). Un messaggio tipico in un gioco può essere `KEY_PRESSED_ENTER`.
|
||||
- **publisher**: questo elemento *pubblica* un messaggio e lo invia a tutti i sottoscrittori.
|
||||
- **subscriber**: questo elemento *ascolta* messaggi specifici e svolge alcune attività come risultato della ricezione di questo messaggio, come sparare con un laser.
|
||||
|
||||
L'implementazione è di dimensioni piuttosto ridotte ma è un modello molto potente. Ecco come può essere implementato:
|
||||
|
||||
```javascript
|
||||
//imposta la classe EventEmitter che contiene i listener
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.listeners = {};
|
||||
}
|
||||
//quando un messaggio viene ricevuto, si fa gestire il suo payload al listener
|
||||
on(message, listener) {
|
||||
if (!this.listeners[message]) {
|
||||
this.listeners[message] = [];
|
||||
}
|
||||
this.listeners[message].push(listener);
|
||||
}
|
||||
//quando un messaggio viene ricevuto, si invia a un listener con un payload
|
||||
emit(message, payload = null) {
|
||||
if (this.listeners[message]) {
|
||||
this.listeners[message].forEach(l => l(message, payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Per utilizzare il codice qui sopra si può creare un'implementazione molto piccola:
|
||||
|
||||
```javascript
|
||||
//impostazione di una struttura di messaggio
|
||||
const Messages = {
|
||||
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
|
||||
};
|
||||
//invoca l'eventEmitter impostato sopra
|
||||
const eventEmitter = new EventEmitter();
|
||||
//imposta un eroe
|
||||
const hero = createHero(0,0);
|
||||
//fa in modo che eventEmitter sappia come monitorare i messages di pertinenza dell'eroe per spostarsi a sinistra, e agisce di conseguenza
|
||||
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
|
||||
hero.move(5,0);
|
||||
});
|
||||
|
||||
//imposta window per ascoltare un evento keyup, nello specifico se viene premuto freccia sinistra emette un messaggio che fa spostare l'eroe a sinistra
|
||||
window.addEventListener('keyup', (evt) => {
|
||||
if (evt.key === 'ArrowLeft') {
|
||||
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Sopra si collega un evento della tastiera, `ArrowLeft` e si invia il messaggio `HERO_MOVE_LEFT` (spostamento a sinistra - n.d.t.). Questo messaggio viene recepito e come risultato si sposta l'eroe (`hero`). Il punto di forza di questo modello è che l'event listener e l'eroe non sanno nulla l'uno dell'altro. Si può rimappare `ArrowLeft` sul tasto `A`. Inoltre sarebbe possibile fare qualcosa di completamente diverso su `ArrowLeft` apportando alcune modifiche alla funzione `on` di eventEmitter:
|
||||
|
||||
```javascript
|
||||
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
|
||||
hero.move(5,0);
|
||||
});
|
||||
```
|
||||
|
||||
Man mano che le cose diventano più complicate quando il gioco cresce, questo modello rimane lo stesso in termini di complessità e il proprio codice rimane pulito. Si consiglia vivamente di adottare questo modello.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Pensare a come il modello pub/sub può migliorare un gioco. Quali parti dovrebbero emettere eventi e come dovrebbe reagire a questi il gioco? Ora si ha la possibilità di essere creativi, pensando a un nuovo gioco e a come potrebbero comportarsi le sue parti.
|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[
|
||||
Quiz post-lezione](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/30)
|
||||
|
||||
## Revisione e Auto Apprendimento
|
||||
|
||||
Saperne di più su Pub/Sub [leggendo qui](https://docs.microsoft.com/it-it/azure/architecture/patterns/publisher-subscriber?WT.mc_id=academic-4621-cxa).
|
||||
|
||||
## Compito
|
||||
|
||||
[Produrre uno schizzo di un gioco](assignment.it.md)
|
@ -0,0 +1,11 @@
|
||||
# Produrre uno schizzo di un gioco
|
||||
|
||||
## Istruzioni
|
||||
|
||||
Utilizzando gli esempi di codice nella lezione, scrivere una rappresentazione di un gioco che piace. Dovrà essere un gioco semplice, ma l'obiettivo è usare la classe o il modello di composizione e il modello pub/sub per mostrare come potrebbe essere avviato un gioco. Si dia sfogo alla propria creatività!
|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | ------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- |
|
||||
| | Tre elementi vengono posizionati sullo schermo e manipolati | Due elementi vengono posizionati sullo schermo e manipolati | Un elemento viene posizionato sullo schermo e manipolato |
|
@ -0,0 +1,217 @@
|
||||
# Costruire un Gioco Spaziale Parte 2: Disegnare Eroi e Mostri sull'elemento Canvas
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/31)
|
||||
|
||||
## Canvas
|
||||
|
||||
Canvas è un elemento HTML che per impostazione predefinita non ha contenuto; è una lavagna vuota. Si può riempirla disegnandoci sopra.
|
||||
|
||||
✅ [Ulteriori informazioni sull'API Canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) su MDN.
|
||||
|
||||
Ecco come viene tipicamente dichiarato, come parte dell'elemento body della pagina:
|
||||
|
||||
```html
|
||||
<canvas id="myCanvas" width="200" height="100"></canvas>
|
||||
```
|
||||
|
||||
Sopra si è impostato l'`id`entificativo, la larghezza `(width)` e l'altezza (`height`).
|
||||
|
||||
- `id`: va impostato in modo da poter ottenere un riferimento quando si deve interagire con l'elemento.
|
||||
- `width`: questa è la larghezza dell'elemento.
|
||||
- `height`: questa è l'altezza dell'elemento.
|
||||
|
||||
## Disegnare una geometria semplice
|
||||
|
||||
Canvas utilizza un sistema di coordinate cartesiane per disegnare le cose. Quindi utilizza un asse x e un asse y per esprimere dove si trova qualcosa. La posizione `0,0` è la posizione in alto a sinistra e quella in basso a destra è ciò che si è determinato come larghezza (WIDTH) e altezza (HEIGHT) del canvas
|
||||
|
||||
![la griglia del canvas](../canvas_grid.png)
|
||||
> Immagine da [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)
|
||||
|
||||
Per disegnare sull'elemento canvas si dovranno eseguire i seguenti passaggi:
|
||||
|
||||
1. **Ottenere un riferimento** all'elemento Canvas.
|
||||
1. **Ottenere un riferimento** all'elemento Context che si trova sull'elemento canvas.
|
||||
1. **Eseguire un'operazione di disegno** utilizzando l'elemento context.
|
||||
|
||||
Il codice per i passaggi precedenti di solito ha questo aspetto:
|
||||
|
||||
```javascript
|
||||
// disegna un rettangolo rosso
|
||||
//1. ottiene il riferimento per il canvas
|
||||
canvas = document.getElementById("myCanvas");
|
||||
|
||||
//2. ottiene l'oggetto context per disegnare forme basiche in 2D
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
//3. lo riempie con il colore rosso
|
||||
ctx.fillStyle = 'red';
|
||||
|
||||
//4. e disegna un rettangolo con questi parametri, impostando posizione e dimensione
|
||||
ctx.fillRect(0,0, 200, 200) // x,y,larghezza, altezza
|
||||
```
|
||||
|
||||
✅ L'API Canvas si concentra principalmente su forme 2D, ma si possono anche disegnare elementi 3D su un sito web; per questo, si potrebbe utilizzare l' [API WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API).
|
||||
|
||||
Si può disegnare ogni sorta di cose con l'API Canvas come:
|
||||
|
||||
- **Forme geometriche**, è già stato mostrato come disegnare un rettangolo, ma c'è molto di più che si può disegnare.
|
||||
- **Testo**, si può disegnare un testo con qualsiasi carattere e colore si desideri.
|
||||
- **Immagini**, si puòdisegnare un'immagine basandosi su una risorsa immagine come .jpg o .png, ad esempio.
|
||||
|
||||
Si faccia una prova! Si sa come disegnare un rettangolo, si può disegnare un cerchio su una pagina? Si dia un'occhiata ad alcuni interessanti disegni su canvas su CodePen. Ecco un [esempio particolarmente degno di nota](https://codepen.io/dissimulate/pen/KrAwx).
|
||||
|
||||
## Caricare e disegnare una risorsa immagine
|
||||
|
||||
Si carica una risorsa immagine creando un oggetto `Image` e impostando la sua proprietà `src` . Quindi ci si mette in ascolto per l'evento di caricamento (`load`) per sapere quando è pronto per essere utilizzato. Il codice si presenta cosí:
|
||||
|
||||
### Caricamento risorsa
|
||||
|
||||
```javascript
|
||||
const img = new Image();
|
||||
img.src = 'path/to/my/image.png';
|
||||
img.onload = () => {
|
||||
// immagine caricata e pronta all'uso
|
||||
}
|
||||
```
|
||||
|
||||
### Modello di Caricamento Risorsa
|
||||
|
||||
Si consiglia di racchiudere quanto sopra in un costrutto come questo, così è più facile da usare e si tenta di manipolarlo solo quando è completamente caricato:
|
||||
|
||||
```javascript
|
||||
function loadAsset(path) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = path;
|
||||
img.onload = () => {
|
||||
// immagine caricata e pronta all'uso
|
||||
resolve(img);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// usarlo in questo modo
|
||||
|
||||
async function run() {
|
||||
const heroImg = await loadAsset('hero.png')
|
||||
const monsterImg = await loadAsset('monster.png')
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Per disegnare risorse di gioco su uno schermo, il codice sarà simile a questo:
|
||||
|
||||
```javascript
|
||||
async function run() {
|
||||
const heroImg = await loadAsset('hero.png')
|
||||
const monsterImg = await loadAsset('monster.png')
|
||||
|
||||
canvas = document.getElementById("myCanvas");
|
||||
ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(heroImg, canvas.width/2,canvas.height/2);
|
||||
ctx.drawImage(monsterImg, 0,0);
|
||||
}
|
||||
```
|
||||
|
||||
## Ora è il momento di iniziare a costruire il gioco
|
||||
|
||||
### Cosa costruire
|
||||
|
||||
Si costruirà una pagina web con un elemento Canvas. Si dovrebbe rendere uno schermo nero `1024 * 768`. Sono state fornite due immagini:
|
||||
|
||||
- Astronave dell'eroe
|
||||
|
||||
![Nave dell'eroe](../solution/assets/player.png)
|
||||
|
||||
- Mostro 5*5
|
||||
|
||||
![Nave del mostro](../solution/assets/enemyShip.png)
|
||||
|
||||
### Passaggi consigliati per iniziare lo sviluppo
|
||||
|
||||
Individuare i file che già sono stati creati nella sottocartella `your-work` della cartella di lavoro Dovrebbe contenere le seguenti informazioni:
|
||||
|
||||
```bash
|
||||
-| assets
|
||||
-| enemyShip.png
|
||||
-| player.png
|
||||
-| index.html
|
||||
-| app.js
|
||||
-| package.json
|
||||
```
|
||||
|
||||
Aprire una copia di questa cartella in Visual Studio Code. È necessario disporre di una configurazione di ambiente di sviluppo locale, preferibilmente con Visual Studio Code con NPM e Node installati. Se non si è impostato `npm` sul proprio computer, [ecco come farlo](https://www.npmjs.com/get-npm).
|
||||
|
||||
Inizializzare il proprio progetto accedendo alla cartella `your_work` :
|
||||
|
||||
```bash
|
||||
cd your-work
|
||||
npm start
|
||||
```
|
||||
|
||||
Quanto sopra avvierà un server HTTP sull'indirizzo `http://localhost:5000`. Aprire un browser e inserire quell'indirizzo. Al momento è una pagina vuota, ma cambierà
|
||||
|
||||
> Nota: per vedere le modifiche sullo schermo, aggiornare il contenuto del browser.
|
||||
|
||||
### Aggiungere codice
|
||||
|
||||
Aggiungi il codice necessario al file `your-work/app.js` per risolvere quanto segue
|
||||
|
||||
1. **Disegnare** un oggetto canvas con sfondo nero
|
||||
> suggerimento: aggiungere due righe sotto il TODO appropriato in `/app.js`, impostando l'elemento `ctx` in modo che sia nero e le coordinate alto/sinistra a 0,0 e l'altezza e la larghezza uguali a quelle del canvas.
|
||||
2. **Caricare** le strutture di gioco
|
||||
> suggerimento: aggiungere le immagini del giocatore e del nemico usando `await loadTexture`, passando il percorso dell'immagine. Non saranno ancora visibili sullo schermo!
|
||||
3. **Disegnare** l'eroe al centro dello schermo nella metà inferiore
|
||||
> suggerimento: usare l'API `drawImage` per disegnare `heroImg` sullo schermo, impostando `canvas.width / 2 - 45` e `canvas.height - canvas.height / 4` come valori di coordinate x, y
|
||||
4. **Disegnare** mostri 5*5
|
||||
> suggerimento: ora si può rimuovere il commento dal codice per disegnare i nemici sullo schermo. Successivamente, passare alla funzione `createEnemies` e crearla.
|
||||
|
||||
Per prima cosa, impostare alcune costanti:
|
||||
|
||||
```javascript
|
||||
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;
|
||||
```
|
||||
|
||||
quindi, creare un ciclo per disegnare l'array di mostri sullo schermo:
|
||||
|
||||
```javascript
|
||||
for (let x = START_X; x < STOP_X; x += 98) {
|
||||
for (let y = 0; y < 50 * 5; y += 50) {
|
||||
ctx.drawImage(enemyImg, x, y);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Risultato
|
||||
|
||||
Il risultato finale dovrebbe essere così:
|
||||
|
||||
![Schermo nero con un eroe e mostri 5*5](../partI-solution.png)
|
||||
|
||||
## Soluzione
|
||||
|
||||
Per favore provare a risolverlo da soli, ma se si rimane bloccati, dare un'occhiata alla [soluzione](../solution/app.js)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Sfida
|
||||
|
||||
Si è imparato a disegnare con l'API Canvas incentrata sul 2D; dare un'occhiata all['API WebGL API](https://developer.mozilla.org/it/docs/Web/API/WebGL_API) e provare a disegnare un oggetto 3D.
|
||||
|
||||
## Quiz Post-Lezione
|
||||
|
||||
[
|
||||
Quiz post-lezione](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/32)
|
||||
|
||||
## Revisione e Auto Apprendimento
|
||||
|
||||
Scoprire di più sull'API Canvas raccogliendo [informazioni su di essa](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
||||
|
||||
## Compito
|
||||
|
||||
[Giocare con l'API Canvas](assignment.it.md)
|
@ -0,0 +1,11 @@
|
||||
# Giocare con l'API Canvas
|
||||
|
||||
## Istruzioni
|
||||
|
||||
Sceglire un elemento dell'API Canvas e creare qualcosa di interessante attorno ad esso. Si è in grado di creare una piccola galassia di stelle ripetute? Si riesce a creare una interessante struttura di linee colorate? Si puoi guardare CodePen per l'ispirazione (ma non copiare)
|
||||
|
||||
## Rubrica
|
||||
|
||||
| Criteri | Ottimo | Adeguato | Necessita miglioramento |
|
||||
| -------- | --------------------------------------------------------- | ----------------------------------- | --------------------- |
|
||||
| | Il codice viene inviato mostrando una struttura o una forma interessante | Il codice viene inviato, ma non viene eseguito | Il codice non è stato inviato |
|
@ -1,24 +1,24 @@
|
||||
function loadTexture(path) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = path;
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
};
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.src = path
|
||||
img.onload = () => {
|
||||
resolve(img)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createEnemies(ctx, canvas, enemyImg) {
|
||||
// TODO draw enemies
|
||||
// TODO disegnare i nemici
|
||||
}
|
||||
|
||||
window.onload = async () => {
|
||||
canvas = document.getElementById('canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
// TODO load textures
|
||||
canvas = document.getElementById('canvas')
|
||||
ctx = canvas.getContext('2d')
|
||||
// TODO 2 - caricare le strutture di gioco
|
||||
|
||||
// TODO draw black background
|
||||
// TODO draw hero
|
||||
// TODO uncomment the next line when you add enemies to screen
|
||||
//createEnemies(ctx, canvas, enemyImg);
|
||||
};
|
||||
// TODO 1 - disegnare lo sfondo nero
|
||||
// TODO 3 - disegnare l'eroe
|
||||
// TODO 4 - togliere il commento dalla riga successiva quando si aggiungono nemici sullo schermo
|
||||
//createEnemies(ctx, canvas, enemyImg);
|
||||
}
|
||||
|
@ -0,0 +1,389 @@
|
||||
# Costruire un Gioco Spaziale parte 3: Aggiungere il Movimento
|
||||
|
||||
## Quiz Pre-Lezione
|
||||
|
||||
[Quiz Pre-Lezione](https://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/33)
|
||||
|
||||
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://nice-beach-0fe9e9d0f.azurestaticapps.net/quiz/34)
|
||||
|
||||
## 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)
|
@ -0,0 +1,11 @@
|
||||
# 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 |
|
@ -0,0 +1,31 @@
|
||||
# Costruire un Gioco Spaziale
|
||||
|
||||
Un gioco spaziale per insegnare fondamenti di JavaScript più avanzati
|
||||
|
||||
In questa lezione si imparerà come costruire un gioco spaziale. Se mai si è giocato a "Space Invaders", questo gioco ha la stessa idea: guidare un'astronave e sparare sui mostri che scendono dall'alto. Ecco come apparirà il gioco finito:
|
||||
|
||||
![Gioco completato](../images/pewpew.gif)
|
||||
|
||||
In queste sei lezioni si imparerà quanto segue:
|
||||
|
||||
- **Interagire** con l'elemento Canvas per disegnare oggetti su uno schermo
|
||||
- **Comprendere** il sistema di coordinate cartesiane
|
||||
- **Imparare** il modello Pub-Sub per creare una solida architettura di gioco più facile da mantenere ed estendere
|
||||
- **Sfruttare** Async/Await per caricare le risorse di gioco
|
||||
- **Gestire** gli eventi da tastiera
|
||||
|
||||
## Panoramica
|
||||
|
||||
- Teoria
|
||||
- [Introduzione alla creazione di giochi con JavaScript](1-introduction/translations/README.it.md)
|
||||
- Esercitazione
|
||||
- [Disegnare sull'elemento canvas](2-drawing-to-canvas/translations/README.it.md)
|
||||
- [Spostamento di elementi sullo schermo](3-moving-elements-around/translations/README.it.md)
|
||||
- [Rilevamento della collisione.](4-collision-detection/translations/README.it.md)
|
||||
- [Tenere il punteggio](5-keeping-score/translations/README.it.md)
|
||||
- [Terminare e riavviare il gioco](6-end-condition/translations/README.it.md)
|
||||
|
||||
## Crediti
|
||||
|
||||
Le risorse utilizzate provengono da https://www.kenney.nl/.
|
||||
Se interessa costruire giochi, queste sono alcune risorse davvero buone, molte sono gratuite e alcune sono a pagamento.
|
Loading…
Reference in new issue