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:
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
. All'interno della classe importiamo i seguenti package necessari per la demo:
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
che si inizializza con le seguenti righe di codice
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
è ereditata dalla superclasse Application superclasse, a sua volta, di SimpleApplication
, questa variabile ci consente di gestire lo stato del bullet.
Dopo aver inizializzato le funzionalità di game physics, facciamo lo stesso per la Camera, ovvero ciò che ci consentirà il movimento all'interno dell'ambiente 3D:
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
e qui viene utilizzata per impostare il background color; flyCam
(anch'essa nella classe Application
) è una estensione della Camera di default presente nella classe SimpleApplication
. La flyCam
è preconfigurata per rispondere ai tasti W,A,S,D che ci permetteranno diruotare all'interno dell'ambiente nelle 4 direzioni.
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 ed il metodo simpleInitApp
:
rootNode.attachChild(sceneModel);
bulletAppState.getPhysicsSpace().add(landscape);
bulletAppState.getPhysicsSpace().add(player);
La classe HelloCollision
implementa com.jme3.input.controls.ActionListener
. Questa classe definisce il metodo onAction che dobbiamo implementare.Ecco il codice che inseriamo in 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
ha un metodo
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)
e left(leftDir)
della camera, si riesce a determinare la posizione del giocatore. Attraverso setWalkDirection()
sull'oggetto player, definito come variabile di istanza in HelloCollision
, realizziamo quella che viene chiamata physics-controlled object per il movimento fluido e continuo dell'oggetto, inoltre il physics engine gestisce le collisioni per noi. Con l'ultima istruzione invece si fa in modo, invece, che la camera segua il giocatore.
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
partirà finalmente la nostra demo in un piccolo ambiente 3D, all'interno del quale possiamo muoverci con i tasti freccia e i tasti w,a,s,d:
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:beginner
Nei 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.