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/pl/6-space-game/3-moving-elements-around
Lee Stott 2daab5271b
Update Quiz Link
3 weeks ago
..
README.md Update Quiz Link 3 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 4 weeks ago

README.md

Budowanie gry kosmicznej, część 3: Dodawanie ruchu

Quiz przed wykładem

Quiz przed wykładem

Gry stają się ciekawsze, gdy na ekranie pojawiają się poruszające się obiekty, takie jak kosmici! W tej grze wykorzystamy dwa rodzaje ruchu:

  • Ruch za pomocą klawiatury/myszy: gdy użytkownik używa klawiatury lub myszy, aby poruszać obiektem na ekranie.
  • Ruch generowany przez grę: gdy gra automatycznie porusza obiektem w określonych odstępach czasu.

Jak więc poruszać obiektami na ekranie? Wszystko opiera się na współrzędnych kartezjańskich: zmieniamy lokalizację obiektu (x, y), a następnie odświeżamy ekran.

Typowe kroki, aby osiągnąć ruch na ekranie, to:

  1. Ustawienie nowej lokalizacji obiektu; jest to konieczne, aby użytkownik mógł zauważyć, że obiekt się poruszył.
  2. Wyczyszczenie ekranu, ekran musi być wyczyszczony między kolejnymi rysowaniami. Możemy to zrobić, rysując prostokąt wypełniony kolorem tła.
  3. Ponowne narysowanie obiektu w nowej lokalizacji. Dzięki temu osiągamy efekt przesunięcia obiektu z jednego miejsca na drugie.

Oto jak może wyglądać kod:

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

Czy potrafisz wymyślić powód, dla którego wielokrotne rysowanie bohatera w wielu klatkach na sekundę może powodować problemy z wydajnością? Przeczytaj o alternatywach dla tego wzorca.

Obsługa zdarzeń klawiatury

Zdarzenia obsługujemy, przypisując określone zdarzenia do kodu. Zdarzenia klawiatury są wywoływane na całym oknie, podczas gdy zdarzenia myszy, takie jak click, mogą być powiązane z kliknięciem konkretnego elementu. W tym projekcie będziemy korzystać ze zdarzeń klawiatury.

Aby obsłużyć zdarzenie, należy użyć metody addEventListener() okna i podać jej dwa parametry wejściowe. Pierwszym parametrem jest nazwa zdarzenia, na przykład keyup. Drugim parametrem jest funkcja, która ma zostać wywołana w wyniku wystąpienia zdarzenia.

Oto przykład:

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

Dla zdarzeń klawiatury dostępne są dwie właściwości zdarzenia, które można wykorzystać do sprawdzenia, który klawisz został naciśnięty:

  • key, czyli tekstowa reprezentacja naciśniętego klawisza, na przykład ArrowUp.
  • keyCode, czyli numeryczna reprezentacja, na przykład 37, odpowiadająca ArrowLeft.

Manipulacja zdarzeniami klawiatury jest przydatna nie tylko w tworzeniu gier. Jakie inne zastosowania możesz wymyślić dla tej techniki?

Klawisze specjalne: uwaga

Niektóre specjalne klawisze wpływają na okno. Oznacza to, że jeśli nasłuchujesz zdarzenia keyup i używasz tych specjalnych klawiszy do poruszania bohaterem, może to również powodować przewijanie poziome. Z tego powodu warto wyłączyć domyślne zachowanie przeglądarki podczas tworzenia gry. Potrzebujesz kodu takiego jak ten:

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

Powyższy kod zapewni, że klawisze strzałek oraz spacja będą miały wyłączone domyślne zachowanie. Mechanizm wyłączania działa, gdy wywołujemy e.preventDefault().

Ruch generowany przez grę

Możemy sprawić, że obiekty będą poruszać się same, używając timerów, takich jak funkcje setTimeout() lub setInterval(), które aktualizują lokalizację obiektu w każdym cyklu czasowym. Oto jak może to wyglądać:

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

Pętla gry

Pętla gry to koncepcja, która polega na funkcji wywoływanej w regularnych odstępach czasu. Nazywa się ją pętlą gry, ponieważ wszystko, co powinno być widoczne dla użytkownika, jest rysowane w tej pętli. Pętla gry wykorzystuje wszystkie obiekty gry, które są jej częścią, rysując je, chyba że z jakiegoś powodu nie powinny już być częścią gry. Na przykład, jeśli obiekt jest wrogiem, który został trafiony laserem i eksplodował, nie jest już częścią bieżącej pętli gry (więcej na ten temat dowiesz się w kolejnych lekcjach).

