Over the past week I've been slowly putting together a rather long post about all the changes that have been made. Instead of trying to make points or talk about experiences, I think this time around I'll just give the updates and explain a couple decisions made along the way.
I'll start with the biggest update: the game engine is now using
OpenGL. I still have all my Direct3D9 wrappers (and anyone is welcome to them), but OpenGL offered [for this project] some extremely nice benefits over Direct3D. I'll get to those in a minute. But, for now, know that Dolphin now has a 100% implementation of OpenGL out there, with extension support.
I've also decided to implement 2/3's of an MVP triad (I'm still trying to wrap my head around MVP): the view and the presenter. This not only simplified my code a ton, but I imagine than an OpenGLPresenter would be extremely useful for others in the Dolphin community as well. Probably the smallest example I can give of it in action would be:
p := OpenGLPresenter show.
p makeCurrent
ifTrue: [(OpenGLLibrary default)
glClear: GL_COLOR_BUFFER_BIT].
p flip.
Simple enough. There should now be a rather large OpenGL rendering window on the desktop with nothing in it. The presenter can be attached to any view object (within reason I suppose), which means it should be very easy to use in dialog applications or anywhere else that rendering is needed.
Since the ExternalLibrary object uses its own #getProcAddress: for looking up external functions, this made creating OpenGL extensions very easy. Subclassed off of OpenGLLibrary is OpenGLExtension, which overrides the #getProcAddress: method and replaces it with wglGetProcAddress(). Also, it has a class method: #find. To use an OpenGL extension, just subclass the extension class, and then just start defining the instance methods as normal, just as if the extension exists. The #find method will search the extensions available against the class name and return it if found, otherwise nil.
"Declaring the extension class..."
OpenGLExtension subclass: #ARB_multitexture
"Defining one of the methods in the extension..."
glMultiTexCoord2fvARB: mode coords: v
<stdcall: void glMultiTexCoord2fvARB dword float*>
^self invalidCall
"Using the extension..."
ARB_multitexture ifFound: [:ext |
ext glMultiTexCoord2fvARB: GL_TEXTURE0_ARB coords: ptr].
That's it! Extensions in OpenGL have never been easier (in my experience).
Okay, so why the switch? Well, there were a few different reasons. The primary one is that all games are different. Sure, I'm making a 2D engine, but there are
many flavors of 2D games. Each with its own set of requirements and rendering needs. And there's no way I could (or would want to) anticipate all of them.
With Direct3D, there were two big problems. First, D3D is handled entirely through a single interface object (the IDirect3DDevice9). This means that if I (or someone else) down the road wanted to do any special sort of rendering, it would require that I make the device available to everyone. And that brings me to the second problem: D3D state management can be a nightmare. Simply put, I couldn't trust external code to properly manage the device and the state. If the device were to get in a mucked state, it could cause serious problems (including image corruption).
Using OpenGL, not only is rendering easier and well understood by most hobby game developers, state is managed through stacks. Allowing outside code to push transforms, enable client states, render, then pop the state back to what it was is trivial. The engine won't be making use of client states or vertex arrays - just display lists and primitive OpenGL functionality. This means future games aren't limited by what I do now.
Asteroids does some of its own rendering (stars and thrust particles).This allowed me to completely rid the engine of the GameRenderer class and a whole host of code that was just a waste of space. It always feels good when extending functionality actually reduces the overall code and what needs to be maintained.
Another benefit that I'll take a minute to mention was shown above in the OpenGLPresenter example. That is, while the game is running I can send OpenGL commands to the game to test ideas from a workspace. This is a wonderful debugging tool to have at my disposal, and I use it constantly to test state, get error codes, or even try out different rendering techniques.
Okay, so what else? It's been a while since my last progress update.
I'm still using DirectInput, and I have joysticks and gamepad controllers working. I've ported over the XINPUT libraries (for Xbox 360 controllers), but have no desire to really get those working in the engine. The interfaces are easy, but significantly different from DirectInput, so I'll just put that on the back burner for now.
Scenes are now composed of Playfield objects. A playfield is basically an Actor manager. A typical scene may have lots of playfields. In fact, the more the better, as they help to manage more than just what's being moved around and rendered. In the asteroids clone, there is a playfield just for asteroids, one for the player and the bullets fired, and one for particles and other short-lived actors. This separation of actors helps to manage z-ordering, collision detection, and more.
Another benefit to using playfields is that actors can now "kill" themselves. This would be akin to removing a renderable object from a scene graph. This is a huge burden off the game developer since actors can be responsible for their entire life cycle. The Asteroid>>explode method takes care of killing itself, as well as spawning new asteroids on the same playfield. And it just works, which is always a good thing.
Oh, wait! I almost forgot another great addition!
With the loss of Direct3D, I lost the D3DX libraries (which I didn't want to use anyway). This meant that texture loading was back to just plain, old bitmaps. But, I decided to code up some nice wrappers for the
DevIL project. It's 95% complete. All that's missing are the D3D8, Allegro, and SDL functions, which were intentionally left out. But all 3 libraries (IL, ILU, and ILUT) are implemented, along with a helper object - DevILImage - that is useful for managing images loaded with DevIL and manipulating them.
The engine actually only uses the lowest level of the 3 libraries: IL. But the others are there if anyone would like to make use of them. Thanks to DevIL, the engine now supports an entire host of
image formats. Also, Direct3D and OpenGL support for DevIL was pulled out into their own packages which just add loose methods to the ILUT library. This was done so that if you didn't need it, no need to install them.
I'm starting to move onto actual interface code now. I've also started playing with
Seaside in
Squeak a little, as I've done enough that I think getting some web hosting going so I can start publishing packages for others to use and help test. I'm a terrible web developer, but Seaside looks pretty neat and fun. If anyone can suggest some very reliable web hosting services, definitely let me know. Eventually I'd like to use
Seaside hosting, but I think it will be a while before I'm ready for that.
Until the next update...