Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    Документация jMonkeyEngine
        jMonkeyEngine Уроки и Документация
            jMonkeyEngine3: Привет мир, Обучающая Серия
                jMonkeyEngine 3 урок (13) — Hello Physics

jMonkeyEngine 3 урок (13) — Hello Physics

Опубликованно: 06.04.2017, 13:39
Последняя редакция, Andry: 02.07.2017 20:52

Предыдущий: Hello Effects, Следующий: Hello Vector3f

Вы помните, урок Hello Collision, где вы сделали модель города твердой и прогулялись по ней с видом от первого лица? Тогда вы, возможно, помните, что для моделирования физических сил, в jME3 интегрирована библиотека jBullet.

Кроме придания «твердости» моделям, наиболее распространенными случаями использования физики в 3D-играх являются:

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

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

beginner-physics

Для того, чтобы использовать примеры игровых ресурсов в вашем новом проекте в jMonkeyEngine SDK, щелкните [ПК мыши] ваш проект, выберите Свойства, перейдите в раздел Библиотеки, нажмите кнопку [Добавить библиотеку] и добавьте библиотеку jme3-test-data.

Пример кода.

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 базовый код от double1984, обновлен zathras
 */
public class HelloPhysics extends SimpleApplication {
 
  public static void main(String args[]) {
    HelloPhysics app = new HelloPhysics();
    app.start();
  }
 
  /** Подготовка физики State (jBullet) */
  private BulletAppState bulletAppState;
 
  /** Подготовка материала */
  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 {
    /** инициализация геометрии пушечных ядер */
    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));
  }
 
  @Override
  public void simpleInitApp() {
    /** Зададим физику в игру */
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
 
    /** настроим обзор сцены камерой */
    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");
    /** Инициализация сцены, геометрий, материала и physics space. */
    initMaterials();
    initWall();
    initFloor();
    initCrossHairs();
  }
 
  /**
  * Каждый раз, когда вы кликаете, срабатывает действие выстрел и создается новое пушечное ядро.  
  * Шар настроен на полет из положения камеры в направлении камеры.  
   */
  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);
    /* Создание физики пола с массой 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);
    /** Добавление кирпича с физикой в physics space. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }
 
  /** Этот метод создает отдельное ядро с физикой.
   *  По умолчанию, ядро ускоряется и летит
   *  от положения камеры по направлению камеры  .*/
   public void makeCannonBall() {
    /** Создание геометрии ядра и прикрепление его к графу сцены. */
    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);
    /**Добавление физики ядра к physics space. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Ускорение ядра с физикой при выстреливании. */
    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, и прикрепите его в менеджер AppState в SimpleApplication.

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 которые добавляют физические свойства узлам.


Создание кирпичей и ядер

Геометрии

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

  /** Подготовка геометрий и физических узлов для кирпичей и ядер. */
  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 {
    /** Инициализация геометрий ядер */
    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.

private RigidBodyControl brick_phy;

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

  • Он имеет видимую Геометрию brick_geo (Box Shape Geometry).
  • Он имеет физические свойства brick_phy (RigidBodyControl)
  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);
    /** Добавление кирпича с физикой в physics space. */
    brick_geo.addControl(brick_phy);
    bulletAppState.getPhysicsSpace().add(brick_phy);
  }

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

  1. Вы создаете Геометрию кирпича brick_geo. Геометрия описывает фигуру и внешний вид объекта.
    • Brick_geo имеет фигуру box
    • brick_geo имеет кирпичный цвет материала.
  2. Вы прикрепляете brick_geo к rootNode
  3. Вы размещаете brick_geo в loc.
  4. Вы создаете 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)
    /** Создание геометрии ядра и прикрепление его к графу сцены. */
    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);
    /** Добавление физики ядра к physics space. */
    ball_geo.addControl(ball_phy);
    bulletAppState.getPhysicsSpace().add(ball_phy);
    /** Ускорение ядра с физикой при выстреливании. */
    ball_phy.setLinearVelocity(cam.getDirection().mult(25));

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

  1. Вы создаете Геометрию шара ball_geo. Геометрия, описывает фигуру и внешний вид объекта.
    • Ball_geo имеет фигуру сферы
    • ball_geo имеет каменный цвет материала.
  2. Вы прикрепляете ball_geo к rootNode
  3. Вы размещаете ball_geo в месте расположения камеры.
  4. Вы создаете RigidBodyControl ball_phy для ball_geo.
    • Ball_phy имеет массу 1f.
    • Вы добавляете ball_phy к ball_geo.
    • Вы регистрируете ball_phy в PhysicsSpace.

