游戏引擎从零开始(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 commit 、 add layers second commit

相关推荐
weixin1997010801630 分钟前
官方授权通道和灰色爬虫,做数据服务该选哪条路(附 python 源码)
c++·爬虫·python
wuminyu35 分钟前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++
Tairitsu_H1 小时前
[C++高阶数据结构·图论上] 最小生成树:为什么“选边”和“选点”是两条路?
数据结构·c++·图论·最小生成树
UWA1 小时前
多人战斗中Lua持续高耗时,是瞬时峰值还是高频调用在作祟
性能优化·lua·游戏开发
Bczheng11 小时前
VS2022 安装MFC和QT
c++·qt·mfc
dialect_2 小时前
CODEFORCES ROUND 1127 DIV2
开发语言·c++·算法
悠仁さん10 小时前
【C++】AVL 树
c++
wuminyu10 小时前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++
小徐不徐说10 小时前
序列化与反序列化:把 C++ 对象可靠地保存下来
开发语言·数据库·c++·php
ragnwang11 小时前
Notes: Python pybind11 import C++ module
开发语言·c++·python