游戏循环

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