You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/da/6-space-game/3-moving-elements-around
localizeflow[bot] 4dc412aa0e
chore(i18n): sync translations with latest source changes (chunk 1/1, 291 changes)
7 months ago
..
README.md chore(i18n): sync translations with latest source changes (chunk 1/1, 291 changes) 7 months ago
assignment.md chore(i18n): sync translations with latest source changes (chunk 1/1, 291 changes) 7 months ago

README.md

Byg et rumspil del 3: Tilføj bevægelse

journey
    title Din Spilanimations Rejse
    section Grundlæggende Bevægelse
      Forstå bevægelsesprincipper: 3: Student
      Lær koordinatopdateringer: 4: Student
      Implementer grundlæggende bevægelse: 4: Student
    section Spillerkontroller
      Håndter tastaturhændelser: 4: Student
      Forhindre standardadfærd: 5: Student
      Opret responsive kontroller: 5: Student
    section Spilsystemer
      Byg spil-loop: 5: Student
      Administrer objekters livscyklus: 5: Student
      Implementer pub/sub-mønster: 5: Student

Tænk på dine yndlingsspil det, der gør dem fængslende, er ikke kun flotte grafik, men måden alt bevæger sig og reagerer på dine handlinger. Lige nu er dit rumspil som et smukt maleri, men vi er ved at tilføje bevægelse, som bringer det til live.

Da NASAs ingeniører programmerede vejledningscomputeren til Apollo-missionerne, stod de over for en lignende udfordring: hvordan får man et rumfartøj til at reagere på pilotens input, mens det automatisk opretholder kurskorrektioner? De principper, vi skal lære i dag, afspejler netop disse koncepter at styre spillerkontrolleret bevægelse sammen med automatiske systemadfærder.

I denne lektion lærer du, hvordan rumskibe svæver hen over skærmen, reagerer på spillerkommandoer og skaber glatte bevægelsesmønstre. Vi bryder det hele ned i håndterbare koncepter, der bygger naturligt på hinanden.

Når vi er færdige, vil spillere flyve deres helteskib rundt på skærmen, mens fjendtlige fartøjer patruljerer ovenover. Endnu vigtigere vil du forstå de grundlæggende principper, der styrer spils bevægelsessystemer.

mindmap
  root((Spilanimering))
    Movement Types
      Player Controlled
      Automatisk Bevægelse
      Fysikbaseret
      Scriptede Stier
    Event Handling
      Tastaturinput
      Mus Begivenheder
      Touch Kontroller
      Standard Forebyggelse
    Game Loop
      Opdater Logik
      Render Frame
      Ryd Lærred
      Billedhastighed Kontrol
    Object Management
      Positionsopdateringer
      Kollisionregistrering
      Livscyklusstyring
      Tilstandssporing
    Communication
      Pub/Sub Mønster
      Begivenhedsudsendere
      Beskedoverførsel
      Løs Kobling

Quiz før forelæsning

Quiz før forelæsning

Forståelse af spilbevægelse

Spil bliver levende, når ting begynder at bevæge sig rundt, og grundlæggende sker der to måder dette kan foregå på:

  • Spillerkontrolleret bevægelse: Når du trykker på en tast eller klikker med musen, bevæger noget sig. Dette er den direkte forbindelse mellem dig og din spilverden.
  • Automatisk bevægelse: Når spillet selv bestemmer at flytte ting som de fjendtlige skibe, der skal patruljere skærmen, uanset om du gør noget eller ej.

At få objekter til at bevæge sig på en computerskærm er enklere, end du måske tror. Kan du huske x- og y-koordinaterne fra matematik? Det er præcis det, vi arbejder med her. Da Galileo i 1610 fulgte Jupiters måner, gjorde han stort set det samme plotte positioner over tid for at forstå bevægelsesmønstre.

At flytte ting på skærmen er som at skabe en flipbook-animation du skal følge disse tre simple trin:

