Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество

Ходячий персонаж

Опубликованно: 13.06.2017, 23:07
Последняя редакция, Andry: 14.06.2017 21:33

В уроке Hello Collision и образце TestQ3.java вы видели, как создавать collidable ландшафты и ходить с видом от первого лица. Камера первого персонажа закрыта фигурой столкновения и управляется BetterCharacterControl.

Тем не менее, для других игр требуется вид от третьего лица: в этих случаях вы используете CharacterControl на Spatial. В этом примере также показано, как настроить control навигации, чтобы вы могли нажимать WASD для того, чтобы совершить прогулку персонажем от третьего лица; и как реализовать перемещение мыши для поворота.

Некоторые детали этой страницы по-прежнему необходимо обновлять со старого API CharacterControl на API BetterCharacterControl.

Примеры кода

Несколько связанных с этим образцов кода можно найти здесь:

Код в этом уроке представляет собой комбинацию этих образцов.

BetterCharacterControl

Мотивация: Когда вы загружаете модель персонажа, даете ей RigidBodyControl и применяете силы для того что бы толкнуть её, вы не получаете желаемого поведения: объекты RigidBodyControl (например, кубы и сферы) катятся или опрокидываются при воздействии физической силы. Это не то поведение, которое вы ожидаете от ходячего персонажа. Интеграция BulletPhysics от JME3 предлагает BetterCharacterControl с помощью метода setWalkDirection(). Вы используете его для создания простых персонажей, которые воспринимают полы и стены как сплошные, и всегда остаются в вертикальном положении при движении.

Этот образец кода создает простой вертикальный персонаж:

// Загрузим любую модель
Node myCharacter = (Node) assetManager.loadModel("Models/myCharacterModel.mesh.xml");
rootNode.attachChild(myCharacter);
// Создайте для него соответствующую физическую форму
CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
CharacterControl myCharacter_phys = new CharacterControl(capsuleShape, 0.01f);
// Применим физические свойства к модели и PhysicalSpace
myCharacter.addControl(myCharacter_phys);
bulletAppState.getPhysicsSpace().add(myCharacter_phys);

Чтобы использовать BetterCharacterControl, вы можете загрузить своего персонажа следующим образом:

// Загрузим любую модель
Spatial playerSpatial = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
// Вы можете задать модель непосредственно player. 
//(Мы просто хотели показать явно, что это spatial.)
player =  (Node)playerSpatial;
// Вы можете создать новый узел, как оболочку для игрока и использовать при точной настройки местоположения.
// (Это позволяет решать проблемы с погружением персонажа в пол и.т.д.)
Node playerNode = new Node(); 
playerNode.attachChild(player); // добавить его в оболочку
player.move(0,3.5f,0); // Отрегулируйте положение, чтобы убедиться, что столкновение происходит правильно.
player.setLocalScale(0.5f); // необязательно настроить масштаб модели
// setup animation:
        control = player.getControl(AnimControl.class);
        control.addListener(this);
        channel = control.createChannel();
        channel.setAnim("stand");
// construct character. (Если ваш персонаж отскакивает, попробуйте увеличить высоту и вес.)
playerControl = new BetterCharacterControl(1.5f, 6f, 1f);
playerNode.addControl(playerControl); // прикрепим оболочку
// зададим базовые физические свойства:
        playerControl.setJumpForce(new Vector3f(0,5f,0));
        playerControl.setGravity(new Vector3f(0,1f,0));
playerControl.warp(new Vector3f(0,10,10)); // оболочка персонажа в пейзаже в определенном месте
// добавим в физическое состояние
        bulletAppState.getPhysicsSpace().add(playerControl);
        bulletAppState.getPhysicsSpace().addAll(playerNode);
rootNode.attachChild(playerNode); // добавим оболочку к root
BulletPhysics CharacterControl сталкивается только с «PhysicsControl(RigidBody)». Он не обнаруживает столкновений с другими CharacterControl! Если вам нужны дополнительные проверки на столкновение, добавьте GhostControl к своим персонажам и создайте пользовательский слушатель столкновений для реакции. (Команда JME3 может реализовать наилучший CharacterControl за один день.)

CharacterControl — специальный кинематический объект с ограниченным движением. У CharacterControls есть фиксированная «восходящая ось», это означает, что они не опрокидываются при переходе через препятствие (в отличие от RigidBodyControls), которое имитирует способность существа балансировать вертикально. CharacterControl может прыгать и падать вдоль своей восходящей оси, и он может масштабировать шаги определенной высоты/крутизны.

Методы CharacterControl  Свойства
setUpAxis(1)  Исправлена восходящая ось. Значения: 0 = ось X, 1 = ось Y, 2 = ось Z.
По умолчанию: 1, потому что для символов и транспортных средств вверх, как правило, вдоль оси Y.
setJumpSpeed(10f)  Скорость перемещения (движение вдоль восходящей оси)
setFallSpeed(20f)  Скорость падения (движение против восходящей оси)
setMaxSlope(1.5f)  Как крут склон для ходьбы — это то, на что персонаж может подняться, не считая это препятствием. Более высокие препятствия необходимо перепрыгивать. Вертикальная высота в мировых единицах.
setGravity(1f)  Сила тяжести для этого объекта CharacterControl.

