3. Fázis · Canvas
3. Fázis · 1. Lecke

Canvas alapok — az első vonal

A Canvas egy HTML elem, amire JavaScript-tel rajzolunk. Minden játékunk erre a "rajztáblára" rajzolja a pályát, a karaktereket és az ellenségeket — képkockáról képkockára.

⏱ 60 perc
📁 Canvas 2D
🎯 fillRect · strokeRect · clearRect

1A canvas létrehozása és a kontextus

A canvas maga csak egy üres téglalapot jelent a HTML-ben. Rajzolni csak a 2D kontextus (ctx) segítségével lehet — ez az a "ceruza", amivel dolgozunk.

canvas_alap.htmlHTML + JS
<!-- HTML-ben: a canvas elem -->
<canvas id="gc" width="540" height="580"></canvas>

// JavaScript-ben: megszerezzük a rajzolási eszközt
const CV  = document.getElementById('gc');
const ctx = CV.getContext('2d');

// Szélességet és magasságot érdemes változóba tenni
const W = CV.width;   // 540
const H = CV.height;  // 580

Az első rajzok — téglalapok

teglalapok.jsJavaScript
// Kitöltött téglalap: fillRect(x, y, szélesség, magasság)
ctx.fillStyle = '#4aaa22';     // szín beállítás
ctx.fillRect(0, 0, W, H);    // egész canvas zöldre töltve

// Keret téglalap: strokeRect
ctx.strokeStyle = '#fff';
ctx.lineWidth   = 2;
ctx.strokeRect(10, 10, 100, 60);

// Töröl egy területet (átlátszóvá teszi)
ctx.clearRect(0, 0, W, H);   // minden képkocka elején ezt hívjuk
A koordinátarendszer

A canvas bal felső sarka a (0, 0) pont. X jobbra nő, Y lefelé nő — ez fordítva van a matematikai koordinátarendszerhez képest! Tehát a (0, 580) pont a bal alsó sarok.

3 példa a játékainkból

🏰 Castle Siege

A pálya háttere és az út rajzolása fillRect-tel:

castle_siege.html
// Sötét háttér az egész canvasra
ctx.fillStyle = '#1a1208';
ctx.fillRect(0, UIH, W, GH);

// Az út (path) szürke téglalapként
ctx.fillStyle = '#3a3028';
ctx.fillRect(PATH_X, UIH, PATH_W, GH);

// Kastély alapterülete
ctx.fillStyle = '#2a1808';
ctx.fillRect(0, UIH, 80, GH);
🐸 Froggy Rush

A pálya sorok (road, river, grass) kirajzolása:

froggy_rush.html
// Minden sorra (row) egy fillRect
for (let r = 0; r < ROWS; r++) {
  const y = rowY(r);           // sor Y pozíciója
  ctx.fillStyle = ROW_COLORS[r]; // sor színe
  ctx.fillRect(0, y, W, ROW_H);
}

// Oldalsó füves terület
ctx.fillStyle = '#2d7a10';
ctx.fillRect(0, 0, TRACK_LEFT, H);
🐢 Teknős Verseny

A pályaszéli fák tövének rajzolása (trunk):

turtle_race.html
// Fa törzse — barna téglalap
ctx.strokeStyle = '#8B6040';
ctx.lineWidth   = 3;
ctx.beginPath();
ctx.moveTo(0, 10);
ctx.lineTo(0, -18);
ctx.stroke();

// Pályaszél zöld sáv (oldalsó fű)
ctx.fillStyle = '#2a6a10';
ctx.fillRect(0, 0, TL, H);
ctx.fillRect(TR, 0, W-TR, H);
✏️ 1. Lecke feladata

Hozz létre egy HTML fájlt canvas-szal és rajzolj rá:

  • Zöld háttér az egész canvasra (fillRect)
  • Szürke út a közepén (fillRect, vízszintesen húzódó)
  • Fehér keret körülötte (strokeRect)
  • Töröld az út felső harmadát (clearRect) — mit látsz?

🧠 Mi a canvas koordinátarendszerének origója (0,0)?

Bal alsó sarok
Középpont
Bal felső sarok
Jobb felső sarok
3. Fázis · 2. Lecke

Körök, ívek, vonalak

A teknős páncélja, a tojások, a kövek, a sirályok — mind körökből és ívekből épülnek fel. A Path (útvonal) rendszerrel bármilyen alakzatot megrajzolhatunk.

⏱ 75 perc
🎯 arc · beginPath · ellipse
🎮 Tojás · Kő · Sirály

1A Path rendszer

A körök és összetett alakzatok rajzolásához először egy útvonalat (path) írunk le, majd azt töltjük ki vagy húzzuk körül.

path_alapok.jsJavaScript
// Kör: arc(x, y, sugár, kezdőszög, végszög)
ctx.beginPath();                    // mindig ezzel kezdjük
ctx.arc(100, 100, 30, 0, Math.PI * 2); // teljes kör
ctx.fillStyle = '#3ddc84';
ctx.fill();                          // kitölti
ctx.stroke();                        // keretet rajzol

