Rough incomplete draft

This commit is contained in:
Caspian Rychlik-Prince 2002-08-24 16:34:40 +00:00
parent 918af28c2b
commit ba2877a75e
2 changed files with 436 additions and 0 deletions

157
doc/tutorial/index.html Normal file
View File

@ -0,0 +1,157 @@
<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>Lightweight Java Game Library Tutorial Index</title>
<style>
<!--
.document_title { text-align: Center; font-size: 24pt; letter-spacing: 4 }
.code_snippet { font-family: Courier New }
-->
</style>
</head>
<body>
<p class="document_title">Lightweight Java Game Library</p>
<p class="document_title">Tutorial</p>
<p align="center">Written by Caspian Rychlik-Prince</p>
<p align="center">Please direct comments, errata, and flames to the author at <a href="mailto:cix_foo@users.sourceforge.net">cix_foo@users.sourceforge.net</a></p>
<p align="center">Last modified <!--webbot bot="Timestamp" S-Type="EDITED"
S-Format="%d %B %Y" startspan -->23 August 2002<!--webbot bot="Timestamp" endspan i-checksum="31308" -->
</p>
<p>&nbsp;</p>
<p><a name="Introduction">1.0 Introduction</a></p>
<p>The Lightweight Java Game Library (LWJGL) is a solution aimed directly at
professional and amateur Java programmers alike to enable commercial quality
games to be written in Java. This tutorial is nonetheless aimed at <i>experienced
</i>programmers and won't be explaining some obvious techniques. Currently the
LWJGL supports only the Win32 platform, and JDK1.4.&nbsp;</p>
<p>LWJGL is not meant to make writing games particularly <i>easy;</i> it is
primarily an <i>enabling technology </i>which allows developers to get at
resources that are simply otherwise unavailable or poorly implemented on the
existing Java platform. We anticipate that the LWJGL will, through evolution and
extension, become the foundation for more complete game libraries and &quot;game
engines&quot; as they have popularly become known, and hide some of the new
evils we have had to expose in the APIs.</p>
<p>2.0 Contents</p>
<p>&nbsp;</p>
<p>3.0 Aims &amp; Design</p>
<p>Because the LWJGL API is not meant to be a fully featured game engine it has
been ruthlessly pruned of all non-essential code. Its ultimate philosophy is
that it provides the <i>bare minimum</i> of API functionality which will let a
game programmer produce games in Java without having to write native code in
order to get <i>performance </i>or <i>access some hardware feature not exposed
by Java 2</i>. We settled on using two other open technologies as our major
foundations, namely OpenGL and OpenAL for graphics and sound respectively.</p>
<p>A sub-requirement of the LWJGL is that it be freed Java programmers from the
requirement to ship a whole JRE with their games. Currently the Sun licensing
terms dictate that J2SE be shipped in its entirety, even for the tiniest of
demos. As this could easily triple the size of a demo and discourage end users
with configuration issues we have made it a primary concern that games written
using LWJGL can be compiled into completely standalone native binary executables
by compilers such as GNU's GCJ and Excelsior's JET. We have done this by
implementing the library in such a way that <i>no dependencies </i>on Sun's
proprietry JRE binaries are present in the library.</p>
<p>The final aim of LWJGL is to make the library available for Win32 systems
above all others, for that is what most commercial programmers need.</p>
<p>Linuxians and Macophiles do not despair! There's nothing inherently
non-portable about the LWJGL - we just don't have the time and expertise to do
it yet. But it will happen.</p>
<p>3.1 General API</p>
<p>The general API gives us the foundations of game programming: we have a
Display class, for initialising the display and querying its available modes; we
have a Math class to provide us with some floating point maths functions (rather
than the double-precision ones provided by Java), and matrix batch operations;
and a Sys class, which gives us our <i>most useful gaming functions:</i> the
ability to get the address of a direct byte buffer so we can cache it on the
Java side of the JNI barrier, and hence access all those lovely hardware calls
we need for performance; and the ability to use the system's <i>hires timer</i>,
which is so critical for animation timing. In addition we can also create a
direct byte buffer at <i>any address in memory.</i></p>
<p>The Evil Of Pointers And What It Means For Security</p>
<p>Yes, we have exposed <i>pointers</i> to Java programmers. Yes, you can write
to just about any bit of memory you so please, and cause untold havoc. You can
break things. You can bypass security constraints and exploit the dreaded <i>buffer
vuln.</i></p>
<p>But <i>why?</i> Because without pointers, all those nice easy native API
calls would suddenly become complex and behave slightly differently to their C
counterparts, and require us to pass direct ByteBuffers to JNI for every call.
This requires that every single call which takes a pointer calls the JNI method
GetDirectBufferAddress every time, which is an unnecessary overhead.</p>
<p>The implications for security are simple and final: your game can <b>no
longer be considered secure</b> and part of the Java security model. This puts
it in exactly the same boat as any other application on the user's system. This
also means you will not be able to use it in applets or with Webstart without
getting your code signed and trusted. LWJGL itself will not be signed nor
trusted; you are expected to deliver it bundled in with every application you
ship and verify that your entire distribution is safe.</p>
<p>We feel that our target developer, the commercial game developer, should not
be concerned with this issue as the status quo is merely maintained from the old
ways of programming with any other language; and used wisely, your exposure to
pointers is unlikely to cause you any problems.</p>
<p>If you are concerned about security, or wish to write games which will run as
applets or from Webstart, or would rather have a full game library which takes
care of things for you, you don't want to use LWJGL at all - it's that simple!
What you need is Sun's Java Gaming Profile, or Java3D. If you feel a need to
argue, you're using LWJGL for the wrong reasons.</p>
<p>3.2 Graphics</p>
<p>Graphics is based on the latest OpenGL1.4, and all the extensions we could
implement that might be vaguely useful for games programmers. These include <i>all</i>
of Nvidia's and ATI's proprietry extensions, and <i>all</i> the ARB extensions,
and most of the EXT extensions, as well as numerous other miscellaneous ones.</p>
<p>For Windows programmers, our primary target, the WGL extensions are present.</p>
<p>All OpenGL functions that take pointers are passed ints. These pointers can
be obtained from direct ByteBuffers using the Sys.getDirectBufferAddress()
method. There are a very few native methods that return pointers as ints as
well. Be sure to read the caveat about using pointers in Java!</p>
<p>3.3 Sounds</p>
<p>Sound is based on the latest OpenAL1.0 specification, which comes with but
one extension, EAX, for interesting environmental effects. The LWJGL binary
distribution includes the OpenAL .dlls for Win32.</p>
<p>3.4 Input</p>
<p>Input can be a complicated topic. A user can have all sorts of strange fancy
force-feedback hardware installed on their systems, with scrolly knobs and
twistgrips and bristling with many buttons. However, the vast majority of gamers
have just a keyboard and a mouse; some of them have analogue joysticks too, and
some of them have a gamepad attached from some console or other. For our first
cut of the input library we've just kept it all rather simple, and decided that
there is but one of each of these devices, and that force feedback and multiple
potentiometers is a feature we may add another time.</p>
<p>So in the interests of keeping things simple, the four input classes -
Keyboard, Mouse, Gamepad and Joystick - are all static, and can all be polled
once per game loop iteration to determine where they've moved since you last
looked and what buttons are down at the time. The Gamepad and Keyboard may
additionally support <i>buffering</i> which is a more reliable way of detecting
rapid changes of state which may occur rather more quickly than your framerate.</p>
<p>3.5 Maths</p>
<p>Java's maths performance leaves much to be desired, particularly with respect
to bulk operations for 3D rendering engines. The main problem is that the
existing maths libraries use double precision when single precision is entirely
adequate for most realtime games programming; and that no clever
processor-specific optimisations can be done because the Hotspot compiler is
simply not supplied enough semantic context to understand that it could use some
special SIMD instruction to achieve the effect you desire in a fraction of the
cycles. Furthermore, all maths in Java is done in Java - and once you've
computed the results you usually have to subsequently copy them into a buffer to
pass to a native rendering method in OpenGL anyway.</p>
<p>The Math class provides two <i>totally generic</i> vector operators for unary
and binary vector operations performed on direct ByteBuffers containing packed
floating point vectors. The idea is to set up the source(s) of the operations
and then perform a single call to JNI to perform a <i>highly optimised operation</i>
on the whole lot in one go. The JNI code is specially optimised for the common
cases in 3d games programming to use processor-specific instructions and take
advantage, where feasible, of memory caching architecture. And the end result is
placed directly back in memory, ready to simply send as a pointer direct to
OpenGL or some other API.</p>
<p>In addition we provide implementations of common Vector and Matrix sizes
similar to those provided by the javax.vecmath package, but ours are <i>open
source</i> and available without downloading the whole of Java3D.</p>
<p>&nbsp;</p>
</body>
</html>

