3d-model-playground / game.html
Neuroblade's picture
How can play
f56e39d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eldoria: Sword & Sorcery</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
#ui {
position: absolute;
bottom: 20px;
left: 20px;
color: white;
font-family: Arial;
}
#health-bar {
width: 200px;
height: 20px;
background-color: #333;
border-radius: 10px;
overflow: hidden;
}
#health-fill {
height: 100%;
width: 100%;
background-color: #4CAF50;
transition: width 0.3s;
}
#power-bar {
margin-top: 10px;
width: 200px;
height: 20px;
background-color: #333;
border-radius: 10px;
overflow: hidden;
}
#power-fill {
height: 100%;
width: 100%;
background-color: #2196F3;
transition: width 0.3s;
}
#controls {
position: absolute;
right: 20px;
bottom: 20px;
}
.btn {
width: 60px;
height: 60px;
background-color: rgba(255,255,255,0.3);
border-radius: 50%;
display: inline-block;
margin: 5px;
text-align: center;
line-height: 60px;
color: white;
font-size: 24px;
user-select: none;
}
#mobile-ui { display: none; }
@media (max-width: 768px) {
#mobile-ui { display: block; }
}
</style>
</head>
<body>
<div id="story-modal" class="fixed inset-0 bg-black bg-opacity-80 z-50 flex items-center justify-center hidden">
<div class="bg-gray-800 p-6 rounded-lg max-w-2xl mx-4">
<h2 class="text-2xl text-yellow-400 mb-4">The Fall of Eldoria</h2>
<div id="story-text" class="text-white mb-4">
The dark sorcerer Malakar has taken over Eldoria, turning its lands into his shadow empire. You are Kael, the last warrior-mage, wielding both sword and magic to defeat Malakar's forces. Survive his endless waves of minions and face him in the final battle...
</div>
<button id="close-story" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded">Begin Journey</button>
</div>
</div>
<div id="ui">
<div class="flex items-center">
<div class="mr-4">
<div>Health: <span id="health-text">100</span></div>
<div id="health-bar"><div id="health-fill"></div></div>
</div>
<div>
<div>Power: <span id="power-text">100</span></div>
<div id="power-bar"><div id="power-fill"></div></div>
</div>
<button id="story-btn" class="ml-4 bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded">Story</button>
<div id="element-display" class="ml-4 flex">
<div class="w-8 h-8 rounded-full bg-red-500 mx-1" title="Fire"></div>
<div class="w-8 h-8 rounded-full bg-blue-500 mx-1" title="Water"></div>
<div class="w-8 h-8 rounded-full bg-green-500 mx-1" title="Earth"></div>
<div class="w-8 h-8 rounded-full bg-gray-300 mx-1" title="Air"></div>
</div>
</div>
</div>
<div id="mobile-ui">
<div id="controls" class="flex">
<div class="btn" id="move-left-btn"></div>
<div class="btn" id="move-right-btn"></div>
<div class="btn" id="move-up-btn"></div>
<div class="btn" id="move-down-btn"></div>
<div class="btn bg-red-500" id="attack-btn">A</div>
<div class="btn bg-orange-500" id="power-btn">P</div>
</div>
</div>
<script>
// Game setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
scene.add(directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(30, 30);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Hero (Kael) - Warrior Mage
const heroGeometry = new THREE.BoxGeometry(0.6, 1.8, 0.6);
const heroMaterial = new THREE.MeshStandardMaterial({ color: 0x3333AA });
const hero = new THREE.Mesh(heroGeometry, heroMaterial);
hero.position.y = 0.9;
hero.castShadow = true;
scene.add(hero);
// Add sword to hero
const swordGeometry = new THREE.BoxGeometry(0.1, 1.5, 0.1);
const swordBladeGeometry = new THREE.BoxGeometry(0.3, 0.1, 0.1);
const swordMaterial = new THREE.MeshStandardMaterial({ color: 0xCCCCCC });
const sword = new THREE.Mesh(swordGeometry, swordMaterial);
const swordBlade = new THREE.Mesh(swordBladeGeometry, swordMaterial);
sword.position.set(0.5, 0.5, 0);
swordBlade.position.set(0.5, 1.25, 0);
hero.add(sword);
hero.add(swordBlade);
// Sword animation variables
let isSwinging = false;
let swingProgress = 0;
const maxSwingAngle = Math.PI * 1.5;
// Story elements
document.getElementById('story-btn').addEventListener('click', () => {
document.getElementById('story-modal').classList.remove('hidden');
});
document.getElementById('close-story').addEventListener('click', () => {
document.getElementById('story-modal').classList.add('hidden');
});
// Element switching
document.addEventListener('keydown', (e) => {
if (e.key === '1') changeElement('fire');
if (e.key === '2') changeElement('water');
if (e.key === '3') changeElement('earth');
if (e.key === '4') changeElement('air');
});
function changeElement(element) {
activeElement = element;
heroMaterial.color.setHex(elementColors[element]);
updateElementDisplay();
}
function updateElementDisplay() {
const elements = document.querySelectorAll('#element-display div');
elements.forEach((el, index) => {
if (Object.keys(elementColors)[index] === activeElement) {
el.classList.add('ring-2', 'ring-yellow-400');
} else {
el.classList.remove('ring-2', 'ring-yellow-400');
}
});
}
// Movement controls
document.addEventListener('keydown', (e) => {
if (keys.hasOwnProperty(e.key)) keys[e.key] = true;
});
document.addEventListener('keyup', (e) => {
if (keys.hasOwnProperty(e.key)) keys[e.key] = false;
});
if (isMobile) {
const mobileBtns = ['move-left-btn', 'move-right-btn', 'move-up-btn', 'move-down-btn'];
mobileBtns.forEach(btn => {
const element = document.getElementById(btn);
element.addEventListener('touchstart', () => {
keys[`Arrow${btn.split('-')[1].charAt(0).toUpperCase() + btn.split('-')[1].slice(1)}`] = true;
});
element.addEventListener('touchend', () => {
keys[`Arrow${btn.split('-')[1].charAt(0).toUpperCase() + btn.split('-')[1].slice(1)}`] = false;
});
});
}
// Game variables
let health = 100;
let power = 100;
let enemies = [];
let isMobile = window.innerWidth <= 768;
let heroSpeed = 0.2;
let activeElement = 'fire'; // fire, water, earth, air
const elementColors = {
fire: 0xFF4500,
water: 0x4169E1,
earth: 0x8B4513,
air: 0x87CEEB
};
let keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false
};
// Villain - Malakar
const createMalakar = () => {
const malakarGeometry = new THREE.BoxGeometry(1.2, 2.5, 1.2);
const malakarMaterial = new THREE.MeshStandardMaterial({ color: 0x990000 });
const malakar = new THREE.Mesh(malakarGeometry, malakarMaterial);
malakar.position.set(0, 1.25, -15);
malakar.castShadow = true;
// Dark aura
const auraGeometry = new THREE.SphereGeometry(1.5, 16, 16);
const auraMaterial = new THREE.MeshBasicMaterial({
color: 0x330000,
transparent: true,
opacity: 0.3
});
const aura = new THREE.Mesh(auraGeometry, auraMaterial);
malakar.add(aura);
scene.add(malakar);
return {
mesh: malakar,
health: 500,
isActive: false,
attackCooldown: 0
};
};
const malakar = createMalakar();
// Create enemies
function createEnemy() {
if (malakar.isActive) return; // Don't spawn minions during boss fight
const types = ['sorcerer', 'wraith', 'beast', 'knight'];
const type = types[Math.floor(Math.random() * types.length)];
let geometry, material;
switch(type) {
case 'sorcerer':
geometry = new THREE.ConeGeometry(0.5, 1.8, 4);
material = new THREE.MeshStandardMaterial({ color: 0x8B0000 });
break;
case 'wraith':
geometry = new THREE.SphereGeometry(0.6, 16, 16);
material = new THREE.MeshStandardMaterial({ color: 0x4B0082, transparent: true, opacity: 0.7 });
break;
case 'beast':
geometry = new THREE.BoxGeometry(1, 1.2, 1.5);
material = new THREE.MeshStandardMaterial({ color: 0x556B2F });
break;
case 'knight':
geometry = new THREE.BoxGeometry(0.8, 1.8, 0.8);
material = new THREE.MeshStandardMaterial({ color: 0x888888 });
break;
}
const enemy = new THREE.Mesh(geometry, material);
// Random position around hero (ensure not too close)
let angle, radius, x, z;
do {
angle = Math.random() * Math.PI * 2;
radius = 5 + Math.random() * 10;
x = Math.cos(angle) * radius;
z = Math.sin(angle) * radius;
} while (Math.abs(x - hero.position.x) < 3 && Math.abs(z - hero.position.z) < 3);
enemy.position.set(x, type === 'wraith' ? 1.5 : 0.75, z);
enemy.castShadow = true;
// Add floating animation for wraiths
if (type === 'wraith') {
enemy.userData.floatOffset = Math.random() * Math.PI * 2;
}
scene.add(enemy);
enemies.push({
mesh: enemy,
type: type,
speed: type === 'wraith' ? 0.3 + Math.random() * 0.3 :
type === 'beast' ? 0.7 + Math.random() * 0.5 : 0.5 + Math.random() * 0.3,
damage: type === 'sorcerer' ? 15 + Math.floor(Math.random() * 10) :
type === 'beast' ? 20 + Math.floor(Math.random() * 15) : 10 + Math.floor(Math.random() * 5),
health: type === 'sorcerer' ? 40 + Math.floor(Math.random() * 20) :
type === 'beast' ? 60 + Math.floor(Math.random() * 30) :
type === 'knight' ? 80 + Math.floor(Math.random() * 40) : 30 + Math.floor(Math.random() * 15),
damage: type === 'sorcerer' ? 15 + Math.floor(Math.random() * 10) :
type === 'beast' ? 20 + Math.floor(Math.random() * 15) :
type === 'knight' ? 25 + Math.floor(Math.random() * 20) : 10 + Math.floor(Math.random() * 5)
});
}
// Sword attack
function swordAttack() {
if (isSwinging) return;
isSwinging = true;
swingProgress = 0;
// Melee hit detection
const swordRange = 2;
enemies.forEach((enemy, index) => {
const distance = hero.position.distanceTo(enemy.mesh.position);
if (distance < swordRange) {
enemy.health -= 30; // Sword does more base damage
if (enemy.health <= 0) {
scene.remove(enemy.mesh);
enemies.splice(index, 1);
}
}
});
}
// Magic attack function
function magicAttack() {
if (power >= 10) {
power -= 10;
updateUI();
// Element-based magic attack
let effectColor, damage, range;
switch(activeElement) {
case 'fire':
effectColor = 0xFF4500;
damage = 20;
range = 3;
break;
case 'water':
effectColor = 0x4169E1;
damage = 15;
range = 4;
break;
case 'earth':
effectColor = 0x8B4513;
damage = 25;
range = 2.5;
break;
case 'air':
effectColor = 0x87CEEB;
damage = 10;
range = 5;
break;
}
// Visual effect
const sphereGeometry = new THREE.SphereGeometry(0.5, 16, 16);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: effectColor, transparent: true, opacity: 0.7 });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.copy(hero.position);
sphere.position.y += 1;
scene.add(sphere);
// Damage enemies in range
enemies.forEach((enemy, index) => {
const distance = hero.position.distanceTo(enemy.mesh.position);
if (distance < range) {
enemy.health -= damage;
if (enemy.health <= 0) {
scene.remove(enemy.mesh);
enemies.splice(index, 1);
}
}
});
// Animate and remove sphere
const scale = { val: 0.5 };
anime({
targets: scale,
val: range,
duration: 300,
easing: 'easeOutQuad',
update: () => {
sphere.scale.set(scale.val, scale.val, scale.val);
},
complete: () => {
scene.remove(sphere);
}
});
}
}
// Power attack
function powerAttack() {
if (power >= 30) {
power -= 30;
updateUI();
// Element-based power attack
let effectColor, damage, range, particles;
switch(activeElement) {
case 'fire':
effectColor = 0xFF8C00;
damage = 50;
range = 5;
particles = 15;
break;
case 'water':
effectColor = 0x1E90FF;
damage = 40;
range = 6;
particles = 20;
break;
case 'earth':
effectColor = 0xA0522D;
damage = 60;
range = 4.5;
particles = 10;
break;
case 'air':
effectColor = 0xADD8E6;
damage = 30;
range = 7;
particles = 25;
break;
}
// Main sphere
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: effectColor, transparent: true, opacity: 0.6 });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.copy(hero.position);
sphere.position.y += 1;
scene.add(sphere);
// Create particles
for (let i = 0; i < particles; i++) {
const particleGeo = new THREE.SphereGeometry(0.1 + Math.random() * 0.2);
const particleMat = new THREE.MeshBasicMaterial({ color: effectColor });
const particle = new THREE.Mesh(particleGeo, particleMat);
// Random direction
const angle = Math.random() * Math.PI * 2;
const speed = 0.05 + Math.random() * 0.1;
particle.userData = {
direction: new THREE.Vector3(
Math.cos(angle) * speed,
(Math.random() - 0.5) * 0.1,
Math.sin(angle) * speed
),
lifetime: 0
};
particle.position.copy(hero.position);
particle.position.y += 1;
scene.add(particle);
// Remove after animation
setTimeout(() => {
if (particle.parent) scene.remove(particle);
}, 800);
}
// Damage enemies
enemies.forEach((enemy, index) => {
const distance = hero.position.distanceTo(enemy.mesh.position);
if (distance < range) {
enemy.health -= damage;
if (enemy.health <= 0) {
scene.remove(enemy.mesh);
enemies.splice(index, 1);
} else {
// Knockback effect
const dir = new THREE.Vector3().subVectors(enemy.mesh.position, hero.position).normalize();
enemy.mesh.position.add(dir.multiplyScalar(2));
}
}
});
// Animate sphere
anime({
targets: sphere.scale,
x: range,
y: range,
z: range,
duration: 500,
easing: 'easeOutQuad',
complete: () => {
scene.remove(sphere);
}
});
// Animate particles in game loop
scene.children.forEach(child => {
if (child.userData?.direction) {
child.position.add(child.userData.direction);
child.userData.lifetime++;
child.material.opacity = 1 - (child.userData.lifetime / 50);
}
});
}
}
// Update UI
function updateUI() {
document.getElementById('health-text').textContent = Math.max(0, Math.floor(health));
document.getElementById('health-fill').style.width = `${Math.max(0, health)}%`;
document.getElementById('power-text').textContent = Math.max(0, Math.floor(power));
document.getElementById('power-fill').style.width = `${Math.max(0, power)}%`;
// Update level display
const level = Math.floor(gameTime / 60000) + 1;
document.getElementById('level-display').textContent = `Level: ${level}`;
}
// Handle controls
function initControls() {
// Add help key
document.addEventListener('keydown', (e) => {
if (e.key === 'h' || e.key === 'H') {
const display = document.getElementById('controls-display');
display.style.display = display.style.display === 'none' ? 'block' : 'none';
}
});
document.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Spacebar') swordAttack();
if (e.key === 'm' || e.key === 'M') magicAttack();
if (e.key === 'p' || e.key === 'P') powerAttack();
});
if (isMobile) {
document.getElementById('attack-btn').addEventListener('touchstart', (e) => {
e.preventDefault();
swordAttack();
});
document.getElementById('power-btn').addEventListener('touchstart', (e) => {
e.preventDefault();
powerAttack();
});
}
}
initControls();
// Show story on load
document.getElementById('story-modal').classList.remove('hidden');
updateElementDisplay();
// Add controls help to story modal
document.getElementById('story-text').innerHTML += `
<br><br><strong>Controls:</strong><br>
Arrow Keys: Move<br>
Space: Sword Attack<br>
M: Magic Attack<br>
P: Power Attack<br>
1-4: Change Elements (Fire/Water/Earth/Air)<br><br>
Press H anytime to toggle this help.
`;
// Add controls display
const controlsDisplay = document.createElement('div');
controlsDisplay.id = 'controls-display';
controlsDisplay.style.position = 'absolute';
controlsDisplay.style.top = '20px';
controlsDisplay.style.right = '20px';
controlsDisplay.style.color = 'white';
controlsDisplay.style.fontFamily = 'Arial';
controlsDisplay.style.fontSize = '14px';
controlsDisplay.style.backgroundColor = 'rgba(0,0,0,0.5)';
controlsDisplay.style.padding = '10px';
controlsDisplay.style.borderRadius = '5px';
controlsDisplay.innerHTML = `
<strong>Controls:</strong><br>
Arrow Keys: Move<br>
Space: Sword Attack<br>
M: Magic Attack<br>
P: Power Attack<br>
1-4: Change Elements<br>
(1-Fire, 2-Water, 3-Earth, 4-Air)
`;
document.body.appendChild(controlsDisplay);
// Toggle controls display
document.addEventListener('keydown', (e) => {
if (e.key === 'h' || e.key === 'H') {
controlsDisplay.style.display = controlsDisplay.style.display === 'none' ? 'block' : 'none';
}
});
// Add level display
const levelDisplay = document.createElement('div');
levelDisplay.id = 'level-display';
levelDisplay.style.position = 'absolute';
levelDisplay.style.top = '20px';
levelDisplay.style.left = '20px';
levelDisplay.style.color = 'white';
levelDisplay.style.fontFamily = 'Arial';
levelDisplay.style.fontSize = '24px';
levelDisplay.textContent = 'Level: 1';
document.body.appendChild(levelDisplay);
// Game loop
let lastEnemySpawn = 0;
let lastPowerRegen = 0;
let gameTime = 0;
let waveCount = 0;
let animationId = null;
let lastTimestamp = 0;
function animate(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
const deltaTime = timestamp - lastTimestamp;
lastTimestamp = timestamp;
animationId = requestAnimationFrame(animate);
gameTime += deltaTime;
// Activate Malakar after 3 minutes
if (!malakar.isActive && gameTime > 180000) {
malakar.isActive = true;
alert('Malakar has entered the battlefield!');
const malakarObj = createMalakar();
malakar.mesh = malakarObj.mesh;
scene.add(malakar.mesh);
}
// Regenerate power
if (time - lastPowerRegen > 1000) {
power = Math.min(100, power + 5);
updateUI();
lastPowerRegen = time;
}
// Spawn enemies in waves
const waveSize = 3 + Math.floor(waveCount / 3);
if (time - lastEnemySpawn > 5000 && enemies.length < waveSize && !malakar.isActive) {
const spawnCount = Math.min(waveSize - enemies.length, 3);
for (let i = 0; i < spawnCount; i++) {
createEnemy();
}
lastEnemySpawn = time;
waveCount++;
}
// Handle hero movement
const moveSpeed = heroSpeed * (deltaTime / 16);
if (keys.ArrowUp) hero.position.z -= moveSpeed;
if (keys.ArrowDown) hero.position.z += moveSpeed;
if (keys.ArrowLeft) hero.position.x -= moveSpeed;
if (keys.ArrowRight) hero.position.x += moveSpeed;
// Keep hero within bounds
hero.position.x = Math.max(-14, Math.min(14, hero.position.x));
hero.position.z = Math.max(-14, Math.min(14, hero.position.z));
hero.position.y = 0.9; // Keep consistent height
// Sword swing animation
if (isSwinging) {
swingProgress += 0.1;
sword.rotation.z = Math.PI/4 + Math.sin(swingProgress) * maxSwingAngle;
if (swingProgress > Math.PI) {
isSwinging = false;
sword.rotation.z = Math.PI/4;
}
}
// Move enemies and handle special behaviors
enemies.forEach(enemy => {
// Wraith floating animation
if (enemy.type === 'wraith') {
enemy.mesh.position.y = 1.5 + Math.sin(Date.now() * 0.002 + enemy.userData.floatOffset) * 0.3;
}
// Different movement patterns
let direction;
if (enemy.type === 'knight') {
// Knights move more strategically
direction = new THREE.Vector3(
hero.position.x - enemy.mesh.position.x + (Math.random() - 0.5),
0,
hero.position.z - enemy.mesh.position.z + (Math.random() - 0.5)
).normalize();
} else {
direction = new THREE.Vector3().subVectors(hero.position, enemy.mesh.position).normalize();
}
enemy.mesh.position.add(direction.multiplyScalar(enemy.speed * 0.05));
// Sorcerers occasionally teleport
if (enemy.type === 'sorcerer' && Math.random() < 0.005) {
enemy.mesh.position.x = hero.position.x + (Math.random() * 10 - 5);
enemy.mesh.position.z = hero.position.z + (Math.random() * 10 - 5);
}
enemy.mesh.lookAt(hero.position);
// Knights block attacks sometimes
if (enemy.type === 'knight' && Math.random() < 0.01) {
enemy.mesh.material.color.setHex(0xAAAAFF); // Block stance
setTimeout(() => {
enemy.mesh.material.color.setHex(0x888888);
}, 500);
}
// Malakar behavior
if (malakar.isActive) {
// Chase player aggressively
const malakarDirection = new THREE.Vector3().subVectors(
hero.position,
malakar.mesh.position
).normalize();
malakar.mesh.position.add(malakarDirection.multiplyScalar(0.08));
malakar.mesh.lookAt(hero.position);
// Special attacks
if (malakar.attackCooldown <= 0) {
// Shadow blast
if (Math.random() < 0.02) {
const blastGeometry = new THREE.SphereGeometry(0.3, 16, 16);
const blastMaterial = new THREE.MeshBasicMaterial({ color: 0x330000 });
const blast = new THREE.Mesh(blastGeometry, blastMaterial);
blast.position.copy(malakar.mesh.position);
blast.position.y += 1;
const blastDirection = new THREE.Vector3().subVectors(
hero.position,
malakar.mesh.position
).normalize();
scene.add(blast);
malakar.attackCooldown = 100;
// Animate blast
const blastSpeed = 0.2;
const animateBlast = () => {
blast.position.add(blastDirection.multiplyScalar(blastSpeed));
// Check hit
if (blast.position.distanceTo(hero.position) < 1.5) {
health -= 30;
updateUI();
scene.remove(blast);
return;
}
// Remove if out of bounds
if (Math.abs(blast.position.x) > 20 || Math.abs(blast.position.z) > 20) {
scene.remove(blast);
return;
}
requestAnimationFrame(animateBlast);
};
animateBlast();
}
} else {
malakar.attackCooldown--;
}
// Check collision with Malakar
if (hero.position.distanceTo(malakar.mesh.position) < 2.5) {
health -= 2;
updateUI();
}
// Sword can damage Malakar
if (isSwinging && hero.position.distanceTo(malakar.mesh.position) < 3) {
malakar.health -= 15;
if (malakar.health <= 0) {
alert('You have defeated Malakar and saved Eldoria!');
malakar.isActive = false;
scene.remove(malakar.mesh);
}
}
}
// Check collision with enemies
enemies.forEach(enemy => {
if (hero.position.distanceTo(enemy.mesh.position) < 1.2) {
health -= enemy.damage * 0.05;
updateUI();
if (health <= 0) {
alert('Game Over! ' + (malakar.isActive ? 'Malakar has won...' : 'Try again warrior!'));
health = 100;
power = 100;
enemies.forEach(e => scene.remove(e.mesh));
enemies = [];
if (malakar.isActive) {
scene.remove(malakar.mesh);
malakar.isActive = false;
malakar.health = 500;
}
updateUI();
gameTime = 0;
waveCount = 0;
document.getElementById('level-display').textContent = 'Level: 1';
}
}
});
renderer.render(scene, camera);
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
isMobile = window.innerWidth <= 768;
// Adjust controls position for mobile
if (isMobile) {
document.getElementById('controls').style.right = '20px';
document.getElementById('controls').style.bottom = '20px';
}
});
// Start game
function startGame() {
if (animationId) cancelAnimationFrame(animationId);
health = 100;
power = 100;
gameTime = 0;
waveCount = 0;
enemies = [];
if (malakar.isActive) {
scene.remove(malakar.mesh);
malakar.isActive = false;
malakar.health = 500;
}
updateUI();
animationId = requestAnimationFrame(animate);
// Add controls help
setTimeout(() => {
alert("Controls:\nArrow Keys: Move\nSpace: Sword Attack\nM: Magic Attack\nP: Power Attack\n1-4: Change Elements\nDefeat Malakar to win!");
}, 1000);
}
startGame();
</script>
<style>
/* Add sword swing animation */
@keyframes swordSwing {
0% { transform: rotate(45deg); }
50% { transform: rotate(-90deg); }
100% { transform: rotate(45deg); }
}
.sword-attack {
animation: swordSwing 0.3s ease-in-out;
}
</style>
</body>
</html>