Skip to main content

Development of a 3D Multiplayer Racing Game

October 10, 2006



Racing games are one of the most popular categories of games and their development has always been a challenge to the game programmer, due to their demands in computer resources and fast networks. Most commercial games have been developed in C++, which is a mature programming language used by experienced programmers in the industry. However, network development, mobile gadgets, and hardware development have highlighted the need for a dynamic, continuously developed, portable, and object-oriented programming language for game development. Java seems to have these characteristics and sometimes can be faster than C++. The traditional game industry can also gain from Java. New game consoles are based on network services and there are thoughts of establishing Java as a mainstream technology in game development in the future.

This article describes work that was carried out as part of an undergraduate thesis. It includes the development of a 3D multiplayer racing game, JautOGL, using Java 1.4's new I/O APIs for networking, JOGL (Java OpenGL) for graphics, and JOAL (Java OpenAL) for sound. Its supported functions and architecture are described below, together with some important and interesting details of the classes, which are illustrated too. Finally, the discussion of future developments and improvements that can be applied to JautOGL concludes the article. Figures 1 to 4 show some print screens of the game.

Figure 1. JautOGL logo

Figure 2. Far camera view

Figure 3. Normal camera view

Figure 4. Driver camera view

Supported Functions

JautOGL is a game engine with some fundamental characteristics and functions that can give a direct visual output and can be a basis for testing, experimentation, and future extensions. Both the number and the development depth of these functions are closely related to the length of development time.

The game engine supports the Full Screen Exclusive Mode (FSEM) system of Java SE 1.4 and 1.5. Thus, JautOGL runs in full screen and incorporates a special class that embeds various functions, which manage issues related to graphics, resolution, color bit depth, screen updates, and window management. Anti-flickering and anti-tearing functions have also been created; they improve animation and synchronize the number of the screen updates with the number of the frames per second that the pipeline produces.

As far as interactivity, JautOGL listens to mouse and keyboard events. The classes that implement these functions can create interactivity patterns in order for a player to set and define the buttons of his preference, which he will use to play the game. The implementation of these two functions is based on the approach of David Brackeen, author of Developing Games in Java.

During the development of JautOGL, a very serious problem was encountered with the animation thread. The game did not seem to run normally, and the reason for this malfunction appeared to be related to the animation threads and the process of multitasking. CPU usage reached 100 percent. The problem was known to the community and the solution of the class FPSAnimator was applied to JautOGL.

The engine uses a parser of Wavefront format files in order to load 3D models. The Wavefront format specification defines two types of files. The .obj files describe the geometry and the .mtl files include information about the materials and lights, which are connected with the geometry of the .obj files. The specification defines many complex geometrical characteristics and shapes. Some of them include Bezier curves, B-splines, Taylor polynomial curves, and various surfaces. The parsers of JautOGL are focused on the basics in order to reduce programming complexity and mathematical calculations that OpenGL easily does through its functions. The 3D models of the cars have been produced using 3D Studio Max 8, which exports the models and creates the Wavefront format files.

Furthermore, JautOGL loads textures and images and applies them in geometry. The classes include a range of methods that manage the textures and apply techniques like mipmapping.

By using OpenAL for the sound engine of JautOGL, the game gains a 3D sound system that plays the different car sounds dynamically into the 3D environment and gives the opportunity for many different future developments.

The movement of the car is based on the interactivity of the user. The cars can be moved in any direction and different camera modes can be chosen.

Last but not least, JautOGL is a multiplayer game, and is based on the client-server model. The new I/O interface of Java offers a non-blocking mode in the communication, which is based on channels and the UDP protocol for fast datagram packet transfers.

Architecture and Analytical Description

JautOGL consists of 22 classes and approximately 5000 lines of code. The classes are developed to serve the supported functions that are described above by using the object-oriented technology of Java, the JOGL and JOAL APIs for graphics and sound respectively, and the new I/O interface for the networking.

The architecture of JautOGL can be expressed by its UML class diagram, which reveals the connections, relationships, attributes, and operations of the JautOGL classes. The attributes also include the objects for two reasons. Firstly, since string is an object of the class String and not a primitive type, I have included it in my UML diagram as an attribute. Secondly, the objects support and explain the relationships, which are depicted by the different types of arrows according to UML specification. Figure 5 illustrates the UML class diagram of JautOGL.

Figure 5
Figure 5. UML class diagram of JautOGL. (Click image for full-size screen shot)

The classes and the functions of JautOGL are based on some programming features of the technologies that are used for the development. Focusing on the graphics and the display of the game, there are issues which are related to 3D model loading, full screen exclusive mode, and others regarding embedding and using the procedural OpenGL in the Java object-oriented programming language. OpenglCore implements a GLEventListener and has two important methods. The init() is executed only once, and does initial calculations, whereas the display() represents the main state/thread of the application, with each execution resulting in a frame for display. ScreenManager creates an instance of GLCanvas, which will embed the content of the OpenglCore instance.

A simplified version of the OpenglCore, which creates the scene and the graphical content, is illustrated below:

import java.awt.*;

public class OpenglCore implements GLEventListener{
   //the angle of the two cars in space
   float movement_angle;
   float movement_angle2;

   protected TextureManager texture_manager;
   protected GLModel model1;
   protected GLModel model2;

   //camera coordinates
   double x_eye;
   double y_eye;
   double z_eye;
   double x_at;
   double y_at;
   double z_at;
   double x_up;
   double y_up;
   double z_up;

   //car 1 coordinates
   float car1_x;
   float car1_y;
   float car1_z;

   //car 2 coordinates
   float car2_x;
   float car2_y;
   float car2_z;

   //distance between camera and car
   float dist_cam_car;

   //OpenGL scene initialisation
   public void init(GLDrawable drawable) {





       GL gl = drawable.getGL();
       GLU glu = drawable.getGLU();


       double w = ((Component) drawable).getWidth();
       double h =  ((Component) drawable).getHeight();
       double aspect = w/h;

       //perspective view with the right aspect
       glu.gluPerspective(60.0, aspect, 1.0, 10.0);

       texture_manager = TextureManager.getInstance( gl, glu );
       gl.glEnable( GL.GL_TEXTURE_2D );
       gl.glShadeModel( GL.GL_SMOOTH );

       //the paths of the 3D models
       String path1 = "models/formula.obj";
       String path2 = "models/formula.obj";

       //here the needed textures for the race-ground are loaded
                       ("road", "textures/roads/road.jpg",
                        GL.GL_TEXTURE_2D, GL.GL_RGB, GL.GL_RGB, GL.GL_LINEAR,
                        GL.GL_LINEAR, true, true );
                       // ...
                       //in the same way we load each texture is needed...

               //a file input stream reads the data and stores them in
               //a buffer reader for each 3D model
               FileInputStream r_path1 = new FileInputStream(path1);
               BufferedReader b_read1 =
                    new BufferedReader(new InputStreamReader(r_path1));
               model1 = new GLModel(b_read1, true, "models/formula.mtl", gl);

               FileInputStream r_path2 = new FileInputStream(path2);
               BufferedReader b_read2 =
                    new BufferedReader(new InputStreamReader(r_path2));
               model2 = new GLModel(b_read2, true, "models/formula.mtl", gl);
       catch( Exception e ){
               System.out.println("LOADING ERROR");

   public void display(GLDrawable drawable) {

           GL gl = drawable.getGL();
           GLU glu = drawable.getGLU();
           GLUT glut = new GLUT();

           //important to increase the view of the driver
           //alternative command
           //gl.glFrustum(-0.875, 0.875, -0.7, 0.7, 1.0, 50.0);


           gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

           //the position of the camera
                   x_eye, y_eye, z_eye,
                   x_at, y_at, z_at,
                   x_up, y_up, z_up

           float position[] = {0f, 15f, -30f, 0f};
           gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, position);

           float diffuse[] = {.7f, .7f, .7f, 0f};
           gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);

           float ambient[] = {.6f, .6f, .6f, 0f};
           gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, ambient);




           gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

           //it draws a race-ground


           //*********positioning the car models*********
           //***car 1***
           gl.glTranslatef(car1_x, car1_y, car1_z);
           gl.glScalef(0.0005F, 0.0005F, 0.0005F); //TOO BIG
           gl.glRotatef(-90.0f, 0.0F, 1.0F, 0.0F);

           //draws the model

           gl.glTranslated(car2_x, car2_y, car2_z);
           gl.glScalef(0.0005F, 0.0005F, 0.0005F); //TOO BIG
           gl.glRotatef(-90.0f, 0.0F, 1.0F, 0.0F);

           //draws the model

