Game programming without OOP

Started by
24 comments, last by apatriarca 14 years, 1 month ago
I've coded a couple of small games and they've all been written in object-oriented languages. I've found OO to be invaluable in my games programming experience. This is why I simply don't understand how a large game can be built without using classes and objects. Yet, most games are/were written in C. So, how is game code structured in non-OO languages? Say for example that we a class "Monster" with the property "hitpoints" and a method called "attack". Without the use of classes, how would we go about associating a thing that attacks and its hitpoints? I already have a few convoluted ideas about how I could go about this, but I'm looking for a way that scales well and isn't a complete pain to program. Thank you for the help. [Edited by - probing on March 13, 2010 7:40:22 PM]
Advertisement
Quote:Original post by probing
Yet, most games are/were written in C. So, how is game code structured in non-OO languages?


Object oriented programming is not bound to a language; it's completely possible to write OO code in C, it just wouldn't look like the C++ equivalent (no classes, etc.)

For example:
typedef struct player{    vec2 position;} player;player *createPlayer(){    player *p = (player*)malloc(sizeof(player));    p.position.x = 0;    p.position.y = 0;    return p;}void destroyPlayer(player *p){    free(p);}void movePlayer(player *p, const vec2 *m){    p->position = addVec2(&p->position, m);}// ...player *p = createPlayer();vec2 m = { 10, 0 }; // move 10 units rightmovePlayer(p, &m);// ...destroyPlayer(p);
Yes, but my question is how would you go about coding a game without using OOP. Actually, the intent of my question is to find out how, when using a language that doesn't have strong support for OOP, what is the best way to code a game?
The code structure depends on the game (a 2D puzzle game and a 3D FPS aren't very similar for example) and on the language (C is completely different from haskell for example). It is impossible to give a satisfactory answer to a question like that. If you make your question a little more specific (a particular game in a particular language), we will be able to give a more specific and useful answer.
To be more specific, suppose we want to create a top-down 2D shooter in C. Is there a good method to follow, one that isn't OO, as nullsquared's is.
The anti-OO argument is primarily about runaway hierarchies. Inheritance trees 10 levels deep, with hundreds of individual classes. Inevitably, the top-down inheritance will become problematic.

Counter-argument is about a different approach (it can be likened to ORM table design) towards structuring data.

Rather than expressing functionality via "X can do something", it is about saying "X has following things".

On one end, there is the entity approach. There is one single object type, which then contains functionality as members. Renderer, Input, Physics, ....

The simplest OO equivalent would be this:
class Object {  void jump();  void run();  void attack();  void die();  void foo();  ...};

To create a new object type, extend this and implement just what you need - leave the rest empty. If you add new functionality - add it to base class for *all* objects to have. Those that don't need it, won't implement it.
Since this doesn't allow code reuse between similar classes, such shared functionality would be implemented externally.

The benefit of this change is that inheritance is not forced by language and can be changed arbitrarily as needed without changing class designs. Zombie is no longer a Monster, but needs to use MonsterAIHandler? No problem.


At the far end of this spectrum, only archetypes are stored. Monster makes sense, Weapon and Armor might as well. The rest is done by configuration:
struct Monster {  int hitpoints;  int damage;  int speed;  bool undead; // zombies  bool holy; // paladins};

Imagine Paladin and Zombie inheriting from same base class. You could not make Zombie Paladin.

The processing then also changes into something like this:
void updateMonsters() {  for (Monster m : monsters) {    if (m.undead) testForHolyDamage();    if (m.holy) applyHolyAura();  }}

If you have separate Paladin and Zombie classes, interaction between holy and undead would need to be explicitly modeled, perhaps via helper classes, or extra damage, etc... Here you get all of this for free. This can become a problem as requirements change, and original class design is no longer suitable.


Two important things:
- Non-OOP designs will be implemented in OO language using objects. Unless people really enjoy pain
- Both, OOP and non-OOP designs can be badly messed up. Just in very different ways. OOP designs become rigid and difficult to extend, non-OOP designs become messy and can suffer from spaghetti code


Experience is about finding a proper compromise on which parts to apply rigid OO principles (emphasizing self-containment), and which to leave flatter and less structured.

There is no 10 steps to good design tutorial.
Thank you for the informative post. I think I understand a bit more now.
Here are a couple links that might interest you: A blog post on data oriented design A PDF of a presentation on data oriented design
There's another point to be made of what non-OOP allows. In an event-based system, things would look like this:
Monster::takeDamage(int d) {  hitpoints -= d;  onHealthChanged(this, d);  if (d < 0) onDie(this);}

The above on___ are events. Whenever and really whenever health changes, they would be triggered. This is convenient, onhealthChange would display that -23 floating text. But at the same time, this can be either slow (imagine firing one such even each time a particle changes).

And here a design problem arises. Ideally, one would want to monitor each and every property change. Perhaps each particle (tens of millions of events firing each second). And when designing classes, each variable would need to predict which events it fires. Only health lost? Also gained? Are the same event? Different? Different events for different values?

This results in combinatorial explosion.

The "non-OO" design would be different:
void updateMonsters() {  for (Monster m : monsters) {    if (m.damageTaken > 0) {      m.hitpoints -= m.damageTaken;      m.damageTaken = 0; // reset damage    }  }  for (Monster m : monsters) {    if (m.oldHitpoints != m.hitPoints) {      displayFloatingText(m.oldHitpoints - m.hitPoints);      m.oldHitpoints = m.hitPoints;    }  }}


Here hitpoints might change many times, but would trigger only one event. And if we want a different kind of event - we don't need to change classes, add more events, or anything - just add more functionality to post-processing step. Want to display only negative health (if (m.OldHitpoints > m.hitPoints)), only health gains, displayed in blue to show healing (if (m.oldHitpoints < m.hitPoints). Detect death (if m.hitPoints < 0) removeMonster(m)).

All this inside one single method, without changing the rest of classes, adding extra delegates, events, methods, handlers, callbacks, observers, .......


But this also exposes the downside, since the changes to data are no longer encapsulated, so two parts of code might be modifying same variables. Here is where OO comes in.

OO at higher level encapsulates these parts. There might be Monsters objects which is responsible for managing monsters, but cannot modify player. Or similar. And this is where the design must be thought over. Who knows about what, who can modify what, and this is where the OO principles pay off.


Which methodology is better depends. If using scripting, the non-OO approach might be slightly better. Most scripting languages do not have strong OO foundation, so they don't except strong OO concepts. No point in forcing it upon them, just give them a well defined interface.

Not separating core chunks into some form of OO would be a mistake and fallback to the almighty global state. It also isn't needed, it is fairly well understood which systems need to talk with each other.

Final detail - above approach can be considerably more performant than fully encapsulated OO. This may not matter much as far as PCs go, but streaming lots of data is friendlier to GPU, it is perhaps slightly easier for interaction with scripting languages, and is likely to incur much less overhead when running in sandboxes (Flash or similar). Finally, if using managed language, it requires much less memory allocations.

Overall, designing a non-OOP system today would be foolish, as would exposing global state (inhibits unit testing, runaway coupling). Polymorphism, inheritance and encapsulation are a good thing if applied at proper level. Central idea is to undertand what needs to be a self-contained, self-aware object, and what can be left as plain data and plain code.

Quote:Here are a couple links that might interest you


While PS3 is apparently very OOP-hostile my objections are only marginally related to performance. I retain a more pragmatic approach dealing more with finding a proper level of abstraction and raising it above typical OO, for the sake of less formal and perhaps more flexible design, rather than strictly following everything-is-object mentality.

While at some point compile-time checking and rigid interfaces were considered superior, the explosion of dynamic languages has shown they have considerably less impact on code quality, and might actually be deterring it. So with all the automated testing possible today, it might be worth reconsidering some of the strict positions on just how much safety and encapsulation is needed in practice.
What you are asking about are the different programming paradigms. They are essentially different ways of thinking about programming. The following are some of the major ones:
- Object-Oriented, e.g. Java, Smalltalk, C++
- Procedural, e.g. Delphi, C, C++
- Functional, e.g. Lisp, Haskell
- Logic, e.g. Prolog

To go over how to write scalable applications in each would take a long time. Languages often do not lock themselves into one type - C++ can be written in an object-oriented or procedural way, for instance.

Briefly: Object-oriented applications encapsulate data and methods in objects - the data structures and subroutines are put together in an object.

Procedural applications are divided up into modules. The subroutines act on the data structures (see nullsquared's example which is actually procedural).

Note: The following I haven't written large-scale software in.
Functional applications have subroutines with no "side-effects" - they only look at their parameters and return values. They are not evaluated step by step, but consist of nested functions which can often be evaluated in any order. They are structured in modules, similar to a procedural applications.

Logic applications consist of a declared set of premises. Programs are structured similar to a mathematical proof, and you run things by trying out theorems.

A quick Google should give you more information on any of these.
[size="1"]Try GardenMind by Inspirado Games !
All feedback welcome.
[s]
[/s]

[size="1"]Twitter: [twitter]Owen_Inspirado[/twitter]
Facebook: Owen Wiggins

[size="1"]Google+: Owen Wiggins

This topic is closed to new replies.

Advertisement