国民党军僵尸 - 模型与行走测试
by TurboKoala83496 lines15.7 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>
body { margin: 0; background: #1a1a2e; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; font-family: 'Microsoft YaHei', sans-serif; }
canvas { background: linear-gradient(180deg, #0f1a1a 0%, #1a2a2a 100%); border-radius: 12px; box-shadow: 0 0 40px rgba(74, 26, 26, 0.5); cursor: grab; }
canvas:active { cursor: grabbing; }
.info { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); color: #8A9A7A; font-size: 14px; letter-spacing: 4px; text-shadow: 0 0 10px rgba(138, 154, 122, 0.8); pointer-events: none; }
.controls { position: absolute; bottom: 20px; color: #5A6B4A; font-size: 12px; letter-spacing: 2px; text-align: center; width: 100%; pointer-events: none; }
</style>
</head>
<body>
<div class="info">★ 国民党军僵尸 · 模型与行走动画测试 ★</div>
<div class="controls">左键拖拽平移 | 右键拖拽旋转 | 滚轮缩放 | 双击重置视角</div>
<canvas id="game" width="900" height="600"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
// ========== 僵尸配色(参考国民党军制服 + 僵尸化) ==========
const Z = {
uniform: '#5A6B4A', // 褪色的黄绿色制服
uniformDark:'#3D4A32', // 制服阴影
uniformLite:'#7A8B6A', // 制服高光
skin: '#8A9A7A', // 灰败的皮肤
skinDark: '#6A7A5A', // 皮肤阴影
blood: '#4A1A1A', // 暗红色的血迹
bloodLite: '#7A2A2A', // 新鲜血迹
bone: '#E8E0D0', // 骨头颜色
helmet: '#4A4A4A', // 德制M35钢盔
helmetDark: '#2A2A2A', // 钢盔阴影
bandage: '#A89878', // 脏兮兮的绷带
bind: '#6A7A5A', // 绑腿
shoe: '#1A1A1A', // 黑布鞋
eye: '#FF3333', // 发红的眼睛
strap: '#6B5A3A', // 帆布弹带
};
// ========== 摄像机系统 ==========
const camera = {
x: canvas.width / 2, y: canvas.height / 2,
zoom: 1.2, rotation: 0,
isDragging: false, dragButton: 0, lastX: 0, lastY: 0
};
// ========== 人物尺寸 (R6比例) ==========
const S = { headW: 52, headH: 52, torsoW: 84, torsoH: 76, armW: 36, armH: 76, legW: 44, legH: 84 };
// =====================================================================
// 绘制工具
// =====================================================================
function roundRect(ctx, x, y, w, h, r) {
ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath();
}
// =====================================================================
// 【部件 1】头部(带钢盔,灰败皮肤,血迹)
// =====================================================================
function drawZombieHead(ctx, x, y, rotation = 0) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
const { headW, headH } = S;
const headX = -headW / 2;
const headY = -headH;
// 脖子(灰败色)
ctx.fillStyle = Z.skinDark;
ctx.fillRect(-8, -6, 16, 6);
// 头部主体
ctx.fillStyle = Z.skin;
roundRect(ctx, headX, headY, headW, headH, 6); ctx.fill();
// 左侧阴影
ctx.fillStyle = Z.skinDark;
ctx.fillRect(headX, headY, 6, headH);
// 眼睛(发红,一只眼睛可能已经缺失或流血)
ctx.fillStyle = '#1A1A1A';
ctx.fillRect(headX + 12, headY + 22, 7, 5); // 左眼眶
ctx.fillStyle = Z.eye;
ctx.fillRect(headX + 13, headY + 23, 5, 3); // 发红的眼球
// 右眼(用血迹代替,表示眼球已经腐烂)
ctx.fillStyle = Z.blood;
ctx.fillRect(headX + headW - 19, headY + 22, 7, 5);
ctx.fillStyle = Z.bloodLite;
ctx.fillRect(headX + headW - 18, headY + 23, 5, 3);
// 嘴巴(撕裂的伤口)
ctx.strokeStyle = Z.blood;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(headX + 15, headY + 38);
ctx.lineTo(headX + 25, headY + 42);
ctx.lineTo(headX + 35, headY + 38);
ctx.stroke();
// 脸部的血迹/污渍
ctx.fillStyle = 'rgba(74, 26, 26, 0.4)';
ctx.beginPath();
ctx.arc(headX + 10, headY + 30, 8, 0, Math.PI * 2);
ctx.fill();
// ===== 德制M35钢盔 =====
const helmetW = headW + 16;
const helmetH = 26;
const helmetX = -helmetW / 2;
const helmetY = headY - helmetH + 4;
// 钢盔主体
ctx.fillStyle = Z.helmet;
ctx.beginPath();
ctx.moveTo(helmetX + 8, helmetY);
ctx.lineTo(helmetX + helmetW - 8, helmetY);
ctx.quadraticCurveTo(helmetX + helmetW, helmetY + 15, helmetX + helmetW - 6, helmetY + helmetH);
ctx.lineTo(helmetX + 6, helmetY + helmetH);
ctx.quadraticCurveTo(helmetX, helmetY + 15, helmetX + 8, helmetY);
ctx.closePath();
ctx.fill();
// 钢盔左侧阴影
ctx.fillStyle = Z.helmetDark;
ctx.beginPath();
ctx.moveTo(helmetX, helmetY + 8);
ctx.lineTo(helmetX + 12, helmetY + 2);
ctx.lineTo(helmetX + 12, helmetY + helmetH);
ctx.lineTo(helmetX + 4, helmetY + helmetH);
ctx.closePath();
ctx.fill();
// 钢盔边缘
ctx.fillStyle = '#333333';
ctx.fillRect(helmetX + 4, helmetY + helmetH - 4, helmetW - 8, 4);
// 钢盔上的弹孔/凹陷
ctx.fillStyle = '#1A1A1A';
ctx.beginPath(); ctx.arc(helmetX + helmetW - 12, helmetY + 10, 4, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#555555';
ctx.beginPath(); ctx.arc(helmetX + helmetW - 13, helmetY + 9, 2, 0, Math.PI * 2); ctx.fill();
ctx.restore();
}
// =====================================================================
// 【部件 2】躯干(破损的制服,帆布弹带)
// =====================================================================
function drawZombieTorso(ctx, x, y, rotation = 0) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
const { torsoW, torsoH } = S;
const torsoX = -torsoW / 2;
const torsoY = -torsoH;
// 军装主体
ctx.fillStyle = Z.uniform;
roundRect(ctx, torsoX, torsoY, torsoW, torsoH, 4); ctx.fill();
// 左侧阴影
ctx.fillStyle = Z.uniformDark;
ctx.fillRect(torsoX, torsoY, 8, torsoH);
// 右侧高光
ctx.fillStyle = Z.uniformLite;
ctx.fillRect(torsoX + torsoW - 6, torsoY, 6, torsoH);
// 领章(国民党军的领章通常是蓝白色或红色,这里做旧化处理)
ctx.fillStyle = '#4A5A6A';
ctx.fillRect(torsoX + 12, torsoY + 2, 14, 8);
ctx.fillRect(torsoX + torsoW - 26, torsoY + 2, 14, 8);
// 中缝
ctx.strokeStyle = Z.uniformDark; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(0, torsoY + 12); ctx.lineTo(0, torsoY + torsoH - 4); ctx.stroke();
// 腰带
const beltY = torsoY + torsoH * 0.55;
ctx.fillStyle = '#2A1A0A'; // 破旧的皮带
ctx.fillRect(torsoX, beltY, torsoW, 7);
ctx.fillStyle = '#5A4A3A'; // 磨损的金属扣
ctx.fillRect(-5, beltY + 1, 10, 5);
// 胸前交叉的帆布弹带(国民党士兵标志性装备)
ctx.strokeStyle = Z.strap;
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(torsoX + 8, torsoY + 4);
ctx.lineTo(torsoX + torsoW - 8, beltY + 3);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(torsoX + torsoW - 8, torsoY + 4);
ctx.lineTo(torsoX + 8, beltY + 3);
ctx.stroke();
// 弹带上的口袋
ctx.fillStyle = '#5A4A3A';
ctx.fillRect(torsoX + 20, torsoY + 20, 10, 12);
ctx.fillRect(torsoX + 20, torsoY + 36, 10, 12);
ctx.fillRect(torsoX + torsoW - 30, torsoY + 20, 10, 12);
ctx.fillRect(torsoX + torsoW - 30, torsoY + 36, 10, 12);
// 制服上的破洞(露出里面的伤口)
ctx.fillStyle = Z.blood;
ctx.beginPath();
ctx.moveTo(torsoX + torsoW - 25, torsoY + 15);
ctx.lineTo(torsoX + torsoW - 15, torsoY + 20);
ctx.lineTo(torsoX + torsoW - 20, torsoY + 30);
ctx.closePath();
ctx.fill();
// 破洞边缘的布料撕裂效果
ctx.strokeStyle = Z.uniformDark;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(torsoX + torsoW - 25, torsoY + 15);
ctx.lineTo(torsoX + torsoW - 15, torsoY + 20);
ctx.lineTo(torsoX + torsoW - 20, torsoY + 30);
ctx.stroke();
ctx.restore();
}
// =====================================================================
// 【部件 3】手臂(僵直下垂或前伸)
// =====================================================================
function drawZombieArm(ctx, x, y, rotation = 0, side = 'left') {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
const { armW, armH } = S;
const armX = -armW / 2;
// 袖子
ctx.fillStyle = Z.uniform;
roundRect(ctx, armX, 0, armW, armH - 12, 3); ctx.fill();
// 阴影/高光
if (side === 'left') {
ctx.fillStyle = Z.uniformDark;
ctx.fillRect(armX, 0, 5, armH - 12);
} else {
ctx.fillStyle = Z.uniformLite;
ctx.fillRect(armX + armW - 5, 0, 5, armH - 12);
}
// 手部(灰败的皮肤)
ctx.fillStyle = Z.skin;
roundRect(ctx, armX + 4, armH - 14, armW - 8, 14, 3); ctx.fill();
// 手指(僵硬的僵尸手,这里用简单的线条表示)
ctx.strokeStyle = Z.skinDark;
ctx.lineWidth = 2;
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(armX + 8 + i * 6, armH);
ctx.lineTo(armX + 8 + i * 6, armH + 8);
ctx.stroke();
}
// 手臂上的血迹
ctx.fillStyle = 'rgba(74, 26, 26, 0.5)';
ctx.beginPath();
ctx.arc(armX + armW / 2, armH * 0.6, 8, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// =====================================================================
// 【部件 4】腿(绑腿,黑布鞋)
// =====================================================================
function drawZombieLeg(ctx, x, y, rotation = 0, side = 'left') {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
const { legW, legH } = S;
const legX = -legW / 2;
// 裤子
ctx.fillStyle = Z.uniform;
roundRect(ctx, legX, 0, legW, legH * 0.55, 3); ctx.fill();
ctx.fillStyle = Z.uniformDark;
ctx.fillRect(legX, 0, 6, legH * 0.55);
// 绑腿(绿黄色,缠绕多圈)
const bindY = legH * 0.5;
const bindH = legH * 0.42;
ctx.fillStyle = Z.bind;
roundRect(ctx, legX - 1, bindY, legW + 2, bindH, 2); ctx.fill();
// 绑腿的螺旋缠绕线
ctx.strokeStyle = '#4A5A3A'; ctx.lineWidth = 1.5;
for (let i = 0; i < 6; i++) {
const yy = bindY + 2 + i * (bindH - 4) / 5;
ctx.beginPath(); ctx.moveTo(legX - 1, yy); ctx.lineTo(legX + legW + 1, yy); ctx.stroke();
}
// 绑腿上的污渍/血迹
ctx.fillStyle = 'rgba(74, 26, 26, 0.4)';
ctx.beginPath();
ctx.arc(legX + legW / 2, bindY + bindH / 2, 6, 0, Math.PI * 2);
ctx.fill();
// 黑布鞋
const shoeY = bindY + bindH;
const shoeH = legH * 0.08;
ctx.fillStyle = Z.shoe;
roundRect(ctx, legX - 3, shoeY, legW + 6, shoeH, 3); ctx.fill();
ctx.restore();
}
// =====================================================================
// 【装配函数】绘制完整僵尸,带“蹒跚行走”动画
// =====================================================================
function drawZombie(ctx, centerX, groundY, scale = 1, anim = {}) {
const a = {
bob: anim.bob || 0,
headRot: anim.headRot || 0,
torsoRot: anim.torsoRot || 0,
leftArmRot: anim.leftArmRot || 0,
rightArmRot: anim.rightArmRot || 0,
leftLegRot: anim.leftLegRot || 0,
rightLegRot: anim.rightLegRot || 0,
};
const shoulderY = -S.torsoH;
const headY = shoulderY;
// 僵尸的腿间距比较近,显得更加笨重
const legSpacing = S.torsoW / 4 - 4;
const armSpacing = S.torsoW / 2 + S.armW / 2 - 2;
ctx.save();
ctx.translate(centerX, groundY + a.bob);
ctx.scale(scale, scale);
// 绘制顺序(从后到前)
drawZombieLeg(ctx, -legSpacing, 0, a.leftLegRot, 'left');
drawZombieLeg(ctx, legSpacing, 0, a.rightLegRot, 'right');
drawZombieTorso(ctx, 0, 0, a.torsoRot);
drawZombieArm(ctx, armSpacing, shoulderY, a.rightArmRot, 'right');
drawZombieHead(ctx, 0, headY, a.headRot);
drawZombieArm(ctx, -armSpacing, shoulderY, a.leftArmRot, 'left');
ctx.restore();
}
// =====================================================================
// 主循环 - 僵尸行走动画
// =====================================================================
let time = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
time += 0.02;
// 僵尸行走相位(较慢,不规律)
const phase = time * 3.5;
// 僵尸特有的“不对称”步态
// 左腿正常迈,右腿拖着走
const leftLegRot = Math.sin(phase) * 0.35;
const rightLegRot = Math.sin(phase - Math.PI * 0.7) * 0.25 - 0.1; // 右腿拖沓
// 手臂:僵尸经典姿势,向前伸出,略微摆动
const leftArmRot = -1.2 + Math.sin(phase) * 0.1; // 左臂向前伸
const rightArmRot = -1.0 + Math.sin(phase - Math.PI * 0.7) * 0.1; // 右臂向前伸
// 身体起伏:僵尸走路一瘸一拐,起伏不规则
const bob = Math.abs(Math.sin(phase)) * 4.0 + Math.sin(phase * 2) * 1.5;
// 头部晃动:僵尸的头会不受控制地抽搐
const headRot = Math.sin(phase * 3) * 0.08 + Math.sin(time * 0.5) * 0.04;
// 躯干晃动:左摇右晃
const torsoRot = Math.sin(phase) * 0.08;
const anim = {
bob,
headRot,
torsoRot,
leftArmRot,
rightArmRot,
leftLegRot,
rightLegRot,
};
const groundY = canvas.height - 100;
const cx = canvas.width / 2;
const scale = 1.8;
// ===== 环境绘制 =====
// 泥泞的地面
ctx.fillStyle = '#1A2A1A';
ctx.fillRect(0, groundY, canvas.width, canvas.height - groundY);
// 地面上的血迹
ctx.fillStyle = 'rgba(74, 26, 26, 0.2)';
ctx.beginPath();
ctx.ellipse(cx - 50, groundY + 10, 40, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(cx + 60, groundY + 20, 30, 6, 0, 0, Math.PI * 2);
ctx.fill();
// 僵尸的阴影
const shadowScale = 1 - bob * 0.015;
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.beginPath();
ctx.ellipse(cx, groundY + 5, 75 * shadowScale, 12 * shadowScale, 0, 0, Math.PI * 2);
ctx.fill();
// ===== 应用摄像机 =====
ctx.save();
ctx.translate(camera.x, camera.y);
ctx.rotate(camera.rotation);
ctx.scale(camera.zoom, camera.zoom);
ctx.translate(-camera.x, -camera.y);
// 绘制僵尸
drawZombie(ctx, cx, groundY, scale, anim);
ctx.restore();
// UI 信息
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.font = '12px sans-serif';
ctx.fillText(`缩放: ${camera.zoom.toFixed(2)}x | 旋转: ${(camera.rotation * 180 / Math.PI).toFixed(0)}°`, 20, 30);
requestAnimationFrame(animate);
}
// =====================================================================
// 交互事件
// =====================================================================
canvas.addEventListener('contextmenu', e => e.preventDefault());
canvas.addEventListener('mousedown', (e) => {
camera.isDragging = true;
camera.dragButton = e.button;
camera.lastX = e.clientX;
camera.lastY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (!camera.isDragging) return;
const dx = e.clientX - camera.lastX;
const dy = e.clientY - camera.lastY;
if (camera.dragButton === 0) {
camera.x += dx; camera.y += dy;
} else if (camera.dragButton === 2) {
camera.rotation += dx * 0.005;
}
camera.lastX = e.clientX;
camera.lastY = e.clientY;
});
canvas.addEventListener('mouseup', () => { camera.isDragging = false; });
canvas.addEventListener('mouseleave', () => { camera.isDragging = false; });
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
camera.zoom *= e.deltaY > 0 ? 0.9 : 1.1;
camera.zoom = Math.max(0.2, Math.min(5.0, camera.zoom));
}, { passive: false });
canvas.addEventListener('dblclick', () => {
camera.x = canvas.width / 2; camera.y = canvas.height / 2;
camera.zoom = 1.2; camera.rotation = 0;
});
animate();
</script>
</body>
</html>Game Source: 国民党军僵尸 - 模型与行走测试
Creator: TurboKoala83
Libraries: none
Complexity: complex (496 lines, 15.7 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-turbokoala83-muquw169" to link back to the original. Then publish at arcadelab.ai/publish.