游戏引擎从零开始(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

相关推荐
还债大湿兄2 小时前
《C++内存泄漏8大战场:Qt/MFC实战详解 + 面试高频陷阱破解》
c++·qt·mfc
珊瑚里的鱼5 小时前
LeetCode 692题解 | 前K个高频单词
开发语言·c++·算法·leetcode·职场和发展·学习方法
AI+程序员在路上5 小时前
QTextCodec的功能及其在Qt5及Qt6中的演变
开发语言·c++·qt
Risehuxyc5 小时前
C++卸载了会影响电脑正常使用吗?解析C++运行库的作用与卸载后果
开发语言·c++
景彡先生8 小时前
C++编译期计算:常量表达式(constexpr)全解析
服务器·c++
tan77º9 小时前
【Linux网络编程】应用层自定义协议与序列化
linux·运维·服务器·网络·c++·tcp/ip
悠哉清闲9 小时前
Android Studio C++/JNI/Kotlin 示例 三
c++·kotlin·android studio
AI迅剑10 小时前
模块三:现代C++工程实践(4篇)第二篇《性能调优:Profile驱动优化与汇编级分析》
汇编·c++
byte轻骑兵11 小时前
【Bluedroid】蓝牙协议栈enable流程深度解析
android·c++·bluedroid
Jooolin12 小时前
【C++】: list介绍以及模拟实现
c++·ai编程·编程语言