Skip to content

04 前端视角对比:系统化知识映射

预计阅读 25 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0)· 微信开发者工具:稳定版 1.06+

本章将系统性地建立前端开发与游戏开发之间的知识映射,帮助你用已有的经验快速理解游戏开发的核心差异。


4.1 事件循环 vs 游戏循环

这是前端与游戏之间最根本的架构差异。

前端事件循环(浏览器)

typescript
// 浏览器事件循环是「被动响应」
button.addEventListener('click', () => handleClick())
input.addEventListener('input', () => handleInput())
// 不触发事件 = 不执行代码 = 不消耗资源

事件循环是事件驱动的——没有事件时,JS 引擎处于空闲状态。

游戏循环(主动驱动)

typescript
// 游戏循环是「主动执行」
function gameLoop() {
  processInput() // 即使没有输入也执行
  updatePhysics() // 物理模拟持续计算
  updateAI() // AI 行为持续决策
  render() // 每帧重绘整个画面
  requestAnimationFrame(gameLoop)
}

游戏循环是帧驱动的——即使没有任何玩家输入,游戏世界也在持续运转。

关键洞察

前端的「响应数据变化」思维在游戏中被翻转了——游戏中你要问的不是「发生了什么变化」,而是「这一帧所有物体应该在哪、应该显示什么」。

4.2 DOM vs Canvas

特性DOMCanvas
元素系统每个元素是独立 DOM 节点,可通过 CSS 控制像素级操作,无独立元素概念
事件系统每个元素可独立绑定事件需手动计算点击坐标判断命中
重绘策略浏览器自动优化,只重绘变化区域每帧全量重绘(或手动脏矩形优化)
性能特点DOM 节点多时性能下降像素多时性能取决于渲染操作量
布局引擎CSS 自动布局(Flex/Grid)完全手动计算位置

4.3 组件系统 vs 游戏对象

typescript
// 前端组件:声明式,框架管理生命周期
const Counter = defineComponent({
  setup() {
    const count = ref(0)
    onMounted(() => {
      /* 初始化 */
    })
    onUnmounted(() => {
      /* 清理 */
    })
    return { count }
  },
})

// 游戏对象:命令式,手动管理生命周期
class GameEntity {
  private active: boolean = true

  start() {
    /* 初始化 */
  }
  update(dt: number) {
    /* 每帧更新 */
  }
  onDestroy() {
    /* 清理 */
  }

  setActive(active: boolean) {
    this.active = active
  }
}
生命周期对比
Vue 组件游戏对象说明
setup()constructor()初始化
onMounted()start()挂载到场景
响应式更新update(dt)每帧执行
onUnmounted()onDestroy()移除清理
v-ifsetActive(bool)激活/停用

4.4 CSS Transform vs 矩阵变换

typescript
// CSS(声明式)
// transform: translate(100px, 50px) rotate(45deg) scale(1.5);

// Canvas 2D(等价操作)
ctx.translate(100, 50)
ctx.rotate((45 * Math.PI) / 180)
ctx.scale(1.5, 1.5)
ctx.drawImage(sprite, 0, 0)

// 或使用变换矩阵(底层等价)
// | a  c  e |   | scaleX skewX  translateX |
// | b  d  f | = | skewY  scaleY translateY |
// | 0  0  1 |   | 0      0      1          |
ctx.setTransform(1.5, 0, 0, 1.5, 100, 50)

4.5 路由 vs 场景管理

typescript
// 前端路由
const routes = [
  { path: '/', component: Home },
  { path: '/game', component: Game },
]

// 游戏场景切换
class SceneManager {
  private current: Scene | null = null

  async loadScene(name: string) {
    await this.current?.unload()
    this.current = await SceneLoader.load(name)
    this.current.start()
  }
}

4.6 核心差异总结

维度前端开发游戏开发
驱动方式事件驱动,空闲时无计算帧驱动,每帧持续计算
渲染控制框架/浏览器控制完全手动控制
性能模型首屏加载 + 交互响应每帧 16.67ms 硬时限
状态持久化组件树自动保持每帧重建画面
调试方式静态检查 + 运行时断点帧级时间线 + GPU 分析
优化焦点包体体积、首屏速度帧率稳定、内存控制
转型寄语

这些差异不需要立即全部消化。后续每章都会在实际场景中反复回访这些概念。带着「前端是怎么做的」这个问题去学习游戏开发,你会发现很多概念其实比你想象的要熟悉。

每个人都是站在自己已有的知识山峰上,去攀登下一座山。


📝 课后练习

练习 1:事件循环 vs 游戏循环

题目: Web 前端用 addEventListener 响应离散用户事件;游戏中每一帧都要检测输入状态。写一个函数将离散触摸事件转换为"当前帧是否按下"的布尔状态。

参考答案:

typescript
let isPressed = false
wx.onTouchStart(() => {
  isPressed = true
})
wx.onTouchEnd(() => {
  isPressed = false
})
function update() {
  if (isPressed) {
    /* 处理 */
  }
}
练习 2:DOM 思维到 Canvas 思维

题目: 写一段 Canvas 2D 代码实现一个"点击按钮切换页面"的功能:绘制按钮、检测点击坐标、切换绘制内容。

参考答案:

typescript
const btn = { x: 100, y: 200, w: 200, h: 50, text: '开始游戏' }
let page = 'menu'

// 微信小游戏中使用 wx.onTouchStart 检测触摸,而非 DOM addEventListener
// Canvas 本身没有点击事件,需要手动转换坐标进行命中检测
wx.onTouchStart((e) => {
  const touch = e.touches[0] // 获取第一个触摸点
  const x = touch.clientX // 触摸点 x 坐标
  const y = touch.clientY // 触摸点 y 坐标
  // 矩形命中检测:判断触摸点是否在按钮区域内
  if (x > btn.x && x < btn.x + btn.w && y > btn.y && y < btn.y + btn.h) {
    page = 'game'
  }
})
练习 3:CSS Transform 到 Canvas 矩阵

题目:transform: translate(100px, 50px) rotate(45deg) scale(2, 2) 翻译为 Canvas 2D API。

参考答案:

javascript
ctx.save()
ctx.translate(100, 50)
ctx.rotate((45 * Math.PI) / 180)
ctx.scale(2, 2)
// ...绘制
ctx.restore()

📚 相关阅读

用心学习,持续实践