feat: input system

main
Sara 2024-11-22 18:16:06 +01:00
parent 4a5f4dcb6b
commit a504bae0f6
6 changed files with 354 additions and 0 deletions

View File

@ -0,0 +1,86 @@
#include "input_effects.hpp"
namespace ce {
bool GamepadButton::evaluate(SDL_Event const &evt, InputValue &value) const {
switch(evt.type) {
case SDL_CONTROLLERBUTTONDOWN:
if(this->button == evt.cbutton.button && this->which == evt.cbutton.which) {
value = true;
return true;
}
break;
case SDL_CONTROLLERBUTTONUP:
if(this->button == evt.cbutton.button && this->which == evt.cbutton.which) {
value = false;
return true;
}
break;
default: break;
}
return false;
}
bool GamepadAxis::evaluate(SDL_Event const &evt, InputValue &value) const {
switch(evt.type) {
case SDL_CONTROLLERAXISMOTION:
if(evt.caxis.axis == this->axis && evt.caxis.which == this->which) {
value = float(evt.caxis.value) / 32767.f;
return true;
}
break;
default: break;
}
return false;
}
bool KeyboardScancode::evaluate(SDL_Event const &evt, InputValue &value) const {
switch(evt.type) {
case SDL_KEYDOWN:
if(evt.key.keysym.scancode == this->code) {
value = 1.f;
return true;
}
break;
case SDL_KEYUP:
if(evt.key.keysym.scancode == this->code) {
value = 0.f;
return true;
}
break;
default: break;
}
return false;
}
bool KeyboardKey::evaluate(SDL_Event const &evt, InputValue &value) const {
switch(evt.type) {
case SDL_KEYDOWN:
if(evt.key.keysym.sym == this->key) {
value = 1.f;
return true;
}
break;
case SDL_KEYUP:
if(evt.key.keysym.scancode == this->key) {
value = 0.f;
return true;
}
break;
default: break;
}
return false;
}
bool ButtonAxis::evaluate(SDL_Event const &evt, InputValue &value) const {
bool changed{false};
InputValue neg_value{0.f};
InputValue pos_value{0.f};
if(neg != nullptr) changed |= neg->evaluate(evt, neg_value);
if(pos != nullptr) changed |= pos->evaluate(evt, pos_value);
if(changed && neg_value.is<float>() && pos_value.is<float>()) {
value = pos_value.get<float>().value() - neg_value.get<float>().value();
return true;
}
return false;
}
}

View File

@ -0,0 +1,53 @@
#ifndef CORE_INPUT_EFFECTS_HPP
#define CORE_INPUT_EFFECTS_HPP
#include "input_value.hpp"
#include <memory>
#include <SDL2/SDL_events.h>
namespace ce {
struct InputEffect {
typedef std::unique_ptr<InputEffect> Ptr;
template<class Effect, typename... Param> Ptr make(Param... p);
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const = 0;
};
struct GamepadButton : InputEffect {
GamepadButton(SDL_JoystickID device, SDL_GameControllerButton button);
SDL_JoystickID which{0};
SDL_GameControllerButton button{SDL_CONTROLLER_BUTTON_A};
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override;
};
struct GamepadAxis : InputEffect {
GamepadAxis(SDL_JoystickID device, SDL_GameControllerAxis axis);
SDL_JoystickID which{0};
SDL_GameControllerAxis axis{SDL_CONTROLLER_AXIS_LEFTX};
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override;
};
struct KeyboardScancode : InputEffect {
KeyboardScancode(SDL_Scancode code);
SDL_Scancode code{SDL_SCANCODE_0};
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override;
};
struct KeyboardKey : InputEffect {
KeyboardKey(SDL_Keycode code);
SDL_Keycode key{SDLK_KP_0};
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override;
};
struct ButtonAxis : InputEffect {
ButtonAxis(InputEffect::Ptr &negative, InputEffect::Ptr &positive);
InputEffect::Ptr neg{};
InputEffect::Ptr pos{};
virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override;
};
template <class Effect, typename... Param> InputEffect::Ptr InputEffect::make(Param... p) {
return std::dynamic_pointer_cast<std::unique_ptr<InputEffect>>(std::make_unique<Effect>(p...));
}
}
#endif // !CORE_INPUT_EFFECTS_HPP

View File

@ -0,0 +1,45 @@
#include "input_map.hpp"
#include <cstring>
#include <SDL2/SDL_events.h>
#include <functional>
namespace ce {
InputAction::InputAction(InputEffect::Ptr &effect) {
this->effects.push_back(std::move(effect));
}
InputAction::InputAction(std::vector<InputEffect::Ptr> &effects) {
for(InputEffect::Ptr &effect : effects)
this->effects.push_back(std::move(effect));
}
void InputAction::add_listener(ListenerFunction function, void *handle) {
ListenerHandle actual{ListenerHandle(handle)};
this->listeners.push_back({function, actual});
}
void InputAction::remove_listeners(void *handle) {
ListenerHandle actual{ListenerHandle(handle)};
std::erase_if(this->listeners,
[&actual](Listener const &listener) -> bool {
return listener.second == actual;
});
}
void InputAction::process_event(SDL_Event const &evt) {
bool changed{false}; // set to true if *any* effect returns as changed
InputValue value{}; // final value of input
// evaluate each event in order
for(InputEffect::Ptr &effect : this->effects)
changed |= effect->evaluate(evt, value);
// invoke listeners if any value in the chain was changed
if(changed)
for(Listener &listener : this->listeners)
std::invoke(listener.first, value);
}
void InputMap::bind_input(std::string const &name, std::vector<InputEffect::Ptr> &effects) {
InputAction action{InputAction(effects)};
this->bindings.insert({name, std::move(action)});
}
}