// Ellipszis: ellipse(x, y, rx, ry, szög, start, end)
ctx.beginPath();
ctx.ellipse(200, 100, 40, 25, 0, 0, Math.PI*2);
ctx.fill();

// Vonal: moveTo + lineTo
ctx.beginPath();
ctx.moveTo(0, 50);   // toll felemelése és odavitele
ctx.lineTo(200, 50); // vonal húzása ide
ctx.stroke();

// Zárt alakzat: closePath
ctx.beginPath();
ctx.moveTo(100, 0);
ctx.lineTo(200, 100);
ctx.lineTo(0, 100);
ctx.closePath();    // visszaköt az elejére → háromszög
ctx.fill();

3 példa a játékainkból

🏰 Castle Siege

Lövedék és ellenség körök rajzolása:

castle_siege.html
// Ellenség: kitöltött kör + keret
ctx.beginPath();
ctx.arc(e.x, e.y, e.r, 0, Math.PI*2);
ctx.fillStyle = e.col;
ctx.fill();
ctx.strokeStyle = 'rgba(0,0,0,.3)';
ctx.lineWidth = 2;
ctx.stroke();

// Hős lövedéke: kis fényes kör
ctx.beginPath();
ctx.arc(p.x, p.y, 4, 0, Math.PI*2);
ctx.fillStyle = '#00d4ff';
ctx.fill();
🐸 Froggy Rush

Tojás rajzolása ellipszisként:

froggy_rush.html
// Tojás = megnyújtott ellipszis
const g = ctx.createRadialGradient(
  x-2, y-3, 1, x, y, 9
);
g.addColorStop(0, '#fffef5');
g.addColorStop(1, '#e8d080');
ctx.fillStyle = g;
ctx.beginPath();
ctx.ellipse(x, y, 7, 9, 0, 0, Math.PI*2);
ctx.fill();
🐢 Teknős Verseny

Kő szabálytalan alakzata moveTo + lineTo-val:

turtle_race.html
// Kő = sokszög (nem kör, mert sziklás)
ctx.beginPath();
ctx.moveTo(-9, 6);
ctx.lineTo(-12, -2);
ctx.lineTo(-6, -10);
ctx.lineTo(5, -12);
ctx.lineTo(13, -4);
ctx.lineTo(11, 6);
ctx.closePath();     // visszazár
ctx.fill();
beginPath() — miért kell?

Ha nem hívod, az előző útvonal is benne marad. Pl. egy kört rajzolsz, majd egy másikat — a régi is újra kitöltődik, dupla keret jelenik meg. Mindig beginPath()-szel kezdj új alakzatot!

✏️ 2. Lecke feladata

Rajzolj egyszerű figurákat a canvasra:

  • Rajzolj egy teljes kört (arc, 0-tól Math.PI*2-ig)
  • Rajzolj egy félkört (arc, 0-tól Math.PI-ig)
  • Rajzolj egy tojást (ellipse, rx≠ry)
  • Rajzolj egy háromszöget (moveTo + lineTo + lineTo + closePath + fill)
  • Bónusz: a Froggy Rush tojás kódja alapján rajzolj radiális gradiens tojást

🧠 Mi történik ha nem hívjuk a beginPath()-t két arc() rajzolás között?

Hiba keletkezik
Az előző és az új alakzat összekapcsolódik, mindkettő újra kitöltődik
Semmi nem látszik
Csak az új alakzat rajzolódik ki
3. Fázis · 3. Lecke

Színek, átlátszóság, gradiensek

A teknős páncéljának mélysége, a kastély árnyékai, a tojás fényes felülete — mind gradienssel készül. Ez adja a vizuális mélységet a játékainknak.

⏱ 60 perc
🎯 rgba · globalAlpha · createLinearGradient
🎮 Páncél · Árnyék · Tojás

1Szín megadási módok

szinek.jsJavaScript
// Szín szöveggel (CSS színek)
ctx.fillStyle = 'red';
ctx.fillStyle = '#4aaa22';       // hex
ctx.fillStyle = '#4aaa22aa';     // hex + alpha

// rgba — piros, zöld, kék + átlátszóság (0=átlátszó, 1=teli)
ctx.fillStyle = 'rgba(255, 100, 0, 0.6)';  // 60% narancssárga

// Lineáris gradiens
const g = ctx.createLinearGradient(x1, y1, x2, y2);
g.addColorStop(0,   '#ff0000'); // piros az elején
g.addColorStop(0.5, '#ffff00'); // sárga a közepén
g.addColorStop(1,   '#00ff00'); // zöld a végén
ctx.fillStyle = g;

// Radiális gradiens (belülről kifelé)
const rg = ctx.createRadialGradient(cx, cy, r1, cx, cy, r2);
rg.addColorStop(0, '#ffffff'); // középen fehér
rg.addColorStop(1, '#1a5a10'); // szélén sötét
ctx.fillStyle = rg;

// Globális átlátszóság — mindent érint
ctx.globalAlpha = 0.5;  // 50% átlátszó minden rajzolás
ctx.fillRect(0, 0, W, H);
ctx.globalAlpha = 1;    // visszaállítás!

3 példa a játékainkból

🏰 Castle Siege

HP sáv gradiens — zöldtől pirosig:

castle_siege.html
// HP szín az aktuális érték alapján
const pct = G.castle.hp / G.castle.maxHp;
let hpCol;
if (pct > 0.6)      hpCol = '#3a8a3a';
else if (pct > 0.3) hpCol = '#9a8a20';
else               hpCol = '#8a2020';

// Sáv háttér
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.fillRect(bx, by, bw, bh);
// Kitöltött rész
ctx.fillStyle = hpCol;
ctx.fillRect(bx, by, bw*pct, bh);
🐢 Teknős Verseny

Teknős páncél radiális gradienssel:

turtle_race.html
// Radiális gradiens: belül világos, kívül sötét
const g2 = ctx.createRadialGradient(
  -3, -4, 2,  // belső kör: eltolt (fény hatás)
   0,  1, 14  // külső kör
);
g2.addColorStop(0, '#3a9a28'); // belül világos zöld
g2.addColorStop(1, '#1a5a10'); // kívül sötét zöld
ctx.fillStyle = g2;
ctx.ellipse(0, 1, 14, 11, 0, 0, Math.PI*2);
ctx.fill();
🐸 Froggy Rush

Boost pad pulzáló animáció globalAlpha-val:

froggy_rush.html
// frame alapján 0-1 között oszcillál
const pulse = (Math.sin(frame * .1) + 1) * .5;

// globalAlpha: pulzáló átlátszóság
ctx.globalAlpha = 0.75 + pulse * .25;
ctx.fillStyle = `rgba(255,180,0,${.35+pulse*.3})`;
ctx.beginPath();
ctx.arc(0, 0, 13, 0, Math.PI*2);
ctx.fill();
ctx.globalAlpha = 1;  // MINDIG visszaállítani!
Figyelem — globalAlpha visszaállítása

Ha beállítod a globalAlpha-t és elfelejtesz visszaállítani 1-re, az összes utána rajzolt elem is átlátszó lesz! Mindig add hozzá: ctx.globalAlpha = 1; a végére.

✏️ 3. Lecke feladata

Kísérletezz a színekkel és gradiensekkel:

  • Rajzolj 3 egymást fedő kört különböző rgba átlátszósággal (0.2, 0.5, 0.9)
  • Készíts lineáris gradienst balról jobbra (kék → zöld) és tölts ki vele egy téglalapot
  • Készíts radiális gradienst és rajzolj vele egy "fényes golyót"
  • Rajzolj egy HP sávot: háttér (szürke), kitöltött rész (zöld), és csökkentsd 50%-ra

🧠 Mit jelent az rgba(255, 0, 0, 0.5)?

50%-os fehér szín
50% átlátszó piros
Teljesen átlátszó piros
RGB kód ahol a 4. szám a kék intenzitása
3. Fázis · 4. Lecke

A game loop — requestAnimationFrame

Minden játék szíve a game loop. Ez fut 60-szor másodpercenként, frissíti a játékállapotot és újrarajzolja a képet. Nélküle a játék csak egy pillanatfelvétel lenne.

⏱ 75 perc
🎯 requestAnimationFrame · update · draw
🎮 Minden játék loop-ja

1A loop szerkezete

game_loop.js — az alap szerkezetJavaScript
// A teljes játék állapota
let x = 100;   // kör vízszintes helyzete
let vx = 2;    // sebesség (pixel/frame)

// 1. UPDATE — frissíti a pozíciókat, fizikát
function update() {
  x += vx;                    // elmozdul
  if (x > W || x < 0) vx *= -1; // fal visszapattan
}

// 2. DRAW — kirajzolja az aktuális állapotot
function draw() {
  ctx.clearRect(0, 0, W, H); // töröl
  ctx.beginPath();
  ctx.arc(x, 100, 20, 0, Math.PI*2);
  ctx.fillStyle = '#3ddc84';
  ctx.fill();
}

// 3. LOOP — meghívja magát újra és újra
function loop() {
  update();
  draw();
  requestAnimationFrame(loop); // ~60x/másodperc
}

loop(); // elindítja
Miért requestAnimationFrame és nem setInterval?

setInterval fix időközönként fut, akkor is ha a tab a háttérben van — pazarolja az energiát. A requestAnimationFrame csak akkor fut, ha a böngésző éppen rajzol — automatikusan szünetet tart háttérben, és szinkronizál a monitor frissítési sebességével (általában 60fps).

3 példa a játékainkból

🏰 Castle Siege

A Castle Siege teljes loop-ja:

castle_siege.html
function loop() {
  update(); // mozgatja a hőst, ellenségeket
  draw();   // kirajzol mindent
  requestAnimationFrame(loop);
}

function update() {
  // Csak ha játékban vagyunk
  if (!G || G.phase !== 'play') return;
  G.frame++;
  moveHero(G.hero);
  moveEnemies(G.enemies);
  moveProjectiles(G.hProj);
  checkCollisions();
}
🐸 Froggy Rush

Frame számláló és fázis-alapú update:

