Expect this Unit to take about 2 hours. To demonstrate what you should have at the end of this Unit, check out the Demo for Unit 5. Don't forget to use the Chrome DevTools to look at the console and check out the source code for my tests!
Components is a concept where we divide up functionality for objects into small, re-usable peices that can be mixed and matched and assigned to an object as needed. The component-entity paradigm is very popular in game engines, in fact you've probably seen it if you've used other game engines out there, and while we can easily get away with just sticking to only using inheritence in this engine I think it's a valuable lesson to get comfortable with building a component system.
Before we talk too much about what components are and what they might do, we'll start with the GameObject, who has the responsibility of managing an arbitrary list of components.
Then we'll get into Components, how they manage being added and removed from a GameObject.
Finally! It's time for the GameObject class! This will be our base class in which all game objects in the game derive from. For now this class will just be a storage unit for components that are attached to it.
A constructor function will create an array to store all the active Components.
We will need three functions to manage components: addComponent, hasComponent, deleteComponent.
We are also going to inherit the destroy() function from DrawNode. We want to make sure that any components that are attached to this GameObject know when it is destroyed, and we'll want to delete all the components as well.
Adding a component onto a GameObject should be as simple as giving it the component type name (what it was registered as) along with any constructor properties that need to get passed along.
We take an additional, optional parameter of a name to give the component. This is useful in case you want to add more than one component of the same type to a given GameObject. Say we had multiple sprite components attached to a single GameObject: a character, a hat, a weapon - each would be added under different names (although that's just one way to approach a solution).
We need to find the Component class that has the passed in name, and then create a new instance of it and store it in the list of .activeComponents
As a shortcut to getting access to the component, we store it by name as a property of the GameObject directly.
Once a component is added, we fire off an event in case any objects are listening.
Now that we see how the storage of components looks, it's very easy to tell if a game object has a specific component or not!
Deleting a component is just the mirror opposite of adding one. We iterate over all of the active components
When a GameObject is destroyed, it destroys all of it's components. Since it is also a DrawNode, calling this._super() makes sure it destroys all of it's child nodes too, and going up the inheritance tree we know that since DrawNodes derive from BaseObject, it debinds listeners attached to it. OOP can be fun!
We have a .destroyed flag just in case someone keeps a handle to the object even after calling destroyed. It can be helpful for debugging, as well as makes sure we don't try destroying an object more than once.
Since components are added to a game object by type, we need a good way to register and store the available component types. You already saw in the GameObject's addComponent that we use a list stored on the global Engine namespace, so let's explain how that works.
Component Registration with the Engine
Whenever you want to create a new Component type, by deriving from the Component class, it'd be great if it automatically registered itself with the Engine.
We are going to write a wrapper function to do just that whenever a user (you!) tries to use Component.Extend(), and change it up so that it expects an extra parameter for what name to register the component under.
The base component class really only needs to handle being added to and removed from an object. Let's have a look at the API first: we are going to want init() and destroy() functions to handle when the component is created and destroy.
We already saw how the components are created when added to a game object, so you should notice now that the init() function is taking an extra parameter which is the object it is being attached to. We store a reference to that and that's it!
When a GameObject is destroyed, all of its components are destroyed as well. If a single component is destroyed on its own, then it needs to make sure to remove itself from the GameObject.
Make sure to call the base class destructor, and also cleanup the GameObject's named reference to this component.
For an example, let's create a Health Component, give it to a Game Object, and see how it might work.
Part 1. Create and add the two files to your project: gameObject.js, components.js
- Create the files and put them all in your /js_engine sub-directory.
- Add all three files to the list of engine files to load through modernizr in main.js
Part 2. Fill out gameObject.js. and components.js with the source code from the class lecture.
Part 3. Test the new features of the Engine
- Make sure you can get the test code in the class lecture working.
- Try creating your own derived Component type. Some suggestions might be something like an AI Component or a Position Component.
- Create a new derived type of GameObject. Make sure you are comfortable with assigning it new default properties, inheriting functions, and creating new function types.