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/cs/6-space-game/3-moving-elements-around/README.md

14 KiB

Vytvoření vesmírné hry, část 3: Přidání pohybu

Kvíz před lekcí

Kvíz před lekcí

Hry nejsou moc zábavné, dokud se na obrazovce nezačnou pohybovat mimozemšťané! V této hře využijeme dva typy pohybu:

  • Pohyb pomocí klávesnice/myši: když uživatel interaguje s klávesnicí nebo myší, aby pohyboval objektem na obrazovce.
  • Pohyb řízený hrou: když hra pohybuje objektem v určitých časových intervalech.

Jak tedy pohybujeme objekty na obrazovce? Vše je o kartézských souřadnicích: změníme polohu objektu (x, y) a poté obrazovku překreslíme.

Obvykle potřebujete následující kroky, abyste dosáhli pohybu na obrazovce:

  1. Nastavte novou polohu objektu; to je nutné, aby se zdálo, že se objekt pohybuje.
  2. Vymažte obrazovku, obrazovka musí být mezi jednotlivými vykresleními vymazána. Toho dosáhneme vykreslením obdélníku, který vyplníme barvou pozadí.
  3. Znovu vykreslete objekt na nové poloze. Tímto způsobem nakonec dosáhneme pohybu objektu z jednoho místa na druhé.

Takto to může vypadat v kódu:

//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);

Dokážete si představit, proč by překreslování vašeho hrdiny mnohokrát za sekundu mohlo způsobit výkonové problémy? Přečtěte si o alternativách k tomuto vzoru.

Zpracování událostí klávesnice

Události zpracováváte připojením konkrétních událostí ke kódu. Události klávesnice se spouštějí na celém okně, zatímco události myši, jako je click, mohou být spojeny s kliknutím na konkrétní prvek. V tomto projektu budeme používat události klávesnice.

Pro zpracování události musíte použít metodu addEventListener() okna a poskytnout jí dva vstupní parametry. Prvním parametrem je název události, například keyup. Druhým parametrem je funkce, která by měla být vyvolána v důsledku události.

Zde je příklad:

window.addEventListener('keyup', (evt) => {
  // `evt.key` = string representation of the key
  if (evt.key === 'ArrowUp') {
    // do something
  }
})

Pro události kláves existují dvě vlastnosti události, které můžete použít k určení, která klávesa byla stisknuta:

  • key, což je textová reprezentace stisknuté klávesy, například ArrowUp
  • keyCode, což je číselná reprezentace, například 37, odpovídá ArrowLeft.

Manipulace s událostmi kláves je užitečná i mimo vývoj her. Jaké další využití této techniky vás napadají?

Speciální klávesy: upozornění

Existují některé speciální klávesy, které ovlivňují okno. To znamená, že pokud posloucháte událost keyup a použijete tyto speciální klávesy k pohybu svého hrdiny, dojde také k horizontálnímu posouvání. Z tohoto důvodu možná budete chtít vypnout toto vestavěné chování prohlížeče při vytváření své hry. Potřebujete kód jako tento:

let onKeyDown = function (e) {
  console.log(e.keyCode);
  switch (e.keyCode) {
    case 37:
    case 39:
    case 38:
    case 40: // Arrow keys
    case 32:
      e.preventDefault();
      break; // Space
    default:
      break; // do not block other keys
  }
};

window.addEventListener('keydown', onKeyDown);

Výše uvedený kód zajistí, že šipky a mezerník budou mít své výchozí chování vypnuté. Mechanismus vypnutí nastane, když zavoláme e.preventDefault().

Pohyb řízený hrou

Objekty se mohou pohybovat samy pomocí časovačů, jako jsou funkce setTimeout() nebo setInterval(), které aktualizují polohu objektu při každém tiknutí nebo časovém intervalu. Takto to může vypadat:

let id = setInterval(() => {
  //move the enemy on the y axis
  enemy.y += 10;
})

Herní smyčka

Herní smyčka je koncept, který v podstatě představuje funkci vyvolávanou v pravidelných intervalech. Říká se jí herní smyčka, protože vše, co by mělo být viditelné pro uživatele, je vykresleno v této smyčce. Herní smyčka využívá všechny herní objekty, které jsou součástí hry, a vykresluje je, pokud z nějakého důvodu již nejsou součástí hry. Například pokud je objekt nepřítel, který byl zasažen laserem a exploduje, již není součástí aktuální herní smyčky (o tom se dozvíte více v dalších lekcích).

Takto může typická herní smyčka vypadat v kódu:

let 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();
}, 200);

Výše uvedená smyčka je vyvolávána každých 200 milisekund, aby překreslila plátno. Máte možnost zvolit nejlepší interval, který dává smysl pro vaši hru.

Pokračování ve vesmírné hře

Vezmete existující kód a rozšíříte ho. Buď začněte s kódem, který jste dokončili během části I, nebo použijte kód z části II - startér.

  • Pohyb hrdiny: přidáte kód, který zajistí, že můžete pohybovat hrdinou pomocí šipek.
  • Pohyb nepřátel: budete také muset přidat kód, který zajistí, že se nepřátelé pohybují shora dolů v daném tempu.

