🎮 Unity C++ Coroutines 实战:终于理解协程的价值

4星
来源: mropert.github.io | 作者: Mathieu Ropert | 发布日期: 2026-03-20

作者在研究 Unity 如何使用协程后,终于理解了 C++ 协程的实际价值。本文探讨了协程在游戏开发中的具体应用场景,以及如何用 C++23 实现类似 Unity 风格的协程运行器。

🎯 核心洞察

为什么协程?

作者之前看过很多关于协程的演讲,但在异步 IO 之外找不到实际用例。直到研究了 Unity 中协程的使用方式——用于生成特效和其他临时行为。

协程 vs 状态机

将序列化的游戏效果写成状态机会变得非常丑陋:

class TimeWarp
{
  enum class State { Jump, StepRight, HandsOnHips, DoAgain };
  State _state = State::Jump;
  int _i = 0;
  Transform* _transform;
  
  bool operator()() {
    switch (_state) {
      case State::Jump:
        _transform->position.x -= 1.f;
        _state = State::StepRight;
        break;
      // ... 更多状态和转换
    }
    return false;
  }
}

用协程实现则简洁得多:

std::generator<std::monostate> TimeWarp(GameObject& obj)
{
  // Jump to the left
  obj.transform.position.x -= 1.f;
  co_yield {};
  
  // Step to the right
  for (int i = 0; i < 4; ++i) {
    obj.transform.position.x += 0.2f;
    co_yield {};
  }
  // ... 更多效果
}

C++23 的实现

使用 std::generator 可以轻松实现 Unity 风格的协程。这和 Unity 多年前的做法是相同的"hack",原因也一样——因为使用 co_yieldco_await 容易得多。

为什么 co_yield 比 co_await 容易?

  • co_yield: 暂停并返回给调用者,决定何时恢复的权力在调用者手中
  • co_await: 需要回答很多问题——我们等待什么?如何通知就绪?用什么执行队列?

Unity 风格的协程运行器

作者用不到一小时实现了一个简单的协程管理器:

class effects_manager
{
public:
  void add(std::generator<std::monostate> effect) { ... }
  void run() { 
    // 逐帧执行协程,移除完成的
  }
};

💡 关键要点

  • 协程最适合的场景:包装有状态的操作序列
  • 将难以阅读的状态机变成简单的函数
  • C++26 的 execution 规范可能会改善 co_await 的使用体验
  • 如果你的项目已经使用 boost::asio,已经有了协程支持

🏷️ 标签

C++ Coroutines Game Development Unity State Machine C++23