feat: input system
parent
4a5f4dcb6b
commit
a504bae0f6
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)});
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue