Developing Games in Java

David Brackeen, Bret Barker, Laurence Vanhelsuwé

Mentioned 6

A guide to Java game programming techniques covers such topics as 2D and 3D graphics, sound, artificial intelligence, multi-player games, collision detection, game scripting and customizing keyboard and mouse controls.

More on Amazon.com

Mentioned in questions and answers.

Can anyone recommend a good Java game engine for developing simple tile-based games? I'm looking for an engine that will allow me to build maps using something like Tiled www.mapeditor.org

Slick is exactly what I'm looking for, slick.cokeandcode.com but I can't get it working on Vista-64. The best I can manage is:Can't load IA 32-bit .dll on a AMD 64-bit platform (and this after downloading the latest LWJGL version).

Can anyone suggest something similar that will run on 64-bit vista?

I'd recommend purchasing the book "Developing Games in Java" by David Brackeen, it includes a tile-based game framework which seems excellent (I haven't implemented anything with it yet though).

Link to amazon

You could also download the code without getting the book, but I'd recommend the book.

I need to create a Java-based platformer game for a high school project (not computer science related), and I want to spend as little time on technical stuff as possible.

I'm already experienced in Java, and I already have most of the gameplay, graphics, etc. All I need to do is code it. I've looked and I'm considering one of two options that do not involving coding a game from scratch:

  • Copy an existing Java platformer (best option, but I can't find an open source Java platformer)
  • Use a Java game engine to avoid coding from scratch. I've looked at JGame but I'm not sure if it's the best bet for a platformer.

Developing Games in Java is a great book, and the first half of it goes in detail about creating a 2D platformer game. Overall I found the book's explanation to be excellent, and the full game is on the CD so you could mod it to fit your needs. [edit: wait a second... it doesn't come with a CD... the source is on the website, see below!]

I don't feel like it's a super fully-developed platformer; it feels more like a demo. But you could use the game as a starting point and add anything else you need. And the code, being a book example project, is well documented and commented. It has a level format too, so if you find it fits all your needs you should be able to just drop in your images and make your own level files, and then load them in-game.

By the way, the rest of the book dives into creating a software 3D engine from scratch using Java 2D. It's really awesome. Just a bit of a bonus after this project is done, eh?

Also a downside, the book is now almost 7 years old. It uses Java 1.4 though, and the book website is still online, so I'd say it's still a viable resource! In fact, it looks like the book website has a demo of the game AND all of the source code, so you might not even need to buy the book. Go check it out!

If I wanted create a Jeopardy or Wheel of Fortune type (TV/board game genre) desktop game, what's the best way to learn how to do so in Java? I'm not trying to create anything complicated like Quake or Spore but something orders of magnitude less complex.

I imagine I would need to render a game board/setting, simple effects and sounds. What Java tools, libraries are recommended for this?

Alternatively, if Python, Ruby or some other environment is more appropriate for this type of game development, please recommend.

Thanks.

For Java, I highly suggest the book "Developing Games in Java" by David Brackeen. It's is a really great book that teaches you everything from simple tile based games to complex, scripted first person shooters.

However, Python is also really nice for simple games using Pygame.

Are there any good books that teach how to make simple physics simulations in Java?

Yes, I recommend you this one: http://www.amazon.com/Physics-Game-Programmers-Grant-Palmer/dp/159059472X

Also you can check a lot of physics for games developed in Java:

I hope that this helps you!

First, thanks for taking a time for read this.

I'm reading the book Developing Games in Java. The chapter 8 shows how to load Wavefront .obj files. My obj file is located at C://pathToMyWorkspace//ProjectName//res//coffeCup.obj

When I try to load a Wavefront .obj file the compiler throws me the error:

java.io.FileNotFoundException: res\coffe
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:146)
at java.io.FileReader.<init>(FileReader.java:72)
at com.base.graphics.graphics3D.ObjectLoader.parseFile(ObjectLoader.java:141)
at com.base.graphics.graphics3D.ObjectLoader$ObjLineParser.parseLine(ObjectLoader.java:228)
at com.base.graphics.graphics3D.ObjectLoader.parseFile(ObjectLoader.java:169)
at com.base.graphics.graphics3D.ObjectLoader.loadObject(ObjectLoader.java:116)
at com.testGame.Texture3DTest.createPolygons(Texture3DTest.java:69)
at com.base.graphics.GameCore3D.init(GameCore3D.java:24)
at com.testGame.Texture3DTest.init(Texture3DTest.java:45)
at com.base.graphics.GameCore.start(GameCore.java:49)
at com.testGame.Texture3DTest.main(Texture3DTest.java:41)

For some reason the compiler tries to load from "res/res/coffeCup.obj", but the path that I specified is just "res/coffeCup.obj".

Here is the new ObjectLoader class (that loads .obj files):

package com.base.graphics.graphics3D;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import com.base.math.PolygonGroup;
import com.base.math.ShadedSurface;
import com.base.math.ShadedTexture;
import com.base.math.TexturedPolygon3D;
import com.base.math.Vector3D;

/**
 * The ObjectLoader class loads a subset of the Alias|Wavefront OBJ file
 * specification.
 * 
 * Lines that begin with '#' are comments.
 * 
 * OBJ file keywords:
 * 
 * <pre>
 *     mtllib [filename]    - Load materials from an external .mtl
 *                            file.
 *     v [x] [y] [z]        - Define a vertex with floating-point
 *                            coords (x,y,z).
 *     f [v1] [v2] [v3] ... - Define a new face. a face is a flat,
 *                            convex polygon with vertices in
 *                            counter-clockwise order. Positive
 *                            numbers indicate the index of the
 *                            vertex that is defined in the file.
 *                            Negative numbers indicate the vertex
 *                            defined relative to last vertex read.
 *                            For example, 1 indicates the first
 *                            vertex in the file, -1 means the last
 *                            vertex read, and -2 is the vertex
 *                            before that.
 *     g [name]             - Define a new group by name. The faces
 *                            following are added to this group.
 *     usemtl [name]        - Use the named material (loaded from a
 *                            .mtl file) for the faces in this group.
 * </pre>
 * 
 * MTL file keywords:
 * 
 * <pre>
 *     newmtl [name]        - Define a new material by name.
 *     map_Kd [filename]    - Give the material a texture map.
 * </pre>
 */
public class ObjectLoader {

    /**
     * The Material class wraps a ShadedTexture.
     */
    public static class Material {
        public File sourceFile;
        public ShadedTexture texture;
    }

    /**
     * A LineParser is an interface to parse a line in a text file. Separate
     * LineParsers and are used for OBJ and MTL files.
     */
    protected interface LineParser {
        public void parseLine(String line) throws IOException, NumberFormatException, NoSuchElementException;
    }

    protected File path;
    protected List vertices;
    protected Material currentMaterial;
    protected HashMap materials;
    protected List lights;
    protected float ambientLightIntensity;
    protected HashMap parsers;
    private PolygonGroup object;
    private PolygonGroup currentGroup;

    /**
     * Creates a new ObjectLoader.
     */
    public ObjectLoader() {
        materials = new HashMap();
        vertices = new ArrayList();
        parsers = new HashMap();
        parsers.put("obj", new ObjLineParser());
        parsers.put("mtl", new MtlLineParser());
        currentMaterial = null;
        setLights(new ArrayList(), 1);
    }

    /**
     * Sets the lights used for the polygons in the parsed objects. After
     * calling this method calls to loadObject use these lights.
     */
    public void setLights(List lights, float ambientLightIntensity) {
        this.lights = lights;
        this.ambientLightIntensity = ambientLightIntensity;
    }

    /**
     * Loads an OBJ file as a PolygonGroup.
     */
    public PolygonGroup loadObject(String parent, String filename) throws IOException {
        object = new PolygonGroup();
        object.setFilename(filename);
        path = new File(parent);

        vertices.clear();
        currentGroup = object;
        parseFile(filename);

        return object;
    }

    /**
     * Gets a Vector3D from the list of vectors in the file. Negative indeces
     * count from the end of the list, postive indeces count from the beginning.
     * 1 is the first index, -1 is the last. 0 is invalid and throws an
     * exception.
     */
    protected Vector3D getVector(String indexStr) {
        int index = Integer.parseInt(indexStr);
        if (index < 0) {
            index = vertices.size() + index + 1;
        }
        return (Vector3D) vertices.get(index - 1);
    }

    /**
     * Parses an OBJ (ends with ".obj") or MTL file (ends with ".mtl").
     */
    protected void parseFile(String filename) throws IOException {
        // get the file relative to the source path
        File file = new File(path, filename);
        BufferedReader reader = new BufferedReader(new FileReader(file));

        // get the parser based on the file extention
        LineParser parser = null;
        int extIndex = filename.lastIndexOf('.');
        if (extIndex != -1) {
            String ext = filename.substring(extIndex + 1);
            parser = (LineParser) parsers.get(ext.toLowerCase());
        }
        if (parser == null) {
            parser = (LineParser) parsers.get("obj");
        }

        // parse every line in the file
        while (true) {
            String line = reader.readLine();
            // no more lines to read
            if (line == null) {
                reader.close();
                return;
            }

            line = line.trim();

            // ignore blank lines and comments
            if (line.length() > 0 && !line.startsWith("#")) {
                // interpret the line
                try {
                    parser.parseLine(line);
                } catch (NumberFormatException ex) {
                    throw new IOException(ex.getMessage());
                } catch (NoSuchElementException ex) {
                    throw new IOException(ex.getMessage());
                }
            }

        }
    }

    /**
     * Parses a line in an OBJ file.
     */
    protected class ObjLineParser implements LineParser {

        public void parseLine(String line) throws IOException, NumberFormatException, NoSuchElementException {
            StringTokenizer tokenizer = new StringTokenizer(line);
            String command = tokenizer.nextToken();
            if (command.equals("v")) {
                // create a new vertex
                vertices.add(new Vector3D(Float.parseFloat(tokenizer.nextToken()), Float.parseFloat(tokenizer.nextToken()), Float.parseFloat(tokenizer.nextToken())));
            } else if (command.equals("f")) {
                // create a new face (flat, convex polygon)
                List currVertices = new ArrayList();
                while (tokenizer.hasMoreTokens()) {
                    String indexStr = tokenizer.nextToken();

                    // ignore texture and normal coords
                    int endIndex = indexStr.indexOf('/');
                    if (endIndex != -1) {
                        indexStr = indexStr.substring(0, endIndex);
                    }

                    currVertices.add(getVector(indexStr));
                }

                // create textured polygon
                Vector3D[] array = new Vector3D[currVertices.size()];
                currVertices.toArray(array);
                TexturedPolygon3D poly = new TexturedPolygon3D(array);

                // set the texture
                ShadedSurface.createShadedSurface(poly, currentMaterial.texture, lights, ambientLightIntensity);

                // add the polygon to the current group
                currentGroup.addPolygon(poly);
            } else if (command.equals("g")) {
                // define the current group
                if (tokenizer.hasMoreTokens()) {
                    String name = tokenizer.nextToken();
                    currentGroup = new PolygonGroup(name);
                } else {
                    currentGroup = new PolygonGroup();
                }
                object.addPolygonGroup(currentGroup);
            } else if (command.equals("mtllib")) {
                // load materials from file
                String name = tokenizer.nextToken();
                parseFile(name);
            } else if (command.equals("usemtl")) {
                // define the current material
                String name = tokenizer.nextToken();
                currentMaterial = (Material) materials.get(name);
                if (currentMaterial == null) {
                    System.out.println("no material: " + name);
                }
            } else {
                // unknown command - ignore it
            }

        }
    }

    /**
     * Parses a line in a material MTL file.
     */
    protected class MtlLineParser implements LineParser {

        public void parseLine(String line) throws NoSuchElementException {
            StringTokenizer tokenizer = new StringTokenizer(line);
            String command = tokenizer.nextToken();

            if (command.equals("newmtl")) {
                // create a new material if needed
                String name = tokenizer.nextToken();
                currentMaterial = (Material) materials.get(name);
                if (currentMaterial == null) {
                    currentMaterial = new Material();
                    materials.put(name, currentMaterial);
                }
            } else if (command.equals("map_Kd")) {
                // give the current material a texture
                String name = tokenizer.nextToken();
                File file = new File(path, name);
                if (!file.equals(currentMaterial.sourceFile)) {
                    currentMaterial.sourceFile = file;
                    currentMaterial.texture = (ShadedTexture) Texture.createTexture(file.getPath(), true);
                }
            } else {
                // unknown command - ignore it
            }
        }
    }
}

The new Texture3DTest (Where I load the files):

package com.testGame;

import java.awt.event.KeyEvent;
import java.io.IOException;

