火焰豌豆大冒险
by MysticPanda75575 lines17.8 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>火焰豌豆大冒险</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent;}
html,body{width:100%;height:100%;overflow:hidden;background:#111;font-family:system-ui,sans-serif;}
#gameCanvas{display:block;width:100%;height:100%;background:#529c49;}
#joystickArea{position:fixed;bottom:40px;left:40px;width:140px;height:140px;background:rgba(255,255,255,0.2);border-radius:50%;touch-action:none;z-index:10;}
#joystickKnob{position:absolute;width:60px;height:60px;background:rgba(255,255,255,0.5);border-radius:50%;left:40px;top:40px;}
#fireBtn{position:fixed;bottom:40px;right:40px;width:110px;height:110px;border-radius:50%;background:#ff6622;color:white;font-size:24px;border:none;touch-action:none;z-index:10;}
#shopBtn{position:fixed;top:20px;right:20px;padding:12px 18px;font-size:20px;border-radius:10px;border:none;background:#4285f4;color:white;z-index:10;}
#shopPanel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:360px;background:#222;padding:20px;border-radius:12px;color:white;display:none;border:2px solid #888;z-index:100;}
.shop-item{padding:10px;margin:8px 0;background:#333;border-radius:6px;display:flex;justify-content:space-between;align-items:center;}
.shop-item button{padding:6px 10px;border:none;border-radius:4px;background:#28a745;color:white;}
#closeShop{width:100%;margin-top:12px;padding:8px;background:#dc3545;border:none;color:white;border-radius:6px;font-size:16px;}
#cardPanel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:460px;background:#1a1a1a;padding:24px;border-radius:14px;color:white;display:none;border:2px solid #ffcc00;text-align:center;z-index:100;}
.card-choice{display:inline-block;width:125px;padding:12px 8px;margin:6px;background:#333;border-radius:8px;border:1px solid #777;cursor:pointer;vertical-align:top;}
.card-choice:hover{background:#444;border-color:#ffcc00;}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="joystickArea">
<div id="joystickKnob"></div>
</div>
<button id="fireBtn">🔥追踪弹</button>
<button id="shopBtn">🏪商店</button>
<div id="shopPanel">
<h3 style="text-align:center;margin:0 0 15px">火焰豌豆商店</h3>
<div class="shop-item">
<span>❤️恢复20血量</span>
<button data-type="heal">20铜币</button>
</div>
<div class="shop-item">
<span>⚡移动速度+1(永久)</span>
<button data-type="speed">50铜币</button>
</div>
<div class="shop-item">
<span>🔥子弹速度+2(永久)</span>
<button data-type="bulletSpeed">60铜币</button>
</div>
<div class="shop-item">
<span>💥子弹伤害+1(永久)</span>
<button data-type="bulletDmg">80铜币</button>
</div>
<div class="shop-item">
<span>🔫射速提升(永久)</span>
<button data-type="fireRate">90铜币</button>
</div>
<div class="shop-item">
<span>🛡️受伤冷却+1秒(永久)</span>
<button data-type="coolUp">100铜币</button>
</div>
<div class="shop-item">
<span>💰击杀铜币+1(永久)</span>
<button data-type="coinBonus">120铜币</button>
</div>
<div class="shop-item">
<span>💖最大血量+20(永久)</span>
<button data-type="maxHp">150铜币</button>
</div>
<div class="shop-item">
<span>🤖自动射击(永久)</span>
<button data-type="autoFire">180铜币</button>
</div>
<div class="shop-item">
<span>💣击杀僵尸爆炸(永久)</span>
<button data-type="explodeKill">200铜币</button>
</div>
<button id="closeShop">关闭商店</button>
</div>
<div id="cardPanel">
<h3 style="margin:0 0 16px">🎴选择一张卡牌</h3>
<div id="cardList"></div>
</div>
<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
function resizeCanvas(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
const player = {
x: canvas.width / 2,
y: canvas.height / 2,
size: 50,
speed: 4,
hp: 100,
maxHp: 100
};
let gameOver = false;
let bulletDamage = 1;
let fireDelay = 800;
let lastFireTime = 0;
let hurtCoolAdd = 0;
let killCoinBonus = 0;
let autoFire = false;
let explodeKill = false;
let coin = 0;
let slowZombieTimer = 0;
let doubleCoinTimer = 0;
let piercingTimer = 0;
let shieldTimer = 0;
let instantKillCount = 0;
let bombWaveTimer = 0;
let summonHelperTimer = 0;
let weakZombieTimer = 0;
let bullets = [];
let enemies = [];
let enemyHitCool = new Map();
let killCount = 0;
let helper = null;
const joystickArea = document.getElementById("joystickArea");
const joystickKnob = document.getElementById("joystickKnob");
const fireBtn = document.getElementById("fireBtn");
const shopBtn = document.getElementById("shopBtn");
const shopPanel = document.getElementById("shopPanel");
const closeShop = document.getElementById("closeShop");
const shopButtons = document.querySelectorAll(".shop-item button");
const cardPanel = document.getElementById("cardPanel");
const cardList = document.getElementById("cardList");
let joyActive = false;
let joyDx = 0, joyDy = 0;
joystickArea.addEventListener("touchstart", e => {
e.preventDefault();
joyActive = true;
});
joystickArea.addEventListener("touchmove", e => {
e.preventDefault();
const rect = joystickArea.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
const touch = e.touches[0];
let x = touch.clientX - cx;
let y = touch.clientY - cy;
const dist = Math.hypot(x, y);
if (dist > 60) {
x = x / dist * 60;
y = y / dist * 60;
}
joyDx = x / 60;
joyDy = y / 60;
joystickKnob.style.left = (40 + x) + "px";
joystickKnob.style.top = (40 + y) + "px";
});
joystickArea.addEventListener("touchend", () => {
joyActive = false;
joyDx = 0;
joyDy = 0;
joystickKnob.style.left = "40px";
joystickKnob.style.top = "40px";
});
function shoot(){
if(gameOver) return;
if(shopPanel.style.display === "block" || cardPanel.style.display === "block") return;
const now = Date.now();
if(now - lastFireTime < fireDelay) return;
if(enemies.length === 0) return;
lastFireTime = now;
let nearest = enemies[0];
let minD = Math.hypot(nearest.x - player.x, nearest.y - player.y);
for(let e of enemies){
let d = Math.hypot(e.x - player.x, e.y - player.y);
if(d < minD){
minD = d;
nearest = e;
}
}
bullets.push({
x: player.x,
y: player.y,
target: nearest,
speed: 9,
size: 14,
damage: bulletDamage,
pierce: piercingTimer > 0
});
}
fireBtn.addEventListener("touchstart", e => {
e.preventDefault();
shoot();
});
shopBtn.onclick = () => {
if(!gameOver) shopPanel.style.display = "block";
};
//===== 修复:关闭商店按钮事件 =====
closeShop.onclick = () => {
shopPanel.style.display = "none";
};
shopButtons.forEach(btn => {
btn.onclick = () => {
const type = btn.dataset.type;
if(type === "heal"){
if(coin >= 20 && player.hp < player.maxHp){
coin -= 20;
player.hp = Math.min(player.maxHp, player.hp + 20);
}
}else if(type === "speed"){
if(coin >= 50){coin -= 50; player.speed += 1;}
}else if(type === "bulletSpeed"){
if(coin >= 60){coin -= 60; bullets.forEach(b => b.speed += 2);}
}else if(type === "bulletDmg"){
if(coin >= 80){coin -= 80; bulletDamage += 1;}
}else if(type === "fireRate"){
if(coin >= 90){coin -= 90; fireDelay -= 100;}
}else if(type === "coolUp"){
if(coin >= 100){coin -= 100; hurtCoolAdd += 1000;}
}else if(type === "coinBonus"){
if(coin >= 120){coin -= 120; killCoinBonus += 1;}
}else if(type === "maxHp"){
if(coin >= 150){coin -= 150; player.maxHp += 20; player.hp += 20;}
}else if(type === "autoFire"){
if(coin >= 180){coin -= 180; autoFire = true;}
}else if(type === "explodeKill"){
if(coin >= 200){coin -= 200; explodeKill = true;}
}
};
});
function spawnEnemy(){
if(gameOver) return;
const side = Math.floor(Math.random() * 4);
let ex, ey;
let type = 0;
let rand = Math.random();
if(rand < 0.7) type = 0;
else if(rand < 0.92) type = 1;
else type = 2;
if(side === 0){ex = 0; ey = Math.random() * canvas.height;}
else if(side === 1){ex = canvas.width; ey = Math.random() * canvas.height;}
else if(side === 2){ex = Math.random() * canvas.width; ey = 0;}
else {ex = Math.random() * canvas.width; ey = canvas.height;}
let enemyData = {
x: ex,
y: ey,
size: 46,
speed: 0.8,
id: Date.now() + Math.random(),
type: type,
hp: 1,
maxHp: 1
};
if(type === 1){
enemyData.speed = 1.1;
enemyData.size = 54;
enemyData.hp = 3;
enemyData.maxHp = 3;
}else if(type === 2){
enemyData.speed = 1.4;
enemyData.size = 62;
enemyData.hp = 5;
enemyData.maxHp = 5;
}
enemies.push(enemyData);
}
function showCardSelect(){
cardList.innerHTML = "";
const cardPool = [
{name:"❄️僵尸减速",desc:"全体僵尸速度减半,持续10秒",action:()=>{ slowZombieTimer = 10000; }},
{name:"💰双倍铜币",desc:"10秒击杀铜币×2",action:()=>{ doubleCoinTimer = 10000; }},
{name:"🔥穿透子弹",desc:"子弹穿透多个僵尸,持续8秒",action:()=>{ piercingTimer = 8000; }},
{name:"🛡️临时护盾",desc:"5秒免伤",action:()=>{ shieldTimer = 5000; }},
{name:"💀一击秒杀",desc:"接下来5发子弹秒杀",action:()=>{ instantKillCount = 5; }},
{name:"❤️回满血量",desc:"血量直接回满",action:()=>{ player.hp = player.maxHp; }},
{name:"🪙铜币+50",desc:"直接获得50铜币",action:()=>{ coin += 50; }},
{name:"💥冲击波",desc:"清空场上全部僵尸",action:()=>{ bombWaveTimer = 1; }},
{name:"🤖召唤帮手",desc:"豌豆助手打怪12秒",action:()=>{ summonHelperTimer = 12000; helper = {x: player.x, y: player.y}; }},
{name:"🥺僵尸虚弱",desc:"僵尸受到伤害+50%持续10秒",action:()=>{ weakZombieTimer = 10000; }}
];
let shuffled = [...cardPool].sort(() => Math.random() - 0.5).slice(0, 3);
shuffled.forEach(card => {
let div = document.createElement("div");
div.className = "card-choice";
div.innerHTML = `<div>${card.name}</div><div style="font-size:12px;margin-top:4px">${card.desc}</div>`;
div.onclick = () => {
card.action();
cardPanel.style.display = "none";
};
cardList.appendChild(div);
});
cardPanel.style.display = "block";
}
function loop(){
const paused = shopPanel.style.display === "block" || cardPanel.style.display === "block";
if(!gameOver && !paused){
player.x += joyDx * player.speed;
player.y += joyDy * 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(autoFire){
shoot();
}
if(killCount > 0 && killCount % 10 === 0 && cardPanel.style.display !== "block"){
showCardSelect();
killCount += 1;
}
if(slowZombieTimer > 0) slowZombieTimer -= 16;
if(doubleCoinTimer > 0) doubleCoinTimer -= 16;
if(piercingTimer > 0) piercingTimer -= 16;
if(shieldTimer > 0) shieldTimer -= 16;
if(summonHelperTimer > 0) summonHelperTimer -= 16;
if(weakZombieTimer > 0) weakZombieTimer -= 16;
if(bombWaveTimer > 0){
bombWaveTimer = 0;
enemies.forEach(e => {
killCount++;
let getCoin = 0;
if(e.type === 0) getCoin = 1 + killCoinBonus;
else if(e.type === 1) getCoin = 5 + killCoinBonus;
else if(e.type === 2) getCoin = 20 + killCoinBonus;
if(doubleCoinTimer > 0) getCoin *= 2;
coin += getCoin;
});
enemies = [];
}
if(summonHelperTimer > 0 && helper){
let nearestEnemy = null;
let minD = 9999;
enemies.forEach(e => {
let d = Math.hypot(e.x - helper.x, e.y - helper.y);
if(d < minD){
minD = d;
nearestEnemy = e;
}
});
if(nearestEnemy){
let ang = Math.atan2(nearestEnemy.y - helper.y, nearestEnemy.x - helper.x);
helper.x += Math.cos(ang) * 3;
helper.y += Math.sin(ang) * 3;
if(Math.hypot(helper.x - nearestEnemy.x, helper.y - nearestEnemy.y) < 30){
nearestEnemy.hp -= 0.08;
}
}
}
for(let i = bullets.length - 1; i >= 0; i--){
const b = bullets[i];
if(!enemies.includes(b.target)){
bullets.splice(i, 1);
continue;
}
let ang = Math.atan2(b.target.y - b.y, b.target.x - b.x);
b.x += Math.cos(ang) * b.speed;
b.y += Math.sin(ang) * b.speed;
if(b.x < 0 || b.x > canvas.width || b.y < 0 || b.y > canvas.height){
bullets.splice(i, 1);
}
}
for(let i = enemies.length - 1; i >= 0; i--){
const e = enemies[i];
let speedMult = slowZombieTimer > 0 ? 0.5 : 1;
const ang = Math.atan2(player.y - e.y, player.x - e.x);
e.x += Math.cos(ang) * e.speed * speedMult;
e.y += Math.sin(ang) * e.speed * speedMult;
const totalHurtCool = 3000 + hurtCoolAdd;
const hitDist = Math.hypot(player.x - e.x, player.y - e.y);
const now = Date.now();
if(hitDist < player.size + e.size){
if(shieldTimer <= 0 && (!enemyHitCool.has(e.id) || now - enemyHitCool.get(e.id) > totalHurtCool)){
player.hp -= 1;
enemyHitCool.set(e.id, now);
if(player.hp <= 0){
player.hp = 0;
gameOver = true;
}
}
}
for(let j = bullets.length - 1; j >= 0; j--){
const b = bullets[j];
const dist = Math.hypot(b.x - e.x, b.y - e.y);
if(dist < e.size + b.size){
let dmg = b.damage;
if(instantKillCount > 0){
dmg = 999;
instantKillCount--;
}
if(weakZombieTimer > 0) dmg *= 1.5;
e.hp -= dmg;
if(!b.pierce){
bullets.splice(j, 1);
}
if(e.hp <= 0){
killCount += 1;
let getCoin = 0;
if(e.type === 0) getCoin = 1 + killCoinBonus;
else if(e.type === 1) getCoin = 5 + killCoinBonus;
else if(e.type === 2) getCoin = 20 + killCoinBonus;
if(doubleCoinTimer > 0) getCoin *= 2;
coin += getCoin;
if(explodeKill){
enemies.forEach(ee => {
if(Math.hypot(ee.x - e.x, ee.y - e.y) < 70){
ee.hp -= 2;
}
});
}
enemies.splice(i, 1);
enemyHitCool.delete(e.id);
}
break;
}
}
}
if(Math.random() < 0.010) spawnEnemy();
}
ctx.fillStyle = "#529c49";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "#407c3a";
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();
}
ctx.fillStyle = "#333";
ctx.fillRect(10, 10, 204, 24);
let hpPercent = player.hp / player.maxHp;
ctx.fillStyle = "#ff3333";
ctx.fillRect(12, 12, 200 * hpPercent, 20);
ctx.fillStyle = "#fff";
ctx.font = "16px system-ui";
ctx.fillText("HP:" + player.hp + "/" + player.maxHp, 15, 28);
ctx.fillStyle = "#fff";
ctx.font = "16px system-ui";
ctx.fillText("🪙铜币:" + coin, 10, 52);
ctx.fillText("💥伤害:" + bulletDamage, 10, 74);
ctx.fillText("🔫射速间隔:" + fireDelay + "ms", 10, 96);
ctx.fillText("🤖自动射击:" + (autoFire ? "开启" : "关闭"), 10, 140);
ctx.fillText("💣击杀爆炸:" + (explodeKill ? "开启" : "关闭"), 10, 162);
let statusX = 220;
if(slowZombieTimer > 0){ctx.fillStyle = "#8cf"; ctx.fillText("❄️僵尸减速", statusX, 28); statusX += 90;}
if(doubleCoinTimer > 0){ctx.fillStyle = "#ffc"; ctx.fillText("💰双倍铜币", statusX, 28); statusX += 90;}
if(piercingTimer > 0){ctx.fillStyle = "#f8f"; ctx.fillText("🔥穿透子弹", statusX, 28); statusX += 90;}
if(shieldTimer > 0){ctx.fillStyle = "#0cf"; ctx.fillText("🛡️护盾", statusX, 28); statusX += 90;}
if(instantKillCount > 0){ctx.fillStyle = "#f44"; ctx.fillText("💀秒杀剩余:" + instantKillCount, statusX, 28); statusX += 90;}
if(summonHelperTimer > 0){ctx.fillStyle = "#4f8"; ctx.fillText("🤖帮手", statusX, 50); statusX += 90;}
if(weakZombieTimer > 0){ctx.fillStyle = "#fa8"; ctx.fillText("🥺僵尸虚弱", statusX, 50); statusX += 90;}
ctx.font = "40px system-ui";
enemies.forEach(e => {
let icon = "🧟";
if(e.type === 1) icon = "👹";
if(e.type === 2) icon = "👾";
ctx.fillText(icon, e.x - e.size / 2, e.y + e.size / 3);
const enemyHpW = 44;
ctx.fillStyle = "#333";
ctx.fillRect(e.x - enemyHpW / 2, e.y - e.size - 10, enemyHpW, 6);
ctx.fillStyle = "#ff5555";
ctx.fillRect(e.x - enemyHpW / 2, e.y - e.size - 10, enemyHpW * (e.hp / e.maxHp), 6);
});
ctx.font = "28px system-ui";
bullets.forEach(b => ctx.fillText("🔥", b.x - 10, b.y + 8));
if(summonHelperTimer > 0 && helper){
ctx.font = "44px system-ui";
ctx.fillText("🌱", helper.x - 18, helper.y + 12);
}
ctx.font = "48px system-ui";
ctx.fillText("🔥", player.x - 20, player.y + 14);
if(gameOver){
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ff4444";
ctx.font = "50px system-ui";
ctx.textAlign = "center";
ctx.fillText("游戏结束", canvas.width / 2, canvas.height / 2 - 30);
ctx.font = "24px system-ui";
ctx.fillText("刷新页面重新游玩", canvas.width / 2, canvas.height / 2 + 20);
ctx.textAlign = "left";
}
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>Game Source: 火焰豌豆大冒险
Creator: MysticPanda75
Libraries: none
Complexity: complex (575 lines, 17.8 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-mysticpanda75" to link back to the original. Then publish at arcadelab.ai/publish.