4KGamesDesign < Games < TWiki

TWiki . Games . 4KGamesDesign

As of this writing, the 4K game programming contest has experienced four years of successful operation. It has challenged programmers both new and old to develop new ideas and new ways of looking at things all so they can keep their game under 4K in size. However, as the competition has expanded with newer and ever cooler entries, it has made life more and more difficult for new entrants to compete.

This article will attempt to share some of the knowledge that has been used by the competitors to keep their games small. With a little understanding, you too can be cranking out small and enjoyable games in no time flat!

General Tips

Manifest

In the past, it was considered acceptable to produce a JAR file that was not executable. As of the 2006 competition, all JAR files must be self-executable unless they are Applets or Webstart files. However, if you allow the JAR utility to create the manifest, it will chew up your available space with miscellaneous information. The solution to this is to create a custom Manifest file. Simply create a file with the following lines:


Main-Class: MyClass



Note that there are precisely two carriage returns after the Main-Class line. These are required by the 1.5 JVM. Without them, the JVM will complain that the file is not executable.

Once you have created the file, ZIP it into your JAR file as "META-INF/MANIFEST.INF". The JVM will recognize it as the manifest and execute the class you specified.

Data Structures

In C/C++ and assembler games of old, it was very easy to create data structures for your games just by using a "STRUCT" datatype. The Java replacement for C style structs is class files themselves. Unfortunately, class files are a no-no in 4K. Especially if they're inner classes.

The solution to this problem is to use arrays for each data type you need to store. For example, a class to store a bad guy might look like this:


public class BadGuy
{
    public int x;
    public int y;
    public int type;
}

To convert that to arrays, you first need to decide what the maximum number of values you're going to use is. I prefer to use 256. Thus code using this trick might look like this:
public void main(String[] args) 
{
    int[] badguy_x = new int[256];
    int[] badguy_y = new int[256];
    int[] badguy_type = new int[256];
}

Usually the "type" array is setup so that zero means 'dead' and non-zero gives the type of an alive creation. A further optimization is to ensure that "type" numbers match up with the graphics in an array.

Graphics

There are currently three major methods used for graphics:
  1. Procedural Graphics
  2. Tight PNGs or GIFs
  3. SuperPackME?

Procedural graphics involves using combinations of the line, oval, and rectangle functions to produce a sprite. An alternative is to dynamically create a bitmap pixel by pixel. The advantage to this method is that you don't need to worry about loading graphics. You can draw as needed, and even scale the graphics with no ill effects. The con to this approach is that it often increases the number of class and method references.

Tight PNGs or GIFs involves storing your images in a grid inside a single, highly compressed image file. That image file is usually run through a program like PNGCrush to ensure that it is as small as possible. After the image had been optimized, it's renamed to a single letter without the extension and added to the JAR archive. The graphic is loaded via the ImageIO classes at runtime. The ImageIO classes auto-detect the file type, thus allowing the developer to save space by not saving the file extension. To draw an individual image, you simply call the Graphics.drawImage(image, x1, y1, width1, height1, x2, y2, width2, height2, ImageObserver?) method with the correct coordinates.

SuperPackME? is a highly sophisticated approach to storing graphics. It involves stripping the image data down to a super-tight, highly compressable data format. That data format is then converted to a string of hexadecimal digits, then embedded into the class file itself. Once decoded, the developer will have access to an array of all his original images with no loss in quality.

Compression

Many programmers think that the key to fitting a game into 4K is to make the class file smaller. More than one programmer has been shocked when their final JAR becomes larger after the class file is optimized.

It's important to realize that the compressor is one of the keys to a successful 4K game. You must work with the compressor, not against it. This means that you should always choose to make information as redundant as possible. Try to write code that will produce as many redundant byte codes as possible. Also be aware that the ZIP compressor starts over for each entry in the ZIP/JAR file. So try to inline your information into strings rather than incur the massive overhead of multiple files.

7Zip or KZip will always produce better compression than using the standard JAR tool. Use the Manifest instructions above to create a manifest file, then follow the instructions below.

To create a JAR with 7Zip, use the following command:

    7z a -tzip -mx=9 <NAME>.jar <NAME>.class META-INF/MANIFEST.MF
There are two caveats with 7Zip that you should be aware of. The first is that the existing JAR file should always be deleted before you recreate it. Reusing an existing file often results in poorer compression. The second caveat is that you MUST use the command line tool. The graphical user interface never produces files as small as the files produced by the command line version.

To create a JAR with KZip, use the following command:

    kzip /s0 /r <NAME>.jar <NAME>.class META-INF/MANIFEST.MF
? There may be more options on other operating systems. This worked great in linux and in one instance gave me 170 more bytes to work with than 7zip was offering.

Sound

[WIP]

Keyboard

Don't implement listeners to read input. Use the processXXXEvent methods instead. It is a lot smaller and cleaner.
import java.awt.AWTEvent;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;


public class T extends JFrame {
    static boolean keys[] = new boolean[65536];

    public static void main(String args[]) {
        T t = new T();
        t.enableEvents(AWTEvent.KEY_EVENT_MASK);
        t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        t.setBounds(0, 0, 800, 600);
        t.setResizable(false);
        t.show();

        while(true) {
            if(keys[KeyEvent.VK_A] || keys[KeyEvent.VK_LEFT]) {
                System.out.println("LEFT");
            }
            if(keys[KeyEvent.VK_D] || keys[KeyEvent.VK_RIGHT]) {
                System.out.println("RIGHT");
            }
            if(keys[KeyEvent.VK_W] || keys[KeyEvent.VK_UP]) {
                System.out.println("UP");
            }
            if(keys[KeyEvent.VK_S] || keys[KeyEvent.VK_DOWN]) {
                System.out.println("DOWN");
            }
            if(keys[KeyEvent.VK_CONTROL] || keys[KeyEvent.VK_SPACE]) {
                System.out.println("FIRE");
            }
            if(keys[KeyEvent.VK_ESCAPE]) {
                System.exit(0);
            }
        }
    }
    public void processKeyEvent(KeyEvent e) {
        keys[e.getKeyCode()] = (e.getID() == KeyEvent.KEY_PRESSED);
    }
}

Miscellaneous Ideas


public class X
{
   private int x;


   public final void x()
   {
      int x = this.x;
      // do a lot with x
      this.x = x;
   }

   private void X(int a); // sinks the ship
   private void X(float a); // lands the plane
   private void X(int[] a); // make histogram of scattered ants
}

Going from this
if(true) {
    someValue = 20;
}
else {
    someValue = 10;
}
to this
someValue = 10;
if(true) {
    someValue = 20;
}
saved me 4 bytes in the final compressed archive.
(!) Anything denoted with an exclamation point is a hack that is potentially unsafe. Use with caution!
(?) This needs to be clarified by someone.

----- Revision r14 - 19 Jan 2009 - 17:18:25 - Main.captainjester