Making Arcade Games

Introduction

I am a hobbyist game maker. My programming experience is heavily based in web technologies; CMS, eCommerce, service-based architectures. I am by no means a game programming expert. But I have always loved arcade games, growing up in the heady '80s. So in developing my asynchronous event engine, I needed a regression test suite which provided quick, visual feedback. And what better way to get that than a 2D game engine. Fortunately (or perhaps unfortunately ;) the 2D game engine consumes most of work effort on the event engine.

I want to run through some basic design decisions and design patterns that I used in creating the game engine. The hope is that this will provide some insight for prospective game developers – and also hopefully be of some interest and educational value. I am going to go into some detail about an event-based 2D arcade engine; the basic mechanics, map generation, networked gaming and some light coverage of other considerations. The engine was never intended to be optimised and slick. It's more a way of demonstrating and hence testing the even-driven execution engine and data transformation layer. A project like this, created from first principles, also teaches you a lot about a broad range of programming techniques. But enough of the background in into the practical stuff…

The Basics

The first step is to set up a window, create an animation thread and process keyboard input. In this case, I am using a JFrame as the window class. This can be used in an applet or an application.

			public GameFrame() {
				readProperties() ;
        			createAndShowGUI() ;
    			}
	
			protected void readProperties() {
				try {
					properties = ResourceBundle.getBundle("GameEngine2D",Locale.getDefault()) ;
				} catch (Exception e) {
					e.printStackTrace() ;
				}
			}

			protected void createAndShowGUI() {
    				// Set up panel
    				panel = new GamePanel(game,bounds) ;
    				GameUtilities.setKeyboardMappings(keyMap) ;
    				// Set up game
    				setUpGame() ;
				// Set up frame
    				addKeyListener(this) ;
    				requestFocus() ;
				getContentPane().add("Center",panel) ;
				pack() ;
				setVisible(true) ;
    			}

			public void run() {
				Thread me = this.getGameThread() ;
				while (this.getGameThread()==me) {
					processInput() ;
					applyTime((new java.util.Date()).getTime()) ; /* Abstract method, will be overriden by subclass */
					panel.repaint() ;
					try {
						Thread.sleep(game.getREPAINT_DELAY()) ;
					} catch (InterruptedException e) {}
				}
			}
	
			public void processInput() {
				// Only show intro before the 1st key is pressed
				if (panel.getIntro()&&!keyActiveMap.isEmpty()) panel.setIntro(false) ;
				// Process all keys that are down
				for (Enumeration keys = keyActiveMap.keys(); keys.hasMoreElements();) {
					String[] commands = {} ;
					int key = ((Integer)keys.nextElement()).intValue() ;
					try {
						commands = GameUtilities.getValue(keyMap,key) ;
						GameUtilities.moveShape(game,commands[1],Integer.parseInt(commands[0])) ;
					} catch (Exception e) {
						e.printStackTrace() ;
					} 
				}
			}

			/* KeyListener impl. */
			public void keyPressed(KeyEvent e) {
				keyActiveMap.put(new Integer(e.getKeyCode()),new Boolean(true)) ;
			}
 			public void keyReleased(KeyEvent e) {
 				keyActiveMap.remove(new Integer(e.getKeyCode())) ;
 			}
 			public void keyTyped(KeyEvent e) {}
			

Procedural Map Generation

A-Star Path Finding AI

Event-driven Gaming

Content still to be added…

Best Prinicples: Separating the View from the Logic

Content still to be added…

Rudimentary Network Gaming

Content still to be added…

Work Breakdown: Parallel Job Execution Design and Optimisation

Content still to be added…

Developing 3D Graphics with Java OpenGL (JOGL)