flowchart LR
    A["Frame N"] --> B["Opdater positioner"]
    B --> C["Ryd lærred"]
    C --> D["Tegn objekter"]
    D --> E["Frame N+1"]
    E --> F{Fortsæt?}
    F -->|Ja| B
    F -->|Nej| G["Spillet er slut"]
    
    subgraph "Animationscyklus"
        H["1. Beregn nye positioner"]
        I["2. Slet forrige frame"]
        J["3. Render nyt frame"]
    end
    
    style B fill:#e1f5fe
    style C fill:#ffebee
    style D fill:#e8f5e8
  1. Opdater positionen Ændr hvor objektet skal være (måske flyt det 5 pixels til højre)
  2. Slet den gamle frame Ryd skærmen, så du ikke ser spøgelsesagtige spor overalt
  3. Tegn den nye frame Placer dit objekt på dets nye sted

Gør dette hurtigt nok, og bum! Så har du glidende bevægelse, der føles naturlig for spillere.

Sådan kan det se ud i kode:

// Indstil heltenes placering
hero.x += 5;
// Ryd rektanglet, der huser helten
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Tegn spillets baggrund og helten igen
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);

Det her gør koden:

  • Opdaterer heltenes x-koordinat med 5 pixels for at flytte den horisontalt
  • Rydder hele canvasområdet for at fjerne den tidligere frame
  • Fylder canvas med en sort baggrundsfarve
  • Tegner igen heltebilledet på sin nye position

Kan du tænke på en grund til, at det at tegne din helt mange frames per sekund kan gå ud over ydeevnen? Læs om alternativer til dette mønster.

Håndtering af tastaturhændelser

Her forbinder vi spillerens input med spillets handling. Når nogen trykker på mellemrumstasten for at skyde en laser eller trykker på piletaster for at undvige en asteroide, skal dit spil registrere og reagere på det input.

Tastaturhændelser sker på vinduesniveau, hvilket betyder, at hele din browserløsning lytter efter disse tastetryk. Musklik kan til gengæld knyttes til specifikke elementer (som at klikke på en knap). Til vores rumspil fokuserer vi på tastaturkontroller, da det giver spillere en klassisk arkadefølelse.

Det minder mig om, hvordan telegrafister i 1800-tallet måtte oversætte morsekodeinput til meningsfulde beskeder vi gør noget lignende, ved at oversætte tastetryk til spilkommandoer.

For at håndtere en hændelse skal du bruge vinduets addEventListener()-metode og give den to parametre. Den første er navnet på hændelsen, for eksempel keyup. Den anden er den funktion, der skal kaldes, når hændelsen sker.

Her er et eksempel:

window.addEventListener('keyup', (evt) => {
  // evt.key = strengrepræsentation af tasten
  if (evt.key === 'ArrowUp') {
    // gør noget
  }
});

Dette sker her:

  • Lytter efter tastaturhændelser på hele vinduet
  • Fanger hændelsesobjektet, som indeholder info om hvilken tast, der blev trykket
  • Tjekker om tasten matcher en bestemt tast (i dette tilfælde pil op)
  • Udfører kode, når betingelsen er opfyldt

For tastaturhændelser kan du bruge to egenskaber på hændelsen for at se, hvilken tast der blev trykket:

  • key - en strengrepræsentation af tasten, f.eks. 'ArrowUp'
  • keyCode - et nummer, f.eks. 37, som svarer til ArrowLeft

Tastaturhåndtering er nyttigt uden for spildesign. Hvilke andre anvendelser kan du tænke dig til denne teknik?

sequenceDiagram
    participant User
    participant Browser
    participant EventSystem
    participant GameLogic
    participant Hero
    
    User->>Browser: Trykker på PilOp-tasten
    Browser->>EventSystem: keydown-hændelse
    EventSystem->>EventSystem: preventDefault()
    EventSystem->>GameLogic: emit('KEY_EVENT_UP')
    GameLogic->>Hero: hero.y -= 5
    Hero->>Hero: Opdater position
    
    Note over Browser,GameLogic: Hændelsesflow forhindrer browser standarder
    Note over GameLogic,Hero: Pub/sub-mønster muliggør ren kommunikation

Særlige taster: en advarsel!

