Programación de juegos y 3D en Java con jMonkeyEngine

Escrito por el , actualizado el .
java programacion blog-stack planeta-codigo planeta-linux
Comentarios

jMonkeyEngine
Java

Erróneamente se sigue pensado que Java es un lenguaje lento en ejecución, en las primeras versiones era cierto pero hoy la realidad es que con las mejoras introducidas en cada versión de Java y la máquina virtual el rendimiento actual es comparable a C y C++. En la programación de juegos y 3D gran parte del proceso de representación gráfica se ha descargado de la CPU a las cada vez más potentes tarjetas gráficas, la potencia de estas GPU son las que determinan la capacidad de proceso gráfico y la calidad gráfica de los juegos.

Java no suele ser considerado como opción para programar videojuegos triple A pero ahí está Minecraft uno de los juegos más populares y un ejemplo de que un juego de buena calidad y rendimiento también se puede hacer en Java. Hay algunos otros ejemplos notables como de variados estilos RPG, Puzzle, MOBA, Rogue, RTS, Card MMOG, FPS, Arcade, Platform ,…:

Este es un vídeo del juego PirateHell que tiene una pinta muy buena:

Algunas capturas de imagen de estos juegos, en los enlaces anteriores se pueden encontrar vídeos de algunos de ellos.

Todos estos juegos están programados utilizando el lenguaje de programación Java y la librería jMonkeyEngine que facilita las tareas de programación de videojuegos proporcionando programación gráfica en 3D usando OpenGL, manejo de eventos de entrada como teclado o ratón, manejo de sonido, pantallas de menús o red. Usando jMonkeyEngine se pueden hacer cosas muy interesantes como se ve en los ejemplos. En el siguiente enlace se pueden encontrar el código fuente de varios ejemplos que podemos probar.

A continuación mostraré el código y unas capturas de pantalla de algunas las posibilidades de jMonkeyEngine. Primeramente veamos como crear un ejemplo básico con un cubo aplicándole una textura con el que veremos como crear una escena. El segundo ejemplo aplica texturas de diferentes formas que varían la visualización de un elemento geométrico.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package io.github.picodotdev.blogbitix.jmonkeyengine;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

/**
 * <h3>Ejemplo 1: Hola mundo</h3>
 * 
 * <p>Este ejemplo muestra el mínimo necesario para empezar con una aplicación
 * que use jMonkeyEngine. Sirve cmo punto de partida para comprobar que  
 * disponemos del entorno instalado correctamente que principalmente en extender
 * de la clase SimpleApplication y disponer de las librerías jar de jMonkeyEngine
 * en el classpath.</p>
 * 
 * <p>También muestra lo mínimo necesario para añadir elementos geométricos y que se
 * visualicen en la escena 3D.</>
 */
public class HolaMundoJMonkeyEngine extends SimpleApplication {

    public static void main(String[] args) {
        HolaMundoJMonkey app = new HolaMundoJMonkey();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        Geometry geometry = buildBox();

        rootNode.attachChild(geometry);
    }

