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

Merge branch 'dev'
Some checks failed
/ deploy (push) Has been cancelled

This commit is contained in:
Nimac0 2025-04-09 22:00:12 +02:00
commit 2ff579dd39
73 changed files with 1959 additions and 801 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
# allow game dev to overwrite settings
root=false
[*]
end_of_line = lf
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

8
.gitmodules vendored
View File

@ -1,22 +1,22 @@
[submodule "SDL"]
path = extern/SDL
url = https://github.com/libsdl-org/SDL.git
branch = release-2.28.x
[submodule "SDL_image"]
path = extern/SDL_image
url = https://github.com/libsdl-org/SDL_image.git
branch = release-2.8.x
[submodule "extern/SDL_mixer"]
path = extern/SDL_mixer
url = https://github.com/libsdl-org/SDL_mixer.git
branch = release-2.8.x
[submodule "extern/SDL_ttf"]
path = extern/SDL_ttf
url = https://github.com/libsdl-org/SDL_ttf.git
branch = release-2.22.x
[submodule "extern/tmxlite"]
path = extern/tmxlite
url = https://github.com/fallahn/tmxlite.git
[submodule "docs/doxygen-awesome-css"]
path = docs/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
[submodule "extern/nlohmann_json"]
path = extern/nlohmann_json
url = https://github.com/nlohmann/json.git

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15)
project(engine)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(ENGINE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
@ -12,10 +12,10 @@ set(ENGINE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(BUILD_SHARED_LIBS FALSE)
set(SDL2MIXER_VENDORED ON)
set(SDL2TTF_VENDORED ON)
set(SDLMIXER_VENDORED ON)
set(SDLTTF_VENDORED ON)
set(SDL2_SOURCE_DIR ${ENGINE_SOURCE_DIR}/extern/SDL”)
set(SDL_SOURCE_DIR ${ENGINE_SOURCE_DIR}/extern/SDL”)
set(TMXLITE_STATIC_LIB TRUE)
@ -24,6 +24,8 @@ add_subdirectory(extern/SDL_image EXCLUDE_FROM_ALL)
add_subdirectory(extern/SDL_mixer EXCLUDE_FROM_ALL)
add_subdirectory(extern/SDL_ttf EXCLUDE_FROM_ALL)
add_subdirectory(extern/tmxlite/tmxlite EXCLUDE_FROM_ALL)
add_subdirectory(extern/nlohmann_json EXCLUDE_FROM_ALL)
file(GLOB_RECURSE SOURCES ${ENGINE_SOURCE_DIR}/src/*.cpp)
add_library(${PROJECT_NAME} ${SOURCES})
@ -31,11 +33,11 @@ add_library(${PROJECT_NAME} ${SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC ${ENGINE_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC # should be private when all SDL functionality has a wrapper
SDL2::SDL2main
SDL2::SDL2-static
SDL2_image::SDL2_image-static
SDL2_mixer::SDL2_mixer-static
SDL2_ttf::SDL2_ttf-static
SDL3::SDL3-static
SDL3_image::SDL3_image-static
SDL3_mixer::SDL3_mixer-static
SDL3_ttf::SDL3_ttf-static
nlohmann_json::nlohmann_json
tmxlite
)

7
config.json Normal file
View File

@ -0,0 +1,7 @@
{
"fullscreen": false,
"title": "VGG (Very Good Game)",
"screen_height": 600,
"screen_width": 800,
"icon": "./engine/internalAssets/iconImage.bmp"
}

View File

@ -2,12 +2,12 @@
"folders":
[
{
"path": "."
"path": ".",
}
],
],
"settings":
{
"tab_size": 4
"tab_size": 4,
},
"build_systems": [
{
@ -23,7 +23,7 @@
"name": "Release",
"shell_cmd": "cmake -DCMAKE_BUILD_TYPE=Release build && cmake --build build",
},
]
],
},
{
"name": "Generate CMake",
@ -43,7 +43,10 @@
"name": "Generate documentation",
"shell_cmd": "docker run --rm -v \"$project_path:/source\" -v \"$project_path/docs:/output\" -v \"$project_path/docs/Doxyfile:/Doxyfile\" vego_engine-docker",
},
]
],
}
]
],
"debugger_configurations":
[
],
}

2
extern/SDL vendored

@ -1 +1 @@
Subproject commit 05eb08053d48fea9052ad02b3d619244aeb868d3
Subproject commit f6864924f76e1a0b4abaefc76ae2ed22b1a8916e

2
extern/SDL_image vendored

@ -1 +1 @@
Subproject commit abcf63aa71b4e3ac32120fa9870a6500ddcdcc89
Subproject commit b1c8ec7d75e3d8398940c9e04a8b82886ae6163d

2
extern/SDL_mixer vendored

@ -1 +1 @@
Subproject commit 5bcd40ad962dc72a3c051084ce128d78f7656566
Subproject commit 5e2a70519294bc6ec44f1870b019ecd4760fde7d

2
extern/SDL_ttf vendored

@ -1 +1 @@
Subproject commit 4a318f8dfaa1bb6f10e0c5e54052e25d3c7f3440
Subproject commit 4a8bda9197cc4d6fafd188bc9df6c7e8749a43a2

1
extern/nlohmann_json vendored Submodule

@ -0,0 +1 @@
Subproject commit a006a7a48bb30a247f0344b788c62c2806edd90b

View File

@ -1,57 +0,0 @@
#pragma once
#include <SDL_render.h>
#include <SDL_mixer.h>
#include <map>
#include <string>
#include <functional>
#include "Entity.h"
class Vector2D;
class Manager;
enum class PowerupType
{
HEART,
WALKINGSPEED,
SHOOTINGSPEED
};
class AssetManager
{
public:
AssetManager(Manager* manager);
~AssetManager();
void createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, int speed, const char* texturePath, Entity* owner);
void createPowerup(Vector2D pos, std::function<void (Entity*)> pickupFunc, std::string texturePath);
/*!
* \brief Calculates a random spawn position for an object within a given area
* \param size The size (collision box) of the object
* \param spawnArea The area within which a spawn position will be calculated
* \returns Spawn Coordinates for the object
*/
Vector2D calculateSpawnPosition(Vector2D size, Vector2D spawnArea);
template <typename T> [[deprecated]] T calculateRandomType(int amount);
//texture management
void addTexture(std::string id, const char* path);
// sound management
void addSoundEffect(std::string id, const char* path);
void addMusic(std::string id, const char* path);
SDL_Texture* getTexture(std::string id);
Mix_Chunk* getSound(std::string id);
Mix_Music* getMusic(std::string id);
private:
Manager* man;
std::map<std::string, SDL_Texture*> textures;
std::map<std::string, Mix_Chunk*> soundEffects;
std::map<std::string, Mix_Music*> music;
};

View File

@ -0,0 +1,3 @@
#pragma once
enum class BackgroundMusic;

View File

@ -1,6 +1,6 @@
#pragma once
#include <SDL.h>
#include <SDL3/SDL.h>
#include "Component.h"
#include "Vector2D.h"
@ -22,7 +22,7 @@ public:
ColliderComponent(const char* tag, float hitboxScale);
void init() override;
void update() override;
void update(uint_fast16_t diffTime) override;
void removeCollision();
void handleCollision(Vector2D& characterPos, SDL_Rect& characterCollider, SDL_Rect& componentCollider);

View File

@ -7,7 +7,7 @@
#include "ColliderComponent.h"
#include "Constants.h"
#include "Entity.h"
#include "SDL_rect.h"
#include <SDL3/SDL_rect.h>
#include "SpriteComponent.h"
#include "Vector2D.h"
#include "Manager.h"

View File

@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
class Entity;
class Component
@ -8,7 +10,7 @@ public:
Entity* entity;
virtual void init() {}
virtual void update() {}
virtual void update(uint_fast16_t diffTime) {}
virtual ~Component() = default;
};

62
include/ConfigLoader.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include <nlohmann/json.hpp>
using json = nlohmann::json;
/*!
* \class ConfigLoader
* \brief Enables configuration of specific engine variables via a custom JSON file.
*
* The Config loader is responsible to handling customization for engine parameters like the
* window icon, window title, ... through json files.
*
* It includes a standard config file and the option to add a custom one by overwriting the setConfigFilePath()
* function within the implementation of the \ref Game class. Those files get merged, with a priorization on
* the parameters set within the custom config file.
*
*
* The currently available config parameters with their default values are:
* \include ../config.json
*
*/
class ConfigLoader {
public:
ConfigLoader();
~ConfigLoader();
/*!
* \brief Creates the final config for the engine
*
* Loads the default config and then creates the final config by either merging
* (if the custom config has been set) or by implementing the standard config (if no custom
* config was set).
*
* \private
*/
void init();
/*!
* \brief Sets the customConfigPath variable
*
* \param path optional variable that should include the path to the custom config JSON file
*
* \private
*/
void setCustomConfig(const std::optional<std::string>& path);
/*!
* \brief Gets final configuration
* \return `json` variable containing the final config
* \private
*/
json getFinalConfig();
private:
std::optional<std::string> customConfigPath;
json finalConfig;
json loadConfigFromJSON(const std::string& path);
json mergeConfigs(json baseConfig, json customConfig); //<! Merges 2 config.json files, prioritising the custom to the base one
};

View File

@ -11,9 +11,6 @@ constexpr std::size_t MAX_GROUPS = 32;
constexpr std::size_t MAX_STATS = 8;
constexpr std::size_t MAX_TEAMS = 8;
constexpr int SCREEN_SIZE_HEIGHT = 640;
constexpr int SCREEN_SIZE_WIDTH = 800;
constexpr int FPS = 60;
constexpr int TILE_SIZE = 32;

38
include/DataComponent.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <map>
#include <any>
#include <string>
#include <optional>
#include "Component.h"
class DataComponent : public Component
{
public:
DataComponent() {};
~DataComponent() {};
/**
* @brief Set a key-value pair of any type in the data map
* @details e.g. setEntry("speed", 180); in this case the key is "speed" and the value is set to an integer of 180
* @param key The name to store the value under
* @param value The value to store of type T
*/
template <typename T>
void setEntry(const std::string& key, const T& value) { dataMap.insert_or_assign(key, value); }
/**
* @brief Get a value of type T from the data map
* @details e.g. getEntry<int>("speed"); in this case the key is "speed" and the value is returned as an integer
* @param key The name to retrieve the value from
* @return An optional of type T containing the value if it exists and matches in typeid, otherwise std::nullopt
*/
template<typename T>
std::optional<T> getEntry(std::string key) const {
if (!this->dataMap.contains(key)) return std::nullopt;
const std::any& value = this->dataMap.at(key);
if (value.type() != typeid(T)) { return std::nullopt; }
return std::any_cast<T>(value);
}
private:
std::map<std::string, std::any> dataMap;
};

View File

@ -35,85 +35,90 @@ class Entity
{
public:
/*!
* \brief Used for rendering order (last is highest) or retrieving entities of group
* \todo Label used in singular entity shouldn't use plural
* \todo HEARTS are rendered above POWERUPS, missleading order
* \todo PROJECTILE are rendered above POWERUPS, missleading order
* \todo Generalize HEARTS as UI or similar
*/
enum class GroupLabel
{
MAPTILES, //!< Entity using TileComponent
PLAYERS, //!< Primary entity in player controll
ENEMIES, //!< \deprecated All players now grouped as Entity::PLAYERS
COLLIDERS, //!< Fixed collider entity, e.g. a wall
PROJECTILE, //!< \todo Document
HEARTS, //!< \todo Document
POWERUPS //!< \todo Document
};
/*!
* \brief Used for rendering order (last is highest) or retrieving entities of group
* \todo Label used in singular entity shouldn't use plural
* \todo HEARTS are rendered above POWERUPS, missleading order
* \todo PROJECTILE are rendered above POWERUPS, missleading order
* \todo Generalize HEARTS as UI or similar
*/
enum class GroupLabel
{
MAPTILES, //!< Entity using TileComponent
PLAYERS, //!< Primary entity in player controll
ENEMIES, //!< \deprecated All players now grouped as Entity::PLAYERS
COLLIDERS, //!< Fixed collider entity, e.g. a wall
PROJECTILE, //!< \todo Document
HEARTS, //!< \todo Document
POWERUPS //!< \todo Document
};
/*!
* \todo Document
*/
explicit Entity(Manager& mManager) :
manager(mManager) { };
/*!
* \todo Document
*/
explicit Entity(Manager& mManager) :
manager(mManager) { };
void update() const; //!< Call each frame to update all components
void update(uint_fast16_t diffTime) const; //!< Call each frame to update all components
bool isActive() const { return this->active; } //!< \sa destroy()
//! Mark for destruction for Manager::refresh() and disables collision
//! \sa ColliderComponent
void destroy() {
this->active = false;
if (this->hasComponent<ColliderComponent>()) {
this->getComponent<ColliderComponent>().removeCollision();
}
}
bool isActive() const { return this->active; } //!< \sa destroy()
//! Mark for destruction for Manager::refresh() and disables collision
//! \sa ColliderComponent
void destroy() {
this->active = false;
if (this->hasComponent<ColliderComponent>()) {
this->getComponent<ColliderComponent>().removeCollision();
}
}
bool hasGroup(Group mGroup); //!< \sa GroupLabel
void addGroup(Group mGroup); //!< \sa GroupLabel
void delGroup(Group mGroup); //!< \sa GroupLabel
//! \returns bitset with true on position GroupLabel if the entity belongs to group
//! \sa GroupLabel
std::bitset<MAX_GROUPS> getGroupBitSet();
bool hasGroup(Group mGroup); //!< \sa GroupLabel
void addGroup(Group mGroup); //!< \sa GroupLabel
void delGroup(Group mGroup); //!< \sa GroupLabel
//! \returns bitset with true on position GroupLabel if the entity belongs to group
//! \sa GroupLabel
std::bitset<MAX_GROUPS> getGroupBitSet();
//! \sa Manager
Manager& getManager() { return manager; };
//! \sa Manager
Manager& getManager() { return manager; };
template <typename T> bool hasComponent() const //! \sa Component
{
return componentBitSet[getComponentTypeID<T>()];
}
template <typename T> bool hasComponent() const //! \sa Component
{
return componentBitSet[getComponentTypeID<T>()];
}
//! \brief Adds specified type as component and calls Component::init()
//! \param mArgs Constructor arguments of component
template <typename T, typename...TArgs> T& addComponent(TArgs&&...mArgs)
{
T* c(new T(std::forward<TArgs>(mArgs)...));
c->entity = this;
std::unique_ptr<Component> uPtr{ c };
this->components.emplace_back(std::move(uPtr));
//! \brief Adds specified type as component and calls Component::init()
//! \param mArgs Constructor arguments of component
template <typename T, typename...TArgs> T& addComponent(TArgs&&...mArgs)
{
T* c(new T(std::forward<TArgs>(mArgs)...));
c->entity = this;
std::shared_ptr<Component> uPtr{ c };
this->components.at(getComponentTypeID<T>()) = std::move(uPtr);
componentArray[getComponentTypeID<T>()] = c;
componentBitSet[getComponentTypeID<T>()] = true;
componentArray[getComponentTypeID<T>()] = c;
componentBitSet[getComponentTypeID<T>()] = true;
c->init();
return *c;
};
template <typename T> T& getComponent() const //! \returns Component of type T
{
auto ptr(componentArray[getComponentTypeID<T>()]);
return *static_cast<T*>(ptr);
}
c->init();
return *c;
};
template <typename T> T& getComponent() const //!< \todo: rewrite to use optionals
{
auto ptr(componentArray[getComponentTypeID<T>()]);
return *static_cast<T*>(ptr);
}
template <typename T> std::shared_ptr<T> getComponentAsPointer() const
{
return std::static_pointer_cast<T>(components.at(getComponentTypeID<T>()));
}
private:
Manager& manager;
bool active = true;
std::vector<std::unique_ptr<Component>> components;
Manager& manager;
bool active = true;
std::array<std::shared_ptr<Component>, MAX_COMPONENTS> components;
ComponentArray componentArray = {};
ComponentBitSet componentBitSet;
GroupBitSet groupBitSet;
ComponentArray componentArray = {};
ComponentBitSet componentBitSet;
GroupBitSet groupBitSet;
};

21
include/EventManager.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include <initializer_list>
#include <map>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
typedef std::function<SDL_AppResult(SDL_EventType, SDL_Event* const)> EventListener;
class EventManager {
public:
EventManager();
void registerListener(EventListener listener, std::initializer_list<Uint32> eventTypes);
SDL_AppResult handleEvent(SDL_Event* const event);
private:
std::map<Uint32, std::vector<EventListener>> eventListeners = std::map<Uint32, std::vector<EventListener>>();
};

View File

@ -8,7 +8,17 @@ public:
virtual ~Game() {}
virtual void init() = 0;
virtual void update() = 0;
virtual void update(uint_fast16_t diffTime) = 0;
/*!
* \brief Sets the path for a custom config file.
*
* Virtual function to be overwritten in the implementation to return the path of a custom config JSON file.
* \sa Layout of the config file is shown in ConfigLoader
*
* \return std::optional<std::string>
*/
virtual std::optional<std::string> setConfigFilePath() {return std::nullopt;}
GameInternal* gameInternal; //!< \deprecated
};

