A simple game engine based of The Cherno’s Youtube Tutorial Series.
Entry Point
Log
//log.h
#pragma once
#include <memory>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "Core.h"
namespace Hazel {
class HAZEL_API Log
{
public:
Log() {}
~Log() {}
static void Init();
inline static std::shared_ptr<spdlog::logger>& GetCoreLogger() { return m_CoreLogger; }
inline static std::shared_ptr<spdlog::logger>& GetClienteLogger() { return m_ClientLogger; }
private:
static std::shared_ptr<spdlog::logger> m_CoreLogger;
static std::shared_ptr<spdlog::logger> m_ClientLogger;
};
}
#define HZ_CORE_TRACE(...) ::Hazel::Log::GetCoreLogger()->trace(__VA_ARGS__)
#define HZ_CORE_INFO(...) ::Hazel::Log::GetCoreLogger()->info(__VA_ARGS__)
#define HZ_CORE_WARN(...) ::Hazel::Log::GetCoreLogger()->warn(__VA_ARGS__)
#define HZ_CORE_ERROR(...) ::Hazel::Log::GetCoreLogger()->error(__VA_ARGS__)
#define HZ_CORE_FATAL(...) ::Hazel::Log::GetCoreLogger()->fatal(__VA_ARGS__)
#define HZ_TRACE(...) ::Hazel::Log::GetClienteLogger()->trace(__VA_ARGS__)
#define HZ_INFO(...) ::Hazel::Log::GetClienteLogger()->info(__VA_ARGS__)
#define HZ_WARN(...) ::Hazel::Log::GetClienteLogger()->warn(__VA_ARGS__)
#define HZ_ERROR(...) ::Hazel::Log::GetClienteLogger()->error(__VA_ARGS__)
#define HZ_FATAL(...) ::Hazel::Log::GetClienteLogger()->fatal(__VA_ARGS__)
//log.cpp
#include "Log.h"
namespace Hazel {
std::shared_ptr<spdlog::logger> Log::m_CoreLogger;
std::shared_ptr<spdlog::logger> Log::m_ClientLogger;
void Log::Init()
{
spdlog::set_pattern("%^[%T] %n: %v%$");
m_CoreLogger = spdlog::stdout_color_mt("HAZEL");
m_CoreLogger->set_level(spdlog::level::trace);
m_ClientLogger = spdlog::stdout_color_mt("APP");
m_ClientLogger->set_level(spdlog::level::trace);
}
}
premake
-- premake5.lua
workspace "Hazel"
architecture "x64"
configurations
{ "Debug", "Release", "Dist" }
outputdir = "%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}"
project "Hazel"
location "Hazel"
kind "SharedLib"
language "C++"
targetdir ("bin/" .. outputdir .. "/%{prj.name}")
objdir ("bin-int/" .. outputdir .. "/%{prj.name}")
files
{
"%{prj.name}/src/**.h",
"%{prj.name}/src/**.cpp"
}
includedirs
{
"%{prj.name}/vendor/spdlog/include"
}
filter "system:windows"
cppdialect "C++17"
staticruntime "On"
systemversion "10.0.19041.0"
defines
{
"HZ_PLATFORM_WINDOWS",
"HZ_BUILD_DLL"
}
postbuildcommands
{
("{COPY} %{cfg.buildtarget.relpath} ../bin/" .. outputdir .. "/Sandbox")
}
filter "configurations:Debug"
defines "HZ_DEBUG"
symbols "On"
filter "configurations:Release"
defines "HZ_RELEASE"
optimize "On"
filter "configurations:Dist"
defines "HZ_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"
}
includedirs
{
"Hazel/vendor/spdlog/include",
"Hazel/src"
}
links
{
"Hazel"
}
filter "system:windows"
cppdialect "C++17"
staticruntime "On"
systemversion "10.0.19041.0"
defines
{
"HZ_PLATFORM_WINDOWS"
}
filter "configurations:Debug"
defines "HZ_DEBUG"
symbols "On"
filter "configurations:Release"
defines "HZ_RELEASE"
optimize "On"
filter "configurations:Dist"
defines "HZ_DIST"
optimize "On"
precompile headers
修改premake5.lua
Event
//Event.h
#pragma once
#include "hzpch.h"
#include "Hazel/Core.h"
#define BIT(x) 1 << x
namespace Hazel {
// 事件的详细类型
enum class HAZEL_API 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; }\
const EventType GetEventType() const override { return GetStaticType(); }\
const char* GetName() const override { return #type; }
#define EVENT_CLASS_CATEGORY(category) \
virtual int GetCategoryFlag() const override { return category; }
class HAZEL_API Event
{
public:
virtual const char* GetName() const = 0;
virtual const EventType GetEventType() const = 0;
virtual int GetCategoryFlag() const = 0;
virtual std::string ToString() const { return GetName(); }
inline bool IsInCategory(EventCategory type)
{
return GetCategoryFlag() & type;
}
protected:
bool m_Handled = false;//用来标记这个事件是否被处理了
};
class EventDistpatcher
{
template<typename T>
using Evenfn = std::function<bool(T&)>;
public:
EventDistpatcher(Event& event)
:m_Event(event) {}
template<typename T>
bool Dispatch(Evenfn<T> func)
{
if (m_Event.GetEventType == T::GetStaticType)
{
m_Event.m_Handled = func(*(T*)&m_Event);
}
return false;
}
private:
Event& m_Event;
};
}
ApplicationEvent
//ApplicationEvent.h
#pragma once
#include "Event.h"
// Windows和APP相关的Event都在这里处理
namespace Hazel
{
class HAZEL_API WindowCloseEvent :public Event
{
public:
WindowCloseEvent() {}
EVENT_CLASS_TYPE(WindowClose)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
std::string ToString() const override
{
std::stringstream a;
a << "Window Close";
return a.str();
}
protected:
};
class HAZEL_API WindowResizedEvent :public Event
{
public:
WindowResizedEvent(int height, int width) :m_Height(height), m_Width(width) {}
EVENT_CLASS_TYPE(WindowResize)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
inline int GetWindowHeight() { return m_Height; }
inline int GetWindowWidth() { return m_Width; }
std::string ToString() const override
{
std::stringstream a;
a << "Window Resize: width = " << m_Width << ", height = " << m_Height;
return a.str();
}
protected:
int m_Height, m_Width;
};
class HAZEL_API AppTickEvent : public Event
{
public:
AppTickEvent() {}
EVENT_CLASS_TYPE(AppTick)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
class HAZEL_API AppUpdateEvent : public Event
{
public:
AppUpdateEvent() {}
EVENT_CLASS_TYPE(AppUpdate)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
class HAZEL_API AppRenderEvent : public Event
{
public:
AppRenderEvent() {}
EVENT_CLASS_TYPE(AppRender)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
}
KeyEvent
//KeyEvent.h
#pragma once
#include "Event.h"
namespace Hazel {
class HAZEL_API KeyEvent : public Event
{
public:
EVENT_CLASS_CATEGORY(EventCategoryKeyboard | EventCategoryInput)
inline int GetKeycode() const { return m_KeyCode; }
protected:
// 构造函数设为Protected,意味着只有其派生类能够调用此函数
KeyEvent(int code) : m_KeyCode(code) {}
int m_KeyCode;
};
class HAZEL_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 << "KeyPressedEvent: " << m_KeyCode << " (" << m_RepeatCount << " repeats)";
return ss.str();
}
EVENT_CLASS_TYPE(KeyPressed)
private:
int m_RepeatCount;
};
class HAZEL_API KeyReleasedEvent : public KeyEvent
{
public:
KeyReleasedEvent(int keycode)
: KeyEvent(keycode) {}
std::string ToString() const override
{
std::stringstream ss;
ss << "KeyReleasedEvent: " << m_KeyCode;
return ss.str();
}
EVENT_CLASS_TYPE(KeyReleased)
};
}
MouseEvent
//MouseEvent.h
#pragma once
#include "Event.h"
namespace Hazel {
class MouseMovedEvent : public Event
{
public:
static EventType GetStaticType() { return EventType::MouseMoved; }
const EventType GetEventType() const override { return GetStaticType(); }
const char* GetName() const override { return "MouseMoved"; }
std::string ToString()
{
// Create a string with represents
std::string a = "MouseMovedEvent: xOffset = " + GetXOffset() + ", yOffset = " + GetYOffset();
return a;
}
inline float GetXOffset() const { return m_xOffset; }
inline float GetYOffset() const { return m_yOffset; }
private:
float m_xOffset, m_yOffset;
};
}
GLFW
然后GLFW文件夹添加一个premake5.lua文件
Window.h
#include "hzpch.h"
#include "Event/Event.h"
#include "Core.h"
namespace Hazel {
struct HAZEL_API WindowProps {
public:
std::string m_title;
unsigned int m_width;
unsigned int m_height;
WindowProps(const std::string& title = "Hazel Engine", unsigned int width = 1280, unsigned int height = 720)
:m_title(title), m_width(width), m_height(height) {
}
};
class HAZEL_API Window
{
public:
using EventCallbackFn = std::function<void(Event&)>;
virtual ~Window() = default;
virtual int GetWindowWidth() const = 0;
virtual int GetWindowHeight() const = 0;
virtual bool IsVSync() const = 0;
virtual void SetVSync(bool) = 0;
virtual void OnUpdate() = 0;
virtual void OnResized(unsigned int width, unsigned int height) = 0;
//virtual void* GetNaiveWindow() = 0;
virtual void SetEventCallback(const EventCallbackFn& callback) = 0;
static Window* Create(const WindowProps& props = WindowProps());
};
}
WindowsWindow.h
#pragma once
#include "Hazel/Window.h"
#include <GLFW/glfw3.h>
namespace Hazel {
class WindowsWindow : public Window
{
public:
WindowsWindow(const WindowProps& props);
~WindowsWindow() override;
int GetWindowWidth() const override { return m_Data.width; }
int GetWindowHeight() const override { return m_Data.height; }
bool IsVSync() const override { return m_Data.isVSync; }
void SetVSync(bool enabled) override;
void OnUpdate() override;
void OnResized(unsigned int width, unsigned int height) override;
//void* GetNaiveWindow() override;
void SetEventCallback(const EventCallbackFn& callback) override { m_Data.eventCallback = callback; };
private:
virtual void Shutdown();
virtual void Init(const WindowProps& props);
private:
GLFWwindow* m_Window;
// WindowData与WindowProp不同,这个创建这个类是为了方便和glfw交互
struct WindowData
{
std::string title;
unsigned int width, height;
bool isVSync;
EventCallbackFn eventCallback;
};
WindowData m_Data;
};
}
WindowsWindow.cpp
#include "hzpch.h"
#include "WindowsWindow.h"
namespace Hazel
{
static bool m_initialized = false;
Window* Window::Create(const WindowProps& props)
{
return new WindowsWindow(props);
}
WindowsWindow::WindowsWindow(const WindowProps& props)
{
Init(props);
}
WindowsWindow::~WindowsWindow()
{
Shutdown();
}
void WindowsWindow::Init(const WindowProps& props)
{
if (!m_initialized)
{
m_initialized = true;
int r = glfwInit();
//HAZEL_ASSERT(r, "Failed to init glfw!")
}
m_Data.title = props.m_title;
m_Data.width = props.m_width;
m_Data.height = props.m_height;
//HZ_CORE_INFO("Creating Window {0} {1} {2}", props.m_title, props.m_width, props.m_height);
m_Window = glfwCreateWindow(m_Data.width, m_Data.height, m_Data.title.c_str(), NULL, NULL);
glfwMakeContextCurrent(m_Window);
glfwSetWindowUserPointer(m_Window, &m_Data);
SetVSync(true);
}
void WindowsWindow::Shutdown()
{
glfwDestroyWindow(m_Window);
}
void WindowsWindow::SetVSync(bool enabled)
{
if (enabled)
glfwSwapInterval(1);
else
glfwSwapInterval(0);
m_Data.isVSync = enabled;
}
void WindowsWindow::OnUpdate()
{
glfwPollEvents();
glfwSwapBuffers(m_Window);
}
void WindowsWindow::OnResized(unsigned int width, unsigned int height)
{
return;
}
/*void* WindowsWindow::GetNaiveWindow()
{
return;
}*/
}
Application.cpp
Application::Application()
{
m_Window = std::unique_ptr<Window>(Window::Create());
}
void Application::run()
{
/*WindowResizedEvent we(1200, 700);
HZ_TRACE(we.ToString());*/
while (m_Running)
{
m_Window->OnUpdate();
}
}
测试效果:
基于GLFW库,创建Hazel引擎的Window类和Event类
Event类
抽象Event类接口
class HAZEL_API Event
{
public:
virtual const char* GetName() const = 0;
virtual const EventType GetEventType() const = 0;
virtual int GetCategoryFlag() const = 0;
virtual std::string ToString() const { return GetName(); }
inline bool IsInCategory(EventCategory type)
{
return GetCategoryFlag() & type;
}
protected:
bool m_Handled = false;//用来标记这个事件是否被处理了
};
具体事件的实现,例如实现按键盘的事件:
class HAZEL_API KeyPressedEvent : public Event
{
public:
KeyPressedEvent(int keycode, int keyRepeated)
:m_Keycode(keycode), m_KeyRepeated(keyRepeated) {}
inline int GetRepeated() const { return m_KeyRepeated; }
std::string ToString()const override
{
std::stringstream ss;
ss << "KeyPressedEvent:\n KeyCode : " << m_Keycode << " KeyRepeated: " << m_KeyRepeated;
return ss.str();
}
static EventType GetStaticType() {return EventType::KeyPressed; } // 此类下的Event类型都是一样的,所以应该设为Static
virtual EventType GetEventType() const override {return GetStaticType();} // 写这个是为了防止没有KeyEvent类型,只有Event类型
virtual const char* GetName() const override { return "KeyPressed"; }
protected:
int m_KeyRepeated;
int m_Keycode;
};
Window类
Window类作为接口类,需要包含通用的窗口内容:
- List item
- 虚析构函数
- 一个Update函数,用于在loop里每帧循环
- 窗口的长和宽,以及相应的Get函数
- 设置窗口的Vsync和Get窗口的Vsync函数
- 窗口的回调函数,当窗口接受事件输入时,会调用这个回调函数
所以Windows接口类设计如下:
class HAZEL_API Window
{
public:
// Window自带一个回调函数,用来处理从glfw库收到的callback
using EventCallbackFn = std::function<void(Event&)>;
virtual ~Window() {};
virtual float const& GetWindowHeight() const = 0;
virtual float const& GetWindowWidth() const = 0;
virtual bool IsVSync() const = 0;
virtual void SetVSync(bool) = 0;
virtual void OnUpdate() = 0;
virtual void SetEventCallback(const EventCallbackFn& callback) = 0;
static Window* Create(const WindowProps& props = WindowProps());
};
由Application创建window,同时Application给与window对应的回调函数,让window接受glfw的回调函数后,再来调用对应Application的回调函数,而window本身是不知道Application的存在的,设计代码如下:
Application::Application()
{
m_Window = std::unique_ptr<Window>(Window::Create());
m_Window->SetEventCallback(std::bind(&Application::OnEvent, this, std::placeholders::_1));// 设置window的callback为此对象的OnEvent函数
// 像下面这样直接写lambda也是可以的
//m_Window->SetEventCallback([](Event& e)->void
//{
// if (e.GetEventType() == EventType::MouseScrolled)
// {
// MouseScrolledEvent ee = (MouseScrolledEvent&)e;
// LOG( "xOffset:{0} and yOffset:{1}", ee.GetXOffset(), ee.GetYOffset());
// }
//}
//);
}
void Application::OnEvent(Event& e)
{
//CORE_LOG("{0}", e);
CORE_LOG(e.ToString());
EventDispatcher dispatcher(e);
dispatcher.Dispatch<WindowCloseEvent>(std::bind(&Application::OnWindowClose, this, std::placeholders::_1));
}
void Application::Run()
{
std::cout << "Run Application" << std::endl;
while (m_Running)
{
// Application并不应该知道调用的是哪个平台的window,Window的init操作放在Window::Create里面
// 所以创建完window后,可以直接调用其loop开始渲染
m_Window->OnUpdate();
}
//LOG(w.ToString());
}
bool Application::OnWindowClose(WindowCloseEvent &e)
{
m_Running = false;
return true;
}
其中,EventDispatcher用于根据事件类型的不同,调用不同的函数:
// 当收到Event时,创建对应的EventDispatcher
class HAZEL_API EventDispatcher
{
template<typename T>
using EventHandler = std::function<bool(T&)>;//EventHandler存储了一个输入为任意类型的引用,返回值为bool的函数指针
public:
EventDispatcher(Event& event):
m_Event(event){}
// T指的是事件类型, 如果输入的类型没有GetStaticType会报错
template<typename T>
void Dispatch(EventHandler<T> handler)
{
if (m_Event.m_Handled)
return;
if (m_Event.GetEventType() == T::GetStaticType())
{
m_Event.m_Handled = handler(*(T*)&m_Event); //使用(T*)把m_Event转换成输入事件的指针类型
}
}
private:
Event& m_Event;//必须是引用,不可以是Event的实例,因为Event带有纯虚函数
};
最终运行结果如下,在console窗口上打印GLFW窗口触发的事件信息
附录
git submodule 删除子模块
网上流传了一些偏法,主要步骤是直接移除模块,并手动修改 .gitmodules、.git/config 和 .git/modules 内容。包含了一大堆类似git rm --cached 、rm -rf 、rm .gitmodules 和 git rm --cached 之类的代码。
实际上这是一种比较野的做法,不建议使用。
根据官方文档的说明,应该使用 git submodule deinit 命令卸载一个子模块。这个命令如果添加上参数 --force,则子模块工作区内即使有本地的修改,也会被移除。
git submodule deinit project-sub-1
git rm project-sub-1
执行 git submodule deinit project-sub-1 命令的实际效果,是自动在 .git/config 中删除了以下内容:
[submodule “project-sub-1”]
url = https://github.com/username/project-sub-1.git
执行 git rm project-sub-1 的效果,是移除了 project-sub-1 文件夹,并自动在 .gitmodules 中删除了以下内容:
[submodule “project-sub-1”]
path = project-sub-1
url = https://github.com/username/project-sub-1.git
此时,主项目中关于子模块的信息基本已经删除(虽然貌似 .git/modules 目录下还有残余):
➜ project-main git:(master) ✗ gs
位于分支 master
您的分支与上游分支 ‘origin/master’ 一致。
要提交的变更:
(使用 “git reset HEAD <文件>…” 以取消暂存)
修改: .gitmodules
删除: project-sub-1
可以提交代码:
git commit -m “delete submodule project-sub-1”
至此完成对子模块的删除。
遇到的GLFW库使用问题
trouble linking with glfw using premake and vs2019 报错如下:
3>GLFW.lib(init.obj) : error LNK2019: unresolved external symbol
_glfwSelectPlatform referenced in function glfwInit 3>GLFW.lib(vulkan.obj) : error LNK2019: unresolved external symbol
_glfwPlatformLoadModule referenced in function _glfwInitVulkan 3>GLFW.lib(vulkan.obj) : error LNK2019: unresolved external symbol
_glfwPlatformFreeModule referenced in function _glfwInitVulkan 3>GLFW.lib(vulkan.obj) : error LNK2019: unresolved external symbol
_glfwPlatformGetModuleSymbol referenced in function _glfwInitVulkan
修改glfw的premake5.lua如下:
project "GLFW"
kind "StaticLib"
language "C"
targetdir ("bin/" .. outputdir .. "/%{prj.name}")
objdir ("bin-int/" .. outputdir .. "/%{prj.name}")
files
{
"include/GLFW/glfw3.h",
"include/GLFW/glfw3native.h",
"src/internal.h",
"src/platform.h",
"src/mappings.h",
"src/context.c",
"src/init.c",
"src/input.c",
"src/monitor.c",
"src/platform.c",
"src/vulkan.c",
"src/window.c",
"src/egl_context.c",
"src/osmesa_context.c",
"src/null_platform.h",
"src/null_joystick.h",
"src/null_init.c",
"src/null_monitor.c",
"src/null_window.c",
"src/null_joystick.c",
}
filter "system:linux"
pic "On"
systemversion "latest"
staticruntime "On"
files
{
"src/x11_init.c",
"src/x11_monitor.c",
"src/x11_window.c",
"src/xkb_unicode.c",
"src/posix_time.c",
"src/posix_thread.c",
"src/glx_context.c",
"src/egl_context.c",
"src/osmesa_context.c",
"src/linux_joystick.c"
}
defines
{
"_GLFW_X11"
}
filter "system:windows"
systemversion "latest"
staticruntime "On"
-- buildoptions{
-- "/MT"
-- }
files
{
"src/win32_init.c",
"src/win32_module.c",
"src/win32_joystick.c",
"src/win32_monitor.c",
"src/win32_time.h",
"src/win32_time.c",
"src/win32_thread.h",
"src/win32_thread.c",
"src/win32_window.c",
"src/wgl_context.c",
"src/egl_context.c",
"src/osmesa_context.c"
}
defines
{
"_GLFW_WIN32",
"_CRT_SECURE_NO_WARNINGS"
}
filter "configurations:Debug"
runtime "Debug"
symbols "On"
filter "configurations:Release"
runtime "Release"
optimize "On"
glfw项目重新生成后即可使用