Block Reactions
This is a game I made at university to be played on Windows, Linux and the Evercade consoles.
Block Reactions is my first game ported to a console, and first proper game made in C++.
Selectables and Buttons
The game needed both buttons and the blocks themselves to be selected so a Selectable abstract class (inherits from GameObject) has been made. This abstract class handled the common functionality of both mechanics of the game. From positioning and rendering, to initializing and sending custom SDL events when the selectable is selected.
The button inherits from Selectable and adds the functionality of settings and rendering text on top of the selectable/button texture.
Button class rendering text alongside Selectable defaults
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Button::RenderActive()
{
Selectable::RenderActive();
RenderText({0, 0, 0, 255}, m_selectedTexture);
}
void Button::RenderInactive()
{
Selectable::RenderInactive();
RenderText({255, 255, 255, 255}, m_unselectedTexture);
}
void Button::RenderText(SDL_Color textColor, Texture2D* buttonTexture)
{
// set text color
m_gameText->SetColor(textColor);
// render at the center of the button
m_gameText->RenderAt(AlignmentInfo(NONE,
m_position.x + (buttonTexture->GetWidth() - m_gameText->GetTextWidth()) / 2,
m_position.y + (buttonTexture->GetHeight() - m_gameText->GetTextHeight()) / 2));
}
The other functions are all virtual functions from Selectable and implemented to fit the Button functionality. For example, overring Selectable::InitializeUserEvent() to set the user event to BUTTON_PRESS which gets called in the constructor.
To easily select between different selectables such as buttons or blocks a Selectable Container was made.
This class uses an index to traverse through a vector of Selectable pointers by using MoveIndex(Direction direction) Direction is an enum:
Direction enum
1
2
3
4
5
6
7
enum Direction
{
Left,
Right,
Up,
Down
};
Here is a youtube video showcasing the selectable container prototype.
Blocks and Texturing
The block class inherits Selectables just like the UI buttons do. It contains an option to Cancel the selected block, and stores a BlockShapeTexture.
A block shape texture consists of a BlockShape 3x3 shape that is build using the BlockShapeName enum. The block shape texture is responsible for rendering the 3x3 shape and later texture it (the plant UML is of the prototype before texturing was added).
With this combination simple shapes could be made inside the Block selectables as seen in the video below:
For the placement of the blocks a Grid class was made to move, rotate and store all blocks placed using a PlacedBlockData struct.
To add textures to the block shape textures I used the SDL_SetTextureBlendMode() function, setting a texture as the render target.
Once I’ve got the textures figured out I made a static map storing all textures made. The problem with this is that all blocks were pointing to the same texture so I couldn’t do any manipulation to a specific texture.
I then researched shallow and deep copying of pointers to find out that SDL made SDL_Textures near impossible to deep copy by design because textures can have different dimensions, therefore different sizes, making it much more difficult to deep copy if the size is unknown.
The workaround I found was barely worth doing compared to having each block loading their own textures.
This video showcases the textures and blocks interacting with each other.
Audio
As the project was bound to be small I decided to use a singleton audio manager that will handle both music and SFXs.
As seen in the plant UML diagram, 2 enums were made for all sounds and musics used in the game. This is a very unscalable and inefficient, but I did cut corners as the project was small and made solo.
But now revisiting this code I can see how much more inefficient the code truly was.
Sounds and Music map initialization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Audio::Audio()
: m_sounds({
{UI_START, "audio/UI_Notification_Start.wav"},
{UI_CONFIRM, "audio/UI_Notification_Confirm.wav"},
{UI_DENIED, "audio/UI_Notification_Denied.wav"},
{BEEP, "audio/UI_Beep.wav"},
{BEEP_CONFIRM, "audio/UI_Beep_Confirm.wav"},
{BEEP_DECLINE, "audio/UI_Beep_Decline.wav"},
{BLOCK_WATER_SPLASH, "audio/reactions/water_splash.wav"},
{BLOCK_EARTH_CRACK, "audio/reactions/earth_crack.wav"},
}),
m_musics({
{FIRE_FLIGHT, "music/firefight_loop.wav"},
{HEAT, "music/heat_loop.wav"},
{AMBIENT_RHYTHM, "music/ambient_rhythm_loop.wav"},
})
{
// ...
}
Two maps for music and SFX was made at initialization and each time a sound was being played it would load it from the file if it wasn’t already the loaded sound.
Play sound function
1
2
3
4
5
6
7
8
9
10
void Audio::Play(SOUNDS sound, int loops)
{
// don't reload the currently loaded sound
if (m_loadedSound != sound)
{
LoadFromFile(m_sounds[sound]);
m_loadedSound = sound;
}
m_sfxChannel = Mix_PlayChannel(m_sfxChannel, m_mix, loops);
}
Instead, I could have made a map of <enum, Mix_Chunk> where the Mix_Chunk is the output from loading a file:
LoadFromFile function
1
2
3
4
void Audio::LoadFromFile(const std::string &path)
{
m_mix = Mix_LoadWAV(path.c_str());
}
And check if the sound trying to be played is in that map, if it is, use the Mix_Chunk, else load and add it to the map. The same changes could be made to the music functions as well as the game can be replayed and remove unnecessary file loading.
These simple changes would get rid of a lot of unnecessary loading and improve performance especially when working with consoles such as the Evercades.