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

Compare commits

...

8 Commits

Author SHA1 Message Date
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
16 changed files with 208 additions and 147 deletions

View File

@ -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)

View File

@ -6,6 +6,7 @@
#include <functional>
#include "Entity.h"
#include "SoundEffects.h"
class Vector2D;
class Manager;
@ -24,24 +25,13 @@ public:
AssetManager(Manager* manager);
~AssetManager();
void createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, float speed, Textures textureEnum, Entity* owner);
void createPowerup(Vector2D pos, std::function<void (Entity*)> pickupFunc, Textures texture);
Vector2D calculateSpawnPosition();
PowerupType calculateType();
// sound management
void addSoundEffect(std::string id, const char* path);
void addMusic(std::string id, const char* path);
Mix_Chunk* getSound(std::string id);
Mix_Music* getMusic(std::string id);
private:
Manager* man;
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;

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

@ -102,7 +102,7 @@ public:
return *c;
};
template <typename T> T& getComponent() const //! \returns Component of type T
template <typename T> T& getComponent() const //!< \todo: rewrite to use optionals
{
auto ptr(componentArray[getComponentTypeID<T>()]);
return *static_cast<T*>(ptr);

View File

@ -6,6 +6,10 @@
class PowerupComponent : public Component
{
public:
/**
* @brief Construct a new Powerup Component object
* @param func The function to be called when the powerup is picked up
*/
PowerupComponent(std::function<void (Entity*)> func);
~PowerupComponent() {};

View File

@ -3,6 +3,7 @@
#include "Component.h"
#include "Vector2D.h"
#include "Constants.h"
#include "SoundEffects.h"
class TransformComponent;
@ -11,8 +12,8 @@ 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;
@ -28,4 +29,6 @@ private:
Entity* owner = nullptr;
Vector2D direction;
SoundEffects soundEffect;
};

3
include/SoundEffects.h Normal file
View File

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

View File

@ -6,6 +6,8 @@
#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

@ -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{
@ -17,12 +21,13 @@ public:
void init() override;
void update(uint_fast16_t diffTime) override;
void modifyStatDur(Stats stat, int duration, int value);
void modifyStatValue(Stats stat, int modifier);
void resetStatValue(Stats stat);
/**
* @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

@ -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(uint_fast16_t diffTime) override;
void setPositionAfterCollision(Vector2D& positionChange);
void modifySpeed(int8_t modifier);
int getSpeed();
private:
int speed = 180;
int speedMod = 0;
};

View File

@ -22,36 +22,6 @@ AssetManager::AssetManager(Manager* manager) : man(manager) {}
AssetManager::~AssetManager() {}
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));
}
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, float speed, Textures textureEnum, 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>(textureEnum, 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, Textures texture) {
auto& powerups(man->addEntity());

View File

@ -14,7 +14,7 @@ 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(uint_fast16_t diffTime)

View File

@ -8,9 +8,10 @@
#include "GameInternal.h"
#include "AssetManager.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;
@ -48,14 +49,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;
@ -64,25 +72,30 @@ 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: " << 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 << "': " << SDL_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;
}
@ -90,11 +103,6 @@ void SoundManager::playMusic(GameInternal* game, std::string music, int loops, i
{
std::cerr << "Error adjusting volume: " << SDL_GetError() << std::endl;
}
if (Mix_PlayMusic(game->assets->getMusic(music), loops) == -1)
{
std::cerr << "Error playing music '" << music << "': " << SDL_GetError() << std::endl;
}
}
void SoundManager::setSoundVolume(int volume, int channel)
@ -134,3 +142,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,7 +1,6 @@
#include "StatEffectsComponent.h"
#include "Entity.h"
#include "TransformComponent.h"
// #include "KeyboardController.h"
#include <algorithm>
#include <iostream>
@ -10,45 +9,18 @@ void StatEffectsComponent::init()
void StatEffectsComponent::update(uint_fast16_t diffTime)
{
for (int i = 0; i < MAX_STATS; i++)
{
this->buffs.at(i) -= diffTime;
if (this->buffs.at(i) <= 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;
}
}
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

@ -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;
@ -65,9 +55,11 @@ void TransformComponent::update(uint_fast16_t diffTime)
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)