View File

@ -1,19 +1,26 @@
#pragma once
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <cstdint>
#include <functional>
#include <vector>
#include "EventManager.h"
#include "InteractionManager.h"
#include "Manager.h"
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
#include "Vector2D.h"
#include "Entity.h"
#include "InputManager.h"
#include "RenderManager.h"
#include "ConfigLoader.h"
#include "PickupManager.h"
typedef std::function<void()> gamefunction;
class AssetManager;
class CollisionHandler;
class TextureManager;
class SoundManager;
@ -23,44 +30,51 @@ class Game;
class GameInternal
{
public:
GameInternal();
~GameInternal();
GameInternal();
~GameInternal();
void init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen);
SDL_AppResult init();
void handleEvents();
void update();
void render();
void clean();
bool isRunning() const;
void setRunning(bool running); // TODO: should be private/not accesible for game dev
void stopGame();
SDL_AppResult handleEvent(SDL_Event* event);
void update(Uint64 frameTime);
void render();
void clean();
bool isRunning() const;
void setRunning(bool running); // TODO: should be private/not accesible for game dev
void stopGame();
/* static */ SDL_Renderer* renderer = nullptr;
/* static */ SDL_Event event;
/* static */ CollisionHandler* collisionHandler;
/* static */ AssetManager* assets;
/* static */ PickupManager* pickupManager;
/* static */ TextureManager* textureManager;
/* static */ SoundManager* soundManager;
/* static */ InputManager* inputManager;
RenderManager* renderManager;
EventManager* eventManager;
InteractionManager* interactionManager;
Manager manager;
RenderManager renderManager;
Map* map; // game specific, might not be needed for all types of games
std::vector<Entity*>& tiles;
std::vector<Entity*>& players;
std::vector<Entity*>& projectiles;
std::vector<Entity*>& hearts;
std::vector<Entity*>& powerups;
// end moved globals
ConfigLoader* config;
std::vector<Entity*>& tiles;
std::vector<Entity*>& players;
std::vector<Entity*>& projectiles;
std::vector<Entity*>& hearts;
std::vector<Entity*>& powerups;
// end moved globals
void refreshPlayers();
private:
Game* gameInstance;
Game* gameInstance;
int counter = 0;
bool running = true;
SDL_Window* window;
int counter = 0;
bool running = true;
SDL_Window* window;
Uint64 lastFrameTime = 0;
};

View File

@ -1,5 +1,5 @@
#pragma once
#include <SDL.h>
#include <SDL3/SDL.h>
#include <map>
#include "Component.h"
@ -94,12 +94,12 @@ public:
~InputComponent();
void init() override;
void update() override;
void update(uint_fast16_t diffTime) override;
bool isKeyDown(Key key);
private:
const Uint8* m_keyStates;
const bool* m_keyStates;
SDL_Scancode mapKeyToSDL(Key key);
std::map<Key, SDL_Scancode> m_keyMappings;
void InitKeyMappings();

139
include/InputManager.h Normal file
View File

@ -0,0 +1,139 @@
#pragma once
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL.h>
#include <map>
#include <string>
#include <functional>
#include <vector>
#include <iostream>
class InputManager {
public:
enum class EventType {
KeyDown,
KeyUp
};
enum class Key
{
UP,
DOWN,
LEFT,
RIGHT,
SPACE,
ENTER,
ESCAPE,
TAB,
BACKSPACE,
DELETE,
HOME,
END,
PAGE_UP,
PAGE_DOWN,
INSERT,
CAPS_LOCK,
LEFT_SHIFT,
RIGHT_SHIFT,
LEFT_CTRL,
RIGHT_CTRL,
LEFT_ALT,
RIGHT_ALT,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
NUM_0,
NUM_1,
NUM_2,
NUM_3,
NUM_4,
NUM_5,
NUM_6,
NUM_7,
NUM_8,
NUM_9,
LEFT_BRACKET,
RIGHT_BRACKET,
SEMICOLON,
APOSTROPHE,
COMMA,
PERIOD,
SLASH,
BACKSLASH,
GRAVE
};
struct InputAction {
std::string name;
std::vector<Key> bindings;
std::function<void(bool)> callback;
};
InputManager();
~InputManager();
void init(); // see if necessary
void processEvents();
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);
std::string getActiveContext() const;
//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);
//std::vector<Key> getBindings(const std::string& actionName, const std::string& context) const;
std::vector<InputAction*> getActionsByKey(const Key key) const;
SDL_AppResult handleEvent(SDL_EventType type, SDL_Event* const event);
void initKeyMap();
private:
// TODO: flesh this out to avoid loops in process actions
// additionally to actionsByContext, not instead
std::map<std::string, std::map<Key, std::vector<InputAction*>>> actionsByContextAndKey;
std::map<Key, SDL_Scancode> keyMap;
std::string activeContext;
};
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);
std::ostream& operator<<(std::ostream& os, const std::vector<InputManager::InputAction>& actions);
std::ostream& operator<<(std::ostream& os, const std::vector<InputManager::InputAction*>& actions);

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

@ -0,0 +1,28 @@
#pragma once
#include "Entity.h"
#include "InteractionListener.h"
#include "InteractionManager.h"
#include "Vector2D.h"
#include <cstdint>
#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 {
/// 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;
/// 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>();
/// Coordinates from which to base targeting on. Is required if strategy is not set to 0 (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

@ -0,0 +1,17 @@
#pragma once
#include "Vector2D.h"
#include <memory>
class InteractionListener {
public:
InteractionListener() { };
virtual ~InteractionListener() { };
virtual void interact(void* actor, void* data) = 0;
virtual std::shared_ptr<Vector2D> getPosition() // required for targeting strategy, return null to only allow explicit targeting
{
return nullptr;
}
};

View File

@ -0,0 +1,35 @@
#pragma once
#include "InteractionListener.h"
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#include <ranges>
// TODO: ranges concept to avoid to<vector> in cpp
typedef std::function<std::shared_ptr<InteractionListener>(Vector2D*, std::vector<std::shared_ptr<InteractionListener>>)> TargetingFunc;
class InteractionManager {
public:
InteractionManager();
InteractionManager (const InteractionManager&) = delete;
InteractionManager& operator= (const InteractionManager&) = delete;
enum class TargetingStrategy : uint8_t {
none = 0,
closest,
manhattenDistance
};
SDL_AppResult handleInteract(SDL_EventType type, SDL_Event* const event);
void registerListener(std::weak_ptr<InteractionListener> listener);
uint8_t registerTargetingFunc(TargetingFunc func);
private:
std::vector<std::weak_ptr<InteractionListener>> listeners;
std::array<TargetingFunc, 256> targetingFuncs;
};

View File

@ -24,7 +24,7 @@ class Manager
public:
Manager(GameInternal* game) : game(game) {};
void update(); //!< \sa Entity::update()
void update(uint_fast16_t diffTime); //!< \sa Entity::update()
//! Disables all functionality of entities marked for destruction
//! \sa Entity::destroy()
void refresh();

View File

@ -1,36 +1,52 @@
#pragma once
#include <tmxlite/Types.hpp>
#include <map>
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include <tmxlite/Map.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/Types.hpp>
class GameInternal;
class Map
{
public:
Map() = default;
~Map() = default;
/*!
*
* \brief
* This loads a map
*
* \param path The path to the map file
* \return Boolean for success
*
*/
[[deprecated("ID based text files are not supported anymore, use .txm maps instead")]]
static void loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict /* backreference */);
[[deprecated]]
static void addTile(unsigned long id, int x, int y, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict);
/*!
* \brief Loads a .tmx map
* \details Loads a `.tmx` file and extracts all relevant data. Any entities (including tiles) are only spawned once
* \param path Path to the `.tmx` map file
*
* \sa Map::generateTiles()
*/
static void loadMapTmx(const char* path);
Map(const char* path);
void generateTiles(); //!< Generates the map based on the loaded definition
private:
static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath);
};
// struct required for initialisation
struct MapData {
const std::vector<tmx::Tileset>* tileSets;
const std::vector<tmx::Layer::Ptr>* mapLayers;
const tmx::Vector2u* mapSize;
const tmx::Vector2u* mapTileSize;
const std::vector<std::string>* texturePaths;
};
struct TileSetData {
std::string texturePath{};
tmx::Vector2f textureSize;
uint32_t tileCount{};
tmx::Vector2u tileCount2D;
uint32_t firstGID{};
};
tmx::Map map;
Map::MapData mapData;
std::vector<std::function<void()>> tileConstructors;
void loadTileLayer(const tmx::TileLayer& layer);
static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision);
template<typename T>
static std::optional<T> getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName) { return std::nullopt; };
};

20
include/PickupComponent.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <functional>
#include "Component.h"
class PickupComponent : public Component
{
public:
/**
* @brief Construct a new Powerup Component object
* @param func The function to be called when the powerup is picked up
*/
PickupComponent(std::function<void (Entity*)> func);
~PickupComponent() {};
void update(uint_fast16_t diffTime) override;
private:
std::function<void (Entity*)> pickupFunc;
};

28
include/PickupManager.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <SDL3/SDL_render.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <map>
#include <string>
#include <functional>
#include "Entity.h"
#include "SoundEffects.h"
class Vector2D;
class Manager;
class PickupManager
{
public:
PickupManager(Manager* manager);
~PickupManager();
void createPowerup(Vector2D pos, std::function<void (Entity*)> pickupFunc, Textures texture);
Vector2D calculateSpawnPosition();
private:
Manager* man;
};