Nogle taster har indbyggede browserfunktioner, der kan forstyrre dit spil. Piletaster ruller siden, og mellemrumstasten hopper ned adfærder du ikke ønsker, når nogen forsøger at styre deres rumskib.

Vi kan forhindre disse standardadfærder og lade vores spil håndtere inputtet i stedet. Det ligner, hvordan tidlige computerprogrammører måtte tilsidesætte systemafbrydelser for at skabe brugerdefineret adfærd vi gør det bare på browserniveau. Sådan:

const onKeyDown = function (e) {
  console.log(e.keyCode);
  switch (e.keyCode) {
    case 37:
    case 39:
    case 38:
    case 40: // Piletaster
    case 32:
      e.preventDefault();
      break; // Mellemrum
    default:
      break; // blokerer ikke andre taster
  }
};

window.addEventListener('keydown', onKeyDown);

Hvad denne blok gør:

  • Tjekker for specifikke tastkoder, som kan skabe uønsket browseradfærd
  • Forhindrer browserens standardaktion for piletaster og mellemrumstasten
  • Lader andre taster fungere normalt
  • Bruger e.preventDefault() til at stoppe browserens indbyggede adfærd

🔄 Pædagogisk status

Forståelse af hændelseshåndtering: Før vi går videre til automatisk bevægelse, sørg for at du kan:

  • Forklare forskellen på keydown og keyup hændelser
  • Forstå hvorfor vi forhindrer standard browserhandlinger
  • Beskrive hvordan event listeners forbinder brugerinput med spilogik
  • Identificere hvilke taster der kan forstyrre spilkontroller

Hurtig selvtest: Hvad ville ske, hvis du ikke forhindrede standardadfærden for piletaster?
Svar: Browseren ville rulle siden, hvilket ville forstyrre spillets bevægelse

Arkitektur for hændelsessystem: Du forstår nu:

  • Lytning på vinduesniveau: Fangst af hændelser på browserniveau
  • Hændelsesobjektets egenskaber: key strenge versus keyCode tal
  • Forhindring af standard: Stoppe uønskede browseradfærd
  • Betinget logik: Reagere på specifikke tastkombinationer

Spilinduceret bevægelse

Lad os nu tale om objekter, der bevæger sig uden spillerinput. Tænk på fjendtlige skibe, der cruiser hen over skærmen, kugler, der flyver ligeud, eller skyer, der driver i baggrunden. Denne autonome bevægelse får din spilverden til at føles levende, selv når ingen rører ved kontrollerne.

Vi bruger JavaScripts indbyggede timere til at opdatere positioner med jævne mellemrum. Dette koncept er lig pendulure en regelmæssig mekanisme, som udløser konsistente, tidsbestemte handlinger. Sådan kan det se ud i simpel form:

const id = setInterval(() => {
  // Flyt fjenden på y-aksen
  enemy.y += 10;
}, 100);

Denne bevægelseskode gør:

  • Opretter en timer, der kører hver 100 millisekund
  • Opdaterer fjendens y-koordinat med 10 pixels hver gang
  • Gemmer interval-ID'et, så vi kan stoppe det senere, hvis nødvendigt
  • Flytter fjenden automatisk nedad på skærmen

Spil-loopen

Her er konceptet, der binder det hele sammen spil-loopen. Hvis dit spil var en film, ville spil-loopen være filmprojektoren, der viser frame efter frame så hurtigt, at alt ser ud til at bevæge sig glat.

Alle spil har sådan en loop, der kører i baggrunden. Det er en funktion, der opdaterer alle spilelementer, tegner skærmen igen og gentager denne proces kontinuerligt. Den holder styr på din helt, alle fjenderne, eventuelle lasere hele spillets tilstand.

Dette minder mig om, hvordan tidlige filmtegnere som Walt Disney måtte tegne figurer på ny frame for frame for at skabe bevægelsesillusionen. Vi gør det samme, bare med kode i stedet for blyanter.

Sådan kan en spil-loop typisk se ud, i kodeeksempel:

