游戏引擎从零开始(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"

相关推荐
软件黑马王子8 小时前
Unity游戏制作中的C#基础(6)方法和类的知识点深度剖析
开发语言·游戏·unity·c#
007_rbq18 小时前
XUnity.AutoTranslator-Gemini——调用Google的Gemini API, 实现Unity游戏中日文文本的自动翻译
人工智能·python·游戏·机器学习·unity·github·机器翻译
Sui_Network19 小时前
Sui 如何支持各种类型的 Web3 游戏
大数据·数据库·人工智能·游戏·web3·区块链
晴空了无痕20 小时前
游戏客户端架构设计与实战:从模块化到性能优化
游戏·性能优化
软件黑马王子1 天前
Unity游戏制作中的C#基础(5)条件语句和循环语句知识点全解析
游戏·unity·c#
韩仔搭建2 天前
七星棋牌顶级运营产品全开源修复版源码教程:6端支持,200+子游戏玩法,完整搭建指南(含代码解析)
游戏·开源
Igallta_8136222 天前
【小游戏】C++控制台版本俄罗斯轮盘赌
c语言·开发语言·c++·windows·游戏·游戏程序
zsyzClb3 天前
nim游戏及其进阶 [SDOI2011] 黑白棋 [SDOI2019] 移动金币
游戏
BingLin-Liu3 天前
蓝桥杯备考:贪心算法之矩阵消除游戏
游戏·贪心算法·矩阵
264玫瑰资源库3 天前
七星棋牌源码高阶技术指南:6端互通、200+子游戏玩法深度剖析与企业级搭建实战(完全开源)
游戏·开源