|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 3 weeks ago |
README.md
Űrjáték készítése 3. rész: Mozgás hozzáadása
Előadás előtti kvíz
A játékok nem igazán szórakoztatóak, amíg nem látunk ide-oda mozgó idegeneket a képernyőn! Ebben a játékban kétféle mozgást fogunk használni:
- Billentyűzet/egér mozgás: amikor a felhasználó a billentyűzettel vagy egérrel mozgat egy objektumot a képernyőn.
- Játék által generált mozgás: amikor a játék bizonyos időközönként mozgat egy objektumot.
De hogyan mozgatunk dolgokat a képernyőn? Az egész a derékszögű koordinátákról szól: megváltoztatjuk az objektum helyét (x, y), majd újrarajzoljuk a képernyőt.
Általában a következő lépések szükségesek a mozgás megvalósításához a képernyőn:
- Új hely meghatározása egy objektum számára; ez szükséges ahhoz, hogy az objektum mozgását érzékeljük.
- Képernyő törlése, a képernyőt minden rajzolás között törölni kell. Ezt úgy tehetjük meg, hogy egy téglalapot rajzolunk, amelyet kitöltünk egy háttérszínnel.
- Objektum újrarajzolása az új helyen. Ezzel végül elérjük, hogy az objektum egyik helyről a másikra mozogjon.
Így nézhet ki ez a kódban:
//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);
✅ Gondolkozz el azon, hogy miért okozhat teljesítményproblémákat, ha a hősödet másodpercenként sokszor újrarajzolod? Olvass utána az alternatív megoldásoknak.
Billentyűesemények kezelése
Az eseményeket úgy kezeljük, hogy specifikus eseményeket kapcsolunk kódhoz. A billentyűesemények az egész ablakra vonatkoznak, míg az egér események, például a click
, egy adott elemhez kapcsolhatók. Ebben a projektben billentyűeseményeket fogunk használni.
Egy esemény kezeléséhez az ablak addEventListener()
metódusát kell használnod, és két bemeneti paramétert kell megadnod. Az első paraméter az esemény neve, például keyup
. A második paraméter az a függvény, amelyet az esemény bekövetkezésekor meg kell hívni.
Íme egy példa:
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
A billentyűeseményekhez két tulajdonságot használhatsz az eseményen belül, hogy megtudd, melyik billentyűt nyomták meg:
key
: ez a megnyomott billentyű szöveges ábrázolása, példáulArrowUp
.keyCode
: ez a számérték, például37
, amely azArrowLeft
-nek felel meg.
✅ A billentyűesemények kezelése a játékfejlesztésen kívül is hasznos lehet. Milyen más felhasználási módokat tudsz elképzelni ehhez a technikához?
Speciális billentyűk: egy figyelmeztetés
Vannak bizonyos speciális billentyűk, amelyek hatással vannak az ablakra. Ez azt jelenti, hogy ha például egy keyup
eseményt figyelsz, és ezeket a speciális billentyűket használod a hős mozgatására, akkor vízszintes görgetés is történhet. Emiatt érdemes lehet kikapcsolni ezt a beépített böngészői viselkedést, miközben a játékodat építed. Ehhez ilyen kódra van szükséged:
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);
A fenti kód biztosítja, hogy a nyílbillentyűk és a szóköz billentyű alapértelmezett viselkedése ki legyen kapcsolva. A kikapcsolás a e.preventDefault()
hívásával történik.
Játék által generált mozgás
Az objektumokat maguktól is mozgathatjuk időzítők, például a setTimeout()
vagy setInterval()
függvények segítségével, amelyek minden időintervallumban frissítik az objektum helyét. Így nézhet ki ez:
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
})
A játékciklus
A játékciklus egy olyan koncepció, amely lényegében egy rendszeresen meghívott függvény. Azért hívják játékciklusnak, mert minden, amit a felhasználónak látnia kell, ebben a ciklusban kerül kirajzolásra. A játékciklus az összes játékobjektumot használja, amelyek a játék részét képezik, és mindet kirajzolja, kivéve, ha valamilyen okból már nem részei a játéknak. Például, ha egy objektum egy ellenség, amelyet egy lézer eltalált és felrobbant, akkor az már nem része az aktuális játékciklusnak (erről többet tanulsz a következő leckékben).
Így nézhet ki egy játékciklus kódban kifejezve:
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);
A fenti ciklus minden 200
milliszekundumban meghívódik, hogy újrarajzolja a vásznat. Te döntheted el, hogy mi a legjobb időköz a játékod számára.
Az Űrjáték folytatása
A meglévő kódot fogod bővíteni. Vagy az első részben elkészült kóddal kezdj, vagy használd a II. rész - kezdő kódját.
- Hős mozgatása: kódot fogsz hozzáadni, hogy a hőst a nyílbillentyűkkel lehessen mozgatni.
- Ellenségek mozgatása: kódot kell hozzáadnod, hogy az ellenségek fentről lefelé mozogjanak egy adott sebességgel.
Ajánlott lépések
Keresd meg azokat a fájlokat, amelyeket a your-work
almappában hoztak létre számodra. Ezeknek a következőket kell tartalmazniuk:
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
A projektedet a your_work
mappában indíthatod el az alábbi parancs beírásával:
cd your-work
npm start
A fenti parancs egy HTTP szervert indít a http://localhost:5000
címen. Nyiss meg egy böngészőt, és írd be ezt a címet. Jelenleg a hős és az összes ellenség megjelenik; még semmi sem mozog!
Kód hozzáadása
-
Hozz létre dedikált objektumokat a
hero
,enemy
ésgame object
számára, ezeknek legyenx
ésy
tulajdonságuk. (Emlékezz az Öröklődés vagy kompozíció részre).TIPP: A
game object
legyen az, amelyik rendelkezikx
ésy
tulajdonságokkal, és képes önmagát kirajzolni a vászonra.tipp: hozz létre egy új GameObject osztályt az alábbi konstruktorral, majd rajzold ki a vászonra:
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); } }
Most bővítsd ki ezt a GameObject-et, hogy létrehozd a Hero-t és az Enemy-t.
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) } }
-
Adj hozzá billentyűesemény-kezelőket, hogy kezeld a hős navigációját (mozgatás fel/le, balra/jobbra).
EMLÉKEZZ: Ez egy derékszögű rendszer, a bal felső sarok a
0,0
. Ne felejts el kódot hozzáadni az alapértelmezett viselkedés leállításához.tipp: hozd létre az onKeyDown függvényedet, és csatold az ablakhoz:
let onKeyDown = function (e) { console.log(e.keyCode); ...add the code from the lesson above to stop default behavior } }; window.addEventListener("keydown", onKeyDown);
Nézd meg a böngésződ konzolját, és figyeld, ahogy a billentyűleütések naplózásra kerülnek.
-
Valósítsd meg a Pub-sub mintát, hogy a kódod tiszta maradjon a további részek során.
Ehhez az utolsó részhez:
-
Adj hozzá egy eseményfigyelőt az ablakhoz:
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); } });
-
Hozz létre egy EventEmitter osztályt, hogy üzeneteket publikálj és iratkozz fel rájuk:
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)); } } }
-
Adj hozzá konstansokat, és állítsd be az EventEmitter-t:
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();
-
Inicializáld a játékot
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; }); }
-
-
Állítsd be a játékciklust
Refaktoráld az
window.onload
függvényt, hogy inicializálja a játékot, és állíts be egy játékciklust megfelelő időközönként. Adj hozzá egy lézersugarat is: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) };
-
Adj hozzá kódot, hogy az ellenségek bizonyos időközönként mozogjanak.
Refaktoráld a
createEnemies()
függvényt, hogy létrehozza az ellenségeket, és hozzáadja őket az új gameObjects osztályhoz: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); } } }
és adj hozzá egy
createHero()
függvényt, hogy hasonló folyamatot végezzen a hős számára.function createHero() { hero = new Hero( canvas.width / 2 - 45, canvas.height - canvas.height / 4 ); hero.img = heroImg; gameObjects.push(hero); }
végül adj hozzá egy
drawGameObjects()
függvényt, hogy elindítsd a rajzolást:function drawGameObjects(ctx) { gameObjects.forEach(go => go.draw(ctx)); }
Az ellenségeid elindulnak a hős űrhajód felé!
🚀 Kihívás
Ahogy láthatod, a kódod könnyen "spagetti kóddá" válhat, amikor elkezdesz funkciókat, változókat és osztályokat hozzáadni. Hogyan tudnád jobban megszervezni a kódodat, hogy olvashatóbb legyen? Vázolj fel egy rendszert a kódod megszervezésére, még akkor is, ha az egy fájlban marad.
Előadás utáni kvíz
Áttekintés és önálló tanulás
Bár a játékunkat keretrendszerek használata nélkül írjuk, számos JavaScript-alapú vászon keretrendszer létezik játékfejlesztéshez. Szánj időt arra, hogy olvass ezekről.
Feladat
Felelősségkizárás:
Ez a dokumentum az Co-op Translator AI fordítási szolgáltatás segítségével készült. Bár törekszünk a pontosságra, kérjük, vegye figyelembe, hogy az automatikus fordítások hibákat vagy pontatlanságokat tartalmazhatnak. Az eredeti dokumentum az eredeti nyelvén tekintendő hiteles forrásnak. Kritikus információk esetén javasolt a professzionális, emberi fordítás igénybevétele. Nem vállalunk felelősséget a fordítás használatából eredő félreértésekért vagy téves értelmezésekért.