Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
flubbernugget

How were video games written before OOP?

Recommended Posts

I've been programming with allegro in C++ recently and as I was building a simple pong game, I realized how heavily I was relying on creating different classes and objects for all of the elements of the game. Although I've never worked with any procedural programming languages like C or (I think) Fortran, I tried to write a game that didn't use any objects or classes thinking about games like Doom that were only written in C. I couldn't think of any organized or practical ideas.

So how were video games written before Object Oriented Programming (mainly C++ I believe) became the norm?

Share this post


Link to post
flubbernugget said:

So how were video games written before Object Oriented Programming (mainly C++ I believe) became the norm?

Pretty much in the worst possible manner, largely. I've seen the reverse-engineered code for Dungeon Keeper and it is an utter nightmare.

The overwhelming philosophy in game programming has always been to cobble something together as quickly as possible and if it "works" then that's good enough.

Mature developers like Carmack are even now just discovering the value of tools like static analysis that have been a staple for quality control in other areas of the field for a decade or longer.

Anyway just imagine the vagaries of the worst entangled 10000-line-long modules, deeply nested loops complete with goto's to break out of them, static hardcoded unchecked limits, reliance on machine-specific hacks and idioms to do even mundane tasks, programs that won't run if they're not compiled in debug mode, complete lack of commenting, etc.

DOOM is a real exception in its time period because Carmack had contemporaneously been using Objective-C and was heavily influenced by it to make his code cleaner, more structured, and more portable.

DOOM includes a pseudo-polymorphism idiom accomplished via type punning of structure pointers (thinker_t serves as a "base class" to all object types which have to "think" each gametic, such as flashing lights, moving floors, and monsters).

Share this post


Link to post

It is important to note that in OOP, objects are a concept known only to the language/compiler. That is to say OOP doesn't really provide anything new that couldn't be done before, it just allows things to be programmed in a way that is more organized.

In C you still have structs, but unlike in C++ structs are only a group of variables. For example the following should be valid in C:

typedef struct
{
    int x, y, z;
} Thing;
In C++ that struct might have methods which can be called to perform operations on the Thing object. These methods are basically nothing more than functions with the first argument being a pointer to the object "this". So where you might have `void Thing::Move(int distance)` in C you would have `void Thing_Move(Thing *this, int distance)`. Use function pointers in your structures and with care you can achieve inheritance and method overriding. What you lose are simply features which exist only at compile time. For example the compiler is the only thing that enforcing encapsulation rules. The public, protected, and private modifiers pretty much disappear at compile time.

If your non-OOP language doesn't support structures then arrays could be used to fake structures. After all pointers and arrays are basically the same thing in C. But again, this means that even more care has to be taken by the programmer.

Share this post


Link to post

Back in the day they used assembly language, and the architectures were much simpler so that wasn't as much of a burden as it sounds.

And structured programming has been around for a very long time. Even as I was growing up, it was common to use GOSUB/RETURN functions or similar CALL/RET in asm. It's also very easy to write clear, structured code in ANSI C. Take a look at NetBSD/OpenBSD's kernels, for example. And C has structs, which can be used to store instance data and passed around via pointers.

As an exercise, rewrite your pong game in ANSI C & SDL. You'll see it's not very hard. More complex games may benefit from OOP, or maybe not. It depends. :) Also, in the 90's, C++ compilers were not very good yet, so some people stayed with C. And it is a much simpler language, so it's got that going for it too.

I miss the days when a machine booted into ROM BASIC, so you could immediately hack away and write games without needing libraries, compilers, and jumping through other such hoops. A pong game would have been a couple screens of BASIC code...

Share this post


Link to post

In my procedural python fumblings, I would feed functions big lists (arrays) mostly, and have them return stuff, minimizing side effects. When I wanted multiple instances of something, like multiple blocks in atari breakout, I started spreading the data out to multiple lists, treating the index like an id for each block. Like say each breakout block has this data: position, color, hitpoints
I'd make 3 lists (arrays):
blockPositions = [(0,0), (5,0), (10,0), (15,0)...]
blockColors = [red, yellow, red, green...]
blockHitpoints = [4,1,4,3...]
Then if I want to deal with block #2 (starting from the 0th index)
i = 2
print blockPositions[i], blockColors[i], blockHitpoints[i]
would print (10,0), red, 4
So my 'object' was sort of spread out through multiple lists.

Maybe retarded but worked for simple stuff. That 'data spread to multiple lists' tactic seemed to 'flatten out' nested lists and simplify stuff for procedural at least and I successfully did somewhat interesting stuff with it.
I'm still new to OOP and see how it can be more powerful and manage complexity better, mostly due to people in this forum recommending it when I.. complained.. about it in a previous thread.

Share this post


Link to post

I've seem the assembly source code to Asteroids.

Pong wasn't programmed, it was built electronically.

hex11 said:

the architectures were much simpler so that wasn't as much of a burden as it sounds.

Try telling that to any Atari 2600 programmer. Modern architectures are significantly more forgiving than putting in too many instructions and screwing up the rasteriser's rendering.

