援救狗星人
by BlazingDragon431238 lines33.0 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>援救狗星人</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: "Microsoft YaHei", "PingFang SC", sans-serif; }
body {
background: #0b1020;
color: #fff;
overflow: hidden;
width: 100vw;
height: 100vh;
user-select: none;
}
#game {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
/* 屏幕震动 */
.shake { animation: shake 0.25s; }
@keyframes shake {
0%,100% { transform: translate(0,0); }
25% { transform: translate(-4px, 2px); }
50% { transform: translate(4px, -2px); }
75% { transform: translate(-2px, -3px); }
}
/* 通用按钮 */
.btn {
background: linear-gradient(180deg, #ffc857, #f28c28);
color: #3a2410;
border: 3px solid #fff;
padding: 10px 16px;
border-radius: 14px;
font-weight: bold;
box-shadow: 0 4px 0 #8a4b14, 0 6px 10px #0006;
}
.btn:active { transform: translateY(2px); box-shadow: 0 2px 0 #8a4b14; }
/* 通用卡片 */
.card {
background: linear-gradient(180deg, #fff8e1, #ffe28a);
border: 3px solid #fff;
border-radius: 14px;
box-shadow: 0 4px 10px #0006;
color: #3a2410;
}
/* ========== 主界面 ========== */
#home {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 50% 20%, #ffd89b 0%, transparent 60%),
linear-gradient(180deg, #3a7bd5 0%, #1e3c72 100%);
z-index: 50;
display: flex;
flex-direction: column;
}
.home-top {
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.home-title {
font-size: 28px;
font-weight: 900;
color: #fff;
text-shadow: 3px 3px 0 #8a4b14, 6px 6px 10px #0008;
}
.resource-bar {
display: flex;
gap: 8px;
}
.resource {
background: #0006;
padding: 6px 10px;
border-radius: 20px;
font-size: 14px;
border: 2px solid #ffd857;
}
.home-map {
flex: 1;
position: relative;
margin: 12px;
border-radius: 20px;
background:
linear-gradient(180deg, #a8e6cf 0%, #6bbf59 60%, #4a7c3a 100%);
border: 4px solid #fff;
overflow: hidden;
}
.map-mountain {
position: absolute;
bottom: 30%;
width: 100%;
height: 40%;
background:
radial-gradient(circle at 20% 100%, #3f6b4f 0%, transparent 40%),
radial-gradient(circle at 60% 100%, #4a7c59 0%, transparent 45%),
radial-gradient(circle at 85% 100%, #5a8f6a 0%, transparent 35%);
}
.map-road {
position: absolute;
bottom: 0;
width: 100%;
height: 35%;
background: linear-gradient(180deg, #d4a574, #a67c52);
border-top: 4px solid #7a5a2a;
}
.map-node {
position: absolute;
width: 54px;
height: 54px;
border-radius: 50%;
background: radial-gradient(circle, #ffd857 0%, #f28c28 100%);
border: 3px solid #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #3a2410;
box-shadow: 0 4px 10px #0008;
}
.home-menu {
padding: 12px 16px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.menu-item {
background: linear-gradient(180deg, #5b8def, #2f5bb7);
border: 3px solid #fff;
border-radius: 14px;
padding: 10px 4px;
text-align: center;
font-size: 13px;
color: #fff;
font-weight: bold;
box-shadow: 0 4px 8px #0006;
}
/* ========== 战斗界面 ========== */
#battle {
position: absolute;
inset: 0;
display: none;
z-index: 40;
}
.battle-scene {
position: absolute;
inset: 0;
overflow: hidden;
}
.sky {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 40%;
background: linear-gradient(180deg, #ffd89b 0%, #87ceeb 100%);
}
.far-hills {
position: absolute;
top: 20%;
left: 0;
width: 100%;
height: 30%;
background:
radial-gradient(circle at 20% 100%, #4a7c59 0%, transparent 40%),
radial-gradient(circle at 60% 90%, #3f6b4f 0%, transparent 45%),
radial-gradient(circle at 85% 70%, #5a8f6a 0%, transparent 35%);
opacity: 0.85;
}
.ground {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 50%;
background: linear-gradient(180deg, #8fbc8f 0%, #6f9a6a 40%, #557a45 100%);
}
.battle-path {
position: absolute;
top: 58%;
left: 0;
width: 100%;
height: 16%;
background: linear-gradient(180deg, #d4a574, #a67c52);
border-top: 4px solid #7a5a2a;
border-bottom: 4px solid #7a5a2a;
}
.base {
position: absolute;
top: 46%;
width: 110px;
height: 70px;
border: 4px solid #fff;
border-radius: 14px;
background: linear-gradient(180deg, #e8c38a, #c99a55);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #3a2410;
z-index: 6;
}
.base-left { left: 10px; }
.base-right { right: 10px; background: linear-gradient(180deg, #ef9a9a, #c62828); color: #fff; }
.base-hp {
position: absolute;
top: -14px;
left: 0;
width: 100%;
height: 8px;
background: #222;
border: 2px solid #fff;
border-radius: 4px;
overflow: hidden;
}
.base-hp-inner {
height: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
transition: width 0.3s;
}
.base-hp-inner.enemy { background: linear-gradient(90deg, #f44336, #ff7961); }
/* 狗将单位 */
.dog-unit {
position: absolute;
width: 72px;
height: 88px;
z-index: 10;
transition: left 0.15s linear;
}
.dog-body {
position: relative;
width: 100%;
height: 100%;
}
.dog-head {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 42px;
height: 42px;
border-radius: 50%;
border: 3px solid #fff;
z-index: 4;
}
.dog-torso {
position: absolute;
top: 26px;
left: 50%;
transform: translateX(-50%);
width: 48px;
height: 38px;
border-radius: 20px;
}
.dog-leg {
position: absolute;
top: 60px;
width: 12px;
height: 24px;
border-radius: 6px;
}
.dog-leg.left { left: 16px; }
.dog-leg.right { right: 16px; }
.dog-tail {
position: absolute;
top: 30px;
right: -8px;
width: 24px;
height: 8px;
border-radius: 8px;
transform-origin: left center;
}
.dog-weapon {
position: absolute;
top: 22px;
right: -12px;
width: 8px;
height: 40px;
background: linear-gradient(180deg, #eee, #888);
border: 1px solid #333;
z-index: 3;
}
.dog-shadow {
position: absolute;
bottom: -4px;
left: 50%;
width: 60%;
height: 8px;
background: rgba(0,0,0,0.35);
border-radius: 50%;
transform: translateX(-50%);
}
.unit-hp {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 6px;
background: #222;
border: 1px solid #fff;
border-radius: 3px;
overflow: hidden;
z-index: 5;
}
.unit-hp-inner {
height: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
transition: width 0.25s;
}
.unit-hp-inner.enemy { background: linear-gradient(90deg, #f44336, #ff7961); }
.unit-energy {
position: absolute;
top: -18px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 4px;
background: #222;
border: 1px solid #fff;
border-radius: 2px;
overflow: hidden;
z-index: 5;
}
.unit-energy-inner {
height: 100%;
background: linear-gradient(90deg, #64b5f6, #1976d2);
width: 0;
transition: width 0.2s;
}
/* 单位动作 */
@keyframes dRun {
0%,100% { transform: translateY(0) rotate(-1deg); }
50% { transform: translateY(-4px) rotate(1deg); }
}
@keyframes dAttack {
0% { transform: translateX(0) rotate(0); }
40% { transform: translateX(18px) rotate(6deg); }
100% { transform: translateX(0) rotate(0); }
}
@keyframes dHit {
0%,100% { filter: brightness(1); }
50% { filter: brightness(2.2) saturate(0); }
}
@keyframes dDie {
0% { transform: rotate(0); opacity: 1; }
100% { transform: rotate(75deg) translateY(14px); opacity: 0.4; }
}
.dog-unit.run .dog-body { animation: dRun 0.32s infinite ease-in-out; }
.dog-unit.atk .dog-body { animation: dAttack 0.42s ease-out; }
.dog-unit.hit .dog-body { animation: dHit 0.22s; }
.dog-unit.dead .dog-body { animation: dDie 0.5s forwards; }
/* 技能特效 */
.fx {
position: absolute;
pointer-events: none;
z-index: 20;
}
.slash-fx {
width: 120px;
height: 80px;
background: radial-gradient(circle, #fff 0%, #ffd700 40%, transparent 70%);
border-radius: 50%;
animation: slash 0.42s forwards;
}
@keyframes slash {
0% { transform: scale(0.2) rotate(-30deg); opacity: 1; }
100% { transform: scale(1.5) rotate(35deg); opacity: 0; }
}
.lightning-fx {
width: 70px;
height: 130px;
background: linear-gradient(180deg, #fff, #64b5f6, #1976d2);
clip-path: polygon(40% 0, 60% 0, 50% 40, 70% 40, 30% 100, 50% 60, 20% 60);
animation: lightning 0.4s forwards;
}
@keyframes lightning {
0%,100% { opacity: 0; }
20%,80% { opacity: 1; }
}
.wave-fx {
width: 170px;
height: 44px;
border: 5px solid #ffeb3b;
border-radius: 50%;
animation: wave 0.55s forwards;
}
@keyframes wave {
0% { transform: scale(0.15); opacity: 1; }
100% { transform: scale(1.7); opacity: 0; }
}
.combo-fx {
width: 90px;
height: 70px;
background: radial-gradient(circle, #ff5722 0%, #ffd700 50%, transparent 70%);
border-radius: 50%;
animation: combo 0.28s forwards;
}
@keyframes combo {
0% { transform: scale(0.3); opacity: 1; }
100% { transform: scale(1.2); opacity: 0; }
}
/* 伤害数字 */
.dmg {
position: absolute;
font-size: 22px;
font-weight: 900;
color: #ff4444;
text-shadow: 2px 2px 0 #000;
animation: floatUp 0.85s forwards;
z-index: 25;
}
.dmg.crit {
font-size: 30px;
color: #ffd700;
}
@keyframes floatUp {
0% { transform: translateY(0) scale(0.8); opacity: 1; }
30% { transform: translateY(-14px) scale(1.15); }
100% { transform: translateY(-60px) scale(1); opacity: 0; }
}
/* 战斗 HUD */
.battle-hud {
position: absolute;
left: 0;
right: 0;
top: 0;
padding: 10px;
display: flex;
justify-content: space-between;
z-index: 30;
}
.hud-box {
background: #0008;
padding: 8px 12px;
border-radius: 12px;
border: 2px solid #ffd857;
font-size: 14px;
}
.energy-bar {
position: absolute;
left: 10px;
right: 10px;
top: 60px;
height: 14px;
background: #222;
border: 2px solid #fff;
border-radius: 8px;
overflow: hidden;
z-index: 20;
}
.energy-inner {
height: 100%;
background: linear-gradient(90deg, #64b5f6, #1976d2);
width: 0;
transition: width 0.15s;
}
.card-row {
position: absolute;
left: 0;
right: 0;
bottom: 10px;
padding: 0 10px;
display: flex;
gap: 10px;
z-index: 30;
}
.hand-card {
flex: 1;
background: linear-gradient(180deg, #fff8e1, #ffe28a);
border: 3px solid #fff;
border-radius: 14px;
padding: 6px;
text-align: center;
box-shadow: 0 4px 10px #0008;
color: #3a2410;
font-weight: bold;
}
.hand-card.disabled {
filter: grayscale(1) brightness(0.7);
}
.hand-card.selected {
border-color: #ff5722;
transform: translateY(-8px);
}
.card-cost {
background: #1976d2;
color: #fff;
border-radius: 50%;
width: 24px;
height: 24px;
line-height: 24px;
margin: 0 auto 2px;
border: 2px solid #fff;
}
.card-img {
width: 100%;
height: 54px;
border-radius: 8px;
background: #eee;
border: 2px solid #fff;
margin-bottom: 2px;
}
.card-name {
font-size: 12px;
}
/* 结算 */
#result {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
background: #000c;
padding: 24px 36px;
border-radius: 20px;
text-align: center;
display: none;
z-index: 60;
}
#result.show { display: block; }
.result-title {
font-size: 28px;
font-weight: 900;
color: #ffd857;
text-shadow: 3px 3px 0 #8a4b14;
margin-bottom: 12px;
}
.result-reward {
margin-bottom: 16px;
font-size: 14px;
}
/* 弹窗 */
.modal {
position: absolute;
inset: 0;
background: #000c;
display: none;
align-items: center;
justify-content: center;
z-index: 70;
padding: 20px;
}
.modal.show { display: flex; }
.modal-box {
background: linear-gradient(180deg, #fff8e1, #ffe28a);
border: 4px solid #fff;
border-radius: 20px;
padding: 20px;
width: 100%;
max-width: 340px;
color: #3a2410;
}
.modal-title {
font-size: 22px;
font-weight: 900;
text-align: center;
margin-bottom: 12px;
}
.hero-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 14px;
}
.hero-tile {
background: #fff;
border: 3px solid #fff;
border-radius: 12px;
padding: 8px;
text-align: center;
}
.hero-tile.selected {
border-color: #ff5722;
}
.hero-tile-img {
width: 100%;
height: 60px;
border-radius: 8px;
background: #eee;
margin-bottom: 4px;
}
.hero-tile-name {
font-size: 12px;
font-weight: bold;
}
</style>
</head>
<body>
<div id="game">
<!-- 主界面 -->
<div id="home">
<div class="home-top">
<div class="home-title">援救狗星人</div>
<div class="resource-bar">
<div class="resource">💰 <span id="homeGold">5000</span></div>
<div class="resource">💎 <span id="homeDiamond">100</span></div>
</div>
</div>
<div class="home-map">
<div class="map-mountain"></div>
<div class="map-road"></div>
<div class="map-node" style="left:10%;top:72%" onclick="goBattle()">1</div>
<div class="map-node" style="left:30%;top:62%" onclick="alert('通关第1关解锁')">2</div>
<div class="map-node" style="left:52%;top:52%" onclick="alert('通关第2关解锁')">3</div>
<div class="map-node" style="left:72%;top:42%" onclick="alert('通关第3关解锁')">4</div>
<div class="map-node" style="left:88%;top:32%" onclick="alert('通关第4关解锁')">5</div>
</div>
<div class="home-menu">
<div class="menu-item" onclick="openHeroes()">🐶 狗将</div>
<div class="menu-item" onclick="openGacha()">🎲 招募</div>
<div class="menu-item" onclick="openShop()">🛒 商店</div>
<div class="menu-item" onclick="openTask()">📜 任务</div>
</div>
</div>
<!-- 战斗界面 -->
<div id="battle">
<div class="battle-scene" id="battleScene">
<div class="sky"></div>
<div class="far-hills"></div>
<div class="ground"></div>
<div class="battle-path"></div>
<div class="base base-left">
<div class="base-hp"><div class="base-hp-inner" id="baseHpInner"></div></div>
我方基地
</div>
<div class="base base-right">
<div class="base-hp"><div class="base-hp-inner enemy" id="enemyBaseHpInner"></div></div>
敌方基地
</div>
</div>
<div class="battle-hud">
<div class="hud-box">第 <span id="stageNum">1</span> 关</div>
<div class="hud-box">波次 <span id="waveNum">1</span>/<span id="waveTotal">3</span></div>
<button class="btn" onclick="toggleAuto()">自动:<span id="autoText">开</span></button>
</div>
<div class="energy-bar">
<div class="energy-inner" id="energyInner"></div>
</div>
<div class="card-row" id="cardRow"></div>
<div id="result">
<div class="result-title" id="resultTitle">胜利!</div>
<div class="result-reward" id="resultReward">获得金币 +500</div>
<button class="btn" onclick="nextStage()">下一关</button>
</div>
</div>
<!-- 狗将弹窗 -->
<div class="modal" id="heroModal">
<div class="modal-box">
<div class="modal-title">我的狗将</div>
<div class="hero-grid" id="heroGrid"></div>
<button class="btn" style="width:100%" onclick="closeModal('heroModal')">关闭</button>
</div>
</div>
<!-- 招募弹窗 -->
<div class="modal" id="gachaModal">
<div class="modal-box">
<div class="modal-title">招募狗将</div>
<p style="text-align:center;margin-bottom:12px">消耗 100 钻石招募一只狗将</p>
<button class="btn" style="width:100%" onclick="doGacha()">招募一次</button>
<button class="btn" style="width:100%;margin-top:8px" onclick="closeModal('gachaModal')">关闭</button>
</div>
</div>
<!-- 商店弹窗 -->
<div class="modal" id="shopModal">
<div class="modal-box">
<div class="modal-title">商店</div>
<p style="text-align:center;margin-bottom:12px">功能开发中</p>
<button class="btn" style="width:100%" onclick="closeModal('shopModal')">关闭</button>
</div>
</div>
<!-- 任务弹窗 -->
<div class="modal" id="taskModal">
<div class="modal-box">
<div class="modal-title">任务</div>
<p style="text-align:center;margin-bottom:12px">功能开发中</p>
<button class="btn" style="width:100%" onclick="closeModal('taskModal')">关闭</button>
</div>
</div>
</div>
<script>
// ========== 狗将数据:不同品种 + 武将特征 ==========
const HERO_DATA = [
{
id: "guanyu", name: "关羽犬", breed: "藏獒", rarity: "SSR",
headColor: "#8b4513", torsoColor: "#2e7d32", legColor: "#1b5e20",
tailColor: "#8b4513", weaponColor: "#bdbdbd",
hp: 1400, atk: 190, def: 40, cost: 4,
skill: { name: "偃月斩", type: "slash", dmg: 3.0 }
},
{
id: "lubu", name: "吕布拉多", breed: "杜宾", rarity: "SSR",
headColor: "#2c2c2c", torsoColor: "#7b1fa2", legColor: "#4a148c",
tailColor: "#2c2c2c", weaponColor: "#9e9e9e",
hp: 1300, atk: 230, def: 38, cost: 5,
skill: { name: "无双乱舞", type: "combo", dmg: 3.2 }
},
{
id: "zhugeliang", name: "诸葛柴犬", breed: "柴犬", rarity: "SSR",
headColor: "#f4c896", torsoColor: "#1565c0", legColor: "#0d47a1",
tailColor: "#f4c896", weaponColor: "#64b5f6",
hp: 1000, atk: 175, def: 30, cost: 3,
skill: { name: "天雷", type: "lightning", dmg: 2.8 }
},
{
id: "zhangfei", name: "张飞恶霸", breed: "恶霸犬", rarity: "SSR",
headColor: "#d4a373", torsoColor: "#e64a19", legColor: "#bf360c",
tailColor: "#d4a373", weaponColor: "#757575",
hp: 1250, atk: 210, def: 35, cost: 4,
skill: { name: "咆哮震地", type: "wave", dmg: 2.6 }
},
{
id: "zhaoyun", name: "赵云萨摩耶", breed: "萨摩耶", rarity: "SSR",
headColor: "#ffffff", torsoColor: "#455a64", legColor: "#263238",
tailColor: "#ffffff", weaponColor: "#cfd8dc",
hp: 1350, atk: 200, def: 42, cost: 4,
skill: { name: "龙胆突击", type: "slash", dmg: 2.9 }
},
{
id: "caocao", name: "曹操罗威纳", breed: "罗威纳", rarity: "SSR",
headColor: "#212121", torsoColor: "#5d4037", legColor: "#3e2723",
tailColor: "#212121", weaponColor: "#ffd700",
hp: 1200, atk: 185, def: 44, cost: 4,
skill: { name: "魏武号令", type: "wave", dmg: 2.7 }
}
];
// ========== 游戏状态 ==========
let state = {
gold: 5000,
diamond: 100,
stage: 1,
maxStage: 1,
heroes: [
{ ...HERO_DATA[0], level: 1 },
{ ...HERO_DATA[2], level: 1 },
{ ...HERO_DATA[4], level: 1 }
],
team: [0, 2, 4],
auto: true,
energy: 0,
maxEnergy: 10,
hand: [0, 2, 4],
battle: null
};
// ========== 工具函数 ==========
function $(id) { return document.getElementById(id); }
function save() { localStorage.setItem("dogStarSave", JSON.stringify(state)); }
function load() {
const s = localStorage.getItem("dogStarSave");
if (s) state = { ...state, ...JSON.parse(s) };
}
// ========== 主界面 ==========
function showHome() {
$("home").classList.add("show");
$("battle").style.display = "none";
$("homeGold").innerText = state.gold;
$("homeDiamond").innerText = state.diamond;
}
function hideHome() {
$("home").classList.remove("show");
$("battle").style.display = "block";
}
// ========== 弹窗 ==========
function openHeroes() {
const grid = $("heroGrid");
grid.innerHTML = "";
state.heroes.forEach((h, i) => {
const div = document.createElement("div");
div.className = "hero-tile";
div.innerHTML = `
<div class="hero-tile-img" style="background:${h.headColor}"></div>
<div class="hero-tile-name">${h.name}</div>
<div style="font-size:11px">Lv.${h.level}</div>
`;
grid.appendChild(div);
});
$("heroModal").classList.add("show");
}
function openGacha() { $("gachaModal").classList.add("show"); }
function openShop() { $("shopModal").classList.add("show"); }
function openTask() { $("taskModal").classList.add("show"); }
function closeModal(id) { $(id).classList.remove("show"); }
function doGacha() {
if (state.diamond < 100) { alert("钻石不足"); return; }
state.diamond -= 100;
const newHero = { ...HERO_DATA[Math.floor(Math.random() * HERO_DATA.length)], level: 1 };
state.heroes.push(newHero);
alert("招募到:" + newHero.name);
save();
showHome();
}
// ========== 战斗系统 ==========
function goBattle() {
hideHome();
startBattle();
}
function startBattle() {
$("result").classList.remove("show");
document.querySelectorAll(".dog-unit, .fx").forEach(el => el.remove());
state.battle = {
self: [],
enemy: [],
energy: 0,
maxEnergy: 10,
wave: 1,
waveTotal: 3,
enemySpawnQueue: buildWave(state.stage),
spawnCd: 0,
selfBaseHp: 1000,
enemyBaseHp: 1000,
selfBaseMax: 1000,
enemyBaseMax: 1000,
over: false
};
$("stageNum").innerText = state.stage;
$("waveNum").innerText = state.battle.wave;
$("waveTotal").innerText = state.battle.waveTotal;
$("autoText").innerText = state.auto ? "开" : "关";
renderHand();
updateEnergy();
updateBaseHp();
battleLoop();
}
function buildWave(stage) {
const q = [];
const count = 3 + Math.min(stage, 4);
for (let i = 0; i < count; i++) {
q.push({
id: "e" + i, name: "小兵", breed: "土狗",
headColor: "#d7ccc8", torsoColor: "#6d4c41", legColor: "#4e342e",
tailColor: "#d7ccc8", weaponColor: "#9e9e9e",
hp: 400 + stage * 80, atk: 40 + stage * 6, def: 8,
skill: { name: "攻击", type: "slash", dmg: 1.0 }
});
}
if (stage % 5 === 0) {
q.push({
id: "boss", name: "BOSS", breed: "战犬",
headColor: "#424242", torsoColor: "#b71c1c", legColor: "#7f0000",
tailColor: "#424242", weaponColor: "#ffd700",
hp: 1200 + stage * 150, atk: 80 + stage * 10, def: 14,
skill: { name: "重击", type: "wave", dmg: 2.2 }
});
}
return q;
}
function renderHand() {
const row = $("cardRow");
row.innerHTML = "";
state.hand.forEach((idx, i) => {
const h = state.heroes[idx];
const card = document.createElement("div");
card.className = "hand-card" + (state.energy < h.cost ? " disabled" : "");
card.innerHTML = `
<div class="card-cost">${h.cost}</div>
<div class="card-img" style="background:${h.headColor}"></div>
<div class="card-name">${h.name}</div>
`;
card.onclick = () => deployHero(i);
row.appendChild(card);
});
}
function deployHero(handIndex) {
const idx = state.hand[handIndex];
const h = state.heroes[idx];
if (state.energy < h.cost) { alert("能量不足"); return; }
state.energy -= h.cost;
updateEnergy();
const unit = {
...h,
uid: "s" + Date.now() + Math.random(),
x: 140,
y: 50 + Math.random() * 20,
nowHp: h.hp,
maxHp: h.hp,
target: null,
cd: 0,
energy: 0,
state: "run"
};
state.battle.self.push(unit);
createDogUnit(unit, false);
state.hand.splice(handIndex, 1);
state.hand.push(state.team[Math.floor(Math.random() * state.team.length)]);
renderHand();
}
function createDogUnit(u, isEnemy) {
const el = document.createElement("div");
el.className = "dog-unit";
el.id = u.uid;
el.style.left = u.x + "px";
el.style.bottom = u.y + "px";
if (isEnemy) el.style.transform = "scaleX(-1)";
el.innerHTML = `
<div class="dog-body">
<div class="unit-hp"><div class="unit-hp-inner ${isEnemy?'enemy':''}" style="width:100%"></div></div>
<div class="unit-energy"><div class="unit-energy-inner" id="en-${u.uid}"></div></div>
<div class="dog-head" style="background:${u.headColor}"></div>
<div class="dog-torso" style="background:${u.torsoColor}"></div>
<div class="dog-leg left" style="background:${u.legColor}"></div>
<div class="dog-leg right" style="background:${u.legColor}"></div>
<div class="dog-tail" style="background:${u.tailColor}"></div>
<div class="dog-weapon" style="background:${u.weaponColor}"></div>
<div class="dog-shadow"></div>
</div>
`;
$("battleScene").appendChild(el);
}
function spawnEnemy() {
if (state.battle.enemySpawnQueue.length === 0) return;
state.battle.spawnCd--;
if (state.battle.spawnCd > 0) return;
state.battle.spawnCd = 90;
const data = state.battle.enemySpawnQueue.shift();
const unit = {
...data,
uid: "e" + Date.now() + Math.random(),
x: window.innerWidth - 160,
y: 50 + Math.random() * 20,
nowHp: data.hp,
maxHp: data.hp,
target: null,
cd: 0,
energy: 0,
state: "run"
};
state.battle.enemy.push(unit);
createDogUnit(unit, true);
}
function battleLoop() {
if (state.battle.over) return;
// 能量恢复
state.battle.energy += 0.08;
if (state.battle.energy > state.battle.maxEnergy) state.battle.energy = state.battle.maxEnergy;
state.energy = state.battle.energy;
updateEnergy();
renderHand();
// 生成敌人
spawnEnemy();
// 我方
state.battle.self.forEach(u => {
if (u.nowHp <= 0) return;
const el = $(u.uid);
if (!el) return;
// 找目标
if (!u.target || u.target.nowHp <= 0) {
u.target = findTarget(u, state.battle.enemy);
}
if (u.target) {
const dx = u.target.x - u.x;
if (Math.abs(dx) > 90) {
u.x += 1.4;
el.style.left = u.x + "px";
el.classList.remove("atk", "hit");
el.classList.add("run");
} else {
el.classList.remove("run");
u.cd--;
if (u.cd <= 0) {
u.cd = 55;
dogAttack(u, u.target);
}
}
} else {
// 攻击敌方基地
if (u.x < window.innerWidth - 180) {
u.x += 1.2;
el.style.left = u.x + "px";
el.classList.remove("atk", "hit");
el.classList.add("run");
} else {
el.classList.remove("run");
u.cd--;
if (u.cd <= 0) {
u.cd = 55;
state.battle.enemyBaseHp -= u.atk;
updateBaseHp();
shakeScreen();
if (state.battle.enemyBaseHp <= 0) return endBattle(true);
}
}
}
// 技能能量
u.energy = Math.min(100, u.energy + 0.4);
const ei = document.getElementById("en-" + u.uid);
if (ei) ei.style.width = u.energy + "%";
if (u.energy >= 100) {
u.energy = 0;
useSkill(u, u.target || { x: u.x, y: u.y });
}
});
// 敌方
state.battle.enemy.forEach(u => {
if (u.nowHp <= 0) return;
const el = $(u.uid);
if (!el) return;
if (!u.target || u.target.nowHp <= 0) {
u.target = findTarget(u, state.battle.self);
}
if (u.target) {
const dx = u.target.x - u.x;
if (Math.abs(dx) > 90) {
u.x -= 1.1;
el.style.left = u.x + "px";
el.classList.remove("atk", "hit");
el.classList.add("run");
} else {
el.classList.remove("run");
u.cd--;
if (u.cd <= 0) {
u.cd = 65;
dogAttack(u, u.target);
}
}
} else {
if (u.x > 140) {
u.x -= 1.0;
el.style.left = u.x + "px";
el.classList.remove("atk", "hit");
el.classList.add("run");
} else {
el.classList.remove("run");
u.cd--;
if (u.cd <= 0) {
u.cd = 65;
state.battle.selfBaseHp -= u.atk;
updateBaseHp();
shakeScreen();
if (state.battle.selfBaseHp <= 0) return endBattle(false);
}
}
}
u.energy = Math.min(100, u.energy + 0.35);
if (u.energy >= 100) {
u.energy = 0;
useSkill(u, u.target || { x: u.x, y: u.y });
}
});
// 波次检查
if (state.battle.enemySpawnQueue.length === 0 && state.battle.enemy.every(e => e.nowHp <= 0)) {
if (state.battle.wave < state.battle.waveTotal) {
state.battle.wave++;
state.battle.enemySpawnQueue = buildWave(state.stage);
$("waveNum").innerText = state.battle.wave;
} else {
return endBattle(true);
}
}
requestAnimationFrame(battleLoop);
}
function findTarget(u, list) {
let best = null, bestDist = Infinity;
list.forEach(t => {
if (t.nowHp <= 0) return;
const d = Math.abs(t.x - u.x);
if (d < bestDist) { bestDist = d; best = t; }
});
return best;
}
function dogAttack(attacker, target) {
const aEl = $(attacker.uid);
const tEl = $(target.uid);
if (!aEl || !tEl) return;
aEl.classList.add("atk");
setTimeout(() => aEl.classList.remove("atk"), 420);
setTimeout(() => {
const dmg = Math.max(1, attacker.atk - target.def + Math.floor(Math.random() * 15));
target.nowHp -= dmg;
showDmg(target, dmg, false);
tEl.classList.add("hit");
setTimeout(() => tEl.classList.remove("hit"), 220);
updateUnitHp(target);
if (target.nowHp <= 0) {
tEl.classList.add("dead");
setTimeout(() => tEl.remove(), 500);
}
}, 260);
}
function useSkill(attacker, target) {
const aEl = $(attacker.uid);
if (!aEl) return;
aEl.classList.add("atk");
setTimeout(() => aEl.classList.remove("atk"), 420);
setTimeout(() => {
if (!target || target.nowHp <= 0) return;
const dmg = Math.floor(attacker.atk * attacker.skill.dmg);
target.nowHp -= dmg;
showDmg(target, dmg, true);
const tEl = $(target.uid);
if (tEl) {
tEl.classList.add("hit");
setTimeout(() => tEl.classList.remove("hit"), 220);
updateUnitHp(target);
if (target.nowHp <= 0) {
tEl.classList.add("dead");
setTimeout(() => tEl.remove(), 500);
}
}
createFx(target, attacker.skill.type);
shakeScreen();
}, 260);
}
function createFx(target, type) {
const fx = document.createElement("div");
fx.className = "fx " + type + "-fx";
fx.style.left = target.x + 20 + "px";
fx.style.bottom = target.y + 40 + "px";
$("battleScene").appendChild(fx);
setTimeout(() => fx.remove(), 600);
}
function showDmg(target, val, crit) {
const d = document.createElement("div");
d.className = "dmg" + (crit ? " crit" : "");
d.innerText = "-" + val;
d.style.left = target.x + 24 + "px";
d.style.bottom = target.y + 80 + "px";
$("battleScene").appendChild(d);
setTimeout(() => d.remove(), 850);
}
function updateUnitHp(u) {
const el = $(u.uid);
if (!el) return;
const bar = el.querySelector(".unit-hp-inner");
if (bar) bar.style.width = Math.max(0, u.nowHp / u.maxHp * 100) + "%";
}
function updateEnergy() {
$("energyInner").style.width = (state.energy / state.maxEnergy * 100) + "%";
}
function updateBaseHp() {
$("baseHpInner").style.width = Math.max(0, state.battle.selfBaseHp / state.battle.selfBaseMax * 100) + "%";
$("enemyBaseHpInner").style.width = Math.max(0, state.battle.enemyBaseHp / state.battle.enemyBaseMax * 100) + "%";
}
function shakeScreen() {
const scene = $("battleScene");
scene.classList.add("shake");
setTimeout(() => scene.classList.remove("shake"), 250);
}
function endBattle(win) {
state.battle.over = true;
$("result").classList.add("show");
if (win) {
$("resultTitle").innerText = "胜利!";
const reward = 500 + state.stage * 100;
state.gold += reward;
state.maxStage = Math.max(state.maxStage, state.stage);
$("resultReward").innerText = "获得金币 +" + reward;
} else {
$("resultTitle").innerText = "战败";
$("resultReward").innerText = "再接再厉";
}
save();
}
function nextStage() {
state.stage++;
startBattle();
}
function toggleAuto() {
state.auto = !state.auto;
$("autoText").innerText = state.auto ? "开" : "关";
}
// ========== 初始化 ==========
load();
showHome();
</script>
</body>
</html>Game Source: 援救狗星人
Creator: BlazingDragon43
Libraries: none
Complexity: complex (1238 lines, 33.0 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-blazingdragon43-mw56a7be" to link back to the original. Then publish at arcadelab.ai/publish.