Table of Contents
GLib is the library that you will include when compiling your game's jar file. Its source resides in the "GLib Source" folder, and it is compiled to the file "GLib.jar". You should at least read the class guide and the code guide before using GameForm; additionally, the buffer guide provides useful but not essential information about how the graphics engine works; and the file guide is provided for the (probably relatively few) users who want to extend GameForm's file format.
The class guide will give you an overview of the GLib classes; for more detailed information about the source code, consult the code guide below. All source files also have Javadoc-style comments descibing the classes and certain non-intuitive methods.
All GLib classes are direct subclasses of Object, exept GameApplet which (naturally) subclasses Applet. Additionally, GLevel, GPixels, GProperties, GSprite, GSpriteForm and GTileForm implement the Cloneable interface.
The word form, in GameForm terminology, is used in a way similar to the use of "class" in object-oriented terminology. For example, a GSpriteFrom object holds the data that is common to all sprites of a certain kind, such as the size and image data. In Java, it would translate to class members and class variables (marked with the "static" keyword). A GSprite object on the other hand translates to a sprite object (instance), a single sprite in other words, and holds the individual data, such as the location and movement of the sprite.
This might sound like re-inventing the wheel, and in a way it is, but it actually makes the class design much simpler and cleaner. Trying to use traditional object-oriented design would mean having an abstract sprite class, and a subclass for every type of sprite. The sprites in a game would then be instances of any of the sprite subclasses. The problem is that GameForm does not at compile-time know which sprite subclasses it needs. Depending on the data file, different subclasses would need to be created at runtime, and filled with static data.
Since creating objects is a lot easier than creating classes, GameForm creates new instances of GSpriteForm at runtime, and adds the sprite-static data to the GSpriteForm instances. Each GSprite is then given a reference to a GSpriteForm, which is used instead of class variables. Of course, you can (and most likely will) sub-class GSpriteForm at compile-time.
Summing up, GSpriteForm holds the data common to all sprites of a certain kind, while GSprite holds the data associated with a single sprite; GTileForm holds the data common to all tiles of a certain kind (GTile does not exist because tiles lack individuality, tiles are simply stored as references to GTileForms); and, of course, GameForm is as a collection of code and classes that are (or can be) common to many games.
Every class description below has tips on how and when to subclass it. Please bear in mind that those are only suggestions to get started with the classes, and they may or may not apply to your game. It is also encouraged, but not required, to open-source bug fixes and improvements to GameForm and put them in the public domain for others to use.
GameApplet is the hub in a game; it runs the main-loop, handles
user input, and updates the screen. It handles starting, pausing and
stopping games, cheat codes and buffers for flicker-free animation.
GameApplet can run your game at a constant speed; it will skip updating
one or more frames when running on a slow computer. You will generally not
create GameApplet objects, but rather subclass it and override certain
GBitReader allows reading from a file, bit by bit (instead of whole
bytes). It is used to read variable-length integers, booleans and Strings,
and compressed arrays of integers from the data files. It only uses the
low 6 bits of a byte, to ensure cross-platform compatibility. Unless you
need to store unsupported data types (such as floating point numbers),
you do not need to subclass GBitReader.
GFactory is responsible for creating many of GameForm's objects;
instead of directly creating objects, GameForm code will ask GFactory to
create them. Because of this central creation of objects, GameForm will
automatically use your subclasses instead of the standard classes if you
simply override the GFactory methods to return instances of your classes,
and replace the default factory with your own.
GFileManager can be used to read a data file in a separate thread in the
background, and give reports of the progress to a GLoadListener, typically
your applet. Unless you implement your own data file format, you will not
need to modify or subclass GFileManager.
GLevel handles, or distributes control of, most of the game behavior. It
holds data about the tiles and sprites in a level, and handles sprite
collision detection. You will generally not need to subclass it, because
it mostly distributes work to the sprites.
GLevelCanvas displays a GLevel on screen. It keeps track of what part of
the level is visible, and what parts need updating. The actual drawing is
generally done by GSprite and GTileForm objects, but you can subclass
GLevelCanvas to add global graphics features, such as line-of-sight. See
the Buffer Guide below for more information.
GLoadListener is an interface that listens to a GFileManager, and receives
reports about how the GFileManager is doing. Generally, your GameApplet
subclass will implement GLoadListener.
GOutput is a filter for messages and exceptions. Generally, when a "minor"
error occurs, such as an illegal argument, an error message will be sent
to GOutput. During development and debugging, you will generally let
GOutput throw an exception or print a message. In the shipping product,
you can try to hide the error by muting GOutput with a single line of code.
GPixels is GameForm's image format, and has methods for drawing to a pixel
buffer. GPixels supports 32-bit ARGB format, and mirrored and rotated drawing.
Sprites and tiles are drawn using the GPixels methods. You can subclass GPixels to
allow drawing with special effects, such as scaled or shadowed drawing.
GProperties is a container for custom, game-specific data. Every GLevel,
GTileForm, GSpriteForm and GSprite has an associated GProperties, which you
can use to store additional information, such as the bonus score for a level
or the name of a sprite or sprite form. Unless you need to store
unsupported data types (such as floating point numbers), you do not need
to subclass GProperties.
GSprite is GameForm's sprite format. It holds data about the state of a
single sprite, and controls the movement, collision handling and drawing.
You will generally create one GSprite subclass for every kind of sprite
(a "kind" of sprite may or may not translate to a GSpriteForm instance).
GSpriteForm holds data about a sprite form, such as the images and
certain default values for the GSprite instances. Since GSpriteForm is
mostly a container for data, you will generally not need to subclass it.
- GTileForm holds data about a tile form, such as the images and whether it is solid or not. You can subclass it to perform more complex tile drawing, but since GTileForm is mostly a container for data, you will generally not need to subclass it.
The code guide describes the code at a low level; the style conventions and design decisions for the method and variable. For an overview of the GLib class hierarchy, consult the class guide above.
The GLib code is written for the Java 1.1.8 specification, and does not use Swing. GEditor is also written for Java 1.1.8, but uses Swing.
The GameForm source code mostly follows Sun's guidelines. Additionally the following name conventions are used, to avoid name collisions and to make code reading easier:
- Names of GLib classes start with "G".
- Names of GEditor classes start with "GE".
- Names of constants start with "k".
- Names of class (static) variables start with "s".
- Names of instance variables start with "c".
All the GLib source files include Javadoc-style comments for all the classes and the methods requiring extra explanation. They also provide comments and code written to be easy to read. Before overriding a method, it is generally a good idea to read the overridden method's source code or at least its comments.
All the non-public variables and methods are marked protected, even though many of them should not be modified by subclasses. This is generally not recommended from a object-oriented design point of view, but I feel that for a game-oriented perspective speed is enough of a concern to allow the subclasses full access to the superclass' variables and methods. For example, you could write a drawing routine using getPixel() and setPixel() in GPixels, but it would make more sense in a real-time game to access the pixel values directly.
GameForm is also not packaged, for the same reason - protected variables in a parent class in one package can not be accessed by subclasses in another package. Also, because the GameForm source code will likely be tightly bound to your own code, packaging would likely have caused more problems than it would have solved.
GameForm's data classes (GLevel, GPixels, GProperties, GSprite, GSpriteForm, GTileForm) share certain common traits:
- You should not invoke their constructors directly, but let a GFactory produce them for you, with the appropriate create() methods.
- They implement Cloneable and provide a deep clone with the clone() method.
- They can be read from a GBitFile through the readFromFile() method.
The drawing pipeline in the GLevelCanvas object is somewhat complex, and you don't need to learn about it to create games with GameForm. However, if you want to take full advantage of GameForm, or if you want to extend the graphics engine with special effects, such as multiple tile layers or dynamic lights, this information will be helpful. If you are just starting out with GameForm, you may want to skip this chapter until you are more familiar with the GameForm package.
GameForm uses two kinds of buffers to store the level view before showing it on screen. The first kind of buffer consists of an array of integers with associated Image and ImageSource objects. The tiles and sprites are written to the array, pixel for pixel, and this kind of buffer will be referred to as a pixel buffer. The second kind of buffer consists of an Image object, and can be drawn to with the standard Graphics methods, including drawImage(). It will be referred to simply as a buffer.
The pixel buffer is very slow, so to keep the drawing as fast as possible, GameForm updates only a small part of the pixel buffer each frame. The pixel buffer is then copied to a (faster) buffer, which is in turn copied to the screen. To perform scrolling, one could update the entire pixel buffer, but that would turn out very slow. Instead, GameForm offsets the source rectangle when copying from the pixel buffer to the buffer. If the source rectangle extends past the border of the pixel buffer, it will wrap around to the left side, and the two parts will be copied independently, and the left part of the pixel buffer will be copied to the right part of the buffer, and vice versa. In the same way, the top part of the pixel buffer will end up at the bottom of the buffer, and vice versa. See the example "Wrapping" for a visual representation of this.
By using a wrapped pixel buffer, GameForm only needs to redraw the newly exposed tiles when scrolling; the pixel buffer stays mostly static when scrolling. This comes at the price of having to copy a lot of data and using three buffers, but fortunately Java is much faster at copying four large image parts, than drawing roughly a hundred thousand pixels of tile image data.
Because the buffer is copied fresh every frame, you can draw on top of it before showing it on screen, without having to worry about erasing or updating it the next frame.
The drawing routines have been designed to allow subclasses to add special effects. A GLevelCanvas object controls the drawing and invokes the draw() methods of GTileForm or GSprite objects, which in turn invoke GPixels methods to perform the actual drawing. GTileForm objects invoke drawTile(), GSprite objects invoke drawWrapped().
Depending on the modification you need, you will subclass any or all of GLevelCanvas, GSprite, GTileForm or GPixels. For global changes, such as modification of the drawing order (such as tiles on top of sprites), you will need to override GLevelCanvas.paint(), and/or any of the methods invoked by paint(). If you want to change the way tiles are drawn, for instance to allow multiple-pass drawing, you will override GTileForm.draw(). More likely, you will want to modify the drawing of sprites, by overriding GSprite.draw(), to provide shadows or lighting, multiple-pass drawing, scaling or other special effects. You may need to add methods to a subclass of GPixels to support the above-mentioned modifications, and add conditions to the GPixels.copyPixels() method which controls which method to use to draw the pixels.
You will not need this information unless you need to store data that is not supported by the GameForm classes (such as resizeable arrays or floating-point values), or you are curious about the internal workings and design decisions behind the file structure.
The data files used by GameForm are read and written with the GBitReader and GBitWriter. They use the 6 least significant bits (bytes in the range 0x40 - 0x7F), because of problems with cross-platform compatibility using the bytes 0x80 - 0xFF and line-break characters (0x0D). Specifically, some web browsers tend to convert them from one character set to another.
The data is loaded in a hierarchial manner, where the static readAllFromFile() methods in turn invoke the readFromFile() methods of the stored objects, and they will in turn invoke the readFromFile() methods of their associated data objects. The data is packed bit by bit to reduce wasted space. Much of the image and level data is automatically compressed using Huffman compression and Run Length encoding.
Certain parts of the data is marked with a version, to make transition to a modified file format easy; a single readFromFile() method can handle multiple file format versions.
The tile forms with associated data (images, properties) are written first, then the sprite forms with associated data, and last the levels. Additionally, labels might be written after the game data. The labels are used internally by the GEditor, they are (generally) not used by the games. Please refer to the source code (specifically the readFromFile() and readAllFromFile() methods, and the GFileManager class) for the specific ordering of data in the files.
GEditor is an application you can use to edit the data files GameForm uses. It needs the source code from both the "GEditor Source" and the "GLib Source" folders. It is compiled to the file "GEditor.jar", with the main class GEditor. It can edit levels, sprites and tiles, including image data.
Most of the interface should be fairly intuitive, but certain features may require some explanation, provided below.
GEditor uses a kind of list component which uses small icons or thumbnails to represent the list items. Although there is no visual feedback until the mouse is released, you can change the order of the items by dragging the images with the option key down.
All GLevels, GSprites, GTileForms and GSpriteForms have associated GProperties objects, which can be edited in GEditor. Note that changing the labels or number of properties will affect all GTileForms, all GSpriteForms or all GLevels. GSpriteForms also store how many properties the GSprites will have ("instance properties"), and you change the properties of GSprites by option-clicking on them in the level editor.
Tile Form Editor
The main GEditor window displays a list of tile forms at the top left, and the images of the selected tile form below. All tiles must have the same size, which can be changed with the "Change Tile Size" menu item, but you can not change the size of individual tiles. It is therefore recommended to select a tile size when creating a new data file, and also because sprite locations are stored in pixel, not tile, coordinates.
Using transparent tile images will generally not produce the desired result, but the option is still available because you can use them for certain special effects, such as drawing some parts of tiles above the sprites.
Sprite Form Editor
The main GEditor window displays a list of sprite forms at the top right, and the images of the selected sprite form below. You can edit the properties for the sprite form (Edit Property Count, Edit Properties, Edit Labels), and also the property count and labels for the GSprites (Edit Instance Property Count, Edit Instance Labels). The GSpriteForm properties are default values for GSprites, which can be modified on a per-sprite basis. You can edit the GSprite properties in the level editor.
The main GEditor window displays a list of level thumbnails at the bottom, and you can click on the selected level's thumbnail to open a level editor window. The level editor window displays a palette of tiles and sprites at the top, and a view of the level at the bottom.
Clicking in the level view causes different actions, depending on the modifier keys and palette selection.
- Click with a tile selected in the palette to apply the tile to the clicked tile in the level.
- Click with a sprite selected in the palette to add the sprite at the clicked location.
- Shift-click on a sprite to remove it.
- Option-click on a sprite to edit its properties.
- Option-click on a tile to use the tile as the selection in the palette.
The image editor is a simple pixel editor, not intended for any serious graphics work. You can change the color with the sliders on the left, and the opacity with the fourth slider. Transparent pixels are displayed in the image view as thinner pixel, not filling the entire pixel area (it is easier to use than to describe).
The image editor lacks a normal tools palette, but implements the following tools with modifier-clicks
- Click to use the pen tool.
- Shift-click to use the paintbucket (filler) tool.
- Option-click to use the the color picker tool.
- Control-click to fill the entire image with the selected color.
You can also work on individual color channels, turning them on or off by clicking on the numbers below the sliders at the bottom left. This is most useful for working on the transparency channel without affecting the colors, or the colors without affecting the transparency. You can combine this with the fill tools, but not with the paste commands.
You can use "Import Image" from the main GEditor window to import an image from a file. Because of the way GameForm stores images, it is recommended to import GIF images only. JPEG compression produces artifacts that not only reduce the quality of the image, they also work poorly with GameForm's compression. GameForm works best with images with as few colors as possible, and the JPEG artifacts add unnecessarily many colors.
Copy and Paste
GEditor has a clipboard that can hold a single GPixels object, but it is not shared with the host system. Because the image editor lacks support for selections, the clipboard has a somewhat limited use, but you can use it to copy images from one tile form or sprite form to another, and paste transparent images on other images.
The "Paste as Mask" command can be used to produce a masked image from an image with a separate mask. "Paste as Mask" will apply the clipboard's (inverted) blue channel as the mask for the destination image. So, to apply a mask to a (preferably opaque) image, you can "Paste as Mask" a grayscale image (where black is considered opaque and white is transparent), onto the destination image. This is a less than ideal situation, but it works.
Files and Labels
GEditor actually uses a custom file format for the data files, since it stores the labels used for custom properties at the end of the file, after the regular data. The files are still compatible with the GLib, but if you want to store your own data after the regular tile forms, sprite forms and levels, keep this fact in mind.
The labels do take up space though, so when you are finished editing your data file, you can remove them (using the "Remove Labels" menu item). When saving, you can see how much space each part of the file uses, so you can see how much you have to gain. Of course, editing the data file will be more difficult afterwards.
If you want to protect your data file from others (which may or may not be worth the effort), you will need to create a custom file format, which can be as simple as inserting a single bit somewhere in the file. The standard GEditor will fail to open the file, but your modified editor and game will work fine. For this to be effective, you will also need to protect your bytecode from being decompiled by using an obfuscator. Information about decompliers and obfuscators can most easily be found on the Internet.
Limitations and Workarounds
GEditor is not as polished as GLib is, many error reports - for instance - are written to System.err instead of being shown in dialogs, partly because System.err is less in the way than dialogs, and partly becuase they are easier to write. If nothing happens when you perform some action, chances are there is an error message you just haven't noticed.
More importantly, GEditor only implements undo in the image editor. The only workardound is to save often and revert to the last saved file when necessary.
Also, all tiles must be of the same size, even if they are used on separate levels. This is actually a limitation of GameForm, which you can work around by subclassing GTileForm in both the game and GEditor (left as an excercise for the reader), but I doubt it would be worth the effort.
There are several possible reasons you may need to extend GEditor.
- If you have really large maps you may need GEditor to scale the tile view.
- You may need to implement your game's custom graphics engine in GEditor.
- You may want to animate the level view.
- If you have objects covering multiple tiles, you may want to place the entire object with a single click.
- Depending on your image format and editor, you may find it useful to import several images from a single GIF.
You can choose to either edit the GEditor source code or to subclass the affected classes, or a combination of the two. Generally, if your modifications can be used in a future project, I would recommend modifying the GEditor source directly. If you on the other hand want to - for instance - add the graphics engine from your game, or change the file format, I suggest you subclass the affected classes instead. Having separate GEditor clones for your different games will make it more difficult to improve the GEditors, and you may end up copying and pasting a lot of code between them. Of course, these are just suggestions, and may or may not apply to your projects.
GameForm comes with some example games and demos to get you started quickly. The examples are commented, but not in Javadoc-style, because the comments are intended to be read together with the code itself.
The examples are not covered by the GameForm license agreement. They are in the public domain, which means that you may freely use their source code (excluding the GLib code) or graphics in any of your own projects. Please read the GameForm license agreement for details about allowed and disallowed usage of GLib.
Wrapping is primarily intended as a visual representation of a wrapped buffer. The left view is the buffer as it is normally shown, the right view shows what goes on "behind the scenes". The colored rectangles show how the different buffer pieces are assembled. Wrapping also shows why the buffer size needs to be a multiple of the tile size.
Bouncing is an example of how you can extend the graphics engine. It subclasses GLevelCanvas and GTileForm to allow tiles to be drawn both below and above the sprites (the darker diamonds in the center, and the black, covered, tiles), and to be animated by drawing two different images with different transparency (the flashing walls).
Bouncing also subclasses GSprite and GPixels to change the color of a single (gray) ball image. (Wrapping uses a different technique to accomplish a similar effect, using two images drawn with different levels of transparency).
Bouncing also shows how to "prime" the Just-in-Time (JIT) compiler. The JIT compiler caches interpreted bytecode the first time it is run, so it can be run a lot faster the second time around. After loading the data file, Bouncing runs a couple of frames of a "fake game" in the background, giving the JIT compiler a chance to compile the bytecode ahead of time. This means the game takes a little time to start up, but runs faster. Toggle kPrimeJIT to see the difference in missed frames.
Snake II is a complete game, created with the GameForm engine. It shows an example of using the GLevelCanvas' buffer for a title screen with a progress bar when loading the data file. It also shows how to use multiple levels, how to map a single GSprite subclass to several GSpriteForm objects (the bugs), and how to create sprites both in the start of a game (the snake), and during a game (bugs).
Thanks goes out to the SpriteWorld developers, Tony Myles, Karl Bunker and Vern Jensen. The wrapping buffer system was inspired by SpriteWorld's design, but modified to suit Java's run-time performance and interpreted bytecode. Read more about SpriteWorld at www.spriteworld.org.
Copyright © 2001, Björn Carlin.
This license agreement covers the source code in the folders "GLib Source" and "GEditor Source", and the libraries "GLib.jar" and "GEditor.jar" ("GameForm"). It does not cover the example code distributed with GameForm.
Permission to use GameForm, whole or in part, is granted for any personal, non-commercial purposes, to any person understanding and fully agreeing with this license agreement.
GameForm may NOT be used, whole or in part, by any commercial company or for any commercial software or development of commercial software, without prior written permission from the copyright holder. 'Commercial software' includes, but is not limited to, charging money for using or distributing the software, or requiring a paid subscription or license to use the software.
GameForm is provided 'as-is', without any express or implied warranty. The author shall not be held liable for any damages caused by the use of GameForm.
GameForm may be freely distibuted in an unaltered state, and with no part of the package missing. Especially this copyright notice and license agreement must be present in all source files of all distributed copies.
Copyright © 2001, Björn Carlin. All rights reserved.
Java, Javadoc, Swing, Sun and Sun Microsystems are trademarks or registered trademarks of Sun Microsystems, Inc.