Share this post


Link to post
GooberMan said:

Try telling that to any Atari 2600 programmer. Modern architectures are significantly more forgiving than putting in too many instructions and screwing up the rasteriser's rendering.


I'm talking architecture, complexity of the CPU, bus, etc. on a hardware level. Which has many variations, over the course of decades. I never met another architecture as convoluted and full of cruft as modern x86. ;)

Anyway, I don't know anybody who programmed their own 2600, or for that matter NES or Sega game consoles. Most of the hobby programming was done on home computers like C64, Amstrad, etc. These, the ones I used, had very simple architecture compared to today. They were a joy to work with, unlike today.

Share this post


Link to post

What the others said, plus a few gotchas:

Some would argue that merely using data structures or even "functions operating on particular instances of data structures" isn't true OOP, but merely "handle oriented programming" and that very few languages are truly OOP anyway (e.g. Java isn't considered a pure OOP language, let alone C++ or C# or D or Delphi), and that OOP is an overrated/hyped/bogus concept anyway.

That's more of a religious position, if anything, and for me most real world applications of OOP are, for all practical effects and purposes, indistinguishable from "handle oriented programming", which is also what Doom uses extensively. I could care less if booleans are not truly objects, as I would dread to think e.g. of a number crunching application using arrays of object-wrapped booleans or floats.

From my experience with early home micro/BASIC games, the usual approach was just to use a bunch of arrays (or even of simple variables named e.g. sprite_1x, sprite_1y etc. !) indicating very simple status like position/speed/health of whatever blobs were your characters/heroes/bouncing balls, throwing in a "playloop" where you polled the keyboard/joystick, do some basic movement and interaction logic, then do the drawing, and rinse/repeat until you "won" or "lost" the game.

This approach did indeed help you cobble together a working blob quickly, but it was near impossible to get anymore complex than that unless you have had previous exposure to data structures or access/knowledge to a better programming language. Most BASIC dialects available on home micros and early PCs as well as their tutorials were quite limited and didn't really encourage using any kind programming finesse. The most complex stuff you could find, other than arcade PEEKs and POKEs, were "DEFINE FUNCTION" or "DEFINE FIELD" directives.

Finally, I remember a similar question being asked literally 20 years ago in a computer magazine, along the lines of "How are professional arcade games made?". The answer was that they were made using a mixture of low and high level languages citing "machine language", C, Pascal, and even FORTRAN. At that level, I bet it paid using some more organization than sprite_1x and player_1_life variables (and possibly NOT calling them PX1 and PL1!)

Share this post


Link to post

That's why I loved the OOP college courses, not because it allows me to define boring databases, but because it's good for making games!

Share this post


Link to post

that OOP is an overrated/hyped/bogus concept anyway


This is pretty much my opinion on OOP. There's no reason an object can't be a simple C structure as used in Blzut3's example. I do miss templates sometimes, but it's worth it not to have to deal with all the other rather useless cruft.

As for Quasar's post, all those bad examples can be applied to C++ as well. They are the symptom of rushed programming, not the language.

Share this post


Link to post
Scet said:

This is pretty much my opinion on OOP. There's no reason an object can't be a simple C structure as used in Blzut3's example.


That's how most "objects" in Real World (TM) "object oriented" languages tend to be used anyway, and that's fine with me.

Then again, I won't say no to the syntactic sugar of being able to write object.someMethod() instead of someMethod(object). I just consider it more convenient, and easier to mentally keep track of (e.g. that it's actually a method that operates on a specific instance of an object, and not some free-ranging method that happens to take object as an argument, and might alter global status as well).

Maybe you can achieve the same convenience with some C preprocessor hacking (or with a total superset C hack like Objective C) but that's not the point.

If you ever examine how e.g. GCJ produces intermediate C output files from Java sources, that's how "objects" map to function + handle couples ;-)

Share this post


Link to post
Scet said:

but it's worth it not to have to deal with all the other rather useless cruft.

While I'm not sure exactly what you mean by this, I will say that OOP doesn't automatically insert extra overhead as some may believe. The intention of my post was to explain how C++ code translates to C concepts at the compiler level. Your simple structs and functions that operate on the struct is exactly the same performance wise as an OOP object since once compiled they should end up more or less the same. All you get are the benefits of compiler assistance.

C++ in particular was designed in a way that if you don't use a feature any overhead from that feature is not present. The story might not be the same for some languages (Java for example allows methods to be freely overridden for example), but I'm sure modern compilers for those languages attempt to minimize the overhead.

Share this post


Link to post

Further on C++, knowing how the compiler demangles class methods is handy.

bool Class::Method(int parameter)
is demangled to something like this by the compiler:
bool Class_Method(Class *this, int parameter)
Yes, it looks just like you'd make it in plain C. It removes the need for you to manage it all yourself. But that's the simplest method of object orientation. What's trickier to do in a language where object orientation of some kind isn't natively supported is modifying the behaviour of that object under certain circumstances.

Doom has function tables for all its objects and handles. This is analagous to the virtual function table in C++ - rather than making your own record of function pointers, the compiler does it for you. Basically, in C it's yet more stuff you need to handle yourself. The virtual function table is more than just a simple collection of function pointers though. It's also a collection of pointers to the base class functions, thus allowing you to not only modify the behaviour easily but use the previous behaviour without any additional effort.

And that's where OOP starts to get interesting if you aren't interested in using an object as a simple collection of data.

hex11 said:

Anyway, I don't know anybody who programmed their own 2600, or for that matter NES or Sega game consoles.

Yeah, I have an advantage over you in that field, having worked with people that have done that. Further, for Game Room one of the guys hooked up an EPROM to a PCB that could slot in to a 2600. We had a home brew 2600 devkit effectively. One of the guys wrote code that ran on it. Good times. I miss working on Game Room.

EDIT: On another note, component based programming is thoroughly more suitable for modern gaming than object oriented programming.

Share this post


Link to post

The elaborate system I designed for metatables in Eternity before it was C++ included, in C, the following:

  • Pseudo-Inheritance
  • Pseudo-Polymorphism with virtual methods
  • Copy constructor/cloning mechanism
  • RTTI
This demonstrates how it's possible to accomplish all of those idioms without language support.

However, if you compare the last C revision with the first C++ revision, you will see just how much bother it is reinventing the wheel in such a way, and how it leads to code which is significantly convoluted.

Share this post


Link to post

It's simply a different paradigm. You have to have a different mindset to program in a non-OOP language, and if you started with OOP, it's going to be difficult. There are some who can use both (myself included) but the more entrenched you get in one paradigm, the harder the other is going to be to understand the semantics of. Of course, a compiler is a tool, and a tool is only as useful as the one wielding it... and the same tool doesn't work for every job. Anyone who sits on one side of the fence or another for every job is a fool. Some tasks are easier/better in C, some are easier/better in C++. Ultimately, both can accomplish the same end result, but the means to the end is going to change, as is the time you spend in development.

DOOM was coded in C for one very obvious reason: speed. Optimizing C++ compilers were still relatively in their infancy in those days, and there wasn't a single one that could generate code that was anywhere near as efficient as an optimizing C compiler (you can thank Microsoft for stifling C++'s progress in those days, though many of us just blame C++'s seemingly foolish design choices which add needless complexity, making compilers much more difficult to develop). Objective-C was out of the question... BASIC compilers could run circles around the Obj-C compiler of the day. C had a decade of progress ahead of both of them. Nowadays though, optimizing C++ compilers run neck and neck with optimizing C compilers most of the time. Of course, the main difference now is deployment size, which really isn't even an issue anymore considering the size of modern hard drives so it's pretty much a moot point. So what it really comes down to now is style and usage. Most 3D engines are coded in OOP-based languages because it's much quicker development-wise, and since much of the maths gets offloaded to the GPU, there's not a noticeable difference so it's better to use C++ in that case since nowadays it's all about cutting development time. On the other hand, virtually all tightly-coded operating systems, drivers, and utilities are coded in C, not only because it's legacy to do so but also because it's preferable in those cases to get the smallest and fastest executable possible without resorting to assembly language. Modern Windows versions are coded in C++ though... but then again, it's not a tightly-coded operating system. :)

Of course, there are some cases where you just can't escape one paradigm or the other. For example: all of my coding teams often target old consoles for game projects. A C++ compiler just isn't going to cut it there, especially when dealing with a 6502 variant, so a SmallC compiler is what gets the job done, or the macro assembler. On the other hand, I also develop Android applications. For that, you have to understand OOP to use the official SDK and toolchain, since it's based heavily on Java syntax, which is pure OOP.

Share this post


Link to post

Being a programmer myself I use both C and C++ for different things. When I grew up I was reading about things like Quake and Q3A so when I managed to get a knowledge of programming, through a BASIC dialect it seemed natural to follow one procedural programming language with another more advanced one so I could mod the GPL'd Quake engine. After that I moved to Q2, which is still a neat engine and my favourite to mod, and then to Quake 3. After that I started reading up on objects and I never really found a use for them for another few years.

I still program in C mainly because I'm used to it but since I learnt to code from reading Quake 1/2/3 I interpreted my own style of coding from the much cleaner "Carmack" style of C coding. However when I looked at the BUILD engine code the first thing I thought was "What a mess!". Although another great inspiration of mine Ken Silverman's code has too much of that quick and dirty feel about it, even though it's rather fast and optimized quite well.

Share this post


Link to post
Stroggos said:

I learnt to code from reading Quake 1/2/3 I interpreted my own style of coding from the much cleaner "Carmack" style of C coding.


Now read the Wolfenstein 3D source code and you'll see "Carmack style" wasn't always very clean. :p

Share this post


Link to post

Way ahead of you there Gez it is a mess I think it wasn't until Doom until things started to change an it took till Quake II to really get clean for Carmack.

Share this post


Link to post

I seem to recall Carmack going on hotel getaway weeks during Q2 development phase, and reading lots of software engineering type books.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×