flowchart TD
    A["Start Spilsløkke"] --> B["Ryd Lærred"]
    B --> C["Fyld Baggrund"]
    C --> D["Opdater Spilobjekter"]
    D --> E["Tegn Helt"]
    E --> F["Tegn Fjender"]
    F --> G["Tegn UI-elementer"]
    G --> H["Vent på Næste Frame"]
    H --> I{Spil Kører?}
    I -->|Ja| B
    I -->|Nej| J["Afslut Spil"]
    
    subgraph "Billedhastighedskontrol"
        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);

Forståelse af spil-loop-strukturen:

  • Rydder hele canvas for at fjerne den forrige frame
  • Fylder baggrunden med en ensfarvet farve
  • Tegner alle spilelementer på deres aktuelle positioner
  • Gentager denne proces hver 200 millisekund for glat animation
  • Styrer billedhastigheden ved at kontrollere intervallets timing

Fortsættelse af rumspillet

Nu tilføjer vi bevægelse til den statiske scene, du byggede tidligere. Vi vil transformere den fra et screenshot til en interaktiv oplevelse. Vi arbejder igennem dette trin for trin, så hvert element bygger på det forrige.

Tag koden derfra, hvor vi slap i den tidligere lektion (eller start med koden i Part II- starter-mappen, hvis du har brug for en frisk start).

Det vi bygger i dag:

  • Heltekontroller: Piletasterne styrer dit rumskib rundt på skærmen
  • Fjendebevægelse: De fremmede skibe begynder deres fremmarch

Lad os begynde at implementere disse funktioner.

Anbefalede trin

Find filerne, som er blevet oprettet til dig i your-work undermappen. Den burde indeholde følgende:

-| assets
  -| enemyShip.png
  -| player.png
-| index.html
-| app.js
-| package.json

Du starter dit projekt i your-work mappen ved at skrive:

cd your-work
npm start

Denne kommando gør:

  • Navigerer til din projektmappe
  • Starter en HTTP-server på adressen http://localhost:5000
  • Serverer dine spildfiler så du kan teste dem i en browser

Ovenstående starter en HTTP-server på adressen http://localhost:5000. Åbn en browser og indtast adressen; lige nu skulle du kunne se helten og alle fjenderne; ingenting bevæger sig endnu!

Tilføj kode

  1. Tilføj dedikerede objekter for hero, enemy og game object; de skal have x og y egenskaber. (Husk delen om Arv eller sammensætning).

    TIP game object skal være den med x og y og evnen til at tegne sig selv på et canvas.

    Tip: Start med at tilføje en ny GameObject klasse med dens konstruktør som vist herunder, og tegn den derefter 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);
      }
    }
    

    Forståelse af denne baseklasse:

    • Definerer fælles egenskaber, som alle spilelementer deler (position, størrelse, billede)
    • Inkluderer et dead-flag til at spore, om objektet skal fjernes
    • Tilbyder en draw()-metode, der tegner objektet på canvas
    • Sætter standardværdier for alle egenskaber, som underklasser kan overskrive
classDiagram
    class GameObject {
        +x: number
        +y: number
        +død: boolean
        +type: string
        +bredde: number
        +højde: number
        +img: Image
        +tegn(ctx)
    }
    
    class Hero {
        +hastighed: number
        +type: "Hero"
        +bredde: 98
        +højde: 75
    }
    
    class Enemy {
        +type: "Enemy"
        +bredde: 98
        +højde: 50
        +setInterval()
    }
    
    GameObject <|-- Hero
    GameObject <|-- Enemy
    
    class EventEmitter {
        +lyttere: object
        +på(besked, lytter)
        +udsend(besked, nyttelast)
    }
Udvid nu `GameObject` for at skabe `Hero` og `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);
  }
}
```

