跟着cherno手搓游戏引擎【3】事件系统和预编译头文件

不多说了直接上代码,课程中的架构讲的比较宽泛,而且有些方法写完之后并未测试。所以先把代码写完。理解其原理,未来使用时候会再此完善此博客。

文件架构:

Event.h:核心基类

#pragma once
#include"../Core.h"
#include<string>
#include<functional>
namespace YOTO {
	// Hazel中的事件当前是阻塞的,这意味着当一个事件发生时,它立即被分派,必须立即处理。
	//将来,一个更好的策略可能是在事件总线中缓冲事件,并在更新阶段的“事件”部分处理它们。
	//事件类型
	enum class EventType
	{
		None = 0,
		WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved,
		AppTick, AppUpdate, AppRender,
		KeyPressed, KeyReleased,
		MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled
	};
	//事件分类
	enum EventCategory {
		None = 0,
		EventCategoryApplication = BIT(0),
		EventCategoryInput = BIT(1),
		EventCategoryKeyboard = BIT(2),
		EventCategoryMouse = BIT(3),
		EventCategoryMouseButton = BIT(4)
	};
	//定义一些重复的重写的函数,只需要传入固定参数,就能少些一大部分代码
#define EVENT_CLASS_TYPE(type) static EventType GetStaticType(){return EventType::##type; }\
								virtual EventType GetEventType() const override {return GetStaticType();}\
								virtual const char* GetName() const override {return #type;}
#define EVENT_CLASS_CATEGORY(category) virtual int GetCategoryFlags() const override {return category; }
	/// <summary>
	/// 事件基类,事件需要继承此类
	/// </summary>
	class Event
	{
	public:
		//获得该事件的具体类型
		virtual EventType GetEventType() const = 0;
		//获得事件的名称
		virtual const char* GetName() const = 0;
		//获得该事件的大类型
		virtual int GetCategoryFlags() const = 0;
		//toString方法,返回事件名
		virtual std::string ToString() const { return GetName(); }
		//判断该事件是否是category类
		inline bool IsInCategory(EventCategory category) {
			return GetCategoryFlags() & category;
		}

	protected:
		//事件是否被处理了
		bool m_Handled = false;
	};
	/// <summary>
	/// 事件拦截器
	/// </summary>
	class EventDispatcher
	{
		template <typename T>
		using EventFn = std::function<bool(T&)>;

	public:
		EventDispatcher(Event& event)
			:m_Event(event) {

		}
		template <typename T>
		bool Dispatch(EventFn<T> func) {
			if (m_Event.GetEventType() == T::GetStaticType()) {
				m_Event.m_Handled = func(*(T*)&m_Event);
				return true;
			}
			return false;
		}

	private:
		Event& m_Event;
	};
	//暂无测试,方便输出
	inline std::ostream& operator<<(std::ostream& os, const Event& e){
		return  os << e.ToString();
	}



}

 ApplicationEvent.h:处理appwindow的各种事件

#pragma once
#include"Event.h"
#include<sstream>
namespace YOTO {
	class YOTO_API WindowResizeEvent :public Event {
	public:
		WindowResizeEvent(unsigned int width, unsigned int height) :m_Width(width), m_Height(height) {}
		inline unsigned int GetWidth() const { return m_Width; }
		inline unsigned int GetHeight() const { return m_Height; }
		std::string ToString() const override {
			std::stringstream ss;
			ss << "窗口大小改变事件:" << m_Width << "," << m_Height;
			return ss.str();
		}
		EVENT_CLASS_TYPE(WindowResize)
		EVENT_CLASS_CATEGORY(EventCategoryApplication)
	private:
		unsigned int m_Width, m_Height;
	};
	class YOTO_API WindowCloseEvent :public Event
	{
	public:
		WindowCloseEvent(){}
		EVENT_CLASS_TYPE(WindowClose)
		EVENT_CLASS_CATEGORY(EventCategoryApplication)
	};
	class YOTO_API AppTickEvent :public Event {
	public:
		AppTickEvent(){}
		EVENT_CLASS_TYPE(AppTick)
		EVENT_CLASS_CATEGORY(EventCategoryApplication)
	};
	class YOTO_API AppUpdateEvent :public Event {
	public:
		AppUpdateEvent(){}
		EVENT_CLASS_TYPE(AppUpdate)
		EVENT_CLASS_CATEGORY(EventCategoryApplication)
	};
	class YOTO_API AppRenderEvent :public Event {
	public:
		AppRenderEvent() {}
		EVENT_CLASS_TYPE(AppRender)
		EVENT_CLASS_CATEGORY(EventCategoryApplication)
	};
}

KeyEvent.h:处理键盘事件

#pragma once
#include"Event.h"
#include<sstream>
namespace YOTO {
	class YOTO_API KeyEvent:public Event
	{
	public:
		inline int GetKeyCode() const { return m_KeyCode; }
		EVENT_CLASS_CATEGORY(EventCategoryKeyboard | EventCategoryInput)
	protected:
		KeyEvent(int keycode)
			:m_KeyCode(keycode){}
		int m_KeyCode;

	};

