游戏循环
游戏的”心脏”——process_input → update → render → present, 固定/可变时间步长, vsync。
概念
游戏循环 (Game Loop) 是游戏引擎的核心驱动机制。每个循环迭代产生一帧,典型目标是 60 FPS (16.67ms/帧)。与事件驱动的 GUI 程序不同,游戏循环是主动轮询模式——即使没有输入或事件,游戏也会持续更新和渲染。这是游戏与常规应用程序的根本区别。
核心组件
| 组件 | 职责 | 频率/目标 |
|---|---|---|
| 输入处理 | 读取键盘、鼠标、手柄、触摸事件 | 每帧 |
| 物理更新 | 位置、速度、碰撞计算 | 固定步长 (16ms/50Hz) |
| AI 更新 | 寻路、行为树、状态机决策 | 可变, 通常低频 |
| 渲染 | 提交绘制指令到 GPU | 每帧 (60Hz) |
| 帧呈现 | swap buffers, vsync 等待 | 屏幕刷新率同步 |
基本游戏循环
FUNCTION game_loop():
WHILE game_running:
frame_start = clock.now()
process_input() # 收集所有输入事件
delta_time = frame_start - last_frame_time
update(delta_time) # 更新游戏状态 (物理、AI、逻辑)
render() # 绘制当前帧
present_frame() # swapchains 交换
frame_end = clock.now()
frame_cost = frame_end - frame_start
IF frame_cost < target_frame_time:
SLEEP(target_frame_time - frame_cost)
last_frame_time = frame_start
固定时间步长 vs 可变时间步长
可变时间步长:
F=ma 每帧用实际 delta_time 积分
优点: 代码简单
缺点: 不同帧率的物理行为不一致, 浮点误差累积
固定时间步长 (deWiTTER):
accumulator = 0.0
fixed_dt = 1.0 / 60.0 # 假设物理以 60Hz 运行
WHILE game_running:
delta_time = clock.update()
accumulator += delta_time
process_input()
WHILE accumulator >= fixed_dt:
physics_update(fixed_dt) # 固定步长物理
accumulator -= fixed_dt
// 插值: 渲染位置 = 上一物理帧位置 + (当前位置 - 上一位置) * (accumulator / fixed_dt)
alpha = accumulator / fixed_dt
render_state = lerp(previous_state, current_state, alpha)
render(render_state)
present_frame()
Delta Time 与帧率关系
目标 60 FPS → frame_time = 16.67ms
帧率波动问题:
frame_time = 16ms: delta_time = 0.016, 速度正常
frame_time = 33ms: delta_time = 0.033, 速度骤变
如果不用 delta_time 修正:
position += velocity # 每帧固定移动
→ 60 FPS 时移动正常, 30 FPS 时移动速度减半
使用 delta_time 修正:
position += velocity * delta_time
→ 无论帧率高低, 每秒移动距离一致
vsync (垂直同步):
开启: GPU 在屏幕刷新之间交换缓冲区 → 无撕裂, 但延迟增加
关闭: 立即交换 → 低延迟, 但可能出现撕裂
渲染与更新分离
现代游戏引擎通常使用多线程分离:
主线程: process_input → update_world → begin_render
渲染线程: submit_draw_calls → wait_gpu → present
这样可以在 GPU 渲染上一帧的同时, 主线程开始下一帧的更新
有效利用 CPU 和 GPU 的并行能力