游戏引擎从零开始(4)-input event

前言

游戏中会有各种各样的输入,键盘、鼠标、游戏手柄等。

这节我们就来定义好常见的输入事件

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类别的事件:

  1. WindowResizeEvent:窗口缩放
  2. WindowCloseEvent:窗口关闭
  3. AppTickEvent:Tick指游戏中Loop循环(像钟表里的tick tick的周期性行为)
  4. 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。

按照键盘点击的类别划分成:

  1. 基类KeyEvent
  2. KeyPressedEvent
  3. KeyReleaseEvent
  4. 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(鼠标事件)

  1. MouseMovedEvent(鼠标移动)
  2. MouseScrolledEvent(鼠标滚动)
  3. 基类MouseButtonEvent(鼠标点击)
  4. MouseButtonPressedEvent(鼠标按下按键)
  5. 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) {

    }
}

运行结果:

这次的代码分两次提交了。完整的代码参考:

event system_v1

"event system v2"

相关推荐
ii_best5 小时前
按键精灵 安卓脚本开发:游戏实战之自动切换账号辅助工具
游戏
Alfred king10 天前
面试150跳跃游戏
python·leetcode·游戏·贪心算法
D1555408805810 天前
游戏护航小程序源码游戏派单小程序搭建游戏代练小程序源码
游戏
旧物有情11 天前
Unity2D 街机风太空射击游戏 学习记录 #12QFramework引入
学习·游戏
帅_shuai_11 天前
UE5 游戏模板 —— FirstShootGame
游戏·ue5
Jooolin11 天前
【Python】什么?Python 可以用来写 Galgame?
python·游戏·ai编程
鱼雀AIGC11 天前
如何仅用AI开发完整的小程序<6>—让AI对视觉效果进行升级
前端·人工智能·游戏·小程序·aigc·ai编程
benben04411 天前
Unity3D仿星露谷物语开发69之动作声音
游戏·ui·unity·c#·游戏引擎
l and12 天前
鸿蒙:启动本地 http-server 加载 h5 游戏
http·游戏·harmonyos
拓端研究室12 天前
专题:2025游戏科技与市场趋势报告|附130+份报告PDF汇总下载
科技·游戏·pdf