	class YOTO_API KeyPressedEvent :public KeyEvent
	{
	public:
		KeyPressedEvent(int keycode, int repeatCount)
			:KeyEvent(keycode),m_RepeatCount(repeatCount){}
		inline int GetRepeatCount() const { return m_RepeatCount; }
		std::string ToString() const override{
			std::stringstream ss;
			ss << "键盘按下事件:" << m_KeyCode << "(" << m_RepeatCount << "重复)";
			return ss.str();
		}
		//static EventType GetStaticType() { return EventType::KeyPressed; }
		//virtual EventType GetEventType()const override { return GetStaticType(); }
		//virtual const char* GetName()const override { return "KeyPressed"; }
		EVENT_CLASS_TYPE(KeyPressed)
	private:
		int m_RepeatCount;

	};
	class KeyReleasedEvent:public KeyEvent
	{
	public:
		KeyReleasedEvent(int keycode)
		:KeyEvent(keycode){

		}
		std::string ToString()const override {
			std::stringstream ss;
			ss << "键盘释放事件:" << m_KeyCode;
			return ss.str(); 
		}
	EVENT_CLASS_TYPE(KeyReleased)
			
	};

}

 MouseEvent.h:处理鼠标事件

#pragma once
#include"Event.h"
#include<sstream>
namespace YOTO {

	class YOTO_API MouseMovedEvent :public Event
	{
	public:
		MouseMovedEvent(float x, float y)
			:m_MouseX(x), m_MouseY(y) {}
		inline float GetX() const { return m_MouseX; }
		inline float GetY() const { return m_MouseY; }
		std::string ToString() const override {
			std::stringstream ss;
			ss << "鼠标移动事件:" << m_MouseX << "," << m_MouseY;
			return ss.str();
		}
		EVENT_CLASS_TYPE(MouseMoved)
		EVENT_CLASS_CATEGORY(EventCategoryMouse |EventCategoryInput )
	private:
		float m_MouseX, m_MouseY;
	};

	class YOTO_API MouseScrolledEvent :public Event
	{
	public:
		MouseScrolledEvent(float x, float y)
			:m_XOffset(x), m_YOffset(y) {}
		inline float GetXOffset() const { return m_XOffset; }
		inline float GetYOffset() const { return m_YOffset; }
		std::string ToString() const override {
			std::stringstream ss;
			ss << "鼠标滚动事件:" << GetXOffset() << "," << GetYOffset();
			return ss.str();
		}
		EVENT_CLASS_TYPE(MouseScrolled)
			EVENT_CLASS_CATEGORY(EventCategoryMouse | EventCategoryInput)
	private:
		float m_XOffset, m_YOffset;
	};

	class YOTO_API MouseButtonEvent :public Event
	{
	public:
		inline int GetMouseButton() const { return m_Button; }
		EVENT_CLASS_CATEGORY(EventCategoryMouse | EventCategoryInput)

