feat(事件系统): 实现完整的事件处理框架

添加事件系统核心组件,包括事件基类、各种输入事件类型和事件分发机制。主要功能:
- 新增基础事件类及键盘、鼠标、触摸、手柄等输入事件
- 在Actor类中实现事件监听和分发功能
- 在Scene类中添加事件处理逻辑
- 在Application类中集成SDL事件转换和分发
- 添加测试用例验证手柄事件处理

事件系统支持冒泡和优先级排序,为游戏交互提供基础支持
This commit is contained in:
2026-03-28 03:57:52 +08:00
parent 0171c9d22a
commit 08570f3aab
13 changed files with 1117 additions and 21 deletions

View File

@@ -4,8 +4,16 @@
#include <frostbite2D/types/type_alias.h> #include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h> #include <frostbite2D/types/type_math.h>
#include <frostbite2D/utils/intrusive_list.hpp> #include <frostbite2D/utils/intrusive_list.hpp>
#include <frostbite2D/event/event.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/event/mouse_event.h>
#include <frostbite2D/event/touch_event.h>
#include <frostbite2D/event/joystick_event.h>
#include <frostbite2D/event/window_event.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <functional>
#include <unordered_map>
namespace frostbite2D { namespace frostbite2D {
@@ -70,6 +78,42 @@ public:
ActorList& GetChildren() { return children_; } ActorList& GetChildren() { return children_; }
const ActorList& GetChildren() const { return children_; } const ActorList& GetChildren() const { return children_; }
bool IsEventReceiveEnabled() const { return eventReceiveEnabled_; }
void EnableEventReceive() { eventReceiveEnabled_ = true; }
void DisableEventReceive() { eventReceiveEnabled_ = false; }
int32 GetEventPriority() const { return eventPriority_; }
void SetEventPriority(int32 priority) { eventPriority_ = priority; }
bool IsEventInterceptEnabled() const { return eventInterceptEnabled_; }
void EnableEventIntercept() { eventInterceptEnabled_ = true; }
void DisableEventIntercept() { eventInterceptEnabled_ = false; }
using EventCallback = std::function<bool(const Event&)>;
uint32 AddEventListener(EventType type, EventCallback callback);
bool RemoveEventListener(uint32 listenerId);
void ClearEventListeners();
virtual bool OnEvent(const Event& event);
virtual bool OnKeyDown(const KeyEvent& event);
virtual bool OnKeyUp(const KeyEvent& event);
virtual bool OnMouseDown(const MouseEvent& event);
virtual bool OnMouseUp(const MouseEvent& event);
virtual bool OnMouseMove(const MouseEvent& event);
virtual bool OnMouseWheel(const MouseEvent& event);
virtual bool OnTouchDown(const TouchEvent& event);
virtual bool OnTouchUp(const TouchEvent& event);
virtual bool OnTouchMove(const TouchEvent& event);
virtual bool OnJoystickAxis(const JoystickEvent& event);
virtual bool OnJoystickButtonDown(const JoystickEvent& event);
virtual bool OnJoystickButtonUp(const JoystickEvent& event);
virtual bool OnWindowResize(const WindowEvent& event);
bool DispatchEvent(const Event& event);
bool BubbleEvent(const Event& event);
protected: protected:
void SetParent(Actor* parent) { parent_ = parent; } void SetParent(Actor* parent) { parent_ = parent; }
void SetScene(Scene* scene) { scene_ = scene; } void SetScene(Scene* scene) { scene_ = scene; }
@@ -99,6 +143,18 @@ private:
mutable Transform2D worldTransform_; mutable Transform2D worldTransform_;
mutable bool transformDirty_ = true; mutable bool transformDirty_ = true;
bool eventReceiveEnabled_ = false;
int32 eventPriority_ = 0;
bool eventInterceptEnabled_ = false;
struct EventListener {
uint32 id;
EventType type;
EventCallback callback;
};
std::vector<EventListener> eventListeners_;
uint32 nextListenerId_ = 1;
void markTransformDirty(); void markTransformDirty();
void updateLocalTransform() const; void updateLocalTransform() const;
void updateWorldTransform() const; void updateWorldTransform() const;

View File

@@ -3,7 +3,9 @@
#include <frostbite2D/core/window.h> #include <frostbite2D/core/window.h>
#include <frostbite2D/graphics/camera.h> #include <frostbite2D/graphics/camera.h>
#include <frostbite2D/types/type_alias.h> #include <frostbite2D/types/type_alias.h>
#include <frostbite2D/event/event.h>
#include <string> #include <string>
#include <memory>
namespace frostbite2D { namespace frostbite2D {
@@ -175,6 +177,9 @@ private:
*/ */
void render(); void render();
std::unique_ptr<Event> convertSDLEvent(const SDL_Event& sdlEvent);
void dispatchEvent(const Event& event);
Window* window_ = nullptr; Window* window_ = nullptr;
class Renderer* renderer_ = nullptr; class Renderer* renderer_ = nullptr;
Camera* camera_ = nullptr; Camera* camera_ = nullptr;

View File

@@ -0,0 +1,63 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
namespace frostbite2D {
class Actor;
enum class EventType : int32 {
None = 0,
WindowClose,
WindowResize,
WindowFocus,
WindowMinimize,
WindowMaximize,
WindowRestore,
KeyDown,
KeyUp,
KeyText,
MouseMove,
MouseButtonDown,
MouseButtonUp,
MouseWheel,
TouchDown,
TouchUp,
TouchMove,
JoystickAxis,
JoystickBall,
JoystickHat,
JoystickButtonDown,
JoystickButtonUp,
Custom
};
class Event {
public:
virtual ~Event() = default;
virtual EventType getType() const = 0;
bool isHandled() const { return handled_; }
void setHandled(bool handled) { handled_ = handled; }
bool isPropagationStopped() const { return propagationStopped_; }
void stopPropagation() { propagationStopped_ = true; }
Actor* getSource() const { return source_; }
void setSource(Actor* actor) { source_ = actor; }
uint32 getTimestamp() const { return timestamp_; }
protected:
Event() = default;
Event(uint32 timestamp)
: timestamp_(timestamp) {}
bool handled_ = false;
bool propagationStopped_ = false;
Actor* source_ = nullptr;
uint32 timestamp_ = 0;
};
}

View File

@@ -0,0 +1,112 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_joystick.h>
namespace frostbite2D {
class JoystickEvent : public Event {
public:
JoystickEvent(EventType type, uint32 timestamp, int32 deviceId)
: Event(timestamp)
, type_(type)
, deviceId_(deviceId) {}
EventType getType() const override {
return type_;
}
int32 getDeviceId() const { return deviceId_; }
protected:
EventType type_;
int32 deviceId_;
};
class JoystickAxisEvent : public JoystickEvent {
public:
JoystickAxisEvent(uint32 timestamp, int32 deviceId, uint8 axis, int16 value)
: JoystickEvent(EventType::JoystickAxis, timestamp, deviceId)
, axis_(axis)
, value_(value) {}
uint8 getAxis() const { return axis_; }
int16 getValue() const { return value_; }
float getNormalizedValue() const {
return static_cast<float>(value_) / 32767.0f;
}
private:
uint8 axis_;
int16 value_;
};
class JoystickBallEvent : public JoystickEvent {
public:
JoystickBallEvent(uint32 timestamp, int32 deviceId, uint8 ball, int16 dx, int16 dy)
: JoystickEvent(EventType::JoystickBall, timestamp, deviceId)
, ball_(ball)
, dx_(dx)
, dy_(dy) {}
uint8 getBall() const { return ball_; }
int16 getDX() const { return dx_; }
int16 getDY() const { return dy_; }
private:
uint8 ball_;
int16 dx_;
int16 dy_;
};
class JoystickHatEvent : public JoystickEvent {
public:
JoystickHatEvent(uint32 timestamp, int32 deviceId, uint8 hat, uint8 value)
: JoystickEvent(EventType::JoystickHat, timestamp, deviceId)
, hat_(hat)
, value_(value) {}
uint8 getHat() const { return hat_; }
uint8 getValue() const { return value_; }
bool isCentered() const { return value_ == SDL_HAT_CENTERED; }
bool isUp() const { return (value_ & SDL_HAT_UP) != 0; }
bool isRight() const { return (value_ & SDL_HAT_RIGHT) != 0; }
bool isDown() const { return (value_ & SDL_HAT_DOWN) != 0; }
bool isLeft() const { return (value_ & SDL_HAT_LEFT) != 0; }
private:
uint8 hat_;
uint8 value_;
};
class JoystickButtonEvent : public JoystickEvent {
public:
JoystickButtonEvent(EventType type, uint32 timestamp, int32 deviceId, uint8 button)
: JoystickEvent(type, timestamp, deviceId)
, button_(button) {}
EventType getType() const override {
return type_;
}
uint8 getButton() const { return button_; }
private:
uint8 button_;
};
class JoystickButtonDownEvent : public JoystickButtonEvent {
public:
JoystickButtonDownEvent(uint32 timestamp, int32 deviceId, uint8 button)
: JoystickButtonEvent(EventType::JoystickButtonDown, timestamp, deviceId, button) {}
};
class JoystickButtonUpEvent : public JoystickButtonEvent {
public:
JoystickButtonUpEvent(uint32 timestamp, int32 deviceId, uint8 button)
: JoystickButtonEvent(EventType::JoystickButtonUp, timestamp, deviceId, button) {}
};
}

View File

@@ -0,0 +1,208 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_keyboard.h>
#include <string>
namespace frostbite2D {
enum class KeyCode {
Unknown = SDLK_UNKNOWN,
Return = SDLK_RETURN,
Escape = SDLK_ESCAPE,
Backspace = SDLK_BACKSPACE,
Tab = SDLK_TAB,
Space = SDLK_SPACE,
Exclaim = SDLK_EXCLAIM,
Quotedbl = SDLK_QUOTEDBL,
Hash = SDLK_HASH,
Percent = SDLK_PERCENT,
Dollar = SDLK_DOLLAR,
Ampersand = SDLK_AMPERSAND,
Quote = SDLK_QUOTE,
LeftParen = SDLK_LEFTPAREN,
RightParen = SDLK_RIGHTPAREN,
Asterisk = SDLK_ASTERISK,
Plus = SDLK_PLUS,
Comma = SDLK_COMMA,
Minus = SDLK_MINUS,
Period = SDLK_PERIOD,
Slash = SDLK_SLASH,
Num0 = SDLK_0,
Num1 = SDLK_1,
Num2 = SDLK_2,
Num3 = SDLK_3,
Num4 = SDLK_4,
Num5 = SDLK_5,
Num6 = SDLK_6,
Num7 = SDLK_7,
Num8 = SDLK_8,
Num9 = SDLK_9,
Colon = SDLK_COLON,
Semicolon = SDLK_SEMICOLON,
Less = SDLK_LESS,
Equals = SDLK_EQUALS,
Greater = SDLK_GREATER,
Question = SDLK_QUESTION,
At = SDLK_AT,
LeftBracket = SDLK_LEFTBRACKET,
Backslash = SDLK_BACKSLASH,
RightBracket = SDLK_RIGHTBRACKET,
Caret = SDLK_CARET,
Underscore = SDLK_UNDERSCORE,
Backquote = SDLK_BACKQUOTE,
a = SDLK_a,
b = SDLK_b,
c = SDLK_c,
d = SDLK_d,
e = SDLK_e,
f = SDLK_f,
g = SDLK_g,
h = SDLK_h,
i = SDLK_i,
j = SDLK_j,
k = SDLK_k,
l = SDLK_l,
m = SDLK_m,
n = SDLK_n,
o = SDLK_o,
p = SDLK_p,
q = SDLK_q,
r = SDLK_r,
s = SDLK_s,
t = SDLK_t,
u = SDLK_u,
v = SDLK_v,
w = SDLK_w,
x = SDLK_x,
y = SDLK_y,
z = SDLK_z,
CapsLock = SDLK_CAPSLOCK,
F1 = SDLK_F1,
F2 = SDLK_F2,
F3 = SDLK_F3,
F4 = SDLK_F4,
F5 = SDLK_F5,
F6 = SDLK_F6,
F7 = SDLK_F7,
F8 = SDLK_F8,
F9 = SDLK_F9,
F10 = SDLK_F10,
F11 = SDLK_F11,
F12 = SDLK_F12,
PrintScreen = SDLK_PRINTSCREEN,
ScrollLock = SDLK_SCROLLLOCK,
Pause = SDLK_PAUSE,
Insert = SDLK_INSERT,
Home = SDLK_HOME,
PageUp = SDLK_PAGEUP,
Delete = SDLK_DELETE,
End = SDLK_END,
PageDown = SDLK_PAGEDOWN,
Right = SDLK_RIGHT,
Left = SDLK_LEFT,
Down = SDLK_DOWN,
Up = SDLK_UP,
Numpad0 = SDLK_KP_0,
Numpad1 = SDLK_KP_1,
Numpad2 = SDLK_KP_2,
Numpad3 = SDLK_KP_3,
Numpad4 = SDLK_KP_4,
Numpad5 = SDLK_KP_5,
Numpad6 = SDLK_KP_6,
Numpad7 = SDLK_KP_7,
Numpad8 = SDLK_KP_8,
Numpad9 = SDLK_KP_9,
NumpadDecimal = SDLK_KP_DECIMAL,
NumpadDivide = SDLK_KP_DIVIDE,
NumpadMultiply = SDLK_KP_MULTIPLY,
NumpadMinus = SDLK_KP_MINUS,
NumpadPlus = SDLK_KP_PLUS,
NumpadEnter = SDLK_KP_ENTER,
NumpadEquals = SDLK_KP_EQUALS,
LShift = SDLK_LSHIFT,
RShift = SDLK_RSHIFT,
LCtrl = SDLK_LCTRL,
RCtrl = SDLK_RCTRL,
LAlt = SDLK_LALT,
RAlt = SDLK_RALT,
LGui = SDLK_LGUI,
RGui = SDLK_RGUI
};
enum class KeyMod {
None = KMOD_NONE,
LShift = KMOD_LSHIFT,
RShift = KMOD_RSHIFT,
Shift = KMOD_SHIFT,
LCtrl = KMOD_LCTRL,
RCtrl = KMOD_RCTRL,
Ctrl = KMOD_CTRL,
LAlt = KMOD_LALT,
RAlt = KMOD_RALT,
Alt = KMOD_ALT,
LGui = KMOD_LGUI,
RGui = KMOD_RGUI,
Gui = KMOD_GUI,
Num = KMOD_NUM,
Caps = KMOD_CAPS
};
class KeyEvent : public Event {
public:
KeyEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers, bool repeat)
: Event(timestamp)
, keyCode_(keyCode)
, modifiers_(modifiers)
, repeat_(repeat) {}
EventType getType() const override {
return EventType::KeyDown;
}
KeyCode getKeyCode() const { return keyCode_; }
KeyMod getModifiers() const { return modifiers_; }
bool isRepeat() const { return repeat_; }
private:
KeyCode keyCode_;
KeyMod modifiers_;
bool repeat_;
};
class KeyUpEvent : public Event {
public:
KeyUpEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers)
: Event(timestamp)
, keyCode_(keyCode)
, modifiers_(modifiers) {}
EventType getType() const override {
return EventType::KeyUp;
}
KeyCode getKeyCode() const { return keyCode_; }
KeyMod getModifiers() const { return modifiers_; }
private:
KeyCode keyCode_;
KeyMod modifiers_;
};
class KeyTextEvent : public Event {
public:
KeyTextEvent(uint32 timestamp, const std::string& text)
: Event(timestamp)
, text_(text) {}
EventType getType() const override {
return EventType::KeyText;
}
const std::string& getText() const { return text_; }
private:
std::string text_;
};
}

View File

@@ -0,0 +1,108 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <frostbite2D/types/type_math.h>
#include <SDL2/SDL_mouse.h>
namespace frostbite2D {
enum class MouseButton {
Left = SDL_BUTTON_LEFT,
Right = SDL_BUTTON_RIGHT,
Middle = SDL_BUTTON_MIDDLE,
X1 = SDL_BUTTON_X1,
X2 = SDL_BUTTON_X2
};
class MouseEvent : public Event {
public:
MouseEvent(EventType type, uint32 timestamp, const Vec2& position)
: Event(timestamp)
, type_(type)
, position_(position) {}
virtual ~MouseEvent() = default;
EventType getType() const override { return type_; }
const Vec2& getPosition() const { return position_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
protected:
EventType type_;
Vec2 position_;
};
class MouseMoveEvent : public Event {
public:
MouseMoveEvent(uint32 timestamp, const Vec2& position, const Vec2& relative)
: Event(timestamp)
, position_(position)
, relative_(relative) {}
EventType getType() const override {
return EventType::MouseMove;
}
const Vec2& getPosition() const { return position_; }
const Vec2& getRelative() const { return relative_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
private:
Vec2 position_;
Vec2 relative_;
};
class MouseButtonEvent : public Event {
public:
MouseButtonEvent(uint32 timestamp, MouseButton button, const Vec2& position, bool down)
: Event(timestamp)
, button_(button)
, position_(position)
, down_(down) {}
EventType getType() const override {
return down_ ? EventType::MouseButtonDown : EventType::MouseButtonUp;
}
MouseButton getButton() const { return button_; }
const Vec2& getPosition() const { return position_; }
bool isDown() const { return down_; }
int32 getX() const { return static_cast<int32>(position_.x); }
int32 getY() const { return static_cast<int32>(position_.y); }
private:
MouseButton button_;
Vec2 position_;
bool down_;
};
class MouseWheelEvent : public Event {
public:
MouseWheelEvent(uint32 timestamp, const Vec2& position, const Vec2& direction, bool flipped)
: Event(timestamp)
, position_(position)
, direction_(direction)
, flipped_(flipped) {}
EventType getType() const override {
return EventType::MouseWheel;
}
const Vec2& getPosition() const { return position_; }
const Vec2& getDirection() const { return direction_; }
bool isFlipped() const { return flipped_; }
int32 getX() const { return static_cast<int32>(direction_.x); }
int32 getY() const { return static_cast<int32>(direction_.y); }
private:
Vec2 position_;
Vec2 direction_;
bool flipped_;
};
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <frostbite2D/types/type_math.h>
#include <SDL2/SDL_touch.h>
namespace frostbite2D {
class TouchEvent : public Event {
public:
TouchEvent(EventType type, uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, const Vec2& delta, float pressure)
: Event(timestamp)
, type_(type)
, touchId_(touchId)
, fingerId_(fingerId)
, position_(position)
, delta_(delta)
, pressure_(pressure) {}
EventType getType() const override {
return type_;
}
int64 getTouchId() const { return touchId_; }
int64 getFingerId() const { return fingerId_; }
const Vec2& getPosition() const { return position_; }
const Vec2& getDelta() const { return delta_; }
float getPressure() const { return pressure_; }
private:
EventType type_;
int64 touchId_;
int64 fingerId_;
Vec2 position_;
Vec2 delta_;
float pressure_;
};
class TouchDownEvent : public TouchEvent {
public:
TouchDownEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, float pressure)
: TouchEvent(EventType::TouchDown, timestamp, touchId, fingerId,
position, Vec2(0, 0), pressure) {}
};
class TouchUpEvent : public TouchEvent {
public:
TouchUpEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, float pressure)
: TouchEvent(EventType::TouchUp, timestamp, touchId, fingerId,
position, Vec2(0, 0), pressure) {}
};
class TouchMoveEvent : public TouchEvent {
public:
TouchMoveEvent(uint32 timestamp, int64 touchId, int64 fingerId,
const Vec2& position, const Vec2& delta, float pressure)
: TouchEvent(EventType::TouchMove, timestamp, touchId, fingerId,
position, delta, pressure) {}
};
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include <frostbite2D/event/event.h>
#include <SDL2/SDL_events.h>
namespace frostbite2D {
class WindowEvent : public Event {
public:
WindowEvent(EventType type, uint32 timestamp, int32 windowId)
: Event(timestamp)
, type_(type)
, windowId_(windowId) {}
EventType getType() const override {
return type_;
}
int32 getWindowId() const { return windowId_; }
protected:
EventType type_;
int32 windowId_;
};
class WindowCloseEvent : public WindowEvent {
public:
WindowCloseEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowClose, timestamp, windowId) {}
};
class WindowResizeEvent : public WindowEvent {
public:
WindowResizeEvent(uint32 timestamp, int32 windowId, int32 width, int32 height)
: WindowEvent(EventType::WindowResize, timestamp, windowId)
, width_(width)
, height_(height) {}
int32 getWidth() const { return width_; }
int32 getHeight() const { return height_; }
private:
int32 width_;
int32 height_;
};
class WindowFocusEvent : public WindowEvent {
public:
WindowFocusEvent(uint32 timestamp, int32 windowId, bool focused)
: WindowEvent(EventType::WindowFocus, timestamp, windowId)
, focused_(focused) {}
bool isFocused() const { return focused_; }
private:
bool focused_;
};
class WindowMinimizeEvent : public WindowEvent {
public:
WindowMinimizeEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowMinimize, timestamp, windowId) {}
};
class WindowMaximizeEvent : public WindowEvent {
public:
WindowMaximizeEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowMaximize, timestamp, windowId) {}
};
class WindowRestoreEvent : public WindowEvent {
public:
WindowRestoreEvent(uint32 timestamp, int32 windowId)
: WindowEvent(EventType::WindowRestore, timestamp, windowId) {}
};
}

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <frostbite2D/2d/actor.h> #include <frostbite2D/2d/actor.h>
#include <frostbite2D/event/event.h>
#include <vector>
namespace frostbite2D { namespace frostbite2D {
@@ -15,7 +17,15 @@ public:
void Update(float deltaTime) override; void Update(float deltaTime) override;
void Render() override; void Render() override;
bool OnEvent(const Event& event) override;
static Scene* GetCurrent();
private: private:
bool dispatchToChildren(const Event& event);
static Scene* current_;
friend class SceneManager; friend class SceneManager;
}; };

View File

@@ -1,4 +1,9 @@
#include <frostbite2D/2d/actor.h> #include <frostbite2D/2d/actor.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/event/mouse_event.h>
#include <frostbite2D/event/touch_event.h>
#include <frostbite2D/event/joystick_event.h>
#include <frostbite2D/event/window_event.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
namespace frostbite2D { namespace frostbite2D {
@@ -210,5 +215,170 @@ void Actor::RenderChildren() {
} }
} }
uint32 Actor::AddEventListener(EventType type, EventCallback callback) {
EventListener listener;
listener.id = nextListenerId_++;
listener.type = type;
listener.callback = std::move(callback);
eventListeners_.push_back(listener);
return listener.id;
}
bool Actor::RemoveEventListener(uint32 listenerId) {
for (auto it = eventListeners_.begin(); it != eventListeners_.end(); ++it) {
if (it->id == listenerId) {
eventListeners_.erase(it);
return true;
}
}
return false;
}
void Actor::ClearEventListeners() {
eventListeners_.clear();
}
bool Actor::OnEvent(const Event& event) {
if (!eventReceiveEnabled_) {
return false;
}
for (const auto& listener : eventListeners_) {
if (listener.type == event.getType()) {
if (listener.callback(event)) {
return true;
}
}
}
switch (event.getType()) {
case EventType::KeyDown:
return OnKeyDown(static_cast<const KeyEvent&>(event));
case EventType::KeyUp:
return OnKeyUp(static_cast<const KeyEvent&>(event));
case EventType::MouseButtonDown:
return OnMouseDown(static_cast<const MouseEvent&>(event));
case EventType::MouseButtonUp:
return OnMouseUp(static_cast<const MouseEvent&>(event));
case EventType::MouseMove:
return OnMouseMove(static_cast<const MouseEvent&>(event));
case EventType::MouseWheel:
return OnMouseWheel(static_cast<const MouseEvent&>(event));
case EventType::TouchDown:
return OnTouchDown(static_cast<const TouchEvent&>(event));
case EventType::TouchUp:
return OnTouchUp(static_cast<const TouchEvent&>(event));
case EventType::TouchMove:
return OnTouchMove(static_cast<const TouchEvent&>(event));
case EventType::JoystickAxis:
return OnJoystickAxis(static_cast<const JoystickEvent&>(event));
case EventType::JoystickButtonDown:
return OnJoystickButtonDown(static_cast<const JoystickEvent&>(event));
case EventType::JoystickButtonUp:
return OnJoystickButtonUp(static_cast<const JoystickEvent&>(event));
case EventType::WindowResize:
return OnWindowResize(static_cast<const WindowEvent&>(event));
default:
return false;
}
}
bool Actor::OnKeyDown(const KeyEvent& event) {
(void)event;
return false;
}
bool Actor::OnKeyUp(const KeyEvent& event) {
(void)event;
return false;
}
bool Actor::OnMouseDown(const MouseEvent& event) {
(void)event;
return false;
}
bool Actor::OnMouseUp(const MouseEvent& event) {
(void)event;
return false;
}
bool Actor::OnMouseMove(const MouseEvent& event) {
(void)event;
return false;
}
bool Actor::OnMouseWheel(const MouseEvent& event) {
(void)event;
return false;
}
bool Actor::OnTouchDown(const TouchEvent& event) {
(void)event;
return false;
}
bool Actor::OnTouchUp(const TouchEvent& event) {
(void)event;
return false;
}
bool Actor::OnTouchMove(const TouchEvent& event) {
(void)event;
return false;
}
bool Actor::OnJoystickAxis(const JoystickEvent& event) {
(void)event;
return false;
}
bool Actor::OnJoystickButtonDown(const JoystickEvent& event) {
(void)event;
return false;
}
bool Actor::OnJoystickButtonUp(const JoystickEvent& event) {
(void)event;
return false;
}
bool Actor::OnWindowResize(const WindowEvent& event) {
(void)event;
return false;
}
bool Actor::DispatchEvent(const Event& event) {
if (event.isPropagationStopped()) {
return false;
}
if (OnEvent(event)) {
return true;
}
for (auto it = children_.begin(); it != children_.end(); ++it) {
if (*it) {
if ((*it)->DispatchEvent(event)) {
return true;
}
}
}
return false;
}
bool Actor::BubbleEvent(const Event& event) {
if (!parent_) {
return false;
}
if (parent_->OnEvent(event)) {
return true;
}
return parent_->BubbleEvent(event);
}
} }

