本文同时发布在我的个人博客上:https://dragon_boy.gitee.io
事件系统的范围很广,这里我暂时只包含:键盘输入、鼠标输入、窗口事件、应用程序事件。
首先构建基本的事件类。
Event.h
:
namespace Dragon
{
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(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;}
我先定义事件的类型枚举类,接着定义事件种类枚举。这里使用的BIT(x)
是一个宏定义:
#define BIT(x) (1 << x)
接着将获取事件类型和名字、种类的方法进行宏定义,##
表示将前后进行连接,#
表示将后方的变量转化为字符串。
上方代码的由三个重写虚方法,它们在下面事件类中定义:
class Event
{
public:
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
{
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.Handled = func(static_cast<T&>(m_Event));
return true;
}
return false;
}
private:
Event& m_Event;
};
std::function<>
是对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,说白了就是可以简单粗暴的调用函数。
Dispatch
方法将一个事件函数作为参数,检测事件的类型后,我们使用这个事件函数,并将调用结果存储在Event
对象中。
有了Event
基类后,我们就可以编写其它事件类,这里以键盘输入事件为例:
KeyEvents.h
namespace Dragon
{
class 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 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 KeyReleasedEvent : public KeyEvent
{
public:
KeyReleasedEvent(int keycode)
: KeyEvent(keycode){}
std::string ToString() const override
{
std::stringstream ss;
ss << "KeyReleaseEvent: " << m_KeyCode;
return ss.str();
}
EVENT_CLASS_TYPE(KeyReleased)
};
class KeyTypedEvent : public KeyEvent
{
public:
KeyTypedEvent(int keycode)
: KeyEvent(keycode) {}
std::string ToString() const override
{
std::stringstream ss;
ss << " KeyTypedEvent: " << m_KeyCode;
return ss.str();
}
EVENT_CLASS_TYPE(KeyTyped)
};
}
首先定义一个键盘输入基类,包含获取键值和事件种类方法,其它键盘输入类的区别分别是,键盘按下事件使用一个按下次数属性来判断持续按键,键盘松开事件和键盘输入事件没有多大的区别。
鼠标事件和应用程序事件没有多大的区别,这里不演示。
那么如何使用这些事件类?最重要的就是我们在Event.h
中定义的EventDispatcher
类。我们在需要进行事件调用的方法中,大概会这么使用,如OnEvent()
:
//创建dispatcher
void OnEvent(Event& e)
{
EventDispatcheer dispatcher(e);
dispacther<Dispatcher>(SomeEventType)(DG_BIND_EVENT_FN(OnSomeEventFunction));
}
这里的DG_BIND_EVENT_FN
是一个使用std::bind()
方法的宏定义:
#define DG_BIND_EVENT_FN(fn) std::bind(&fn, this, std::placeholders::_1)
std::bind()
将参数传递给位于第一个参数的函数引用或指针,第二个参数表明传入函数的第一个参数,这里的this
表明当前的对象,第三个参数为std::placeholders::_1
表明这个参数由fn
的第二个参数由fn
传入的第一个参数决定。
比如我们定义一个具体的事件函数:
bool OnSomeEventFunction(SomeEventType& e)
{
//someProcessing
}
那么上面的std::placeholders::_1
对应的就是e
,对应方法的第二个参数。方法的第一个参数默认是当前的类对象this。
下一节介绍层的概念。
项目github地址:https://github.com/Dragon-Baby/Dragon
网友评论