- redesign audio library to use Chunk and Music classes which align more closely with SDL Mix_Chunk and Mix_Music
- remove default texture index from Model class - add support for two textures to Pad class - restore SDL audio mixer initialization parameters to SDL defaults - add optional log level to SDL error logging function
This commit is contained in:
parent
5046b4bcf1
commit
d575307b15
237
src/Audio.cpp
237
src/Audio.cpp
|
@ -1,102 +1,138 @@
|
|||
#include "Audio.hpp"
|
||||
|
||||
BGM::BGM(Node* parent) : Node(parent) {}
|
||||
using namespace sb::audio;
|
||||
|
||||
BGM::BGM() : BGM(nullptr) {}
|
||||
|
||||
BGM::BGM(Node* parent, const fs::path& path) : BGM(parent)
|
||||
Chunk::Chunk(const fs::path& path)
|
||||
{
|
||||
load_music(path);
|
||||
load(path);
|
||||
}
|
||||
|
||||
BGM::BGM(const fs::path& path) : BGM(nullptr, path) {}
|
||||
|
||||
void BGM::load_music(const fs::path& path)
|
||||
void Chunk::load(const fs::path& path)
|
||||
{
|
||||
music = Mix_LoadMUS(path.c_str());
|
||||
chunk = std::shared_ptr<Mix_Chunk>(Mix_LoadWAV(path.c_str()), Mix_FreeChunk);
|
||||
if (chunk.get() == nullptr)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Unable to load audio chunk at " << path << ".";
|
||||
Log::sdl_error(message.str(), Log::Level::WARN);
|
||||
}
|
||||
}
|
||||
|
||||
void BGM::set_volume(float v)
|
||||
std::uint8_t Chunk::convert_volume(float volume)
|
||||
{
|
||||
volume = v;
|
||||
return std::clamp(static_cast<int>(std::round(volume * MIX_MAX_VOLUME)), 0, MIX_MAX_VOLUME);
|
||||
}
|
||||
|
||||
void BGM::play(int loops)
|
||||
float Chunk::volume() const
|
||||
{
|
||||
Mix_VolumeMusic(std::round(volume * 127));
|
||||
Mix_PlayMusic(music, loops);
|
||||
std::uint8_t sdl_volume = Mix_VolumeChunk(chunk.get(), -1);
|
||||
return static_cast<float>(sdl_volume) / static_cast<float>(MIX_MAX_VOLUME);
|
||||
}
|
||||
|
||||
BGM::~BGM()
|
||||
void Chunk::volume(float level)
|
||||
{
|
||||
Mix_FreeMusic(music);
|
||||
Mix_VolumeChunk(chunk.get(), convert_volume(level));
|
||||
}
|
||||
|
||||
SoundEffect::SoundEffect(Node* parent) : Node(parent) {}
|
||||
|
||||
SoundEffect::SoundEffect() : SoundEffect(nullptr) {}
|
||||
|
||||
SoundEffect::SoundEffect(Node* parent, const fs::path& path) : SoundEffect(parent)
|
||||
void Chunk::channel_volume(float volume)
|
||||
{
|
||||
load_chunk(path);
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel) == chunk.get())
|
||||
{
|
||||
Mix_Volume(channel, convert_volume(volume));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoundEffect::SoundEffect(const fs::path& path) : SoundEffect(nullptr, path) {}
|
||||
|
||||
void SoundEffect::load_chunk(const fs::path& path)
|
||||
{
|
||||
chunk = Mix_LoadWAV(path.c_str());
|
||||
Mix_VolumeChunk(chunk, std::round(volume * 127));
|
||||
}
|
||||
|
||||
void SoundEffect::play(float location)
|
||||
{
|
||||
play();
|
||||
Box window = window_box();
|
||||
location = std::clamp(location, window.left(), window.right());
|
||||
float location_relative = (location - window.left()) / window.width();
|
||||
int angle = 270 - location_relative * 180;
|
||||
Mix_SetPosition(channel, angle, 0);
|
||||
}
|
||||
|
||||
void SoundEffect::play()
|
||||
{
|
||||
channel = Mix_PlayChannel(-1, chunk, loops);
|
||||
}
|
||||
|
||||
void SoundEffect::play_after(float delay)
|
||||
{
|
||||
play_animation.play_once(delay);
|
||||
}
|
||||
|
||||
void SoundEffect::set_volume(float v)
|
||||
{
|
||||
volume = v;
|
||||
Mix_VolumeChunk(chunk, std::round(volume * 127));
|
||||
}
|
||||
|
||||
void SoundEffect::set_loop_count(int count)
|
||||
{
|
||||
loops = count;
|
||||
}
|
||||
|
||||
void SoundEffect::set_loop_forever()
|
||||
void Chunk::loop()
|
||||
{
|
||||
loops = -1;
|
||||
}
|
||||
|
||||
bool SoundEffect::playing(bool include_delay) const
|
||||
void Chunk::loop(int count)
|
||||
{
|
||||
if (include_delay)
|
||||
loops = count;
|
||||
}
|
||||
|
||||
int Chunk::play(float fade, int channel)
|
||||
{
|
||||
/* Play the audio with a fade in time if any was specified. */
|
||||
int assignment;
|
||||
if (fade <= 0.0f)
|
||||
{
|
||||
if (play_animation.playing())
|
||||
assignment = Mix_PlayChannel(channel, chunk.get(), loops);
|
||||
}
|
||||
else
|
||||
{
|
||||
int milliseconds = static_cast<int>(std::round(fade * 1000.0f));
|
||||
assignment = Mix_FadeInChannel(channel, chunk.get(), loops, milliseconds);
|
||||
}
|
||||
|
||||
/* Check if the audio is paused on other channels. If so, stop the audio on those channels. */
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
if (index != assignment && Mix_GetChunk(index) == chunk.get() && Mix_Paused(index))
|
||||
{
|
||||
return true;
|
||||
Mix_HaltChannel(index);
|
||||
}
|
||||
}
|
||||
for (int channel_ii = 0; channel_ii < Mix_GroupAvailable(-1); channel_ii++)
|
||||
|
||||
return assignment;
|
||||
}
|
||||
|
||||
void Chunk::stop(float fade)
|
||||
{
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel_ii) == chunk)
|
||||
if (Mix_GetChunk(channel) == chunk.get())
|
||||
{
|
||||
if (fade <= 0.0f)
|
||||
{
|
||||
Mix_HaltChannel(channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
int milliseconds = static_cast<int>(std::round(fade * 1000.0f));
|
||||
Mix_FadeOutChannel(channel, milliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chunk::pause()
|
||||
{
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel) == chunk.get())
|
||||
{
|
||||
Mix_Pause(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chunk::resume()
|
||||
{
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel) == chunk.get())
|
||||
{
|
||||
Mix_Resume(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Chunk::playing() const
|
||||
{
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel) == chunk.get() && Mix_Playing(channel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -104,32 +140,47 @@ bool SoundEffect::playing(bool include_delay) const
|
|||
return false;
|
||||
}
|
||||
|
||||
void SoundEffect::update(float timestamp)
|
||||
bool Chunk::paused() const
|
||||
{
|
||||
play_animation.update(timestamp);
|
||||
}
|
||||
|
||||
SoundEffect::~SoundEffect()
|
||||
{
|
||||
Mix_FreeChunk(chunk);
|
||||
}
|
||||
|
||||
Audio::Audio(Node* parent) : Node(parent) {}
|
||||
|
||||
void Audio::load_sfx()
|
||||
{
|
||||
load_directory(configuration()["audio"]["default-sfx-root"], sfx, this);
|
||||
}
|
||||
|
||||
void Audio::load_bgm()
|
||||
{
|
||||
load_directory(configuration()["audio"]["default-bgm-root"], bgm, this);
|
||||
}
|
||||
|
||||
void Audio::update(float timestamp)
|
||||
{
|
||||
for (auto& [name, sound_effect] : sfx)
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
sound_effect.update(timestamp);
|
||||
if (Mix_GetChunk(channel) == chunk.get() && Mix_Paused(channel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Chunk::fading() const
|
||||
{
|
||||
int count = Mix_GroupCount(-1);
|
||||
for (int channel = 0; channel < count; channel++)
|
||||
{
|
||||
if (Mix_GetChunk(channel) == chunk.get() && Mix_FadingChannel(channel))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Music::Music(const fs::path& path)
|
||||
{
|
||||
load(path);
|
||||
}
|
||||
|
||||
void Music::load(const fs::path& path)
|
||||
{
|
||||
music = std::shared_ptr<Mix_Music>(Mix_LoadMUS(path.c_str()), Mix_FreeMusic);
|
||||
if (music.get() == nullptr)
|
||||
{
|
||||
Log::sdl_error();
|
||||
}
|
||||
}
|
||||
|
||||
void Music::play(int loops)
|
||||
{
|
||||
Mix_PlayMusic(music.get(), loops);
|
||||
}
|
||||
|
|
230
src/Audio.hpp
230
src/Audio.hpp
|
@ -13,91 +13,163 @@
|
|||
#include <regex>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include "SDL.h"
|
||||
#include "SDL_mixer.h"
|
||||
#include "filesystem.hpp"
|
||||
#include "Node.hpp"
|
||||
#include "Animation.hpp"
|
||||
#include "extension.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
struct BGM : Node
|
||||
namespace sb::audio
|
||||
{
|
||||
|
||||
Mix_Music* music;
|
||||
float volume = 1.0f;
|
||||
|
||||
BGM();
|
||||
BGM(Node*);
|
||||
BGM(Node*, const fs::path&);
|
||||
BGM(const fs::path&);
|
||||
void load_music(const fs::path&);
|
||||
void set_volume(float);
|
||||
void play(int = -1);
|
||||
~BGM();
|
||||
|
||||
};
|
||||
|
||||
struct SoundEffect : Node
|
||||
{
|
||||
|
||||
Mix_Chunk* chunk = nullptr;
|
||||
int channel, loops = 0;
|
||||
float volume = 1.0f;
|
||||
|
||||
/* The cast is required so that the overloaded play function without arguments will be selected. */
|
||||
Animation play_animation = Animation(std::bind(static_cast<void(SoundEffect::*)()>(&SoundEffect::play), this));
|
||||
|
||||
SoundEffect(Node*);
|
||||
SoundEffect();
|
||||
SoundEffect(Node*, const fs::path&);
|
||||
SoundEffect(const fs::path&);
|
||||
void load_chunk(const fs::path&);
|
||||
void play(float);
|
||||
void play();
|
||||
void play_after(float);
|
||||
void set_volume(float);
|
||||
void set_loop_count(int);
|
||||
void set_loop_forever();
|
||||
bool playing(bool = false) const;
|
||||
void update(float timestamp);
|
||||
~SoundEffect();
|
||||
|
||||
};
|
||||
|
||||
struct Audio : Node
|
||||
{
|
||||
|
||||
std::map<std::string, SoundEffect> sfx;
|
||||
std::map<std::string, BGM> bgm;
|
||||
|
||||
Audio(Node*);
|
||||
void load_sfx();
|
||||
void load_bgm();
|
||||
void update(float timestamp);
|
||||
|
||||
template <typename T>
|
||||
void load_directory(const fs::path& root, std::map<std::string, T>& storage, Node* parent = nullptr)
|
||||
/*!
|
||||
* Load audio from an OGG or WAV file and play it on multiple channels.
|
||||
*
|
||||
* This class interfaces with SDL mixer's API and its `Mix_Chunk` audio data struct.
|
||||
*
|
||||
* There are some differences between how `Mix_Chunk` and `Mix_Music` can be used with the API, most notably that multiple chunks can be playing on
|
||||
* multiple channels simultaneously, but only one music object can be playing at a time. This is most likely because `Mix_Music` objects are streamed
|
||||
* and `Mix_Chunk` are loaded into memory, but I haven't verified that by looking at the internal code.
|
||||
*/
|
||||
class Chunk
|
||||
{
|
||||
SDL_Log("looking for audio files in %s", root.c_str());
|
||||
if (fs::exists(root))
|
||||
{
|
||||
for (const fs::path& path : sb::glob(root / ".*"))
|
||||
{
|
||||
std::regex pattern("([^.]+).*$");
|
||||
std::smatch match;
|
||||
std::string basename = path.filename();
|
||||
if (std::regex_match(basename, match, pattern))
|
||||
{
|
||||
std::string key = match[1].str();
|
||||
SDL_Log("loading %s as %s", path.c_str(), key.c_str());
|
||||
storage.try_emplace(key, parent, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<Mix_Chunk> chunk;
|
||||
|
||||
/* -1 means loop forever, any other value is the number of times to loop */
|
||||
int loops = 0;
|
||||
|
||||
/*!
|
||||
* Convert floating point volume between 0.0 and 1.0 to SDL 8-bit unsigned integer volume between 0 and `MIX_MAX_VOLUME`.
|
||||
*
|
||||
* @param volume Volume between 0.0 and 1.0
|
||||
*/
|
||||
static std::uint8_t convert_volume(float volume);
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Create an empty audio chunk.
|
||||
*/
|
||||
Chunk() = default;
|
||||
|
||||
/*!
|
||||
* Create an audio chunk and load it with data from an OGG or WAV file at the given path. If the given path does not contain loadable data,
|
||||
* a warning will be printed, and an empty chunk will be created.
|
||||
*
|
||||
* This will call Chunk::load(const fs::path&) automatically.
|
||||
*
|
||||
* @param path path to an OGG or WAV file
|
||||
*/
|
||||
Chunk(const fs::path& path);
|
||||
|
||||
/*!
|
||||
* Load audio data from an OGG or WAV file at the given path, replacing any existing audio data in this object. If the given path does not contain
|
||||
* loadable data, a warning will be printed, and existing audio data will not be overwritten.
|
||||
*
|
||||
* @param path path to an OGG or WAV file
|
||||
*/
|
||||
void load(const fs::path& path);
|
||||
|
||||
/*!
|
||||
* @return the volume of the audio chunk in the range 0.0 - 1.0
|
||||
*/
|
||||
float volume() const;
|
||||
|
||||
/*!
|
||||
* @param level set the volume of the chunk to a value between 0.0 and 1.0
|
||||
*/
|
||||
void volume(float level);
|
||||
|
||||
/*!
|
||||
* Set the volume of all channels currently playing the audio chunk to a given level. This does not modify the volume of the chunk itself.
|
||||
*
|
||||
* @param level Volume between 0.0 and 1.0
|
||||
*/
|
||||
void channel_volume(float level);
|
||||
|
||||
/*!
|
||||
* Set audio to loop forever.
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/*!
|
||||
* @param count Number of times audio should loop when played
|
||||
*/
|
||||
void loop(int count);
|
||||
|
||||
/*!
|
||||
* Play the audio data loaded into this object once on the first available free channel. Optionally fade in playback over a given amount of time
|
||||
* in seconds.
|
||||
*
|
||||
* This always plays the audio from the beginning even if the audio is paused. To resume instead, use Chunk::resume(). Any audio chunks currently
|
||||
* paused will be stopped.
|
||||
*
|
||||
* If the audio is playing already, however, it will continue to play on that channel, and it will be played again from the beginning on another
|
||||
* channel.
|
||||
*
|
||||
* @param fade Length of fade in in seconds
|
||||
* @param channel The SDL mixer channel to play the sound on. A value of -1 means choose a channel automatically.
|
||||
* @return The channel assigned to the audio chunk
|
||||
*/
|
||||
int play(float fade = 0.0f, int channel = -1);
|
||||
|
||||
/*!
|
||||
* Stop playback of this audio chunk on all channels it is playing on. Optionally fade out over the given amount of time in seconds.
|
||||
*
|
||||
* @param fade length of fade out in seconds
|
||||
*/
|
||||
void stop(float fade = 0.0f);
|
||||
|
||||
/*!
|
||||
* Pause audio chunk on all channels it is playing on.
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/*!
|
||||
* Resume audio chunk on all channels it is playing on.
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/*!
|
||||
* @return True if the audio is playing on any channel, false otherwise. Note that paused or fading audio is considered playing.
|
||||
*/
|
||||
bool playing() const;
|
||||
|
||||
/*!
|
||||
* @return True if the audio is paused on any channels, false otherwise.
|
||||
*/
|
||||
bool paused() const;
|
||||
|
||||
/*!
|
||||
* @return True if the audio is fading in or out on any channels, false otherwise.
|
||||
*/
|
||||
bool fading() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Load audio from an OGG or WAV file and play it on a single channel dedicated to music. Only one music object can be playing at a time.
|
||||
*
|
||||
* This class interfaces with SDL mixer's API and Mix_Music audio data pointer.
|
||||
*
|
||||
* There are some differences between how Mix_Chunk and Mix_Music can be used with the API, most notably that multiple chunks can be playing on
|
||||
* multiple channels simultaneously, but only one music object can be playing at a time. This is most likely because Mix_Music objects are streamed
|
||||
* and Mix_Chunk are loaded into memory, but I haven't verified that by looking at the internal code.
|
||||
*/
|
||||
class Music
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<Mix_Music> music;
|
||||
|
||||
public:
|
||||
|
||||
Music() = default;
|
||||
Music(const fs::path& path);
|
||||
void load(const fs::path& path);
|
||||
void play(int loops = -1);
|
||||
};
|
||||
};
|
||||
|
||||
#include "Box.hpp"
|
||||
#include "Configuration.hpp"
|
||||
#include "Display.hpp"
|
||||
|
|
14
src/Game.cpp
14
src/Game.cpp
|
@ -187,9 +187,8 @@ Game::Game()
|
|||
{
|
||||
SDL_Log("initialized SDL mixer %d.%d.%d", SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL);
|
||||
}
|
||||
// if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
|
||||
// MIX_DEFAULT_CHANNELS, 1024) < 0)
|
||||
if (Mix_OpenAudio(11025, AUDIO_U8, MIX_DEFAULT_CHANNELS, 2048) < 0)
|
||||
if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 1024) < 0)
|
||||
// if (Mix_OpenAudio(11025, AUDIO_U8, MIX_DEFAULT_CHANNELS, 2048) < 0)
|
||||
{
|
||||
sb::Log::sdl_error("Could not set up audio");
|
||||
}
|
||||
|
@ -201,8 +200,6 @@ Game::Game()
|
|||
message << "Found audio capture device " << ii << ": " << SDL_GetAudioDeviceName(ii, SDL_TRUE);
|
||||
sb::Log::log(message);
|
||||
}
|
||||
audio.load_sfx();
|
||||
audio.load_bgm();
|
||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||
sb::Log::log("big endian");
|
||||
#else
|
||||
|
@ -539,11 +536,6 @@ Input& Game::get_input()
|
|||
return input;
|
||||
}
|
||||
|
||||
Audio& Game::get_audio()
|
||||
{
|
||||
return audio;
|
||||
}
|
||||
|
||||
std::shared_ptr<TTF_Font> sb::Game::font() const
|
||||
{
|
||||
return _font;
|
||||
|
@ -621,7 +613,6 @@ void Game::frame(float timestamp)
|
|||
float timestamp_seconds = timestamp / 1000.0f;
|
||||
// recorder.update(timestamp_seconds);
|
||||
_delegate.dispatch();
|
||||
audio.update(timestamp_seconds);
|
||||
input.unsuppress_animation.update(timestamp_seconds);
|
||||
update(timestamp_seconds);
|
||||
_configuration.update(timestamp_seconds);
|
||||
|
@ -676,7 +667,6 @@ void Game::handle_quit_event(SDL_Event &event)
|
|||
void Game::quit()
|
||||
{
|
||||
/* Unsubscribe all delegate object's subscribers. */
|
||||
_delegate.unsubscribe(&audio);
|
||||
_delegate.unsubscribe(&display);
|
||||
_delegate.unsubscribe(&input);
|
||||
_delegate.unsubscribe(this);
|
||||
|
|
|
@ -110,7 +110,6 @@ public:
|
|||
sb::Display display {this};
|
||||
// Recorder recorder {this};
|
||||
Input input {this};
|
||||
Audio audio {this};
|
||||
std::vector<float> frame_length_history;
|
||||
|
||||
Game();
|
||||
|
@ -144,7 +143,6 @@ public:
|
|||
SDL_Renderer* get_renderer();
|
||||
const Input& get_input() const;
|
||||
Input& get_input();
|
||||
Audio& get_audio();
|
||||
|
||||
/*!
|
||||
* @return shared pointer to the default font pre-loaded at construction
|
||||
|
|
|
@ -72,10 +72,10 @@ bool sb::Log::gl_errors(const std::string& heading)
|
|||
return error_logged;
|
||||
}
|
||||
|
||||
std::ostringstream sb::Log::sdl_error(const std::string& message)
|
||||
std::ostringstream sb::Log::sdl_error(const std::string& message, const Level level)
|
||||
{
|
||||
std::ostringstream full_message;
|
||||
full_message << message << " " << SDL_GetError();
|
||||
log(full_message, Level::ERROR);
|
||||
log(full_message, level);
|
||||
return full_message;
|
||||
}
|
||||
|
|
|
@ -80,13 +80,15 @@ namespace sb
|
|||
static bool gl_errors(const std::string& heading = "");
|
||||
|
||||
/*!
|
||||
* Log a message, adding the results of `SDL_GetError` to the end of the message. Should be used to add more information to
|
||||
* an error statement when the error happened in SDL. The priority level will always be `Level::ERROR`.
|
||||
* Log a message, adding the results of `SDL_GetError` to the end of the message. This should be used to log a message after an
|
||||
* SDL API function returns an error value because SDL functions are written to set the value of SDL_GetError in that case. The
|
||||
* priority level will be `Level::ERROR` unless a different level is specified.
|
||||
*
|
||||
* @param message text to log before the SDL error is appended
|
||||
* @param level message priority level
|
||||
* @return a string stream containing the full message
|
||||
*/
|
||||
static std::ostringstream sdl_error(const std::string& message = "");
|
||||
static std::ostringstream sdl_error(const std::string& message = "", const Level level = ERROR);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -180,23 +180,22 @@ namespace sb
|
|||
std::vector<sb::Texture>& textures();
|
||||
|
||||
/*!
|
||||
* Get a constant reference to the texture at the given index. If no index is given, get the texture at index 0. If
|
||||
* there are no textures, an exception will be thrown.
|
||||
* Get a constant reference to the texture at the given index. If there are no textures, an exception will be thrown.
|
||||
*
|
||||
* @param index index of texture to get
|
||||
*/
|
||||
const sb::Texture& texture(int index = 0) const;
|
||||
const sb::Texture& texture(int index) const;
|
||||
|
||||
/*!
|
||||
* Get the texture at the given index. If no index is given, get the texture at index 0. If there are no textures,
|
||||
* an exception will be thrown.
|
||||
* Get the texture at the given index. If there are no textures, an exception will be thrown.
|
||||
*
|
||||
* @param index index of texture to get
|
||||
*/
|
||||
sb::Texture& texture(int index = 0);
|
||||
sb::Texture& texture(int index);
|
||||
|
||||
/*!
|
||||
* Add a copy of the given texture to model's list of textures.
|
||||
* Add a copy of the given texture to model's list of textures. Note that the texture object is copied, but the texture data is not copied.
|
||||
* The texture data is stored as a shared pointer in the texture object, so only the pointer is copied.
|
||||
*
|
||||
* @param texture texture to add to model
|
||||
*/
|
||||
|
|
32
src/Pad.hpp
32
src/Pad.hpp
|
@ -50,6 +50,7 @@ namespace sb
|
|||
sb::Switch<ReturnType, Arguments...> connection;
|
||||
sb::Plane _plane;
|
||||
Box box;
|
||||
int texture_index = 0;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -221,7 +222,18 @@ namespace sb
|
|||
{
|
||||
glUniform1i(texture_flag_uniform.value(), true);
|
||||
}
|
||||
_plane.texture().bind();
|
||||
|
||||
/* Determine texture index by checking the state of the pad and the amount of available textures. If there is more than 1 texture,
|
||||
* the texture will correspond with the state. */
|
||||
if (connection && _plane.textures().size() > 1)
|
||||
{
|
||||
texture_index = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture_index = 0;
|
||||
}
|
||||
_plane.texture(texture_index).bind();
|
||||
}
|
||||
else if (texture_flag_uniform.has_value())
|
||||
{
|
||||
|
@ -245,6 +257,24 @@ namespace sb
|
|||
return connection.flip(args...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return the state of the pad
|
||||
*/
|
||||
bool pressed() const
|
||||
{
|
||||
return connection;
|
||||
}
|
||||
|
||||
/*!
|
||||
* This will not trigger the callback if any is set. To do that, use Pad::press(Arguments...).
|
||||
*
|
||||
* @param state true or false to set the pad state
|
||||
*/
|
||||
void state(bool pressed)
|
||||
{
|
||||
connection = pressed;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the pad object's plane object
|
||||
*/
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
namespace sb
|
||||
{
|
||||
/*!
|
||||
* A Sprite is a container for an sb::Plane object that resets and stores scale, translation, and rotation matrices every time they are set
|
||||
* A Sprite is a wrapper around an sb::Plane object that resets and stores scale, translation, and rotation matrices every time they are set
|
||||
* and combines them automatically when getting the full transformation. This allows those transformations to be set repeatedly without having
|
||||
* to call sb::Plane::untransform() after each set. In the case of translation, there is also a move function that allows the translation to be
|
||||
* modified without resetting, so the sprite can move relative amounts.
|
||||
|
@ -44,8 +44,8 @@ namespace sb
|
|||
public:
|
||||
|
||||
/*!
|
||||
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits will
|
||||
* need to go through the Sprite class.
|
||||
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits must
|
||||
* be made using the Sprite class.
|
||||
*
|
||||
* @param plane flat model of the sprite
|
||||
*/
|
||||
|
@ -63,7 +63,7 @@ namespace sb
|
|||
|
||||
/*!
|
||||
* Construct a Sprite with a default constructed sb::Plane and attach a list of textures to the plane. Each texture is a frame of the
|
||||
* sprite's animation. The texture is the 2D graphic that displays in the sprite object's location.
|
||||
* sprite's animation. The texture is the 2D graphic drawn at the sprite's location.
|
||||
*
|
||||
* @param textures list of textures
|
||||
* @param scale amount to scale
|
||||
|
@ -77,8 +77,8 @@ namespace sb
|
|||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic that displays
|
||||
* in the sprite object's location.
|
||||
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic drawn at the
|
||||
* sprite's location.
|
||||
*
|
||||
* @param texture sprite's 2D graphic
|
||||
* @param scale amount to scale
|
||||
|
@ -128,7 +128,7 @@ namespace sb
|
|||
}
|
||||
|
||||
/*!
|
||||
* Add a new texture to the sprite's plane object from a path to an image file which will converted into a new texture.
|
||||
* Add a new texture to the sprite's plane object from a path to an image file which will be converted into a new texture.
|
||||
*
|
||||
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to the texture,
|
||||
* and it must be loaded with a call to Sprite::load.
|
||||
|
@ -167,12 +167,10 @@ namespace sb
|
|||
|
||||
/*!
|
||||
* @param index set the object's texture index
|
||||
* @return constant reference to the texture at the given index
|
||||
*/
|
||||
const sb::Texture& texture_index(int index)
|
||||
void texture_index(int index)
|
||||
{
|
||||
_texture_index = index;
|
||||
return texture();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -107,8 +107,8 @@ void Text::refresh()
|
|||
/* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object will handle
|
||||
* destroying the previous texture. The texture object is optimized so that it won't reallocate pixel memory if it was previously
|
||||
* generated at the same size. */
|
||||
texture().generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
|
||||
texture().load(flipped.get());
|
||||
texture(0).generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
|
||||
texture(0).load(flipped.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,11 +157,11 @@ namespace sb
|
|||
* generated, using Texture::generate() or Texture::generate(glm::vec2, GLenum, GLint), otherwise an exception will be
|
||||
* thrown.
|
||||
*
|
||||
* From the OpenGL manual:
|
||||
*
|
||||
* > While a texture is bound, GL operations on the target to which it is bound affect the bound texture, and queries of
|
||||
* > the target to which it is bound return state from the bound texture. In effect, the texture targets become aliases for
|
||||
* > the textures currently bound to them.
|
||||
* >
|
||||
* > - OpenGL Manual
|
||||
*/
|
||||
void bind() const override;
|
||||
|
||||
|
|
Loading…
Reference in New Issue