红军战士 · 方块人
by EpicCoder88390 lines11.0 KB
<!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 {
border-radius: 10px;
box-shadow: 0 0 70px rgba(180, 40, 30, 0.22), 0 0 0 1px rgba(120, 60, 40, 0.3);
}
.label {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: rgba(200, 150, 120, 0.35);
font-size: 13px;
letter-spacing: 0.4em;
pointer-events: none;
}
</style>
</head>
<body>
<canvas id="c" width="520" height="600"></canvas>
<div class="label">红 军 战 士</div>
<script>
(() => {
'use strict';
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 520, H = 600;
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);
// ==================== 颜色 ====================
const C = {
hat: '#6a7a99',
coat: '#6a7078',
pants: '#555c64',
wrap: '#8a8578',
shoe: '#7a5f3a',
skin: '#e8b88a',
star: '#d02020',
collar: '#d02020',
belt: '#3a2418',
buckle: '#c8a040',
gun: '#2a2018',
gunHi: '#4a3828',
wood: '#6a4a28',
woodHi: '#8a6040',
eye: '#1a1208',
mouth: '#a06848',
};
// ==================== 工具 ====================
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('');
}
// ==================== 绘制立体方块 ====================
// 斜投影:正面矩形 + 顶面 + 右侧面
const DEPTH = 16; // 深度像素
const OX = DEPTH * 0.5; // 8
const OY = -DEPTH * 0.5; // -8
function drawBox(x, y, w, h, color) {
// 顶面(最亮)
ctx.fillStyle = shade(color, 0.2);
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w + OX, y + OY);
ctx.lineTo(x + OX, y + OY);
ctx.closePath();
ctx.fill();
// 右侧面(最暗)
ctx.fillStyle = shade(color, -0.22);
ctx.beginPath();
ctx.moveTo(x + w, y);
ctx.lineTo(x + w + OX, y + OY);
ctx.lineTo(x + w + OX, y + h + OY);
ctx.lineTo(x + w, y + h);
ctx.closePath();
ctx.fill();
// 正面(中等)
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
// 五角星
function drawStar(cx, cy, outer, inner, points, rot, color) {
ctx.beginPath();
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? outer : inner;
const a = rot + i * Math.PI / points - Math.PI / 2;
const x = cx + Math.cos(a) * r;
const y = cy + Math.sin(a) * r;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
// ==================== 角色尺寸 ====================
// Roblox R6 比例(放大版)
const LEG_W = 30, LEG_H = 60;
const TORSO_W = 60, TORSO_H = 60;
const ARM_W = 30, ARM_H = 60;
const HEAD_W = 60, HEAD_H = 30;
const HAT_W = 66, HAT_H = 26;
const CX = 260; // 中心 x
const GROUND_Y = 520; // 脚底 y
// ==================== 绘制角色 ====================
function drawSoldier(pose, frame) {
// ---- 动画偏移 ----
let lLegY = 0, rLegY = 0, lArmY = 0, rArmY = 0;
if (pose === 'walk') {
const s = Math.sin(frame * 0.14);
lLegY = Math.round(s * 4);
rLegY = -Math.round(s * 4);
lArmY = -Math.round(s * 3);
rArmY = Math.round(s * 3);
}
// ---- 位置计算 ----
const legY = GROUND_Y - LEG_H;
const torsoY = legY - TORSO_H;
const armY = torsoY;
const headY = torsoY - HEAD_H;
const hatY = headY - 12;
const leftArmX = CX - TORSO_W / 2 - ARM_W;
const rightArmX = CX + TORSO_W / 2;
const leftLegX = CX - LEG_W;
const rightLegX = CX;
const torsoX = CX - TORSO_W / 2;
const headX = CX - HEAD_W / 2;
const hatX = CX - HAT_W / 2;
// ---- 1. 左臂 ----
drawBox(leftArmX, armY + lArmY, ARM_W, ARM_H, C.coat);
// ---- 2. 右臂 ----
drawBox(rightArmX, armY + rArmY, ARM_W, ARM_H, C.coat);
// ---- 3. 左腿 ----
drawBox(leftLegX, legY + lLegY, LEG_W, LEG_H, C.pants);
// 绑腿(左腿正面横条纹)
for (let i = 0; i < 5; i++) {
const wy = legY + lLegY + 32 + i * 5;
ctx.fillStyle = i % 2 === 0 ? C.wrap : shade(C.wrap, -0.15);
ctx.fillRect(leftLegX, wy, LEG_W, 3);
}
// 草鞋(左)
ctx.fillStyle = C.shoe;
ctx.fillRect(leftLegX - 2, legY + lLegY + LEG_H - 6, LEG_W + 4, 6);
ctx.fillStyle = shade(C.shoe, -0.15);
ctx.fillRect(leftLegX - 2, legY + lLegY + LEG_H - 3, LEG_W + 4, 3);
// ---- 4. 右腿 ----
drawBox(rightLegX, legY + rLegY, LEG_W, LEG_H, C.pants);
for (let i = 0; i < 5; i++) {
const wy = legY + rLegY + 32 + i * 5;
ctx.fillStyle = i % 2 === 0 ? C.wrap : shade(C.wrap, -0.15);
ctx.fillRect(rightLegX, wy, LEG_W, 3);
}
ctx.fillStyle = C.shoe;
ctx.fillRect(rightLegX - 2, legY + rLegY + LEG_H - 6, LEG_W + 4, 6);
ctx.fillStyle = shade(C.shoe, -0.15);
ctx.fillRect(rightLegX - 2, legY + rLegY + LEG_H - 3, LEG_W + 4, 3);
// ---- 5. 躯干 ----
drawBox(torsoX, torsoY, TORSO_W, TORSO_H, C.coat);
// 领章
ctx.fillStyle = C.collar;
ctx.fillRect(torsoX + 10, torsoY + 4, 12, 10);
ctx.fillRect(torsoX + TORSO_W - 22, torsoY + 4, 12, 10);
// 领章高光
ctx.fillStyle = shade(C.collar, 0.2);
ctx.fillRect(torsoX + 10, torsoY + 4, 12, 3);
ctx.fillRect(torsoX + TORSO_W - 22, torsoY + 4, 12, 3);
// 口袋
ctx.fillStyle = shade(C.coat, -0.08);
ctx.fillRect(torsoX + 8, torsoY + 22, 16, 14);
ctx.fillRect(torsoX + TORSO_W - 24, torsoY + 22, 16, 14);
// 口袋盖
ctx.fillStyle = shade(C.coat, -0.18);
ctx.fillRect(torsoX + 8, torsoY + 22, 16, 3);
ctx.fillRect(torsoX + TORSO_W - 24, torsoY + 22, 16, 3);
// 腰带
ctx.fillStyle = C.belt;
ctx.fillRect(torsoX, torsoY + TORSO_H - 12, TORSO_W, 10);
// 铜扣
ctx.fillStyle = C.buckle;
ctx.fillRect(torsoX + TORSO_W / 2 - 5, torsoY + TORSO_H - 11, 10, 8);
ctx.fillStyle = shade(C.buckle, 0.2);
ctx.fillRect(torsoX + TORSO_W / 2 - 5, torsoY + TORSO_H - 11, 10, 3);
// 躯干正面暗面(右侧)
ctx.fillStyle = shade(C.coat, -0.06);
ctx.fillRect(torsoX + TORSO_W - 4, torsoY, 4, TORSO_H);
// ---- 6. 头 ----
drawBox(headX, headY, HEAD_W, HEAD_H, C.skin);
// 眼睛
ctx.fillStyle = C.eye;
ctx.fillRect(headX + 14, headY + 10, 6, 6);
ctx.fillRect(headX + HEAD_W - 20, headY + 10, 6, 6);
// 眼睛高光
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillRect(headX + 14, headY + 10, 2, 2);
ctx.fillRect(headX + HEAD_W - 20, headY + 10, 2, 2);
// 嘴
ctx.fillStyle = C.mouth;
ctx.fillRect(headX + HEAD_W / 2 - 4, headY + 21, 8, 3);
// 下巴暗面
ctx.fillStyle = shade(C.skin, -0.08);
ctx.fillRect(headX + 4, headY + HEAD_H - 4, HEAD_W - 8, 4);
// ---- 7. 军帽 ----
drawBox(hatX, hatY, HAT_W, HAT_H, C.hat);
// 帽檐(正面下方深色带)
ctx.fillStyle = shade(C.hat, -0.28);
ctx.fillRect(hatX, hatY + HAT_H - 6, HAT_W, 6);
// 帽檐高光
ctx.fillStyle = shade(C.hat, -0.1);
ctx.fillRect(hatX, hatY + HAT_H - 6, HAT_W, 2);
// 五角星
drawStar(
hatX + HAT_W / 2,
hatY + HAT_H / 2 - 2,
9, 4, 5, -Math.PI / 2,
C.star
);
// 五角星高光
drawStar(
hatX + HAT_W / 2 - 1,
hatY + HAT_H / 2 - 3,
4, 1.8, 5, -Math.PI / 2,
'rgba(255,255,255,0.35)'
);
// ---- 8. 步枪 ----
if (pose === 'aim') {
// 持枪:斜跨在胸前
ctx.save();
ctx.translate(CX, torsoY + 30);
ctx.rotate(-0.35);
// 枪管
ctx.fillStyle = C.gun;
ctx.fillRect(-45, -4, 70, 6);
// 枪管高光
ctx.fillStyle = C.gunHi;
ctx.fillRect(-45, -4, 70, 2);
// 枪托
ctx.fillStyle = C.wood;
ctx.fillRect(20, -8, 24, 14);
ctx.fillStyle = C.woodHi;
ctx.fillRect(20, -8, 24, 3);
// 枪机
ctx.fillStyle = shade(C.gun, 0.1);
ctx.fillRect(5, -8, 8, 5);
ctx.restore();
// 双手前伸
ctx.fillStyle = C.skin;
ctx.fillRect(CX - 10, torsoY + 38, 12, 10);
ctx.fillRect(CX + 10, torsoY + 42, 12, 10);
} else {
// 站立/行走:步枪竖在右侧
const gunX = rightArmX + ARM_W + 2;
const gunY = armY + 10;
// 枪管
ctx.fillStyle = C.gun;
ctx.fillRect(gunX, gunY, 6, 70);
ctx.fillStyle = C.gunHi;
ctx.fillRect(gunX, gunY, 2, 70);
// 枪托
ctx.fillStyle = C.wood;
ctx.fillRect(gunX - 2, gunY + 60, 10, 22);
ctx.fillStyle = C.woodHi;
ctx.fillRect(gunX - 2, gunY + 60, 10, 4);
// 枪机
ctx.fillStyle = shade(C.gun, 0.12);
ctx.fillRect(gunX - 2, gunY + 35, 8, 5);
}
}
// ==================== 主循环 ====================
let frame = 0;
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.1)';
ctx.lineWidth = 1;
for (let x = 0; x <= W; x += 40) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
}
for (let y = 0; y <= H; y += 40) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
}
// 地面
ctx.fillStyle = '#1a1410';
ctx.fillRect(0, GROUND_Y, W, H - GROUND_Y);
ctx.fillStyle = 'rgba(120, 80, 60, 0.15)';
ctx.fillRect(0, GROUND_Y, W, 3);
// 地面阴影
ctx.fillStyle = 'rgba(0,0,0,0.35)';
ctx.beginPath();
ctx.ellipse(CX, GROUND_Y + 6, 80, 16, 0, 0, Math.PI * 2);
ctx.fill();
// 姿势轮播
const poseIndex = Math.floor(frame / 140) % 3;
const poses = ['stand', 'walk', 'aim'];
const pose = poses[poseIndex];
drawSoldier(pose, frame);
// 姿势名称
ctx.fillStyle = 'rgba(200, 150, 120, 0.25)';
ctx.font = '13px system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.fillText(pose.toUpperCase(), CX, H - 16);
}
loop();
})();
</script>
</body>
</html>Game Source: 红军战士 · 方块人
Creator: EpicCoder88
Libraries: none
Complexity: complex (390 lines, 11.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-epiccoder88-murvydtv" to link back to the original. Then publish at arcadelab.ai/publish.