新增 GameFlow 运行时状态机:PIE 与独立运行统一生命周期
Stopped→Loading→Running↔Paused 单一状态源,编辑器 Play/Stop 与 main.cpp 独立运行共用同一套加载/恢复/暂停语义
This commit is contained in:
+51
-30
@@ -21,7 +21,6 @@
|
||||
#include "panels/StatsPanel.h"
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/Core/Time.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -40,9 +39,22 @@ Editor::~Editor() = default;
|
||||
void Editor::Init(const EditorContext& ctx)
|
||||
{
|
||||
m_Ctx = ctx;
|
||||
// PIE 运行/暂停态指针:指向本对象成员(ctx 拷贝后补接,面板只读)
|
||||
m_Ctx.playState = &m_Playing;
|
||||
m_Ctx.pausedState = &m_Paused;
|
||||
// PIE 运行/暂停镜像指针:指向本对象成员(ctx 拷贝后补接,面板只读;
|
||||
// 权威 = GameFlow,镜像在 Draw 开头同步——面板接口保持 const bool*)
|
||||
m_Ctx.playState = &m_PlayingMirror;
|
||||
m_Ctx.pausedState = &m_PausedMirror;
|
||||
|
||||
// PIE 转换副作用挂监听(GameFlow 不知场景;快照/恢复是编辑器职责)
|
||||
if (m_Ctx.flow)
|
||||
{
|
||||
m_Ctx.flow->AddListener(
|
||||
[this](GameFlowState, GameFlowState to) {
|
||||
if (to == GameFlowState::Loading) OnFlowEnterLoading();
|
||||
else if (to == GameFlowState::Stopped) OnFlowEnterStopped();
|
||||
// →Running/→Paused:timeScale 由 GameFlow 权威处理,
|
||||
// 编辑器无额外副作用
|
||||
});
|
||||
}
|
||||
|
||||
m_Hierarchy = std::make_unique<HierarchyPanel>();
|
||||
m_Inspector = std::make_unique<InspectorPanel>();
|
||||
@@ -84,40 +96,42 @@ void Editor::Init(const EditorContext& ctx)
|
||||
|
||||
// ---- PIE(Play-In-Editor)----
|
||||
|
||||
// Play:当前场景 → 内存 YAML 快照 → 切 gameView(Viewport 面板内嵌
|
||||
// primary 相机渲染,UE PIE 语义——编辑器 UI 保持可用,不切全屏;
|
||||
// kDisplayDirect 全屏预览是后期发布形态,手动经 Stats 切换)
|
||||
// → 解冻时间(物理/脚本/动画开始跑)。快照失败不进 Play(现场不动)
|
||||
void Editor::DoPlay()
|
||||
// →Loading 监听器:Play 半程。当前场景 → 内存 YAML 快照 → 切 gameView
|
||||
//(Viewport 面板内嵌 primary 相机渲染,UE PIE 语义——编辑器 UI 保持
|
||||
// 可用,不切全屏)→ 同步加载完成即 CompleteLoading(快照是同步操作,
|
||||
// 不完成的 Loading 会卡死主循环的物理/脚本门控)。快照失败 → RequestStop
|
||||
// 回滚(现场不动)
|
||||
void Editor::OnFlowEnterLoading()
|
||||
{
|
||||
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer || !m_Ctx.flow) return;
|
||||
|
||||
m_PlaySnapshot = Serialization::SceneSerializer::SerializeToString(
|
||||
*m_Ctx.scene, *m_Ctx.assets, "PlaySnapshot");
|
||||
if (m_PlaySnapshot.empty())
|
||||
{
|
||||
ENGINE_ERROR("PIE: snapshot failed, play aborted");
|
||||
m_Ctx.flow->RequestStop(); // 回滚 → 触发 →Stopped 监听器(空快照跳过恢复)
|
||||
return;
|
||||
}
|
||||
|
||||
m_Playing = true;
|
||||
m_Paused = false;
|
||||
m_Gizmo.dragging = false; // 编辑态拖杆中点 Play:杆运行态不出,复位
|
||||
m_Ctx.renderer->SetGameView(true);
|
||||
Time::SetTimeScale(1.0f);
|
||||
m_Ctx.flow->CompleteLoading(); // 同步加载完成 → Running(解冻)
|
||||
ENGINE_INFO("PIE: playing (scene snapshot {} bytes)",
|
||||
m_PlaySnapshot.size());
|
||||
}
|
||||
|
||||
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
|
||||
// →Stopped 监听器:Stop 半程。快照原子换血恢复 → 回编辑器渲染。
|
||||
// 换血后所有 Entity 句柄悬空:清选中(描边同步清)、Gizmo 拖拽态
|
||||
// 复位。物理 body/约束/角色由对账系统随实体重建/清理,无需干预。
|
||||
// 恢复失败(快照损坏,理论罕见)也必须回编辑态,现场留给用户
|
||||
// File→Load 或退出前 Save
|
||||
void Editor::DoStop()
|
||||
// File→Load 或退出前 Save。空快照 = 快照失败回滚(未真 Play 过),跳过恢复
|
||||
void Editor::OnFlowEnterStopped()
|
||||
{
|
||||
if (!m_Ctx.scene || !m_Ctx.assets || !m_Ctx.renderer) return;
|
||||
|
||||
if (!m_PlaySnapshot.empty())
|
||||
{
|
||||
if (!Serialization::SceneSerializer::DeserializeFromString(
|
||||
*m_Ctx.scene, *m_Ctx.assets, m_PlaySnapshot))
|
||||
{
|
||||
@@ -125,11 +139,9 @@ void Editor::DoStop()
|
||||
"played-out state)");
|
||||
}
|
||||
m_PlaySnapshot.clear();
|
||||
}
|
||||
|
||||
m_Playing = false;
|
||||
m_Paused = false;
|
||||
m_Ctx.renderer->SetGameView(false);
|
||||
Time::SetTimeScale(0.0f);
|
||||
SelectEntity(entt::null);
|
||||
m_Gizmo.dragging = false;
|
||||
ENGINE_INFO("PIE: stopped, editor scene restored");
|
||||
@@ -137,19 +149,28 @@ void Editor::DoStop()
|
||||
|
||||
void Editor::TogglePlay()
|
||||
{
|
||||
m_Playing ? DoStop() : DoPlay();
|
||||
if (!m_Ctx.flow) return;
|
||||
IsPlaying() ? m_Ctx.flow->RequestStop() : m_Ctx.flow->RequestStart();
|
||||
}
|
||||
|
||||
// PIE 暂停(UE Pause 语义):timeScale 0 ↔ 1。运行态才有意义
|
||||
// PIE 暂停(UE Pause 语义):Running ↔ Paused。运行态才有意义
|
||||
//(编辑器态本来就冻结,Pause 按钮不出现)
|
||||
void Editor::TogglePause()
|
||||
{
|
||||
m_Paused = !m_Paused;
|
||||
Time::SetTimeScale(m_Paused ? 0.0f : 1.0f);
|
||||
if (!m_Ctx.flow) return;
|
||||
IsPaused() ? m_Ctx.flow->RequestResume() : m_Ctx.flow->RequestPause();
|
||||
}
|
||||
|
||||
void Editor::Draw()
|
||||
{
|
||||
// PIE 镜像同步(面板只读指针消费;权威 = GameFlow,此处适配)
|
||||
if (m_Ctx.flow)
|
||||
{
|
||||
m_PlayingMirror = m_Ctx.flow->IsIn(GameFlowState::Running) ||
|
||||
m_Ctx.flow->IsIn(GameFlowState::Paused);
|
||||
m_PausedMirror = m_Ctx.flow->IsIn(GameFlowState::Paused);
|
||||
}
|
||||
|
||||
// ImGuizmo 每帧首调(官方约定):内部会清 mbOverGizmoHotspot 等
|
||||
// 帧级状态——不调则 hover 过一次杆后 hotspot 永久置位,GetMoveType
|
||||
// 恒返 MT_NONE → 杆可见但永远抓不住(只能看不能拖)
|
||||
@@ -253,7 +274,7 @@ void Editor::DrawMenuBar()
|
||||
{
|
||||
// 运行态禁存取:Save 会把"玩出来的状态"当编辑结果落盘,
|
||||
// Load 会撕掉快照恢复链(UE 运行态同样禁)
|
||||
ImGui::BeginDisabled(m_Playing);
|
||||
ImGui::BeginDisabled(IsPlaying());
|
||||
if (ImGui::MenuItem("Save Scene...", nullptr))
|
||||
{
|
||||
m_ShowSaveModal = true;
|
||||
@@ -302,11 +323,11 @@ void Editor::DrawToolbar()
|
||||
// 按钮加大触感(工具栏行高 = 一个按钮高;DockSpace avail 自适应)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 4.0f));
|
||||
const ImGuiStyle& st = ImGui::GetStyle();
|
||||
const char* pauseLabel = m_Paused ? "Resume" : "Pause";
|
||||
const char* pauseLabel = IsPaused() ? "Resume" : "Pause";
|
||||
|
||||
// Play 组宽度 → 水平居中(编辑态 1 钮 / 运行态 2 钮)
|
||||
float groupW = 0.0f;
|
||||
if (m_Playing)
|
||||
if (IsPlaying())
|
||||
{
|
||||
groupW = ImGui::CalcTextSize(pauseLabel).x +
|
||||
ImGui::CalcTextSize("Stop").x +
|
||||
@@ -318,14 +339,14 @@ void Editor::DrawToolbar()
|
||||
}
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - groupW) * 0.5f);
|
||||
|
||||
if (m_Playing)
|
||||
if (IsPlaying())
|
||||
{
|
||||
// Pause/Resume:暂停中亮黄常驻(UE 按下态),运行中暗黄
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
m_Paused ? ImVec4(0.85f, 0.62f, 0.12f, 1.0f)
|
||||
IsPaused() ? ImVec4(0.85f, 0.62f, 0.12f, 1.0f)
|
||||
: ImVec4(0.55f, 0.42f, 0.10f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
m_Paused ? ImVec4(0.92f, 0.70f, 0.18f, 1.0f)
|
||||
IsPaused() ? ImVec4(0.92f, 0.70f, 0.18f, 1.0f)
|
||||
: ImVec4(0.65f, 0.50f, 0.14f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
ImVec4(0.95f, 0.76f, 0.24f, 1.0f));
|
||||
@@ -336,7 +357,7 @@ void Editor::DrawToolbar()
|
||||
ImGui::PopStyleColor(3);
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip(m_Paused ? "Resume simulation"
|
||||
ImGui::SetTooltip(IsPaused() ? "Resume simulation"
|
||||
: "Pause simulation");
|
||||
}
|
||||
|
||||
|
||||
+29
-12
@@ -18,6 +18,7 @@
|
||||
#include "panels/ViewportPanel.h" // ViewportInputState(成员按值持有)
|
||||
|
||||
#include <Engine/Core/Types.h> // u32(SelectEntity 位模式转换)
|
||||
#include <Engine/Core/GameFlow.h> // PIE 状态权威(IsPlaying 直查)
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
@@ -60,19 +61,35 @@ public:
|
||||
void RequestQuit() { m_ShowQuitConfirm = true; }
|
||||
|
||||
// ---- PIE(Play-In-Editor,UE 主工具栏语义)----
|
||||
bool IsPlaying() const { return m_Playing; }
|
||||
bool IsPaused() const { return m_Paused; }
|
||||
// Play:内存快照当前场景 → 切 gameView(Viewport 内嵌 primary 相机
|
||||
// 渲染,UE PIE 语义;不切全屏)→ 解冻时间;
|
||||
// Stop:快照原子换血恢复 → 回编辑器渲染 → 冻结时间。
|
||||
// 编辑器态默认 timeScale=0(物理冻结 = UE 语义),Play 解冻
|
||||
// 状态权威 = GameFlow(EditorContext::flow);编辑器只是转换的
|
||||
// 发起者(Request*)与监听器(快照/恢复挂在 →Loading/→Stopped)
|
||||
bool IsPlaying() const
|
||||
{
|
||||
return m_Ctx.flow &&
|
||||
(m_Ctx.flow->IsIn(Engine::GameFlowState::Running) ||
|
||||
m_Ctx.flow->IsIn(Engine::GameFlowState::Paused));
|
||||
}
|
||||
bool IsPaused() const
|
||||
{
|
||||
return m_Ctx.flow &&
|
||||
m_Ctx.flow->IsIn(Engine::GameFlowState::Paused);
|
||||
}
|
||||
// Play:RequestStart → →Loading 监听器做快照 + 切 gameView(Viewport
|
||||
// 内嵌 primary 相机渲染,UE PIE 语义;不切全屏)→ 同步加载完成
|
||||
// CompleteLoading → Running 解冻;Stop:RequestStop → →Stopped 监听器
|
||||
// 快照原子换血恢复 → 回编辑器渲染。编辑器态默认冻结(GameFlow 构造
|
||||
// 即 timeScale=0),Play 解冻由 GameFlow 权威处理
|
||||
void TogglePlay();
|
||||
// PIE 暂停切换(UE Pause 语义):timeScale 0 ↔ 1
|
||||
// PIE 暂停切换(UE Pause 语义):Running ↔ Paused(timeScale 由
|
||||
// GameFlow 权威处理)。运行态才有意义(编辑器态本来就冻结,
|
||||
// Pause 按钮不出现)
|
||||
void TogglePause();
|
||||
|
||||
private:
|
||||
void DoPlay(); // TogglePlay 的 Play 半程(快照 + 切渲染 + 解冻)
|
||||
void DoStop(); // TogglePlay 的 Stop 半程(恢复 + 回编辑器 + 冻结)
|
||||
// GameFlow 监听器(Init 注册):编辑器快照/恢复挂在此处——
|
||||
// GameFlow 不知场景,转换的副作用由消费端自己挂
|
||||
void OnFlowEnterLoading(); // →Loading:快照 + 切 gameView + 同步完成加载
|
||||
void OnFlowEnterStopped(); // →Stopped:快照换血恢复 + 回编辑器渲染
|
||||
|
||||
// ---- 选中状态 ----
|
||||
// 同步描边:Renderer 落帧后 SelectionMask/Outline 只画选中实体。
|
||||
@@ -135,9 +152,9 @@ private:
|
||||
bool m_ShowLoadModal = false;
|
||||
char m_ScenePathBuf[256] = "scenes/sandbox.scene";
|
||||
|
||||
// PIE 状态:Play 时场景 YAML 内存快照,Stop 时原子换血恢复
|
||||
bool m_Playing = false;
|
||||
bool m_Paused = false; // PIE 暂停(timeScale=0;Stop 时复位)
|
||||
// PIE 镜像态(面板只读指针消费;权威 = GameFlow,Draw 开头同步)
|
||||
bool m_PlayingMirror = false;
|
||||
bool m_PausedMirror = false;
|
||||
std::string m_PlaySnapshot;
|
||||
};
|
||||
|
||||
|
||||
+39
-8
@@ -23,9 +23,12 @@
|
||||
#include <Engine/Core/Stats.h>
|
||||
#include <Engine/Renderer/Renderer.h>
|
||||
#include <Engine/Renderer/EditorCamera.h>
|
||||
#include <Engine/Serialization/SceneSerializer.h>
|
||||
|
||||
#include <Engine/Core/Input.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
// editor/game 编译期分离(HELLOVULKAN_EDITOR CMake option):editor 形态
|
||||
// 挂接 docking Editor;game 形态 = Direct 直渲 + primary 相机,零编辑器代码
|
||||
#ifdef HELLO_EDITOR
|
||||
@@ -66,7 +69,9 @@ namespace
|
||||
InitEditor();
|
||||
#else
|
||||
// game 形态:全窗口 Direct 直渲(Renderer 默认即 kDisplayDirect),
|
||||
// 相机由场景 primary camera 提供,无 UI
|
||||
// 相机由场景 primary camera 提供,无 UI。
|
||||
// 游戏流:Stopped → Loading(OnUpdate 泵场景加载)→ Running
|
||||
GetGameFlow().RequestStart();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -84,7 +89,31 @@ namespace
|
||||
GetRenderer().GetEditorCamera().Update(frameTime.UnscaledDt());
|
||||
}
|
||||
#else
|
||||
(void)frameTime; // game 形态:本沙盒无游戏逻辑
|
||||
// Loading 泵(同步示例):场景文件存在则加载,不存在 =
|
||||
// 空场景照常进 Running(引擎冒烟路径)。加载完成后必须
|
||||
// CompleteLoading,否则物理/脚本门控永远跳过(卡死 Loading)
|
||||
if (GetGameFlow().IsIn(GameFlowState::Loading))
|
||||
{
|
||||
if (std::filesystem::exists(m_ScenePath))
|
||||
{
|
||||
if (Serialization::SceneSerializer::Deserialize(
|
||||
GetScene(), GetAssets(), m_ScenePath))
|
||||
{
|
||||
ENGINE_INFO("Game: scene loaded from {}", m_ScenePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
ENGINE_ERROR("Game: scene load failed: {}",
|
||||
m_ScenePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ENGINE_WARN("Game: scene file not found ({}), "
|
||||
"starting empty", m_ScenePath);
|
||||
}
|
||||
GetGameFlow().CompleteLoading();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -99,16 +128,14 @@ namespace
|
||||
|
||||
// ---- 事件 ----
|
||||
|
||||
#ifdef HELLO_EDITOR
|
||||
void OnWindowClose(const Event& event) override
|
||||
{
|
||||
#ifdef HELLO_EDITOR
|
||||
// 退出确认走 Editor 的模态框(ImGui 必须可见才有 UI)
|
||||
SetImGuiEnabled(true);
|
||||
m_Editor->RequestQuit();
|
||||
#else
|
||||
(void)event;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void OnKeyPress(const Event& event) override
|
||||
{
|
||||
@@ -141,9 +168,10 @@ namespace
|
||||
ctx.scene = &GetScene();
|
||||
ctx.assets = &GetAssets();
|
||||
ctx.app = this;
|
||||
ctx.flow = &GetGameFlow();
|
||||
ctx.editorCamera = &GetRenderer().GetEditorCamera();
|
||||
// 继承原沙盒默认:初始暂停(StatsPanel 的 Pause Simulation 开关恢复)
|
||||
Time::SetTimeScale(0.0f);
|
||||
// 编辑器态默认冻结:GameFlow 构造即 timeScale=0(Stopped 语义),
|
||||
// timeScale 权威在 GameFlow,此处不再手设
|
||||
m_Editor->Init(ctx);
|
||||
|
||||
// 输入归属:覆盖 EnableImGui 的全局 WantCapture* 谓词
|
||||
@@ -158,6 +186,9 @@ namespace
|
||||
|
||||
private:
|
||||
std::unique_ptr<Editor> m_Editor = std::make_unique<Editor>();
|
||||
#else
|
||||
// game 形态:启动场景路径(CWD = bin;编辑器 Save 的产物位置)
|
||||
const char* m_ScenePath = "assets/scenes/sandbox.scene";
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <Engine/Core/Types.h>
|
||||
#include <Engine/Core/Event.h>
|
||||
#include <Engine/Core/GameFlow.h>
|
||||
#include <Engine/Core/Time.h>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
@@ -75,6 +76,7 @@ namespace Engine {
|
||||
Assets::AssetManager& GetAssets() const { return *m_Assets; }
|
||||
PhysicsWorld& GetPhysics() const { return *m_Physics; }
|
||||
Scripting::ScriptRuntime& GetScripts() const { return *m_Scripts; }
|
||||
GameFlow& GetGameFlow() { return m_GameFlow; }
|
||||
u64 GetFrameCount() const { return m_FrameCount; }
|
||||
|
||||
// ImGui相关
|
||||
@@ -127,6 +129,9 @@ namespace Engine {
|
||||
// 其后端销毁需要 Device 与 GLFWwindow 存活)
|
||||
Scope<ImGuiLayer> m_ImGuiLayer;
|
||||
|
||||
// 游戏流状态机(构造即冻结:Stopped → timeScale=0)
|
||||
GameFlow m_GameFlow;
|
||||
|
||||
bool m_Running = false;
|
||||
u64 m_FrameCount = 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// GameFlow.h:游戏流状态机(引擎层唯一的"游戏在干什么"权威)。
|
||||
//
|
||||
// 状态:Stopped(编辑态/未启动)→ Loading(加载中)→ Running ⇄ Paused,
|
||||
// 任意活动态可 RequestStop 回 Stopped。转换矩阵外的请求拒绝 + warn。
|
||||
//
|
||||
// 职责边界:
|
||||
// - timeScale 单一权威:每次转换统一设 Time::SetTimeScale
|
||||
// (Running/Loading→1,Paused/Stopped→0)。上层(编辑器/游戏)
|
||||
// 不再自行碰 timeScale——冻结语义由状态推导,不再散落
|
||||
// - 状态转换广播:AddListener 注册监听(组合根/编辑器 Init 期注册,
|
||||
// 非每帧路径)。编辑器在 →Loading 挂场景快照、→Stopped 挂恢复;
|
||||
// 脚本/系统将来同样可订阅(跨系统解耦)
|
||||
// - 不知场景不知资产:加载本身(读文件/反序列化)是调用方的事,
|
||||
// 本类只管状态与转换纪律(CompleteLoading 由加载方显式调)
|
||||
//
|
||||
// 与主循环的契约(Application::Run):
|
||||
// - Input::Poll 仅 Running 激活(Paused/Stopped 动作静默)
|
||||
// - 物理步进仅 Loading 跳过(编辑态对账照跑,停摆靠 dt=0)
|
||||
// - 脚本仅 Running 执行(Unity Play Mode / UE PIE 语义——脚本副
|
||||
// 作用不依赖 dt,编辑态不得运行);→Stopped 由监听 EndPlay 终
|
||||
// 局脚本实例(组合根注册,先于编辑器回滚监听)
|
||||
//
|
||||
|
||||
#ifndef HELLOVULKAN_GAMEFLOW_H
|
||||
#define HELLOVULKAN_GAMEFLOW_H
|
||||
|
||||
#include "Engine/Core/Types.h"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace Engine {
|
||||
|
||||
enum class GameFlowState : u8 {
|
||||
Stopped, // 编辑态 / 未启动(timeScale=0)
|
||||
Loading, // 加载中(物理/脚本步进跳过;timeScale=1 无消费方)
|
||||
Running, // 游戏运行(timeScale=1)
|
||||
Paused, // 暂停(timeScale=0;编辑器 Pause / 游戏菜单挂起)
|
||||
};
|
||||
|
||||
class GameFlow {
|
||||
public:
|
||||
// (from, to);注册期 = 组合根/编辑器 Init,非每帧路径
|
||||
using Listener = std::function<void(GameFlowState, GameFlowState)>;
|
||||
|
||||
GameFlow(); // 构造即冻结(Stopped 语义:timeScale=0)
|
||||
|
||||
void AddListener(Listener l) { m_Listeners.push_back(std::move(l)); }
|
||||
|
||||
GameFlowState State() const { return m_State; }
|
||||
bool IsIn(GameFlowState s) const { return m_State == s; }
|
||||
|
||||
// ---- 转换请求(矩阵外拒绝 + warn,返回是否生效)----
|
||||
bool RequestStart(); // Stopped → Loading
|
||||
void CompleteLoading(); // Loading → Running(加载方显式调)
|
||||
bool RequestPause(); // Running → Paused
|
||||
bool RequestResume(); // Paused → Running
|
||||
bool RequestStop(); // Loading/Running/Paused → Stopped
|
||||
|
||||
private:
|
||||
// 立即生效转换:设 timeScale + 广播(同既有 DoPlay 同步语义)
|
||||
void Transition(GameFlowState to);
|
||||
|
||||
GameFlowState m_State = GameFlowState::Stopped;
|
||||
std::vector<Listener> m_Listeners;
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
#endif //HELLOVULKAN_GAMEFLOW_H
|
||||
@@ -44,6 +44,17 @@ namespace Engine {
|
||||
// 统一输入门面装配:脚本/游戏逻辑经 Input 查询键鼠,不直接
|
||||
// 依赖 Window 后端(GLFW 解耦)
|
||||
Input::Init(*m_Window);
|
||||
|
||||
// GameFlow →Stopped:脚本终局(Unity 退出 Play 语义——实例与
|
||||
// 成员状态不跨局存活,下次 Play 由对账重建)。注册在组合根最
|
||||
// 早处:先于编辑器 Init 的监听执行,EndPlay 跑在场景回滚之前,
|
||||
// OnDestroy 见到的仍是上局实体。m_Scripts 在 Run 里才创建,而
|
||||
// →Stopped 转换最早发生于 PIE Stop(届时已存在),空指针兜底
|
||||
m_GameFlow.AddListener([this](GameFlowState, GameFlowState to) {
|
||||
if (to == GameFlowState::Stopped && m_Scripts) {
|
||||
m_Scripts->EndPlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
@@ -132,15 +143,29 @@ namespace Engine {
|
||||
const FrameTime& frame = Time::Current();
|
||||
m_LastFrameTime = now;
|
||||
|
||||
// 输入动作层采样(单点,整帧一致):仅 Running 激活——
|
||||
// Paused/Stopped(编辑器态)动作静默清零,编辑器相机继续走
|
||||
// 原始 IsKeyDown 通道(自带 UI 捕获门控,两套互不干扰)
|
||||
Input::Poll(m_GameFlow.IsIn(GameFlowState::Running));
|
||||
|
||||
// 物理步进(OnUpdate 之前,Unity FixedUpdate→Update 语义):
|
||||
// 动态体姿态写回 Transform 并标脏,UpdateTransforms 统一重算;
|
||||
// 吃 scaled dt → timeScale=0(编辑器暂停)时 accumulator 冻结,
|
||||
// SyncScene 对账仍跑(拖静态体照常同步)
|
||||
// 吃 scaled dt → timeScale=0(Paused/Stopped 冻结)时
|
||||
// accumulator 冻结,SyncScene 对账仍跑(拖静态体照常同步)。
|
||||
// Loading 态整段跳过(加载中不步进)
|
||||
if (!m_GameFlow.IsIn(GameFlowState::Loading)) {
|
||||
m_Physics->Step(frame.Dt());
|
||||
|
||||
// 脚本更新(物理之后、业务 OnUpdate 之前,Unity FixedUpdate→Update
|
||||
// 语义;吃 scaled dt——timeScale=0 暂停时 dt=0 脚本自然停摆)
|
||||
// 脚本只在 Running 执行(Unity Play Mode / UE PIE 语义):
|
||||
// 编辑态/暂停态 gameplay 脚本一律不跑——脚本副作用不依赖
|
||||
// dt(读输入/画调试图元/翻转动画状态),"dt=0 自然停摆"
|
||||
// 不成立,曾致编辑态按 WASD 就画箭头+翻动画状态。实例
|
||||
// 生命周期 = 一次 PIE 局:→Stopped 由 GameFlow 监听
|
||||
// EndPlay 统一销毁
|
||||
if (m_GameFlow.IsIn(GameFlowState::Running)) {
|
||||
m_Scripts->Update(frame.Dt());
|
||||
}
|
||||
}
|
||||
|
||||
OnUpdate(frame);
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// GameFlow.cpp:游戏流状态机实现。
|
||||
//
|
||||
|
||||
#include "Engine/Core/GameFlow.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Time.h"
|
||||
|
||||
namespace Engine {
|
||||
|
||||
namespace {
|
||||
const char* StateName(GameFlowState s)
|
||||
{
|
||||
switch (s) {
|
||||
case GameFlowState::Stopped: return "Stopped";
|
||||
case GameFlowState::Loading: return "Loading";
|
||||
case GameFlowState::Running: return "Running";
|
||||
case GameFlowState::Paused: return "Paused";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
GameFlow::GameFlow()
|
||||
{
|
||||
// Stopped 语义即冻结:替代各处散落的初始 SetTimeScale(0)
|
||||
Time::SetTimeScale(0.0f);
|
||||
}
|
||||
|
||||
bool GameFlow::RequestStart()
|
||||
{
|
||||
if (m_State != GameFlowState::Stopped) {
|
||||
ENGINE_WARN("GameFlow: RequestStart rejected in {}",
|
||||
StateName(m_State));
|
||||
return false;
|
||||
}
|
||||
Transition(GameFlowState::Loading);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameFlow::CompleteLoading()
|
||||
{
|
||||
if (m_State != GameFlowState::Loading) {
|
||||
ENGINE_WARN("GameFlow: CompleteLoading rejected in {}",
|
||||
StateName(m_State));
|
||||
return;
|
||||
}
|
||||
Transition(GameFlowState::Running);
|
||||
}
|
||||
|
||||
bool GameFlow::RequestPause()
|
||||
{
|
||||
if (m_State != GameFlowState::Running) {
|
||||
ENGINE_WARN("GameFlow: RequestPause rejected in {}",
|
||||
StateName(m_State));
|
||||
return false;
|
||||
}
|
||||
Transition(GameFlowState::Paused);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameFlow::RequestResume()
|
||||
{
|
||||
if (m_State != GameFlowState::Paused) {
|
||||
ENGINE_WARN("GameFlow: RequestResume rejected in {}",
|
||||
StateName(m_State));
|
||||
return false;
|
||||
}
|
||||
Transition(GameFlowState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameFlow::RequestStop()
|
||||
{
|
||||
if (m_State == GameFlowState::Stopped) {
|
||||
ENGINE_WARN("GameFlow: RequestStop rejected in Stopped");
|
||||
return false;
|
||||
}
|
||||
Transition(GameFlowState::Stopped);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameFlow::Transition(GameFlowState to)
|
||||
{
|
||||
const GameFlowState from = m_State;
|
||||
m_State = to;
|
||||
|
||||
// timeScale 单一权威:Running/Loading 解冻,Paused/Stopped 冻结
|
||||
Time::SetTimeScale(
|
||||
to == GameFlowState::Running || to == GameFlowState::Loading
|
||||
? 1.0f : 0.0f);
|
||||
|
||||
ENGINE_INFO("GameFlow: {} -> {}", StateName(from), StateName(to));
|
||||
for (const Listener& l : m_Listeners) l(from, to);
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
Reference in New Issue
Block a user