**Nøglekoncepter i disse klasser:**  
- **Arver** fra `GameObject` med `extends` nøgleordet  
- **Kalder** forældrekonstruktøren med `super(x, y)`  
- **Sætter** specifikke dimensioner og egenskaber for hver objekttype  
- **Implementerer** automatisk bevægelse for fjender med `setInterval()`  
  1. Tilføj tastaturhåndteringsfunktioner til at flytte helten op/ned, til venstre/højre

    HUSK det er et kartesisk system, øverst til venstre er 0,0. Husk også at tilføje kode, der stopper standardadfærden

    Tip: Opret din onKeyDown funktion og bind den til vinduet:

    const onKeyDown = function (e) {
      console.log(e.keyCode);
      // Tilføj koden fra lektionen ovenfor for at stoppe standardadfærd
      switch (e.keyCode) {
        case 37:
        case 39:
        case 38:
        case 40: // Piletaster
        case 32:
          e.preventDefault();
          break; // Mellemrum
        default:
          break; // bloker ikke andre taster
      }
    };
    
    window.addEventListener("keydown", onKeyDown);
    

    Det denne hændelsesfunktion gør:

    • Lytter efter keydown-hændelser på hele vinduet
    • Logger tastkoden til fejlsøgning for at se, hvilke taster der trykkes
    • Forhindrer standardbrowseradfærd for piletaster og mellemrumstasten
    • Lader andre taster fungere normalt

    Tjek din browserkonsol nu, og se tastetrykkene blive logget.

  2. Implementer Pub sub mønstret, som holder din kode ren, mens du følger de sidste dele.

    Publish-Subscribe-mønstret hjælper med at organisere din kode ved at adskille hændelsesdetektion fra hændelseshåndtering. Det gør din kode mere modulær og lettere at vedligeholde.

    For at gøre denne sidste del kan du:

    1. Tilføj en event listener på vinduet:

      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 dette hændelsessystem gør:

    • Registrerer tastaturinput og omsætter det til egne spil-hændelser
    • Adskiller inputregistrering fra spilogik
    • Gør det nemt at ændre kontroller uden at påvirke spillets kode
    • Lader flere systemer reagere på samme input
