Un gioco coi canvas
Movimento di una pallina 2D
Utilizzando i canvas proviamo a simulare il comportamento di una pallina in 2D. Qui uno screenshot di come si può vedere la “pallina” (cioè un piccolo cerchio).

Vediamo nello specifico il funzionamento di questo algoritmo.
Algoritmo di animazione
Per rappresentare uno o più oggetti che si muovono nel tempo, è necessario creare una animazione, ovvero un algoritmo che coi canvas consiste nella ripetizione (tramite un setInterval) dei seguenti passi:
a) aggiornamento della posizione di tutti gli oggetti all’interno del canvas.
b) eliminazione degli oggetti esistenti nel canvas
c) disegno degli oggetti nella nuova posizione.
Se l’intervallo è sufficientemente veloce, l’utente ha l’illusione di vedere gli oggetti muoversi nella schermata.
Questo meccanismo, come vedremo, è alla base anche dei videogiochi. Applichiamolo all’animazione della pallina.
Il componente ball
Utilizziamo la progettazione a componenti, quindi creiamo ua funzione costruttrice che crea la pallina. Questa riceve come parametro una coppia di coordinate iniziali, una dimensione, un angolo ed una velocità, ed infine la dimensione del canvas stesso.
La refresh
L’effetto visivo dello spostamento della pallina si ottiene mediante aggiornamento della posizione (x,y) della pallina stessa. Dobbiamo quindi scrivere una funzione refresh che calcola le nuove coordinate in cui si dovrà trovare la pallina in base alla sua velocità e la sua direzione. Questa funzione dovrà essere eseguita periodicamente tramite setInterval con una frequenza abbastanza veloce da rendere il movimento fluido per l’occhio umano.
Ma come si calcolano le nuove coordinate?
La pallina ha una direzione (angle) e una velocità (speed). La nuova posizione ad ogni aggiornamento corrisponderà ad uno spostamento verso destra o sinistra e verso l’alto o verso il basso che sono rispettivamente le componenti orizzontale e verticale della direzione della pallina. Qui un esempio di spostamento con angolo di 30 gradi.

La traslazione della pallina ha una componente orizzontale, che corrisponde al coseno dell’angolo di direzione, ed una componente verticale pari al seno dell’angolo. Questi valori poi sono moltiplicati per la velocità della pallina stessa.
Quindi la nuova posizione sarà determinata da queste due formule:

Quindi se ad esempio all’istante t la pallina si trova nella posizione (100,100) ed ha velocità di 20, le nuove posizioni saranno:

Ricordare sempre che nei canvas il punto in alto a sinistra è (0,0). Quello che si deve fare nell’esercizio è quindi semplicemente calcolare le nuove posizioni con la formula sopra riportata.
Ma cosa succede quando la pallina raggiunge immancabilmente la parete? Nella nostra simulazione ipotizziamo che l’urto sia elastico, quindi non c’è perdita di velocità, ma cambia solo l’angolo di uscita, che per effetto del rimbalzo cambia secondo la seguente regola:
– se l’urto è verso le pareti verticali (dx o sx) il nuovo angolo è:

se l’urto è verso le pareti orizzontali (sopra o sotto) il nuovo angolo è:

