Skip to main content
2 of 2
Focusing title, minor cleanup.
DMGregory
  • 136.3k
  • 22
  • 247
  • 373

How can I avoid coupling between units and their environment?

Currently in the process of making a game like Dwarf Fortress, so without graphics. I'm mostly thinking about how I want to implement everything. While writing code I keep stumbling on general game implementation problems that are really annoying me. Assume this:

We have a Map class which stores Chunk classes which stores Tile classes which stores Players and Enemys currently on this tile.

class Map {
    public:
        std::vector<std::vector<Chunk>> chunks;
};

class Chunk {
    public:
        std::vector<std::vector<Tile>> tiles;
        std::vector<Player*> player_need_update;
        std::vector<Enemy*> needs_update;
};

class Tile {
    public:
        std::vector<Player*> players;
        std::vector<Enemy*> enemies;
};

Now let's say I want to move() a player to a different Tile. For this I created a needs_update vector storing entities that need to be updated on the game Map. Every game tick we loop all chunks and loop all entities needing an update.

I think (thought) this is a logical way of doing this because we can have a tile that is independent of a chunk that is independent of a map. Same for entities. So let's say we want to move an Entity:

void Entity::move(uint16_t move_x, uint16_t move_y) {
    x += move_x;
    y += move_y;
    
    changed() // Notify chunk that something changed about us
}

This is already where it is going wrong. To be able to notify the chunk we have moved, we need to know ourselves in which chunk we are Chunk *in_chunk; in class Entity. And this breaks the structure I wanted to achieve (independence across classes).

Another example, suppose we want to attack an Entity on a different Tile. We would need to know there is an entity in the first place, but we would also need to know things about the enemy to then decide whether to attack it or not.

Player::attack(Enemy &enemy /* ?? */) {
    // Attack
}

I think it is wrong to move a player by calling a function in Map:

void Map::movePlayer(Player &player) {
    // Do stuff to move player
}

So, how do you implement basic game structures like this, keeping things logically structured but while still being able to ie. move a player across the map?