Added spatial sound effects, fixed a bunch of memory leaks

This commit is contained in:
Ethan 2025-04-04 13:34:27 -04:00
parent d3ad840169
commit 93b0c0ea0d
34 changed files with 589 additions and 132 deletions

View file

@ -26,7 +26,9 @@ SET(FT_DISABLE_ZLIB 1)
option(GLM_ENABLE_FAST_MATH OFF)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffloat-store -fexcess-precision=standard -ffp-contract=off")
# -fsanitize=address I will forget, renable this flag no more leaks allowed!
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ffloat-store -fexcess-precision=standard -ffp-contract=off")
FetchContent_Declare(
tracy

Binary file not shown.

View file

@ -3,18 +3,18 @@
<scene type="shooter" id="000" bg="backgrounds/blue_sky.png">
<map name="newmap"/>
<entities>
<player x="7" y="5" weapon="shotgun">
<player x="7" y="5" weapon="gun/machine">
<animation id="character/player"/>
</player>
<entity x="10" y="3" weapon="pistol">
<entity x="10" y="3" weapon="gun/pistol">
<animation id="character/player"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
</entity>
<entity x="6" y="3" weapon="pistol">
<entity x="6" y="3" weapon="gun/pistol">
<animation id="character/tmp"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
</entity>
<entity x="5" y="3" weapon="pistol">
<entity x="5" y="3" weapon="gun/pistol">
<animation id="character/tmp"/>
<script file="scripts/ai/grunt_behaviour.lua"/>
</entity>

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<weapons>
<weapon name="bubblegun" fireSpeed="100.0" maxAmmo="40000" clipSize="1000">
<weapon id="gun/machine" fireSpeed="100.0" maxAmmo="40000" clipSize="1000">
<bullet anim="obj/bubble">
<spread>60</spread>
<speed>20.0</speed>
@ -11,9 +11,9 @@
</bullet>
</weapon>
<weapon name="shotgun" fireSpeed="1750.0" maxAmmo="64" clipSize="4">
<weapon id="gun/shotgun" fireSpeed="1750.0" maxAmmo="64" clipSize="4">
<script file="scripts/weapons/shotgun_script.lua"/>
<animation id="gun/shotgun">
<animation>
<size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/>
</animation>
@ -26,8 +26,8 @@
</bullet>
</weapon>
<weapon name="machine_gun" fireSpeed="50.0" maxAmmo="512" clipSize="64">
<animation id="gun/machine">
<weapon id="gun/machine" fireSpeed="50.0" maxAmmo="512" clipSize="64">
<animation>
<size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/>
</animation>
@ -40,8 +40,8 @@
</bullet>
</weapon>
<weapon name="pistol" fireSpeed="750.0" maxAmmo="512" clipSize="21">
<animation id="gun/pistol">
<weapon id="gun/pistol" fireSpeed="750.0" maxAmmo="512" clipSize="21">
<animation>
<size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/>
</animation>

View file

@ -36,6 +36,7 @@ add_executable (YuppleMayham
"src/sound/engine.cpp"
"src/sound/audiostream.cpp"
"src/sound/soundeffect.cpp"
"src/sound/soundmanager.cpp"
"include/thirdparty/stb_vorbis.c"
"src/utility/data/font_data.c"
"src/utility/ftfont.cpp"

View file

@ -17,25 +17,25 @@ enum class AIState {
Patrol
};
class AI : public std::enable_shared_from_this<AI> {
class AI {
public:
AI(const std::shared_ptr<GameActor>& actor, const std::shared_ptr<Raycaster>& raycaster);
AI(GameActor* actor, std::unique_ptr<Raycaster> raycaster);
void update();
void setTarget(const std::shared_ptr<GameActor>& target) { this->target = target; }
void attachBehaviourScript(const std::shared_ptr<AIScript>& behaviour);
void setTarget(GameActor* target) { this->target = target; }
void attachBehaviourScript(std::unique_ptr<AIScript> behaviour);
AIState getState() const { return state; }
void setState(AIState state) { this->state = state; }
~AI() {}
~AI();
private:
AIState state;
std::shared_ptr<Raycaster> raycaster;
std::shared_ptr<AIScript> behaviour;
std::shared_ptr<GameActor> actor;
std::shared_ptr<GameActor> target;
std::unique_ptr<Raycaster> raycaster;
std::unique_ptr<AIScript> behaviour;
GameActor* actor;
GameActor* target;
// cache our lua function calls
sol::function idleFunc;
@ -46,4 +46,4 @@ private:
std::chrono::minutes GCTimeout;
};
#endif
#endif

View file

@ -62,12 +62,12 @@ class InputHandler : public Keyboard, public Mouse
{
private:
struct CommandWithDelay{
Command* cmd;
std::unique_ptr<Command> cmd;
Uint32 lastExecution;
Uint32 delay;
};
struct MouseCommandWithDelay{
MouseCommand* cmd;
std::unique_ptr<MouseCommand> cmd;
Uint32 lastExecution;
Uint32 delay;
};
@ -80,29 +80,31 @@ public:
this->actor = actor;
}
void bindKeyCommand(SDL_Keycode key, Uint32 delay, Command* command) {
keyCommands[key] = { command, 0, delay };
void bindKeyCommand(SDL_Keycode key, Uint32 delay, std::unique_ptr<Command> command) {
keyCommands[key] = { std::move(command), 0, delay };
}
void bindMouseCommand(Uint8 mouse, Uint32 delay, MouseCommand* command) {
mouseCommands[mouse] = { command, 0, delay };
void bindMouseCommand(Uint8 mouse, Uint32 delay, std::unique_ptr<MouseCommand> command) {
mouseCommands[mouse] = { std::move(command), 0, delay };
}
void bindMouseMotion(MouseCommand* command) {
mouseMotionCommand = command;
void bindMouseMotion(std::unique_ptr<MouseCommand> command) {
mouseMotionCommand = std::move(command);
}
void bindMouseScroll(MouseCommand* command) {
mouseScrollCommand = command;
void bindMouseScroll(std::unique_ptr<MouseCommand> command) {
mouseScrollCommand = std::move(command);
}
void handleInput();
void executeCommands();
~InputHandler();
private:
GameActor* actor = nullptr;
std::unordered_map<SDL_Keycode, CommandWithDelay> keyCommands;
std::unordered_map<Uint8, MouseCommandWithDelay> mouseCommands;
std::vector<Command*> commandQueue;
std::vector<MouseCommand*> mouseQueue;
MouseCommand* mouseMotionCommand = nullptr;
MouseCommand* mouseScrollCommand = nullptr;
std::unique_ptr<MouseCommand> mouseMotionCommand = nullptr;
std::unique_ptr<MouseCommand> mouseScrollCommand = nullptr;
};
#endif // _H_INPUT_H

View file

@ -35,7 +35,7 @@ public:
void putaway() { wielded = false; }
void addComponent(std::unique_ptr<Component> component);
void attachScript(const std::shared_ptr<WeaponScript>& script);
void attachScript(std::unique_ptr<WeaponScript> script);
void hookEventManager(std::weak_ptr<EventManager> eventManager);
bool shoot();
void reload();
@ -69,6 +69,8 @@ public:
const BulletManager* getBulletManager() { return bulletManager.get(); }
~Weapon();
private:
void adjustWeapon();
@ -104,7 +106,7 @@ private:
Direction lastDir;
std::vector <std::unique_ptr<Component>> components;
std::shared_ptr<WeaponScript> weaponScript;
std::unique_ptr<WeaponScript> weaponScript;
};

View file

@ -52,6 +52,8 @@ public:
const TextureArray* getTextureArray() { return textures; }
const Texture* getTexture() { return texture; }
~TileTextureInstance();
private:
void setup() override;
TextureArray* textures = nullptr;

View file

@ -38,14 +38,14 @@ public:
void bind();
TextureData* operator[](const size_t index) {
TextureData operator[](const size_t index) {
try
{
return textures.at(index);
}
catch (std::exception&)
{
return nullptr;
{
return { 0, 0 };
}
}
@ -59,7 +59,7 @@ private:
size_t numOfLayers;
unsigned ID = 0;
std::vector<TextureData*> textures;
std::vector<TextureData> textures;
int canvasWidth = 0;
int canvasHeight = 0;
};

View file

@ -4,27 +4,35 @@
#include <AL/al.h>
#include <AL/alc.h>
#include <memory>
#include <glm/glm.hpp>
#include "utility/events.h"
#include "sound/audiostream.h"
#include "sound/soundmanager.h"
#include "utility/resourcemanager.h"
#include "utility/logger.h"
class AudioEngine
class AudioEngine : public std::enable_shared_from_this<AudioEngine>
{
public:
AudioEngine(std::weak_ptr<ResourceManager> _resource);
void hookEventManager(std::weak_ptr<EventManager> _events);
void hookSceneManager(std::weak_ptr<EventManager> _events);
void pushMusic(std::string _songName);
void playMusic();
void pauseMusic();
void killMusic();
void poll();
void updateListener(const glm::vec3& pos);
~AudioEngine();
private:
std::unique_ptr<AudioStream> musicPlayer;
std::unique_ptr<SoundManager> soundManager;
std::weak_ptr<EventManager> globalEventManager;
std::weak_ptr<EventManager> sceneEventManager;
std::weak_ptr<ResourceManager> resourceManager;
ALCdevice *device;
ALCcontext *context;

View file

@ -0,0 +1,25 @@
#ifndef _H_SOUNDMANAGER_H
#define _H_SOUNDMANAGER_H
#include <AL/al.h>
#include <array>
#include <glm/glm.hpp>
class SoundManager
{
public:
SoundManager();
void playSound(ALuint buffer, int priority=10, const glm::vec3& pos = glm::vec3(0));
void pollSources();
~SoundManager();
private:
struct AudioSource {
ALuint source;
bool inUse;
int priority;
};
std::array<AudioSource, 10> sources;
std::size_t lastUsed = 0;
};
#endif // _H_SOUNDMANAGER_H

View file

@ -30,6 +30,14 @@ namespace UTIL
return (pos != std::string::npos) ? id.substr(pos + 1) : id;
};
constexpr auto get_generic = [](const std::string& id) {
size_t left = 0;
size_t right = 0;
std::string out;
split(id, &left, &right, &out);
return (out + "/generic/" + get_type(id));
};
void flip_surface(SDL_Surface* surface);
std::string parsePrefix(const std::string& id);

View file

@ -145,8 +145,10 @@ public:
void idle() override { /*unused*/ }
TYPE getType() const override { return TYPE::AI; }
~AIComponent() { }
private:
std::shared_ptr<AI> ai;
};
#endif // _H_COMPONENT_H
#endif // _H_COMPONENT_H

View file

@ -30,6 +30,7 @@ public:
xmlLoader->loadTileSets("maps/tilesets");
xmlLoader->loadMaps("maps");
xmlLoader->loadScenes("scenes");
xmlLoader->loadSoundEffects("sounds");
shaders["__fallback__"] = std::make_unique<GenericShader>();
};
@ -39,8 +40,9 @@ public:
Sprite* loadSpriteStatic (const std::string& path);
// Returns a NON-OWNING pointer to a background
Background* loadBackground (const std::string& path);
std::shared_ptr<AIScript> loadAIScript (const std::string& path);
std::shared_ptr<WeaponScript> loadWeaponScript (const std::string& path);
const unsigned loadSoundEffect (const std::string& id);
std::unique_ptr<AIScript> loadAIScript (const std::string& path);
std::unique_ptr<WeaponScript> loadWeaponScript (const std::string& path);
std::shared_ptr<Weapon> loadWeapon (const std::string& name, const unsigned weaponShaderID, const unsigned bulletShaderID);
std::shared_ptr<AnimationSet> loadAnimationSet (const std::string& name, int entityid = 0);
@ -61,7 +63,7 @@ private:
std::unordered_map<std::string, std::unique_ptr<SoundEffect>> sounds;
std::unordered_map<std::string, std::unique_ptr<Sprite>> sprites;
std::unordered_map<std::string, std::shared_ptr<Weapon>> weapons;
std::unordered_map<std::string, std::shared_ptr<Script>> scripts;
//std::unordered_map<std::string, std::string> scripts;
std::unordered_map<std::string, std::unique_ptr<Background>> backgrounds;
std::unordered_map<std::string, std::shared_ptr<TileSetData>> tileSets;
//std::unordered_map<std::string, std::shared_ptr<EntityData>> entityData;

View file

@ -11,7 +11,7 @@ class Script {
public:
sol::state lua;
Script(const std::string& path);
virtual ~Script() {};
~Script();
private:
bool loadScript(const std::string& path) {
auto result = lua.script_file(path);
@ -29,6 +29,8 @@ class AIScript : public Script {
public:
AIScript(const std::string& path) : Script(path) { registerUserTypes(); }
~AIScript();
private:
void registerUserTypes() ;
@ -37,8 +39,9 @@ private:
class WeaponScript : public Script {
public:
WeaponScript(const std::string& path) : Script(path) { registerUserTypes(); }
~WeaponScript();
private:
void registerUserTypes() ;
};
#endif // _H_SCRIPT_H
#endif // _H_SCRIPT_H

View file

@ -72,13 +72,12 @@ struct SceneData {
};
struct WeaponData {
std::string name;
std::string id;
float fireSpeed = 250.0f;
int clipSize = 21;
int maxAmmo = 512;
std::string script = "";
std::string graphic;
std::string animPrefix;
bool animated = false;
float sizeX = 50.f, sizeY = 50.f;
float offsetX = 0.f, offsetY = 0.f;
@ -118,6 +117,7 @@ public:
bool loadAnimations(const char* animationFolder);
bool loadTileSets(const char* tileSetFolder);
bool loadMaps(const char* mapFolder);
bool loadSoundEffects(const char* soundFolder);
const SceneData* getSceneData(const std::string& id) const {
try {
@ -137,9 +137,9 @@ public:
}
}
const WeaponData* getWeaponData(const std::string& name) const {
const WeaponData* getWeaponData(const std::string& id) const {
try {
return weapons.at(name).get();
return weapons.at(id).get();
}
catch (std::exception&) {
return nullptr;
@ -164,6 +164,15 @@ public:
}
}
const SoundData* getSoundData(const std::string& id) const {
try {
return sounds.at(id).get();
}
catch (std::exception&) {
return nullptr;
}
}
// return a full set of animations, may need further optimization.
// one idea is when loading animations we create a seperate map that holds each set by their reference, so we can just do a simple,
// hash table lookup.
@ -189,6 +198,7 @@ private:
std::unordered_map<std::string, std::unique_ptr<AnimationData>> animations;
std::unordered_map<std::string, std::unique_ptr<MapData>> maps;
std::unordered_map<std::string, std::unique_ptr<TileSetData>> tileSets;
std::unordered_map<std::string, std::unique_ptr<SoundData>> sounds;
};
#endif // _H_XMLLOADER_H

134
YuppleMayham/src/gameplay/' Normal file
View file

@ -0,0 +1,134 @@
#include "gameplay/game.h"
#include "gameplay/input.h"
#include "gameplay/scene.h"
/*due for possible removal!*/
#include "gameplay/gameactor.h"
#include "gameplay/weapons/weapon.h"
/*-------------------------*/
#include "utility/command.h"
#include "utility/resourcemanager.h"
#include "utility/ftfont.h"
#include "utility/logger.h"
#include "graphics/glwindow.h"
#include "sound/engine.h"
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
#include <utility/events.h>
bool Game::init()
{
window = std::make_shared<GLWindow>("Yupple Mayham", 800, 600);
if (!window->Init())
ERROR_LOG("Failed to init GLWindow: {}", SDL_GetError());
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
ERROR_LOG("Failed to load GLLoader", NULL);
#if _DEBUG
LOG_LEVEL(DEBUG);
#else
LOG_LEVEL(INFO);
#endif
SDL_GL_SetSwapInterval(1);
glViewport(0, 0, window->width(), window->height());
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// For now we'll init default bindings, but as we move forward controls will be set by the player and saved in controls.xml
inputHandler = std::make_shared<InputHandler>();
inputHandler->bindKeyCommand(SDLK_w, 0, new MoveUpCommand());
inputHandler->bindKeyCommand(SDLK_a, 0, new MoveLeftCommand());
inputHandler->bindKeyCommand(SDLK_s, 0, new MoveDownCommand());
inputHandler->bindKeyCommand(SDLK_d, 0, new MoveRightCommand());
inputHandler->bindMouseCommand(MOUSE_BUTTON_LEFT, 100, new ShootCommand());
inputHandler->bindMouseMotion(new FollowMouseCommand());
inputHandler->bindMouseScroll(new CycleCommand());
game_state |= GAME_RUNNING;
globalEventManager = std::make_shared<EventManager>();
resourceManager = std::make_shared<ResourceManager>();
renderer = std::make_shared<Renderer>(resourceManager);
audioEngine = std::make_shared<AudioEngine>(resourceManager);
audioEngine->hookEventManager(globalEventManager);
/* Testing */
audioEngine->pushMusic("music/short_song.ogg");
audioEngine->pushMusic("music/bright.ogg");
audioEngine->pushMusic("music/main_song.ogg");
audioEngine->playMusic();
/* */
renderer->hookEventManager(globalEventManager);
textHandler = std::make_shared<Text>();
if (!textHandler->loadFonts("fonts"))
return false;
return true;
}
const unsigned Game::getWindowWidth() const { return window->width(); }
const unsigned Game::getWindowHeight() const { return window->height(); }
bool Game::loadDebugScene()
{
currentScene = std::make_shared<Scene>(SCENE_SHOOTER, resourceManager, globalEventManager);
currentScene->init();
audioEngine->hookSceneManager(currentScene->getEventManager());
if (currentScene->getPlayer() == nullptr)
return false;
inputHandler->setActor(currentScene->getPlayer().get());
return true;
}
void Game::captureInput(SDL_Event& e)
{
inputHandler->captureInput(e);
}
void Game::executeInputs()
{
inputHandler->handleInput();
inputHandler->executeCommands();
}
void Game::update(double deltaTime)
{
if (currentScene)
currentScene->update(deltaTime);
audioEngine->poll();
}
void Game::render()
{
glClearColor(0.05f, 0.25f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (currentScene)
{
currentScene->render(renderer);
/*Debug ammo indicator*/
textHandler->DrawText("comic.ttf", std::to_string(currentScene->getPlayer()->getHeldWeapon()->getMagazine()), glm::vec2(10, 10), 0.5f);
textHandler->DrawText("comic.ttf", "/", glm::vec2(50, 10), 0.5f);
textHandler->DrawText("comic.ttf", std::to_string(currentScene->getPlayer()->getHeldWeapon()->getAmmo()), glm::vec2(90, 10), 0.5f);
}
window->swap();
}
void Game::quit()
{
game_state = GAME_QUITTING;
}
Game::~Game()
{
if (audioEngine) {
audioEngine->killMusic();
}
resourceManager->clearResources();
}

View file

@ -4,18 +4,18 @@
#include "utility/raycaster.h"
#include "utility/script.h"
AI::AI(const std::shared_ptr<GameActor>& actor, const std::shared_ptr<Raycaster>& raycaster)
: state(AIState::Idle), raycaster(raycaster), actor(actor),
AI::AI(GameActor* actor, std::unique_ptr<Raycaster> raycaster)
: state(AIState::Idle), raycaster(std::move(raycaster)), actor(actor),
lastGCTime(std::chrono::high_resolution_clock::now()), GCTimeout(3)
{}
void AI::attachBehaviourScript(const std::shared_ptr<AIScript>& behaviour)
void AI::attachBehaviourScript(std::unique_ptr<AIScript> behaviour)
{
// passing out instance of raycaster and this AI into our scripting api
// pay special attention each ai script has control of only their own instance of ai!
this->behaviour = behaviour;
this->behaviour->lua["raycaster"] = raycaster;
this->behaviour->lua["ai"] = sol::make_reference(this->behaviour->lua, shared_from_this());
this->behaviour = std::move(behaviour);
this->behaviour->lua["raycaster"] = sol::make_reference(this->behaviour->lua, raycaster.get());
this->behaviour->lua["ai"] = sol::make_reference(this->behaviour->lua, this);
idleFunc = this->behaviour->lua["idle"];
patrolFunc = this->behaviour->lua["patrol"];
@ -25,41 +25,43 @@ void AI::attachBehaviourScript(const std::shared_ptr<AIScript>& behaviour)
void AI::update()
{
try {
switch (state)
{
case AIState::Idle:
if (idleFunc.valid())
if (actor && target) {
switch (state)
{
auto result = idleFunc(actor, target);
if (!result.valid())
case AIState::Idle:
if (idleFunc.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
auto result = idleFunc(actor, target);
if (!result.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
}
}
}
break;
case AIState::Patrol:
if (patrolFunc.valid())
{
auto result = patrolFunc(actor, target);
if (!result.valid())
break;
case AIState::Patrol:
if (patrolFunc.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
auto result = patrolFunc(actor, target);
if (!result.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
}
}
}
break;
case AIState::Alert:
if (alertFunc.valid())
{
auto result = alertFunc(actor, target);
if (!result.valid())
break;
case AIState::Alert:
if (alertFunc.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
auto result = alertFunc(actor, target);
if (!result.valid())
{
sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl;
}
}
break;
}
break;
}
std::chrono::high_resolution_clock::time_point curTime = std::chrono::high_resolution_clock::now();
if (curTime - lastGCTime >= GCTimeout)
@ -73,3 +75,13 @@ void AI::update()
state = AIState::Idle;
}
}
AI::~AI()
{
behaviour->lua["raycaster"] = sol::nil;
behaviour->lua["ai"] = sol::nil;
behaviour->lua["idle"] = sol::nil;
behaviour->lua["patrol"] = sol::nil;
behaviour->lua["alert"] = sol::nil;
behaviour->lua.collect_gc();
}

View file

@ -17,6 +17,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
#include <thread>
#include <utility/events.h>
bool Game::init()
@ -43,14 +44,14 @@ bool Game::init()
// For now we'll init default bindings, but as we move forward controls will be set by the player and saved in controls.xml
inputHandler = std::make_shared<InputHandler>();
inputHandler->bindKeyCommand(SDLK_w, 0, new MoveUpCommand());
inputHandler->bindKeyCommand(SDLK_a, 0, new MoveLeftCommand());
inputHandler->bindKeyCommand(SDLK_s, 0, new MoveDownCommand());
inputHandler->bindKeyCommand(SDLK_d, 0, new MoveRightCommand());
inputHandler->bindKeyCommand(SDLK_w, 0, std::make_unique<MoveUpCommand>());
inputHandler->bindKeyCommand(SDLK_a, 0, std::make_unique<MoveLeftCommand>());
inputHandler->bindKeyCommand(SDLK_s, 0, std::make_unique<MoveDownCommand>());
inputHandler->bindKeyCommand(SDLK_d, 0, std::make_unique<MoveRightCommand>());
inputHandler->bindMouseCommand(MOUSE_BUTTON_LEFT, 100, new ShootCommand());
inputHandler->bindMouseMotion(new FollowMouseCommand());
inputHandler->bindMouseScroll(new CycleCommand());
inputHandler->bindMouseCommand(MOUSE_BUTTON_LEFT, 100, std::make_unique<ShootCommand>());
inputHandler->bindMouseMotion(std::make_unique<FollowMouseCommand>());
inputHandler->bindMouseScroll(std::make_unique<CycleCommand>());
game_state |= GAME_RUNNING;
globalEventManager = std::make_shared<EventManager>();
@ -78,6 +79,7 @@ bool Game::loadDebugScene()
{
currentScene = std::make_shared<Scene>(SCENE_SHOOTER, resourceManager, globalEventManager);
currentScene->init();
audioEngine->hookSceneManager(currentScene->getEventManager());
if (currentScene->getPlayer() == nullptr)
return false;
inputHandler->setActor(currentScene->getPlayer().get());
@ -97,8 +99,12 @@ void Game::executeInputs()
void Game::update(double deltaTime)
{
if (currentScene)
if (currentScene) {
currentScene->update(deltaTime);
if (auto player = currentScene->getPlayer())
audioEngine->updateListener(player->getPosition());
}
audioEngine->poll();
}
void Game::render()
@ -121,12 +127,12 @@ void Game::render()
void Game::quit()
{
game_state = GAME_QUITTING;
if (audioEngine) {
audioEngine->killMusic();
}
}
Game::~Game()
{
if (audioEngine) {
audioEngine->killMusic();
}
resourceManager->clearResources();
}

View file

@ -16,7 +16,7 @@ void InputHandler::handleInput()
{
if (currentTime - command.lastExecution >= command.delay)
{
commandQueue.push_back(command.cmd);
commandQueue.push_back(command.cmd.get());
command.lastExecution = currentTime;
}
}
@ -28,7 +28,7 @@ void InputHandler::handleInput()
{
if (currentTime - command.lastExecution >= command.delay)
{
mouseQueue.push_back(command.cmd);
mouseQueue.push_back(command.cmd.get());
command.lastExecution = currentTime;
}
}
@ -52,3 +52,7 @@ void InputHandler::executeCommands()
commandQueue.clear();
mouseQueue.clear();
}
InputHandler::~InputHandler()
{
}

View file

@ -11,6 +11,7 @@
#include "graphics/animation.h"
#include "graphics/background.h"
#include "utility/script.h"
#include "utility/component.h"
#include "utility/ftfont.h"
#include "utility/xmlloader.h"
@ -88,7 +89,7 @@ void Scene::loadDebugShooterScene()
auto entitySprite = resourceManager->loadSpriteStatic(entityData.graphic);
entity->addComponent(std::make_unique<SpriteComponent>(entitySprite));
}
auto defaultWeapon = resourceManager->loadWeapon("pistol", weaponShader, bubbleShader);
auto defaultWeapon = resourceManager->loadWeapon("gun/pistol", weaponShader, bubbleShader);
auto entityWeapon = resourceManager->loadWeapon(entityData.weapon, weaponShader, bubbleShader);
entity->pickupWeapon(defaultWeapon);
@ -115,11 +116,11 @@ void Scene::loadDebugShooterScene()
// attach ai
if (!entityData.script.empty())
{
auto behaviourScript = resourceManager->loadAIScript(entityData.script);
auto rayCaster = std::make_shared<Raycaster>(40.f, 300.f, map->getCollisionMap(), mapData->tileSize);
auto ai = std::make_shared<AI>(entity, rayCaster);
ai->setTarget(player);
ai->attachBehaviourScript(behaviourScript);
auto behaviour = resourceManager->loadAIScript(entityData.script);
auto rayCaster = std::make_unique<Raycaster>(40.f, 300.f, map->getCollisionMap(), mapData->tileSize);
auto ai = std::make_shared<AI>(entity.get(), std::move(rayCaster));
ai->setTarget(player.get());
ai->attachBehaviourScript(std::move(behaviour));
entity->addComponent(std::make_unique<AIComponent>(ai));
}
}
@ -173,6 +174,7 @@ void Scene::render(std::shared_ptr<Renderer> renderer)
}
renderer->render();
/*
for (const auto& bullet : player->getHeldWeapon()->getBulletManager()->getBullets()) {
DebugDrawer::getInstance().addLine(player->getCenter(), bullet->getCenter(), glm::vec4(1.f, 0.f, 0.f, 0.f));
DEBUG_TEXT(
@ -203,6 +205,7 @@ void Scene::render(std::shared_ptr<Renderer> renderer)
bullet->getPhysicsComponent()->rigidBody.velocity.x,
bullet->getPhysicsComponent()->rigidBody.velocity.y);
}
*/
DebugDrawer::getInstance().draw(camera->getProjectionMatrix() * camera->getViewMatrix());
}

View file

@ -17,7 +17,7 @@
Weapon::Weapon(const WeaponData* data, const unsigned weaponShaderID, const unsigned bulletShaderID, ResourceManager* resourceManager)
:
Entity (weaponShaderID),
weaponType (data->name),
weaponType (data->id),
weaponSize (glm::vec2(data->sizeX, data->sizeY)),
weaponOffset (glm::vec2(data->offsetX, data->offsetY)),
weaponMag (data->clipSize),
@ -39,7 +39,7 @@ Weapon::Weapon(const WeaponData* data, const unsigned weaponShaderID, const unsi
if (data->animated)
{
addComponent(std::make_unique<AnimationComponent>(resourceManager->loadAnimationSet(data->graphic, entityid)));
addComponent(std::make_unique<AnimationComponent>(resourceManager->loadAnimationSet(data->id, entityid)));
}
else
addComponent(std::make_unique<SpriteComponent>(resourceManager->loadSpriteStatic(data->graphic)));
@ -79,7 +79,7 @@ bool Weapon::shoot()
{
shotsFired = true;
if (auto event = eventManager.lock())
event->notify<EntityFireEvent>((EntityFireEvent){entityid, fireSpeed, wielder->getPosition(), weaponType});
event->notify<EntityFireEvent>({entityid, fireSpeed, wielder->getPosition(), weaponType});
if (!weaponScript || !weaponScript->lua["onShoot"].valid())
{
// create bullet using this generated data
@ -137,10 +137,10 @@ void Weapon::hookEventManager(std::weak_ptr<EventManager> eventManager)
bulletManager->hookEventManager(eventManager);
}
void Weapon::attachScript(const std::shared_ptr<WeaponScript>& script)
void Weapon::attachScript(std::unique_ptr<WeaponScript> script)
{
weaponScript = script;
weaponScript->lua["weapon"] = shared_from_this();
weaponScript = std::move(script);
weaponScript->lua["weapon"] = this;
LOG(DEBUG, "Weapon state bound", NULL);
}
@ -262,6 +262,14 @@ void Weapon::createBullet(const Weapon::BulletData& data)
bulletManager->addBullet(bullet);
}
Weapon::~Weapon()
{
if (weaponScript) {
weaponScript->lua["onShoot"] = sol::nil;
weaponScript->lua.collect_gc();
}
}
/*
!| SLATED FOR REMOVAL |!

View file

@ -124,3 +124,14 @@ void TileTextureInstance::draw()
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr, numOfInstances);
glBindVertexArray(0);
}
TileTextureInstance::~TileTextureInstance()
{
if (texture)
delete texture;
else if (textures)
delete textures;
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteVertexArrays(1, &VAO);
}

View file

@ -124,7 +124,7 @@ bool TextureArray::adjustCanvasSizes(std::vector<SDL_Surface*>& surfaces)
if (surface->w > maxWidth) maxWidth = surface->w;
if (surface->h > maxHeight) maxHeight = surface->h;
textures.push_back(new TextureData({ surface->w, surface->h }));
textures.push_back(TextureData({ surface->w, surface->h }));
}
for (int i = 0; i < surfaces.size(); ++i)
{

View file

@ -9,7 +9,7 @@ AudioStream::AudioStream()
eof = true;
paused = true;
streamThread = std::thread(&AudioStream::stream, this);
streamThread.detach();
//streamThread.detach();
}
std::string AudioStream::CurStreamName()
@ -153,7 +153,7 @@ void AudioStream::stream()
if (state != AL_PLAYING) {
alSourcePlay(source);
}
while (state == AL_PLAYING) {
while (state == AL_PLAYING && !stopThread) {
if (paused) {
alSourcePause(source);
}
@ -161,8 +161,12 @@ void AudioStream::stream()
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
while (processed != 0) {
alSourceUnqueueBuffers(source, 1, &buffers[last_buffer]);
while (processed != 0 && !stopThread) {
ALuint unqueued;
alSourceUnqueueBuffers(source, 1, &unqueued);
for (int i = 0; i < 4; ++i)
if (unqueued == buffers[i])
last_buffer = i;
int samples = loadNextChunk();
if (samples == 0) {
LOG(INFO, "Hit the end of the song!", NULL);
@ -176,7 +180,7 @@ void AudioStream::stream()
}
//LOG(INFO, "Buffer index: {}", last_buffer);
alSourceQueueBuffers(source, 1, &buffers[last_buffer]);
last_buffer = (last_buffer + 1) % 4;
//last_buffer = (last_buffer + 1) % 4;
processed--;
}
}
@ -185,7 +189,12 @@ void AudioStream::stream()
AudioStream::~AudioStream()
{
stopThread = true;
if (streamThread.joinable())
streamThread.join();
stb_vorbis_close(file);
std::free(data);
alDeleteBuffers(4, buffers);
alSourcei(source, AL_BUFFER, 0);
alDeleteSources(1, &source);
alDeleteBuffers(4, buffers);
}

View file

@ -1,6 +1,7 @@
#include "sound/engine.h"
#include <AL/al.h>
#include <AL/alc.h>
#include <glm/gtc/type_ptr.hpp>
AudioEngine::AudioEngine(std::weak_ptr<ResourceManager> _resource)
{
@ -12,6 +13,7 @@ AudioEngine::AudioEngine(std::weak_ptr<ResourceManager> _resource)
context = alcCreateContext(device, NULL);
alcMakeContextCurrent(context);
musicPlayer = std::make_unique<AudioStream>();
soundManager = std::make_unique<SoundManager>();
resourceManager = _resource;
alGetError();
}
@ -21,6 +23,34 @@ void AudioEngine::hookEventManager(std::weak_ptr<EventManager> _events)
globalEventManager = _events;
}
void AudioEngine::hookSceneManager(std::weak_ptr<EventManager> _events)
{
sceneEventManager = _events;
if (auto weak = sceneEventManager.lock()) {
std::weak_ptr<AudioEngine> weakSelf = shared_from_this();
weak->subscribe<EntityFireEvent>([weakSelf](const EntityFireEvent& e){
if (auto self = weakSelf.lock()) {
if (auto res = self->resourceManager.lock()) {
auto buf = res->loadSoundEffect(e.weaponType + "/shoot");
if (buf != 0) {
self->soundManager->playSound(buf, 10, e.firePosition);
}
}
}
});
}
}
void AudioEngine::poll()
{
soundManager->pollSources();
}
void AudioEngine::updateListener(const glm::vec3& pos)
{
alListenerfv(AL_POSITION, glm::value_ptr(pos));
}
void AudioEngine::pushMusic(std::string _songName)
{
LOG(INFO, "Loading song {}", _songName);

View file

@ -10,7 +10,9 @@ SoundEffect::SoundEffect(const std::string& filename)
// load the whole file into a buffer, this should only be used for small sound effects. Never with music or sounds exceeding 10MB!
bool SoundEffect::loadFile(const std::string& filename)
{
short *data = (short *)std::malloc(UTIL::AUDIO::CHUNK_SIZE * sizeof(short) * 2);
short *data;
//std::unique_ptr<short[]> data((short*)new short[UTIL::AUDIO::CHUNK_SIZE * sizeof(short) * 2]());
//std::vector<short> data(UTIL::AUDIO::CHUNK_SIZE * sizeof(short) * 2);
int channels, sample_rate, samples;
samples = stb_vorbis_decode_filename(filename.c_str(), &channels, &sample_rate, &data);

View file

@ -0,0 +1,103 @@
#include "sound/soundmanager.h"
#include <AL/al.h>
#include <glm/gtc/type_ptr.hpp>
SoundManager::SoundManager()
{
for (auto& s : sources) {
alGenSources(1, &s.source);
s.inUse = false;
s.priority = 0;
alSourcef(s.source, AL_REFERENCE_DISTANCE, 1.0f);
alSourcef(s.source, AL_MAX_DISTANCE, 10000.f);
alSourcef(s.source, AL_ROLLOFF_FACTOR, 1.0f);
}
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
}
void SoundManager::playSound(ALuint buffer, int priority, const glm::vec3& pos)
{
std::size_t ring = lastUsed;
AudioSource* nextBest = nullptr;
std::size_t secIndex = 0;
auto nextIndex = [](std::size_t i){ return (i + 1) % 10; };
if (!sources[ring].inUse) {
sources[ring].inUse = true;
sources[ring].priority = priority;
alSourcei(sources[ring].source, AL_BUFFER, buffer);
if (pos != glm::vec3(0)) {
//alSourcei(sources[ring].source, AL_SOURCE_RELATIVE, 1);
alSourcefv(sources[ring].source, AL_POSITION, glm::value_ptr(pos));
} else {
//alSourcei(sources[ring].source, AL_SOURCE_RELATIVE, 0);
}
alSourcePlay(sources[ring].source);
lastUsed = nextIndex(lastUsed);
return;
}
for (ring = nextIndex(lastUsed); ring != lastUsed; ring = nextIndex(ring)) {
if (!sources[ring].inUse) {
sources[ring].inUse = true;
sources[ring].priority = priority;
alSourcei(sources[ring].source, AL_BUFFER, buffer);
if (pos != glm::vec3(0)) {
//alSourcei(sources[ring].source, AL_SOURCE_RELATIVE, 1);
alSourcefv(sources[ring].source, AL_POSITION, glm::value_ptr(pos));
} else {
//alSourcei(sources[ring].source, AL_SOURCE_RELATIVE, 0);
}
alSourcePlay(sources[ring].source);
lastUsed = ring;
return;
}
if (sources[ring].priority <= priority) {
if (nextBest) {
if (nextBest->priority > sources[ring].priority) {
nextBest = &sources[ring];
secIndex = ring;
}
} else {
nextBest = &sources[ring];
secIndex = ring;
}
}
}
if (nextBest) {
nextBest->priority = priority;
alSourcei(nextBest->source, AL_BUFFER, buffer);
if (pos != glm::vec3(0)) {
//alSourcei(nextBest->source, AL_SOURCE_RELATIVE, 1);
alSourcefv(nextBest->source, AL_POSITION, glm::value_ptr(pos));
} else {
//alSourcei(nextBest->source, AL_SOURCE_RELATIVE, 0);
}
alSourcePlay(nextBest->source);
lastUsed = secIndex;
}
}
void SoundManager::pollSources()
{
for (auto& s : sources) {
if (s.inUse) {
ALint processed, state;
alGetSourcei(s.source, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(s.source, AL_SOURCE_STATE, &state);
if (state == AL_STOPPED) {
s.inUse = false;
alSourcei(s.source, AL_BUFFER, 0);
}
//else if (state != AL_PLAYING)
// alSourcePlay(s.source);
}
}
}
SoundManager::~SoundManager()
{
for (auto& s : sources) {
alSourcei(s.source, AL_BUFFER, 0);
alDeleteSources(1, &s.source);
}
}

View file

@ -1,5 +1,6 @@
#include "utility/resourcemanager.h"
#include "graphics/sprite.h"
#include "util.h"
#include "utility/script.h"
#include "graphics/shader.h"
#include "graphics/animation.h"
@ -29,14 +30,14 @@ Sprite* ResourceManager::loadSpriteStatic(const std::string& path)
return sprites[path].get();
}
std::shared_ptr<AIScript> ResourceManager::loadAIScript(const std::string& path)
std::unique_ptr<AIScript> ResourceManager::loadAIScript(const std::string& path)
{
return std::make_shared<AIScript>(path.c_str());
return std::make_unique<AIScript>(path.c_str());
}
std::shared_ptr<WeaponScript> ResourceManager::loadWeaponScript(const std::string& path)
std::unique_ptr<WeaponScript> ResourceManager::loadWeaponScript(const std::string& path)
{
return std::make_shared<WeaponScript>(path.c_str());
return std::make_unique<WeaponScript>(path.c_str());
}
const TileSetData* ResourceManager::loadTileSet(const std::string& name)
@ -77,15 +78,36 @@ Background* ResourceManager::loadBackground(const std::string& path)
// We attach our script after we create our weapon because we are passing a reference to the weapon into the script and we don't want to pass an
// incomplete reference to our script.
std::shared_ptr<Weapon> ResourceManager::loadWeapon(const std::string& name, const unsigned weaponShaderID, const unsigned bulletShaderID)
std::shared_ptr<Weapon> ResourceManager::loadWeapon(const std::string& id, const unsigned weaponShaderID, const unsigned bulletShaderID)
{
const WeaponData* data = xmlLoader->getWeaponData(name);
const WeaponData* data = xmlLoader->getWeaponData(id);
if (!data) {
LOG(ERROR, "Could not load weapon id '{}', falling back to pistol", id);
data = xmlLoader->getWeaponData("gun/pistol");// using this as a fallback for now
}
auto weapon = std::make_shared<Weapon>(data, weaponShaderID, bulletShaderID, this);
if (!data->script.empty())
weapon->attachScript(loadWeaponScript(data->script));
return weapon;
}
const unsigned ResourceManager::loadSoundEffect(const std::string& id)
{
auto iterator = sounds.find(id);
if (iterator != sounds.end())
return iterator->second->getBuffer();
auto soundData = xmlLoader->getSoundData(id) ;
if (!soundData) {
soundData = xmlLoader->getSoundData(UTIL::get_generic(id));
LOG(WARN, "Could not load sound id: '{}' trying generic", id);
if (!soundData)
return 0;
}
auto sound = std::make_unique<SoundEffect>(soundData->path);
sounds[id] = std::move(sound);
return sounds[id]->getBuffer();
}
const SceneData* ResourceManager::loadScene(const std::string& id)
{
return xmlLoader->getSceneData(id);

View file

@ -108,3 +108,20 @@ Script::Script(const std::string& path)
loadScript(path);
}
Script::~Script()
{
lua.collect_garbage();
}
AIScript::~AIScript()
{
lua.collect_garbage();
lua = sol::state{};
}
WeaponScript::~WeaponScript()
{
lua.collect_gc();
lua = sol::state{};
}

View file

@ -1,6 +1,7 @@
#include "utility/xmlloader.h"
#include "utility/logger.h"
#include <filesystem>
#include <tinyxml2.h>
/*
@ -181,9 +182,9 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
{
// populate weapon data into this temp buffer, then push it into our list of weapons
WeaponData data;
const char *name, *graphic, *bulletGraphic, *script;
const char *id, *graphic, *bulletGraphic, *script;
// Getting top level weapon data, attribs in the weapon Node
if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS)
if (weapon->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS)
continue;
weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed);
weapon->QueryIntAttribute("maxAmmo", &data.maxAmmo);
@ -199,9 +200,6 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
if (anim)
{
data.animated = true;
if (anim->QueryStringAttribute("id", &graphic) != tinyxml2::XML_SUCCESS)
continue;
if (anim->ChildElementCount() != 0)
{
tinyxml2::XMLElement* size = anim->FirstChildElement("size");
@ -251,9 +249,8 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS)
continue;
data.name = name;
data.id = id;
data.script = script;
data.graphic = graphic;
data.bulletGraphic = bulletGraphic;
tinyxml2::XMLElement* spread = bullet->FirstChildElement("spread");
@ -272,8 +269,8 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
modifier->QueryFloatAttribute("max", &data.modMax);
}
LOG(DEBUG, "Loaded {} from {}", data.name, file.path().filename().generic_string());
weapons.try_emplace(data.name, std::make_unique<WeaponData>(data));
LOG(DEBUG, "Loaded {} from {}", data.id, file.path().filename().generic_string());
weapons.try_emplace(data.id, std::make_unique<WeaponData>(data));
}
}
return (!weapons.empty());
@ -584,3 +581,35 @@ bool XMLLoader::loadMaps(const char* mapFolder)
}
return true;
}
bool XMLLoader::loadSoundEffects(const char *soundFolder)
{
std::filesystem::path folder(soundFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder))
ERROR_LOG("Directory '{}' does not exist!", soundFolder);
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
continue;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().string().c_str()) != tinyxml2::XML_SUCCESS)
continue;
for (tinyxml2::XMLElement *e = doc.FirstChildElement("sound"); e != NULL; e = e->NextSiblingElement("sound")) {
SoundData sound;
const char *id, *path;
if (e->QueryStringAttribute("id", &id) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("path", &path) != tinyxml2::XML_SUCCESS)
continue;
e->QueryBoolAttribute("spatial", &sound.spatial);
sound.id = id;
sound.path = path;
sounds.try_emplace(sound.id, std::make_unique<SoundData>(sound));
}
}
return true;
}