Doporučené kroky

Najděte soubory, které byly vytvořeny pro vás ve složce your-work. Měly by obsahovat následující:

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

Spusťte svůj projekt ve složce your_work zadáním:

cd your-work
npm start

Výše uvedené spustí HTTP server na adrese http://localhost:5000. Otevřete prohlížeč a zadejte tuto adresu, aktuálně by měl zobrazit hrdinu a všechny nepřátele; zatím se nic nehýbe!

Přidání kódu

  1. Přidejte dedikované objekty pro hero, enemy a game object, měly by mít vlastnosti x a y. (Pamatujte na část o dědičnosti nebo kompozici).

    TIP: Začněte přidáním nové třídy GameObject s jejím konstruktorem, jak je uvedeno níže, a poté ji vykreslete na plátno:

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

    Nyní rozšiřte tento GameObject a vytvořte Hero a Enemy.

    class Hero extends GameObject {
      constructor(x, y) {
        ...it needs an x, y, type, and speed
      }
    }
    
    class Enemy extends GameObject {
      constructor(x, y) {
        super(x, y);
        (this.width = 98), (this.height = 50);
        this.type = "Enemy";
        let id = setInterval(() => {
          if (this.y < canvas.height - this.height) {
            this.y += 5;
          } else {
            console.log('Stopped at', this.y)
            clearInterval(id);
          }
        }, 300)
      }
    }
    
  2. Přidejte obslužné funkce událostí kláves pro navigaci (pohyb hrdiny nahoru/dolů, vlevo/vpravo).

    PAMATUJTE: Jde o kartézský systém, levý horní roh je 0,0. Také nezapomeňte přidat kód pro zastavení výchozího chování.

    tip: vytvořte svou funkci onKeyDown a připojte ji k oknu:

     let onKeyDown = function (e) {
           console.log(e.keyCode);
             ...add the code from the lesson above to stop default behavior
           }
     };
    
     window.addEventListener("keydown", onKeyDown);
    

    Zkontrolujte konzoli prohlížeče a sledujte, jak se zaznamenávají stisky kláves.

  3. Implementujte Pub-sub vzor, který udrží váš kód čistý, jak budete pokračovat v dalších částech.

    Pro tuto poslední část můžete:

    1. Přidat posluchač událostí na okno:

       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);
         }
       });
      
    2. Vytvořit třídu EventEmitter pro publikování a odběr zpráv:

      class EventEmitter {
        constructor() {
          this.listeners = {};
        }
      
        on(message, listener) {
          if (!this.listeners[message]) {
            this.listeners[message] = [];
          }
          this.listeners[message].push(listener);
        }
      
        emit(message, payload = null) {
          if (this.listeners[message]) {
            this.listeners[message].forEach((l) => l(message, payload));
          }
        }
      }
      
    3. Přidat konstanty a nastavit 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();
      
    4. Inicializovat hru

    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;
      });
    
      eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
        hero.x += 5;
      });
    }
    
  4. Nastavte herní smyčku

    Refaktorujte funkci window.onload tak, aby inicializovala hru a nastavila herní smyčku na vhodný interval. Přidáte také laserový paprsek:

    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();
      let gameLoopId = setInterval(() => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        drawGameObjects(ctx);
      }, 100)
    
    };
    
  5. Přidejte kód pro pohyb nepřátel v určitém intervalu.

    Refaktorujte funkci createEnemies(), aby vytvořila nepřátele a přidala je do nové třídy gameObjects:

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

    a přidejte funkci createHero(), která provede podobný proces pro hrdinu.

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

    a nakonec přidejte funkci drawGameObjects(), která zahájí vykreslování:

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

    Vaši nepřátelé by měli začít postupovat směrem k vaší vesmírné lodi!


🚀 Výzva

Jak vidíte, váš kód se může stát „špagetovým kódem“, když začnete přidávat funkce, proměnné a třídy. Jak můžete lépe organizovat svůj kód, aby byl čitelnější? Navrhněte systém pro organizaci svého kódu, i když stále zůstává v jednom souboru.

Kvíz po lekci

Kvíz po lekci

Recenze a samostudium

I když píšeme naši hru bez použití frameworků, existuje mnoho JavaScriptových frameworků pro vývoj her na plátně. Věnujte nějaký čas čtení o těchto.

Zadání

Okomentujte svůj kód


Prohlášení:
Tento dokument byl přeložen pomocí služby pro automatický překlad Co-op Translator. I když se snažíme o co největší přesnost, mějte prosím na paměti, že automatické překlady mohou obsahovat chyby nebo nepřesnosti. Za autoritativní zdroj by měl být považován původní dokument v jeho původním jazyce. Pro důležité informace doporučujeme profesionální lidský překlad. Neodpovídáme za žádná nedorozumění nebo nesprávné výklady vyplývající z použití tohoto překladu.