Чтобы изменить направление гравитации для персонажа, измените вертикальную ось.

setWalkDirection(new Vector3f(0f,0f,0.1f))  (Только CharacterControl) Постоянно перемещая физически персонажа, проверяйте полы и стены, а так же сплошные препятствия. Для этого следует использовать setPositionIncrementPerSimulatorStep. Этот аргумент не является ни направлением, ни скоростью, а увеличенной суммой положение на каждом такте физической симуляции: vector length = accuracy*speed in m/s.
Используйте setWalkDirection(Vector3f.ZERO), чтобы остановить направленное движение.

Для получения наилучших практических рекомендаций по использованию setWalkDirection() см. Приведенный ниже пример «Навигационные ввод(Navigation Inputs)».

Ходячий персонаж Демо

Код Скелета

public class WalkingCharacterDemo extends SimpleApplication
        implements ActionListener, AnimEventListener {

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

  public void simpleInitApp() { }

  public void simpleUpdate(float tpf) { }

  public void onAction(String name, boolean isPressed, float tpf) { }

  public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { }

  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { }

Обзор

Чтобы создать ходящего персонажа:

  1. (Если у вас его уже есть) Активируйте физику в сцене, добавив BulletAppState.
  2. Инициируйте сцену, загрузив модель игрового уровня (рельеф или пол/здания), и предоставив сцену MeshCollisionShape.
  3. Создайте анимированный персонаж:
    1. Загрузите модель анимированного персонажа.
    2. Добавьте в модель CharacterControl.
  4. Настройте канал анимации и контроллеры.
  5. Добавьте ChaseCam или CameraNode.
  6. Управление навигационными вводом.

Активация Физики

private BulletAppState bulletAppState;
...
public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    //bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
    stateManager.attach(bulletAppState);
    ...
}

Инициализация Сцены

В методе simpleInitApp() вы инициализируете сцену и присваиваете ей MeshCollisionShape. Образец в источниках jme3 использует собственный вспомогательный класс, который просто создает ровный пол и бросает на него кубы и сферы:

public void simpleInitApp() {
  ...
  PhysicsTestHelper.createPhysicsTestWorld(rootNode,
      assetManager, bulletAppState.getPhysicsSpace());
  ...

В реальной игре вы загрузите здесь сцену вместо тестового мира. Вы можете загрузить модель из локального или удаленного zip-файла, а также размещать и масштабировать его:

private Node gameLevel;
..
public void simpleInitApp() {
  ...
  //assetManager.registerLocator("quake3level.zip", ZipLocator.class);
  assetManager.registerLocator(
  "http://jmonkeyengine.googlecode.com/files/quake3level.zip",
    HttpZipLocator.class);
  MaterialList matList = (MaterialList) assetManager.loadAsset("Scene.material");
  OgreMeshKey key = new OgreMeshKey("main.meshxml", matList);
  gameLevel = (Node) assetManager.loadAsset(key);
  gameLevel.setLocalTranslation(-20, -16, 20);
  gameLevel.setLocalScale(0.10f);
  gameLevel.addControl(new RigidBodyControl(0));
  rootNode.attachChild(gameLevel);
  bulletAppState.getPhysicsSpace().addAll(gameLevel);
  ...

Кроме того, добавьте источник света, чтобы увидеть сцену.

  AmbientLight light = new AmbientLight();
  light.setColor(ColorRGBA.White.mult(2));
  rootNode.addLight(light);

Создание анимированного персонажа

Вы создаете анимированную модель, такую как Oto.mesh.xml.

  1. Поместите «Oto model» в каталог assets/Models/Oto/ вашего проекта.
  2. Создайте CollisionShape и настройте радиус и высоту капсулы, чтобы она соответствовала вашей модели персонажа.
  3. Создайте CharacterControl и настройте stepheight (здесь 0.05f) на высоту, на которую персонаж может подняться без прыжков.
  4. Загрузите видимую модель. Убедитесь, что её начальное положение не перекрывается объектами сцены.
  5. Добавьте CharacterControl в модель и зарегистрируйте ее в physicsSpace.
  6. Прикрепите видимую модель к rootNode.
private CharacterControl character;
private Node model;
...
public void simpleInitApp() {
  ...
  CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f);
  character = new CharacterControl(capsule, 0.05f);
  character.setJumpSpeed(20f);
  model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
  model.addControl(character);
  bulletAppState.getPhysicsSpace().add(character);
  rootNode.attachChild(model);
  ...
А вы знали? CapsuleCollisionShape — это цилиндр с закругленным верхом и низом. Капсула, вращающаяся вертикально, является хорошей фигурой столкновения для гуманоидного персонажа, поскольку ее округлость уменьшает риск застревания на препятствиях.

Настройка AnimControl и AnimChannel

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

private AnimChannel animationChannel;
private AnimChannel attackChannel;
private AnimControl animationControl;
...
public void simpleInitApp() {
  ...
  animationControl = model.getControl(AnimControl.class);
  animationControl.addListener(this);
  animationChannel = animationControl.createChannel();
  attackChannel = animationControl.createChannel();
  attackChannel.addBone(animationControl.getSkeleton().getBone("uparm.right"));
  attackChannel.addBone(animationControl.getSkeleton().getBone("arm.right"));
  attackChannel.addBone(animationControl.getSkeleton().getBone("hand.right"));
  ...

attackChannel только управляет одной рукой, а каналы ходьбы контролируют весь персонаж.

Добавить ChaseCam/CameraNode

private ChaseCamera chaseCam;

...

public void simpleInitApp() {
  ...
  flyCam.setEnabled(false);
  chaseCam = new ChaseCamera(cam, model, inputManager);
  ...

Управление с клавиатуры

Настройте пользовательские клавиш WASD, которые вы будете использовать, чтобы заставить персонаж ходить. Затем вычислите вектор, по которому пользователь хочет, чтобы персонаж перемещался. Обратите внимание на использование специального метода setWalkDirection() ниже.

// отслеживать вводимое направление, поэтому мы можем идти влево-вперед и.т.д.
private boolean left = false, right = false, up = false, down = false;
...

public void simpleInitApp() {
  ...
  // настройка сопоставлений клавиш, например WASD
  inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A));
  inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D));
  inputManager.addMapping("CharForward", new KeyTrigger(KeyInput.KEY_W));
  inputManager.addMapping("CharBackward", new KeyTrigger(KeyInput.KEY_S));
  inputManager.addMapping("CharJump", new KeyTrigger(KeyInput.KEY_RETURN));
  inputManager.addMapping("CharAttack", new KeyTrigger(KeyInput.KEY_SPACE));
  inputManager.addListener(this, "CharLeft", "CharRight");
  inputManager.addListener(this, "CharForward", "CharBackward");
  inputManager.addListener(this, "CharJump", "CharAttack");
  ...
}

