Get the Mytodolist Free Android app from SlideME.

vendredi 4 juillet 2014

9/ La gestion de collisions avec JMonkey Engine.



Ce tutoriel vous montrera comment charger un modèle de scène et rendre les murs et les planchers assez solides pour qu'un personnage puisse y se promener ou entrer en collision. Vous utiliserai un RigidBodyControl pour une scène statique collidable ( c'est à dire qu'on peut cogner ) , et un CharacterControl pour les personnages mobiles . Vous allez apprendre également comment configurer la valeur par défaut de la caméra (qui sert des yeux ) du personnage et travailler avec le controlleur de physique. Vous pouvez utiliser la solution présentée ici pour 
créer des jeux de style first-person shooters (call of duty par exemple ), des labyrinthes et des jeux similaires.





Code d'exemple

Cliquez sur le lien suivant pour télécharger les ressources du code download the town.zip 


jMonkeyProjects$ ls -1 BasicGame
assets/
build.xml
town.zip
src/

Mettez town.zip dans la racine de votre projet JME3. Voici le code complet à tester:


package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
 
/**
 * Example 9 - How to make walls and floors solid.
 * This collision code uses Physics and a custom Action Listener.
 * @author normen, with edits by Zathras
 */
public class HelloCollision extends SimpleApplication
        implements ActionListener {
 
  private Spatial sceneModel;
  private BulletAppState bulletAppState;
  private RigidBodyControl landscape;
  private CharacterControl player;
  private Vector3f walkDirection = new Vector3f();
  private boolean left = false, right = false, up = false, down = false;
 
  //Temporary vectors used on each frame.
  //They here to avoid instanciating new vectors on each frame
  private Vector3f camDir = new Vector3f();
  private Vector3f camLeft = new Vector3f();
 
  public static void main(String[] args) {
    HelloCollision app = new HelloCollision();
    app.start();
  }
 
  public void simpleInitApp() {
    /** Set up Physics */
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
 
    // We re-use the flyby camera for rotation, while positioning is handled by physics
    viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
    flyCam.setMoveSpeed(100);
    setUpKeys();
    setUpLight();
 
    // We load the scene from the zip file and adjust its size.
    assetManager.registerLocator("town.zip", ZipLocator.class);
    sceneModel = assetManager.loadModel("main.scene");
    sceneModel.setLocalScale(2f);
 
    // We set up collision detection for the scene by creating a
    // compound collision shape and a static RigidBodyControl with mass zero.
    CollisionShape sceneShape =
            CollisionShapeFactory.createMeshShape((Node) sceneModel);
    landscape = new RigidBodyControl(sceneShape, 0);
    sceneModel.addControl(landscape);
 
    // We set up collision detection for the player by creating
    // a capsule collision shape and a CharacterControl.
    // The CharacterControl offers extra settings for
    // size, stepheight, jumping, falling, and gravity.
    // We also put the player in its starting position.
    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));
 
    // We attach the scene and the player to the rootnode and the physics space,
    // to make them appear in the game world.
    rootNode.attachChild(sceneModel);
    bulletAppState.getPhysicsSpace().add(landscape);
    bulletAppState.getPhysicsSpace().add(player);
  }
 
  private void setUpLight() {
    // We add light so we see the scene
    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);
  }
 
  /** We over-write some navigational key mappings here, so we can
   * add physics-controlled walking and jumping: */
  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");
  }
 
  /** These are our custom actions triggered by key presses.
   * We do not walk yet, we just keep track of the direction the user pressed. */
  public void onAction(String binding, boolean isPressed, float tpf) {
    if (binding.equals("Left")) {
      left = isPressed;
    } else if (binding.equals("Right")) {
      right= isPressed;
    } else if (binding.equals("Up")) {
      up = isPressed;
    } else if (binding.equals("Down")) {
      down = isPressed;
    } else if (binding.equals("Jump")) {
      if (isPressed) { player.jump(); }
    }
  }
 
  /**
   * This is the main event loop--walking happens here.
   * We check in which direction the player is walking by interpreting
   * the camera direction forward (camDir) and to the side (camLeft).
   * The setWalkDirection() command is what lets a physics-controlled player walk.
   * We also make sure here that the camera moves with player.
   */
  @Override
    public void simpleUpdate(float tpf) {
        camDir.set(cam.getDirection()).multLocal(0.6f);
        camLeft.set(cam.getLeft()).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());
    }
}