    @Override
    public void simpleUpdate(float tpf) {
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
    
    private Geometry buildBox() {
        Box box = new Box(1, 1, 1);
        
        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.setColor("Color", ColorRGBA.Blue);

        Geometry geometry = new Geometry("Box", box);
        geometry.setMaterial(material);
        
        return geometry;
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package io.github.picodotdev.blogbitix.jmonkeyengine;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;

/**
 * Aplicando texturas y materiales a geometrías.
 */
public class Materiales extends SimpleApplication {

    public static void main(String[] args) {
        Materials app = new Materials();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(15);
        initializeLight();

        Geometry geometry1 = buildBoxUnshaded("Box");
        Geometry geometry2 = buildBoxShaded("Box Shaded");
        Geometry geometry3 = buildBoxTransparent("Box Transparent");
        Geometry geometry4 = buildBoxWireframe("Box Wireframe");
        Geometry geometry5 = buildSphereLighting("Sphere Lighting");
        Geometry geometry6 = buildSphereShiny("Sphere Shiny");
        Geometry geometry7 = buildWindow("window");

        geometry1.setLocalTranslation(-5, 0, 0);
        geometry2.setLocalTranslation(-2, 0, 0);
        geometry3.setLocalTranslation(1, 0, 0);
        geometry4.setLocalTranslation(4, 0, 0);
        geometry5.setLocalTranslation(7, 0, 0);
        geometry6.setLocalTranslation(10, 0, 0);
        geometry7.setLocalTranslation(13, 0, 0);

        rootNode.attachChild(geometry1);
        rootNode.attachChild(geometry2);
        rootNode.attachChild(geometry3);
        rootNode.attachChild(geometry4);
        rootNode.attachChild(geometry5);
        rootNode.attachChild(geometry6);
        rootNode.attachChild(geometry7);
    }

    @Override
    public void simpleUpdate(float tpf) {
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }

    private Geometry buildBoxUnshaded(String name) {
        Box sphere = new Box(1, 1, 1);

        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.setColor("Color", ColorRGBA.White);
        material.setTexture("ColorMap", assetManager.loadTexture("assets/interface/Monkey.png"));
        material.setTexture("LightMap", assetManager.loadTexture("assets/interface/Monkey_light.png"));

        Geometry geometry = new Geometry(name, sphere);
        geometry.setMaterial(material);

        return geometry;
    }

    private Geometry buildBoxShaded(String name) {
        Box box = new Box(1, 1, 1);

        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Diffuse", ColorRGBA.White);
        material.setColor("Ambient", ColorRGBA.Gray);
        material.setTexture("DiffuseMap", assetManager.loadTexture("assets/interface/Monkey.png"));

        Geometry geometry = new Geometry(name, box);
        geometry.setMaterial(material);
        
        return geometry;
    }
    
    private Geometry buildBoxTransparent(String name) {
        Box box = new Box(1, 1, 1);

        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Diffuse", ColorRGBA.White);
        material.setColor("Ambient", ColorRGBA.Gray);
        material.setTexture("DiffuseMap", assetManager.loadTexture("assets/interface/Monkey.png"));

        material.getAdditionalRenderState().setAlphaTest(true);
        material.getAdditionalRenderState().setAlphaFallOff(0.5f);
        
        Geometry geometry = new Geometry(name, box);
        geometry.setMaterial(material);
        
        geometry.setQueueBucket(Bucket.Transparent);

        return geometry;
    }
    
    private Geometry buildBoxWireframe(String name) {
        Box box = new Box(1, 1, 1);

        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.getAdditionalRenderState().setWireframe(true);

        Geometry geometry = new Geometry(name, box);
        geometry.setMaterial(material);
        
        geometry.setQueueBucket(Bucket.Transparent);

        return geometry;
    }

    private Geometry buildSphereLighting(String name) {
        Sphere sphere = new Sphere(32, 32, 1f);

        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Diffuse", ColorRGBA.Blue);
        material.setColor("Ambient", ColorRGBA.Gray);

        Geometry geometry = new Geometry(name, sphere);
        geometry.setMaterial(material);

        return geometry;
    }

    private Geometry buildSphereShiny(String name) {
        Sphere sphere = new Sphere(32, 32, 1f);

        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Diffuse", ColorRGBA.Blue);
        material.setColor("Ambient", ColorRGBA.Gray);
        material.setColor("Specular", ColorRGBA.White);
        material.setFloat("Shininess", 64f);
        
        Geometry geometry = new Geometry(name, sphere);
        geometry.setMaterial(material);
        
        return geometry;
    }

    private Geometry buildWindow(String name) {
        Box box = new Box(1, 1, 1);
                
        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setTexture("DiffuseMap", assetManager.loadTexture("assets/interface/mucha-window.png"));
        material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
        
        Geometry geometry = new Geometry(name, box);
        geometry.setMaterial(material);
        geometry.setQueueBucket(Bucket.Transparent);
        
        return geometry;
    }

    private void initializeLight() {
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(1, 0, -2));
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);

        AmbientLight ambient = new AmbientLight();
        ambient.setColor(ColorRGBA.White);
        rootNode.addLight(ambient);
    }
}

Se pueden crear objetos con texturas transparentes, efectos de luz, ray casting, sistemas de partículas con las que simular fuego, chispas, polvo, establecer animaciones a objetos como cuando un personaje está descansando, terrenos, paisajes, aplicar efectos simulando la física del mundo real, sonido ambiental y posicional y más cosas. En las siguientes imágenes se pueden ver algunos ejemplos de las anteriores posibilidades (la tasa de fps normal es de 60, al tomar las capturas baja).

Un videojuego se compone de múltiples recursos como imágenes, modelos 3D, música, sprites, texturas, fuentes de texto, sonidos, iconos… en la página Open Game Art podemos encontrar todo este tipo de material sin necesidad de tener que crearlo desde la nada.

jMonkeyEngine ofrece un entorno de desarrollo (IDE) basado NetBeans. Descargando el paquete de jMonkeyEngine y copiando las librerías .jar los programas se puede ejecutar perfectamente independientemente del IDE y desarrollar con eclipse y usar la herramienta de construcción gradle.

Para instalar jMonkeyEngine debemos descargar el SDK adecuado para la plataforma que usemos ya sea Windows, Linux o Macintosh. En el caso de Linux es un archivo .sh que deberemos ejecutar (dando permisos de ejecución si es necesario), seguimos las instrucciones y seleccionamos el directorio de instalación del SDK. En jmonkeyplatform/libs de la carpeta de instalación encontramos los archivos .jar que deberemos usar en el IDE o en los programas de los ejemplos.

El libro jMonkeyEngine 3.0 Beginners Guide me ha resultado muy interesante como punto de introducción a la programación gráfica 3D con Java, pero también si realmente nos interesa la programación de videojuegos es muy recomendable leer el material ofrecido en el Curso de Experto en Desarrollo de Videojuegos, un libro de una extensión de más de 1100 páginas de muy buena calidad, en español y descargables gratuitamente. En la wiki de jMonkeyEngine se pueden encontrar numerosos tutoriales para principiantes, también numerosos artículos de nivel más avanzado y el javadoc de la API.

Otras librerías como Slick2D permiten hacer videojuegos en 2D como serían los juegos de plataformas, más o menos lo que permite jMonkeyEngine en el 3D aplicado a 2D también usando como lenguaje Java. Sin duda los videojuegos han sido el motivo en parte de que muchos hoy seamos programadores e informáticos aunque en nuestro trabajo nos dediquemos a otro tipo de aplicaciones y entornos.

Que, ¿aún crees que en Java no se pueden hacer juegos que no tienen que envidiar a muchos otros?