Reputation: 59
I'm writing a small game in C++. I'm relatively new to the language and I'm struggling with how best to store all the game objects in memory and how to refer to them during runtime.
It's a puzzle game where you push 'shapes' which consists of 'blocks' like tetris-pieces:
struct Block{
int x,y,z;
bool visible, solid, floating, pushable;
};
struct Shape {
set<Block*> blocks;
};
Currently my setup for storing the blocks in the level looks something like this:
Block* all_block_data = new Block[200]; //Preallocated fixed array, objects never get removed from here
vector<Block*> all_blocks; //All pointers point to blocks in 'all_block_data'
vector<Shape*> all_shapes;
The player is one of the blocks and I have a global variable 'player' of type Block*. Once the blocks are created I can forget about all_block_data and just work with the pointers: I can do things like:
for(Block* b : all_blocks){ //Drawing all the blocks in the game
b->draw();
}
player->x++; //Moving the player
My problem now is that I want to have more types of game objects, and make different types of blocks that inherit from Block. I could duplicate the above for each new type but that would be messy.
I read online some ideas about storing the game objects themselves in a vector, but then - if I understand correctly - this vector could get reallocated in memory, and I can no longer be sure that 'player' points to the right block anymore.
How would you structure this? Is there some standard way to set it up?
Upvotes: 0
Views: 694
Reputation: 477
If you need more types of block objects that inherit from block, then it sounds like you need polymorphism. Polymorphism is the idea that the same type of class can act differently depending on the context. For example, say some Shape *
points to a shape derived from the base class. Say there is a member method area()
. Calculating the area is different for each shape. Calling my_shape->area()
on a square would do length * length, while calling my_shape->area()
on a triangle would do length * height / 2.
With this method, you only need one type of pointer (Block * in your case), but the objects it refers to are dynamic and can change at runtime.
Here's an example for your understanding:
class Block
{
int x,y,z;
bool visible, solid, floating, pushable;
public:
virtual void draw() { //do something... }
virtual ~Block() {}
};
class Player : public Block
{
public:
virtual void draw() { //do something else! }
virtual ~Player() {}
};
Block * all_blocks[200];
//First block is block
all_blocks[0] = new Block();
//Second is player
all_blocks[1] = new Player();
all_blocks[0]->draw(); //Calls Block::draw()
all_blocks[1]->draw(); //Call Player::draw()
Some important notes:
Polymorphism in this way is dynamic and occurs at runtime, therefore it incurs a performance penalty in the form of a vtable in memory and an extra step of indirection with each virtual function call.
Upvotes: 2
Reputation:
Memory allocation strategies in game engines, especially custom game engines, are typically designed to address the specific needs and challenges that are unique to the game that you are building.
There are a myriad of different strategies that can be employed, and each of them are suitable for different scenarios, with various advantages and drawbacks.
Because of this, I think you should wait until you know what the specific issues your game is going to be struggling with before comiting to an approach, lest you have to go through the whole process again once you understand your situation better.
In the meantime, what you should do is use bog-standard memory allocation. This will have a few advantages.
Basically, start with the following, and take it from there:
std::unique_ptr<Block> new_block = std::make_unique<Block>();
Block* block_ptr = new_block.get();
You'll quickly realize that you need to pick a place where the new_block
unique_ptr has to live, and various aspects of your design will sort of fall naturally into place from that.
N.B. If you go down that road, you will be quickly tempted to rely on shared_ptr<>
and weak_ptr<>
. While they are useful tools, you should really make an effort to avoid these in a game engine if you can.
Upvotes: 1
Reputation: 11410
What you have so far is very acceptable. Smart pointers would make it slightly better, but otherwise it's good.
I could duplicate the above for each new type but that would be messy.
I want to answer this specific concern.
This is why we have inheritance. You could have:
struct MagicBlock: public Block
{
int mana;
int animationState;
};
And one of the Shape
could have a MagicBlock
pointer in the midst of its other Block
pointer.
Then, you just need a generic API that is usable, for example:
struct Block
{
int x,y,z;
bool visible, solid, floating, pushable;
virtual void Update(); // normal Block behaviour
};
struct MagicBlock: public Block
{
...
virtual void Update(); // magic Block special behaviour code
};
Upvotes: 1