View File

@ -1,16 +0,0 @@
#pragma once
#include <functional>
#include "Component.h"
class PowerupComponent : public Component
{
public:
PowerupComponent(std::function<void (Entity*)> func);
~PowerupComponent() {};
void update() override;
private:
std::function<void (Entity*)> pickupFunc;
};

View File

@ -3,6 +3,7 @@
#include "Component.h"
#include "Vector2D.h"
#include "Constants.h"
#include "SoundEffects.h"
class TransformComponent;
@ -11,21 +12,23 @@ class ProjectileComponent : public Component
//can maybe be split in separate .cpp file
public:
ProjectileComponent(int range, int speed, Vector2D direction, Entity* owner)
: range(range), speed(speed), direction(direction), owner(owner) {}
ProjectileComponent(int range, int speed, Vector2D direction, Entity* owner, SoundEffects soundEffect)
: range(range), speed(speed), direction(direction), owner(owner), soundEffect(soundEffect) {}
~ProjectileComponent() {}
void init() override;
void update() override;
void update(uint_fast16_t diffTime) override;
private:
TransformComponent* transformComponent;
int range = 0;
int speed = 0;
int distance = 0;
float speed = 0;
float distance = 0;
Entity* owner = nullptr;
Vector2D direction;
SoundEffects soundEffect;
};

View File

@ -7,7 +7,7 @@ class RenderObject
public:
virtual void draw() = 0;
RenderObject(int zIndex, RenderManager& renderManager);
RenderObject(int zIndex, RenderManager* renderManager);
~RenderObject();
int getZIndex() { return this->zIndex; };
@ -23,5 +23,5 @@ private:
int zIndex = 0;
protected:
RenderManager& renderManager;
RenderManager* renderManager;
};

3
include/SoundEffects.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
enum class SoundEffects;

View File

@ -1,11 +1,13 @@
#pragma once
#include <SDL_mixer.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <map>
#include <vector>
#include "ECS.h"
#include "TextureManager.h"
#include "BackgroundMusic.h"
#include "SoundEffects.h"
class GameInternal;
@ -17,8 +19,17 @@ class GameInternal;
*/
class SoundManager
{
public:
SoundManager() {}
SoundManager() {
if (this_instance == nullptr) {
this_instance = this;
}
else {
throw std::runtime_error("SoundManager instance already exists!");
}
}
~SoundManager() {
for (auto& it : this->sound_cache) {
Mix_FreeChunk(it.second);
@ -32,35 +43,32 @@ class SoundManager
SoundManager(SoundManager const&) = delete;
void operator=(SoundManager const&) = delete;
std::map<const char*, Mix_Music*> music_cache;
std::map<const char*, Mix_Chunk*> sound_cache;
/*!
/*
* \brief Loads music from a file (mp3)
* \returns a pointer to Mix_Music
* \sa AssetManager::AddMusic(std::string id, const char* path)
*/
Mix_Music* loadMusic(const char* fileName);
/*!
* \brief Loads sound effects from a file (wav)
* \returns a pointer to Mix_Chunk
* \sa AssetManager::AddSound(std::string id, const char* path)
*/
Mix_Chunk* loadSound(const char* fileName);
Mix_Chunk* loadSound(const char* fileName);
*/
/*!
* \brief Handles playing of sound effects
*
* Handles if sounds can overlap, how often they can loop, as well as the volume at which the specified sound effect should play
* and on which channel the soundeffect should play.
*/
static void playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel);
static void playSound(SoundEffects sound, bool canOverlap, int loops, int volume, int channel);
/*!
* \brief Handles playing of music
*
* Handles how often track can loop, as well as the volume at which the specified track should play and if it fades in.
*/
static void playMusic(GameInternal* game, std::string sound, int loops, int volume, int ms);
static void playMusic(BackgroundMusic sound, int loops, int volume, int milliseconds);
static void setSoundVolume(int volume, int channel); //!< Volume handling for sound effects (either all or on a specific channel)
static void setMusicVolume(int volume); //!< Volume handling for music track
@ -73,5 +81,29 @@ class SoundManager
static void fadeOutMusic(int ms); //!< Handles fading out a music track
/*!
* \brief Initializes sound-effects and adds them to a cache
*
*/
static void addSoundEffects(const std::map<SoundEffects, const char*> &effects);
/*!
* \brief Initializes background-music and adds them to a cache
*
*/
static void addBackgroundMusic(const std::map<BackgroundMusic, const char*> &backgroundMusic);
static SoundManager* getInstance() {
return this_instance;
}
private:
std::map<BackgroundMusic, Mix_Music*> music_cache;
std::map<SoundEffects, Mix_Chunk*> sound_cache;
static SoundManager* this_instance;
static void addSingleBackgroundMusic(BackgroundMusic backgroundMusic, const char* path);
static void addSingleSoundEffect(SoundEffects soundEffect, const char* path);
};

View File

@ -1,10 +1,11 @@
#pragma once
#include <map>
#include <SDL_render.h>
#include <SDL3/SDL_render.h>
#include <memory>
#include <string>
#include "Textures.h"
#include "AnimationHandler.h"
#include "Component.h"
#include "Direction.h"
@ -22,9 +23,9 @@ public:
private:
TransformComponent* transform;
SDL_Texture* texture;
SDL_Rect srcRect, destRect;
SDL_FRect srcRect, destRect;
const char* texturePath;
Textures textureEnum;
bool animated = false;
uint8_t frames = 0;
@ -34,21 +35,32 @@ private:
int textureXOffset;
int textureYOffset;
//there should be a better solution as this variable is only used for the loading of the tmx map
//TODO: improve this in the future and also remove it from the scope of the developer
const char* path; //!< empty string if texture has a texture enum value, otherwise the path of the texture
public:
SpriteComponent(const char* path, int zIndex);
//debug
Textures getTexture() { return this->textureEnum; }
SpriteComponent(Textures texture, int zIndex);
SpriteComponent(Textures texture, int xOffset, int yOffset, int zIndex);
SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex);
SpriteComponent(
const char* path,
Textures texture,
bool isAnimated,
std::map<std::string, std::unique_ptr<Animation>>* animationList,
std::string defaultAnimation,
int zIndex);
~SpriteComponent();
void setTexture(const char* path);
void setTexture(Textures texture);
void setMapTileTexture(const char* path);
void init() override;
void update() override;
void update(uint_fast16_t diffTime) override;
void draw() override;
void playAnimation(std::string type);
void setDirection(Direction direction);

View File

@ -3,11 +3,15 @@
#include "Constants.h"
#include <cstdint>
#include <array>
#include <functional>
enum class Stats
{
MOVEMENT_SPEED,
ATTACK_SPEED
/**
* @brief Struct to hold the duration, reset function and start time of a stat effect
*/
struct StatEffect {
uint32_t duration; //!< Duration of the effect in milliseconds
std::function<void()> resetFunction; //!< Function to reset the effect, will be called on expiry of duration
uint32_t startTime;
};
class StatEffectsComponent : public Component{
@ -16,13 +20,14 @@ public:
~StatEffectsComponent() {};
void init() override;
void update() override;
void modifyStatDur(Stats stat, int duration, int value);
void modifyStatValue(Stats stat, int modifier);
void resetStatValue(Stats stat);
void update(uint_fast16_t diffTime) override;
/**
* @brief Add a stat effect to the entity
* @param duration The duration of the effect in milliseconds
* @param resetFunction The function to reset the effect, will be called on expiry of duration
*/
void addEffect(uint32_t duration, std::function<void()> resetFunction);
private:
std::array<int, MAX_STATS> buffs = { 0 };
std::vector<StatEffect> effects = {};
};

View File

@ -1,11 +1,25 @@
#pragma once
#include "ECS.h"
#include <SDL_render.h>
#include "SDL3/SDL_surface.h"
#include <SDL3/SDL_render.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "Textures.h"
/*!
* \class TextureManager
* \brief A manager for loading, caching, and drawing textures.
*
* The `TextureManager` class is responsible for handling texture loading, caching,
* and rendering in the engine. It provides functions to add, load, and draw textures
* from files, as well as manage sprite sheets.
*
* \sa Textures are used to identify textures within the engine.
* It is expected that they are implemented within the games scope.
*/
class TextureManager
{
@ -15,13 +29,69 @@ class TextureManager
for (auto& it : this->texture_cache) {
SDL_DestroyTexture(it.second);
}
for (auto& it : this->mapTile_texture_cache) {
SDL_DestroyTexture(it.second);
}
}
std::map<std::string, SDL_Texture*> texture_cache;
/*!
* \brief Adds a single texture to the cache.
* \param texture The texture identifier.
* \param filePath The file path to the texture file.
* \throws std::runtime_error Is thrown if the texture could not be loaded correctly
*
* This function loads the texture from the specified file and stores it in
* a cache.
*/
void addSingleTexture(Textures texture, const char* filePath);
SDL_Texture* loadTexture(const char* fileName);
/*!
* \brief Adds multiple textures to the cache.
* \param textures A map of texture identifiers and corresponding file paths.
*
* This function iterates over the provided map of textures and loads each
* texture using `addSingleTexture`. It allows for several
* textures to be added at once.
*/
void addTextures(const std::map<Textures, const char*>& textures);
/*!
* \brief Loads a texture from the cache.
* \param texture The texture identifier.
* \return A pointer to the `SDL_Texture` if found, or `nullptr` if not found.
*
* This function looks up a texture within the cache and returns the
* corresponding `SDL_Texture*`. If the texture is not found, it logs an error
* message and returns `nullptr`.
*/
SDL_Texture* loadTexture(Textures texture);
static std::vector<SDL_Rect> splitSpriteSheet(SDL_Texture* spriteSheet, int width, int height, int spritesOnSheet);
static void draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect src, SDL_Rect dest, bool flipped = false);
static void draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect src, SDL_FRect dest, bool flipped = false);
void setScaleMode(SDL_ScaleMode scaleMode) { this->scaleMode = scaleMode; }
/*!
* \brief Loads a map tile texture from the file system and caches it.
* \param path The file path to the texture.
* \return `SDL_Texture*` representing the map tile.
* \throws std::runtime_error Is thrown if the texture could not be loaded correctly
*
* This function checks if the map tile texture is already cached. If not, it
* loads the texture from the file system and stores it in the cache.
*
* \todo should not be usable for the developer and only be accessed by the map class
*/
SDL_Texture* loadMapTileTexture(const char* path);
std::string getTexturePath(Textures texture) {
return this->texture_references.at(texture);
}
private:
SDL_ScaleMode scaleMode = SDL_SCALEMODE_NEAREST;
Manager* manager;
std::map<Textures, SDL_Texture*> texture_cache;
std::map<std::string, SDL_Texture*> mapTile_texture_cache;
std::map<Textures, std::string> texture_references;
};

14
include/Textures.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
/*!
* \class Textures
* \brief Forward declaration of the \c Textures enum class.
*
* The \c Textures enum class is intended to be implemented within the game scope.
* This allows for customized texture entries to be defined based on the specific needs of the project.
* The base declaration ensures that the enum class can be referenced and used consistently throughout
* the engine while leaving the details of the texture identifiers up to the implementation.
* \sa \ref TextureManager "TextureManager" for how the enum is used.
*/
enum class Textures;

View File

@ -1,10 +1,11 @@
#pragma once
#include <SDL.h>
#include <SDL3/SDL.h>
#include <string>
#include <map>
#include "Component.h"
#include "Textures.h"
class SpriteComponent;
class TransformComponent;
@ -17,17 +18,19 @@ public:
SDL_Rect tileRect;
int tileID;
const char* path;
Textures texture;
TileComponent() = default;
TileComponent(int x, int y, int w, int h, int id, const std::map<int, std::pair<std::string, bool>>* textureDict);
TileComponent(int x, int y, int w, int h, int id, const std::map<int, std::pair<Textures, bool>>* textureDict);
~TileComponent() = default;
void init() override;
bool hasCollision(){return this->collision;}
std::string getName(){return this->tileName;}
bool hasCollision() {
return this->collision;
}
private:
bool collision;
std::string tileName;
};

View File

@ -3,6 +3,7 @@
#include "Component.h"
#include "Vector2D.h"
#include "Constants.h"
#include "DataComponent.h"
class TransformComponent : public Component
{
@ -14,22 +15,14 @@ public:
int width = 32;
int scale = 1;
int getSpeed() { return speed + speedMod; };
void resetSpeedMod() { speedMod = 0; };
TransformComponent();
explicit TransformComponent(int scale);
TransformComponent(float x, float y);
TransformComponent(float x, float y, int scale);
TransformComponent(float x, float y, int w, int h, int scale);
explicit TransformComponent(int scale = 1);
TransformComponent(float x, float y, int scale = 1);
TransformComponent(float x, float y, int w, int h, int scale = 1);
void init() override;
/*! TODO: document usage of collision handler */
void update() override;
void update(uint_fast16_t diffTime) override;
void setPositionAfterCollision(Vector2D& positionChange);
void modifySpeed(int8_t modifier);
int getSpeed();
private:
int speed = 3;
int speedMod = 0;
};

