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

Compare commits

...

53 Commits

Author SHA1 Message Date
2ff579dd39 Merge branch 'dev'
Some checks failed
/ deploy (push) Has been cancelled
2025-04-09 22:00:12 +02:00
7a3c845c40 Merge branch 'interactions-v2' into dev 2025-04-09 21:33:19 +02:00
0695c6cacb Merge branch 'BA' into dev 2025-04-09 21:11:08 +02:00
3c5d56de6b feat/ref: Interaction and Event management 2025-04-09 21:06:34 +02:00
adaed679af InteractionManager + proof of concept 2025-03-22 14:38:26 +01:00
325f6e8e8d ref: powerup component now pickup component, assetmanager removed
assetmanager was redundant therefore any traces and usages were removed
2025-03-22 12:45:28 +01:00
a9e754dd4f Merge branch 'input2' into interactions-v2 2025-03-21 17:19:59 +01:00
d66d860cdc Implemented event manager 2025-03-21 16:35:03 +01:00
e0c35aa690 Merge branch 'UpdateSoundMaps' into dev 2025-01-28 23:01:45 +01:00
1b795c3732 Merge branch 'data' into dev 2025-01-28 22:43:29 +01:00
eba3cdb6c8 doc: add docu
- datacomponent
- stateffectcomponent
- powerupcomponent
2025-01-28 22:33:07 +01:00
freezarite
1dc00408de pr stuff 2025-01-28 21:50:48 +01:00
freezarite
e215fbd5b6 instance changes in constructor and .cpp file for SoundManager 2025-01-28 19:40:14 +01:00
freezarite
007538f760 added getInstance method to soundmanager 2025-01-28 19:31:54 +01:00
freezarite
9bb9d0fbcc removed maps regarding music and sound effects from the AssetManager
reworked SoundManager to use enum classes as keys in its maps and to no longer use the AssetManager
added possibility to set sound effect of projectiles when creating them (was hard coded)
2025-01-28 19:08:05 +01:00
044d957106 feat: data and stats
- reimplemented/unhardcoded stateffects
- reimplemented/unhardcoded pickupables
- implemented datacomponent
- some minor cleanup
2025-01-28 17:31:03 +01:00
Freezarite
361687f09f
Merge pull request #95 from VEGO-Engine/config-system
Config system
2025-01-25 16:11:30 +01:00
freezarite
9195b19bdb changed default width and height in config.json 2025-01-14 21:40:00 +01:00
freezarite
201b5c8b5d added documentation 2025-01-14 19:27:56 +01:00
freezarite
91f15671c0 fixed pull request issues 2024-12-17 16:08:28 +01:00
freezarite
abe018f99b fixed some issues stemming from the removal of the window-size constant variables 2024-12-16 13:12:53 +01:00
freezarite
84cee5e307 removed no longer used variables in Constants.h
updated _Init.cpp to use updated init() function from GameInternal.cpp
2024-12-16 06:21:26 +01:00
freezarite
55b60624c4 added a folder to hold internal assets for the standard config of the engine 2024-12-16 06:10:31 +01:00
freezarite
1f3cf01419 fixed a bug with the implementation of the nlohmann library in the CMakeLists.txt
fixed a bug where the config was not loaded correctly in the GameInternal.cpp
2024-12-16 06:08:52 +01:00
freezarite
691ea06eb0 Added and implemented some more config options in the GameInternal.cpp. Also removed GameInternal::init() parameters as they are no longer needed 2024-12-16 06:02:58 +01:00
freezarite
acdbf29896 Fixed a bug where the custom json was not loaded correctly
Changed base config location to ./engine/config.json to make implementation better for the game-dev
2024-12-16 05:47:28 +01:00
freezarite
5e48f4e34f Config now gets read by the GameInternal.cpp
Made the process of adding a custom Config file work via a Virtual function within Game.h
2024-12-13 14:36:43 +01:00
freezarite
9247b8df8a started work on config-system
added nlohmann_json library as submodule and to CMakeLists.txt
created basic config.json file in root folder
created ConfigLoader.h and added basic functionalities to it
2024-12-03 18:17:31 +01:00
7c50c8d1fb Merge branch '80-migrate-to-sdl3' into dev 2024-12-02 23:10:05 +01:00
6a0b5197f9 Implemented suggested changes 2024-12-02 21:47:36 +01:00
Freezarite
ff27a0e55c
Merge pull request #86 from VEGO-Engine/textureManagerChanges
Texture manager changes
2024-12-01 21:09:09 +01:00
freezarite
58be6b05f0 more documentation changes 2024-12-01 21:04:45 +01:00
freezarite
b490e2dc17 removed magic enum and documentation improvements
since getName is no longer used from the TileComponent.h we no longer need magic_enum for the enum to string conversion
also some minor documentation changes
2024-12-01 20:25:31 +01:00
2483b75983 Added config file 2024-12-01 18:41:58 +01:00
freezarite
dadf846470 fixed \todo in TextureManager.h 2024-12-01 14:51:10 +01:00
freezarite
4ead20ecb7 Cleanup of documentation and refactoring
renamed TextureEnumBase.h to Textures.h
improved some of the doxygen documentation
2024-12-01 14:39:45 +01:00
freezarite
7dbcfe876c Added some doxygen documentation to TextureEnumBase.h and TextureManager.h 2024-11-30 15:43:15 +01:00
freezarite
cbd1993c20 Extra map for MapTiles generated by TMX file now works like intended.
removed textures map from AssetManager as it is no longer used.
Updated SpriteComponent to now check if it is a normal Texture or a MapTileTexture.
Added if condition in TextureManager::LoadMapTileTexture to check if the texture was able to be loaded by SDL
2024-11-30 15:42:56 +01:00
b497991975 fixed developer skill issue 2024-11-19 18:47:37 +01:00
25414524a0 new main logic & new time keeping 2024-11-18 16:20:41 +01:00
625ac98a57 first pass - changed function names etc. 2024-11-18 13:37:10 +01:00
freezarite
27a80d9766 added new Constructor for SpriteComponent as Tiles wont work with enums 2024-11-17 17:05:26 +01:00
freezarite
ac217e931b added new Map for tile-textures as they wont work with our current enum-class maps. 2024-11-17 16:59:46 +01:00
freezarite
1a8a196e95 merged dev into textureManagerChanges 2024-11-17 16:26:05 +01:00
freezarite
494ff8aa0a magic_enum library stuff and refactored TextureEnum to Textures due to bad naming 2024-11-17 16:00:47 +01:00
a8052b4bbb Merge branch 'tmxlite' into dev 2024-11-17 14:07:42 +01:00
92dfbacd9b Fixed templates and const char* for texture 2024-11-17 13:07:33 +01:00
1fbbb39843 Merge branch 'v0-2-0' into dev 2024-11-17 12:50:32 +01:00
70260c01bb structural improvements & code comments 2024-11-17 12:39:14 +01:00
7f4b1df833 Restructured code 2024-11-16 20:02:35 +01:00
9e346a719d Reimplemented collision
still missing documentation
2024-11-15 22:25:43 +01:00
freezarite
65e00c2314 changed texture_cache map to use enums instead of string for keys and functions using said map 2024-10-15 13:24:56 +02:00
freezarite
68079d0279 made texture_cache map private 2024-10-13 13:29:32 +02:00
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;
}