Rough incomplete draft
This commit is contained in:
parent
918af28c2b
commit
ba2877a75e
|
@ -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> </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. </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 "game
|
||||
engines" 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> </p>
|
||||
<p>3.0 Aims & 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> </p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -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>
|
||||
<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>
|
||||
<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("Available display modes:");<br>
|
||||
for (int i = 0; i < 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("Created display.");<br>
|
||||
} catch (Exception e) {<br>
|
||||
System.err.println("Failed to create display due to "+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("Created OpenGL.");<br>
|
||||
} catch (Exception e) {<br>
|
||||
System.err.println("Failed to create OpenGL due to "+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 >= 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 > 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>
|
Loading…
Reference in New Issue