7
include/VEGO_Event.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <SDL3/SDL_stdinc.h>
namespace vego {
extern Uint32 VEGO_Event_Interaction;
}

View File

@ -1,7 +1,7 @@
#pragma once
#include <SDL.h>
#include <SDL_rect.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_rect.h>
class Vector2D
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,112 +0,0 @@
#include "AssetManager.h"
#include "TextureManager.h"
#include "SoundManager.h"
#include "ProjectileComponent.h"
#include "GameInternal.h"
#include "TransformComponent.h"
#include "CollisionHandler.h"
#include "ColliderComponent.h"
#include "Constants.h"
#include "Entity.h"
#include "Vector2D.h"
#include "PowerupComponent.h"
#include <iostream>
#include <algorithm>
AssetManager::AssetManager(Manager* manager) : man(manager) {}
AssetManager::~AssetManager() {}
void AssetManager::addTexture(std::string id, const char* path) {
textures.emplace(id, this->man->getGame()->textureManager->loadTexture(path));
}
void AssetManager::addSoundEffect(std::string id, const char* path)
{
soundEffects.emplace(id, this->man->getGame()->soundManager->loadSound(path));
}
void AssetManager::addMusic(std::string id, const char* path)
{
music.emplace(id, this->man->getGame()->soundManager->loadMusic(path));
}
SDL_Texture* AssetManager::getTexture(std::string id) {
return textures.at(id);
}
Mix_Chunk* AssetManager::getSound(std::string id) {
return soundEffects.at(id);
}
Mix_Music* AssetManager::getMusic(std::string id)
{
return music.at(id);
}
void AssetManager::createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, int speed, const char* texturePath, Entity* owner) {
auto& projectile(man->addEntity());
projectile.addComponent<TransformComponent>(pos.x, pos.y, 32, 32, scale); //32x32 is standard size for objects
projectile.addComponent<SpriteComponent>(texturePath, 4);
projectile.addComponent<ProjectileComponent>(range, speed, velocity, owner);
projectile.addComponent<ColliderComponent>("projectile", 0.6f);
projectile.addGroup((size_t)Entity::GroupLabel::PROJECTILE);
}
void AssetManager::createPowerup(Vector2D pos, std::function<void (Entity*)> pickupFunc, std::string texturePath) {
auto& powerups(man->addEntity());
powerups.addComponent<TransformComponent>(pos.x, pos.y, 32, 32, 1); //32x32 is standard size for objects
try {
powerups.addComponent<SpriteComponent>(texturePath.c_str(), 3);
}
catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
powerups.addComponent<ColliderComponent>("powerup", 0.6f);
powerups.addComponent<PowerupComponent>(pickupFunc);
powerups.addGroup((size_t)Entity::GroupLabel::POWERUPS);
}
Vector2D AssetManager::calculateSpawnPosition(Vector2D size, Vector2D spawnArea)
{
Vector2D spawnPos = Vector2D(-1, -1);
for(int i = 0; i <= SPAWN_ATTEMPTS; i++)
{
SDL_Rect spawnRect = {
rand() % (int)(spawnArea.x - size.x),
rand() % (int)(spawnArea.y - size.y),
size.x,
size.y
};
std::vector<ColliderComponent*> colliders = this->man->getGame()->collisionHandler->getColliders({Entity::GroupLabel::MAPTILES});
bool conflict = std::any_of(colliders.begin(), colliders.end(),
[&](const auto& cc) {
return SDL_HasIntersection(&spawnRect, &cc->collider);} );
if(!conflict)
{
spawnPos = Vector2D(spawnRect.x, spawnRect.y);
break;
}
}
return spawnPos;
}
template <typename T>
T AssetManager::calculateRandomType(int amount)
{
T type = T(rand() % amount);
return type;
}

View File

