Hello again!
In this video, we are going to look at how the entity manager performs operations on entity objects
in our game.
Most of these are actually fairly straightforward, so I will go through them when we look at the code.
As an example, get_all(), which will return all the entities of a given type.
This will just look up the type in the grouped_entities map.
So we get the hash code of the entity type.
We find the element in the map, which has that hash code as its key.
And then we return the value of the map, which will be the vector of alias pointers.
So for example, if we call get_all() with brick as the type parameter, this will return a vector of
pointers to all the bricks in the game.
The difficult part is going to be the handle_collision().
We had the ball and the paddle and the vector of bricks as local variables. So we could access them directly.
We no longer have that: the entity objects are hidden away, inside the entity manager.
So we need to do something else.
The obvious way is to write code like this.
So we get all the ball objects, for example.
Then we iterate over them.
Then this will give us a pointer to the base class.
So we convert that to a pointer to a ball.
Then we do the same thing for the bricks.
And then, when we get down here, we have a pointer to one ball and a pointer to one brick. And then
we can call handle_collision(). And then we need to do that again for the ball and the paddle.
And if we add other entities, we need to do that, for every possible combination of entities which can
collide with each other.
And that is going to be an awful lot of code, and it is going to make our game loop look very untidy.
So instead, we are going to write a function which will abstract away most of that functionality.
This will be the apply_all() function.
So we can call this and this will apply a function to all entities which have a given type.
For example, we can call apply_all() with brick as the type parameter. And then we can pass a lambda expression,
which takes a brick as argument and then calls update().
So this will call update(), for every brick in the game.
So, apply_all()
will basically do
what all that code in the game loop did.
So we call get_all(), to get the vector of aliases for that type.
Then we iterate over the vector. That will give us a pointer to the base class.
So we cast that to the dynamic type. And then we call the function, with that element as argument.
So we have code like this.
So this is in the game loop.
We have a entity manager object, then we call its apply_all() member function, with ball as the parameter,
and we give that a callable object. And this callable object will in turn call apply_all() again, with
brick as the parameter and this will call handle collision.
And then we have the ball and the brick as arguments to handle_collision(). The way that the scoping rules
for lambdas work, makes this a bit complicated.
We need to be able to call the manager object inside a lambda expression.
So we need to capture the manager.
And because we are actually inside a member function of the manager, we need to capture "this". And also,
we need to capture the ball. As otherwise, that will not be visible inside here.
And one final point.
When we have templates, we need to think about how we are going to organize the code.
In this case, we have member templates which are going to be called from the game manager, but not
from anywhere else in the code.
So the simplest way to do this will be to write the member template definitions inline in the game
manager header above the definition of the game manager class.
And that way the compiler will always be able to see the member template definitions, when it has to
instantiate them.
So here is our new version of the game header.
We have to add some extra headers for the unique_ptr and runtime type information.
We create some aliases,
as otherwise, our code is going to be full of colons and angle brackets!
This uses the C++11 "using" keyword, which is easier to read than the old typedef.
The vector of unique_ptrs to entity is going to be "entity_vector".
So this will be the all_entities member of the entity manager.
And then we have an entity_alias_vector, which will be a vector of pointers to entities.
So that is going to be the value type for the map, which is the grouped_entities. And the key type will
be size_t, because that is what the hash code function returns.
Then we have our create() member function, which we looked at before.
Then we have declarations of the non-template member functions.
The get_all() member function, which just looks up the type in the grouped_entities map, and returns
the alias vector. And then the apply_all() function.
So this will call get_all(). That will retrieve the vector of entities.
So entity_group will now be a vector of all the entities of this type, T. And then we iterate over this vector
of pointers.
We cast it down from pointer to base class to pointer to the derived type.
Then we dereference it.
So that gives us the object.
And then we call the function with the object as argument.
In the source file we have the definitions of the non-template functions.
The refresh() memory function is called at the end of every game loop iteration.
This will go through all the entities in the game, and it will delete all the entities which have been
destroyed.
And we actually have two vectors now, one with the alias pointers and one with the unique_ptrs.
We need to delete the alias pointers first, because they are just shadowing the unique_ptrs.
If we do it the other way around, then the alias pointers are going to be "dangling" pointers.
We have this expression that we had before, where we call remove_if(), to logically remove all the elements
which have been destroyed.
And then the erase() call, which will actually delete them from the vector.
So that will delete all the destroyed objects from the alias vector.
And then we do the same again with the unique_ptr vector.
To destroy all the entities we just call the clear() member functions.
Again, we have to deal with the alias pointers first.
So we call the map::clear() member function, then the vector::clear() member function.
To update all the entities,
we just have range for loops. And for drawing them, again.
And then the basic part of the game [loop] is the same.
It is just different when we get to the update stage.
So instead of updating each entity directly, we just call the update member function of the manager.
Then we have the code that we saw before, for handling collisions between the ball and the brick. And
we do the same again, for the ball and the paddle.
And then finally, we call the refresh() member function of the manager.
So that will delete all the objects which have been destroyed.
OK. And finally, just to prove that the game does still work after all this refactoring!
So there we are.
Okay, so there is quite a lot to "get your head around" with the entity manager.
You may need to go back and look at it a few times, and think about it.
If it is too hard for you, then it does not really matter.
We are not going to actually go back to this code again.
You can regard this as a library if you like. So you can just include this code in your programs and
not actually be concerned about the details of how it works, internally.
Okay, so that is it for this video.
I will see you next time.
But until then, keep coding!