🎮ArcadeLab

抗战大刀 - 独立建模

by TurboKoala83
336 lines11.1 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: #0a0a12; overflow: hidden; font-family: 'Microsoft YaHei', sans-serif; touch-action: none; }
  canvas { display: block; cursor: grab; }
  canvas:active { cursor: grabbing; }
  .info { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); color: #8A9A7A; font-size: 12px; letter-spacing: 3px; text-shadow: 0 0 10px rgba(138, 154, 122, 0.8); pointer-events: none; z-index: 10; }
  .controls { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); color: #666; font-size: 11px; text-align: center; pointer-events: none; z-index: 10; width: 100%; }
</style>
</head>
<body>
<div class="info">★ 抗战大刀 · 独立建模 ★</div>
<div class="controls">左键拖拽平移 | 右键拖拽旋转 | 滚轮缩放 | 双击重置视角</div>
<canvas id="game"></canvas>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

let W = window.innerWidth;
let H = window.innerHeight;
canvas.width = W;
canvas.height = H;

window.addEventListener('resize', () => {
  W = window.innerWidth; H = window.innerHeight;
  canvas.width = W; canvas.height = H;
});

// ========== 大刀配色 ==========
const D = {
  blade:      '#8A8A8A', // 刀刃主色
  bladeDark:  '#5A5A5A', // 刀刃阴影
  bladeLite:  '#B0B0B0', // 刀刃高光
  bladeEdge:  '#C8C8C8', // 锋刃
  bloodGroove:'#6A6A6A', // 血槽
  back:       '#4A4A4A', // 刀背
  guard:      '#B8963A', // 铜护手
  guardDark:  '#8A6A1A', // 护手阴影
  handle:     '#3A2A1A', // 刀柄木质
  wrap:       '#5A6B7C', // 缠布(蓝灰色)
  wrapDark:   '#3D4A57', // 缠布阴影
  ring:       '#8A8A8A', // 刀环
  ringDark:   '#5A5A5A', // 刀环阴影
  redCloth:   '#C41E3A', // 红绸
};

// ========== 摄像机 ==========
const camera = {
  x: W / 2, y: H / 2,
  zoom: 1.0, rotation: 0,
  isDragging: false, dragButton: 0, lastX: 0, lastY: 0,
};

// =====================================================================
// 绘制抗战大刀(R6方块风格)
// 原点 (0,0) 在【护手中心】,x 轴正方向为刀尖方向
// 参考真实尺寸:全长约90cm,刃长59.5cm,柄长21.5cm
// 代码比例:刀身长约 420px,刀柄长约 150px
// =====================================================================
function drawDadao(ctx) {
  // ---------------------------------------------------------
  // 1. 刀身(刀背 + 刀面 + 刀刃)
  //    刀身从护手处向后逐渐加宽,前宽后窄
  //    刀尖上挑呈雁翅形
  // ---------------------------------------------------------
  const bladeStartX = 10;   // 刀身起点(护手右侧)
  const bladeLength = 420;
  const bladeEndX = bladeStartX + bladeLength;

  // 刀背线(上边缘):从护手处平直延伸,前端斜下
  // 刀刃线(下边缘):从护手处较窄,向前逐渐加宽
  const backY0 = -14;       // 刀背起点高度
  const backY1 = -18;       // 刀背前端高度
  const edgeY0 = 6;         // 刀刃起点高度
  const edgeY1 = 14;        // 刀刃前端高度(前宽后窄)
  const tipY = -28;         // 刀尖上挑

  // 刀身主轮廓
  ctx.fillStyle = D.blade;
  ctx.beginPath();
  ctx.moveTo(bladeStartX, backY0);          // 刀背起点
  ctx.lineTo(bladeEndX - 40, backY1);       // 刀背前端
  ctx.lineTo(bladeEndX, tipY);              // 刀尖(雁翅上挑)
  ctx.lineTo(bladeEndX - 15, edgeY1);       // 刀尖下沿
  ctx.lineTo(bladeStartX, edgeY0);          // 刀刃起点
  ctx.closePath();
  ctx.fill();

  // 刀面高光(上半部分)
  ctx.fillStyle = D.bladeLite;
  ctx.beginPath();
  ctx.moveTo(bladeStartX, backY0);
  ctx.lineTo(bladeEndX - 40, backY1);
  ctx.lineTo(bladeEndX - 20, (backY1 + tipY) / 2);
  ctx.lineTo(bladeStartX, backY0 + 8);
  ctx.closePath();
  ctx.fill();

  // 刀面阴影(下半部分)
  ctx.fillStyle = D.bladeDark;
  ctx.beginPath();
  ctx.moveTo(bladeStartX, edgeY0);
  ctx.lineTo(bladeEndX - 15, edgeY1);
  ctx.lineTo(bladeEndX - 30, edgeY1 - 6);
  ctx.lineTo(bladeStartX, edgeY0 - 6);
  ctx.closePath();
  ctx.fill();

  // 刀背(上边缘的一条厚线)
  ctx.strokeStyle = D.back;
  ctx.lineWidth = 4;
  ctx.beginPath();
  ctx.moveTo(bladeStartX, backY0);
  ctx.lineTo(bladeEndX - 40, backY1);
  ctx.stroke();

  // 锋刃(下边缘的亮线)
  ctx.strokeStyle = D.bladeEdge;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(bladeStartX, edgeY0);
  ctx.lineTo(bladeEndX - 15, edgeY1);
  ctx.stroke();

  // 刀尖锋刃
  ctx.strokeStyle = D.bladeEdge;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(bladeEndX - 40, backY1);
  ctx.lineTo(bladeEndX, tipY);
  ctx.lineTo(bladeEndX - 15, edgeY1);
  ctx.stroke();

  // ---------------------------------------------------------
  // 2. 血槽(刀身靠近刀背处的两条凹槽)
  // ---------------------------------------------------------
  ctx.strokeStyle = D.bloodGroove;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(bladeStartX + 20, backY0 + 6);
  ctx.lineTo(bladeEndX - 60, backY1 + 8);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(bladeStartX + 20, backY0 + 12);
  ctx.lineTo(bladeEndX - 60, backY1 + 14);
  ctx.stroke();

  // ---------------------------------------------------------
  // 3. 护手(元宝形,铜质)
  // ---------------------------------------------------------
  const guardW = 30;
  const guardH = 40;
  const guardX = bladeStartX - guardW + 2;
  const guardY = -guardH / 2;

  ctx.fillStyle = D.guard;
  ctx.beginPath();
  ctx.moveTo(guardX, guardY);                          // 左上
  ctx.lineTo(guardX + guardW, guardY - 4);             // 右上(向外弯)
  ctx.lineTo(guardX + guardW, guardY + guardH);        // 右下
  ctx.lineTo(guardX, guardY + guardH + 4);             // 左下(向外弯)
  ctx.closePath();
  ctx.fill();

  // 护手阴影
  ctx.fillStyle = D.guardDark;
  ctx.fillRect(guardX, guardY + guardH - 6, guardW, 6);

  // 护手高光
  ctx.fillStyle = '#D4B04A';
  ctx.fillRect(guardX + 2, guardY + 2, 4, guardH - 4);

  // ---------------------------------------------------------
  // 4. 刀柄(木质,缠布)
  // ---------------------------------------------------------
  const handleStartX = guardX - 5;
  const handleLength = 150;
  const handleEndX = handleStartX - handleLength;
  const handleW = 16;
  const handleY = -handleW / 2;

  // 刀柄主体(木质)
  ctx.fillStyle = D.handle;
  ctx.fillRect(handleEndX, handleY, handleLength, handleW);

  // 刀柄高光
  ctx.fillStyle = '#5A3A2A';
  ctx.fillRect(handleEndX, handleY, handleLength, 3);

  // 刀柄缠布(蓝灰色,一圈一圈)
  ctx.strokeStyle = D.wrap;
  ctx.lineWidth = 4;
  for (let i = 0; i < 12; i++) {
    const wx = handleEndX + 10 + i * 11;
    ctx.beginPath();
    ctx.moveTo(wx, handleY - 1);
    ctx.lineTo(wx + 6, handleY + handleW + 1);
    ctx.stroke();
  }

  // 缠布阴影(增加立体感)
  ctx.strokeStyle = D.wrapDark;
  ctx.lineWidth = 1;
  for (let i = 0; i < 12; i++) {
    const wx = handleEndX + 10 + i * 11;
    ctx.beginPath();
    ctx.moveTo(wx + 4, handleY - 1);
    ctx.lineTo(wx + 10, handleY + handleW + 1);
    ctx.stroke();
  }

  // ---------------------------------------------------------
  // 5. 刀环(刀柄末端的圆环,带红绸)
  // ---------------------------------------------------------
  const ringX = handleEndX - 5;
  const ringY = 0;
  const ringR = 14;

  // 刀环(圆环)
  ctx.strokeStyle = D.ring;
  ctx.lineWidth = 5;
  ctx.beginPath();
  ctx.arc(ringX, ringY, ringR, 0, Math.PI * 2);
  ctx.stroke();

  // 刀环高光
  ctx.strokeStyle = '#B0B0B0';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(ringX - 2, ringY - 2, ringR - 2, 0, Math.PI * 2);
  ctx.stroke();

  // 红绸(从刀环上飘出)
  ctx.strokeStyle = D.redCloth;
  ctx.lineWidth = 3;
  ctx.beginPath();
  ctx.moveTo(ringX - ringR, ringY - 5);
  ctx.quadraticCurveTo(ringX - ringR - 20, ringY - 20, ringX - ringR - 35, ringY - 10);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(ringX - ringR, ringY + 5);
  ctx.quadraticCurveTo(ringX - ringR - 15, ringY + 18, ringX - ringR - 30, ringY + 10);
  ctx.stroke();

  // ---------------------------------------------------------
  // 6. 细节:刀身与护手连接处的套环
  // ---------------------------------------------------------
  ctx.fillStyle = D.bladeDark;
  ctx.fillRect(bladeStartX - 4, backY0 - 4, 8, edgeY0 - backY0 + 8);
}

