Hazel游戏引擎(013)Layers游戏的层级

news/2024/11/24 21:02:45/

文中若有代码、术语等错误,欢迎指正

文章目录

  • 前言
  • 增加Layer后的主要类图
  • 项目相关
    • 代码
    • 项目流程
    • 效果
  • LayerStack类的错误

前言

  • 此节目的

    • 为完成008事件系统设计的第四步,将事件从Application传递分发给Layer层。

      请添加图片描述

    • 使引擎事件系统模块完整

  • Layer的理解

    想象同Ps中一张图有多个层级,可以在层级上绘制图画

  • Layer的设计

    • 数据结构:vector

    • 渲染顺序

      从前往后渲染各个层的图像,这样后面渲染的会覆盖前面渲染的图像,在屏幕的最顶层。

    • 处理事件顺序

      从后往前依次处理事件,当一个事件被一个层处理完不会传递给前一个层,结合渲染顺序,这样在屏幕最顶层的(也就是在vector最后的layer)图像最先处理事件。

    • 例子解释

      比如常见的3D游戏有UI。

      渲染顺序:将3D图形先渲染,再渲染2DUI,这样屏幕上2DUI永远在3D图形上方,显示正确;

      事件顺序:点击屏幕的图形,应该是2DUI最先处理,如果是相应UI事件,处理完后不传递给前一个3D层,若不是自己的UI事件,才传递给前一个3D层。

    请添加图片描述

增加Layer后的主要类图

注意区分LayerStack、Layer以及ExampleLayer

  • LayerStack

    管理Layer层的类

  • Layer

    所有层的父类,定义了虚函数

  • Examplayer

    真正需要更新和处理事件的层,被添加到LayerStack的vector中

项目相关

