<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Florr.io Offline</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #1a1a1a; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
#game { border: 3px solid #333; background: #2d2d2d; }
#ui { position: absolute; top: 10px; left: 10px; color: #fff; font-size: 14px; line-height: 1.5; background: rgba(0,0,0,0.5); padding: 8px 12px; border-radius: 6px; }
</style>
</head>
<body>
<div id="ui">
Level: <span id="level">1</span> | XP: <span id="xp">0</span>/100<br>
HP: <span id="hp">100</span> | Petals: <span id="petalCount">3</span>
</div>
<canvas id="game" width="1000" height="600"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const ui = {
level: document.getElementById('level'),
xp: document.getElementById('xp'),
hp: document.getElementById('hp'),
petalCount: document.getElementById('petalCount')
};
// --------------------------
// 游戏数据
// --------------------------
const player = {
x: canvas.width/2,
y: canvas.height/2,
size: 24,
speed: 3.5,
hp: 100,
maxHp: 100,
level: 1,
xp: 0,
xpToNext: 100,
petals: [],
invulnerable: 0
};
const mobs = [];
const drops = [];
const keys = {};
let mouse = { x: player.x, y: player.y };
// 花瓣类型(文字替代)
const PETAL_TYPES = [
{ char: 'P', color: '#ff69b4', dmg: 8, hp: 20, speed: 0.12 },
{ char: 'S', color: '#ff4500', dmg: 168, hp: 1, speed: 0.18 },
{ char: 'B', color: '#1e90ff', dmg: 5, hp: 35, speed: 0.09 },
{ char: 'Y', color: '#ffd700', dmg: 12, hp: 18, speed: 0.15 }
];
// 怪物类型
const MOB_TYPES = [
{ char: '普通', color: '#90ee90', hp: 40, dmg: 6, speed: 1.5, xp: 150, dropChance: 0.20 },
{ char: '罕见', color: '#ffd700', hp: 120, dmg: 18, speed: 1.0, xp: 500, dropChance: 0.80 },
{ char: '稀有', color: '#1e90ff', hp: 360, dmg: 54, speed: 0.75, xp: 2000, dropChance: 4 }
];
// --------------------------
// 初始化
// --------------------------
function init() {
// 初始花瓣
for (let i=0; i<3; i++) {
addPetal(0);
}
// 初始怪物
for (let i=0; i<player.level+5; i++) spawnMob();
loop();
}
// 添加花瓣
function addPetal(typeIdx) {
const t = PETAL_TYPES[typeIdx];
player.petals.push({
type: typeIdx,
angle: Math.random() * Math.PI*2,
dist: 35+20,
hp: t.hp,
maxHp: t.hp
});
}
// 生成怪物
function spawnMob() {
const t = MOB_TYPES[Math.floor(Math.random()*MOB_TYPES.length)];
const side = Math.random() < 0.5 ? 0 : canvas.width;
mobs.push({
x: side + (Math.random()-0.5)*100,
y: Math.random()*canvas.height,
size: 20,
type: t,
hp: t.hp,
maxHp: t.hp
});
}
// --------------------------
// 输入
// --------------------------
window.addEventListener('mousemove', e => {
const r = canvas.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
window.addEventListener('keydown', e => { keys[e.code] = true; });
window.addEventListener('keyup', e => { keys[e.code] = false; });
// --------------------------
// 游戏循环
// --------------------------
function loop() {
update();
render();
requestAnimationFrame(loop);
}
// --------------------------
// 更新逻辑
// --------------------------
function update() {
// 玩家移动
const dx = mouse.x - player.x;
const dy = mouse.y - player.y;
const dist = Math.hypot(dx, dy);
if (dist > 4) {
player.x += (dx/dist) * player.speed;
player.y += (dy/dist) * player.speed;
}
// 边界
player.x = Math.max(player.size, Math.min(canvas.width-player.size, player.x));
player.y = Math.max(player.size, Math.min(canvas.height-player.size, player.y));
// 无敌帧
if (player.invulnerable > 0) player.invulnerable -= 1;
// 花瓣旋转
player.petals.forEach((p,i) => {
p.angle += PETAL_TYPES[p.type].speed;
if (p.hp <= 0) {
player.petals.splice(i, 1);
// 补一个低级花瓣
setTimeout(()=>addPetal(0), 3000);
}
});
// 怪物移动 & 追击
mobs.forEach((mob,i) => {
const dx = player.x - mob.x;
const dy = player.y - mob.y;
const dist = Math.hypot(dx, dy);
if (dist > 0) {
mob.x += (dx/dist) * mob.type.speed;
mob.y += (dy/dist) * mob.type.speed;
}
// 玩家碰撞
if (dist < mob.size + player.size && player.invulnerable === 0) {
player.hp -= mob.type.dmg;
player.invulnerable = 60;
checkGameOver();
}
// 死亡
if (mob.hp <= 0) {
player.xp += mob.type.xp;
checkLevelUp();
// 掉落
if (Math.random() < mob.type.dropChance) {
drops.push({
x: mob.x, y: mob.y,
type: Math.floor(Math.random()*PETAL_TYPES.length)
});
}
mobs.splice(i,1);
setTimeout(spawnMob, 800);
}
});
// 花瓣 vs 怪物
player.petals.forEach(p => {
const t = PETAL_TYPES[p.type];
const px = player.x + Math.cos(p.angle)*p.dist;
const py = player.y + Math.sin(p.angle)*p.dist;
mobs.forEach(mob => {
const d = Math.hypot(px-mob.x, py-mob.y);
if (d < 12 + mob.size/2) {
mob.hp -= t.dmg;
p.hp -= mob.type.dmg * 0.5;
}
});
});
// 拾取花瓣
drops.forEach((d,i) => {
if (Math.hypot(d.x-player.x, d.y-player.y) < 30) {
addPetal(d.type);
drops.splice(i,1);
}
});
// UI
ui.level.textContent = player.level;
ui.xp.textContent = player.xp;
ui.hp.textContent = Math.max(0, player.hp);
ui.petalCount.textContent = player.petals.length;
}
// 升级
function checkLevelUp() {
while (player.xp >= player.xpToNext) {
player.xp -= player.xpToNext;
player.level++;
player.xpToNext = Math.floor(player.xpToNext * 1.35);
player.maxHp += 15;
player.hp = player.maxHp;
player.speed += 0.15;
// 加花瓣
if (player.level % 2 === 0) {
addPetal(Math.min(1, player.level-1));
}
}
}
function checkGameOver() {
if (player.hp <= 0) {
alert(`Game Over | Level: ${player.level}`);
location.reload();
}
}
// --------------------------
// 渲染(文字版)
// --------------------------
function render() {
ctx.clearRect(0,0,canvas.width,canvas.height);
// 背景网格
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
for (let x=0;x<canvas.width;x+=40) {
ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,canvas.height); ctx.stroke();
}
for (let y=0;y<canvas.height;y+=40) {
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(canvas.width,y); ctx.stroke();
}
// 花瓣
player.petals.forEach(p => {
const t = PETAL_TYPES[p.type];
const px = player.x + Math.cos(p.angle)*p.dist;
const py = player.y + Math.sin(p.angle)*p.dist;
ctx.fillStyle = t.color;
ctx.font = '20px monospace';
ctx.textAlign = 'center';
ctx.fillText(t.char, px, py+6);
});
// 玩家
ctx.fillStyle = player.invulnerable%10<5 ? '#fff' : '#87ceeb';
ctx.font = 'bold 28px monospace';
ctx.fillText('●', player.x, player.y+8);
// 怪物
mobs.forEach(mob => {
ctx.fillStyle = mob.type.color;
ctx.font = '22px monospace';
ctx.fillText(mob.type.char, mob.x, mob.y+7);
// 血条
const w = 22;
const h = 3;
ctx.fillStyle = '#444';
ctx.fillRect(mob.x-w/2, mob.y-15, w, h);
ctx.fillStyle = '#ff4444';
ctx.fillRect(mob.x-w/2, mob.y-15, w*(mob.hp/mob.maxHp), h);
});
// 掉落
drops.forEach(d => {
const t = PETAL_TYPES[d.type];
ctx.fillStyle = t.color;
ctx.font = '16px monospace';
ctx.fillText(t.char, d.x, d.y+5);
});
// 玩家血条
const bw = 40;
const bh = 4;
ctx.fillStyle = '#333';
ctx.fillRect(player.x-bw/2, player.y-20, bw, bh);
ctx.fillStyle = '#7ccd7c';
ctx.fillRect(player.x-bw/2, player.y-20, bw*(player.hp/player.maxHp), bh);
}
// 启动
init();
</script>
</body>
</html></u>*</u>