Con questo articolo introduciamo Jmonkey: un'interessante framework opnesource per la realizzazione di giochi 3D con il linguaggio Java. Il framework utilizza una licenza BSD, ed è liberamente utilizzabile per hobby, fini educazionali o commerciali. L'aspetto particolarmente interessante è quello di poter realizzare giochi non soltanto per piattaforme desktop, ma anche per il web (pensiamo alla crescente richiesta di giochi per le piattaforme "social", come facebook) o per i dispositivi Android.
JMonkeyEngine è un engine ad alte performance scritto in Java e che utilizza LWJGL per l'accesso ad OpenGL. LWJGL (Lightweight Java Game Library) è la libreria Java che permette agli sviluppatori l'accesso a librerie quali OpenGL (Open Graphics Library), OpenCL (Open Computing Language), OpenAL (Open Audio Library).
La libreria è disponibile per Windows, Linux e Mac. I requisiti hardware sono i seguenti:
Sistemi operativi | Mac OS X, Windows, Linux, or Solaris |
Memoria (JVM heap size) | > 40 MB + memory for assets |
CPU | 1 GHz |
Scheda grafica | ATI Radeon 9500, NVIDIA GeForce 5 FX, Intel GMA 4500, o superiori supporto OpenGL 2.0 (ultimo driver raccomandato) |
Java Development Kit | JDK 6 |
Vogliamo fornire un assaggio di questo motore 3D vedendo l'esecuzione di una applicazione base che visualizzerà una scena 3D di una piccola cittadina. Proviamo il tutto sotto Windows: scarichiamo ed installiamo la platform di sviluppo dall'url:
http://jmonkeyengine.org/downloads/Una volta completata l'installazione lanciamo l'ambiente.Dovremmo trovarci di fronte ad una schermata simile alla seguente:
(clic per ingrandire)