A livello di codice, bisogna controllare se la futura posizione della palla è sul bordo orizzontale, o sul bordo verticale, e quindi aggiornare l’angolo di conseguenza. Se si vede che sta uscendo bisogna ricalcolare il nuovo angolo, così alla traslazione successiva la pallina avrà cambiato direzione, per effetto appunto del rimbalzo.
Qui il codice completo:
const createBall = (posx, posy, width, startAngle, speed, size) => {
const ball = {
x: posx,
y: posy,
width: width,
angle: startAngle,
speed: speed
}
return {
refresh: () => {
let newx = ball.x + ball.speed * Math.cos(ball.angle);
let newy = ball.y + ball.speed * Math.sin(ball.angle);
console.log("newx: " + newx + " - newy: " + newy);
if (newx > size - ball.width || newx < ball.width) {
ball.angle = Math.PI - ball.angle;
} else if (newy > size - ball.width || newy < ball.width) {
ball.angle = 2 * Math.PI - ball.angle;
} else {
ball.x = newx;
ball.y = newy;
}
},
get: ball
}
}A questo punto creiamo il componente che gestisce la render.
const createDrawComponent = (canvas) => {
const ctx = canvas.getContext("2d");
return {
render: (ball) => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.width, 0, 2 * Math.PI);
ctx.lineWidth = 1;
ctx.fill();
},
clear: () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
}La funzione render riceve i dati della pallina, e la disegna sul canvas, mentre la funzione clear ripulisce l’intero canvas per l’aggiornamento.
A questo punto possiamo mettere tutto insieme ed avviare l’animazione:
const ball = createBall(50, 50, 10, Math.random()%360, 5, 500);
const drawComponent = createDrawComponent(document.getElementById("canvas"));
setInterval(() => {
ball.refresh();
drawComponent.clear();
drawComponent.render(ball.get);
}, 10);Vogliamo creare due palline? Nessun problema:
const ball1 = createBall(50, 50, 10, Math.random()%360, 5, 500);
const ball2 = createBall(450, 450, 10, Math.random()%360, 6, 500);
const drawComponent = createDrawComponent(document.getElementById("canvas"));
setInterval(() => {
ball1.refresh();
ball2.refresh();
drawComponent.clear();
drawComponent.render(ball1.get);
drawComponent.render(ball2.get);
}, 10);La programmazione per componenti permette quindi di separare la logica dell’animazione (il refresh) dall’aggiornamento dell’interfaccia utente (render).
Eventi da tastiera
Per intercettare un evento da tastiera in una pagina web è sufficiente eseguire il seguente codice:
document.onkeydown = function(e) {
console.log(String.fromCharCode(e.keyCode)+" --> "+e.keyCode);
};
Il valore di keyCode indica il codice numerico del tasto premuto.
Qui un elenco di codici.
| Keyboard key Pressed | JavaScript Key Code value |
| backspace | 8 |
| tab | 9 |
| enter | 13 |
| shift | 16 |
| ctrl | 17 |
| alt | 18 |
| pause/break | 19 |
| caps lock | 20 |
| escape | 27 |
| page up | 33 |
| Space | 32 |
| page down | 34 |
| end | 35 |
| home | 36 |
| arrow left | 37 |
| arrow up | 38 |
| arrow right | 39 |
| arrow down | 40 |
| print screen | 44 |
| insert | 45 |
| delete | 46 |
| 0 | 48 |
| 1 | 49 |
| 2 | 50 |
| 3 | 51 |
| 4 | 52 |
| 5 | 53 |
| 6 | 54 |
| 7 | 55 |
| 8 | 56 |
| 9 | 57 |
| a | 65 |
| b | 66 |
| c | 67 |
| d | 68 |
| e | 69 |
| f | 70 |
| g | 71 |
| h | 72 |
| i | 73 |
| j | 74 |
| k | 75 |
| l | 76 |
| m | 77 |
| n | 78 |
| o | 79 |
| p | 80 |
| q | 81 |
| r | 82 |
| s | 83 |
| t | 84 |
| u | 85 |
| v | 86 |
| w | 87 |
| x | 88 |
| y | 89 |
| z | 90 |
| left window key | 91 |
| right window key | 92 |
| select key | 93 |
| numpad 0 | 96 |
| numpad 1 | 97 |
| numpad 2 | 98 |
| numpad 3 | 99 |
| numpad 4 | 100 |
| numpad 5 | 101 |
| numpad 6 | 102 |
| numpad 7 | 103 |
| numpad 8 | 104 |
| numpad 9 | 105 |
| multiply | 106 |
| add | 107 |
| subtract | 109 |
| decimal point | 110 |
| divide | 111 |
| f1 | 112 |
| f2 | 113 |
| f3 | 114 |
| f4 | 115 |
| f5 | 116 |
| f6 | 117 |
| f7 | 118 |
| f8 | 119 |
| f9 | 120 |
| f10 | 121 |
| f11 | 122 |
| f12 | 123 |
| num lock | 144 |
| scroll lock | 145 |
| semi-colon | 186 |
| equal sign | 187 |
| comma | 188 |
| dash | 189 |
| period | 190 |
| forward slash | 191 |
| open bracket | 219 |
| back slash | 220 |
| close braket | 221 |
| single quote | 222 |
