游戏引擎从零开始(7)-图层

前言

游戏中的元素并不会平铺到一层上处理,而是分到不同的层级中。有点像photoshop中的图层。

一个点击事件到底是触发前面的按钮,还是触发后面的人物模型动画呢?

按优先级,应该是优先按钮消费,如果所有元素平铺到同一层上处理,逻辑就耦合到一起。通过分层,按照优先级,上面的层级优先消费,不消费再继续传到下一层。这样设计结构更清晰、也更容易拓展。

成熟的引擎,或者客户端平台比如Android、iOS,都有层级、布局的概念,帮开发者封装好了事件传递机制,不需要开发者关心事件的优先级。

Layer & LayerStack

Layer的处理流程:

Window->SandboxApp->LayerStack->Layer

类调用关系:

Layer

Layer是基类,设计5个函数,主要作用是接收事件和更新。真正的操作在派生类里实现。

Sandbox/Hazel/src/Hazel/Layer.h

c++ 复制代码
#pragma once

#include "Hazel/Core.h"
#include "Hazel/Events/Event.h"

namespace Hazel {

	class HAZEL_API Layer
	{
	public:
		Layer(const std::string& name = "Layer");
		virtual ~Layer();

		virtual void OnAttach() {}
		virtual void OnDetach() {}
		virtual void OnUpdate() {}
		virtual void OnEvent(Event& event) {}

		inline const std::string& GetName() const { return m_DebugName; }
	protected:
		std::string m_DebugName;
	};

}

Sandbox/Hazel/src/Hazel/Layer.cpp 现在是空实现

c++ 复制代码
#include "hzpch.h"
#include "Layer.h"

namespace Hazel {

	Layer::Layer(const std::string& debugName)
		: m_DebugName(debugName)
	{}

	Layer::~Layer()
	{}
}

LayerStack

用Vector容器实现,封装了PushLayer、PushOverlay、PopLayer、PopOverlay的操作。

Overlay层放到队列末尾,对vector从后往前遍历,则Overlay层优先处理,相当于盖在最上面。

Sandbox/Hazel/src/Hazel/LayerStack.h

c++ 复制代码
#pragma once

#include "Hazel/Core.h"
#include "Layer.h"

#include <vector>

namespace Hazel {

	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;
		std::vector<Layer*>::iterator m_LayerInsert;
	};

}

LayerStack.cpp中对push pop的实现

Sandbox/Hazel/src/Hazel/LayerStack.cpp

c++ 复制代码
#include "LayerStack.h"
namespace Hazel{
    LayerStack::LayerStack() {
        m_LayerInsert = m_Layers.begin();
    }

    LayerStack::~LayerStack() {
        for(Layer* layer : m_Layers){
            delete layer;
        }
    }

    void LayerStack::PushLayer(Layer *layer) {
        m_LayerInsert = m_Layers.emplace(m_LayerInsert, layer);
    }

    void LayerStack::PushOverlay(Layer *overlay) {
        m_Layers.emplace_back(overlay);
    }

    void LayerStack::PopLayer(Layer *layer) {
        auto it = std::find(m_Layers.begin(), m_Layers.end(), layer);
        if (it != m_Layers.end()) {
            m_Layers.erase(it);
            m_LayerInsert--;
        }
    }

    void LayerStack::PopOverlay(Layer *overlay) {
        auto it = std::find(m_Layers.begin(), m_Layers.end(), overlay);
        if (it != m_Layers.end()) {
            m_Layers.erase(it);
        }
    }
} 

Application中添加LayerStack

头文件增加声明: Sandbox/Hazel/src/Hazel/Application.h

c++ 复制代码
#include "LayerStack.h"
namespace Hazel{
class Application {
public:
    ...
    void PushLayer(Layer* layer);
    void PushOverlay(Layer* layer);

private:
    ...
    LayerStack m_LayerStack;
};

对应实现: Sandbox/Hazel/src/Hazel/Application.cpp

c++ 复制代码
void Application::PushLayer(Layer *layer) {
    m_LayerStack.PushLayer(layer);
}

void Application::PushOverlay(Layer *layer) {
    m_LayerStack.PushOverlay(layer);
}

OnEvent中,透传给LayerStack,只要有一个layer消费掉了event,该事件就不在往下传递了。

c++ 复制代码
 void Application::OnEvent(Event &e) {
    ...
    for (auto it = m_LayerStack.end();  it != m_LayerStack.begin();) {
        (*--it)->OnEvent(e);
        if (e.Handled) {
            break;
        }
    }
} 

有些场景下,是允许多个layer都要接收event事件的,此时前面layer消费event时就不要将该event的Handled置为true。本节layer的实现中还没有对event做处理,默认都不消费event.

更新Hazel.h

在统一的对外头文件Hazel.h中增加Layer.h,保证业务方能拿到layer类

Sandbox/Hazel/src/Hazel.h

arduino 复制代码
#include <stdio.h>
...
#include "Layer.h"
...

更新Hazel引擎的CMake文件

Sandbox/Hazel/CMakeLists.txt

css 复制代码
project(hazel)
set(SRC_LIST
  ...
  src/Hazel/Layer.h src/Hazel/Layer.cpp
  src/Hazel/LayerStack.cpp src/Hazel/LayerStack.h)

CMakeLists.txt中将.h文件也包含到需要编译的源码中,否则,IDE中有"非项目文件"的提示,很烦~

SandBoxApp中增加Layer测试代码

增加Layer的实现类ExamleLayer,在OnUpdate和OnEvent中打印日志.

Sandbox/src/SandBoxApp.cpp

c++ 复制代码
class ExampleLayer : public Hazel::Layer {
public:
  ExampleLayer() : Layer("Example") {

  }

  void OnUpdate() override{
      HZ_INFO("ExampleLayer::Update");
  }

  void OnEvent(Hazel::Event& event) override {
      HZ_TRACE("ExampleLayer::OnEvent {0}", event.ToString());
  }
}; 
  
class Sandbox : public Hazel::Application {
public:
    Sandbox(){
        PushOverlay(new ExampleLayer());
    }

实现效果、完整代码

OK,run起来,可以看到ExampleLayer的日志

这次代码分两次提交,参考 add layers first commitadd layers second commit

相关推荐
「QT(C++)开发工程师」1 小时前
C++ std::move 详解
开发语言·c++
豆沙沙包?4 小时前
C++~~~set容器、map容器(p57-P69)
开发语言·c++
qeen874 小时前
【数据结构】哈希表的C++实现与封装
数据结构·c++·散列表·哈希表
乐观勇敢坚强的老彭6 小时前
C++信奥GESP四级的常见题型和解题模板速查表
开发语言·c++
欧特克_Glodon6 小时前
OpenCV计算机视觉开发入门与实践<二十二>:图像平滑之线性滤波
c++·人工智能·opencv·计算机视觉
沙盘客7 小时前
AFSIM 示例解读(09)· 传感器全家桶 sensor_demos(下):ESM / SAR / 被动测向
c++·经验分享·后端
大鹏的NLP博客7 小时前
WSL2 资源治理:从 ONNX Runtime CUDA 编译 OOM 到 `.wslconfig` 防御配置
c++·深度学习·onnx runtime
星星.7227 小时前
C++算法竞赛|二分查找与二分答案:边界模板、浮点二分、STL
数据结构·c++·算法
老当益壮梁奶奶8 小时前
Linux 软件编程学习笔记(六):线程编程入门与实践
linux·c语言·c++·笔记·学习
小小龙学IT8 小时前
Dear ImGui 开源即时模式 GUI 库深度解析
c++·ui·开源