Java Monkey Engine with Eclipse Ganymede25 czerwca wydana została nowa wersja Eclipse o kosmicznej nazwie Ganymede. Mimo, że na co dzień używam czegoś zupełnie innego, muszę zobaczyć co u konkurencji słychać :D Dodatkowo z okazji wydania Ganymede fundacja Eclipse zorganizowała
Ganymede Around the World Contest. Ja z kolei od jakiegoś czasu bawię się Java Monkey Engine, wiec postanowiłem coś o nim napisać.
Na początek... co to jest JME?
To pełnoprawny (czyt. w pełni wypasiony :D) silnik do tworzenia gier, wspierający grafikę przestrzenną i dzwięk 3d. Ale jak to, w Javie? A tak! Oczywiście zgodnie z filozofią platformy, nie obeszło sie bez natywnych bibliotek, ale coś za coś, a tutaj 'cały komplet' cosiów jest dostarczony wraz z jme.
Zaczynamy: z
repozytorium sciągamy aktualną wersję JME2, kompilujemy (ant dist-all) i dodajemy do naszego projektu biblioteki. Istnieje też repozytorium jme na java.net, ale zawiera kod w starszej (1.x) wersji.
Ponieważ będzie to pierwsza aplikacja, nie będzie robiła niczego skomplikowanego - ot, zainicjalizujemy sobie wyświetlacz i pokażemy kilka obiektów.
Glówna klasa naszego programu oparta będzie o klasę com.jme.app.SimpleGame - dzięki temu nie będziemy musieli (tym razem!) wykonywać wielu czynności ręcznie - implementacji dostarczy nam właśnie klasa SimpleGame. Pozostaje ja tylko rozszerzyć :)
public class Demo extends com.jme.app.SimpleGame {
public static void main(String[] args) {
Demo demo = new Demo();
demo.start();
System.out.println("Done with the game");
}
@Override
protected void simpleInitGame() {
// TODO Auto-generated method stub
}
}
Z ciekawości i niecierpliwości spróbujemy uruchomić już teraz i... Niestety, nie działa.
Jul 16, 2008 12:35:47 AM com.jme.app.BaseGame start
INFO: Application started.
Jul 16, 2008 12:35:47 AM com.jme.system.PropertiesGameSettings <init>
INFO: PropertiesGameSettings created
Jul 16, 2008 12:35:47 AM com.jme.system.PropertiesGameSettings load
WARNING: Could not load properties. Creating a new one.
Jul 16, 2008 12:35:47 AM com.jme.app.BaseSimpleGame initSystem
INFO: jME version 2.0 dev build 1
Jul 16, 2008 12:35:47 AM com.jme.input.joystick.DummyJoystickInput <init>
INFO: Joystick support is disabled
Jul 16, 2008 12:35:47 AM com.jme.system.lwjgl.LWJGLDisplaySystem <init>
INFO: LWJGL Display System created.
Jul 16, 2008 12:35:47 AM class pl.ags.jme.Demo start()
SEVERE: Exception in game loop
java.lang.UnsatisfiedLinkError: no lwjgl in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1682)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1030)
at org.lwjgl.Sys$1.run(Sys.java:72)
at java.security.AccessController.doPrivileged(Native Method)
at org.lwjgl.Sys.doLoadLibrary(Sys.java:65)
at org.lwjgl.Sys.loadLibrary(Sys.java:81)
at org.lwjgl.Sys.<clinit>(Sys.java:98)
at org.lwjgl.opengl.Display.<clinit>(Display.java:128)
at com.jme.system.lwjgl.LWJGLDisplaySystem.getValidDisplayMode(Unknown Source)
at com.jme.system.lwjgl.LWJGLDisplaySystem.selectMode(Unknown Source)
at com.jme.system.lwjgl.LWJGLDisplaySystem.initDisplay(Unknown Source)
at com.jme.system.lwjgl.LWJGLDisplaySystem.createWindow(Unknown Source)
at com.jme.app.BaseSimpleGame.initSystem(Unknown Source)
at com.jme.app.BaseGame.start(Unknown Source)
at pl.ags.jme.Demo.main(Demo.java:10)
Jul 16, 2008 12:35:47 AM com.jme.app.BaseSimpleGame cleanup
INFO: Cleaning up resources.
Jul 16, 2008 12:35:47 AM com.jme.app.BaseGame start
INFO: Application ending.
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.lwjgl.opengl.Display
at com.jme.system.lwjgl.LWJGLDisplaySystem.close(Unknown Source)
at com.jme.app.BaseGame.quit(Unknown Source)
at com.jme.app.BaseSimpleGame.quit(Unknown Source)
at com.jme.app.BaseGame.start(Unknown Source)
at pl.ags.jme.Demo.main(Demo.java:10)
Wpadliśmy, w dodatku przez te 'nieszczęsne' bindingi. :) Na szczęście rozwiązanie problemu jest banalnie proste, wystarczy zadeklarować w parametrze java.library.path podać lokalizację bibliotek odpowiednich dla naszego systemu.
Np: 
-Djava.library.path=c:/workspace/ganymede/jmeDemo/lib/lwjgl/native/win32
Spróbujmy jeszcze raz: coś sie włącza, (dzwięku nie ruszaliśmy, więc nie buczy), mruga - Hurra! Great success! Ale: co się dzieje, black screen?! Chociaz nie, w prawym górnym rogu jest napisane "F4 - toggle stats". Czyli jednak działa :) Czas więc najwyższy dodać cokolwiek do naszej gry, zeby nie wyglądala jak tor dla pcheł. Zrobimy sobie bardzo biedny model jakiegoś układu słonecznego. Układ składał się będzie z planety i gwiazdy.
new Sphere("planet", 10, 10, 10);
tworzy nową kulę o nazwie "planet" w polożeniu (10, 10, 10). Żeby nie było statycznie (nudno), planeta będzie kręcić się wokół własnej osi, a gwiazda rosnąć :) Umiesćmy więc w metodzie simpleUpdate wywołania rotateSphere() i growSphere().
protected void simpleUpdate() {
rotateSphere(planet);
growSphere(star);
}
private void rotateSphere(Sphere sphere) {
angle = angle + (tpf * 1);
if (angle > 360)
angle = 0;
rotationQuat.fromAngleAxis(angle, axis);
sphere.setLocalRotation(rotationQuat);
}
private void growSphere(Sphere sphere) {
Vector3f scale = sphere.getLocalScale();
scale = scale.mult(1.0001f);
sphere.setLocalScale(scale);
}
1) 
demo.setConfigShowMode(AbstractGame.ConfigShowMode.AlwaysShow);
Metoda setCofnigShowMode ustala w jakich okolicznościach wyświetlone będzie okno z ustawieniami (wybiera sie rozdzielczość, głębię kolorów, tryb pełnoekranowy, renderer). My chcemy, zeby bylo widoczne za kazdym razem.
2) metoda simpleUpdate() jest wykonywana przy każdym przebiegu glównej pętli gry.

