Game map questions (Resource vs Entity)
Fumseck opened this issue · 1 comments
Hi,
I hope it's ok to open an issue to ask questions rather than reporting an issue. I have a couple questions on how to handle game maps.
The first question is : Should the map be an entity or a resource ?
The map is a central piece of the game and many systems need to access it, so it makes sense in my opinion to have it defined as a resource.
This has worked fairly well for me so far but i'm now implementing saving and loading in the game, and it looks like resources can't be serialized with a simple function call like the world can.
- Is it a good practice to save a custom object containing various resources in addition to the world state ?
- Should the map be defined as an entity from the start to be saved and loaded as any other entity?
- Should i create a single component map entity right before saving so i can load it as a resource and delete the entity when loading the game?
I'm curious how other people are handling that.
Second question : Is it ok to store entities in map cells ?
My map is a vector of cells, and each cell has a entities field which is a Vec<Entity>
, a vector of all entities currently in that cell. This allows me to easily get the list of entities in a specific cell.
I'm not sure if that design is correct. it feels like the ECS way of doing it would be to iterate over entities that have a Position component and return the list of entities which have the position i'm looking for. But that also seems like a huge overhead when you start adding a lot of entities in the game.
Is there an agreed-upon best way to let the map object provide this basic "list what is in place X" function ?
Thanks for taking the time to read,
Peace!
Both of your questions depend on the type of game. Let's assume a turn-based roguelike.
The first question is : Should the map be an entity or a resource ?
It is more common to store it as a resource. There's no advantage to having it as an entity. Multiple systems would need access and you'd have to do cumbersome queries to retrieve it. It doesn't gain anything by having a composable component style layout.
Second question : Is it ok to store entities in map cells ?
No, it's not. Options are: (1) Store position only on entity in ECS; (2) Store position only in game map by associating the entity ID inside a cell as you've described; (3) Store position in both.
(3) is not great because after the turn you have to query a position in the ECS anyway so you can update your map contents.
(2) is bad. imagine just trying to render the map. not only do you have to iterate the Vec<Cell>
but also Vec<Entity>
in each cell just to render. Jumping back and forth to read vectors on the heap is slowwwww. not to mention all the other spells and effects that would require long searches through your entire map to find affected candidates. and your map will be huge. Just do a std::mem::size_of and see how big your Cell type is. Depends on how you designed it but consider that an average roguelike map is 80*50=4000 tiles. It will always end up being larger than option 1.
(1) is the winner. The fastest setup is a 1 byte TileType. Do not have variants containing extra information as it will resize all of your enum variants to the largest variant. On a 80x50, 4000 tile map, you'd be only using up 4000 bytes, sweet. And your cache hits will be bananas if it's stored as a 1-dimensional array. Query the blazing fast ECS for position and you can relate any entity position to any map tile using the map methods.
enum TileType { Floor, Wall, Tree, ... }
struct Map {
pub tiles: Vec<Tile>,
pub width: i32,
pub height: i32,
pub map_count: usize,
}
impl Map {
pub fn new(width: i32, height: i32) -> Self {
Self {
tiles: Vec::new(),
width,
height,
map_count: (width * height) as usize,
}
}
pub fn get_idx(&self, pos: &Point) -> usize {
(pos.x + pos.y * self.width) as usize
}
pub fn get_pos(&self, idx: usize) -> Point {
Point::new(idx as i32 % self.width, idx as i32 / self.width)
}
}