🎮ArcadeLab

红军战士 · 模型

by EpicCoder88
283 lines7.2 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>红军战士 · 模型</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    height: 100%;
    background: #0d0a08;
    overflow: hidden;
    font-family: system-ui, -apple-system, "PingFang SC", sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  canvas {
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    border-radius: 8px;
    box-shadow: 0 0 60px rgba(180, 40, 30, 0.2), 0 0 0 1px rgba(120, 60, 40, 0.3);
  }
  .label {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    color: rgba(200, 150, 120, 0.4);
    font-size: 13px;
    letter-spacing: 0.3em;
    pointer-events: none;
  }
</style>
</head>
<body>
<canvas id="c" width="480" height="540"></canvas>
<div class="label">红 军 战 士</div>

<script>
(() => {
  'use strict';

  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d');
  const W = 480, H = 540;

  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  canvas.width = W * dpr;
  canvas.height = H * dpr;
  canvas.style.width = W + 'px';
  canvas.style.height = H + 'px';
  ctx.scale(dpr, dpr);
  ctx.imageSmoothingEnabled = false;

  // ==================== 颜色 ====================
  const C = {
    hat:    '#7a8299',
    coat:   '#6b7280',
    pants:  '#5a626f',
    wrap:   '#8b8578',
    shoe:   '#8b6f47',
    skin:   '#e8b88a',
    star:   '#d02020',
    collar: '#d02020',
    belt:   '#3a2418',
    buckle: '#8a6a40',
    gun:    '#2a2018',
    gunHi:  '#4a3828',
    wood:   '#5a3a20',
    woodHi: '#7a5030',
    eye:    '#2a1a10',
    mouth:  '#a06848',
  };

  // ==================== 工具 ====================
  function px(x, y, w, h, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x, y, w, h);
  }

  function shade(hex, amt) {
    const n = hex.replace('#', '');
    let r = parseInt(n.substr(0, 2), 16);
    let g = parseInt(n.substr(2, 2), 16);
    let b = parseInt(n.substr(4, 2), 16);
    r = Math.max(0, Math.min(255, Math.round(r + amt * 255)));
    g = Math.max(0, Math.min(255, Math.round(g + amt * 255)));
    b = Math.max(0, Math.min(255, Math.round(b + amt * 255)));
    return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');
  }

  function roundRect(x, y, w, h, r) {
    r = Math.min(r, w / 2, h / 2);
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
    ctx.arcTo(x + w, y + h, x, y + h, r);
    ctx.arcTo(x, y + h, x, y, r);
    ctx.arcTo(x, y, x + w, y, r);
    ctx.closePath();
  }

  // ==================== 绘制战士 ====================
  // 基础单位是"像素块",绘制时整体放大
  // 角色原始尺寸 32×48,放大 9 倍 = 288×432
  const PX = 9;
  const BASE_X = 24;   // 原始坐标偏移
  const BASE_Y = 6;

  // 帧计数器(用于行走动画)
  let frame = 0;

  function drawSoldier(pose, f) {
    const ox = BASE_X * PX;
    const oy = BASE_Y * PX;
    const S = PX;

    // 便捷绘制:原始坐标 → 屏幕坐标
    const p = (x, y, w, h, color) => {
      ctx.fillStyle = color;
      ctx.fillRect(ox + x * S, oy + y * S, w * S, h * S);
    };

    // ---- 动画偏移 ----
    let lLegY = 0, rLegY = 0, lArmY = 0, rArmY = 0;
    if (pose === 'walk') {
      const s = Math.sin(f * 0.18);
      lLegY = Math.round(s * 2);
      rLegY = -Math.round(s * 2);
      lArmY = -Math.round(s * 1.5);
      rArmY = Math.round(s * 1.5);
    }

    // ============ 腿 ============
    // 左腿
    p(10, 28 + lLegY, 5, 8, C.pants);
    // 右腿
    p(17, 28 + rLegY, 5, 8, C.pants);

    // 绑腿(横条纹)
    for (let i = 0; i < 5; i++) {
      const col = i % 2 === 0 ? C.wrap : shade(C.wrap, -0.16);
      p(10, 36 + i + lLegY, 5, 1, col);
      p(17, 36 + i + rLegY, 5, 1, col);
    }

    // 草鞋
    p(9, 41 + lLegY, 7, 3, C.shoe);
    p(16, 41 + rLegY, 7, 3, C.shoe);
    p(9, 42 + lLegY, 7, 1, shade(C.shoe, -0.12));
    p(16, 42 + rLegY, 7, 1, shade(C.shoe, -0.12));

    // ============ 躯干 ============
    p(9, 18, 14, 13, C.coat);

    // 衣领
    p(11, 18, 3, 3, C.collar);
    p(18, 18, 3, 3, C.collar);

    // 口袋
    p(11, 23, 4, 3, shade(C.coat, -0.1));
    p(17, 23, 4, 3, shade(C.coat, -0.1));
    p(11, 23, 4, 1, shade(C.coat, -0.2));
    p(17, 23, 4, 1, shade(C.coat, -0.2));

    // 腰带
    p(9, 28, 14, 2, C.belt);
    p(15, 28, 2, 2, C.buckle);

    // 躯干暗面(右侧)
    p(22, 18, 1, 13, shade(C.coat, -0.1));

    // ============ 手臂 ============
    // 左臂
    p(5, 19 + lArmY, 4, 9, C.coat);
    p(5, 28 + lArmY, 4, 3, C.skin);
    p(5, 19 + lArmY, 1, 9, shade(C.coat, -0.12));

    // 右臂
    p(23, 19 + rArmY, 4, 9, C.coat);
    p(23, 28 + rArmY, 4, 3, C.skin);
    p(26, 19 + rArmY, 1, 9, shade(C.coat, -0.12));

    // ============ 头部 ============
    p(10, 10, 12, 8, C.skin);

    // 眼睛
    p(12, 13, 2, 2, C.eye);
    p(18, 13, 2, 2, C.eye);

    // 嘴
    p(15, 16, 2, 1, C.mouth);

    // 耳朵
    p(9, 13, 1, 3, shade(C.skin, -0.08));
    p(22, 13, 1, 3, shade(C.skin, -0.08));

    // 下巴暗面
    p(10, 17, 12, 1, shade(C.skin, -0.06));

    // ============ 军帽 ============
    // 帽身
    p(9, 2, 14, 6, C.hat);
    // 顶部高光
    p(9, 2, 14, 1, shade(C.hat, 0.1));
    // 帽檐
    p(7, 8, 18, 2, shade(C.hat, -0.22));
    p(7, 9, 18, 1, shade(C.hat, -0.35));

    // 五角星
    p(14, 3, 4, 4, C.star);
    p(15, 4, 2, 2, shade(C.star, 0.25));

    // ============ 汉阳造步枪 ============
    // 枪管
    p(25, 13, 2, 19, C.gun);
    p(25, 13, 1, 19, C.gunHi);
    // 枪托
    p(24, 32, 4, 6, C.wood);
    p(24, 32, 4, 1, C.woodHi);
    // 枪机
    p(24, 22, 3, 2, '#3a2a1a');

    // ============ 瞄准状态 ============
    if (pose === 'aim') {
      // 手臂前伸
      // 右臂改画到前方
      p(23, 20, 4, 4, C.coat);
      p(24, 23, 3, 2, C.skin);
    }
  }

  // ==================== 主循环 ====================
  function loop() {
    requestAnimationFrame(loop);
    frame++;

    ctx.clearRect(0, 0, W, H);

    // 背景
    const g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, '#12100d');
    g.addColorStop(0.5, '#1a1613');
    g.addColorStop(1, '#12100d');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // 背景网格
    ctx.strokeStyle = 'rgba(80, 50, 40, 0.12)';
    ctx.lineWidth = 1;
    for (let x = 0; x <= W; x += 30) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
    }
    for (let y = 0; y <= H; y += 30) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
    }

    // 地面阴影
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.beginPath();
    ctx.ellipse(W / 2, BASE_Y * PX + 44 * PX, 70, 14, 0, 0, Math.PI * 2);
    ctx.fill();

    // 三种姿势轮播(每 120 帧切换)
    const poseIndex = Math.floor(frame / 120) % 3;
    const poses = ['stand', 'walk', 'aim'];
    const pose = poses[poseIndex];

    drawSoldier(pose, frame);

    // 姿势名称
    ctx.fillStyle = 'rgba(200, 150, 120, 0.3)';
    ctx.font = '12px system-ui, sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText(pose.toUpperCase(), W / 2, H - 16);
  }

  loop();
})();
</script>
</body>
</html>

Game Source: 红军战士 · 模型

Creator: EpicCoder88

Libraries: none

Complexity: complex (283 lines, 7.2 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-epiccoder88-murvw036" to link back to the original. Then publish at arcadelab.ai/publish.