🎮ArcadeLab

国民党军僵尸 - 模型修正版

by TurboKoala83
305 lines14.6 KB
▶ Play
<!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">左键拖拽平移 | 右键拖拽旋转 | 滚轮缩放 | 双击重置 | R键重置</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:       '#7A8A6A', skinDark:   '#5A6A4A', skinRotten: '#4A5A3A',
  blood:      '#4A1A1A', bloodLite:  '#7A2A2A', bloodFresh: '#8A1A1A',
  bone:       '#E8E0D0', helmet:     '#4A4A4A', helmetDark: '#2A2A2A',
  helmetLite: '#6A6A6A', eye:        '#FF3333', eyeDark:    '#8A1A1A',
  strap:      '#6B5A3A', bind:       '#5A6A4A', shoe:       '#1A1A1A',
};

// ========== 摄像机 ==========
const camera = { x: canvas.width / 2, y: canvas.height / 2, zoom: 1.2, rotation: 0, isDragging: false, dragButton: 0, lastX: 0, lastY: 0 };

// ========== 尺寸 ==========
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(); }

// =====================================================================
// 【修正】僵尸头部(钢盔压头,狰狞表情)
// =====================================================================
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.skinRotten; ctx.fillRect(-8, -8, 16, 8);

  // 头部主体
  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 = Z.skinRotten; ctx.fillRect(headX + 6, headY + 2, headW - 12, 8);

  // === 狰狞的眼睛 ===
  // 左眼:深陷的眼窝 + 发红的眼球
  ctx.fillStyle = '#1A1A1A'; ctx.fillRect(headX + 10, headY + 20, 9, 7);
  ctx.fillStyle = Z.eye; ctx.fillRect(headX + 12, headY + 22, 5, 3);
  ctx.fillStyle = Z.eyeDark; ctx.fillRect(headX + 12, headY + 22, 2, 1); // 瞳孔

  // 右眼:腐烂的血窟窿
  ctx.fillStyle = Z.blood; ctx.fillRect(headX + headW - 19, headY + 19, 9, 8);
  ctx.fillStyle = Z.bloodFresh; ctx.fillRect(headX + headW - 18, headY + 21, 7, 4);
  ctx.fillStyle = '#1A1A1A'; ctx.fillRect(headX + headW - 17, headY + 22, 5, 2);

  // === 扭曲的嘴(撕裂的伤口,露出牙齿)===
  ctx.fillStyle = Z.blood; 
  ctx.beginPath();
  ctx.moveTo(headX + 12, headY + 36);
  ctx.lineTo(headX + 20, headY + 33); // 左边嘴角上扬
  ctx.lineTo(headX + 28, headY + 38); // 中间凹陷
  ctx.lineTo(headX + 36, headY + 34); // 右边嘴角上扬
  ctx.lineTo(headX + 40, headY + 40); // 右下
  ctx.lineTo(headX + 30, headY + 43); // 下唇
  ctx.lineTo(headX + 18, headY + 42); // 左下
  ctx.closePath(); ctx.fill();
  
  // 牙齿
  ctx.fillStyle = Z.bone;
  ctx.fillRect(headX + 18, headY + 36, 4, 4);
  ctx.fillRect(headX + 24, headY + 37, 4, 3);
  ctx.fillRect(headX + 30, headY + 36, 4, 4);
  
  // 嘴角的血迹
  ctx.fillStyle = Z.bloodFresh;
  ctx.beginPath(); ctx.arc(headX + 10, headY + 38, 4, 0, Math.PI * 2); ctx.fill();

  // 脸部血迹
  ctx.fillStyle = 'rgba(74, 26, 26, 0.6)';
  ctx.beginPath(); ctx.arc(headX + 8, headY + 28, 6, 0, Math.PI * 2); ctx.fill();

  // ===== 【修正】M35钢盔 =====
  // 钢盔需要覆盖整个头部,并明显遮住额头
  const helmetW = headW + 20;
  const helmetH = 30;
  const helmetX = -helmetW / 2;
  const helmetY = headY - helmetH + 6; // 关键:让钢盔下沿压住额头

  // 钢盔主体(卵形轮廓)
  ctx.fillStyle = Z.helmet;
  ctx.beginPath();
  ctx.moveTo(helmetX + 10, helmetY);
  ctx.quadraticCurveTo(helmetX + helmetW / 2, helmetY - 8, helmetX + helmetW - 10, helmetY);
  ctx.quadraticCurveTo(helmetX + helmetW, helmetY + 10, helmetX + helmetW - 6, helmetY + helmetH);
  ctx.lineTo(helmetX + 6, helmetY + helmetH);
  ctx.quadraticCurveTo(helmetX, helmetY + 10, helmetX + 10, helmetY);
  ctx.closePath(); ctx.fill();

  // 钢盔左侧阴影
  ctx.fillStyle = Z.helmetDark;
  ctx.beginPath();
  ctx.moveTo(helmetX, helmetY + 8);
  ctx.lineTo(helmetX + 14, helmetY + 2);
  ctx.lineTo(helmetX + 14, helmetY + helmetH);
  ctx.lineTo(helmetX + 4, helmetY + helmetH);
  ctx.closePath(); ctx.fill();

  // 钢盔顶部高光
  ctx.fillStyle = Z.helmetLite;
  ctx.beginPath();
  ctx.moveTo(helmetX + 18, helmetY + 2);
  ctx.quadraticCurveTo(helmetX + helmetW / 2, helmetY - 6, helmetX + helmetW - 18, helmetY + 2);
  ctx.lineTo(helmetX + helmetW - 22, helmetY + 6);
  ctx.quadraticCurveTo(helmetX + helmetW / 2, helmetY - 2, helmetX + 22, helmetY + 6);
  ctx.closePath(); ctx.fill();

  // 钢盔边缘(卷边工艺)
  ctx.fillStyle = '#2A2A2A';
  ctx.beginPath();
  ctx.moveTo(helmetX + 6, helmetY + helmetH - 4);
  ctx.lineTo(helmetX + helmetW - 6, helmetY + helmetH - 4);
  ctx.lineTo(helmetX + helmetW - 4, helmetY + helmetH);
  ctx.lineTo(helmetX + 4, helmetY + helmetH);
  ctx.closePath(); ctx.fill();

  // 钢盔上的弹孔/凹陷
  ctx.fillStyle = '#1A1A1A';
  ctx.beginPath(); ctx.arc(helmetX + helmetW - 15, helmetY + 12, 5, 0, Math.PI * 2); ctx.fill();
  ctx.fillStyle = '#333333';
  ctx.beginPath(); ctx.arc(helmetX + helmetW - 16, helmetY + 11, 2, 0, Math.PI * 2); ctx.fill();

  // 钢盔上的血迹(从弹孔流下)
  ctx.fillStyle = Z.blood;
  ctx.fillRect(helmetX + helmetW - 17, helmetY + 14, 3, 8);

  ctx.restore();
}

// =====================================================================
// 躯干、手臂、腿(保持之前的逻辑,稍作调整)
// =====================================================================
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.restore();
}

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();
}

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 = '#3A4A2A'; 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(); }
  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();

  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', resetCamera);
window.addEventListener('keydown', (e) => { if (e.key === 'r' || e.key === 'R') resetCamera(); });
function resetCamera() { 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 (305 lines, 14.6 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-muquy0ko" to link back to the original. Then publish at arcadelab.ai/publish.