|
|
1 month ago | |
|---|---|---|
| .. | ||
| solution | 3 months ago | |
| your-work | 3 months ago | |
| README.md | 1 month ago | |
| assignment.md | 1 month ago | |
README.md
Byg et Rumspil Del 6: Afslutning og Genstart
Ethvert godt spil har brug for klare afslutningsbetingelser og en glidende genstartsmekanisme. Du har allerede bygget et imponerende rumspil med bevægelse, kamp og pointgivning - nu er det tid til at tilføje de sidste brikker, der gør det komplet.
Dit spil kører i øjeblikket uendeligt, ligesom Voyager-sonderne, som NASA sendte ud i rummet i 1977 - og som stadig rejser gennem rummet årtier senere. Selvom det er fint til rumforskning, har spil brug for definerede slutpunkter for at skabe tilfredsstillende oplevelser.
I dag implementerer vi ordentlige vinder-/taberbetingelser og et genstartssystem. Når du er færdig med denne lektion, vil du have et poleret spil, som spillere kan fuldføre og spille igen, ligesom de klassiske arkadespil, der definerede mediet.
Quiz før lektionen
Forståelse af spillets afslutningsbetingelser
Hvornår skal dit spil slutte? Dette fundamentale spørgsmål har formet spildesign siden den tidlige arkadeæra. Pac-Man slutter, når du bliver fanget af spøgelser eller rydder alle prikker, mens Space Invaders slutter, når rumvæsnerne når bunden eller du har ødelagt dem alle.
Som spilskaber definerer du selv sejr- og nederlagsbetingelserne. For vores rumspil er her nogle velprøvede tilgange, der skaber engagerende gameplay:
Nfjendtlige skibe er blevet ødelagt: Det er ret almindeligt, hvis du deler et spil op i forskellige niveauer, at du skal ødelæggeNfjendtlige skibe for at fuldføre et niveau.- Dit skib er blevet ødelagt: Der er bestemt spil, hvor du taber, hvis dit skib bliver ødelagt. En anden almindelig tilgang er at have et livssystem. Hver gang dit skib bliver ødelagt, mister du et liv. Når alle liv er tabt, taber du spillet.
- Du har samlet
Npoint: En anden almindelig afslutningsbetingelse er at samle point. Hvordan du får point, er op til dig, men det er ret almindeligt at tildele point til forskellige aktiviteter som at ødelægge et fjendtligt skib eller måske samle genstande, som fjender taber, når de bliver ødelagt. - Fuldfør et niveau: Dette kan involvere flere betingelser såsom
Xødelagte fjendtlige skibe,Ysamlede point eller måske, at en specifik genstand er blevet samlet.
Implementering af genstartsfunktionalitet
Gode spil opmuntrer til genafspilning gennem glidende genstartsmekanismer. Når spillere fuldfører et spil (eller møder nederlag), vil de ofte gerne prøve igen med det samme - enten for at slå deres score eller forbedre deres præstation.
Tetris er et perfekt eksempel: Når dine blokke når toppen, kan du straks starte et nyt spil uden at navigere gennem komplekse menuer. Vi vil bygge et lignende genstartssystem, der renser spiltilstanden og hurtigt får spillerne tilbage i aktion.
✅ Refleksion: Tænk over de spil, du har spillet. Under hvilke betingelser slutter de, og hvordan bliver du opfordret til at genstarte? Hvad gør en genstartoplevelse glidende i stedet for frustrerende?
Hvad du vil bygge
Du vil implementere de sidste funktioner, der forvandler dit projekt til en komplet spiloplevelse. Disse elementer adskiller polerede spil fra grundlæggende prototyper.
Her er, hvad vi tilføjer i dag:
- Sejrsbetingelse: Skyd alle fjenderne og få en ordentlig fejring (det har du fortjent!)
- Nederlagsbetingelse: Løb tør for liv og mød nederlaget med en taberskærm
- Genstartsmekanisme: Tryk på Enter for straks at starte igen - fordi ét spil aldrig er nok
- Tilstandshåndtering: Ren start hver gang - ingen resterende fjender eller mærkelige fejl fra sidste spil
Kom godt i gang
Lad os forberede dit udviklingsmiljø. Du bør have alle dine rumspilsfiler fra de tidligere lektioner klar.
Dit projekt bør se nogenlunde sådan ud:
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
Start din udviklingsserver:
cd your-work
npm start
Denne kommando:
- Kører en lokal server på
http://localhost:5000 - Serverer dine filer korrekt
- Opdaterer automatisk, når du foretager ændringer
Åbn http://localhost:5000 i din browser og verificer, at dit spil kører. Du bør kunne bevæge dig, skyde og interagere med fjender. Når det er bekræftet, kan vi fortsætte med implementeringen.
💡 Pro Tip: For at undgå advarsler i Visual Studio Code, deklarer
gameLoopIdøverst i din fil somlet gameLoopId;i stedet for at deklarere det inde i funktionenwindow.onload. Dette følger moderne JavaScript-praksis for variabeldeklaration.
Implementeringstrin
Trin 1: Opret funktioner til spore afslutningsbetingelser
Vi har brug for funktioner til at overvåge, hvornår spillet skal slutte. Ligesom sensorer på den internationale rumstation, der konstant overvåger kritiske systemer, vil disse funktioner løbende tjekke spiltilstanden.
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
Her er, hvad der sker bag kulisserne:
- Tjekker, om vores helt er løbet tør for liv (av!)
- Tæller, hvor mange fjender der stadig er i live
- Returnerer
true, når slagmarken er ryddet for fjender - Bruger simpel sand/falsk logik for at holde det enkelt
- Filtrerer gennem alle spilobjekter for at finde de overlevende
Trin 2: Opdater hændelseshåndterere for afslutningsbetingelser
Nu vil vi forbinde disse betingelsestjek med spillets hændelsessystem. Hver gang en kollision opstår, vil spillet evaluere, om det udløser en afslutningsbetingelse. Dette skaber øjeblikkelig feedback for kritiske spilbegivenheder.
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
Hvad der sker her:
- Laser rammer fjende: Begge forsvinder, du får point, og vi tjekker, om du har vundet
- Fjende rammer dig: Du mister et liv, og vi tjekker, om du stadig er i live
- Smart rækkefølge: Vi tjekker for nederlag først (ingen ønsker at vinde og tabe på samme tid!)
- Øjeblikkelige reaktioner: Så snart noget vigtigt sker, ved spillet det
Trin 3: Tilføj nye beskedkonstanter
Du skal tilføje nye beskedtyper til dit Messages konstantobjekt. Disse konstanter hjælper med at opretholde konsistens og forhindre tastefejl i dit hændelsessystem.
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
I ovenstående har vi:
- Tilføjet konstanter for spilafslutningsbegivenheder for at opretholde konsistens
- Brugt beskrivende navne, der tydeligt angiver begivenhedens formål
- Fulgte den eksisterende navngivningskonvention for beskedtyper
Trin 4: Implementer genstartskontroller
Nu vil du tilføje tastaturkontroller, der giver spillere mulighed for at genstarte spillet. Enter-tasten er et naturligt valg, da den ofte er forbundet med at bekræfte handlinger og starte nye spil.
Tilføj detektion af Enter-tasten til din eksisterende keydown-hændelseslytter:
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
Tilføj den nye beskedkonstant:
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
Hvad du skal vide:
- Udvider dit eksisterende tastaturhændelseshåndteringssystem
- Bruger Enter-tasten som genstartstrigger for intuitiv brugeroplevelse
- Udsender en brugerdefineret hændelse, som andre dele af dit spil kan lytte efter
- Opretholder samme mønster som dine andre tastaturkontroller
Trin 5: Opret beskedvisningssystem
Dit spil skal kommunikere resultater tydeligt til spillerne. Vi opretter et beskedsystem, der viser sejrs- og nederlagstilstande med farvekodet tekst, ligesom terminalgrænsefladerne på tidlige computersystemer, hvor grøn indikerede succes, og rød signalerede fejl.
Opret funktionen displayMessage():
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
Trin for trin, her er hvad der sker:
- Indstiller skrifttype og størrelse for klar og læsbar tekst
- Anvender en farveparameter med "rød" som standard for advarsler
- Centrerer teksten horisontalt og vertikalt på lærredet
- Bruger moderne JavaScript standardparametre for fleksible farvevalg
- Udnytter canvas 2D-konteksten til direkte tekstgengivelse
Opret funktionen endGame():
function endGame(win) {
clearInterval(gameLoopId);
// Set a delay to ensure any pending renders complete
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
Hvad denne funktion gør:
- Fryser alt på plads - ingen flere bevægelige skibe eller lasere
- Tager en lille pause (200ms) for at lade den sidste frame blive tegnet færdig
- Rydder skærmen og maler den sort for dramatisk effekt
- Viser forskellige beskeder for vindere og tabere
- Farvekoder nyhederne - grøn for godt, rød for... ja, ikke så godt
- Fortæller spillerne præcis, hvordan de kan hoppe tilbage i spillet
Trin 6: Implementer spilnulstillingsfunktionalitet
Nulstillingssystemet skal fuldstændigt rydde den aktuelle spiltilstand og initialisere en ny spilsession. Dette sikrer, at spillerne får en ren start uden resterende data fra det tidligere spil.
Opret funktionen resetGame():
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
Lad os forstå hver del:
- Tjekker, om en spilsløjfe kører i øjeblikket, før den nulstilles
- Rydder den eksisterende spilsløjfe for at stoppe al nuværende spilaktivitet
- Fjerner alle hændelseslyttere for at forhindre hukommelseslækager
- Reinitialiserer spiltilstanden med friske objekter og variabler
- Starter en ny spilsløjfe med alle de essentielle spilfunktioner
- Opretholder samme 100ms interval for konsistent spilpræstation
Tilføj Enter-tastens hændelseshåndtering til din initGame()-funktion:
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
Tilføj metoden clear() til din EventEmitter-klasse:
clear() {
this.listeners = {};
}
Vigtige punkter at huske:
- Forbinder Enter-tastetryk med nulstillingsfunktionaliteten
- Registrerer denne hændelseslytter under spilinitialisering
- Giver en ren måde at fjerne alle hændelseslyttere ved nulstilling
- Forhindrer hukommelseslækager ved at rydde hændelseshåndterere mellem spil
- Nulstiller lyttere-objektet til en tom tilstand for frisk initialisering
Tillykke! 🎉
👽 💥 🚀 Du har med succes bygget et komplet spil fra bunden. Ligesom programmørerne, der skabte de første videospil i 1970'erne, har du forvandlet linjer af kode til en interaktiv oplevelse med ordentlige spilmekanikker og brugerfeedback. 🚀 💥 👽
Du har opnået:
- Implementeret komplette vinder- og taberbetingelser med brugerfeedback
- Skabt et glidende genstartssystem for kontinuerlig gameplay
- Designet klar visuel kommunikation for spiltilstande
- Håndteret komplekse spiltilstandsovergange og oprydning
- Sammensat alle komponenter til et sammenhængende, spilbart spil
GitHub Copilot Agent Challenge 🚀
Brug Agent-tilstand til at fuldføre følgende udfordring:
Beskrivelse: Forbedr rumspillet ved at implementere et niveauprogressionssystem med stigende sværhedsgrad og bonusfunktioner.
Opgave: Opret et flerniveaus rumspilssystem, hvor hvert niveau har flere fjendtlige skibe med øget hastighed og sundhed. Tilføj en pointmultiplikator, der stiger med hvert niveau, og implementer power-ups (som hurtig skydning eller skjold), der tilfældigt dukker op, når fjender bliver ødelagt. Inkluder en niveauafslutningsbonus og vis det aktuelle niveau på skærmen sammen med den eksisterende score og liv.
Læs mere om agent mode her.
🚀 Valgfri Forbedringsudfordring
Tilføj Lyd til Dit Spil: Forbedr din spiloplevelse ved at implementere lydeffekter! Overvej at tilføje lyd til:
- Laser skud, når spilleren skyder
- Fjendens ødelæggelse, når skibe bliver ramt
- Helteskade, når spilleren bliver ramt
- Sejrsmusik, når spillet er vundet
- Nederlagslyd, når spillet er tabt
Eksempel på lydimplementering:
// Create audio objects
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');
// Play sounds during game events
function playLaserSound() {
laserSound.currentTime = 0; // Reset to beginning
laserSound.play();
}
Hvad du skal vide:
- Opretter Audio-objekter til forskellige lydeffekter
- Nulstiller
currentTimefor at tillade hurtige lydeffekter - Håndterer browserens autoplay-politikker ved at udløse lyde fra brugerinteraktioner
- Administrerer lydstyrke og timing for bedre spiloplevelse
💡 Læringsressource: Udforsk denne audio sandbox for at lære mere om implementering af lyd i JavaScript-spil.
Quiz efter lektionen
Gennemgang & Selvstudie
Din opgave er at skabe et nyt prøvespil, så udforsk nogle af de interessante spil derude for at se, hvilken type spil du kunne bygge.
Opgave
Ansvarsfraskrivelse:
Dette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi bestræber os på nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi er ikke ansvarlige for eventuelle misforståelser eller fejltolkninger, der opstår som følge af brugen af denne oversættelse.