Proiectele de programare nu trebuie să fie întotdeauna legate de zona enterprise. Realizarea de jocuri reprezintă o posibilă evadare din monotonia unor proiecte prea serioase. Bineînțeles că există frameworkuri speciale pentru realizarea acestora precum Unity3D sau Unreal Engine. Mai aproape de scopul acestui articol trebuie să menționăm și câteva frameworkuri de gaming ca JavaScript: Phaser, PixiJSvvși BabylonJS. În schimb, deoarece unora dintre noi ne place să re-inventăm uneori roata, vă propun să scriem micul nostru joc în JavaScript simplu.
Realizarea unui joc de ping pong în browser în prima etapă. În a doua parte vom adăuga comunicarea în rețea folosind web sockets iar jucătorii vor putea să se joace în rețea. Odată implementate, vom putea transforma ideea jocului într-unul mai complex.
Vom porni cu ultima versiune LTS de JavaScript: 22.13.1 și vom folosi express ca server web:
const express = require(‚express’);
const {Square} = require(„./square.mjs”);
const app = express();
const port = 80;
// Start the server
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
app.get('/', (req, res) => {
res.sendFile('index.html', {root: __dirname});
});
app.get('/game.mjs', (req, res) => {
res.sendFile('game.mjs', {root: __dirname});
});
app.get('/ElementsFactory.mjs',(req,res)=>{
res.sendFile('/ElementsFactory.mjs',
{root: __dirname})
});
app.get('/square.mjs',(req,res)=>{
res.sendFile('/square.mjs',{root: __dirname})
});
Am putea folosi un folder public (ex. app.use(express.static('public')
), dar prefer să avem controlul tuturor fișierelor expuse. Un alt avantaj este faptul că putem interveni în fișierul expus fără a mai modifica importurile.
Pagina web expusă va conține doar un buton de start și elementul Canvas pe care vom desena cele două palete de ping pong.
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8”>
<title>Ping game</title>
</head>
<body>
<div>
<button id=”startGame”>Start Game</button>
<canvas id=”canvas” width=”800” height=”600”
style=”;margin: 0 0;border: 0; padding: 0 0” >
</canvas>
<script type=”module” src=”/game.mjs”></script>
</div>
</body>
</html>
Folosirea Canvas
pentru afișare are un principiu destul de simplu. Vom avea aprox. 30-60 de imagini /sec care vor constitui animația. În cazul de față, vom desena dreptunghiuri și le vom șterge pe cele anterioare pentru a simula mișcarea. Cele două palete vor reacționa la apăsarea tastaturi și vom simula astfel mișcarea.
import {factory} from „./ElementsFactory.mjs”;
const canvas=document.querySelector(‚#canvas’)
const ctx=canvas.getContext(‚2d’);
const startGame=document.querySelector(„#startGame”)
let paddleLeft=null;
let paddleRight=null;
const keys = []
Butonul Start va porni procesul de afișare a paletelor de ping pong. Vom chema pentru fiecare instanță metoda draw().
startGame.addEventListener(‚click’, async e => {
let factPaddle=factory.getPaddle(ctx);
paddleLeft=factPaddle.simpleOrange(10,10,20,100,
40,38);
paddleRight=factPaddle.simpleOrange(700,500,20,100,
99,102 );
startDrawingLoop();
});
function startDrawingLoop() {
const draw = () => {
takePic();
requestAnimationFrame(draw);
};
requestAnimationFrame(draw);
}
function takePic() {
paddleLeft.draw();
paddleRight.draw();
}
Pentru citirea datelor de la tastatură a trebuit să avem o implementare specială care citește toate tastele apăsate la un moment dat. Inițial citeam doar valoarea returnată la keydown, dar nu era suficient pentru în cazul în care ambii jucători folosesc aceeași tastatură.
document.addEventListener(‚keydown’, e => {
!event.repeat && keys.push(e.keyCode)
doSomethingWithKeys(keys)
})
document.addEventListener(‚keyup’, e => {
keys.splice(0, keys.length)
})
function doSomethingWithKeys(keys) {
if (paddleRight) {
paddleRight.handleKeys(keys);
}
if (paddleLeft) {
paddleLeft.handleKeys(keys);
}
}
La final, vom transmite fiecărei palete tastele apăsate pentru a își modifica poziția dacă este nevoie.
Clasa Factory ne returnează instanțele prin invocarea metodei simpleOrange()
:
import {Square} from „./square.mjs”
class ElementsFactory{
constructor(ctx){
this.ctx=ctx;
}
simpleOrange(x,y,w,h, up, down){
return new Square(this.ctx,x,y,w,h,up,
down,{idle:”#fb8d7d”, over:”#e9775c”,
pressed:”#cf6858”});
}
}
export const factory={
getPaddle(ctx){
return new ElementsFactory(ctx);
}
}
La final, implementarea Square
:
class Square{
#usedColor;
#step=10;
constructor(ctx, x, y, w, h, up, down,
color={idle:”#fb8d7d”, over:”#490004”,
pressed:”#e9775c”}){
this.ctx=ctx;
this.x=x;
this.y=y;
this.w=w;
this.h=h;
this.color=color;
this.up=up;
this.down=down;
this.#usedColor=this.color.idle;
}
handleKeys(keys){
for (let i=0;i 10) {
this.y -= this.#step;
}
break;
}
}
}
draw(){
this.ctx.beginPath();
this.ctx.clearRect(this.x-20,this.y-20,
this.w+40,this.h+40);
this.ctx.fillStyle = this.#usedColor;
this.ctx.fillRect(this.x,this.y, this.w,this.h);
}
}
export {Square}
Folosim handleSquare()
pentru a modifica poziția pe verticală a paletelor în limitele acceptate. Metoda draw()
este apelată pentru redesenare și constă din ștergerea dreptunghiului anterior prin clearRect()
și redesenarea acestuia fillRect()
.
Deși a fost un cod simplu, sper ca acesta să vă facă să încercați și voi folosirea Canvas. Combinat cu Web Audio API, poate să fie punctul de pornire pentru un proiect interesant. Vom reveni în numărul următor cu continuarea acestui proiect.