froggy_rush.html
function update() {
  // Mindig fut (loop-ban hívják)
  if (!G) return;
  G.frame++;  // 0, 1, 2, 3... folyamatosan nő

  if (G.phase === 'play')  updatePlay();
  if (G.phase === 'dead')  updateDead();
  if (G.phase === 'levelup') updateLevel();
}

function loop() {
  update(); draw();
  requestAnimationFrame(loop);
}
loop();
🐢 Teknős Verseny

A frame szám szinusz-animációhoz való felhasználása:

turtle_race.html
function loop() {
  update(); draw();
  requestAnimationFrame(loop);
}

function draw() {
  // G.frame folyamatosan nő
  // sin(frame * 0.2) → hullámzó animáció
  const walk = Math.sin(G.frame * .2) * 2;
  // teknős lábai walk alapján mozognak
  drawTurtle(G.player.x, PLAYER_Y,
             G.player, G.frame);
}
✏️ 4. Lecke feladata — Első animáció

Készítsd el az első saját animációdat:

  • Egy kör mozogjon jobbra-balra és pattanjon vissza a falnál
  • Adj hozzá függőleges mozgást is (fel-le pattanás)
  • A kör hagyon maga után halvány nyomot (ne clearRect-eld teljesen)
  • Bónusz: állítsd meg/indítsd el szóköz billentyűvel (pause/resume)

🧠 Miért jobb a requestAnimationFrame a setInterval-nál játékhoz?

Mert gyorsabb, mindig 120fps-sel fut
Szinkronizál a monitor frissítési sebességével, és szünetel ha a tab háttérben van
Mert nem kell meghívni manuálisan
Csak animációhoz való, játékhoz a setInterval a helyes
3. Fázis · 5. Lecke

Koordinátarendszer és transzformációk

A teknős irányának megfelelően dől, a fa forgásból épül fel, az árnyék eltolva jelenik meg. Mindez save/restore és translate/rotate segítségével valósul meg.

⏱ 75 perc
🎯 save · restore · translate · rotate · scale
🎮 Teknős dőlés · Fa rajzolás

1save() és restore() — a kontextus mentése

transzformacio.jsJavaScript
// save/restore nélkül: az egyik elem transzformációja
// befolyásolja az összeset utána!

// HELYES megközelítés:
ctx.save();              // elmenti: szín, transform, stb.

  ctx.translate(100, 200);  // origó áttolása
  ctx.rotate(0.5);          // 0.5 radián = ~28.6 fok
  ctx.scale(1.5, 1.5);      // 1.5x nagyítás

  // Minden rajzolás a NEW origóhoz képest:
  ctx.fillStyle = '#3ddc84';
  ctx.fillRect(-25, -25, 50, 50); // origó körül

ctx.restore();           // visszaállítja az eredeti állapotot
// Innentől minden visszatért normálisba

3 példa a játékainkból

🐢 Teknős Verseny

Teknős dőlése irány szerint (vx alapján):

turtle_race.html
function drawTurtle(x, y, t) {
  ctx.save();
  ctx.translate(x, y);  // origó a teknős közepére

  // vx alapján dől: ha jobbra megy → jobbra dől
  const tilt = Math.max(-0.28,
    Math.min(0.28, t.vx * .075)
  );
  ctx.rotate(tilt);

  // Rajzolás (0,0) körül → a teknős közepéből
  drawShell();
  drawHead();
  drawLegs();

  ctx.restore(); // visszaáll az eredeti transform
}
🐸 Froggy Rush

Sirály rajzolása saját origó köré translate-tel:

froggy_rush.html
function drawGull(x, y, frame) {
  const flap = Math.sin(frame * .2) * 7;

  ctx.save();
  ctx.translate(x, y);  // sirály közepébe

  // Test, szárnyak, fej (0,0) körül)
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.ellipse(0, 0, 9, 5, 0, 0, Math.PI*2);
  ctx.fill();
  // Szárnyak flap szerint mozognak
  ctx.ellipse(-12, flap, 10, 3, 0, 0, Math.PI*2);
  ctx.fill();

  ctx.restore();
}
🐢 Teknős Verseny

Fák rajzolása — mindig saját origó körül:

turtle_race.html
function drawTree(x, y, level) {
  ctx.save();
  ctx.translate(x, y);  // origó a fa tövéhez

  // Törzs — felfelé (negatív Y)
  ctx.strokeStyle = '#8B6040';
  ctx.lineWidth = 2.5;
  ctx.beginPath();
  ctx.moveTo(0, 10);
  ctx.lineTo(0, -18);
  ctx.stroke();

  // Lomb — a törzs tetején
  ctx.fillStyle = '#2a8010';
  ctx.beginPath();
  ctx.arc(0, -22, 12, 0, Math.PI*2);
  ctx.fill();

  ctx.restore();
}
Szabály: minden karakterrajzolás save/restore között

Minden komplex rajzolási függvényt (drawTurtle, drawGull, drawTree) ctx.save() és ctx.restore() közé kell tenni, és ctx.translate(x, y)-al odatolni. Így a rajzoló függvény mindig (0,0) körül dolgozik, és nem kell tudnia hol van a karakter a canvas-on.