	protected:
		MouseButtonEvent(int button)
			:m_Button(button){}
		int m_Button;

	};
	class YOTO_API MouseButtonPressedEvent :public MouseButtonEvent
	{
	public:
		MouseButtonPressedEvent(int button) 
			:MouseButtonEvent(button) {}
		std::string ToString() const override {
			std::stringstream ss;
			ss << "鼠标按下事件:" << m_Button;
			return ss.str();
		}
	EVENT_CLASS_TYPE(MouseButtonPressed)
	};
	class YOTO_API MouseButtonReleasedEvent :public MouseButtonEvent
	{
	public:
		MouseButtonReleasedEvent(int button)
			:MouseButtonEvent(button) {}
		std::string ToString() const override {
			std::stringstream ss;
			ss << "鼠标松开事件:" << m_Button;
			return ss.str();
		}
		EVENT_CLASS_TYPE(MouseButtonReleased)
	};
}

说白了就是写了个基类,然后写了实现类。

修改:

Core.h

#pragma once
//用于dll的宏
#ifdef YT_PLATFORM_WINDOWS
#ifdef YT_BUILD_DLL
#define YOTO_API __declspec(dllexport) 
#else
#define YOTO_API __declspec(dllimport) 

#endif // DEBUG
#else
#error YOTO_ONLY_SUPPORT_WINDOWS
#endif // YOTO_PLATFORM_WINDOWS
#define BIT(x)(1<<x)

记得加Event.h的头文件

premake5.lua

workspace "YOTOEngine"		-- sln文件名
	architecture "x64"	
	configurations{
		"Debug",
		"Release",
		"Dist"
	}
-- https://github.com/premake/premake-core/wiki/Tokens#value-tokens
-- 组成输出目录:Debug-windows-x86_64
outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}"

project "YOTOEngine"		--Hazel项目
	location "YOTOEngine"--在sln所属文件夹下的Hazel文件夹
	kind "SharedLib"--dll动态库
	language "C++"
	targetdir ("bin/" .. outputdir .. "/%{prj.name}") -- 输出目录
	objdir ("bin-int/" .. outputdir .. "/%{prj.name}")-- 中间目录

	-- 包含的所有h和cpp文件
	files{
		"%{prj.name}/src/**.h",
		"%{prj.name}/src/**.cpp"
	}
	-- 包含目录
	includedirs{
		"%{prj.name}/src",
		"%{prj.name}/vendor/spdlog-1.x/include"
	}
	-- 如果是window系统
	filter "system:windows"
		cppdialect "C++17"
		-- On:代码生成的运行库选项是MTD,静态链接MSVCRT.lib库;
		-- Off:代码生成的运行库选项是MDD,动态链接MSVCRT.dll库;打包后的exe放到另一台电脑上若无这个dll会报错
		staticruntime "On"	
		systemversion "latest"	-- windowSDK版本
		-- 预处理器定义
		defines{
			"YT_PLATFORM_WINDOWS",
			"YT_BUILD_DLL"
		}
		-- 编译好后移动Hazel.dll文件到Sandbox文件夹下
		postbuildcommands{
			("{COPY} %{cfg.buildtarget.relpath} ../bin/" .. outputdir .. "/Sandbox")
		}
	-- 不同配置下的预定义不同
	filter "configurations:Debug"
		defines "YT_DEBUG"
		symbols "On"

	filter "configurations:Release"
		defines "YT_RELEASE"
		optimize "On"

	filter "configurations:Dist"
		defines "YT_DIST"
		optimize "On"

project "Sandbox"
	location "Sandbox"
	kind "ConsoleApp"
	language "C++"

	targetdir ("bin/" .. outputdir .. "/%{prj.name}")
	objdir ("bin-int/" .. outputdir .. "/%{prj.name}")

	files{
		"%{prj.name}/src/**.h",
		"%{prj.name}/src/**.cpp"
	}
	-- 同样包含spdlog头文件
	includedirs{
		"YOTOEngine/vendor/spdlog-1.x/include",
		"YOTOEngine/src"
	}
	-- 引用hazel
	links{
		"YOTOEngine"
	}

	filter "system:windows"
		cppdialect "C++17"
		staticruntime "On"
		systemversion "latest"

		defines{
			"YT_PLATFORM_WINDOWS"
		}

	filter "configurations:Debug"
		defines "YT_DEBUG"
		symbols "On"

	filter "configurations:Release"
		defines "YT_RELEASE"
		optimize "On"

	filter "configurations:Dist"
		defines "YT_DIST"
		optimize "On"

