Olimp Royale
A downloadable game
========================================================================
DOCUMENTATIE TEHNICA: OLIMP ROYALE (Spartacus Edition)
========================================================================
1. INFORMATII GENERALE
------------------------------------------------------------------------
* Numele Jocului: Olimp Royale - Spartacus Edition
* Tehnologie utilizata: HTML5, CSS3, JavaScript (ES6+), Framework-ul Phaser 3 (v3.60.0 Arcade Physics)
* Gen: Top-Down 2D Action / Rogue-lite elementar
* Rezolutie ecran: 800 x 600 pixeli (centrat pe ecran)
Echipa de Dezvoltare:
* Ana Serban: Dezvoltare software (Cod), Structura arhitecturala, Documentatie tehnica si Design Grafic.
* Maria Trancioveanu: Design Grafic, Procesare asset-uri vizuale (cadre personaje).
* Alexandra Dimian: Design Grafic, Ilustratii elemente de decor si interfata.
2. CONCEPTUL SI MECANICA JOCULUI
------------------------------------------------------------------------
Jocul transpune jucatorul in Atena anului 430 i.Hr., unde retraieste o batalie mitologica prin ochii a trei zei selectabili din Olimp. Obiectivul principal este supravietuirea in fata valurilor de monstri si infrangerea unui Boss final.
Mecanica de Combat (Rock-Paper-Scissors):
Inamicii normali si Boss-ul au vulnerabilitati elementare stricte. Jucatorul trebuie sa schimbe dinamic arma sau elementul selectat folosind tastele 1, 2, 3 pentru a aplica daune:
Inamic Galben -> Vulnerabilitate: scut (Tasta 1) -> Denumire: Oglinda
Inamic Rosu -> Vulnerabilitate: foc (Tasta 2) -> Denumire: Foc
Inamic Albastru -> Vulnerabilitate: sulita (Tasta 3) -> Denumire: Sulita
Regula: Daca inamicul este lovit cu un element gresit, proiectilul este distrus fara a produce daune. Inamicii normali mor dintr-o singura lovitura corecta.
Obstacole practice (Vazele / Amforele):
Pe harta sunt generate static 8 amfore. Acestea actioneaza ca scuturi pentru inamici: daca un proiectil loveste o vaza, acesta este absorbit si distrus. Vazele se distrug la impact si reapar automat dupa un timp de reincarcare de 5 secunde.
3. PERSONAJE (ZEII DIN OLIMP)
------------------------------------------------------------------------
Fiecare zeu selectabil ofera un stil de joc diferit prin modificarea atributelor de baza:
* Zeus: Viteza echilibrata (200), proiectile foarte rapide (800) si rata de foc mare (cooldown mic de 200ms). Are o animatie complexa formata din 23 de cadre.
* Poseidon: Viteza mare de deplasare (260), proiectile lente (400) dar masive ca dimensiune (scalare 2.0). Animatie de mers din 8 cadre.
* Ares: Viteza cea mai mica (180), cooldown mare la atac (1000ms), dar atacurile sale sunt liniare, de tip sabie lunga, capabile sa strapunga si sa distruga vazele fara ca proiectilul sa dispara.
4. STRUCTURA SCENELOR (ARHITECTURA PHASER)
------------------------------------------------------------------------
Jocul este modularizat in 3 scene native Phaser care ruleaza secvential:
Scena 0: IntroScene (Prolog)
* Rol: Introducere narativa si imersie.
* Functionalitate: Ruleaza un text dramatic rand cu rand folosind un sistem recursiv (showNextLine) bazat pe efecte de tranzitie Tween (Fade In / Hold / Fade Out). La final, face blackout pe camera si porneste meniul.
Scena 1: MenuScene (Selectie)
* Rol: Interfata principala si configurare erou.
* Functionalitate: Randeaza fundalul cosmic si genereaza butoanele de selectie in mod dinamic, iterand prin proprietatile obiectului global GODS. Transmite obiectul zeului ales ca parametru de initializare catre scena urmatoare.
Scena 2: GameScene (Jocul propriu-zis)
* Miezul jocului: Gestioneaza motorul de fizica, inputul de la tastatura/mouse, coliziunile, inteligenta artificiala a inamicilor si UI-ul.
* Harta si Coliziuni: Prin functia createRestrictedBlueAreas(), se construiesc pereti invizibili (corpuri fizice statice) peste decorul grafic (temple, munti, cladiri, margini) pentru a impiedica jucatorul sau monstrii sa paraseasca drumurile pavate.
5. INTELIGENTA ARTIFICIALA SI LOGICA DE BOSS
------------------------------------------------------------------------
Inamicii Normali:
* Apar la fiecare 3 secunde in puncte sigure de pe alei (spawnEnemy).
* Daca distanta pana la jucator este mai mare de 200 de pixeli, acestia patruleaza haotic (directie unghiulara aleatorie calculata trigonometric prin cosinus si sinus).
* Daca jucatorul intra in raza de 200 de pixeli, AI-ul comuta pe urmarire agresiva spre coordonatele jucatorului.
Boss-ul Final:
* Se declanseaza fix la atingerea pragului de 20 de inamici invinsi (kills == 20).
* Are o bara dedicata de viata in partea de sus a ecranului (1500 HP).
* Abilitate speciala 1 (Slow): La fiecare 6 secunde, flash-uieste ecranul si incetineste viteza jucatorului cu 25% timp de 2 secunde.
* Abilitate speciala 2 (Adaptabilitate): La fiecare 3 secunde isi schimba aleatoriu vulnerabilitatea elementara si culoarea corespunzatoare.
* Faza a doua (Rage): Cand viata Boss-ului scade sub 750 HP, viteza lui de miscare creste de la 120 la 170.
6. CONTROL SI INTERFATA (USER INPUT)
------------------------------------------------------------------------
* Miscare: Tastele W (Sus), A (Stanga), S (Jos), D (Dreapta). Vectorul de miscare este normalizat pentru a preveni accelerarea rapida pe diagonala.
* Atac: Tasta SPACE sau Click Stanga Mouse (trage in directia cursorului).
* Schimbare elemente: Tastele 1, 2, 3.
* Meniu Ajutor: Butonul HELP (sus, dreapta) opreste complet fizica si jocul prin physics.pause(), afisand ghidul elementelor.
7. MANAGEMENTUL RESURSELOR (OPTIMIZARE)
------------------------------------------------------------------------
* Font Personalizat: Fisierul Spartacus-KVdLp.ttf este incarcat binar direct in memoria cache Phaser si injectat securizat in DOM-ul paginii web prin API-ul nativ FontFace inainte de randarea textelor din UI.
* Sistemul de Damage: La contactul cu inamicii, jucatorul pierde 25 HP si primeste o secunda de invulnerabilitate (flag-ul isHurt), timp in care sprite-ul clipeste prin modificarea opacitatii (setAlpha), prevenind moartea instantanee in acelasi cadru de procesare.
Codul jocului este:
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<title>Olimp Royale</title>
<!-- Importarea framework-ului Phaser 3 prin CDN -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
<style>
/* Resetarea marginilor și centrarea canvas-ului jocului pe ecran */
body {
margin: 0;
background: #000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
}
</style>
</head>
<body>
<script>
// ==========================================
// --- CONFIGURAȚII GLOBALE ȘI STATICE ---
// ==========================================
// Obiect ce stochează atributele unice ale fiecărui personaj (zeu) selectabil
const GODS = {
ZEUS: { name: 'Zeus', color: 0xffff00, speed: 200, bSpeed: 800, bScale: 0.6, cooldown: 200 },
POSEIDON: { name: 'Poseidon', color: 0x00aaff, speed: 260, bSpeed: 400, bScale: 2.0, cooldown: 500 },
ARES: { name: 'Ares', color: 0xff4400, speed: 180, bSpeed: 500, bScale: 1.5, cooldown: 1000 }
};
const FONT = 'Spartacus'; // Numele fontului personalizat utilizat în UI
let selectedGod = GODS.ZEUS; // Reține zeul selectat curent (implicit Zeus)
let kills = 0; // Contorizează inamicii învinși
let isBossActive = false; // Flag pentru a ști dacă bătălia cu Boss-ul a început
// ==========================================
// --- SCENA 0: PROLOG / INTRODUCERE ---
// ==========================================
class IntroScene extends Phaser.Scene {
constructor() { super('IntroScene'); }
create() {
// Declanșează povestea după un scurt delay (150ms)
this.time.delayedCall(150, () => {
const lines = [
"Atena, anul 430 i.Hr...",
"Un batran grec priveste umbrele templului.",
"In mintea sa, zeii coboara din Olimp...",
"Povestea lui incepe acum."
];
let currentLine = 0;
// Crearea obiectului text (inițial invizibil prin setAlpha(0))
let storyText = this.add.text(400, 300, '', {
fontFamily: FONT,
fontSize: '35px',
fill: '#ffd700',
align: 'center'
}).setOrigin(0.5).setAlpha(0);
// Funcție recursivă pentru afișarea textului rând cu rând (Fade In -> Hold -> Fade Out)
const showNextLine = () => {
if (currentLine < lines.length) {
storyText.setText(lines[currentLine]);
this.tweens.add({
targets: storyText,
alpha: 1,
duration: 1000,
hold: 1500,
yoyo: true, // Face fade out automat după ce trece timpul de 'hold'
onComplete: () => {
currentLine++;
showNextLine();
}
});
} else {
// Schimbă scena către meniu după ce s-au terminat replicile
this.cameras.main.fadeOut(1000, 0, 0, 0);
this.cameras.main.once('camerafadeoutcomplete', () => this.scene.start('MenuScene'));
}
};
showNextLine();
});
}
}
// ==========================================
// --- SCENA 1: MENIUL PRINCIPAL ---
// ==========================================
class MenuScene extends Phaser.Scene {
constructor() { super('MenuScene'); }
preload() {
// Încărcarea imaginii de fundal pentru meniu

}
create() {
this.add.image(400, 300, 'bg_menu').setAlpha(0.5);
this.add.text(400, 150, 'OLIMP ROYALE', { fontFamily: FONT, fontSize: '64px', fill: '#ffd700' }).setOrigin(0.5);
let y = 320; // Poziția inițială pe verticală a butoanelor
// Generarea dinamică a butoanelor de selecție pe baza obiectului GODS
Object.values(GODS).forEach(god => {
this.add.text(400, y, `Alege ${god.name}`, {
fontFamily: FONT, fontSize: '32px', fill: '#fff', backgroundColor: '#333', padding: 10
})
.setOrigin(0.5)
.setInteractive({ useHandCursor: true }) // Permite interacțiunea cu mouse-ul
.on('pointerdown', () => this.scene.start('GameScene', { god })); // Trimite zeul ales către GameScene
y += 80; // Spațiere între butoane
});
}
}
// ==========================================
// --- SCENA 2: JOCUL PROPRIU-ZIS ---
// ==========================================
class GameScene extends Phaser.Scene {
constructor() { super('GameScene'); }
// Resetează stările generale când scena repornește sau primește date noi
init(data) {
if (data.god) selectedGod = data.god;
kills = 0;
isBossActive = false;
}
preload() {
// Încărcare asset-uri de bază (Jucător temporar, proiectil, hartă și amforă)
![]()
![]()
this.load.image('game_map', 'ImaginiCuratateGameJam/AlteChestii/harta.png');
this.load.image('vase', 'ImaginiCuratateGameJam/AlteChestii/amfora.png');
// Încărcarea fontului ca fișier binar pentru procesare manuală ulterioară
this.load.binary('spartacusFont', 'spartacus-font/Spartacus-KVdLp.ttf');
// Încărcare cadre animație pentru Ares
for (let i = 1; i <= 5; i++) {
this.load.image(`ares_walk_${i}`, `ImaginiCuratateGameJam/Ares/r1/A${i}.png`);
}
// Încărcare cadre animație pentru Poseidon
this.load.image('poseidon_idle', 'ImaginiCuratateGameJam/Poseidon/r1/PIdle.png');
this.load.image('poseidon_walk_1', 'ImaginiCuratateGameJam/Poseidon/r1/P8.png');
this.load.image('poseidon_walk_2', 'ImaginiCuratateGameJam/Poseidon/r1/P9.png');
this.load.image('poseidon_walk_3', 'ImaginiCuratateGameJam/Poseidon/r1/P11.png');
this.load.image('poseidon_walk_4', 'ImaginiCuratateGameJam/Poseidon/r1/P12.png');
this.load.image('poseidon_walk_5', 'ImaginiCuratateGameJam/Poseidon/r1/P13.png');
this.load.image('poseidon_walk_6', 'ImaginiCuratateGameJam/Poseidon/r1/P15.png');
this.load.image('poseidon_walk_7', 'ImaginiCuratateGameJam/Poseidon/r1/P14.png');
this.load.image('poseidon_walk_8', 'ImaginiCuratateGameJam/Poseidon/r1/P10.png');
// Încărcare cadre animație pentru Zeus (23 de cadre de mișcare)
for (let i = 1; i <= 23; i++) {
let frameNum = i < 10 ? '0' + i : i;
this.load.image(`zeus_move_${frameNum}`, `ImaginiCuratateGameJam/Zeus/Miscare/Z${i}.png`);
}
}
create() {
// --- PROCESARE ȘI INJECTARE FONT PERSONALIZAT ---
let fontBuffer = this.cache.binary.get('spartacusFont');
let newFont = new FontFace(FONT, fontBuffer);
newFont.load().then((loadedFace) => {
document.fonts.add(loadedFace);
if(this.uiText) this.uiText.setFontFamily(FONT);
}).catch(err => console.log("Eroare incarcare font: ", err));
// Adăugare fundal hartă
let background = this.add.image(400, 300, 'game_map');
background.setDisplaySize(800, 600);
// --- CONSTRUIRE ANIMAȚII (Ares & Poseidon) ---
this.anims.create({
key: 'ares_walk',
frames: [{ key: 'ares_walk_2' }, { key: 'ares_walk_3' }, { key: 'ares_walk_4' }, { key: 'ares_walk_5' }],
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'poseidon_walk',
frames: [
{ key: 'poseidon_walk_1' }, { key: 'poseidon_walk_2' }, { key: 'poseidon_walk_3' },
{ key: 'poseidon_walk_4' }, { key: 'poseidon_walk_5' }, { key: 'poseidon_walk_6' },
{ key: 'poseidon_walk_2' }, { key: 'poseidon_walk_7' }, { key: 'poseidon_walk_8' },
{ key: 'poseidon_walk_2' }
],
frameRate: 8,
repeat: -1
});
// --- INIȚIALIZARE VARIABILE DE STARE SCENĂ ---
this.isGameOver = false;
this.isVictory = false;
this.isPaused = false;
this.lastFired = 0; // Timestamp pentru controlul ratei de foc
this.isSlowed = false; // Control debuff aplicat de Boss
this.currentElement = 'shield'; // Elementul selectat implicit (Arma activa)
// Variabile interne pentru animația manuală a lui Zeus
this.zeusFrameTime = 0;
this.zeusCurrentFrame = 1;
// Determinarea texturii inițiale în funcție de zeul ales
let initialTexture = 'player';
if (selectedGod.name === 'Zeus') initialTexture = 'zeus_move_01';
else if (selectedGod.name === 'Ares') initialTexture = 'ares_walk_1';
else if (selectedGod.name === 'Poseidon') initialTexture = 'poseidon_idle';
// Crearea sprite-ului jucătorului cu proprietăți fizice (Arcade Physics)
this.player = this.physics.add.sprite(400, 480, initialTexture).setCollideWorldBounds(true);
// --- AJUSTARE HITBOX-URI ȘI SCALE ÎN FUNCȚIE DE SPREE-UL ZEULUI ---
if (selectedGod.name === 'Zeus') {
this.player.setScale(0.15);
this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.4);
this.player.body.setOffset(this.player.width * 0.3, this.player.height * 0.3);
} else if (selectedGod.name === 'Ares') {
this.player.setScale(0.5);
this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.4);
this.player.body.setOffset(this.player.width * 0.3, this.player.height * 0.3);
} else if (selectedGod.name === 'Poseidon') {
this.player.setScale(0.75);
this.player.body.setSize(this.player.width * 0.4, this.player.height * 0.7);
this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);
}
this.player.health = 100; // Sănătate inițială jucător
// --- GRUPURI FIZICE ---
this.enemies = this.physics.add.group(); // Grup dinamic inamici
this.projectiles = this.physics.add.group(); // Grup dinamic proiectile
this.destructibles = this.physics.add.staticGroup(); // Grup static amfore (vase)
this.obstacles = this.add.group(); // Grup obstacole invizibile
this.physics.world.enable(this.obstacles, Phaser.Physics.Arcade.STATIC_BODY); // Activare corpuri statice
// Apelare funcție creare pereți invizibili
this.createRestrictedBlueAreas();
// Spawn amfore inițiale
for (let i = 0; i < 8; i++) { this.spawnVase(); }
// Buton UI de ajutor/pauză
this.helpBtn = this.add.text(720, 30, ' HELP ', {
fontFamily: FONT, fontSize: '25px', fill: '#000', backgroundColor: '#ffd700', padding: 8
}).setInteractive({ useHandCursor: true }).setDepth(2000).on('pointerdown', () => this.toggleHelp());
this.setupUI();
// Maparea controalelor de la tastatură
this.keys = this.input.keyboard.addKeys('W,A,S,D,ONE,TWO,THREE,SPACE');
// --- COLIZIUNI ȘI SUPRAPUNERI (PHYSICS OVERLAPS & COLLIDERS) ---
this.physics.add.collider(this.player, this.obstacles);
this.physics.add.collider(this.enemies, this.obstacles);
this.physics.add.collider(this.projectiles, this.obstacles, this.hitWall, null, this);
this.physics.add.overlap(this.projectiles, this.enemies, this.handleCombat, null, this);
this.physics.add.overlap(this.player, this.enemies, this.takeDamage, null, this);
this.physics.add.overlap(this.projectiles, this.destructibles, this.hitVase, null, this);
// Generator de inamici la fiecare 3 secunde
this.time.addEvent({ delay: 3000, loop: true, callback: this.spawnEnemy, callbackScope: this });
}
// Creează zone invizibile (coliziuni) pe hartă bazate pe elementele grafice din fundal
createRestrictedBlueAreas() {
let createWall = (x, y, w, h) => {
let zone = this.add.zone(x, y, w, h);
this.physics.add.existing(zone, true);
zone.body.immovable = true;
zone.body.moves = false;
this.obstacles.add(zone);
};
// 1. Zona Templului de Sus + Fortificația Nordică
createWall(400, 75, 520, 120);
createWall(215, 175, 45, 140);
createWall(350, 355, 50, 50);
createWall(460, 360, 50, 50);
createWall(585, 300, 200, 100);
// 2. Fortificația în formă de V din mijloc
createWall(250, 275, 50, 45);
createWall(300, 300, 50, 40);
// 3. Masivul muntos din colțul din Dreapta-Sus
createWall(725, 230, 150, 420);
// 4. Templul complex din colțul din Stânga-Jos
createWall(50, 350, 75, 150);
// 5. Clădirea din Sud și zona adiacentă (Mijloc-Jos)
createWall(500, 550, 300, 100);
// 6. Copacii și chiparoșii de pe marginea drumurilor
createWall(50, 155, 100, 60);
createWall(205, 500, 45, 65);
// 7. Marginile exterioare ale hărții (bariere)
createWall(10, 582, 20, 1500);
createWall(16, 582, 1750, 4);
createWall(790, 582, 20, 1500);
createWall(30, 18, 1750, 4);
}
// Configurare panouri UI, text și ecrane de Game Over / Victorie
setupUI() {
this.uiText = this.add.text(16, 16, '', { fontFamily: FONT, fontSize: '25px', fill: '#ffd700' }).setDepth(100);
// Bara de viață pentru boss (ascunsă inițial)
this.bossBarBG = this.add.rectangle(400, 50, 400, 20, 0x333333).setVisible(false).setDepth(150);
this.bossHealthBar = this.add.rectangle(400, 50, 400, 20, 0xff0000).setVisible(false).setDepth(151);
this.slowText = this.add.text(400, 100, "TIMPUL SE INCETINESTE...", { fontFamily: FONT, fontSize: '35px', fill: '#00ffff' }).setOrigin(0.5).setVisible(false).setDepth(200);
// Container UI pentru ecranul de final (Ascuns la început)
this.endScreen = this.add.container(400, 300).setVisible(false).setDepth(4000);
this.endScreen.add([
this.add.rectangle(0, 0, 800, 600, 0x000000, 0.85),
this.endText = this.add.text(0, -50, "", { fontFamily: FONT, fontSize: '38px', align: 'center', fill: '#fff' }).setOrigin(0.5),
this.add.text(0, 70, " REINCEARCA ", { fontFamily: FONT, fontSize: '30px', backgroundColor: '#ffd700', fill: '#000', padding: 10 }).setOrigin(0.5).setInteractive().on('pointerdown', () => this.scene.start('MenuScene'))
]);
// Container UI pentru fereastra de ajutor (Manualul eroului)
this.helpContainer = this.add.container(400, 300).setVisible(false).setDepth(3000);
const hBg = this.add.rectangle(0, 0, 600, 450, 0x000000, 0.95).setStrokeStyle(3, 0xffd700);
const hTxt = this.add.text(0, 0,
"=== MANUALUL EROULUI ===\n\n" +
"Inamicul Galben -> [1] OGLINDA\n" +
"Inamicul Rosu -> [2] FOC\n" +
"Inamicul Albastru -> [3] SULITA\n\n" +
"BOSS apare la 20 kills.\n\n" +
"WASD: Miscare | Space: Atac",
{ fontFamily: FONT, fontSize: '25px', fill: '#fff', align: 'center', lineSpacing: 10 }
).setOrigin(0.5);
this.helpContainer.add([hBg, hTxt]);
}
// Activează sau dezactivează pauza și manualul de ajutor
toggleHelp() {
if (this.isGameOver || this.isVictory) return;
this.isPaused = !this.isPaused;
this.helpContainer.setVisible(this.isPaused);
if (this.isPaused) this.physics.pause(); else this.physics.resume();
}
// Bucla principală de actualizare a stării jocului (randată la fiecare cadru)
update(time, delta) {
if (this.isGameOver || this.isVictory || this.isPaused) return;
// Aplică încetinirea dacă Boss-ul a activat abilitatea
let speed = this.isSlowed ? selectedGod.speed * 0.75 : selectedGod.speed;
let moveDirection = new Phaser.Math.Vector2(0, 0);
let isMoving = false;
// Citirea tastelor direcționale și întoarcerea vizuală a sprite-ului (FlipX)
if (this.keys.A.isDown) { moveDirection.x = -1; isMoving = true; this.player.setFlipX(true); }
else if (this.keys.D.isDown) { moveDirection.x = 1; isMoving = true; this.player.setFlipX(false); }
if (this.keys.W.isDown) { moveDirection.y = -1; isMoving = true; }
else if (this.keys.S.isDown) { moveDirection.y = 1; isMoving = true; }
// Calcularea vectorului de mișcare normalizat pentru a preveni deplasarea mai rapidă pe diagonală
if (isMoving) {
moveDirection.normalize();
this.player.setVelocityX(moveDirection.x * speed);
this.player.setVelocityY(moveDirection.y * speed);
} else {
this.player.setVelocity(0); // Oprește jucătorul dacă nu apasă nicio tastă
}
// --- SISTEM MANAGEMENT ANIMAȚII (Diferit pentru fiecare Zeu) ---
if (selectedGod.name === 'Zeus') {
// Zeus nu folosește sistemul standard `.play()`, ci își schimbă texturile manual bazat pe delta time
if (isMoving) {
this.zeusFrameTime += delta * 0.75;
if (this.zeusFrameTime >= 65) { // Schimbă cadrul la fiecare 65ms
this.zeusFrameTime = 0;
this.zeusCurrentFrame = (this.zeusCurrentFrame % 23) + 1;
let frameNum = this.zeusCurrentFrame < 10 ? '0' + this.zeusCurrentFrame : this.zeusCurrentFrame;
this.player.setTexture(`zeus_move_${frameNum}`);
}
} else {
this.player.setTexture('zeus_move_01'); // Revine la cadrul static
this.zeusCurrentFrame = 1;
this.zeusFrameTime = 0;
}
} else if (selectedGod.name === 'Ares') {
if (isMoving) this.player.play('ares_walk', true);
else { this.player.stop(); this.player.setTexture('ares_walk_1'); }
} else if (selectedGod.name === 'Poseidon') {
if (isMoving) {
this.player.setScale(0.75);
this.player.body.setSize(this.player.width * 0.5, this.player.height * 0.8);
this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);
this.player.play('poseidon_walk', true);
} else {
this.player.stop();
this.player.setScale(0.75);
this.player.body.setSize(this.player.width * 0.5, this.player.height * 0.8);
this.player.body.setOffset(this.player.width * 0.25, this.player.height * 0.1);
this.player.setTexture('poseidon_idle');
}
}
// --- COMANDE SCHIMBARE ARMĂ ȘI ATAC ---
if (Phaser.Input.Keyboard.JustDown(this.keys.ONE)) this.currentElement = 'shield';
if (Phaser.Input.Keyboard.JustDown(this.keys.TWO)) this.currentElement = 'fire';
if (Phaser.Input.Keyboard.JustDown(this.keys.THREE)) this.currentElement = 'spear';
// Executare atac prin Space sau Click Stânga Mouse, ținând cont de Cooldown
if ((this.keys.SPACE.isDown || this.input.activePointer.isDown) && time > this.lastFired) this.fire(time);
// --- INTELIGENȚA ARTIFICIALĂ A INAMICILOR (Urmărire / Mișcare Random) ---
this.enemies.getChildren().forEach(e => {
if (!e.active) return;
if (isBossActive && e.isBoss) {
// Boss-ul urmărește agresiv jucătorul, accelerând când are sub jumătate din viață (750 HP)
this.physics.moveToObject(e, this.player, e.health < 750 ? 170 : 120);
} else {
// Inamicii normali urmăresc jucătorul doar dacă acesta se află la o distanță mai mică de 200 pixeli
if (Phaser.Math.Distance.Between(this.player.x, this.player.y, e.x, e.y) < 200) {
this.physics.moveToObject(e, this.player, 100);
} else if (time > (e.nextMove || 0) || e.body.velocity.x === 0) {
// Dacă jucătorul e departe, merg random în direcții aleatorii calculate la intervale de 2-4 secunde
let angle = Phaser.Math.FloatBetween(0, Math.PI * 2);
e.setVelocity(Math.cos(angle) * 70, Math.sin(angle) * 70);
e.nextMove = time + Phaser.Math.Between(2000, 4000);
}
}
});
// Actualizare dinamică a dimensiunii barei de viață a Boss-ului
if (isBossActive && this.bossEntity) this.bossHealthBar.width = 400 * (this.bossEntity.health / 1500);
// Actualizare text UI info
const names = { shield: 'Oglinda', fire: 'Foc', spear: 'Sulita' };
this.uiText.setText(`HP: ${this.player.health} | Kills: ${kills}\nArma: ${names[this.currentElement]}`);
}
// Funcția care generează un proiectil orientat spre coordonatele cursorului de mouse
fire(time) {
let p = this.projectiles.create(this.player.x, this.player.y, 'bullet');
p.weaponType = this.currentElement; // Stochează tipul elementului pe proiectil
let target = this.input.activePointer;
// Ares are o mecanică specială: atacurile sale devin lame lungi/subțiri orientate spre țintă
if (selectedGod.name === 'Ares') {
p.setScale(3.5, 0.2);
p.setRotation(Phaser.Math.Angle.Between(this.player.x, this.player.y, target.worldX, target.worldY));
} else p.setScale(selectedGod.bScale);
// Colorează proiectilul în funcție de elementul ales
p.setTint({ shield: 0xffff00, fire: 0xff0000, spear: 0x0000ff }[p.weaponType]);
// Pune în mișcare proiectilul către țintă cu viteza proprie a zeului ales
this.physics.moveTo(p, target.worldX, target.worldY, selectedGod.bSpeed);
// Actualizează timestamp-ul pentru următorul atac permis (respectând cooldown-ul)
this.lastFired = time + selectedGod.cooldown;
}
// Distruge proiectilul dacă acesta lovește un perete/zonă restricționată
hitWall(obj1, obj2) {
if (obj1 && obj1.weaponType) obj1.destroy();
else if (obj2 && obj2.weaponType) obj2.destroy();
}
// Gestionarea luptei: Verifică algoritmul de tip Piatră-Foarfecă-Hârtie (Element vs Vulnerabilitate)
handleCombat(p, e) {
if (p.weaponType === e.vulnerability) {
if (isBossActive && e.isBoss) {
// Cazul în care inamicul lovit este Boss-ul
e.health -= 50;
p.destroy();
this.cameras.main.shake(100, 0.002); // Efect de cutremur pe ecran la impact
if (e.health <= 0) this.triggerVictory();
} else {
// Cazul inamicilor standard (mor dintr-o lovitură corectă)
e.destroy();
p.destroy();
kills++;
if (kills === 20 && !isBossActive) this.spawnBoss();
}
} else p.destroy(); // Dacă elementul nu se potrivește, proiectilul se distruge fără daune
}
// Curăță variabilele aferente bătăliei cu boss-ul și deschide ecranul de Victorie
triggerVictory() {
this.isVictory = true;
this.physics.pause();
if (this.bossColorTimer) this.bossColorTimer.remove();
if (this.bossSlowTimer) this.bossSlowTimer.remove();
if (this.bossEntity) { this.bossEntity.destroy(); this.bossEntity = null; }
isBossActive = false;
this.bossBarBG.setVisible(false);
this.bossHealthBar.setVisible(false);
this.slowText.setVisible(false);
this.endText.setText("BOSS INVINS!\nLegenda va dainui peste veacuri.");
this.endScreen.setVisible(true);
}
// Instanțierea și configurarea Boss-ului final
spawnBoss() {
isBossActive = true;
this.bossEntity = this.enemies.create(400, 200, 'player');
this.bossEntity.setScale(5).setTint(0xffffff).setCollideWorldBounds(true).setBounce(1);
this.bossEntity.body.setSize(20, 20).setOffset(5, 5);
this.bossEntity.health = 1500;
this.bossEntity.isBoss = true;
this.bossEntity.vulnerability = 'shield'; // Vulnerabilitate inițială
this.bossBarBG.setVisible(true);
this.bossHealthBar.setVisible(true);
// Timer buclă: La fiecare 6 secunde, boss-ul încetinește jucătorul timp de 2 secunde (Debuff)
this.bossSlowTimer = this.time.addEvent({
delay: 6000, loop: true,
callback: () => {
if (!this.bossEntity || this.isGameOver) return;
this.cameras.main.flash(400, 0, 200, 255, 0.5);
this.isSlowed = true;
this.slowText.setVisible(true);
this.player.setAlpha(0.7);
this.time.delayedCall(2000, () => {
this.isSlowed = false;
this.slowText.setVisible(false);
this.player.setAlpha(1);
});
}
});
// Timer buclă: La fiecare 3 secunde, boss-ul își schimbă culoarea și elementul la care este vulnerabil
this.bossColorTimer = this.time.addEvent({
delay: 3000, loop: true,
callback: () => {
if (!this.bossEntity) return;
this.bossEntity.vulnerability = Phaser.Math.RND.pick(['shield', 'fire', 'spear']);
this.bossEntity.setTint({ shield: 0xffff00, fire: 0xff0000, spear: 0x0000ff }[this.bossEntity.vulnerability]);
}
});
}
// Spawnează inamici simpli în mod aleatoriu doar în zone/drumuri sigure (fără pereți)
spawnEnemy() {
if (isBossActive || this.isGameOver || this.isVictory || this.isPaused) return;
let drumuriSigure = [
{x: 400, y: 460}, {x: 180, y: 250}, {x: 620, y: 350}, {x: 400, y: 310}
];
let punctAles = Phaser.Math.RND.pick(drumuriSigure);
// Creare sprite inamic cu deviație mică de poziție (+/- 15px)
let e = this.enemies.create(punctAles.x + Phaser.Math.Between(-15, 15), punctAles.y + Phaser.Math.Between(-15, 15), 'player');
// Alegerea aleatorie a tipului de inamic (Culoare și Vulnerabilitate atașată)
let t = Phaser.Math.RND.pick([{w:'shield',c:0xffff00}, {w:'fire',c:0xff0000}, {w:'spear',c:0x0000ff}]);
e.setTint(t.c).setCollideWorldBounds(true).setBounce(1).vulnerability = t.w;
}
// Generarea amforelor decorative și distructibile pe hartă
spawnVase() {
let locatiiSigureAmfore = [
{x: 330, y: 460}, {x: 470, y: 460}, {x: 400, y: 270}, {x: 180, y: 190},
{x: 620, y: 190}, {x: 230, y: 460}, {x: 570, y: 460}, {x: 400, y: 325}
];
// Folosește lungimea curentă a grupului pentru a distribui amforele uniform în vectorul de locații
let coord = locatiiSigureAmfore[this.destructibles.getLength() % locatiiSigureAmfore.length];
let v = this.destructibles.create(coord.x, coord.y, 'vase');
v.setScale(0.35);
v.body.setSize(v.width * 0.8, v.height * 0.9);
v.refreshBody(); // Obligatoriu pentru corpurile statice după setScale pentru a realinia hitbox-ul
}
// Logica distrugerii unei amfore. Se reîntoarce pe hartă (Respawn) după 5 secunde
hitVase(p, v) {
if (selectedGod.name !== 'Ares') p.destroy(); // Proiectilele lui Ares penetrează obiectele distructibile
v.disableBody(true, true); // O face invizibilă și îi oprește fizica
this.time.delayedCall(5000, () => {
if (!this.isGameOver && !this.isVictory) v.enableBody(true, v.x, v.y, true, true);
});
}
// Gestionarea daunelor încasate de jucător la contactul direct cu inamicii/boss-ul
takeDamage(p, e) {
// Verifică dacă jucătorul nu este deja într-o stare temporară de invulnerabilitate (`isHurt`)
if (!p.isHurt && !this.isGameOver && !this.isVictory) {
p.isHurt = true;
p.health -= 25; // Scade viața cu 25 unități
// Feedback vizual diferențiat în funcție de zeul controlat
if (selectedGod.name === 'Ares' || selectedGod.name === 'Poseidon') {
this.time.delayedCall(100, () => { p.setAlpha(0.5); });
this.time.delayedCall(300, () => { p.setAlpha(1); });
} else if (selectedGod.name !== 'Zeus') {
p.setTint(0xff0000); // Se colorează în roșu complet
}
// Condiție de eșec (Sănătatea ajunge la 0)
if (p.health <= 0) {
this.isGameOver = true;
this.endText.setText("VISUL S-A SFARSIT...");
this.endScreen.setVisible(true);
this.physics.pause();
} else {
// Oprirea efectului de avarie și resetarea culorilor după 1 secundă (fereastra de invulnerabilitate)
this.time.delayedCall(1000, () => {
if (selectedGod.name === 'Ares' || selectedGod.name === 'Poseidon') {
p.setAlpha(1);
} else if (selectedGod.name !== 'Zeus') {
p.clearTint();
p.setTint(selectedGod.color);
}
p.isHurt = false;
});
}
}
}
}
// ==========================================
// --- CONFIGURARE ENGINE NATIVĂ ---
// ==========================================
const config = {
type: Phaser.AUTO, // Încearcă randare WebGL, altfel face fallback pe Canvas 2D
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: false // Setat pe 'false' pentru a ascunde liniile verzi de debug ale hitbox-urilor
}
},
scene: [IntroScene, MenuScene, GameScene] // Înregistrarea scenelor în ordinea rulării
};
// Pornirea propriu-zisă a instanței de joc Phaser
new Phaser.Game(config);
</script>
</body>
</html>
| Published | 22 days ago |
| Status | Released |
| Author | Die Coding |
Download
Install instructions
Pentru a juca jocul, trebuie sa dezarhivati fisierul zip si sa pastrati toate fisierele in folderul in care sunt.
Leave a comment
Log in with itch.io to leave a comment.