diff --git a/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp b/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp index a68a21d1e..bb1171abf 100644 --- a/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp +++ b/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp @@ -200,8 +200,8 @@ namespace luastg } dispatchOnAfterBatchDestroy(); } - void GameObjectPool::updateNext() { - tracy_zone_scoped_with_name("LOBJMGR.AfterFrame(New)"); + void GameObjectPool::updateNextOld() { + tracy_zone_scoped_with_name("LOBJMGR.AfterFrame(V2)"); dispatchOnBeforeBatchDestroy(); auto const super_pause_time = UpdateSuperPause(); // 更新超级暂停 for (auto p = m_update_list.first(); p != nullptr;) { @@ -218,6 +218,18 @@ namespace luastg } dispatchOnAfterBatchDestroy(); } + void GameObjectPool::updateNext() { + tracy_zone_scoped_with_name("LOBJMGR.UpdateXY(V2)"); + auto const super_pause_time = UpdateSuperPause(); // 更新超级暂停 + for (auto p = m_update_list.first(); p != nullptr;) { + if (super_pause_time > 0 && !p->ignore_super_pause) { + p = p->update_list_next; + continue; + } + p->UpdateLastV2(); + p = p->update_list_next; + } + } void GameObjectPool::detectOutOfWorldBoundLegacy() { tracy_zone_scoped_with_name("LOBJMGR.BoundCheck"); @@ -470,6 +482,23 @@ namespace luastg auto const has_callback_legacy_kill = legacy_kill_mode && object->features.has_callback_legacy_kill; return has_callback_destroy || has_callback_legacy_kill; } + void GameObjectPool::freeMarkedForDeletion() { + tracy_zone_scoped_with_name("LOBJMGR.AfterFrame(V3)"); + dispatchOnBeforeBatchDestroy(); + const auto super_pause_time = GetSuperPauseTime(); + for (auto p = m_update_list.first(); p != nullptr;) { + if (super_pause_time > 0 && !p->ignore_super_pause) { + p = p->update_list_next; + continue; + } + if (p->status != GameObjectStatus::Active) { + p = freeWithCallbacks(p); + continue; + } + p = p->update_list_next; + } + dispatchOnAfterBatchDestroy(); + } void GameObjectPool::DrawCollider() { diff --git a/LuaSTG/LuaSTG/GameObject/GameObjectPool.h b/LuaSTG/LuaSTG/GameObject/GameObjectPool.h index 89a53c92b..30c218b5f 100644 --- a/LuaSTG/LuaSTG/GameObject/GameObjectPool.h +++ b/LuaSTG/LuaSTG/GameObject/GameObjectPool.h @@ -318,7 +318,10 @@ namespace luastg // 对象更新:传统模式新旧帧衔接 void updateNextLegacy(); - // 对象更新:新旧帧衔接 + // 对象更新:新旧帧衔接(带对象回收) + void updateNextOld(); + + // 对象更新:新旧帧衔接(不带对象回收) void updateNext(); // 渲染所有游戏对象 @@ -354,10 +357,10 @@ namespace luastg // 检测所有 -> 回调所有 void detectIntersection(std::pmr::vector const& group_pairs); - /// @brief 更新对象的XY坐标偏移量 + // 更新对象的XY坐标偏移量 void UpdateXY() noexcept; - //重置对象的各项属性,并释放资源,保留uid和id + // 重置对象的各项属性,并释放资源,保留uid和id void DirtResetObject(GameObject* p) noexcept; // 修改游戏对象所在的碰撞组:从原碰撞组链表移除,插入到新碰撞组链表,并更新 group 属性 @@ -366,7 +369,10 @@ namespace luastg // 修改游戏对象渲染图层:从有序渲染链表移除,更新 layer 属性,重新插入有序渲染链表 void setLayer(GameObject* object, double layer); - /// @brief 清空对象池 + // 回收标记为删除的游戏对象 + void freeMarkedForDeletion(); + + // 清空并重置对象池 void ResetPool() noexcept; [[nodiscard]] GameObject* allocate() { return allocateWithCallbacks(nullptr); } diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_GameObjectManager.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_GameObjectManager.cpp index cf6e2eb57..5202cde94 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LW_GameObjectManager.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LW_GameObjectManager.cpp @@ -22,11 +22,6 @@ void luastg::binding::GameObjectManager::Register(lua_State* L) noexcept ); return 0; } - static int UpdateXY(lua_State* L) noexcept - { - LPOOL.UpdateXY(); - return 0; - } // EX+ 对象更新相关,影响 frame 回调函数以及对象更新 static int GetSuperPause(lua_State* L) noexcept { @@ -103,7 +98,6 @@ void luastg::binding::GameObjectManager::Register(lua_State* L) noexcept // 对象管理器 { "GetnObj", &Wrapper::GetnObj }, { "SetBound", &Wrapper::SetBound }, - { "UpdateXY", &Wrapper::UpdateXY }, // EX+ { "GetSuperPause", &Wrapper::GetSuperPause }, { "SetSuperPause", &Wrapper::SetSuperPause }, diff --git a/LuaSTG/LuaSTG/LuaBinding/modern/GameObject.cpp b/LuaSTG/LuaSTG/LuaBinding/modern/GameObject.cpp index 054f7b245..13fa0f338 100644 --- a/LuaSTG/LuaSTG/LuaBinding/modern/GameObject.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/modern/GameObject.cpp @@ -845,16 +845,44 @@ namespace luastg::binding { ctx.push_value(has_callback); return 1; } - static int updateNext(lua_State* const vm) { + + static int apiUpdateXY(lua_State* const vm) { // TODO: 移动到 GameObjectManager 绑定 // version 2 if (lua::stack_t const ctx(vm); ctx.is_number(1)) { - if (auto const version = ctx.get_value(1); version == 2) { + const auto version = ctx.get_value(1); + if (version == 2) { GameObjectManagerCallbacks::getInstance().lua_vm.push_back(vm); LPOOL.updateNext(); GameObjectManagerCallbacks::getInstance().lua_vm.pop_back(); return 0; } + else { + return luaL_error(vm, "unknown version number %d", version); + } + } + // version 1 + LPOOL.UpdateXY(); + return 0; + } + static int apiAfterFrame(lua_State* const vm) { + // TODO: 移动到 GameObjectManager 绑定 + // version 2 + if (lua::stack_t const ctx(vm); ctx.is_number(1)) { + const auto version = ctx.get_value(1); + if (version == 2) { + GameObjectManagerCallbacks::getInstance().lua_vm.push_back(vm); + LPOOL.updateNextOld(); + GameObjectManagerCallbacks::getInstance().lua_vm.pop_back(); + return 0; + } + else if (version == 3) { + LPOOL.freeMarkedForDeletion(); + return 0; + } + else { + return luaL_error(vm, "unknown version number %d", version); + } } // version 1 GameObjectManagerCallbacks::getInstance().lua_vm.push_back(vm); @@ -1091,7 +1119,8 @@ namespace luastg::binding { ctx.set_map_value(lstg_table, "ResetObject"sv, &GameObjectBinding::dirtyReset); // TODO: WTF? ctx.set_map_value(lstg_table, "_Del"sv, &GameObjectBinding::queueToFree); ctx.set_map_value(lstg_table, "_Kill"sv, &GameObjectBinding::queueToFreeLegacyKillMode); - ctx.set_map_value(lstg_table, "AfterFrame"sv, &GameObjectBinding::updateNext); + ctx.set_map_value(lstg_table, "UpdateXY"sv, &GameObjectBinding::apiUpdateXY); + ctx.set_map_value(lstg_table, "AfterFrame"sv, &GameObjectBinding::apiAfterFrame); ctx.set_map_value(lstg_table, "ResetPool"sv, &GameObjectBinding::resetGameObjectManager); ctx.set_map_value(lstg_table, "ObjFrame"sv, &GameObjectBinding::updateGameObjectManager); ctx.set_map_value(lstg_table, "ObjRender"sv, &GameObjectBinding::renderGameObjectManager); diff --git a/LuaSTG/doc/luastg/legacy/GameObjectManager.lua b/LuaSTG/doc/luastg/legacy/GameObjectManager.lua index 09b8f184d..c4ebafb10 100644 --- a/LuaSTG/doc/luastg/legacy/GameObjectManager.lua +++ b/LuaSTG/doc/luastg/legacy/GameObjectManager.lua @@ -1,3 +1,5 @@ +---@diagnostic disable: duplicate-set-field, unused-local + -------------------------------------------------------------------------------- --- LuaSTG Sub 游戏对象管理器 --- 璀境石 @@ -10,7 +12,7 @@ local M = {} -------------------------------------------------------------------------------- --- 游戏对象管理器 ----获取申请的对象数 +--- 获取申请的对象数 ---@return number function M.GetnObj() end @@ -23,29 +25,118 @@ end function M.ObjList(group_id) end ----回收所有对象,并释放绑定的资源 +--- 回收所有对象,并释放绑定的资源 function M.ResetPool() end ---- 【禁止在协同程序中调用此方法】 +--- **禁止在协同程序(continue)中调用此方法** +--- --- 更新所有游戏对象并触发游戏对象的frame回调函数 ---- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version`: ---- * 不传递 `version` 参数或参数为 1 时,遵循旧逻辑 ---- * `version` 参数或参数为 2 时,启用新逻辑 +--- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version` +--- +--- * 不传递 `version` 参数或参数值为 `1` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 执行 frame 回调函数 +--- object:frame() +--- -- 根据 ax、ay、ag 更新 vx、vy +--- object.vx = object.vx + object.ax +--- object.vy = object.vy + object.ay - object.ag +--- -- 根据 maxv 限制 vx、vy +--- local speed = sqrt(object.vx * object.vx + object.vy * object.vy) +--- if speed > maxv then +--- local scale = maxv / speed +--- object.vx = object.vx * scale +--- object.vy = object.vy * scale +--- end +--- -- 根据 maxvx、maxvy 限制 vx、vy 范围 +--- object.vx = clamp(object.vx, -object.maxvx, object.maxvx) +--- object.vy = clamp(object.vy, -object.maxvy, object.maxvy) +--- -- 根据 vx、vy 更新 x、y +--- object.x = object.x + object.vx +--- object.y = object.y + object.vy +--- -- 根据 omega(omiga)更新 rot +--- object.rot = object.rot + object.omega +--- -- 更新粒子系统(若有) +--- updateParticleSystem(object) +--- end +--- ``` +--- * `version` 参数值为 `2` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 执行 frame 回调函数 +--- object:frame() +--- end +--- for object in lstg.ObjList() do +--- -- 根据 ax、ay、ag 更新 vx、vy +--- object.vx = object.vx + object.ax +--- object.vy = object.vy + object.ay - object.ag +--- -- 根据 maxv 限制 vx、vy +--- local speed = sqrt(object.vx * object.vx + object.vy * object.vy) +--- if speed > maxv then +--- local scale = maxv / speed +--- object.vx = object.vx * scale +--- object.vy = object.vy * scale +--- end +--- -- 根据 maxvx、maxvy 限制 vx、vy 范围 +--- object.vx = clamp(object.vx, -object.maxvx, object.maxvx) +--- object.vy = clamp(object.vy, -object.maxvy, object.maxvy) +--- -- 根据 vx、vy 更新 x、y +--- object.x = object.x + object.vx +--- object.y = object.y + object.vy +--- -- 根据 omega(omiga)更新 rot +--- object.rot = object.rot + object.omega +--- -- 根据 navi 更新 rot +--- if object.navi then +--- object.rot = atan2(object.dy, object.dx) +--- end +--- -- 更新粒子系统(若有) +--- updateParticleSystem(object) +--- end +--- ```` +--- ---@param version integer? function M.ObjFrame(version) end ----【禁止在协同程序中调用此方法】 ----绘制所有游戏对象并触发游戏对象的render回调函数 +--- **禁止在协同程序(continue)中调用此方法** +--- +--- 绘制所有游戏对象并触发游戏对象的render回调函数 function M.ObjRender() end ---- 【禁止在协同程序中调用此方法】 +--- **禁止在协同程序(continue)中调用此方法** +--- --- 对所有游戏对象进行出界判断,如果离开场景边界,将会触发对象的 del 回调函数 ---- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version`: ---- * 不传递 `version` 参数或参数为 1 时,遵循旧逻辑 ---- * `version` 参数或参数为 2 时,启用新逻辑 +--- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version` +--- +--- * 不传递 `version` 参数或参数值为 `1` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 判断游戏对象中心位置是否离开世界边界 +--- if not isInWorldBoundary(object) then +--- -- 标记删除游戏对象 +--- lstg.Del(object) +--- end +--- end +--- ``` +--- * `version` 参数值为 `2` 时,逻辑伪代码 +--- ```lua +--- -- 第一步 +--- local results = {} +--- for object in lstg.ObjList() do +--- -- 判断游戏对象中心位置是否离开世界边界 +--- if not isInWorldBoundary(object) then +--- table.insert(results, object) +--- end +--- end +--- -- 第二步 +--- for _, object in ipairs(results) do +--- -- 标记删除游戏对象 +--- lstg.Del(object) +--- end +--- ``` +--- ---@param version integer? function M.BoundCheck(version) end @@ -60,26 +151,136 @@ end function M.SetBound(left, right, bottom, top) end ----【禁止在协同程序中调用此方法】 ---- 对两个碰撞组的对象进行碰撞检测 ---- 如果发生碰撞则触发groupidA内的对象的colli回调函数,并传入groupidB内的对象作为参数 ----@param groupidA number @只能为0到15范围内的整数 ----@param groupidB number @只能为0到15范围内的整数 -function M.CollisionCheck(groupidA, groupidB) -end - ---- 【禁止在协同程序中调用此方法】 ---- 保存游戏对象的x, y坐标并计算dx, dy ---- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,如果启用新逻辑, ---- 请勿调用 `lstg.UpdateXY` 方法,相关逻辑已合并到 `lstg.AfterFrame` 中 -function M.UpdateXY() +--- **禁止在协同程序(continue)中调用此方法** +--- +--- 对两个碰撞组的对象进行相交检测 +--- `group1` 和 `group2` 的取值范围是 0 到 15 的整数 +--- +--- * 逻辑伪代码 +--- ```lua +--- for object1 in lstg.ObjList(group1) do +--- for object2 in lstg.ObjList(group2) do +--- if hasIntersection(object1, object2) then +--- object1:colli(object2) +--- end +--- end +--- end +--- ``` +--- +---@param group1 number +---@param group2 number +function M.CollisionCheck(group1, group2) +end + +--- **禁止在协同程序(continue)中调用此方法** +--- +--- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,该方法重载可用 +--- 一次性对多个碰撞组对的对象进行相交检测 +--- `group_pairs` 参数是碰撞组对列表,每个碰撞组对是一个长度为 2 的数组,分别代表 `group1` 和 `group2` +--- +--- * 逻辑伪代码 +--- ```lua +--- -- 第一步 +--- local results = {} +--- for group_pair in ipairs(group_pairs) do +--- for object1 in lstg.ObjList(group_pair[1]) do +--- for object2 in lstg.ObjList(group_pair[2]) do +--- if hasIntersection(object1, object2) then +--- table.insert(results, { object1, object2 }) +--- end +--- end +--- end +--- end +--- -- 第二步 +--- for _, result in ipairs(results) do +--- result[1]:colli(result[2]) +--- end +--- ``` +--- +---@param group_pairs { [1]: number, [2]: number }[] +function M.CollisionCheck(group_pairs) +end + +--- **禁止在协同程序(continue)中调用此方法** +--- +--- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version` +--- +--- * 不传递 `version` 参数或参数值为 `1` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 更新 dx、dy(注意 lastx、lasty 在 lua 层不可访问) +--- object.dx = object.x - object.lastx +--- object.dy = object.y - object.lasty +--- object.lastx = object.x +--- object.lasty = object.y +--- -- 根据 navi 更新 rot +--- if object.navi then +--- object.rot = atan2(object.dy, object.dx) +--- end +--- end +--- ``` +--- * `version` 参数值为 `2` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 更新 dx、dy,注意 lastx、lasty 在 lua 层不可访问 +--- object.dx = object.x - object.lastx +--- object.dy = object.y - object.lasty +--- object.lastx = object.x +--- object.lasty = object.y +--- -- 更新计时器 +--- object.timer = object.timer + 1 +--- object.ani = object.ani + 1 +--- end +--- ``` +--- +---@param version integer? +function M.UpdateXY(version) end ---- 【禁止在协同程序中调用此方法】 ---- 增加游戏对象的timer, ani计数器,如果对象被标记为kill或者del,则回收该对象 +--- **禁止在协同程序(continue)中调用此方法** +--- --- 从 LuaSTG Sub v0.21.13(第二代游戏循环更新顺序)开始,可以传递版本参数 `version`: ---- * 不传递 `version` 参数或参数为 1 时,遵循旧逻辑 ---- * `version` 参数或参数为 2 时,启用新逻辑 +--- +--- * 不传递 `version` 参数或参数值为 `1` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 更新计时器 +--- object.timer = object.timer + 1 +--- object.ani = object.ani + 1 +--- -- 如果对象标记为删除(不是正常状态),回收对象 +--- if object.status ~= "normal" then +--- freeGameObject(object) +--- end +--- end +--- ``` +--- * `version` 参数值为 `2` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- -- 如果游戏对象是正常状态,执行更新,否则回收对象 +--- if object.status == "normal" then +--- -- 更新 dx、dy,注意 lastx、lasty 在 lua 层不可访问 +--- object.dx = object.x - object.lastx +--- object.dy = object.y - object.lasty +--- object.lastx = object.x +--- object.lasty = object.y +--- -- 更新计时器 +--- object.timer = object.timer + 1 +--- object.ani = object.ani + 1 +--- else +--- -- 回收对象 +--- freeGameObject(object) +--- end +--- end +--- ``` +--- * `version` 参数值为 `3` 时,逻辑伪代码 +--- ```lua +--- for object in lstg.ObjList() do +--- if object.status ~= "normal" then +--- freeGameObject(object) +--- end +--- end +--- ``` +--- ---@param version integer? function M.AfterFrame(version) end diff --git a/data/example/scripts/main.lua b/data/example/scripts/main.lua index 8d0dbf593..6666c6cf6 100644 --- a/data/example/scripts/main.lua +++ b/data/example/scripts/main.lua @@ -120,12 +120,32 @@ local function buildGameObjectScene() end end) end -local function updateGameObject() - lstg.AfterFrame(2) -- TODO: remove (2) - lstg.ObjFrame(2) -- TODO: remove (2) +local function updateGameObjectV1() + lstg.ObjFrame() lstg.SetBound(0, window.width, 0, window.height) + lstg.BoundCheck() lstg.CollisionCheck(GROUP_PLAYER, GROUP_ENEMY_BULLET) - lstg.BoundCheck(2) -- TODO: remove (2) + lstg.UpdateXY() + lstg.AfterFrame() +end +local function updateGameObjectV2() + lstg.AfterFrame(2) + lstg.ObjFrame(2) + lstg.SetBound(0, window.width, 0, window.height) + lstg.CollisionCheck({ + {GROUP_PLAYER, GROUP_ENEMY_BULLET}, + }) + lstg.BoundCheck(2) +end +local function updateGameObjectV3() + lstg.UpdateXY(2) + lstg.ObjFrame(2) + lstg.SetBound(0, window.width, 0, window.height) + lstg.CollisionCheck({ + {GROUP_PLAYER, GROUP_ENEMY_BULLET}, + }) + lstg.BoundCheck(2) + lstg.AfterFrame(3) end local function renderGameObject() lstg.ObjRender() @@ -207,7 +227,7 @@ function FrameFunc() global_tasks:resume() global_tasks:gc() updateBackground() - updateGameObject() + updateGameObjectV3() if Keyboard.GetKeyState(Keyboard.Escape) then return true -- exit end