用Vue复现Chrome小恐龙游戏
2025.09.23 12:22浏览量:0简介:本文通过Vue3实现Chrome离线小恐龙游戏,详细讲解Canvas渲染、碰撞检测、键盘事件等核心功能,提供完整代码实现与性能优化方案。
Vue 实现 Chrome 小恐龙游戏:从原理到完整实现
Chrome 浏览器在断网时显示的隐藏小恐龙游戏,凭借其简单的操作和复古的像素风格成为经典。本文将通过 Vue 3 组合式 API 实现这一游戏,重点解析 Canvas 渲染、物理碰撞检测、键盘事件监听等核心功能,并提供完整的代码实现与性能优化方案。
一、项目初始化与基础架构
1.1 Vue3 项目搭建
使用 Vite 创建 Vue3 项目,选择 TypeScript 模板以获得类型提示支持:
npm create vite@latest vue-dino-game -- --template vue-ts
1.2 游戏组件结构
采用单文件组件结构,将游戏划分为核心组件:
src/
├── components/
│ ├── GameCanvas.vue # 主画布渲染
│ ├── Dino.vue # 恐龙角色
│ ├── Obstacle.vue # 障碍物(仙人掌/鸟)
│ └── GameController.vue # 游戏控制逻辑
└── composables/
└── useGame.ts # 游戏状态管理
1.3 Canvas 渲染基础
在 GameCanvas.vue
中初始化画布:
<template>
<canvas ref="canvasRef" :width="width" :height="height"></canvas>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const canvasRef = ref<HTMLCanvasElement>()
const width = 800
const height = 300
onMounted(() => {
const ctx = canvasRef.value!.getContext('2d')!
// 后续渲染逻辑
})
</script>
二、核心游戏逻辑实现
2.1 游戏状态管理
使用 Composition API 封装游戏状态:
// composables/useGame.ts
import { ref, reactive } from 'vue'
export function useGame() {
const gameState = reactive({
isRunning: false,
score: 0,
speed: 5,
gravity: 0.6
})
const dino = reactive({
x: 50,
y: 200,
width: 40,
height: 50,
velocityY: 0,
isJumping: false
})
const obstacles = ref<Array<{x: number, type: 'cactus'|'bird'}>>([])
return { gameState, dino, obstacles }
}
2.2 物理系统实现
重力模拟:
function applyGravity() {
if (dino.y < 200) { // 地面高度
dino.velocityY += gameState.gravity
dino.y += dino.velocityY
} else {
dino.y = 200
dino.velocityY = 0
dino.isJumping = false
}
}
跳跃控制:
function jump() {
if (!dino.isJumping) {
dino.velocityY = -12 // 初始跳跃速度
dino.isJumping = true
}
}
2.3 障碍物生成系统
采用定时器生成随机障碍物:
function spawnObstacle() {
const types = ['cactus', 'bird'] as const
const type = types[Math.floor(Math.random() * types.length)]
obstacles.value.push({
x: width,
type
})
}
// 每2秒生成一个障碍物
setInterval(spawnObstacle, 2000)
三、碰撞检测系统
3.1 矩形碰撞检测
实现基础的 AABB(轴对齐边界框)碰撞检测:
function checkCollision(dino: typeof dino, obstacle: {x: number, type: string}) {
const obstacleWidth = obstacle.type === 'cactus' ? 30 : 50
const obstacleHeight = obstacle.type === 'cactus' ? 50 : 30
return (
dino.x < obstacle.x + obstacleWidth &&
dino.x + dino.width > obstacle.x &&
dino.y < height - obstacleHeight && // 地面障碍物
dino.y + dino.height > height - obstacleHeight
)
}
3.2 碰撞响应
检测到碰撞时停止游戏:
function updateGame() {
obstacles.value.forEach(obs => {
obs.x -= gameState.speed
if (checkCollision(dino, obs)) {
gameState.isRunning = false
// 触发游戏结束逻辑
}
})
// 移除屏幕外障碍物
obstacles.value = obstacles.value.filter(obs => obs.x > -50)
}
四、完整渲染循环
4.1 游戏主循环
使用 requestAnimationFrame
实现流畅动画:
let animationId: number
function gameLoop() {
if (!gameState.isRunning) return
applyGravity()
updateGame()
render()
animationId = requestAnimationFrame(gameLoop)
}
function startGame() {
gameState.isRunning = true
gameState.score = 0
obstacles.value = []
gameLoop()
}
4.2 Canvas 渲染
实现完整的渲染逻辑:
function render() {
const ctx = canvasRef.value!.getContext('2d')!
// 清空画布
ctx.clearRect(0, 0, width, height)
// 绘制地面
ctx.fillStyle = '#777'
ctx.fillRect(0, height - 20, width, 20)
// 绘制恐龙
ctx.fillStyle = '#333'
ctx.fillRect(dino.x, dino.y, dino.width, dino.height)
// 绘制障碍物
obstacles.value.forEach(obs => {
ctx.fillStyle = obs.type === 'cactus' ? '#0a0' : '#a00'
if (obs.type === 'cactus') {
ctx.fillRect(obs.x, height - 50, 30, 50)
} else {
ctx.fillRect(obs.x, height - 70, 50, 30) // 飞行鸟更高
}
})
// 绘制分数
ctx.fillStyle = '#000'
ctx.font = '20px Arial'
ctx.fillText(`Score: ${gameState.score}`, 10, 30)
}
五、性能优化方案
5.1 对象池技术
预创建障碍物对象避免频繁内存分配:
const obstaclePool: Array<{x: number, type: string}> = []
for (let i = 0; i < 10; i++) {
obstaclePool.push({ x: -100, type: 'cactus' })
}
5.2 节流渲染
对分数更新等非关键操作进行节流:
let lastScoreUpdate = 0
function updateScore() {
const now = Date.now()
if (now - lastScoreUpdate > 100) { // 每100ms更新一次
gameState.score += 1
lastScoreUpdate = now
}
}
5.3 Web Workers 处理复杂计算
将碰撞检测等计算密集型任务移至 Web Worker:
// worker.ts
self.onmessage = function(e) {
const { dino, obstacles } = e.data
const collision = obstacles.some(obs => checkCollision(dino, obs))
self.postMessage(collision)
}
六、完整实现代码
6.1 主组件整合
<template>
<div class="game-container">
<GameCanvas ref="canvasComponent" />
<div class="controls">
<button @click="startGame">Start</button>
<button @click="jump" :disabled="!gameState.isRunning">Jump</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import GameCanvas from './components/GameCanvas.vue'
import { useGame } from './composables/useGame'
const canvasComponent = ref()
const { gameState, dino } = useGame()
function jump() {
// 通过ref调用子组件方法
canvasComponent.value?.jump()
}
function startGame() {
canvasComponent.value?.startGame()
}
</script>
6.2 完整游戏循环
在 GameCanvas.vue
中整合所有逻辑:
// 省略部分重复代码...
onMounted(() => {
const ctx = canvasRef.value!.getContext('2d')!
// 键盘事件监听
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') jump()
})
// 初始化游戏循环
return () => {
cancelAnimationFrame(animationId)
}
})
// 暴露方法给父组件
defineExpose({
jump,
startGame
})
七、扩展功能建议
难度递增系统:随分数提高增加游戏速度
function updateDifficulty() {
if (gameState.score % 500 === 0) {
gameState.speed += 0.5
}
}
音效系统:添加跳跃和碰撞音效
function playSound(type: 'jump'|'hit') {
const audio = new Audio(type === 'jump' ? '/jump.mp3' : '/hit.mp3')
audio.play()
}
响应式设计:适配不同屏幕尺寸
.game-container {
max-width: 100%;
aspect-ratio: 800/300;
margin: 0 auto;
}
八、总结与最佳实践
- 状态管理:使用 Vue 的响应式系统管理游戏状态,避免直接操作 DOM
- 性能监控:通过
performance.now()
测量帧率,确保 60fps - 模块化设计:将游戏逻辑拆分为独立模块,便于维护和测试
- TypeScript 优势:利用类型系统减少运行时错误
完整实现可参考 GitHub 仓库:vue-dino-game-demo,包含详细注释和部署配置。此实现不仅复现了 Chrome 小恐龙游戏的核心玩法,还通过 Vue 的响应式特性实现了更易维护的代码结构,适合作为学习 Vue3 和游戏开发的实践项目。
发表评论
登录后可评论,请前往 登录 或 注册