Jompa
Jompa

Reputation: 59

Structuring memory allocation and pointers to game objects in C++

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

Answers (3)

Lucas Streanga
Lucas Streanga

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

user4442671
user4442671

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.

  • It'll let you focus on actually developing the game instead (aka avoid bikeshedding).
  • It'll force you to think about what "owns" the blocks, which is something your posted code severely lacks, and WILL come and haunt you in the future.
  • It'll set you up to switch to a different memory allocator later.
  • Maybe that'll be plenty for what you need, so you won't have "wasted" time building a system you never needed in the first place.

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

Jeffrey
Jeffrey

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

Related Questions