The third part in an ongoing workshop introducing theories behind an Object Oriented approach to 3D Engine Design.
visit http://www.bays-and-bears.net/howard/workshops/enginedesign/ for source code.
Today we will be taking a little bit of a detour from the code we’ve built the last two lessons in order
to discuss some important considerations in the design of our Engine.
In my synopsis of the goals I had
hoped to achieve for this workshop, I mentioned phrases like polymorphism, inheritance, encapsulation, and
modularity but I didn’t explain very much about them. Today I would like to focus on these key terms, discuss how they apply to us as programmers –
specifically game programmers – and how this will eventually effect our design choices for this engine.
What does Object Oriented mean exactly?
When we discuss Object Oriented programming, it is important to also note the other styles of programming
in order to really appreciate how this methodology helps us work on such vast projects such as games. Let
me describe other styles of programming before I come back to OOP (object oriented programming).
Unstructured Programming (UP) is how most people begin learning how to program. I know it’s definitely
where I started! The best way to describe UP is by pointing out the very popular “Hello World” program, the
humble beginnings every programmer started at =].
In a UP there is only one function ( ie. the main( ) ) and
all the data in the program are exposed to this function (are global). All of the functionality of the program
is implemented directly into this single main function.
Procedural Programming (PP) is the next step in the evolution of languages. It introduces the idea
of encapsulating specific functionality into a separate block of code, and giving the ability of any
function to call another function.
As you can see, this gives rise to the idea of reusability, or
enabling a program, indeed multiple programs, the ability to reuse functions without rewriting them. The value
of this is self evident. It prevents dev teams from “reinventing the wheel” so to speak, and focus on the unique
aspects of their project.
The term
encapsulation means that we build an entire function that performs many operations but has a predefined result.
For example: I write a function for an object that draws it to the screen, and name it Render( ). Because I’ve
encapsulated this function, other members of the dev team shouldn’t need to know what happens inside this function
in order to use it. At first this may seem obvious, but it becomes extremely important when you start designing
your engine.
You want to make sure to build it in such a way that you can encapsulate (effectively make a black
box) out of what you are coding, so other members of your team don’t need to learn what you did. This allows
a dev team to effectively specialize, or, gives the ability for each of us to become masters of our particular
domain. In the game we develop, for example, I may be asked to encapsulate all of the graphics routine,
while Churroe is asked to develop the sneak system.
As you can see both of these features are unrelated to one
another, proper encapsulation would allow both Churroe and I to work on our respective parts of the engine
at the same time without causing conflicts, and being wholly unaware of what the other is doing. This is good engine design,
and it is also pretty much the textbook definition of modularity.
Object Oriented Programming is the next step in the progression, allowing the programmer all of the functionality
of the previous programming paradigms with some new advanced features: modularity, inheritance, and polymorphism.
Lemma 3.1 – Key Concepts of OOP
While I may touch upon and relate many of the concepts of Object Oriented Programming to the task at hand, it might be good to see
it in it’s more pure form, one not specifically spoken in the context of video games. Also, because this workshop
is designed to be a quick introduction and by no means an all expansive tutorial, I would recommend the good students
to seek more information about object oriented programming and direct your browsers
here
Modularity
Modularity is one of the most basic tools in any OOP programmers toolkit. The concept of modularity builds off of
the programming paradigm of structured programming, where blocks of code are separated in order to both encapsulate
their functionality and provide a high level of reusability. Modularity adds to this by both pairing functions with
their own data set.
Example 3.1 – Modularity
Consider the following example: You have a game of pac man, and you want to have 5 ghosts on the screen. Without
modularity you would be forced to keep 5 sets of data available to the various functions. You can imagine having
all the images, positions of the ghosts, AI states all in one lump could get very confusing and error prone. What
modularity allows you to do is encapsulate each ghost into a single module, or object:
Object Ghost
{
int positionX, positionY;
AISTATE ghostAI;
IMG ghostImage;
void MoveGhost( Direction );
void SetAI( ghostAI );
}
Now we merely create 5 instances of ghost: Ghost ghost1, ghost2 … ghost4, ghost5; Each ghost has the same
types of data, but the data remains separate from each other. Notice they also get their own functions, so calling
ghost1.MoveGhost(-1,10); will only move ghost1, and the other 4 ghosts will remain untouched. Also note how easy
it would be to add a sixth ghost, just create a new instance of the ghost object, and viola another ghost
will appear. This is obviously a very powerful technique.
Inheritance
The next step up from modularity is the concept of inheritance, or the ability to build new objects based
on the properties of old objects. This technique can get rather complicated so instead of going over specific
implementation I will describe the basic idea behind it, and leave it to you to research the specifics. Some key
terms associated with inheritance: (if some of these terms don’t make perfect sense to you from the short descriptions
I recommend you do a Google search on it. Also, seeing the example that follows may make things more clear.)
- Class: this is basically an implementation of a module. It’s functions and data contained in one single data
type. Classes, aside from the primary building block in inheritance, also add the ability to specify whether or
not a data is used only by the class, or whether it is accessible by outside functions. For more information look
into public, private, and protected data members. - Data Members: components of a class – this includes the data and functions it encapsulates.
- Base Class / Pure Virtual Class: Although these terms are not synonyms, they are related. A base
class is a class that other classes derive themselves from. A pure virtual class is one that doesn’t do anything
in of itself, but rather, is just a template for other classes to build from. The following example might clarify
this concept. - Ancestor/Descendant: As these words imply, a descendant is a class that has inherited functionality from
it’s ancestor. - Overloading: this will be covered in polymorphism, but basically it’s the ability
to create a function with the same name that does the same thing. - Overriding: when you inherit from a class that has functions, you can override that
behavior. So lets say I have a class Monster that can move, and it has a move command
built into it. But this move command is good for zombies but not for flying creatures.
When I build my Bat monster, I can override the move command that comes with monster, and
modify it so it works the way I want it to for this new class Bat.
Example 3.2 – Inheritance
Consider the following example: In a video game you often have many different objects that have
similar data but different implementation, or the same data but also additional functionality and
data sets. Take objects for example. In any particular scene you may be presented with a number
of objects like: crates on the floor, a car, an enemy, a gun on the floor.
All of these have many
things in common. They all have at least position in the world, a model to represent them, and a function
which draws them to the screen. What
Inheritance allows us to do is make one base class, which will handle all the tasks that are the same
for all the objects in the world. Then, we inherit this functionality in new classes, that expand
on this ability.
class WorldObject
{
int positionX, positionY;
3dModel objectModel;
void Render( );
}
//Next lets make a specific type of object
class Crate : based on WorldObject
{
int crateHP; //how much damage before it's destroyed?
int weight; //how heavy is the crate;
std::vector contents; //what is inside the crate?
}
An instance of Crate now will have all of the data shown above in addition to the data from WorldObject.
Pretty neat huh? Since all objects will derive themselves from WorldObject, we consider this the
“base class”. If the WorldObject didn’t implement the Render( ) function and it was expected that
the inherited class “Crate” would overload it – then the WorldObject would be considered a purely
virtual class, ie. a class that didn’t do anything until you derived another class from it and implemented
all it’s functions.
Polymorphism
If you think about the word polymorphism it’s meaning becomes evident: many forms. Polymorphism
allows us to avoid recoding the same principal for different specifics. I won’t make a big box out
of this because it’s really simple to understand and as this tutorial gets longer, I am getting lazier.
Lets go back to our object class, and consider the following: Let’s say we have our rendering function,
that normally will just draw the image to the screen.
But what if we want to render it differently, say,
with a custom pixel shader attached to it? Instead of writing Render( ) and RenderWithShader( shader* )
we can just write an overloaded render function, or one that takes two different types of arguments.
Render( ) //no arguments, and Render( shader* ) will take a shader if we so choose. There are other uses
for polymorphism, but you get the idea.
Of course there are many other features to OOP, (both good and bad!), but whole books can (and have) been
written on this, so I won’t dwell on it here. I encourage you guys to do some more research of your own, the
internet is a veritable font of information, and I’ve equipped you with some of the basic terminology and concepts
to help you sort through the mess =].
Until next time, have code will travel.












Leave Your Response