First, ask yourself if you really need to run in an applet. There are a few restrictions when using applets, so your application may be better suited to java web start or a standalone java application. For example, max memory in an applet is somewhere between 64 and 96MB by default(this may change in future Java updates). The only way to increase that is to make the end user go into their java control panel and manually type in a java command line arg - not very likely to happen in most cases. Also, because applets are AWT based, you will be using jME's AWT/Swing support which means you have to use the AWT input system instead of jinput and your performance will be maybe a third or more impacted versus running in a regular opengl window.
Let's Begin! Writing the Applet
Writing your first Java applet that uses jMonkeyEngine is easy if you follow these few simple steps:
Step One
Like SimpleGame, jMonkeyEngine has a new simple-type base class for writing your first applet. This class is called com.jmex.awt.applet.SimpleJMEApplet. We're going to recreate the classic jme test “TestBoxColor” in an applet, so let's start off by extending that class and declaring a few variables we'll need in later steps:
public class AppletTestBoxColor extends SimpleJMEApplet { private TriMesh t; private Quaternion rotQuat; private float angle = 0; private Vector3f axis; }
What to note:
We're going to assume you are familiar with jME code in general, so there isn't too much to explain at this step. Extending SimpleJMEApplet gives us the familiar FPS/Stats text at the bottom of the applet. It also sets up a wireframe state, zbufferstate and basic lightstate with a single PointLight. You can enabled/disable the lights, wireframe, normals debugging, bounds debugging and so forth with the same keystrokes as SimpleGame derived apps.
Step Two
To populate our 3d scene, we extend the method simpleAppletSetup. Here's the code:
public void simpleAppletSetup() { getLightState().setEnabled(false); rotQuat = new Quaternion(); axis = new Vector3f(1, 1, 0.5f).normalizeLocal(); Vector3f max = new Vector3f(5, 5, 5); Vector3f min = new Vector3f(-5, -5, -5); t = new Box("Box", min, max); t.setModelBound(new BoundingBox()); t.updateModelBound(); t.setLocalTranslation(new Vector3f(0, 0, -15)); getRootNode().attachChild(t); t.setRandomColors(); TextureState ts = getRenderer().createTextureState(); ts.setEnabled(true); ts.setTexture(TextureManager.loadTexture( TestBoxColor.class.getClassLoader().getResource( "jmetest/data/images/Monkey.png"), Texture.MM_LINEAR, Texture.FM_LINEAR)); getRootNode().setRenderState(ts); }
What to note:
Unlike SimpleGame apps, you don't have direct access to many of the variables. Instead, use the provided getXXXX methods. Currently those are: getCamera, getRenderer, getRootNode, getFPSNode, getTimePerFrame, get/setInputHandler, getLightState and getWireframeState. So instead of using display.getRenderer().createXXXXState() you just use getRenderer().createXXXXState(). Everything else is pretty much the same.
Step Three
Well, we've got our box in the scene. Now we want it to rotate each frame. To do that, we override the method simpleAppletUpdate like so:
public void simpleAppletUpdate() { float tpf = getTimePerFrame(); if (tpf < 1) { angle = angle + (tpf * 25); if (angle > 360) { angle -= 360; } } rotQuat.fromAngleNormalAxis(angle * FastMath.DEG_TO_RAD, axis); t.setLocalRotation(rotQuat); }
What to note:
Nothing really odd going on here. Again note the usage of the get methods.
Step Four
And that's it, now you have an applet that shows off a colorful spinning cube. Now you just need to package it up and show it to the world.
Show it off! Deploying your Applet
Setting up the page
To deploy your applet on a webpage, you must first create the html page that it will appear on. This html page contains the standard applet tag that defines the code that will be run in the browser. For example, if we created an Applet called MyApplet you might load it in a page as:
<html> <body> <h1>MyApplet Page</h1> <center> <applet code="my.own.jar.package.MyApplet" archive="lwjgl_util_applet.jar,lwjgl.jar,jme.jar,natives.jar,jme-awt.jar,my.jar" width="800" height="600"> <param name="useAppletCanvasSize" value="true"/> </applet> </center> </body> </html>
This applet page defines the applet to run (MyApplet) in the namespace my.own.jar.package. The required jars are defined in the archive sections. This section lists are the jars required to run your Applet.
| Jars | ||
|---|---|---|
| lwjgl_util_applet.jar | Used for the LWJGLInstaller, a class that installs the proper library files during load. | |
| lwjgl.jar | LWJGL API used for low level OpenGL, Sound and Input Bindings. | |
| jme.jar | jME API used as the middle-ware game engine. | |
| natives.jar | This jar contains the native bindings to OpenGL, sound API and input systems. (this file can be found in the “lwjgl applettest” or “lwjgl-applet” download) | |
| jme-awt.jar | This jar contains JME applet support stuff and other AWT jME extensions. | |
| my.jar | This jar would contain the user created code. | |
These jars represent the minimum set-up to get a Applet up and running using jME. You may need to add jars to this resource as you use them. For example, if you add FMOD sound you'd add the lwjgl_fmod3.jar as well as jorbis and jogg jars.
If you want to run the example above, you need to add two jars from the jME test project:
| jmetest.jar | for the TestBoxColor class |
| jmetest-data-images.jar | this jar contains the image for the texture (Monkey.png) |
These jars must also live in the proper location that is being referenced in the html page. In our example above we would need to put all the referenced jars in the same directory as the html code.
The <param name=“useAppletCanvasSize” value=“true”/> tells the jME applet to create a glCanvas with a resolution equal to the specified applet size, if not specified the glCanvas of default (640×480) resolution will be created.
Signing
You might notice that when running some of the examples that a dialog appears stating “The application's digital signature is invalid. Do you want to run the application?” where it then lists the name of the publisher and gives the option to run or cancel. This is because the jars are self-signed. That is, the developer created his/her own certificates rather than purchasing a certificate from an authority. This will always appear when self-signing as a security measure. For this dialog to not appear, a valid certificate must be purchased. See Deployment Tutorial for information on how to self-sign.
For purchasing of a digital signature see such services as verisign. A dialog box will still appear for the user to accept, but this dialog will be much friendlier with a verified signature. I.e. users will be much less likely to be scared off.
Issues and Gotchas
Cleaning up on exit
If you have OpenGL (rendering) resources that need to be cleaned up properly on exit, these must be called from the same thread in which they were created (the OpenGL thread). In most cases, this thread is the AWTEventQueue thread. Therefore, if you have specific resources, such as a PBuffer from rendering to texture, you'll need to handle this yourself in the stop() method of the Applet.
The following example will show how to handle cleaning up of a PBuffer.
You'll want to overwrite the stop() method to tell the applet to clean up before the destroy.
public void stop() { status = STATUS_DESTROYING; long time = System.currentTimeMillis(); while (status != STATUS_DEAD && (System.currentTimeMillis() - time) < 5000) { // only keep waiting for 5 secs try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } super.stop(); }
You'll notice that you first want to set the status to STATUS_DESTROYING, this will tell the main render queue that we are now destroying objects. We will then ensure that things have time to clean up, waiting on the status to change to STATUS_DEAD. Once STATUS_DEAD occurs we can let the applet close normally. To prevent lock-up we put a 5 second timer on the wait, if the close takes longer than this, we have larger problems to resolve.
Of course, you'll also need to let the applet know when you've finished destroying things. Typically you would do that in either simpleAppletRender or simpleAppletUpdate. simpleAppletRender is generally safer since it happens at the end of a paint cycle. Here's an example:
public void simpleAppletRender() { if (status == STATUS_DESTROYING) { imposterNode.getTextureRenderer().cleanup(); status = STATUS_DEAD; } }
Threads and OpenGL
You can only call OpenGL from a single thread; the thread in which the OpenGL context was created. The thread that creates OpenGL is the AWTEventQueue. To insure that you are not calling OpenGL inappropriately, only use simpleAppletRender(), simpleAppletUpdate() and simpleAppletSetup() for any calls that might touch it.
Memory Usage
To reiterate the caution from the first section on this page, applets have a fixed amount of available memory that can not be increased by the programmer (unlike, for example, a webstart application where you can set your max memory size.) Thus, you are stuck with whatever the user has setup in their Java control panel - typically 64MB-96MB. So keep your object creation and retention as low as possible.
Next Steps
Now that you've got the basics down, open up the source code of SimpleJMEApplet and start poking around. Examine the automatic resizing, the repainting thread, the awt-based key and mouse input and the relationship to jme's awt canvas classes. Then try writing your next applet by extending Applet or JApplet and leave the SimpleGame crutch behind. :)
discuss this topic to forum