A tutaj cały kod naszej klasy (nie tej).public class Demo extends com.jme.app.SimpleGame {
private Quaternion rotationQuat = new Quaternion();
private float angle = 0;
private Vector3f axis = new Vector3f(1, 1, 0);
private Sphere planet;
private Sphere star;
public static void main(String[] args)
{
Demo demo = new Demo();
demo.setConfigShowMode(AbstractGame.ConfigShowMode.AlwaysShow);
demo.start();
}
protected void simpleUpdate()
{
rotateSphere(planet);
growSphere(star);
}
private void rotateSphere(Sphere sphere)
{
angle = angle + (tpf * 1);
if (angle > 360)
{
angle = 0;
}
rotationQuat.fromAngleAxis(angle, axis);
sphere.setLocalRotation(rotationQuat);
}
private void growSphere(Sphere sphere)
{
Vector3f scale = sphere.getLocalScale();
scale = scale.mult(1.0001f);
sphere.setLocalScale(scale);
}
@Override
protected void simpleInitGame()
{
display.setTitle("java malpa silnik");
planet = createEarth();
star = createSun();
rootNode.attachChild(star);
rootNode.attachChild(planet);
try
{
MultiFormatResourceLocator loc2 = new MultiFormatResourceLocator(
new File("c:/workspace/ganymede/jmeDemo/model").toURI(),
".jpg", ".png", ".tga");
ResourceLocatorTool.addResourceLocator(
ResourceLocatorTool.TYPE_TEXTURE, loc2);
}
catch (Exception e)
{
e.printStackTrace();
}
URL grass = ResourceLocatorTool.locateResource(
ResourceLocatorTool.TYPE_TEXTURE, "model/grass.jpg");
URL sun = ResourceLocatorTool.locateResource(
ResourceLocatorTool.TYPE_TEXTURE, "model/sun.jpg");
TextureState grassTexture = loadTexture(grass);
TextureState sunTexture = loadTexture(sun);
planet.setRenderState(grassTexture);
star.setRenderState(sunTexture);
}
private Sphere createEarth()
{
Sphere s = new Sphere("Earth", 5, 10, 15);
s.setLocalTranslation(new Vector3f(0, 0, -40));
s.setModelBound(new BoundingSphere());
s.updateModelBound();
return s;
}
private Sphere createSun()
{
Sphere s = new Sphere("Sun", 100, 100, 100);
s.setLocalTranslation(new Vector3f(0, 0, -500));
s.setModelBound(new BoundingSphere());
s.setLocalScale(2);
s.updateModelBound();
return s;
}
private TextureState loadTexture(URL u)
{
TextureState ts = display.getRenderer().createTextureState();
ts.setEnabled(true);
ts.setTexture(TextureManager.loadTexture(u,
Texture.MinificationFilter.BilinearNearestMipMap,
Texture.MagnificationFilter.Bilinear));
return ts;
}
}