Поскольку вы выстреливаете ядра, то последняя строка ускоряет ядро в направление в которое смотрит камера, до скорости 25F.


RigidBodyControl: Пол

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

  • Он имеет видимую Геометрию floor_geo (Box Shape Geometry)
  • Он имеет физические свойства 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);
    /* Создание физики пола с массой 0.0f! */
    floor_phy = new RigidBodyControl(0.0f);
    floor_geo.addControl(floor_phy);
    bulletAppState.getPhysicsSpace().add(floor_phy);
  }

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

  1. Вы создаете Геометрию пола floor_geo. Геометрия, описывает фигуру и внешний вид объекта.
    • Floor_geo имеет фигуру куба
    • floor_geo имеет материал цвета гальки.
  2. Вы прикрепляете floor_geo к rootNode
  3. Вы размещаете floor_geo немного ниже у = 0 (для предотвращения наложение с другими Spatial с PhysicControl).
  4. Вы создаете RigidBodyControl floor_phy для floor_geo.
    • floor_phy имеет массу 0f
    • Вы добавляете floor_phy к floor_geo.
    • Вы регистрируете floor_phy в PhysicsSpace.

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

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

  • initMaterial() — этот метод инициализирует все материалы, которые мы используем в этой демонстрации.
  • InitWall() — двойной цикл, который создает стену путем размещения кирпичных объектов: 15 рядов высотой, с 6 кирпичами в каждом ряду. Важно, чтобы кирпичи не накладывались друг на друга.
  • InitCrossHairs() — этот метод просто отображает знак плюс, который вы используете в качестве перекрестия для прицеливания. Обратите внимание, что элементы экранные, такие как перекрестье прикреплены к guiNode, не rootNode!
  • InitInputs() — этот метод устанавливает действие выстрел при щелчка мыши.

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


Действие стрельбы ядрами

В методе initInputs(), вы добавляете сопоставление ввода, которое запускает действие стрельбы при нажатии на [ЛК мыши].

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

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

    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 объекты могут подталкивать динамический Spatial (как пул/бильярдный шар).

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

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

Узнать больше о статическом, кинематическом, динамическом можно в расширенной документации о Физике.


Упражнения

Упражнение 1: Формы отладки

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

// Для более старых версий до JME sdk 3.0.10
bulletAppState.getPhysicsSpace().enableDebug(assetManager);

или

// Для новых версий после той
bulletAppState.setDebugEnabled(true);

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

Упражнение 2: Нет, статике

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

Упражнение 3: За занавесом

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

Популярные AAA игры используют умное сочетание физики, анимации и предварительно сформированной графики, чтобы дать вам иллюзию реального «физического мира». Подумайте о ваших любимых видеоиграх и попытайтесь определить, где и как разработчики игры обманули вас стараясь заставить поверить, в то, что вся сцена подвержена влиянию физики. Например, подумайте о здании «ломающемся» на 4-8 частей после взрыва. Куски вполне вероятно летят по строго предопределенным (так называемые кинематическим) путям и заменяются динамическими Spatial только после того, как они касаются земли … Теперь, когда вы начинаете заниматься физикой игры самостоятельно, загляните за занавес!

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


Выводы

Вы узнали, как активировать jBullet PhysicsSpace в приложении путем добавления BulletAppState. Вы создали PhysicsControl для простых Геометрий на основе базовых-Фигур (для более сложных фигур, читайте в CollisionShapes). Вы узнали, что физические объекты не только прикреплены к rootNode, но и зарегистрирован в PhysicsSpace. Вы узнали, что имеет значение, имеет ли физический объект, массу (динамический) или нет (статический). Вы узнали, что чрезмерное использование физики имеет огромное влияние на производительность компьютера.

Поздравляем! — Вы завершили последний урок для начинающих. Теперь вы готовы начать комбинировать то, чему вы научились, чтобы создать классную 3D игру самостоятельно. Покажите нам, что вы можете сделать, и не стесняйтесь делиться своими игровыми демками, видео и скриншотами на User Code & Projects Forum!

Переведено для jmonkeyengine.ru, оригинал
Автор перевода: BuGaGa

Добавить комментарий

Содержание

jMonkeyEngine.ru © 2017. Все права сохранены.