flowchart TD
    A["Tastatur Input"] --> B["Vindues Begivenheds Lytter"]
    B --> C["Begivenheds Udsender"]
    C --> D["TAST_BE_GIVEN_OP"]
    C --> E["TAST_BE_GIVEN_NED"]
    C --> F["TAST_BE_GIVEN_VENSTRE"]
    C --> G["TAST_BE_GIVEN_HØJRE"]
    
    D --> H["Helte Bevægelse"]
    D --> I["Lyd System"]
    D --> J["Visuelle Effekter"]
    
    E --> H
    F --> H
    G --> H
    
    style A fill:#e1f5fe
    style C fill:#e8f5e8
    style H fill:#fff3e0
  1. Opret en EventEmitter-klasse til at publicere og abonnere på beskeder:

    class EventEmitter {
      constructor() {
        this.listeners = {};
      }
    
      on(message, listener) {
        if (!this.listeners[message]) {
          this.listeners[message] = [];
        }
        this.listeners[message].push(listener);
      }
    
    
    
  2. Tilføj konstanter og sæt EventEmitter op:

    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();
    

Forståelse af opsætningen:

  • Definerer beskedkonstanter for at undgå stavefejl og gøre refaktorering lettere
  • Deklarerer variabler for billeder, canvas kontekst og spiltilstand
  • Opretter en global event emitter til pub-sub systemet
  • Initialiserer et array til at holde alle spilobjekter
  1. Initialiser spillet

    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;
      });
    
    
  2. Opsæt spilloopen

    Refaktorer window.onload funktionen for at initialisere spillet og sætte en spilloop op med en god interval. Du vil også tilføje 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);
    };
    

    Forståelse af spilsætup:

    • Venter på, at siden er fuldt indlæst før start
    • Henter canvas-elementet og dets 2D-rendering kontekst
    • Loader alle billede-assets asynkront ved brug af await
    • Starter spilloopen, der kører med 100ms intervaller (10 FPS)
    • Rydder og tegner hele skærmen om hver frame
  3. Tilføj kode for at flytte fjender med et bestemt interval

    Refaktorer createEnemies() funktionen for at skabe fjenderne og skubbe dem ind i den nye gameObjects klasse:

    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);
        }
      }
    }
    

    Hvad fjendeskabelsen gør:

    • Beregner positioner for at centrere fjenderne på skærmen
    • Skaber et grid af fjender ved hjælp af nestede loops
    • Tildeler fjendebilledet til hvert fjendeobjekt
    • Tilføjer hver fjende til det globale spilobjekter-array

    og tilføj en createHero() funktion til at gøre en lignende proces for helten.

    function createHero() {
      hero = new Hero(
        canvas.width / 2 - 45,
        canvas.height - canvas.height / 4
      );
      hero.img = heroImg;
      gameObjects.push(hero);
    }
    

    Hvad helteskabelsen gør:

    • Positionerer helten i bunden midt på skærmen
    • Tildeler heltebilledet til helteobjektet
    • Tilføjer helten til spilobjekts-arrayet til rendering

    og til sidst tilføj en drawGameObjects() funktion for at starte tegningen:

    function drawGameObjects(ctx) {
      gameObjects.forEach(go => go.draw(ctx));
    }
    

    Forståelse af tegnefunktionen:

    • Itererer gennem alle spilobjekter i arrayet
    • Kalder draw() metoden på hvert objekt
    • Sender canvas-konteksten, så objekter kan rendere sig selv

    🔄 Pædagogisk status

    Fuld forståelse af spilsystemet: Bekræft din forståelse af hele arkitekturen:

    • Hvordan tillader arv at Hero og Enemy deler fælles GameObject egenskaber?
    • Hvorfor gør pub/sub-mønstret din kode mere vedligeholdelsesvenlig?
    • Hvilken rolle spiller spilloopen i at skabe glidende animation?
    • Hvordan forbinder event listeners brugerinput med spilobjekternes adfærd?

    Systemintegration: Dit spil demonstrerer nu:

    • Objektorienteret design: Basisklasser med specialiseret arv
    • Event-drevet arkitektur: Pub/sub-mønster for løs kobling
    • Animationsrammeværk: Spilloop med konsistente frame-opdateringer
    • Inputhåndtering: Tastatur-events med standardforbyggelse
    • Asset management: Billedindlæsning og sprite rendering

    Professionelle mønstre: Du har implementeret:

    • Adskillelse af ansvar: Input, logik og rendering adskilt
    • Polymorfi: Alle spilobjekter deler fælles tegneinterface
    • Beskedoverførsel: Ren kommunikation mellem komponenter
    • Ressourcestyring: Effektiv sprite- og animationshåndtering

    Dine fjender skulle nu begynde at rykke frem mod dit helteskib! } }

    
    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);
    }
    

    og til sidst tilføj en drawGameObjects() funktion for at starte tegningen:

    function drawGameObjects(ctx) {
      gameObjects.forEach(go => go.draw(ctx));
    }
    

    Dine fjender skulle nu begynde at rykke frem mod dit helteskib!


GitHub Copilot Agent Challenge 🚀

Her er en udfordring, der vil forbedre dit spils polering: at tilføje grænser og glidende styring. Lige nu kan din helt flyve ud af skærmen, og bevægelsen kan føles hakkende.

Din mission: Lad dit rumskib føles mere realistisk ved at implementere skærmgrænser og flydende bevægelse. Det er ligesom hvordan NASAs flyvestyringssystemer forhindrer rumfartøjer i at overskride sikre driftsparametre.

Dette skal du bygge: Skab et system, der holder dit helterumskib på skærmen, og gør kontrollerne glatte. Når spillere holder en pil nede, skal skibet glide kontinuerligt i stedet for at bevæge sig i diskrete trin. Overvej at tilføje visuel feedback, når skibet rammer skærmgrænserne måske en subtil effekt, der indikerer spilleområdets kant.

Læs mere om agent mode her.

🚀 Udfordring

Kodeorganisation bliver mere og mere vigtigt, efterhånden som projekter vokser. Du har måske lagt mærke til, at din fil bliver overfyldt med funktioner, variabler og klasser, der alle er blandet sammen. Det minder mig om, hvordan ingeniørerne, der organiserede Apollo-missionens kode, måtte skabe klare, vedligeholdelsesvenlige systemer, som flere teams kunne arbejde på samtidigt.

