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

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

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

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

Тем не менее, в других играх требуется вид от третьего лица: в этих случаях вы используете для Spatial CharacterControl. В этом примере также показано, как настроить пользовательский 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); // необязательно настроить масштаб модели
// настроим анимацию:
        control = player.getControl(AnimControl.class);
        control.addListener(this);
        channel = control.createChannel();
        channel.setAnim("stand");
// соберём пресоонажа. (Если ваш персонаж отскакивает, попробуйте увеличить высоту и вес.)
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

Character Control

BulletPhysics CharacterControl сталкивается только с «реальным PhysicsControl(RigidBody)». Он не обнаруживает столкновений с другими CharacterControl! Если вам нужны дополнительные проверки на столкновение, добавьте GhostControl к своим персонажам и создайте пользовательский слушатель столкновений для реакции.

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. Этот аргумент не является ни направлением, ни скоростью, а суммой инкрементов положений на каждом такте физической симуляции: длинна вектора = (точность средства измерений)*скорость в м/с.
Используйте 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» в папку 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 управляет только одной рукой, в том время как канал ходьбы управляет персонажем целиком.

Добавим Следящую Камеру/Узел Камеры.(Add ChaseCam / CameraNode)

private ChaseCamera chaseCam;

...

public void simpleInitApp() {
  ...
  flyCam.setEnabled(false);
  chaseCam = new ChaseCamera(cam, model, inputManager);
//chaseCam.setDragToRotate(false); //если будет нужно
  ...

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

Настройте пользовательские клавиш 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. Все права сохранены.