2
\$\begingroup\$

GameObjects are stored in a Tilemap class, within a std::map<Vec2Int, GameObject> container. The Vec2Int struct describes the object's x and y coordinates. Each GameObject also contains a Vec2Int describing the same thing.

There are two things I would like to do (unless there is an entirely different/better way):

  • The Tilemap should be able to traverse all GameObjects so that they may be drawn to the screen. The Tilemap uses a Camera class so that it can figure out which tiles/objects can be seen in the current camera view.
  • However, GameObjects should be in control of their own position, not the Tilemap! The GameObject should be able to reposition itself by simply changing its x or y coordinates

For reference (I've only included the relevant stuff):

struct Vec2int {
    int x, y;
};

class Tilemap {
    public:
        Tilemap(const int &w, const int &h);
        void LoadLevel();
    private:
        //A 2D array of Tile pointers
        std::vector<std::vector<Tile*>> _tileMap;
        std::map<Vec2int, GameObject*> _objects;
};

class GameObject : public Tile {
    public:
        GameObject(const int &tileID, const Vec2int &position);
        //virtual void Update() = 0;
    private:
        Vec2int _position;
};
\$\endgroup\$
1
\$\begingroup\$

Crucially (and your question title is wrong here, because it omits the *), you've got a map of pointers to GameObjects, so the Tilemap isn't in control of the GameObjects. It's really just a helpful look-up table.

Fundamentally you need a back-reference to the Tilemap in every GameObject, so they can remove themselves from the tilemap and re-add themselves in their new position. This is less about control than it is about deciding where the maintenance of your look-up table should be. If you think about it, it makes no sense to consider a GameObject in isolation; it has a position field, and that means it always has to be somewhere on your Tilemap. There's no concept of a GameObject that's not actually on the Tilemap somewhere, so they are already intimately related.

If this feels a little squinky, it's probably because you've got the same information in two places. You could keep the location information wholly in the GameObject (and just maintain a flat list of tiles in the Tilemap), or you could make the GameObjects ignorant of their position, just reference them by ID and move them in the Tilemap, and both would work equally well, but leave you having to do some awkward queries in one place or another.

If you are going to maintain the information in two different places, you want to make one function that maintains both sets of information at the same time, so they can never get out of sync. That means you either put it in the Tilemap (and don't allow GameObjects to change their own position), or you put it in the GameObject (and don't allow the Tilemap to alter its own information about GameObject location). If you do the latter, then every GameObject needs to know about the Tilemap it's associated with so that it can modify the std::map itself.

This sort of relationship is awkward in C++ because to preserve encapsulation you end up wanting to make the GameObject a 'friend' of the Tilemap, so it can access private/protected members, which should already be ringing alarm bells. The only alternative is to initialise the GameObject with a direct reference to the std::map object itself (so it doesn't know it's part of a Tilemap, it just knows that it's got a look-up table that will have a reference to itself, and it should maintain that lookup table)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.