Il progetto Basic Game
É il momento di creare il primo progetto, che chiameremo per semplicità BasicGame:
A questo punto, nel package mygame creato automaticamente definiamo una classe con il nome HelloCollision
public class HelloCollision
extends SimpleApplication
implements ActionListener {
// ...
}
Iniziamo con l'aggiungere le seguenti variabili di istanza che utilizzeremo successivamente:
private BulletAppState bulletAppState;
private Spatial sceneModel;
private RigidBodyControl landscape;
private CharacterControl player;
private Vector3f walkDirection = new Vector3f();
private boolean left = false, right = false, up = false, down = false;
L'esempio fa uso di quella che viene chiamata game physics
(l'implementazione della fisica nel gioco, insomma); ne abbiamo bisogno per simulare massa, gravità, collisioni: pensiamo ad esempio ai fenomeni fisici nel gioco del biliardo o nei giochi che simulano corse di auto. Per utilizzarla abbiamo bisogno di un oggetto BulletAppState
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
definiamo all'interno della classe il metodo
public void simpleInitApp() {
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
// ...
}
e cominciamo con l'inserire al suo interno le righe di codice appena descritte. La variabile stateManager
SimpleApplication
Dopo aver inizializzato le funzionalità di game physics
public void simpleInitApp() {
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
flyCam.setMoveSpeed(100);
}
Anche viewPort
è ereditata dalla classe Application
flyCam
(anch'essa nella classe Application
estensione della Camera di default
SimpleApplication
flyCam
Interazione con l'utente
Per completare la configurazione del movimento all'interno dell'ambiente, definiamo il seguente metodo che invochiamo subito dopo l'istruzione relativa a flyCam
:
private void setUpKeys() {
inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addListener(this, "Left");
inputManager.addListener(this, "Right");
inputManager.addListener(this, "Up");
inputManager.addListener(this, "Down");
inputManager.addListener(this, "Jump");
}
L'interazione con l'utente avviene attraverso la tastiera, il mouse, il joystick, per gestire questi dispositivi si fa uso dell'oggetto inputManager ereditato dalla classe SimpleApplication
.
Attraverso il metodo addMapping
definiamo dei trigger che si attivano per determinate azioni. Ad esempio Left, Right, Up, Down
rappresentano le azioni di movimento verso sinistra, destra, in alto, in basso. Nel metodo addMapping
le specifichiamo attraverso il primo parametro, mentre con il secondo indichiamo,nel nostro caso, quale tasto premuto le attiva : KeInput.KEY_A
tasto con lettera A
, KeyInput.KEY_B
tasto con lettera B
e cosi via. Il metodo addListener
serve per impostare l'ascoltatore per le azioni-evento.
Il metodo simpletInitApp
diventa:
public void simpleInitApp() {
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
flyCam.setMoveSpeed(100);
setupKeys();
}
gestione della luce e delle ombre
Il prossimo aspetto da considerare e la gestione delle luci e delle ombre: ad ogni oggetto visibile dovrà avere associata una sorgente di luce con una locazione e direzione.
In JMonkey abbiamo a disposizione differenti tipi di sorgenti di luce. Quelle che utilizziamo in questo esempio sono DirectionalLight
e AmbientLight
. DirectionalLight
è caratterizzata dal non avere posizione ma solo direzione, è considerata infinita, tipicamente usata per simulare la luce del sole.
AmbientLight
viene utilizzata per influenzare la luminosità della scena globale, non ha direzione ne posizione e non comporta nessuna ombra. Inizializziamo queste sorgenti di luce all'interno del seguente metodo:
private void setUpLight() {
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(al);
DirectionalLight dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
rootNode.addLight(dl);
}
che invochiamo subito dopo setupKeys all'interno del metodo simpleInitApp:
public void simpleInitApp() {
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
flyCam.setMoveSpeed(100);
setupKeys();
setupLight();
}
configurazione della scena
A questo punto continuiamo caricando tutto l'ambiente all'interno del quale ci muoveremo, definito in termini di risorse immagini, font, suoni, nel file town.zip
. Il codice da aggiungere dopo setupLight
è il seguente:
assetManager.registerLocator("town.zip", ZipLocator.class.getName());
sceneModel = assetManager.loadModel("main.scene");
sceneModel.setLocalScale(2f);
assetManager
è ereditata dalla classe Application
estesa da SimpleApplication
, mentre sceneModel
è stata dichiarata come variabile di istanza precedentemente. Il codice del metodo si completa con la gestione delle collisioni tra l'ambiente ed il giocatore che si muove in esso:
CollisionShape sceneShape =
CollisionShapeFactory.createMeshShape((Node) sceneModel);
landscape = new RigidBodyControl(sceneShape, 0);
sceneModel.addControl(landscape);
CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
player = new CharacterControl(capsuleShape, 0.05f);
player.setJumpSpeed(20);
player.setFallSpeed(30);
player.setGravity(30);
player.setPhysicsLocation(new Vector3f(0, 10, 0));
le seguenti ultime righe di codice completano la configurazione della scena
simpleInitApp
rootNode.attachChild(sceneModel);
bulletAppState.getPhysicsSpace().add(landscape);
bulletAppState.getPhysicsSpace().add(player);
La classe HelloCollision
com.jme3.input.controls.ActionListener
HelloCollision
public void onAction(String binding, boolean value, float tpf) {
if (binding.equals("Left")) {
if (value) { left = true; } else { left = false; }
} else if (binding.equals("Right")) {
if (value) { right = true; } else { right = false; }
} else if (binding.equals("Up")) {
if (value) { up = true; } else { up = false; }
} else if (binding.equals("Down")) {
if (value) { down = true; } else { down = false; }
} else if (binding.equals("Jump")) {
player.jump();
}
}
il codice all'interno del metodo non effettua nessun movimento in una determinata direzione ma ne mantiene solo traccia. La classe SimpleApplication
public void simpleUpdate(float tpf)
la cui implementazione è vuota. Dobbiamo fare override di questo metodo in HelloCollision
@Override
public void simpleUpdate(float tpf) {
Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);
walkDirection.set(0, 0, 0);
if (left) { walkDirection.addLocal(camLeft); }
if (right) { walkDirection.addLocal(camLeft.negate()); }
if (up) { walkDirection.addLocal(camDir); }
if (down) { walkDirection.addLocal(camDir.negate()); }
player.setWalkDirection(walkDirection);
cam.setLocation(player.getPhysicsLocation());
}
Attraverso questo metodo viene ripetutamente controllata la posizione della camera. Grazie alla direzione forward(camDir)
left(leftDir)
setWalkDirection()
HelloCollision
physics-controlled object
L'applicazione demo è praticamente conclusa, dobbiamo aggiungere in HelloCollision il metodo main di lancio:
public static void main(String[] args) {
HelloCollision app = new HelloCollision();
app.start();
}
Infine copiamo all'interno del cartella del progetto il file town.zip
fornito in allegato con l'articolo. Questo file contiene tutto ciò che riguarda la grafica dell'ambiente. Siamo pronti per compilare e lanciare l'applicativo HelloCollision. La prima schermata che viene visualizzata è quella di lancio:
(clic per ingrandire)

Facendo click su ok
(clic per ingrandire)

Conclusioni
Abbiamo introdotto velocemente il software senza soffermarci sugli aspetti realizzativi e concetti matematici 3D, necessari per poter comprendere i codice scritto, e poter effettivamente sfruttare la libreria partendo da un livello principiante. Potete trovare questo ed altri tutorial al seguente link:
http://jmonkeyengine.org/wiki/doku.php/jme3:beginnerNei prossimi articoli vedremo in dettaglio le caratteristiche fondamentali degli strumenti messi a disposizione, concetti matematici alla base del loro utilizzo e come costruire oggetti ed ambienti.