前言
游戏中会有各种各样的输入,键盘、鼠标、游戏手柄等。
这节我们就来定义好常见的输入事件
Event设计与实现

新增Event相关类之后的代码结构:
vbnet
.
├── Hazel
│ ├── Application.cpp
│ ├── Application.h
│ ├── Core
│ │ ├── Base.h
│ │ ├── KeyCodes.h
│ │ └── MouseCodes.h
│ ├── Core.h
│ ├── EntryPoint.h
│ ├── Events
│ │ ├── ApplicationEvent.h
│ │ ├── Event.h
│ │ ├── KeyEvent.h
│ │ └── MouseEvent.h
│ ├── Log.cpp
│ └── Log.h
└── Hazel.h
辅助类
- Hazel/src/Hazel/Core/Base.h
实现一个位移操作的宏定义,后面用的上
c++
#define BIT(x) (1 << x)
- Hazel/src/Hazel/Core/KeyCodes.h 从glfw3.h里抠出来的定义,不用自己写。
代码比较长,截取了一部分如下:
c++
namespace Hazel
{
using KeyCode = uint16_t;
namespace Key
{
enum : KeyCode
{
// From glfw3.h
Space = 32,
Apostrophe = 39, /* ' */
Comma = 44, /* , */
Minus = 45, /* - */
Period = 46, /* . */
Slash = 47, /* / */
D0 = 48, /* 0 */
D1 = 49, /* 1 */
D2 = 50, /* 2 */
D3 = 51, /* 3 */
D4 = 52, /* 4 */
//.....
主意!enum : KeyCode,其实就是enum:uint16_t,和我们常见的枚举不太一样,是一个匿名的、指定类型的枚举,作用是方便定义一组int常量。
- Hazel/src/Hazel/Core/MouseCodes.h
定义了一组Button类型,先放一放,后面用到的时候再看为什么这么定义
c++
#ifndef SANBOX_MOUSECODES_H
#define SANBOX_MOUSECODES_H
namespace Hazel{
using MouseCode = uint16_t;
namespace Mouse {
enum : MouseCode {
// From glfw3.h
Button0 = 0,
Button1 = 1,
Button2 = 2,
Button3 = 3,
Button4 = 4,
Button5 = 5,
Button6 = 6,
Button7 = 7,
ButtonLast = Button7,
ButtonLeft = Button0,
ButtonRight = Button1,
ButtonMiddle = Button2
};
}
}
#endif //SANBOX_MOUSECODES_H
基类Event
当前这个版本的Event实现是阻塞式的,即处理一个事件时,会等待执行完再进行后面的任务。
Hazel/src/Hazel/Events/Event.h
定义EventType,表示事件最详细的分类,有四大类事件:Window、App、Key、Mouse。
EventCategory是比较大的分类,用来过滤事件大类别,以方便做不同的处理。
c++
enum class EventType
{
None = 0,
WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved,
AppTick, AppUpdate, AppRender,
KeyPressed, KeyReleased, KeyTyped,
MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled
};
enum EventCategory
{
None = 0,
EventCategoryApplication = BIT(1),
EventCategoryInput = BIT(2),
EventCategoryKeyboard = BIT(3),
EventCategoryMouseButton = BIT(4),
};
注意!这里用到了宏定义BIT,在上面Base.h里有定义。
声明Event类和EventDispatcher。
Event中包含基本的获取事件类别、类别名称、ToString()、以及重载<<操作符。
EventDispatcher声明事件的处理,如果事件被消费了,结果返回true。
c++
class Event {
public:
virtual ~Event() = default;
bool Handled = false;
virtual EventType GetEventType() const = 0;
virtual const char* GetName() const = 0;
virtual int GetCategoryFlags() const = 0;
virtual std::string ToString() const {return GetName();}
bool IsInCategory(EventCategory category){
return GetCategoryFlags() & category;
}
};
class EventDispatcher {
public:
EventDispatcher(Event& event): m_Event(event) {
}
template<typename T, typename F>
bool Dispatch(const F& func) {
if (m_Event.GetEventType() == T::GetStaticType()) {
m_Event.Handled |= func(static_cast<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();
}
Event类中,声明了4个虚函数,需要在继承类中实现,为了很方便的写继承类的代码,增加两个宏定义:
c++
#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;}
很精妙的写法,用一个宏,把子类中需要重复写的代码封装起来了。
注意!c++中,继承类中重写了父类的虚函数,前面可以加virtual也可以不加。
OK!稍微有点啰嗦的Event.h到此告一段落了
ApplicationEvent.h(应用事件)
ApplicationEvent中,实际定义了4个App类别的事件:
- WindowResizeEvent:窗口缩放
- WindowCloseEvent:窗口关闭
- AppTickEvent:Tick指游戏中Loop循环(像钟表里的tick tick的周期性行为)
- AppRenderEvent:渲染事件
得益于基类Event中巧妙的宏定义,子类中需要实现的函数写起来方便多了。
c++
#ifndef SANBOX_APPLICATIONEVENT_H
#define SANBOX_APPLICATIONEVENT_H
#include "Event.h"
namespace Hazel {
class WindowResizeEvent : public Event {
public:
WindowResizeEvent(unsigned int width, unsigned int height) : m_Width(width), m_Height(height) {}
unsigned int GetWidth() const {return m_Width;}
unsigned int GetHeight() const {return m_Height;}
std::string ToString() const override {
std::stringstream ss;
ss << "WindowResizeEvent : " << m_Width << ", " << m_Height;
return ss.str();
}
EVENT_CLASS_TYPE(WindowResize)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
private:
unsigned int m_Width, m_Height;
};
class WindowCloseEvent : public Event {
public:
WindowCloseEvent() = default;
EVENT_CLASS_TYPE(WindowClose)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
class AppTickEvent : public Event {
public:
AppTickEvent() = default;
EVENT_CLASS_TYPE(AppTick)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
class AppRenderEvent : public Event {
public:
AppRenderEvent() = default;
EVENT_CLASS_TYPE(AppRender)
EVENT_CLASS_CATEGORY(EventCategoryApplication)
};
}
#endif //SANBOX_APPLICATIONEVENT_H
KeyEvent.h(键盘事件)
KeyEvent有个按键码,需要再封装一个基类KeyEvent继承Event。
按照键盘点击的类别划分成:
- 基类KeyEvent
- KeyPressedEvent
- KeyReleaseEvent
- KeyTypedEvent
arduino
#ifndef SANBOX_KEYEVENT_H
#define SANBOX_KEYEVENT_H
#include "Event.h"
#include "KeyCodes.h"
namespace Hazel{
class KeyEvent : public Event {
public:
KeyCode GetKeyCode() const {return m_KeyCode;}
EVENT_CLASS_CATEGORY(EventCategoryKeyboard | EventCategoryInput)
protected:
KeyEvent(const KeyCode keycode) : m_KeyCode(keycode){}
keyCode m_KeyCode;
};
class KeyPressedEvent : public KeyEvent {
public:
KeyPressedEvent(const KeyCode keycode, bool isRepeat = false)
: KeyEvent(keycode), m_isRepeat(isRepeat){}
bool IsRepeat() const {return m_IsRepeat;}
std::string ToString() const override {
std::stringstream ss;
ss << "KeyPressedEvent: " << m_KeyCode << " (repeat = " << m_IsRepeat << ")";
return ss.str();
}
EVENT_CLASS_TYPE(KeyPressed);
private:
bool m_IsRepeat;
};
class KeyReleaseEvent : public KeyEvent {
public:
KeyReleaseEvent(const KeyCode keycode) : KeyEvent(keycode){}
std::string ToString() const override {
std::stringstream ss;
ss << "KeyPressedEvent : " << m_KeyCode << " (repeat = " << m_IsRepeat << ")";
return ss.str();
}
private:
bool m_IsRepeat;
};
class KeyTypedEvent : public KeyEvent {
public:
KeyTypedEvent(const KeyCode keycode) : KeyEvent(keycode){}
std::string ToString() const override
{
std::stringstream ss;
ss << "KeyTypedEvent: " << m_KeyCode;
return ss.str();
}
EVENT_CLASS_TYPE(KeyTyped);
};
}
#endif //SANBOX_KEYEVENT_H
MouseEvent.h(鼠标事件)
- MouseMovedEvent(鼠标移动)
- MouseScrolledEvent(鼠标滚动)
- 基类MouseButtonEvent(鼠标点击)
- MouseButtonPressedEvent(鼠标按下按键)
- MouseButtonReleasedEvent(鼠标释放按键)
详细的代码不一一说明了,参考如下:
c++
#ifndef SANBOX_MOUSEEVENT_H
#define SANBOX_MOUSEEVENT_H
#include "Event.h"
#include "MouseCodes.h"
namespace Hazel {
class MouseMovedEvent : public Event {
public:
MouseMovedEvent(const float x, const float y) : m_MouseX(x), m_MouseY(y){
}
float GetX() const {return m_MouseX;}
float GetY() const {return m_MouseY;}
std::string ToString() const override {
std::stringstream ss;
ss << "MouseMovedEvent: " << m_MouseX << "," << m_MouseY;
return ss.str();
}
EVENT_CLASS_TYPE(MouseMoved)
EVENT_CLASS_CATEGORY(EventCategoryMouse | EventCategoryEnput)
private:
float m_MouseX, m_MouseY;
};
class MouseScrolledEvent : public Event {
public:
MouseScrolledEvent(const float xOffset, const float yOffset) : m_XOffset(xOffset), m_YOffset(yOffset) {
}
float GetXOffset() const {return m_XOffset;}
float GetYOffset() const {return m_YOffset;}
std::string ToString() const override {
std::stringstream ss;
ss << "MouseScrolledEvent : " << GetXOffset() << ", " << GetYOffset();
return ss.str();
}
EVENT_CLASS_TYPE(MouseScrolled)
EVENT_CLASS_CATEGORY(EventCategoryMouse | EventCagetoryInput)
private:
float m_XOffset, m_YOffset;
};
class MouseButtonEvent : public Event {
public:
MouseCode GetMouseButton() const {return m_Button;}
EVENT_CLASS_CATEGORY(EventCategoryMouse | EventCategoryInput | EventCategoryMouseButton)
protected:
MouseButtonEvent(const MouseCode button) : m_Button(button){}
MouseCode m_Button;
};
class MouseButtonPressedEvent : public MouseButtonEvent {
public:
MouseButtonPressedEvent(const MouseCode button) : MouseButtonEvent(button) {}
std::string ToString() const override {
std::stringstream ss;
ss << "MouseButtonPressedEvent : " << m_Button;
return ss.str();
}
EVENT_CLASS_TYPE(MouseButtonPressed)
};
class MouseButtonReleasedEvent : public MouseButtonEvent {
public:
MouseButtonReleasedEvent(const MouseCode button) : MouseButtonEvent(button) {}
std::string ToString() const override {
std::stringstream ss;
ss << "MouseButtonReleasedEvent : " << m_Button;
return ss.str();
}
EVENT_CLASS_TYPE(MouseButtonReleased)
};
}
#endif //SANBOX_MOUSEEVENT_H
测试
Hazel引擎工程的CMakeLists.txt中增加Events的include目录
bash
target_include_directories(${PROJECT_NAME} PUBLIC
vendor/spdlog/include
src/Hazel/Core
Src/Hazel/Events)
回到Hazel/src/Hazel/Application.cpp中增加事件的测试
scss
void Application::Run() {
WindowResizeEvent e(1200, 720);
if (e.IsInCategory(EventCategoryApplication)) {
HZ_TRACE(e.ToString());
}
if (e.IsInCategory(EventCategoryInput)) {
HZ_TRACE(e.ToString());
}
while(true) {
}
}
运行结果:
这次的代码分两次提交了。完整的代码参考: