0
0
mirror of https://github.com/Nimac0/SDL_Minigame synced 2026-01-12 13:43:41 +00:00

feat/ref: Interaction and Event management

This commit is contained in:
Nimac0 2025-04-09 21:06:34 +02:00
parent adaed679af
commit 3c5d56de6b
9 changed files with 120 additions and 121 deletions

View File

@ -102,7 +102,7 @@ public:
struct InputAction { struct InputAction {
std::string name; std::string name;
std::vector<Key> bindings; std::vector<Key> bindings;
std::function<void()> callback; std::function<void(bool)> callback;
}; };
InputManager(); InputManager();
@ -110,28 +110,26 @@ public:
void init(); // see if necessary void init(); // see if necessary
void processEvents(); void processEvents();
void registerAction(const std::string& actionName, const std::vector<Key>& keys, std::function<void()> callback, const std::string& context); void registerAction(const std::string& actionName, const std::vector<Key>& keys, std::function<void(bool)> callback, const std::string& context);
void setActiveContext(const std::string& context); void setActiveContext(const std::string& context);
std::string getActiveContext() const; std::string getActiveContext() const;
void rebindAction(const std::string& actionName, const std::vector<Key>& newBindings, const std::string& context); //void rebindAction(const std::string& actionName, const std::vector<Key>& newBindings, const std::string& context);
void removeBindings(const std::string& actionName, const std::string& context); //void removeBindings(const std::string& actionName, const std::string& context);
std::vector<Key> getBindings(const std::string& actionName, const std::string& context) const; //std::vector<Key> getBindings(const std::string& actionName, const std::string& context) const;
std::vector<InputAction*> getActionsByKey(const Key key) const; std::vector<InputAction*> getActionsByKey(const Key key) const;
SDL_AppResult handleEvent(SDL_EventType type, SDL_Event* const event); SDL_AppResult handleEvent(SDL_EventType type, SDL_Event* const event);
void initKeyMap();
private: private:
// TODO: flesh this out to avoid loops in process actions // TODO: flesh this out to avoid loops in process actions
// additionally to actionsByContext, not instead // additionally to actionsByContext, not instead
std::map<std::string, std::vector<InputAction>> actionsByContext; std::map<std::string, std::map<Key, std::vector<InputAction*>>> actionsByContextAndKey;
std::map<Key, std::vector<InputAction*>> actionsByKey;
std::map<Key, SDL_Scancode> keyMap; std::map<Key, SDL_Scancode> keyMap;
std::string activeContext; std::string activeContext;
void initKeyMap();
}; };
std::ostream& operator<<(std::ostream& os, InputManager::Key key); std::ostream& operator<<(std::ostream& os, InputManager::Key key);

View File

@ -0,0 +1,28 @@
#pragma once
#include "Component.h"
#include "InteractionListener.h"
#include <functional>
class InteractionComponent : public Component, public InteractionListener
{
public:
/**
* @brief Constructor for the InteractionComponent.
* @param callback A function to be called when an interaction event is triggered. void* actor, void* data are passed to the callback function from InteractionEventdataStruct.
*/
InteractionComponent(std::function<void(void*,void*)> callback);
/**
* @brief Internal function to be called when an interaction event is triggered.
*/
void interact(void* actor, void* data) override;
/**
* @brief Internal function to use as reference for targeting.
*/
std::shared_ptr<Vector2D> getPosition() override;
private:
std::function<void(void*,void*)> interactionCallback;
};

View File

@ -2,14 +2,27 @@
#include "Entity.h" #include "Entity.h"
#include "InteractionListener.h" #include "InteractionListener.h"
#include "InteractionManager.h"
#include "Vector2D.h" #include "Vector2D.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
/**
* @brief Struct to hold data for interaction events.
* This struct is used to pass data to the interaction manager when an interaction event is triggered.
*/
struct InteractionEventdataStruct { struct InteractionEventdataStruct {
void* actor; // suggestion, can also be used for other arbitrary data /// Arbitray data to pass to the interaction listener. Can for example be an Entity ptr to represent the actor.
void* actor;
/// The data to pass to the interaction listener. Can be any type of pointer.
void* data; void* data;
/// The target of the interaction, e.g. InteractionComponent of an Entity. Is required if strategy is set to 0 (none)
std::weak_ptr<InteractionListener> target = std::weak_ptr<InteractionListener>(); std::weak_ptr<InteractionListener> target = std::weak_ptr<InteractionListener>();
std::shared_ptr<Vector2D> targetingReference; // required without explicit target /// Coordinates from which to base targeting on. Is required if strategy is not set to 0 (none)
uint8_t strategy = 0; // required without explicit target, defaults to none std::shared_ptr<Vector2D> targetingReference = nullptr;
/// required without explicit target, defaults to none
/// @sa InteractionManager::TargetingStrategy
uint8_t strategy = 0; // int since enum would be impossibling user defined targetingStrategies
void triggerEvent();
}; };

View File

