新增 GameFlow 运行时状态机:PIE 与独立运行统一生命周期

Stopped→Loading→Running↔Paused 单一状态源,编辑器 Play/Stop
与 main.cpp 独立运行共用同一套加载/恢复/暂停语义
This commit is contained in:
2026-10-08 17:02:09 +08:00
parent f73e52926f
commit f327f44269
7 changed files with 329 additions and 62 deletions
+5
View File
@@ -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;
+71
View File
@@ -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
+31 -6
View File
@@ -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 对账仍跑(拖静态体照常同步)
m_Physics->Step(frame.Dt());
// 吃 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 脚本自然停摆)
m_Scripts->Update(frame.Dt());
// 脚本只在 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);
+97
View File
@@ -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