The whole project would be a bit much to post, but there were a couple of questions that came up while I worked on this project. This is a fully working space invader game I coded in C++ with SDL2. I am an experienced C# developer and first done this in C# using MonoGame and then "ported" it over to SDL2. Platform is Windows 10 using Visual Studio 2019.
1: I do have a class tree where the base class is Object, and other classes such a Player, Enemy, Bullet and Bunker inherit from it. My ObjectManager keeps track of them all and updates, checks collisions and draws them. My ObjectManager holds them in a vector:
std::vector<std::unique_ptr<Object>> m_colliableObjects;
I'm using a unique_ptr because unlike C# (which uses ptrs too of course) I can't just use the base class in a vector. Now to add an object to this vector, I have this template function:
template<class T>
void addColliableObject(T* co)
{
static_assert(std::is_base_of<Object, T>::value, "T must be Object.");
m_colliableObjects.push_back(std::unique_ptr<T>(co));
}
To make the registration, each class inheriting "Object" implements a reg function:
void Player::reg()
{
ObjectManager::getInstance()->addColliableObject<Player>(this);
}
Which must be called from the constructor from all classes that inherit. Is there a way to implement this function once in the Object base class, so I can also call it straight from the base class constructor? Meanwhile the opposite destroy() function I was able to implement in the base class easily.
2: I always read to use std::string and not wstring. But the windows API in visual studio defaults to UNICODE. Which I can turn off, but what's the best to use? I want to be able to store normal UTF8 strings, Ascii is too limited.
3: I used a unique_ptr to store this struct in the vector so I do not need a copy and move constructor..is this a wise move? Also is ownership clear in this struct? Because Texture is just pointing to a texture which is owned in the Content namespace
struct SpriteFrame
{
SDL_Texture* Texture;
float Time;
std::unique_ptr<SDL_Rect> SourceRect;
SpriteFrame(SDL_Texture* texture, const float time, SDL_Rect* sourceRect)
: Texture(texture), Time(time), SourceRect(sourceRect)
{
}
};
std::vector<std::unique_ptr<SpriteFrame>> m_frames;
4: Is there any point of a unique_ptr in a namespace class other than showing ownership? Because these variables never go out of the stack I still need to call reset manually:
namespace Content
{
SDL_Texture* getSpriteSheet();
SDL_Texture* getSpaceBackground();
void loadTextures(const std::string& root);
TTF_Font* loadFont(const std::string& name, int size);
TTF_Font* getFont(const std::string& name, int size, bool loadIfNotFound);
void reset();
namespace
{
SDL_Texture* loadTexture(const std::string& root, const std::string& name);
std::unique_ptr<SDL_Texture, SDL_Deleter> Spritesheet;
std::unique_ptr<SDL_Texture, SDL_Deleter> SpaceBackground;
std::map<std::string, std::unique_ptr<TTF_Font, SDL_Deleter>> fonts;
std::string fontToKey(const std::string& name, int size);
}
}
5: Similar to 4, to ease access to the ObjectManager I implemented a getInstance function. But since the instance member is stored static, I still need to reset/delete it manually again. Is there a better way?
static ObjectManager* getInstance()
{
if (!m_instance)
m_instance.reset(new ObjectManager());
return m_instance.get();
}
static void reset()
{
m_instance.reset();
}
6: I'm hosting enemies in a nested array. Which works, but it's not C++ 11 of course. Enemy itself is already owned by ObjectManager, but as soon I use unique_ptr for the array the [] operator does not work. But I need to fill the array quiet dynamically as you can see below:
typedef Enemy** EnemyArr;
EnemyArr* m_enemies;
void EnemyManager::createEnemies(const int xCount, const int yCount, const bool initial)
{
this->m_xCount = xCount;
this->m_yCount = yCount;
m_enemies = new EnemyArr[xCount]);
for (int x = 0; x < xCount; ++x)
{
m_enemies[x] = new Enemy*[yCount];
for (int y = 0; y < yCount; ++y)
{
m_enemies[x][y] = new Enemy(this,{
x * Defs::EnemySpacing + Defs::EnemyMarginIn,
y * Defs::EnemySpacing + Defs::EnemyMarginIn });
m_enemies[x][y]->m_ignoreCollision = y < yCount - 1;
}
}
m_currentMoveAmount = 0;
m_moveToRight = 0;
m_reachedPlayer = false;
if (initial)
{
m_moveDelay = Defs::EnemyMoveDelay;
m_shootDelay = Defs::EnemyShootDelay;
}
else
{
m_moveDelay /= Defs::EnemyIncreaseRoundSpeedMult;
m_shootDelay /= Defs::EnemyIncreaseRoundShootSpeedMult;
}
}
Please let me know if you need more info because the code is quite straight to the point, I just didn't want to post code of 30 files but I'm happy to share whatever you need, the whole project too.