Exploration
Exploring the unknown is a common mechanic in many games. It is very easy to implement in turn-based strategy games, but coupled with randomly generated maps can make for a very exciting experience for the player.
The banner image from Heroes of Might and Magic I (above) illustrates this perfectly: the Knight is staring off into the distance, wondering what wonders are hidden out of sight to the east.
ECS Design
Components
Tile
- x: Integer
- y: Integer
- graphic: String / BinaryBlob
Exploration
- explored: Boolean (defaults to False initially)
Position
- x: Integer
- y: Integer
Explorer
- range: Integer
Entities
Each map tile entity has Tile and Exploration components. Tile has information about what the tile looks like, which in this case has been abstracted to the graphic field for purposes of the example. In a real game there will probably be a variety of entities that need to be rendered instead.
The unit doing the exploration has a Position and Explorer component - the latter tells us how far the explorer can see.
Systems
ExplorationSystem
The exploration system is responsible for marking tiles as explored once they have been in range of an explorer.
explorers = world.get_entities_with_components(Position, Explorer)
for entity in explorers:
# I avoid the sqrt for euclidean distance because multiply
# is cheaper than sqrt in computers, result is the same
max_distance = entity.explorer.range * entity.explorer.range
min_y = entity.position.y - entity.explorer.range
max_y = entity.position.y + entity.explorer.range + 1
min_x = entity.position.x - entity.explorer.range
max_x = entity.position.x + entity.explorer.range + 1
for y in range(min_y, max_y):
for x in range(min_x, max_x):
delta_x = entity.position.x - x
delta_y = entity.position.y - y
distance = delta_x * delta_x + delta_y + delta_y
if distance <= max_distance:
tile = map.tiles[x][y]
tile.exploration.explored = True
Notes:
- This system can be made a great deal cheaper to run by keeping a map of explorer positions and only executing the inner loops an explorer’s position has changed.
RenderSystem
The render system is responsible for drawing the game map and entities on the screen. When an exploration system is in play, the render system needs to consider this to decide if something should be drawn or not.
tiles = world.get_entities_with_components(Tile, Exploration)
for entity in tiles:
if entity.exploration.explored:
draw(entity.tile.graphics)
else:
draw(unexplored_graphic)
Related Mechanics
- TODO: Vision / Line of sight
- TODO: Fog of war