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

jMonkeyEngine 3 урок (9) — Hello Collision

Опубликованно: 05.04.2017, 14:57
Последняя редакция, Andry: 30.06.2017 21:11

Предыдущий:Hello Picking, Следующий: Hello Terrain

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

beginner-scene

Пример кода

Если у вас его еще нет то загрузите образец сцены town.zip.

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

Поместите town.zip в корневую папку вашего проекта JME3. Вот код:

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;
 
/** Пример 9 - Как сделать стены и полы твердыми.
  * Этот код столкновения используется Физика и Action Listener.
  * @author normen, отредактирован 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;
 
// Временные векторы, используемые в каждом кадре. 
// Они здесь, чтобы избежать создания новых векторов в каждом кадре
  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() {
    /** Настроим физику */
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
 
    // Мы повторно используем летающую камеру для поворотов, в то 
    // время как положение в пространстве управляется физикой
    viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
    flyCam.setMoveSpeed(100);
    setUpKeys();
    setUpLight();
 
    // Загружаем сцену из архива и настраиваем её размер.
    assetManager.registerLocator("town.zip", ZipLocator.class);
    sceneModel = assetManager.loadModel("main.scene");
    sceneModel.setLocalScale(2f);
 
    // Мы устанавливаем обнаружение столкновений для сцены путем создания сложной 
    // фигуру столкновения и статического RigidBodyControl с массой, равной нулю.
    CollisionShape sceneShape =
            CollisionShapeFactory.createMeshShape(sceneModel);
    landscape = new RigidBodyControl(sceneShape, 0);
    sceneModel.addControl(landscape);
 
   // Мы устанавливаем обнаружение столкновений для player путем создания 
   // фигуры столкновения капсулы и CharacterControl. 
   // CharacterControl предоставляет дополнительные настройки 
   // для размер, высоты шага, прыжков, падения, и гравитации. 
   // Мы также устанавливаем player в исходное положение.
    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));
 
    // Прикрепляем сцену и player к RootNode и пространству с физикой(physics space), 
    // чтобы они появились в игровом мире.

    rootNode.attachChild(sceneModel);
    bulletAppState.getPhysicsSpace().add(landscape);
    bulletAppState.getPhysicsSpace().add(player);
  }
 
  private void setUpLight() {
    // Мы добавляем свет, чтобы сцена стала видна
    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);
  }
 
/** Мы перезапишем некоторые сопоставления навигационных клавиш здесь, таки образом мы
   * добавим контролируемую физикой ходьбу и прыжки: */
  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");
  }
 
