Dragon Engine:事件系统

2020-07-06  本文已影响0人  Dragon_boy

本文同时发布在我的个人博客上: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

上一篇 下一篇

猜你喜欢

热点阅读