diff --git a/include/radix/BaseGame.hpp b/include/radix/BaseGame.hpp index 90d1128f..37c2a914 100644 --- a/include/radix/BaseGame.hpp +++ b/include/radix/BaseGame.hpp @@ -11,6 +11,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace radix { @@ -60,6 +69,10 @@ class BaseGame { void deferPostCycle(const std::function&); World* getWorld(); + Config& getConfig(); + inline InputManager& getInputManager() { + return inputManager; + } template T& createOtherWorld(const std::string &name, Args&&... args) { @@ -95,6 +108,7 @@ class BaseGame { virtual void customTriggerHook(); Window window; + InputManager inputManager; std::map> otherWorlds; std::unique_ptr world; Config config; diff --git a/include/radix/Window.hpp b/include/radix/Window.hpp index 8a683de9..9f0d0de6 100644 --- a/include/radix/Window.hpp +++ b/include/radix/Window.hpp @@ -7,9 +7,9 @@ #include +#include #include #include -#include #include namespace radix { @@ -90,23 +90,73 @@ class Window : public Viewport, public InputSource { * @param key input keyboard type * @param mod input keyboard mode */ - void keyPressed(KeyboardKey key, KeyboardModifier mod) override; + void keyPressed(const KeyboardKey &key, const KeyboardModifier &mod) override; /** * @brief keyReleased handle released keyboard input * @param key input keyboard type * @param mod input keyboard modifier */ - void keyReleased(KeyboardKey key, KeyboardModifier mod) override; + void keyReleased(const KeyboardKey &key, const KeyboardModifier &mod) override; /** * @brief isKeyDown check keyboard status * @param key input keyboard type * @return if key is pressed */ - bool isKeyDown(KeyboardKey key) override; + bool isKeyDown(const KeyboardKey &key) override; + /**@} */ + + /** + * @name controller methods + *@{ */ + /** + * @brief controllerButtonPressed handle pressed controller button input + * @param button input button type + */ + void controllerButtonPressed(const ControllerButton &button, const ControllerIndex &index) override; + + /** + * @brief controllerButtonPressed handle released controller button input + * @param button input button type + */ + void controllerButtonReleased(const ControllerButton &button, const ControllerIndex &index) override; + + /** + * @brief controllerButtonPressed check controller button status + * @param button input button type + * @return if button is depressed + */ + bool isControllerButtonDown(const ControllerButton &button, const ControllerIndex &index) override; + + /** + * @brief getControllerAxisValue check controller axis value + * @param axis axis index + * @param index controller index + */ + float getControllerAxisValue(const ControllerAxis &axis, const ControllerIndex &index) override; /**@} */ + /** + * @brief controllerButtonPressed check mouse button status + * @param button input button type + * @return if button is depressed + */ + bool isMouseButtonDown(const int &button) override; + + /** + * @brief getMouseAxisValue get mouse axis value + * @param axis x or y + */ + float getRelativeMouseAxisValue(const int &axis) override; + + /** + * @brief getRelativeMouseState track mouse movement + * @param dx mouse x movement + * @param dy mouse y movement + */ + void getRelativeMouseState(int *dx, int *dy) override; + /** * @name CharBuffer methods * Manipulate input buffer @@ -168,19 +218,25 @@ class Window : public Viewport, public InputSource { */ void setSdlGlAttributes(); + void processMouseAxisEvents(); + + void processControllerStickEvents(); + + void processControllerTriggerEvents(); + /** * @brief processMouseButtonEvents get mouse event * and dispatch to subscribed listeners * @param event input mouse event */ - void processMouseButtonEvents(SDL_Event &event); + void processMouseButtonEvents(const SDL_Event &event); /** * @brief processWindowEvents get window event * and dispatch to subscribed listeners * @param event input window event */ - void processWindowEvents(SDL_Event &event); + void processWindowEvents(const SDL_Event &event); /** * @brief getOpenGlVersionString Create OpenGL version string @@ -197,17 +253,26 @@ class Window : public Viewport, public InputSource { void initGl(); - unsigned int width; /**< main screen width */ - unsigned int height; /**< main screen height */ - SDL_Window *window; /**< SDL identifier for current window */ - SDL_GLContext context; /**< SDL Handler for OpenGL Context */ + unsigned int width; /**< main screen width */ + unsigned int height; /**< main screen height */ + SDL_Window *window; /**< SDL identifier for current window */ + SDL_GLContext context; /**< SDL Handler for OpenGL Context */ + + SDL_Joystick *joystick; + SDL_GameController *controller; - std::vector keystates; /**< Keyboard pressed key status */ - std::string charbuffer; /**< Text input buffer */ + std::vector mouseButtonStates; /**< Mouse button pressed status */ + std::vector keyStates; /**< Keyboard key pressed status */ + std::vector controllerButtonStates; /**< Controller button pressed status */ + std::vector controllerStickStates; /**< Controller button pressed status */ + std::vector controllerStickMax; /**< Controller button pressed status */ + std::vector controllerTriggerStates; /**< Controller button pressed status */ + std::string charbuffer; /**< Text input buffer */ + bool lastNonZero; - static const char* DEFAULT_TITLE; /**< Default Title Name */ - static const unsigned int DEFAULT_WIDTH; /**< Default Window width */ - static const unsigned int DEFAULT_HEIGHT; /**< Default Window Height */ + static const char* DEFAULT_TITLE; /**< Default Title Name */ + static const unsigned int DEFAULT_WIDTH; /**< Default Window width */ + static const unsigned int DEFAULT_HEIGHT; /**< Default Window Height */ }; } /* namespace radix */ diff --git a/include/radix/core/math/Vector2f.hpp b/include/radix/core/math/Vector2f.hpp index 8c7024d2..c1e6788e 100644 --- a/include/radix/core/math/Vector2f.hpp +++ b/include/radix/core/math/Vector2f.hpp @@ -13,6 +13,8 @@ #ifndef VECTOR2F_HPP #define VECTOR2F_HPP +#include + #include namespace radix { @@ -38,6 +40,9 @@ struct Vector2f { constexpr Vector2f(float v) : x(v), y(v) {} + constexpr Vector2f(const Vector2i &v) + : x(v.x), y(v.y) {} + float length() const; std::string str() const; @@ -99,6 +104,9 @@ struct Vector2f { return *this; } + constexpr Vector2f operator/(const Vector2f& v) const { + return Vector2f(x / v.x, y / v.y); + } constexpr Vector2f operator/=(const Vector2f& v) const { return Vector2f(x / v.x, y / v.y); } diff --git a/include/radix/core/math/Vector2i.hpp b/include/radix/core/math/Vector2i.hpp index d40c638e..3cf96bdb 100644 --- a/include/radix/core/math/Vector2i.hpp +++ b/include/radix/core/math/Vector2i.hpp @@ -1,6 +1,10 @@ #ifndef VECTOR2I_HPP #define VECTOR2I_HPP + +#include + namespace radix { + struct Vector2i { union { int x, width; @@ -9,6 +13,8 @@ struct Vector2i { int y, height; }; + static const Vector2i ZERO, UP; + constexpr Vector2i() : x(0), y(0) {} constexpr Vector2i(int x, int y) @@ -16,6 +22,19 @@ struct Vector2i { constexpr Vector2i(int v) : x(v), y(v) {} + std::string str() const; + + constexpr bool operator==(const Vector2i &v) const { + return x == v.x && y == v.y; + } + + constexpr bool operator!=(const Vector2i &v) const { + return x != v.x || y != v.y; + } + + constexpr Vector2i operator-(const Vector2i& v) const { + return Vector2i(x - v.x, y - v.y); + } }; } /* namespace radix */ #endif /* VECTOR2I_HPP */ diff --git a/include/radix/entities/Player.hpp b/include/radix/entities/Player.hpp index 6ea5701e..bb24531f 100644 --- a/include/radix/entities/Player.hpp +++ b/include/radix/entities/Player.hpp @@ -14,6 +14,9 @@ #include namespace radix { + +class Vector2f; + namespace entities { class Player : @@ -29,9 +32,11 @@ class Player : KinematicCharacterController *controller; Vector3f velocity, headAngle; - bool flying, noclip, frozen; + bool flying, noclip, frozen, attemptJump; float speed; float stepCounter; + Vector3f movement; + Vector3f headingChange; Trigger *trigger; @@ -39,6 +44,11 @@ class Player : ~Player(); void tick(TDelta) override; + void jump(); + void move(const Vector2f &move); + void moveX(const float &move); + void moveY(const float &move); + void changeHeading(const Vector2f& lookVector); Quaternion getBaseHeadOrientation() const; Quaternion getHeadOrientation() const; diff --git a/include/radix/env/Config.hpp b/include/radix/env/Config.hpp index 7ddc0a0b..5473ac6d 100644 --- a/include/radix/env/Config.hpp +++ b/include/radix/env/Config.hpp @@ -2,10 +2,14 @@ #define RADIX_CONFIG_HPP #include +#include +#include #include #include +#include + using namespace json11; namespace radix { @@ -20,53 +24,58 @@ class Config { friend class ArgumentsParser; public: + using Bindings = std::vector>; + Config(); void load(); bool isLoaded(); - unsigned int getWidth() const { return width; } - unsigned int getHeight() const { return height; } - float getSensitivity() const { return sensitivity; } - bool isFullscreen() const { return fullscreen; } - int getAntialiasLevel() const { return antialiasing; } - int getRecursionLevel() const { return recursivePortal; } - bool hasSound() const { return sound; } - bool hasVsync() const { return vsync; } - bool isHidePortalsByClick() const { return hidePortalsByClick; } - bool isConsoleEnabled() const { return consoleEnabled; } - bool isProfilerEnabled() const { return profilerEnabled; } - bool isFlyingEnabled() const { return flyingEnabled; } - bool isDebugViewEnabled() const { return debugViewEnabled; } - bool getCursorVisibility() const { return cursorVisibility; } - bool getIgnoreGlVersion() const { return ignoreGlVersion; } - bool getGlContextEnableDebug() const { return glContextEnableDebug; } - LogLevel getLoglevel() const { return loglevel; } - std::string getMap() const { return map; } - std::string getMapPath() const { return mapPath; } - int getScreen() const { return screen; } + unsigned int getWidth() const { return width; } + unsigned int getHeight() const { return height; } + float getMouseSensitivity() const { return mouseSensitivity; } + bool isFullscreen() const { return fullscreen; } + int getAntialiasLevel() const { return antialiasing; } + int getRecursionLevel() const { return recursivePortal; } + bool hasSound() const { return sound; } + bool hasVsync() const { return vsync; } + bool isHidePortalsByClick() const { return hidePortalsByClick; } + bool isConsoleEnabled() const { return consoleEnabled; } + bool isProfilerEnabled() const { return profilerEnabled; } + bool isFlyingEnabled() const { return flyingEnabled; } + bool isDebugViewEnabled() const { return debugViewEnabled; } + bool getCursorVisibility() const { return cursorVisibility; } + bool getIgnoreGlVersion() const { return ignoreGlVersion; } + bool getGlContextEnableDebug() const { return glContextEnableDebug; } + Bindings getBindings() const { return bindings; } + LogLevel getLoglevel() const { return loglevel; } + std::string getMap() const { return map; } + std::string getMapPath() const { return mapPath; } + int getScreen() const { return screen; } + + static std::string actionToString(const int &action); private: void loadVideoSettings(const Json &json); void loadSoundSettings(const Json &json); void loadMouseSettings(const Json &json); + void loadKeyboardSettings(const Json &json); + void loadControllerSettings(const Json &json); void loadLoglevelSettings(const Json &json); + void loadDefaultBindings(); - bool loaded; unsigned int width; unsigned int height; - float sensitivity; + float mouseSensitivity; int antialiasing; int recursivePortal; - bool fullscreen; - bool sound; - bool vsync; - bool hidePortalsByClick; - bool cursorVisibility; - bool ignoreGlVersion; - bool glContextEnableDebug; - bool consoleEnabled; - bool profilerEnabled; - bool flyingEnabled; - bool debugViewEnabled; + bool loaded, fullscreen, sound, vsync, flyingEnabled + , hidePortalsByClick, cursorVisibility + , ignoreGlVersion, glContextEnableDebug + , consoleEnabled, profilerEnabled, debugViewEnabled; + + Bindings bindings; + + static const Bindings defaultBindings; + LogLevel loglevel; std::string map; std::string mapPath; diff --git a/include/radix/input/Bind.hpp b/include/radix/input/Bind.hpp new file mode 100644 index 00000000..5f623ea7 --- /dev/null +++ b/include/radix/input/Bind.hpp @@ -0,0 +1,59 @@ +#ifndef RADIX_BIND_HPP +#define RADIX_BIND_HPP + +namespace radix { + +struct Bind { + int8_t action; + int8_t inputType; + int8_t inputCode; + float sensitivity; + union { + float deadZone, actPoint; + }; + enum InputType : int8_t { + INPUT_TYPE_INVALID = -1, + KEYBOARD = 0, + MOUSE_BUTTON = 1, + MOUSE_AXIS = 2, + CONTROLLER_BUTTON = 3, + CONTROLLER_AXIS = 4, + CONTROLLER_TRIGGER = 5, + INPUT_TYPE_MAX = 6 + }; + static bool isInputTypeDigital(const int& input) { + switch (input) { + case KEYBOARD: + case MOUSE_BUTTON: + case CONTROLLER_BUTTON: { + return true; + break; + } + case MOUSE_AXIS: + case CONTROLLER_AXIS: + case CONTROLLER_TRIGGER: + case INPUT_TYPE_INVALID: + case INPUT_TYPE_MAX: { + return false; + break; + } + } + } + + Bind() + :action(-1), + inputType(-1), + inputCode(-1), + sensitivity(0.0f), + deadZone(0.0f) {} + Bind(const int &action, const int &inputType, const int &inputCode, const float &sensitivity = 0.0f, const float &deadZone = 0.5) + : action(action), + inputType(inputType), + inputCode(inputCode), + sensitivity(sensitivity), + deadZone(deadZone) {} +}; + +} + +#endif /* RADIX_BIND_HPP */ \ No newline at end of file diff --git a/include/radix/input/Channel.hpp b/include/radix/input/Channel.hpp new file mode 100644 index 00000000..095204e5 --- /dev/null +++ b/include/radix/input/Channel.hpp @@ -0,0 +1,57 @@ +#ifndef RADIX_CHANNEL_HPP +#define RADIX_CHANNEL_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace radix { + +template +class SubChannel; + +template +class Channel: public ChannelBase, ChannelListener { +public: + Channel() = default; + Channel(ChannelListener *listener) + : ChannelBase(listener) {} + Channel(ChannelListener *listener, const int &id, EventDispatcher &event, const std::vector &binds) + : ChannelBase(listener) { + this->init(id, event, binds); + } + void init(const int &id, EventDispatcher &event, const std::vector &binds); + + virtual void channelChanged(const int &id) override; + +protected: + std::vector> subChannels; + +}; + +template +class SubChannel: public ChannelBase { +public: + SubChannel() = default; + SubChannel(ChannelListener *listener) + : ChannelBase(listener) {} + SubChannel(ChannelListener *listener, const int &id, EventDispatcher &event, const Bind &bind) + : ChannelBase(listener) { + this->init(id, event, bind); + } + void init(const int &id, EventDispatcher &event, const Bind &bind); + +protected: + Bind bind; + std::array callbacks; + +}; + +} + +#endif /* RADIX_INPUT_MANAGER_HPP */ diff --git a/include/radix/input/ChannelBase.hpp b/include/radix/input/ChannelBase.hpp new file mode 100644 index 00000000..bd6b815a --- /dev/null +++ b/include/radix/input/ChannelBase.hpp @@ -0,0 +1,43 @@ +#ifndef RADIX_CHANNEL_BASE_HPP +#define RADIX_CHANNEL_BASE_HPP + +#include + +namespace radix { + +class ChannelListener; + +template +class ChannelBase { +public: + ChannelBase(); + ChannelBase(ChannelListener *listener); + + void setId(const int &id) { this->id = id; } + void addListener(ChannelListener* listener); + void setDigital(const float &actPoint); + void setAnalogue(const float &deadZone); + void setBound(const float& bound); + void setSensitivity(const float& sensitivity) { this->sensitivity = sensitivity; } + void setAutoZero() { autoZero = true; } + void setAlwaysNotifyListener() { alwaysNotifyListener = true; } + void set(T newValue); + int getId() const { return id; } + T get() const { return value; } + +protected: + void notifyListeners(); + + std::vector listeners; + int id; + float bound, sensitivity; + bool hasBound, isDigital, autoZero, alwaysNotifyListener; + union { + float deadZone, actPoint; + }; + T value; +}; + +} + +#endif /* RADIX_INPUT_MANAGER_HPP */ diff --git a/include/radix/input/ChannelListener.hpp b/include/radix/input/ChannelListener.hpp new file mode 100644 index 00000000..6f8ac00f --- /dev/null +++ b/include/radix/input/ChannelListener.hpp @@ -0,0 +1,14 @@ +#ifndef RADIX_CHANNEL_LISTENER_HPP +#define RADIX_CHANNEL_LISTENER_HPP + +namespace radix { + +class ChannelListener { +public: + virtual void channelChanged(const int &id) = 0; + +}; + +} + +#endif /* RADIX_INPUT_MANAGER_HPP */ diff --git a/include/radix/input/InputManager.hpp b/include/radix/input/InputManager.hpp new file mode 100644 index 00000000..6f28d02f --- /dev/null +++ b/include/radix/input/InputManager.hpp @@ -0,0 +1,54 @@ +#ifndef RADIX_INPUT_MANAGER_HPP +#define RADIX_INPUT_MANAGER_HPP + +#include + +#include +#include +#include +#include + +namespace radix { + +class BaseGame; +class EventDispatcher; + +class InputManager : public ChannelListener { +public: + enum Action : int8_t { + ACTION_INVALID = -1, + PLAYER_MOVE_ANALOGUE = 0, + PLAYER_LOOK_ANALOGUE, + PLAYER_JUMP, + PLAYER_PORTAL_0, + PLAYER_PORTAL_1, + PLAYER_FORWARD, + PLAYER_BACK, + PLAYER_LEFT, + PLAYER_RIGHT, + GAME_PAUSE, + GAME_QUIT, + ACTION_MAX + }; + + InputManager() = delete; + InputManager(BaseGame &baseGame); + void setConfig(const Config &config); + void init(EventDispatcher &event); + + virtual void channelChanged(const int &id) override; + Vector2f getPlayerMovementVector() const; + + static bool isActionDigital(const int &act); + +protected: + BaseGame &game; + Config config; + std::map> digitalChannels; + std::map> analogueChannels; + +}; + +} + +#endif /* RADIX_INPUT_MANAGER_HPP */ diff --git a/include/radix/input/InputSource.hpp b/include/radix/input/InputSource.hpp index 76828d37..c520476d 100644 --- a/include/radix/input/InputSource.hpp +++ b/include/radix/input/InputSource.hpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include +#include namespace radix { @@ -29,7 +31,7 @@ class InputSource { InputSource &source; const KeyboardKey key; const KeyboardModifier mod; - KeyPressedEvent(InputSource &source, KeyboardKey key, KeyboardModifier mod) + KeyPressedEvent(InputSource &source, const KeyboardKey &key, const KeyboardModifier &mod) : source(source), key(key), mod(mod) {} }; struct KeyReleasedEvent : public Event { @@ -37,157 +39,221 @@ class InputSource { InputSource &source; const KeyboardKey key; const KeyboardModifier mod; - KeyReleasedEvent(InputSource &source, KeyboardKey key, KeyboardModifier mod) + KeyReleasedEvent(InputSource &source, const KeyboardKey &key, const KeyboardModifier &mod) : source(source), key(key), mod(mod) {} }; /* ================= */ /* Mouse buttons */ /* ================= */ - enum class MouseButton : uint8_t { + using MouseAxisValue = Vector2f; + + enum class MouseButton : int8_t { + Invalid = -1, Left = 0, - Right = 1, - Middle = 2, + Middle = 1, + Right = 2, Aux1, Aux2, Aux3, Aux4, Aux5, Aux6, - Aux7, - Aux8, - Unknown = 0xFF + Max }; struct MouseButtonPressedEvent : public Event { radix_event_declare("radix/InputSource:MouseButtonPressed") InputSource &source; const MouseButton button; - MouseButtonPressedEvent(InputSource &source, MouseButton button) + MouseButtonPressedEvent(InputSource &source, const MouseButton &button) : source(source), button(button) {} }; struct MouseButtonReleasedEvent : public Event { radix_event_declare("radix/InputSource:MouseButtonReleased") InputSource &source; const MouseButton button; - MouseButtonReleasedEvent(InputSource &source, MouseButton button) + MouseButtonReleasedEvent(InputSource &source, const MouseButton &button) : source(source), button(button) {} }; - - /* =============== */ - /* Mouse wheel */ - /* =============== */ + struct MouseAxisEvent : public Event { + radix_event_declare("radix/InputSource:MouseMoved") + InputSource &source; + const MouseAxisValue value; + MouseAxisEvent(InputSource &source, const MouseAxisValue &value) + : source(source), value(value) {} + }; struct MouseWheelScrolledEvent : public Event { radix_event_declare("radix/InputSource:MouseWheelScrolled") InputSource &source; const int x, y; - MouseWheelScrolledEvent(InputSource &source, int x, int y) + MouseWheelScrolledEvent(InputSource &source, const int &x, const int &y) : source(source), x(x), y(y) {} }; +public: + /* ================= */ + /* Controller */ + /* ================= */ + using ControllerButton = int; + using ControllerAxis = int; // 0/1 for left/right respectively + using ControllerTrigger = int; // 0/1 for left/right respectively + using ControllerIndex = int; + using ControllerAxisValue = Vector2f; + using ControllerTriggerValue = float; + + struct ControllerButtonPressedEvent : public Event { + radix_event_declare("radix/InputSource:ControllerButtonPressed") + InputSource &source; + const ControllerButton button; + const ControllerIndex index; + ControllerButtonPressedEvent(InputSource &source, const ControllerButton &button, const ControllerIndex &index) + : source(source), button(button), index(index) {} + }; + struct ControllerButtonReleasedEvent : public Event { + radix_event_declare("radix/InputSource:ControllerButtonReleased") + InputSource &source; + const ControllerButton button; + const ControllerIndex index; + ControllerButtonReleasedEvent(InputSource &source, const ControllerButton &button, const ControllerIndex &index) + : source(source), button(button), index(index) {} + }; + struct ControllerAxisEvent : public Event { + radix_event_declare("radix/InputSource:ControllerAxis") + InputSource &source; + const ControllerAxis axis; + const ControllerAxisValue value; + const ControllerIndex index; + ControllerAxisEvent(InputSource &source, const ControllerAxis &axis, const ControllerAxisValue &value, const ControllerIndex &index) + : source(source), axis(axis), value(value), index(index) {} + }; + struct ControllerTriggerEvent : public Event { + radix_event_declare("radix/InputSource:ControllerTrigger") + InputSource &source; + const ControllerTrigger trigger; + const ControllerTriggerValue value; + const ControllerIndex index; + ControllerTriggerEvent(InputSource &source, const ControllerTrigger &trigger, const ControllerTriggerValue &value , const ControllerIndex &index) + : source(source), trigger(trigger), value(value), index(index) {} + }; + struct ControllerAddedEvent : public Event { + radix_event_declare("radix/InputSource:ControllerAdded") + InputSource &source; + const ControllerIndex index; + ControllerAddedEvent(InputSource &source, const ControllerIndex &index) + : source(source), index(index) {} + }; + struct ControllerRemovedEvent : public Event { + radix_event_declare("radix/InputSource:ControllerRemoved") + InputSource &source; + const ControllerIndex index; + ControllerRemovedEvent(InputSource &source, const ControllerIndex &index) + : source(source), index(index) {} + }; + /* =============== */ /* Window */ /* =============== */ struct WindowShownEvent : public Event { radix_event_declare("radix/InputSource:WindowShown") InputSource &source; - Uint32 windowID; - WindowShownEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowShownEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowHiddenEvent : public Event { radix_event_declare("radix/InputSource:WindowHidden") InputSource &source; - Uint32 windowID; - WindowHiddenEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowHiddenEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowExposedEvent : public Event { radix_event_declare("radix/InputSource:WindowExposed") InputSource &source; - Uint32 windowID; - WindowExposedEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowExposedEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowMovedEvent : public Event { radix_event_declare("radix/InputSource:WindowMoved") InputSource &source; - Uint32 windowID; - Sint32 x; - Sint32 y; + const Uint32 windowID; + const Sint32 x; + const Sint32 y; WindowMovedEvent(InputSource &source, Uint32 windowID, Sint32 x, Sint32 y) : source(source), windowID(windowID), x(x), y(y) {} }; struct WindowResizedEvent : public Event { radix_event_declare("radix/InputSource:WindowResized") InputSource &source; - Uint32 windowID; - Sint32 x; - Sint32 y; - WindowResizedEvent(InputSource &source, Uint32 windowID, Sint32 x, Sint32 y) + const Uint32 windowID; + const Sint32 x; + const Sint32 y; + WindowResizedEvent(InputSource &source, const Uint32 & windowID, const Sint32 &x, const Sint32 &y) : source(source), windowID(windowID), x(x), y(y) {} }; struct WindowSizeChangedEvent : public Event { radix_event_declare("radix/InputSource:WindowSizeChanged") InputSource &source; - Uint32 windowID; - WindowSizeChangedEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowSizeChangedEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowMinimizedEvent : public Event { radix_event_declare("radix/InputSource:WindowMinimized") InputSource &source; - Uint32 windowID; - WindowMinimizedEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowMinimizedEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowMaximizedEvent : public Event { radix_event_declare("radix/InputSource:WindowMaximized") InputSource &source; - Uint32 windowID; - WindowMaximizedEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowMaximizedEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowRestoredEvent : public Event { radix_event_declare("radix/InputSource:WindowRestored") InputSource &source; - Uint32 windowID; - WindowRestoredEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowRestoredEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowEnterEvent : public Event { radix_event_declare("radix/InputSource:EnterExposed") InputSource &source; - Uint32 windowID; - WindowEnterEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowEnterEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowLeaveEvent : public Event { radix_event_declare("radix/InputSource:WindowLeave") InputSource &source; - Uint32 windowID; - WindowLeaveEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowLeaveEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowFocusGainedEvent : public Event { radix_event_declare("radix/InputSource:WindowFocusGained") InputSource &source; - Uint32 windowID; - WindowFocusGainedEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowFocusGainedEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowFocusLostEvent : public Event { radix_event_declare("radix/InputSource:FocusLost") InputSource &source; - Uint32 windowID; - WindowFocusLostEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowFocusLostEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; struct WindowCloseEvent : public Event { radix_event_declare("radix/InputSource:WindowClose") InputSource &source; - Uint32 windowID; - WindowCloseEvent(InputSource &source, Uint32 windowID) + const Uint32 windowID; + WindowCloseEvent(InputSource &source, const Uint32 &windowID) : source(source), windowID(windowID) {} }; @@ -197,14 +263,34 @@ class InputSource { void removeDispatcher(EventDispatcher &d); virtual void processEvents() = 0; - virtual void keyPressed(KeyboardKey key, KeyboardModifier mod) = 0; - virtual void keyReleased(KeyboardKey key, KeyboardModifier mod) = 0; - virtual bool isKeyDown(KeyboardKey key) = 0; + virtual void keyPressed(const KeyboardKey &key, const KeyboardModifier &mod) = 0; + virtual void keyReleased(const KeyboardKey &key, const KeyboardModifier &mod) = 0; + virtual bool isKeyDown(const KeyboardKey &key) = 0; + virtual void controllerButtonPressed(const ControllerButton &button, const ControllerIndex &index) = 0; + virtual void controllerButtonReleased(const ControllerButton &button, const ControllerIndex &index) = 0; + virtual bool isControllerButtonDown(const ControllerButton &button, const ControllerIndex &index) = 0; + virtual float getControllerAxisValue(const ControllerAxis &axis, const ControllerIndex &index) = 0; + virtual bool isMouseButtonDown(const int &button) = 0; + virtual float getRelativeMouseAxisValue(const int &axis) = 0; + virtual void getRelativeMouseState(int *dx, int *dy) = 0; virtual std::string getCharBuffer() = 0; virtual void addToBuffer(const std::string &character) = 0; virtual void clearBuffer() = 0; virtual void truncateCharBuffer() = 0; virtual void clear() = 0; + + using LookUpTable = std::map; + +private: + static const LookUpTable mouseButtonLookUp; + static const LookUpTable controllerButtonLookUp; + +public: + static int keyboardGetKeyFromString(const std::string &keyStr); + static int mouseGetButtonFromString(const std::string &buttonStr); + static int gameControllerGetButtonFromString(const std::string &buttonStr); + static int gameControllerGetAxisFromString(const std::string &axisStr); + static int gameControllerGetTriggerFromString(const std::string &triggerStr); }; } /* namespace radix */ diff --git a/source/BaseGame.cpp b/source/BaseGame.cpp index 834c5edb..add8bac4 100644 --- a/source/BaseGame.cpp +++ b/source/BaseGame.cpp @@ -21,6 +21,7 @@ Fps BaseGame::fps; BaseGame::BaseGame() : config(), + inputManager(*this), gameWorld(window), closed(false) { radix::Environment::init(); @@ -34,6 +35,7 @@ BaseGame::BaseGame() : } window.setConfig(config); + inputManager.setConfig(config); } BaseGame::~BaseGame() { @@ -68,6 +70,8 @@ void BaseGame::setup() { screenRenderer = std::make_unique(*world, *renderer.get(), gameWorld); renderer->addRenderer(*screenRenderer); + + inputManager.init(getWorld()->event); } bool BaseGame::isRunning() { @@ -78,6 +82,10 @@ World* BaseGame::getWorld() { return world.get(); } +Config& BaseGame::getConfig() { + return config; +} + void BaseGame::switchToOtherWorld(const std::string &name) { auto it = otherWorlds.find(name); if (it == otherWorlds.end()) { @@ -132,9 +140,9 @@ void BaseGame::removeHook() { } void BaseGame::customTriggerHook() { } void BaseGame::cleanUp() { - removeHook(); - setWorld({}); - window.close(); + removeHook(); + setWorld({}); + window.close(); } void BaseGame::render() { diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 1ea6ba2a..445dbbec 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -22,6 +22,7 @@ set(RADIX_SOURCES ${D}/core/math/Matrix3f.cpp ${D}/core/math/Matrix4f.cpp ${D}/core/math/Vector2f.cpp + ${D}/core/math/Vector2i.cpp ${D}/core/math/Vector2ui.cpp ${D}/core/math/Vector3f.cpp ${D}/core/math/Vector4f.cpp @@ -43,6 +44,9 @@ set(RADIX_SOURCES ${D}/env/Util.cpp ${D}/GameWorld.cpp ${D}/input/InputSource.cpp + ${D}/input/InputManager.cpp + ${D}/input/ChannelBase.cpp + ${D}/input/Channel.cpp ${D}/data/map/MapListLoader.cpp ${D}/data/map/MapListLoader.cpp ${D}/data/map/MapLoader.cpp diff --git a/source/Window.cpp b/source/Window.cpp index 201f5c86..5f20832d 100644 --- a/source/Window.cpp +++ b/source/Window.cpp @@ -1,8 +1,10 @@ -#include +#include #include #include #include +#include +#include #include @@ -13,8 +15,12 @@ #include #include +#include + namespace radix { +class Config; + const unsigned int Window::DEFAULT_WIDTH = 800; const unsigned int Window::DEFAULT_HEIGHT = 600; const char* Window::DEFAULT_TITLE = "Radix Engine"; @@ -24,7 +30,14 @@ Window::Window() : width(0), height(0), window(nullptr), - keystates(SDL_NUM_SCANCODES) {} + joystick(NULL), + controller(NULL), + mouseButtonStates((int)MouseButton::Max), + keyStates(SDL_NUM_SCANCODES), + controllerButtonStates(SDL_CONTROLLER_BUTTON_MAX), + controllerStickStates(2), + controllerStickMax(2, Vector2i(25000)), + controllerTriggerStates(2) {} Window::~Window() = default; @@ -59,7 +72,27 @@ void Window::initGl() { } void Window::create(const char *title) { - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + + int numJoysticks = SDL_NumJoysticks(); + Util::Log(Verbose, "Window") << "Number of joysticks " << std::to_string(numJoysticks); + + for (int i = 0; i < numJoysticks; ++i) { + if (SDL_IsGameController(i)) { + controller = SDL_GameControllerOpen(i); + if (controller) { + Util::Log(Warning, "Window") << "Controller of index " << i << " loaded"; + + joystick = SDL_JoystickOpen(i); + + break; + } else { + Util::Log(Warning, "Window") << "Controller of index " << i << " unable to load"; + } + } else { + Util::Log(Warning, "Window") << "Joystick of index " << i << " not a controller"; + } + } SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); @@ -159,6 +192,9 @@ void Window::getSize(int *width, int *height) const { } void Window::close() { + SDL_GameControllerClose( controller ); + controller = NULL; + SDL_HideWindow(window); SDL_GL_DeleteContext(context); @@ -180,6 +216,14 @@ void Window::unlockMouse() { void Window::processEvents() { SDL_Event event; + + processMouseAxisEvents(); + + if (controller) { + processControllerStickEvents(); + processControllerTriggerEvents(); + } + while (SDL_PollEvent(&event)) { int key = event.key.keysym.scancode; int mod = event.key.keysym.mod; @@ -211,10 +255,32 @@ void Window::processEvents() { keyReleased(key, mod); break; } + case SDL_CONTROLLERBUTTONDOWN: { + controllerButtonPressed(event.cbutton.button, event.cbutton.which); + break; + } + case SDL_CONTROLLERBUTTONUP: { + controllerButtonReleased(event.cbutton.button, event.cbutton.which); + break; + } + case SDL_CONTROLLERDEVICEADDED: { + const ControllerAddedEvent cae(*this, event.cdevice.which); + for (auto &d : dispatchers) { + d.get().dispatch(cae); + } + break; + } + case SDL_CONTROLLERDEVICEREMOVED: { + const ControllerRemovedEvent cre(*this, event.cdevice.which); + for (auto &d : dispatchers) { + d.get().dispatch(cre); + } + break; + } case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { - processMouseButtonEvents(event); - break; + processMouseButtonEvents(event); + break; } case SDL_MOUSEWHEEL: { const int dirmult = (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1; @@ -228,11 +294,79 @@ void Window::processEvents() { processWindowEvents(event); break; } + default: { + break; + } + } + } +} + +void Window::processMouseAxisEvents() { + Vector2i mouseRelative; + getRelativeMouseState(&mouseRelative.x, &mouseRelative.y); + + bool nonZero = mouseRelative != Vector2i::ZERO; + + if (nonZero or lastNonZero) { + const MouseAxisEvent mae(*this, Vector2f(mouseRelative)); + for (auto &d : dispatchers) { + d.get().dispatch(mae); + } + } + + lastNonZero = nonZero; +} + +void Window::processControllerStickEvents() { + for (int i = 0; i < 2; ++i) { + Vector2i currentStickState; + currentStickState.x = SDL_GameControllerGetAxis(controller, SDL_GameControllerAxis(2*i)); + currentStickState.y = SDL_GameControllerGetAxis(controller, SDL_GameControllerAxis(2*i + 1)); + + if (currentStickState.x > controllerStickMax.at(i).x) { + controllerStickMax.at(i).x = currentStickState.x; + Util::Log(Info, "InputSource") << i << " max x " << currentStickState.x; + } + if (currentStickState.y > controllerStickMax.at(i).y) { + controllerStickMax.at(i).y = currentStickState.y; + Util::Log(Info, "InputSource") << i << " max y " << currentStickState.y; } + + Vector2i stickDelta = currentStickState - controllerStickStates.at(i); + + if (stickDelta != Vector2i::ZERO) { + Vector2f normalisedStickState = Vector2f(currentStickState) / Vector2f(controllerStickMax[i]); + + const ControllerAxisEvent cae(*this, i, normalisedStickState, 0); + for (auto &d : dispatchers) { + d.get().dispatch(cae); + } + } + + controllerStickStates[i] = currentStickState; + } +} + +void Window::processControllerTriggerEvents() { + for (int i = 0; i < 2; ++i) { + int currentTriggerState = SDL_GameControllerGetAxis(controller, (SDL_GameControllerAxis)(i+4)); + + int triggerDelta = currentTriggerState - controllerTriggerStates[i]; + + if (triggerDelta) { + float normalisedTriggerState = float(currentTriggerState) / 32767.0f; + + const ControllerTriggerEvent cte(*this, i, normalisedTriggerState, 0); + for (auto &d : dispatchers) { + d.get().dispatch(cte); + } + } + + controllerTriggerStates[i] = currentTriggerState; } } -void Window::processMouseButtonEvents(SDL_Event &event) { +void Window::processMouseButtonEvents(const SDL_Event &event) { MouseButton button; switch (event.button.button) { @@ -257,18 +391,20 @@ void Window::processMouseButtonEvents(SDL_Event &event) { break; } default: { - button = MouseButton::Unknown; + button = MouseButton::Invalid; break; } } // Dispatch mouse event to subscribed listeners if (event.type == SDL_MOUSEBUTTONDOWN) { + mouseButtonStates[(int)button] = true; const MouseButtonPressedEvent mbpe(*this, button); for (auto &d : dispatchers) { d.get().dispatch(mbpe); } } else { + mouseButtonStates[(int)button] = false; const MouseButtonReleasedEvent mbre(*this, button); for (auto &d : dispatchers) { d.get().dispatch(mbre); @@ -276,7 +412,7 @@ void Window::processMouseButtonEvents(SDL_Event &event) { } } -void Window::processWindowEvents(SDL_Event &event) { +void Window::processWindowEvents(const SDL_Event &event) { switch (event.window.event) { case SDL_WINDOWEVENT_SHOWN: { const WindowShownEvent wse(*this, event.window.windowID); @@ -383,24 +519,67 @@ void Window::processWindowEvents(SDL_Event &event) { } } -void Window::keyPressed(KeyboardKey key, KeyboardModifier mod) { - keystates[key] = true; +void Window::keyPressed(const KeyboardKey &key, const KeyboardModifier &mod) { + keyStates[key] = true; const KeyPressedEvent kpe(*this, key, mod); for (auto &d : dispatchers) { d.get().dispatch(kpe); } } -void Window::keyReleased(KeyboardKey key, KeyboardModifier mod) { - keystates[key] = false; +void Window::keyReleased(const KeyboardKey &key, const KeyboardModifier &mod) { + keyStates[key] = false; const KeyReleasedEvent kre(*this, key, mod); for (std::reference_wrapper &d : dispatchers) { d.get().dispatch(kre); } } -bool Window::isKeyDown(KeyboardKey key) { - return keystates[key]; +bool Window::isKeyDown(const KeyboardKey &key) { + return keyStates[key]; +} + +// controller index is unused as of now, only the the first controller added is checked +void Window::controllerButtonPressed(const ControllerButton &button, const ControllerIndex &index) { + this->controllerButtonStates[button] = true; + const ControllerButtonPressedEvent cbpe(*this, button, index); + for (auto &d : dispatchers) { + d.get().dispatch(cbpe); + } +} + +void Window::controllerButtonReleased(const ControllerButton &button, const ControllerIndex &index) { + this->controllerButtonStates[button] = false; + const ControllerButtonReleasedEvent cbre(*this, button, index); + for (auto &d : dispatchers) { + d.get().dispatch(cbre); + } +} + +bool Window::isControllerButtonDown(const ControllerButton &button, const ControllerIndex &index) { + return this->controllerButtonStates[button]; +} + +float Window::getControllerAxisValue(const ControllerAxis &axis, const ControllerIndex &index) { + return float(SDL_GameControllerGetAxis(controller, (SDL_GameControllerAxis)axis)) / 32767.0f; +} + +bool Window::isMouseButtonDown(const int &button) { + return this->mouseButtonStates[button]; +} + +float Window::getRelativeMouseAxisValue(const int &axis) { /* + if (axis == int(MouseAxis::MOUSE_AXIS_X)) { + return (float)mouseRelativeX; + } else if (axis == int(MouseAxis::MOUSE_AXIS_Y)) { + return (float)mouseRelativeY; + } else { + return 0; + }*/ +} + +void Window::getRelativeMouseState(int *dx, int *dy) { + SDL_GetRelativeMouseState(dx, dy); } std::string Window::getCharBuffer() { @@ -420,7 +599,7 @@ void Window::truncateCharBuffer() { } void Window::clear() { - keystates.clear(); + keyStates.clear(); } void Window::printScreenToFile(const std::string& fileName) { @@ -469,3 +648,4 @@ void Window::setSdlGlAttributes() { } } /* namespace radix */ + diff --git a/source/core/math/Vector2i.cpp b/source/core/math/Vector2i.cpp new file mode 100644 index 00000000..b774e41f --- /dev/null +++ b/source/core/math/Vector2i.cpp @@ -0,0 +1,16 @@ +#include + +#include + +namespace radix { + +const Vector2i Vector2i::ZERO(0, 0); +const Vector2i Vector2i::UP(0, 1); + +std::string Vector2i::str() const { + std::stringstream ss; + ss << "(" << x << ", " << y << ")"; + return ss.str(); +} + +} /* namespace radix */ diff --git a/source/entities/Player.cpp b/source/entities/Player.cpp index 3561aea0..1e46a795 100644 --- a/source/entities/Player.cpp +++ b/source/entities/Player.cpp @@ -2,12 +2,15 @@ #include -#include - #include -#include #include #include +#include + +#include +#include +#include +#include namespace radix { namespace entities { @@ -48,7 +51,8 @@ Player::Player(const CreationParams &cp) : flying(false), noclip(false), frozen(false), - stepCounter(0) { + stepCounter(0), + attemptJump(false) { setScale(PLAYER_SIZE); @@ -82,68 +86,36 @@ void Player::tick(TDelta dtime) { return; } - // Head rotation - // TODO: don't use SDL directly - int mousedx, mousedy; - SDL_GetRelativeMouseState(&mousedx, &mousedy); - // Apply mouse movement to view - if (world.getConfig().isLoaded()) { - headAngle.attitude -= rad(mousedy * world.getConfig().getSensitivity()); - headAngle.heading -= rad(mousedx * world.getConfig().getSensitivity()); - } else { - headAngle.attitude -= rad(mousedy * 0.30); - headAngle.heading -= rad(mousedx * 0.30); - } - headAngle.tilt *= 0.8; - - // Restrict rotation in horizontal axis - headAngle.attitude = Math::clamp(headAngle.attitude, rad(-89.99), rad(89.99)); - - InputSource &input = *world.input; - bool movingFwd = input.isKeyDown(SDL_SCANCODE_W) or input.isKeyDown(SDL_SCANCODE_UP), - movingBack = input.isKeyDown(SDL_SCANCODE_S) or input.isKeyDown(SDL_SCANCODE_DOWN), - strafingLeft = input.isKeyDown(SDL_SCANCODE_A) or input.isKeyDown(SDL_SCANCODE_LEFT), - strafingRight = input.isKeyDown(SDL_SCANCODE_D) or input.isKeyDown(SDL_SCANCODE_RIGHT), - jumping = input.isKeyDown(SDL_SCANCODE_SPACE) or - input.isKeyDown(SDL_SCANCODE_BACKSPACE); + headAngle.attitude -= headingChange.y; + headAngle.attitude = Math::clamp(headAngle.attitude, rad(-89.99f), rad(89.99f)); + headAngle.heading -= headingChange.x; + headAngle.tilt *= 0.8f; + float rot = headAngle.heading; - Vector3f movement; privSetPosition(obj->getWorldTransform().getOrigin()); - if (jumping and controller->canJump()) { + if (attemptJump and controller->canJump()) { std::uniform_int_distribution<> dis(0, PLAYER_JUMP_SOUND.size()-1); playSound(Environment::getDataDir() + PLAYER_JUMP_SOUND[dis(Util::Rand)]); controller->jump(); } - if (movingFwd || movingBack || strafingLeft || strafingRight) { + if (movement != Vector3f::ZERO) { if (trigger) { trigger->actionOnMove(*trigger); } } - if (movingFwd) { - movement.x += -sin(rot); - movement.z += -cos(rot); - } - if (movingBack) { - movement.x += sin(rot); - movement.z += cos(rot); - } - if (strafingLeft) { - movement.x += -cos(rot); - movement.z += sin(rot); - } - if (strafingRight) { - movement.x += cos(rot); - movement.z += -sin(rot); - } + Matrix4f rotationMatrix; + rotationMatrix.rotate(rot, 0.0f, 1.0f, 0.0f); - movement *= RUNNING_SPEED; - controller->setWalkDirection(movement); + Vector3f newMovement = rotationMatrix * movement; + newMovement *= RUNNING_SPEED; + + controller->setWalkDirection(newMovement); if (controller->onGround()) { - stepCounter += std::sqrt(movement.x*movement.x + movement.z*movement.z); + stepCounter += std::sqrt(newMovement.x*newMovement.x + newMovement.z*newMovement.z); if (stepCounter >= 2.5f) { std::uniform_int_distribution<> distribution(0, PLAYER_FOOT_SOUND.size()-1); @@ -151,9 +123,32 @@ void Player::tick(TDelta dtime) { stepCounter -= 2.5f; } } + attemptJump = false; trigger = nullptr; } +void Player::jump() { + attemptJump = true; +} + +void Player::move(const Vector2f &move) { + movement.x = move.x; + movement.z = move.y; +} + +void Player::moveX(const float &moveX) { + movement.x = moveX; +} + +void Player::moveY(const float &moveY) { + movement.z = moveY; +} + +void Player::changeHeading(const Vector2f &lookVector) { + headingChange.x = lookVector.x; + headingChange.y = lookVector.y; +} + Quaternion Player::getBaseHeadOrientation() const { return Quaternion().fromAero(headAngle); } diff --git a/source/env/Config.cpp b/source/env/Config.cpp index ddb00eed..49129ce7 100644 --- a/source/env/Config.cpp +++ b/source/env/Config.cpp @@ -1,12 +1,51 @@ #include +#include #include #include +#include +#include #include +#include +#include +#include namespace radix { +const std::vector> Config::defaultBindings = { + {Bind()}, + {Bind(InputManager::PLAYER_LOOK_ANALOGUE, Bind::MOUSE_AXIS, 0, 1.0f)}, + {Bind(InputManager::PLAYER_JUMP, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("Space"))}, + {Bind(InputManager::PLAYER_PORTAL_0, Bind::MOUSE_BUTTON, (int)InputSource::MouseButton::Left)}, + {Bind(InputManager::PLAYER_PORTAL_1, Bind::MOUSE_BUTTON, (int)InputSource::MouseButton::Right)}, + {Bind(InputManager::PLAYER_FORWARD, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("W"))}, + {Bind(InputManager::PLAYER_BACK, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("S"))}, + {Bind(InputManager::PLAYER_LEFT, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("A"))}, + {Bind(InputManager::PLAYER_RIGHT, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("D"))}, + {Bind(InputManager::GAME_PAUSE, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("Escape"))}, + {Bind(InputManager::GAME_QUIT, Bind::KEYBOARD, InputSource::keyboardGetKeyFromString("Q"))} +}; + +std::string Config::actionToString(const int &action) { + switch (InputManager::Action(action)) { + case InputManager::PLAYER_JUMP : return "jump"; + case InputManager::PLAYER_PORTAL_0 : return "portal_0"; + case InputManager::PLAYER_PORTAL_1 : return "portal_1"; + case InputManager::PLAYER_MOVE_ANALOGUE : return "move_analogue"; + case InputManager::PLAYER_LOOK_ANALOGUE : return "look_analogue"; + case InputManager::PLAYER_FORWARD : return "forward"; + case InputManager::PLAYER_BACK : return "back"; + case InputManager::PLAYER_LEFT : return "left"; + case InputManager::PLAYER_RIGHT : return "right"; + case InputManager::GAME_PAUSE : return "pause"; + case InputManager::GAME_QUIT : return "quit"; + case InputManager::ACTION_INVALID : + case InputManager::ACTION_MAX : + default : return ""; + } +} + Config::Config() : loaded(false), ignoreGlVersion(false), @@ -14,9 +53,8 @@ Config::Config() : flyingEnabled(false), profilerEnabled(false), debugViewEnabled(false), - screen(0) { -} - + bindings(InputManager::ACTION_MAX, std::vector()), + screen(0) {} void Config::load() { std::string err; @@ -39,6 +77,9 @@ void Config::load() { this->loadVideoSettings(configJson["video"]); this->loadSoundSettings(configJson["sound"]); this->loadMouseSettings(configJson["mouse"]); + this->loadControllerSettings(configJson["controller"]); + this->loadKeyboardSettings(configJson["keyboard"]); + this->loadDefaultBindings(); this->loadLoglevelSettings(configJson["logging"]); const Json &debug = configJson["debug"]; @@ -61,18 +102,110 @@ void Config::loadVideoSettings(const Json &json) { height = json["height"].number_value(); recursivePortal = json["recursive_portal"].number_value(); screen = json["screen"].number_value(); - } void Config::loadSoundSettings(const Json &json) { sound = json["enable"].bool_value(); - } void Config::loadMouseSettings(const Json &json) { - sensitivity = json["sensitivity"].number_value(); + mouseSensitivity = json["sensitivity"].number_value() / 500.0f; hidePortalsByClick = json["hide_portals_by_click"].bool_value(); cursorVisibility = json["cursor_visibility"].bool_value(); + + for (int action = 0; action < int(InputManager::Action::ACTION_MAX); ++action) { + std::string actionStr = actionToString(action); + + for (int index = 0; index < 5; ++index) { + std::string buttonStr = json["bindings"][actionStr][index].string_value(); + if (buttonStr == "") { + break; + } else { + int button = InputSource::mouseGetButtonFromString(buttonStr); + bool axis = buttonStr == "mouse_move"; + + if (button != -1) { + Bind bind(action, Bind::MOUSE_BUTTON, button); + bindings[action].push_back(bind); + Util::Log(Info, "Config") << buttonStr << button << " bound to " << actionStr; + } else if (axis) { + Bind bind(action, Bind::MOUSE_AXIS, 0, mouseSensitivity, 0); + bindings[action].push_back(bind); + Util::Log(Info, "Config") << buttonStr << " bound to " << actionStr << " with sensitivity " << mouseSensitivity; + } else { + Util::Log(Info, "Config") << buttonStr << "is an invalid control name"; + } + } + } + } +} + +void Config::loadKeyboardSettings(const Json &json) { + for (int action = 0; action < InputManager::ACTION_MAX; ++action) { + std::string actionStr = actionToString(action); + + for (int index = 0; index < 5; ++index) { + std::string keyStr = json["bindings"][actionStr][index].string_value(); + if (keyStr == "") { + break; + } else { + int key = InputSource::keyboardGetKeyFromString(keyStr); + + if (key > 0) { + Bind bind(action, Bind::KEYBOARD, key); + bindings[action].push_back(bind); + Util::Log(Info, "Config") << keyStr << " bound to " << actionStr; + } else { + Util::Log(Info, "Config") << keyStr << "is an invalid control name"; + } + } + } + } +} + +void Config::loadControllerSettings(const Json &json) { + for (int action = 0; action < int(InputManager::Action::ACTION_MAX); ++action) { + std::string actionStr = actionToString(action); + + for (int index = 0; index < 5; ++index) { + std::string buttonStr = json["bindings"][actionStr][index].string_value(); + if (buttonStr == "") { + break; + } else { + int button = InputSource::gameControllerGetButtonFromString(buttonStr); + int axis = InputSource::gameControllerGetAxisFromString(buttonStr); + int trigger = InputSource::gameControllerGetTriggerFromString(buttonStr); + + if (button != -1) { + Bind bind(action, Bind::CONTROLLER_BUTTON, button); + bindings[action].push_back(bind); + Util::Log(Info, "Config") << buttonStr << " bound to " << actionStr; + } else if (axis != -1) { + float sensitivity = json["sensitivity"][buttonStr].number_value(); + float deadZone = json["dead_zone"][buttonStr].number_value(); + Bind bind(action, Bind::CONTROLLER_AXIS, axis, sensitivity, deadZone); + bindings[action].push_back(bind); + Util::Log(Info, "Config") << buttonStr << " bound to " << actionStr << " with sensitivity " << sensitivity << " and deadzone " << deadZone; + } else if (trigger != -1) { + float sensitivity = json["sensitivity"][buttonStr].number_value(); + float actPoint = json["dead_zone"][buttonStr].number_value(); + Bind bind(action, Bind::CONTROLLER_TRIGGER, trigger, sensitivity, actPoint); + bindings[action].push_back(bind); + } else { + Util::Log(Info, "Config") << buttonStr << "is an invalid control name"; + } + } + } + } +} + +void Config::loadDefaultBindings() { + for (int i(0); i < InputManager::ACTION_MAX; ++i) { + if (bindings[i].empty()) { + bindings[i] = defaultBindings[i]; + Util::Log(Info, "Config") << actionToString(i) << " set to default bind"; + } + } } void Config::loadLoglevelSettings(const Json &json) { @@ -95,3 +228,4 @@ void Config::loadLoglevelSettings(const Json &json) { } } /* namespace radix */ + diff --git a/source/input/Channel.cpp b/source/input/Channel.cpp new file mode 100644 index 00000000..43675121 --- /dev/null +++ b/source/input/Channel.cpp @@ -0,0 +1,173 @@ +#include + +#include +#include +#include + +#include + +namespace radix { + +template +void Channel::init(const int &id, EventDispatcher &event, const std::vector &binds) { + if (this->listeners.empty()) { + throw Exception::Error("Channel", "Tried to initialise sub-channel, id: " + std::to_string(id) + ", without a listener"); + } + + this->setId(id); + + if (InputManager::isActionDigital(id)) { + this->setDigital(0.5f); + + if (id == InputManager::PLAYER_JUMP) { + this->setAutoZero(); + } + } else { + this->setAnalogue(0.0f); + } + + for (int i = 0; i < (int)binds.size(); ++i) { + this->subChannels.push_back(SubChannel(this)); + } + //these have to be separate for some reason + for (int i = 0; i < (int)binds.size(); ++i) { + this->subChannels[i].init(i, event, binds[i]); + } +} + +template +void Channel::channelChanged(const int &id) { + this->Channel::set(this->subChannels.at(id).SubChannel::get()); +} + +template +void SubChannel::init(const int &id, EventDispatcher &event, const Bind& bind) { + if (this->listeners.empty()) { + throw Exception::Error("SubChannel", "Tried to initialise sub-channel, id: " + std::to_string(id) + ", without a listener"); + } + + this->setId(id); + this->bind = bind; + this->setSensitivity(bind.sensitivity); + + if (InputManager::isActionDigital(bind.action) or Bind::isInputTypeDigital(bind.inputType)) { + this->setDigital(bind.actPoint); + } else { + this->setAnalogue(bind.deadZone); + this->setBound(1.0f); + } + + switch (bind.inputType) { + + case Bind::KEYBOARD: { + this->callbacks[0] = event.addObserver(InputSource::KeyPressedEvent::Type, [this](const radix::Event& event) { + const int key = ((radix::InputSource::KeyPressedEvent &) event).key; + if (key == this->bind.inputCode) { + this->set(1.0f); + } + }); + + this->callbacks[1] = event.addObserver(InputSource::KeyReleasedEvent::Type, [this](const radix::Event& event) { + const int key = ((radix::InputSource::KeyReleasedEvent &) event).key; + if (key == this->bind.inputCode) { + this->set(0.0f); + } + }); + break; + } + case Bind::MOUSE_BUTTON: { + this->callbacks[0] = event.addObserver(InputSource::MouseButtonPressedEvent::Type, [this](const radix::Event& event) { + const int button = (int)((radix::InputSource::MouseButtonPressedEvent &) event).button; + if (button == this->bind.inputCode) { + this->set((T)1.0f); + } + }); + + this->callbacks[1] = event.addObserver(InputSource::MouseButtonReleasedEvent::Type, [this](const radix::Event& event) { + const int button = (int)((radix::InputSource::MouseButtonPressedEvent &) event).button; + if (button == this->bind.inputCode) { + this->set((T)0.0f); + } + }); + break; + } + case Bind::CONTROLLER_BUTTON: { + this->callbacks[0] = event.addObserver(InputSource::ControllerButtonPressedEvent::Type, [this](const radix::Event& event) { + const int button = ((radix::InputSource::ControllerButtonPressedEvent &) event).button; + if (button == this->bind.inputCode) { + this->set((T)1.0f); + } + }); + + this->callbacks[1] = event.addObserver(InputSource::ControllerButtonReleasedEvent::Type, [this](const radix::Event& event) { + const int button = ((radix::InputSource::ControllerButtonReleasedEvent &) event).button; + if (button == this->bind.inputCode) { + this->set((T)0.0f); + } + }); + break; + } + case Bind::CONTROLLER_TRIGGER: { + this->callbacks[0] = event.addObserver(InputSource::ControllerTriggerEvent::Type, [this](const radix::Event& event) { + const int trigger = ((radix::InputSource::ControllerTriggerEvent &) event).trigger; + const float value = ((radix::InputSource::ControllerTriggerEvent &) event).value; + if (trigger == this->bind.inputCode) { + this->set((T)value); + } + }); + break; + } + } +} + +template<> +void SubChannel::init(const int &id, EventDispatcher &event, const Bind& bind) { + if (this->listeners.empty()) { + throw Exception::Error("SubChannel", "Tried to initialise sub-channel, id: " + std::to_string(id) + ", without a listener"); + } + + this->setId(id); + this->bind = bind; + this->setSensitivity(bind.sensitivity); + if (InputManager::isActionDigital(bind.action) or Bind::isInputTypeDigital(bind.inputType)) { + } else { + if (bind.inputType == Bind::CONTROLLER_AXIS) { + setAnalogue(bind.deadZone); + setBound(0.8f); + } + if (bind.inputType == Bind::MOUSE_AXIS) { + setAnalogue(0.0f); + setAutoZero(); + } + } + + switch (bind.inputType) { + case Bind::MOUSE_AXIS: { + this->callbacks[0] = event.addObserver(InputSource::MouseAxisEvent::Type, [this](const radix::Event& event) { + const Vector2f value = ((radix::InputSource::MouseAxisEvent &) event).value; + + this->set(value); + }); + break; + } + case Bind::CONTROLLER_AXIS: { + this->callbacks[1] = event.addObserver(InputSource::ControllerAxisEvent::Type, [this](const radix::Event& event) { + const int axis = ((radix::InputSource::ControllerAxisEvent &) event).axis; + const Vector2f value = ((radix::InputSource::ControllerAxisEvent &) event).value; + + if (axis == this->bind.inputCode) { + this->SubChannel::set(value); + } + }); + break; + } + } +} + +template class Channel; +template class Channel; + +template class SubChannel; +template class SubChannel; + +} diff --git a/source/input/ChannelBase.cpp b/source/input/ChannelBase.cpp new file mode 100644 index 00000000..f27146e8 --- /dev/null +++ b/source/input/ChannelBase.cpp @@ -0,0 +1,143 @@ +#include + +#include +#include +#include + +#include + +namespace radix { + +template +ChannelBase::ChannelBase() + : listeners(1, nullptr), + id(0), + bound(0), + sensitivity(1.0f), + hasBound(false), + isDigital(false), + autoZero(false), + alwaysNotifyListener(false), + actPoint(0), + value() {} + +template +ChannelBase::ChannelBase(ChannelListener *listener) + : listeners(1, listener), + id(0), + bound(0), + sensitivity(1.0f), + hasBound(false), + isDigital(false), + autoZero(false), + alwaysNotifyListener(false), + actPoint(0), + value() {} + +template +void ChannelBase::addListener(ChannelListener* listener) { + for (ChannelListener* mListener: listeners) { + if (listener == mListener) { + return; + } + } + + listeners.push_back(listener); +} + +template +void ChannelBase::setDigital(const float &actPoint) { + this->actPoint = actPoint; + isDigital = true; +} + +template +void ChannelBase::setAnalogue(const float &deadZone) { + this->deadZone = deadZone; + isDigital = false; +} + +template +void ChannelBase::setBound(const float &bound) { + this->bound = bound; + hasBound = true; +} + +template +void ChannelBase::set(T newValue) { + if (isDigital) { + newValue = std::abs(newValue) >= actPoint ? 1.0f : 0.0f; + } else { + newValue = std::abs(newValue) >= deadZone ? newValue : 0.0f; + + if (hasBound) { + newValue = std::abs(newValue) >= bound ? 1.0f : newValue; + } + + newValue *= sensitivity; + } + + if (value != newValue or alwaysNotifyListener) { + value = newValue; + + if (not listeners.empty()) { + notifyListeners(); + } + } else { + return; + } + + if (autoZero) { + value = 0.0f; + } +} + +template <> +void ChannelBase::set(Vector2f newValue) { + if (isDigital) { + newValue.x = newValue.x >= actPoint ? 1.0f : 0.0f; + newValue.y = newValue.y >= actPoint ? 1.0f : 0.0f; + } else { + float length = newValue.length(); + if (deadZone > 0) { + if (length <= deadZone) { + newValue = Vector2f::ZERO; + } else { + newValue *= (length - deadZone) / length; + } + } + + if (hasBound) { + newValue.x = std::abs(newValue.x) >= bound ? 1.0f : newValue.x; + newValue.y = std::abs(newValue.y) >= bound ? 1.0f : newValue.y; + } + + newValue *= sensitivity; + } + + if (alwaysNotifyListener or !newValue.fuzzyEqual(value, 0.0001)) { + value = newValue; + + if (not listeners.empty()) { + notifyListeners(); + } + } else { + return; + } + + if (false) { + value = Vector2f(0.0f); + } +} + +template +void ChannelBase::notifyListeners() { + for (ChannelListener* listener: this->listeners) { + listener->channelChanged(id); + } +} + +template class ChannelBase; +template class ChannelBase; + +} diff --git a/source/input/InputManager.cpp b/source/input/InputManager.cpp new file mode 100644 index 00000000..b8f530af --- /dev/null +++ b/source/input/InputManager.cpp @@ -0,0 +1,121 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace radix { + +InputManager::InputManager(BaseGame &game) : + game(game) {} + +void InputManager::setConfig(const Config &config) { + this->config = config; +} + +void InputManager::init(EventDispatcher &event) { + if (not config.getBindings()[PLAYER_MOVE_ANALOGUE].empty()) { + analogueChannels[PLAYER_MOVE_ANALOGUE] = Channel((ChannelListener*)this); + analogueChannels[PLAYER_MOVE_ANALOGUE].init(PLAYER_MOVE_ANALOGUE, event, config.getBindings()[PLAYER_MOVE_ANALOGUE]); + } + + analogueChannels[PLAYER_LOOK_ANALOGUE] = Channel((ChannelListener*)this); + analogueChannels[PLAYER_LOOK_ANALOGUE].init(PLAYER_LOOK_ANALOGUE, event, config.getBindings()[PLAYER_LOOK_ANALOGUE]); + + for (int id = 2; id < ACTION_MAX; ++id) { + digitalChannels[id] = Channel((ChannelListener*)this); + digitalChannels[id].init(id, event, config.getBindings()[id]); + } +} + +void InputManager::channelChanged(const int &id) { + entities::Player& player = game.getWorld()->getPlayer(); + + switch(id) { + case PLAYER_MOVE_ANALOGUE: { + Vector2f movement = analogueChannels[PLAYER_MOVE_ANALOGUE].get(); + player.move(movement); + break; + } + case PLAYER_LOOK_ANALOGUE: { + Vector2f heading = analogueChannels[PLAYER_LOOK_ANALOGUE].get(); + player.changeHeading(heading); + break; + } + case PLAYER_JUMP: { + if (digitalChannels[PLAYER_JUMP].get()) { + player.jump(); + } + break; + } + case PLAYER_PORTAL_0: { + // + } + case PLAYER_PORTAL_1: { + // + } + case PLAYER_FORWARD: + case PLAYER_BACK: { + float moveY = digitalChannels[PLAYER_BACK].get() - digitalChannels[PLAYER_FORWARD].get(); + player.moveY(moveY); + break; + } + case PLAYER_LEFT: + case PLAYER_RIGHT: { + float moveX = digitalChannels[PLAYER_RIGHT].get() - digitalChannels[PLAYER_LEFT].get(); + player.moveX(moveX); + break; + } + case GAME_PAUSE: { + + } + case GAME_QUIT: { + if (digitalChannels[GAME_QUIT].get()) { + game.close(); + } + break; + } + default: { + return; + } + } +} + +Vector2f InputManager::getPlayerMovementVector() const { + Vector2f movement = analogueChannels.at(PLAYER_MOVE_ANALOGUE).get(); + movement.x += digitalChannels.at(PLAYER_RIGHT).get() - digitalChannels.at(PLAYER_LEFT).get(); + movement.y += digitalChannels.at(PLAYER_BACK).get() - digitalChannels.at(PLAYER_FORWARD).get(); + return movement; +} + +bool InputManager::isActionDigital(const int &act) { + switch(act) { + case PLAYER_JUMP: + case PLAYER_PORTAL_0: + case PLAYER_PORTAL_1: + case PLAYER_FORWARD: + case PLAYER_BACK: + case PLAYER_LEFT: + case PLAYER_RIGHT : + case GAME_PAUSE : + case GAME_QUIT : { + return true; + break; + } + case PLAYER_MOVE_ANALOGUE: + case PLAYER_LOOK_ANALOGUE: + case ACTION_MAX : + case ACTION_INVALID : + default : { + return false; + break; + } + } +} + +} diff --git a/source/input/InputSource.cpp b/source/input/InputSource.cpp index 886dc491..66df8d81 100644 --- a/source/input/InputSource.cpp +++ b/source/input/InputSource.cpp @@ -1,5 +1,8 @@ #include +#include +#include + #include #include @@ -8,10 +11,82 @@ using namespace std; namespace radix { +const InputSource::LookUpTable InputSource::mouseButtonLookUp = { + {"mouse_button_left", (int)MouseButton::Left}, + {"mouse_button_middle", (int)MouseButton::Middle}, + {"mouse_button_right", (int)MouseButton::Right}, + {"mouse_button_aux_1", (int)MouseButton::Aux1}, + {"mouse_button_aux_2", (int)MouseButton::Aux2}, + {"mouse_button_aux_3", (int)MouseButton::Aux3}, + {"mouse_button_aux_4", (int)MouseButton::Aux4}, + {"mouse_button_aux_5", (int)MouseButton::Aux5}, + {"mouse_button_aux_6", (int)MouseButton::Aux6} +}; + +const InputSource::LookUpTable InputSource::controllerButtonLookUp = { + {"button_a", (int)SDL_CONTROLLER_BUTTON_A}, + {"button_b", (int)SDL_CONTROLLER_BUTTON_B}, + {"button_x", (int)SDL_CONTROLLER_BUTTON_X}, + {"button_y", (int)SDL_CONTROLLER_BUTTON_Y}, + {"button_back", (int)SDL_CONTROLLER_BUTTON_BACK}, + {"button_guide", (int)SDL_CONTROLLER_BUTTON_GUIDE}, + {"button_start", (int)SDL_CONTROLLER_BUTTON_START}, + {"left_stick", (int)SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {"right_stick", (int)SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {"left_shoulder", (int)SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {"right_shoulder", (int)SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {"dpad_up", (int)SDL_CONTROLLER_BUTTON_DPAD_UP}, + {"dpad_down", (int)SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {"dpad_left", (int)SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {"dpad_right", (int)SDL_CONTROLLER_BUTTON_DPAD_RIGHT} +}; + void InputSource::removeDispatcher(EventDispatcher &d) { dispatchers.erase(std::remove_if(dispatchers.begin(), dispatchers.end(), [&d](EventDispatcher &e) { return std::addressof(e) == std::addressof(d); }), dispatchers.end()); } +int InputSource::keyboardGetKeyFromString(const std::string &key) { + return (int)SDL_GetScancodeFromName(key.c_str()); +} + +int InputSource::mouseGetButtonFromString(const std::string &buttonStr) { + LookUpTable::const_iterator button = mouseButtonLookUp.find(buttonStr); + if (button != mouseButtonLookUp.end()) { + return button->second; + } else { + return -1; + } +} + +int InputSource::gameControllerGetButtonFromString(const std::string &buttonStr) { + LookUpTable::const_iterator button = controllerButtonLookUp.find(buttonStr); + if (button != controllerButtonLookUp.end()) { + return button->second; + } else { + return -1; + } +} + +int InputSource::gameControllerGetAxisFromString(const std::string &axisStr) { + if (axisStr == "stick_left") { + return 0; + } else if (axisStr == "stick_right") { + return 1; + } else { + return -1; + } +} + +int InputSource::gameControllerGetTriggerFromString(const std::string &triggerStr) { + if (triggerStr == "trigger_left") { + return 0; + } else if (triggerStr == "trigger_right") { + return 1; + } else { + return -1; + } +} + } /* namespace radix */