@ -31,10 +31,10 @@ void ColliderComponent::init()
}
transform = &entity->getComponent<TransformComponent>();
this->update();
this->update(0);
}
void ColliderComponent::update()
void ColliderComponent::update(uint_fast16_t diffTime)
{
collider.x = transform->position.x - (transform->width - transform->width * transform->scale * this->hitboxScale) / 2;
collider.y = transform->position.y - (transform->width - transform->width * transform->scale * this->hitboxScale) / 2;

View File

@ -6,10 +6,11 @@
#include "Manager.h"
#include "Vector2D.h"
#include <SDL_rect.h>
#include <SDL3/SDL_rect.h>
#include <bitset>
#include <cstdio>
#include <memory>
#include <VEGO.h>
IntersectionBitSet CollisionHandler::getIntersection(Entity* entityA, Entity* entityB, Vector2D posModA, Vector2D posModB)
{
@ -26,7 +27,7 @@ IntersectionBitSet CollisionHandler::getIntersection(Entity* entityA, Entity* en
colliderB.x += posModB.x;
colliderB.y += posModB.y;
if (!SDL_HasIntersection(
if (!SDL_HasRectIntersection(
&colliderA,
&colliderB))
return std::bitset<DIRECTION_C>();
@ -66,20 +67,20 @@ IntersectionBitSet CollisionHandler::getIntersectionWithBounds(Entity* entity, V
// all 4 directions and both sides to allow checking for fully out of bounds
if (collider->x + posMod.x < 0 ||
collider->x + posMod.x > SCREEN_SIZE_WIDTH) {
collider->x + posMod.x > VEGO_Game().config->getFinalConfig().at("screen_width")) {
intersections.set((size_t) Direction::LEFT);
}
if (collider->x + collider->w + posMod.x < 0 ||
collider->x + collider->w + posMod.x > SCREEN_SIZE_WIDTH)
collider->x + collider->w + posMod.x > VEGO_Game().config->getFinalConfig().at("screen_width"))
intersections.set((size_t) Direction::RIGHT);
if (collider->y + posMod.y < 0 ||
collider->y + posMod.y > SCREEN_SIZE_HEIGHT)
collider->y + posMod.y > VEGO_Game().config->getFinalConfig().at("screen_height"))
intersections.set((size_t) Direction::UP);
if (collider->y + collider->h + posMod.y < 0 ||
collider->y + collider->h + posMod.y > SCREEN_SIZE_HEIGHT)
collider->y + collider->h + posMod.y > VEGO_Game().config->getFinalConfig().at("screen_height"))
intersections.set((size_t) Direction::DOWN);
return intersections;
@ -152,7 +153,7 @@ Entity* CollisionHandler::getAnyIntersection<Entity*>(
if (!entity->hasComponent<ColliderComponent>()) return nullptr;
for (auto& collider : getColliders(groupLabels, excludedEntities)) {
SDL_Rect rect = entity->getComponent<ColliderComponent>().collider + posMod;
if (SDL_HasIntersection(&rect, &collider->collider)) {
if (SDL_HasRectIntersection(&rect, &collider->collider)) {
return collider->entity;
}
}
@ -175,7 +176,7 @@ bool CollisionHandler::getAnyIntersection<bool>(
if (!entity->hasComponent<ColliderComponent>()) return false;
for (auto& collider : getColliders(groupLabels, excludedEntities)) {
SDL_Rect rect = entity->getComponent<ColliderComponent>().collider + posMod;
if (SDL_HasIntersection(&rect, &collider->collider)) {
if (SDL_HasRectIntersection(&rect, &collider->collider)) {
return true;
}
}

51
src/ConfigLoader.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "ConfigLoader.h"
#include <fstream>
ConfigLoader::ConfigLoader() {}
ConfigLoader::~ConfigLoader() {}
void ConfigLoader::init() {
//TODO: look into adaptive paths for better handling as this requires the implemented game
// to have ./engine in the root folder (very low prio)
const json baseConfig = loadConfigFromJSON("./engine/config.json");
if (!customConfigPath.has_value()) {
finalConfig = baseConfig;
return;
}
finalConfig = mergeConfigs(baseConfig, loadConfigFromJSON(customConfigPath.value()));
}
json ConfigLoader::loadConfigFromJSON(const std::string& path) {
std::ifstream config_file(path);
json config;
if (!config_file.is_open()) {
throw std::runtime_error(std::string("Could not load config file at: " + path));
}
config_file >> config;
return config;
}
void ConfigLoader::setCustomConfig(const std::optional<std::string>& path) {
customConfigPath = path;
}
json ConfigLoader::mergeConfigs(json baseConfig, json customConfig) {
for (auto& entry : customConfig.items()) {
baseConfig[entry.key()] = entry.value();
}
return baseConfig;
}
json ConfigLoader::getFinalConfig() {
return finalConfig;
}

View File

@ -4,9 +4,11 @@
#include "Component.h"
#include <cstddef>
void Entity::update() const
void Entity::update(uint_fast16_t diffTime) const
{
for (auto const& c : components) c->update();
for (auto const& c : components)
if (c)
c->update(diffTime);
}
bool Entity::hasGroup(Group mGroup)

44
src/EventManager.cpp Normal file
View File

@ -0,0 +1,44 @@
#include "EventManager.h"
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_stdinc.h"
#include "VEGO_Event.h"
#include <algorithm>
#include <functional>
#include <ranges>
#include <vector>
Uint32 vego::VEGO_Event_Interaction;
EventManager::EventManager()
{
/// \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
}
void EventManager::registerListener(EventListener listener, std::initializer_list<Uint32> eventTypes)
{
std::ranges::for_each(eventTypes.begin(), eventTypes.end(), [this, &listener](const Uint32& eventType) {
if (!this->eventListeners.contains(eventType)) {
this->eventListeners.insert({eventType, std::vector<EventListener>()});
}
this->eventListeners.at(eventType).emplace_back(listener);
});
}
SDL_AppResult EventManager::handleEvent(SDL_Event* event)
{
SDL_EventType type = (SDL_EventType) event->type;
if (this->eventListeners.contains(type)) {
std::vector<SDL_AppResult> results;
for (auto& listener : this->eventListeners.at(type)) {
results.emplace_back(listener((SDL_EventType) event->type, event));
}
if (std::ranges::contains(results, SDL_APP_FAILURE))
return SDL_APP_FAILURE;
if (std::ranges::contains(results, SDL_APP_SUCCESS))
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}

View File

@ -1,159 +1,182 @@
#include "GameInternal.h"
#include <SDL_error.h>
#include "CollisionHandler.h"
#include "AssetManager.h"
#include "EventManager.h"
#include "InputManager.h"
#include "InteractionManager.h"
#include "RenderManager.h"
#include "SDL_mixer.h"
#include <SDL3_mixer/SDL_mixer.h>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
#include "SDL3/SDL_oldnames.h"
#include "SoundManager.h"
#include "TileComponent.h"
#include "Direction.h"
#include "Entity.h"
#include "HealthComponent.h"
#include "Map.h"
#include "TextureManager.h"
#include "StatEffectsComponent.h"
#include "Constants.h"
#include "Game.h"
#include "GameFactory.h"
#include <VEGO.h>
#include <VEGO_Event.h>
#include <functional>
#include "ConfigLoader.h"
GameInternal::GameInternal() :
manager(this),
renderManager(),
tiles(manager.getGroup((size_t)Entity::GroupLabel::MAPTILES)),
players(manager.getGroup((size_t)Entity::GroupLabel::PLAYERS)),
projectiles(manager.getGroup((size_t)Entity::GroupLabel::PROJECTILE)),
hearts(manager.getGroup((size_t)Entity::GroupLabel::HEARTS)),
powerups(manager.getGroup((size_t)Entity::GroupLabel::POWERUPS))
manager(this),
tiles(manager.getGroup((size_t)Entity::GroupLabel::MAPTILES)),
players(manager.getGroup((size_t)Entity::GroupLabel::PLAYERS)),
projectiles(manager.getGroup((size_t)Entity::GroupLabel::PROJECTILE)),
hearts(manager.getGroup((size_t)Entity::GroupLabel::HEARTS)),
powerups(manager.getGroup((size_t)Entity::GroupLabel::POWERUPS))
{};
GameInternal::~GameInternal() = default;
void GameInternal::init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen)
SDL_AppResult GameInternal::init()
{
GameInternal::assets = new AssetManager(&manager);
GameInternal::textureManager = new TextureManager(&manager);
GameInternal::soundManager = new SoundManager();
GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer?
int flags = 0;
if (fullscreen)
{
flags = SDL_WINDOW_FULLSCREEN;
}
config = new ConfigLoader();
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
std::cout << "ERROR. Subsystem couldnt be initialized! " << SDL_GetError() << std::endl;
SDL_ClearError();
return;
}
this->gameInstance = GameFactory::instance().create(this);
config->setCustomConfig(this->gameInstance->setConfigFilePath());
config->init();
if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) {
std::cout << "ERROR. Subsystem couldnt be initialized!" << std::endl;
return;
}
json finalConfig = config->getFinalConfig();
GameInternal::pickupManager = new PickupManager(&manager);
GameInternal::textureManager = new TextureManager(&manager);
GameInternal::soundManager = new SoundManager();
GameInternal::collisionHandler = new CollisionHandler(manager); // why does this use a referrence, but AssetManager a pointer?
GameInternal::inputManager = new InputManager();
GameInternal::inputManager->initKeyMap();
window = SDL_CreateWindow(title, xpos, ypos, width, height, flags);
if (!window)
{
std::cout << "ERROR: Window couldnt be created! " << SDL_GetError() << std::endl;
SDL_ClearError();
return;
}
GameInternal::renderManager = new RenderManager();
GameInternal::eventManager = new EventManager();
GameInternal::interactionManager = new InteractionManager();
this->eventManager->registerListener(std::bind_front(&InputManager::handleEvent, this->inputManager), { SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP });
this->eventManager->registerListener(std::bind_front(&InteractionManager::handleInteract, VEGO_Game().interactionManager), { vego::VEGO_Event_Interaction });
int flags = 0;
if (finalConfig.at("fullscreen"))
{
flags = SDL_WINDOW_FULLSCREEN;
}
if (!SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO))
{
std::cout << "ERROR. Subsystem couldnt be initialized! " << SDL_GetError() << std::endl;
SDL_ClearError();
return SDL_APP_FAILURE;
}
if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) {
std::cout << "ERROR. Subsystem couldnt be initialized!" << std::endl;
return SDL_APP_FAILURE;
}
window = SDL_CreateWindow(finalConfig.at("title").get<std::string>().c_str(),
finalConfig.at("screen_width"), finalConfig.at("screen_height"), flags);
if (!window)
{
std::cout << "ERROR: Window couldnt be created! " << SDL_GetError() << std::endl;
SDL_ClearError();
return SDL_APP_FAILURE;
}
// bad
SDL_Surface* icon;
if((icon = SDL_LoadBMP("assets/iconImage.bmp")))
if((icon = SDL_LoadBMP(finalConfig.at("icon").get<std::string>().c_str())))
{
SDL_SetWindowIcon(window, icon);
SDL_SetWindowIcon(window, icon);
}
SDL_SetWindowIcon(window, icon);
renderer = SDL_CreateRenderer(window, -1, 0);
if (!renderer)
{
std::cout << "ERROR: Renderer couldnt be created! " << SDL_GetError() << std::endl;
SDL_ClearError();
return;
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
renderer = SDL_CreateRenderer(window, NULL);
if (!renderer)
{
std::cout << "ERROR: Renderer couldnt be created! " << SDL_GetError() << std::endl;
SDL_ClearError();
return SDL_APP_FAILURE;
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0)
{
std::cout << "ERROR: Mixer couldnt be initialized! " << SDL_GetError() << std::endl;
SDL_ClearError();
return;
}
if (!Mix_OpenAudio(0, NULL))
{
std::cout << "ERROR: Mixer couldnt be initialized! " << SDL_GetError() << std::endl;
SDL_ClearError();
return SDL_APP_FAILURE;
}
Mix_Volume(-1, MIX_MAX_VOLUME);
Mix_AllocateChannels(16);
Mix_Volume(-1, MIX_MAX_VOLUME);
Mix_AllocateChannels(16);
map = new Map();
// loading sounds
// assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav");
// assets->addSoundEffect("steps", "assets/sound/steps.wav");
// loading sounds
// assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav");
// assets->addSoundEffect("steps", "assets/sound/steps.wav");
// loading music
// assets->addMusic("background_music", "assets/sound/background_music.mp3");
// loading music
// assets->addMusic("background_music", "assets/sound/background_music.mp3");
this->gameInstance->init();
this->gameInstance = GameFactory::instance().create(this);
this->gameInstance->init();
return SDL_APP_CONTINUE;
}
void GameInternal::handleEvents()
{
SDL_PollEvent(&event);
SDL_AppResult GameInternal::handleEvent(SDL_Event* event) {
SDL_AppResult result = this->eventManager->handleEvent(event);
switch (event.type)
{
case SDL_QUIT: this->setRunning(false);
break;
if (event->type == SDL_EVENT_QUIT) {
this->clean();
return result == SDL_APP_FAILURE ? SDL_APP_FAILURE : SDL_APP_SUCCESS;
}
default:
break;
}
return result;
}
void GameInternal::update()
void GameInternal::update(Uint64 frameTime)
{
manager.refresh();
manager.update();
manager.refresh();
this->gameInstance->update(); // TODO: this might have to be split up into two update functions, before and after manager...
uint_fast16_t diffTime = frameTime - this->lastFrameTime;
manager.update(diffTime);
this->gameInstance->update(diffTime); // TODO: this might have to be split up into two update functions, before and after manager...
this->lastFrameTime = frameTime;
}
void GameInternal::render()
{
SDL_RenderClear(renderer);
this->renderManager.renderAll();
SDL_RenderPresent(renderer);
SDL_RenderClear(renderer);
this->renderManager->renderAll();
SDL_RenderPresent(renderer);
}
void GameInternal::clean()
{
delete(textureManager);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
std::cout << "Game Cleaned!" << std::endl;
delete(textureManager);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
std::cout << "Game Cleaned!" << std::endl;
}
bool GameInternal::isRunning() const
{
return running;
return running;
}
void GameInternal::setRunning(bool running) //TODO: might be depracted
{
this->running = running;
this->running = running;
}
void GameInternal::stopGame()
{
this->running = false;
this->running = false;
}

View File

@ -10,7 +10,7 @@ InputComponent::~InputComponent() = default;
void InputComponent::init(){}
void InputComponent::update()
void InputComponent::update(uint_fast16_t diffTime)
{
SDL_PumpEvents();
}

303
src/InputManager.cpp Normal file
View File

@ -0,0 +1,303 @@
#include "InputManager.h"
#include "InteractionEventdataStruct.h"
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_init.h"
#include <iostream>
#include "SDL3/SDL_stdinc.h"
#include "VEGO.h"
#include "VEGO_Event.h"
std::ostream& operator<<(std::ostream& os, InputManager::Key key) {
static const std::unordered_map<InputManager::Key, std::string> keyToString {
{InputManager::Key::UP, "UP"},
{InputManager::Key::DOWN, "DOWN"},
{InputManager::Key::LEFT, "LEFT"},
{InputManager::Key::RIGHT, "RIGHT"},
{InputManager::Key::SPACE, "SPACE"},
{InputManager::Key::ENTER, "ENTER"},
{InputManager::Key::ESCAPE, "ESCAPE"},
{InputManager::Key::TAB, "TAB"},
{InputManager::Key::BACKSPACE, "BACKSPACE"},
{InputManager::Key::DELETE, "DELETE"},
{InputManager::Key::HOME, "HOME"},
{InputManager::Key::END, "END"},
{InputManager::Key::PAGE_UP, "PAGE_UP"},
{InputManager::Key::PAGE_DOWN, "PAGE_DOWN"},
{InputManager::Key::INSERT, "INSERT"},
{InputManager::Key::CAPS_LOCK, "CAPS_LOCK"},
{InputManager::Key::LEFT_SHIFT, "LEFT_SHIFT"},
{InputManager::Key::RIGHT_SHIFT, "RIGHT_SHIFT"},
{InputManager::Key::LEFT_CTRL, "LEFT_CTRL"},
{InputManager::Key::RIGHT_CTRL, "RIGHT_CTRL"},
{InputManager::Key::LEFT_ALT, "LEFT_ALT"},
{InputManager::Key::RIGHT_ALT, "RIGHT_ALT"},
{InputManager::Key::F1, "F1"},
{InputManager::Key::F2, "F2"},
{InputManager::Key::F3, "F3"},
{InputManager::Key::F4, "F4"},
{InputManager::Key::F5, "F5"},
{InputManager::Key::F6, "F6"},
{InputManager::Key::F7, "F7"},
{InputManager::Key::F8, "F8"},
{InputManager::Key::F9, "F9"},
{InputManager::Key::F10, "F10"},
{InputManager::Key::F11, "F11"},
{InputManager::Key::F12, "F12"},
{InputManager::Key::A, "A"},
{InputManager::Key::B, "B"},
{InputManager::Key::C, "C"},
{InputManager::Key::D, "D"},
{InputManager::Key::E, "E"},
{InputManager::Key::F, "F"},
{InputManager::Key::G, "G"},
{InputManager::Key::H, "H"},
{InputManager::Key::I, "I"},
{InputManager::Key::J, "J"},
{InputManager::Key::K, "K"},
{InputManager::Key::L, "L"},
{InputManager::Key::M, "M"},
{InputManager::Key::N, "N"},
{InputManager::Key::O, "O"},
{InputManager::Key::P, "P"},
{InputManager::Key::Q, "Q"},
{InputManager::Key::R, "R"},
{InputManager::Key::S, "S"},
{InputManager::Key::T, "T"},
{InputManager::Key::U, "U"},
{InputManager::Key::V, "V"},
{InputManager::Key::W, "W"},
{InputManager::Key::X, "X"},
{InputManager::Key::Y, "Y"},
{InputManager::Key::Z, "Z"},
{InputManager::Key::NUM_0, "NUM_0"},
{InputManager::Key::NUM_1, "NUM_1"},
{InputManager::Key::NUM_2, "NUM_2"},
{InputManager::Key::NUM_3, "NUM_3"},
{InputManager::Key::NUM_4, "NUM_4"},
{InputManager::Key::NUM_5, "NUM_5"},
{InputManager::Key::NUM_6, "NUM_6"},
{InputManager::Key::NUM_7, "NUM_7"},
{InputManager::Key::NUM_8, "NUM_8"},
{InputManager::Key::NUM_9, "NUM_9"},
{InputManager::Key::LEFT_BRACKET, "LEFT_BRACKET"},
{InputManager::Key::RIGHT_BRACKET, "RIGHT_BRACKET"},
{InputManager::Key::SEMICOLON, "SEMICOLON"},
{InputManager::Key::APOSTROPHE, "APOSTROPHE"},
{InputManager::Key::COMMA, "COMMA"},
{InputManager::Key::PERIOD, "PERIOD"},
{InputManager::Key::SLASH, "SLASH"},
{InputManager::Key::BACKSLASH, "BACKSLASH"},
{InputManager::Key::GRAVE, "GRAVE"}
};
auto it = keyToString.find(key);
if (it != keyToString.end()) {
os << it->second;
} else {
os << "UNKNOWN_KEY";
}
return os;
}
std::ostream& operator<<(std::ostream& os, const InputManager::InputAction& action) {
os << action.name << " with binding(s): ";
for (auto& binding : action.bindings) {
os << binding << ", ";
}
return os;
}
std::ostream& operator<<(std::ostream& os, const InputManager::InputAction* action) {
if (action) {
os << *action;
} else {
os << "NULL_ACTION";
}
return os;
}
std::ostream& operator<<(std::ostream& os, const std::vector<InputManager::InputAction>& actions) {
os << "Actions: ";
if (actions.empty()) {
os << "None";
} else {
for (size_t i = 0; i < actions.size(); ++i) {
os << actions[i];
if (i < actions.size() - 1) {
os << " | ";
}
}
}
return os;
}
// TODO: find out why it doesnt work??
std::ostream& operator<<(std::ostream& os, const std::vector<InputManager::InputAction*>& actions) {
os << "Actions: ";
if (actions.empty()) {
os << "None";
} else {
for (size_t i = 0; i < actions.size(); ++i) {
if (actions[i]) {
os << *actions[i];
} else {
os << "NULL_ACTION";
}
if (i < actions.size() - 1) {
os << " | ";
}
}
}
return os;
}
// overloads end --------------------------------------------------------------------------------------
InputManager::InputManager() : activeContext("Default") {}
InputManager::~InputManager() {}
void InputManager::initKeyMap() {
keyMap = {
{Key::UP, SDL_SCANCODE_UP},
{Key::DOWN, SDL_SCANCODE_DOWN},
{Key::LEFT, SDL_SCANCODE_LEFT},
{Key::RIGHT, SDL_SCANCODE_RIGHT},
{Key::SPACE, SDL_SCANCODE_SPACE},
{Key::ENTER, SDL_SCANCODE_RETURN},
{Key::ESCAPE, SDL_SCANCODE_ESCAPE},
{Key::TAB, SDL_SCANCODE_TAB},
{Key::BACKSPACE, SDL_SCANCODE_BACKSPACE},
{Key::DELETE, SDL_SCANCODE_DELETE},
{Key::HOME, SDL_SCANCODE_HOME},
{Key::END, SDL_SCANCODE_END},
{Key::PAGE_UP, SDL_SCANCODE_PAGEUP},
{Key::PAGE_DOWN, SDL_SCANCODE_PAGEDOWN},
{Key::INSERT, SDL_SCANCODE_INSERT},
{Key::CAPS_LOCK, SDL_SCANCODE_CAPSLOCK},
{Key::LEFT_SHIFT, SDL_SCANCODE_LSHIFT},
{Key::RIGHT_SHIFT, SDL_SCANCODE_RSHIFT},
{Key::LEFT_CTRL, SDL_SCANCODE_LCTRL},
{Key::RIGHT_CTRL, SDL_SCANCODE_RCTRL},
{Key::LEFT_ALT, SDL_SCANCODE_LALT},
{Key::RIGHT_ALT, SDL_SCANCODE_RALT},
{Key::F1, SDL_SCANCODE_F1},
{Key::F2, SDL_SCANCODE_F2},
{Key::F3, SDL_SCANCODE_F3},
{Key::F4, SDL_SCANCODE_F4},
{Key::F5, SDL_SCANCODE_F5},
{Key::F6, SDL_SCANCODE_F6},
{Key::F7, SDL_SCANCODE_F7},
{Key::F8, SDL_SCANCODE_F8},
{Key::F9, SDL_SCANCODE_F9},
{Key::F10, SDL_SCANCODE_F10},
{Key::F11, SDL_SCANCODE_F11},
{Key::F12, SDL_SCANCODE_F12},
{Key::A, SDL_SCANCODE_A},
{Key::B, SDL_SCANCODE_B},
{Key::C, SDL_SCANCODE_C},
{Key::D, SDL_SCANCODE_D},
{Key::E, SDL_SCANCODE_E},
{Key::F, SDL_SCANCODE_F},
{Key::G, SDL_SCANCODE_G},
{Key::H, SDL_SCANCODE_H},
{Key::I, SDL_SCANCODE_I},
{Key::J, SDL_SCANCODE_J},
{Key::K, SDL_SCANCODE_K},
{Key::L, SDL_SCANCODE_L},
{Key::M, SDL_SCANCODE_M},
{Key::N, SDL_SCANCODE_N},
{Key::O, SDL_SCANCODE_O},
{Key::P, SDL_SCANCODE_P},
{Key::Q, SDL_SCANCODE_Q},
{Key::R, SDL_SCANCODE_R},
{Key::S, SDL_SCANCODE_S},
{Key::T, SDL_SCANCODE_T},
{Key::U, SDL_SCANCODE_U},
{Key::V, SDL_SCANCODE_V},
{Key::W, SDL_SCANCODE_W},
{Key::X, SDL_SCANCODE_X},
{Key::Y, SDL_SCANCODE_Y},
{Key::Z, SDL_SCANCODE_Z},
{Key::NUM_0, SDL_SCANCODE_0},
{Key::NUM_1, SDL_SCANCODE_1},
{Key::NUM_2, SDL_SCANCODE_2},
{Key::NUM_3, SDL_SCANCODE_3},
{Key::NUM_4, SDL_SCANCODE_4},
{Key::NUM_5, SDL_SCANCODE_5},
{Key::NUM_6, SDL_SCANCODE_6},
{Key::NUM_7, SDL_SCANCODE_7},
{Key::NUM_8, SDL_SCANCODE_8},
{Key::NUM_9, SDL_SCANCODE_9},
{Key::LEFT_BRACKET, SDL_SCANCODE_LEFTBRACKET},
{Key::RIGHT_BRACKET, SDL_SCANCODE_RIGHTBRACKET},
{Key::SEMICOLON, SDL_SCANCODE_SEMICOLON},
{Key::APOSTROPHE, SDL_SCANCODE_APOSTROPHE},
{Key::COMMA, SDL_SCANCODE_COMMA},
{Key::PERIOD, SDL_SCANCODE_PERIOD},
{Key::SLASH, SDL_SCANCODE_SLASH},
{Key::BACKSLASH, SDL_SCANCODE_BACKSLASH},
{Key::GRAVE, SDL_SCANCODE_GRAVE}
};
}
void InputManager::registerAction(const std::string& actionName, const std::vector<Key>& keys, std::function<void(bool)> callback, const std::string& context) {
InputAction* storedAction = new InputAction{actionName, keys, callback};
for (const auto& key : keys) {
actionsByContextAndKey[context][key].emplace_back(storedAction);
}
std::cout << "Registered action: " << storedAction << " in context: " << context << std::endl;
}
std::vector<InputManager::InputAction*> InputManager::getActionsByKey(const Key key) const {
std::vector<InputAction*> result;
for (const auto& [context, keyMap] : actionsByContextAndKey) {
auto it = keyMap.find(key);
if (it != keyMap.end()) {
result.insert(result.end(), it->second.begin(), it->second.end());
}
}
return result;
}
void InputManager::setActiveContext(const std::string& context) {
activeContext = context;
std::cout << "Active context set to: " << activeContext << std::endl;
}
std::string InputManager::getActiveContext() const {
return activeContext;
}
SDL_AppResult InputManager::handleEvent(SDL_EventType type, SDL_Event* const event) {
if (event->key.repeat) {
return SDL_APP_CONTINUE;
}
auto keyIt = std::ranges::find_if(keyMap, [&](const auto& pair)
{ return pair.second == event->key.scancode; });
if (keyIt != keyMap.end()) {
std::cout << "in != keymap.end" << std::endl;
Key pressedKey = keyIt->first;
auto keyActions = actionsByContextAndKey[activeContext];
auto it = keyActions.find(pressedKey);
if (it != keyActions.end()) {
for (auto& action : it->second) {
std::cout << "Action triggered: " << action->name << " in context: " << activeContext << std::endl;
action->callback(type == SDL_EVENT_KEY_UP);
}
}
}
return SDL_APP_CONTINUE;
}

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

@ -0,0 +1,90 @@
#include "InteractionManager.h"
#include "InteractionEventdataStruct.h"
#include "InteractionListener.h"
#include "SDL3/SDL_init.h"
#include "VEGO_Event.h"
#include "Vector2D.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <iterator>
#include <memory>
#include <ranges>
#include <type_traits>
#include <vector>
InteractionManager::InteractionManager()
{
this->targetingFuncs.fill(nullptr);
this->targetingFuncs.at(static_cast<std::underlying_type<InteractionManager::TargetingStrategy>::type>(InteractionManager::TargetingStrategy::closest)) = [](Vector2D* reference, std::vector<std::shared_ptr<InteractionListener>> input) {
return std::shared_ptr<InteractionListener>();
};
this->targetingFuncs.at(static_cast<std::underlying_type<InteractionManager::TargetingStrategy>::type>(InteractionManager::TargetingStrategy::closest)) = [](Vector2D* reference, std::vector<std::shared_ptr<InteractionListener>> input) {
auto min = std::ranges::min_element(input, [&reference](std::shared_ptr<InteractionListener>& a, std::shared_ptr<InteractionListener>& b) {
std::shared_ptr<Vector2D> coordA = a->getPosition();
std::shared_ptr<Vector2D> coordB = b->getPosition();
if (coordB == nullptr) return true;
if (coordA == nullptr) return false;
return std::sqrt(std::pow(coordA->x - reference->x, 2) + std::pow(coordA->y - reference->y, 2)) < std::sqrt(std::pow(coordB->x - reference->x, 2) + std::pow(coordB->y - reference->y, 2));
});
return min == std::ranges::end(input) ? nullptr : *min;
};
this->targetingFuncs.at(static_cast<std::underlying_type<InteractionManager::TargetingStrategy>::type>(InteractionManager::TargetingStrategy::manhattenDistance)) = [](Vector2D* reference, std::vector<std::shared_ptr<InteractionListener>> input) {
auto min = std::ranges::min_element(input, [&reference](std::shared_ptr<InteractionListener>& a, std::shared_ptr<InteractionListener>& b) {
std::shared_ptr<Vector2D> coordA = a->getPosition();
std::shared_ptr<Vector2D> coordB = b->getPosition();
if (coordB == nullptr) return true;
if (coordA == nullptr) return false;
return (std::abs(coordA->x - reference->x) + std::abs(coordA->y - reference->y)) < (std::abs(coordB->x - reference->x) + std::abs(coordB->y - reference->y));
});
return min == std::ranges::end(input) ? nullptr : *min;
};
}
SDL_AppResult InteractionManager::handleInteract(SDL_EventType type, SDL_Event* const event)
{
if (type != vego::VEGO_Event_Interaction) { // error handling
return SDL_APP_CONTINUE;
}
InteractionEventdataStruct* data = static_cast<InteractionEventdataStruct*>(event->user.data1);
std::shared_ptr<InteractionListener> listener = data->target.lock();
if (data->strategy != static_cast<std::underlying_type<InteractionManager::TargetingStrategy>::type>(InteractionManager::TargetingStrategy::none)) {
listener = this->targetingFuncs.at(data->strategy)(
data->targetingReference.get(),
this->listeners
| std::views::transform(&std::weak_ptr<InteractionListener>::lock)
| std::views::filter(&std::shared_ptr<InteractionListener>::operator bool)
| std::ranges::to<std::vector>()
);
}
if (listener) {
listener->interact(data->actor, data->data);
}
return SDL_APP_CONTINUE;
}
void InteractionManager::registerListener(std::weak_ptr<InteractionListener> listener)
{
this->listeners.emplace_back(listener);
}
uint8_t InteractionManager::registerTargetingFunc(TargetingFunc func)
{
auto it = std::ranges::find_if(this->targetingFuncs, [](const auto& func) {
return !func;
});
(*it) = func;
return std::distance(this->targetingFuncs.begin(), it);
}

View File

@ -27,9 +27,9 @@ void Manager::refresh()
std::end(entities));
}
void Manager::update()
void Manager::update(uint_fast16_t diffTime)
{
for (auto& e : entities) e->update();
for (auto& e : entities) e->update(diffTime);
}
void Manager::addToGroup(Entity* mEntity, Group mGroup)

View File

@ -2,170 +2,184 @@
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <type_traits>
#include <utility>
#include <functional>
#include <optional>
#include <ranges>
#include <vector>
#include <SDL_error.h>
#include <SDL_render.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_render.h>
#include <tmxlite/Layer.hpp>
#include <tmxlite/Map.hpp>
#include <tmxlite/Tileset.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/Types.hpp>
#include "Constants.h"
#include "ColliderComponent.h"
#include "GameInternal.h"
#include "SpriteComponent.h"
#include "TextureManager.h"
#include "TileComponent.h"
#include "VEGO.h"
#include "tmxlite/Types.hpp"
void Map::loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict /* backreference */)
{
std::string tileIDstr;
char singleChar = 0;
std::ifstream mapFile;
mapFile.open(path);
if (!mapFile.is_open()) {
SDL_SetError("Error loading map: Couldn't open map file!");
std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl;
SDL_ClearError();
}
template<> std::optional<bool> Map::getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName) {
auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) {
return property.getName().compare(propertyName) == 0;
});
int x = 0, y = 0; // needed outside for-loop for error handling
for (; !mapFile.eof(); mapFile.get(singleChar))
{
if (singleChar == ',' || singleChar == '\n') {
if (tileIDstr.empty())
continue;
Map::addTile(std::stoi(tileIDstr), x * TILE_SIZE, y * TILE_SIZE, game, textureDict);
tileIDstr.clear();
x++;
if (singleChar == '\n') {
if (x != sizeX) {
SDL_SetError("Error loading map: specified x size doesn't match map file!");
std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl;
SDL_ClearError();
}
x = 0;
y++;
continue;
}
continue;
}
if (!std::isdigit(singleChar)) continue;
tileIDstr += singleChar;
}
if (y != sizeY) {
SDL_SetError("Error loading map: specified y size doesn't match map file!");
std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl;
SDL_ClearError();
}
mapFile.close();
}
void Map::addTile(unsigned long id, int x, int y, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict) // tile entity
{
auto& tile(game->manager.addEntity());
tile.addComponent<TileComponent>(x, y, TILE_SIZE, TILE_SIZE, id, textureDict);
if(tile.getComponent<TileComponent>().hasCollision()) tile.addComponent<ColliderComponent>("tile"/*tile.getComponent<TileComponent>().getName().data()*/);
tile.addGroup((size_t)Entity::GroupLabel::MAPTILES);
}
void Map::loadMapTmx(const char* path)
{
tmx::Map map;
if (!map.load(path)) {
// TODO: log to console
if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Boolean) {
return zIndexIterator->getBoolValue();
}
const std::vector<tmx::Tileset>& tileSets = map.getTilesets();
return std::nullopt;
}
const std::vector<tmx::Layer::Ptr>& mapLayers = map.getLayers();
const auto mapSize = map.getTileCount();
const auto mapTileSize = map.getTileSize();
template<> std::optional<int> Map::getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName)
{
auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) {
return property.getName().compare(propertyName) == 0;
});
if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Int) {
return zIndexIterator->getIntValue();
}
return std::nullopt;
}
Map::Map(const char* path)
{
if (!this->map.load(path)) {
// TODO: log to console
// TODO: error handling
}
std::vector<std::string> texturePaths = {};
for (auto tileSet : tileSets) {
for (const auto& tileSet : map.getTilesets()) {
texturePaths.emplace_back(tileSet.getImagePath());
}
for (auto& layer : mapLayers) {
this->mapData = {
&map.getTilesets(),
&map.getLayers(),
&map.getTileCount(),
&map.getTileSize(),
&texturePaths
};
for (auto& layer : *this->mapData.mapLayers) {
if (layer->getType() == tmx::Layer::Type::Tile) {
auto& tileLayer = layer->getLayerAs<tmx::TileLayer>();
int zIndex = 0;
const std::vector<tmx::Property>& properties = layer->getProperties();
auto zIndexIterator = std::find_if(properties.begin(), properties.end(), [](const tmx::Property& property) {
return property.getName() == "zIndex";
});
if (zIndexIterator != properties.end() && std::is_nothrow_convertible<decltype(zIndexIterator->getType()), int>::value) {
zIndex = zIndexIterator->getIntValue();
}
const auto& tiles = tileLayer.getTiles();
for (auto i = 0u; i < tileSets.size(); i++) {
auto tilesetTexture = VEGO_Game().textureManager->loadTexture(texturePaths.at(i).c_str());
tmx::Vector2i textureSize;
SDL_QueryTexture(tilesetTexture, nullptr, nullptr, &(textureSize.x), &(textureSize.y));
const auto tileCountX = textureSize.x / mapTileSize.x;
const auto tileCountY = textureSize.y / mapTileSize.y;
for (auto idx = 0ul; idx < mapSize.x * mapSize.y; idx++) {
if (idx >= tiles.size() || tiles[idx].ID < tileSets.at(i).getFirstGID()
|| tiles[idx].ID >= (tileSets.at(i).getFirstGID() + tileSets.at(i).getTileCount())) {
continue;
}
const auto x = idx % mapSize.x;
const auto y = idx / mapSize.x;
auto idIndex = (tiles[idx].ID - tileSets.at(i).getFirstGID());
int u = idIndex % tileCountX;
int v = idIndex / tileCountY;
u *= mapTileSize.x; //TODO we should be using the tile set size, as this may be different from the map's grid size
v *= mapTileSize.y;
//normalise the UV
u /= textureSize.x;
v /= textureSize.y;
//vert pos
const float tilePosX = static_cast<float>(x) * mapTileSize.x;
const float tilePosY = (static_cast<float>(y) * mapTileSize.y);
Map::addTile(tilePosX, tilePosY, mapTileSize, u, v, zIndex, texturePaths.at(i).c_str());
}
}
if (layer->getType() == tmx::Layer::Type::Object) {
// spawn objects
continue;
}
loadTileLayer(layer->getLayerAs<tmx::TileLayer>());
continue;
}
if (layer->getType() == tmx::Layer::Type::Object) {
// spawn objects
continue;
}
}
}
void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath)
void Map::loadTileLayer(const tmx::TileLayer& layer)
{
const std::vector<tmx::Property>& properties = layer.getProperties();
int zIndex = getLayerProperty<int>(properties, "zIndex").value_or(0);
bool collision = getLayerProperty<bool>(properties, "collision").value_or(false);
const auto& tiles = layer.getTiles();
// for each tile set
auto tileConstructorRange = std::views::iota(0)
| std::views::take(this->mapData.tileSets->size())
// return the tile set metadata
| std::views::transform([&](uint16_t i) {
const char* texturePath = this->mapData.texturePaths->at(i).c_str();
tmx::Vector2f textureSize;
SDL_GetTextureSize(
VEGO_Game().textureManager->loadMapTileTexture(texturePath),
&(textureSize.x),
&(textureSize.y)
);
tmx::Vector2u tileCount2D = { static_cast<unsigned int>(textureSize.x / this->mapData.mapTileSize->x), static_cast<unsigned int>(textureSize.y / this->mapData.mapTileSize->y) };
uint32_t tileCount = this->mapData.tileSets->at(i).getTileCount();
uint32_t firstGID = this->mapData.tileSets->at(i).getFirstGID();
return TileSetData { texturePath, textureSize, tileCount, tileCount2D, firstGID };
})
| std::views::transform([=, this](const TileSetData& data) {
// for each tile on the tile set
return std::views::iota(0)
| std::views::take(this->mapData.mapSize->x * this->mapData.mapSize->y)
// only take tiles that are on the ID range of the tile set
| std::views::filter([=](uint16_t idx) {
return
idx < tiles.size()
&& tiles[idx].ID >= data.firstGID
&& tiles[idx].ID < (data.firstGID + data.tileCount);
})
// extract tile data
| std::views::transform([=, this](uint16_t idx) {
const auto x = idx % this->mapData.mapSize->x;
const auto y = idx / this->mapData.mapSize->x;
const auto idIndex = (tiles[idx].ID - data.firstGID);
uint32_t u = idIndex % data.tileCount2D.x;
uint32_t v = idIndex / data.tileCount2D.y;
u *= this->mapData.mapTileSize->x; // TODO: we should be using the tile set size, as this may be different from the map's grid size
v *= this->mapData.mapTileSize->y;
// normalise the UV
u /= data.textureSize.x;
v /= data.textureSize.y;
// vert pos
const float tilePosX = static_cast<float>(x) * this->mapData.mapTileSize->x;
const float tilePosY = (static_cast<float>(y) * this->mapData.mapTileSize->y);
// return tile data as a function to spawn said tile
return std::function<void()>(
[tilePosX, tilePosY, capture0 = *this->mapData.mapTileSize, u, v, zIndex, capture1 = data.texturePath, collision] {
Map::addTile(tilePosX, tilePosY, capture0, u, v, zIndex, capture1, collision);
}
);
});
})
// 2D view to 1D vector; might be better keep as view with scene management
| std::views::join
| std::ranges::to<std::vector>();
this->tileConstructors.insert(this->tileConstructors.end(), tileConstructorRange.begin(), tileConstructorRange.end());
}
void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision)
{
auto& tile(VEGO_Game().manager.addEntity());
tile.addComponent<TransformComponent>(x, y, mapTileSize.x, mapTileSize.y, 1);
tile.addComponent<SpriteComponent>(texturePath, v, u, zIndex); // why does uv need to be reversed?
tile.addComponent<SpriteComponent>(texturePath.c_str(), v, u, zIndex); // why does uv need to be reversed?
//TODO: also implement updated map stuff for this
if (hasCollision) {
// tag currently does not have a clear purposes, TODO: figure out appropriate tag name
tile.addComponent<ColliderComponent>("hello I am a collider of a tile!");
tile.addGroup((size_t)Entity::GroupLabel::MAPTILES);
}
}
void Map::generateTiles()
{
std::ranges::for_each(this->tileConstructors, [](auto& function) {
function();
});
}

View File

@ -1,18 +1,22 @@
#include "PowerupComponent.h"
#include "PickupComponent.h"
#include "GameInternal.h"
#include "CollisionHandler.h"
#include "Entity.h"
#include "HealthComponent.h"
#include "SpriteComponent.h"
#include "StatEffectsComponent.h"
#include "Constants.h"
#include "TextureManager.h"
#include "TransformComponent.h"
#include <cstdint>
#include "VEGO.h"
PowerupComponent::PowerupComponent(std::function<void (Entity*)> func)
PickupComponent::PickupComponent(std::function<void (Entity*)> func)
{
this->pickupFunc = func;
}
void PowerupComponent::update()
void PickupComponent::update(uint_fast16_t diffTime)
{
Entity* player;
if ((player = this->entity->getManager().getGame()->collisionHandler->getAnyIntersection<Entity*>(

65
src/PickupManager.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "PickupManager.h"
#include "TextureManager.h"
#include "SoundManager.h"
#include "ProjectileComponent.h"
#include "GameInternal.h"
#include "TransformComponent.h"
#include "CollisionHandler.h"
#include "ColliderComponent.h"
#include "Constants.h"
#include "Entity.h"
#include "Vector2D.h"
#include "PickupComponent.h"
#include <iostream>
#include <VEGO.h>
#include "Textures.h"
PickupManager::PickupManager(Manager* manager) : man(manager) {}
PickupManager::~PickupManager() {}
void PickupManager::createPowerup(Vector2D pos, std::function<void (Entity*)> pickupFunc, Textures texture) {
auto& powerups(man->addEntity());
powerups.addComponent<TransformComponent>(pos.x, pos.y, 32, 32, 1); //32x32 is standard size for objects
try {
powerups.addComponent<SpriteComponent>(texture, 3);
}
catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
powerups.addComponent<ColliderComponent>("powerup", 0.6f);
powerups.addComponent<PickupComponent>(pickupFunc);
powerups.addGroup((size_t)Entity::GroupLabel::POWERUPS);
}
Vector2D PickupManager::calculateSpawnPosition()
{
Vector2D spawnPos = Vector2D(-1, -1);
bool conflict = false;
for (int i = 0; i <= SPAWN_ATTEMPTS; i++)
{
SDL_Rect spawnRect;
spawnRect.h = spawnRect.w = 32;
spawnRect.x = rand() % (VEGO_Game().config->getFinalConfig().at("screen_width").get<int>() - spawnRect.w);
spawnRect.y = rand() % (VEGO_Game().config->getFinalConfig().at("screen_height").get<int>() - spawnRect.h);
conflict = false;
for (auto cc : this->man->getGame()->collisionHandler->getColliders({ Entity::GroupLabel::MAPTILES }))
{
if (SDL_HasRectIntersection(&spawnRect, &cc->collider) && strcmp(cc->tag, "projectile"))
{
conflict = true;
break;
}
}
if (conflict) continue;
spawnPos = Vector2D(spawnRect.x, spawnRect.y);
}
return spawnPos;
}

View File

@ -14,12 +14,12 @@ void ProjectileComponent::init()
{
transformComponent = &entity->getComponent<TransformComponent>();
transformComponent->direction = direction;
SoundManager::playSound(this->entity->getManager().getGame(), "throw_egg", true, PLAY_ONCE, MAX_VOLUME, -1);
SoundManager::playSound(this->soundEffect, true, PLAY_ONCE, MAX_VOLUME, -1);
}
void ProjectileComponent::update()
void ProjectileComponent::update(uint_fast16_t diffTime)
{
distance += speed;
distance += speed * diffTime * (1.f/1000);
IntersectionBitSet boundsIntersection = this->entity->getManager().getGame()->collisionHandler->getIntersectionWithBounds(entity);

View File

@ -1,10 +1,10 @@
#include "RenderObject.h"
#include "RenderManager.h"
RenderObject::RenderObject(int zIndex, RenderManager& renderManager) : zIndex(zIndex), renderManager(renderManager) {
renderManager.add(this);
RenderObject::RenderObject(int zIndex, RenderManager* renderManager) : zIndex(zIndex), renderManager(renderManager) {
renderManager->add(this);
}
RenderObject::~RenderObject() {
this->renderManager.remove(this);
this->renderManager->remove(this);
}

View File

@ -1,15 +1,16 @@
#include "SoundManager.h"
#include <stdexcept>
#include <string>
#include <iostream>
#include "GameInternal.h"
#include "AssetManager.h"
#include <SDL3_mixer/SDL_mixer.h>
#include "GameInternal.h"
/*
Mix_Music* SoundManager::loadMusic(const char* fileName)
{
auto it = this->music_cache.find(fileName);
//auto it = this->music_cache.find(fileName);
if (it != this->music_cache.end()) {
return it->second;
@ -47,14 +48,21 @@ Mix_Chunk* SoundManager::loadSound(const char* fileName)
return sound;
}
void SoundManager::playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel)
*/
void SoundManager::playSound(SoundEffects sound, bool canOverlap, int loops, int volume, int channel)
{
if (!this_instance->sound_cache.contains(sound)) {
std::cerr << "Error playing Sound-Effect: sound effect not found" << std::endl;
return;
}
if(!canOverlap)
{
// dev needs to specify a channel for this check to work, if they set it to -1 and let sdl pick the first available
// channel mix_getchunk() won't work
if (Mix_Playing(channel) != 0 &&
Mix_GetChunk(channel) == game->assets->getSound(sound) &&
Mix_GetChunk(channel) == this_instance->sound_cache.at(sound) &&
channel != -1)
{
return;
@ -63,36 +71,36 @@ void SoundManager::playSound(GameInternal* game, std::string sound, bool canOver
Mix_HaltChannel(channel);
}
if(Mix_VolumeChunk(game->assets->getSound(sound), volume) == -1)
if(Mix_VolumeChunk(this_instance->sound_cache.at(sound), volume) == -1)
{
std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl;
std::cerr << "Error adjusting volume: " << SDL_GetError() << std::endl;
}
if (Mix_PlayChannel(channel, game->assets->getSound(sound), loops) == -1)
if (Mix_PlayChannel(channel, this_instance->sound_cache.at(sound), loops) == -1)
{
std::cerr << "Error playing sound '" << sound << "': " << Mix_GetError() << std::endl;
std::cerr << "Error playing sound " << ": " << SDL_GetError() << std::endl;
}
}
void SoundManager::playMusic(GameInternal* game, std::string music, int loops, int volume, int ms)
void SoundManager::playMusic(BackgroundMusic music, int loops, int volume, int milliseconds)
{
if (!this_instance->music_cache.contains(music)) {
std::cerr << "Error playing music: music not found" << std::endl;
return;
}
if (Mix_PlayingMusic() != 0 || Mix_Fading() == Mix_Fading::MIX_FADING_IN)
return;
if(ms > 0)
if(milliseconds > 0)
{
Mix_FadeInMusic(game->assets->getMusic(music), loops, ms);
Mix_FadeInMusic(this_instance->music_cache.at(music), loops, milliseconds);
return;
}
if(Mix_VolumeMusic(volume) == -1)
{
std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl;
}
if (Mix_PlayMusic(game->assets->getMusic(music), loops) == -1)
{
std::cerr << "Error playing music '" << music << "': " << Mix_GetError() << std::endl;
std::cerr << "Error adjusting volume: " << SDL_GetError() << std::endl;
}
}
@ -133,3 +141,51 @@ void SoundManager::fadeOutMusic(int ms)
Mix_FadeOutMusic(ms);
}
void SoundManager::addSingleSoundEffect(SoundEffects soundEffect, const char *path) {
if (this_instance->sound_cache.contains(soundEffect)) {
std::cerr << "Error when adding Sound-Effect: sound-effect with that key already in cache" << std::endl;
return;
}
Mix_Chunk* sound = Mix_LoadWAV(path);
if (sound == nullptr) {
std::cerr << "Error when loading Sound-Effect: could not load sound effect from " << path << std::endl;
return;
}
this_instance->sound_cache.emplace(soundEffect, sound);
}
void SoundManager::addSingleBackgroundMusic(BackgroundMusic backgroundMusic, const char *path) {
if (this_instance->music_cache.contains(backgroundMusic)) {
std::cerr << "Error when adding Sound-Effect: sound-effect with that key already in cache" << std::endl;
return;
}
Mix_Music* music = Mix_LoadMUS(path);
if (music == nullptr) {
std::cerr << "Error when loading Sound-Effect: could not load sound effect from " << path << std::endl;
return;
}
this_instance->music_cache.emplace(backgroundMusic, music);
}
void SoundManager::addSoundEffects(const std::map<SoundEffects, const char *> &effects) {
for (auto effect : effects)
addSingleSoundEffect(effect.first, effect.second);
}
void SoundManager::addBackgroundMusic(const std::map<BackgroundMusic, const char *> &backgroundMusic) {
for (auto track : backgroundMusic)
addSingleBackgroundMusic(track.first, track.second);
}
SoundManager* SoundManager::this_instance = nullptr;

View File

@ -1,6 +1,6 @@
#include "SpriteComponent.h"
#include <SDL_timer.h>
#include <SDL3/SDL_timer.h>
#include <cstring>
#include <memory>
@ -15,18 +15,25 @@
#include "Manager.h"
#include "VEGO.h"
SpriteComponent::SpriteComponent(const char* path, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(0), textureYOffset(0)
SpriteComponent::SpriteComponent(Textures texture, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(0), textureYOffset(0)
{
this->texturePath = path;
this->textureEnum = texture;
this->path = "";
}
SpriteComponent::SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset)
SpriteComponent::SpriteComponent(Textures texture, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset)
{
this->texturePath = path;
this->textureEnum = texture;
this->path = "";
}
SpriteComponent::SpriteComponent(const char* path, int xOffset, int yOffset, int zIndex) : RenderObject(zIndex, VEGO_Game().renderManager), textureXOffset(xOffset), textureYOffset(yOffset) {
this->path = path;
}
SpriteComponent::SpriteComponent(
const char* path,
Textures texture,
bool isAnimated,
std::map<std::string, std::unique_ptr<Animation>>* animationMap,
std::string defaultAnimation,
@ -38,19 +45,26 @@ SpriteComponent::SpriteComponent(
playAnimation(defaultAnimation);
this->texturePath = path;
this->textureEnum = texture;
this->path = "";
}
SpriteComponent::~SpriteComponent() {}
void SpriteComponent::setTexture(const char* path)
void SpriteComponent::setTexture(Textures texture)
{
this->texture = VEGO_Game().textureManager->loadTexture(path);
this->texture = VEGO_Game().textureManager->loadTexture(texture);
}
void SpriteComponent::init()
{
setTexture(this->texturePath);
if (this->path == "") {
setTexture(this->textureEnum);
}
else {
setMapTileTexture(this->path);
}
this->transform = &entity->getComponent<TransformComponent>();
@ -59,14 +73,14 @@ void SpriteComponent::init()
this->srcRect.x = this->textureXOffset * this->srcRect.w;
this->srcRect.y = this->textureYOffset * this->srcRect.h;;
this->update();
this->update(0);
}
void SpriteComponent::update()
void SpriteComponent::update(uint_fast16_t diffTime)
{
// This code is not compatible for animated tiles
if (animated) {
srcRect.x = srcRect.w * static_cast<int>((SDL_GetTicks() / speed) % frames);
srcRect.x = srcRect.w * static_cast<int>((SDL_GetTicks() / speed) % frames); // TODO: should not call SDL_GetTicks() but use diffTime
srcRect.y = animationIndex * transform->height;
}
@ -92,4 +106,8 @@ void SpriteComponent::playAnimation(std::string type)
void SpriteComponent::setDirection(Direction direction)
{
this->flipped = direction == Direction::RIGHT;
}
}
void SpriteComponent::setMapTileTexture(const char *path) {
this->texture = VEGO_Game().textureManager->loadMapTileTexture(path);
}

View File

@ -1,56 +1,26 @@
#include "StatEffectsComponent.h"
#include "Entity.h"
#include "TransformComponent.h"
// #include "KeyboardController.h"
#include <algorithm>
#include <iostream>
void StatEffectsComponent::init()
{}
void StatEffectsComponent::update()
void StatEffectsComponent::update(uint_fast16_t diffTime)
{
for (int i = 0; i < MAX_STATS; i++)
{
if (this->buffs.at(i) == 0) continue;
if (this->buffs.at(i) - 1 == 0)
{
this->resetStatValue((Stats)i);
for (auto it = effects.begin(); it != effects.end(); ) {
it->duration -= diffTime;
if (it->duration <= 0) {
it->resetFunction();
it = effects.erase(it);
continue;
}
this->buffs.at(i) -= 1;
}
it++;
}
}
void StatEffectsComponent::modifyStatDur(Stats stat, int duration, int value)
{
if(this->buffs.at((uint8_t)stat) == 0) this->modifyStatValue(stat, value);
this->buffs.at((uint8_t)stat) += duration;
}
void StatEffectsComponent::modifyStatValue(Stats stat, int modifier) //modifier is basically there so the modifyfuncs in the components know if stats should be increased or decreased
{
switch (stat)
{
case Stats::MOVEMENT_SPEED:
this->entity->getComponent<TransformComponent>().modifySpeed(modifier);
break;
case Stats::ATTACK_SPEED:
// this->entity->getComponent<KeyboardController>().modifyAtkSpeed(modifier);
break;
default: break;
}
}
void StatEffectsComponent::resetStatValue(Stats stat)
{
switch (stat)
{
case Stats::MOVEMENT_SPEED:
this->entity->getComponent<TransformComponent>().resetSpeedMod();
break;
case Stats::ATTACK_SPEED:
// this->entity->getComponent<KeyboardController>().resetAtkSpeedMod();
break;
default: break;
}
void StatEffectsComponent::addEffect(uint32_t duration, std::function<void()> resetFunction) {
effects.push_back({duration, resetFunction});
}

View File

@ -1,26 +1,62 @@
#include "TextureManager.h"
#include <memory>
#include <stdexcept>
#include <string>
#include <VEGO.h>
#include "GameInternal.h"
SDL_Texture* TextureManager::loadTexture(const char* fileName)
{
auto it = this->texture_cache.find(fileName);
if (it != this->texture_cache.end()) {
return it->second;
}
auto texture = IMG_LoadTexture(this->manager->getGame()->renderer, fileName);
if (texture == NULL) throw std::runtime_error(std::string("Couldn't load texture '") + fileName + "'");
this->texture_cache.emplace(std::string(fileName), texture);
printf("Loaded texture at '%s'\n", fileName);
return texture;
void TextureManager::addSingleTexture(Textures texture, const char* filePath) {
auto sdlTexture = IMG_LoadTexture(VEGO_Game().renderer, filePath);
if (sdlTexture == nullptr)
throw std::runtime_error(std::string("Couldn't load texture '") + filePath + "'");
SDL_SetTextureScaleMode(sdlTexture, this->scaleMode); // linear scaling results in blurry images
this->texture_cache.emplace(texture, sdlTexture);
if (filePath != nullptr) {
this->texture_references.emplace(texture, std::string(filePath));
}
std::cout << "Loaded texture at " << filePath << std::endl;
}
void TextureManager::draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_Rect src, SDL_Rect dest, bool flipped)
void TextureManager::addTextures(const std::map<Textures, const char*> &textures) {
for (auto texture : textures) {
addSingleTexture(texture.first, texture.second);
}
}
SDL_Texture* TextureManager::loadTexture(Textures texture) {
auto it = this->texture_cache.find(texture);
if (it != this->texture_cache.end())
return it->second;
std::cout << "ERROR: Couldn't load texture!" << std::endl;
return nullptr;
}
void TextureManager::draw(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect src, SDL_FRect dest, bool flipped)
{
SDL_RendererFlip flip = flipped ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
SDL_RenderCopyEx(renderer, texture, &src, &dest, 0, NULL, flip);
}
SDL_FlipMode flip = flipped ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
SDL_RenderTextureRotated(renderer, texture, &src, &dest, 0, NULL, flip);
}
SDL_Texture* TextureManager::loadMapTileTexture(const char *path) {
//returns tile if it exists already
if(mapTile_texture_cache.contains(std::string(path)))
return mapTile_texture_cache.find(std::string(path))->second;
auto newTexture = IMG_LoadTexture(VEGO_Game().renderer, path);
if (newTexture == nullptr)
throw std::runtime_error(std::string("Couldn't load texture '") + path + "'");
SDL_SetTextureScaleMode(newTexture, this->scaleMode); // linear scaling results in blurry images
this->mapTile_texture_cache.emplace(std::string(path), newTexture);
return newTexture;
}

View File

@ -5,9 +5,9 @@
#include "Entity.h"
#include "TransformComponent.h"
#include "SpriteComponent.h"
#include "TileComponent.h"
TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map<int, std::pair<std::string, bool>>* textureDict)
TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map<int, std::pair<Textures, bool>>* textureDict)
{
this->tileRect.x = x;
this->tileRect.y = y;
@ -22,8 +22,7 @@ TileComponent::TileComponent(int x, int y, int w, int h, int id, const std::map<
}
this->collision = it->second.second;
this->tileName = it->second.first;
this->path = it->second.first.data();
this->texture = it->second.first;
}
void TileComponent::init()
@ -31,7 +30,7 @@ void TileComponent::init()
this->entity->addComponent<TransformComponent>(this->tileRect.x, this->tileRect.y, this->tileRect.w, this->tileRect.h, 1);
this->transform = &entity->getComponent<TransformComponent>();
this->entity->addComponent<SpriteComponent>(this->path, 0);
this->entity->addComponent<SpriteComponent>(this->texture, 0);
this->sprite = &entity->getComponent<SpriteComponent>();
}

View File

@ -9,26 +9,16 @@
#include <cstdio>
#include <initializer_list>
#include <iostream>
#include <optional>
#include "SoundManager.h"
TransformComponent::TransformComponent()
{
position.zero();
}
TransformComponent::TransformComponent(int scale)
{
position.zero();
this->scale = scale;
}
TransformComponent::TransformComponent(float x, float y)
{
this->position.x = x;
this->position.y = y;
}
TransformComponent::TransformComponent(float x, float y, int scale)
{
this->position.x = x;
@ -50,12 +40,15 @@ void TransformComponent::init()
direction.zero();
}
void TransformComponent::update()
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°
Vector2D positionChange(
direction.x * this->getSpeed() * multiplier,
direction.y * this->getSpeed() * multiplier
direction.x * this->getSpeed() * multiplier * diffTime * (1.f/1000),
direction.y * this->getSpeed() * multiplier * diffTime * (1.f/1000)
);
if (this->entity->hasGroup((size_t)Entity::GroupLabel::PLAYERS)){
@ -65,9 +58,11 @@ void TransformComponent::update()
position += positionChange;
}
void TransformComponent::modifySpeed(int8_t modifier)
{
this->speedMod += modifier;
int TransformComponent::getSpeed()
{
return (this->entity->hasComponent<DataComponent>()
? this->entity->getComponent<DataComponent>().getEntry<int>("speed").value_or(0)
: 0);
}
void TransformComponent::setPositionAfterCollision(Vector2D& positionChange)

View File

@ -1,5 +1,5 @@
#include "Vector2D.h"
#include "SDL_rect.h"
#include <SDL3/SDL_rect.h>
Vector2D::Vector2D()
{

49
src/_Init.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "SDL3/SDL_init.h"
#include <cstdint>
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <iostream>
#include <ctime>
#include "VEGO.h"
#include "Entity.h"
#include "GameInternal.h"
#include "Constants.h"
GameInternal* vego::game = nullptr;
SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
srand(time(NULL));
bool playing = true;
*appstate = vego::game = new GameInternal();
return vego::game->init();
}
SDL_AppResult SDL_AppIterate(void *appstate) {
if (!vego::game->isRunning()) {
return SDL_APP_SUCCESS;
}
//vego::game->handleEvents(); // bad
Uint64 frameStart = SDL_GetTicks();
vego::game->update(frameStart);
vego::game->render();
int frameTime = SDL_GetTicks() - frameStart;
return SDL_APP_CONTINUE;
}
// triggers upon every event
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
return vego::game->handleEvent(event);
}
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
vego::game->clean();
}

View File

@ -1,41 +0,0 @@
#include <iostream>
#include <ctime>
#include "VEGO.h"
#include "Entity.h"
#include "GameInternal.h"
#include "Constants.h"
GameInternal* vego::game = nullptr;
int main(int argc, char* argv[])
{
srand(time(NULL));
bool playing = true;
const int frameDelay = 1000 / FPS;
Uint32 frameStart;
int frameTime;
vego::game = new GameInternal();
vego::game->init("No_Name_Chicken_Game", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_SIZE_WIDTH, SCREEN_SIZE_HEIGHT, false);
while (vego::game->isRunning()) {
frameStart = SDL_GetTicks();
vego::game->handleEvents();
vego::game->update();
vego::game->render();
frameTime = SDL_GetTicks() - frameStart;
if (frameDelay > frameTime) {
SDL_Delay(frameDelay - frameTime);
}
}
vego::game->clean();
return 0;
}