@ -12,11 +12,11 @@ Uint32 vego::VEGO_Event_Interaction;
EventManager::EventManager() EventManager::EventManager()
{ {
/// \TODO: from c++26 you (should be able to) can get the amount of name values in an enum /// \TODO: from c++26 you (should be able to) can get the amount of name values in an enum
vego::VEGO_Event_Interaction = SDL_RegisterEvents(1); // TODO: error handling vego::VEGO_Event_Interaction = SDL_RegisterEvents(1); // TODO: error handling
} }
void EventManager::registerListener(EventListener listener, std::initializer_list<Uint32> eventTypes) void EventManager::registerListener(EventListener listener, std::initializer_list<Uint32> eventTypes)
{ {
std::ranges::for_each(eventTypes.begin(), eventTypes.end(), [this, &listener](const Uint32& eventType) { std::ranges::for_each(eventTypes.begin(), eventTypes.end(), [this, &listener](const Uint32& eventType) {
@ -31,10 +31,10 @@ SDL_AppResult EventManager::handleEvent(SDL_Event* event)
{ {
SDL_EventType type = (SDL_EventType) event->type; SDL_EventType type = (SDL_EventType) event->type;
if (this->eventListeners.contains(type)) { if (this->eventListeners.contains(type)) {
auto results = this->eventListeners.at(type) | std::views::transform( std::vector<SDL_AppResult> results;
[&event](EventListener listener) { for (auto& listener : this->eventListeners.at(type)) {
return listener((SDL_EventType) event->type, event); results.emplace_back(listener((SDL_EventType) event->type, event));
}); }
if (std::ranges::contains(results, SDL_APP_FAILURE)) if (std::ranges::contains(results, SDL_APP_FAILURE))
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
if (std::ranges::contains(results, SDL_APP_SUCCESS)) if (std::ranges::contains(results, SDL_APP_SUCCESS))

View File

@ -50,6 +50,7 @@ SDL_AppResult GameInternal::init()
GameInternal::soundManager = new SoundManager(); GameInternal::soundManager = new SoundManager();
GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer? GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer?
GameInternal::inputManager = new InputManager(); GameInternal::inputManager = new InputManager();
GameInternal::inputManager->initKeyMap();
GameInternal::renderManager = new RenderManager(); GameInternal::renderManager = new RenderManager();
GameInternal::eventManager = new EventManager(); GameInternal::eventManager = new EventManager();

View File

@ -101,12 +101,8 @@ std::ostream& operator<<(std::ostream& os, InputManager::Key key) {
std::ostream& operator<<(std::ostream& os, const InputManager::InputAction& action) { std::ostream& operator<<(std::ostream& os, const InputManager::InputAction& action) {
os << action.name << " with binding(s): "; os << action.name << " with binding(s): ";
for (size_t i = 0; i < action.bindings.size(); ++i) { for (auto& binding : action.bindings) {
os << action.bindings[i]; os << binding << ", ";
if (i < action.bindings.size() - 1) {
os << ", ";
}
} }
return os; return os;
} }
@ -168,13 +164,6 @@ InputManager::InputManager() : activeContext("Default") {}
InputManager::~InputManager() {} InputManager::~InputManager() {}
void InputManager::init() {
if (SDL_Init(SDL_INIT_EVENTS) != 0) {
std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
return;
}
}
void InputManager::initKeyMap() { void InputManager::initKeyMap() {
keyMap = { keyMap = {
{Key::UP, SDL_SCANCODE_UP}, {Key::UP, SDL_SCANCODE_UP},
@ -259,81 +248,25 @@ void InputManager::initKeyMap() {
}; };
} }
void InputManager::registerAction(const std::string& actionName, const std::vector<Key>& keys, std::function<void()> callback, const std::string& context) { void InputManager::registerAction(const std::string& actionName, const std::vector<Key>& keys, std::function<void(bool)> callback, const std::string& context) {
actionsByContext[context].emplace_back(actionName, keys, callback); InputAction* storedAction = new InputAction{actionName, keys, callback};
InputAction& storedAction = actionsByContext[context].back();
for (const auto& key : keys) { for (const auto& key : keys) {
actionsByKey[key].push_back(&storedAction); actionsByContextAndKey[context][key].emplace_back(storedAction);
} }
std::cout << "Registered action: " << storedAction << " in context: " << context << std::endl; std::cout << "Registered action: " << storedAction << " in context: " << context << std::endl;
} }
void InputManager::rebindAction(const std::string& actionName, const std::vector<Key>& newBindings, const std::string& context) {
auto it = actionsByContext.find(context);
if (it != actionsByContext.end()) {
for (auto& action : it->second) {
if (action.name == actionName) {
for (const auto& key : action.bindings) {
auto& keyActions = actionsByKey[key];
keyActions.erase(std::remove(keyActions.begin(), keyActions.end(), &action), keyActions.end());
if (keyActions.empty()) {
actionsByKey.erase(key);
}
}
action.bindings = newBindings;
for (const auto& key : newBindings) {
actionsByKey[key].push_back(&action);
}
std::cout << "Rebound action: " << actionName << " in context: " << context << " to new bindings: ";
for (const auto& key : newBindings) {
std::cout << key << " ";
}
std::cout << std::endl;
return;
}
}
}
std::cout << "Action not found: " << actionName << " in context: " << context << std::endl;
}
void InputManager::removeBindings(const std::string& actionName, const std::string& context) {
auto it = actionsByContext.find(context);
if (it != actionsByContext.end()) {
for (auto& action : it->second) {
if (action.name == actionName) {
action.bindings.clear();
std::cout << "Removed bindings for action: " << actionName << " in context: " << context << std::endl;
return;
}
}
}
std::cout << "Action not found: " << actionName << " in context: " << context << std::endl;
}
std::vector<InputManager::Key> InputManager::getBindings(const std::string& actionName, const std::string& context) const {
auto it = actionsByContext.find(context);
if (it != actionsByContext.end()) {
for (const auto& action : it->second) {
if (action.name == actionName) {
return action.bindings;
}
}
}
std::cout << "Action not found: " << actionName << " in context: " << context << "\n";
return {};
}
std::vector<InputManager::InputAction*> InputManager::getActionsByKey(const Key key) const { std::vector<InputManager::InputAction*> InputManager::getActionsByKey(const Key key) const {
auto it = actionsByKey.find(key); std::vector<InputAction*> result;
if (it != actionsByKey.end()) {
std::cout << "DEBUG: Found " << it->second.size() << " actions for key " << key << std::endl; for (const auto& [context, keyMap] : actionsByContextAndKey) {
return it->second; auto it = keyMap.find(key);
if (it != keyMap.end()) {
result.insert(result.end(), it->second.begin(), it->second.end());
} }
std::cout << "DEBUG: No actions found for key " << key << std::endl; }
return {};
return result;
} }
void InputManager::setActiveContext(const std::string& context) { void InputManager::setActiveContext(const std::string& context) {
@ -346,37 +279,22 @@ std::string InputManager::getActiveContext() const {
} }
SDL_AppResult InputManager::handleEvent(SDL_EventType type, SDL_Event* const event) { SDL_AppResult InputManager::handleEvent(SDL_EventType type, SDL_Event* const event) {
if (type != SDL_EVENT_KEY_DOWN) {
return SDL_APP_CONTINUE;
}
if (event->key.repeat) { if (event->key.repeat) {
return SDL_APP_CONTINUE; return SDL_APP_CONTINUE;
} }
auto keyIt = std::ranges::find_if(keyMap, [&](const auto& pair)
auto keyIt = std::ranges::find_if(keyMap, { return pair.second == event->key.scancode; });
[&](const auto& pair) { return pair.second == event->key.scancode; });
if (keyIt != keyMap.end()) { if (keyIt != keyMap.end()) {
std::cout << "in != keymap.end" << std::endl; std::cout << "in != keymap.end" << std::endl;
Key pressedKey = keyIt->first; Key pressedKey = keyIt->first;
auto actionIt = actionsByKey.find(pressedKey); auto keyActions = actionsByContextAndKey[activeContext];
if (actionIt != actionsByKey.end()) { auto it = keyActions.find(pressedKey);
std::cout << "in != actionsByKey.end" << std::endl; if (it != keyActions.end()) {
for (auto& action : it->second) {
for (auto* action : actionIt->second) {
std::cout << "before if(action)" << std::endl;
if (action) {
std::cout << "after if(action)" << std::endl;
auto& activeActions = actionsByContext[activeContext];
if (std::ranges::find_if(activeActions,
[&](const InputAction& act) { return &act == action; }) != activeActions.end()) {
std::cout << "Action triggered: " << action->name << " in context: " << activeContext << std::endl; std::cout << "Action triggered: " << action->name << " in context: " << activeContext << std::endl;
action->callback(); action->callback(type == SDL_EVENT_KEY_UP);
}
}
} }
} }
} }

View File

@ -0,0 +1,22 @@
#include "InteractionComponent.h"
#include "VEGO.h"
InteractionComponent::InteractionComponent(std::function<void(void*,void*)> callback) : interactionCallback(callback)
{
VEGO_Game().interactionManager->registerListener(this->entity->getComponentAsPointer<InteractionComponent>());
}
void InteractionComponent::interact(void* actor, void* data)
{
if (interactionCallback) {
interactionCallback(actor, data);
}
}
std::shared_ptr<Vector2D> InteractionComponent::getPosition() // required for targeting strategy, return null to only allow explicit targeting
{
if (entity->hasComponent<TransformComponent>()) {
return std::make_shared<Vector2D>(entity->getComponent<TransformComponent>().position);
}
return nullptr;
}

View File

@ -0,0 +1,16 @@
#include "InteractionEventdataStruct.h"
#include "VEGO.h"
#include "VEGO_Event.h"
void InteractionEventdataStruct::triggerEvent()
{
// TODO: if target is null && strategy is 0, error
SDL_Event event;
SDL_zero(event);
event.type = vego::VEGO_Event_Interaction;
event.user.data1 = this;
SDL_PushEvent(&event);
}

View File

@ -42,6 +42,9 @@ void TransformComponent::init()
void TransformComponent::update(uint_fast16_t diffTime) void TransformComponent::update(uint_fast16_t diffTime)
{ {
direction.x = direction.x > 0 ? 1 : direction.x < 0 ? -1 : 0;
direction.y = direction.y > 0 ? 1 : direction.y < 0 ? -1 : 0;
float multiplier = direction.x != 0 && direction.y != 0 ? 0.707 : 1; // normalizes vector; only works if directions are in increments of 45° float multiplier = direction.x != 0 && direction.y != 0 ? 0.707 : 1; // normalizes vector; only works if directions are in increments of 45°
Vector2D positionChange( Vector2D positionChange(
direction.x * this->getSpeed() * multiplier * diffTime * (1.f/1000), direction.x * this->getSpeed() * multiplier * diffTime * (1.f/1000),