116行纯 JavaScript 的 Stick Champ 游戏
<html>
<body>
<canvas id="myCanvas" width="600" height="500" style='position:absolute; left:0;'>canvas>
<script>
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext("2d");
context.font = 'bold 30px sans-serif';
context.lineWidth = 4;
let image = new Image();
image.src = "sprite.png";
const maxStones = 6, size = 40;
let angle = -Math.PI / 2;
let x, y, frame, currentStone, mode, run, offset, stickLength, stones;
function reset() {
currentStone = 0;
x = 100;
y = 360;
frame = 0;
stones = [];
stickLength = 0;
offset = 0;
run = 0;
for (let i = 0; i < maxStones; i++) {
stones[i] = {
x: i * 300 + Math.floor(Math.random() * 80),
width: 50 + Math.floor(Math.random() * 50)
};
}
stones[0].x = 80;
mode = 'wait';
}
function animate() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillText('Distance remaining: ' + (maxStones - currentStone - 1), 250, 100);
stones.forEach((stone)=>{
context.fillRect(stone.x - offset, 398, stone.width, 600);
}
);
context.drawImage(image, Math.floor(frame) * size, 0, size, size, x + size / 2, y, size, size);
switch (mode) {
case 'pointerdown':
stickLength++;
break;
case 'stickFall':
angle = angle + Math.PI / 64;
if (angle >= 0)
mode = 'run';
break;
case 'run':
offset++;
run++;
frame = frame + .5;
if (frame == 20)
frame = 0;
if (stickLength == run) {
mode = 'wait';
angle = -Math.PI / 2;
stickLength = 0;
run = 0;
let gameOver = true;
stones.forEach((stone,index)=>{
if (offset + x + size > stone.x && offset + x < stone.x + stone.width - size) {
gameOver = false;
currentStone = Math.max(currentStone, index);
if (currentStone == maxStones - 1) {
mode = 'gameOver';
frame = 21;
}
}
}
);
if (gameOver) {
mode = 'gameOver';
frame = 20;
}
}
break;
case 'gameOver':
if (currentStone < maxStones - 1) {
y++;
context.fillText('Game over. Click to restart', 20, 60);
} else
context.fillText('You win! Click to restart', 20, 60);
}
let x2 = x + (stickLength - run) * Math.cos(angle);
let y2 = y + (stickLength - run) * Math.sin(angle);
context.beginPath();
context.moveTo(x + size - run, y + size);
context.lineTo(x2 + size, y2 + size);
context.stroke();
window.requestAnimationFrame(animate);
}
window.onpointerdown = function() {
switch (mode) {
case 'wait':
mode = 'pointerdown';
break;
case 'gameOver':
mode = 'wait';
reset();
}
};
window.onpointerup = function() {
if (mode == 'pointerdown')
mode = 'stickFall';
};
reset();
animate();
script>
body>
html>
代码步骤拆解
[1-8] 行设置了 HTML5 Canvas 和包含 spritesheet的 2d 上下文
[9-10] 不可见图像(运行序列的 20 帧,1 次跌倒,1 次庆祝)
[11-13] 声明游戏变量和常量:
[11] 最大石头数(=要覆盖的距离),精灵的大小(40x40 像素)
[12] 棒的初始角度 - 它直线上升
[13] x - x 冠军的坐标
y - y摇杆
框架顶部坐标- 当前动画帧
currentStone - 已经达到了多少石头
mode - 游戏模式(wait/pointerdown/stickFall/gameOver)
run - 当前运行
偏移的长度- 屏幕水平滚动偏移
stickLength - 棍子的当前扩展
- 石头对象数组
[15-32] 将所有变量重置为初始值
[24-29] 随机化石头位置和宽度
[34-96] 主游戏循环:
[35] 清除框架
[36] 在屏幕上显示剩余距离
[37-40] 绘制石头(即使它们不适合屏幕 - 为简单起见)
[42] 绘制精灵的当前动画帧,如果您需要详细信息关于drawImage 的工作原理,点击这里
[44-46] 如果指针(鼠标/触摸)被按下,增加棍子长度
[47-51] 如果棍子正在下降,增加角度。
[49-50]如果一直往下掉,把游戏模式改成“跑”
[52-80] 如果我们处于“运行”模式:
[53-54] 增加偏移量和运行长度
[55] 精灵帧每 2 个动画帧改变一次
[56-57] 如果我们到达终点在 spritesheet 中的动画序列中,如果运行的长度等于摇杆的末端,则返回开始[58-62],重置摇杆长度、角度、运行并切换到“等待”模式
[63-74] ] 检查我们是否落在石头上:
[63] 假设我们落在一个空的空间
[64-65] 比较每个石头的英雄坐标
[66] 如果英雄落在石头上,更正 [63] 中的悲观假设
[67] 计算当前的石头
[68-71] 如果到达最后一块石头,你就赢了!
[75-78] 如果冠军降落在空地上,切换模式到 gameOver 并
在“gameOver”模式中显示下落姿势[81-87]:
[82-84] 如果你输了,你就下落
[85-86] ] 如果你赢了,你就赢了。
[88-89] 计算摇杆末端的坐标
[90-93] 绘制摇杆
[94] 如果点击/触摸屏幕,则触发下一个动画帧[97-107]:
[98-101] 如果我们正在等待,如果我们处于“gameOver”模式,我们切换到“pointerdown”模式[102-105],如果释放指针并且我们处于“pointerdown”模式,游戏将重新启动[108-111],我们切换到“stickFall”模式
[112] 启动游戏
[113] 开始动画