Exécutez l'exemple. Vous devriez voir une place de la ville avec des maisons et un monument. Utilisez les touches WASD et la souris pour naviguer dans une perspective à la première personne. Marcher et sauter en appuyant respectivement sur W et de l'espace. Notez que vous marchez sur le trottoir, et les marches du monument. Vous pouvez vous promener dans les ruelles entre les maisons, mais les murs sont solides. N'osez pas marcher sur le bord du monde! 




Explication du code

Commençons par la déclaration de la classe
public class HelloCollision extends SimpleApplication
        implements ActionListener { ... }
Vous savez déjà que SimpleApplication est la classe de base pour tous les jeux jME3. Vous implémenter dans cette classe l'interface ActionListener pour personnaliser les entrées de navigation plus tard.


 private Spatial sceneModel;
  private BulletAppState bulletAppState;
  private RigidBodyControl landscape;
  private CharacterControl player;
  private Vector3f walkDirection = new Vector3f();
  private boolean left = false, right = false, up = false, down = false;
 
  //Temporary vectors used on each frame.
  //They here to avoid instanciating new vectors on each frame
  private Vector3f camDir = new Vector3f();
  private Vector3f camLeft = new Vector3f();
Vous initialiser quelques champs privés: 


  • Le BulletAppState donne à SimpleApplication l'accès aux fonctionnalités de la physique (telles que la détection de collision) fourni par l'intégration jBullet de jME3 
  • Le sceneModel spatiale permet de charger le modèle OgreXML d'une ville. 
  • Vous aurez besoin d'un RigidBodyControl pour rendre le modèle de ville solide. 
  • Le (invisible) du joueur est représenté par un objet CharacterControl
  • Les champs walkDirection et les quatre booléens sont utilisés pour la navigation du controlleur de physique . 
  • camDir et camLeft vecteurs sont temporairement utilisées ultérieurement lors du calcul de la walkingDirection à partir de la position de la camera et de la rotation 


Ayons un regard sur tous les détails:


Initialisation du jeu

Comme d'habitude, vous devez initialiser le jeu dans la méthode simpleInitApp ().


 viewPort.setBackgroundColor(new ColorRGBA(0.7f,0.8f,1f,1f));
    flyCam.setMoveSpeed(100);
    setUpKeys();
    setUpLight();


  • Vous définissez la couleur de fond bleu clair, puisqu'il s'agit d'une scène avec un ciel. 
  • Vous réutiliser le contrôle de la caméra par défaut "Flycam" comme caméra ou vue de la personne et régler sa vitesse. 
  • Les méthodes setUpLights() ajoutent les sources de lumière. 
  • La méthode setUpKeys () configure les mappages des entrées que nous verrons plus tard.


La scène de physique contrôlée 

La première chose que vous faites à chaque jeu utilisant beaucoup la physique est de créer un objet BulletAppState. Il vous donne accès à l'intégration de jBullet de jME3 qui gère les forces physiques et les collisions.
 bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
Pour la scène, vous chargerez le sceneModel partir d'un fichier zip, et ajustez sa taille.


assetManager.registerLocator("town.zip", ZipLocator.class);
    sceneModel = assetManager.loadModel("main.scene");
    sceneModel.setLocalScale(2f);