Din mission: Tænk som en softwarearkitekt. Hvordan ville du organisere din kode, så du om seks måneder, eller en kollega, kunne forstå, hvad der foregår? Selv hvis alt forbliver i én fil for nu, kan du skabe bedre organisering:

  • Grupper relaterede funktioner sammen med klare kommentaroverskrifter
  • Adskil ansvarsområder - hold spilslogik adskilt fra rendering
  • Brug konsekvente navngivningskonventioner for variabler og funktioner
  • Skab moduler eller namespaces for at organisere forskellige aspekter af dit spil
  • Tilføj dokumentation som forklarer formålet med hver større sektion

Refleksionsspørgsmål:

  • Hvilke dele af din kode er sværest at forstå, når du vender tilbage til dem?
  • Hvordan kunne du organisere din kode, så det bliver nemmere for en anden at bidrage?
  • Hvad ville der ske, hvis du ville tilføje nye funktioner som power-ups eller forskellige fjendetyper?

Post-Lecture Quiz

Post-lecture quiz

Gennemgang & Selvstudie

Vi har bygget alt fra bunden, hvilket er fantastisk til læring, men her er en lille hemmelighed der findes nogle fantastiske JavaScript frameworks, som kan håndtere meget af det tunge arbejde for dig. Når du føler dig tryg ved de fundamentale ting, vi har dækket, er det værd at udforske, hvad der findes.

Tænk på frameworks som at have en veludstyret værktøjskasse i stedet for at lave hvert værktøj i hånden. De kan løse mange af de kodeorganiseringsudfordringer, vi har talt om, plus tilbyde funktioner, der ville tage uger at bygge selv.

Ting værd at udforske:

  • Hvordan spilengines organiserer kode du vil blive imponeret over de smarte mønstre, de bruger
  • Performance tricks til at få canvas-spil til at køre helt glat
  • Moderne JavaScript-funktioner, der kan gøre din kode renere og mere vedligeholdelsesvenlig
  • Forskellige tilgange til at styre spilobjekter og deres relationer

🎯 Din tidslinje for mestring af spilanimation

timeline
    title Spilanimation og Interaktions Læringsforløb
    
    section Bevægelsesgrundlag (20 minutter)
        Animationsprincipper: Billedbaseret animation
                            : Positionsopdateringer
                            : Koordinatsystemer
                            : Flydende bevægelse
        
    section Begivenhedssystemer (25 minutter)
        Brugerinput: Tastaturhåndtering af begivenheder
                  : Forebyggelse af standardadfærd
                  : Egenskaber ved begivenhedsobjekter
                  : Vinduesniveau-lytning
        
    section Spilarkitektur (30 minutter)
        Objekt Design: Arvemønstre
                     : Oprettelse af baseklasse
                     : Specialiserede adfærd
                     : Polymorfe interfaces
        
    section Kommunikationsmønstre (35 minutter)
        Pub/Sub Implementering: Begivenhedsudsendere
                              : Meddelelseskonstanter
                              : Løs kobling
                              : Systemintegration
        
    section Mestering af Spil Loop (40 minutter)
        Realtidsystemer: Kontrol af billedhastighed
                         : Opdaterings-/renderingscyklus
                         : Tilstandsadministration
                         : Ydeevneoptimering
        
    section Avancerede teknikker (45 minutter)
        Professionelle funktioner: Kollisionsdetektion
                             : Fysiksimulation
                             : Tilstandsmaskiner
                             : Komponent systemer
        
    section Spilmotor Koncepter (1 uge)
        Forståelse af Framework: Entity-komponent systemer
                               : Scene grafer
                               : Asset pipelines
                               : Ydeevneprofilering
        
    section Produktionsfærdigheder (1 måned)
        Professionel udvikling: Kodeorganisering
                                : Teamsamarbejde
                                : Teststrategier
                                : Implementeringsoptimering

🛠️ Opsummering af dit spiludviklingsværktøj

Efter at have gennemført denne lektion har du nu mestret:

  • Animationsprincipper: Frame-baseret bevægelse og glidende overgange
  • Event-drevet programmering: Tastaturinputhåndtering med korrekt eventstyring
  • Objektorienteret design: Arvshierarkier og polymorfe interfaces
  • Kommunikationsmønstre: Pub/sub arkitektur for vedligeholdelsesvenlig kode
  • Spilloop-arkitektur: Real-time opdaterings- og renderingscyklusser
  • Inputsystemer: Brugerkontroller med standardadfærdsforebyggelse
  • Asset management: Sprite-indlæsning og effektiv renderingsteknik

Hvad du kan gøre de næste 5 minutter

  • Åbn browserkonsollen og prøv addEventListener('keydown', console.log) for at se tastaturevents
  • Opret et simpelt div-element og flyt det rundt med piletaster
  • Eksperimenter med setInterval for at skabe kontinuerlig bevægelse
  • Prøv at forhindre standardadfærd med event.preventDefault()

🎯 Hvad du kan opnå denne time

  • Gennemfør post-lecture quizzen og forstå event-drevet programmering
  • Byg det bevægelige helterumskib med fuld tastaturstyring
  • Implementer glidende fjendebevægelsesmønstre
  • Tilføj grænser for at forhindre, at spilobjekter forlader skærmen
  • Skab grundlæggende kollisionsdetektion mellem spilobjekter

📅 Din ugelange animationsrejse

  • Færdiggør det fulde rumspil med polerede bevægelser og interaktioner
  • Tilføj avancerede bevægelsesmønstre som kurver, acceleration og fysik
  • Implementer glidende overgange og easing-funktioner
  • Skab partikeleffekter og visuelle feedback-systemer
  • Optimer spillets ydeevne til glat 60fps gameplay
  • Tilføj mobil touch-kontroller og responsivt design

🌟 Din månedslange interaktive udvikling

  • Byg komplekse interaktive applikationer med avancerede animationssystemer
  • Lær animationsbiblioteker som GSAP eller skab din egen animationsmotor
  • Bidrag til open source spiludvikling og animationsprojekter
  • Mestre performanceoptimering for grafikintensive applikationer
  • Skab undervisningsindhold om spiludvikling og animation
  • Byg en portfolio, der viser avancerede interaktive programmeringsfærdigheder

Anvendelser i den virkelige verden: Dine spilanimationsfærdigheder kan direkte bruges til:

  • Interaktive webapplikationer: Dynamiske dashboards og realtidsgrænseflader
  • Datavisualisering: Animerede diagrammer og interaktive grafik
  • Undervisningssoftware: Interaktive simulationer og læringsværktøjer
  • Mobiludvikling: Touch-baserede spil og gestushåndtering
  • Desktop-applikationer: Electron-apps med glidende animationer
  • Webanimationer: CSS og JavaScript animationsbiblioteker

Professionelle færdigheder erhvervet: Du kan nu:

  • Designe event-drevne systemer, der skalerer med kompleksitet
  • Implementere glidende animationer med matematiske principper
  • Fejlsøge komplekse interaktionssystemer med browserudviklerværktøjer
  • Optimere spilperformance til forskellige enheder og browsere
  • Designe vedligeholdelsesvenlige kodestrukturer med gennemprøvede mønstre

Spiludviklingskoncepter mestret:

  • Frame rate styring: Forståelse af FPS og timingkontrol
  • Inputhåndtering: Tværsplatform tastatur- og eventsystemer
  • Objektlivscyklus: Skabelse, opdatering og destruering
  • State-synkronisering: Holde spiltilstanden konsistent på tværs af frames
  • Event-arkitektur: Løst koblet kommunikation mellem spilsystemer

Næste niveau: Du er klar til at tilføje kollisionsdetektion, scoringssystemer, lydeffekter eller udforske moderne spilframeworks som Phaser eller Three.js!

🌟 Præstation opnået: Du har bygget et komplet interaktivt spilsystem med professionelle arkitekturmønstre!

Opgave

Kommenter din kode


Ansvarsfraskrivelse:
Dette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi bestræber os på nøjagtighed, bedes du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det oprindelige dokument på dets modersmål bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi påtager os intet ansvar for misforståelser eller fejltolkninger, der måtte opstå som følge af brugen af denne oversættelse.