/** Это наши пользовательские действия, вызываемые нажатием на клавиши.
* Мы еще не ходим, мы просто отслеживаем направление, которое пользователь нажал */
  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(); }
    }
  }
 
  /**
* Это главный цикл обработки событий - здесь происходит ходьба. 
* Мы проверяем, в каком направлении игрок идет интерпретируя 
* направление камеры вперед (camDir) и сторону (camLeft). 
* Команда setWalkDirection() позволяет player осуществлять контролируемому физикой ходьбу . 
* Мы также следим за тем, что бы камера перемещается вместе с 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());
    }
}

Запустите пример. Вы должны увидеть городскую площадь с домами и памятник. Используйте клавиши WASD и мышь, чтобы перемещаться с видом от первого лица. Бегите вперед и прыгайте, нажав W и Пробел. Обратите внимание, как вы переходите через тротуару, и поднимаетесь по ступенькам к памятнику. Вы можете ходить по переулкам между домами, но стены являются твердыми и непроходимыми. Не стоит ходить по краю мира! 😩


Понимание кода

Давайте начнем с объявления класса:

public class HelloCollision extends SimpleApplication
        implements ActionListener { ... }

Вы уже знаете, что SimpleApplication является базовым классом для всех игр jME3. Этот класс реализует интерфейс 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;
 
  // Временные векторы, используемые в каждом кадре. 
  // Они здесь, чтобы избежать создания новых векторов в каждом кадре
  private Vector3f camDir = new Vector3f();
  private Vector3f camLeft = new Vector3f();

Вы инициализируете несколько полей уровнем доступа private:

  • BulletAppState предоставляет этому SimpleApplication доступ к функциям физики (например, обнаружение столкновений), предоставляемым в jME3 интегрированным в неё jBullet.
  • Spatial sceneModel предназначен для загрузки OgreXML модели города.
  • Вам нужен RigidBodyControl, чтобы придать модели города твердость.
  • (Невидимый) игрок с видом от первого лица представлен объектом CharacterControl.
  • Поля walkDirection и четыре Boolean используются для контролируемого физикой движения.
  • camDir и camLeft временные векторы, используемые позже при вычислении walkingDirection из положения и поворота камеры

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


Инициализация игры

Как обычно, вы инициализируете игру в методе simpleInitApp().

    viewPort.setBackgroundColor(new ColorRGBA(0.7f,0.8f,1f,1f));
    flyCam.setMoveSpeed(100);
    setUpKeys();
    setUpLight();
  1. Вы устанавливаете цвет фона сцены на светло-голубой, так как это будет небо в сцене.
  2. Вы переориентируете управление камерой по умолчанию flyCam на камеру от первого лица и устанавливаете ее скорость.
  3. Вспомогательный метод setUpLights() добавляет источники света.
  4. Вспомогательный метод setUpKeys() настраивает сопоставления ввода, мы будем рассматривать его него позже.

Физика Сцены

Первое, что вы делаете в каждой игре с физикой, это создаете объект BulletAppState. Это дает вам доступ к интегрированному в jME3 jBullet, который обрабатывает физические силы и столкновения.

    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);

Для сцены, вы загрузите sceneModel из архива, и настраиваете её размер.

    assetManager.registerLocator("town.zip", ZipLocator.class);
    sceneModel = assetManager.loadModel("main.scene");
    sceneModel.setLocalScale(2f);

Файл town.zip включен в качестве модели образца в исходники JME3 — вы можете скачать его здесь. (При желании, можете использовать любую свою OgreXML сцену) В случае с этим образцом, поместите zip файл в каталоге верхнего уровня приложения (то есть, рядом с src/, assets/, build.xml).

    CollisionShape sceneShape =
      CollisionShapeFactory.createMeshShape((Node) sceneModel);
    landscape = new RigidBodyControl(sceneShape, 0);
    sceneModel.addControl(landscape);
    rootNode.attachChild(sceneModel);

Чтобы использовать обнаружение столкновений, вы добавляете RigidBodyControl к Spatial sceneModel. RigidBodyControl для сложной модели принимает два аргумента: Collision Shape и массу объекта.

  • JME3 предлагает CollisionShapeFactory, который предварительно вычисляет столкновения фигур с точной сеткой для Spatial. Вы выбираете, сгенерировать CompoundCollisionShape (который имеет MeshCollisionShapes в качестве своего потомка), так как этот тип столкновения фигур является оптимальным для неподвижных объектов, таких как рельеф местности, дома и целых уровней шутеров.
  • Вы устанавливаете массу равную нулю, так как сцена статична и ее масса не имеет значения.
  • Добавьте элемент control в Spatial, чтобы придать ему физические свойства.
  • Как всегда, прикрепите sceneModel к RootNode, чтобы сделать его видимым.
Не забудьте добавить источник света, чтобы вы могли видеть сцену.

Физика игрока

Игрок, от первого лица как правило, невидим. Когда вы используете flyCam по умолчанию в качестве камеры от первого лица, она даже не проверяет наличие столкновений и просто прохождения сквозь стены. Это происходит потому, что control-у flyCam не заданна фигура с физикой. В этом примере кода, вы представляете игрока от первого лица, как (невидимую) фигуру с физикой. Вы можете использовать клавиши WASD, чтобы управлять этой фигурой с физикой, в то время как движок физии управляет для вас тем как она вынуждена ходить вдоль твердых стен и по твердым полам, и прыгает по твердым препятствиям. Затем вы просто устанавливаете камеру следовать за этой ходячей фигурой — и вы получите иллюзию того, что есть физическое тело которое окружено твердыми телами в окружающем её мире видимом вами через камеру.

Итак давайте зададим обнаружение столкновения для игрока от первого лица.

CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);

Опять же, вы создаете CollisionShape: На этот раз вы выбрали CapsuleCollisionShape, цилиндр с закругленным верхом и низом. Эта фигура является оптимальной для человека: она высокая и округлая, что помогает реже застревать на препятствиях.

  • Задайте конструктору CapsuleCollisionShape требуемый радиус и высоту ограничивающей капсулу, чтобы она лучше соответствовала фигуре вашего персонажа. В этом примере персонаж имеет ширину 2*1.5f единицы в ширину, и 6f единиц в высоту.
  • Конечный целочисленный аргумент указывает ориентацию цилиндра: 1 ось-Y, которая подходит для вертикальных персонажей. Для животных, которые скорее длинные, чем высокие можно использовать 0 или 2 (в зависимости от того, как они поворачиваются).
  player = new CharacterControl(capsuleShape, 0.05f);
Разве это CollisionShape заставляет меня выглядеть толстым? Если вы когда-нибудь запутаетесь в физическом поведении, не забудьте взглянуть на фигуры столкновений. Добавьте следующую строку после инициализации bulletAppState, чтобы сделать фигуры видимыми:

bulletAppState.getPhysicsSpace().enableDebug(assetManager);

Теперь вам нужно использовать CollisionShape, чтобы создать CharacterControl, который представляется персонажу от первого лица. Последний аргумент конструктора CharacterControl (здесь .05f) является размером шага, который персонаж будет преодолевать при ходьбе.

    player.setJumpSpeed(20);
    player.setFallSpeed(30);
    player.setGravity(30);

Помимо высоты шага, и размера персонажа, CharacterControl позволяет настроить прыжки, падения, и силу гравитации. Отрегулируйте значения, чтобы они соответствовали вашей игровой ситуации.

    player.setPhysicsLocation(new Vector3f(0, 10, 0));

Наконец мы помещаем игрока в его исходное положение и обновляем его состояние — не забудьте использовать setPhysicsLocation() вместо setLocalTranslation(), так как вы имеете дело с объектом с физикой.


PhysicsSpace (Пространство с Физикой)

Помните, что в играх с физикой, вы должны зарегистрировать все твердые предметы (как правило, персонажи и сцены) в PhysicsSpace!

    bulletAppState.getPhysicsSpace().add(landscape);
    bulletAppState.getPhysicsSpace().add(player);

Невидимое тело персонажа просто находится на полу с физикой. Он еще не может ходить — вы будете иметь дело с этим далее.


Навигация

По умолчанию контроллер камеры(cam) является камерой от третьего лица. JME3 также предлагает контроллер камеры от первого лица, flyCam, который мы используем здесь, чтобы управлять поворотами камеры. Control flyCam перемещает камеру с помощью setLocation().

Однако, вы должны заново определить, как управлять ходьбой (движением камеры) для объекта контролируемого физикой:Когда вы перемещаетесь как узел без физики (например, как flyCam по умолчанию), вы просто задаете как изменять свое местоположение. Нет никаких тестов, которые мешают flyCam двигаться вроде столкновений со стенами! Когда вы перемещаете с PhysicsControl, вам нужно задавать направление ходьбы. Тогда PhysicsSpace может рассчитать для вас, насколько персонаж может на самом деле двигаться в нужном направлении не сталкиваясь с препятствиями — или препятствует ли его дальнейшему движению что либо.

Короче говоря, вы должны заново определить сопоставления навигационных клавиш для flyCam, чтобы использовать setWalkDirection() вместо setLocalTranslation(). Вот шаги:


1. InputManager

В методе simpleInitApp(), вы повторно настраиваете знакомые вам клавиши ввода WASD для ходьбы, и клавишу пробел для прыжков.

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");
}

Вы можете переместить этот блок кода во вспомогательный метод setupKeys() и вызывать его из simpleInitApp() — чтобы сохранить код более читабельным.


2. OnAction()

Помните, что этот класс реализует интерфейс ActionListener, так что вы можете настроить вводы от flyCam. Интерфейс ActionListener требует, чтобы вы реализовали метод OnAction(): Вы переопределяете действия, инициируемые нажатием навигационных клавиш для работы с физикой.

  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();
    }
  }

Единственное движение, которое вы не должны реализовывать сами — это действие прыжки. Вызов player.jump() представляет собой специальный метод, который обрабатывает правильное движение при прыжке для вашего PhysicsCharacterNode.

Для всех остальных направлений: Каждый раз, когда пользователь нажимает одну из клавиш WASD, вы отслеживаете направление куда пользователь хочет пойти, сохраняя эту информацию в четырех boolean направлений. Никакой, реальной ходьба здесь не происходит, пока. Цикл обновления, это то что выдает информацию о направлении хранящуюся в boolean, и заставляет игрока перемещаться, как показано в следующем фрагменте кода:


3. setWalkDirection()

Ранее в методе OnAction(), вы собрали информацию, в каком направлении пользователь хочет идти с точки зрения «Вперед» или «Влево». В цикле обновления, вы повторно опрашиваете текущее поворот камеры. Вы рассчитываете фактические векторы, которые соответствуют направлению «Вперед» или «Влево» в системе координат.

Это последний и самый важный фрагмент кода разместится в методе 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());
    }

Так работает ходьба:

  1. Инициализировать вектор walkDirection равным нулю. Здесь вам нужно сохранять рассчитанное направление ходьбы.
    1. Добавим в walkDirection векторы недавних движений, которые вы опросили с камеры. Таким образом, это дает возможность персонажу двигаться например вперед и влево, одновременно,!
    2. Эта одна из последних строк осуществляет «магию ходьбы»:
    3. player.setWalkDirection(walkDirection);
      

      Всегда используйте setWalkDirection(), чтобы контролируемые физикой объект двигался непрерывно, а физический движок обрабатывал для вас обнаружения столкновений.

    4. Сделайте камеру от первого лица следующую вместе за игроком контролируемым физикой:
    5. cam.setLocation(player.getPhysicsLocation());
      
Опять же, не используйте setLocalTranslation(), чтобы осуществлять ходьбу игроком. Вы получите игрока застрявшим в другой объекте с физикой из-за наложения друг на друга. Вы можете поместить игрока в начальную позиции с помощью setPhysicalLocation(), только убедились, что поместили его немного выше пола и подальше от препятствий.

Вывод

Вы научились загружать «твердую» модель сцены с физикой и ходить в ней с перспективой от первого лица. Вы научились ускорять расчеты физики с помощью CollisionShapeFactory для создания эффективных CollisionShapes для сложных геометрий. Вы знаете, как добавить PhysicsControl в ваши collidable геометрии и зарегистрировать их в PhysicsSpace. Вы также научились использовать player.setWalkDirection(walkDirection) для перемещения со столкновениями персонажа, а не setLocalTranslation().

Terrains другой тип сцены, в которой вы захотите походить. Давайте продолжим изучение, и изучим как генерировать местности.


Дополнительная информация:


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

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

Содержание

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