层的概念
层是一个用于封装功能模块、输入处理、渲染调用等逻辑的抽象单元。
每个 Layer 可以代表一个子系统,比如:
- UI 层(处理 UI 渲染和输入)
- 游戏逻辑层(GameLayer)
- 调试层(ImGuiLayer)
- 特效层(例如 ParticleLayer)
Layer 之间可以通过栈或队列有序管理,按照优先级依次更新和渲染。
层的作用
- 模块化组织代码:每个 Layer 独立负责一类逻辑,便于开发与维护。
2. 控制生命周期
Layer 通常具有统一的接口,如:
virtual void OnAttach(); // 初始化
virtual void OnDetach(); // 清理资源
virtual void OnUpdate(float deltaTime); // 每帧更新
virtual void OnRender(); // 渲染调用
virtual void OnEvent(Event& e); // 输入事件响应
3. 事件传递机制
常配合事件分发系统使用,比如:从顶层 Layer 开始向下传递事件,直到某个 Layer 拦截处理。
4. 支持临时功能插入(例如 Debug GUI)
可以临时 Push 一个 Layer 来展示调试界面或测试功能,不干扰主逻辑。
层的模块独立性
对于一个理想的游戏引擎,层与层之间逻辑相互独立是非常有必要的。这是现代游戏引擎模块化、解耦设计的体现之一。
- 层的功能划分与设计理念
每个层都是一个独立的功能模块,分别封装了自己的状态、生命周期、逻辑、渲染、事件处理等内容,并且用于实现不同功能:
| Layer | 功能 |
|---|---|
| GameLayer | 管理游戏世界、实体、逻辑更新 |
| UILayer | 渲染 UI、处理按钮点击 |
| ImGuiLayer | 调试用的 GUI |
| PauseLayer | 游戏暂停逻辑、菜单控制 |
它们各自实现自己的接口,互不依赖,互不调用对方的逻辑。
2. 层栈机制与事件的传递分发
由于层与层之间相互独立,所以需要一个层栈(Layer Stack)来将它们组织起来,从而实现统一调度。
层栈至少应该实现以下功能:
- 保证 UI 在 GameLayer 之上渲染
- 保证调试层永远在最上面
- 控制事件、更新、渲染的顺序性
当一个事件出现时,游戏引擎会通过事件系统从层栈中,自上而下传递事件:
for (auto it = m_LayerStack.rbegin(); it != m_LayerStack.rend(); ++it) {
(*it)->OnEvent(e);
if (e.Handled) break;
}
比如:
- 一个点击事件,先传到
ImGuiLayer,它判断鼠标点在了某个 UI 按钮上,处理并将e.Handled = true。 - 如果不是 UI 按钮,事件继续传给
GameLayer,用于选中游戏角色。
3. 易于插拔、调试与拓展
- 你可以临时插入一个
DebugOverlayLayer显示帧率,而不改动主逻辑。 - 你可以单独测试一个 Layer,比如 UI 或 Game Layer,在其他 Layer 被禁用的环境下运行。
- 做 Mod 系统时,开发者可以通过扩展 Layer 来添加功能,而不用直接修改引擎核心代码。
层的架构设计
- 层的基类(Layer)
class Layer
{
public:
Layer(const std::string& name = "Layer");
virtual ~Layer();
virtual void OnAttach() {}
virtual void OnDetach() {}
virtual void OnUpdate() {}
virtual void OnImGuiRender() {}
virtual void OnEvent(Event& event) {}
inline const std::string& GetName() const { return m_DebugName; }
protected:
std::string m_DebugName;
};
2. 层栈(LayerStack)
层栈的实现并不是一定要用栈的数据结构,主要原因在于有些层需要始终处于层栈的最上方,比如:UI层,而有些层只需要按照插入顺序排列即可。并且由于在游戏引擎中并不会频繁地插入或删除某一个层,所以可以实现随机访问的Vector是一个非常好的选择。
由于我们使用Vector来实现层栈,所以需要一个index变量来记录当前可插入的位置。并且对于函数Push()和Pop()的实现,需要建立一个特殊函数以用于将某一层放入层栈最上方,所以就有了函数PushOverlay()和PopOverlay()。
class LayerStack
{
public:
LayerStack();
~LayerStack();
void PushLayer(Layer* layer);
void PushOverlay(Layer* overlay);
void PopLayer(Layer* layer);
void PopOverlay(Layer* overlay);
std::vector<Layer*>::iterator begin() { return m_Layers.begin(); }
std::vector<Layer*>::iterator end() { return m_Layers.end(); }
private:
std::vector<Layer*> m_Layers;
unsigned int m_LayerInsertIndex = 0;
};

Leave a comment