Le fichier town.zip est inclus comme un exemple de modèle dans les sources JME3 - vous pouvez le télécharger ici. (En option, utiliser n'importe quelle scène OgreXML de votre choix.) Pour cet exemple, placez le fichier zip dans le répertoire supérieur de l'application (c'est, à côté de src /, actifs /, build.xml).


CollisionShape sceneShape =
      CollisionShapeFactory.createMeshShape((Node) sceneModel);
    landscape = new RigidBodyControl(sceneShape, 0);
    sceneModel.addControl(landscape);
    rootNode.attachChild(sceneModel);
Pour utiliser la détection de collision, vous ajoutez un RigidBodyControl au sceneModel spatiale. Le RigidBodyControl pour un modèle complexe prend deux arguments: une forme de collision, et la masse de l'objet. 


  • JME3 offre une CollisionShapeFactory qui précalcule une forme maillée de collision précise pour une surface. Vous choisissirez de générer un CompoundCollisionShape (qui est MeshCollisionShapes  de ses enfants) parce que ce type de forme de collision est optimale pour des objets immobiles, tels que terrain, maisons, et les niveaux de tir entières. 
  • Vous mettez la masse à zéro parce que la scène est statique et sa masse n'est pas important. 
  • Ajoutez le contrôle a l'espace pour lui conférer des propriétés physiques. 
  • Comme toujours, fixez le sceneModel au rootNode pour le rendre visible. 


Astuce: N'oubliez pas d'ajouter une source de lumière afin que vous puissiez voir la scène.



Contrôleur de physique appliquée au joueur 


Le joueur de jeu de type first-person est habituellement invisible. Lorsque vous utilisez la valeur par défaut FlyCAM comme la vue du personnage, le joueur peut traverser n'importe quelle objet même les murs. C'est parce que le contrôle FlyCAM n'a pas de forme physique attribuée. Dans cet exemple de code, vous représentez le joueur à la first-person comme une forme physique (invisible). Vous utiliserez les touches WASD pour déplacer cette forme physique, tandis que le moteur physique gère pour vous la façon dont il marche sur les sols le long des murs solides et saute par-dessus des obstacles solides. Ensuite, vous faites simplement suivre la caméra à l'emplacement de la forme lorsqu'elle marche - et vous obtenez l'illusion d'être un corps physique dans un environnement solide qui peut voir à travers la caméra. 

Donc, nous allons mettre en place une détection de collision pour le joueur de type first-person.


   CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
Encore une fois, vous créez un CollisionShape: Cette fois, vous choisissez un CapsuleCollisionShape, un cylindre avec un sommet arrondi et bas. Cette forme est optimal pour une personne: Il est grand et la rondeur permet de rester moins souvent coincé entre ​​les obstacles. 


  • Fournir le constructeur CapsuleCollisionShape avec le rayon désiré et la hauteur de la capsule délimitant pour épouser la forme de votre personnage. Dans cet exemple, le caractère a 2 * 1.5F unités de large, et les unités de haut sont 6f. 
  • L'argument entier finale spécifie l'orientation du cylindre: 1 est l'axe Y, ce qui correspond une personne droite. Pour les animaux qui sont plus long que haut vous devez utiliser 0 ou 2 (selon la façon dont il est mis en rotation).



player = new CharacterControl(capsuleShape, 0.05f);
"Est-ce que CollisionShape me fait grossir?" Si jamais vous ête confondu dans le comportement de la physique, n'oubliez pas de jeter un oeil à des formes de collision. Ajoutez la ligne suivante après l'initialisation bulletAppState pour rendre les formes visibles:
bulletAppState.getPhysicsSpace().enableDebug(assetManager);
Maintenant, vous utiliserez la CollisionShape pour créer un CharacterControl qui représente le joueur à la première personne (first-person). Le dernier argument du constructeur CharacterControl (ici 0,05 f) est la taille d'une étape que le personnage doit être capable de surmonter.


 player.setJumpSpeed(20);
    player.setFallSpeed(30);
    player.setGravity(30);
En plus de la longueur de marche et la taille de caractères, la CharacterControl vous permet de configurer le saut, la chute , et la gravité. Réglez les valeurs pour s'adapter à votre situation de jeu.

 player.setPhysicsLocation(new Vector3f(0, 10, 0));
Enfin, nous mettons le joueur dans sa position de départ et à jour son état - n'oubliez pas d'utiliser setPhysicsLocation () au lieu de setLocalTranslation () maintenant, puisque vous avez affaire à un objet physique.


Espace physique ou PhysicsSpace

Rappelez-vous, dans les jeux physiques, vous devez inscrire tous les objets solides (généralement les personnages et la scène) à la PhysicsSpace (espace physique)!


 bulletAppState.getPhysicsSpace().add(landscape);
    bulletAppState.getPhysicsSpace().add(player);
Le corps invisible du personnage se trouve juste là sur le sol physique. Il ne peut encore marcher - on va voir ça à la suite.


Navigation 


La commande de caméra par défaut cam est une caméra à la troisième personne. JME3 propose également un contrôleur à la première personne, FlyCAM, que nous utilisons ici pour gérer la rotation de la caméra. Le contrôle FlyCAM  fait déplacer la caméra à l'aide setLocation ()

Cependant, vous devez redéfinir la façon dont la marche (mouvement de la caméra) est gérée pour les objets physique: Lorsque vous naviguez dans un nœud non-physique (par exemple la FlyCAM par défaut), vous spécifiez simplement l'emplacement cible. Il n'existe aucun test qui empêche la FlyCAM de rester coincer dans un mur! Lorsque vous déplacez un PhysicsControl, vous souhaitez spécifier une direction de marche à la place. Puis le PhysicsSpace peut calculer pour vous dans quelle mesure le caractère peut effectivement se déplacer dans la direction souhaitée - ou si un obstacle l'empêche d'aller plus loin. 


En bref, vous devez redéfinir les mappages de touches de navigation de la FlyCAM en utilisant setWalkDirection () au lieu de setLocalTranslation (). Voici les étapes:


1. InputManager 



Dans la méthode simpleInitApp (), vous re-configurer les entrées WASD familières à la marche, et la touche espace pour sauter.


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");
}
Vous pouvez déplacer ce bloc de code dans une méthode  setupKeys () et appeler cette méthode depuis simpleInitApp () - pour garder le code lisible


2 onAction() 



Rappelez-vous que cette classe implémente l'interface ActionListener, de sorte que vous pouvez personnaliser les entrées FlyCAM. L'interface ActionListener vous oblige à mettre en œuvre la méthode onAction (): Vous re-définir les actions déclenchées par touches de navigation pour travailler avec la physique.


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();
    }
  }
