WAYNETS.ORG

Game and Program

层(Layer)

作者:

发表于

Context Polling System Post-Processing Renderer

层的概念

层是一个用于封装功能模块、输入处理、渲染调用等逻辑的抽象单元。

每个 Layer 可以代表一个子系统,比如:

  • UI 层(处理 UI 渲染和输入)
  • 游戏逻辑层(GameLayer)
  • 调试层(ImGuiLayer)
  • 特效层(例如 ParticleLayer)

Layer 之间可以通过栈或队列有序管理,按照优先级依次更新和渲染。

层的作用

  1. 模块化组织代码:每个 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 来展示调试界面或测试功能,不干扰主逻辑。

层的模块独立性

对于一个理想的游戏引擎,层与层之间逻辑相互独立是非常有必要的。这是现代游戏引擎模块化、解耦设计的体现之一。

  1. 层的功能划分与设计理念

每个层都是一个独立的功能模块,分别封装了自己的状态、生命周期、逻辑、渲染、事件处理等内容,并且用于实现不同功能:

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 来添加功能,而不用直接修改引擎核心代码。

层的架构设计

  1. 层的基类(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