Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ set(GRAPHICS_DIRECTX12_SOURCES
src/Graphics/DirectX12/DirectX12Texture.cpp
)

set(INPUT_SOURCES
src/Input/Input.cpp
)

set(LIGHTING_SOURCES
src/Lighting/AreaLight.cpp
src/Lighting/DirectionalLight.cpp
Expand Down Expand Up @@ -225,6 +229,7 @@ set(ENGINE_SOURCES
${GRAPHICS_COMMON_SOURCES}
${GRAPHICS_OPENGL_SOURCES}
${GRAPHICS_VULKAN_SOURCES}
${INPUT_SOURCES}
${LIGHTING_SOURCES}
${MATH_SOURCES}
${PHYSICS_SOURCES}
Expand Down
79 changes: 49 additions & 30 deletions docs/pages/events_and_input.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,18 @@ as used throughout `src/Scene/FirstPersonController.cpp`.
public `Handled` flag. Real subclasses:

- `Sleak::Events` (`Events/ApplicationEvent.hpp`): `WindowResizeEvent`,
`WindowOpenEvent`, `WindowFullScreen`, `WindowCloseEvent`, `TickEvent`,
`UpdateEvent`, `RenderEvent`.
`WindowOpenEvent`, `WindowFullScreen`, `WindowFocusEvent`,
`WindowLostFocusEvent`, `WindowMovedEvent`, `WindowCloseEvent`,
`TickEvent`, `UpdateEvent`, `RenderEvent`.
- `Sleak::Events::Input` (`Events/KeyboardEvent.hpp`): `KeyEvent` (base),
`KeyPressedEvent` (has `IsRepeat()`), `KeyReleasedEvent`, `KeyTypedEvent`.
`KeyPressedEvent` (has `IsRepeat()`), `KeyReleasedEvent`, `KeyTypedEvent`
(UTF-8 in `GetText()`, sent while SDL text input is active).
- `Sleak::Events::Input` (`Events/MouseEvent.hpp`): `MouseMovedEvent`,
`MouseScrolledEvent`, `MouseButtonEvent` (base),
`MouseButtonPressedEvent`, `MouseButtonReleasedEvent`.

The `EventType` enum also lists `WindowFocus`, `WindowLostFocus`, and
`WindowMoved`, but no corresponding `Event` subclasses exist for them yet.
- `Sleak::Events::Input` (`Events/GamepadEvent.hpp`):
`GamepadConnectedEvent`, `GamepadDisconnectedEvent`, both carrying the
`Input::Gamepad` index.

Underneath `EventDispatcher`, `Sleak::Delegate<Args...>`
(`include/public/Events/Delegate.hpp`) wraps a `std::function<void(Args...)>`.
Expand All @@ -101,30 +103,47 @@ Two macros in `Events/KeyboardEvent.hpp` test a key inside an event handler:

## 2. Input

There is no working hardware-state polling API in the engine.
`Sleak::Input::InputManager` (`include/public/Input/InputManager.hpp`) only
declares `RegisterListener` / `UnregisterListener` for an
`InputEventListener`; it has no key or mouse query methods, no `.cpp`
implementation, and is referenced only by the equally unused
`InputAction.hpp`. `Sleak::Input::Keyboard`
(`include/public/Input/Keyboard.hpp`) declares
`IsKeyPressed` / `IsKeyHold` / `IsKeyReleased(KEY_CODE)`, but also has no
implementation and no call sites anywhere in the engine.
`include/public/Input/Mouse.hpp` is not a class at all; it contains only a
commented-out code snippet.

In practice, held-key state is tracked ad hoc per component by registering
`OnKeyPressed` / `OnKeyReleased` handlers through `EventDispatcher` and
flipping local booleans, as `FirstPersonController` does. Key and mouse
codes are `Sleak::Input::KEY_CODE` and `Sleak::Input::MOUSE_CODE`
(`include/public/Input/KeyCodes.hpp`). `KEY_CODE` values line up with SDL
scancodes, and `MOUSE_CODE::ButtonLeft` equals SDL's `Button1` /
`SDL_BUTTON_LEFT`.

Cursor capture has no engine-level API either; controllers toggle it
directly with SDL calls, e.g.
`FirstPersonController::ToggleCursor()` calling `SDL_ShowCursor()` /
`SDL_HideCursor()`.
Next to the events there is a per-frame polling API in
`include/public/Input/`. `Window::Update()` resets it at the start of every
frame and then folds each SDL event into it, so anything that runs in the
scene update sees this frame's input. Game code never needs SDL for it.
Everything is static and main-thread only.

- `Input::Keyboard`: `IsKeyPressed` / `IsKeyHold` / `IsKeyReleased(KEY_CODE)`.
Pressed and released are edges for this frame and survive a tap that goes
down and up between two frames. OS key repeat is ignored.
- `Input::Mouse`: the same three for `MOUSE_CODE`, plus `GetPosition()`,
`GetDelta()` (motion since last frame, also valid in relative mode) and
`GetWheel()`. `SetCursorVisible()` shows or hides the cursor.
- `Input::Gamepad`: up to `MaxGamepads` (4) controllers through SDL3's
gamepad API, with hot plug. Buttons use `GAMEPAD_BUTTON`, axes use
`GAMEPAD_AXIS`. `GetAxis()` applies a per-axis deadzone (0.15 by default,
`SetDeadzone()`), `GetRawAxis()` does not.
- `Input::InputManager`: named actions. `AddAction("Jump")` returns an
`InputAction` you bind keys, mouse buttons, gamepad buttons and gamepad
axes (with a threshold) to, then query with `IsActionPressed` /
`IsActionHold` / `IsActionReleased` / `GetActionValue`. Pressed and
released are for the action as a whole across all of its bindings.
`RegisterListener` / `UnregisterListener` still exist for raw
`InputEventListener` callbacks.

```cpp
using namespace Sleak::Input;
InputManager::AddAction("Jump")
.BindKey(KEY_CODE::KEY__SPACE)
.BindGamepadButton(GAMEPAD_BUTTON::SOUTH);

if (InputManager::IsActionPressed("Jump")) Jump();
Math::Vector2D look = Mouse::GetDelta();
```

When the window loses focus every held key and mouse button is released,
so nothing stays stuck down. `FirstPersonController` and
`FreeLookCameraController` read their movement keys and mouse delta from
this API. Key and mouse codes are `Sleak::Input::KEY_CODE` and
`Sleak::Input::MOUSE_CODE` (`include/public/Input/KeyCodes.hpp`).
`KEY_CODE` values line up with SDL scancodes, and `MOUSE_CODE::ButtonLeft`
equals SDL's `Button1` / `SDL_BUTTON_LEFT`.

---

Expand Down
24 changes: 24 additions & 0 deletions include/private/Input/InputBackend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef _INPUT_BACKEND_HPP_
#define _INPUT_BACKEND_HPP_

#include <SDL3/SDL_events.h>

namespace Sleak {
namespace Input {
namespace Backend {

/// Clears last frame's edges and deltas; call once before pumping events.
void BeginFrame();
/// Folds one SDL event into the polling state and raw listeners.
/// Returns the gamepad index for a connect/disconnect event, else -1.
int ProcessEvent(const SDL_Event& event);
/// Releases every key and button, e.g. when the window loses focus.
void ReleaseAll();
/// Closes all open gamepads; call before SDL_Quit.
void Shutdown();

} // namespace Backend
} // namespace Input
} // namespace Sleak

#endif
85 changes: 43 additions & 42 deletions include/public/ECS/Components/FirstPersonController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,52 +101,53 @@ namespace Sleak {
void SetPitch(float pitch) { m_pitch = pitch; }
void SetYaw(float yaw) { m_yaw = yaw; }

/// Updates translation/sprint input state from a key-down event.
/// No longer used: movement keys are polled in Update(). Kept so
/// existing callers still compile.
void OnKeyPressed(const Events::Input::KeyPressedEvent& e);
/// Clears translation/sprint input state from a key-up event.
/// No longer used: movement keys are polled in Update().
void OnKeyReleased(const Events::Input::KeyReleasedEvent& e);

private:
/// Reads mouse delta into yaw/pitch, clamped to m_pitchRange.
void UpdateInput(float deltaTime) override;
/// Applies UE-style acceleration/braking to velocity, resolves jump/fly, and moves the camera.
void UpdateCamera(float deltaTime) override;

// UE-style movement parameters — tuned slower for a calmer pace
float m_maxWalkSpeed = 2.5f; // Casual walking pace
float m_sprintMultiplier = 2.0f; // Sprint speed = walk * multiplier
bool m_sprinting = false;
float m_maxAcceleration = 10.0f; // Gentle ramp-up (~0.25s to full speed)
float m_brakingDeceleration = 12.0f; // Smooth stop
float m_groundFriction = 8.0f; // UE default
float m_airControl = 0.05f; // Minimal air control

// Jump — gentle hop, ~0.45m height
// v = sqrt(2 * 9.81 * 0.45) ≈ 2.97
float m_jumpZVelocity = 3.0f;

// Fly state
bool m_flying = false;
float m_maxFlySpeed = 10.0f;
float m_flySprintMultiplier = 2.5f;
float m_flyVerticalInput = 0.0f;
float m_flyVerticalVelocity = 0.0f;

// Mouse look
float m_pitch = 0.0f;
float m_yaw = 0.0f;
Math::Vector2D m_pitchRange = Math::Vector2D(-89, 89);

// Horizontal velocity (managed by controller, separate from rigidbody Y axis)
Math::Vector3D m_velocity;

// Input state
bool m_firstFrame = true;

RigidbodyComponent* m_rigidbody = nullptr;

std::string m_keyPressedHandlerId;
std::string m_keyReleasedHandlerId;
/// Polls movement keys and reads mouse delta into yaw/pitch, clamped to
/// m_pitchRange.
void UpdateInput(float deltaTime) override;
/// Applies UE-style acceleration/braking to velocity, resolves jump/fly,
/// and moves the camera.
void UpdateCamera(float deltaTime) override;

// UE-style movement parameters — tuned slower for a calmer pace
float m_maxWalkSpeed = 2.5f; // Casual walking pace
float m_sprintMultiplier = 2.0f; // Sprint speed = walk * multiplier
bool m_sprinting = false;
float m_maxAcceleration = 10.0f; // Gentle ramp-up (~0.25s to full speed)
float m_brakingDeceleration = 12.0f; // Smooth stop
float m_groundFriction = 8.0f; // UE default
float m_airControl = 0.05f; // Minimal air control

// Jump — gentle hop, ~0.45m height
// v = sqrt(2 * 9.81 * 0.45) ≈ 2.97
float m_jumpZVelocity = 3.0f;

// Fly state
bool m_flying = false;
float m_maxFlySpeed = 10.0f;
float m_flySprintMultiplier = 2.5f;
float m_flyVerticalInput = 0.0f;
float m_flyVerticalVelocity = 0.0f;

// Mouse look
float m_pitch = 0.0f;
float m_yaw = 0.0f;
Math::Vector2D m_pitchRange = Math::Vector2D(-89, 89);

// Horizontal velocity (managed by controller, separate from rigidbody Y
// axis)
Math::Vector3D m_velocity;

// Input state
bool m_firstFrame = true;

RigidbodyComponent* m_rigidbody = nullptr;
};

} // namespace Sleak
Expand Down
69 changes: 35 additions & 34 deletions include/public/ECS/Components/FreeLookCameraController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,44 +52,45 @@ namespace Sleak {
void SetRoll(float roll) { this->roll = roll; }
float GetRoll() const { return roll; }


/// Updates translation input state from a key-down event; doubles speed while LCTRL is held.
/// No longer used: movement keys are polled in Update(). Kept so
/// existing overrides still compile.
virtual void OnKeyPressed(const Sleak::Events::Input::KeyPressedEvent& e);
/// Clears translation input state from a key-up event.
/// No longer used: movement keys are polled in Update().
virtual void OnKeyReleased(const Sleak::Events::Input::KeyReleasedEvent& e);

private:
/// Reads and smooths mouse delta into yaw/pitch, clamped to PitchRange.
void UpdateInput(float deltaTime) override;
/// Accelerates toward the input-driven target velocity, applies damping, and moves the camera.
void UpdateCamera(float deltaTime) override;

/// Exponentially decays velocity toward zero when there's no translation input.
void ApplyDamping(float DeltaTime);
/// Accelerates velocity toward the input-driven target velocity.
void ApplyAcceleration(float DeltaTime);
/// Clamps velocity magnitude to maxSpeed.
void ClampVelocity();

Math::Vector3D velocity;
float speed = 1.0f;
float sensitivity = 0.1f;
float acceleration;
float damping;
float maxSpeed;

bool isInvertY = false;
float pitch = 0.0f;
float yaw = 0.0f;
float roll = 0.0f;
Math::Vector2D PitchRange;
Math::Vector2D YawRange;
Math::Vector2D RollRange;

bool m_firstFrame = true;

std::string m_keyPressedHandlerId;
std::string m_keyReleasedHandlerId;
/// Polls movement keys, then reads and smooths mouse delta into yaw/pitch,
/// clamped to PitchRange.
void UpdateInput(float deltaTime) override;
/// Accelerates toward the input-driven target velocity, applies damping,
/// and moves the camera.
void UpdateCamera(float deltaTime) override;

/// Exponentially decays velocity toward zero when there's no translation
/// input.
void ApplyDamping(float DeltaTime);
/// Accelerates velocity toward the input-driven target velocity.
void ApplyAcceleration(float DeltaTime);
/// Clamps velocity magnitude to maxSpeed.
void ClampVelocity();

Math::Vector3D velocity;
float speed = 1.0f;
float sensitivity = 0.1f;
float acceleration;
float damping;
float maxSpeed;

bool isInvertY = false;
float pitch = 0.0f;
float yaw = 0.0f;
float roll = 0.0f;
Math::Vector2D PitchRange;
Math::Vector2D YawRange;
Math::Vector2D RollRange;

bool m_firstFrame = true;
bool m_boost = false;
};
}