✏️ 5. Lecke feladata

Transzformációk gyakorlása:

  • Rajzolj egy négyzetet, forgasd el 45 fokkal (Math.PI/4 radián)
  • Írj egy drawCar(x, y) függvényt save/translate/restore-ral, hívd meg 3 különböző pozícióban
  • Animáció: egy kar forgása — save → translate a csukló pozícióba → rotate(frame*0.05) → lineTo → restore

🧠 Miért rajzolunk (0,0) körül a drawTurtle-ben, ha a teknős pl. (170, 400)-on van?

Mert a canvas origója mindig (0,0)
Mert ctx.translate(170, 400) eltolja az origót, ezután a (0,0) már a teknős közepét jelenti — így a rotate is ott forog ahol kell
Mert a koordináták relatívak az előző elemhez
Nem igaz, a teknős (170,400)-ban rajzolódik fillRect(170,400,...) hívással
3. Fázis · 6. Lecke

Szöveg rajzolása a canvasra

A pontszám, a "GAME OVER", a lebegő "+10" szövegek, a pályaszám — mind a ctx.fillText() segítségével kerülnek a képernyőre.

⏱ 45 perc
🎯 fillText · font · textAlign · measureText
🎮 HUD · Pontszám · Lebegő szöveg

1Szöveg alapok

szoveg_canvas.jsJavaScript
// Betűtípus: "vastagság méret családnév"
ctx.font = 'bold 20px Fredoka One, sans-serif';

// Vízszintes igazítás: 'left', 'center', 'right'
ctx.textAlign = 'center';

// Függőleges igazítás: 'top', 'middle', 'bottom'
ctx.textBaseline = 'middle';

// Rajzolás: fillText(szöveg, x, y)
ctx.fillStyle = '#ffe44d';
ctx.fillText('Pont: 1500', W/2, 30);

// Keret szöveg (ritkán használjuk)
ctx.strokeStyle = '#000';
ctx.strokeText('GAME OVER', W/2, H/2);

// Szöveg szélessége (pl. középre igazításhoz)
const w = ctx.measureText('Helló').width;

3 példa a játékainkból

🏰 Castle Siege

Lebegő szöveg (+arany, -HP) animáció:

castle_siege.html
// floats tömb: {x, y, txt, col, life, vy}
G.floats.forEach(f => {
  // Átlátszóság csökken ahogy eltűnik
  ctx.globalAlpha = f.life / 95;
  ctx.fillStyle = f.col;
  ctx.font = 'bold 13px Nunito,sans-serif';
  ctx.textAlign = 'center';
  ctx.fillText(f.txt, f.x, f.y);

  f.y  += f.vy;   // felfelé úszik
  f.life--;        // elhalványul
});
ctx.globalAlpha = 1;
🐸 Froggy Rush

Game Over / győzelem overlay szöveg:

froggy_rush.html
// Sötét overlay háttér
ctx.fillStyle = 'rgba(0,0,0,.6)';
ctx.fillRect(0, 0, W, H);

// Nagy cím középen
ctx.fillStyle = '#ffe44d';
ctx.font = 'bold 38px Fredoka One';
ctx.textAlign = 'center';
ctx.fillText('🏆 GYŐZELEM!', W/2, H/2-18);

// Kisebb segédszöveg alatta
ctx.fillStyle = '#fff';
ctx.font = '16px Nunito';
ctx.fillText('Pont: ' + G.score, W/2, H/2+16);
🐢 Teknős Verseny

Teknős neve és tojásszám felirat:

turtle_race.html
ctx.save();
ctx.translate(x, y);

// Cimke a teknős felett
ctx.fillStyle = 'rgba(0,80,0,.8)';
ctx.font = 'bold 9px Nunito';
ctx.textAlign = 'center';
ctx.fillText('🐢 Te', 0, -28);

// Tojásszám felett ha van
if (t.eggs > 0) {
  ctx.fillStyle = 'rgba(255,200,0,.9)';
  ctx.fillText('🥚×' + t.eggs, 0, -39);
}
ctx.restore();
✏️ 6. Lecke feladata

Szövegek a canvason:

  • Írj középre igazított "JÁTÉK" feliratot a canvas tetejére (textAlign: center)
  • Jobb alsó sarokba kis pontszám szöveg (textAlign: right, textBaseline: bottom)
  • Animáció: egy szöveg csúszik felfelé és elhalványul (globalAlpha csökken)
  • Bónusz: GameOver overlay — sötét háttér + nagy szöveg + kisebb leírás

🧠 Melyik textAlign érték igazítja a szöveget úgy hogy az X koordináta a szöveg közepét jelöli?

left
right
center
middle
3. Fázis · 7. Lecke

Ütközésdetektálás

Ha a béka eléri a tojást, az eltűnik. Ha az ellenség eléri a kastélyt, HP vész. Mindkettő ütközésdetektálás — két módszerrel: körös és téglalapps.

⏱ 75 perc
🎯 Math.hypot · AABB · overlap
🎮 Tojásgyűjtés · Torony hatótáv

