🎮 Unity C++ Coroutines 实战:终于理解协程的价值
★★★★ 4星
作者在研究 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_yield 比 co_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