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 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) {}
Content still to be added…
Content still to be added…
Content still to be added…
Content still to be added…
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) {}
}
}
| Revision # | Comments |
|---|---|
| 26 |
Changed heaps
|
| 28 | Added network gaming implementation fundamentals. Added SocketEventListener.java, code to the ExecutionEngine to serialize the events and MagicCaveFrame implements the creation of a socket |
| 29 | Added:
|
| 31 | GirderFrame now uses procedurally generated maps. Needs optimisations - to check only for on-screen collisions in order to run at a reasonable speed. |
| 33 | Finally got applet and application version working in harmony :) |
| 34 | Added:
|