1Köröknél: Math.hypot távolság

utkozesdetektalas.jsJavaScript
// Két kör ütközik ha a köztük lévő távolság
// kisebb mint a sugaraik összege
function circleHit(ax, ay, ar, bx, by, br) {
  const dist = Math.hypot(bx-ax, by-ay);
  return dist < ar + br;
}

// Használat:
if (circleHit(frog.x, frog.y, 13,
              egg.x,  egg.y,  9)) {
  egg.collected = true;
  score += 10;
}

// Téglalap ütközés (AABB - Axis-Aligned Bounding Box)
function rectHit(ax, ay, aw, ah, bx, by, bw, bh) {
  return ax < bx+bw && ax+aw > bx &&
         ay < by+bh && ay+ah > by;
}

3 példa a játékainkból

🏰 Castle Siege

Torony hatótáv vizsgálat — megvan-e az ellenség?

castle_siege.html
// Torony lövedékeit csak az ellenségek
// HATÓTÁVON BELÜL kapják meg
G.enemies.forEach(enemy => {
  const d = Math.hypot(
    enemy.x - tower.x,
    enemy.y - tower.y
  );
  if (d < tower.range) {
    // Eltalálta!
    enemy.hp -= tower.dmg;
  }
});
🐸 Froggy Rush

Tojás és sirály ütközés a béka körével:

froggy_rush.html
G.objects.forEach(o => {
  if (o.done) return;
  const sy = o.y + G.scrollY;
  // Math.hypot: a béka és objektum távolsága
  const d = Math.hypot(
    p.x - o.x,
    (PLAYER_Y - p.jumpH) - sy
  );
  if (o.type === 'egg' && d < o.r + 13) {
    o.done = true;
    p.eggs++;
  }
});
🐢 Teknős Verseny

Kő ütközés — ugráskor nem ütközik:

turtle_race.html
// Kő ütközés: csak ha NEM ugrik
if (o.type === 'rock' &&
    !p.jumping &&
    !p.stunned) {
  const pd = Math.hypot(
    p.x - o.x,
    (PLAYER_Y - p.jumpH) - sy
  );
  if (pd < o.r + 11) {
    loseLife(p);  // ütközés!
  }
}
Miért Math.hypot és nem saját képlet?

A Math.hypot(dx, dy) ugyanaz mint Math.sqrt(dx*dx + dy*dy) — de rövidebb és nem kell sqrt-t írni. Pitagorasz tétele: a² + b² = c², ahol c a két pont távolsága.

✏️ 7. Lecke feladata

Ütközésdetektálás gyakorlása:

  • Rajzolj 2 kört, és írd ki ha ütköznek (circleHit függvény)
  • Mozgó kör + álló cél: jelezd vizuálisan az ütközést (szín változás)
  • Írj rectHit függvényt és tesztelj 2 téglalap átfedéssel
  • Bónusz: gyűjtős játék — egy irányítható kör gyűjt tojásokat (körös ütközés)

🧠 Két kör mikor ütközik?

Ha középpontjaik ugyanott vannak
Ha X koordinátáik egyenlők
Ha a köztük lévő távolság kisebb mint a két sugár összege
Ha területük átfed
3. Fázis · 8. Lecke

Scroll és kamerakövetés

A Teknős Versenyben a pálya hosszabb mint a képernyő — a kamera görget. A Froggy Rush-ban a pálya "felfelé" görög. Mindkettő ugyanazon az elvon alapul.

⏱ 75 perc
🎯 scrollY · world-space · screen-space
🎮 Teknős Verseny · Froggy Rush

1World-space vs Screen-space

scroll_rendszer.js — az alap elvJavaScript
// scrollY: mennyit görgettünk (nő ahogy haladunk)
let scrollY = 0;

// Objektumnak van WORLD pozíciója (fix)
const egg = { x: 200, y: -800 }; // a pálya egy pontján

// SCREEN pozíció = world + scroll (ahol a képernyőn látszik)
const screenY = egg.y + scrollY;

// Az objektum a képernyőn van ha screenY in [0, H]
if (screenY > -30 && screenY < H+30) {
  ctx.fillRect(egg.x, screenY, 14, 18); // rajzolás
}

// Update-ben: scrollY nő → objektumok feljebb kerülnek
scrollY += scrollSpd; // pl. 2.2 px/frame

// A játékos marad a helyén (PLAYER_Y fix pozíció)
// Ez adja a "haladás" érzetet!

3 példa a játékainkból

🐢 Teknős Verseny

Az objektumok world-space-ben fixek, csak scrollY mozog:

turtle_race.html
// Spawn: world Y = PLAYER_Y - scrollAtPlayer
// Ekkor a játékos pont rajta áll
const y = PLAYER_Y - S_start;

// Draw-ban: screen_y = o.y + scrollY
G.objects.forEach(o => {
  const sy = o.y + G.scrollY;
  if (sy < -40 || sy > H+40) return;
  drawEgg(o.x, sy);
});

// Update: csak scrollY nő, objektumok NEM mozognak
G.scrollY += scrollSpd;
🐸 Froggy Rush