Реакция на привязки клавиш, установив переменные, которые отслеживают, в каком направлении вы будете идти. Это позволяет одновременно управлять персонажем в направлении вперед и влево. Заметьте, что никакой настоящей ходьбы здесь не происходит! Мы просто отслеживаем ввод.

@Override
public void onAction(String binding, boolean value, float tpf) {
  if (binding.equals("CharLeft")) {
      if (value) left = true;
      else left = false;
  } else if (binding.equals("CharRight")) {
      if (value) right = true;
      else right = false;
  } else if (binding.equals("CharForward")) {
      if (value) up = true;
      else up = false;
  } else if (binding.equals("CharBackward")) {
      if (value) down = true;
      else down = false;
  } else if (binding.equals("CharJump"))
      character.jump();
  if (binding.equals("CharAttack"))
    attack();
}

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

private void attack() {
    attackChannel.setAnim("Dodge", 0.1f);
    attackChannel.setLoopMode(LoopMode.DontLoop);
}

Наконец, цикл обновления рассматривает переменные направления и соответственно перемещает персонаж. Поскольку это специальный кинематический CharacterControl, мы используем метод setWalkDirection().

Переменная airTime отслеживает, как долго персонаж находится вне земли (например, при прыжках или падении) и адаптирует анимацию ходьбы и стояния.

private Vector3f walkDirection = new Vector3f(0,0,0); // остановка

private float airTime = 0;

public void simpleUpdate(float tpf) {
  Vector3f camDir = cam.getDirection().clone();
  Vector3f camLeft = cam.getLeft().clone();
  camDir.y = 0;
  camLeft.y = 0;
  camDir.normalizeLocal();
  camLeft.normalizeLocal();
  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());

  if (!character.onGround()) { // применяйте !character.isOnGround() персонаж является типом BetterCharacterControl.
      airTime += tpf;
  } else {
      airTime = 0;
  }

  if (walkDirection.lengthSquared() == 0) { //используйте lengthSquared() (Нет необходимости в дополнительном sqrt())
      if (!"stand".equals(animationChannel.getAnimationName())) {
        animationChannel.setAnim("stand", 1f);
      }
  } else {
      character.setViewDirection(walkDirection);
      if (airTime > .3f) {
        if (!"stand".equals(animationChannel.getAnimationName())) {
          animationChannel.setAnim("stand");
        }
      } else if (!"Walk".equals(animationChannel.getAnimationName())) {
        animationChannel.setAnim("Walk", 0.7f);
      }
    }

  // Использование первого multLocal здесь состоит в том, чтобы контролировать скорость мультипликатора движения для скорости 
  //движения персонажа. Во-вторых, убедится, что персонаж движется с той же скоростью независимо от того, какова частота кадров.
  walkDirection.multLocal(25f).multLocal(tpf);
  character.setWalkDirection(walkDirection); // ЭТО ГДЕ ПРОЯВЛЯЕТСЯ ХОДЬБА
}

Этот метод сбрасывает анимацию ходьбы.

public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
        if (channel == attackChannel) channel.setAnim("stand");
}

public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { }

Смотрите так же


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

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

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