Skip to content

Commit 4f36538

Browse files
authored
Merge pull request #24 from devscafecommunity/22-input-system
feat(input): implement action-based input system with remappable bind…
2 parents ee19801 + 467d7e5 commit 4f36538

5 files changed

Lines changed: 1069 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ endif()
1515
add_library(Caffeine
1616
src/core/Timer.cpp
1717
src/core/GameLoop.cpp
18+
src/input/InputManager.cpp
1819
)
1920

2021
target_include_directories(Caffeine PUBLIC

src/input/InputManager.cpp

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#include "InputManager.hpp"
2+
3+
namespace Caffeine::Input {
4+
5+
InputManager::InputManager() {
6+
m_keyState.fill(false);
7+
m_mouseState.fill(false);
8+
m_gamepadButtonState.fill(false);
9+
m_gamepadAxisState.fill(0.0f);
10+
m_prevKeyState.fill(false);
11+
m_prevMouseState.fill(false);
12+
m_prevGamepadButtonState.fill(false);
13+
setupDefaultBindings();
14+
}
15+
16+
void InputManager::beginFrame() {
17+
m_prevKeyState = m_keyState;
18+
m_prevMouseState = m_mouseState;
19+
m_prevGamepadButtonState = m_gamepadButtonState;
20+
m_prevMousePos = m_mousePos;
21+
m_prevActionStates = m_actionStates;
22+
}
23+
24+
void InputManager::endFrame() {
25+
for (usize i = 0; i < static_cast<usize>(Action::Count); ++i) {
26+
auto action = static_cast<Action>(i);
27+
const auto& bindings = m_actionBindings[i];
28+
29+
bool active = false;
30+
for (u8 b = 0; b < bindings.count; ++b) {
31+
if (isBindingActive(bindings.bindings[b])) {
32+
active = true;
33+
break;
34+
}
35+
}
36+
37+
bool wasActive = m_prevActionStates[i].pressed;
38+
39+
ActionState& state = m_actionStates[i];
40+
state.pressed = active;
41+
state.justPressed = active && !wasActive;
42+
state.justReleased = !active && wasActive;
43+
44+
if (state.justPressed) fireActionPressed(action);
45+
if (state.justReleased) fireActionReleased(action);
46+
}
47+
}
48+
49+
void InputManager::injectKeyDown(Key key) {
50+
auto idx = static_cast<usize>(key);
51+
if (idx < m_keyState.size()) m_keyState[idx] = true;
52+
}
53+
54+
void InputManager::injectKeyUp(Key key) {
55+
auto idx = static_cast<usize>(key);
56+
if (idx < m_keyState.size()) m_keyState[idx] = false;
57+
}
58+
59+
void InputManager::injectMouseButtonDown(MouseButton button) {
60+
auto idx = static_cast<usize>(button);
61+
if (idx < m_mouseState.size()) m_mouseState[idx] = true;
62+
}
63+
64+
void InputManager::injectMouseButtonUp(MouseButton button) {
65+
auto idx = static_cast<usize>(button);
66+
if (idx < m_mouseState.size()) m_mouseState[idx] = false;
67+
}
68+
69+
void InputManager::injectMouseMove(f32 x, f32 y) {
70+
m_mousePos = Vec2(x, y);
71+
}
72+
73+
void InputManager::injectGamepadButtonDown(GamepadButton button) {
74+
auto idx = static_cast<usize>(button);
75+
if (idx < m_gamepadButtonState.size()) m_gamepadButtonState[idx] = true;
76+
}
77+
78+
void InputManager::injectGamepadButtonUp(GamepadButton button) {
79+
auto idx = static_cast<usize>(button);
80+
if (idx < m_gamepadButtonState.size()) m_gamepadButtonState[idx] = false;
81+
}
82+
83+
void InputManager::injectGamepadAxis(GamepadAxis axis, f32 value) {
84+
auto idx = static_cast<usize>(axis);
85+
if (idx < m_gamepadAxisState.size()) m_gamepadAxisState[idx] = value;
86+
}
87+
88+
ActionState InputManager::actionState(Action action) const {
89+
auto idx = static_cast<usize>(action);
90+
if (idx < m_actionStates.size()) return m_actionStates[idx];
91+
return {};
92+
}
93+
94+
AxisState InputManager::axisState(Axis axis) const {
95+
auto idx = static_cast<usize>(axis);
96+
if (idx >= static_cast<usize>(Axis::Count)) return {};
97+
98+
const auto& pair = m_axisBindings[idx];
99+
if (!pair.bound) return {};
100+
101+
f32 value = 0.0f;
102+
103+
if (pair.negative.type == BindingType::GamepadAxis &&
104+
pair.positive.type == BindingType::GamepadAxis &&
105+
pair.negative == pair.positive) {
106+
auto axisIdx = static_cast<usize>(pair.negative.gamepadAxis);
107+
if (axisIdx < m_gamepadAxisState.size()) {
108+
value = m_gamepadAxisState[axisIdx];
109+
}
110+
} else {
111+
f32 neg = isBindingActive(pair.negative) ? -1.0f : 0.0f;
112+
f32 pos = isBindingActive(pair.positive) ? 1.0f : 0.0f;
113+
value = neg + pos;
114+
}
115+
116+
f32 prevValue = 0.0f;
117+
if (pair.negative.type == BindingType::GamepadAxis &&
118+
pair.positive.type == BindingType::GamepadAxis &&
119+
pair.negative == pair.positive) {
120+
prevValue = value;
121+
} else {
122+
f32 prevNeg = wasBindingActive(pair.negative) ? -1.0f : 0.0f;
123+
f32 prevPos = wasBindingActive(pair.positive) ? 1.0f : 0.0f;
124+
prevValue = prevNeg + prevPos;
125+
}
126+
127+
AxisState state;
128+
state.value = value;
129+
state.delta = value - prevValue;
130+
return state;
131+
}
132+
133+
Vec2 InputManager::mousePosition() const {
134+
return m_mousePos;
135+
}
136+
137+
Vec2 InputManager::mouseDelta() const {
138+
return m_mousePos - m_prevMousePos;
139+
}
140+
141+
void InputManager::bind(Action action, Binding binding) {
142+
auto idx = static_cast<usize>(action);
143+
if (idx >= static_cast<usize>(Action::Count)) return;
144+
145+
auto& ab = m_actionBindings[idx];
146+
if (ab.count >= MAX_BINDINGS_PER_ACTION) return;
147+
ab.bindings[ab.count] = binding;
148+
++ab.count;
149+
}
150+
151+
void InputManager::clearBindings(Action action) {
152+
auto idx = static_cast<usize>(action);
153+
if (idx >= static_cast<usize>(Action::Count)) return;
154+
m_actionBindings[idx].count = 0;
155+
}
156+
157+
void InputManager::clearAllBindings() {
158+
for (usize i = 0; i < static_cast<usize>(Action::Count); ++i) {
159+
m_actionBindings[i].count = 0;
160+
}
161+
for (usize i = 0; i < static_cast<usize>(Axis::Count); ++i) {
162+
m_axisBindings[i].bound = false;
163+
}
164+
}
165+
166+
void InputManager::resetToDefaults() {
167+
clearAllBindings();
168+
setupDefaultBindings();
169+
}
170+
171+
void InputManager::bindAxis(Axis axis, Binding negative, Binding positive) {
172+
auto idx = static_cast<usize>(axis);
173+
if (idx >= static_cast<usize>(Axis::Count)) return;
174+
m_axisBindings[idx].negative = negative;
175+
m_axisBindings[idx].positive = positive;
176+
m_axisBindings[idx].bound = true;
177+
}
178+
179+
void InputManager::setCallbackHandler(IInputCallbacks* handler) {
180+
m_callbackHandler = handler;
181+
}
182+
183+
void InputManager::setupDefaultBindings() {
184+
bind(Action::MoveUp, Binding::fromKey(Key::W));
185+
bind(Action::MoveDown, Binding::fromKey(Key::S));
186+
bind(Action::MoveLeft, Binding::fromKey(Key::A));
187+
bind(Action::MoveRight, Binding::fromKey(Key::D));
188+
bind(Action::Jump, Binding::fromKey(Key::Space));
189+
bind(Action::Pause, Binding::fromKey(Key::Escape));
190+
191+
bind(Action::MoveUp, Binding::fromKey(Key::Up));
192+
bind(Action::MoveDown, Binding::fromKey(Key::Down));
193+
bind(Action::MoveLeft, Binding::fromKey(Key::Left));
194+
bind(Action::MoveRight, Binding::fromKey(Key::Right));
195+
}
196+
197+
bool InputManager::isBindingActive(const Binding& binding) const {
198+
switch (binding.type) {
199+
case BindingType::Key: {
200+
auto idx = static_cast<usize>(binding.key);
201+
return idx < m_keyState.size() && m_keyState[idx];
202+
}
203+
case BindingType::MouseButton: {
204+
auto idx = static_cast<usize>(binding.mouseButton);
205+
return idx < m_mouseState.size() && m_mouseState[idx];
206+
}
207+
case BindingType::GamepadButton: {
208+
auto idx = static_cast<usize>(binding.gamepadButton);
209+
return idx < m_gamepadButtonState.size() && m_gamepadButtonState[idx];
210+
}
211+
case BindingType::GamepadAxis: {
212+
auto idx = static_cast<usize>(binding.gamepadAxis);
213+
return idx < m_gamepadAxisState.size() && m_gamepadAxisState[idx] > 0.5f;
214+
}
215+
}
216+
return false;
217+
}
218+
219+
bool InputManager::wasBindingActive(const Binding& binding) const {
220+
switch (binding.type) {
221+
case BindingType::Key: {
222+
auto idx = static_cast<usize>(binding.key);
223+
return idx < m_prevKeyState.size() && m_prevKeyState[idx];
224+
}
225+
case BindingType::MouseButton: {
226+
auto idx = static_cast<usize>(binding.mouseButton);
227+
return idx < m_prevMouseState.size() && m_prevMouseState[idx];
228+
}
229+
case BindingType::GamepadButton: {
230+
auto idx = static_cast<usize>(binding.gamepadButton);
231+
return idx < m_prevGamepadButtonState.size() && m_prevGamepadButtonState[idx];
232+
}
233+
case BindingType::GamepadAxis:
234+
return false;
235+
}
236+
return false;
237+
}
238+
239+
void InputManager::fireActionPressed(Action action) {
240+
if (onActionPressed) onActionPressed(action);
241+
if (m_callbackHandler) m_callbackHandler->onActionPressed(action);
242+
}
243+
244+
void InputManager::fireActionReleased(Action action) {
245+
if (onActionReleased) onActionReleased(action);
246+
if (m_callbackHandler) m_callbackHandler->onActionReleased(action);
247+
}
248+
249+
} // namespace Caffeine::Input

0 commit comments

Comments
 (0)