import com.base.graphics.GameCore3D;
import com.base.graphics.graphics3D.ObjectLoader;
import com.base.graphics.graphics3D.Rectangle3D;
import com.base.graphics.graphics3D.Texture;
import com.base.graphics.graphics3D.ZBufferedRenderer;
import com.base.input.GameAction;
import com.base.input.InputManager;
import com.base.input.Mouse;
import com.base.math.PolygonGroup;
import com.base.math.TexturedPolygon3D;
import com.base.math.Transform3D;
import com.base.math.Vector3D;
import com.base.math.ViewWindow;

public class Texture3DTest extends GameCore3D {

    protected InputManager inputManager;
    protected GameAction exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);

    protected GameAction moveForward = new GameAction("moveForward");
    protected GameAction moveBackward = new GameAction("moveBackward");
    protected GameAction moveUp = new GameAction("moveUp");
    protected GameAction moveDown = new GameAction("moveDown");
    protected GameAction moveLeft = new GameAction("moveLeft");
    protected GameAction moveRight = new GameAction("moveRight");

    protected GameAction rootUp = new GameAction("rootUp");
    protected GameAction rootDown = new GameAction("rootDown");
    protected GameAction rootLeft = new GameAction("rootLeft");
    protected GameAction rootRight = new GameAction("rootRight");

    protected final int SPEED = 6;
    protected final float ROOTATION_SPEED = 0.01f;

    public static void main(String[] args) {
        new Texture3DTest().start();
    }

    public void init() {
        super.init();
        Mouse.hide(frame);

        inputManager = new InputManager(frame);

        inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);

        inputManager.mapToKey(moveForward, KeyEvent.VK_W);
        inputManager.mapToKey(moveBackward, KeyEvent.VK_S);
        inputManager.mapToKey(moveLeft, KeyEvent.VK_A);
        inputManager.mapToKey(moveRight, KeyEvent.VK_D);
        inputManager.mapToKey(moveUp, KeyEvent.VK_SPACE);
        inputManager.mapToKey(moveDown, KeyEvent.VK_SHIFT);

        inputManager.mapToKey(rootUp, KeyEvent.VK_UP);
        inputManager.mapToKey(rootDown, KeyEvent.VK_DOWN);
        inputManager.mapToKey(rootLeft, KeyEvent.VK_LEFT);
        inputManager.mapToKey(rootRight, KeyEvent.VK_RIGHT);
    }

    public void createPolygons() {
        ObjectLoader objLoader = new ObjectLoader();

        try {
            PolygonGroup object = objLoader.loadObject("res/", "coffeCup.obj");
            polygons.add(object);
        } catch (IOException e) { 
            e.printStackTrace();
        }
    }

    public void setTexture(TexturedPolygon3D poly, Texture texture) {
        Vector3D origin = poly.getVertex(0);

        Vector3D dv = new Vector3D(poly.getVertex(1));
        dv.subtract(origin);

        Vector3D du = new Vector3D();
        du.setToCrossProduct(poly.getNormal(), dv);

        Rectangle3D textureBounds = new Rectangle3D(origin, du, dv, texture.getWidth(), texture.getHeight());

        poly.setTexture(texture, textureBounds);
    }

    public void update() {
        if (exit.isPressed())
            System.exit(0);

        Transform3D camera = polygonRenderer.getCamera();
        Vector3D cameraLoc = polygonRenderer.getCamera().getLocation();

        if (moveForward.isPressed()) {
            cameraLoc.x -= SPEED * camera.getSinAngleY();
            cameraLoc.z -= SPEED * camera.getCosAngleY();
        }

        if (moveBackward.isPressed()) {
            cameraLoc.x += SPEED * camera.getSinAngleY();
            cameraLoc.z += SPEED * camera.getCosAngleY();
        }

        if (moveLeft.isPressed()) {
            cameraLoc.x -= SPEED * camera.getCosAngleY();
            cameraLoc.z += SPEED * camera.getSinAngleY();
        }

        if (moveRight.isPressed()) {
            cameraLoc.x += SPEED * camera.getCosAngleY();
            cameraLoc.z -= SPEED * camera.getSinAngleY();
        }

        if (moveUp.isPressed()) {
            camera.getLocation().y += SPEED;
        }

        if (moveDown.isPressed()) {
            camera.getLocation().y -= SPEED;
        }

        if (rootUp.isPressed())
            camera.rotateAngleX(ROOTATION_SPEED);

        if (rootDown.isPressed())
            camera.rotateAngleX(-ROOTATION_SPEED);

        if (rootLeft.isPressed())
            camera.rotateAngleY(ROOTATION_SPEED);

        if (rootRight.isPressed())
            camera.rotateAngleY(-ROOTATION_SPEED);
    }

    public Texture loadTexture(String imageName) {
        return Texture.createTexture(imageName, true);
    }

    public void createPolygonRenderer() {
        viewWindow = new ViewWindow(0, 0, frame.getWidth(), frame.getHeight(), (float) Math.toRadians(75));

        Transform3D camera = new Transform3D(0, 100, 0);
        polygonRenderer = new ZBufferedRenderer(camera, viewWindow);
    }
}

