Efficient Objects

Author: Amit Patel

Historical context: back in the 1990s, game programmers distrusted C++ and OOP as being inherently inefficient compared to C. I was trying to argue otherwise, in part by suggesting not using OOP when it doesn’t buy you anything.

From: amitp@Xenon.Stanford.EDU (Amit Patel[1])
Newsgroups: rec.games.programmer
Subject: Re: OOP vs. procedural programming
Date: 29 Feb 1996 03:14:56 GMT

Mat Noguchi wrote:

Exactly how much extra code would programming a game in C++ with classes for the characters and such (primitives written in assembly, of course) entail; or to put it another way, how bloated would my game get using OOP?

It should be smaller, and possibly faster using OOP.

Okay, okay, that’s not what “everyone” says. The problem with a lot of benchmarks is that it’s a “C” program written with a procedural design compared with a “C++” program written with a procedural design. To get good space and time comparisons, you need to look at a program written with an object-oriented design.

Most small programs don’t benefit from C++. It’s rare that someone’s going to design a large program twice (procedural and object- oriented). Also, many times, it’s the old version that’s procedural and the new version that object-oriented, so you have to consider that the new version also DOES MORE. (New versions are almost always larger, and object-oriented systems tend to give you more flexibility.)

How do you design for efficiency?

Look for things you need in your game (not the program, but the game). Now look for things that share behavior or attributes. Describe what’s common and what’s different among those things.

For example, in an RPG, you might have a player, weapons, armor, scrolls, treasure, monsters, rooms, doors, and dungeon levels. What’s similar between these things? What’s different?

Well, players and monsters both move around and have various characteristics like strength, speed, inventory, and so on. So one thing you could say is that players and monsters both fall into the category of “Creatures”.

Weapons, armor, scrolls, and treasure are all carried or placed in rooms. Perhaps these could be grouped under “Items”. These are related to creatures: a creature HAS several items.

Rooms, doors, and dungeon levels do not have very much in common, but they are related. A dungeon level HAS several rooms. A room HAS several doors.

Here are the relationships we now have:

a player IS A creature
a monster IS A creature
a weapon IS AN item
armor IS AN item
a scroll IS AN item
treasure IS AN item

a creature HAS MANY items
a dungeon level HAS MANY rooms
a room HAS MANY doors

The “HAS” relationships can be expressed easily in both procedural and object-oriented designs. (Usually with a list or array.) The “IS A” relationship is easier to express in an OO system.

In a procedural system, you may define a set of routines on players and another set of routines on monsters. In an OO system, you can also define some routines on creatures. For example, get_movement() might be a function that’s defined on players to get input from the joystick/mouse/keyboard. On monsters, it could ask the AI routines for movement. Now, you’re going to need a loop to handle movement. Instead of writing a loop for the player and another loop for monsters, you can treat both players and monsters in the same way. Just define a loop that works on Creatures.

You now have one copy of that loop instead of two -- saving space.

You might have a function to allow a player to fight a monster. If you can, make it allow a Creature to fight a Creature. The advantage is that later, you might want to add monsters vs. monsters, or a multiplayer mode. Instead of writing three routines (M vs P, M vs M, and P vs P), you can write just one -- saving more space.

Another place you can save space is by writing only ONE set of routines to handle a creature picking up / dropping an item. Instead of separate routines for monsters and players, armor and weapons and scrolls and treasure, you can have just one. Compare Ultima Underworld, where you can interact with all the items, to Wolfenstein3D, where there are things you can see but can’t interact with. If you have just one set of routines for all cases, you won’t leave any out (like many Wolf3D objects).

Once you’ve made these general categories, try to take advantage of the flexibility you have. For example, if you’ve written a function for creatures to fight creatures, you might want to consider using it for more than just player versus monster. You might be able to make NPCs easily, or different races of monsters that fight each other.

You can also add new objects into your categories fairly easily. If you wanted to have a new type of item, like pianos, you could add a new type of object that has a few functions on it. All the functions that took an Item as a parameter automatically work with your piano.

Pitfalls

Beware! Good OO design is harder than good procedural design. It may take more effort, too. You have to think hard about what the difference between a “creature” and a “monster” is; it is much easier when you can just put all the routines in one place.

It’s also easy to go overboard. This commonly leads to inefficiencies. A lot of books are written from the Smalltalk perspective, where Everything is an Object. If you look at what would benefit from being an object and what wouldn’t, you’ll see that not everything needs to be an object. For example, I wouldn’t recommend making your linked lists or arrays into objects. Why? Well, if it’s an object, you should have categories of things that you’d like to treat alike. Are arrays and linked lists in the same cateogry? Perhaps. Are you going to treat them alike? Probably not. (This really depends on your program, though.)

In C++, the “class” construct can be used for more than object-oriented programming. It’s also used for abstract data types (ADTs). A linked list or array can be put into an ADT, so you would want to use a class for it. However, objects have encapsulation and polymorphism, while ADTs have encapsulation. You should pay for only what you use. If you don’t need polymorphism, use ADTs; if you need it, you were going to need it in a non-OO design anyway (usually with `switch’ statements), so it’s not expensive because you chose OOP -- it’s inherently expensive. (Your classes used for ADTs won’t have many virtual functions, while those used for objects will be mostly virtual functions.)

- Amit

Email me , or tweet @redblobgames, or comment: