Okay, I've made lots of stuff again!
Tile collision, falling into pitsFirst of all, you can fall into pits now.
For that to work, I had to integrate previously implemented collision detection into my game. This only took several hours, but now I have diagonal collision as well, which will allow me to make more interesting levels!
I also used cornering system from tiles to entities as well, so that hero now doesn't get stuck on corners of another objects and gets pushed it needed direction automatically.
So, how does falling work?
Now in every frame I check if common tile of entity changes. As most entities don't occupy many tiles, I just iterate over all tiles it intersects and if all of them are of the same type (e.g. "fall tiles"), I send
CommonTileChanged event. Hero's state machine catches that event, sees that common tile is now a "fall tile" and changes hero's current state to
FallState.
This will work with other systems good as well, for example if I add footstep sounds, I will use
CommonTileChanged event to change walking sounds from "grass" footsteps to "rock" footsteps, depending on which tiles player walks on.
PlatformsThings don't stop here. If player steps on the platform, he doesn't fall! This is not as simple as it looks, but I'm glad I didn't have to hardcode it.
When player collides with platform,
CollisionEvent is queued.
CommonTileChanged event is queued too, but as
CollisionSystem checks collisions before
TileCollisionSystem,
CollisionEvents is processed first afterwards. When something collides with moving platform, it adds it to child list (if it wasn't added before). Hero becomes a child of moving platform and inherits platform's position (moves with it).
Then
CommonTileChanged event is checked, but in the callback I check if entity has a parent or not. If it does, it won't go to
FallState, so the hero doesn't fall into a pit, as it has moving platform as a parent. I'll probably make it event more explicit, maybe I'll add
isFlying flag which will be true for moving platform and later in
CommonTileChanged callback I'll check it on parent of entity which is about to fall down. I'll see what works the best.
Huge changes to ECS implementationI've also checked out
EntityX source code and found some very useful things which I liked about its implementation. In my implementation references to components of currently active entities were stored in corresponding systems. So,
CollisionSystem had vector of references to
CollisionComponents. But now I implemented the system which allows me just to do this:
for(auto& e : entityManager.getEntitiesWith<CollisionComponent, MovementComponent>()) {
auto& mc = e.get<MovementComponent>();
auto& cc = e.get<CollisionComponent>();
... // do something
}
Nice! This allows me to always get active entities with needed components and not have to store and constantly update component lists as entities get deactivated or components get disabled.
The implementation is pretty simple. First of all, each component type get unique id from 0 to N. And I also started to store all entity's components in a
std::array<std::unique_ptr<Component>, MAX_COMPONENTS>. I don't have a lot of component types (I'm only counting those implemented in C++, low level ones!), so this array is very small and holds pointers after all. But this means that I can get needed components very fast just by using their unique id as an array index! I previously used
unordered_map for getting storing components and getting components was pretty slow, especially when done frequently, it became noticeable in profiler.
I can also now very quickly test if entity has needed components or not. I store
activeComponentMask bitset which has bits set to 1 in corresponding positions if component is active (not only present, but active, as I can sometimes disable collision, for example!).
EntityManager stores ids of currently active entities (entities nearby the hero which need to be updated) and
getEntitiesWith returns
EntityView object which is then used for easy iteration. Iterator just checks if entity has needed components by comparing mask being searched to
activeComponentMask. It all works very fast even with lots of entities, which makes me very happy, as I don't have to store component lists anywhere now.
Iterating over tile indicesI've also implemented cool range/iterator for 2d indices. I had lots of code like this:
const auto tilesIdx = TileMap::getTileIndicesInRect(boundingBox);
// tilesIdx is sf::IntRect which has (left-top) set to left-top tile in range and (width, height) correspond number of X and Y tiles
for (int y = 0; y < tilesIdx.height; ++y) {
for (int x = 0; x < tilesIdx.width; ++x) {
// for all tiles that entity intersects...
const TileIndex tileIndex(tilesIdx.left + x, tilesIdx.top + y); // this is sf::Vector 2f
const auto& tile = tileMap.getTile(tileIndex);
... // do something
But this got really annoying as I wrote this code again and again... And then I realized, what if I make range which returns indices like this?
(0, 0), (1, 0), (2, 0), ... (5, 0), (0, 1), (1, 1), ...
And I've made this range by implementing RowColumnIterator which operator++ increases x if it's less than number of X tiles in range. And it worked perfectly! Now I just write code like this:
for(const auto& tileIndex : TileMap::getTileIndicesInRect(boundingBox)) {
const auto& tile = tileMap.getTile(tileIndex);
... // do something
}
Much better!