View Full Version : Do you use OOP or what method?
ergas
07-28-2003, 01:03 AM
I am concerned about what programming methods indie game developers use. I have written both structured code and object oriented. Now I am more tending to OO, but still using structured features whenever I need. I wonder what method you (everyone here) use and why?
I personally use OO when I plan a package for generic use. For example for UI or physics or even graphics I used OO. But in some cases within an OO package I insert structured functions which need higher performance. For C++, OO is better for managing the memory, but there is always a lack of performance and also OO makes your application take a bit more memory. There is one more disadvantage of OO, in OO sometimes you loose the purpose of a game because it takes time at the begining, and you loose your motivation. I think when developing a prototype, OO is not as good. But if one has several years of OOD experience I think he/she shall go on with OO in prototype.
Having several years of development experience, I could say that I am now spending quite a lot of effort to develop OO games as good as my old structured games. I could just now do as good as I did in the past. But I can see that OO is a good future knowledge investment. I would recommend game developers to use OO but never forget the older methods and continue to use them.
Erkin
Nikster
07-28-2003, 01:19 AM
I use OO because it's easier to hide my bad code in modules ;)
but I really use it because that's what I am used to now. rather than using it because it would be better to..
Mattias
07-28-2003, 01:30 AM
I find it quite interesting that you put it this way. For me, using OOP is a necessity, I find that it saves me a lot of time, makes the code clearer and more reusable, and introduces fewer bugs. I would never want to go back to using structured programming again, simply because it would slow me down and produce code of less quality.
If OOP doesn't save you time, or produces better code, there really is no reason for you to use it. If you feel that structured programming works better for you, allowing you to create better games faster, then I think you should stick to that. Myself, I really can't imagine why someone would prefer structured programming instead of OOP... but I guess we re all different :-)
As for the performance concern, I don't think that's an issue at all anymore. I assume that you profile before optimizing, and if there is a case where choosing one methodology over another will give you a reasonable performance boost, then by all means use it, but don't optimize as you go along, choosing a methodology that makes you slower in the hope it might make your program faster.
svero
07-28-2003, 03:01 AM
I find people get too hung up with methodology -- like it's some holy grail that will give you good code or a solid program. Programming design methods are like tools. It's like the old saying... a bad carpenter always blames his tools.
Take UML for instance. There's a tool to lay out and think about the design of a set of OO classes. While the very act of using UML to lay your design out may be helpful and force you to confront things you might ordinarily not have thought of, at the end of the day the design you come up with is still your design. (its also helpful for other reasons but I dont want to get into that) Someone who designs poorly or who doesn't have a knack for how to organize their code can very easily lay out an obfuscated set of useless bug-prone crummy broken UML diagrams. That guy is using OO methodology. But does he really understand what he's using it for? He's like a carpentar using a saw to knock some nails in and a hammer to cut wood.
Good design is not about following rules or ideas or methods. Those are tools that help you think about a design. A bad programmer in BASIC, C, or Assembler will be a bad programmer in C++ or Java or using rational rose. What has to happen is that you have to understand what your goals, from a code point of view really are, and use the appropriate method for your particular scenario. (ie what kind of game your making, what language your using, what goals you have in terms of interface or re-usability etc...) That's why to some extent I agree with the statement that one shouldn't just forget structured programming. Things have their time and place and it isn't clear that one method is better than another in all cases.
Henrik
07-28-2003, 04:15 AM
I use mostly OOP with some normal "structured" code where appropriate. For me that's the best approach.. I don't agree about OOP being particularly slow though, it's in no way slower than structured programming if you know what you're doing.
Kai-Peter
07-28-2003, 05:40 AM
I agree with Steve V on the general idea of a pragmatic approach. A good reference about the mindset is the book (surprisingly named) "The Pragmatic Programmer" by Hunt and Thomas.
princec
07-28-2003, 06:00 AM
OOP through and through. My definition of OOP is radically different to that understood by most programmers though, and it's got a healthy dose of pragmatism and common sense chucked in.
Cas :)
Dexterity
07-28-2003, 07:46 AM
I use OOP. I think it's a good choice for a puzzle game like Dweep, since the game is all about objects and their interactions.
Jake Stine
07-28-2003, 07:46 AM
Originally posted by ergas
I personally use OO when I plan a package for generic use. For example for UI or physics or even graphics I used OO. But in some cases within an OO package I insert structured functions which need higher performance. For C++, OO is better for managing the memory, but there is always a lack of performance and also OO makes your application take a bit more memory.[/B]This used to be a legitimate concern years ago when Pentium 2's were just hitting the market and the compilers out there had a much harder time churning out efficient C++ code. These days, while employing all the features of C++ does incur a slight overhead, the nature of modern PCs and software is such that the difference is beneath a measurable amount. On modern machines, your game is going to spend over 75% of its time battling the memory bus and bandwidth limitations (as is the pitfall of all intense multimedia apps) .. and more if it uses a software-based engine. The extra istructions involed in OOP are nothing in comparison-- 0.1% perhaps of your total application CPU time in a worst case.
Just so long as you don't go creating CPIXEL classes for each dot in your sprite or other extreme program design flaw, you aren't going to have any performance issues what-so-ever. :)
Having several years of development experience, I could say that I am now spending quite a lot of effort to develop OO games as good as my old structured games. I could just now do as good as I did in the past. But I can see that OO is a good future knowledge investment. I would recommend game developers to use OO but never forget the older methods and continue to use them.I think this is an extremely wise statement and I live by it myself. If not for performance reasons, for simplicity and for readabilty. The same brush or knife isn't always the best tool for all situations, after all.
- Air
Dan MacDonald
07-28-2003, 08:05 AM
I use OOP because frankly, I can barely remember how to write linear/structured code. If your doing anything on windows I dont konw how you can avoid using OOP. STL, WTL, ATL, Winsock, DirectX, just about any package you plug into your code is going to be a set of OOP objects.
When I said I couldn't remember how to write structured code, it's not that I can't make a main( ) function and start coding. It's that I don't know how to break down a design into logical components and implement them in an efficent way wi structured programming. All my application design work is based on objects, they give me the ability to think about my design at different levels. A the object level for a high level understanding, or at the implementation of each object for the low level. I can't imagine trying to contain the entire design for a program at the low level in my head. I think it would explode :)
Lizardsoft
07-28-2003, 10:18 AM
OOP in C++. It took a while to get off structured programming and arguably I'm still not all the way there (your program can always become more OO ;)). The object concept works very well because it provides a model that can logically represent the problem at hand. Even if all you use it for is to group functions and data together, you have already realized a great deal of benefit (no more functions like MyAILibrary_MoveCreatore() littering the namespace). There is a great deal of thought that has to go into OOP design but the result can be an extremely flexible application. I think there's few people out there that know how to realize the full power of an OO language like C++. Everytime I take a C approach to a problem I feel queasy because I know that sooner or later I will discover that C++ has a much more elegant solution. I truly believe you could become a monk and spend the rest of your life meditating on OOP design, meaning that there are always things to discover no matter how much experience you already have. One of the biggest challenges I face right now is getting rid of my habit of using structs (I mean this in a C-style way, where the name of the struct is capitalized and it does not have member functions). Everytime I create this sort of struct it comes back to bite me later, when I realize that I do want member functions and data encapsulation after all (even when the struct consists of two ints).
Structured programming in PHP. I generally do not find a *need* for objects in PHP development due to the nature of web sites. Most of my scripts have the goal of being as small, fast, and secure as possible. Very few aspects of a site are complex enough for OOP in PHP to pay off. It is convienant for things like shopping carts and to a lesser extent, user authentication. Most of these systems aren't very OO either since they generally use classes simply to keep the namespace cleaner and make data handling slightly easier. There are of course situations where OO does make sense and if PHP ever changes to favour OO over structured programming I will gladly make the switch. Right now the cost of rewriting all current libraries simply to be able to say they use OO is too great.
tjones
07-28-2003, 10:39 AM
I'm the odd man out it looks. I used structured programming for most things.
I find that when using functions I tend to write with structures being inputs and returning some output, with no side effects, and many times, without failure cases.
The structure passing is somewhat OOP-like, but when I program with classes, I tend to use procedures (with side effects) and I generally return error codes.
Its totally a stylistic thing, but not having a lot of failure cases and having no side effects allows me to spend more time coding than debugging. However, game objects lend themselves well to being implemented as C++ objects, so YMMV.
My stuff is hardly pure, but the more I can eliminate my tendency for bugs the better. I see no reason not to use the technique most appropriate for the situation.
ergas
07-28-2003, 12:11 PM
After having read the replies I feel more confident in using OO.
I have a question (maybe Jake could answer). Is there any performance difference between calling
void set();
method and
virtual void set();
method of a class in C++ on a Pentium 4? Does anybody know or have tried to measure this?
ergas
BrewKnowC
07-28-2003, 12:24 PM
I try to always use OOP because most game objects can be put into a C++ class structure. The only time I revert to structured programming is when the routine is just too simple and would be MORE complex using OOP. (I know people say if OOP doesn't work out, then you designed your classes wrong, which is probably true, but I sometimes take the quick way out of a hole I dug myself into ;) )
tjones
07-28-2003, 12:24 PM
Technically, there is a performance difference, but it negligably insignificant.
In the game Unreal, pretty much everything is virtual, and it has a deep hierarchy, and it runs just fine.
Its always better to test and measure than to worry about such local optimizations. Tools like V-Tune give really good results. Otherwise, you end up optimizing things that don't give you much back.
Lizardsoft
07-28-2003, 02:09 PM
Regarding virtual functions, what tjones said. There is also a small size penalty for maintaining the table of pointers to the virtual functions since the address of the function to call has to be resolved at run-time instead of during linking. In practice this is also seldom a problem. Don't forget that C++ was developed when 66MHz and 32MB of RAM was common and great care was taken to make sure that switching to C++ would not have a great performance penalty. Consequently unless a few extra bytes and extra instructions matter in your program, you shouldn't avoid C++ features because they are technically slower. On a Pentium 4 I wouldn't be surprised if you had trouble even measuring the virtual call penalty ;)
Jake Stine
07-28-2003, 02:43 PM
Originally posted by ergas
I have a question (maybe Jake could answer). Is there any performance difference between calling
void set();
method and
virtual void set();
method of a class in C++ on a Pentium 4? Does anybody know or have tried to measure this?Just to backup the statement already made: Yes, the compiler needs to generate a little extra code to perform a virtual function lookup, which is effectively one ASM instruction more than calling a function via a function pointer in structural C code. On a P4 this translates to, at worst case, an extra cycle, and often nothing at all (because it may very well pipeline where there was no suitable pipelineable instruction before).
To be honest, the more glaring hidden inefficiency in C++ is in any method call at all. C++ automatically passes the owner class to any method 'behind-the-scenes.' For example:
void Image::Load(char *path) {}
// Is functionally equivalent to the code:
void Image_Load(CIMAGE *image, char *path) {}
In the OOP case at the top, the compiler pushs the handle to the class onto the stack automatically for you. In the second example you do it yourself, in order to pass the class handle to the code. In both situations the code then knows what class it should act on.
Now I'm not saying that the second method is faster than the first. Not at all, as both methods are effectively the same. The point is that when calling methods in OOP there is an extra parameter passed behind-the-scenes that you don't explicity declare, which results in two to four extra instructions per method call, depending on optimization features and total # of parameters. Likewise, OOP design means encapsulating everything you can within the object, and that means having to dereference the object pointer when you want to access data members. (more on this in a minute).
In the Pre-Pentium days this was almost deadly as stack manipulations were slow and could quickly eat up large %'s of your processor time; and the fact that OOP programming encourages the use of functions and more function calls as a whole merely compounded the issue. On Pentiums the stack instructions were sped up, and as of the P2 could pipeline more reliably... and so now days an extra parameter is hardly anything to fret about. Four extra instructions, in a worst case, come to two cycles. Something to keep in mind perhaps in tight loops running tens of thousands of iterations per second, but otherwise a small price to pay for object encapsulation.
The Quirks of Dereferencing
Ok, here's the biggest reason C++ got a bad name as a slow language. To access an structure pointer or class member, in the form of struct->data or this->data, the compiler must load the structure/class pointer into a register and then use that to load the requested member data into another register. Fortunately, this can be optimized easily so that the structure/class pointer is only loaded once and then used through-out the function. Unfortunately the Borland C++ 2.0 and 3.0 compilers neglected to do this 90% of the time, as did early Microsoft C++ compilers. To top things off, any non-professional edition of the Microsoft Visual Studio doesn't do it either. Using any of these compilers results hundreds of extra asm instructions per function when using good OOP-style programming habits.
Finally, anything running in Debug mode on any compiler, once again, will have thousands of these pointer dereferences just littered throughout their OOP code. The result can be code that runs up to five times slower than similar code written to avoid the use of struct pointers or classes. But then compile in Release mode and suddenly the difference in speed becomes quite minute. :)
So alas, C++ got a bad rap as a slow language as other peoples' code, littered with stand-alone variables and globals, simply sped past as if they were on turbo. For the record, with advancements in compiler optimization technology globals are now generally the slowest variables you can have in your code, as the compiler is forced to disgard a number of otherwise-safe assumptions it can make for anything passed to a function via the parameter list.
Both of these points are fairly moot anymore because most experienced structural-based coders will find themselves just passing the structure handles manually all the time anyways (data encapsulation is simply a necessity in most modern applications, especially anything using threads or implementing a decent user interface). So at that point the difference is nil, save for the the fact the C++ compiler is doing some of the work for you by passing needed parameters and dereferencing class pointers automagically.
- Air
Larry Hastings
07-28-2003, 05:08 PM
Some notes about C++ programming.
Why are virtual method calls slower than non-virtual method calls?
Virtual method calls are implemented by having every object store a pointer to a "virtual method table" or vtable. You can see the vtable if you inspect your objects at runtime... it will nearly-always be the very first thing in your class.
All the virtual methods defined for your class are assigned an index into this table. For instance, your "setTexture" call might be index 9. When your code calls setTexture(), the compiler generates code to pull the ninth element of the table (whatever it may be) and call it. This is a couple extra instructions, but (as we'll see below) it's really not very slow.
How much overhead does C++ add?
Most of the time, very little.
Windows compilers support different "calling conventions". There's the standard C calling convention, which says "all arguments are pushed on the stack, and the caller pops the stack". There's the slightly-faster Pascal calling convention, which says "all arguments are pushed on the stack, and the callee pops the stack". (This is faster because of the specialized "return and pop the stack" instruction x86 CPUs support).
For speed freaks, there's the "fastcall" calling convention. This says "the first two no-more-than-32-bit-wide arguments are stored in registers, all other arguments are pushed on the stack, and the callee pops the stack". Passing arguments in registers is muy bueno. You can theoretically set fastcall as the "default" calling convention, but I'm afraid of interactions with external libraries... it's rare that a header file explicitly states a function's calling convention.
So what about C++ method calls, you ask? Aha. For those, you don't get a choice. All C++ method calls use the "thiscall" calling convention (unless they use varargs). And, happily, thiscall says that the "this" pointer goes in a specific register.
So what's so bad about C++?
There's plenty to not like about C++. Apart from lots of dark corners and broken glass scattered about the "standard", there are occasional places where you'll get a tremendous speed hit without even realizing it.
Here's the example I remember from my past. It was from a large (at the time) homegrown class library for doing cross-platform multimedia titles.class CString
{
// a fully-featured string class!
char *storage;
int length;
int bufferSize;
CString(const char *s)
{
length = strlen(s);
storage = strdup(s);
bufferSize = length + 1;
}
~CString()
{
free(storage);
}
// ...
};
class CRenderTarget
{
// ...
SetWindowTitle(CString &s);
};So what happens when you call: renderTarget->SetWindowTitle("bumbles the wacky clown!");I'll tell you what happens: the C++ compiler generates a temporary CString object on the stack, with the constructor call and everything. It then passes that object into SetWindowTitle(). After SetWindowTitle(), it deletes the object.
This happened basically every time we wanted to pass in a constant string into a class... very few of the classes had members that took straight "const char *" strings. So these temporary CString objects were getting created left and right, which meant allocating and freeing memory from the C heap.
Obviously, once you know about this behavior, you can account for it in various ways. But there are other hidden pitfalls awaiting the unwary C++ programmer.
One last point about C++ being slow.
If you're thinking about using a scripting language with an interpreter, then using C++ should be a foregone conclusion. Interpreted scripting languages are 10x slower than C++ under the best conditions... most of the time they're slower even than that.
(Note that this rule doesn't count run-time compilers or assemblers like Softwire (http://softwire.sourceforge.net/).)
LordKronos
07-28-2003, 05:49 PM
When using OOP, there are some things you can do to help the compiler optimize your code.
One is to pass/return references or const references whenever possible. This can often save you from having to make extra copies of objects. This tip applies to structured programming as well. However, since classes often have complex constructors, it's a lot more important to remember to do this in OOP.
Another is to declare any method that doesn't modify its own object (either directly or by calling another method that does modify) as a const method. For example, if you have an dynamic array class (or template), and the class has a getCount method that returns the current number of elements in the array, you know that the array doesn't get modified in the process. So declare the method as:
unsigned int CDynamicIntegerArray::getCount() const {.....};
This tells the compiler no modifications were made, and that it can often optimized calls to these functions. Then if you also had a "get" method:
unsigned int CDynamicIntegerArray::get(unsigned int index) const {...};
you could then do a loop:
int total = 0;
for(int ctr=0; ctr<array.getCount(); ctr++)
{
total += array.get(ctr);
};
In this case, the compiler would know that since "getCount" and "get" are const methods, they don't modify the "array" object, and that it is probably safe to only call "getCount" once.
Depending on what it is your are optimizing, this may make a big or small difference. In the case of getCount, you probably aren't saving much. However, if it were a class to perform some calculus (integrations or something) you could find huge performance gains by doing this. Personally, since simply adding the "const" takes about 1 second of work, I just try to do it whenever its applicable, even if the return is likely to be small.
Performance aside, perhaps one often overlooked advantage of using OOP is when dealing with tools like VC++ that have code completion. In VC++, if you have a dynamic array class and you have an object "array" of that class, you can just type "array." and you instantly get a list of only methods and properties relevant to that class. I often have trouble remember the exact name of a function, so this can narrow it down from a list of 200 functions to only the 5 or so that are relevant to an array class. It lets me code faster and disguises my forgetfulness, so I feel better about myself in the process :)
Julian
07-28-2003, 06:22 PM
Regarding (unnecessary!) inefficiencies in C++, I think the following proposal is very interesting:
http://std.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
Probably doesn't really solve that particular CString problem, but lots of other, imho even worse ones. *dreams of a less conservative committee* It's not directly related to OO, but would have a large impact on it.
JackNathan
07-28-2003, 07:51 PM
OOP - most definately. As far as optimization and C++ performance -- don't worry about it. Write the cleanest, easiest to read, and maintainable code. Optimize when you have to.
For those who have time to read for a while on the topic this guy has some interesting views.
Optimization: Your Worst Enemy
http://users.stargate.net/~newcomer/optimization.htm
Jack
Fenix Down
07-28-2003, 08:03 PM
I can't imagine not using OOP to make anything fairly complex. Maybe I don't have a very good imagination :) but OOP makes everything so much more organized, and gives you free encapsulation. And like has been said a few times already, the speed cost is negligible at this point, so there's really no reason not to use it.
svero
07-28-2003, 08:19 PM
Originally posted by JackNathan
OOP - most definately. As far as optimization and C++ performance -- don't worry about it. Write the cleanest, easiest to read, and maintainable code. Optimize when you have to.
(snip)
Jack
Exactly! All this talk about optimization and C++ performance IMHO comes across as very amature. Anyone experienced knows that this simply isn't an issue even worth seriously discussing. Most claims that C++ is inefficient are bogus anyway. Like this argument that large classes can be created on the stack or copied. Pass a reference! Java of course is a little different but then you end up with tricky garbage collection issues instead.
It's especially true in games where the bottlekneck is almost always some display related issue and not a calling convention or an extra indirection somewhere... those are almost always negligeable. The first rule of code performance tuning is to profile the code, identify bottlenecks, and only make the changes that are necessary leaving the bulk of everything else as maintainable as possible. Heavily optimized code tends to be hard to work with and bug prone.
Diodor
07-28-2003, 08:36 PM
I use procedural C exclusively - even the libraries I use are all C.
What I have found, compared to my C++ days is that I simply don't have to do a lot of design thinking anymore. I still pay a lot of attention to how the modules talk to each other, but inside the module - which is seldom bigger than about a thousand lines, I simply write good old straight forward no design needed C code.
One nice thing for my choice: where ever I read code, I can be very sure of what it actually means. In C++ with all those namespaces flying around, you can have a similar method or member variable in more classes, (let alone function and operator overloading). Things are straight in C - due to naming conventions I always know from what module do function calls and variables come from anywhere in the code. I don't have to be aware of a lot of context.
Nick Bischoff
07-28-2003, 10:19 PM
My biggest problem with regard to OOP is that I was taught in school for for 4 years in Pascal (yeah...crappy DOS Pascal), which is so structured it hurts. In our final year we did C...again, no C++ and so np OO theory. Now having to shift over to a newer way of thinking is just to hard :) Getting there however.
ergas
07-28-2003, 10:46 PM
Thanks everybody for the information you have given. I learned quite a lot today, and I think I couldn't find any better specific information than this.
If not tiring you too much I have the next question! Have you ever implemented or know how to implement intefaces in C++, as just the same as Java does?
Example:
In the below code, there is a base class A, and interface class Interface, a class B derived from A which implements Interface and a manager to manage any base object A that is an instance of Interface. But the code doesn't work! In Java it would. If you can understand the problem (maybe try on your computer) and suggest soutions I would be very grateful.
class A
{
public:
A() {}
};
class Interface
{
virtual void set() = 0;
};
class B : public A, public Interface
{
public:
B() {}
virtual void set() {}
};
class ManagerOfInterface
{
public:
ManagerOfInterface(A* a)
{
( (Interface*)a)->set(); //here is the line where it crashes
}
};
main()
{
B* b = new B();
new ManagerOfInterface(b); //crash!
}
Have a look. This pattern is used very commonly in Java but I can't do this with proper ways in C++.
Thanks everybody
luggage
07-29-2003, 02:13 AM
Originally posted by Diodor
I use procedural C exclusively - even the libraries I use are all C.
What I have found, compared to my C++ days is that I simply don't have to do a lot of design thinking anymore. I still pay a lot of attention to how the modules talk to each other, but inside the module - which is seldom bigger than about a thousand lines, I simply write good old straight forward no design needed C code.
One nice thing for my choice: where ever I read code, I can be very sure of what it actually means. In C++ with all those namespaces flying around, you can have a similar method or member variable in more classes, (let alone function and operator overloading). Things are straight in C - due to naming conventions I always know from what module do function calls and variables come from anywhere in the code. I don't have to be aware of a lot of context.
Exactly the same here.
Julian
07-29-2003, 03:07 AM
Originally posted by ergas
In the below code, there is a base class A, and interface class Interface, a class B derived from A which implements Interface and a manager to manage any base object A that is an instance of Interface. But the code doesn't work!
Why don't you pass pointers or references to Interface to ManagerOfInterface?
class A {};
class Interface
{
public:
virtual void set() = 0;
};
class B : public A, public Interface
{
public:
void set() { /* do something * }
};
void doSomethingWithAnObjectThatSupportsInterface(Interface& object)
{
object.set();
}
int main()
{
B b;
doSomethingWithAnObjectThatSupportsInterface(b);
}
This should work. I don't think this is the right forum for C++ questions, though.
Carrot
07-29-2003, 04:39 AM
Actually, I think this thread introduces the bigger topic of game design in general.
I have been programming for almost twenty years and have been using conventional design methods for the last ten of those years as a professional developer.
I've never had much difficulty applying OO-design methods to projects that I've worked on in my 'real' job, but using the same methods for game design has never really work satisfactorily.
It's not that identifying the classes of objects is the problem, I just think both types of projects have different design goals.
Games tend to have more time-critical constraints for one.
Typical design patterns that work well on non-time critical projects tend to fall to pieces when applied to game development.
I nearly always end up making some sort of OO-concession and tend to fall back to some structured programming technique instead.
It may be that the initial OO-design isn't up to scratch, up I believe that its more to do with the dynamic nature of game design itself (especially independent games).
The design itself tended to change throughout the development cycle and initial OO assumptions that were made at the start of development no longer held - maybe the game just wasn't any fun!
It may be the case that some OO-constructs/use-cases (especially in games) just don't fit well into the game development process.
As a side note, there's an interesting article on Gamasutra (http://www.gamasutra.com/resource_guide/20030714/hamann_01.shtml) about game design in general you might like to look at.
princec
07-29-2003, 05:38 AM
I wrote the whole of my game in Java which pretty much mandates OOP techniques, and it's even reknowned to be slow too, but it isn't.
I think the biggest OOP mistake is to think that everything is an instance of an object; followed by designing for abstract extensibility and generic programming when what's required is a concrete solution to a well-defined problem. This ends up with what I described as "Object Oriented Wank" in my development diary, and leads to a big refactoring to simplify everything when you get too confused later on in the project.
Cas :)
Larry Hastings
07-29-2003, 05:51 AM
class ManagerOfInterface
{
public:
ManagerOfInterface(A* a)
{
( (Interface*)a)->set(); //here is the line where it crashes
}
};
main()
{
B* b = new B();
new ManagerOfInterface(b); //crash!
}
The problem is the two casts: the cast from B to A, then the cast in the line you marked "here is the line where it crashes". There is a special kind of cast (I don't know the terminology) that happens when casting an object from a multiple-inheritance class to one of the class's parents. The simple answer to your problem is: there's no relationship between an object of class A and an object of class Interface. If you establish one, your problem will go away.
Remember that vtable I mentioned? Instances of objects from multiply-inheriting classes have one vtable for each class they (multiply) inherit from that has one. In other words, if you made th:
class A {int w; virtual void set(int w); };
class B {int x; virtual int get(void); };
class C { int y; virtual void annoy(void); };
class D : public A, public B, public C
{
int z;
// ...
}An instance of D will actually contain three vtables, like so:00: [vtable for A] // start of A mini-object
04: int w
08: [vtable for B] // start of B mini-object
12: int x
16: [vtable for C] // start of C mini-object
20: int y
24: int zWithout this, you wouldn't be able to cast an object of class D to class B or C properly. Each of these mini-objects inside our D object needs its own vtable.
Consider this code, added to the above example:int getFromBObject(B *object)
{
return object->get();
}
// ...
D object;
getFromBObject(&object);
This code should compile and run without incident. When you call getFromBObject(&object);, the compiler passes in the address of the vtable-for-B (offset 08). This means our little mini-B object inside our D object looks and behaves exactly like any other D object.
So, the problem is, the compiler doesn't know in your ManagerOfInterface() constructor that the A* object passed in is really a B* object. So instead of doing this clever MI casting, it does a brute-force "pretend this is really an Interface* object".
If you really want to pass around totally generic objects, and arbitrarily pull out interfaces that they might or might not actually implement, you'll need to either use COM or reinvent it. COM addresses this problem nicely; all COM objects inherit from IUnknown, and you can ask IUnknown objects for a pointer to a more interesting interface. If you'd like to learn more about COM, I recommend "Essential COM" by Don Box.
ergas
07-29-2003, 01:05 PM
Thanks Larry. I do not use COM but I think I understand what you mean. This is something like holding a list of implemented intefaces for every new object. And from that list, you can request the interface and go on with that object. Thanks a lot Larry you have given me the principle idea.
ergas
07-29-2003, 01:32 PM
Originally posted by Julian
Why don't you pass pointers or references to Interface to ManagerOfInterface?
The reason is that sometimes a class implements more than one interface and the manager intends to manage objects by their roles acquired from multiple interfaces. Assume that there are 3 different interfaces and the manager wants to filter objects which implement 2 of those only. In this case the manager shall filter objects which implement the 2 interfaces and call their methods. For filtering, Java uses "instanceof". But in the code I had given, I did not go into filtering because I wasn't expecting a discussion as complicated as this.
ergas
Lizardsoft
07-29-2003, 02:49 PM
Originally posted by Carrot
Actually, I think this thread introduces the bigger topic of game design in general. [....] but using the same methods for game design has never really work satisfactorily.
It's not that identifying the classes of objects is the problem, I just think both types of projects have different design goals.
Games tend to have more time-critical constraints for one.
Your post about OOP clashing with games reminded me of one type of OOP approach that I do dislike. It goes something like this:
CCreature
/ \
CMonster CHuman
/ \
CAlien CSlug
Etc, etc, etc. This concept of separate classes for each monster type is the first idea anyone who has read an OOP comes up with. I disagree with it though due to the amount of hardcoding required. The worst part of this approach is that you cannot add a new creature without changing the exe (or dll if you want to get fancy). I like customizable games and strive for it in all my development. I feel a better class structure would instead create building blocks like a CAI class or CHealth class that could then be pieced together. You can even create a general CCreature class (and probably should), but the actual creatures should be created using information from data files so that the game can be expanded without touching a line of C++ (incorporating a scripting language like Lua goes a long way for this). Of course for a simple game that will never have more than 2 types of monsters this is overkill, but for larger games with a variety of creatures I think it works well (and doesn't have to be very complex at all).
Dexterity
07-29-2003, 03:59 PM
I did use the above approach for Dweep. I created a CGameObject class, and from there I derived CDweep, CLaser, CFan, CMirror, CBomb, etc. This seemed very natural at the time, and it was relatively easy to code. But this was for a one-programmer project where I was the designer too. Working in C++ is just as easy for me as working in a scripting language. The design of the game was locked down early, and no major changes were made once coding began.
I don't know how things would have worked out if I didn't hardcode the object behavior. But I do know it would have taken a lot of extra development time. While I feel this approach makes a lot of sense on a big-budget project with lots of programmers and level designers, I think it might be overkill for a lone wolf project. After the basic design was done, Dweep took only two months of coding and level design. A scripting system could have doubled the development time.
It's tricky to balance flexibility with rapid development. There's a push in the retail side of the industry to code things as general as possible now... writing engines that are largely data-driven. I've done a lot of this myself, often writing very flexible modules that could work in a variety of games. But sometimes I wonder if the cost of this flexibility is too high for most indie projects and that I wouldn't be better off doing more hard-coding for specific games. Technology can sometimes advance so quickly that reusable components quickly become obsolete after only being used once or twice.
Lizardsoft
07-29-2003, 04:17 PM
I agree that for a game like Dweep, where every object behaves very differently and the game itself is small and easy to code, using separate classes is fine. A game that relies heavily on slightly different creatures though, like arcade shooters, space invadors games, RPGs, turn-based combat, etc could really benefit from allowing creatures to be added through data files. As for going all out on a scripting language, it's not too hard because of the free Lua, but I would reserve that for complex systems like RPGs and FPS games. For the sake of argument, you could have a system where every object has a flag that specifies if it can blow things, teleport, shoot, etc, and handle things that way but you wouldn't gain much with this approach (code could be messier in fact).
Dexterity
07-29-2003, 04:57 PM
Makes sense. Thanks for clarifying.
With Dweep the objects were all very different. They inherited basic methods like Draw() and Update(), but after that each object had a unique behavior that would have required custom scripting.
dreeze
07-29-2003, 05:13 PM
Hmm, I've been pondering about this the last few months. Take Quake 1 for example (which is one of the most modded engines of all time), Quake 1 used a homegrown scripting language called QuakeC. I think this was done for extensibility since they discovered that it was popular in Doom to modify the game.
Quake 2 used dll's written in pure C (like the rest of the engine) and I don't think the code is any messier than the QuakeC version. The disadvantage of this was that you had to recompile the dynamic library for each OS Quake 2 was ported to. (They fixed this in Quake 3)
Thus, unless the scripting language moves the programming to a noticably higher level and unless you want the user to be able to modify the game, I don't think it's worth the time it takes to integrate it into the game.
Originally posted by dreeze
Thus, unless the scripting language moves the programming to a noticably higher level and unless you want the user to be able to modify the game, I don't think it's worth the time it takes to integrate it into the game. I dissagree for two reasons. Firstly consider users being able to modify the game. That is essentially people being able to modify the game without being in constant communication with the programmer(s). The exact same thing applies in-house if there is more than one devloper. Working on my game, the scripting engine is there so that the people who will be doing levels can add stuff without bothering me as much so I can focus on finishing other aspects of the game.
Secondly, Script code is a great way to encapsulate behaviour that is limited in scope. I would hate to have code in my game to say that if the player jumped 5 times on a spot X,Y and the level was Level number Z, then create object Q. This is code specific to a level. It should be devloped by the people who make the level and it should be bundled with the level data. Scripting facicitates this nicely.
dreeze
07-29-2003, 06:06 PM
From Lizardsofts post I got the impression he wanted to implement enemies and game objects through scripts. Of course you can just use the scripts as glorified object-descriptors but I have a half-working system which uses lua for parsing input, moving objects, initializing objects and some other stuff.
This is a part of the game logic which I think should be kept outside scripts on smaller games which don't require that flexibility. (which means I'm moving away from using scripts in that manner)
I have been considering using level scripts for stuff like doors, triggers and scripted events because that flexibility is worth it. But I think level scripts will be a lot smaller than what I described above.
Larry Hastings
07-29-2003, 06:15 PM
Initially in my game (an overengineered Arkanoid-style game), I also had a separate class for each onscreen object. That meant I had a paddle, a ball, a wall, a brick, a moving brick, a round brick, a moving wall, a scripted wall, a scripted brick, a one-way wall (where the ball passes through in one direction and bounces in the others)... I quickly had an explosion of classes.
One proper solution would have been to use interfaces, as above. But I wanted to interoperate with my scripting language, and my scripting language doesn't do multiple inheritance.
So I wound up switching from an "is-a" relationship to a "has-a" relationship. Now there is a generic onscreen "pawn", and each pawn has four "behaviors": mover, collider, toucher, destroyer. Mover is what moves it every frame. Collider is how the pawn negotiates with the other pawn to decide whether or not they care if they collide. Toucher is what does the actual reacting when a pawn touches another pawn. Destroyer handles special-case code for when the pawn is destroyed (e.g. a brick plays a sound and gives you some points).
This works pretty well. It reduced the class explosion from movers * colliders * touchers * destroyers to a much more manageable pawn + movers + colliders + touchers + destroyers. And it's easy to create new behaviors at runtime in script, so I can have special-case one-off behaviors wherever I like. And it's no problem for pawns to be a lumpy combination of some scripted and some hard-coded-in-C++ behaviors.
Dexterity
07-29-2003, 07:04 PM
Originally posted by Lerc
Working on my game, the scripting engine is there so that the people who will be doing levels can add stuff without bothering me as much so I can focus on finishing other aspects of the game.
I agree that some degree of scripting makes sense if there will be custom behaviors for different levels, such as unique triggers for switches. It would indeed be a pain for the programmer to get involved with custom coding for every level if others can handle that work more efficiently.
I didn't have to worry about this with Dweep because all the objects were coded with fixed behaviors, including all their possible interactions with other objects. There isn't a single line of custom code for any level in the game. All gameplay results from the objects' pre-programmed interactions. Even though there are only about 10 different objects in the game, there are 200+ levels now available, most of which were created by players. I've enjoyed seeing the creative levels that players devise -- things I didn't even think could be done. It was really amazing to see new kinds of gameplay I hadn't anticipated... just a natural consequence of the objects behaving according to their nature.
I hope to develop a Dweep 2 in the future using this same design approach, but with a whole new set of objects and behaviors. It took me a solid four months though just to get the right set of objects designed for Dweep 1 before I wrote any code at all. Early versions of the original design included things like electromagnets, batteries, puddles, and all kinds of other objects that didn't make the cut.
Siebharinn
07-29-2003, 07:18 PM
I use OOP just about exclusively.
I like C++, but I didn't really "understand" OOP until I'd used a few other languages. Objective-C and Python had the most impact on me.
I have to agree with the guys that said that worrying about the overhead of virtual calls or "this" parameter passing is just silly. There are a lot of other areas that need your attention first, and if you're that worried about clock cycles, it's time for assembly.
I'll take a good design over highly optimized code any day.