PS:I'm using eclipse

Many Thanks

(This answer refers to a previous revision of the question. The question has been updated based on this answer. See the comments for details)

Well, the problem here is that the path handling of the ObjectLoader class is wrong. The relevant call sequence can be seen in this minimal example:

import java.io.File;

public class FilePathTest
{
    public static void main(String[] args)
    {
        loadObject("res/SomeFile.txt");
        loadObject("SomeFile.txt");
    }

    static File path;

    static void loadObject(String filename)
    {
        File file = new File(filename);
        path = file.getParentFile();        
        parseFile(filename);
    }

    static void parseFile(String filename)
    {
        File file = new File(path, filename);
        System.out.println("File: "+file+" exists? "+file.exists());
    }
}

From the given file, it obtains the "parent file" (that is, the directory where the file is contained in) and stores it as the path. Later, the file name is attached to this path again, in order to obtain the final file name. So when the filename starts with a relative path prefix (like the res/ in your case), this part is duplicated.

(BTW: The reason why he stores the path is that is that the OBJ file may contain references to other files that are assumed to be in the same directory, e.g. the MTL file, which in turn may contain further references to texture files).

The "simplest" solution that I can imagine now would be to manually handle the path and the file name. The basic idea is sketched here, it should be possible to transfer this to the original ObjectLoader class:

import java.io.File;

public class FilePathTest
{
    public static void main(String[] args)
    {
        loadObject("res/", "SomeFile.obj");
    }

    static File path;

    static void loadObject(String parent, String filename)
    {
        File file = new File(parent+File.separator+filename);
        path = new File(parent);        
        parseFile(file);
    }

    static void parseFile(File file)
    {
        System.out.println("File: "+file+" exists? "+file.exists());

        String mtlName = "SomeFile.mtl";
        File mtlFile = new File(path, mtlName);

        System.out.println("MTL file: "+mtlFile+" exists? "+mtlFile.exists());
    }
}

EDIT: A MVCE, created by removing everything from the original code that caused compilation errors

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import stackoverflow.objload.ObjectLoader.LineParser;
import stackoverflow.objload.ObjectLoader.Material;
import stackoverflow.objload.ObjectLoader.MtlLineParser;
import stackoverflow.objload.ObjectLoader.ObjLineParser;

public class ObjectLoaderTest
{
    public static void main(String[] args)
    {
        ObjectLoader objLoader = new ObjectLoader();

        try {
            PolygonGroup object = objLoader.loadObject("res/", "coffeCup.obj");
        } catch (IOException e) { 
            e.printStackTrace();
        }    

    }
}

class PolygonGroup
{
    public PolygonGroup(String name)
    {
    }

    public PolygonGroup()
    {
    }

    public void setFilename(String filename)
    {
    }

    public void addPolygonGroup(PolygonGroup currentGroup)
    {
    }
}
class Vector3D
{
    public Vector3D(float parseFloat, float parseFloat2, float parseFloat3)
    {
    }
}

class ObjectLoader {

    /**
     * The Material class wraps a ShadedTexture.
     */
    public static class Material {
        public File sourceFile;
    }

    /**
     * A LineParser is an interface to parse a line in a text file. Separate
     * LineParsers and are used for OBJ and MTL files.
     */
    protected interface LineParser {
        public void parseLine(String line) throws IOException, NumberFormatException, NoSuchElementException;
    }

    protected File path;
    protected List vertices;
    protected Material currentMaterial;
    protected HashMap materials;
    protected List lights;
    protected float ambientLightIntensity;
    protected HashMap parsers;
    private PolygonGroup object;
    private PolygonGroup currentGroup;

