<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Florr.io - 高度还原版</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
body {
background: #1a1a2e;
overflow: hidden;
font-family: Arial, sans-serif;
touch-action: none;
}
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
#gameCanvas {
position: absolute;
top: 0;
left: 0;
background: #87CEEB;
}
#uiCanvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index: 100;
}
.ui-element {
position: absolute;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #4a4a6d;
border-radius: 5px;
color: white;
font-family: Arial, sans-serif;
font-size: 12px;
}
#playerInfo {
top: 10px;
left: 10px;
padding: 8px;
min-width: 150px;
}
.stat-bar {
height: 6px;
background: #333;
border-radius: 3px;
margin: 3px 0;
overflow: hidden;
}
.health-bar {
background: linear-gradient(to right, #ff4444, #ff8888);
}
.xp-bar {
background: linear-gradient(to right, #44aaff, #88ccff);
}
#levelDisplay {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid gold;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: gold;
}
#inventory {
bottom: 10px;
left: 50%;
transform: translateX(-50%);
padding: 5px;
display: flex;
gap: 5px;
background: rgba(0, 0, 0, 0.7);
border-radius: 10px;
}
.petal-slot {
width: 40px;
height: 40px;
border: 2px solid #666;
border-radius: 5px;
position: relative;
overflow: hidden;
}
.petal-slot.active {
border-color: gold;
box-shadow: 0 0 10px gold;
}
.petal-count {
position: absolute;
bottom: 1px;
right: 2px;
color: white;
font-size: 10px;
text-shadow: 1px 1px 1px black;
}
#miniMap {
top: 10px;
right: 70px;
width: 150px;
height: 150px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid #4a4a6d;
}
#controls {
position: absolute;
bottom: 60px;
right: 10px;
display: flex;
gap: 5px;
}
.control-btn {
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #666;
border-radius: 10px;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
cursor: pointer;
user-select: none;
}
.control-btn:active {
background: rgba(100, 100, 100, 0.7);
}
#mobileJoystick {
position: absolute;
bottom: 100px;
left: 100px;
width: 120px;
height: 120px;
display: none;
}
.joystick-base {
position: absolute;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.joystick-thumb {
position: absolute;
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.7);
border-radius: 50%;
top: 30px;
left: 30px;
}
.upgrade-panel {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
border: 3px solid gold;
border-radius: 10px;
padding: 20px;
display: none;
z-index: 1000;
min-width: 300px;
}
.upgrade-option {
background: rgba(50, 50, 80, 0.8);
border: 2px solid #666;
border-radius: 5px;
padding: 10px;
margin: 5px 0;
cursor: pointer;
transition: all 0.2s;
}
.upgrade-option:hover {
border-color: gold;
background: rgba(80, 80, 120, 0.8);
}
.upgrade-title {
color: gold;
font-weight: bold;
margin-bottom: 5px;
}
.death-screen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
border: 3px solid #ff4444;
border-radius: 10px;
padding: 30px;
text-align: center;
display: none;
z-index: 1000;
}
.death-title {
color: #ff4444;
font-size: 32px;
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin: 20px 0;
}
.stat-item {
background: rgba(50, 50, 80, 0.8);
padding: 10px;
border-radius: 5px;
}
.stat-label {
color: #aaa;
font-size: 12px;
}
.stat-value {
color: white;
font-size: 18px;
font-weight: bold;
}
.btn-restart {
background: linear-gradient(to bottom, #4CAF50, #2E7D32);
border: none;
color: white;
padding: 12px 24px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
}
.btn-restart:hover {
background: linear-gradient(to bottom, #66BB6A, #388E3C);
}
.chat-box {
position: absolute;
bottom: 10px;
left: 10px;
width: 300px;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.5);
border-radius: 5px;
padding: 5px;
display: none;
}
.chat-message {
color: white;
font-size: 12px;
margin: 2px 0;
word-break: break-word;
}
.damage-popup {
position: absolute;
color: #ff4444;
font-weight: bold;
font-size: 16px;
pointer-events: none;
z-index: 50;
text-shadow: 1px 1px 2px black;
animation: damageFloat 1s forwards;
}
@keyframes damageFloat {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-30px); }
}
.loading-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10000;
}
.loading-title {
color: white;
font-size: 36px;
margin-bottom: 20px;
}
.loading-bar {
width: 300px;
height: 20px;
background: #333;
border-radius: 10px;
overflow: hidden;
}
.loading-fill {
height: 100%;
background: linear-gradient(to right, #4CAF50, #8BC34A);
width: 0%;
transition: width 0.3s;
}
@media (max-width: 768px) {
#mobileJoystick {
display: block;
}
.control-btn {
width: 40px;
height: 40px;
font-size: 16px;
}
#inventory {
bottom: 150px;
}
.petal-slot {
width: 35px;
height: 35px;
}
#miniMap {
width: 100px;
height: 100px;
right: 10px;
top: 70px;
}
#levelDisplay {
top: 70px;
right: 120px;
width: 40px;
height: 40px;
font-size: 16px;
}
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<canvas id="uiCanvas"></canvas>
<div class="loading-screen" id="loadingScreen">
<div class="loading-title">Florr.io</div>
<div class="loading-bar">
<div class="loading-fill" id="loadingFill"></div>
</div>
<div style="color: #aaa; margin-top: 10px;">加载中...</div>
</div>
<div id="playerInfo" class="ui-element">
<div style="color: gold; font-weight: bold; margin-bottom: 5px;">玩家</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span>HP: <span id="hpValue">100/100</span></span>
<span>XP: <span id="xpValue">0/100</span></span>
</div>
<div class="stat-bar">
<div class="health-bar" id="healthBar" style="width: 100%"></div>
</div>
<div class="stat-bar">
<div class="xp-bar" id="xpBar" style="width: 0%"></div>
</div>
</div>
<div id="levelDisplay">1</div>
<div id="miniMap" class="ui-element"></div>
<div id="inventory" class="ui-element">
</div>
<div id="controls">
<div class="control-btn" id="btnAuto">A</div>
<div class="control-btn" id="btnSplit">S</div>
<div class="control-btn" id="btnUpgrade">U</div>
</div>
<div id="mobileJoystick">
<div class="joystick-base"></div>
<div class="joystick-thumb"></div>
</div>
<div class="chat-box" id="chatBox"></div>
<div class="upgrade-panel" id="upgradePanel">
<div style="color: gold; font-size: 20px; text-align: center; margin-bottom: 20px;">选择升级</div>
<div id="upgradeOptions">
</div>
</div>
<div class="death-screen" id="deathScreen">
<div class="death-title">游戏结束</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-label">最终等级</div>
<div class="stat-value" id="finalLevel">1</div>
</div>
<div class="stat-item">
<div class="stat-label">游戏时间</div>
<div class="stat-value" id="finalTime">0s</div>
</div>
<div class="stat-item">
<div class="stat-label">击杀数</div>
<div class="stat-value" id="finalKills">0</div>
</div>
<div class="stat-item">
<div class="stat-label">最高分数</div>
<div class="stat-value" id="finalScore">0</div>
</div>
</div>
<button class="btn-restart" onclick="restartGame()">重新开始</button>
</div>
</div>
<script>
const CONFIG = {
WORLD_SIZE: 5000,
VIEWPORT_PADDING: 100,
CELL_SIZE: 20,
MAX_ENEMIES: 100,
MAX_PELLETS: 200,
PETAL_TYPES: 8,
INITIAL_PETALS: 4
};
const game = {
canvas: null,
uiCanvas: null,
ctx: null,
uiCtx: null,
player: null,
entities: [],
pellets: [],
enemies: [],
particles: [],
camera: { x: 0, y: 0 },
keys: {},
mouse: { x: 0, y: 0 },
touch: { x: 0, y: 0, active: false },
lastTime: 0,
deltaTime: 0,
gameTime: 0,
isPaused: false,
isAlive: true,
upgradePending: false,
autoShoot: false,
petals: [
{ id: 0, name: "Basic", color: "#4CAF50", damage: 5, speed: 8, pierce: 1, count: 1, rarity: 0 },
{ id: 1, name: "Rose", color: "#E91E63", damage: 8, speed: 6, pierce: 1, count: 1, rarity: 1 },
{ id: 2, name: "Lily", color: "#FFFFFF", damage: 4, speed: 10, pierce: 2, count: 1, rarity: 1 },
{ id: 3, name: "Sunflower", color: "#FFC107", damage: 3, speed: 5, pierce: 1, count: 2, rarity: 0 },
{ id: 4, name: "Cactus", color: "#8BC34A", damage: 12, speed: 4, pierce: 1, count: 1, rarity: 2 },
{ id: 5, name: "Egg", color: "#FFEB3B", damage: 2, speed: 7, pierce: 1, count: 3, rarity: 0 },
{ id: 6, name: "Triplet", color: "#2196F3", damage: 6, speed: 8, pierce: 1, count: 3, rarity: 2 },
{ id: 7, name: "Square", color: "#9C27B0", damage: 10, speed: 6, pierce: 2, count: 4, rarity: 3 }
],
enemyTypes: [
{ name: "Ladybug", color: "#E53935", size: 20, health: 30, speed: 2, damage: 5, xp: 10 },
{ name: "Bee", color: "#FFB300", size: 15, health: 20, speed: 3, damage: 3, xp: 7 },
{ name: "Spider", color: "#5D4037", size: 25, health: 50, speed: 1.5, damage: 8, xp: 15 },
{ name: "Ant", color: "#424242", size: 12, health: 15, speed: 2.5, damage: 2, xp: 5 },
{ name: "Beetle", color: "#37474F", size: 30, health: 100, speed: 1, damage: 12, xp: 30 }
],
upgrades: [
{ type: "health", name: "最大生命值", desc: "+20 最大生命值", value: 20 },
{ type: "damage", name: "伤害", desc: "+2 伤害", value: 2 },
{ type: "speed", name: "移动速度", desc: "+0.5 移动速度", value: 0.5 },
{ type: "attackSpeed", name: "攻击速度", desc: "+0.1 攻击速度", value: 0.1 },
{ type: "pierce", name: "穿透", desc: "+1 穿透", value: 1 },
{ type: "regen", name: "生命恢复", desc: "+0.5 生命恢复", value: 0.5 },
{ type: "petalCount", name: "额外花瓣", desc: "获得一个随机花瓣", value: 1 }
]
};
class Player {
constructor() {
this.x = CONFIG.WORLD_SIZE / 2;
this.y = CONFIG.WORLD_SIZE / 2;
this.size = 25;
this.color = "#2196F3";
this.maxHealth = 100;
this.health = 100;
this.speed = 5;
this.damage = 5;
this.attackSpeed = 1;
this.pierce = 1;
this.regen = 0.1;
this.level = 1;
this.xp = 0;
this.xpToNext = 100;
this.kills = 0;
this.score = 0;
this.petals = [];
this.inventory = [];
this.selectedSlot = 0;
this.attackCooldown = 0;
this.lastRegen = 0;
this.initializePetals();
}
initializePetals() {
for (let i = 0; i < CONFIG.INITIAL_PETALS; i++) {
const randomPetal = Math.floor(Math.random() * 4);
this.addPetal(randomPetal);
}
this.updateInventory();
}
addPetal(petalId) {
const petal = { ...game.petals[petalId], count: 1 };
this.petals.push(petal);
this.updateInventory();
}
updateInventory() {
const inventory = document.getElementById('inventory');
inventory.innerHTML = '';
this.petals.forEach((petal, index) => {
const slot = document.createElement('div');
slot.className = `petal-slot ${index === this.selectedSlot ? 'active' : ''}`;
slot.style.background = petal.color;
slot.innerHTML = `<div class="petal-count">${petal.count}</div>`;
slot.onclick = () => this.selectSlot(index);
inventory.appendChild(slot);
});
}
selectSlot(index) {
this.selectedSlot = index;
this.updateInventory();
}
update(deltaTime) {
if (!game.isAlive) return;
let moveX = 0;
let moveY = 0;
if (game.keys['w'] || game.keys['ArrowUp']) moveY -= 1;
if (game.keys['s'] || game.keys['ArrowDown']) moveY += 1;
if (game.keys['a'] || game.keys['ArrowLeft']) moveX -= 1;
if (game.keys['d'] || game.keys['ArrowRight']) moveX += 1;
if (game.touch.active) {
const joyX = game.touch.x;
const joyY = game.touch.y;
const dist = Math.sqrt(joyX * joyX + joyY * joyY);
if (dist > 10) {
moveX = joyX / 50;
moveY = joyY / 50;
}
}
if (moveX !== 0 || moveY !== 0) {
const magnitude = Math.sqrt(moveX * moveX + moveY * moveY);
moveX /= magnitude;
moveY /= magnitude;
this.x += moveX * this.speed;
this.y += moveY * this.speed;
this.x = Math.max(this.size, Math.min(CONFIG.WORLD_SIZE - this.size, this.x));
this.y = Math.max(this.size, Math.min(CONFIG.WORLD_SIZE - this.size, this.y));
}
this.attackCooldown -= deltaTime;
if (this.attackCooldown <= 0 && (game.autoShoot || game.keys[' '])) {
this.attack();
this.attackCooldown = 60 / (this.attackSpeed * 10);
}
this.lastRegen += deltaTime;
if (this.lastRegen >= 60) {
this.heal(this.regen);
this.lastRegen = 0;
}
game.camera.x = this.x - game.canvas.width / 2;
game.camera.y = this.y - game.canvas.height / 2;
}
attack() {
if (this.petals.length === 0) return;
const petal = this.petals[this.selectedSlot];
const count = petal.count;
const angleStep = (Math.PI * 2) / count;
for (let i = 0; i < count; i++) {
const angle = Math.atan2(game.mouse.y, game.mouse.x) + angleStep * i;
const projectile = {
x: this.x,
y: this.y,
vx: Math.cos(angle) * petal.speed,
vy: Math.sin(angle) * petal.speed,
damage: this.damage + petal.damage,
pierce: this.pierce + petal.pierce - 1,
size: 8,
color: petal.color,
lifetime: 120,
owner: this
};
game.entities.push(projectile);
}
for (let i = 0; i < 5; i++) {
const angle = Math.atan2(game.mouse.y, game.mouse.x) + (Math.random() - 0.5) * 0.5;
game.particles.push({
x: this.x,
y: this.y,
vx: Math.cos(angle) * 3,
vy: Math.sin(angle) * 3,
size: 3,
color: petal.color,
lifetime: 20
});
}
}
takeDamage(damage) {
this.health -= damage;
createDamagePopup(this.x, this.y, damage);
for (let i = 0; i < 10; i++) {
game.particles.push({
x: this.x + (Math.random() - 0.5) * 20,
y: this.y + (Math.random() - 0.5) * 20,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
size: 4,
color: '#ff4444',
lifetime: 30
});
}
if (this.health <= 0) {
this.die();
}
updateUI();
}
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
updateUI();
}
addXP(amount) {
this.xp += amount;
this.score += amount;
while (this.xp >= this.xpToNext) {
this.levelUp();
}
updateUI();
}
levelUp() {
this.xp -= this.xpToNext;
this.level++;
this.xpToNext = Math.floor(100 * Math.pow(1.2, this.level - 1));
game.upgradePending = true;
showUpgradePanel();
for (let i = 0; i < 20; i++) {
const angle = (i / 20) * Math.PI * 2;
game.particles.push({
x: this.x + Math.cos(angle) * 30,
y: this.y + Math.sin(angle) * 30,
vx: Math.cos(angle) * 2,
vy: Math.sin(angle) * 2,
size: 6,
color: '#FFD700',
lifetime: 40
});
}
}
die() {
game.isAlive = false;
for (let i = 0; i < 30; i++) {
const angle = (i / 30) * Math.PI * 2;
game.particles.push({
x: this.x + Math.cos(angle) * 20,
y: this.y + Math.sin(angle) * 20,
vx: Math.cos(angle) * 5,
vy: Math.sin(angle) * 5,
size: 8,
color: this.color,
lifetime: 60
});
}
showDeathScreen();
}
draw(ctx) {
const screenX = this.x - game.camera.x;
const screenY = this.y - game.camera.y;
ctx.save();
ctx.translate(screenX, screenY);
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(-8, -5, 6, 0, Math.PI * 2);
ctx.arc(8, -5, 6, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
const lookAngle = Math.atan2(game.mouse.y, game.mouse.x);
ctx.arc(-8 + Math.cos(lookAngle) * 3, -5 + Math.sin(lookAngle) * 3, 3, 0, Math.PI * 2);
ctx.arc(8 + Math.cos(lookAngle) * 3, -5 + Math.sin(lookAngle) * 3, 3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(0, 8, 10, 0.2, 0.8 * Math.PI);
ctx.stroke();
ctx.restore();
}
}
class Enemy {
constructor(type, x, y) {
const enemyType = game.enemyTypes[type];
Object.assign(this, enemyType);
this.x = x;
this.y = y;
this.maxHealth = this.health;
this.target = game.player;
this.attackCooldown = 0;
}
update(deltaTime) {
if (!game.isAlive) return;
const dx = this.target.x - this.x;
const dy = this.target.y - this.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
this.x += (dx / dist) * this.speed;
this.y += (dy / dist) * this.speed;
}
this.attackCooldown -= deltaTime;
if (dist < this.size + game.player.size && this.attackCooldown <= 0) {
game.player.takeDamage(this.damage);
this.attackCooldown = 60;
}
}
takeDamage(damage) {
this.health -= damage;
createDamagePopup(this.x, this.y, damage);
if (this.health <= 0) {
this.die();
return true;
}
return false;
}
die() {
game.player.addXP(this.xp);
game.player.kills++;
if (Math.random() < 0.1) {
spawnPellet(this.x, this.y);
}
for (let i = 0; i < 15; i++) {
game.particles.push({
x: this.x + (Math.random() - 0.5) * 20,
y: this.y + (Math.random() - 0.5) * 20,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
size: 5,
color: this.color,
lifetime: 40
});
}
}
draw(ctx) {
const screenX = this.x - game.camera.x;
const screenY = this.y - game.camera.y;
ctx.save();
ctx.translate(screenX, screenY);
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.stroke();
if (this.health < this.maxHealth) {
const healthPercent = this.health / this.maxHealth;
const barWidth = this.size * 2;
const barHeight = 4;
ctx.fillStyle = '#f00';
ctx.fillRect(-this.size, -this.size - 10, barWidth, barHeight);
ctx.fillStyle = '#0f0';
ctx.fillRect(-this.size, -this.size - 10, barWidth * healthPercent, barHeight);
}
ctx.restore();
}
}
function spawnEnemy() {
if (game.enemies.length >= CONFIG.MAX_ENEMIES) return;
const type = Math.floor(Math.random() * game.enemyTypes.length);
const angle = Math.random() * Math.PI * 2;
const distance = 1000;
const x = game.player.x + Math.cos(angle) * distance;
const y = game.player.y + Math.sin(angle) * distance;
const validX = Math.max(50, Math.min(CONFIG.WORLD_SIZE - 50, x));
const validY = Math.max(50, Math.min(CONFIG.WORLD_SIZE - 50, y));
const enemy = new Enemy(type, validX, validY);
game.enemies.push(enemy);
}
function spawnPellet(x, y) {
if (game.pellets.length >= CONFIG.MAX_PELLETS) return;
const petalId = Math.floor(Math.random() * game.petals.length);
const petal = game.petals[petalId];
game.pellets.push({
x: x,
y: y,
size: 10,
color: petal.color,
petalId: petalId,
collected: false
});
}
function updateEntities(deltaTime) {
game.player.update(deltaTime);
for (let i = game.enemies.length - 1; i >= 0; i--) {
const enemy = game.enemies[i];
enemy.update(deltaTime);
if (enemy.health <= 0) {
game.enemies.splice(i, 1);
}
}
for (let i = game.entities.length - 1; i >= 0; i--) {
const entity = game.entities[i];
entity.x += entity.vx;
entity.y += entity.vy;
entity.lifetime--;
if (entity.x < 0 || entity.x > CONFIG.WORLD_SIZE ||
entity.y < 0 || entity.y > CONFIG.WORLD_SIZE ||
entity.lifetime <= 0) {
game.entities.splice(i, 1);
continue;
}
if (entity.owner === game.player) {
for (let j = 0; j < game.enemies.length; j++) {
const enemy = game.enemies[j];
const dx = entity.x - enemy.x;
const dy = entity.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < entity.size + enemy.size) {
if (enemy.takeDamage(entity.damage)) {
if (entity.pierce <= 0) {
game.entities.splice(i, 1);
break;
} else {
entity.pierce--;
}
} else {
game.entities.splice(i, 1);
break;
}
}
}
}
}
for (let i = game.pellets.length - 1; i >= 0; i--) {
const pellet = game.pellets[i];
const dx = game.player.x - pellet.x;
const dy = game.player.y - pellet.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < game.player.size + pellet.size) {
game.player.addPetal(pellet.petalId);
game.pellets.splice(i, 1);
for (let j = 0; j < 10; j++) {
game.particles.push({
x: pellet.x,
y: pellet.y,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
size: 3,
color: pellet.color,
lifetime: 30
});
}
}
}
for (let i = game.particles.length - 1; i >= 0; i--) {
const particle = game.particles[i];
particle.x += particle.vx;
particle.y += particle.vy;
particle.vx *= 0.95;
particle.vy *= 0.95;
particle.lifetime--;
if (particle.lifetime <= 0) {
game.particles.splice(i, 1);
}
}
}
function drawWorld() {
const ctx = game.ctx;
const canvas = game.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
game.pellets.forEach(pellet => {
const screenX = pellet.x - game.camera.x;
const screenY = pellet.y - game.camera.y;
ctx.fillStyle = pellet.color;
ctx.beginPath();
ctx.arc(screenX, screenY, pellet.size, 0, Math.PI * 2);
ctx.fill();
ctx.shadowColor = pellet.color;
ctx.shadowBlur = 10;
ctx.fill();
ctx.shadowBlur = 0;
});
game.enemies.forEach(enemy => enemy.draw(ctx));
game.entities.forEach(entity => {
const screenX = entity.x - game.camera.x;
const screenY = entity.y - game.camera.y;
ctx.fillStyle = entity.color;
ctx.beginPath();
ctx.arc(screenX, screenY, entity.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = entity.color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(screenX, screenY);
ctx.lineTo(screenX - entity.vx, screenY - entity.vy);
ctx.stroke();
});
if (game.isAlive) {
game.player.draw(ctx);
}
game.particles.forEach(particle => {
const screenX = particle.x - game.camera.x;
const screenY = particle.y - game.camera.y;
ctx.fillStyle = particle.color;
ctx.globalAlpha = particle.lifetime / 60;
ctx.beginPath();
ctx.arc(screenX, screenY, particle.size, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
});
}
function drawGrid() {
const ctx = game.ctx;
const canvas = game.canvas;
const gridSize = 100;
const startX = Math.floor(game.camera.x / gridSize) * gridSize;
const startY = Math.floor(game.camera.y / gridSize) * gridSize;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
for (let x = startX; x < game.camera.x + canvas.width; x += gridSize) {
const screenX = x - game.camera.x;
ctx.beginPath();
ctx.moveTo(screenX, 0);
ctx.lineTo(screenX, canvas.height);
ctx.stroke();
}
for (let y = startY; y < game.camera.y + canvas.height; y += gridSize) {
const screenY = y - game.camera.y;
ctx.beginPath();
ctx.moveTo(0, screenY);
ctx.lineTo(canvas.width, screenY);
ctx.stroke();
}
}
function drawUI() {
const ctx = game.uiCtx;
const canvas = game.uiCanvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawMiniMap();
if (game.isAlive) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(centerX - 10, centerY);
ctx.lineTo(centerX + 10, centerY);
ctx.moveTo(centerX, centerY - 10);
ctx.lineTo(centerX, centerY + 10);
ctx.stroke();
ctx.beginPath();
ctx.arc(centerX, centerY, 20, 0, Math.PI * 2);
ctx.stroke();
}
}
function drawMiniMap() {
const ctx = game.uiCtx;
const map = document.getElementById('miniMap');
const rect = map.getBoundingClientRect();
const scale = 0.02;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
ctx.clearRect(rect.left, rect.top, rect.width, rect.height);
ctx.strokeStyle = '#4a4a6d';
ctx.lineWidth = 2;
ctx.strokeRect(rect.left, rect.top, rect.width, rect.height);
ctx.fillStyle = game.player.color;
ctx.beginPath();
ctx.arc(
rect.left + centerX,
rect.top + centerY,
4, 0, Math.PI * 2
);
ctx.fill();
ctx.fillStyle = '#ff4444';
game.enemies.forEach(enemy => {
const dx = (enemy.x - game.player.x) * scale;
const dy = (enemy.y - game.player.y) * scale;
if (Math.abs(dx) < centerX && Math.abs(dy) < centerY) {
ctx.beginPath();
ctx.arc(
rect.left + centerX + dx,
rect.top + centerY + dy,
2, 0, Math.PI * 2
);
ctx.fill();
}
});
ctx.fillStyle = '#ffff00';
game.pellets.forEach(pellet => {
const dx = (pellet.x - game.player.x) * scale;
const dy = (pellet.y - game.player.y) * scale;
if (Math.abs(dx) < centerX && Math.abs(dy) < centerY) {
ctx.beginPath();
ctx.arc(
rect.left + centerX + dx,
rect.top + centerY + dy,
1, 0, Math.PI * 2
);
ctx.fill();
}
});
}
function updateUI() {
const player = game.player;
document.getElementById('hpValue').textContent =
`${Math.floor(player.health)}/${Math.floor(player.maxHealth)}`;
document.getElementById('healthBar').style.width =
`${(player.health / player.maxHealth) * 100}%`;
document.getElementById('xpValue').textContent =
`${Math.floor(player.xp)}/${Math.floor(player.xpToNext)}`;
document.getElementById('xpBar').style.width =
`${(player.xp / player.xpToNext) * 100}%`;
document.getElementById('levelDisplay').textContent = player.level;
document.getElementById('finalTime').textContent =
`${Math.floor(game.gameTime / 60)}s`;
}
function createDamagePopup(x, y, damage) {
const popup = document.createElement('div');
popup.className = 'damage-popup';
popup.textContent = Math.floor(damage);
popup.style.left = (x - game.camera.x) + 'px';
popup.style.top = (y - game.camera.y) + 'px';
document.getElementById('gameContainer').appendChild(popup);
setTimeout(() => {
popup.remove();
}, 1000);
}
function showUpgradePanel() {
const panel = document.getElementById('upgradePanel');
const options = document.getElementById('upgradeOptions');
options.innerHTML = '';
const shuffled = [...game.upgrades].sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, 3);
selected.forEach(upgrade => {
const option = document.createElement('div');
option.className = 'upgrade-option';
option.innerHTML = `
<div class="upgrade-title">${upgrade.name}</div>
<div style="color: #aaa; font-size: 12px;">${upgrade.desc}</div>
`;
option.onclick = () => applyUpgrade(upgrade);
options.appendChild(option);
});
panel.style.display = 'block';
}
function applyUpgrade(upgrade) {
const player = game.player;
switch(upgrade.type) {
case 'health':
player.maxHealth += upgrade.value;
player.health += upgrade.value;
break;
case 'damage':
player.damage += upgrade.value;
break;
case 'speed':
player.speed += upgrade.value;
break;
case 'attackSpeed':
player.attackSpeed += upgrade.value;
break;
case 'pierce':
player.pierce += upgrade.value;
break;
case 'regen':
player.regen += upgrade.value;
break;
case 'petalCount':
const randomPetal = Math.floor(Math.random() * game.petals.length);
player.addPetal(randomPetal);
break;
}
game.upgradePending = false;
document.getElementById('upgradePanel').style.display = 'none';
}
function showDeathScreen() {
const player = game.player;
document.getElementById('finalLevel').textContent = player.level;
document.getElementById('finalTime').textContent = `${Math.floor(game.gameTime / 60)}s`;
document.getElementById('finalKills').textContent = player.kills;
document.getElementById('finalScore').textContent = player.score;
document.getElementById('deathScreen').style.display = 'block';
}
function restartGame() {
game.player = new Player();
game.entities = [];
game.pellets = [];
game.enemies = [];
game.particles = [];
game.gameTime = 0;
game.isAlive = true;
game.upgradePending = false;
document.getElementById('deathScreen').style.display = 'none';
for (let i = 0; i < 10; i++) spawnEnemy();
for (let i = 0; i < 20; i++) {
const x = Math.random() * CONFIG.WORLD_SIZE;
const y = Math.random() * CONFIG.WORLD_SIZE;
spawnPellet(x, y);
}
updateUI();
}
function gameLoop(currentTime) {
if (!game.lastTime) game.lastTime = currentTime;
game.deltaTime = (currentTime - game.lastTime) / 16.67;
game.lastTime = currentTime;
if (game.isAlive) {
game.gameTime++;
if (Math.random() < 0.01 && game.enemies.length < CONFIG.MAX_ENEMIES) {
spawnEnemy();
}
if (Math.random() < 0.005 && game.pellets.length < CONFIG.MAX_PELLETS) {
const x = Math.random() * CONFIG.WORLD_SIZE;
const y = Math.random() * CONFIG.WORLD_SIZE;
spawnPellet(x, y);
}
updateEntities(game.deltaTime);
drawWorld();
drawUI();
}
requestAnimationFrame(gameLoop);
}
function initEventListeners() {
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
game.keys[key] = true;
if (key === ' ') e.preventDefault();
if (key >= '1' && key <= '8') {
const index = parseInt(key) - 1;
if (index < game.player.petals.length) {
game.player.selectSlot(index);
}
}
if (key === 'a') {
game.autoShoot = !game.autoShoot;
}
if (key === 'u' && game.upgradePending) {
showUpgradePanel();
}
});
document.addEventListener('keyup', (e) => {
game.keys[e.key.toLowerCase()] = false;
});
game.canvas.addEventListener('mousemove', (e) => {
const rect = game.canvas.getBoundingClientRect();
game.mouse.x = e.clientX - rect.left - game.canvas.width / 2;
game.mouse.y = e.clientY - rect.top - game.canvas.height / 2;
});
let touchStartX = 0;
let touchStartY = 0;
game.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = game.canvas.getBoundingClientRect();
touchStartX = touch.clientX;
touchStartY = touch.clientY;
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
if (x < 200 && y > game.canvas.height - 200) {
game.touch.active = true;
game.touch.x = 0;
game.touch.y = 0;
} else {
game.mouse.x = x - game.canvas.width / 2;
game.mouse.y = y - game.canvas.height / 2;
}
});
game.canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (e.touches.length === 1) {
const touch = e.touches[0];
const rect = game.canvas.getBoundingClientRect();
if (game.touch.active) {
const joyX = touch.clientX - touchStartX;
const joyY = touch.clientY - touchStartY;
const dist = Math.sqrt(joyX * joyX + joyY * joyY);
if (dist > 50) {
game.touch.x = (joyX / dist) * 50;
game.touch.y = (joyY / dist) * 50;
} else {
game.touch.x = joyX;
game.touch.y = joyY;
}
const joystick = document.querySelector('.joystick-thumb');
if (joystick) {
joystick.style.transform = `translate(${game.touch.x}px, ${game.touch.y}px)`;
}
} else {
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
game.mouse.x = x - game.canvas.width / 2;
game.mouse.y = y - game.canvas.height / 2;
}
}
});
game.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
game.touch.active = false;
game.touch.x = 0;
game.touch.y = 0;
const joystick = document.querySelector('.joystick-thumb');
if (joystick) {
joystick.style.transform = 'translate(0px, 0px)';
}
});
document.getElementById('btnAuto').addEventListener('click', () => {
game.autoShoot = !game.autoShoot;
document.getElementById('btnAuto').style.background =
game.autoShoot ? 'rgba(76, 175, 80, 0.7)' : 'rgba(0, 0, 0, 0.7)';
});
document.getElementById('btnUpgrade').addEventListener('click', () => {
if (game.upgradePending) {
showUpgradePanel();
}
});
window.addEventListener('resize', resizeCanvases);
game.canvas.addEventListener('contextmenu', (e) => e.preventDefault());
}
function resizeCanvases() {
const container = document.getElementById('gameContainer');
game.canvas.width = container.clientWidth;
game.canvas.height = container.clientHeight;
game.uiCanvas.width = container.clientWidth;
game.uiCanvas.height = container.clientHeight;
}
function loadResources() {
const loadingFill = document.getElementById('loadingFill');
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 20;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
setTimeout(() => {
document.getElementById('loadingScreen').style.display = 'none';
startGame();
}, 500);
}
loadingFill.style.width = `${progress}%`;
}, 100);
}
function startGame() {
game.canvas = document.getElementById('gameCanvas');
game.uiCanvas = document.getElementById('uiCanvas');
game.ctx = game.canvas.getContext('2d');
game.uiCtx = game.uiCanvas.getContext('2d');
resizeCanvases();
game.player = new Player();
initEventListeners();
for (let i = 0; i < 10; i++) spawnEnemy();
for (let i = 0; i < 20; i++) {
const x = Math.random() * CONFIG.WORLD_SIZE;
const y = Math.random() * CONFIG.WORLD_SIZE;
spawnPellet(x, y);
}
updateUI();
requestAnimationFrame(gameLoop);
}
window.onload = loadResources;
</script>
</body>
</html>