使用C++协程实现游戏敌人脚本:消除co_await重复编写
解决C++协程行为脚本中重复co_await的方案
不用宏的话,最优的实现思路是将行为命令(command_*())设计为协程promise类型的成员函数,让这些函数内部自动处理协程挂起逻辑,无需在脚本中显式编写co_await。核心是利用promise可以访问当前协程句柄的特性,在命令函数内部完成awaiter的创建、挂起判断和协程暂停操作。
具体实现步骤
1. 定义基础awaiter类型
先实现对应行为逻辑的awaiter,这里以等待时长、移动目标为例:
#include <coroutine> #include <chrono> #include <vector> #include <cstdio> #include <thread> // 全局模拟游戏的协程恢复队列 inline std::vector<std::pair<std::chrono::steady_clock::time_point, std::coroutine_handle<>>> g_resume_queue; // 游戏帧更新:处理到点需要恢复的协程 void game_update() { auto now = std::chrono::steady_clock::now(); for (auto it = g_resume_queue.begin(); it != g_resume_queue.end();) { if (it->first <= now) { it->second.resume(); it = g_resume_queue.erase(it); } else { ++it; } } } // 等待时长的awaiter struct WaitAwaiter { float duration; std::chrono::steady_clock::time_point resume_time; bool await_ready() const { return duration <= 0.f; } void await_suspend(std::coroutine_handle<> handle) { g_resume_queue.emplace_back(resume_time, handle); } void await_resume() {} WaitAwaiter(float sec) : duration(sec), resume_time(std::chrono::steady_clock::now() + std::chrono::duration<float>(sec)) {} }; // 移动目标的awaiter(简化为等待1秒模拟移动耗时) struct MoveAwaiter { float target_x, target_y; bool await_ready() const { return false; } void await_suspend(std::coroutine_handle<> handle) { // 实际项目中这里会注册移动逻辑到游戏更新系统,完成后恢复协程 g_resume_queue.emplace_back(std::chrono::steady_clock::now() + std::chrono::seconds(1), handle); } void await_resume() { printf("已移动到目标位置(%.1f, %.1f)\n", target_x, target_y); } MoveAwaiter(float x, float y) : target_x(x), target_y(y) {} };
2. 设计协程返回类型与Promise
将行为命令作为Promise的成员函数,内部封装挂起逻辑:
// 协程返回类型:管理敌人行为脚本的生命周期 struct EnemyBehaviorScript { struct promise_type; using Handle = std::coroutine_handle<promise_type>; Handle coro_handle; EnemyBehaviorScript(Handle h) : coro_handle(h) {} ~EnemyBehaviorScript() { if (coro_handle) coro_handle.destroy(); } // 禁止拷贝,支持移动 EnemyBehaviorScript(const EnemyBehaviorScript&) = delete; EnemyBehaviorScript& operator=(const EnemyBehaviorScript&) = delete; EnemyBehaviorScript(EnemyBehaviorScript&& other) : coro_handle(other.coro_handle) { other.coro_handle = nullptr; } EnemyBehaviorScript& operator=(EnemyBehaviorScript&& other) { if (this != &other) { if (coro_handle) coro_handle.destroy(); coro_handle = other.coro_handle; other.coro_handle = nullptr; } return *this; } struct promise_type { // 获取协程返回对象 EnemyBehaviorScript get_return_object() { return EnemyBehaviorScript(Handle::from_promise(*this)); } // 初始不挂起,直接执行协程逻辑 std::suspend_never initial_suspend() { return {}; } // 结束不挂起,直接销毁协程 std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } // 获取当前promise引用,方便协程内部调用命令 promise_type& self() { return *this; } // 行为命令:等待指定时间 void command_wait(float seconds) { WaitAwaiter awaiter{seconds}; if (!awaiter.await_ready()) { auto handle = Handle::from_promise(*this); awaiter.await_suspend(handle); } } // 行为命令:移动到目标位置 void command_move_to(float x, float y) { MoveAwaiter awaiter{x, y}; auto handle = Handle::from_promise(*this); awaiter.await_suspend(handle); } }; };
3. 编写简洁的行为脚本
在协程内部直接调用命令函数,无需co_await:
EnemyBehaviorScript enemy_ai() { // 获取当前promise的引用,用于调用命令 auto& ai = EnemyBehaviorScript::Handle::from_promise(*this).promise(); ai.command_move_to(10.0f, 0.0f); ai.command_wait(2.0f); ai.command_move_to(0.0f, 0.0f); ai.command_wait(1.0f); printf("敌人回到初始位置\n"); }
4. 模拟游戏运行
int main() { auto script = enemy_ai(); // 模拟20帧游戏更新,每帧间隔0.5秒 for (int i = 0; i < 20; ++i) { game_update(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } return 0; }
方案优势
- 完全消除重复的
co_await语句,脚本逻辑更简洁直观 - 挂起逻辑封装在Promise内部,命令函数与协程状态解耦
- 类型安全,相比宏定义更易维护、调试和扩展
内容的提问来源于stack exchange,提问作者nj16
相关产品推荐
相关产品推荐

