logo

用Vue复现Chrome小恐龙游戏

作者:渣渣辉2025.09.23 12:22浏览量:0

简介:本文通过Vue3实现Chrome离线小恐龙游戏,详细讲解Canvas渲染、碰撞检测、键盘事件等核心功能,提供完整代码实现与性能优化方案。

Vue 实现 Chrome 小恐龙游戏:从原理到完整实现

Chrome 浏览器在断网时显示的隐藏小恐龙游戏,凭借其简单的操作和复古的像素风格成为经典。本文将通过 Vue 3 组合式 API 实现这一游戏,重点解析 Canvas 渲染、物理碰撞检测、键盘事件监听等核心功能,并提供完整的代码实现与性能优化方案。

一、项目初始化与基础架构

1.1 Vue3 项目搭建

使用 Vite 创建 Vue3 项目,选择 TypeScript 模板以获得类型提示支持:

  1. npm create vite@latest vue-dino-game -- --template vue-ts

1.2 游戏组件结构

采用单文件组件结构,将游戏划分为核心组件:

  1. src/
  2. ├── components/
  3. ├── GameCanvas.vue # 主画布渲染
  4. ├── Dino.vue # 恐龙角色
  5. ├── Obstacle.vue # 障碍物(仙人掌/鸟)
  6. └── GameController.vue # 游戏控制逻辑
  7. └── composables/
  8. └── useGame.ts # 游戏状态管理

1.3 Canvas 渲染基础

GameCanvas.vue 中初始化画布:

  1. <template>
  2. <canvas ref="canvasRef" :width="width" :height="height"></canvas>
  3. </template>
  4. <script setup lang="ts">
  5. import { ref, onMounted } from 'vue'
  6. const canvasRef = ref<HTMLCanvasElement>()
  7. const width = 800
  8. const height = 300
  9. onMounted(() => {
  10. const ctx = canvasRef.value!.getContext('2d')!
  11. // 后续渲染逻辑
  12. })
  13. </script>

二、核心游戏逻辑实现

2.1 游戏状态管理

使用 Composition API 封装游戏状态:

  1. // composables/useGame.ts
  2. import { ref, reactive } from 'vue'
  3. export function useGame() {
  4. const gameState = reactive({
  5. isRunning: false,
  6. score: 0,
  7. speed: 5,
  8. gravity: 0.6
  9. })
  10. const dino = reactive({
  11. x: 50,
  12. y: 200,
  13. width: 40,
  14. height: 50,
  15. velocityY: 0,
  16. isJumping: false
  17. })
  18. const obstacles = ref<Array<{x: number, type: 'cactus'|'bird'}>>([])
  19. return { gameState, dino, obstacles }
  20. }

2.2 物理系统实现

重力模拟

  1. function applyGravity() {
  2. if (dino.y < 200) { // 地面高度
  3. dino.velocityY += gameState.gravity
  4. dino.y += dino.velocityY
  5. } else {
  6. dino.y = 200
  7. dino.velocityY = 0
  8. dino.isJumping = false
  9. }
  10. }

跳跃控制

  1. function jump() {
  2. if (!dino.isJumping) {
  3. dino.velocityY = -12 // 初始跳跃速度
  4. dino.isJumping = true
  5. }
  6. }

2.3 障碍物生成系统

采用定时器生成随机障碍物:

  1. function spawnObstacle() {
  2. const types = ['cactus', 'bird'] as const
  3. const type = types[Math.floor(Math.random() * types.length)]
  4. obstacles.value.push({
  5. x: width,
  6. type
  7. })
  8. }
  9. // 每2秒生成一个障碍物
  10. setInterval(spawnObstacle, 2000)

三、碰撞检测系统

3.1 矩形碰撞检测

实现基础的 AABB(轴对齐边界框)碰撞检测:

  1. function checkCollision(dino: typeof dino, obstacle: {x: number, type: string}) {
  2. const obstacleWidth = obstacle.type === 'cactus' ? 30 : 50
  3. const obstacleHeight = obstacle.type === 'cactus' ? 50 : 30
  4. return (
  5. dino.x < obstacle.x + obstacleWidth &&
  6. dino.x + dino.width > obstacle.x &&
  7. dino.y < height - obstacleHeight && // 地面障碍物
  8. dino.y + dino.height > height - obstacleHeight
  9. )
  10. }

3.2 碰撞响应

检测到碰撞时停止游戏:

  1. function updateGame() {
  2. obstacles.value.forEach(obs => {
  3. obs.x -= gameState.speed
  4. if (checkCollision(dino, obs)) {
  5. gameState.isRunning = false
  6. // 触发游戏结束逻辑
  7. }
  8. })
  9. // 移除屏幕外障碍物
  10. obstacles.value = obstacles.value.filter(obs => obs.x > -50)
  11. }

四、完整渲染循环

4.1 游戏主循环

使用 requestAnimationFrame 实现流畅动画:

  1. let animationId: number
  2. function gameLoop() {
  3. if (!gameState.isRunning) return
  4. applyGravity()
  5. updateGame()
  6. render()
  7. animationId = requestAnimationFrame(gameLoop)
  8. }
  9. function startGame() {
  10. gameState.isRunning = true
  11. gameState.score = 0
  12. obstacles.value = []
  13. gameLoop()
  14. }