Le seul mouvement que vous n'avez pas à mettre en œuvre est l'action de saut. Le player.jump() est une méthode spéciale qui gère un mouvement de saut correct pour votre PhysicsCharacterNode. 


Pour toutes les autres directions: Chaque fois que l'utilisateur appuie sur une des touches WASD, vous gardez une trace de la direction que l'utilisateur veut aller, en stockant cette information dans quatre booléens directionnelles. Aucune marche effective se passe pour le moment. La boucle de mise à jour lit ​​les valeurs directionnelles stockées dans les booléens pour faire bouger le joueur, comme le montre l'extrait de code suivant:


3. SetWalkDirection () 


Auparavant, dans la méthode onAction (), vous avez recueilli les informations de la direction dans laquelle l'utilisateur veut aller en termes de "avant" ou "gauche". La boucle de mise à jour interroge de façon répétée la rotation actuelle de la camera. Vous calculez les vecteurs réels "avant" ou "gauche" pour les projeter dans le système de coordonnées. 


Ce dernier et le plus important extrait de code va dans la méthode simpleUpdate ().


 public void simpleUpdate(float tpf) {
        camDir.set(cam.getDirection()).multLocal(0.6f);
        camLeft.set(cam.getLeft()).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());
    }
Voici comment on déclenche la marche: 


  • Initialiser le vecteur walkDirection à zéro. C'est là que vous allez stocker le sens du déplacement calculée. 
  • Ajouter à walkDirection les dernières vecteurs de mouvement que vous obtenez de la caméra. De cette façon, il est posible qu'un personnage aille de l'avant et à gauche en même temps, par exemple! 
  • Cette dernière ligne fait la «magie marche":


player.setWalkDirection(walkDirection);
Toujours utiliser setWalkDirection () pour faire déplacer un objet physique contrôlé en permanence, et le moteur physique gère la détection de collision pour vous. 

  • Faire de l'objet de la caméra à la première personne suivre le physique du joueur contrôlée:


cam.setLocation(player.getPhysicsLocation());
Important: Encore une fois, ne pas utiliser setLocalTranslation () pour faire marcher le joueur. Vous vous ferez coincé par le chevauchement avec un autre objet physique. Vous pouvez mettre le joueur dans une position de départ avec setPhysicalLocation () si vous veillez à le placer un peu au-dessus du sol et loin des obstacles.



Conclusion 



Vous avez appris comment charger un modèle «solide» physique de la scène et marcher autour d'elle avec une vue à la première personne. Vous avez appris à accélérer les calculs de physique en utilisant le CollisionShapeFactory pour créer CollisionShapes efficaces pour des géométries complexes. Vous savez comment ajouter PhysicsControls à vos géométries Collidable et vous les inscrivez à la PhysicsSpace. Vous avez également appris à utiliser player.setWalkDirection (walkDirection) pour déplacer les caractères de collision-courant, et non setLocalTranslation ().

Le terrain est un autre type de scène dans laquelle vous voulez vous promener. La prochaine fois nous allons apprendre à générer ou créer des terrains.

<< Précédent                                        Sommaire                   Suivant >>





2 commentaires: