Tutorial 13 - Hello Physics

jMonkeyEngine 3 Tutorial (13) - Hello Physics

Anterior: Hello Effects, Próximo: documentação JME 3

Você se lembra do tutorial Hello Collision onde você fez o modelo de uma cidade sólido e caminhou através dele em uma perspectiva de primeira pessoa? Então você pode lembrar que, para a simulação de forças físicas, jME3 integra a biblioteca jBullet.

Exceto fazer modelos "sólidos" ("solid"), os casos de uso mais comum oara física em jogos 3D são:

  • Dirigir veículos com suspensão, fricção de pneu, pulando na rampa, a deriva (drifting) – Exemplo: carros de corrida
  • Rolando e balançando bolas - Exemplo: pong, bilhar, boliche
  • Caixas de deslizar e cair – Exemplo: Breakout, Arkanoid
  • Expor objetos para força e gravidade – Exemplo: espaçonaves ou vôo a zero-g
  • Animando bonecas de pano (ragdolls) – Exemplo: simulações de personagem "realísticos"
  • Balançando pêndulos, pontes de corda, cadeias flexíveis, e muito mais…

Todas estas propriedades físicas podem ser simuladas em JME3. Vamos dar uma olhada na simulação de forças físicas neste exemplo onde você atira bolas de canhão em uma parede de tijolos.

Para usar os ativos de exemplo em um novo projeto do SDK da jMonkeyEngine, dê um clique com o botão direito em seu projeto, selecione "Propriedades" ("Properties"), vá para "Bibliotecas" ("Libraries"), pressione "Adicionar Biblioteca" e adicione a biblioteca "jme3-test-data".

Código de Amostra

Obrigado a ouble1984 por contribuir com esta amostra divertida!

package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;

/**
 * Example 12 - how to give objects physical properties so they bounce and fall.
 * @author base code by double1984, updated by zathras
 */
public class HelloPhysics extends SimpleApplication {

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

  /** Prepare the Physics Application State (jBullet) */
  private BulletAppState bulletAppState;

  /** Prepare Materials */
  Material wall_mat;
  Material stone_mat;
  Material floor_mat;

  /** Prepare geometries and physical nodes for bricks and cannon balls. */
  private RigidBodyControl    brick_phy;
  private static final Box    box;
  private RigidBodyControl    ball_phy;
  private static final Sphere sphere;
  private RigidBodyControl    floor_phy;
  private static final Box    floor;

  /** dimensions used for bricks and wall */
  private static final float brickLength = 0.48f;
  private static final float brickWidth  = 0.24f;
  private static final float brickHeight = 0.12f;

  static {
    /** Initialize the cannon ball geometry */
    sphere = new Sphere(32, 32, 0.4f, true, false);
    sphere.setTextureMode(TextureMode.Projected);
    /** Initialize the brick geometry */
    box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
    box.scaleTextureCoordinates(new Vector2f(1f, .5f));
    /** Initialize the floor geometry */
    floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
    floor.scaleTextureCoordinates(new Vector2f(3, 6));
  }

  @Override
  public void simpleInitApp() {
    /** Set up Physics Game */
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);

