lwjgl/doc/tutorial/skeleton_code.html

275 lines
15 KiB
HTML

<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>
&nbsp;&nbsp;&nbsp; <br>
&nbsp;&nbsp;&nbsp; /** The single instance of the Game */<br>
&nbsp;&nbsp;&nbsp; private final Game game = new Game();<br>
<br>
&nbsp;&nbsp;&nbsp; /** Don't allow construction from anywhere else */<br>
&nbsp;&nbsp;&nbsp; private Game() {</code><code><br>
&nbsp;&nbsp;&nbsp; }<br>
</code><code><br>
&nbsp;&nbsp;&nbsp; /** @return the game instance */<br>
&nbsp;&nbsp;&nbsp; public static Game getGame() {<br>
&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return game;</code><code><br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /** Start a new game */<br>
&nbsp;&nbsp;&nbsp; public void newGame() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /** The main game loop */<br>
&nbsp;&nbsp;&nbsp; public void mainLoop() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; // 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>
&nbsp;&nbsp;&nbsp; /** Don't allow construction from anywhere else */<br>
&nbsp;&nbsp;&nbsp; private Game() {</code><code><br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /** Start a new game */<br>
&nbsp;&nbsp;&nbsp; public static void newGame() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /** The main game loop */<br>
&nbsp;&nbsp;&nbsp; public static void mainLoop() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // etc<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; // 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
&quot;main loop&quot;. 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
&quot;context&quot;). 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>
<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
final class Game {<br>
&nbsp;&nbsp;&nbsp; static {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DisplayMode[] modes = Display.getAvailableDisplayModes();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Available display modes:&quot;);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; modes.length; i ++)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
System.out.println(modes[i]);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // For now let's just pick a mode we're certain to have<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Display.create(new DisplayMode(640, 480, 16, 60), true);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Created display.&quot;);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (Exception e) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.err.println(&quot;Failed to create display due to &quot;+e);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.exit(1);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; public static final GL gl = new GL(16, 0, 16, 8);<br>
&nbsp;&nbsp;&nbsp; static {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.create();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(&quot;Created OpenGL.&quot;);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (Exception e) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.err.println(&quot;Failed to create OpenGL due to &quot;+e);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.exit(1);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /** Is the game finished? */<br>
&nbsp;&nbsp;&nbsp; private static boolean finished;<br>
<br>
&nbsp;&nbsp;&nbsp; /** A rotating square! */<br>
&nbsp;&nbsp;&nbsp; private static float angle;<br>
<br>
&nbsp;&nbsp;&nbsp; /**<br>
&nbsp;&nbsp;&nbsp;&nbsp; * No construction allowed<br>
&nbsp;&nbsp;&nbsp;&nbsp; */<br>
&nbsp;&nbsp;&nbsp; private Game() {<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; public static void main(String[] arguments) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; init();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (!finished) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Keyboard.poll();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
mainLoop();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
render();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
gl.swapBuffers();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</code>&nbsp;&nbsp;&nbsp;<code><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (Throwable t) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
t.printStackTrace();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } finally {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cleanup();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /**<br>
&nbsp;&nbsp;&nbsp;&nbsp; * All calculations are done in here<br>
&nbsp;&nbsp;&nbsp;&nbsp; */<br>
&nbsp;&nbsp;&nbsp; private static void mainLoop() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; angle += 1f;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (angle &gt; 360.0f)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; angle = 0.0f;<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Keyboard.poll();
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finished = true;<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /**<br>
&nbsp;&nbsp;&nbsp;&nbsp; * All rendering is done in here<br>
&nbsp;&nbsp;&nbsp;&nbsp; */<br>
&nbsp;&nbsp;&nbsp; private static void render() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.clear(GL.COLOR_BUFFER_BIT);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.pushMatrix();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.translatef(Display.getWidth() / 2, Display.getHeight() / 2, 0.0f);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.rotatef(angle, 0, 0, 1.0f);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.begin(GL.QUADS);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.vertex2i(-50, -50);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.vertex2i(50, -50);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.vertex2i(50, 50);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.vertex2i(-50, 50);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.end();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.popMatrix();<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /**<br>
&nbsp;&nbsp;&nbsp;&nbsp; * Initialize<br>
&nbsp;&nbsp;&nbsp;&nbsp; */<br>
&nbsp;&nbsp;&nbsp; private static void init() throws Exception {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Keyboard.create();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Go into orthographic projection mode.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.matrixMode(GL.PROJECTION);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.loadIdentity();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; glu.ortho2D(0, Display.getWidth(), 0, Display.getHeight());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.matrixMode(GL.MODELVIEW);<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.loadIdentity();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.viewport(0, 0, Display.getWidth(), Display.getHeight());<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Fix the refresh rate to the display frequency.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl.wglSwapIntervalEXT(1);<br>
&nbsp;&nbsp;&nbsp; }<br>
<br>
&nbsp;&nbsp;&nbsp; /**<br>
&nbsp;&nbsp;&nbsp;&nbsp; * Cleanup<br>
&nbsp;&nbsp;&nbsp;&nbsp; */<br>
&nbsp;&nbsp;&nbsp; private static void cleanup() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Keyboard.destroy();<br>
&nbsp;&nbsp;&nbsp;</code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <code>gl.destroy();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Display.destroy();<br>
&nbsp;&nbsp;&nbsp; }<br>
}</code></p>
</body>
</html>