2002-08-24 12:34:40 -04:00
|
|
|
<html>
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<meta http-equiv="Content-Language" content="en-gb">
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
|
|
|
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
|
|
|
|
<meta name="ProgId" content="FrontPage.Editor.Document">
|
|
|
|
<title>4</title>
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
<p>4.0 Skeleton Game Code</p>
|
|
|
|
<p>So now you want to know how to just get stuck in and start writing games.
|
|
|
|
First you need to understand a couple of game programming design patterns and
|
|
|
|
why they'll make your life easier; and then you'll want to fill in the gaps in a
|
|
|
|
bit of skeleton code that we're going to discuss below.</p>
|
|
|
|
<p>4.1 Design Decisions</p>
|
|
|
|
<p>4.1.1 Static Game Class</p>
|
|
|
|
<p>How many instances of your game do you expect to be running in a JVM? One,
|
|
|
|
and only one. Given this, you may feel the urge to implement the singleton
|
|
|
|
pattern on your game, where your game class holds a single instance of itself
|
|
|
|
and a method to retrieve that instance, something like:</p>
|
|
|
|
<p style="background-color: #FFFFCC; border-style: solid; border-width: 1; padding: 3"><code>public
|
|
|
|
class Game {</code><code><br>
|
|
|
|
<br>
|
|
|
|
/** The single instance of the Game */<br>
|
|
|
|
private final Game game = new Game();<br>
|
|
|
|
<br>
|
|
|
|
/** Don't allow construction from anywhere else */<br>
|
|
|
|
private Game() {</code><code><br>
|
|
|
|
}<br>
|
|
|
|
</code><code><br>
|
|
|
|
/** @return the game instance */<br>
|
|
|
|
public static Game getGame() {<br>
|
|
|
|
return game;</code><code><br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/** Start a new game */<br>
|
|
|
|
public void newGame() {<br>
|
|
|
|
// etc<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/** The main game loop */<br>
|
|
|
|
public void mainLoop() {<br>
|
|
|
|
// etc<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
// etc<br>
|
|
|
|
}</code></p>
|
|
|
|
<p>However this does result in code all over your application calling
|
|
|
|
Game.getGame().someMethod() - and to what end? It might <i>look</i> like
|
|
|
|
object-oriented code but in reality you're just making things harder for
|
|
|
|
yourself. It's far more intuitive to write your class purely statically, like
|
|
|
|
this:</p>
|
|
|
|
<p style="background-color: #FFFFCC; border-style: solid; border-width: 1; padding: 3"><code>public
|
|
|
|
class Game {</code><code><br>
|
|
|
|
<br>
|
|
|
|
/** Don't allow construction from anywhere else */<br>
|
|
|
|
private Game() {</code><code><br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/** Start a new game */<br>
|
|
|
|
public static void newGame() {<br>
|
|
|
|
// etc<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/** The main game loop */<br>
|
|
|
|
public static void mainLoop() {<br>
|
|
|
|
// etc<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
// etc<br>
|
|
|
|
}</code></p>
|
|
|
|
<p>Now you can just call your methods directly on the Game class. We're going to
|
|
|
|
use this pattern in the forthcoming skeleton game code.</p>
|
|
|
|
<p>4.1.2 About Threads and Timeslicing</p>
|
|
|
|
<p>There is a tendency for Java programs to be heavily multithreaded, simply
|
|
|
|
because you <i>can</i> in Java, whereas it is exceedingly difficult to do
|
|
|
|
properly in C++. However, you are likely to fall into a number of pitfalls in
|
|
|
|
game programming above and beyond those experienced in general multithreaded
|
|
|
|
programming:</p>
|
|
|
|
<p>Firstly, threads were designed a long time ago for a specific purpose, and
|
|
|
|
that purpose is keeping the processor busy whilst some slow I/O operation is
|
|
|
|
being carried out. How many slow I/O operations are you going to do in your
|
|
|
|
game? Apart from loading the graphics, sounds, and level data at the start -
|
|
|
|
none (unless you're doing networking but that's a whole other issue).</p>
|
|
|
|
<p>What does this mean for you? Well, in normal Java, programmers will tend to
|
|
|
|
set off a number of threads and expect them to more or less behave like <i>time
|
|
|
|
slices</i> rather than <i>threads.</i> Time slices work in a rather different
|
|
|
|
way to threads: each is executed, in turn, for a finite amount of time. You can
|
|
|
|
more or less guarantee that a slice will execute for its allotted time, and that
|
|
|
|
all slices will get a look-in.</p>
|
|
|
|
<p>Threads don't work like this. To put it bluntly, you can't <i>accurately</i>
|
|
|
|
predict when a thread is going to execute (or if it will ever be executed), or
|
|
|
|
even how long it will execute for. Therefore you should not be using threads to
|
|
|
|
do stuff that you expect to occur in a timely fashion. This includes <i>collision
|
|
|
|
detection, animation, sound playback</i> and <i>device input, </i>and the
|
|
|
|
ultimate blow, <i>event handling.</i></p>
|
|
|
|
<p>In the absence of threads to do this useful stuff, we have to resort to a <i>single-threaded
|
|
|
|
model</i> where the game effectively runs in one, very busy, thread - the
|
|
|
|
"main loop". This is quite convenient, however, because it highlights
|
|
|
|
another problem with multithreaded game code that is not immediately obvious.</p>
|
|
|
|
<p>4.1.3 About Threads and Hardware</p>
|
|
|
|
<p>Your machine's <i>hardware</i> is only ever in <i>one state at a time, </i>unless
|
|
|
|
it is some kind of quantum computer. The device drivers for the hardware -
|
|
|
|
namely OpenGL and the like - keep track of this state in order to send the
|
|
|
|
correct hardware setup commands to the device. Can you imagine what might happen
|
|
|
|
if one thread is trying to do some drawing at the same time that another thread
|
|
|
|
is trying to do some drawing? Yes, you get a mess. You will discover that in
|
|
|
|
order to draw in a thread in OpenGL that you need to associate the current
|
|
|
|
rendering thread with the current state of the hardware (the
|
|
|
|
"context"). Suddenly you need to synchronize every method which can
|
|
|
|
alter the hardware state. Suddenly your application looks <i>incredibly
|
|
|
|
complicated</i> and <i>runs like a dog!</i> So much for threads.</p>
|
|
|
|
<p>So be aware of this next time you think it's a good idea to load your
|
|
|
|
textures in a background thread, and remember that only one thread - the main
|
|
|
|
thread - is allowed to execute any commands on your hardware devices.</p>
|
|
|
|
<p>It is for this reason that we use <i>polling </i>to read input devices as
|
|
|
|
well. Not to mention the fact that we can't guarantee that an event loop will be
|
|
|
|
executed every game frame, and so our input will be erratic.</p>
|
|
|
|
<p>4.1.4 Object-orientation</p>
|
|
|
|
<p>Object-orientation is <i>good</i> when it's done right. It makes your code
|
|
|
|
much, much, easier to understand and work on. However you may be led astray by <i>parrots
|
|
|
|
who repeat mantras.</i> There are some programmers that say <i>everything should
|
|
|
|
be private</i> and you should expose your instance variables with getters and
|
|
|
|
setters. The Hotspot virtual machine is even cleverly optimised to make this
|
|
|
|
operation almost entirely free. <i>But wait! </i>Ask yourself why you're filling
|
|
|
|
your classes up with getters and setters <i>when a dot would do the same job
|
|
|
|
without any need to maintain it.</i></p>
|
|
|
|
<p>You're writing a game: the source code is probably only going to be used
|
|
|
|
once, or by a very few people. Most of your game code is so unique to your
|
|
|
|
actual game that it is <i>disposable code.</i> So save yourself some effort and
|
|
|
|
use a dot if you can. A critical mistake I have observed time and time again in
|
|
|
|
object-oriented projects is to get so bogged down trying to design a perfect
|
|
|
|
object-oriented model that the project takes absolutely ages to design. Before
|
|
|
|
you know it there are hundreds of getters and setters to maintain, and
|
|
|
|
interfaces all over the place to keep the reuse of your classes to a maximum -
|
|
|
|
when <i>all along the project design goal was not reuse but to get the bloody
|
|
|
|
thing finished on time and under budget!</i></p>
|
|
|
|
<p>Your mileage may vary...</p>
|
|
|
|
<p>4.2 Show Me The Money</p>
|
|
|
|
<p>Ok, ok, without further ado, here is a skeleton listing which you can use to
|
|
|
|
write a fullscreen Java game using LWJGL. Because we're games programmers, we
|
|
|
|
don't want to do a Hello World as we'd probably rather shoot it. But before we
|
|
|
|
can make bullets we must pay homage to the <i>rotating square!</i></p>
|
2002-08-25 06:04:09 -04:00
|
|
|
<p style="background-color: #FFFFCC; border-style: solid; border-width: 1; padding: 3"><code>import
|
|
|
|
org.lwjgl.*;<br>
|
|
|
|
import org.lwjgl.opengl.*;<br>
|
|
|
|
import org.lwjgl.input.*;<br>
|
|
|
|
<br>
|
|
|
|
<br>
|
|
|
|
public
|
2002-08-24 12:34:40 -04:00
|
|
|
final class Game {<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
static {<br>
|
|
|
|
try {<br>
|
|
|
|
DisplayMode[] modes = Display.getAvailableDisplayModes();<br>
|
|
|
|
System.out.println("Available display modes:");<br>
|
|
|
|
for (int i = 0; i < modes.length; i ++)<br>
|
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
System.out.println(modes[i]);<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
// For now let's just pick a mode we're certain to have<br>
|
|
|
|
Display.create(new DisplayMode(640, 480, 16, 60), true);<br>
|
|
|
|
System.out.println("Created display.");<br>
|
|
|
|
} catch (Exception e) {<br>
|
|
|
|
System.err.println("Failed to create display due to "+e);<br>
|
|
|
|
System.exit(1);<br>
|
|
|
|
}<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
public static final GL gl = new GL(16, 0, 16, 8);<br>
|
|
|
|
static {<br>
|
|
|
|
try {<br>
|
|
|
|
gl.create();<br>
|
|
|
|
System.out.println("Created OpenGL.");<br>
|
|
|
|
} catch (Exception e) {<br>
|
|
|
|
System.err.println("Failed to create OpenGL due to "+e);<br>
|
|
|
|
System.exit(1);<br>
|
|
|
|
}<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/** Is the game finished? */<br>
|
|
|
|
private static boolean finished;<br>
|
|
|
|
<br>
|
|
|
|
/** A rotating square! */<br>
|
|
|
|
private static float angle;<br>
|
|
|
|
<br>
|
|
|
|
/**<br>
|
|
|
|
* No construction allowed<br>
|
|
|
|
*/<br>
|
|
|
|
private Game() {<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
public static void main(String[] arguments) {<br>
|
|
|
|
try {<br>
|
|
|
|
init();<br>
|
|
|
|
while (!finished) {<br>
|
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
Keyboard.poll();<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
mainLoop();<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
render();<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
gl.swapBuffers();<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
}</code> <code><br>
|
|
|
|
} catch (Throwable t) {<br>
|
|
|
|
|
2002-08-24 12:34:40 -04:00
|
|
|
t.printStackTrace();<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
} finally {<br>
|
|
|
|
cleanup();<br>
|
|
|
|
}<br>
|
|
|
|
}<br>
|
2002-08-24 12:34:40 -04:00
|
|
|
<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
/**<br>
|
|
|
|
* All calculations are done in here<br>
|
|
|
|
*/<br>
|
|
|
|
private static void mainLoop() {<br>
|
|
|
|
angle += 1f;<br>
|
|
|
|
if (angle > 360.0f)<br>
|
|
|
|
angle = 0.0f;<br>
|
2002-08-24 12:34:40 -04:00
|
|
|
<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
Keyboard.poll();
|
2002-08-24 12:34:40 -04:00
|
|
|
<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))<br>
|
|
|
|
finished = true;<br>
|
|
|
|
}<br>
|
2002-08-24 12:34:40 -04:00
|
|
|
<br>
|
2002-08-25 06:04:09 -04:00
|
|
|
/**<br>
|
|
|
|
* All rendering is done in here<br>
|
|
|
|
*/<br>
|
|
|
|
private static void render() {<br>
|
|
|
|
gl.clear(GL.COLOR_BUFFER_BIT);<br>
|
|
|
|
gl.pushMatrix();<br>
|
|
|
|
gl.translatef(Display.getWidth() / 2, Display.getHeight() / 2, 0.0f);<br>
|
|
|
|
gl.rotatef(angle, 0, 0, 1.0f);<br>
|
|
|
|
gl.begin(GL.QUADS);<br>
|
|
|
|
gl.vertex2i(-50, -50);<br>
|
|
|
|
gl.vertex2i(50, -50);<br>
|
|
|
|
gl.vertex2i(50, 50);<br>
|
|
|
|
gl.vertex2i(-50, 50);<br>
|
|
|
|
gl.end();<br>
|
|
|
|
gl.popMatrix();<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/**<br>
|
|
|
|
* Initialize<br>
|
|
|
|
*/<br>
|
|
|
|
private static void init() throws Exception {<br>
|
|
|
|
Keyboard.create();<br>
|
|
|
|
// Go into orthographic projection mode.<br>
|
|
|
|
gl.matrixMode(GL.PROJECTION);<br>
|
|
|
|
gl.loadIdentity();<br>
|
|
|
|
glu.ortho2D(0, Display.getWidth(), 0, Display.getHeight());<br>
|
|
|
|
gl.matrixMode(GL.MODELVIEW);<br>
|
|
|
|
gl.loadIdentity();<br>
|
|
|
|
gl.viewport(0, 0, Display.getWidth(), Display.getHeight());<br>
|
|
|
|
// Fix the refresh rate to the display frequency.<br>
|
|
|
|
gl.wglSwapIntervalEXT(1);<br>
|
|
|
|
}<br>
|
|
|
|
<br>
|
|
|
|
/**<br>
|
|
|
|
* Cleanup<br>
|
|
|
|
*/<br>
|
|
|
|
private static void cleanup() {<br>
|
|
|
|
Keyboard.destroy();<br>
|
|
|
|
</code> <code>gl.destroy();<br>
|
|
|
|
Display.destroy();<br>
|
|
|
|
}<br>
|
2002-08-24 12:34:40 -04:00
|
|
|
}</code></p>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
</html>
|