Woodward Developer Guide

Introduction:

Most of this work is the direct result of a very interesting paper by Olivier Marcoux; many thanks to him even though he never answered my emails. Also many thanks to the people at the Exult project whose code is an inspiration. More thanks goes to SourceForge for making open-source a reality and for putting the fun back into programming in general.

1. What this is (general rant):

Ultima7 is the best computer role-playing/adventure game ever made. Subjective opinions aside, I've never experienced a greater sense of "suspension of disbelief" in any other game. An endless map offers lots of areas to explore, every object can be moved, used, etc. NPC-s really seem to have a personality (as well as a schedule.) The story-line is mysterious and lengthy. On top of all this Ultima7 has a great sense of humor and a timeless visual presentation.

So woodward... I'm aiming to create a game with all the trimmings of Ultima7. It is written mostly in Java because it's a simple language, it's cross platform and has good object-oriented features I need for game development. The run-time graphics engine uses SDL (more on this below) which a cross-platform C api that lets me run the game full screen. I plan to keep the odd-angled, cartoony look, the humor and the vastness of the original. The story-line, characters, music and rpg-system will be new.

2. General architecture:

The main package name is com.rainbow.wood. RainbowSoft is a historical name I liked so it's been kept and wood is the project's name. The code is organized into the following sections: (Note that packages that don't appear here are deprecated.)

  • editor - This is the game's editor. For a user guide on how to use the editor see this document.
  • game - This is the home of the run-time game engine.
  • gfx - Different graphics adapters for Swing and SDL. The editor uses swing to display the game map and the run-time uses SDL.
  • io - The io system. This is what reads/writes the game's state and various other files (like the map) to/from the disk.
  • rpg - The role-playing system. What classes, races etc. do in the game.
  • rpg.action - Actions are the heart of the role-playing system. Everything an item, spell or special ability does are described by actions.
  • rpg.archetype - Archetypes are like 'classes' in standard rpg terminology.
  • util - Some utility classes.

Detailed descriptions:

Map object hierarchy:

The map is made up of 16x16 regions. Regions are made of 16x16 chunks and chunks are made of (you got it) 16x16 tiles. Each tile is 16x16 pixels. So this means the entire map is 256x256 chunks or 4096x4096 tiles or 65536x65536 pixels big. This is quite large (actually it's somewhat bigger than Ultima7's map.) Chunks contain shapes. A shape is made up of multiple frames. A frame is basically an image with some data that describes it as a 3d block. Currently images are saved as 'raw' data: 1 byte per pixel. In fact, I use Adobe Photoshop to create the images; it has a RAW file type. A chunk's data is the static data that the user can't change in the game. (For example, the grass, water, sides of buildings, etc.) At game-time, dynamic data is added to the static data. Dynamic data is things that the user can change, for example open a door or pick up an item. Dynamic data is stored in the "savegame" folder.

** insert a better description here, copied from the editor's user guide **

IO package:

There are two challenges in writing a persistence layer for Woodward. First is that the map is quite big and can't be in memory all at once. The second is that I didn't want to rely on using a database, (maybe I should've) because I didn't want the overhead and the setup troubles.
All of Woodward's data-files live in the "data" directory.
There are several different file types the Woodward uses for persistence. FlexFiles (*.flx) are indexed data files (com.rainbow.wood.io.FlexFile). The first 2048 entries (4 + 2048 * 16 bytes) of a flex file are 'pointers' (file locations) into the second part of the file which contains the data. Flex files are used for saving the content of chunks for example, or anywhere when there's a variable amount of data (a chunk contains a variable number of shapes) to save. A caching flex file (com.rainbow.wood.io.CachingFlexFile) extends FlexFile and stores a certain number of items in memory.
List files (*.lst) are used when there are fixed number of data items. For example a region's data is saved in a flex file because it contains exactly 16x16 chunks.
A palette file (*.act) is a Photoshop 5 palette file. It contains 768 (3 bytes per color) bytes of palette data and some crap at the end.
The io package is mostly various flex file implementations for different game classes.

Graphics package (including SDL and JSDL):

The class that is responsible for drawing the screen is com.rainbow.wood.MapView. This class figures out which regions and chunks are visible, loads their data (using the IO package) loads the dynamic chunk data and calls the paint method on all the chunks (which are now RuntimeChunks) that are needed for a screen update. A RuntimeChunk has a graph of ShapePositions which represent a (surprise!) certain shape in a certain position within that chunk. The reason this is a graph (and is sometimes referred to as the "covers" graph) is because the paint method recursively paints shapes as they cover each other. To determine which shape covers which others, the RuntimeChunk's insert method uses a CoverStrategy implementation class. A CoverStrategy has one method which finds out if shape A covers shape B or vica-versa or neither. I wrote the original strategy for this (com.rainbow.wood.Cover3D) but I found a much more optimized version from Exult. I tried my best to optimize MapView (and then on down the paint stack, RuntimeChunk, etc.) as this is the most time-consuming part of the game. So there's some object pooling in paint and little tricks like a Chunk's shapes are saved "in-order" (that is the entire covers-graph is serialized).
The MapView and its components (composite pattern) like RuntimeChunk make callbacks to a graphics adapter when they draw the map. The graphics adapter has two implementations, one for Swing (used in the editor) and one for JSDL (used in the game.)
This brings us to SDL. Simple DirectMedia Layer is a cross-platform multimedia library designed to provide fast access to the graphics framebuffer and audio device. It was originally created by Sam Latinga who worked on the game Diablo by Blizzard. There's an Sourceforge project called JSDL which is a Java wrapper around the C code. Woodward uses JSDL to draw in fullscreen mode. There is still a fair amount of work to be done on the JSDL code to clean up some memory leaks.

The rpg system:

I am currently working on developing the rpg system. So this information may change if I'm suddenly inspired by the ood muse. So, the classic "class" concept in woodward has been replaced by the idea of an archetype. Character creation is sort of like taking a prototype and mutating it into the hero (or villain) of your choice. Therefore, when you select an archetype that is your general world view, it's how you see yourself in the mysterious ways of the universe. This is represented by the com.rainbow.wood.rpg.Archetype interface whose implementation live in the rpg/archetype folder. The idea here is that future game expansions can add new archetypes. A character is further specialized by assigning some statistical points and skill levels. (see the RPG section) Each character has a set of items (com.rainbow.wood.rpg.Item) in their inventory (...rpg.Inventory) and some spells in a spellbook. Both spells, items and an archetype's special abilities are eventually represented as a set of actions. Actions (com.rainbow.wood.rpg.Action) are also dynamically loaded (like archetypes) from the rpg/action folder. So for example, if you possess the "Holy Avenger" sword it has two actions (at least) one to attack as a sword and another to release some fireballs. There is a lot missing here (NPC-s, spells, etc.) that I'm currently working on.

The editor:

The editor is a Swing app that uses tabs for each sub-editor. For a full description on how to use the editor see this document. For a general architecture overview, the main class is com.rainbow.wood.editor.Editor. There's an interface (com.rainbow.wood.editor.EditorPanel which all the tabs implement. An important consideration when writing components for the editor is that given the possible vastness of the game (number of items, map size, etc.) no gui component should attempt to hold all of its items in memory at once. For a good example, see com.rainbow.wood.editor.ChunkTree. In general the rest of the editor is the usual jumble of inner classes, listeners, adapters, etc. I try to (swing-style) separate model from controller from view but in general I don't see anything wrong with this style of coding for gui elements.

3. A note on naming conventions:

I realize that everyone has their favorite naming conventions that they religiously obey. Most programmers are less than flexible when it comes to discussing changing this sacred habit. So rather than enforce a naming convention I only attempt to discribe the system that I use. (It is of course encouraged that -should you work on woodward- you follow this convention but it is not mandatory.) I follow something similar to what Doug Lea uses in his book Concurrent Programming in Java and I think even he got it from someplace else as well. Basically, all class names are capitalized and use inner caps (like ImageHandler) all package names are in lower case. I add a trailing underscore to private member vars and use underscores to separate words rather than inner caps. (For example: private int some_variable_;) Static private members get two trailing underscores: private static int something__; Public finals (constants) are in all caps with underscores to separate words. I like putting my curly braces on the same line like:

if(something_ == something_else_) {
  // do something...
} else {
  // do something else...
}
Instead of tabs I use 3 spaces, most text editors (emacs!) can be set to use this.

4. How to build Woodward:

I use the standard linux/unix 'make' utility to build the code. (For windows, you could use ant which is pretty cool or Borland also has a good make program. At any rate I'm not sure what shape the windows makefile is in...) So the build everything do:

make -f Makefile.linux

If you only want to compile, run:
make -f Makefile.linux jikes_compile

To build the jar file only, type:
make -f Makefile.linux jar

To build the javadocs only, type:
make -f Makefile.linux doc

5. How to contribute:

A couple of people have expressed interest in Woodward and honestly I've always thought of this as my pet project that I most likely will never finish. However, I would like it if other people contributed. There's a lot to code, but the most time-consuming operation (for me) is drawing the pictures... So, if you'd like to be part of the "Woodward phenomenon" send me an email (I almost never read the discussion lists) with a proposal of what you're planning to do. The first time you add code I will review it (not for style, but to make sure you're not trying to do something like load the entire map into memory) and thereafter you will have full permissions to do anything. Check out the Coding page for currently pending projects.

6. A short description of isometric 3d images used in the game:

I use a kind of fake 3d called "isometric view". This is an earlier version of the type of display pretty commonly used in games like "Diablo" of "Ultima". The camera is at a 45/45/45 angle above and in front of the object seen. This means that looking at a cube from this angle would render the base and top of the cube as perfect squares and the four connecting edges as lines at 45-degrees. The length of the lines would be 1/2 of an edge along the base of the cube. This is not a "true" 3d view, but one that is common in architecture, drafting, etc. The 1/2-rule is good to remember b/c woodward uses it when calculating object heights. Another (more estethic) guideline I like to follow is to outline the object with 2 pixels of black color. The game's engine can only handle (roughly) box shaped objects. This means that if you imagine a tree where the trunk is slimmer than its crown, if you make it one image, and give the size of the crown as the base of the tree, the object will block (that is the player will not be able to move through it) in a box with a lot of empty space. Even though it seems like we should be able stand next to the narrow tree-trunk, if the base is derived from the size of the crown this is not possible. It's best to break objects like this up into two and have separate frames for the trunk and the crown. (This also lets you use different colored crowns on the same trunk.) You can see this bounding box and base displayed in the "Shape" tab of the editor.