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.
402 lines
14 KiB
402 lines
14 KiB
<!--
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
{
|
|
"original_hash": "a9a161871de7706cb0e23b1bd0c74559",
|
|
"translation_date": "2025-08-29T11:10:14+00:00",
|
|
"source_file": "6-space-game/3-moving-elements-around/README.md",
|
|
"language_code": "sk"
|
|
}
|
|
-->
|
|
# Vytvorenie vesmírnej hry, časť 3: Pridanie pohybu
|
|
|
|
## Kvíz pred prednáškou
|
|
|
|
[Kvíz pred prednáškou](https://ff-quizzes.netlify.app/web/quiz/33)
|
|
|
|
Hry nie sú veľmi zábavné, kým sa na obrazovke nezačnú pohybovať mimozemšťania! V tejto hre využijeme dva typy pohybov:
|
|
|
|
- **Pohyb klávesnicou/myšou**: keď používateľ interaguje s klávesnicou alebo myšou na pohyb objektu na obrazovke.
|
|
- **Pohyb vyvolaný hrou**: keď hra pohybuje objektom v určitých časových intervaloch.
|
|
|
|
Ako teda pohybujeme vecami na obrazovke? Všetko je to o karteziánskych súradniciach: zmeníme polohu (x, y) objektu a potom prekreslíme obrazovku.
|
|
|
|
Typicky potrebujete nasledujúce kroky na dosiahnutie *pohybu* na obrazovke:
|
|
|
|
1. **Nastaviť novú polohu** objektu; to je potrebné na to, aby sa objekt javil, že sa pohybuje.
|
|
2. **Vyčistiť obrazovku**, obrazovka musí byť vyčistená medzi jednotlivými prekresleniami. Môžeme ju vyčistiť nakreslením obdĺžnika, ktorý vyplníme farbou pozadia.
|
|
3. **Prekresliť objekt** na novej polohe. Týmto konečne dosiahneme pohyb objektu z jednej polohy na druhú.
|
|
|
|
Takto to môže vyzerať v kóde:
|
|
|
|
```javascript
|
|
//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 predstaviť dôvod, prečo by prekresľovanie vášho hrdinu mnohokrát za sekundu mohlo spôsobiť výkonové náklady? Prečítajte si o [alternatívach k tomuto vzoru](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas).
|
|
|
|
## Spracovanie udalostí klávesnice
|
|
|
|
Udalosti spracovávate pripojením konkrétnych udalostí ku kódu. Udalosti klávesnice sa spúšťajú na celom okne, zatiaľ čo udalosti myši, ako napríklad `click`, môžu byť pripojené ku kliknutiu na konkrétny prvok. Počas tohto projektu budeme používať udalosti klávesnice.
|
|
|
|
Na spracovanie udalosti musíte použiť metódu `addEventListener()` okna a poskytnúť jej dva vstupné parametre. Prvým parametrom je názov udalosti, napríklad `keyup`. Druhým parametrom je funkcia, ktorá by sa mala spustiť ako výsledok udalosti.
|
|
|
|
Tu je príklad:
|
|
|
|
```javascript
|
|
window.addEventListener('keyup', (evt) => {
|
|
// `evt.key` = string representation of the key
|
|
if (evt.key === 'ArrowUp') {
|
|
// do something
|
|
}
|
|
})
|
|
```
|
|
|
|
Pre udalosti klávesnice existujú dve vlastnosti na udalosti, ktoré môžete použiť na zistenie, ktorá klávesa bola stlačená:
|
|
|
|
- `key`, toto je reťazcová reprezentácia stlačenej klávesy, napríklad `ArrowUp`.
|
|
- `keyCode`, toto je číselná reprezentácia, napríklad `37`, čo zodpovedá `ArrowLeft`.
|
|
|
|
✅ Manipulácia s udalosťami klávesnice je užitočná aj mimo vývoja hier. Na aké iné použitia tejto techniky dokážete myslieť?
|
|
|
|
### Špeciálne klávesy: upozornenie
|
|
|
|
Existujú niektoré *špeciálne* klávesy, ktoré ovplyvňujú okno. To znamená, že ak počúvate udalosť `keyup` a použijete tieto špeciálne klávesy na pohyb vášho hrdinu, vykoná sa aj horizontálne posúvanie. Z tohto dôvodu možno budete chcieť *vypnúť* toto vstavané správanie prehliadača, keď budete budovať svoju hru. Potrebujete kód ako tento:
|
|
|
|
```javascript
|
|
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);
|
|
```
|
|
|
|
Vyššie uvedený kód zabezpečí, že šípky a medzerník budú mať svoje *predvolené* správanie vypnuté. Mechanizmus *vypnutia* sa spustí, keď zavoláme `e.preventDefault()`.
|
|
|
|
## Pohyb vyvolaný hrou
|
|
|
|
Veci môžeme nechať pohybovať sa samé pomocou časovačov, ako sú funkcie `setTimeout()` alebo `setInterval()`, ktoré aktualizujú polohu objektu pri každom tiknutí alebo časovom intervale. Takto to môže vyzerať:
|
|
|
|
```javascript
|
|
let id = setInterval(() => {
|
|
//move the enemy on the y axis
|
|
enemy.y += 10;
|
|
})
|
|
```
|
|
|
|
## Herná slučka
|
|
|
|
Herná slučka je koncept, ktorý v podstate predstavuje funkciu, ktorá sa spúšťa v pravidelných intervaloch. Nazýva sa herná slučka, pretože všetko, čo by malo byť viditeľné pre používateľa, sa kreslí v rámci tejto slučky. Herná slučka využíva všetky herné objekty, ktoré sú súčasťou hry, a kreslí ich, pokiaľ z nejakého dôvodu už nie sú súčasťou hry. Napríklad, ak je objekt nepriateľ, ktorý bol zasiahnutý laserom a exploduje, už nie je súčasťou aktuálnej hernej slučky (o tom sa dozviete viac v nasledujúcich lekciách).
|
|
|
|
Takto môže typická herná slučka vyzerať, vyjadrená v kóde:
|
|
|
|
```javascript
|
|
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);
|
|
```
|
|
|
|
Vyššie uvedená slučka sa spúšťa každých `200` milisekúnd na prekreslenie plátna. Máte možnosť zvoliť si najlepší interval, ktorý dáva zmysel pre vašu hru.
|
|
|
|
## Pokračovanie vo vesmírnej hre
|
|
|
|
Vezmete existujúci kód a rozšírite ho. Buď začnite s kódom, ktorý ste dokončili počas časti I, alebo použite kód v [časti II - štartér](../../../../6-space-game/3-moving-elements-around/your-work).
|
|
|
|
- **Pohyb hrdinu**: pridáte kód na zabezpečenie pohybu hrdinu pomocou šípok.
|
|
- **Pohyb nepriateľov**: budete tiež musieť pridať kód na zabezpečenie pohybu nepriateľov zhora nadol v danom tempe.
|
|
|
|
## Odporúčané kroky
|
|
|
|
Nájdite súbory, ktoré boli pre vás vytvorené v podpriečinku `your-work`. Mali by obsahovať nasledujúce:
|
|
|
|
```bash
|
|
-| assets
|
|
-| enemyShip.png
|
|
-| player.png
|
|
-| index.html
|
|
-| app.js
|
|
-| package.json
|
|
```
|
|
|
|
Svoj projekt spustíte v priečinku `your_work` zadaním:
|
|
|
|
```bash
|
|
cd your-work
|
|
npm start
|
|
```
|
|
|
|
Vyššie uvedené spustí HTTP server na adrese `http://localhost:5000`. Otvorte prehliadač a zadajte túto adresu, momentálne by sa mal zobraziť hrdina a všetci nepriatelia; zatiaľ sa nič nehýbe!
|
|
|
|
### Pridajte kód
|
|
|
|
1. **Pridajte špecifické objekty** pre `hero`, `enemy` a `game object`, mali by mať vlastnosti `x` a `y`. (Pamätajte na časť o [Dedičnosti alebo kompozícii](../README.md)).
|
|
|
|
*TIP*: `game object` by mal byť ten, ktorý má `x` a `y` a schopnosť kresliť sa na plátno.
|
|
|
|
>tip: začnite pridaním novej triedy GameObject s jej konštruktorom, ako je uvedené nižšie, a potom ju nakreslite na plátno:
|
|
|
|
```javascript
|
|
|
|
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 rozšírte tento GameObject na vytvorenie Hero a Enemy.
|
|
|
|
```javascript
|
|
class Hero extends GameObject {
|
|
constructor(x, y) {
|
|
...it needs an x, y, type, and speed
|
|
}
|
|
}
|
|
```
|
|
|
|
```javascript
|
|
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. **Pridajte spracovanie udalostí klávesnice** na spracovanie navigácie klávesami (pohyb hrdinu hore/dole, vľavo/vpravo).
|
|
|
|
*PAMÄTAJTE*: ide o karteziánsky systém, ľavý horný roh je `0,0`. Tiež nezabudnite pridať kód na zastavenie *predvoleného správania*.
|
|
|
|
>tip: vytvorte svoju funkciu onKeyDown a pripojte ju k oknu:
|
|
|
|
```javascript
|
|
let onKeyDown = function (e) {
|
|
console.log(e.keyCode);
|
|
...add the code from the lesson above to stop default behavior
|
|
}
|
|
};
|
|
|
|
window.addEventListener("keydown", onKeyDown);
|
|
```
|
|
|
|
Skontrolujte konzolu prehliadača v tomto bode a sledujte, ako sa zaznamenávajú stlačenia kláves.
|
|
|
|
3. **Implementujte** [Pub sub vzor](../README.md), ktorý udrží váš kód čistý, keď budete pokračovať v ďalších častiach.
|
|
|
|
Na vykonanie tejto poslednej časti môžete:
|
|
|
|
1. **Pridať poslucháča udalostí** na okno:
|
|
|
|
```javascript
|
|
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);
|
|
}
|
|
});
|
|
```
|
|
|
|
1. **Vytvoriť triedu EventEmitter** na publikovanie a odoberanie správ:
|
|
|
|
```javascript
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
1. **Pridať konštanty** a nastaviť EventEmitter:
|
|
|
|
```javascript
|
|
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();
|
|
```
|
|
|
|
1. **Inicializovať hru**
|
|
|
|
```javascript
|
|
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;
|
|
});
|
|
}
|
|
```
|
|
|
|
1. **Nastaviť hernú slučku**
|
|
|
|
Refaktorujte funkciu window.onload na inicializáciu hry a nastavenie hernej slučky v dobrom intervale. Tiež pridáte laserový lúč:
|
|
|
|
```javascript
|
|
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. **Pridajte kód** na pohyb nepriateľov v určitých intervaloch.
|
|
|
|
Refaktorujte funkciu `createEnemies()` na vytvorenie nepriateľov a ich pridanie do novej triedy gameObjects:
|
|
|
|
```javascript
|
|
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 pridajte funkciu `createHero()` na vykonanie podobného procesu pre hrdinu.
|
|
|
|
```javascript
|
|
function createHero() {
|
|
hero = new Hero(
|
|
canvas.width / 2 - 45,
|
|
canvas.height - canvas.height / 4
|
|
);
|
|
hero.img = heroImg;
|
|
gameObjects.push(hero);
|
|
}
|
|
```
|
|
|
|
a nakoniec pridajte funkciu `drawGameObjects()` na spustenie kreslenia:
|
|
|
|
```javascript
|
|
function drawGameObjects(ctx) {
|
|
gameObjects.forEach(go => go.draw(ctx));
|
|
}
|
|
```
|
|
|
|
Vaši nepriatelia by mali začať postupovať na vašu vesmírnu loď!
|
|
|
|
---
|
|
|
|
## 🚀 Výzva
|
|
|
|
Ako vidíte, váš kód sa môže zmeniť na "špagetový kód", keď začnete pridávať funkcie, premenné a triedy. Ako môžete lepšie organizovať svoj kód, aby bol čitateľnejší? Návrhnite systém na organizáciu vášho kódu, aj keď stále zostáva v jednom súbore.
|
|
|
|
## Kvíz po prednáške
|
|
|
|
[Kvíz po prednáške](https://ff-quizzes.netlify.app/web/quiz/34)
|
|
|
|
## Recenzia a samoštúdium
|
|
|
|
Aj keď píšeme našu hru bez použitia frameworkov, existuje mnoho JavaScriptových frameworkov pre vývoj hier na báze plátna. Nájdite si čas na [čítanie o nich](https://github.com/collections/javascript-game-engines).
|
|
|
|
## Zadanie
|
|
|
|
[Okomentujte svoj kód](assignment.md)
|
|
|
|
---
|
|
|
|
**Upozornenie**:
|
|
Tento dokument bol preložený pomocou služby na automatický preklad [Co-op Translator](https://github.com/Azure/co-op-translator). Hoci sa snažíme o presnosť, upozorňujeme, že automatické preklady môžu obsahovať chyby alebo nepresnosti. Pôvodný dokument v jeho pôvodnom jazyku by mal byť považovaný za autoritatívny zdroj. Pre kritické informácie sa odporúča profesionálny ľudský preklad. Nezodpovedáme za akékoľvek nedorozumenia alebo nesprávne interpretácie vyplývajúce z použitia tohto prekladu. |