CLASE PRINCIPAL
/*
* Aplicación que simula una bolera. Si se pulsa la tecla espacio o el botón izquierdo del ratón, se lanza una bola
* que impacta contra 10 bolos(clindros) ocasionando su caída según el ángulo
*/
package bolera;
// Importo las clases necesarias para trabajar
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.audio.AudioNode;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.PssmShadowRenderer;
import com.jme3.texture.Texture;
/**
*
* @author Gabriel López Marcos
*/
public class Bolera extends SimpleApplication {
public static void main(String[] args) {
// Inicio de la aplicación
Bolera app = new Bolera();
app.start();
}
// Variables del juego
private BulletAppState bulletAppState;
private Material material_bola;
private Material material_suelo;
private Material material_pared;
private Material material_bordes_pista;
private Material material_bordes_carril;
private Material material_bolo;
private AudioNode audio_impacto;
private AudioNode audio_general;
private AudioNode audio_bola;
private Geometry bola_geo;
private RigidBodyControl bola_fis;
private int disparo=0;
private String gravedad;
BitmapText hudText;
@Override
public void simpleInitApp() {
bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
// Se establece la gravedad. He utilizado la gravedad terrestre como gravedad base.
// Espero que sea correcto ya que tampoco estoy muy seguro con las conversiones.
bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0,-9.83f,0));
gravedad = "Sí"; // Esta variable almacena en texto si la gravedad está activa o no lo está.
bulletAppState.getPhysicsSpace().addCollisionListener(collisionListener); // Listener de colisiones
crearMateriales(); // Se crean los materiales
crearPista(); // Se crea la pista
crearBordes(); // Se crean los bordes delimitadores de la pista
crearBordesPista(); // Se crean los carriles exteriores. Hago esto para evitar que la bola y los bolos caigan al vacío.
crearPared(); // Se crea la pared.
crearBola(); // Se crea la bola.
musicaAmbiente(); // Se añade el sonido ambiente.
// Se crean los 10 bolos. Le paso su posición X y su posición Z. La posición Y ya que conozco
// ya que ésta es a ras del suelo.
crearBolos(0.20f,-7f);
crearBolos(0.60f,-7f);
crearBolos(-0.20f,-7f);
crearBolos(-0.60f,-7f);
crearBolos(0,-6f);
crearBolos(0.40f,-6f);
crearBolos(-0.40f,-6f);
crearBolos(0.20f,-5f);
crearBolos(-0.20f,-5f);
crearBolos(0,-4f);
// Se crea una luz ambiental que proyecta cierto brillo en los objetos. No produce sombras.
AmbientLight luzAmbiente = new AmbientLight();
luzAmbiente.setColor(ColorRGBA.White.mult(0.6f)); // La luz será blanca. Establezco su potencia.
rootNode.addLight(luzAmbiente);
// Se crea una luz posicional que emite un foco general desde la derecha.
// sobre todos los objetos.
DirectionalLight luzDireccional = new DirectionalLight();
luzDireccional.setColor(ColorRGBA.White.mult(0.6f));
luzDireccional.setDirection(new Vector3f(-10,20,14).normalizeLocal()); // Establezco desde donde procede el foco de luz.
rootNode.addLight(luzDireccional);
// Se crea un renderizado de sombras que se encarga de proyectar sombras.
PssmShadowRenderer pssmRenderer = new PssmShadowRenderer(assetManager, 2048, 3); // Le indico el tamaño de mi escena.
pssmRenderer.setDirection(new Vector3f(-10,-20,-14).normalizeLocal()); // Le indico desde donde procede la luz.
pssmRenderer.setShadowIntensity(0.5f);
pssmRenderer.setEdgesThickness(1);
viewPort.addProcessor(pssmRenderer);
// Colocamos la cámara en una posición adecuada para ver la superficie
// del suelo y mirando hacia ella.
cam.setLocation(new Vector3f(0, 4f, 10f));
cam.lookAt(new Vector3f(0, 2, 0), Vector3f.UNIT_Y);
// Ponemos un color de fondo azul oscuro
viewPort.setBackgroundColor(new ColorRGBA(0f, 0f, 0.2f, 0));
// Se escribe sobre la pantalla por encima del hud.
// Aquí se aportará información sobre el estado de la gravedad. ON o OFF
hudText = new BitmapText(guiFont, false);
hudText.setSize(guiFont.getCharSet().getRenderedSize());
hudText.setColor(ColorRGBA.Red);
hudText.setText("Gravedad : ON");
hudText.setLocalTranslation(0,(hudText.getLineHeight()+170),0);
guiNode.attachChild(hudText);
// Se crea un mapping de controles para interactuar con el juego.
// La tecla (espacio) o el botón izquierdo del ratón, provocará el lanzamiento de la bola.
inputManager.addMapping("Lanzar",new KeyTrigger(KeyInput.KEY_SPACE),new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
// La tecla G activa o desactiva la gravedad que se aplica
inputManager.addMapping("Gravedad",new KeyTrigger(KeyInput.KEY_G));
// Añado los controles al listener de acciones.
inputManager.addListener(actionListener, new String[]{"Lanzar"});
inputManager.addListener(actionListener, new String[]{"Gravedad"});
}
// Este método implementa las acciones que se realizarán en respuesta a las pulsaciones del usuario.
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean KeyReleased, float tpf) {
// Si el nombre de la acción es 'Lanzar' y aún no se ha lanzado la bola se ejecuta la acción.
// La variable 'disparo' almacena si ya se ha disparado o no. 0 no, 1, sí.
// He incluído esta variable para controlar que no se pueda imprimir fuerza sobre la bola una vez lanzada.
// También controlo si la tecla ha sido liberada para no seguir aumentando la velocidad del disparo.
if (name.equals("Lanzar") && disparo==0 && KeyReleased==true) {
audioBola(); // Añado el sonido que se produce al lanzar la bola.
bola_fis.setLinearVelocity(cam.getDirection().mult(17)); // Se lanza la bola a 17 m/seg
disparo=1; // Indico a la variable que ya se lanzó la bola
//crearBola();
}
// Si el nombre de la acción es 'Gravedad' se activará o desactivará la gravedad que se aplica según su estado.
// Controla que la tecla ha sido liberada.
if (name.equals("Gravedad") && KeyReleased==true) {
// Se cambia el estado de la gravedad según del estado que esté.
if ("Sí".equals(gravedad)) { // Si la gravedad está activa ...
gravedad = "No"; // La cambio a no activa.
hudText.setText("Gravedad : OFF"); // Establezco el texto del hud.
// Establezco gravedad 0. Los objeto que no se mueven permanecen como estén, sin embargo, a los que se
// le aplique alguna fuerza flotarán cerca del suelo.
bulletAppState.getPhysicsSpace().setGravity(Vector3f.ZERO);
} else { // Si la gravedad está inactiva ...
gravedad = "Sí"; // La cambio a actica
hudText.setText("Gravedad : ON"); // Establezco el texto del hud.
// Establezco la gravedad terrestre. Los objetos que flotasen serán atraídos nuevamente al suelo.
bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0,-9.83f,0));
}
}
}
};
// Método que crea los materiales
private void crearMateriales() {
// MATERIAL DEL SUELO
// Indico el tipo de material que se añadirá.
material_suelo = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
TextureKey key = new TextureKey("Textures/madera_suelo.jpg");
key.setGenerateMips(true);
Texture textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.Repeat); // Indico que se repita.
material_suelo.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
// MATERIAL DE LA BOLA
// Indico el tipo de material que se añadirá.
material_bola = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
key = new TextureKey("Textures/metal_bola.jpg");
key.setGenerateMips(true);
textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.Repeat); // Indico que se repita.
material_bola.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
// MATERIAL DE LOS BORDES DELIMITADORES DE LA PISTA
// Indico el tipo de material que se añadirá.
material_bordes_pista = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
key = new TextureKey("Textures/metal_bordes.jpg"); // Cargo la textura.
key.setGenerateMips(true);
textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.Repeat); // Indico que se repita.
material_bordes_pista.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
// MATERIAL DE LOS CARRILES EXTERNOS DE LA PISTA.
// Indico el tipo de material que se añadirá.
material_bordes_carril = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
key = new TextureKey("Textures/metal_borde_carril.jpg");
key.setGenerateMips(true);
textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.Repeat); // Indico que se repita.
material_bordes_carril.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
// MATERIAL DE LA PARED DE LA PISTA
// Indico el tipo de material que se añadirá.
material_pared = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
key = new TextureKey("Textures/pared_gris.jpg");
key.setGenerateMips(true);
textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.MirroredRepeat); // Indico que se repita.
material_pared.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
// MATERIAL DE LOS BOLOS
// Indico el tipo de material que se añadirá.
material_bolo = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
// Indico la textura a utilizar.
key = new TextureKey("Textures/material_bolo.jpg");
key.setGenerateMips(true);
textura = assetManager.loadTexture(key); // Cargo la textura.
textura.setWrap(Texture.WrapMode.Repeat); // Indico que se repita.
material_bolo.setTexture("ColorMap", textura); // Le indico al material la textura que debe usar.
}
// Método que crea la pista central de la bolera.
public void crearPista() {
// Se crea una forma de caja con 3 metros de ancho, 10 centímetros de grosor y 11 metros de largo.
Box pista = new Box(Vector3f.ZERO, 3, 0.1f, 11);
// Se ajusta la textura. (¡¡REALMENTE NO SÉ COMO MANEJAR CORRECTAMENTE ESTE AJUSTE!!)
pista.scaleTextureCoordinates(new Vector2f(6, 3));
// Se crea un spatial de tipo geometría para asociarlo al suelo.
Geometry pista_geo = new Geometry("Pista", pista);
// Asigno su material.
pista_geo.setMaterial(material_suelo);
// Coloco la pista en la posición central
pista_geo.setLocalTranslation(0, 0, 0);
// Se añade su ajuste de sombras. Establezco que únicamente reciba ya que no hay objetos debajo.
pista_geo.setShadowMode(RenderQueue.ShadowMode.Receive);
// Lo añado a la escena
rootNode.attachChild(pista_geo);
// Crearemos un objeto de control físico para asociarlo al suelo
// IMPORTANTE: tiene masa 0 para convertirlo en un objeto estático
RigidBodyControl suelo_fis = new RigidBodyControl(0.0f);
// asociamos el objeto de control a la geometría del suelo de la pista.
pista_geo.addControl(suelo_fis);
// y añadimos el objeto de control al motor de físicas
bulletAppState.getPhysicsSpace().add(suelo_fis);
}
// Método que crea los bordes de la pista
public void crearBordes() {
// Se crean dos formas de caja con 1 metro de ancho, 10 centímetros de grosor y 11 metros de largo.
// Coíncide completamente con la pista.
Box borde1 = new Box(Vector3f.ZERO, 1, 0.1f, 11f);
Box borde2 = new Box(Vector3f.ZERO, 1, 0.1f, 11f);
// Se ajusta la textura. (¡¡REALMENTE NO SÉ COMO MANEJAR CORRECTAMENTE ESTE AJUSTE.!!)
borde1.scaleTextureCoordinates(new Vector2f(6, 3));
borde2.scaleTextureCoordinates(new Vector2f(6, 3));
// Se crean dos spatials de tipo geometría para asociarlo a los bordes.
Geometry borde1_geo = new Geometry("Bordes", borde1);
Geometry borde2_geo = new Geometry("Bordes", borde2);
// Se asigna el material a los dos bordes
borde1_geo.setMaterial(material_bordes_pista);
borde2_geo.setMaterial(material_bordes_pista);
// Los bordes se colocan en los extremos de la pista. Estás situados más bajos que la misma ya que
// quiero que la bola se quede en estos bordes si se sale.
borde1_geo.setLocalTranslation(-2.8f, -0.1f, 0);
borde2_geo.setLocalTranslation(2.8f, -0.1f, 0);
// Establezco las sombras de los bordes. Únicamente reciben sombra.
borde1_geo.setShadowMode(RenderQueue.ShadowMode.Receive);
borde2_geo.setShadowMode(RenderQueue.ShadowMode.Receive);
// Los añado a la escena.
rootNode.attachChild(borde1_geo);
rootNode.attachChild(borde2_geo);
// Crearemos un objeto de control físico para asociarlo a la pared
// IMPORTANTE: tiene masa 0 para convertirlo en un objeto estático
RigidBodyControl borde1_fis = new RigidBodyControl(0.0f);
RigidBodyControl borde2_fis = new RigidBodyControl(0.0f);
// asociamos el objeto de control a la geometría de los bordes.
borde1_geo.addControl(borde1_fis);
borde2_geo.addControl(borde2_fis);
// y añadimos los objetos de control al motor de físicas
bulletAppState.getPhysicsSpace().add(borde1_fis);
bulletAppState.getPhysicsSpace().add(borde2_fis);
}
// Método que crea los carriles exteriores de la pista.
public void crearBordesPista() {
// Se crean dos formas de caja con 20 centímetros de ancho, 10 centímetros de grosor y 11 metros de largo.
// Coíncide completamente con la pista.
Box borde1 = new Box(Vector3f.ZERO, 0.2f, 0.1f, 11);
Box borde2 = new Box(Vector3f.ZERO, 0.2f, 0.1f, 11);
// Se ajusta la textura. (¡¡REALMENTE NO SÉ COMO MANEJAR CORRECTAMENTE ESTE AJUSTE.!!)
borde1.scaleTextureCoordinates(new Vector2f(6, 3));
borde2.scaleTextureCoordinates(new Vector2f(6, 3));
// Se crean dos spatial de tipo geometría para asociarlo a los carriles.
Geometry borde1_geo = new Geometry("Carriles", borde1);
Geometry borde2_geo = new Geometry("Carriles", borde2);
// Se asigna el material correspondiente.
borde1_geo.setMaterial(material_bordes_carril);
borde2_geo.setMaterial(material_bordes_carril);
// Se colocan en los extremos de la pista.
// Estos carriles impiden que la bola o los boles caigan por los laterales.
borde1_geo.setLocalTranslation(-3.9f, 0.1f, 0);
borde2_geo.setLocalTranslation(3.9f, 0.1f, 0);
// Se añaden sus sombras. Reciben y proyectan sombras.
borde1_geo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
borde2_geo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// Se añaden a la escena.
rootNode.attachChild(borde1_geo);
rootNode.attachChild(borde2_geo);
// Crearemos un objeto de control físico para asociarlo a la pared
// IMPORTANTE: tiene masa 0 para convertirlo en un objeto estático
RigidBodyControl borde1_fis = new RigidBodyControl(0.0f);
RigidBodyControl borde2_fis = new RigidBodyControl(0.0f);
// asociamos el objeto de control a la geometría de los carriles.
borde1_geo.addControl(borde1_fis);
borde2_geo.addControl(borde2_fis);
// y añadimos el objeto de control al motor de físicas
bulletAppState.getPhysicsSpace().add(borde1_fis);
bulletAppState.getPhysicsSpace().add(borde2_fis);
}
// Método que crea la pared frontal de la pista.
public void crearPared() {
// Se crea una forma de caja con 20 metros de ancho, 50 centímetros de grosor y 10 metros de largo.
// Coíncide completamente con la pista.
Box pared = new Box(Vector3f.ZERO, 10f, 0.5f, 10f);
// Se ajusta la textura. (¡¡REALMENTE NO SÉ COMO MANEJAR CORRECTAMENTE ESTE AJUSTE.!!)
pared.scaleTextureCoordinates(new Vector2f(6, 3));
// Se crea un spatial de tipo geometría para asociarlo a la pared
Geometry pared_geo = new Geometry("Floor", pared);
// asignamos el material
pared_geo.setMaterial(material_pared);
// lo desplazamos detrás del suelo e inclinado 90 grados en el eje X
// para ponerla en vertical
pared_geo.setLocalTranslation(0, -0.1f, -11);
pared_geo.rotate((float) Math.PI / 2.0f, 0f, 0);
// Se añaden sus sombras. La pared recibe y proyecta sombra.
pared_geo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// y, finalmente, lo incluimos en el grafo de escena
rootNode.attachChild(pared_geo);
// Crearemos un objeto de control físico para asociarlo a la pared
// IMPORTANTE: tiene masa 0 para convertirlo en un objeto estático
RigidBodyControl pared_fis = new RigidBodyControl(0.0f);
// asociamos el objeto de control a la geometría de la pared
pared_geo.addControl(pared_fis);
// y añadimos el objeto de control al motor de físicas
bulletAppState.getPhysicsSpace().add(pared_fis);
}
// Método que crea los bolos. En el constructor se le pasa la posición en X y la posición en Z
// La posición en Y siempre es 0.
public void crearBolos(float posicionX, float posicionZ) {
// Se crea un objeto cilíndrico con 11 centímetros de radio y 40 centímetros de altura.
Cylinder bolo = new Cylinder(15, 15, 0.11f,0.40f,true);
// Se coloca en posición vertical.
Quaternion q = new Quaternion();
q.fromAngleAxis(FastMath.PI/2,new Vector3f(1,0,0));
// Se ajusta la textura. (¡¡REALMENTE NO SÉ COMO MANEJAR CORRECTAMENTE ESTE AJUSTE.!!)
bolo.scaleTextureCoordinates(new Vector2f(6, 3));
// Se crea un spatial de tipo geometría para asociarlo al bolo.
Geometry bolo_geo = new Geometry("Bolo", bolo);
// Se asigna el material.
bolo_geo.setMaterial(material_bolo);
// Se colocan sonbre la pista. En el constructor se indica las posiciones que ocuparán.
bolo_geo.setLocalTranslation(posicionX, 0.25f, posicionZ);
bolo_geo.setLocalRotation(q);
// Se añaden sus sombras. El bolo recibe y proyecta sombra.
bolo_geo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// Se añade a la escena.
rootNode.attachChild(bolo_geo);
// Crearemos un objeto de control físico para asociarlo al suelo
// Cada bolo tiene un peso de 1 kilo y medio.
RigidBodyControl bolo_fis = new RigidBodyControl(1.50f);
// asociamos el objeto de control a la geometría del bolo.
bolo_geo.addControl(bolo_fis);
// y añadimos el objeto de control al motor de físicas
bulletAppState.getPhysicsSpace().add(bolo_fis);
}
// Método que crea una bola.
public void crearBola() {
// Se crea un esfera con 21 centímetros de diámetro.
Sphere esfera = new Sphere(32, 32, 0.216f);
// Se crea un spatial de tipo geometría para asociarlo a la bola.
bola_geo = new Geometry("Bola", esfera);
// Se asigna su material.
bola_geo.setMaterial(material_bola);
// Se añaden sus sombras. La sombra recibe y proyecta sombra.
bola_geo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// Se añade a la escena.
rootNode.attachChild(bola_geo);
// Se coloca sobre la pista.
bola_geo.setLocalTranslation(0, 0.1f, 10f);
// La bola pesa 3 kilos.
bola_fis = new RigidBodyControl(3f);
// Asociar la geometría de la bola al control físico
bola_geo.addControl(bola_fis);
// Añadirla al motor de física
bulletAppState.getPhysicsSpace().add(bola_fis);
}
// Método que implemeta la música que acompañará la aplicación.
private void musicaAmbiente() {
// Se crea el nodo de tipo audio. He cargado la canción 'Saturday Night Fever' de los Bee Gees.
// Se irá reproduciendo a la vez que se carga en buffer.
audio_general = new AudioNode(assetManager, "Sounds/night_fever.wav", false);
audio_general.setLooping(true); // Se reproducirá en bucle.
audio_general.setPositional(true);
// Se añade a la escena.
rootNode.attachChild(audio_general);
audio_general.play(); // Reproducción.
}
// Método que reproduce un audio cuando la bola impacta con cada bolo.
private void impacto() {
// Se crea el nodo de tipo audio. Cuando se produzca una colisión bola-bolo, se reproducirá un sonido.
audio_impacto = new AudioNode(assetManager, "Sounds/rompe.wav", false);
audio_impacto.setLooping(false); // Sólo sonará una vez.
audio_impacto.setPositional(true);
// Se añade a la escena.
rootNode.attachChild(audio_impacto);
audio_impacto.play(); // Reproducción.
}
// Método que reproduce un sonido al lanzar la bola.
private void audioBola() {
// Se crea el nodo de tipo audio. Un sonido se reproducirá cuando se dispare la bola.
audio_bola = new AudioNode(assetManager, "Sounds/Bang.wav", false);
audio_bola.setLooping(false); // Sólo sonará una vez.
audio_bola.setPositional(true);
// Se añade a la escena.
rootNode.attachChild(audio_bola);
audio_bola.play(); // Reproducción.
}
// Método que detecta las colisiones que se produzcan
private PhysicsCollisionListener collisionListener = new PhysicsCollisionListener() {
@Override
public void collision(PhysicsCollisionEvent colision) {
// Si la bola colisiones con un bolo ......
if ("Bola".equals(colision.getNodeA().getName()) && "Bolo".equals(colision.getNodeB().getName())) {
impacto(); // Se reproduce el audio del impacto.
}
}
};
@Override
public void simpleUpdate(float tpf) {
// Aquí controlo los cambios direccionales de los audios.
// La fuente de éstos es la posición de la cámara.
listener.setLocation(cam.getLocation());
listener.setRotation(cam.getRotation());
}
@Override
public void simpleRender(RenderManager rm) {
}
}