View File

@ -0,0 +1,279 @@
<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>public
final class Game {<br>
static {<br>
try {<br>
DisplayMode[] modes = Display.getAvailableDisplayModes();<br>
System.out.println(&quot;Available display modes:&quot;);<br>
for (int i = 0; i &lt; modes.length; i ++)<br>
System.out.println(modes[i]);<br>
// 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(&quot;Created display.&quot;);<br>
} catch (Exception e) {<br>
System.err.println(&quot;Failed to create display due to &quot;+e);<br>
System.exit(1);<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(&quot;Created OpenGL.&quot;);<br>
} catch (Exception e) {<br>
System.err.println(&quot;Failed to create OpenGL due to &quot;+e);<br>
System.exit(1);<br>
}<br>
}<br>
/** A handy number formatter for use displaying FPS and the like */<br>
public static NumberFormat fmt = new DecimalFormat();<br>
static {<br>
fmt.setMaximumFractionDigits(1);<br>
fmt.setMinimumFractionDigits(1);<br>
fmt.setGroupingUsed(false);<br>
}<br>
/** Is the game finished? */<br>
private static boolean finished;<br>
/** A rotating square! */<br>
private static float angle;<br>
/**<br>
* No construction allowed<br>
*/<br>
private Game() {<br>
}<br>
public static void main(String[] arguments) {<br>
try {<br>
init();<br>
int frames = 0;<br>
float fps = 0.0f;<br>
Timer timer = new Timer();<br>
timer.reset();<br>
timer.resume();<br>
while (!finished) {<br>
AbsMouse.poll();<br>
Keyboard.poll();<br>
Keyboard.read();<br>
gl.clear(GL.COLOR_BUFFER_BIT);<br>
mainLoop();<br>
render();<br>
gl.swapBuffers();<br>
// Count the frames per second. Do with this what you will..<br>
frames++;<br>
float time = timer.getTime();<br>
if (time &gt;= 1.0f) {<br>
fps = (int) (frames / time);<br>
timer.reset();<br>
frames = 0;<br>
}<br>
}<br>
} catch (Throwable t) {<br>
t.printStackTrace();<br>
} finally {<br>
cleanup();<br>
}<br>
}<br>
<br>
/**<br>
* All calculations are done in here<br>
*/<br>
private static void mainLoop() {<br>
angle += 1f;<br>
if (angle &gt; 360.0f)<br>
angle = 0.0f;<br>
<br>
if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))<br>
finished = true;<br>
}<br>
<br>
/**<br>
* All rendering is done in here<br>
*/<br>
private static void render() {<br>
<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>
* Initialize<br>
*/<br>
private static void init() throws Exception {<br>
Keyboard.create();<br>
Keyboard.enableBuffer();<br>
Mouse.create();<br>
// Go into orthographic projection mode.<br>
gl.matrixMode(GL.PROJECTION);<br>
gl.loadIdentity();<br>
gl.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>
* Cleanup<br>
*/<br>
private static void cleanup() {<br>
Keyboard.destroy();<br>
Mouse.destroy();<br>
gl.destroy();<br>
Display.destroy();<br>
}<br>
}</code></p>
</body>
</html>