<!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;
font-family: 'Arial', sans-serif;
user-select: none;
}
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}
.game-header {
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #00ff88;
z-index: 100;
}
.logo {
font-size: 24px;
font-weight: bold;
color: #00ff88;
text-shadow: 0 0 10px #00ff88;
}
.player-stats {
display: flex;
gap: 20px;
}
.stat {
background: rgba(0, 0, 0, 0.5);
padding: 5px 15px;
border-radius: 20px;
border: 1px solid #00ff88;
display: flex;
align-items: center;
gap: 8px;
}
.stat-icon {
color: #00ff88;
font-weight: bold;
}
.game-container {
flex: 1;
position: relative;
overflow: hidden;
}
#gameCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #0f3460 0%, #1a5f7a 100%);
}
.controls {
position: absolute;
bottom: 20px;
left: 20px;
display: flex;
gap: 15px;
z-index: 10;
}
.control-btn {
width: 60px;
height: 60px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #00ff88;
border-radius: 50%;
color: white;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.control-btn:hover {
background: rgba(0, 255, 136, 0.3);
transform: scale(1.1);
}
.control-btn:active {
transform: scale(0.95);
}
.abilities {
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
gap: 10px;
z-index: 10;
}
.ability {
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #ff8800;
border-radius: 10px;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
font-weight: bold;
position: relative;
}
.ability:hover {
background: rgba(255, 136, 0, 0.3);
}
.ability:active {
transform: scale(0.95);
}
.ability-cooldown {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background: rgba(255, 0, 0, 0.7);
transition: height 0.3s;
}
.minimap {
position: absolute;
top: 20px;
right: 20px;
width: 200px;
height: 200px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #00ff88;
border-radius: 10px;
z-index: 10;
}
.level-up-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.level-up-title {
font-size: 48px;
color: #ffcc00;
margin-bottom: 30px;
text-shadow: 0 0 20px #ffcc00;
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.upgrade-options {
display: flex;
gap: 20px;
margin-top: 30px;
}
.upgrade-option {
width: 150px;
height: 150px;
background: rgba(255, 255, 255, 0.1);
border: 3px solid #00ff88;
border-radius: 10px;
padding: 15px;
cursor: pointer;
transition: all 0.3s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.upgrade-option:hover {
transform: scale(1.1);
background: rgba(0, 255, 136, 0.2);
}
.petal-types {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 10;
}
.petal {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s;
}
.petal.selected {
border-color: #ffcc00;
transform: scale(1.2);
box-shadow: 0 0 10px #ffcc00;
}
.health-bar {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 20px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid #ff4444;
border-radius: 10px;
overflow: hidden;
z-index: 10;
}
.health-fill {
height: 100%;
background: linear-gradient(90deg, #ff4444, #ff8888);
transition: width 0.3s;
}
.death-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.death-title {
font-size: 48px;
color: #ff4444;
margin-bottom: 20px;
}
.death-stats {
background: rgba(255, 255, 255, 0.1);
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
}
.enemy-counter {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 10px;
border: 2px solid #ff8800;
z-index: 10;
}
.enemy-icon {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
margin: 0 5px;
}
.damage-popup {
position: absolute;
color: #ff4444;
font-weight: bold;
font-size: 16px;
pointer-events: none;
animation: floatUp 1s forwards;
}
@keyframes floatUp {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-50px); }
}
.xp-bar {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 10px;
background: rgba(0, 0, 0, 0.5);
border: 1px solid #00ccff;
border-radius: 5px;
overflow: hidden;
z-index: 10;
}
.xp-fill {
height: 100%;
background: linear-gradient(90deg, #00ccff, #88eeff);
transition: width 0.3s;
}
.chat-box {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 400px;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #00ff88;
border-radius: 10px;
padding: 10px;
z-index: 10;
display: none;
}
.chat-message {
margin-bottom: 5px;
font-size: 12px;
word-break: break-all;
}
.chat-input {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 400px;
padding: 10px;
background: rgba(0, 0, 0, 0.9);
border: 2px solid #00ff88;
border-radius: 10px;
color: white;
display: none;
z-index: 11;
}
.mobile-controls {
display: none;
}
@media (max-width: 768px) {
.minimap {
width: 100px;
height: 100px;
top: 10px;
right: 10px;
}
.health-bar, .xp-bar {
width: 200px;
}
.controls, .abilities {
display: none;
}
.mobile-controls {
display: block;
position: absolute;
bottom: 20px;
width: 100%;
padding: 0 20px;
}
.joystick-area {
position: relative;
width: 120px;
height: 120px;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
border: 2px solid #00ff88;
}
.joystick {
position: absolute;
width: 60px;
height: 60px;
background: rgba(0, 255, 136, 0.7);
border-radius: 50%;
top: 30px;
left: 30px;
}
}
</style>
</head>
<body>
<div class="game-header">
<div class="logo">FLORR.IO</div>
<div class="player-stats">
<div class="stat">
<span class="stat-icon">❤️</span>
<span id="health">100/100</span>
</div>
<div class="stat">
<span class="stat-icon">⭐</span>
<span id="level">1</span>
</div>
<div class="stat">
<span class="stat-icon">💎</span>
<span id="score">0</span>
</div>
</div>
</div>
<div class="game-container">
<canvas id="gameCanvas"></canvas>
<div class="health-bar">
<div class="health-fill" id="health-fill" style="width: 100%"></div>
</div>
<div class="xp-bar">
<div class="xp-fill" id="xp-fill" style="width: 0%"></div>
</div>
<div class="enemy-counter">
敌人: <span id="enemy-count">0</span>
</div>
<div class="minimap" id="minimap"></div>
<div class="controls">
<div class="control-btn" id="up">↑</div>
<div class="control-btn" id="down">↓</div>
<div class="control-btn" id="left">←</div>
<div class="control-btn" id="right">→</div>
</div>
<div class="abilities">
<div class="ability" id="ability1">
<span>Q</span>
<div class="ability-cooldown"></div>
</div>
<div class="ability" id="ability2">
<span>W</span>
<div class="ability-cooldown"></div>
</div>
<div class="ability" id="ability3">
<span>E</span>
<div class="ability-cooldown"></div>
</div>
</div>
<div class="petal-types" id="petal-types"></div>
<div class="chat-box" id="chat-box"></div>
<input type="text" class="chat-input" id="chat-input" placeholder="按回车发送消息..." />
<div class="mobile-controls">
<div class="joystick-area" id="joystick-area">
<div class="joystick" id="joystick"></div>
</div>
</div>
</div>
<div class="level-up-screen" id="level-up-screen">
<div class="level-up-title">等级提升!</div>
<div>选择一项升级:</div>
<div class="upgrade-options" id="upgrade-options"></div>
</div>
<div class="death-screen" id="death-screen">
<div class="death-title">游戏结束</div>
<div class="death-stats">
<div>最终等级: <span id="final-level">1</span></div>
<div>最终分数: <span id="final-score">0</span></div>
<div>击败敌人: <span id="final-kills">0</span></div>
<div>游戏时间: <span id="final-time">0s</span></div>
</div>
<div class="control-btn" onclick="restartGame()">重新开始</div>
</div>
<script>
const config = {
canvasWidth: 3000,
canvasHeight: 3000,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight - 60,
cellSize: 40,
playerSpeed: 5,
enemySpawnRate: 0.01,
maxEnemies: 50,
maxPellets: 100
};
const gameState = {
player: null,
enemies: [],
pellets: [],
particles: [],
projectiles: [],
level: 1,
xp: 0,
xpToNextLevel: 100,
score: 0,
kills: 0,
gameTime: 0,
gameRunning: true,
keys: {},
mouse: { x: 0, y: 0 },
camera: { x: 0, y: 0 },
abilities: {
q: { cooldown: 0, maxCooldown: 30, active: false },
w: { cooldown: 0, maxCooldown: 60, active: false },
e: { cooldown: 0, maxCooldown: 120, active: false }
},
petals: [
{ type: 'basic', color: '#00ff88', damage: 10, speed: 8, count: 1 },
{ type: 'rose', color: '#ff0066', damage: 15, speed: 6, count: 1 },
{ type: 'lily', color: '#ffffff', damage: 8, speed: 10, count: 2 },
{ type: 'sunflower', color: '#ffcc00', damage: 5, speed: 5, count: 3 }
],
selectedPetal: 0,
playerStats: {
maxHealth: 100,
health: 100,
damage: 10,
speed: 5,
attackSpeed: 1,
petalCount: 1
}
};
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const healthEl = document.getElementById('health');
const healthFillEl = document.getElementById('health-fill');
const levelEl = document.getElementById('level');
const scoreEl = document.getElementById('score');
const xpFillEl = document.getElementById('xp-fill');
const enemyCountEl = document.getElementById('enemy-count');
const levelUpScreen = document.getElementById('level-up-screen');
const upgradeOptions = document.getElementById('upgrade-options');
const deathScreen = document.getElementById('death-screen');
const chatBox = document.getElementById('chat-box');
const chatInput = document.getElementById('chat-input');
const petalTypes = document.getElementById('petal-types');
canvas.width = config.viewportWidth;
canvas.height = config.viewportHeight;
function initPetalSelector() {
petalTypes.innerHTML = '';
gameState.petals.forEach((petal, index) => {
const petalEl = document.createElement('div');
petalEl.className = 'petal' + (index === gameState.selectedPetal ? ' selected' : '');
petalEl.style.background = petal.color;
petalEl.textContent = petal.type.charAt(0).toUpperCase();
petalEl.title = `${petal.type}\n伤害: ${petal.damage}\n速度: ${petal.speed}\n数量: ${petal.count}`;
petalEl.onclick = () => selectPetal(index);
petalTypes.appendChild(petalEl);
});
}
function selectPetal(index) {
gameState.selectedPetal = index;
document.querySelectorAll('.petal').forEach((el, i) => {
el.classList.toggle('selected', i === index);
});
}
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 25;
this.color = '#00ff88';
this.angle = 0;
this.speed = gameState.playerStats.speed;
this.maxHealth = gameState.playerStats.maxHealth;
this.health = gameState.playerStats.health;
this.damage = gameState.playerStats.damage;
this.attackCooldown = 0;
this.attackSpeed = gameState.playerStats.attackSpeed;
this.petals = [];
this.petalCount = gameState.playerStats.petalCount;
this.lastShot = 0;
this.invulnerable = 0;
this.initPetals();
}
initPetals() {
this.petals = [];
const petal = gameState.petals[gameState.selectedPetal];
const angleStep = (Math.PI * 2) / petal.count;
for (let i = 0; i < petal.count; i++) {
this.petals.push({
angle: angleStep * i,
distance: this.radius + 10,
color: petal.color,
damage: petal.damage,
speed: petal.speed
});
}
}
update() {
let moveX = 0;
let moveY = 0;
if (gameState.keys['ArrowUp'] || gameState.keys['w']) moveY -= 1;
if (gameState.keys['ArrowDown'] || gameState.keys['s']) moveY += 1;
if (gameState.keys['ArrowLeft'] || gameState.keys['a']) moveX -= 1;
if (gameState.keys['ArrowRight'] || gameState.keys['d']) moveX += 1;
if (moveX !== 0 && moveY !== 0) {
moveX *= 0.7071;
moveY *= 0.7071;
}
this.x += moveX * this.speed;
this.y += moveY * this.speed;
this.x = Math.max(this.radius, Math.min(config.canvasWidth - this.radius, this.x));
this.y = Math.max(this.radius, Math.min(config.canvasHeight - this.radius, this.y));
const dx = gameState.mouse.x + gameState.camera.x - this.x;
const dy = gameState.mouse.y + gameState.camera.y - this.y;
this.angle = Math.atan2(dy, dx);
this.attackCooldown--;
if (this.attackCooldown <= 0) {
this.shoot();
this.attackCooldown = 60 / this.attackSpeed;
}
this.petalCount = gameState.playerStats.petalCount;
const currentPetal = gameState.petals[gameState.selectedPetal];
if (this.petals.length !== currentPetal.count) {
this.initPetals();
}
if (this.invulnerable > 0) {
this.invulnerable--;
}
gameState.camera.x = this.x - config.viewportWidth / 2;
gameState.camera.y = this.y - config.viewportHeight / 2;
}
shoot() {
const petal = gameState.petals[gameState.selectedPetal];
for (let i = 0; i < this.petals.length; i++) {
const petalInfo = this.petals[i];
const angle = this.angle + petalInfo.angle;
const projectile = {
x: this.x + Math.cos(angle) * petalInfo.distance,
y: this.y + Math.sin(angle) * petalInfo.distance,
angle: angle,
speed: petalInfo.speed,
damage: petalInfo.damage,
color: petalInfo.color,
radius: 8,
lifetime: 60,
owner: 'player'
};
gameState.projectiles.push(projectile);
for (let j = 0; j < 3; j++) {
gameState.particles.push({
x: projectile.x,
y: projectile.y,
vx: Math.cos(angle) * 2 + (Math.random() - 0.5) * 2,
vy: Math.sin(angle) * 2 + (Math.random() - 0.5) * 2,
radius: 3,
color: petalInfo.color,
lifetime: 20
});
}
}
}
takeDamage(amount) {
if (this.invulnerable > 0) return;
this.health -= amount;
gameState.playerStats.health = this.health;
showDamage(this.x, this.y, amount);
this.invulnerable = 20;
for (let i = 0; i < 10; i++) {
gameState.particles.push({
x: this.x,
y: this.y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
radius: 4,
color: '#ff4444',
lifetime: 30
});
}
updateHealth();
if (this.health <= 0) {
this.die();
}
}
heal(amount) {
this.health = Math.min(this.maxHealth, this.health + amount);
gameState.playerStats.health = this.health;
updateHealth();
for (let i = 0; i < 5; i++) {
gameState.particles.push({
x: this.x,
y: this.y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
radius: 3,
color: '#00ff88',
lifetime: 40
});
}
}
die() {
gameState.gameRunning = false;
for (let i = 0; i < 20; i++) {
gameState.particles.push({
x: this.x,
y: this.y,
vx: (Math.random() - 0.5) * 15,
vy: (Math.random() - 0.5) * 15,
radius: 6,
color: this.color,
lifetime: 60
});
}
showDeathScreen();
}
draw() {
ctx.save();
ctx.translate(this.x - gameState.camera.x, this.y - gameState.camera.y);
for (let i = 0; i < this.petals.length; i++) {
const petal = this.petals[i];
const angle = this.angle + petal.angle;
const x = Math.cos(angle) * petal.distance;
const y = Math.sin(angle) * petal.distance;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle + Math.PI / 2);
ctx.fillStyle = petal.color;
ctx.beginPath();
ctx.moveTo(0, -10);
ctx.lineTo(8, 5);
ctx.lineTo(0, 15);
ctx.lineTo(-8, 5);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
}
ctx.fillStyle = this.invulnerable % 10 < 5 ? this.color : 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(8, -5, 5, 0, Math.PI * 2);
ctx.arc(-8, -5, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
const lookAngle = this.angle;
ctx.arc(8 + Math.cos(lookAngle) * 2, -5 + Math.sin(lookAngle) * 2, 2, 0, Math.PI * 2);
ctx.arc(-8 + Math.cos(lookAngle) * 2, -5 + Math.sin(lookAngle) * 2, 2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 5, 8, 0.2 * Math.PI, 0.8 * Math.PI);
ctx.stroke();
ctx.restore();
}
}
class Enemy {
constructor(x, y, type = 'basic') {
this.x = x;
this.y = y;
this.type = type;
switch(type) {
case 'basic':
this.radius = 20;
this.color = '#ff8800';
this.speed = 1.5;
this.health = 30;
this.damage = 10;
this.xpValue = 10;
break;
case 'fast':
this.radius = 15;
this.color = '#00ccff';
this.speed = 3;
this.health = 15;
this.damage = 5;
this.xpValue = 7;
break;
case 'tank':
this.radius = 35;
this.color = '#ff4444';
this.speed = 0.8;
this.health = 100;
this.damage = 20;
this.xpValue = 25;
break;
case 'boss':
this.radius = 50;
this.color = '#ff00ff';
this.speed = 1;
this.health = 300;
this.damage = 30;
this.xpValue = 100;
break;
}
this.maxHealth = this.health;
this.angle = Math.random() * Math.PI * 2;
this.attackCooldown = 0;
this.flash = 0;
}
update() {
const dx = gameState.player.x - this.x;
const dy = gameState.player.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
this.angle = Math.atan2(dy, dx);
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
}
if (this.attackCooldown > 0) {
this.attackCooldown--;
}
if (this.flash > 0) {
this.flash--;
}
if (distance < this.radius + gameState.player.radius) {
if (this.attackCooldown === 0) {
gameState.player.takeDamage(this.damage);
this.attackCooldown = 60;
}
}
this.x = Math.max(this.radius, Math.min(config.canvasWidth - this.radius, this.x));
this.y = Math.max(this.radius, Math.min(config.canvasHeight - this.radius, this.y));
}
takeDamage(amount) {
this.health -= amount;
this.flash = 5;
showDamage(this.x, this.y, amount);
if (this.health <= 0) {
this.die();
return true;
}
return false;
}
die() {
addXP(this.xpValue);
gameState.score += this.xpValue;
gameState.kills++;
if (Math.random() < 0.3) {
spawnPellet(this.x, this.y);
}
for (let i = 0; i < 15; i++) {
gameState.particles.push({
x: this.x,
y: this.y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
radius: 4,
color: this.color,
lifetime: 40
});
}
const index = gameState.enemies.indexOf(this);
if (index > -1) {
gameState.enemies.splice(index, 1);
}
}
draw() {
const screenX = this.x - gameState.camera.x;
const screenY = this.y - gameState.camera.y;
ctx.save();
ctx.translate(screenX, screenY);
const color = this.flash > 0 ? '#ffffff' : this.color;
ctx.fillStyle = color;
ctx.beginPath();
if (this.type === 'boss') {
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ffcc00';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(0, 0, this.radius + 5, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = '#ff4444';
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
ctx.save();
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, -this.radius - 10);
ctx.lineTo(5, -this.radius);
ctx.lineTo(-5, -this.radius);
ctx.closePath();
ctx.fill();
ctx.restore();
}
} else {
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(5, -3, 4, 0, Math.PI * 2);
ctx.arc(-5, -3, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(6, -3, 2, 0, Math.PI * 2);
ctx.arc(-4, -3, 2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 5, 6, 0.1 * Math.PI, 0.9 * Math.PI);
ctx.stroke();
}
if (this.health < this.maxHealth) {
const healthPercent = this.health / this.maxHealth;
const barWidth = this.radius * 2;
const barHeight = 5;
ctx.fillStyle = '#ff4444';
ctx.fillRect(-this.radius, -this.radius - 10, barWidth, barHeight);
ctx.fillStyle = '#00ff88';
ctx.fillRect(-this.radius, -this.radius - 10, barWidth * healthPercent, barHeight);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 1;
ctx.strokeRect(-this.radius, -this.radius - 10, barWidth, barHeight);
}
ctx.restore();
}
}
function spawnEnemy() {
if (gameState.enemies.length >= config.maxEnemies) return;
const types = ['basic', 'fast', 'tank'];
const weights = [0.7, 0.2, 0.1];
if (gameState.level >= 10 && Math.random() < 0.01) {
types.push('boss');
weights.push(0.01);
}
let type = 'basic';
let rand = Math.random();
let cumulative = 0;
for (let i = 0; i < types.length; i++) {
cumulative += weights[i];
if (rand < cumulative) {
type = types[i];
break;
}
}
const angle = Math.random() * Math.PI * 2;
const distance = Math.min(config.canvasWidth, config.canvasHeight) * 0.4;
const x = gameState.player.x + Math.cos(angle) * distance;
const y = gameState.player.y + Math.sin(angle) * distance;
const validX = Math.max(50, Math.min(config.canvasWidth - 50, x));
const validY = Math.max(50, Math.min(config.canvasHeight - 50, y));
const enemy = new Enemy(validX, validY, type);
gameState.enemies.push(enemy);
}
function spawnPellet(x, y) {
if (gameState.pellets.length >= config.maxPellets) return;
const colors = ['#00ff88', '#ff8800', '#00ccff', '#ff00ff', '#ffff00'];
const color = colors[Math.floor(Math.random() * colors.length)];
gameState.pellets.push({
x: x,
y: y,
radius: 6,
color: color,
value: 1,
type: Math.random() < 0.1 ? 'health' : 'xp'
});
}
function addXP(amount) {
gameState.xp += amount;
for (let i = 0; i < 3; i++) {
gameState.particles.push({
x: gameState.player.x,
y: gameState.player.y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4 - 2,
radius: 3,
color: '#00ccff',
lifetime: 30
});
}
if (gameState.xp >= gameState.xpToNextLevel) {
levelUp();
}
updateXP();
}
function levelUp() {
gameState.level++;
gameState.xp -= gameState.xpToNextLevel;
gameState.xpToNextLevel = Math.floor(100 * Math.pow(1.2, gameState.level - 1));
showLevelUpScreen();
for (let i = 0; i < 20; i++) {
gameState.particles.push({
x: gameState.player.x,
y: gameState.player.y,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
radius: 6,
color: '#ffcc00',
lifetime: 60
});
}
updateLevel();
}
function showLevelUpScreen() {
levelUpScreen.style.display = 'flex';
upgradeOptions.innerHTML = '';
const upgrades = [
{ type: 'health', name: '最大生命值', desc: '+20 最大生命值', icon: '❤️', value: 20 },
{ type: 'damage', name: '伤害', desc: '+5 伤害', icon: '⚔️', value: 5 },
{ type: 'speed', name: '移动速度', desc: '+1 移动速度', icon: '⚡', value: 1 },
{ type: 'attackSpeed', name: '攻击速度', desc: '+0.2 攻击速度', icon: '🎯', value: 0.2 },
{ type: 'petalCount', name: '花瓣数量', desc: '+1 花瓣数量', icon: '🌸', value: 1 },
{ type: 'heal', name: '治疗', desc: '恢复 50 生命值', icon: '💊', value: 50 }
];
const selectedUpgrades = [];
while (selectedUpgrades.length < 3 && upgrades.length > 0) {
const index = Math.floor(Math.random() * upgrades.length);
selectedUpgrades.push(upgrades.splice(index, 1)[0]);
}
selectedUpgrades.forEach(upgrade => {
const option = document.createElement('div');
option.className = 'upgrade-option';
option.innerHTML = `
<div style="font-size: 24px;">${upgrade.icon}</div>
<div style="font-weight: bold; margin: 10px 0;">${upgrade.name}</div>
<div style="font-size: 12px;">${upgrade.desc}</div>
`;
option.onclick = () => selectUpgrade(upgrade);
upgradeOptions.appendChild(option);
});
}
function selectUpgrade(upgrade) {
switch(upgrade.type) {
case 'health':
gameState.playerStats.maxHealth += upgrade.value;
gameState.player.maxHealth = gameState.playerStats.maxHealth;
gameState.player.heal(upgrade.value);
break;
case 'damage':
gameState.playerStats.damage += upgrade.value;
gameState.player.damage = gameState.playerStats.damage;
break;
case 'speed':
gameState.playerStats.speed += upgrade.value;
gameState.player.speed = gameState.playerStats.speed;
break;
case 'attackSpeed':
gameState.playerStats.attackSpeed += upgrade.value;
gameState.player.attackSpeed = gameState.playerStats.attackSpeed;
break;
case 'petalCount':
gameState.playerStats.petalCount += upgrade.value;
gameState.player.petalCount = gameState.playerStats.petalCount;
gameState.player.initPetals();
break;
case 'heal':
gameState.player.heal(upgrade.value);
break;
}
levelUpScreen.style.display = 'none';
}
function showDeathScreen() {
document.getElementById('final-level').textContent = gameState.level;
document.getElementById('final-score').textContent = gameState.score;
document.getElementById('final-kills').textContent = gameState.kills;
document.getElementById('final-time').textContent = Math.floor(gameState.gameTime / 60) + 's';
deathScreen.style.display = 'flex';
}
function restartGame() {
gameState.level = 1;
gameState.xp = 0;
gameState.xpToNextLevel = 100;
gameState.score = 0;
gameState.kills = 0;
gameState.gameTime = 0;
gameState.gameRunning = true;
gameState.playerStats = {
maxHealth: 100,
health: 100,
damage: 10,
speed: 5,
attackSpeed: 1,
petalCount: 1
};
gameState.player = new Player(config.canvasWidth / 2, config.canvasHeight / 2);
gameState.enemies = [];
gameState.pellets = [];
gameState.projectiles = [];
gameState.particles = [];
deathScreen.style.display = 'none';
updateHealth();
updateLevel();
updateScore();
updateXP();
enemyCountEl.textContent = '0';
requestAnimationFrame(gameLoop);
}
function showDamage(x, y, amount) {
const screenX = x - gameState.camera.x;
const screenY = y - gameState.camera.y;
const popup = document.createElement('div');
popup.className = 'damage-popup';
popup.textContent = Math.round(amount);
popup.style.left = screenX + 'px';
popup.style.top = screenY + 'px';
document.querySelector('.game-container').appendChild(popup);
setTimeout(() => {
popup.remove();
}, 1000);
}
function updateHealth() {
const percent = (gameState.player.health / gameState.player.maxHealth) * 100;
healthFillEl.style.width = percent + '%';
healthEl.textContent = Math.round(gameState.player.health) + '/' + gameState.player.maxHealth;
}
function updateLevel() {
levelEl.textContent = gameState.level;
}
function updateScore() {
scoreEl.textContent = gameState.score;
}
function updateXP() {
const percent = (gameState.xp / gameState.xpToNextLevel) * 100;
xpFillEl.style.width = Math.min(100, percent) + '%';
}
function gameLoop() {
if (!gameState.gameRunning) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
gameState.player.update();
if (Math.random() < config.enemySpawnRate && gameState.enemies.length < config.maxEnemies) {
spawnEnemy();
}
if (Math.random() < 0.02 && gameState.pellets.length < config.maxPellets) {
const x = Math.random() * config.canvasWidth;
const y = Math.random() * config.canvasHeight;
spawnPellet(x, y);
}
gameState.enemies.forEach(enemy => enemy.update());
gameState.projectiles = gameState.projectiles.filter(projectile => {
projectile.x += Math.cos(projectile.angle) * projectile.speed;
projectile.y += Math.sin(projectile.angle) * projectile.speed;
projectile.lifetime--;
if (projectile.x < 0 || projectile.x > config.canvasWidth ||
projectile.y < 0 || projectile.y > config.canvasHeight) {
return false;
}
if (projectile.owner === 'player') {
for (let i = 0; i < gameState.enemies.length; i++) {
const enemy = gameState.enemies[i];
const dx = projectile.x - enemy.x;
const dy = projectile.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < projectile.radius + enemy.radius) {
const killed = enemy.takeDamage(projectile.damage);
for (let j = 0; j < 5; j++) {
gameState.particles.push({
x: projectile.x,
y: projectile.y,
vx: (Math.random() - 0.5) * 5,
vy: (Math.random() - 0.5) * 5,
radius: 3,
color: projectile.color,
lifetime: 20
});
}
return false;
}
}
}
return projectile.lifetime > 0;
});
gameState.pellets = gameState.pellets.filter(pellet => {
const dx = gameState.player.x - pellet.x;
const dy = gameState.player.y - pellet.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < gameState.player.radius + pellet.radius) {
if (pellet.type === 'xp') {
addXP(pellet.value * 5);
} else if (pellet.type === 'health') {
gameState.player.heal(pellet.value * 10);
}
for (let i = 0; i < 5; i++) {
gameState.particles.push({
x: pellet.x,
y: pellet.y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
radius: 3,
color: pellet.color,
lifetime: 20
});
}
return false;
}
return true;
});
gameState.particles = gameState.particles.filter(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
particle.vx *= 0.98;
particle.vy *= 0.98;
particle.lifetime--;
return particle.lifetime > 0;
});
for (const key in gameState.abilities) {
if (gameState.abilities[key].cooldown > 0) {
gameState.abilities[key].cooldown--;
const abilityEl = document.getElementById(`ability${key.toUpperCase()}`);
if (abilityEl) {
const cooldownEl = abilityEl.querySelector('.ability-cooldown');
const percent = (gameState.abilities[key].cooldown / gameState.abilities[key].maxCooldown) * 100;
cooldownEl.style.height = percent + '%';
}
}
}
gameState.gameTime++;
enemyCountEl.textContent = gameState.enemies.length;
gameState.pellets.forEach(pellet => {
const screenX = pellet.x - gameState.camera.x;
const screenY = pellet.y - gameState.camera.y;
ctx.fillStyle = pellet.color;
ctx.beginPath();
ctx.arc(screenX, screenY, pellet.radius, 0, Math.PI * 2);
ctx.fill();
ctx.shadowColor = pellet.color;
ctx.shadowBlur = 10;
ctx.fill();
ctx.shadowBlur = 0;
});
gameState.enemies.forEach(enemy => enemy.draw());
gameState.projectiles.forEach(projectile => {
const screenX = projectile.x - gameState.camera.x;
const screenY = projectile.y - gameState.camera.y;
ctx.fillStyle = projectile.color;
ctx.beginPath();
ctx.arc(screenX, screenY, projectile.radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = projectile.color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(screenX, screenY);
ctx.lineTo(
screenX - Math.cos(projectile.angle) * 10,
screenY - Math.sin(projectile.angle) * 10
);
ctx.stroke();
});
gameState.player.draw();
gameState.particles.forEach(particle => {
const screenX = particle.x - gameState.camera.x;
const screenY = particle.y - gameState.camera.y;
ctx.fillStyle = particle.color;
ctx.globalAlpha = particle.lifetime / 60;
ctx.beginPath();
ctx.arc(screenX, screenY, particle.radius, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
});
requestAnimationFrame(gameLoop);
}
function drawGrid() {
const gridSize = 100;
const startX = Math.floor(gameState.camera.x / gridSize) * gridSize;
const startY = Math.floor(gameState.camera.y / gridSize) * gridSize;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
for (let x = startX; x < gameState.camera.x + canvas.width; x += gridSize) {
const screenX = x - gameState.camera.x;
ctx.beginPath();
ctx.moveTo(screenX, 0);
ctx.lineTo(screenX, canvas.height);
ctx.stroke();
}
for (let y = startY; y < gameState.camera.y + canvas.height; y += gridSize) {
const screenY = y - gameState.camera.y;
ctx.beginPath();
ctx.moveTo(0, screenY);
ctx.lineTo(canvas.width, screenY);
ctx.stroke();
}
}
function initEventListeners() {
document.addEventListener('keydown', (e) => {
gameState.keys[e.key.toLowerCase()] = true;
if (e.key === 'q' && gameState.abilities.q.cooldown === 0) {
useAbility('q');
}
if (e.key === 'w' && gameState.abilities.w.cooldown === 0) {
useAbility('w');
}
if (e.key === 'e' && gameState.abilities.e.cooldown === 0) {
useAbility('e');
}
if (e.key === 'Enter') {
if (chatInput.style.display === 'none' || chatInput.style.display === '') {
chatInput.style.display = 'block';
chatInput.focus();
} else {
if (chatInput.value.trim()) {
addChatMessage('玩家: ' + chatInput.value);
chatInput.value = '';
}
chatInput.style.display = 'none';
}
}
if (e.key === 't' && chatInput.style.display === 'none') {
chatInput.style.display = 'block';
chatBox.style.display = 'block';
chatInput.focus();
}
if (e.key >= '1' && e.key <= '4') {
const index = parseInt(e.key) - 1;
if (index < gameState.petals.length) {
selectPetal(index);
}
}
});
document.addEventListener('keyup', (e) => {
gameState.keys[e.key.toLowerCase()] = false;
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
gameState.mouse.x = e.clientX - rect.left;
gameState.mouse.y = e.clientY - rect.top;
});
let joystickActive = false;
const joystickArea = document.getElementById('joystick-area');
const joystick = document.getElementById('joystick');
joystickArea.addEventListener('touchstart', (e) => {
e.preventDefault();
joystickActive = true;
updateJoystick(e.touches[0]);
});
document.addEventListener('touchmove', (e) => {
if (joystickActive) {
e.preventDefault();
updateJoystick(e.touches[0]);
}
});
document.addEventListener('touchend', (e) => {
if (joystickActive) {
joystickActive = false;
joystick.style.left = '30px';
joystick.style.top = '30px';
gameState.keys['w'] = false;
gameState.keys['a'] = false;
gameState.keys['s'] = false;
gameState.keys['d'] = false;
}
});
function updateJoystick(touch) {
const rect = joystickArea.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
const distance = Math.sqrt(x * x + y * y);
const maxDistance = 60;
const angle = Math.atan2(y, x);
if (distance > maxDistance) {
const limitedX = Math.cos(angle) * maxDistance;
const limitedY = Math.sin(angle) * maxDistance;
joystick.style.left = (limitedX + 30 - 15) + 'px';
joystick.style.top = (limitedY + 30 - 15) + 'px';
} else {
joystick.style.left = (x - 15) + 'px';
joystick.style.top = (y - 15) + 'px';
}
const deadzone = 20;
if (distance > deadzone) {
gameState.keys['w'] = y < -deadzone;
gameState.keys['s'] = y > deadzone;
gameState.keys['a'] = x < -deadzone;
gameState.keys['d'] = x > deadzone;
} else {
gameState.keys['w'] = false;
gameState.keys['s'] = false;
gameState.keys['a'] = false;
gameState.keys['d'] = false;
}
}
document.getElementById('ability1').addEventListener('click', () => useAbility('q'));
document.getElementById('ability2').addEventListener('click', () => useAbility('w'));
document.getElementById('ability3').addEventListener('click', () => useAbility('e'));
document.getElementById('up').addEventListener('mousedown', () => gameState.keys['w'] = true);
document.getElementById('up').addEventListener('mouseup', () => gameState.keys['w'] = false);
document.getElementById('down').addEventListener('mousedown', () => gameState.keys['s'] = true);
document.getElementById('down').addEventListener('mouseup', () => gameState.keys['s'] = false);
document.getElementById('left').addEventListener('mousedown', () => gameState.keys['a'] = true);
document.getElementById('left').addEventListener('mouseup', () => gameState.keys['a'] = false);
document.getElementById('right').addEventListener('mousedown', () => gameState.keys['d'] = true);
document.getElementById('right').addEventListener('mouseup', () => gameState.keys['d'] = false);
document.getElementById('up').addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.keys['w'] = true;
});
document.getElementById('up').addEventListener('touchend', (e) => {
e.preventDefault();
gameState.keys['w'] = false;
});
document.getElementById('down').addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.keys['s'] = true;
});
document.getElementById('down').addEventListener('touchend', (e) => {
e.preventDefault();
gameState.keys['s'] = false;
});
document.getElementById('left').addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.keys['a'] = true;
});
document.getElementById('left').addEventListener('touchend', (e) => {
e.preventDefault();
gameState.keys['a'] = false;
});
document.getElementById('right').addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.keys['d'] = true;
});
document.getElementById('right').addEventListener('touchend', (e) => {
e.preventDefault();
gameState.keys['d'] = false;
});
window.addEventListener('resize', () => {
config.viewportWidth = window.innerWidth;
config.viewportHeight = window.innerHeight - 60;
canvas.width = config.viewportWidth;
canvas.height = config.viewportHeight;
});
}
function useAbility(ability) {
const abilityData = gameState.abilities[ability];
if (abilityData.cooldown > 0) return;
abilityData.cooldown = abilityData.maxCooldown;
abilityData.active = true;
switch(ability) {
case 'q':
gameState.player.attackCooldown = 0;
for (let i = 0; i < 10; i++) {
gameState.particles.push({
x: gameState.player.x,
y: gameState.player.y,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
radius: 4,
color: '#ff8800',
lifetime: 30
});
}
break;
case 'w':
gameState.player.heal(30);
for (let i = 0; i < 15; i++) {
const angle = (i / 15) * Math.PI * 2;
gameState.particles.push({
x: gameState.player.x + Math.cos(angle) * 50,
y: gameState.player.y + Math.sin(angle) * 50,
vx: Math.cos(angle + Math.PI) * 3,
vy: Math.sin(angle + Math.PI) * 3,
radius: 5,
color: '#00ff88',
lifetime: 40
});
}
break;
case 'e':
for (let i = 0; i < gameState.enemies.length; i++) {
const enemy = gameState.enemies[i];
const dx = enemy.x - gameState.player.x;
const dy = enemy.y - gameState.player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
enemy.takeDamage(50);
}
}
for (let i = 0; i < 30; i++) {
const angle = (i / 30) * Math.PI * 2;
gameState.particles.push({
x: gameState.player.x,
y: gameState.player.y,
vx: Math.cos(angle) * 10,
vy: Math.sin(angle) * 10,
radius: 6,
color: '#ff4444',
lifetime: 50
});
}
break;
}
}
function addChatMessage(message) {
const chatMessage = document.createElement('div');
chatMessage.className = 'chat-message';
chatMessage.textContent = message;
chatBox.appendChild(chatMessage);
chatBox.scrollTop = chatBox.scrollHeight;
}
function initGame() {
gameState.player = new Player(config.canvasWidth / 2, config.canvasHeight / 2);
updateHealth();
updateLevel();
updateScore();
updateXP();
initPetalSelector();
for (let i = 0; i < 5; i++) {
spawnEnemy();
}
for (let i = 0; i < 10; i++) {
const x = Math.random() * config.canvasWidth;
const y = Math.random() * config.canvasHeight;
spawnPellet(x, y);
}
initEventListeners();
requestAnimationFrame(gameLoop);
addChatMessage('系统: 欢迎来到 Florr.io 风格竞技场!');
addChatMessage('系统: 使用 WASD 或方向键移动,鼠标控制射击方向');
addChatMessage('系统: Q/W/E 键使用特殊能力');
addChatMessage('系统: 数字键 1-4 切换花瓣类型');
}
window.onload = initGame;
</script>
</body>
</html>