    /** Configure cam to look at scene */
    cam.setLocation(new Vector3f(0, 4f, 6f));
    cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
    /** Add InputManager action: Left click triggers shooting. */
    inputManager.addMapping("shoot", 
            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "shoot");
    /** Initialize the scene, materials, and physics space */
    initMaterials();
    initWall();
    initFloor();
    initCrossHairs();
  }

  /**
   * Every time the shoot action is triggered, a new cannon ball is produced.
   * The ball is set up to fly from the camera position in the camera direction.
   */
  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("shoot") && !keyPressed) {
        makeCannonBall();
      }
    }
  };

  /** Initialize the materials used in this scene. */
  public void initMaterials() {
    wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
    key.setGenerateMips(true);
    Texture tex = assetManager.loadTexture(key);
    wall_mat.setTexture("ColorMap", tex);

    stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
    key2.setGenerateMips(true);
    Texture tex2 = assetManager.loadTexture(key2);
    stone_mat.setTexture("ColorMap", tex2);

    floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
    key3.setGenerateMips(true);
    Texture tex3 = assetManager.loadTexture(key3);
    tex3.setWrap(WrapMode.Repeat);
    floor_mat.setTexture("ColorMap", tex3);
  }

  /** Make a solid floor and add it to the scene. */
  public void initFloor() {
    Geometry floor_geo = new Geometry("Floor", floor);
    floor_geo.setMaterial(floor_mat);
    floor_geo.setLocalTranslation(0, -0.1f, 0);
    this.rootNode.attachChild(floor_geo);
    /* Make the floor physical with mass 0.0f! */
    floor_phy = new RigidBodyControl(0.0f);
    floor_geo.addControl(floor_phy);
    bulletAppState.getPhysicsSpace().add(floor_phy);
  }

  /** This loop builds a wall out of individual bricks. */
  public void initWall() {
    float startpt = brickLength / 4;
    float height = 0;
    for (int j = 0; j < 15; j++) {
      for (int i = 0; i < 6; i++) {
        Vector3f vt =
         new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
        makeBrick(vt);
      }
      startpt = -startpt;
      height += 2 * brickHeight;
    }
  }

  /** This method creates one individual physical brick. */
  public void makeBrick(Vector3f loc) {
    /** Create a brick geometry and attach to scene graph. */
    Geometry brick_geo = new Geometry("brick", box);
    brick_geo.setMaterial(wall_mat);
    rootNode.attachChild(brick_geo);
    /** Position the brick geometry  */
    brick_geo.setLocalTranslation(loc);
    /** Make brick physical with a mass > 0.0f. */
    brick_phy = new RigidBodyControl(2f);
    /** Add physical brick to physics space. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }

  /** This method creates one individual physical cannon ball.
   * By defaul, the ball is accelerated and flies
   * from the camera position in the camera direction.*/
   public void makeCannonBall() {
    /** Create a cannon ball geometry and attach to scene graph. */
    Geometry ball_geo = new Geometry("cannon ball", sphere);
    ball_geo.setMaterial(stone_mat);
    rootNode.attachChild(ball_geo);
    /** Position the cannon ball  */
    ball_geo.setLocalTranslation(cam.getLocation());
    /** Make the ball physcial with a mass > 0.0f */
    ball_phy = new RigidBodyControl(1f);
    /** Add physical ball to physics space. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Accelerate the physcial ball to shoot it. */
    ball_phy.setLinearVelocity(cam.getDirection().mult(25));
  }

  /** A plus sign used as crosshairs to help the player with aiming.*/
  protected void initCrossHairs() {
    guiNode.detachAllChildren();
    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
    BitmapText ch = new BitmapText(guiFont, false);
    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
    ch.setText("+");        // fake crosshairs :)
    ch.setLocalTranslation( // center
      settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
      settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiNode.attachChild(ch);
  }
}

Você deveria ver uma parede de tijolos. Clique para disparar bolas de canhão. Assista os tijolos cairem e balançarem um e outro!

Uma Aplicação Física Básica

Nos tutoriais anteriores você usou Geometrias (Geometries0 estáticas (caixas, esferas e modelos) que você colocava na cena. Dependendo da translação delas, Geometrias (Geometries) podem "flutuar no meio do ar" e mesmo se sobrepor – elas não são afetadas pela "gravidade" e não têm massa física. Este tutorial mostra como adicionar propriedades físicas para Geometrias (Geometries).

Como sempre, inicie com uma com.jme3.app.SimpleApplication padrão. Para ativar física crie um com.jme3.bullet.BulletAppState, e ligue ela ao gerenciador de AppState da SimpleApplication.

public class HelloPhysics extends SimpleApplication {
  private BulletAppState bulletAppState;

  public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    ...
  }
  ...
}

A BulletAppState dá ao jogo acesso a um Espaço Físico (PhysicsSpace). O Espaço Físico (PhysicsSpace) deixa você usar com.jme3.bullet.control.PhysicsControls que adicionam propriedades físicas para Nós (Nodes).

Ctiando Bolas de Canhão e Tijolos

Geometrias

Neste exemplo "atire na parede" você usa Geometrias (Geometries) como bolas e tijolos. Geometrias contém malhas, tais como Formas (Shapes). Vamos criar e inicializar algumas Formas: Caixas e Esferas.

  /** Prepare geometries and physical nodes for bricks and cannon balls. */
  private static final Box    box;
  private static final Sphere sphere;
  private static final Box    floor;
  /** dimensions used for bricks and wall */
  private static final float brickLength = 0.48f;
  private static final float brickWidth  = 0.24f;
  private static final float brickHeight = 0.12f;
  static {
    /** Initialize the cannon ball geometry */
    sphere = new Sphere(32, 32, 0.4f, true, false);
    sphere.setTextureMode(TextureMode.Projected);
    /** Initialize the brick geometry */
    box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
    box.scaleTextureCoordinates(new Vector2f(1f, .5f));
    /** Initialize the floor geometry */
    floor = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
    floor.scaleTextureCoordinates(new Vector2f(3, 6));
  }

Controle de Corpo Rígido (RigidBodyControl): Tijolos

Nós queremos Geometrias (Geometries) de tijolos daquelas caixas. Para cada Geomeotria (Geometry) com propriedades físicas você cria um Controle de Corpo Rígido (RigidBodyControl).

  private RigidBodyControl brick_phy;

Os métodos customziados makeBrick(loc) criam tijolos individuais no local loc. Um tijolo tem as seguintes propriedades:

  • Ele tem uma Geometria (Geometry) visível brick_geo (Geometria de Forma de Caixa).
  • Ele tem propriedades físicas brick_phy (RigidBodyControl)
  public void makeBrick(Vector3f loc) {
    /** Create a brick geometry and attach to scene graph. */
    Geometry brick_geo = new Geometry("brick", box);
    brick_geo.setMaterial(wall_mat);
    rootNode.attachChild(brick_geo);
    /** Position the brick geometry  */
    brick_geo.setLocalTranslation(loc);
    /** Make brick physical with a mass > 0.0f. */
    brick_phy = new RigidBodyControl(2f);
    /** Add physical brick to physics space. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }

Estas amostra de código faz o seguinte:

  • Você cria um Geometria (Geometry) do tijolo brick_geo. Uma Geometria (Geometry) descreve a forma e a aparência de um objeto.
    • brick_geo tem uma forma de caixa
    • brick_geo tem um material colorido de tijlo.
  • Você anexa brick_geo para o nó raiz (rootNode_
  • Você posiciona brick_geo em loc.
  • Você cria um RigidBodyControl brick_phy para brick_geo.
    • brick_phy tem uma massa de 2f.
    • Você adiciona brick_phy para brick_geo.
    • Você registra brick_phy para o Espaço Físico PhysicsSpace.

RigidBodyControl: Bola de Canhão

Você nota que a bola do canhão é criada na mesma amneira usando o método personalizado makeCannonBall(). A bola de canhão tem as seguintes propriedades:

  • Ela tem uma Geometria (Geometry) visível ball_geo (Geometria de Forma Esfera)
  • Ela tem propriedades físicas ball_phy (RigidBodyControl)
    /** Create a cannon ball geometry and attach to scene graph. */
    Geometry ball_geo = new Geometry("cannon ball", sphere);
    ball_geo.setMaterial(stone_mat);
    rootNode.attachChild(ball_geo);
    /** Position the cannon ball  */
    ball_geo.setLocalTranslation(cam.getLocation());
    /** Make the ball physcial with a mass > 0.0f */
    ball_phy = new RigidBodyControl(1f);
    /** Add physical ball to physics space. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Accelerate the physcial ball to shoot it. */
    ball_phy.setLinearVelocity(cam.getDirection().mult(25));

Esta amostra de código faz o seguinte:

  • Você cria uma Geometria (Geometry) de tijolo ball_geo. Uma Geometria (Geometry) descreve a forma e aparência de um objeto.
    • ball_geo tem uma forma esfera
    • ball_geo tem um material cor de pedra.
  • Você anexa ball_geo para o nó raiz (rootNode)
  • Você posiciona ball_geo na localização da câmera
  • Você cria um RigidBodyControl ball_phy para ball_geo.
    • ball_phy tem uma massa de 1f.
    • Você adiciona ball_phy para ball_geo.
    • Você registra ball_phy para o Espaço Físico (PhysicsSpace).

Desde que você está disparando bolas de canhão, a última linha acalera a bola na direção que a câmera está olhando, com uma velocidade de 25f.

RigidBodyControl: Chão

O chão (estático) tem uma importante diferença comparado aos tijolos (dinâmicos) e bolas de canhão: Objetos estáticos têm uma massa zero. Como antes, você escreve um método initFloor() customizado que cria uma caixa plana com uma textura de rocha que você usa como chão. O chão tem as seguintes propriedades:

  • Ele tem uma Geometria (Geometry) visível floor_geo (Geometria de Forma Caixa)
  • Ele tem propriedades físicas floor_phy (RigidBodyControl)
  public void initFloor() {
    Geometry floor_geo = new Geometry("Floor", floor);
    floor_geo.setMaterial(floor_mat);
    floor_geo.setLocalTranslation(0, -0.1f, 0);
    this.rootNode.attachChild(floor_geo);
    /* Make the floor physical with mass 0.0f! */
    floor_phy = new RigidBodyControl(0.0f);
    floor_geo.addControl(floor_phy);
    bulletAppState.getPhysicsSpace().add(floor_phy);
  }

Esta amostra de código faz o seguinte:

  • Você cria uma Geometria (Geometry) de chão floor_geo. Uma Geometria (Geometry) descreve a forma e a aparência de um objeto.
    • floor_geo tem uma forma de caixa
    • floor_geo tem um material cor de cascalho.
  • Você anexa floor_geo para o nó raiz (rootNode)
  • Você posiciona floor_geo um pouco abaixo y=0 (para prevenir sobreposição com outros Espaciais Controlados por Física).
  • Você cria um RigidBodyControl floor_phy para floor_geo.
    • floor_phy tem uma massa de 0f :!:
    • Você adiciona floor_phy para floor_geo.
    • Você registra floor_phy para o Espaço Físico (PhysicsSpace_.

Criando a Cena

Vamos dar uma olhada nos métodos prestativos customizados:

  • initMaterial() – Este método inicializa todos os materiais que nós usamos neste demo.
  • initWall() – um loop duplo que gera uma parede por posicionar objetos tijolos: 15 linhas de altura com 6 tijolos por linha. É importante dar um espaço para os tijolos físicos para que eles não se sobreponham.
  • initCrossHairs() – Este método simplesmente exibe um sinal de mais que você usa como crosshairs para mirar. Note que elementos da tela como crosshairs são anexados para o nó da GUI (guiNode), não para o nó raiz (rootNode)!
  • initInputs() – Este método configura a ação clique para disparar.

Cada um destes métodos é chamado uma vez no método simpleInitApp() no início do jogo. Como você vê, você pode escrever qualquer número de métodos customizados para configurar a cena do seu jogo.

A ação de Disparar Bola de Canhão

No método initInputs(), você adiciona um mapeamento de entrada que ativa uma ação de disparar quando o botão esquerdo do mouse é pressionado.

  private void initInputs() {
    inputManager.addMapping("shoot", 
            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "shoot");
  }

Você define a ação verdadeira de disparar uma bola de canhão como se segue:

    private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean keyPressed, float tpf) {
            if (name.equals("shoot") && !keyPressed) {
                makeCannonBall();
            }
        }
    };

No momento em que a bola de canhão aparece na cena ela voa com a velocidade (e a direção) que você especificou usando setLinearVelocity() dentro de makeCannonBall(). A bola de canhão recém criada voa, atinge a parede e exerce uma força que impacta os tijolos individualmente.

Movendo um Espacial Físico

A localização de espaciais dinâmicos é controlada por seu RigidBodyControl. Mova o RigidBodyControl para mover o Espacial. Se é um PhysicsControl dinâmico você pode usar setLinearVelocity() e aplicar forças e torques para ele. Outros objetos Controlados por Corpo Rígidos (RigidBodyControl) podem empurrar o Espacial (Spatial) dinãmico ao redor (como bolas de bilhar).

Você pode fazer Espaciais que não são dinãmicos: Mude o RigidBodyControl para setKinematic(true) para que ele se mova junto de seu Espacial (Spatial).

  • Um cinemático não é afetado pelas forças nem pela gravidade, o que significa que ele pode flutuar no meio de ar e não pode ser empurrado pelas "bolas de canhão" dinâmicas etc.
  • Um Corpo Rígido (RigidBody) cinemático tem uma massa.
  • Um cinemático pode ser movido e pode exercer forças em Corpos Rígidos (RigidBodys) dinâmicos. Isto significa que você pode usar um nó cinemático como uma fila de bilhar ou um ariete controlado remotamente.

Aprenda mais sobre estático versus cinemático versus dinâmico no documento de física avançada.

Exercícios

Exerício 1: Formas de Debug

Adicione a seguinte linha depois da inicialização de bulletAppState.

bulletAppState.getPhysicsSpace().enableDebug(assetManager);

Agora você vê as Formas de colisão (collisionShapes) dos tijolos e das esferas, e o chão destacado.

Exercício 2: Sem Mo estático

O que acontece se você dar a um nó estático como o chão, uma massa de mais que 0.0f?

Exerício 3: Atrás das Cortinas

Preencha suas cenas com paredes, tijolos e bolas de canhão. Quando você inicia a ver um impacto de desempenho?

Jogos AAA populares usam uma mistura inteligente de física, animação e gráficos pré-renderizados para dar a você a ilusão de mundo "físico", real. Pense de seus video games favoritos e tente encontrar onde e como os projetistas do jogo enganam você em acreditar que a cena inteira é física. Por exemplo, pense de um edifício se "partindo" de 4-8 partes depois de uma explosão. As peças que mais provavelmente voam em caminhos pré-definidos (assim chamados cinemáticos) e que são somente substituídos por Espaciais dinâmicos depois que eles tocam o chão… Agora que você iniciar a implementar um jogo físico por si mesmo, olhe atrás das cortinas!

Usar física em todo lugar em um jogo parece uma idéia legal, mas ela é facilmente exagerada. Embora os nós físicos são postos para "dormir" quando eles não estão se movendo, criar um mundo unicamente de nós físicos rapidamente trará você aos limites das capacidades de seu computador.

Conclusão

Você aprendeu a como ativar o Espaço Físico (PhysicsSpace) da jBullet em uma aplicação por adicionar um BulletAppState. Você criou PhysicsControls para simpels Geometrias baseadas em Forma (para formas mais complexas leia sobre CollisionShapes). Voc~e aprendey qye objetos físicos não são somente anexados para o nó raiz (rootNode), mas também registrados para o Espaço Físico (PhysicsSpace). Você sabe que faz diferença se um objeto físico tem uma massa (dinâmico) ou não (estático). Você está consciente que "super-usar" físicas tem um enorme impacto de desempenho.

parabéns! - Você completou o último tutorial de iniciante. Agora você está pronto para iniciar a combinar o que você aprendeu, para criar um jogo legal 3D por si mesmo. Mostre-nos o que você pode fazer e sinta-se livre para compartilhar seus demos, vídeos de jogos, e instantâneos no fórum de Anúncios Livres (Free Announcements)!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-Share Alike 2.5 License.