游戏循环

游戏的”心脏”——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 的并行能力