GameFrame for Java 0.9.4 Class Library Game Developer Documentation - Introduction
Version 0.9 04. Jul 1999 by Pasi Keränen, javanerd@geocities.com
Version 0.9B 25. Oct 1999 by Pasi Keränen, javanerd@geocities.com
Version 0.9.1 02. Nov 1999 by Pasi Keränen, javanerd@geocities.com
Version 0.9.2 22. Dec 1999 by Pasi Keränen, javanerd@geocities.com
Version 0.9.3 02. Apr 2000 by Pasi Keränen, javanerd@geocities.com
Version 0.9.4 16. Jul 2000 by Pasi Keränen, javanerd@geocities.com
Even though the GameFrame for Java
(GF4J) class library has been built to be as simple as it can be, you probably
should read this small document before starting to use the library to avoid
wasting your time when pondering "how should I do this?". Chapters 1.3
and 1.4 contain no "real" information about the library so they can be
skipped if you are in a hurry to get something done. Editing of this document is
hereby freely allowed for as long as the version history above is kept intact (=
no deleting, only adding).
Index
1. INTRODUCTION
2. USING THE LIBRARY
3. THINGS TO NOTE2.1. What to do when starting your game?
2.3. Creating the needed engines
2.5. Managing the memory usage
2.6. Changing used engine implementations on the fly
2.7. How to find out what full screen resolutions are available?
4. APPENDIXES
APPENDIX A: History (the ramblings of the original implementer)
1. INTRODUCTION
GameFrame for Java (GF4J in short) is a simple game programmers library that by abstracting the used "engines" (an engine in this context means a class or collection of classes that offer some specific functionality for the programmer e.g. graphics engine offers graphics related functionality, sound engine offers sound related functionality etc.) in a way that allows some platforms to use native implementations (such as SVGALib on Linux, DirectX on Win32) that enhance the gamers experience somehow. The games made with GF4J are still able to run with plain 100% Java (1.1 and 1.2) and the GF4J is usable on any Java (1.1 or 1.2) compatible Java VM's although a JIT (just in time compiler) is probably a mandatory requirement due to the slowness of plain interpretation of Java.GF4J supports only double buffered 2D graphics and just very simple variations of that. The user can load bitmaps and transparent bitmaps. When loading a bitmap as transparent bitmap the library looks at the upper left pixel of the picture (the pixel at coordinates 0,0) and sets the color of that pixel to be the key color (the color left as transparent when drawing the bitmap on to the screen). After the bitmap has been loaded it can be drawn to the graphics engines backbuffer and upon request the graphics engine will switch its backbuffer with its currently visible buffer and the just drawn bitmap is made visible to the gamer. At the moment the GF4J supports only uncompressed Windows BMP bitmaps in 2-, 4-, 8-, 16- or 24-bits per pixel. One bitmap object can be used to draw that same bitmap over and over again at different locations in between backbuffer/frontbuffer flips.
Sampled sound support is also included and the sample support allows setting of volume of the played sound and setting the pan of the played sound (which are not required to be supported by all implementations and thus might not work on all platforms). A sound can be ordered to play once or play looped until it is ordered to stop. One sound sample object can only play its sound once in any point of time, this means that if e.g. the same laser firing sound needs to be played for two monsters shooting at approximately same moment, you need two sound objects that have the same sound information. You need not load the sound over and over again as the one sound object can be ordered to make a copy of itself and return that copy to the user.
Input support at the moment covers keyboard and mouse input.
This releases of the library most likely still contains some bugs and design flaws, so it would be better to shake as many bugs out of the code that implement core functionality before starting the development of more advanced features that use the underlying code. Of course the statistics have shown that the larger a program gets the less is the likelihood of finding the cause of some odd behaviors. So if you are not satisfied to the way the library works at the moment, make a suggestion! If you find buggy behavior, eMail me about it!
The main idea is to try out the free source approach: release early, release often (well this can be a bit tricky for me, as my free time these days is so limited). Some might say that I delayed the first release for too long, but I just didn't want to release a completely unusable thingamabob, as the Internet is full of those already. I wanted the library to be at least somehow usable for game developers in the first version. Also having something functional to begin with should help gathering some additional developers to this project. It's always easier to find improvement suggestions on something that already exists than come up with new requirements.
Releasing the source is a move that I hope will drive this library in to new unforeseen heights. You are welcome to make changes to the code as you like, but if you find and fix any bugs I'd appreciate if you'd forward the fix also to me.
2. USING THE LIBRARY
Usage of the library is very straightforward. In short you just set the requirements you want to the GameFrame object and get the engine implementations from it. The following chapters offer a bit more detailed view on how to do this. To gain better understanding of the library see the examples.html document that explains all the examples contained in the examples.zip file.
When starting the game you should test for the existence of GF4J library and notify the user if the library is missing. As the library is meant to be shared by installed games, this step is quite necessary. The confirmation is easy to do with the following code snippet (although you might want to replace the text printing with some kind of nicer looking dialog):String strNoLibraryMessage; strNoLibraryMessage = "You need to have the GameFrame for\n"; strNoLibraryMessage += "Java library installed in order to use\n"; strNoLibraryMessage += "this program. The GameFrame for\n"; strNoLibraryMessage += "Java library can be gotten from:\n"; strNoLibraryMessage += "http://www.gamedev.net/hosted/javanerd"; try { gameframe.GameFrame.getBuild(); } catch( Error anyError ) { System.out.println( strNoLibraryMessage ); System.exit( 0 ); } catch( Exception anyException ) { System.out.println( strNoLibraryMessage ); System.exit( 0 ); }
After making sure the user has the library you should make sure the library is of correct version for your game. The checking depends on your game, in some cases it might be necessary to specify a range of versions the game works with. In other cases it might be enough to require a minimum version of the library. The GameFrame class offers methods to query the library version, the following example shows how to make sure the library is at least of version 0.9.4:String strWrongLibraryVersionMessage; strWrongLibraryVersionMessage = "You need to have the GameFrame for\n"; strWrongLibraryVersionMessage += "Java library version 0.9.3 installed in\n"; strWrongLibraryVersionMessage += " order to use this program. The GameFrame for\n"; strWrongLibraryVersionMessage += "Java library can be gotten from:\n"; strWrongLibraryVersionMessage += "http://www.gamedev.net/hosted/javanerd"; // Get the major version number of the library int majVer = gameframe.GameFrame.getMajorVersion; // Get the minor version number of the library int minVer = gameframe.GameFrame.getMinorVersion(); // Get the build number of the library int build = gameframe.GameFrame.getBuild(); // Get whether the library is stable or beta build boolean isStable = gameframe.GameFrame.isStableVersion(); if ( ( majVer < 0 ) || ( minVer < 9 ) || ( build < 4 ) ) { System.out.println( strWrongLibraryVersionMessage ); System.exit( 0 ); }2.2. Initializing the library
The first thing you should do is set all the settings in gameframe.GameFrameSettings class to be what you want. These include required resolution, disabling any engine implementations and disabling full screen modes if those are not wanted. This is necessary as the deciding of the used engine implementations will depend on these settings, so if the settings are not correct when you get the recommended engine implementation you will probably not get a suitable one for your purposes. After you have set your settings to the GameFrameSettings object use that object to create a GameFrame instance.Note: After you have created one GameFrame instance you can't create another before you have finalized the already created one. The logic is that when you have the library configured to do something, you must first uninitialize the library before you can set it up again to do something different. The following code snippet illustrates this procedure:
GameFrameSettings settings = new GameFrameSettings(); settings.setRequiredResolution( "320x240" ); settings.setTitle( "Just a test" ); // We create the one and only instance of GameFrame that is possible try { new GameFrame(settings); } catch( SettingsException error ) { System.out.println( "Wrong use of settings:" ); System.out.println( ""+error.getMessage() ); } catch( GameFrameException error ) { System.out.println( "Error in main factory creation:" ); System.out.println( ""+error.getMessage() ); }After this step you can forget the GameFrameSettings class completely. It is only used in this initialization phase.
With the initialized library you now have you can create various engines like graphics-, sound- or input engines. When the library returns the engine implementation to you it is immediately in usable state, you don't need to do any additional initializations or setups. All engines that load some resources, use the current directory as the default directory. You can set the directory to be whatever is required and the resources loaded after that are loaded from the set directory. Note: Avoid using Win32 specific paths like ("C:\MyDirectory\Pics") instead use relative paths (e.g. ".."+File.separator+"pics" or "pics") from the main directory of your game.The order in which engines are gotten is not important. Example code snippet below illustrates the procedure of getting the engines and settings the data directories:
GraphicsEngine graphicsEngine = GameFrame.getGraphicsEngine();; SoundEngine soundEngine = GameFrame.getSoundEngine(); InputEngine inputEngine = GameFrame.getInputEngine(); graphicsEngine.setDataDirectory( "grfx" ); soundEngine.setDataDirectory( "sounds" );Below example code shows how to create a timer. Note: Timer creation must be inside try-catch blocks as it can fail for some reason or another:Timer timer = null; try { timer = GameFrame.createTimer(); } catch(GameFrameException frameError) { System.out.println("Timer creation failed"); System.out.println(""+frameError); System.exit(0); }It should be noted that the GF4J will return a "do nothing" implementation of SoundEngine if the current platform doesn't support sound (e.g. we have a PC without soundcard). The reason why it returns a "do nothing" implementation is that the sound is quite often used in games so it wouldn't be very nice to force the game programmer to place "if (soundEngine == null)" comparisons everyplace in his code where he tries to use sound. So the solution is to simply return a dummy sound engine that seems to work and the game programmer can ignore this annoying fact. What this causes, is that the game programmer/designer should build the game so that no vital information is conveyed only by using sounds or at least the gamer should be able to select some textual description of these events ("Base under attack" or "Energy low" are the kind of events I'm talking about here). You can automatically use text events if no sound is available by checking if the returned sound engine is an instance of gameframe.engines.NullSoundEngine. The following code snippet shows how to do this:boolean fUseTextMessages = false; if (soundEngine instanceof gameframe.engines.NullSoundEngine) { fUseTextMessages = true; }Other engines do not provide this kind of dummy versions as these engines are deemed to be too vital (e.g. if the graphics engine is not created, this usually can't be ignored. The same applies for the input engine and input devices, if they are not available the game programmer MUST somehow react to the fact).
Usage of the engines is described in more detail in the engine API documentations in JavaDoc format and in the provided example programs, but some simple operations are illustrated in the following code snippet:Bitmap background = null; Bitmap player = null; try { // Load a non transparent bitmap for the background background = graphicsEngine.loadBitmap("Background"); // Load a transparent bitmap that represents the player player = graphicsEngine.loadAlphaBitmap("Player"); } catch(FileNotFoundException noSuchFileError) { System.out.println("File not found."); System.out.println(""+noSuchFileError); GameFrame.exit(0); } catch(GameFrameException frameError) { System.out.println("Bitmap loading failed."); System.out.println(""+frameError); GameFrame.exit(0); } // Get the keyboard input device from the input engine KeyboardDevice keyboard = null; try { keyboard = inputEngine.getDefaultKeyboardDevice(); } catch(GameFrameException frameError) { System.out.println("Couldn't get keyboard."); System.out.println(""+frameError); GameFrame.exit(0); } // Get the width of the screen int width = graphicsEngine.getBounds().width; int x = 0; int y = 0; // The games main loop while(fGameRunning) { // Draw the background to the backbuffer // which also clears the screen if the // background bitmap is of same size or // larger than the current resolution background.blitTo(0,0); // Draw the player to the backbuffer player.blitTo(x,y); // Flip back and front buffers // (make the just drawn backbuffer visible) graphicsEngine.flip(); // move the player to the right x++; // stop the game loop if he has left // the screen or user presses escape key if ( (x > width) || (keyboard.isKeyDown(KeyEvent.VK_ESCAPE) ) { fGameRunning = false; } } // After the program has finished we exit using the // exit method in GameFrame. This allows GF4J library // to do some cleaning up after itself. GameFrame.exit(0);2.5. Managing the memory usage
A "well known fact" is that it is quite hard to manage the memory in Java by yourself, but every effort has been taken in the GF4J library to make this at least somehow possible. For example you can free the memory taken by a bitmap by calling its finalize() method and then calling yourself the System.gc() method. Of course as the documentation for the System.gc() method says this doesn't guarantee that memory has been freed, but in my experience this usually happens in today's VM's. This simple finalize() method usage allows you to unload from memory individual bitmaps or sounds. The usage of once finalized bitmap or sound is unspecified, this means that anything from just not drawing the image or playing the sound to crashing the whole operating system can happen. This decision has been made to take some extra burden off the methods that are used actively during the game execution (namely blitTo, clitpTo, strecthTo, playOnce, playLooped and stop methods).
So one good rule of thumb is: Once you finalize an object, set the variable referencing it to null. This causes only null pointer exceptions if you try to use the variable to reference the object. The other option of possibly crashing the computer is quite a lot more risky chance to take.
Changing engine implementations on the fly is not currently supported by the GF4J library. If this feature is requested I'll look into it. You should be able to change engine implementations by first finalizing the GameFrame object (which finalizes all used components of the library) and the re-creating the GameFrame object with new settings and then re-loading every bitmap, sound, music etc. object you were using.
2.7. How to find out what full screen resolutions are available?
The first implementations allowed user to get the full screen resolutions before instantiating the GraphicsEngine. This has changed a bit and now you must first initialize the library by creating GameFrame (with default settings if you don't want to bother) instance and then enumerate the supported full screen resolutions by calling GameFrame.getSupportedResolutions() method and finally you must finalize() the GameFrame object. After this you can use the full screen resolution information as you like... probably create new GameFrame instance using one of the resolutions gotten from the enumeration process.
You are required to finalize the GameFrame object before the game exits! I tried to make the library work just by using the finalize() method that should be invoked by the garbage collector. But this doesn't work upon System.exit(0) call. For some reason when the exit method is called the Java runtime doesn't run finalizers for the objects and that leads to major problems when we are using native code components inside the library that need to be explicitly freed. One example is the Microsoft Java implementation that uses DirectX COM objects that don't get freed when you don't run the finalize() methods of the engine objects. This leads to e.g. your soundcard "getting stuck" as it is not freed by the library.To make things easier I've implemented a static exit() method in the GameFrame object that takes care of finalizing the whole library and waiting until everything is ok and then exiting the application. So instead of using System.exit() method use the GameFrame.exit() method instead. It works exactly as the System.exit() method, but just takes care of first freeing up the library (and your soundcard and video card etc.).
The user of the library can still access the JDK specific Java class libraries and use them in a way that makes running of the game impossible under different JDK versions. E.g. if you make a startup front end for your game using JDK 1.2 and Swing, the game will not be able to run under JDK 1.1 without the Swing library.
To remedy this situation I've decided to include some of the most often needed classes in the GF4J library itself. Next releases should add more and more of these classes.
3.2. Speed differences under different Java runtimes
You should be aware that your game can run at different speeds under different Java runtimes even on the same machine. Also you should note that Java runtimes are available for handheld machines to supercomputers. So your game should use the timer classes to adapt to the different platforms speeds. There are several ways to approach this problem and I'm sure you can find solutions to the problem. GF4J offers the Timer class that can be used to time the intervals of calls made to its calculateTime() method. The included example TimerExample shows how to use this class to time the frames per second of your game.
GF4J is not multithread safe. This stems from the fact that multithreading is not that viable choice when it comes to games. If GF4J would be made multithread safe it would slow down the library considerably from its current state and this would IMHO be unacceptable.
You should be able to access e.g. GraphicsEngine and all its related objects (Bitmap etc.) from one thread and SoundEngine and all its related objects (Sound etc.) from another thread and InputEngine and all its related objects from a third thread. So this could mean that you can have a separate thread that does the painting of the graphics, a separate thread for playing the sounds and a third thread for collecting the input from the player. But remember that in this kind of a setup it is often very difficult to make sure no thread gains access to another threads "territory". Also this kind of a setup is not very feasible as there will be lots of context switching between the threads and (depending on the platform you are running the game on) could be prone to thread starvation or some other nasty multithreading problem.
So my advice is: "Keep it simple". Have only one thread in your game that runs the main loop of your game. Or have at maximum two threads, one for the game logic and another for GF4J library access.
4. APPENDIXES
APPENDIX A: History (the ramblings of the original implementer)
I first tried to implement this library during Christmas holidays in 1997, but at that time I tried to do too much in the engines. My dream of a simple to use game programming library lead me to believe that the library should have everything including the kitchen sink. Needless to say I didn't ever have enough time to implement this kind of library and the overly complicated design lead to the fact that this project died a silent death. Well actually it didn't die, it just went to the freezer for a while, until I had learned some new things and remembered some old...
In the beginning of spring 1999 I reopened the project and started to really do some designing (having learned a few more things such as programming patterns and again remembering the age old mantra "design before you implement"). Also some thanks go to my former piano and accordion playing teacher Jukka Ollila who thought to me that the most important stage in any project is the defining of what is relevant and what is not relevant to the project. And thus with the new knowledge, some free time and ever-growing urge to program my own games again (after 12 years break) I began the implementation of this library.
This time around I had a clear design on paper and a very good "project plan" for doing the implementation in stages rather than trying to implement the whole lot at a time. And this time everything just started to click into place (of course the design experienced some changes as they always do during a software project) and as I was able to test the library every now and then with some actual code (as the library was implemented in stages) it was much more fun to do. Also the knowledge that I could leave out the kitchen sink and some other stuff was also encouraging. The project might even be finished at some point of time!
The most difficult part was probably keeping in line with the original intent of "run with 100% Java well and a bit better with the help from specific implementations". As I implemented more and more of the DirectX engines I had quite a lot of temptations to leave e.g. the pure Java AWT graphics engine out of the picture to be able to offer some of the more powerful features (such as different alpha blending modes) to the users of the library. The temptation is still there, but it just doesn't make that much sense. If you implement some platform dependent (be it computer or operating system dependent) features, then the usage of Java as language becomes a bit questionable. Yes, Java is quite simple to understand when compared to C++ and thus quite suitable for beginning game programmers with its "shoot and forget" style, but its also quite a bit slower so in that sense it really is not THE language to use for making commercial standard games. I should make a note here that whenever I have said this kind of things on other subjects somebody has proven me wrong and I really hope that someone will do that also this time around ;)
The issue of Java not being a particularly well suited for game programming will probably popup every now and then (and hopefully a lot more often because of this library ;). But its always good to remember that even though today's average computer (300MHz MMX?) isn't capable of running complicated Java based games well, the modern gamers are always getting the hottest hardware and you can count on the fact that probably during the this year the computer base used for gaming will reach the new speed standard (with 500MHz and faster processors). With those powerful computers it will be possible to run even complicated Java based games at reasonable speeds. But will the users of such computers settle for the bit "old fashioned" look of Java games? I think so... think about the Civilization series of games, they really do not look that "hot", but still masses like to play those games. Why? Because those games have more content than some games that simply try to satisfy the gamer with lots of eye candy. This of course doesn't mean that I don't like eye candy, I play Q3, Unreal Tournament and Homeworld as much as the next guy just to get an eyeful of their graphics, but those games also have very good playability.
Copyright ©1999-2000 Pasi Keränen, javanerd@geocities.com
Re-use with the GameFrame for Java library builds
allowed if the history field in the beginning of the
document is left intact (only adding of entries
is allowed, no removing).