

= jMonkeyEngine 3 Урок (13) - Знакомство с Физикой

Предыдущий: <<документация/jme3_ru/начальная/знакомство_с_эффектами#,Знакомство с Эффектами>>,
Следующий: <<документация/jme3_ru#,JME 3 документация>>
Английская версия: <<jme3/beginner/hello_physics#,Hello Physics>>


Вы помните <<документация/jme3_ru/начальная/знакомство_с_столкновениям#,Знакомство с Столкновениям>> tutorial где вы сделали модель города твердой и ходил через него с видом от первого лица? Тогда вы, возможно, помните, что для моделирования физических сил, jME3 использует библиотеку link:http://jbullet.advel.cz/[jBullet]. 


Помимо “solid(твердых) моделей наиболее распространенные случаи использования физики в 3D-играх:


*  Вождение транспортных средств  с подвесами, трения шин, рампы, прыжки с дрифтом – Пример: автомобиль гонщиков
*  Качения, прыгающие шарики – Пример: теннис, бильярд, боулинг
*  Скользящие и падающие ящики – пример: Breakout, арканоид
*  Воздействие на объекты сил и гравитации - Пример: космические корабли или полет в невесомости
*  Анимация куклы, - например: “реалистичные Моделирование персонажа
*  Качающиеся маятники, веревки мостов, гибкие цепи, и многое другое…

Все эти физические свойства могут быть смоделированы в JME3. Давайте посмотрим на моделирование физических сил в этом примере, где стреляют пушки шарами по кирпичной стене.



image::jme3/beginner/beginner-physics.png[beginner-physics.png,with="360",height="291",align="center"]



““““



== Пример Кода

Благодарим double1984 за этот забавный пример!


[source,java]

----
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;

/**
 * Пример 12 - Как дать физические свойства объектам, что бы они отскакивали и падали.
 * @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();
  }

  /** Подготовить физику Application State (jBullet) */
  private BulletAppState bulletAppState;

  /** Подготовить Materials */
  Material wall_mat;
  Material stone_mat;
  Material floor_mat;

  /** Подготовка геометрии и физические узлы для кирпичей и пушечных ядер. */
  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;
  
  /** размеры, используемые для кирпича и стены */
  private static final float brickLength = 0.48f;
  private static final float brickWidth  = 0.24f;
  private static final float brickHeight = 0.12f;

  static {
    /** Инициализировать cannon ball геометрии */
    sphere = new Sphere(32, 32, 0.4f, true, false);
    sphere.setTextureMode(TextureMode.Projected);
    /** Инициализировать brick геометрии */
    box = new Box(brickLength, brickHeight, brickWidth);
    box.scaleTextureCoordinates(new Vector2f(1f, .5f));
    /** Инициализировать floor геометрии */
    floor = new Box(10f, 0.1f, 5f);
    floor.scaleTextureCoordinates(new Vector2f(3, 6));
  }

  @Override
  public void simpleInitApp() {
    /** Настроить Физику игры */
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
    
    /** Настроить cam смотреть на сцену */
    cam.setLocation(new Vector3f(0, 4f, 6f));
    cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
    /** Добавить InputManager действий: щелкнуть левой кнопкой мыши для запуска съемки. */
    inputManager.addMapping("shoot", 
            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "shoot");
    /** Инициализации сцены, материалов и физики пространства */
    initMaterials();
    initWall();
    initFloor();
    initCrossHairs();
  }

  /**
   * При каждом действии shoot срабатывает выпуск нового cannon ball.
   * ball устанавливается лететь от положения камеры в направлении камеры.
   */
  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("shoot") && !keyPressed) {
        makeCannonBall();
      }
    }
  };

  /** Инициализировать материалы, используемые в этой сцене. */
  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);
  }

  /** Сделать сплошной пол и добавляем его на сцену. */
  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);
  }

  /** Этот цикл строит стену из отдельных кирпичей. */
  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;
    }
  }

  /** Этот метод создает индивидуально физически кирпич. */
  public void makeBrick(Vector3f loc) {
    /** Создает геометрию кирпича и вложит её в граф сцены. */
    Geometry brick_geo = new Geometry("brick", box);
    brick_geo.setMaterial(wall_mat);
    rootNode.attachChild(brick_geo);
    /** Позиция геометрии кирпича  */
    brick_geo.setLocalTranslation(loc);
    /** Сделать кирпич физической массой > 0.0f. */
    brick_phy = new RigidBodyControl(2f);
    /** Добавить физику кирпича к физике космоса. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }

  /** Этот метод создает индивидуальные физические cannon ball.
   * По умолчанию ball ускоряется и летит
   * от положения камеры в направлении камеры.*/
   public void makeCannonBall() {
    /** Создать геометрию cannon ball и вложить её в граф сцены. */
    Geometry ball_geo = new Geometry("cannon ball", sphere);
    ball_geo.setMaterial(stone_mat);
    rootNode.attachChild(ball_geo);
    /** Установите пушечное ядро  */
    ball_geo.setLocalTranslation(cam.getLocation());
    /** Сделать ball с физической массой > 0.0f */
    ball_phy = new RigidBodyControl(1f);
    /** Добавить физический ball физике пространства. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Ускорить физически ball, чтобы стрелять. */
    ball_phy.setLinearVelocity(cam.getDirection().mult(25));
  }

  /** Знак " плюс " используется как прицел, чтобы помочь игроку целится.*/
  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("+");        // поддельные прицел :)
    ch.setLocalTranslation( // центр
      settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
      settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiNode.attachChild(ch);
  }
}

----

Вы должны увидеть кирпичную стену. Нажмите, чтобы пушка стреляла шарами. Смотреть кирпичи падают и отскакивают друг друга!



== Базовая Физика Приложения

В предыдущих уроках вы использовали статические Геометрий (ящики, шары, и модели), чтобы разместить их на сцене. В зависимости от их перевода, геометрии могут “парить в воздухе и даже пересекаться – на них не влияет “гравитация и они не имеют никакой физической массой. Этот урок покажет, как добавить физические свойства Геометрий.


Как всегда, начнем со стандартного com.jme3.app.SimpleApplication. Чтобы активировать физику, создадим com.jme3.bullet.BulletAppState,  и прикрепим его к SimpleApplication's AppState manager.


[source,java]

----

public class HelloPhysics extends SimpleApplication {
  private BulletAppState bulletAppState;
  
  public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    ...
  }
  ...
}
----

BulletAppState дает игре доступ к PhysicsSpace. PhysicsSpace позволяет использовать com.jme3.bullet.control.PhysicsControls который добавляет физические свойства Узлу.



== Создание кирпичей и пушечных ядер


=== Геометрии

В этом примере “стрельбы по стене можно использовании геометрических фигуры пушечные ядра и кирпича.  Геометрии содержат сетки, Фигур. Давайте создадим и инициализируем некоторые Фигуры: Коробки и шары.


[source,java]

----

  /** Подготовка геометрии и физических узлов для кирпича и пушечных ядер. */
  private static final Box    box;
  private static final Sphere sphere;
  private static final Box    floor;
  /** размеры, используемые для кирпича и стены */
  private static final float brickLength = 0.48f;
  private static final float brickWidth  = 0.24f;
  private static final float brickHeight = 0.12f;
  static {
    /** Инициализировать геометрии cannon ball */
    sphere = new Sphere(32, 32, 0.4f, true, false);
    sphere.setTextureMode(TextureMode.Projected);
    /** Инициализировать геометрии кирпичей */
    box = new Box(brickLength, brickHeight, brickWidth);
    box.scaleTextureCoordinates(new Vector2f(1f, .5f));
    /** Инициализировать геометрию пола */
    floor = new Box(10f, 0.1f, 5f);
    floor.scaleTextureCoordinates(new Vector2f(3, 6));
  }
----


=== RigidBodyControl: Кирпич

Мы хотим создать геометрии кирпичей из тех коробок. Для каждой геометрии с физическими свойствам, создадим RigidBodyControl.


[source,java]

----

  private RigidBodyControl brick_phy;

----

Пользовательский метод `makeBrick(loc)` создает отдельные кирпичи на месте `loc`. Кирпич имеет следующие свойства:


*  Он имеет видимую геометрию `brick_geo` (Box Shape Geometry).
*  Он имеет физические свойства `brick_phy` (RigidBodyControl)

[source,java]

----

  public void makeBrick(Vector3f loc) {
    /** Создаем геометрии кирпича и вложить их в граф сцены. */
    Geometry brick_geo = new Geometry("brick", box);
    brick_geo.setMaterial(wall_mat);
    rootNode.attachChild(brick_geo);
    /** Расположим геометрию кирпича  */
    brick_geo.setLocalTranslation(loc);
    /** Дадим кирпичу физическую массу > 0.0f. */
    brick_phy = new RigidBodyControl(2f);
    /** Добавим физику кирпича физику пространства. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }
----

Этот пример кода выполняет следующие действия:


.  Вы создаете геометрию кирпича brick_geo. Геометрия описывает фигуру и внешний вид объекта.
**  brick_geo есть поле фигуры
**  brick_geo имеет материал кирпичного цвета.

.  Вы прикрепите brick_geo к rootNode
.  Вы расположите brick_geo на `loc`. 
.  Вы создадите RigidBodyControl brick_phy для brick_geo.
**  brick_phy имеет массу 2f.
**  Добавить brick_phy к brick_geo.
**  Вы регистрируете brick_phy в PhysicsSpace.



=== RigidBodyControl: Пушечное ядро

Можно заметить, что пушечное ядро создается таким же образом, с помощью пользовательского метода `makeCannonBall()`. Пушечное ядро имеет следующие свойства:


*  Оно имеет видимую геометрию `ball_geo` (Sphere Shape Geometry)
*  Оно имеет физические свойства `ball_phy` (RigidBodyControl)

[source,java]

----

    /** Создаем геометрию cannon ball и вложим её в граф сцены. */
    Geometry ball_geo = new Geometry("cannon ball", sphere);
    ball_geo.setMaterial(stone_mat);
    rootNode.attachChild(ball_geo);
    /** Расположим пушечное ядро  */
    ball_geo.setLocalTranslation(cam.getLocation());
    /** Дадим шару физическую массу > 0.0f */
    ball_phy = new RigidBodyControl(1f);
    /** Добавим физику шара физике пространства. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Ускорим физически шар, чтобы выстрелить им. */
    ball_phy.setLinearVelocity(cam.getDirection().mult(25));
    
----

Этот пример кода выполняет следующие действия:


.  Вы создаете геометрию шара ball_geo. Геометрия описывает фигуру и внешний вид объекта.
**  ball_geo имеет сферическую фигуру
**  ball_geo имеет цвета материала камень.

.  Вы прикрепите ball_geo to the rootNode 
.  You position ball_geo на расположение камеры. 
.  Вы создадите RigidBodyControl ball_phy для ball_geo.
**  ball_phy имеет массу 1f.
**  Добавить ball_phy к ball_geo.
**  Вы регистрируете ball_phy в PhysicsSpace.


Так как вы снимаете пушечные ядра, последняя строка ускоряет мяч в направлении, куда смотрит камера, со скоростью 25f.



=== RigidBodyControl: Пол

(Статический) пол имеет одно важное отличие по сравнению с (динамическими) кирпичами и пушечными ядрами: *Статические объекты имеют массу ноль.*
Как и раньше, нужно написать пользовательскую метод `initFloor()` который создает плоский box с текстурой rock которые можно использовать в качестве пола. Пол имеет следующие свойства:


*  Он имеет видимую геометрию `floor_geo` (Box Shape Geometry)
*  Он имеет физические свойства `floor_phy` (RigidBodyControl)

[source,java]

----

  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);
    /* Сделать пол с физической массой 0.0f! */
    floor_phy = new RigidBodyControl(0.0f);
    floor_geo.addControl(floor_phy);
    bulletAppState.getPhysicsSpace().add(floor_phy);
  }
----

Этот пример кода выполняет следующие действия:


.  Вы создаете floor_geo геометрию пола. Объект Geometry описывает фигуру и вид объекта.
**  floor_geo имеет фигуру box
**  floor_geo имеет материал цвета гальки.

.  Вы прикрепите floor_geo к rootNode 
.  Вы прикрепите floor_geo немного ниже y=0 (для предотвращения наложения на другие PhysicControl'ed Spatials). 
.  Вы создаете RigidBodyControl floor_phy для floor_geo.
**  floor_phy имеет массу 0f emoji:
**  Добавить floor_phy к floor_geo.
**  Вы регистрируете floor_phy к PhysicsSpace.



== Создание сцены

Давайте кратко рассмотрим пользовательские вспомогательные методы:


*  `initMaterial()` – Этот метод инициализирует все материалы, которые мы используем в этой демо.
*  `initWall()` – Двойной цикл, создает стену, раскладывая объекты кирпичи: 15 рядов в высоту по 6 кирпичей в ряду. Важно, чтобы пространство кирпичей, не пересекались друг с другом.
*  `initCrossHairs()` – Этот метод просто выводит знак плюс, который можно использовать как прицел для прицеливания. Обратите внимание, что элементы экрана, такие как прицелы крепятся к `guiNode`, а не `rootNode`!
*  `initInputs()` – Этот метод устанавливает действие Нажми и стреляй.

Эти методы, каждый вызывается один раз из `simpleInitApp()` метод в начале игры. Как вы видите, вы можете написать любое количество пользовательских методов для создания сцены вашей игры.



== Пушечное Ядро Действие Стрельба

В метод `initInputs()` вы добавить input mapping которая будет вызывать действие стрелять, когда нажата левая кнопка мыши.


[source,java]

----

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

Вы задаете действие стрельба новым пушечным ядром следующим образом:


[source,java]

----

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

В данный момент пушечное ядро появляется на сцене,  она улетает со скоростью (и в направлении), которое вы указали при помощи `setLinearVelocity()` внутри `makeCannonBall()`. Вновь созданное пушечное ядро летит, попадает в стену, и оказывает физическую силу, которые оказывают влияние на отдельные кирпичи.



== Перемещение Spatial Физически

Расположение динамического Spatial контролируется его RigidBodyControl. Переместим RigidBodyControl для перемещения Spatial. Если это динамический PhysicsControl, можно использовать setLinearVelocity() и применить к нему момент силы. Другие RigidBodyControl'led объекты вокруг могут подтолкнуть динамический Spatial (like pool/бильярдные шары).


Вы можете сделать Spatials, которые не будет динамическими: Включите RigidBodyControl к setKinematic(true) чтобы он двигаться вместе с его Spatial.


*  Кинематические значит они не зависят от сил или силы тяжести, что означает, что они могут парить в воздухе и не могут быть отброшены в сторону динамическими “пушечным ядром и т.п.
*  Кинематический RigidBody имеет массу.
*  Кинематические можно перемещать и может оказывать силы на динамические RigidBodys. Это означает, что вы можете использовать кинематический узел, как бильярдный кий или дистанционно управляемый таран.

Узнать больше о статических и кинематических и динамических в <<jme3/advanced/physics#,дополнительно физике doc>>.



== Упражнения


=== Упражнение 1: Отладка Фигур

Добавьте следующую строку после bulletAppState инициализации.


[source,java]

----
bulletAppState.getPhysicsSpace().enableDebug(assetManager);
----

Теперь вы видите collisionShapes кирпича и сферах, и пол подсвечен.



=== Упражнение 2: No Mo' Static

Что произойдет, если вы дадите статическому узлу, такому как пол, массу более 0.0f?



=== Упражнение 3: За Кулисами

Заполните вашу сцену с стенами, кирпичей и пушечными ядрами. Когда вы начинаете видеть влияние на производительность?


Популярные ААА игры используют умное сочетание физики, анимации и заранее прорисованной графики, чтобы дать вам иллюзию реального, “физического мира. Подумайте о ваших любимых видео игр и попробуйте определить, где и как гейм-дизайнеры обмануть вас так что вы поверили в то, что вся сцена является физической. Например думаю, что “ломая на 4-8 частей здание после взрыва. Скорее всего куски летать на заранее (так называемый кинематическом) пути и являются только заменена динамической Spatials после того, как они касаются земли… Теперь, когда вы начинаете самостоятельно реализовать игровую физику, вы заглянете за занавес!


Использование физику везде в игре звучит как классная идея, но ей легко злоупотребить. Хотя узлы физики помещаются в “спящий режим, когда они не двигаются, создавая мир исключительно из узлов динамической физики вы быстро придете к пределу возможностей вашего компьютера.



== Вывод

Вы узнали, как активировать jBullet PhysicsSpace в приложение путем добавления `BulletAppState`. Вы создали PhysicsControls для простой фигуры на основе геометрий (для более сложных фигур, прочитать о <<jme3/advanced/physics#,CollisionShapes>>). Вы узнали, что физические объекты не только прикреплены к RootNode, но и зарегистрирован в PhysicsSpace. Вы знаете, что есть разница, будет ли физический объект иметь массу (динамический) или нет (статический). Вы в курсе, что чрезмерное увлечение физика оказывает огромное влияние на производительность.


–<<jme3#,сочетать то, что вы узнали>>link:http://jmonkeyengine.org/groups/free-announcements/forum/[Free Announcements Forum]

<tags><tag target="beginner" /><tag target="intro" /><tag target="physics" /><tag target="documentation" /><tag target="input" /><tag target="model" /><tag target="control" /></tags>