Expand Down
33 changes: 33 additions & 0 deletions include/public/Events/ApplicationEvent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,39 @@ namespace Sleak {
bool bIsFullScreen;
};

/// Fired when the window gains keyboard focus.
/// @ingroup events
class ENGINE_API WindowFocusEvent : public Event {
public:
WindowFocusEvent() = default;
EVENT_CLASS_TYPE(WindowFocus)
EVENT_CLASS_CATEGORY(EventCategory::Application)
};

/// Fired when the window loses keyboard focus. Held keys and buttons
/// are released in the polling state at the same time.
/// @ingroup events
class ENGINE_API WindowLostFocusEvent : public Event {
public:
WindowLostFocusEvent() = default;
EVENT_CLASS_TYPE(WindowLostFocus)
EVENT_CLASS_CATEGORY(EventCategory::Application)
};

/// Fired when the window moves, carrying its new top-left position on
/// the desktop.
/// @ingroup events
class ENGINE_API WindowMovedEvent : public Event {
public:
WindowMovedEvent(int x, int y) : x(x), y(y) {}
int GetX() const { return x; }
int GetY() const { return y; }
EVENT_CLASS_TYPE(WindowMoved)
EVENT_CLASS_CATEGORY(EventCategory::Application)
private:
int x, y;
};

/// Fired when the window is about to close.
/// @ingroup events
class ENGINE_API WindowCloseEvent : public Event {
Expand Down
Loading
Loading