Java OpenGL (JOGL) is a fast, efficient toolset for developing modern 3D games. I backs onto the C implementation of OpenGL. And in fact, the Java classes simply act as a wrapper for the C libraries (DLL in Wondoze or SO in LINUCKS).

   // Constructor to create profile, caps, drawable, animator, and initialize Frame
   public GLGame() {
	   // Get the default OpenGL profile that best reflect your running platform.
	   GLProfile glp = GLProfile.getDefault();
	   // Specifies a set of OpenGL capabilities, based on your profile.
	   GLCapabilities capabilities = new GLCapabilities(glp);
	   // Allocate a GLDrawable, based on your OpenGL capabilities.
	   GLCanvas canvas = new GLCanvas(capabilities);
	   canvas.setPreferredSize(new Dimension(CANVAS_WIDTH,CANVAS_HEIGHT));
	   canvas.addGLEventListener(this);
	   // Setting some OpenGL parameters.
           capabilities.setHardwareAccelerated(true);
           capabilities.setDoubleBuffered(true);
           // Try to enable 2x anti aliasing. It should be supported on most hardware.
           capabilities.setNumSamples(2);
           capabilities.setSampleBuffers(true);
      
	   // Create a animator that drives canvas' display() at 60 fps. 
	   animator = new FPSAnimator(canvas, FPS);
      
	   addWindowListener(new WindowAdapter() {     // For the close button
		   @Override
		   public void windowClosing(WindowEvent e) {
			   // Use a dedicate thread to run the stop() to ensure that the
			   // animator stops before program exits.
			   new Thread() {
				   @Override
				   public void run() {
					   animator.stop();
					   System.exit(0);
				   }
			   }.start();
		   }
	   });
	   canvas.addKeyListener(new KeyAdapter() {
		   public void keyPressed(KeyEvent e) {
			   keyActiveMap.put(new Integer(e.getKeyCode()),new Boolean(true)) ;
		   }
		   public void keyReleased(KeyEvent e) {
		 		keyActiveMap.remove(new Integer(e.getKeyCode())) ;
		 	}
	   });
	   
	   add(canvas);
	   // setContentPane(new GLGamePanel()) ;
	   pack();
	   canvas.setBackground(game.getBackgroundColour());
	   canvas.setFocusable(true);
	   canvas.requestFocus();
	   // Registering the canvas to the animator.
           animator.add(canvas);
	   animator.start();   // Start the animator
	   game.setAnimator(animator) ;
	   setTitle("3D Games Engine - Using JOGL2");
	   setVisible(true);
	   
           (new Thread(this)).start() ;
   }
   
   public static void main(String[] args) {
	   new GLGame();
   }
  
   /* *** OpenGL GLEventListener functions */
   @Override
   public void init(GLAutoDrawable drawable) {
	   // Your OpenGL codes to perform one-time initialization tasks such as setting up of lights and display lists.
	   GL2 gl = drawable.getGL().getGL2(); // Get the OpenGL graphics context
	   glu = GLU.createGLU(); // Get GL Utilities after the GL context created
	   
	   // Enable smooth shading, which blends colors nicely, and smoothes out lighting.
	   gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
	   // Set background color in RGBA. Alpha: 0 (transparent) 1 (opaque) 
	   gl.glClearColor(0.0f,0.0f,0.0f,1.0f);
	   // Setup the depth buffer and enable the depth testing
	   gl.glClearDepth(1.0f);         // clear z-buffer to the farthest
	   gl.glEnable(GL.GL_DEPTH_TEST); // enables depth testing
	   gl.glDepthFunc(GL.GL_LEQUAL);  // the type of depth test to do
	   // Do the best perspective correction
	   gl.glHint(GL2ES1.GL_PERSPECTIVE_CORRECTION_HINT,GL.GL_NICEST);
	      
	   this.gl = gl ;

	   // Create a display list for the blocks
	   blocksDisplayList = gl.glGenLists(1);
	   gl.glNewList(blocksDisplayList,GL2.GL_COMPILE);
	   for (int blockIndex = 0;blockIndex&blocks.size();blockIndex++) 
		   drawBlocks(gl,block,blocks.get(blockIndex),EDGE) ;
	   gl.glEndList();
   }

   @Override
   public void display(GLAutoDrawable drawable) {
	   // Your OpenGL graphic rendering codes for each refresh.
	   GL2 gl = drawable.getGL().getGL2(); // Get the OpenGL graphics context
	      
	   gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); // Clear the color and the depth buffers
	   gl.glLoadIdentity(); // Reset the view (x, y, z axes back to normal)
	   gl.glClearColor(0.0f,0.0f,0.0f,0.5f);

	   this.setCameraPosition((float)game.getObserverTransform().getTranslateX(),(float)game.getObserverTransform().getTranslateY(),10);
	   double[] block100 = {EDGE+1,1,-4+distance};
	   // glu.gluLookAt(x,y,z,0,0,0,0,1,0);
	   
	   // execute display list for drawing the blocks
	   gl.glCallList(blocksDisplayList);
	   gl.glCallList(nextBlocksDisplayList);
	   drawBlocks(gl,block,block100,EDGE) ;
	   // The "floor" block
	   drawBlocks(gl,floor,groundLocation,GROUND_SIZE) ;
	   drawSlantedBlocks(gl,floor,slantedGroundLocation,GROUND_SIZE) ;
	   /* display the player image */
	   // drawTexture(gl,piplup,player.getPosition()) ;
	   renderPlayer(gl,piplup,player.getPosition()) ;
	   drawLitSpheroid(gl) ;
	   drawCube(gl,intersection) ;
	   
	   // Hack to reset velocity - in the absence of friction
	   player.setVelocityX(0) ;
	   // player.setVelocityY(0) ;
	   player.setVelocityZ(0) ;
	          	   	   
       gl.glFlush() ;
   }
   @Override
   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
	   // Your OpenGL codes to set up the view port, projection mode and view volume. 
	   // Get the OpenGL graphics context
	   GL2 gl = drawable.getGL().getGL2();
	      
	   height = (height == 0) ? 1 : height;  // Prevent divide by zero
	   float aspect = (float)width/height; // Compute aspect ratio
	      
	   // Set view port to cover full screen 
	   gl.glViewport(0,0,width,height);
	      
	   // Set up the projection matrix - choose perspective view
	   gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);  
	   gl.glLoadIdentity();    // reset
	 
	   // Angle of view (fovy) is 45 degrees (in the up y-direction). Based on this canvas's aspect ratio. Clipping z-near is 0.1f and z-far is 100.0f.
	   glu.gluPerspective(65.0f,aspect,0.1f,1000.0f); // fovy, aspect, zNear, zFar
	   glu.gluLookAt(0,2,12,3,0,0,0.1,1,0);
	      
	   // Switch to the model-view transform
	   gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
	   gl.glLoadIdentity();    // reset
   }
   public void displayChanged(GLAutoDrawable canvas, boolean modeChanged, boolean deviceChanged) {
	   return;
   } 
   @Override
   public void dispose(GLAutoDrawable drawable) {
      // Hardly used.
   }
   
   /* *** Game Engine 2D Methods */
   public void run() {
	   Thread me = this.getGameThread() ;
	   while (this.getGameThread()==me) {
		   /* Process key interaction */
		   processInput() ;
		   applyTime((new java.util.Date()).getTime()) ;
		   try {
			   Thread.sleep(game.getREPAINT_DELAY()) ;
		   } catch (InterruptedException e) {}
	   }
   }
   

Examples





Controls

Revision History

Revision #Comments
26 Changed heaps
  • Added file output for events from the ExecutionEngine
  • Velocity in MagicCaveFrame
  • Worked on Applet version of the FrameEngine
  • Moving platforms
  • Changed assets
28Added network gaming implementation fundamentals. Added SocketEventListener.java, code to the ExecutionEngine to serialize the events and MagicCaveFrame implements the creation of a socket
29Added:
  • game selection menu
  • character hit player (character "sensitivity" attribute)
  • GirderFrame (game)
  • other things??
31GirderFrame now uses procedurally generated maps. Needs optimisations - to check only for on-screen collisions in order to run at a reasonable speed.
33Finally got applet and application version working in harmony :)
34Added:
  • You can't shoot yourself
  • MagicCaveFrame enemies now shoot straight rather than curveballs

⇑ go to top ⇑