View File

@ -0,0 +1,61 @@
#ifndef CORE_INPUT_MAP_HPP
#define CORE_INPUT_MAP_HPP
#include "input_effects.hpp"
#include "input_value.hpp"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <map>
#include <utility>
#include <vector>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_gamecontroller.h>
#include <SDL2/SDL_joystick.h>
#include <SDL2/SDL_keyboard.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_scancode.h>
#include <SDL2/SDL_stdinc.h>
namespace ce {
/*! Represents a collection of bindings.
*/
class InputAction {
typedef std::uintptr_t ListenerHandle; //!< handle used to connect listener functions to origin
typedef std::function<void(InputValue)> ListenerFunction;
typedef std::pair<ListenerFunction, ListenerHandle> Listener;
private:
std::vector<InputEffect::Ptr> effects{}; //!< list of all effects used to produce final result
std::vector<Listener> listeners{}; //!< list of all listeners to invoke on completion
public:
InputAction() = default;
InputAction(InputEffect::Ptr &effect);
InputAction(std::vector<InputEffect::Ptr> &effects);
void add_listener(ListenerFunction function, void *handle); //!< add a listener identified under a specific handle
void remove_listeners(void *handle); //!< remove all listeners identified under this handle
void process_event(SDL_Event const &evt); //!< process an incoming OS event
template <class Effect> Effect *get_effect_of_type(); //!< get the first effect from the effects list that is of type Effect
};
/*! Map input values to functions.
*/
class InputMap {
std::map<std::string, InputAction> bindings{};
public:
void bind_input(std::string const &name, std::vector<InputEffect::Ptr> &binds);
InputAction &get_action(std::string const &action_id);
};
template <class Effect> Effect *InputAction::get_effect_of_type() {
std::vector<InputEffect::Ptr>::iterator itr{std::find_if(this->effects.begin(), this->effects.end(),
[](InputEffect::Ptr const &effect) {
return dynamic_cast<Effect*>(effect.get()) != nullptr;
})};
return itr == this->effects.end() ? nullptr : dynamic_cast<Effect*>(itr->get());
}
}
#endif // !CORE_INPUT_MAP_HPP

View File

@ -0,0 +1,76 @@
#include "input_value.hpp"
#include <cstring>
namespace ce {
InputValue::InputValue()
: type{INPUTVALUE_BOOL} {
std::memset(&this->type, 0x0, sizeof(this->data));
}
InputValue::InputValue(bool b) : InputValue() {
this->type = INPUTVALUE_BOOL;
this->data.bool_value = b;
}
InputValue::InputValue(float f) : InputValue() {
this->type = INPUTVALUE_FLOAT;
this->data.float_value = f;
}
InputValue::InputValue(Vecf v) : InputValue() {
this->type = INPUTVALUE_VEC;
this->data.vec_value[0] = v.x;
this->data.vec_value[1] = v.y;
}
InputValue::InputValue(InputValue const &src) : InputValue() {
this->type = src.type;
this->data = src.data;
}
InputValue &InputValue::operator=(InputValue const &src) {
this->type = src.type;
this->data = src.data;
return *this;
}
template <> bool InputValue::is<bool>() const {
return this->type == INPUTVALUE_BOOL;
}
template <> bool InputValue::is<float>() const {
return this->type == INPUTVALUE_FLOAT;
}
template <> bool InputValue::is<Vecf>() const {
return this->type == INPUTVALUE_VEC;
}
template <> std::optional<bool> InputValue::get<bool>() const {
switch(this->type) {
case INPUTVALUE_BOOL:
return this->data.bool_value;
case INPUTVALUE_FLOAT:
return this->data.float_value > 0.f;
case INPUTVALUE_VEC:
return this->data.vec_value[0] != 0.f && this->data.vec_value[1] != 0.f;
default: return std::nullopt;
}
}
template <> std::optional<float> InputValue::get<float>() const {
switch(this->type) {
case INPUTVALUE_BOOL:
return this->data.bool_value ? 1.f : 0.f;
case INPUTVALUE_FLOAT:
return this->data.float_value;
case INPUTVALUE_VEC:
return Vecf(this->data.vec_value[0], this->data.vec_value[1]).magnitude();
default: return std::nullopt;
}
}
template <> std::optional<Vecf> InputValue::get<Vecf>() const {
switch(this->type) {
case INPUTVALUE_VEC:
return Vecf(this->data.vec_value[0], this->data.vec_value[1]);
default: return std::nullopt;
}
}
}

View File

@ -0,0 +1,33 @@
#ifndef CORE_INPUT_VALUE_HPP
#define CORE_INPUT_VALUE_HPP
#include "core/math/vector.hpp"
#include <optional>
namespace ce {
class InputValue {
enum Type {
INPUTVALUE_BOOL,
INPUTVALUE_FLOAT,
INPUTVALUE_VEC,
} type{INPUTVALUE_BOOL};
private:
union {
bool bool_value;
float float_value;
float vec_value[2];
} data;
public:
InputValue();
InputValue(bool b);
InputValue(float f);
InputValue(Vecf v);
InputValue(InputValue const &src);
InputValue(InputValue const &&src);
InputValue &operator=(InputValue const &src);
template <typename T> bool is() const;
template <typename T> std::optional<T> get() const;
};
}
#endif // !CORE_INPUT_VALUE_HPP