💾 localStorage — Mentés a böngészőben
A böngésző tartósan megőrzhet adatokat — felhasználói adatbázis, szerver és internet nélkül. A Castle Siege és a Galactic Conquest ezt használja a pályahaladás és a ranglista mentéséhez.
1Mi az a localStorage?
A localStorage egy kulcs-érték tárhely, amely a böngészőben él, az oldalzárás után is megmarad. Olyan mint egy mini adatbázis, amelyhez JavaScript-ből lehet hozzáférni — szerveroldal nélkül.
| Helyszín | Megmarad bezárás után? | Méret | Mire jó? |
|---|---|---|---|
let x = 5 | ❌ Nem | korlátlan | Futás közbeni adat |
sessionStorage | ❌ Nem (tab záráskor törlődik) | ~5 MB | Ideiglenes munkamenet |
localStorage | ✅ Igen | ~5 MB | Mentés, beállítások, ranglista |
| Szerver adatbázis | ✅ Igen | korlátlan | Több felhasználó, szinkronizálás |
Az 5 alapfüggvény
// MENTÉS — szöveget ment kulcs-érték párként localStorage.setItem('jatekos_neve', 'Zoli'); // OLVASÁS — visszaadja az értéket (vagy null-t, ha nem létezik) const nev = localStorage.getItem('jatekos_neve'); // 'Zoli' // TÖRLÉS — egy kulcs törlése localStorage.removeItem('jatekos_neve'); // ÖSSZES TÖRLÉSE — veszélyes! mindent töröl localStorage.clear(); // KULCSOK SZÁMA const darab = localStorage.length; // hány kulcs van elmentve
A localStorage mindig szöveget (string) vár és ad vissza. Számokat és objektumokat JSON-né kell alakítani. Ezért kell a JSON.stringify() és JSON.parse().
2JSON — objektumok mentése
A játékállapot összetett objektum: pályaszám, arany, tornyok, beállítások. Ezt egyetlen szövegként kell elmenteni — ezt csinálja a JSON.
// ─── MENTÉS ─────────────────────────────────────────────── function saveGame() { const mentett = { level: G.level, // jelenlegi pálya gold: G.gold, // arany highscore: G.highscore // legjobb eredmény }; // Objektum → szöveg (JSON) const szoveg = JSON.stringify(mentett); // Szöveg → localStorage localStorage.setItem('castle_siege_save', szoveg); console.log('Elmentve!', szoveg); // Például: '{"level":7,"gold":340,"highscore":12500}' } // ─── BETÖLTÉS ───────────────────────────────────────────── function loadGame() { const szoveg = localStorage.getItem('castle_siege_save'); // Ha nincs mentett állás, null-t kapunk if (!szoveg) { console.log('Nincs mentett játék.'); return; } // Szöveg → Objektum (JSON) const mentett = JSON.parse(szoveg); G.level = mentett.level; G.gold = mentett.gold; G.highscore = mentett.highscore; console.log('Betöltve! Pálya: ' + G.level); }
stringify(obj) → objektumból szöveget csinál. parse(str) → szövegből objektumot csinál. Ha stringify-vel mentettük, parse-szal kell olvasni.
Biztonságos betöltés — mindig try-catch
function loadGame() { try { const str = localStorage.getItem('castle_siege_save'); if (!str) return false; // nincs mentés const d = JSON.parse(str); // Ellenőrzés: érvényes-e a mentett adat? if (typeof d.level !== 'number') return false; G.level = d.level; G.gold = d.gold || 100; // alapérték, ha hiányzik return true; } catch(e) { // Sérült JSON esetén nem omlik össze a játék console.warn('Betöltési hiba:', e); localStorage.removeItem('castle_siege_save'); return false; } }
Ha a localStorage-ban lévő szöveg sérült (pl. félbe szakadt mentés, más oldalról átírt adat), a JSON.parse() kivételt dob és a játék összeomlik. A try-catch elkapja a hibát és biztonságosan kezeli.
3Automatikus mentés — autosave
// Minden pályaváltáskor automatikusan ment function nextLevel() { G.level++; G.gold += 50; // jutalomra arany saveGame(); // ← autosave minden szint után genLevel(G.level); // pályagenerálás } // Játék indításakor betöltés window.addEventListener('load', () => { const ok = loadGame(); if (ok) { showMessage('Üdv vissza! ' + G.level + '. pályánál tartasz.'); } genLevel(G.level); });
Nyisd meg a castle_siege.html-t VS Code-ban, és keresd meg a localStorage hívásokat:
- Nyomj F12-t böngészőben, majd Application → Local Storage — látod a mentett adatot?
- Játssz le 2 pályát, majd frissítsd az oldalt — visszatér a pályádra?
- Írj egy
clearSave()függvényt, ami törli a mentést és visszaállítja az 1. pályát - Add hozzá ezt egy "Új játék" gombhoz
🧠 Mit ad vissza a localStorage.getItem('kulcs'), ha a kulcs nem létezik?
🏆 Top 10 ranglista implementálása
Tömb rendezés, slice(), és localStorage kombinációja — így tároljuk a legjobb eredmények listáját névvel és pontszámmal. A Galactic Conquest ranglistája alapján tanulunk.
1A ranglista adatszerkezete
A ranglista egy tömb, amelynek minden eleme egy objektum: {name, score, date}. A legjobb 10-et tároljuk — rendezve, pontszám szerint csökkenő sorrendben.
// Ranglista betöltése localStorage-ból function loadScores() { try { const str = localStorage.getItem('galactic_scores'); return str ? JSON.parse(str) : []; } catch { return []; } } // Új eredmény hozzáadása a ranglistához function addScore(name, score) { const scores = loadScores(); // 1. Hozzáadjuk az új eredményt scores.push({ name: name, score: score, date: new Date().toLocaleDateString('hu-HU') }); // 2. Rendezés: pontszám szerint csökkenő (b-a = nagyobb előre) scores.sort((a, b) => b.score - a.score); // 3. Csak a legjobb 10-et tartjuk meg const top10 = scores.slice(0, 10); // 4. Visszamentjük localStorage.setItem('galactic_scores', JSON.stringify(top10)); return top10; }
Az array.sort((a, b) => b.score - a.score) összehasonlít két elemet. Ha az eredmény negatív, a csere nem történik meg. Ha pozitív, felcseréli őket. A b - a képlet mindig a nagyobb értéket rakja előre (csökkenő sorrend).
A slice() — csak a legjobbak
const pontok = [300, 1200, 850, 75, 2100, 440]; // sort: helyben rendez, az eredeti tömböt módosítja pontok.sort((a, b) => b - a); // → [2100, 1200, 850, 440, 300, 75] // slice(start, end): másolatot ad, nem módosítja az eredetit const top3 = pontok.slice(0, 3); // → [2100, 1200, 850] // Az eredeti pontok tömb változatlan! // Különbség splice vs slice: // splice → módosítja az eredetit (és elemeket tud törölni/szúrni) // slice → csak másolatot ad, az eredetit nem bántja
2A ranglista megjelenítése HTML-ben
// A ranglista kirajzolása a canvasra function drawLeaderboard() { const scores = loadScores(); const medaliok = ['🥇', '🥈', '🥉']; ctx.fillStyle = 'rgba(0,0,0,0.85)'; ctx.fillRect(0, 0, W, H); // sötét háttér ctx.font = 'bold 28px Inter'; ctx.fillStyle = '#ffe066'; ctx.fillText('🏆 TOP 10', W/2, 60); scores.forEach((s, i) => { const y = 110 + i * 36; // Kiemelés az első háromnak ctx.fillStyle = i < 3 ? '#fff' : '#8895aa'; ctx.font = i < 3 ? 'bold 18px Inter' : '16px Inter'; // Medál az első 3-nak, szám a többinek const prefix = i < 3 ? medaliok[i] : `${i+1}. `; ctx.fillText(`${prefix} ${s.name} — ${s.score} pont`, 60, y); // Dátum jobb oldalon ctx.fillStyle = '#4a6272'; ctx.font = '13px Inter'; ctx.fillText(s.date, W - 110, y); }); }
Építs egy egyszerű ranglista rendszert:
- Hozz létre egy
addScore('Teszt', 9999)hívást, és nézd meg F12 → Application → Local Storage-ban az eredményt - Hívd meg 5-ször különböző nevekkel és pontokkal — mi marad meg?
- Módosítsd úgy, hogy Top 5-öt tároljon Top 10 helyett
- Adj hozzá egy szűrőt: ugyanaz a név csak egyszer szerepelhet (a jobb eredményével)
🧠 Mit csinál az [5,2,8,1].sort((a,b) => b - a) hívás?
🔊 Web Audio API — Zene és hangeffektek
Hang lejátszása böngészőben három megközelítéssel: HTML5 Audio elem, beágyazott base64 hang, és a haladó AudioContext API. Miért nem elég mindig a legegyszerűbb megoldás?
1A legegyszerűbb: new Audio()
Az Audio objektum a böngésző beépített hanglejátszója. Egy fájlnevet kap, és azonnal tudja lejátszani.
// Hang betöltése fájlból const bgm = new Audio('frog_music1.mp3'); bgm.loop = true; // ismétlés bekapcsolva bgm.volume = 0.6; // hangerő: 0.0 – 1.0 // Lejátszás — fontos: felhasználói interakció kell! document.addEventListener('click', () => { bgm.play().catch(e => console.warn('Hang hiba:', e)); }, { once: true }); // csak az első kattintásra // Megállítás és visszatekerés bgm.pause(); bgm.currentTime = 0; // visszatekerés az elejére
A böngészők 2018 óta blokkolják az automatikus hanglejátszást — a felhasználónak előbb interakcióba kell lépni az oldallal (kattintás, billentyű). Ezért a legtöbb játékban az első kattintásra indul el a zene.
2Base64 hang beágyazás — mobil megoldás
Android esetén a böngésző sokszor nem tölti be a külső mp3 fájlt — különösen ha a játék lokálisan (fájlrendszerből, szerver nélkül) fut. A megoldás: a hangot beágyazzuk közvetlenül a HTML fájlba, base64 kódolással.
// A base64 kód így néz ki (lerövidítve): // data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2... // (valójában százezres karakterhossz — a teljes hangfájl) // Ezt az Audio objektumnak adjuk forrásként const MUSIC_DATA = 'data:audio/mp3;base64,SUQzBAAA...'; // a teljes adat const bgm = new Audio(MUSIC_DATA); bgm.loop = true; // A hangeffektek is ugyanígy — kis hangok base64-ben const SFX_HIT = new Audio('data:audio/wav;base64,UklGRiQ...'); const SFX_GOLD = new Audio('data:audio/wav;base64,UklGRjQ...'); // Hangeffekt lejátszása (clone → egyszerre több is szólhat) function playSound(audio) { const klon = audio.cloneNode(); klon.volume = 0.7; klon.play().catch(() => {}); }
Hogyan kódolunk hangot base64-be?
// Futtasd ezt Node.js-sel: node convert.js const fs = require('fs'); const data = fs.readFileSync('zene.mp3'); const b64 = 'data:audio/mp3;base64,' + data.toString('base64'); fs.writeFileSync('zene_b64.txt', b64); console.log('Kész! Méret: ' + b64.length + ' karakter'); // Alternatíva: böngészőben, drag & drop megoldás // → btoa() függvény nem jó mp3-ra, mert bináris adat // → FileReader API-t kell használni böngészőben: const reader = new FileReader(); reader.onload = e => console.log(e.target.result); // a b64 szöveg reader.readAsDataURL(fajl); // fajl = input[type=file] .files[0]
Ha ugyanazt az Audio objektumot játszod le kétszer egymás után gyorsan (pl. két ellenség egyszerre hal meg), a második play() az első végére ugrik. A cloneNode() egy másolatot hoz létre — így egyszerre több hangeffekt is szólhat párhuzamosan.
3AudioContext — haladó hangkezelés
Az AudioContext a böngésző teljes hangszerkesztő rendszere: hangerő-szabályozás, effektek, keverés, szintetizált hangok generálása. A játékfejlesztésben főleg a hangerőhöz és a zenei effektekhez használjuk.
// Létrehozzuk a hangrendszert const actx = new AudioContext(); // Szintetizált "érem összegyűjtve" hang — hangfájl nélkül! function beep(hz = 440, hossz = 0.15, hanger = 0.3) { const osc = actx.createOscillator(); // rezgés generátor const gain = actx.createGain(); // hangerő szabályozó osc.connect(gain).connect(actx.destination); osc.frequency.value = hz; // 440 Hz = A hang (A4) osc.type = 'sine'; // hullámforma: sine / square / sawtooth gain.gain.value = hanger; // hangerő // Elhalkulás a végén (envelope) gain.gain.exponentialRampToValueAtTime(0.001, actx.currentTime + hossz); osc.start(); osc.stop(actx.currentTime + hossz); } // Felhasználás: beep(880, 0.1); // éles pip — pont szerzés beep(220, 0.4); // mély búgás — életvesztés beep(1047, 0.2); // magas csing — pálya teljesítve
new Audio(fájl) — egyszerű hangok, külső mp3/wav fájlokból.
new Audio(base64) — mobil/offline kompatibilis, fájl nélkül.
AudioContext beep() — szintetizált hangok, hangfájl nélkül, kis méretű játékokhoz.
Adj hangot a saját mini játékodhoz:
- Hozz létre egy
beep()függvényt a fenti kód alapján - Szólaltasd meg pontszerzéskor (magas hang) és életerő-vesztéskor (mély hang)
- Nyisd meg az F12 → Console-t — kapsz-e "AudioContext was not allowed to start" figyelmeztetést?
- Javítsd ki: az AudioContext induljon el egy kattintás eseményből
🧠 Miért nem elég az audio.play() a játék indulásakor?
📱 Mobilos D-pad és érintésvezérlés
A játékokat mobilon is játszani kell — de billentyűzet nincs. A megoldás: canvas fölé rajzolt irányítógombok, amelyek touch eseményekre reagálnak. Így csináltuk a Froggy Rush és Castle Siege mobilos irányítóját.
1Touch vs Pointer events — melyiket használjuk?
| Esemény típus | Kompatibilitás | Előny | Hátrány |
|---|---|---|---|
touchstart / touchend | Csak érintőképernyő | Régebbi eszközök is | Külön egér és touch kód kell |
pointerdown / pointerup | Egér + érintő + toll | Egy kód mindháromhoz | IE11 nem támogatja |
click | Mindenhol | Legegyszerűbb | ~300ms késés mobilon, multi-touch nincs |
Modern játékokhoz a pointerdown / pointerup / pointermove eseményeket ajánlott használni — ezek egységesen működnek egérrel és érintőképernyővel is, egy kódbázissal.
2A D-pad implementációja
// ─── 1. A D-PAD GOMBOK DEFINÍCIÓJA ───────────────────────── const DPAD = [ { id: 'up', x:75, y:40, w:50, h:50, label:'▲' }, { id: 'down', x:75, y:140, w:50, h:50, label:'▼' }, { id: 'left', x:20, y:90, w:50, h:50, label:'◀' }, { id: 'right', x:130, y:90, w:50, h:50, label:'▶' }, ]; // Melyik gombok vannak lenyomva const pressed = new Set(); // ─── 2. CANVAS KOORDINÁTA SZÁMÍTÁS ──────────────────────── function getCanvasPos(e) { const r = canvas.getBoundingClientRect(); // Méretarány: ha a canvas CSS-ben más méretű const scaleX = canvas.width / r.width; const scaleY = canvas.height / r.height; return { x: (e.clientX - r.left) * scaleX, y: (e.clientY - r.top) * scaleY }; } // ─── 3. HIT-TEST: melyik gombra kattintottak? ────────────── function hitDpad(px, py) { // D-pad a canvas bal-alsó sarkában van const offX = 10, offY = H - 200; // D-pad bal-felső sarokpontja for (const btn of DPAD) { if (px >= offX+btn.x && px <= offX+btn.x+btn.w && py >= offY+btn.y && py <= offY+btn.y+btn.h) { return btn.id; // 'up', 'down', 'left' vagy 'right' } } return null; } // ─── 4. ESEMÉNYKEZELŐK ──────────────────────────────────── canvas.addEventListener('pointerdown', e => { e.preventDefault(); // scroll blokkolása const pos = getCanvasPos(e); const btn = hitDpad(pos.x, pos.y); if (btn) pressed.add(btn); }); canvas.addEventListener('pointerup', e => { const pos = getCanvasPos(e); const btn = hitDpad(pos.x, pos.y); if (btn) pressed.delete(btn); }); // A game loop-ban: pressed tartalmazza az aktív gombokat function update() { if (pressed.has('left') || K.left) p.vx -= 0.8; if (pressed.has('right') || K.right) p.vx += 0.8; if (pressed.has('up') || K.up) jump(); }
A Set automatikusan szűri a duplikátumokat: ha kétszer nyomod le az "up" gombot, csak egyszer szerepel. Az add() és has() műveletek O(1) gyorsak. Tömbbel ugyanez lehetséges, de Set-tel tisztább és gyorsabb.
3A D-pad kirajzolása canvasra
function drawDpad() { // Csak mobilon jelenjen meg if (!isMobile()) return; const offX = 10, offY = H - 200; for (const btn of DPAD) { const bx = offX + btn.x, by = offY + btn.y; // Lenyomott gomb: más szín ctx.fillStyle = pressed.has(btn.id) ? 'rgba(255,154,60,0.7)' // lenyomott: narancssárga : 'rgba(255,255,255,0.12)'; // alapállapot: félig átlátszó // Lekerekített téglalap (roundRect) ctx.beginPath(); ctx.roundRect(bx, by, btn.w, btn.h, 10); ctx.fill(); // Keret ctx.strokeStyle = 'rgba(255,255,255,0.25)'; ctx.lineWidth = 1.5; ctx.stroke(); // Nyíl ikon ctx.fillStyle = 'rgba(255,255,255,0.8)'; ctx.font = 'bold 20px Inter'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(btn.label, bx + btn.w/2, by + btn.h/2); } } // Mobilfelismerés function isMobile() { return 'ontouchstart' in window || navigator.maxTouchPoints > 0; }
Multitouch — egyszerre több gomb
// Minden pointer (ujj) kap egy ID-t const activePointers = new Map(); // pointerId → dpad gomb canvas.addEventListener('pointerdown', e => { e.preventDefault(); const pos = getCanvasPos(e); const btn = hitDpad(pos.x, pos.y); if (btn) { activePointers.set(e.pointerId, btn); // megjegyezzük melyik ujj melyik gombon pressed.add(btn); } }); canvas.addEventListener('pointerup', e => { const btn = activePointers.get(e.pointerId); // melyik gombhoz tartozik ez az ujj? if (btn) { pressed.delete(btn); activePointers.delete(e.pointerId); } });
Add hozzá a mobilos D-pad-et a saját játékodhoz:
- Húzd be a
DPADdefiníciót és apressedSet-et - Regisztráld a
pointerdownéspointerupeseményeket - A
drawDpad()-et hívd meg a draw() végén - Teszteld Chrome DevTools → Toggle Device Toolbar (Ctrl+Shift+M) — megjelenik a D-pad?
🧠 Miért Set-et használunk a lenyomott gombok tárolásához?
🚀 Deploy — A játék felkerül az internetre
Az elkészült játékot mindenki ki tudja próbálni a saját telefonján egy link segítségével — szerver, tárhely, programozói tudás nélkül. A Netlify ingyenes és drag-and-drop egyszerűségű.
1Mi az a statikus hosting?
A játékaink statikus fájlok: HTML, CSS, JavaScript — nincs adatbázis, nincs szerver oldali kód. Egy statikus hosting szolgáltatás egyszerűen kiszolgálja ezeket a fájlokat a böngészőnek, mindenki számára elérhetővé téve őket.
| Szolgáltatás | Ingyenes csomag | Nehézség | Megjegyzés |
|---|---|---|---|
| Netlify | ✅ Igen, korlátlan | ⭐ Legkönnyebb | Drag & drop, egyedi domain lehetséges |
| GitHub Pages | ✅ Igen | ⭐⭐ Könnyű | GitHub fiók kell, git push |
| Vercel | ✅ Igen | ⭐⭐ Könnyű | Fejlesztőknek, automatikus deploy |
| itch.io | ✅ Igen | ⭐ Legkönnyebb | Kifejezetten játékokhoz, közösség |
2Netlify — lépésről lépésre
Látogasd meg a netlify.com oldalt, kattints a "Sign up" gombra. Ingyenes fiókot hozhatsz létre Google vagy GitHub-fiókoddal is.
Győződj meg róla, hogy minden szükséges fájl egy mappában van: index.html, a játék html fájljai, és az mp3 zenék.
A Netlify főoldalán a "Sites" fülön van egy "Drop your site folder here" terület. Húzd rá a teljes projekt mappát erre a területre.
Néhány másodperc után kapsz egy linket: valami-nev-12345.netlify.app. Ez az oldalad URL-je — bárki elérheti!
Site settings → Domain management → Options → Edit site name. Legyen pl. froggy-rush-zoli.netlify.app.
Ha változtatás után újra fel kell tölteni: a Netlify admin felületen a "Deploys" fülön van egy "Drop your site folder here" terület — ugyanúgy drag & drop-al. Ugyanaz az URL marad, automatikusan frissül.
3Ellenőrzési lista telepítés előtt
Mielőtt feltöltesz, ellenőrizd a következőket:
- A főoldal neve
index.html— ez nyílik meg automatikusan - Minden fájlnév kisbetűs, ékezet nélkül, szóköz nélkül (froggy_rush.html ✅, Froggy Rush.html ❌)
- A hangfájlok base64-be ágyazva vannak, vagy a megfelelő alkönyvtárban vannak
- Az összes relatív hivatkozás helyes (
href="https://superlative-pothos-2c7d49.netlify.app" target="_blank" rel="noopener"nemhref="C:/Users/zoli/castle_siege.html") - A játékot tesztelted Chrome és Firefox böngészőkben
- Megnyitottad mobilon (Chrome DevTools → Ctrl+Shift+M) és a D-pad működik
- A localStorage mentés és betöltés tesztelve van
4Itch.io — játékok megosztása játékosoknak
Az itch.io egy indie játék platform — ha nemcsak link formájában, hanem egy dedikált játékoldalon szeretnéd megosztani a játékot, ez a legjobb választás.
# Windows: jobb klikk a mappán → Küldés → Tömörített (zip) mappa # Mac: jobb klikk → Compress # Linux: zip -r jatek.zip jatek_mappa/ # Az itch.io-n: # 1. Regisztrálj (ingyenes) # 2. "Create new project" → Pricing: Free # 3. Kind of project: HTML # 4. Uploads: feltöltöd a zip-et, pipálod: "This file will be played in the browser" # 5. Viewport dimensions: 480×700 (portrait) vagy 800×600 (landscape) # 6. Save & publish → kész! # A játékod elérhető lesz: https://felhasznalonev.itch.io/jatek-neve
Töltsd fel a saját játékodat az internetre:
- Regisztrálj a Netlify-on (ingyenes)
- Győződj meg róla, hogy az index.html a főoldal
- Húzd rá a mappát a Netlify drop területére
- Másold ki a kapott URL-t és küldd el egy barátodnak mobilon
- Bónusz: állíts be saját nevet az oldalnak
🧠 Miért fontos, hogy a főoldal neve pontosan index.html legyen?
✓Amit elvégeztél — összefoglaló
| Fázis | Témakör | Leckék | Amit elsajátítottál |
|---|---|---|---|
| 1. HTML & CSS | Weboldal szerkezete, stílusok | 1–10 | DOCTYPE, tagek, CSS, Flexbox, reszponzív design |
| 2. JavaScript | Programlogika | 11–20 | Változók, feltételek, ciklusok, függvények, tömbök, objektumok, események |
| 3. Canvas | Grafika és animáció | 21–30 | Rajzolás, game loop, animáció, ütközés, kamera, sprite animáció |
| 4. Játékok | Teljes játékok elemzése | 31–45 | AI, State Machine, pályagenerálás, strategy pattern, Tower Defense |
| 5. Haladó | Mentés, hang, mobil, deploy | 46–50 | localStorage, JSON, ranglista, Web Audio, D-pad, Netlify |
→Hogyan tovább?
Végy egy ötletet amelyet szeretsz (platform, puzzle, tower defense), és építsd fel a semmiből. Minden ami megakad — Google, MDN docs, vagy a játékok forráskódja a minta.
A webfejlesztés következő szintje: React (komponensek, state management) és Node.js (szerveri oldal, valódi adatbázis, több felhasználó).
Phaser.js — professzionális HTML5 játékmotor. Three.js — 3D grafika WebGL-lel. Howler.js — fejlettebb hangkezelés.
MDN Web Docs — a legjobb HTML/JS dokumentáció. JavaScript.info — mélyebb JS tanuláshoz. itch.io — játékod megosztása.
💡A legfontosabb tanulság
Nem olvasással, nem videók nézésével. A kurzus elvégzése után az igazi fejlődés a kezeidben van: nyiss meg egy üres HTML fájlt, gondolj ki valamit amit szeretnél látni a képernyőn, és kezdj el írni. Az első 10 kísérlet nem fog tökéletesen működni — de a 11. már igen. Mindenki így tanul meg programozni.