4.2 Canvas 渲染

实现完整的渲染逻辑:

  1. function render() {
  2. const ctx = canvasRef.value!.getContext('2d')!
  3. // 清空画布
  4. ctx.clearRect(0, 0, width, height)
  5. // 绘制地面
  6. ctx.fillStyle = '#777'
  7. ctx.fillRect(0, height - 20, width, 20)
  8. // 绘制恐龙
  9. ctx.fillStyle = '#333'
  10. ctx.fillRect(dino.x, dino.y, dino.width, dino.height)
  11. // 绘制障碍物
  12. obstacles.value.forEach(obs => {
  13. ctx.fillStyle = obs.type === 'cactus' ? '#0a0' : '#a00'
  14. if (obs.type === 'cactus') {
  15. ctx.fillRect(obs.x, height - 50, 30, 50)
  16. } else {
  17. ctx.fillRect(obs.x, height - 70, 50, 30) // 飞行鸟更高
  18. }
  19. })
  20. // 绘制分数
  21. ctx.fillStyle = '#000'
  22. ctx.font = '20px Arial'
  23. ctx.fillText(`Score: ${gameState.score}`, 10, 30)
  24. }

五、性能优化方案

5.1 对象池技术

预创建障碍物对象避免频繁内存分配:

  1. const obstaclePool: Array<{x: number, type: string}> = []
  2. for (let i = 0; i < 10; i++) {
  3. obstaclePool.push({ x: -100, type: 'cactus' })
  4. }

5.2 节流渲染

对分数更新等非关键操作进行节流:

  1. let lastScoreUpdate = 0
  2. function updateScore() {
  3. const now = Date.now()
  4. if (now - lastScoreUpdate > 100) { // 每100ms更新一次
  5. gameState.score += 1
  6. lastScoreUpdate = now
  7. }
  8. }

5.3 Web Workers 处理复杂计算

将碰撞检测等计算密集型任务移至 Web Worker:

  1. // worker.ts
  2. self.onmessage = function(e) {
  3. const { dino, obstacles } = e.data
  4. const collision = obstacles.some(obs => checkCollision(dino, obs))
  5. self.postMessage(collision)
  6. }

六、完整实现代码

6.1 主组件整合

  1. <template>
  2. <div class="game-container">
  3. <GameCanvas ref="canvasComponent" />
  4. <div class="controls">
  5. <button @click="startGame">Start</button>
  6. <button @click="jump" :disabled="!gameState.isRunning">Jump</button>
  7. </div>
  8. </div>
  9. </template>
  10. <script setup lang="ts">
  11. import { ref } from 'vue'
  12. import GameCanvas from './components/GameCanvas.vue'
  13. import { useGame } from './composables/useGame'
  14. const canvasComponent = ref()
  15. const { gameState, dino } = useGame()
  16. function jump() {
  17. // 通过ref调用子组件方法
  18. canvasComponent.value?.jump()
  19. }
  20. function startGame() {
  21. canvasComponent.value?.startGame()
  22. }
  23. </script>

6.2 完整游戏循环

GameCanvas.vue 中整合所有逻辑:

  1. // 省略部分重复代码...
  2. onMounted(() => {
  3. const ctx = canvasRef.value!.getContext('2d')!
  4. // 键盘事件监听
  5. window.addEventListener('keydown', (e) => {
  6. if (e.code === 'Space') jump()
  7. })
  8. // 初始化游戏循环
  9. return () => {
  10. cancelAnimationFrame(animationId)
  11. }
  12. })
  13. // 暴露方法给父组件
  14. defineExpose({
  15. jump,
  16. startGame
  17. })

七、扩展功能建议

  1. 难度递增系统:随分数提高增加游戏速度

    1. function updateDifficulty() {
    2. if (gameState.score % 500 === 0) {
    3. gameState.speed += 0.5
    4. }
    5. }
  2. 音效系统:添加跳跃和碰撞音效

    1. function playSound(type: 'jump'|'hit') {
    2. const audio = new Audio(type === 'jump' ? '/jump.mp3' : '/hit.mp3')
    3. audio.play()
    4. }
  3. 响应式设计:适配不同屏幕尺寸

    1. .game-container {
    2. max-width: 100%;
    3. aspect-ratio: 800/300;
    4. margin: 0 auto;
    5. }

八、总结与最佳实践

  1. 状态管理:使用 Vue 的响应式系统管理游戏状态,避免直接操作 DOM
  2. 性能监控:通过 performance.now() 测量帧率,确保 60fps
  3. 模块化设计:将游戏逻辑拆分为独立模块,便于维护和测试
  4. TypeScript 优势:利用类型系统减少运行时错误

完整实现可参考 GitHub 仓库:vue-dino-game-demo,包含详细注释和部署配置。此实现不仅复现了 Chrome 小恐龙游戏的核心玩法,还通过 Vue 的响应式特性实现了更易维护的代码结构,适合作为学习 Vue3 和游戏开发的实践项目。

相关文章推荐

发表评论