Oto jak typowa pętla gry może wyglądać w kodzie:

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

Powyższa pętla jest wywoływana co 200 milisekund, aby odświeżyć płótno. Możesz wybrać najlepszy interwał, który będzie odpowiedni dla Twojej gry.

Kontynuacja gry kosmicznej

Weź istniejący kod i rozbuduj go. Możesz zacząć od kodu, który ukończyłeś w części I, lub skorzystać z kodu w Part II- starter.

  • Poruszanie bohaterem: dodasz kod, który umożliwi poruszanie bohaterem za pomocą klawiszy strzałek.
  • Poruszanie wrogami: dodasz również kod, który sprawi, że wrogowie będą poruszać się z góry na dół w określonym tempie.

Zalecane kroki

Znajdź pliki utworzone dla Ciebie w podfolderze your-work. Powinny zawierać następujące:

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

Rozpocznij projekt w folderze your_work, wpisując:

cd your-work
npm start

Powyższe uruchomi serwer HTTP pod adresem http://localhost:5000. Otwórz przeglądarkę i wpisz ten adres. Na razie powinien wyświetlać bohatera i wszystkich wrogów; nic się jeszcze nie porusza!

Dodaj kod

  1. Dodaj dedykowane obiekty dla hero, enemy i game object, które powinny mieć właściwości x i y. (Pamiętaj o części dotyczącej Dziedziczenia lub kompozycji).

    Wskazówka: game object powinien być tym, który ma właściwości x i y oraz możliwość rysowania się na płótnie.

    Wskazówka: zacznij od dodania nowej klasy GameObject z jej konstruktorem zdefiniowanym poniżej, a następnie narysuj ją na płótnie:

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

    Teraz rozszerz tę klasę GameObject, aby utworzyć Hero i 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. Dodaj obsługę zdarzeń klawiatury, aby obsługiwać nawigację klawiszami (poruszanie bohaterem w górę/dół, lewo/prawo).

    Pamiętaj: to system kartezjański, lewy górny róg to 0,0. Pamiętaj również, aby dodać kod zatrzymujący domyślne zachowanie.

    Wskazówka: utwórz funkcję onKeyDown i przypisz ją do okna:

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

    Sprawdź konsolę przeglądarki w tym momencie i obserwuj logowane naciśnięcia klawiszy.

  3. Zaimplementuj Wzorzec publikacja-subskrypcja, aby utrzymać kod w czystości podczas realizacji kolejnych części.

    Aby wykonać ten ostatni krok, możesz:

    1. Dodać nasłuch zdarzeń do okna:

       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. Utworzyć klasę EventEmitter, aby publikować i subskrybować wiadomości:

      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. Dodać stałe i skonfigurować 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. Zainicjalizować grę

    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. Skonfiguruj pętlę gry

    Przekształć funkcję window.onload, aby zainicjalizować grę i ustawić pętlę gry w odpowiednim interwale. Dodasz również wiązkę laserową:

    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. Dodaj kod, aby poruszać wrogami w określonym interwale

    Przekształć funkcję createEnemies(), aby tworzyła wrogów i dodawała ich do nowej klasy 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);
        }
      }
    }
    

    oraz dodaj funkcję createHero(), aby wykonać podobny proces dla bohatera.

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

    na koniec dodaj funkcję drawGameObjects(), aby rozpocząć rysowanie:

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

    Twoi wrogowie powinni zacząć zbliżać się do statku kosmicznego bohatera!


🚀 Wyzwanie

Jak widzisz, Twój kod może zamienić się w "spaghetti", gdy zaczynasz dodawać funkcje, zmienne i klasy. Jak możesz lepiej zorganizować swój kod, aby był bardziej czytelny? Naszkicuj system organizacji kodu, nawet jeśli nadal znajduje się w jednym pliku.

Quiz po wykładzie

Quiz po wykładzie

Przegląd i samodzielna nauka

Chociaż piszemy naszą grę bez użycia frameworków, istnieje wiele frameworków opartych na JavaScript dla rozwoju gier na płótnie. Poświęć trochę czasu na przeczytanie o nich.

Zadanie

Skomentuj swój kod

Zastrzeżenie:
Ten dokument został przetłumaczony za pomocą usługi tłumaczenia AI Co-op Translator. Chociaż staramy się zapewnić dokładność, prosimy mieć na uwadze, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za wiarygodne źródło. W przypadku informacji krytycznych zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.