代码

  • Layer

    #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;};
    }
    
  • LayerStack

    #pragma once
    namespace Hazel {class HAZEL_API LayerStack{public:LayerStack();~LayerStack();void PushLayer(Layer* layer);	// vector在头部添加一个层void PushOverlay(Layer* overlay);// 在vector末尾添加一个覆盖层,在屏幕的最上方的层void PopLayer(Layer* layer);	// vector弹出指定层void PopOverlay(Layer* overlay);// vector弹出覆盖层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;};
    }
    
    namespace Hazel {LayerStack::LayerStack(){m_LayerInsert = m_Layers.begin();}LayerStack::~LayerStack(){for (Layer* layer : m_Layers)delete layer;}void LayerStack::PushLayer(Layer* layer){// emplace在vector容器指定位置之前插入一个新的元素。返回插入元素的位置// 插入 1 2 3,vector是 3 2 1m_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--;	// 指向Begin}}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);}
    }
    
  • SandboxApp

    class ExampleLayer : public Hazel::Layer{
    public:ExampleLayer(): Layer("Example"){}void OnUpdate() override{HZ_INFO("ExampleLayer::Update");	// 最终会被输出}void OnEvent(Hazel::Event& event) override{HZ_TRACE("{0}", event);	// 最终会被输出}
    };
    class Sandbox : public Hazel::Application{
    public:Sandbox(){PushLayer(new ExampleLayer());}~Sandbox(){}
    };
    
  • Application

    void Application::PushLayer(Layer* layer){m_LayerStack.PushLayer(layer);
    }void Application::PushOverlay(Layer* layer){m_LayerStack.PushOverlay(layer);
    }
    // 回调glfw窗口事件的函数
    void Application::OnEvent(Event& e){// 4.用事件调度器,拦截自己层想要拦截的事件并处理EventDispatcher dispatcher(e);dispatcher.Dispatch<WindowCloseEvent>(BIND_EVENT_FN(OnWindowClose));// 从后往前顺序处理事件for (auto it = m_LayerStack.end(); it != m_LayerStack.begin(); ){(*--it)->OnEvent(e);if (e.Handled)// 处理完就不要传入前一个层break;}
    }
    void Application::Run(){while (m_Running){glClearColor(1, 0, 1, 1);glClear(GL_COLOR_BUFFER_BIT);// 从前往后顺序更新层for (Layer* layer : m_LayerStack)layer->OnUpdate();m_Window->OnUpdate();	// 更新glfw}
    }
    

项目流程

  • 文字

    1. Application定义了LayerStack对象m_LayerStack
    2. 在Sandbox构造函数中,执行PushLayer(new ExampleLayer());,将ExampleLayer放入m_LayerStack的vector中
    3. Application的OnEvent函数从后往前顺序遍历m_LayerStack的vector,得到ExampleLayer对象,并把事件e作为参数执行它的OnEvent函数,所以一直在控制台输出窗口事件
    4. Application的OnUpdate函数从前往后遍历m_LayerStack的vector,得到ExampleLayer对象,并执行它的OnUpdate函数,所以一直在控制台输出**“ExampleLayer::Update”**
  • 图示

    以下是以活动图的样子绘制的,并不符合活动图的规范,但大意是这样

    请添加图片描述

效果

请添加图片描述

LayerStack类的错误

LayerStack的vector管理layer有错

  • 简化的例子

    #include <iostream>
    #include <vector>
    using namespace std;void Test1() {vector<int> vec;std::vector<int>::iterator m_LayerInsertIndex = vec.begin();// 头部插入位置// 在头部插入1 2,此时vector 2 1m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 1);m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 2);// 在尾部插入4,  此时vector 2 1 4vec.emplace_back(4);// 在头部插入3,   此时vector并不是 3 2 1 4,而是会报错//m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 3);for (int i = 0; i < vec.size(); i++) {cout << vec[i] << endl; }
    }
    int main() {Test1();return 0;
    }
  • 报错结果

    请添加图片描述

  • 分析原因

    不太准确,个人猜测可能在尾部插入元素(emplace_back)使vector在内存位置发生改变,会破坏m_LayerInsertIndex这个迭代器无效吧

  • 解决方法

    在尾部插入元素(emplace_back)后让m_LayerInsertIndex迭代器重新指向头部

    #include <iostream>
    #include <vector>
    using namespace std;void Test1() {vector<int> vec;std::vector<int>::iterator m_LayerInsertIndex = vec.begin();// 头部插入位置// 在头部插入1 2,此时vector 2 1m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 1);m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 2);// 在尾部插入4,  此时vector 2 1 4vec.emplace_back(4);// 在头部插入3,   此时vector并不是 3 2 1 4,而是会报错m_LayerInsertIndex = vec.begin(); // 需要重新让头部迭代器指向头部m_LayerInsertIndex = vec.emplace(m_LayerInsertIndex, 3);for (int i = 0; i < vec.size(); i++) {cout << vec[i] << endl; }
    }
    int main() {Test1();return 0;
    }
    


http://www.ppmy.cn/news/332320.html

相关文章

Docker常用基本命令

一、docker的基础命令 1、启动docker systemctl start docker 2、关闭docker systemctl stop docker 3、重启docker systemctl restart docker 4、设置docker开机自启动 systemctl enable docker 5 &#xff0c; 查看docker运行状态&#xff08;显示绿色代表正常启动…

MACHENIKE 机械师F117 B1 8代i7独显全面屏游戏本轻薄笔记本电脑怎么样?

机械师F117-B1【猎空】全面屏 立省500元 全面屏 RGB机械键盘 i7-8750H处理器/GTX 1050Ti 4GB/256GB PCIe SSD/金属拉丝机身/RGB 炫彩机械键盘/15.6”超窄边框屏/20mm AC金属拉丝设计/发光logo及CNC钻孔科技蓝灯带/跑分27万 官方活动链接:https://s.click.taobao.com/OhS34Rw …

雷神ZERO游戏本和ROG冰刃5Plus的 区别 选哪个

雷神ZERO搭载 16.0 英寸 16:10 窄边框全面屏&#xff0c;具有 2.5K 高分辨率、165Hz 刷新率&#xff0c;同时支持 100% sRGB 色域&#xff0c;500nit 高亮度。选雷神ZERO游戏本还是ROG冰刃5Plus这些点很重要看过你就懂了http://www.adiannao.cn/dy 此外&#xff0c;该机采用风…

第一次“盲硬件知识”拆机——固态硬盘的新发现

一图胜千言&#xff0c;图1三星固态硬盘&#xff08;型号&#xff1a;Samsung MZVLW256HEHP PM961 256GB M.2 NVMe PCIe Internal SSD - OEM&#xff09;图1 三星固态硬盘 图2 佳翼 X16 PCIE 3.0 m.2 NVME满速M-Key扩展GEN3转接卡pci-e 金金燕MX16 图2 固态硬盘转接卡 最初&a…

Linux查看硬盘属性(机械硬盘/固态硬盘)

通过命令lsblk -d -o name,rota查看&#xff0c;0表示固态硬盘&#xff0c;1表示机械硬盘&#xff0c;sda为机械硬盘&#xff0c;sdb为固态硬盘。

购买固态硬盘的参数

1: 购买固态硬盘的参数参数一: 总线 什么是总线: 总线是&#xff1a; 计算机各种功能部件之间传毒数据信息的公共通信干线。。数据从固态硬盘中拿出来需要走的路。参数二: 硬盘到CPU 之间需要走的路: 总线SATA (乡间小路, 最快550m/s);总线: PCIE (告诉公路, 最快路线) …

笔记本固态硬盘温度测试软件,台式电脑ssd固态硬盘温度多少算正常?查看ssd固态硬盘温度的方法...

‍ ‍   我们都知道ssd固态硬盘即固态电子存储阵列硬盘&#xff0c;其接口的规范和定义、功能及使用方法上与普通硬盘的完全相同&#xff0c;在产品外形和尺寸上也完全与普通硬盘一致。台式电脑ssd固态硬盘温度一直是大家关心的&#xff0c;如果温度太高会断电容易导致数据丢…

固态硬盘是什么接口_5分钟教会你怎么区分M.2固态硬盘接口和协议

M.2接口也叫NGFF&#xff0c;英文全称Next Generation Form Factor&#xff0c;是Intel推出的一种替代MSATA新的接口规范。由于速度更快&#xff0c;体积更小&#xff0c;现在大部分笔记本电脑里都是采用这种硬盘。 一、M.2接口类型 首先我们来了解一下M.2固态硬盘有哪几种接口…