JMonkeyEngine,
Creación
de nuestro primer juego.
En este tutorial intentaremos enseñaros a crear vuestro
primer juego, partiremos de un ejemplo hecho de JMonkey Engine que es el
jmetest.flagrushtut.lesson9, que trata sobre el manejo de una moto por
un escenario.
Para
realizar este tutorial primero
tendremos que instalar el JMonkeyEngine con todos sus ejemplos(estos se
encuentran en todos los paquetes que empiezan por jmestest)., puede ver
como se hace en:
https://adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=BlenderavaMonkeyEngine
Seguiremos trabajando con el
proyecto creado con anterioridad en el
tutorial «Iniciarse
en el manejo de JMonkeyEngine, Creación
de un Cloth.». Ha este proyecto
le tendremos que añadir un
nuevo paquete que en nuestro caso lo hemos llamado «Intento1»:
Una
vez creado, añadimos las clases del ejemplo y las
renombremos si
lo vemos oportuno, en nuestro caso lo hemos hecho ya que de este modo,
podemos tener una mejor compresion, de que es cada fichero java:
FlagRushHandler.java —————-> teclado.java
Vehicle.java ————————–> moto.java
ForceFieldFence.java —————-> escenario.java
Lesson9.java ————————-> juego.java
DriftAction.java ———————-> AccionDerrape.java
ForwardAndBackwardAction.java —> AvanzarRetroceder.java
VehicleRotateACtion.java ————> RotacionMoto.java
Lesson2.java ————————-> Lecion2.java
Una
vez hecho esto quedará algo asi
:
Una vez añadido los
ficheros java tenemos que añadir
los recursos, es decir las imágenes asi que nos dirigimos a la carpeta
del proyecto, nos vamos a la carpeta src/Intento1 y una vez ahi creamos
tres carpetas: imágenes, modelo, texturas
Dentro de cada carpeta metemos
las imagenes correspondientes:
imagenes
modelo
texturas
Empecemos analizando las clases:
moto.java
package Intento1; import com.jme.image.Texture; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.scene.Node; import com.jme.scene.Spatial; import com.jmex.effects.particles.ParticleMesh; public class moto extends Node { private static final long serialVersionUID = 1L; private static final float LEAN_BUFFER = 0.05f; private Spatial model; private float weight; private float velocity; private float acceleration; private float braking; private float turnSpeed; private float maxSpeed = 30; private float minSpeed = 10; private static final Vector3f tempVa = new Vector3f(); private int lean; private float leanAngle; private Vector3f leanAxis = new Vector3f(0,0,1); private Quaternion q = new Quaternion(); Spatial frontwheel, backwheel,gun; private Vector3f wheelAxis = new Vector3f(0, 1, 0); private float angle = 0; private Quaternion rotQuat = new Quaternion(); public moto(String id, Spatial model) { super(id); setModel(model); } public moto(String id, Spatial model, float maxSpeed, float minSpeed, float weight, float acceleration, float braking, float turnSpeed) { super(id); setModel(model); this.maxSpeed = maxSpeed; this.minSpeed = minSpeed; this.weight = weight; this.acceleration = acceleration; this.braking = braking; this.turnSpeed = turnSpeed; } /** * Este método actualiza la moto basando en el tiempo * que transcurre. */ public void update(float time) { this.localTranslation.addLocal(this.localRotation.getRotationColumn(2, tempVa) .multLocal(velocity * time)); rotateWheels(time); processLean(time); } /** * rorateWheels hace que las ruedas rotan un cierto angulo * segun la velocidad * de la moto. */ private void rotateWheels(float time) { //Rotate the tires if the vehicle is moving. if (vehicleIsMoving()) { if(velocity > FastMath.FLT_EPSILON) { angle = angle - ((time) * velocity * 0.5f); if (angle < -360) { angle = 0; } } else { angle = angle + ((time) * velocity * 0.5f); if (angle > 360) { angle = 0; } } rotQuat.fromAngleAxis(angle, wheelAxis); frontwheel.getLocalRotation().multLocal(rotQuat); backwheel.setLocalRotation(frontwheel.getLocalRotation()); } } /** * Este metodo determina si la moto esta moviendose o no, esto se consigue observando * si la velocidad es cercana a cero, teniendo en cuenta un margen de error. * Devuelve verdadero si la moto se esta moviendo. */ public boolean vehicleIsMoving() { return velocity > FastMath.FLT_EPSILON || velocity < -FastMath.FLT_EPSILON; } /** * processLean calcula el angulo de inclinacion de la moto basandose en un * factor de inclinación. Inclinamos la moto mas que inclinar el vehiculo, ya * que este esta centrado en la posicion que ocupa en el terreno. */ private void processLean(float time) { //check if we are leaning at all if(lean != 0) { if(lean == -1 && leanAngle < 0) { leanAngle += -lean * 4 * time; } else if(lean == 1 && leanAngle > 0) { leanAngle += -lean * 4 * time; } else { leanAngle += -lean * 2 * time; } //max lean is 1 and -1 if(leanAngle > 1) { leanAngle = 1; } else if(leanAngle < -1) { leanAngle = -1; } } else { //we are not leaning, so right ourself back up. if(leanAngle < LEAN_BUFFER && leanAngle > -LEAN_BUFFER) { leanAngle = 0; } else if(leanAngle < -FastMath.FLT_EPSILON) { leanAngle += time * 4; } else if(leanAngle > FastMath.FLT_EPSILON) { leanAngle -= time * 4; } else { leanAngle = 0; } } q.fromAngleAxis(leanAngle, leanAxis); model.setLocalRotation(q); lean = 0; } /** * Se definen distintas propiedades que modifican parametros de la moto */ public void setWeight(float weight) { this.weight = weight; } public float getWeight() { return weight; } public float getAcceleration() { return acceleration; } public void setAcceleration(float acceleration) { this.acceleration = acceleration; } public float getBraking() { return braking; } public void setBraking(float braking) { this.braking = braking; } public Spatial getModel() { return model; } public void setModel(Spatial model) { this.detachChild(this.model); this.model = model; this.attachChild(this.model); //Obtiene la referencia de rueda delantera y trasera. backwheel = ((Node)model).getChild("backwheel"); frontwheel = ((Node)model).getChild("frontwheel"); } public float getVelocity() { return velocity; } public void setVelocity(float velocity) { this.velocity = velocity; } public float getTurnSpeed() { return turnSpeed; } public void setTurnSpeed(float turnSpeed) { this.turnSpeed = turnSpeed; } public float getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(float maxSpeed) { this.maxSpeed = maxSpeed; } public float getMinSpeed() { return minSpeed; } public void setMinSpeed(float minSpeed) { this.minSpeed = minSpeed; } /** * brake disminuye la velocidad cuando la moto se dirige hacia delante y pulsamos * la tecla para que vaya marcha atras, para la moto y luego le pondra velocidad negativa */ public void brake(float time) { velocity -= time * braking; if(velocity < -minSpeed) { velocity = -minSpeed; } } /** * accelerate acelera la moto hasta su velocidad maxima */ public void accelerate(float time) { velocity += time * acceleration; if(velocity > maxSpeed) { velocity = maxSpeed; } } /** * drift calcula que ocurre con la moto tanto cuando acelera como cuando frena. * La moto frenara segun su peso. */ public void drift(float time) { if(velocity < -FastMath.FLT_EPSILON) { velocity += ((weight/5) * time); if(velocity > 0) { velocity = 0; } } else if(velocity > FastMath.FLT_EPSILON){ velocity -= ((weight/5) * time); if(velocity < 0) { velocity = 0; } } } public void setRotateOn(int modifier) { lean = modifier; } }
teclado.java
package Intento1; import com.jme.input.InputHandler; import com.jme.input.KeyBindingManager; import com.jme.input.KeyInput; public class teclado extends InputHandler { private moto vehicle; private AccionDerrape drift; public void update(float time) { if ( !isEnabled() ) return; super.update(time); drift.performAction(event); vehicle.update(time); } public teclado(moto vehicle, String api) { this.vehicle = vehicle; setKeyBindings(api); setActions(vehicle); } /** * setKeyBindings establece las teclas que permitiran manejar * la moto. En este caso las flechas */ private void setKeyBindings(String api) { KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager(); keyboard.set("forward", KeyInput.KEY_UP); keyboard.set("backward", KeyInput.KEY_DOWN); keyboard.set("turnRight", KeyInput.KEY_RIGHT); keyboard.set("turnLeft", KeyInput.KEY_LEFT); } /** * setActions define los triggers para las teclas, * para que hagan la accion que queremos cuando pulsamos. */ private void setActions(moto node) { AvanzarRetroceder forward = new AvanzarRetroceder(node, AvanzarRetroceder.FORWARD); addAction(forward, "forward", true); AvanzarRetroceder backward = new AvanzarRetroceder(node, AvanzarRetroceder.BACKWARD); addAction(backward, "backward", true); RotacionMoto rotateLeft = new RotacionMoto(node, RotacionMoto.LEFT); addAction(rotateLeft, "turnLeft", true); RotacionMoto rotateRight = new RotacionMoto(node, RotacionMoto.RIGHT); addAction(rotateRight, "turnRight", true); drift = new AccionDerrape(node); } }
escenario.java
package Intento1; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.math.FastMath; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.SharedMesh; import com.jme.scene.shape.Box; import com.jme.scene.shape.Cylinder; import com.jme.scene.state.BlendState; import com.jme.scene.state.TextureState; import com.jme.system.DisplaySystem; import com.jme.util.TextureManager; public class escenario extends Node { private static final long serialVersionUID = 1L; private Texture t; public escenario(String name) { super(name); buildFence(); } /** * Tiene este metodo ya que se creara una especie de campo y con este metodo * lo podemos animar */ public void update(float interpolation) { /** * Se usara el valor de interpolation para mantener la velocidad del * campo de fuerza constante. */ t.getTranslation().y += 0.3f * interpolation; if(t.getTranslation().y > 1) { t.getTranslation().y = 0; } } /** * buildFence crea la geometria de la valla */ private void buildFence() { Cylinder postGeometry = new Cylinder("post", 10, 10, 1, 10); Quaternion q = new Quaternion(); q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0)); postGeometry.setLocalRotation(q); postGeometry.setModelBound(new BoundingBox()); postGeometry.updateModelBound(); /** * creamos los 4 postes, no es buena idea que compartamos la estructura * asi que usaremos los valores locales */ SharedMesh post1 = new SharedMesh("post1", postGeometry); post1.setLocalTranslation(new Vector3f(0,0.5f,0)); SharedMesh post2 = new SharedMesh("post2", postGeometry); post2.setLocalTranslation(new Vector3f(32,0.5f,0)); SharedMesh post3 = new SharedMesh("post3", postGeometry); post3.setLocalTranslation(new Vector3f(0,0.5f,32)); SharedMesh post4 = new SharedMesh("post4", postGeometry); post4.setLocalTranslation(new Vector3f(32,0.5f,32)); /** * este sera el cilindro maestro qe mantendra el campo de fuerza * en su sitio */ Cylinder strutGeometry = new Cylinder("strut", 10,10, 0.125f, 32); strutGeometry.setModelBound(new BoundingBox()); strutGeometry.updateModelBound(); /** * Otra vez tenemos que compartir esta estrucutra asi que la rotaremos * para hacer las conexiones */ SharedMesh strut1 = new SharedMesh("strut1", strutGeometry); Quaternion rotate90 = new Quaternion(); rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0)); strut1.setLocalRotation(rotate90); strut1.setLocalTranslation(new Vector3f(16,3f,0)); SharedMesh strut2 = new SharedMesh("strut2", strutGeometry); strut2.setLocalTranslation(new Vector3f(0,3f,16)); SharedMesh strut3 = new SharedMesh("strut3", strutGeometry); strut3.setLocalTranslation(new Vector3f(32,3f,16)); SharedMesh strut4 = new SharedMesh("strut4", strutGeometry); strut4.setLocalRotation(rotate90); strut4.setLocalTranslation(new Vector3f(16,3f,32)); /** * Crea el campo de fuerza * La primera caja controla el eje x, la segunda el eje z. * No se rota la caja para ense�ar que se pueden crear de varias formas. */ Box forceFieldX = new Box("forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16f, 3f, 0.1f)); forceFieldX.setModelBound(new BoundingBox()); forceFieldX.updateModelBound(); /** * Tambien compartiremos estas cajas */ SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1",forceFieldX); forceFieldX1.setLocalTranslation(new Vector3f(16,0,0)); SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2",forceFieldX); forceFieldX2.setLocalTranslation(new Vector3f(16,0,32)); Box forceFieldZ = new Box("forceFieldZ", new Vector3f(-0.1f, -3f, -16), new Vector3f(0.1f, 3f, 16)); forceFieldZ.setModelBound(new BoundingBox()); forceFieldZ.updateModelBound(); SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1",forceFieldZ); forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16)); SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2",forceFieldZ); forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16)); /** * se a�ade todos los campos de fuerza a un solo nodo y se le hace parte * de la zona transparente. */ Node forceFieldNode = new Node("forceFieldNode"); forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); forceFieldNode.attachChild(forceFieldX1); forceFieldNode.attachChild(forceFieldX2); forceFieldNode.attachChild(forceFieldZ1); forceFieldNode.attachChild(forceFieldZ2); /** * se a�ade los valores alfa para el nodo transparente */ BlendState as1 = DisplaySystem.getDisplaySystem().getRenderer().createBlendState(); as1.setBlendEnabled(true); as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha); as1.setDestinationFunction(BlendState.DestinationFunction.One); as1.setTestEnabled(true); as1.setTestFunction(BlendState.TestFunction.GreaterThan); as1.setEnabled(true); forceFieldNode.setRenderState(as1); /** * cargamos la textura para el campo de fuerza */ TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); t = TextureManager.loadTexture(Leccion2.class.getClassLoader() .getResource("Intento1/texturas/reflector.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear); t.setWrap(Texture.WrapMode.Repeat); t.setTranslation(new Vector3f()); ts.setTexture(t); forceFieldNode.setRenderState(ts); /** * ponemos todas las postes en el nodo de una torre */ Node towerNode = new Node("tower"); towerNode.attachChild(post1); towerNode.attachChild(post2); towerNode.attachChild(post3); towerNode.attachChild(post4); /** * se a�ade la torre a la zona opaca ya que no queremos que se vea a * traves de ella */ towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); /** * cargamos la textura para las torres */ TextureState ts2 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t2 = TextureManager.loadTexture(Leccion2.class.getClassLoader() .getResource("Intento1/texturas/post.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear); ts2.setTexture(t2); towerNode.setRenderState(ts2); /** * ponemos todas las estructuras en un solo nodo */ Node strutNode = new Node("strutNode"); strutNode.attachChild(strut1); strutNode.attachChild(strut2); strutNode.attachChild(strut3); strutNode.attachChild(strut4); /** * Esto tambien se encuentra en la zona opaca */ strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE); /** * cargamos la textura de las estructuras */ TextureState ts3 = DisplaySystem.getDisplaySystem().getRenderer().createTextureState(); Texture t3 = TextureManager.loadTexture(Leccion2.class.getClassLoader() .getResource("Intento1/texturas/rust.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear); ts3.setTexture(t3); strutNode.setRenderState(ts3); /** * a�adimos todas las piezas en el nodo principal de la valla */ this.attachChild(forceFieldNode); this.attachChild(towerNode); this.attachChild(strutNode); } }
RotacionMoto.java
package Intento1; import com.jme.input.action.InputActionEvent; import com.jme.input.action.KeyInputAction; import com.jme.math.FastMath; import com.jme.math.Matrix3f; import com.jme.math.Vector3f; public class RotacionMoto extends KeyInputAction { public static final int RIGHT = 0; public static final int LEFT = 1; /** * Variables temporales para la rotacion */ private static final Matrix3f incr = new Matrix3f(); private static final Matrix3f tempMa = new Matrix3f(); private static final Matrix3f tempMb = new Matrix3f(); private Vector3f upAxis = new Vector3f(0,1,0); private moto vehicle; private int direction; private int modifier = 1; public RotacionMoto(moto vehicle, int direction) { this.vehicle = vehicle; this.direction = direction; } /** * giraremos por su velocidad de giro. Si vamos marcha atras girará al reves */ public void performAction(InputActionEvent evt) { if(vehicle.getVelocity() > -FastMath.FLT_EPSILON && vehicle.getVelocity() < FastMath.FLT_EPSILON) { return; } if(direction == LEFT) { modifier = 1; } else if(direction == RIGHT) { modifier = -1; } if(vehicle.getVelocity() < 0) { incr.fromAngleNormalAxis(-modifier * vehicle.getTurnSpeed() * evt.getTime(), upAxis); } else { incr.fromAngleNormalAxis(modifier * vehicle.getTurnSpeed() * evt.getTime(), upAxis); } vehicle.getLocalRotation().fromRotationMatrix( incr.mult(vehicle.getLocalRotation().toRotationMatrix(tempMa), tempMb)); vehicle.getLocalRotation().normalize(); vehicle.setRotateOn(modifier); } }
AvanzarRetroceder.java
package Intento1; import com.jme.input.action.InputActionEvent; import com.jme.input.action.KeyInputAction; public class AvanzarRetroceder extends KeyInputAction { public static final int FORWARD = 0; public static final int BACKWARD = 1; private moto node; private int direction; public AvanzarRetroceder(moto node, int direction) { this.node = node; this.direction = direction; } /** * la acción llama a los metodos de accelerate o brake que son los que ajustan su velocidad */ public void performAction(InputActionEvent evt) { if(direction == FORWARD) { node.accelerate(evt.getTime()); } else if(direction == BACKWARD){ node.brake(evt.getTime()); } } }
AccionDerrape.java
package Intento1; import com.jme.input.action.InputActionEvent; import com.jme.input.action.KeyInputAction; public class AccionDerrape extends KeyInputAction { private moto vehicle; public AccionDerrape(moto vehicle) { this.vehicle = vehicle; } /** * la accion llama al metodo drift de la moto */ public void performAction(InputActionEvent evt) { vehicle.drift(evt.getTime()); } }
juego.java
package Intento1; import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ImageIcon; import jmetest.renderer.ShadowTweaker; import jmetest.renderer.TestSkybox; import jmetest.terrain.TestTerrain; import com.jme.app.BaseGame; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.input.ChaseCamera; import com.jme.input.InputHandler; import com.jme.input.KeyBindingManager; import com.jme.input.KeyInput; import com.jme.input.thirdperson.ThirdPersonMouseLook; import com.jme.intersection.BoundingCollisionResults; import com.jme.intersection.CollisionResults; import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; import com.jme.renderer.Camera; import com.jme.renderer.ColorRGBA; import com.jme.renderer.Renderer; import com.jme.renderer.pass.BasicPassManager; import com.jme.renderer.pass.RenderPass; import com.jme.renderer.pass.ShadowedRenderPass; import com.jme.scene.Node; import com.jme.scene.Skybox; import com.jme.scene.Spatial; import com.jme.scene.Text; import com.jme.scene.state.CullState; import com.jme.scene.state.LightState; import com.jme.scene.state.MaterialState; import com.jme.scene.state.TextureState; import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.system.JmeException; import com.jme.util.TextureManager; import com.jme.util.Timer; import com.jme.util.export.binary.BinaryImporter; import com.jmex.terrain.TerrainBlock; import com.jmex.terrain.util.MidPointHeightMap; import com.jmex.terrain.util.ProceduralTextureGenerator; public class juego extends BaseGame { private static final Logger logger = Logger.getLogger(juego.class .getName()); // El terreno donde andara nuestra moto. private TerrainBlock tb; // La valla que nos mantendra en el escenario. private escenario fence; //La caja que contiene el cielo (la actualizaremos en cada frame). private Skybox skybox; //el jugador que moveremos. private moto player; //private ChaseCamera chaser; protected InputHandler input; //Variable de tiempo que se usa para las velocidades protected Timer timer; //La camara private Camera cam; //La camara que sigue a nuestro jugador private ChaseCamera chaser; //El nodo principal del juego protected Node scene; /** * los atributos par ala ventana */ private int width, height, depth, freq; private boolean fullscreen; //almacena el vector normal del terreno private Vector3f normal = new Vector3f(); //Altura sobre el nivel el suelo private float agl; private static ShadowedRenderPass shadowPass = new ShadowedRenderPass(); private BasicPassManager passManager; public static void main(String[] args) { juego app = new juego(); app.setConfigShowMode(ConfigShowMode.AlwaysShow, juego.class .getClassLoader().getResource( "Intento1/imagenes/Autentia.jpg")); new ShadowTweaker(shadowPass).setVisible(true); app.start(); } /** * durante la actualizacion de la pantalla miraremos si se ha pulsado el boton * escape. */ protected void update(float interpolation) { // actualizacion del framerate timer.update(); interpolation = timer.getTimePerFrame(); //actualizacion de las entradas de teclado para que el jugador se mueva input.update(interpolation); //actualizacion de la camara chaser.update(interpolation); //actualizacion de la valla del campo de fuerza fence.update(interpolation); /** * queremos que el cielose mantenga a la vista asi que lo moveremos * con la camara */ skybox.setLocalTranslation(cam.getLocation()); skybox.updateGeometricState(0, true); //cuando pulsemos la tecla escape salimos if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) { finished = true; } /** * no queremos que la camara de seguimiento pueda ir por debajo del mundo, * asi que lo mantenemos siempre 2 unidades por encima */ if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) { cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2; cam.update(); } /** *Nos aseguramos de que el jugador no choca por abandonar el nivel(esto nos sera mas util cuando * a�adamos colisiones) */ float characterMinHeight = tb.getHeight(player .getLocalTranslation())+agl; if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) { player.getLocalTranslation().y = characterMinHeight; } /** * cogemos el vector normal del terreno en la posicion actual de la moto * y se la aplicamos al vector de subida del jugador */ tb.getSurfaceNormal(player.getLocalTranslation(), normal); if(normal != null) { player.rotateUpTo(normal); } /** * como estamos cambiado la escena, necesitamos actualizar * los graficos */ scene.updateGeometricState(interpolation, true); } protected void render(float interpolation) { // limpiamos la pantalla display.getRenderer().clearBuffers(); passManager.renderPasses(display.getRenderer()); } protected void initSystem() { width = settings.getWidth(); height = settings.getHeight(); depth = settings.getDepth(); freq = settings.getFrequency(); fullscreen = settings.isFullscreen(); try { display = DisplaySystem.getDisplaySystem(settings.getRenderer()); display.setMinStencilBits(8); display.createWindow(width, height, depth, freq, fullscreen); cam = display.getRenderer().createCamera(width, height); } catch (JmeException e) { logger.log(Level.SEVERE, "Could not create displaySystem", e); System.exit(1); } display.getRenderer().setBackgroundColor(ColorRGBA.black.clone()); cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1, 5000); cam.setLocation(new Vector3f(200,1000,200)); cam.update(); timer = Timer.getTimer(); display.getRenderer().setCamera(cam); KeyBindingManager.getKeyBindingManager().set("exit", KeyInput.KEY_ESCAPE); } protected void initGame() { display.setTitle("Moto Autentia"); scene = new Node("Scene graph node"); ZBufferState buf = display.getRenderer().createZBufferState(); buf.setEnabled(true); buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); scene.setRenderState(buf); /** * vamos hacer una peque�a optimizacion. No necesitamos renderizar, la cara * de atras de los triangulos, entonces no lo haremos, esto nos dara un empuje * sin mucho esfuerzo */ CullState cs = display.getRenderer().createCullState(); cs.setCullFace(CullState.Face.Back); scene.setRenderState(cs); //a�adimos el terreno a la escena buildTerrain(); //a�adimos luz buildLighting(); //a�adimos e� campo de fuerza buildEnvironment(); //a�adimos el cielo buildSkyBox(); //construimos al jugador buildPlayer(); //construimos la camara de seguimiento buildChaseCamera(); //construimos el Input del jugador buildInput(); buildPassManager(); // update the scene graph for rendering scene.updateGeometricState(0.0f, true); scene.updateRenderState(); } private void buildPassManager() { passManager = new BasicPassManager(); // Add skybox first to make sure it is in the background RenderPass rPass = new RenderPass(); rPass.add(skybox); passManager.add(rPass); shadowPass.add(scene); shadowPass.addOccluder(player); shadowPass.setRenderShadows(true); shadowPass.setLightingMethod(ShadowedRenderPass.LightingMethod.Modulative); passManager.add(shadowPass); } private void buildPlayer() { Spatial model = null; try { URL bikeFile = juego.class.getClassLoader().getResource("Intento1/modelo/bike.jme"); BinaryImporter importer = new BinaryImporter(); model = (Spatial)importer.load(bikeFile.openStream()); model.setModelBound(new BoundingBox()); model.updateModelBound(); //scale it to be MUCH smaller than it is originally model.setLocalScale(.0025f); } catch (IOException e) { logger .throwing(this.getClass().toString(), "buildPlayer()", e); } player = new moto("Player Node", model); player.setAcceleration(15); player.setBraking(15); player.setTurnSpeed(2.5f); player.setWeight(25); player.setMaxSpeed(25); player.setMinSpeed(15); player.setLocalTranslation(new Vector3f(100,0, 100)); scene.attachChild(player); scene.updateGeometricState(0, true); agl = ((BoundingBox)player.getWorldBound()).yExtent; player.setRenderQueueMode(Renderer.QUEUE_OPAQUE); } private void buildEnvironment() { fence = new escenario("fence"); fence.setLocalScale(5); fence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25)+10, 25)); scene.attachChild(fence); } private void buildLighting() { DirectionalLight light = new DirectionalLight(); light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f)); light.setDirection(new Vector3f(1,-1,0)); light.setShadowCaster(true); light.setEnabled(true); LightState lightState = display.getRenderer().createLightState(); lightState.setEnabled(true); lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f)); lightState.attach(light); scene.setRenderState(lightState); } private void buildTerrain() { MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f); Vector3f terrainScale = new Vector3f(4, 0.0575f, 4); tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0)); tb.setModelBound(new BoundingBox()); tb.updateModelBound(); ProceduralTextureGenerator pt = new ProceduralTextureGenerator( heightMap); pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader() .getResource("Intento1/texturas/grassb.png")), -128, 0, 128); pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader() .getResource("Intento1/texturas/dirt.jpg")), 0, 128, 255); pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader() .getResource("Intento1/texturas/highest.jpg")), 128, 255, 384); pt.createTexture(32); TextureState ts = display.getRenderer().createTextureState(); Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true); ts.setTexture(t1, 0); Texture t2 = TextureManager.loadTexture( TestTerrain.class.getClassLoader().getResource( "Intento1/texturas/Detail.jpg"), Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear); ts.setTexture(t2, 1); t2.setWrap(Texture.WrapMode.Repeat); t1.setApply(Texture.ApplyMode.Combine); t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate); t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor); t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor); t2.setApply(Texture.ApplyMode.Combine); t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned); t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture); t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor); t2.setCombineSrc1RGB(Texture.CombinerSource.Previous); t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor); tb.setRenderState(ts); tb.setDetailTexture(1, 16); tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE); scene.attachChild(tb); } private void buildSkyBox() { skybox = new Skybox("skybox", 10, 10, 10); Texture north = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/north.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); Texture south = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/south.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); Texture east = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/east.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); Texture west = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/west.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); Texture up = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/top.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); Texture down = TextureManager.loadTexture( TestSkybox.class.getClassLoader().getResource( "Intento1/texturas/bottom.jpg"), Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); skybox.setTexture(Skybox.Face.North, north); skybox.setTexture(Skybox.Face.West, west); skybox.setTexture(Skybox.Face.South, south); skybox.setTexture(Skybox.Face.East, east); skybox.setTexture(Skybox.Face.Up, up); skybox.setTexture(Skybox.Face.Down, down); skybox.preloadTextures(); skybox.updateRenderState(); } private void buildChaseCamera() { HashMapprops = new HashMap (); props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6"); props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3"); props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD); props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD)); props.put(ChaseCamera.PROP_DAMPINGK, "4"); props.put(ChaseCamera.PROP_SPRINGK, "9"); chaser = new ChaseCamera(cam, player, props); chaser.setMaxDistance(8); chaser.setMinDistance(2); } private void buildInput() { input = new teclado(player, settings.getRenderer()); } /** * se llamara si cambia la resolucion */ protected void reinit() { display.recreateWindow(width, height, depth, freq, fullscreen); } /** * cerrams la ventana y salimos de la aplicacion */ protected void quit() { super.quit(); System.exit(0); } /** * limpiamos las texturas */ protected void cleanup() { } }
Para cualquier duda de los
metodos y las clases de JMonkey Engine se
peude consultar http://wiki.jmonkeyengine.org/doku.php/
Con esto ya tenemos una moto funcional, es decir que se mueve alrededor del escenario sin problemas, en el siguiente tutorial os diremos como añadir colisiones.
Para ver algunos detalles de este juego vean Detalles del juego de la moto
Esperemos que les haya sido
útil este tutorial, seguiremos haciendo más
tutoriales
sobre esta
tecnología analizando más ejemplos algo
más
complicados, todo el que quiera hacer una aportación
séra
bien recibida.
Para comunicarme cualquier problema o sugerencia de mejora
podeís utilizar la
zona de comentarios, de este modo todo el mundo se podrá
aprovechar de
las respuestas.
Que mas Javier, muy claro el tuto, pero tengo problemas por que no encuentro ni los modelos ni las texturas para poderlo correr. Le agradezco si puede ayudarme.
…Yeyo…
Jeje ya los encontre de todas maneras gracias
Hola Javier primero que todo quiero felicitarlo por tan excelente material, y el aporte tan importante que nos deja a los que visitamos sus paginas. Quisiera si es posible, que me orientara ya que baje todo el código, lo escribi igual a como esta en la pagina y no me funciona nada. Le agradeceria sus comentarios.