View File

@@ -1,8 +1,16 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#include <memory>
#include <frostbite2D/audio/audio_system.h> #include <frostbite2D/audio/audio_system.h>
#include <frostbite2D/core/application.h> #include <frostbite2D/core/application.h>
#include <frostbite2D/event/event.h>
#include <frostbite2D/event/key_event.h>
#include <frostbite2D/event/mouse_event.h>
#include <frostbite2D/event/touch_event.h>
#include <frostbite2D/event/joystick_event.h>
#include <frostbite2D/event/window_event.h>
#include <frostbite2D/graphics/camera.h> #include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/renderer.h> #include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/platform/switch.h> #include <frostbite2D/platform/switch.h>
@@ -10,6 +18,7 @@
#include <frostbite2D/resource/npk_archive.h> #include <frostbite2D/resource/npk_archive.h>
#include <frostbite2D/resource/pvf_archive.h> #include <frostbite2D/resource/pvf_archive.h>
#include <frostbite2D/resource/sound_pack_archive.h> #include <frostbite2D/resource/sound_pack_archive.h>
#include <frostbite2D/scene/scene.h>
#include <frostbite2D/scene/scene_manager.h> #include <frostbite2D/scene/scene_manager.h>
#include <frostbite2D/types/type_math.h> #include <frostbite2D/types/type_math.h>
@@ -116,11 +125,18 @@ bool Application::initCoreModules() {
asset.setWorkingDirectory("/switch/Frostbite2D/" + config_.appName); asset.setWorkingDirectory("/switch/Frostbite2D/" + config_.appName);
SDL_Log("Asset working directory: %s", asset.getWorkingDirectory().c_str()); SDL_Log("Asset working directory: %s", asset.getWorkingDirectory().c_str());
#endif #endif
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER) != 0) {
SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
return false; return false;
} }
// 打开第一个手柄(如果存在)
if (SDL_GameController *controller = SDL_GameControllerOpen(0)) {
SDL_Log("GameController opened: %s", SDL_GameControllerName(controller));
} else {
SDL_Log("No GameController found");
}
// 使用SDL2创建窗口 // 使用SDL2创建窗口
this->window_ = new Window(); this->window_ = new Window();
@@ -201,29 +217,19 @@ void Application::resume() {
} }
void Application::mainLoop() { void Application::mainLoop() {
// 记录程序启动时的初始时间(毫秒)
Uint32 start_time = SDL_GetTicks();
// 定时退出时间1秒 = 1000毫秒
const Uint32 EXIT_DELAY = 1000;
while (!shouldQuit_) { while (!shouldQuit_) {
SDL_Event event; SDL_Event sdlEvent;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&sdlEvent)) {
if (event.type == SDL_QUIT) { if (sdlEvent.type == SDL_QUIT) {
shouldQuit_ = true; shouldQuit_ = true;
break; break;
} }
}
// ===================== 新增1秒定时退出 ===================== auto event = convertSDLEvent(sdlEvent);
Uint32 current_time = SDL_GetTicks(); if (event) {
// 判断是否达到1秒 dispatchEvent(*event);
if (current_time - start_time >= EXIT_DELAY) { }
shouldQuit_ = true;
// 跳出事件循环
break;
} }
// ============================================================
if (!paused_) { if (!paused_) {
update(); update();
@@ -233,6 +239,129 @@ void Application::mainLoop() {
} }
} }
std::unique_ptr<Event> Application::convertSDLEvent(const SDL_Event& sdlEvent) {
switch (sdlEvent.type) {
case SDL_WINDOWEVENT: {
switch (sdlEvent.window.event) {
case SDL_WINDOWEVENT_CLOSE:
return std::make_unique<WindowCloseEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID);
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
return std::make_unique<WindowResizeEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID,
sdlEvent.window.data1, sdlEvent.window.data2);
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
return std::make_unique<WindowFocusEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID,
sdlEvent.window.event == SDL_WINDOWEVENT_FOCUS_GAINED);
case SDL_WINDOWEVENT_MINIMIZED:
return std::make_unique<WindowMinimizeEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID);
case SDL_WINDOWEVENT_MAXIMIZED:
return std::make_unique<WindowMaximizeEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID);
case SDL_WINDOWEVENT_RESTORED:
return std::make_unique<WindowRestoreEvent>(sdlEvent.window.timestamp, sdlEvent.window.windowID);
}
break;
}
case SDL_KEYDOWN: {
return std::make_unique<KeyEvent>(sdlEvent.key.timestamp,
static_cast<KeyCode>(sdlEvent.key.keysym.sym),
static_cast<KeyMod>(sdlEvent.key.keysym.mod),
sdlEvent.key.repeat != 0);
}
case SDL_KEYUP: {
return std::make_unique<KeyUpEvent>(sdlEvent.key.timestamp,
static_cast<KeyCode>(sdlEvent.key.keysym.sym),
static_cast<KeyMod>(sdlEvent.key.keysym.mod));
}
case SDL_TEXTINPUT: {
return std::make_unique<KeyTextEvent>(sdlEvent.text.timestamp, sdlEvent.text.text);
}
case SDL_MOUSEMOTION: {
return std::make_unique<MouseMoveEvent>(sdlEvent.motion.timestamp,
Vec2(static_cast<float>(sdlEvent.motion.x), static_cast<float>(sdlEvent.motion.y)),
Vec2(static_cast<float>(sdlEvent.motion.xrel), static_cast<float>(sdlEvent.motion.yrel)));
}
case SDL_MOUSEBUTTONDOWN: {
return std::make_unique<MouseButtonEvent>(sdlEvent.button.timestamp,
static_cast<MouseButton>(sdlEvent.button.button),
Vec2(static_cast<float>(sdlEvent.button.x), static_cast<float>(sdlEvent.button.y)),
true);
}
case SDL_MOUSEBUTTONUP: {
return std::make_unique<MouseButtonEvent>(sdlEvent.button.timestamp,
static_cast<MouseButton>(sdlEvent.button.button),
Vec2(static_cast<float>(sdlEvent.button.x), static_cast<float>(sdlEvent.button.y)),
false);
}
case SDL_MOUSEWHEEL: {
return std::make_unique<MouseWheelEvent>(sdlEvent.wheel.timestamp,
Vec2(static_cast<float>(sdlEvent.wheel.mouseX), static_cast<float>(sdlEvent.wheel.mouseY)),
Vec2(static_cast<float>(sdlEvent.wheel.x), static_cast<float>(sdlEvent.wheel.y)),
sdlEvent.wheel.direction == SDL_MOUSEWHEEL_FLIPPED);
}
case SDL_FINGERDOWN: {
return std::make_unique<TouchDownEvent>(sdlEvent.tfinger.timestamp,
sdlEvent.tfinger.touchId,
sdlEvent.tfinger.fingerId,
Vec2(static_cast<float>(sdlEvent.tfinger.x), static_cast<float>(sdlEvent.tfinger.y)),
sdlEvent.tfinger.pressure);
}
case SDL_FINGERUP: {
return std::make_unique<TouchUpEvent>(sdlEvent.tfinger.timestamp,
sdlEvent.tfinger.touchId,
sdlEvent.tfinger.fingerId,
Vec2(static_cast<float>(sdlEvent.tfinger.x), static_cast<float>(sdlEvent.tfinger.y)),
sdlEvent.tfinger.pressure);
}
case SDL_FINGERMOTION: {
return std::make_unique<TouchMoveEvent>(sdlEvent.tfinger.timestamp,
sdlEvent.tfinger.touchId,
sdlEvent.tfinger.fingerId,
Vec2(static_cast<float>(sdlEvent.tfinger.x), static_cast<float>(sdlEvent.tfinger.y)),
Vec2(static_cast<float>(sdlEvent.tfinger.dx), static_cast<float>(sdlEvent.tfinger.dy)),
sdlEvent.tfinger.pressure);
}
case SDL_CONTROLLERBUTTONDOWN: {
return std::make_unique<JoystickButtonDownEvent>(sdlEvent.cbutton.timestamp,
sdlEvent.cbutton.which,
sdlEvent.cbutton.button);
}
case SDL_CONTROLLERBUTTONUP: {
return std::make_unique<JoystickButtonUpEvent>(sdlEvent.cbutton.timestamp,
sdlEvent.cbutton.which,
sdlEvent.cbutton.button);
}
case SDL_CONTROLLERAXISMOTION: {
return std::make_unique<JoystickAxisEvent>(sdlEvent.caxis.timestamp,
sdlEvent.caxis.which,
sdlEvent.caxis.axis,
sdlEvent.caxis.value);
}
}
return nullptr;
}
void Application::dispatchEvent(const Event& event) {
Scene* currentScene = SceneManager::get().GetCurrentScene();
if (currentScene) {
currentScene->OnEvent(event);
}
}
void Application::update() { void Application::update() {
double currentTime = SDL_GetPerformanceCounter() / static_cast<double>(SDL_GetPerformanceFrequency()); double currentTime = SDL_GetPerformanceCounter() / static_cast<double>(SDL_GetPerformanceFrequency());
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_); deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);