// =====================================================================
// 主循环
// =====================================================================
function animate() {
  ctx.clearRect(0, 0, W, H);
  ctx.fillStyle = '#0F1A0F';
  ctx.fillRect(0, 0, W, H);

  // 背景网格
  const gridSize = 80;
  ctx.strokeStyle = 'rgba(74, 90, 74, 0.12)';
  ctx.lineWidth = 1;
  for (let x = 0; x < W; x += gridSize) {
    for (let y = 0; y < H; y += gridSize) {
      ctx.strokeRect(x, y, gridSize, gridSize);
    }
  }

  // 参考线(护手中心)
  ctx.strokeStyle = 'rgba(74, 90, 74, 0.3)';
  ctx.lineWidth = 1;
  ctx.beginPath(); ctx.moveTo(0, H / 2); ctx.lineTo(W, H / 2); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(W / 2, 0); ctx.lineTo(W / 2, H); ctx.stroke();

  // 应用摄像机
  ctx.save();
  ctx.translate(camera.x, camera.y);
  ctx.rotate(camera.rotation);
  ctx.scale(camera.zoom, camera.zoom);
  ctx.translate(-camera.x, -camera.y);

  // 在屏幕中心绘制大刀,原点对齐护手
  ctx.save();
  ctx.translate(W / 2, H / 2);
  drawDadao(ctx);
  ctx.restore();

  ctx.restore();

  // UI 信息
  ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
  ctx.font = '12px sans-serif';
  ctx.textAlign = 'left';
  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 = W / 2; camera.y = H / 2;
  camera.zoom = 1.0; camera.rotation = 0;
});

requestAnimationFrame(animate);
</script>
</body>
</html>

Game Source: 抗战大刀 - 独立建模

Creator: TurboKoala83

Libraries: none

Complexity: complex (336 lines, 11.1 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-muqvmps8" to link back to the original. Then publish at arcadelab.ai/publish.