A pályasorok Y pozíciója scroll alapján:

froggy_rush.html
// A sorok (row 0-12) Y pozíciója
// Fix: a sor helye a képernyőn nem változik
function rowY(row) {
  return H - ROW_H * (row + 1);
}
// A béka "felfelé" halad = az objektumok
// "lefelé" görögnek (scrollY nő)
const sy = o.y + G.scrollY; // screen pozíció
// Ütközéskor is scrollY-t kell figyelembe venni
const d = Math.hypot(p.x-o.x, PLAYER_Y-sy);
💡 Kitalált példa

Egyszerű oldalnézetű kamerakövetés:

kamera.js
// Kamera = a játékos X-je mínusz képernyő közepe
let camX = 0;

function update() {
  player.x += player.vx;
  // Kamera simán követi a játékost
  camX += (player.x - W/2 - camX) * 0.1;
}

function draw() {
  ctx.save();
  // Kamera eltolása: minden a camX-szel balra tolódik
  ctx.translate(-camX, 0);

  // Pálya (a kamera koordinátarendszerében)
  ground.forEach(g => ctx.fillRect(g.x, g.y, g.w, 20));
  drawPlayer(player.x, player.y);

  ctx.restore();
}
✏️ 8. Lecke feladata

Scroll rendszer implementálása:

  • Hozz létre 20 objektumot (tojás) véletlenszerű world-space Y pozícióval (-100 és -2000 között)
  • A game loopban növeld a scrollY értékét (pl. 2 px/frame)
  • Rajzold ki az objektumokat screenY = obj.y + scrollY alapján
  • Ha a screenY > H+30, vedd ki a tömbből (filter)
  • Bónusz: ütközésdetektálás a játékossal (Math.hypot)

🧠 Mi a screen_y = obj.y + scrollY képlet?

Az objektum eltávolítja magát a scrollY-tól
Az objektum world pozíciójához adjuk a kamera eltolását, hogy megkapjuk hol látszik a képernyőn
A scrollY az objektum saját sebessége
Így számítjuk az ütközést
3. Fázis · 9. Lecke

Sprite animáció — képkockák nélkül

A játékainkban nincsenek képfájlok — a teknős lábai, a béka ugrása, a sirály szárnycsapása mind Math.sin() és a frame számláló segítségével animálódik.

⏱ 75 perc
🎯 Math.sin · frame · jumpH · walkBob
🎮 Teknős lábak · Béka ugrás · Sirály szárny

1A sin() alapú animáció

sin_animacio.js — hogyan működikJavaScript
// A frame folyamatosan nő: 0, 1, 2, 3, 4...
// Math.sin(frame * sebesség) → -1 és +1 között oszcillál
// * amplitúdó → a mozgás nagyságát adja meg

// Lassú hullámzás: 
const slow = Math.sin(frame * 0.05) * 10; // ±10px lassan

// Közepes léptetés:
const walk = Math.sin(frame * 0.2) * 3;  // ±3px közepesen

// Gyors szárnycsapás:
const flap = Math.sin(frame * 0.25) * 8; // ±8px gyorsan

// Pulzáló méret (0 és 1 között, nem -1 és 1):
const pulse = (Math.sin(frame * 0.1) + 1) * .5;

Ugrás parabolája sin()-nel

ugras.js — parabolaívJavaScript
// jumpT: az ugrás ideje (0-tól N-ig számol)
let jumpT = 0;
const JUMP_FRAMES = 22; // 22 frame alatt le is ér

if (jumping) {
  jumpT++;
  // sin(0) = 0, sin(π/2) = 1, sin(π) = 0
  // → parabola: felmegy és visszajön
  jumpH = Math.sin(jumpT / JUMP_FRAMES * Math.PI) * 38;
  // 38px a csúcsmagasság

  if (jumpT >= JUMP_FRAMES) {
    jumping = false;
    jumpH   = 0;
  }
}

// Rajzoláskor: Y-ból kivonjuk a jumpH-t (felfelé mozdul)
ctx.translate(p.x, PLAYER_Y - p.jumpH);

3 példa a játékainkból

🐢 Teknős Verseny

Teknős lábak ellentétes léptetése:

turtle_race.html
// walk: -2 és +2 között oszcillál
const walk = Math.sin(frame * .2) * 2;

// Bal hátsó, jobb hátsó — ELLENTÉTESEN lépnek
ctx.ellipse(-10, 12 + walk,  7, 4, .3, 0, Math.PI*2);
ctx.fill();
ctx.ellipse( 10, 12 - walk,  7, 4, -.3, 0, Math.PI*2);
ctx.fill();
// Első lábak is ellentétesen:
ctx.ellipse(-10, -8 - walk, 6, 4, .2, 0, Math.PI*2);
ctx.ellipse( 10, -8 + walk, 6, 4, -.2, 0, Math.PI*2);
🐸 Froggy Rush

Béka ugrás parabolaíve sin()-nel:

froggy_rush.html
// jumpT 0→22 alatt lefut (22 frame)
if (p.jumping) {
  p.jumpT++;
  p.jumpH = Math.sin(
    p.jumpT / 22 * Math.PI
  ) * 38;  // max 38px magasan

  if (p.jumpT >= 22) {
    p.jumping = false;
    p.jumpH   = 0;
  }
}
// Rajzoláskor:
ctx.translate(p.x, PLAYER_Y - p.jumpH);
🏰 Castle Siege

Hős "lélegzés" animáció nyugalomban:

castle_siege.html
// Lassú fel-le mozgás = "lélegzés"
const bob = Math.sin(G.frame * 0.04) * 1.5;

ctx.save();
// A hős kissé feljebb-lejjebb mozog
ctx.translate(h.x, h.y + bob);

drawHeroBody();
drawHeroHead();
drawHeroWeapon();
ctx.restore();
✏️ 9. Lecke feladata

Animáljunk mindent sin()-nel:

  • Rajzolj egy karaktert (egyszerű pálcika ember) és animáld a karját sin()-nel
  • Implementáld a parabolás ugrást (jumpT, jumpH, sin(jumpT/N * PI))
  • Egy kör "lüktet" — mérete sin()-nel oszcillál 20 és 30 pixel között
  • Bónusz: két karakter ellentétes léptetéssel (egyik +walk, másik -walk)

🧠 Miért adódik 1 és osztódik 2-vel a pulzáló animációnál: (sin(frame)+1)*.5?

Hogy gyorsabb legyen az animáció
Mert a sin() -1 és +1 között van, de nekünk 0 és 1 kell — a +1 feltolja 0-2-re, az *.5 lehozza 0-1-re
Mert különben negatív lesz az átlátszóság
Csak canvas-on szükséges, más kontextusban nem
3. Fázis · 10. Lecke

🏆 Projektzáró — Mozgó karakter

A 3. Fázis összefoglalója: egy teljes, irányítható, animált karakter a canvason — game loop, scroll, ütközés, sin() animáció mind egyszerre.

⏱ 3–4 óra
🎯 Önálló fejlesztés
📁 Egyetlen HTML fájl

1A projekt feladata

Készíts egy egyszerű canvas-alapú mini játékot ahol egy karakter mozog és gyűjt tárgyakat. Minden amit ebben a fázisban tanultál, benne van.

Minimális követelmények

✓ Canvas + 2D kontextus, clearRect minden frame-ben
✓ Game loop: requestAnimationFrame, update + draw szétválasztva
✓ Irányítható karakter: billentyű / érintés, save/translate/restore
✓ Legalább 1 animáció: sin() alapú lábdobogás vagy ugrás
✓ Legalább 5 gyűjthető tárgy: Math.random spawning
✓ Ütközésdetektálás: Math.hypot
✓ Pontszám szöveg a canvason: ctx.fillText

mini_jatek.html — javasolt vázszerkezetJavaScript
// === ÁLLAPOT ===
const CV = ..., ctx = CV.getContext('2d');
let player = { x: 200, y: 300, vx:0, vy:0 };
let items   = []; // gyűjthető tárgyak
let score   = 0;
let frame   = 0;
const K     = {};  // lenyomott billentyűk

// === UPDATE ===
function update() {
  frame++;
  // Mozgás billentyűk alapján
  if (K['ArrowLeft'])  player.vx -= 0.5;
  if (K['ArrowRight']) player.vx += 0.5;
  player.vx *= 0.85; // súrlódás
  player.x  += player.vx;
  // Ütközés: gyűjt-e tárgyat?
  items = items.filter(item => {
    if (Math.hypot(player.x-item.x, player.y-item.y) < 25) {
      score++; return false; // eltűnik
    }
    return true;
  });
}

// === DRAW ===
function draw() {
  ctx.clearRect(0, 0, W, H);
  items.forEach(item => { drawItem(item.x, item.y); });
  drawPlayer(player.x, player.y, frame);
  // Pontszám
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 18px sans-serif';
  ctx.textAlign = 'left';
  ctx.fillText('Pont: ' + score, 10, 30);
}

// === LOOP ===
function loop() { update(); draw(); requestAnimationFrame(loop); }
loop();
✏️ Projekt Checklist
  • Canvas létrehozva, ctx megszerezte, W és H változóban
  • requestAnimationFrame loop: update() + draw() elkülönítve
  • clearRect minden draw() elején (nem marad nyom)
  • Karakter billentyűkkel irányítható (keydown/keyup, K objektum)
  • Karakter save/translate/restore-ral rajzolódik
  • Legalább egy sin() alapú animáció (lábdobogás, hullámzás)
  • Gyűjthető tárgyak Math.random pozícióban spawolva
  • Ütközésdetektálás Math.hypot-tal
  • Pontszám ctx.fillText-tel a canvason
  • Mobilon is működik (D-pad gombok HTML-ben)
Bónusz kihívások

⭐ Scroll rendszer — a pálya hosszabb mint a képernyő
⭐⭐ Ugrás implementálása (jumpT, jumpH, sin() parabolaív)
⭐⭐ Ellenség ami üldözi a játékost (AI: közeledés a játékos felé)
⭐⭐⭐ Életrendszer + Game Over képernyő