测试: 

Application.cpp

#include "Application.h"
#include"Event/ApplicationEvent.h"
#include"Log.h"
namespace YOTO {
	Application::Application() {

	}
	Application::~Application() {

	}
	void Application::Run() {
		WindowResizeEvent e(1280, 720);
		if (e.IsInCategory(EventCategoryApplication)) {
			YT_CORE_TRACE(e);
		}
		if (e.IsInCategory(EventCategoryInput)) {
			YT_CORE_ERROR(e);
		}

		while (true)
		{

		}
	}
}

 预编译头文件:

ytpch.h

#pragma once
#include<iostream>
#include<memory>
#include<utility>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
#include<unordered_map>
#include<unordered_set>
#include<sstream>
#ifdef YT_PLATFORM_WINDOWS
#include<Windows.h>
#endif // YT_PLATFORM_WINDOWS


ytpch.cpp

#include "ytpch.h"

premake5.lua:

workspace "YOTOEngine"		-- sln文件名
	architecture "x64"	
	configurations{
		"Debug",
		"Release",
		"Dist"
	}
-- https://github.com/premake/premake-core/wiki/Tokens#value-tokens
-- 组成输出目录:Debug-windows-x86_64
outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}"

project "YOTOEngine"		--Hazel项目
	location "YOTOEngine"--在sln所属文件夹下的Hazel文件夹
	kind "SharedLib"--dll动态库
	language "C++"
	targetdir ("bin/" .. outputdir .. "/%{prj.name}") -- 输出目录
	objdir ("bin-int/" .. outputdir .. "/%{prj.name}")-- 中间目录
	pchheader "ytpch.h"
	pchsource "YOTOEngine/src/ytpch.cpp"
	-- 包含的所有h和cpp文件
	files{
		"%{prj.name}/src/**.h",
		"%{prj.name}/src/**.cpp"
	}
	-- 包含目录
	includedirs{
		"%{prj.name}/src",
		"%{prj.name}/vendor/spdlog-1.x/include"
	}
	-- 如果是window系统
	filter "system:windows"
		cppdialect "C++17"
		-- On:代码生成的运行库选项是MTD,静态链接MSVCRT.lib库;
		-- Off:代码生成的运行库选项是MDD,动态链接MSVCRT.dll库;打包后的exe放到另一台电脑上若无这个dll会报错
		staticruntime "On"	
		systemversion "latest"	-- windowSDK版本
		-- 预处理器定义
		defines{
			"YT_PLATFORM_WINDOWS",
			"YT_BUILD_DLL"
		}
		-- 编译好后移动Hazel.dll文件到Sandbox文件夹下
		postbuildcommands{
			("{COPY} %{cfg.buildtarget.relpath} ../bin/" .. outputdir .. "/Sandbox")
		}
	-- 不同配置下的预定义不同
	filter "configurations:Debug"
		defines "YT_DEBUG"
		symbols "On"

	filter "configurations:Release"
		defines "YT_RELEASE"
		optimize "On"

	filter "configurations:Dist"
		defines "YT_DIST"
		optimize "On"

project "Sandbox"
	location "Sandbox"
	kind "ConsoleApp"
	language "C++"

	targetdir ("bin/" .. outputdir .. "/%{prj.name}")
	objdir ("bin-int/" .. outputdir .. "/%{prj.name}")

	files{
		"%{prj.name}/src/**.h",
		"%{prj.name}/src/**.cpp"
	}
	-- 同样包含spdlog头文件
	includedirs{
		"YOTOEngine/vendor/spdlog-1.x/include",
		"YOTOEngine/src"
	}
	-- 引用hazel
	links{
		"YOTOEngine"
	}

	filter "system:windows"
		cppdialect "C++17"
		staticruntime "On"
		systemversion "latest"

		defines{
			"YT_PLATFORM_WINDOWS"
		}

	filter "configurations:Debug"
		defines "YT_DEBUG"
		symbols "On"

	filter "configurations:Release"
		defines "YT_RELEASE"
		optimize "On"

	filter "configurations:Dist"
		defines "YT_DIST"
		optimize "On"

把所有使用库文件的地方都换成#include"ytpch.h"即可