I would suggest starting by reading Mike Acton's 3 big lies, because you violate two of them. I'm serious, this will change the way you design your code: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html
So which do you violate?
Lie #3 - Code is more important than data
You talk about dependency injection, which may be useful in some (and only some) instances but should always ring a great big alarm bell if you use it, especially in game development! Why? Because it is an often unnecessary abstraction. And abstractions in the wrong places are horrible. So you have a game. The game has managers for different components. The components are all defined. So make a class somewhere in your main game loop code that "has" the managers. Like:
private CollissionManager _collissionManager;
private BulletManager _bulletManager;
Give it some getter functions to get each manager class (getBulletManager()). Maybe this class itself is a Singleton or it is reachable from one (you've probably got a central Game singleton somewhere anyway). There's nothing wrong with well defined hard-coded data and behavior.
Don't make a ManagerManager which lets you register Managers using a key, which can be retrieved using that key by other classes that want to use the manager. It's a great system and very flexible, but where talking about a game here. You know exactly which systems are in the game. Why pretend like you don't? Because this is a system for people who think code is more important than data. They will say "The code is flexible, the data just fills it". But code is just data. The system I described is far easier, more reliable, easier to maintain and a lot more flexible (for instance, if the behavior of one manager differs from other managers, you only have to change a few lines instead of reworking the entire system)
Lie #2 - Code should be designed around a model of the world
So you have an entity in the game world. The entity has a number of components defining its behavior. So you make an Entity class with a list of Component objects, and an Update() function which calls the Update() function of each Component. Right?
Nope :) That's designing around a model of the world: you have bullet in your game, so you add a Bullet class. Then you update each Bullet and move on to the next one. This will absolutely kill your performance and it gives you a horrible convoluted codebase with duplicate code everywhere and no logical structuring of similar code. (Check out my answer here for a more in-detail explanation of why traditional OO design sucks, or look up Data Oriented Design)
Let's take a look at the situation without our OO bias. We want the following, no more no less (please note there's no requirement to make a class for entity or object):
- You have a bunch of entities
- Entities comprise of a number of components which define the entity's behavior
- You want to update each component in the game each frame, preferably in a controlled way
- Other than identifying components as belonging together, there is nothing the entity itself needs to do. It's a link/ID for a couple of components.
And let's look at the situation. Your component system will update the behavior of every object in the game every frame. This is definitely a critical system of your engine. Performance is important here!
If you're familiar with either computer architecture or Data Oriented Design, you know how the best performance is achieved: tightly packed memory and by grouping code execution. If you execute snippets of code A, B and C like this: ABCABCABC, you will not get the same performance as when you execute it like this: AAABBBCCC. This isn't just because the instruction and data cache will be used more efficiently, but also because if you execute all "A"s after one another, there is a lot of room for optimization: removing duplicate code, precalculate data that's used by all "A"s, etc.
So if we want to update all components, let's not make them classes/objects with an update function. Let's not call that update function for each component in each entity. That's the "ABCABCABC" solution. Let's group all identical component updates together. Then we can update all A-components, followed by B, etc. What do we need to make this?
First, we need Component Managers. For every type of component in the game, we need a manager class. It has an update function which will update all components of that type. It has a create function that will add a new component of that type and a remove function that will destroy the specified component. There may be other helper functions to get and set data specific to that component (eg: set the 3D model for Model Component). Note that the manager is in some ways a black box to the outside world. We don't know how data of each component is stored. We don't know how each component is updated. We don't care, as long as the components behave as they should.
Next we need an entity. You could make this a class, but that's hardly necessary. An entity could be nothing more than a unique integer ID or a hashed string (so also an integer). When you create a component for the Entity, you pass the ID as argument to the Manager. When you want to remove the component, you pass the ID again. There may be some advantages to adding a bit more data to the Entity instead of just making it an ID, but those will only be helper functions because as I've listed in the requirements, all entity behavior is defined by the components themselves. It's your engine though, so do what makes sense for you.
What we do need is an Entity Manager. This class will either generate unique IDs if you use the ID-only solution, or it can be used to create/manage Entity objects. It can also keep a list of all entities in the game if you need that. The Entity Manager could be the central class of your component system, storing the references to all the ComponentManagers in your game and calling their update functions in the right order. That way all the game loop has to do is call EntityManager.update() and the whole system is nicely separated from the rest of your engine.
That's the bird-eye view, let's take a look at how the component managers work. Here's what you need:
- Create component data when create(entityID) is called
- Delete component data when remove(entityID) is called
- Update all (applicable) component data when update() is called (ie not all components need to update each frame)
The last one is where you define the components behavior/logic and depends completely on the type of component you're writing. The AnimationComponent will update the Animation data based on the frame it's at. The DragableComponent will update only a component that is being dragged by the mouse. The PhysicsComponent will update data in the physics system. Still, because you update all components of the same type in one go, you can do some optimizations that are not possible when each component is a separate object with an update function that could be called at any time.
Note that I've still never called for the creation of a XxxComponent class to hold component data. That's up to you. Do you like Data Oriented Design? Then structure the data in separate arrays for each variable. Do you like Object Oriented Design? (I wouldn't recommend it, it will still kill your performance in a lot of places) Then create a XxxComponent object that will hold the data of each component.
The great thing about the managers is encapsulation. Now encapsulation is one of the most horribly misused philosophies in the world of programming. This is how it should be used. Only the manager knows what component data is stored where, how a component's logic works. There are a few functions to get/set data but that's it. You could rewrite the entire manager and its underlying classes and if you don't change the public interface, no-one even notices. Changed physics engine? Just rewrite PhysicsComponentManager and you're done.
Then there's one final thing: communication and data sharing between components. Now this is tricky and there is no one-size-fits-all solution. You could create get/set functions in the managers to allow for instance the collision component to get the position from the position component (ie PositionManager.getPosition(entityID)). You could use an event system. You could store some shared data in entity (the ugliest solution in my opinion). You could use (this is often used) a messaging system. Or use a combination of multiple systems! I don't have the time or experience to go into each of these systems, but google and stackoverflow search are your friends.