18 KiB
Construiește un Joc Spațial Partea 1: Introducere
La fel cum centrul de control al NASA coordonează mai multe sisteme în timpul unei lansări spațiale, vom construi un joc spațial care demonstrează cum diferite părți ale unui program pot lucra împreună fără probleme. În timp ce creezi ceva ce poți juca efectiv, vei învăța concepte esențiale de programare care se aplică oricărui proiect software.
Vom explora două abordări fundamentale pentru organizarea codului: moștenirea și compoziția. Acestea nu sunt doar concepte academice – sunt aceleași modele care alimentează totul, de la jocuri video la sisteme bancare. De asemenea, vom implementa un sistem de comunicare numit pub/sub, care funcționează ca rețelele de comunicare utilizate în navele spațiale, permițând diferitelor componente să împărtășească informații fără a crea dependențe.
Până la sfârșitul acestei serii, vei înțelege cum să construiești aplicații care pot scala și evolua – fie că dezvolți jocuri, aplicații web sau orice alt sistem software.
Chestionar Pre-Lecție
Moștenirea și Compoziția în Dezvoltarea Jocurilor
Pe măsură ce proiectele devin mai complexe, organizarea codului devine crucială. Ceea ce începe ca un script simplu poate deveni dificil de întreținut fără o structură adecvată – la fel cum misiunile Apollo au necesitat o coordonare atentă între mii de componente.
Vom explora două abordări fundamentale pentru organizarea codului: moștenirea și compoziția. Fiecare are avantaje distincte, iar înțelegerea ambelor te ajută să alegi abordarea potrivită pentru diferite situații. Vom demonstra aceste concepte prin jocul nostru spațial, unde eroii, inamicii, power-up-urile și alte obiecte trebuie să interacționeze eficient.
✅ Una dintre cele mai faimoase cărți de programare scrise vreodată are legătură cu modelele de design.
În orice joc, ai obiecte de joc – elementele interactive care populează lumea jocului. Eroii, inamicii, power-up-urile și efectele vizuale sunt toate obiecte de joc. Fiecare există la coordonate specifice pe ecran folosind valorile x și y, similar cu trasarea punctelor pe un plan de coordonate.
În ciuda diferențelor vizuale, aceste obiecte împărtășesc adesea comportamente fundamentale:
- Există undeva – Fiecare obiect are coordonate x și y, astfel încât jocul să știe unde să-l deseneze
- Multe se pot mișca – Eroii aleargă, inamicii urmăresc, gloanțele zboară pe ecran
- Au o durată de viață – Unele rămân pentru totdeauna, altele (cum ar fi exploziile) apar pentru scurt timp și dispar
- Reacționează la lucruri – Când lucrurile se ciocnesc, power-up-urile sunt colectate, barele de sănătate se actualizează
✅ Gândește-te la un joc precum Pac-Man. Poți identifica cele patru tipuri de obiecte enumerate mai sus în acest joc?
Exprimarea Comportamentului Prin Cod
Acum că înțelegi comportamentele comune pe care le împărtășesc obiectele de joc, să explorăm cum să implementăm aceste comportamente în JavaScript. Poți exprima comportamentul obiectelor prin metode atașate fie claselor, fie obiectelor individuale, și există mai multe abordări din care să alegi.
Abordarea Bazată pe Clase
Clasele și moștenirea oferă o abordare structurată pentru organizarea obiectelor de joc. La fel ca sistemul de clasificare taxonomică dezvoltat de Carl Linnaeus, începi cu o clasă de bază care conține proprietăți comune, apoi creezi clase specializate care moștenesc aceste fundamente, adăugând în același timp capabilități specifice.
✅ Moștenirea este un concept important de înțeles. Află mai multe din articolul MDN despre moștenire.
Iată cum poți implementa obiectele de joc folosind clase și moștenire:
// Step 1: Create the base GameObject class
class GameObject {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
}
Să descompunem acest lucru pas cu pas:
- Creăm un șablon de bază pe care fiecare obiect de joc îl poate folosi
- Constructorul salvează unde se află obiectul (
x,y) și ce tip de obiect este - Acesta devine fundația pe care toate obiectele de joc se vor construi
// Step 2: Add movement capability through inheritance
class Movable extends GameObject {
constructor(x, y, type) {
super(x, y, type); // Call parent constructor
}
// Add the ability to move to a new position
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
În cele de mai sus, am:
- Extins clasa GameObject pentru a adăuga funcționalitate de mișcare
- Apelat constructorul părintelui folosind
super()pentru a inițializa proprietățile moștenite - Adăugat o metodă
moveTo()care actualizează poziția obiectului
// Step 3: Create specific game object types
class Hero extends Movable {
constructor(x, y) {
super(x, y, 'Hero'); // Set type automatically
}
}
class Tree extends GameObject {
constructor(x, y) {
super(x, y, 'Tree'); // Trees don't need movement
}
}
// Step 4: Use your game objects
const hero = new Hero(0, 0);
hero.moveTo(5, 5); // Hero can move!
const tree = new Tree(10, 15);
// tree.moveTo() would cause an error - trees can't move
Înțelegerea acestor concepte:
- Creează tipuri de obiecte specializate care moștenesc comportamentele adecvate
- Demonstrează cum moștenirea permite includerea selectivă a caracteristicilor
- Arată că eroii se pot mișca în timp ce copacii rămân staționari
- Ilustrează cum ierarhia claselor previne acțiunile nepotrivite
✅ Ia câteva minute pentru a reimagina un erou din Pac-Man (de exemplu, Inky, Pinky sau Blinky) și cum ar fi scris în JavaScript.
Abordarea Compoziției
Compoziția urmează o filozofie de design modular, similară cu modul în care inginerii proiectează nave spațiale cu componente interschimbabile. În loc să moștenești de la o clasă părinte, combini comportamente specifice pentru a crea obiecte cu exact funcționalitatea de care au nevoie. Această abordare oferă flexibilitate fără constrângeri ierarhice rigide.
// Step 1: Create base behavior objects
const gameObject = {
x: 0,
y: 0,
type: ''
};
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
};
Iată ce face acest cod:
- Definește un obiect de bază
gameObjectcu proprietăți de poziție și tip - Creează un obiect de comportament separat
movablecu funcționalitate de mișcare - Separă preocupările prin păstrarea datelor de poziție și logicii de mișcare independente
// Step 2: Compose objects by combining behaviors
const movableObject = { ...gameObject, ...movable };
// Step 3: Create factory functions for different object types
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
};
}
function createStatic(x, y, type) {
return {
...gameObject,
x,
y,
type
};
}
În cele de mai sus, am:
- Combinat proprietățile obiectului de bază cu comportamentul de mișcare folosind sintaxa spread
- Creat funcții de fabrică care returnează obiecte personalizate
- Permis crearea flexibilă a obiectelor fără ierarhii rigide de clase
- Permis obiectelor să aibă exact comportamentele de care au nevoie
// Step 4: Create and use your composed objects
const hero = createHero(10, 10);
hero.moveTo(5, 5); // Works perfectly!
const tree = createStatic(0, 0, 'Tree');
// tree.moveTo() is undefined - no movement behavior was composed
Puncte cheie de reținut:
- Compune obiecte prin combinarea comportamentelor, mai degrabă decât prin moștenirea lor
- Oferă mai multă flexibilitate decât ierarhiile rigide de moștenire
- Permite obiectelor să aibă exact caracteristicile de care au nevoie
- Folosește sintaxa modernă JavaScript spread pentru o combinație curată a obiectelor
**Which Pattern Should You Choose?**
> 💡 **Pro Tip**: Both patterns have their place in modern JavaScript development. Classes work well for clearly defined hierarchies, while composition shines when you need maximum flexibility.
>
**Here's when to use each approach:**
- **Choose** inheritance when you have clear "is-a" relationships (a Hero *is-a* Movable object)
- **Select** composition when you need "has-a" relationships (a Hero *has* movement abilities)
- **Consider** your team's preferences and project requirements
- **Remember** that you can mix both approaches in the same application
## Communication Patterns: The Pub/Sub System
As applications grow complex, managing communication between components becomes challenging. The publish-subscribe pattern (pub/sub) solves this problem using principles similar to radio broadcasting – one transmitter can reach multiple receivers without knowing who's listening.
Consider what happens when a hero takes damage: the health bar updates, sound effects play, visual feedback appears. Rather than coupling the hero object directly to these systems, pub/sub allows the hero to broadcast a "damage taken" message. Any system that needs to respond can subscribe to this message type and react accordingly.
✅ **Pub/Sub** stands for 'publish-subscribe'
### Understanding the Pub/Sub Architecture
The pub/sub pattern keeps different parts of your application loosely coupled, meaning they can work together without being directly dependent on each other. This separation makes your code more maintainable, testable, and flexible to changes.
**The key players in pub/sub:**
- **Messages** – Simple text labels like `'PLAYER_SCORED'` that describe what happened (plus any extra info)
- **Publishers** – The objects that shout out "Something happened!" to anyone who's listening
- **Subscribers** – The objects that say "I care about that event" and react when it happens
- **Event System** – The middleman that makes sure messages get to the right listeners
### Building an Event System
Let's create a simple but powerful event system that demonstrates these concepts:
```javascript
// Step 1: Create the EventEmitter class
class EventEmitter {
constructor() {
this.listeners = {}; // Store all event listeners
}
// Register a listener for a specific message type
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
// Send a message to all registered listeners
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(listener => {
listener(message, payload);
});
}
}
}
Descompunerea a ceea ce se întâmplă aici:
- Creează un sistem central de gestionare a evenimentelor folosind o clasă simplă
- Stochează ascultătorii într-un obiect organizat pe tipuri de mesaje
- Înregistrează noi ascultători folosind metoda
on() - Transmite mesaje tuturor ascultătorilor interesați folosind
emit() - Suportă încărcături de date opționale pentru transmiterea informațiilor relevante
Punând Totul Împreună: Un Exemplu Practic
Bun, să vedem asta în acțiune! Vom construi un sistem simplu de mișcare care arată cât de curat și flexibil poate fi pub/sub:
// Step 1: Define your message types
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT',
HERO_MOVE_RIGHT: 'HERO_MOVE_RIGHT',
ENEMY_SPOTTED: 'ENEMY_SPOTTED'
};
// Step 2: Create your event system and game objects
const eventEmitter = new EventEmitter();
const hero = createHero(0, 0);
Iată ce face acest cod:
- Definește un obiect de constante pentru a preveni greșelile de tastare în numele mesajelor
- Creează o instanță de emițător de evenimente pentru a gestiona toată comunicarea
- Inițializează un obiect erou la poziția de start
// Step 3: Set up event listeners (subscribers)
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.moveTo(hero.x - 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
eventEmitter.on(Messages.HERO_MOVE_RIGHT, () => {
hero.moveTo(hero.x + 5, hero.y);
console.log(`Hero moved to position: ${hero.x}, ${hero.y}`);
});
În cele de mai sus, am:
- Înregistrat ascultători de evenimente care răspund la mesaje de mișcare
- Actualizat poziția eroului pe baza direcției de mișcare
- Adăugat logare în consolă pentru a urmări schimbările de poziție ale eroului
- Separat logica de mișcare de gestionarea inputului
// Step 4: Connect keyboard input to events (publishers)
window.addEventListener('keydown', (event) => {
switch(event.key) {
case 'ArrowLeft':
eventEmitter.emit(Messages.HERO_MOVE_LEFT);
break;
case 'ArrowRight':
eventEmitter.emit(Messages.HERO_MOVE_RIGHT);
break;
}
});
Înțelegerea acestor concepte:
- Conectează inputul de la tastatură la evenimentele jocului fără o cuplare strânsă
- Permite sistemului de input să comunice indirect cu obiectele de joc
- Permite mai multor sisteme să răspundă la aceleași evenimente de tastatură
- Facilitează schimbarea legăturilor de taste sau adăugarea de noi metode de input
💡 Pro Tip: Frumusețea acestui model constă în flexibilitate! Poți adăuga cu ușurință efecte sonore, tremurături de ecran sau efecte de particule doar adăugând mai mulți ascultători de evenimente – fără a modifica codul existent pentru tastatură sau mișcare.
Iată de ce vei iubi această abordare:
- Adăugarea de noi funcționalități devine super ușoară – doar ascultă evenimentele care te interesează
- Mai multe lucruri pot reacționa la același eveniment fără a se încurca între ele
- Testarea devine mult mai simplă deoarece fiecare piesă funcționează independent
- Când ceva se strică, știi exact unde să cauți
De ce Pub/Sub Scalează Eficient
Modelul pub/sub menține simplitatea pe măsură ce aplicațiile devin mai complexe. Fie că gestionezi zeci de inamici, actualizări dinamice ale interfeței sau sisteme sonore, modelul face față creșterii complexității fără modificări arhitecturale. Noile funcționalități se integrează în sistemul de evenimente existent fără a afecta funcționalitatea stabilită.
⚠️ Greșeală Comună: Nu crea prea multe tipuri de mesaje specifice de la început. Începe cu categorii largi și rafinează-le pe măsură ce nevoile jocului devin mai clare.
Cele mai bune practici de urmat:
- Grupează mesajele legate în categorii logice
- Folosește nume descriptive care indică clar ce s-a întâmplat
- Păstrează încărcăturile de mesaje simple și concentrate
- Documentează tipurile de mesaje pentru colaborarea în echipă
Provocarea Agentului GitHub Copilot 🚀
Folosește modul Agent pentru a finaliza următoarea provocare:
Descriere: Creează un sistem simplu de obiecte de joc folosind atât moștenirea, cât și modelul pub/sub. Vei implementa un joc de bază în care diferite obiecte pot comunica prin evenimente fără a se cunoaște direct între ele.
Prompt: Creează un sistem de joc în JavaScript cu următoarele cerințe: 1) Creează o clasă de bază GameObject cu coordonate x, y și o proprietate de tip. 2) Creează o clasă Hero care extinde GameObject și se poate mișca. 3) Creează o clasă Enemy care extinde GameObject și poate urmări eroul. 4) Implementează o clasă EventEmitter pentru modelul pub/sub. 5) Configurează ascultători de evenimente astfel încât, atunci când eroul se mișcă, inamicii din apropiere să primească un eveniment 'HERO_MOVED' și să-și actualizeze poziția pentru a se apropia de erou. Include declarații console.log pentru a arăta comunicarea între obiecte.
Află mai multe despre modul agent aici.
🚀 Provocare
Gândește-te cum modelul pub-sub poate îmbunătăți arhitectura jocului. Identifică ce componente ar trebui să emită evenimente și cum ar trebui să răspundă sistemul. Proiectează un concept de joc și schițează modelele de comunicare între componentele sale.
Chestionar Post-Lecție
Recapitulare & Studiu Individual
Află mai multe despre Pub/Sub citind despre acest subiect.
Temă
Declinare de responsabilitate:
Acest document a fost tradus folosind serviciul de traducere AI Co-op Translator. Deși ne străduim să asigurăm acuratețea, vă rugăm să fiți conștienți că traducerile automate pot conține erori sau inexactități. Documentul original în limba sa natală ar trebui considerat sursa autoritară. Pentru informații critice, se recomandă traducerea profesională realizată de un specialist uman. Nu ne asumăm responsabilitatea pentru eventualele neînțelegeri sau interpretări greșite care pot apărea din utilizarea acestei traduceri.
