Files
nl-game/src/games/components/Down100Game.vue
李琦 cd1ab90013 初始化
需要注意的是饥荒不够完善:动作、手持工具、细节交互、系统逻辑
2026-08-15 07:24:34 +08:00

182 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
// 下100层不断下落踩稳平台顶部尖刺不能碰层数即分数
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { createLoop, Keys, palette, randInt } from '../engine'
const emit = defineEmits(['score', 'end'])
const canvas = ref(null)
const W = 480
const H = 560
let ctx
let keys
let loop
let player = { x: W / 2, y: 100, vy: 0, onPlat: null }
let platforms = [] // {x,y,w,type: normal|spike|move, dir}
let cameraY = 0 // 摄像机向下滚动
let floor = 0 // 已下的层数(分数)
let alive = false
let scrollSpeed = 40 // 画面自动上移速度
function spawnPlatform(y) {
const roll = Math.random()
const type = roll < 0.68 ? 'normal' : roll < 0.85 ? 'move' : 'spike'
platforms.push({
x: randInt(20, W - 120),
y,
w: randInt(80, 140),
type,
dir: Math.random() < 0.5 ? 1 : -1,
})
}
function update(dt) {
// 画面自动上滚(越来越快)
scrollSpeed = 40 + floor * 0.9
cameraY += scrollSpeed * dt
// 左右移动
const ms = 300
if (keys.has('ArrowLeft') || keys.has('a')) player.x -= ms * dt
if (keys.has('ArrowRight') || keys.has('d')) player.x += ms * dt
player.x = Math.max(14, Math.min(W - 14, player.x))
// 重力
player.vy += 1300 * dt
const prevY = player.y
player.y += player.vy * dt
// 平台碰撞(只在下落时检测顶面)
player.onPlat = null
for (const p of platforms) {
if (player.vy >= 0 && prevY <= p.y - 10 + 1 && player.y >= p.y - 10 && player.x > p.x - 10 && player.x < p.x + p.w + 10) {
if (p.type === 'spike') {
// 尖刺平台:踩上即死
die()
return
}
player.y = p.y - 10
player.vy = 0
player.onPlat = p
}
}
// 移动平台带着玩家走
platforms.forEach((p) => {
if (p.type === 'move') {
p.x += p.dir * 90 * dt
if (p.x < 10 || p.x + p.w > W - 10) p.dir *= -1
if (player.onPlat === p) player.x += p.dir * 90 * dt
}
})
// 计层:每通过一块平台的高度 +1 层
const newFloor = Math.floor((player.y - 160) / 90)
if (newFloor > floor) {
floor = newFloor
emit('score', floor)
}
// 生成更深的平台 / 清理头顶的
const deepest = Math.max(...platforms.map((p) => p.y))
if (deepest < cameraY + H + 200) spawnPlatform(deepest + 90)
platforms = platforms.filter((p) => p.y > cameraY - 60)
// 死亡判定:被顶出屏幕上沿(被卷进尖刺天花板)或掉出屏幕下沿
if (player.y < cameraY + 24 || player.y > cameraY + H + 40) {
die()
return
}
draw()
}
function die() {
alive = false
loop.stop()
draw()
emit('end', { score: floor })
}
function draw() {
const p = palette()
ctx.fillStyle = p.bg
ctx.fillRect(0, 0, W, H)
// 顶部尖刺天花板
ctx.fillStyle = p.primary
for (let x = 0; x < W; x += 24) {
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x + 12, 18)
ctx.lineTo(x + 24, 0)
ctx.fill()
}
// 平台
platforms.forEach((pl) => {
const sy = pl.y - cameraY
if (sy < -20 || sy > H + 20) return
if (pl.type === 'spike') {
ctx.fillStyle = p.primary
for (let x = pl.x; x < pl.x + pl.w - 10; x += 14) {
ctx.beginPath()
ctx.moveTo(x, sy)
ctx.lineTo(x + 7, sy - 12)
ctx.lineTo(x + 14, sy)
ctx.fill()
}
ctx.fillStyle = '#8b3a44'
ctx.fillRect(pl.x, sy, pl.w, 10)
} else {
ctx.fillStyle = pl.type === 'move' ? p.accent : p.primary2
ctx.beginPath()
ctx.roundRect(pl.x, sy, pl.w, 10, 5)
ctx.fill()
}
})
// 玩家
ctx.font = '26px serif'
ctx.textAlign = 'center'
ctx.textBaseline = 'bottom'
ctx.fillText('🧗', player.x, player.y - cameraY + 4)
// 层数
ctx.fillStyle = p.text
ctx.font = 'bold 18px sans-serif'
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
ctx.fillText(`${floor}`, 10, 26)
}
// ---- 对外接口 ----
function start() {
player = { x: W / 2, y: 120, vy: 0, onPlat: null }
platforms = [{ x: W / 2 - 70, y: 180, w: 140, type: 'normal', dir: 1 }]
for (let y = 270; y < H + 300; y += 90) spawnPlatform(y)
cameraY = 0
floor = 0
alive = true
emit('score', 0)
loop.start()
}
function stop() {
loop?.stop()
keys?.detach()
}
// 复活卡:回到屏幕中间的安全平台继续
function useProp(code) {
if (code === 'revive' && !alive) {
const safe = { x: W / 2 - 70, y: cameraY + H / 2, w: 140, type: 'normal', dir: 1 }
platforms.push(safe)
player = { x: W / 2, y: safe.y - 10, vy: 0, onPlat: safe }
alive = true
loop.start()
return true
}
return false
}
defineExpose({ start, stop, useProp })
onMounted(() => {
ctx = canvas.value.getContext('2d')
keys = new Keys()
keys.attach()
loop = createLoop(update)
draw()
})
onBeforeUnmount(stop)
</script>
<template>
<canvas ref="canvas" :width="W" :height="H"></canvas>
</template>