    /**
     * Creates a new ObjectLoader.
     */
    public ObjectLoader() {
        materials = new HashMap();
        vertices = new ArrayList();
        parsers = new HashMap();
        parsers.put("obj", new ObjLineParser());
        parsers.put("mtl", new MtlLineParser());
        currentMaterial = null;
        setLights(new ArrayList(), 1);
    }

    /**
     * Sets the lights used for the polygons in the parsed objects. After
     * calling this method calls to loadObject use these lights.
     */
    public void setLights(List lights, float ambientLightIntensity) {
        this.lights = lights;
        this.ambientLightIntensity = ambientLightIntensity;
    }

    /**
     * Loads an OBJ file as a PolygonGroup.
     */
    public PolygonGroup loadObject(String parent, String filename) throws IOException {
        object = new PolygonGroup();
        object.setFilename(filename);
        path = new File(parent);

        vertices.clear();
        currentGroup = object;
        parseFile(filename);

        return object;
    }

    /**
     * Gets a Vector3D from the list of vectors in the file. Negative indeces
     * count from the end of the list, postive indeces count from the beginning.
     * 1 is the first index, -1 is the last. 0 is invalid and throws an
     * exception.
     */
    protected Vector3D getVector(String indexStr) {
        int index = Integer.parseInt(indexStr);
        if (index < 0) {
            index = vertices.size() + index + 1;
        }
        return (Vector3D) vertices.get(index - 1);
    }

    /**
     * Parses an OBJ (ends with ".obj") or MTL file (ends with ".mtl").
     */
    protected void parseFile(String filename) throws IOException {
        // get the file relative to the source path
        File file = new File(path, filename);

        System.out.println("Reading "+file+", exists "+file.exists());
        BufferedReader reader = new BufferedReader(new FileReader(file));

        // get the parser based on the file extention
        LineParser parser = null;
        int extIndex = filename.lastIndexOf('.');
        if (extIndex != -1) {
            String ext = filename.substring(extIndex + 1);
            parser = (LineParser) parsers.get(ext.toLowerCase());
        }
        if (parser == null) {
            parser = (LineParser) parsers.get("obj");
        }

        // parse every line in the file
        while (true) {
            String line = reader.readLine();
            // no more lines to read
            if (line == null) {
                reader.close();
                return;
            }

            line = line.trim();

            // ignore blank lines and comments
            if (line.length() > 0 && !line.startsWith("#")) {
                // interpret the line
                try {
                    parser.parseLine(line);
                } catch (NumberFormatException ex) {
                    throw new IOException(ex.getMessage());
                } catch (NoSuchElementException ex) {
                    throw new IOException(ex.getMessage());
                }
            }

        }
    }

    /**
     * Parses a line in an OBJ file.
     */
    protected class ObjLineParser implements LineParser {

        public void parseLine(String line) throws IOException, NumberFormatException, NoSuchElementException {
            StringTokenizer tokenizer = new StringTokenizer(line);
            String command = tokenizer.nextToken();
            if (command.equals("v")) {
                // create a new vertex
                vertices.add(new Vector3D(Float.parseFloat(tokenizer.nextToken()), Float.parseFloat(tokenizer.nextToken()), Float.parseFloat(tokenizer.nextToken())));
            } else if (command.equals("f")) {
                // create a new face (flat, convex polygon)
                List currVertices = new ArrayList();
                while (tokenizer.hasMoreTokens()) {
                    String indexStr = tokenizer.nextToken();

                    // ignore texture and normal coords
                    int endIndex = indexStr.indexOf('/');
                    if (endIndex != -1) {
                        indexStr = indexStr.substring(0, endIndex);
                    }

                    currVertices.add(getVector(indexStr));
                }

                // create textured polygon
                Vector3D[] array = new Vector3D[currVertices.size()];
                currVertices.toArray(array);

            } else if (command.equals("g")) {
                // define the current group
                if (tokenizer.hasMoreTokens()) {
                    String name = tokenizer.nextToken();
                    currentGroup = new PolygonGroup(name);
                } else {
                    currentGroup = new PolygonGroup();
                }
                object.addPolygonGroup(currentGroup);
            } else if (command.equals("mtllib")) {
                // load materials from file
                String name = tokenizer.nextToken();
                parseFile(name);
            } else if (command.equals("usemtl")) {
                // define the current material
                String name = tokenizer.nextToken();
                currentMaterial = (Material) materials.get(name);
                if (currentMaterial == null) {
                    System.out.println("no material: " + name);
                }
            } else {
                // unknown command - ignore it
            }

        }
    }

