|
|
4 months ago | |
|---|---|---|
| .. | ||
| README.md | 4 months ago | |
| assignment.md | 4 months ago | |
README.md
Tworzenie gry kosmicznej Część 3: Dodawanie ruchu
journey
title Twoja podróż animacji w grze
section Podstawy ruchu
Zrozum zasady ruchu: 3: Student
Naucz się aktualizacji współrzędnych: 4: Student
Wdroż podstawowy ruch: 4: Student
section Sterowanie graczem
Obsłuż zdarzenia klawiatury: 4: Student
Zapobiegaj domyślnym zachowaniom: 5: Student
Stwórz responsywne sterowanie: 5: Student
section Systemy gry
Zbuduj pętlę gry: 5: Student
Zarządzaj cyklem życia obiektów: 5: Student
Wdroż wzorzec pub/sub: 5: Student
Pomyśl o swoich ulubionych grach – to, co czyni je fascynującymi, to nie tylko ładna grafika, ale sposób, w jaki wszystko się porusza i reaguje na twoje działania. Obecnie twoja gra kosmiczna przypomina piękny obraz, ale zaraz dodamy ruch, który ożywi ją.
Gdy inżynierowie NASA programowali komputer nawigacyjny dla misji Apollo, stali przed podobnym wyzwaniem: jak sprawić, by statek kosmiczny reagował na polecenia pilota, jednocześnie automatycznie utrzymując poprawki kursu? Zasady, których się dziś nauczymy, odzwierciedlają właśnie te koncepcje – zarządzanie ruchem kontrolowanym przez gracza oraz automatycznymi zachowaniami systemu.
W tej lekcji nauczysz się, jak sprawić, by statki kosmiczne sunęły po ekranie, reagowały na polecenia gracza i tworzyły płynne wzorce ruchu. Wszystko rozłożymy na przystępne koncepcje, które naturalnie ze sobą współgrają.
Na koniec gracze będą pilotować swój statek bohatera po ekranie, podczas gdy statki wroga będą patrolować niebo. Co ważniejsze, zrozumiesz podstawowe zasady napędzające systemy ruchu w grach.
mindmap
root((Animacja Gry))
Movement Types
Sterowanie Gracza
Ruch Automatyczny
Oparte na Fizyce
Zaprogramowane Ścieżki
Event Handling
Wejście z Klawiatury
Zdarzenia Myszy
Sterowanie Dotykowe
Zapobieganie Domyślne
Game Loop
Aktualizacja Logiki
Renderowanie Klatki
Czyszczenie Płótna
Kontrola Częstotliwości Klatek
Object Management
Aktualizacje Pozycji
Detekcja Kolizji
Zarządzanie Cyklem Życia
Śledzenie Stanu
Communication
Wzorzec Pub/Sub
Emitery Zdarzeń
Przesyłanie Wiadomości
Luźne Powiązania
Quiz przed wykładem
Zrozumienie ruchu w grach
Gry ożywają, gdy coś zaczyna się poruszać, a zasadniczo dzieje się to na dwa sposoby:
- Ruch kontrolowany przez gracza: Gdy naciskasz klawisz lub klikasz myszą, coś się porusza. To bezpośrednie powiązanie między tobą a światem gry.
- Ruch automatyczny: Gdy gra sama decyduje o ruchu – na przykład statki wroga, które muszą patrolować ekran, niezależnie od tego, co robisz.
Poruszanie obiektów na ekranie komputera jest prostsze, niż myślisz. Pamiętasz te współrzędne x i y z lekcji matematyki? To właśnie nimi się tu posługujemy. Gdy Galileo obserwował księżyce Jowisza w 1610 roku, robił zasadniczo to samo – śledził pozycje w czasie, by zrozumieć wzory ruchu.
Poruszanie czegoś na ekranie jest jak tworzenie animacji flipbook – trzeba wykonać te trzy proste kroki:
flowchart LR
A["Klatka N"] --> B["Aktualizuj pozycje"]
B --> C["Wyczyść płótno"]
C --> D["Narysuj obiekty"]
D --> E["Klatka N+1"]
E --> F{Kontynuować?}
F -->|Tak| B
F -->|Nie| G["Koniec gry"]
subgraph "Cykl animacji"
H["1. Oblicz nowe pozycje"]
I["2. Zetrzyj poprzednią klatkę"]
J["3. Renderuj nową klatkę"]
end
style B fill:#e1f5fe
style C fill:#ffebee
style D fill:#e8f5e8
- Zaktualizuj pozycję – Zmień miejsce, w którym powinien się znaleźć obiekt (np. przesuń go o 5 pikseli w prawo)
- Usuń stary obraz – Wyczyść ekran, aby nie było widoczne "duchowe" ślady
- Narysuj nową klatkę – Umieść obiekt w nowym miejscu
Robiąc to wystarczająco szybko, masz płynny ruch, który graczom wydaje się naturalny.
Oto jak może to wyglądać w kodzie:
// Ustaw lokalizację bohatera
hero.x += 5;
// Wyczyść prostokąt, który gości bohatera
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Prerysuj tło gry i bohatera
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
To, co robi ten kod:
- Aktualizuje współrzędną x bohatera o 5 pikseli, aby przesunąć go w poziomie
- Czyści cały obszar canvas, usuwając poprzednią klatkę
- Wypełnia canvas czarnym kolorem tła
- Rysuje ponownie obraz bohatera na jego nowej pozycji
✅ Potrafisz wskazać powód, dla którego rysowanie bohatera wielu klatek na sekundę może obciążać wydajność? Przeczytaj o alternatywach dla tego wzorca.
Obsługa zdarzeń klawiatury
To miejsce, gdzie łączymy wejście gracza z akcją w grze. Gdy ktoś naciśnie spację, by wystrzelić laser, lub dotknie klawisza strzałki, by uniknąć asteroidy, twoja gra musi wykryć i zareagować na te dane wejściowe.
Zdarzenia klawiatury zachodzą na poziomie okna, co oznacza, że całe twoje okno przeglądarki nasłuchuje na naciśnięcia klawiszy. Kliknięcia myszą natomiast mogą być powiązane z konkretnymi elementami (np. kliknięcie przycisku). W naszej grze kosmicznej skupimy się na sterowaniu klawiaturą, ponieważ to one dają graczom klasyczne arcade'owe wrażenie.
Przypomina mi to, jak operatorzy telegrafu w XIX wieku musieli tłumaczyć kod Morse’a na zrozumiałe wiadomości – robimy podobnie, tłumacząc naciśnięcia klawiszy na polecenia gry.
Aby obsłużyć zdarzenie, należy użyć metody addEventListener() okna i podać jej dwa parametry. Pierwszy to nazwa zdarzenia, np. keyup. Drugi to funkcja, która zostanie wywołana po zajściu zdarzenia.
Oto przykład:
window.addEventListener('keyup', (evt) => {
// evt.key = reprezentacja klawisza w postaci łańcucha znaków
if (evt.key === 'ArrowUp') {
// zrób coś
}
});
Analiza tego, co się tutaj dzieje:
- Nasłuchuje zdarzeń klawiatury na całym oknie
- Przechwytuje obiekt zdarzenia, który zawiera informacje, jaki klawisz został naciśnięty
- Sprawdza, czy naciśnięty klawisz to konkretny (tu strzałka w górę)
- Wykonuje kod, jeśli warunek jest spełniony
Do zdarzeń klawiaturowych można użyć dwóch właściwości obiektu event, by zobaczyć, który klawisz został naciśnięty:
key– to jest reprezentacja tekstowa klawisza, np.'ArrowUp'keyCode– to jest liczba, np.37, odpowiadającaArrowLeft
✅ Manipulacja zdarzeniami klawiatury jest przydatna także poza tworzeniem gier. Jakie inne zastosowania przychodzą ci do głowy dla tej techniki?
sequenceDiagram
participant User
participant Browser
participant EventSystem
participant GameLogic
participant Hero
User->>Browser: Naciska klawisz StrzałkaWgórę
Browser->>EventSystem: zdarzenie keydown
EventSystem->>EventSystem: preventDefault()
EventSystem->>GameLogic: emit('KEY_EVENT_UP')
GameLogic->>Hero: hero.y -= 5
Hero->>Hero: Aktualizuj pozycję
Note over Browser,GameLogic: Przepływ zdarzeń zapobiega domyślnym działaniom przeglądarki
Note over GameLogic,Hero: Wzorzec pub/sub umożliwia czystą komunikację
Specjalne klawisze: uwaga!
Niektóre klawisze mają wbudowane zachowania przeglądarki, które mogą przeszkadzać w grze. Strzałki przewijają stronę, a spacja przesuwa ją w dół – tego nie chcemy, gdy ktoś steruje statkiem kosmicznym.
Możemy zapobiec tym domyślnym zachowaniom i pozwolić naszej grze obsłużyć wejście. To podobne do tego, jak pierwsi programiści musieli nadpisać przerwania systemowe, by tworzyć własne zachowania – tutaj robimy to na poziomie przeglądarki. Oto jak:
const onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Klawisze strzałek
case 32:
e.preventDefault();
break; // Spacja
default:
break; // nie blokuj innych klawiszy
}
};
window.addEventListener('keydown', onKeyDown);
Zrozumienie tego kodu zapobiegającego:
- Sprawdza konkretne kody klawiszy, które mogą wywołać niechciane zachowania przeglądarki
- Zatrzymuje domyślną akcję przeglądarki dla klawiszy strzałek i spacji
- Pozwala innym klawiszom działać normalnie
- Używa
e.preventDefault()aby zatrzymać wbudowane zachowanie przeglądarki
🔄 Pedagogiczne podsumowanie
Zrozumienie obsługi zdarzeń: Zanim przejdziemy do ruchu automatycznego, upewnij się, że potrafisz:
- ✅ Wyjaśnić różnicę między zdarzeniami
keydownikeyup - ✅ Zrozumieć, dlaczego zapobiegamy domyślnym zachowaniom przeglądarki
- ✅ Opisać jak event listenery łączą wejścia użytkownika z logiką gry
- ✅ Wskazać, które klawisze mogą przeszkadzać w sterowaniu grą
Szybki test: Co by się stało, gdybyś nie zapobiegł domyślnemu zachowaniu klawiszy strzałek? Odpowiedź: Przeglądarka przewijałaby stronę, przeszkadzając w ruchu gry
Architektura systemu zdarzeń: Teraz rozumiesz:
- Nasłuchiwanie na poziomie okna: przechwytywanie zdarzeń w przeglądarce
- Właściwości obiektu zdarzenia: łańcuchy
keyvs liczbykeyCode - Zapobieganie domyślnemu zachowaniu: zatrzymywanie niepożądanych działań przeglądarki
- Logika warunkowa: reagowanie na konkretne kombinacje klawiszy
Ruch sterowany przez grę
Porozmawiajmy teraz o obiektach, które poruszają się bez wejścia od gracza. Pomyśl o statkach wroga przemierzających ekran, pociskach lecących po liniach prostych lub chmurach unoszących się w tle. Ten autonomiczny ruch sprawia, że świat twojej gry jest żywy, nawet gdy nikt nie steruje.
Używamy wbudowanych timerów JavaScript do aktualizacji pozycji w regularnych odstępach czasu. Ta koncepcja przypomina działanie zegarów wahadłowych – mechanizm wyzwalający spójne, ustalone działania w określonych odstępach. Oto jak prosto to może działać:
const id = setInterval(() => {
// Przesuń wroga na osi y
enemy.y += 10;
}, 100);
To, co robi ten kod ruchu:
- Tworzy timer, który działa co 100 milisekund
- Aktualizuje współrzędną y wroga o 10 pikseli za każdym razem
- Przechowuje ID interwału, aby można go było zatrzymać w razie potrzeby
- Przesuwa wroga automatycznie w dół na ekranie
Pętla gry
Oto koncepcja łącząca wszystko razem – pętla gry. Gdyby twoja gra była filmem, pętla gry byłaby projektorem, pokazującym klatkę za klatką na tyle szybko, że wszystko wygląda na płynny ruch.
Każda gra ma taką pętlę działającą w tle. To funkcja, która aktualizuje obiekty gry, rysuje ekran i ciągle powtarza ten proces. Śledzi twojego bohatera, wszystkich wrogów, pociski latające — stan całej gry.
Przypomina mi to, jak pierwsi filmowi animatorzy, tacy jak Walt Disney, musieli rysować postaci klatka po klatce, by stworzyć iluzję ruchu. Teraz robimy to samo, ale za pomocą kodu zamiast ołówków.
Oto jak typowo może wyglądać pętla gry, wyrażona w kodzie:
flowchart TD
A["Rozpocznij pętlę gry"] --> B["Wyczyść płótno"]
B --> C["Wypełnij tło"]
C --> D["Zaktualizuj obiekty gry"]
D --> E["Narysuj bohatera"]
E --> F["Narysuj przeciwników"]
F --> G["Narysuj elementy interfejsu"]
G --> H["Poczekaj na następnej klatki"]
H --> I{Gra działa?}
I -->|Tak| B
I -->|Nie| J["Zakończ grę"]
subgraph "Sterowanie liczba klatek"
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);
Zrozumienie struktury pętli gry:
- Czyści cały canvas, usuwając poprzednią klatkę
- Wypełnia tło jednostajnym kolorem
- Rysuje wszystkie obiekty gry w ich aktualnych pozycjach
- Powtarza ten proces co 200 milisekund, tworząc płynną animację
- Zarządza liczbą klatek przez kontrolę interwału czasowego
Kontynuacja gry kosmicznej
Teraz dodamy ruch do statycznej sceny, którą wcześniej zbudowałeś. Zamienimy ją ze statycznego obrazka w interaktywne doświadczenie. Przejdziemy przez to krok po kroku, aby każdy element naturalnie wynikał z poprzedniego.
Weź kod z miejsca, w którym skończyliśmy w poprzedniej lekcji (lub zacznij od kodu w folderze Part II- starter, jeśli potrzebujesz świeży start).
To, co dziś budujemy:
- Sterowanie bohaterem: Klawisze strzałek będą pilotować twój statek kosmiczny po ekranie
- Ruch wrogów: Te obce statki rozpoczną swój marsz
Zacznijmy implementować te funkcje.
Polecane kroki
Znajdź pliki utworzone dla ciebie w podfolderze your-work. Powinny zawierać:
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
Projekt rozpoczynasz w folderze your-work wpisując:
cd your-work
npm start
Co robi to polecenie:
- Przechodzi do katalogu twojego projektu
- Uruchamia serwer HTTP pod adresem
http://localhost:5000 - Serwuje pliki gry, byś mógł testować ją w przeglądarce
Powyższe uruchomi serwer HTTP pod adresem http://localhost:5000. Otwórz przeglądarkę i wpisz ten adres – powinien wyświetlić bohatera i wszystkich wrogów; nic się jeszcze nie porusza!
Dodaj kod
-
Dodaj dedykowane obiekty dla
hero,enemyigame object, powinny mieć właściwościxiy. (Przypomnij sobie fragment o dziedziczeniu lub kompozycji).PODPOWIEDŹ
game objectpowinien miećxiyoraz zdolność do rysowania siebie na canvas.Wskazówka: Zacznij od dodania nowej klasy
GameObjectz konstruktorem przedstawionym poniżej, a potem narysuj ją na 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); } }Zrozumienie tej klasy bazowej:
- Definiuje wspólne właściwości współdzielone przez wszystkie obiekty gry (pozycja, rozmiar, obraz)
- Zawiera flagę
dead, by śledzić, czy obiekt powinien zostać usunięty - Dostarcza metodę
draw(), która renderuje obiekt na canvas - Ustawia wartości domyślne dla wszystkich właściwości, które mogą być nadpisane przez klasy potomne
classDiagram
class GameObject {
+x: number
+y: number
+dead: boolean
+type: string
+width: number
+height: number
+img: Image
+draw(ctx)
}
class Hero {
+speed: number
+type: "Bohater"
+width: 98
+height: 75
}
class Enemy {
+type: "Wróg"
+width: 98
+height: 50
+setInterval()
}
GameObject <|-- Hero
GameObject <|-- Enemy
class EventEmitter {
+listeners: object
+on(wiadomość, słuchacz)
+emit(wiadomość, ładunek)
}
Teraz rozszerz tę klasę `GameObject`, aby stworzyć `Hero` i `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);
}
}
```
**Kluczowe koncepcje w tych klasach:**
- **Dziedziczy** po `GameObject` używając słowa kluczowego `extends`
- **Wywołuje** konstruktor klasy nadrzędnej za pomocą `super(x, y)`
- **Ustawia** specyficzne wymiary i właściwości dla każdego typu obiektu
- **Implementuje** automatyczny ruch wrogów wykorzystując `setInterval()`
-
Dodaj obsługę zdarzeń klawiatury do nawigacji (ruch bohatera góra/dół/lewo/prawo)
PAMIĘTAJ to system kartezjański, lewy górny róg to
0,0. Pamiętaj też o dodaniu kodu blokującego domyślne zachowanieWskazówka: Utwórz funkcję
onKeyDowni podłącz ją do okna:const onKeyDown = function (e) { console.log(e.keyCode); // Dodaj kod z powyższej lekcji, aby zatrzymać domyślne zachowanie switch (e.keyCode) { case 37: case 39: case 38: case 40: // Klawisze strzałek case 32: e.preventDefault(); break; // Spacja default: break; // nie blokuj innych klawiszy } }; window.addEventListener("keydown", onKeyDown);Co robi ten handler zdarzeń:
- Nasłuchuje zdarzeń
keydownna całym oknie - Loguje kod klawisza, aby ułatwić debugowanie, które klawisze są naciskane
- Zapobiega domyślnemu zachowaniu przeglądarki dla klawiszy strzałek i spacji
- Pozwala innym klawiszom działać normalnie
Sprawdź teraz konsolę przeglądarki i obserwuj logowanie naciśnięć klawiszy.
- Nasłuchuje zdarzeń
-
Zaimplementuj wzorzec Pub sub, co pozwoli utrzymać kod w czystości, gdy przejdziesz do dalszych części.
Wzorzec Publish-Subscribe pomaga zorganizować kod przez oddzielenie wykrywania zdarzeń od ich obsługi. Sprawia to, że kod jest bardziej modułowy i łatwiejszy w utrzymaniu.
Aby to zrobić, możesz:
-
Dodać listener zdarzeń na oknie:
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); } });
Co robi ten system zdarzeń:
- Wykrywa wejścia z klawiatury i zamienia je na niestandardowe zdarzenia gry
- Oddziela wykrywanie wejścia od logiki gry
- Ułatwia późniejszą zmianę sterowania bez wpływu na kod gry
- Pozwala wielu systemom reagować na to samo wejście
-
flowchart TD
A["Wejście z klawiatury"] --> B["Nasłuchiwacz zdarzeń okna"]
B --> C["Emiter zdarzeń"]
C --> D["WYDARZENIE_KLAWISZA_GÓRA"]
C --> E["WYDARZENIE_KLAWISZA_DÓŁ"]
C --> F["WYDARZENIE_KLAWISZA_LEWO"]
C --> G["WYDARZENIE_KLAWISZA_PRAWO"]
D --> H["Ruch bohatera"]
D --> I["System dźwiękowy"]
D --> J["Efekty wizualne"]
E --> H
F --> H
G --> H
style A fill:#e1f5fe
style C fill:#e8f5e8
style H fill:#fff3e0
-
Utwórz 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); } -
Dodaj stałe i skonfiguruj 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();
Zrozumienie konfiguracji:
- Definiuje stałe wiadomości, by unikać literówek i ułatwić refaktoryzację
- Deklaruje zmienne dla obrazów, kontekstu canvas i stanu gry
- Tworzy globalny emiter zdarzeń dla systemu pub-sub
- Inicjuje tablicę do przechowywania wszystkich obiektów gry
-
Zainicjuj 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; }); -
Ustaw pętlę gry
Zrefaktoryzuj funkcję
window.onload, aby zainicjować grę i ustawić pętlę gry w dobrym odstępie czasowym. Dodasz też wiązkę lasera: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); };Zrozumienie ustawień gry:
- Czeka na pełne załadowanie strony przed rozpoczęciem
- Pobiera element canvas i jego kontekst renderowania 2D
- Ładuje asynchronicznie wszystkie assety obrazów używając
await - Uruchamia pętlę gry działającą co 100 ms (10 kl./s)
- Czyści i ponownie rysuje cały ekran każdą klatkę
-
Dodaj kod do poruszania się wrogów w określonych odstępach czasu
Zrefaktoryzuj funkcję
createEnemies(), aby tworzyć wrogów i dodawać 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); } } }Co robi tworzenie wrogów:
- Oblicza pozycje, by wycentrować wrogów na ekranie
- Tworzy siatkę wrogów za pomocą zagnieżdżonych pętli
- Przypisuje obraz wroga do każdego obiektu wroga
- Dodaje każdego wroga do globalnej tablicy obiektów gry
oraz dodaj funkcję
createHero(), aby w podobny sposób utworzyć bohatera.function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }Co robi tworzenie bohatera:
- Ustawia bohatera na dole, na środku ekranu
- Przypisuje obraz bohatera do obiektu bohatera
- Dodaje bohatera do tablicy obiektów gry do renderowania
i na koniec dodaj funkcję
drawGameObjects(), aby rozpocząć rysowanie:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }Zrozumienie funkcji rysującej:
- Iteruje przez wszystkie obiekty gry w tablicy
- Wywołuje metodę
draw()na każdym obiekcie - Przekazuje kontekst canvas, aby obiekty mogły się same renderować
🔄 Pedagogiczne Podsumowanie
Kompletne zrozumienie systemu gry: Zweryfikuj swoją znajomość całej architektury:
- ✅ Jak dziedziczenie pozwala bohaterowi i wrogom dzielić wspólne właściwości GameObject?
- ✅ Dlaczego wzorzec pub/sub sprawia, że twój kod jest bardziej utrzymywany?
- ✅ Jaką rolę pełni pętla gry w tworzeniu płynnej animacji?
- ✅ Jak nasłuchiwacze zdarzeń łączą interakcję użytkownika z zachowaniem obiektów gry?
Integracja systemu: Twoja gra teraz demonstruje:
- Projektowanie obiektowe: Klasy bazowe z wyspecjalizowanym dziedziczeniem
- Architektura zdarzeniowa: Wzorzec pub/sub dla luźnego powiązania
- Framework animacji: Pętla gry ze spójnymi aktualizacjami klatek
- Obsługa wejścia: Zdarzenia klawiatury z zapobieganiem domyślnemu działaniu
- Zarządzanie assetami: Ładowanie obrazów i renderowanie sprite'ów
Wzorce profesjonalne: Zaimplementowałeś:
- Separation of Concerns: Separacja logiki, wejścia i renderowania
- Polimorfizm: Wszystkie obiekty gry dzielą wspólny interfejs rysowania
- Przesyłanie wiadomości: Czysta komunikacja między komponentami
- Zarządzanie zasobami: Efektywne zarządzanie sprite'ami i animacjami
Twoi wrogowie powinni zacząć przesuwać się w stronę Twojego statku bohatera! } }
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); }i na koniec dodaj funkcję
drawGameObjects(), aby rozpocząć rysowanie:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }Twoi wrogowie powinni zacząć przesuwać się w stronę Twojego statku bohatera!
GitHub Copilot Agent Challenge 🚀
Oto wyzwanie, które poprawi dopracowanie Twojej gry: dodanie granic i płynnych sterowań. Obecnie Twój bohater może wyjść poza ekran, a ruch może wydawać się szarpany.
Twoja misja: Spraw, aby Twój statek kosmiczny był bardziej realistyczny, implementując granice ekranu i płynny ruch. To podobne do systemów kontroli lotów NASA, które zapobiegają przekraczaniu bezpiecznych parametrów operacyjnych przez statki kosmiczne.
Co zbudować: Stwórz system, który utrzymuje statek bohatera na ekranie i spraw, aby sterowanie było płynne. Gdy gracze przytrzymają klawisz strzałki, statek powinien się ślizgać ciągle, zamiast poruszać się skokowo. Rozważ dodanie wizualnej informacji zwrotnej, gdy statek dotrze do granic ekranu – np. subtelny efekt wskazujący krawędź obszaru gry.
Dowiedz się więcej o trybie agenta.
🚀 Wyzwanie
Organizacja kodu staje się coraz ważniejsza w miarę rozrostu projektów. Mogłeś zauważyć, że Twój plik robi się zatłoczony od funkcji, zmiennych i klas wymieszanych razem. Przypomina to to, jak inżynierowie organizujący kod misji Apollo musieli tworzyć jasne, łatwe w utrzymaniu systemy, nad którymi mogło pracować wiele zespołów jednocześnie.
Twoja misja: Myśl jak architekt oprogramowania. Jak zorganizowałbyś swój kod, aby za sześć miesięcy Ty (lub kolega) mogli z łatwością zrozumieć, co się dzieje? Nawet jeśli wszystko zostanie w jednym pliku, możesz stworzyć lepszą organizację:
- Grupowanie powiązanych funkcji razem z wyraźnymi nagłówkami komentarzy
- Separacja obowiązków – oddzielenie logiki gry od renderowania
- Stosowanie spójnej nazewnictwa zmiennych i funkcji
- Tworzenie modułów lub przestrzeni nazw do organizacji różnych aspektów Twojej gry
- Dodanie dokumentacji wyjaśniającej cel każdej większej sekcji
Pytania refleksyjne:
- Które części Twojego kodu są najtrudniejsze do zrozumienia po powrocie do nich?
- Jak mógłbyś zorganizować kod, by ułatwić innym programistom współpracę?
- Co by się stało, gdybyś chciał dodać nowe funkcje, jak power-upy lub różne typy wrogów?
Quiz po wykładzie
Przegląd & Samodzielna nauka
Budowaliśmy wszystko od podstaw, co jest fantastyczne dla nauki, ale oto mały sekret – istnieje wiele świetnych frameworków JavaScript, które mogą odciążyć Cię w wielu zadaniach. Kiedy poczujesz się pewnie z podstawami, warto sprawdzić, co jest dostępne.
Pomyśl o frameworkach jak o dobrze wyposażonej skrzynce narzędziowej zamiast tworzeniu każdego narzędzia samodzielnie. Mogą rozwiązać wiele wyzwań związanych z organizacją kodu, o których rozmawialiśmy, a także oferują funkcje, które zbudowanie samemu zajęłoby tygodnie.
Warto zbadać:
- Jak silniki gier organizują kod – zdziwisz się, jakie sprytne wzorce stosują
- Triki wydajnościowe pozwalające uruchomić gry canvas płynnie
- Nowoczesne funkcje JavaScript, które mogą uczynić Twój kod czystszym i łatwiejszym w utrzymaniu
- Różne podejścia do zarządzania obiektami gry i ich relacjami
🎯 Twój Oś Czasu Mistrzostwa Animacji Gry
timeline
title Postępy w nauce animacji i interakcji w grach
section Podstawy ruchu (20 minut)
Zasady animacji: Animacja klatkowa
: Aktualizacje pozycji
: Układy współrzędnych
: Płynny ruch
section Systemy zdarzeń (25 minut)
Wejście użytkownika: Obsługa zdarzeń klawiatury
: Zapobieganie domyślnemu zachowaniu
: Właściwości obiektu zdarzenia
: Nasłuchiwanie na poziomie okna
section Architektura gry (30 minut)
Projektowanie obiektów: Wzorce dziedziczenia
: Tworzenie klasy bazowej
: Wyszukane zachowania
: Polimorficzne interfejsy
section Wzorce komunikacji (35 minut)
Implementacja Pub/Sub: Emiter zdarzeń
: Stałe wiadomości
: Luźne powiązania
: Integracja systemu
section Opanowanie pętli gry (40 minut)
Systemy czasu rzeczywistego: Kontrola liczby klatek
: Cykl aktualizacji/renderowania
: Zarządzanie stanem
: Optymalizacja wydajności
section Zaawansowane techniki (45 minut)
Funkcje profesjonalne: Wykrywanie kolizji
: Symulacja fizyki
: Maszyny stanów
: Systemy komponentów
section Koncepcje silnika gry (1 tydzień)
Zrozumienie frameworka: Systemy encja-komponent
: Grafy scen
: Kanały zasobów
: Profilowanie wydajności
section Umiejętności produkcyjne (1 miesiąc)
Rozwój zawodowy: Organizacja kodu
: Współpraca w zespole
: Strategie testowania
: Optymalizacja wdrożenia
🛠️ Podsumowanie Twojego Zestawu Narzędzi Do Tworzenia Gier
Po ukończeniu tej lekcji, opanowałeś:
- Zasady animacji: Ruch oparty na klatkach i płynne przejścia
- Programowanie zdarzeniowe: Obsługa wejścia z klawiatury z odpowiednim zarządzaniem zdarzeniami
- Projektowanie obiektowe: Hierarchie dziedziczenia i polimorficzne interfejsy
- Wzorce komunikacji: Architektura pub/sub dla utrzymania kodu
- Architektura pętli gry: Aktualizacje i cykle renderowania w czasie rzeczywistym
- Systemy wejścia: Mapowanie sterowania użytkownika z zapobieganiem domyślnemu zachowaniu
- Zarządzanie zasobami: Ładowanie sprite’ów i efektywne techniki renderowania
⚡ Co Możesz Zrobić w Następnych 5 Minutach
- Otwórz konsolę przeglądarki i wypróbuj
addEventListener('keydown', console.log)aby zobaczyć zdarzenia klawiatury - Stwórz prosty element div i przesuwaj go za pomocą klawiszy strzałek
- Eksperymentuj z
setInterval, aby uzyskać ciągły ruch - Spróbuj zapobiegać domyślnemu zachowaniu za pomocą
event.preventDefault()
🎯 Co Możesz Osiągnąć w Ciągu Godziny
- Ukończ quiz po lekcji i zrozum programowanie zdarzeniowe
- Zbuduj poruszający się statek bohatera z pełną obsługą klawiatury
- Zaimplementuj płynne wzory ruchu wrogów
- Dodaj granice zapobiegające opuszczaniu ekranu przez obiekty gry
- Stwórz podstawową detekcję kolizji między obiektami gry
📅 Twoja Tygodniowa Podróż Animacji
- Ukończ pełną grę kosmiczną z dopracowanym ruchem i interakcjami
- Dodaj zaawansowane wzory ruchu, jak krzywe, przyspieszenia i fizyka
- Wprowadź płynne przejścia i funkcje easing
- Stwórz efekty cząsteczkowe i systemy informacji wizualnej
- Optymalizuj wydajność gry dla płynnej rozgrywki 60fps
- Dodaj sterowanie dotykowe i responsywny design
🌟 Twój Miesięczny Interaktywny Rozwój
- Buduj złożone aplikacje interaktywne z zaawansowanymi systemami animacji
- Ucz się bibliotek animacji takich jak GSAP lub stwórz własny silnik animacji
- Współpracuj przy open source’owych projektach gier i animacji
- Opanuj optymalizację wydajności dla aplikacji intensywnie korzystających z grafiki
- Twórz materiały edukacyjne o tworzeniu gier i animacji
- Zbuduj portfolio prezentujące zaawansowane umiejętności programowania interaktywnego
Zastosowania w rzeczywistości: Twoje umiejętności animacji gier odnoszą się bezpośrednio do:
- Interaktywnych aplikacji webowych: Dynamiczne pulpity i interfejsy w czasie rzeczywistym
- Wizualizacji danych: Animowane wykresy i grafiki interaktywne
- Oprogramowania edukacyjnego: Symulacje interaktywne i narzędzia do nauki
- Rozwoju mobilnego: Gry i obsługa gestów dotykowych
- Aplikacji desktopowych: Aplikacje Electron z płynnymi animacjami
- Animacji webowych: Biblioteki animacji CSS i JavaScript
Umiejętności zawodowe zdobyte: Potrafisz teraz:
- Projektować systemy zdarzeniowe skalujące się złożonością
- Implementować płynne animacje za pomocą zasad matematycznych
- Debugować złożone systemy interakcji używając narzędzi deweloperskich przeglądarki
- Optymalizować wydajność gry na różne urządzenia i przeglądarki
- Projektować utrzymywalne struktury kodu wg sprawdzonych wzorców
Opanowane koncepcje tworzenia gier:
- Zarządzanie liczbą klatek: Zrozumienie FPS i kontroli czasu
- Obsługa wejścia: Wieloplatformowe systemy klawiatury i zdarzeń
- Cykl życia obiektu: Wzorce tworzenia, aktualizacji i niszczenia
- Synchronizacja stanu: Utrzymanie spójności stanu gry między klatkami
- Architektura zdarzeń: Luzowanie powiązań między systemami gry
Kolejny poziom: Jesteś gotowy dodać detekcję kolizji, systemy punktacji, efekty dźwiękowe lub zgłębić nowoczesne frameworki gier, takie jak Phaser lub Three.js!
🌟 Osiągnięcie odblokowane: Zbudowałeś kompletny interaktywny system gry z profesjonalnymi wzorcami architektonicznymi!
Zadanie
Oświadczenie: Niniejszy dokument został przetłumaczony za pomocą usługi tłumaczenia AI Co-op Translator. Mimo że dokładamy starań, aby tłumaczenie było jak najbardziej precyzyjne, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w języku źródłowym powinien być uważany za źródło autorytatywne. W przypadku informacji krytycznych zaleca się skorzystanie z profesjonalnego tłumaczenia wykonanego przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.