www.CokeAndCode.com | Space Invaders 101 | 102 | 103 | 104 |
This is going to a relatively short tutorial for several reasons. LWJGL is very simple to work with and has some strong similarities to JOGL and hence the last tutorial.
The final game can be see here. The complete source for the tutorial can be found here. Its intended that you read through this tutorial with the source code at your side. The tutorial isn't going to cover every line of code but should give you enough to fully understand how it works.
Context hightlighted source is also available here:
Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.
This seems like a trivial point, especially when being illustrated by such a simplistic demo as this spaceinvaders game. However, LWJGL provides not just a graphics binding but a binding to OpenAL (an open standards sound system). In addition, LWJGL gives your access to input devices. This means your spaceinvaders game could be controlled from a gamepad and the aliens could squelch as they die, all from one library. Worth thinking about.
Adding a new rendering implementation for our SpaceInvaders game should be pretty simple (especially as in this case I didn't have to write the code, thanks Matzon). We're going to add 4 classes then plug then into our architecture.
Window.create(title, 100, 100, width, height, Display.getDepth(), 0, 8, 0, 0);Since there is only one window, the static call on the "Window" class makes sense. The next thing to accept is that there is only one OpenGL context at any given time. Hence we can also access this in a purely static way. These sort of assumptions are one of the things that make LWJGL simple to use.
As you can see from the following code, OpenGL calls are simply prefixed with the class "GL11":
public void startRendering() { try { Window.create(title, 100, 100, width, height, Display.getDepth(), 0, 8, 0, 0); // grab the mouse, dont want that hideous cursor when we're playing! Mouse.setGrabbed(true); // enable textures since we're going to use these for our sprites GL11.glEnable(GL11.GL_TEXTURE_2D); // disable the OpenGL depth test since we're rendering 2D graphics GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glOrtho(0, width, height, 0, -1, 1); textureLoader = new TextureLoader(); if(callback != null) { callback.initialise(); } } catch (LWJGLException le) { callback.windowClosed(); } gameLoop(); }So, we create our window, initialise OpenGL by setting our options and using glOrtho() to configure our view (for more on this see SpaceInvaders 103). Next we create a texture loader, which we will use to load our sprite images. Finally we notify the infrastructure that the initialization has been completed.
Also note the line "Mouse.setGrabbed(true);". As we'll see later, the mouse and keyboard are also monitored from static contexts. This line simple says that the mouse cursor shouldn't be displayed in the window.
private void gameLoop() { while (gameRunning) { // clear screen GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // let subsystem paint if (callback != null) { callback.frameRendering(); } // update window contents Window.update(); if(Window.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { gameRunning = false; Window.destroy(); callback.windowClosed(); } } }The final addition here is to check our exit cases. If the user has closed the window (Window.isCloseRequested()) or if the escape key has been pressed (Keyboard.isKeyDown(Keyboard.KEY_SCAPE)) then we need to notify the infrastructure and destroy the game window. In LWJGL we accomplish this by calling Window.destroy().
public boolean isKeyPressed(int keyCode) { // apparently, someone at decided not to use standard // keycode, so we have to map them over: switch(keyCode) { case KeyEvent.VK_SPACE: keyCode = Keyboard.KEY_SPACE; break; case KeyEvent.VK_LEFT: keyCode = Keyboard.KEY_LEFT; break; case KeyEvent.VK_RIGHT: keyCode = Keyboard.KEY_RIGHT; break; } return Keyboard.isKeyDown(keyCode); }The switch() here is a little confusing, why do we need to map VK_SPACE to KEY_SPACE and so on? LWJGL has stuck to the original key codes provided by "most" operating systems and since its a games oriented API this makes perfect sense. Java's AWT however uses its own set of key codes, which means we need a mapping between the two sets. As you can see, for the purposes of SpaceInvaders this is very simple.
public void draw(int x, int y) { // store the current model matrix GL11.glPushMatrix(); // bind to the appropriate texture for this sprite texture.bind(); // translate to the right location and prepare to draw GL11.glTranslatef(x, y, 0); GL11.glColor3f(1,1,1); // draw a quad textured to match the sprite GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex2f(0, 0); GL11.glTexCoord2f(0, texture.getHeight()); GL11.glVertex2f(0, height); GL11.glTexCoord2f(texture.getWidth(), texture.getHeight()); GL11.glVertex2f(width,height); GL11.glTexCoord2f(texture.getWidth(), 0); GL11.glVertex2f(width,0); } GL11.glEnd(); // restore the model view matrix to prevent contamination GL11.glPopMatrix(); }Note that the only significant change is the use of our GL context. In LWJGL instead of have a GL class implementation, we have static access to the GL11 class which provides us our OpenGL access.
public GameWindow getGameWindow() { // if we've yet to create the game window, create the appropriate one // now if (window == null) { switch (renderingType) { case JAVA2D: { window = new Java2DGameWindow(); break; } case OPENGL_JOGL: { window = new JoglGameWindow(); break; } case OPENGL_LWJGL: { window = new LWJGLGameWindow(); break; } } } return window; }All we had to do was add a new leg to the switch will return the appropriate game window for LWJGL. Finally, we need to make a similar change for sprites. Here we're going to create an LWJGLSprite if we're using LWJGL to render:
public Sprite getSprite(String ref) { if (window == null) { throw new RuntimeException("Attempt to retrieve sprite before game window was created"); } switch (renderingType) { case JAVA2D: { return Java2DSpriteStore.get().getSprite((Java2DGameWindow) window,ref); } case OPENGL_JOGL: { return new JoglSprite((JoglGameWindow) window,ref); } case OPENGL_LWJGL: { return new LWJGLSprite((LWJGLGameWindow) window,ref); } } throw new RuntimeException("Unknown rendering type: "+renderingType); }And thats it! As noted this is relatively short tutorial. This is partly because our existing infrastruture was designed specifically to support showing the changes in rendering layers. However, hopefully this tutorial has also shown has simple it is to use LWJGL to develop 2D games.