|
|
4 months ago | |
|---|---|---|
| .. | ||
| README.md | 4 months ago | |
| assignment.md | 6 months ago | |
README.md
Skapa ett rymdspel del 3: Lägga till rörelse
journey
title Din SpelsAnimationsresa
section Rörelsegrunder
Förstå rörelseprinciper: 3: Student
Lära sig koordinatuppdateringar: 4: Student
Implementera grundläggande rörelse: 4: Student
section Spelarkontroller
Hantera tangentbordevenemang: 4: Student
Förhindra standardbeteenden: 5: Student
Skapa responsiva kontroller: 5: Student
section Spelsystem
Bygg spelloop: 5: Student
Hantera objekts livscykel: 5: Student
Implementera pub/sub-mönster: 5: Student
Tänk på dina favoritspel – det som gör dem fängslande är inte bara snygg grafik, det är sättet allt rör sig och svarar på dina handlingar. Just nu är ditt rymdspel som en vacker målning, men vi är på väg att lägga till rörelse som ger det liv.
När NASAs ingenjörer programmerade styrdatorn för Apollo-uppdragen stod de inför en liknande utmaning: hur får man ett rymdskepp att svara på pilotens input samtidigt som det automatiskt gör kurskorrigeringar? Principerna vi ska lära oss idag ekar samma koncept – att hantera spelarstyrd rörelse tillsammans med automatiska systembeteenden.
I denna lektion ska du lära dig hur du får rymdskepp att glida över skärmen, svara på spelarkommandon och skapa mjuka rörelsemönster. Vi delar upp allt i hanterbara koncept som bygger på varandra på ett naturligt sätt.
I slutet kommer spelarna flyga sitt hjälteskepp runt på skärmen medan fientliga skepp patrullerar ovanför. Än viktigare är att du kommer förstå de grundläggande principerna som driver spelsystemen för rörelse.
mindmap
root((Spelanimation))
Movement Types
Spelarkontrollerad
Automatisk Rörelse
Fysikbaserad
Skriptade Vägar
Event Handling
Tangentbordsinmatning
Musehändelser
Pekskärmskontroller
Standardförebyggande
Game Loop
Uppdatera Logik
Rendera Bildruta
Rensa Canvas
Bildrutehastighetskontroll
Object Management
Positionsuppdateringar
Kollisionsdetektion
Livscykelhantering
Statusspårning
Communication
Pub/Sub Mönster
Händelseavsändare
Meddelandeöverföring
Lös Koppling
Förförelsetest
Förståelse för spelrörelse
Spel blir levande när saker börjar röra sig, och det finns i grunden två sätt detta händer på:
- Spelarstyrd rörelse: När du trycker på en tangent eller klickar med musen, rör sig något. Det är den direkta kopplingen mellan dig och din spelvärld.
- Automatisk rörelse: När spelet självt bestämmer att något ska röra sig – som de där fientliga skeppen som måste patrullera skärmen oavsett om du gör någonting eller inte.
Att få objekt att röra sig på en datorskärm är enklare än du kanske tror. Kommer du ihåg x- och y-koordinaterna från mattetimmarna? Det är precis det vi jobbar med här. När Galileo 1610 kartlade Jupiters månar gjorde han i princip samma sak – plottade positioner över tid för att förstå rörelsemönster.
Att flytta saker på skärmen är som att skapa en bläddra-bok-animation – du behöver följa dessa tre enkla steg:
flowchart LR
A["Bildruta N"] --> B["Uppdatera positioner"]
B --> C["Rensa duk"]
C --> D["Rita objekt"]
D --> E["Bildruta N+1"]
E --> F{Fortsätta?}
F -->|Ja| B
F -->|Nej| G["Spelet är slut"]
subgraph "Animationscykel"
H["1. Beräkna nya positioner"]
I["2. Radera föregående bildruta"]
J["3. Rendera ny bildruta"]
end
style B fill:#e1f5fe
style C fill:#ffebee
style D fill:#e8f5e8
- Uppdatera positionen – Ändra var ditt objekt ska vara (kanske flytta det 5 pixlar åt höger)
- Radera den gamla ramen – Rensa skärmen så att du inte ser spöklika spår överallt
- Rita den nya ramen – Placera ditt objekt på sin nya plats
Gör du detta tillräckligt snabbt, och pang! Har du mjuk rörelse som känns naturlig för spelarna.
Så här kan det se ut i kod:
// Ställ in hjälterens plats
hero.x += 5;
// Rensa rektangeln som rymmer hjälten
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Rita om spelbakgrunden och hjälten
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
Det här gör koden:
- Uppdaterar hjälterymdskeppets x-koordinat med 5 pixlar för att flytta det horisontellt
- Rensar hela canvasområdet för att ta bort den tidigare ramen
- Fyller canvas med en svart bakgrundsfärg
- Ritar om hjälten i sin nya position
✅ Kan du tänka dig en anledning till varför att rita om hjälten många gånger per sekund kan leda till prestandakostnader? Läs om alternativ till detta mönster.
Hantera tangentbords-händelser
Här kopplar vi spelarinmatning till spelfunktioner. När någon trycker på mellanslag för att skjuta en laser eller trycker på en piltangent för att ducka för en asteroid, måste spelet känna av och svara på den inmatningen.
Tangentbordshändelser inträffar på fönsternivå, vilket betyder att hela din webbläsarfönster lyssnar efter dessa tangenttryckningar. Musklick kan däremot kopplas till specifika element (som att klicka på en knapp). För vårt rymdspel fokuserar vi på tangentbordsstyrning eftersom det ger spelarna den klassiska arkadkänslan.
Det påminner om hur telegrafoperatörer på 1800-talet behövde översätta morsekod till meningsfulla meddelanden – vi gör något liknande här, vi översätter tangenttryckningar till spelkommandon.
För att hantera en händelse måste du använda fönstrets addEventListener()-metod och ge den två inparametrar. Den första parametern är namnet på händelsen, t.ex. keyup. Den andra är funktionen som ska anropas när händelsen inträffar.
Så här ser ett exempel ut:
window.addEventListener('keyup', (evt) => {
// evt.key = strängrepresentation av tangenten
if (evt.key === 'ArrowUp') {
// gör något
}
});
Det här händer i koden:
- Lyssnar efter tangentbordshändelser på hela fönstret
- Fångar event-objektet som innehåller information om vilken tangent som trycktes
- Kontrollerar om den tryckta tangenten matchar en viss tangent (i detta fall pil upp)
- Utför kod när villkoret är sant
För tangentbords-händelser finns två egenskaper på event-objektet som du kan använda för att se vilken tangent som trycktes:
key- detta är en strängrepresentation av tryckta tangenten, t.ex.'ArrowUp'keyCode- detta är ett nummer, t.ex.37, som motsvararArrowLeft
✅ Tangentbordsmanipulation är användbart även utanför spelutveckling. Vilka andra användningar kan du tänka dig för denna teknik?
sequenceDiagram
participant User
participant Browser
participant EventSystem
participant GameLogic
participant Hero
User->>Browser: Trycker på pil upp-tangenten
Browser->>EventSystem: keydown-händelse
EventSystem->>EventSystem: preventDefault()
EventSystem->>GameLogic: emit('KEY_EVENT_UP')
GameLogic->>Hero: hero.y -= 5
Hero->>Hero: Uppdaterar position
Note over Browser,GameLogic: Händelseflöde förhindrar webbläsarens standardåtgärder
Note over GameLogic,Hero: Pub/sub-mönster möjliggör ren kommunikation
Speciella tangenter: en varning!
Vissa tangenter har inbyggda beteenden i webbläsaren som kan störa ditt spel. Piltangenter scrollar sidan och mellanslag hoppar ner – beteenden du inte vill ha när någon försöker styra sitt rymdskepp.
Vi kan förhindra dessa standardbeteenden och låta vårt spel hantera inmatningen istället. Det är likt hur tidiga programmerare var tvungna att överstyra systemavbrott för att skapa egna beteenden – här gör vi det på webbläsarnivå. Så här gör du:
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Piltangenter
case 32:
e.preventDefault();
break; // Mellanslag
default:
break; // blockera inte andra tangenter
}
};
window.addEventListener('keydown', onKeyDown);
Det här förhindrande-koden gör:
- Kollar specifika tangentkoder som kan orsaka oönskat beteende i webbläsaren
- Stoppar standardåtgärden i webbläsaren för piltangenter och mellanslag
- Tillåter andra tangenter att fungera normalt
- Använder
e.preventDefault()för att stoppa webbläsarens inbyggda beteende
🔄 Pedagogisk kontroll
Förståelse för Händelsehantering: Innan du går vidare till automatisk rörelse, se till att du kan:
- ✅ Förklara skillnaden mellan
keydownochkeyup-händelser - ✅ Förstå varför vi förhindrar standardbeteenden i webbläsaren
- ✅ Beskriva hur eventlyssnare kopplar användarinmatning till spel-logik
- ✅ Identifiera vilka tangenter som kan störa spelkontroller
Snabbt självtest: Vad skulle hända om du inte förhindrade standardbeteende för piltangenterna? Svar: Webbläsaren skulle scrolla sidan och störa spelets rörelse
Event-systemets arkitektur: Du förstår nu:
- Lyssnande på fönsternivå: Fånga händelser på webbläsarnivå
- Egenskaper på event-objektet:
keysträngar vskeyCodenummer - Standardförhindrande: Stoppa oönskade webbläsarbeteenden
- Villkorslogik: Svara på specifika tangentkombinationer
Spelinducerad rörelse
Nu pratar vi om objekt som rör sig utan spelarinput. Tänk på fiendeskepp som seglar över skärmen, kulor som flyger i raka linjer eller moln som driver i bakgrunden. Denna autonoma rörelse gör att din spelvärld känns levande även när ingen rör kontrollerna.
Vi använder JavaScripts inbyggda timers för att uppdatera positioner med jämna intervaller. Detta koncept påminner om hur pendelur fungerar – en regelbunden mekanism som triggar konsekventa, tidsstyrda åtgärder. Så här enkelt kan det vara:
const id = setInterval(() => {
// Flytta fienden på y-axeln
enemy.y += 10;
}, 100);
Det här gör rörelsekoden:
- Skapar en timer som körs var 100:e millisekund
- Uppdaterar fiendens y-koordinat med 10 pixlar varje gång
- Sparar intervallets ID så vi kan stoppa det senare om det behövs
- Flyttar fienden automatiskt nedåt på skärmen
Spelloopen
Här är konceptet som binder samman allt – spelloopen. Om ditt spel vore en film, skulle spelloopen vara filmprojektorn som visar bildruta efter bildruta så snabbt att allt ser ut att röra sig mjukt.
Alla spel har en sådan loop som körs i bakgrunden. Det är en funktion som uppdaterar alla spelobjekt, ritar om skärmen och upprepar detta kontinuerligt. Den håller koll på hjälten, alla fiender, laserskott – hela spelstatusen.
Det påminner mig om hur tidiga filmanimatörer som Walt Disney var tvungna att rita om karaktärer bildruta för bildruta för att skapa illusionen av rörelse. Vi gör samma sak, fast med kod istället för pennor.
Så här kan en spelloop typiskt se ut i kod:
flowchart TD
A["Starta Spel Loopen"] --> B["Rensa Duk"]
B --> C["Fyll Bakgrund"]
C --> D["Uppdatera Spelobjekt"]
D --> E["Rita Hjälte"]
E --> F["Rita Fiender"]
F --> G["Rita UI Element"]
G --> H["Vänta på Nästa Bildruta"]
H --> I{Spel Körs?}
I -->|Ja| B
I -->|Nej| J["Avsluta Spel"]
subgraph "Bildrutehastighetskontroll"
K["60 FPS = 16.67ms"]
L["30 FPS = 33.33ms"]
M["10 FPS = 100ms"]
end
style B fill:#ffebee
style D fill:#e1f5fe
style E fill:#e8f5e8
style F fill:#e8f5e8
const 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();
}
gameLoop();
}, 200);
Förståelse för spelloopsstruktur:
- Rensar hela canvas för att ta bort föregående bildruta
- Fyller bakgrunden med en enfärgad bakgrund
- Ritar alla spelobjekt i sina aktuella positioner
- Upprepar denna process var 200:e millisekund för att skapa mjuk animation
- Hanterar bildrutefrekvensen genom att kontrollera intervallets timing
Fortsättning på Rymdspelet
Nu ska vi lägga till rörelse till den statiska scenen du byggt tidigare. Vi ska förvandla den från en stillbild till en interaktiv upplevelse. Vi arbetar steg för steg för att varje del ska bygga på den föregående.
Hämta koden från där vi slutade i föregående lektion (eller börja med koden i mappen Part II- starter om du behöver börja från början).
Det här bygger vi idag:
- Hjälte-kontroller: Piltangenter styr ditt rymdskepp runt skärmen
- Fienderörelse: De där utomjordiska skeppen påbörjar sin framryckning
Låt oss börja implementera dessa funktioner.
Rekommenderade steg
Leta upp filerna som har skapats åt dig i undermappen your-work. Den bör innehålla följande:
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
Du börjar ditt projekt i mappen your-work genom att skriva:
cd your-work
npm start
Det här kommandot gör:
- Navigerar till din projektkatalog
- Startar en HTTP-server på adressen
http://localhost:5000 - Serverar dina spel-filer så att du kan testa dem i en webbläsare
Ovanstående startar en HTTP-server på adressen http://localhost:5000. Öppna en webbläsare och skriv in den adressen, för tillfället ska det visa hjälten och alla fiender; inget rör sig – än!
Lägg till kod
-
Lägg till dedikerade objekt för
hero,enemyochgame object, de ska haxochyegenskaper. (Kom ihåg delen om Inheritance eller composition).TIPS
game objectska vara den som harxochyoch förmågan att rita sig själv på en canvas.Tips: Börja med att lägga till en ny
GameObject-klass med konstruktorn definierad enligt nedan, och sedan rita den på canvas: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); } }Förståelse för basklassen:
- Definierar gemensamma egenskaper som alla spelobjekt delar (position, storlek, bild)
- Inkluderar en flagga
deadför att hålla koll på om objektet ska tas bort - Tillhandahåller en
draw()-metod som ritar objektet på canvas - Sätter standardvärden för alla egenskaper som underklasser kan åsidosätta
classDiagram
class GameObject {
+x: number
+y: number
+död: boolean
+typ: string
+bredd: number
+höjd: number
+bild: Image
+rita(ctx)
}
class Hero {
+hastighet: number
+typ: "Hero"
+bredd: 98
+höjd: 75
}
class Enemy {
+typ: "Enemy"
+bredd: 98
+höjd: 50
+sättIntervall()
}
GameObject <|-- Hero
GameObject <|-- Enemy
class EventEmitter {
+lyssnare: object
+på(meddelande, lyssnare)
+skicka(meddelande, data)
}
Nu, utöka denna `GameObject` för att skapa `Hero` och `Enemy`:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 98;
this.height = 75;
this.type = "Hero";
this.speed = 5;
}
}
```
```javascript
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
this.width = 98;
this.height = 50;
this.type = "Enemy";
const id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y);
clearInterval(id);
}
}, 300);
}
}
```
**Nyckelkoncept i dessa klasser:**
- **Ärver** från `GameObject` via `extends`-nyckelordet
- **Kallar** föräldrakonstruktorn med `super(x, y)`
- **Sätter** specifika dimensioner och egenskaper för varje objekttyp
- **Implementerar** automatisk rörelse för fiender med hjälp av `setInterval()`
-
Lägg till tangentbords-händelsehanterare för att hantera tangentsurfning (flytta hjälten upp/ner vänster/höger)
KOM IHÅG det är ett kartesiskt system, övre vänstra hörnet är
0,0. Kom också ihåg att lägga till kod för att stoppa standardbeteendeTips: Skapa din
onKeyDown-funktion och koppla den till fönstret:const onKeyDown = function (e) { console.log(e.keyCode); // Lägg till koden från lektionen ovan för att stoppa standardbeteendet switch (e.keyCode) { case 37: case 39: case 38: case 40: // Piltangenter case 32: e.preventDefault(); break; // Mellanslag default: break; // blockera inte andra tangenter } }; window.addEventListener("keydown", onKeyDown);Det här gör eventhanteraren:
- Lyssnar efter keydown-händelser på hela fönstret
- Loggar tangentkoden för att hjälpa dig debugga vilka tangenter som trycks
- Förhindrar standard webbläsarbeteende för piltangenter och mellanslag
- Tillåter andra tangenter att fungera normalt
Kolla din webbläsarkonsol vid detta steg och se tangenttryckningarna loggas.
-
Implementera Pub sub-mönstret, detta håller din kod ren när du följer resterande delar.
Publish-Subscribe-mönstret hjälper till att organisera koden genom att separera detektering av händelser från händelsehantering. Det gör koden mer modulär och enklare att underhålla.
För att göra denna sista del kan du:
-
Lägga till en event listener på fönstret:
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); } });
Det här gör event-systemet:
- Känner av tangentbordsinput och konverterar det till egna spelsevenemang
- Separera inmatningsdetektion från spelloop-logik
- Gör det lätt att ändra kontroller senare utan att påverka spelkoden
- Tillåter flera system att svara på samma inmatning
-
flowchart TD
A["Tangentbordsinmatning"] --> B["Fönsterhändelselyssnare"]
B --> C["Händelseutlösare"]
C --> D["TANGENTEVENT_UP"]
C --> E["TANGENTEVENT_NER"]
C --> F["TANGENTEVENT_VÄNSTER"]
C --> G["TANGENTEVENT_HÖGER"]
D --> H["Hjältens rörelse"]
D --> I["Ljudsystem"]
D --> J["Visuella effekter"]
E --> H
F --> H
G --> H
style A fill:#e1f5fe
style C fill:#e8f5e8
style H fill:#fff3e0
-
Skapa en EventEmitter-klass för att publicera och prenumerera på meddelanden:
class EventEmitter { constructor() { this.listeners = {}; } on(message, listener) { if (!this.listeners[message]) { this.listeners[message] = []; } this.listeners[message].push(listener); } -
Lägg till konstanter och sätt upp EventEmitter:
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();
Förståelse för uppsättningen:
- Definierar meddelandekonstanter för att undvika felskrivningar och underlätta refaktorering
- Deklarerar variabler för bilder, canvas-kontext och spelstatus
- Skapar en global event emitter för pub-sub-systemet
- Initierar en array för att hålla alla spelobjekt
-
Initiera spelet
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; }); -
Sätt upp spel-loopen
Refaktorera funktionen
window.onloadför att initiera spelet och sätta upp en spel-loop med ett bra intervall. Du kommer även att lägga till en laserstråle: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(); const gameLoopId = setInterval(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); drawGameObjects(ctx); }, 100); };Förståelse för speluppsättningen:
- Väntar på att sidan ska laddas klart innan start
- Hämtar canvas-elementet och dess 2D-renderingskontext
- Laddar alla bildresurser asynkront med
await - Startar spel-loopen som körs med 100ms intervall (10 FPS)
- Rensar och ritar om hela skärmen varje bildruta
-
Lägg till kod för att röra fiender med ett visst intervall
Refaktorera funktionen
createEnemies()för att skapa fienderna och lägga till dem i den nya gameObjects-klassen: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); } } }Vad fiendeskapandet gör:
- Beräknar positioner för att centrera fiender på skärmen
- Skapar ett rutnät av fiender med nästlade loopar
- Tilldelar fiendebilden till varje fiendeobjekt
- Lägger till varje fiende i den globala game objects-arrayen
och lägg till en
createHero()-funktion för att göra en liknande process för hjälten.function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }Vad hjälteskapandet gör:
- Positionerar hjälten längst ner i mitten av skärmen
- Tilldelar hjältebilden till hjälteobjektet
- Lägger till hjälten i game objects-arrayen för rendering
och slutligen, lägg till en
drawGameObjects()-funktion för att börja rita:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }Förståelse för ritfunktionen:
- Itererar genom alla spelobjekt i arrayen
- Anropar
draw()-metoden på varje objekt - Skickar canvas-kontexten så att objekten kan rita sig själva
🔄 Pedagogisk kontroll
Fullständig förståelse av spelsystemet: Verifiera din behärskning av hela arkitekturen:
- ✅ Hur låter arv hjälte och fiende dela gemensamma GameObject-egenskaper?
- ✅ Varför gör pub/sub-mönstret din kod mer underhållbar?
- ✅ Vilken roll spelar spel-loopen för att skapa smidig animation?
- ✅ Hur kopplar event-lyssnare användarinput till spelobjektsbeteende?
Systemintegration: Ditt spel demonstrerar nu:
- Objektorienterad design: Bas-klasser med specialiserat arv
- Händelsestyrd arkitektur: Pub/sub-mönster för lös koppling
- Animationsramverk: Spel-loop med konsekventa uppdateringar
- Inputhantering: Tangentbords-händelser med standardförhindran
- Resurshantering: Bildinladdning och spritrendering
Professionella mönster: Du har implementerat:
- Separation av ansvar: Input, logik och rendering separerade
- Polymorfism: Alla spelobjekt delar en gemensam ritningsgränssnitt
- Meddelandepassning: Ren kommunikation mellan komponenter
- Resurshantering: Effektiv sprite- och animationshantering
Dina fiender bör nu börja avancera mot ditt hjälteskepp! } }
and add a `createHero()` function to do a similar process for the hero. ```javascript function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }och slutligen, lägg till en
drawGameObjects()-funktion för att börja rita:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }Dina fiender bör nu börja avancera mot ditt hjälteskepp!
GitHub Copilot Agent-utmaning 🚀
Här är en utmaning som kommer att förbättra ditt spels höjdpunkt: att lägga till gränser och smidiga kontroller. Just nu kan din hjälte flyga utanför skärmen, och rörelsen kan kännas hackig.
Din uppgift: Få ditt rymdskepp att kännas mer realistiskt genom att implementera skärmkanter och flytande rörelse. Detta liknar hur NASAs flygkontrollsystem förhindrar att rymdfarkoster överskrider säkra operativa gränser.
Det här ska du bygga: Skapa ett system som håller ditt hjälteskepp på skärmen, och gör kontrollerna smidiga. När spelare håller ned en piltangent ska skeppet glida kontinuerligt istället för att röra sig i steg. Tänk också på att lägga till visuell feedback när skeppet når skärmens gränser – kanske en subtil effekt för att indikera spelområdet.
Läs mer om agentläge här.
🚀 Utmaning
Kodorganisation blir allt viktigare i takt med att projekt växer. Du kanske har märkt att din fil börjar bli överfull med funktioner, variabler och klasser blandat tillsammans. Detta påminner mig om hur ingenjörerna som organiserade Apollo-uppdragets kod var tvungna att skapa tydliga, underhållbara system som flera team kunde arbeta på samtidigt.
Din uppgift: Tänk som en mjukvaruarkitekt. Hur skulle du organisera din kod så att sex månader från nu, du (eller en kollega) kan förstå vad som händer? Även om allt är i en fil just nu kan du skapa bättre organisation:
- Gruppera relaterade funktioner tillsammans med tydliga kommentar-rubriker
- Separera ansvarsområden - håll spel-logik åtskild från rendering
- Använda konsekventa namngivningsprinciper för variabler och funktioner
- Skapa moduler eller namnrymder för att organisera olika delar av spelet
- Lägga till dokumentation som förklarar syftet med varje större sektion
Reflektionsfrågor:
- Vilka delar av din kod är svårast att förstå när du återvänder till dem?
- Hur skulle du organisera din kod för att göra det lättare för någon annan att bidra?
- Vad skulle hända om du ville lägga till nya funktioner som power-ups eller olika fiendetyper?
Quiz efter föreläsning
Granskning & Självstudier
Vi har byggt allt från grunden, vilket är fantastiskt för lärande, men här är en liten hemlighet – det finns några fantastiska JavaScript-ramverk som kan hantera mycket av det tunga arbetet åt dig. När du känner dig bekväm med grunderna vi gått igenom är det värt att utforska vad som finns.
Tänk på ramverk som en välfylld verktygslåda istället för att göra varje verktyg för hand. De kan lösa många av de kodorganisationsutmaningar vi talade om, plus erbjuda funktioner som skulle ta veckor att utveckla själv.
Saker värda att utforska:
- Hur spelmotorer organiserar kod – du kommer att bli förvånad över de smarta mönster de använder
- Prestandatricks för att få canvas-spel att flyta på fint
- Moderna JavaScript-funktioner som kan göra din kod renare och mer underhållbar
- Olika metoder för att hantera spelobjekt och deras relationer
🎯 Din tidslinje för spelanimationsexpertis
timeline
title Spelanimering & Interaktionsinlärningsprogression
section Rörelsegrunder (20 minuter)
Animationsprinciper: Ram-baserad animation
: Positionsuppdateringar
: Koordinatsystem
: Mjuk rörelse
section Händelsesystem (25 minuter)
Användarinmatning: Tangentbords-händelsehantering
: Förhindrande av standardbeteende
: Egenskaper hos händelseobjekt
: Lyssning på fönsternivå
section Spelarkitektur (30 minuter)
Objektutformning: Arvsmönster
: Skapande av basklass
: Specialiserade beteenden
: Polymorfa gränssnitt
section Kommunikationsmönster (35 minuter)
Pub/Sub-implementering: Händelseutfärdare
: Meddelandekonstanter
: Lös koppling
: Systemintegration
section Spelloopskunskap (40 minuter)
Realtidssystem: Bildrutehastighetskontroll
: Uppdaterings/rendercykel
: Statushantering
: Prestandaoptimering
section Avancerade tekniker (45 minuter)
Professionella funktioner: Kollisiondetektion
: Fysiksimulering
: Tillståndsmaskiner
: Komponentsystem
section Spelmotorbegrepp (1 vecka)
Ramverksförståelse: Entitets-komponentsystem
: Scengrafer
: Resurspipelines
: Prestandaprofilering
section Produktionskunskaper (1 månad)
Professionell utveckling: Kodorganisation
: Teamsamarbete
: Teststrategier
: Driftsättningsoptimering
🛠️ Din sammanfattning av spelutvecklingsverktyg
Efter att ha slutfört denna lektion har du nu behärskat:
- Animationsprinciper: Bildrörelse och mjuka övergångar
- Händelsestyrd programmering: Tangentbordsinmatning med korrekt eventhantering
- Objektorienterad design: Arvshierarkier och polymorfa gränssnitt
- Kommunikationsmönster: Pub/sub-arkitektur för underhållbar kod
- Spel-loop-arkitektur: Realtidsuppdatering och renderingscykler
- Inmatningssystem: Användarkontroll med standardbeteendeförhindran
- Resurshantering: Spriteinladdning och effektiv rendering
⚡ Vad du kan göra de närmaste 5 minuterna
- Öppna webbläsarens konsol och prova
addEventListener('keydown', console.log)för att se tangentbords-händelser - Skapa ett enkelt div-element och flytta det med piltangenter
- Experimentera med
setIntervalför kontinuerlig rörelse - Testa att förhindra standardbeteende med
event.preventDefault()
🎯 Vad du kan åstadkomma denna timme
- Klara quizet efter lektionen och förstå händelsestyrd programmering
- Bygga det rörliga hjälteskeppet med full tangentbordskontroll
- Implementera smidiga fienderörelsemönster
- Lägga till gränser för att förhindra att spelobjekt lämnar skärmen
- Skapa grundläggande krockdetektering mellan spelobjekt
📅 Din vecka med animation
- Slutföra hela rymdspelet med polerad rörelse och interaktioner
- Lägga till avancerade rörelsemönster som kurvor, acceleration och fysik
- Implementera mjuka övergångar och easing-funktioner
- Skapa partikeleffekter och visuella feedback-system
- Optimera spelets prestanda för mjuk 60fps
- Lägga till mobil touch-kontroller och responsiv design
🌟 Din månad med interaktiv utveckling
- Bygga komplexa interaktiva applikationer med avancerade animationssystem
- Lära dig animationsbibliotek som GSAP eller skapa din egen animationsmotor
- Bidra till open source projekt för spelutveckling och animation
- Bemästra prestandaoptimering för grafikintensiva applikationer
- Skapa utbildningsmaterial om spelutveckling och animation
- Bygga en portfolio som visar avancerad interaktiv programmering
Tillämpningar i verkliga världen: Dina animationskunskaper används direkt för:
- Interaktiva webbapplikationer: Dynamiska instrumentpaneler och realtidsgränssnitt
- Datavisualisering: Animerade diagram och interaktiva grafik
- Utbildningsprogramvara: Interaktiva simuleringar och lärverktyg
- Mobilutveckling: Touchbaserade spel och geststyrning
- Skrivbordsapplikationer: Electron-appar med smidig animation
- Webbanimationer: CSS- och JavaScript animationsbibliotek
Professionella färdigheter som uppnåtts: Du kan nu:
- Designa händelsestyrda system som skalar med komplexitet
- Implementera mjuka animationer med matematiska principer
- Felsöka komplexa interaktionssystem med webbläsarens utvecklarverktyg
- Optimera spels prestanda för olika enheter och webbläsare
- Designa underhållbara kodstrukturer med beprövade mönster
Behärskade koncept för spelutveckling:
- Bildfrekvenshantering: Förstå FPS och tidsstyrning
- Inputhantering: Plattformoberoende tangentbord och event-system
- Objektlivscykel: Skapande, uppdatering och destruktion
- Tillståndssynkronisering: Hålla spelstatus konsekvent över bildrutor
- Händelsearkitektur: Lös kopplad kommunikation mellan spelsystem
Nästa nivå: Du är redo att lägga till krockdetektering, poängsystem, ljudeffekter eller utforska moderna spelramverk som Phaser eller Three.js!
🌟 Uppdrag slutfört: Du har byggt ett komplett interaktivt spelsystem med professionella arkitekturmönster!
Uppgift
Ansvarsfriskrivning: Detta dokument har översatts med hjälp av AI-översättningstjänsten Co-op Translator. Även om vi strävar efter noggrannhet, vänligen observera att automatiska översättningar kan innehålla fel eller brister. Originaldokumentet på dess ursprungliga språk bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för några missförstånd eller feltolkningar som uppstår från användningen av denna översättning.