Appearance
砖块跑酷
玩法
方向键 上
跳跃, 按住不放跳的更高;
方向键 左 右
可以左右移动方块,在空中和地上均可以移动;
有点难
感觉挺难的
算法
为了提高性能,此游戏使用 canvas
渲染;
障碍物生产
随机生成一定间距、高度、粗细的障碍物,同时在障碍物离开屏幕后移除,防止内存溢出;
ts
const createBox = () => {
const last = roadBlocks.length ? roadBlocks[roadBlocks.length - 1] : null;
if (last == null) {
const box = new Box(getRandomInteger(20, 50), getRandomInteger(20, 200));
box.posX = width;
box.h = 0;
roadBlocks.push(box);
} else if (last.posX < width + 100) {
const box = new Box(getRandomInteger(20, 50), getRandomInteger(20, 200));
box.posX = last.posX + last.width + getRandomInteger(0, 300);
box.h = 0;
roadBlocks.push(box);
}
}
方块处理
使用重力加速度公式模拟重力感,每次按 上
给一个 负向
速度 v0
, 并将 S0
设置为当前高度即可;
ts
const drawPlayer = () => {
const t = frameTime - t0;
const tc = frameTime - playerBox.createTime;
playerBox.posX += runSpeed * tc;
playerBox.posX = Math.max(0, playerBox.posX);
playerBox.posX = Math.min(width - playerSize, playerBox.posX);
playerBox.createTime = frameTime;
playerBox.h = g * t * t / 2 + v0 * t + s0;
playerBox.h = Math.max(0, playerBox.h);
ctx.fillStyle = "#1ebabe";
ctx.fillRect(playerBox.posX, height - playerBox.h - playerSize - horizonH, playerSize, playerSize);
}
另外还有按键长安跳的较高的识别;
ts
const keydownJump = () => {
if (playerBox.h === 0) spaceDownTime = -1;
if (playerBox.h !== 0 || spaceDownTime > 0) return;
spaceDownTime = new Date().getTime();
upPlayer();
}
const keyupJump = () => {
spaceDownTime = -1;
}
源码
vue
<template>
<div class="parkour flex-column flex-center-all">
<div class="pos-r" :style="{ width: width + 'px', height: height + 'px' }">
<canvas ref="canvasRef" :width="width" :height="height" style="background-color: #f5f5f5" />
<start-over-mask :status="status" @on-click-start="clickStart"> </start-over-mask>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onBeforeUnmount, onMounted, ref } from "vue";
import StartOverMask from "../common/start-over-mask.vue";
import { getRandomInteger } from "../../utils/number.util";
class Box {
constructor(w, h) {
this.width = w;
this.height = h;
this.h = 0;
this.posX = 0;
this.createTime = new Date().getTime();
}
width: number;
height: number;
createTime: number;
posX: number;
h: number;
}
export default defineComponent({
name: "parkour",
components: { StartOverMask },
props: {},
setup(props: any) {
const canvasRef: any = ref("");
const width = 800,
height = 400;
onMounted(() => {
window.addEventListener("keydown", keydown);
window.addEventListener("keyup", keyup);
ctx = canvasRef.value.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, height - horizonH, width, horizonH);
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", keydown);
window.removeEventListener("keyup", keyup);
});
let runtimeTimeout = null,
status = ref(1);
let ctx = null,
spaceDownTime = 0,
rightDown = false,
leftDown = false;
let g = -0.005,
upV = 0.7,
t0 = 0,
v0 = 0,
s0 = 0;
let defPlayerX = 100,
horizonH = 10,
playerSize = 30,
roadBlocks: Box[] = [];
let defSpeed = 0.2,
speed = 0.1,
defRunSpeed = 0.5,
runSpeed = 0,
frameTime = 0,
playerBox: Box = new Box(playerSize, playerSize);
const runtime = () => {
clearTimeout(runtimeTimeout);
if (status.value !== 2) return;
frameTime = new Date().getTime();
clearCanvas();
drawPlayer();
upPlayer();
createBox();
renderRoadBlocks();
runtimeTimeout = setTimeout(runtime, 1);
};
const clearCanvas = () => {
ctx.clearRect(0, 0, width, height - horizonH);
};
const drawPlayer = () => {
const t = frameTime - t0;
const tc = frameTime - playerBox.createTime;
playerBox.posX += runSpeed * tc;
playerBox.posX = Math.max(0, playerBox.posX);
playerBox.posX = Math.min(width - playerSize, playerBox.posX);
playerBox.createTime = frameTime;
playerBox.h = (g * t * t) / 2 + v0 * t + s0;
playerBox.h = Math.max(0, playerBox.h);
ctx.fillStyle = "#1ebabe";
ctx.fillRect(
playerBox.posX,
height - playerBox.h - playerSize - horizonH,
playerSize,
playerSize
);
};
const getPosByBox = box => {
return height - box.h - box.height - horizonH;
};
const createBox = () => {
const last = roadBlocks.length ? roadBlocks[roadBlocks.length - 1] : null;
if (last == null) {
const box = new Box(getRandomInteger(20, 50), getRandomInteger(20, 200));
box.posX = width;
box.h = 0;
roadBlocks.push(box);
} else if (last.posX < width + 100) {
const box = new Box(getRandomInteger(20, 50), getRandomInteger(20, 200));
box.posX = last.posX + last.width + getRandomInteger(0, 300);
box.h = 0;
roadBlocks.push(box);
}
};
const renderRoadBlocks = () => {
const filter = [];
let crash = false;
for (const box of roadBlocks) {
if (box.posX < -500) continue;
ctx.fillStyle = "black";
const tc = frameTime - box.createTime;
box.posX = box.posX - speed * tc;
box.createTime = frameTime;
ctx.fillRect(box.posX, getPosByBox(box), box.width, box.height);
filter.push(box);
if (!crash) crash = checkCrash(playerBox, box);
}
if (crash) return gameOver();
roadBlocks = filter;
};
const gameOver = () => {
status.value = 3;
};
const checkCrash = (b1: Box, b2: Box) => {
const lt1 = [b1.posX, b1.h + b1.height],
rt1 = [b1.posX + b1.width, b1.h + b1.height],
lb1 = [b1.posX, b1.h],
rb1 = [b1.posX + b1.width, b1.h];
const lt2 = [b2.posX, b2.h + b2.height],
rt2 = [b2.posX + b2.width, b2.h + b2.height],
lb2 = [b2.posX, b2.h],
rb2 = [b2.posX + b2.width, b2.h];
if (lb1[1] >= lt2[1]) return false;
if (lb1[0] >= rb2[0]) return false;
if (lt1[1] <= rb2[1]) return false;
if (rb1[0] <= lb2[0]) return false;
return true;
};
const upPlayer = () => {
if (spaceDownTime <= 0 || new Date().getTime() - spaceDownTime > 400) return;
v0 = upV;
t0 = new Date().getTime();
s0 = playerBox.h;
};
const keydown = e => {
e.preventDefault();
if ([32, 38, 87].includes(e.keyCode)) return keydownJump();
if ([39, 68].includes(e.keyCode)) return keydownRight();
if ([65, 37].includes(e.keyCode)) return keydownLeft();
};
const keyup = e => {
e.preventDefault();
if ([32, 38, 87].includes(e.keyCode)) return keyupJump();
if ([39, 68].includes(e.keyCode)) return keyupRight();
if ([65, 37].includes(e.keyCode)) return keyupLeft();
};
const keydownRight = () => {
runSpeed = Math.abs(defRunSpeed);
rightDown = true;
};
const keyupRight = () => {
runSpeed = 0;
rightDown = false;
};
const keydownLeft = () => {
runSpeed = -Math.abs(defRunSpeed);
leftDown = true;
};
const keyupLeft = () => {
runSpeed = 0;
leftDown = false;
};
const keydownJump = () => {
if (playerBox.h === 0) spaceDownTime = -1;
if (playerBox.h !== 0 || spaceDownTime > 0) return;
spaceDownTime = new Date().getTime();
upPlayer();
};
const keyupJump = () => {
spaceDownTime = -1;
};
return {
canvasRef,
width,
height,
status,
clickStart: () => {
status.value = [0, 2, 3, 2][status.value];
if (status.value === 2) {
playerBox = new Box(playerSize, playerSize);
playerBox.posX = defPlayerX;
playerBox.h = 0;
runSpeed = 0;
speed = defSpeed;
roadBlocks = [];
runtime();
}
},
};
},
});
</script>
<style lang="less" scoped>
.parkour {
}
</style>