<!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: 'Segoe UI', sans-serif;
}
body {
background: #1a1a1a;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 20px;
color: #fff;
overflow: hidden;
}
#game-container {
position: relative;
border: 2px solid #3498db;
border-radius: 4px;
background: #111;
overflow: hidden;
}
#game-canvas {
display: block;
background: #0f0f0f;
image-rendering: pixelated;
}
#game-hud {
display: flex;
justify-content: space-between;
width: 1000px;
margin-top: 15px;
background: #222;
padding: 10px;
border-radius: 6px;
}
#stats-panel {
display: flex;
gap: 15px;
font-size: 14px;
}
.level { color: #f1c40f; }
.hp { color: #e74c3c; }
.petals { color: #2ecc71; }
#controls-panel {
font-size: 13px;
color: #aaa;
}
#petal-slots {
width: 1000px;
margin-top: 10px;
background: #222;
padding: 10px;
border-radius: 6px;
display: flex;
gap: 8px;
}
.petal-slot {
width: 40px;
height: 40px;
border-radius: 50%;
background: #333;
border: 2px solid #444;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.petal-slot.active {
border-color: #3498db;
}
.petal-slot .petal {
width: 30px;
height: 30px;
border-radius: 50%;
}
.petal-slot .count {
position: absolute;
bottom: -5px;
right: -5px;
background: #000;
color: #fff;
font-size: 10px;
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas" width="1000" height="700"></canvas>
</div>
<div id="game-hud">
<div id="stats-panel">
<div><span class="level">等级: 1</span></div>
<div><span class="hp">生命值: 100/100</span></div>
<div><span class="petals">花瓣数: 15</span></div>
<div><span class="xp">经验: 0/100</span></div>
</div>
<div id="controls-panel">
🖱️ 鼠标控制移动方向 | 🌸 花瓣直接碰撞攻击 | 🎒 数字键切换花瓣类型
</div>
</div>
<div id="petal-slots">
<div class="petal-slot active" data-slot="1">
<div class="petal" style="background: #ffd700;"></div>
<div class="count">15</div>
</div>
<div class="petal-slot" data-slot="2">
<div class="petal" style="background: #1e90ff;"></div>
<div class="count">8</div>
</div>
<div class="petal-slot" data-slot="3">
<div class="petal" style="background: #ff6347;"></div>
<div class="count">5</div>
</div>
</div>
<script>
// 游戏核心配置 (完全匹配florr.io)
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const statsPanel = document.getElementById('stats-panel');
// 游戏世界(玩家始终在视觉中心,移动的是世界而非玩家)
const world = {
offsetX: 0,
offsetY: 0,
scale: 1
};
// 玩家对象 (florr.io核心属性)
const player = {
// 玩家始终在画布中心(视觉上)
visualX: canvas.width / 2,
visualY: canvas.height / 2,
// 实际世界坐标
worldX: 0,
worldY: 0,
coreSize: 20, // 核心大小
speed: 4, // 移动速度
level: 1,
hp: 100,
maxHp: 100,
xp: 0,
xpToNextLevel: 100,
// 花瓣系统 (核心战斗机制)
activePetalType: 1,
petals: {
1: { type: 'normal', count: 15, damage: 2, size: 12, color: '#ffd700', speed: 0.03 },
2: { type: 'fast', count: 8, damage: 1, size: 8, color: '#1e90ff', speed: 0.05 },
3: { type: 'strong', count: 5, damage: 5, size: 16, color: '#ff6347', speed: 0.02 }
},
// 花瓣实体(围绕核心旋转的战斗单元)
petalEntities: [],
// 鼠标相对位置(控制移动)
mouseRelativeX: 0,
mouseRelativeY: 0
};
// 游戏实体
const entities = {
enemies: [], // 敌人 (bug/larva/flower)
resources: [], // 可收集资源 (花瓣/经验球)
particles: [] // 特效粒子
};
// 游戏状态
let mouseX = 0;
let mouseY = 0;
let frameCount = 0;
// ======================
// 初始化花瓣实体 (核心战斗单元)
// ======================
function initPetals() {
player.petalEntities = [];
const petalType = player.petals[player.activePetalType];
const petalCount = petalType.count;
// 创建围绕玩家核心旋转的花瓣
for (let i = 0; i < petalCount; i++) {
const angle = (i / petalCount) * Math.PI * 2;
const distance = player.coreSize + 10 + (i % 3) * 8; // 分层排列
player.petalEntities.push({
angle: angle,
distance: distance,
size: petalType.size,
color: petalType.color,
damage: petalType.damage,
rotateSpeed: petalType.speed,
// 碰撞盒
worldX: player.worldX + Math.cos(angle) * distance,
worldY: player.worldY + Math.sin(angle) * distance
});
}
}
// ======================
// 事件监听 (florr.io原版操作)
// ======================
// 鼠标位置跟踪(核心移动控制)
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
// 计算鼠标相对于玩家视觉中心的偏移
player.mouseRelativeX = mouseX - player.visualX;
player.mouseRelativeY = mouseY - player.visualY;
});
// 键盘切换花瓣类型
document.addEventListener('keydown', (e) => {
const slotNum = parseInt(e.key);
if (slotNum >= 1 && slotNum <= 3) {
player.activePetalType = slotNum;
initPetals(); // 切换花瓣类型
// 更新UI
document.querySelectorAll('.petal-slot').forEach(slot => {
slot.classList.remove('active');
if (parseInt(slot.dataset.slot) === slotNum) {
slot.classList.add('active');
}
});
}
});
// ======================
// 核心游戏逻辑
// ======================
// 玩家移动 (florr.io核心机制:按鼠标相对方向移动)
function updatePlayerMovement() {
// 计算移动方向(基于鼠标相对位置)
const moveAngle = Math.atan2(player.mouseRelativeY, player.mouseRelativeX);
const moveDistance = Math.hypot(player.mouseRelativeX, player.mouseRelativeY);
// 只有鼠标远离中心一定距离才移动
if (moveDistance > 20) {
// 移动玩家的世界坐标
player.worldX += Math.cos(moveAngle) * player.speed;
player.worldY += Math.sin(moveAngle) * player.speed;
// 更新世界偏移(保持玩家在视觉中心)
world.offsetX = -player.worldX + player.visualX;
world.offsetY = -player.worldY + player.visualY;
}
}
// 更新花瓣位置和旋转
function updatePetals() {
const petalType = player.petals[player.activePetalType];
player.petalEntities.forEach((petal, index) => {
// 花瓣旋转
petal.angle += petal.rotateSpeed;
// 更新花瓣世界坐标
petal.worldX = player.worldX + Math.cos(petal.angle) * petal.distance;
petal.worldY = player.worldY + Math.sin(petal.angle) * petal.distance;
// 花瓣碰撞检测(直接接触攻击)
checkPetalCollision(petal, index);
});
}
// 花瓣碰撞检测(核心战斗逻辑:直接接触伤害)
function checkPetalCollision(petal, petalIndex) {
for (let i = entities.enemies.length - 1; i >= 0; i--) {
const enemy = entities.enemies[i];
const distance = Math.hypot(petal.worldX - enemy.worldX, petal.worldY - enemy.worldY);
// 花瓣接触敌人 = 造成伤害
if (distance < petal.size/2 + enemy.size/2) {
enemy.hp -= petal.damage;
// 生成伤害粒子
entities.particles.push({
x: petal.worldX,
y: petal.worldY,
size: 3 + Math.random() * 2,
color: petal.color,
speed: 1 + Math.random() * 2,
angle: Math.random() * Math.PI * 2,
lifetime: 15
});
// 敌人死亡
if (enemy.hp <= 0) {
// 掉落资源
spawnResources(enemy.worldX, enemy.worldY, enemy.type);
entities.enemies.splice(i, 1);
player.xp += enemy.maxHp * 2;
// 升级检测
if (player.xp >= player.xpToNextLevel) {
levelUp();
}
}
break;
}
}
}
// 生成敌人
function spawnEnemies() {
if (frameCount % 120 !== 0 || entities.enemies.length > 10) return;
const enemyTypes = [
{ type: 'bug', size: 15, hp: 10, speed: 2.5, color: '#34495e', damage: 1 },
{ type: 'larva', size: 18, hp: 15, speed: 1.8, color: '#8e44ad', damage: 2 },
{ type: 'small_flower', size: 22, hp: 25, speed: 1.5, color: '#e74c3c', damage: 3 }
];
const enemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
// 随机生成在玩家周围一定距离
const spawnDistance = 400 + Math.random() * 200;
const spawnAngle = Math.random() * Math.PI * 2;
entities.enemies.push({
worldX: player.worldX + Math.cos(spawnAngle) * spawnDistance,
worldY: player.worldY + Math.sin(spawnAngle) * spawnDistance,
size: enemyType.size,
color: enemyType.color,
hp: enemyType.hp,
maxHp: enemyType.hp,
speed: enemyType.speed,
damage: enemyType.damage,
type: enemyType.type
});
}
// 生成资源
function spawnResources(x, y, enemyType) {
// 掉落经验球
entities.resources.push({
worldX: x + (Math.random() - 0.5) * 30,
worldY: y + (Math.random() - 0.5) * 30,
size: 10,
color: '#9370db',
type: 'xp',
value: 10,
floatSpeed: 0.02 + Math.random() * 0.02
});
// 随机掉落花瓣
if (Math.random() > 0.3) {
const petalTypes = ['normal', 'fast', 'strong'];
const randomPetal = petalTypes[Math.floor(Math.random() * petalTypes)];
let color, size, typeId;
if (randomPetal === 'normal') { color = '#ffd700'; size = 8; typeId = 1; }
else if (randomPetal === 'fast') { color = '#1e90ff'; size = 6; typeId = 2; }
else { color = '#ff6347'; size = 10; typeId = 3; }
entities.resources.push({
worldX: x + (Math.random() - 0.5) * 40,
worldY: y + (Math.random() - 0.5) * 40,
size: size,
color: color,
type: 'petal',
petalType: typeId,
floatSpeed: 0.01 + Math.random() * 0.02
});
}
}
// 更新敌人AI
function updateEnemies() {
entities.enemies.forEach(enemy => {
// 敌人向玩家移动
const angle = Math.atan2(player.worldY - enemy.worldY, player.worldX - enemy.worldX);
enemy.worldX += Math.cos(angle) * enemy.speed;
enemy.worldY += Math.sin(angle) * enemy.speed;
// 敌人接触玩家核心 = 玩家掉血
const distanceToPlayer = Math.hypot(player.worldX - enemy.worldX, player.worldY - enemy.worldY);
if (distanceToPlayer < player.coreSize/2 + enemy.size/2) {
player.hp -= enemy.damage * 0.1;
if (player.hp <= 0) {
resetGame();
}
}
});
}
// 资源收集(触碰即收集)
function collectResources() {
for (let i = entities.resources.length - 1; i >= 0; i--) {
const resource = entities.resources[i];
const distance = Math.hypot(player.worldX - resource.worldX, player.worldY - resource.worldY);
// 触碰收集
if (distance < player.coreSize + resource.size) {
if (resource.type === 'xp') {
player.xp += resource.value;
if (player.xp >= player.xpToNextLevel) {
levelUp();
}
} else if (resource.type === 'petal') {
// 收集花瓣
player.petals[resource.petalType].count++;
initPetals(); // 更新花瓣实体
}
entities.resources.splice(i, 1);
} else {
// 资源漂浮动画
resource.worldY += Math.sin(frameCount * resource.floatSpeed) * 0.8;
}
}
}
// 更新粒子特效
function updateParticles() {
for (let i = entities.particles.length - 1; i >= 0; i--) {
const particle = entities.particles[i];
particle.worldX = particle.x;
particle.worldY = particle.y;
particle.x += Math.cos(particle.angle) * particle.speed;
particle.y += Math.sin(particle.angle) * particle.speed;
particle.size *= 0.95;
particle.lifetime--;
if (particle.lifetime <= 0 || particle.size < 0.5) {
entities.particles.splice(i, 1);
}
}
}
// 玩家升级
function levelUp() {
player.level++;
player.xp -= player.xpToNextLevel;
player.xpToNextLevel = Math.floor(player.xpToNextLevel * 1.6);
player.maxHp += 25;
player.hp = player.maxHp;
player.speed += 0.2;
// 升级奖励花瓣
player.petals[1].count += 3;
initPetals();
}
// 重置游戏
function resetGame() {
player.worldX = 0;
player.worldY = 0;
world.offsetX = player.visualX;
world.offsetY = player.visualY;
player.level = 1;
player.hp = 100;
player.maxHp = 100;
player.xp = 0;
player.xpToNextLevel = 100;
player.speed = 4;
// 重置花瓣
player.petals[1].count = 15;
player.petals[2].count = 8;
player.petals[3].count = 5;
player.activePetalType = 1;
// 清空实体
entities.enemies = [];
entities.resources = [];
entities.particles = [];
initPetals();
}
// 更新UI
function updateUI() {
const totalPetals = player.petals[1].count + player.petals[2].count + player.petals[3].count;
statsPanel.innerHTML = `
<div><span class="level">等级: ${player.level}</span></div>
<div><span class="hp">生命值: ${Math.floor(player.hp)}/${player.maxHp}</span></div>
<div><span class="petals">花瓣数: ${totalPetals}</span></div>
<div><span class="xp">经验: ${player.xp}/${player.xpToNextLevel}</span></div>
`;
// 更新花瓣槽位计数
document.querySelectorAll('.petal-slot .count')[0].textContent = player.petals[1].count;
document.querySelectorAll('.petal-slot .count')[1].textContent = player.petals[2].count;
document.querySelectorAll('.petal-slot .count')[2].textContent = player.petals[3].count;
}
// ======================
// 渲染系统
// ======================
function renderGame() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制背景网格
ctx.strokeStyle = '#222';
ctx.lineWidth = 1;
// 绘制无限网格(跟随世界移动)
const gridSize = 40;
const startX = (world.offsetX % gridSize) - gridSize;
const startY = (world.offsetY % gridSize) - gridSize;
for (let x = startX; x < canvas.width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = startY; y < canvas.height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// 绘制资源
entities.resources.forEach(resource => {
const screenX = resource.worldX + world.offsetX;
const screenY = resource.worldY + world.offsetY;
ctx.beginPath();
ctx.arc(screenX, screenY, resource.size, 0, Math.PI * 2);
ctx.fillStyle = resource.color;
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 0.5;
ctx.stroke();
});
// 绘制敌人
entities.enemies.forEach(enemy => {
const screenX = enemy.worldX + world.offsetX;
const screenY = enemy.worldY + world.offsetY;
ctx.save();
ctx.translate(screenX, screenY);
// 敌人主体
ctx.beginPath();
ctx.arc(0, 0, enemy.size/2, 0, Math.PI * 2);
ctx.fillStyle = enemy.color;
ctx.fill();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.stroke();
// HP条
const hpWidth = enemy.size;
ctx.fillStyle = '#e74c3c';
ctx.fillRect(-hpWidth/2, -enemy.size/2 - 5, hpWidth * (enemy.hp/enemy.maxHp), 3);
ctx.strokeStyle = '#000';
ctx.strokeRect(-hpWidth/2, -enemy.size/2 - 5, hpWidth, 3);
ctx.restore();
});
// 绘制粒子
entities.particles.forEach(particle => {
const screenX = particle.x + world.offsetX;
const screenY = particle.y + world.offsetY;
ctx.beginPath();
ctx.arc(screenX, screenY, particle.size, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
// 绘制玩家花瓣(核心战斗单元)
player.petalEntities.forEach(petal => {
const screenX = petal.worldX + world.offsetX;
const screenY = petal.worldY + world.offsetY;
ctx.beginPath();
ctx.arc(screenX, screenY, petal.size/2, 0, Math.PI * 2);
ctx.fillStyle = petal.color;
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 0.5;
ctx.stroke();
});
// 绘制玩家核心(始终在视觉中心)
ctx.beginPath();
ctx.arc(player.visualX, player.visualY, player.coreSize/2, 0, Math.PI * 2);
ctx.fillStyle = '#2ecc71';
ctx.fill();
ctx.strokeStyle = '#27ae60';
ctx.lineWidth = 2;
ctx.stroke();
// 玩家核心内部
ctx.beginPath();
ctx.arc(player.visualX, player.visualY, player.coreSize/6, 0, Math.PI * 2);
ctx.fillStyle = '#27ae60';
ctx.fill();
}
// ======================
// 游戏主循环
// ======================
function gameLoop() {
// 更新游戏逻辑
updatePlayerMovement();
spawnEnemies();
updatePetals();
updateEnemies();
collectResources();
updateParticles();
updateUI();
// 渲染画面
renderGame();
frameCount++;
requestAnimationFrame(gameLoop);
}
// 初始化游戏
function initGame() {
world.offsetX = player.visualX;
world.offsetY = player.visualY;
initPetals();
gameLoop();
}
// 启动游戏
window.addEventListener('load', initGame);
</script>
</body>
</html>