    /**
     * Parses a line in a material MTL file.
     */
    protected class MtlLineParser implements LineParser {

        public void parseLine(String line) throws NoSuchElementException {
            StringTokenizer tokenizer = new StringTokenizer(line);
            String command = tokenizer.nextToken();

            if (command.equals("newmtl")) {
                // create a new material if needed
                String name = tokenizer.nextToken();
                currentMaterial = (Material) materials.get(name);
                if (currentMaterial == null) {
                    currentMaterial = new Material();
                    materials.put(name, currentMaterial);
                }
            } else if (command.equals("map_Kd")) {
                // give the current material a texture
                String name = tokenizer.nextToken();
                File file = new File(path, name);
                if (!file.equals(currentMaterial.sourceFile)) {
                    currentMaterial.sourceFile = file;
                }
            } else {
                // unknown command - ignore it
            }
        }
    }
}

I'm creating a 3D game based on the book Developing Games in Java (you don't need to read or know the book to answer). To create the map I'm using a .map file that specifies the walls, floors, ceilings, and so forth. I just don't understand how the walls are created. Instead of using a begin x (coordinate) and a final x, a begin z (coordinate) and a final z, he just uses one x coordinate and one z coordinate. The commands are:

#    v [x] [y] [z]        - Define a vertex with floating-point 
#                           coords (x,y,z).
#    mtllib [filename]    - Load materials from an external .mtl 
#                           file.
#    usemtl [name]        - Use the named material (loaded from a 
#                           .mtl file) for the next floor, ceiling,
#                           or wall.
#    ambientLightIntensity 
#        [value]          - Defines the ambient light intensity
#                           for the next room, from 0 to 1.
#    pointlight [v]       - Defines a point light located at the  
#        [intensity]        specfied vector. Optionally, light
#        [falloff]          intesity and falloff distance can
#                           be specified.
#    player [v] [angle]   - Specifies the starting location of the
#                           player and optionally a starting 
#                           angle, in radians, around the y-axis.
#    obj [uniqueName]     - Defines an object from an external
#        [filename] [v]     OBJ file. The unique name allows this
#        [angle]            object to be uniquely identfied, but
#                           can be "null" if no unique name is 
#                           needed. The filename is an external
#                           OBJ file. Optionally, the starting 
#                           angle, in radians, around the y-axis 
#                           can be specified.
#    room [name]          - Defines a new room, optionally giving
#                           the room a name. A room consists of
#                           vertical walls, a horizontal floor
#                           and a horizontal ceiling. Concave rooms
#                           are currently not supported, but can be
#                           simulated by adjacent convex rooms.
#    floor [height]       - Defines the height of the floor of 
#                           the current room, using the current
#                           material. The current material can
#                           be null, in which case no floor 
#                           polygon is created. The floor can be
#                           above the ceiling, in which case a 
#                           "pillar" or "block" structure is 
#                           created, rather than a "room".
#    ceil [height]        - Defines the height of the ceiling of 
#                           the current room, using the current
#                           material. The current material can
#                           be null, in which case no ceiling
#                           polygon is created. The ceiling can be
#                           below the floor, in which case a 
#                           "pillar" or "block" structure is 
#                           created, rather than a "room".
#    wall [x] [z]         - Defines a wall vertex in a room using
#         [bottom] [top]    the specified x and z coordinates.
#                           Walls should be defined in clockwise 
#                           order. If "bottom" and "top" is not
#                           defined, the floor and ceiling height
#                           are used. If the current material is
#                           null, or bottom is equal to top, no
#                           wall polygon is created.

Maybe you can understand the "wall instructions" and explain in more details? I'm about 2 days testing some "theories" but I still can't understand. Other thing I don't get: what is the 'v' command for?

PS: Is there a way to create the .map file from a external program (like a program that construct this kind of .map file)? And what would be a vertex?

Many thanks

What the wall command does is define a wall vertex. That means that it defines a corner or an endpoint of a wall. You'd need to give multiple wall vertices to define a wall. For instance:

wall 0 0
wall 0 2
wall 2 2

That will define a wall consisting of two straight pieces in a 90 degree corner.

The height of the wall is defined by the last two optional parameters. I'm not certain how you would go about creating separate pieces of wall though, but perhaps you could find an example of a maze that does this.