View File

@@ -1,9 +1,13 @@
#include <frostbite2D/scene/scene.h> #include <frostbite2D/scene/scene.h>
#include <algorithm>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
namespace frostbite2D { namespace frostbite2D {
Scene* Scene::current_ = nullptr;
Scene::Scene() { Scene::Scene() {
current_ = this;
} }
Scene::~Scene() { Scene::~Scene() {
@@ -25,5 +29,54 @@ void Scene::Render() {
RenderChildren(); RenderChildren();
} }
Scene* Scene::GetCurrent() {
return current_;
}
bool Scene::OnEvent(const Event& event) {
if (Actor::OnEvent(event)) {
return true;
}
if (dispatchToChildren(event)) {
return true;
}
return false;
}
bool Scene::dispatchToChildren(const Event& event) {
if (event.isPropagationStopped()) {
return false;
}
std::vector<Actor*> sortedChildren;
for (auto it = children_.begin(); it != children_.end(); ++it) {
Actor* child = it->Get();
if (child && child->IsEventReceiveEnabled()) {
sortedChildren.push_back(child);
}
}
std::sort(sortedChildren.begin(), sortedChildren.end(),
[](Actor* a, Actor* b) {
return a->GetEventPriority() < b->GetEventPriority();
});
for (Actor* child : sortedChildren) {
if (child->IsEventInterceptEnabled()) {
if (child->OnEvent(event)) {
return true;
}
} else {
if (child->DispatchEvent(event)) {
return true;
}
}
}
return false;
}
} }

View File

@@ -3,10 +3,12 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <frostbite2D/core/application.h> #include <frostbite2D/core/application.h>
#include <frostbite2D/core/window.h> #include <frostbite2D/core/window.h>
#include <frostbite2D/event/event.h>
#include <frostbite2D/graphics/renderer.h> #include <frostbite2D/graphics/renderer.h>
#include <frostbite2D/graphics/texture.h> #include <frostbite2D/graphics/texture.h>
#include <glad/glad.h> #include <glad/glad.h>
#include <frostbite2D/2d/sprite.h> #include <frostbite2D/2d/sprite.h>
#include <frostbite2D/scene/scene.h> #include <frostbite2D/scene/scene.h>
#include <frostbite2D/scene/scene_manager.h> #include <frostbite2D/scene/scene_manager.h>
@@ -41,8 +43,47 @@ int main(int argc, char **argv) {
// SDL_Log("Starting main loop..."); // SDL_Log("Starting main loop...");
// auto menuScene = MakePtr<Scene>(); auto menuScene = MakePtr<Scene>();
// SceneManager::get().PushScene(menuScene); SceneManager::get().PushScene(menuScene);
auto TestActor = MakePtr<Actor>();
menuScene->AddChild(TestActor);
TestActor->EnableEventReceive();
// 监听手柄按键按下
TestActor->AddEventListener(
EventType::JoystickButtonDown, [](const Event &event) {
const auto &ke = static_cast<const JoystickButtonEvent &>(event);
SDL_Log("Joystick Button Down: device=%d, button=%d", ke.getDeviceId(),
ke.getButton());
if(ke.getButton() == 6) {
Application::get().quit();
}
return true;
});
// 监听手柄按键抬起
TestActor->AddEventListener(
EventType::JoystickButtonUp, [](const Event &event) {
const auto &ke = static_cast<const JoystickButtonEvent &>(event);
SDL_Log("Joystick Button Up: device=%d, button=%d", ke.getDeviceId(),
ke.getButton());
return true;
});
// 监听手柄轴
TestActor->AddEventListener(EventType::JoystickAxis, [](const Event &event) {
const auto &ae = static_cast<const JoystickAxisEvent &>(event);
if (std::abs(ae.getNormalizedValue()) > 0.1f) {
SDL_Log("Joystick Axis: device=%d, axis=%d, value=%.2f", ae.getDeviceId(),
ae.getAxis(), ae.getNormalizedValue());
}
return true;
});
// // 尝试加载精灵 // // 尝试加载精灵
// auto sprite = Sprite::createFromFile("assets/player.png"); // auto sprite = Sprite::createFromFile("assets/player.png");