   public void reshape(GLDrawable drawable, int x, int y, int width, int height) {
   public void displayChanged(GLDrawable drawable,
                        boolean modeChanged, boolean deviceChanged) {
ScreenManager is responsible for the screen display. It contains the setFullScreen() method, which creates the OpenGL canvas with the graphical content of the OpenglCore instance. The canvas is added to a JFrame instance. Furthermore, this method starts the animation by using the methods of FPSnimator, which extends the Animator class. It fixes a bug in NVidia's drivers (80174) which results in prevention of multithreaded access to the context.

public void setFullScreen(DisplayMode displayMode) {
   GLCapabilities glcaps = new GLCapabilities();
   GLCanvas glcanvas = GLDrawableFactory.getFactory().createGLCanvas(glcaps);
   glcanvas.addGLEventListener(new OpenglCore());
   animator=new FPSAnimator(glcanvas,70);
   final JFrame frame = new JFrame();
   //options for full screen mode
   SwingUtilities.invokeLater(new Runnable(){
       public void run(){
   if (displayMode!=null&&device.isDisplayChangeSupported()){
       try {
       catch (IllegalArgumentException ex) {
       //adaption for mac
   // in order to avoid potential deadlock in 1.4.1_02
   try {
       EventQueue.invokeAndWait(new Runnable() {
           public void run() {
   catch (InterruptedException ex) {
   catch (InvocationTargetException  ex) {

Another important issue related to the screen display is the definition of the display mode. This includes the screen resolution, the color bit depth, and the refresh rate of the game. Depending on the approach followed, the programmer can choose the settings, or let the gamer choose through a graphical interface, or even develop code to automatically choose the best compatible display mode according to the graphics device. Some code that can support the last approach is illustrated below:

public DisplayMode findFirstCompatibleMode(DisplayMode userModes[]){
   DisplayMode supportedModes[] = device.getDisplayModes();
   for (int i = 0; i < userModes.length; i++) {
       for (int j = 0; j < supportedModes.length; j++) {
           if (displayModesMatch(userModes[i], supportedModes[j])) {
               return userModes[i];
   return null;
public boolean displayModesMatch(DisplayMode mode1,DisplayMode mode2){
   if (mode1.getWidth()!=mode2.getWidth() ||
       return false;
   if (mode1.getBitDepth()!=DisplayMode.BIT_DEPTH_MULTI&&
       mode2.getBitDepth()!= DisplayMode.BIT_DEPTH_MULTI&&
       mode1.getBitDepth()!= mode2.getBitDepth())
       return false;
   if (mode1.getRefreshRate()!=
       mode1.getRefreshRate()!= mode2.getRefreshRate())
        return false;
   return true;

The JautOGL game engine includes parsers that load the 3D models by reading the information of the Wavefront format files. There are two classes responsible for this game element: GLModel and MtlLoader. Each one loads the information from the .obj and .mtl files, respectively. The files are read line by line and the parser looks for an identifier at the beginning of each line. For example, the format v x y z loads a vertex at the (x,y,z) coordinates of the 3D environment, while vn x y z loads a vertex normal at the (x,y,z). The .mtl files contain information about the ambient, diffuse, and specular lighting. JautOGL loads each one in an ArrayList or in multidimensional arrays. The splitting and the identification of the these strings relies on the StringTokenizer class and the methods trim(), startsWith(), and charAt(). As soon as JautOGL has stored all the information in array lists, it must then be transferred to the OpenGL pipeline. The information is loaded by using display lists (glGenLists()), which are an efficient and flexible way to retrieve such data. An example of simplified code of the class GLModel follows:

private void loadobject(BufferedReader br){
   String newline;
   while((newline = br.readLine()) != null){
       if(newline.length() > 0){
               //we initially trim the line
               newline = newline.trim();

               //reading the identifiers and loding the information
               if(newline.startsWith("v ")) { //vertex
                   float coords[] = new float[4];
                   String coordstext[] = new String[4];
                   newline = newline.substring(2, newline.length());
                   StringTokenizer st = new StringTokenizer(newline, " ");
                   for(int i = 0; st.hasMoreTokens(); i++)
                           coords[i] = Float.parseFloat(st.nextToken());
               //just the same for every occasion
                   if (newline.startsWith("vt")){//vertex texture
                       if(newline.startsWith("vn")){//vertex normals
                           if(newline.startsWith("f ")) { //face
                               newline = newline.substring(2, newline.length());
                               StringTokenizer st =
                                    new StringTokenizer(newline, " ");
                               int count = st.countTokens();
                               int v[] = new int[count];
                               int vt[] = new int[count];
                               int vn[] = new int[count];
                               for(int i = 0; i < count; i++) {
                                   char chars[] = st.nextToken().toCharArray();
                                   StringBuffer sb = new StringBuffer();
                                   char lc = 'x';
                                   for(int k=0; k < chars.length; k++){
                                       if(chars[k]=='/'&& lc=='/'){
                                       lc = chars[k];
                                   StringTokenizer st2 =
                                        new StringTokenizer(sb.toString(), "/");
                                   int num = st2.countTokens();
                                   v[i] = Integer.parseInt(st2.nextToken());
                                   if(num > 1)
                                           vt[i] = Integer.parseInt(st2.nextToken());
                                           vt[i] = 0;
                                   if(num > 2)
                                           vn[i] = Integer.parseInt(st2.nextToken());
                                           vn[i] = 0;
                               if (newline.charAt(0)=='m'&&
                                   newline.charAt(5)=='b'){ //materials
                                       String[] coordstext = new String[3];
                                       coordstext = newline.split("\\s+");
                                               loadmaterials(); //a method
                                               // responsible for this action
                                       if (newline.charAt(0)=='u'&&
                                           newline.charAt(5) == 'l'){
                                               String[] coords = new String[2];
                                               String[] coordstext = new String[3];
                                               coordstext = newline.split("\\s+");
                                               coords[0] = coordstext[1];
                                               coords[1] = facecounter + "";

Interactivity in JautOGL is based on the GameInteractivity and InputManager classes. The first one maps possible events. The second gives the ability to the programmer to create interactivity patterns. This means that players could configure the buttons of their preference through a graphical interface. JautOGL uses these classes to define buttons for the car driving, the camera view changes, and the exit action:

ScreenManager screen=new ScreenManager();
Window window=screen.getFullScreenWindow();   
InputManager inputManager=new InputManager(window);

GameInteractivity accelerator=new GameInteractivity("accelerator");
inputManager.mapToKey(accelerator, KeyEvent.VK_UP);

GameInteractivity stopSounds=
    new GameInteractivity("stopSounds",GameInteractivity.DETECT_INITIAL_PRESS_ONLY);
inputManager.mapToKey(stopSounds, KeyEvent.VK_Z);

GameInteractivity back=new GameInteractivity("back");
inputManager.mapToKey(back, KeyEvent.VK_DOWN);

GameInteractivity turn_left=new GameInteractivity("turn_left");
inputManager.mapToKey(turn_left, KeyEvent.VK_LEFT);

GameInteractivity turn_right=new GameInteractivity("turn_right");
inputManager.mapToKey(turn_right, KeyEvent.VK_RIGHT);

GameInteractivity camera_change=
    new GameInteractivity("camera_change", GameInteractivity.DETECT_INITIAL_PRESS_ONLY);
inputManager.mapToKey(camera_change, KeyEvent.VK_F1);

GameInteractivity exit=
    new GameInteractivity("exit", GameInteractivity.DETECT_INITIAL_PRESS_ONLY);
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);

Then the main game thread checks for interactivity events coming from the player:

if(accelerator.isPressed()&&turn_left.isPressed()){//car is turning left
   //define an angle for the turning
   //calculation of the new positions
   //NOTE: It is better for higher efficiency to avoid trigonometric methods
   //movements maps is a better solution

The sound part of the engine, the class OpenalCore, defines the characteristics of the listener and the sound sources according to OpenAL philosophy on 3D sound. These include position, orientation, and velocity. The sounds are loaded in byte buffers. Their size is limited by JVM and this results in a problem when loading large sound files (such as music tracks). The problem can be solved either by using data streams, which store limited data in byte buffers, or by configuring the limit of JVM. The first way is suggested.

The multiplayer mode of JautOGL is based on the client-server model and the UDP protocol for fast data transfers. The NIOClient and NIOServer classes form the networking part of the engine. New I/0 interface offers non-blocking communication, which is fundamental in realtime applications and fast-action games like JautOGL. Reading and writing can take place by using only one thread. Generally, racing games need fast transfers, and game servers of such demanding games (high bandwidth, low latency) run the risk of being unable to respond to requests. The client and server communicate over channels that transfer data by using the read() and write() methods. The mechanism that controls reading and writing data over non-blocking mode is in Selector and SelectionKey. At the beginning, the server accepts one datagram packet/identifier from every player/client in order to save their address and establish the communication. These packets are sent when the init() method of the OpenglCore class is executed. Then the server is ready to do its main task by entering the main loop of execution.

A simplified version of the server follows:

import java.nio.*;
import java.nio.channels.*;

public class NIOServer {
   public final static int DEFAULT_PORT = 3011;
   public final static int MAX_PACKET_SIZE = 256;

   public static void main(String[] args) {
       int port = DEFAULT_PORT;
       try {
           DatagramChannel channel =;
           DatagramSocket socket = channel.socket();
           SocketAddress address = new InetSocketAddress(port);
           ByteBuffer buffer = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);

           //This simplified version accepts 2 clients-players
           SocketAddress client1 = null;
           SocketAddress client2 = null;

           //get the addresses-initialisation phase
                   client1 = channel.receive(buffer);
                   client2 = channel.receive(buffer);
           //server now knows the players

           //MAIN LOOP-server is working for the players
           while (true) {
                   SocketAddress client = channel.receive(buffer);
                   //check the source of the datagram
                           //to update the other player
                           channel.send(buffer, client2);
                           //to update the other player
                           channel.send(buffer, client1);
       catch (IOException ex) {

The client defines send() and receive() methods for the communication. A simplified version is illustrated below:

import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class NIOClient{
       public  final static int DEFAULT_PORT = 3011;
       int port = DEFAULT_PORT;
       SocketAddress remote;
       DatagramChannel channel;
       Selector selector;
       ByteBuffer buffer1;
       ByteBuffer buffer2;

       public NIOClient(){
               remote = new InetSocketAddress("localhost", port);
               channel =;
               selector =;
               channel.register(selector, SelectionKey.OP_READ |    
               buffer1 = ByteBuffer.allocate(4);
               buffer2 = ByteBuffer.allocate(4);
           catch (IOException ex) {

       public void send(int command){
           try {
               Set readyKeys = selector.selectedKeys();
               Iterator iterator = readyKeys.iterator();
               if (iterator.hasNext()) {
                       SelectionKey key = (SelectionKey);
                       if (key.isWritable()) {
           catch (IOException ex) {
       public int receive(){
           int r=0;
           try {
               Set readyKeys2 = selector.selectedKeys();
               Iterator iterator2 = readyKeys2.iterator();
               if (iterator2.hasNext()) {
                   SelectionKey key2 = (SelectionKey);
                   if (key2.isReadable()) {
                       int command = buffer2.getInt();
           catch (IOException ex) {
           return r;

There are many aspects of the game that can be discussed and analyzed further. The code, together with the UML class diagram, can reveal the correlations between the different elements of the game.


This article has described the development of a 3D multiplayer racing game developed in Java using the new I/O interface for networking, along with the JOGL and JOAL APIs. JautOGL's technologies seem to make an interesting solution for games development. Although there is difficulty in object-oriented expression between Java and the procedural OpenGL and OpenAL, well written applications and deep knowledge of the technologies can overcome the difficulties. JautOGL can be improved and integrated. Some ideas include a better sound engine with Doppler effect simulation (which OpenAL supports), more interactivity systems support, a collision detection system, artificial intelligence, a game protocol for better networking management, and other functions. Java is already the main technology in mobile computing, and with the extension in game consoles, Java can be evolved to become the leading technology in the game programming industry.


I would like to thank Dr. Nikitas M. Sgouros, who was my supervisor during my undergraduate studies in University of Piraeus, Department of Technology Education and Digital Systems, for his invaluable advice and support.


Some relevant books:

Java NIO
OpenGL Superbible, Third Edition
Killer Game Programming in Java
Java Network Programming
Advanced Graphics Programming Using OpenGL
Learning Java Bindings for OpenGL (JOGL)
Developing Games in Java
Core Techniques and Algorithms in Game Programming

Other references:

C++-Java benchmarking
Phantom Console
NIO Tutorial
JOAL and the Doppler effect
OpenAL documentation
FSEM definition
FPSAnimator class
Wavefront format specification


width="1" height="1" border="0" alt=" " />
Evangelos Pournaras is a student at the University of Surrey in the United Kingdom, doing Msc in Internet Computing.
Related Topics >> Programming   |