jMonkeyEngine 3 Tutorial (2) - Hello Node

» опубликован
Предыдущий туториал: jMonkeyEngine 3 Tutorial (1) - Hello SimpleApplication
Следующий туториал: jMonkeyEngine 3 Tutorial (3) - Hello Assets
Перед вами предварительная версия перевода одного из туториалов по jMonkeyEngine "для самых маленьких". Английский вариант взят с официального сайта jMonkeyEngine. В перспективе планируется перевод остальных туториалов и создание проекта, посвященного этому замечательному движку.
В переводе намеренно сохранены многие английские названия и термины, автор перевода не несет ответственности за использование русскоязычных версий средств разработки.
Версия перевода еще достаточно сырая и не подвергалась окончательной вычитке, так что замечания приветствуются.

jMonkeyEngine 3 Tutorial (2) - Hello Node

В этом туториале мы рассмотрим работу со сценой.
В процессе создания 3D игры часто встречаются следующие действия:
  • Создание различных объектов для сцены, таких как игроки, здания и тому подобное.
  • Добавление созданных объектов на сцену.
  • Перемещение, масштабирование, вращение, изменение цвета и анимация объектов на сцене.
Вы узнаете зачем нужен rootNode, как создавать простые объекты и назначать им пользовательские данные. Вы узнаете о таких трансформациях объектов как перемещение, масштабирование и поворот. Также вы узнаете в чем разница между двумя видами пространственных объектов (Spatials): узлами (Nodes) и геометрией.

Пример кода

package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
 
/** Пример 2 - использование узлов (nodes) для манипуляции объектами на сцене.
 * Можно поворачивать, перемещать и масштабировать объекты, изменяя их родительские узлы (parent nodes).
 * Корневой узел (Root Node) это особый узел: на сцене отображается только то, что прикреплено к корневому узлу. */
public class HelloNode extends SimpleApplication {
 
    public static void main(String[] args){
        HelloNode app = new HelloNode();
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
 
        /** создаем синий куб в координатах (1,-1,1) */
        Box box1 = new Box(1,1,1);
        Geometry blue = new Geometry("Box", box1);
        blue.setLocalTranslation(new Vector3f(1,-1,1));
        Material mat1 = new Material(assetManager, 
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Blue);
        blue.setMaterial(mat1);
 
        /** создаем красный куб в координатах (1,3,1) прямо над синим */
        Box box2 = new Box(1,1,1);      
        Geometry red = new Geometry("Box", box2);
        red.setLocalTranslation(new Vector3f(1,3,1));
        Material mat2 = new Material(assetManager, 
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Red);
        red.setMaterial(mat2);
 
        /** создаем узел (node) под названием pivot в координатах (0,0,0) и прикрепляем его к rootNode */
        Node pivot = new Node("pivot");
        rootNode.attachChild(pivot); // добавляем узел на сцену
 
        /** Прикрепляем кубы к узлу pivot, а значит и к rootNode (транзитивность в действии). */
        pivot.attachChild(blue);
        pivot.attachChild(red);
        /** поворачиваем узел pivot и обращаем внимание что при этом повернутся оба куба.*/
        pivot.rotate(.4f,.4f,0f);
    }
}
Скомпилируйте и запустите пример. Вы должны увидеть два разноцветных куба, наклоненных под одним углом.

Усваиваем терминологию

Что вы хотите сделать Как это будет в терминах JME3
Создание объектов сцены Создание пространственных объектов, в частности геометрию
Отображение объекта на сцене Прикрепление пространственного объекта к rootNode
Убрать объект со сцены Открепление пространственного объекта от rootNode
Изменение положения и размера, поворот объекта на сцене Трансформация пространственного объекта
Каждое приложение на JME3 содержит rootNode, ваша игра автоматически наследует rootNode из SimpleApplication. Все что присоединено к rootNode становится частью сцены. Элементы сцены являются пространственными объектами (Spatials).
  • Пространственные объекты содержат информацию о положении, повороте и масштабе объекта.
  • Пространственный объект может быть загружен, трансформирован и сохранен.
  • Существует два вида пространственных объектов: узлы (nodes) и геометрия.
Геометрия Узел (node)
Видимость: Геометрия это видимый объект на сцене. Узел это невидимый handle для объектов сцены.
Назначение: Геометрия определяет внешний вид объекта. Узел позволяет группировать геометрии и другие узлы.
Примеры: Куб, сфера, игрок, здание, часть ландшафта, машина, ракеты, NPC, и тому подобное… rootNode, узел "пол" для группировки нескольких фрагментов ландшафта, узел "машина-с-пассажирами", узел "игрок-с-оружием", узел для хранения и воспроизведения звука, прочее…

Разбираем код

Что же происходит в приведенном примере? Метод simpleInitApp(), который был представлен в предыдущем туториале, используется для инициализации сцены.

Создание геометрии для первого куба.

  • Создание формы (shape) типа Box с размерами (1,1,1) или 2x2x2 в единицах измерения игрового мира.
  • Создание геометрии на основе формы.
  • Перемещение первого куба в координаты (1,-1,1) с помощью метода setLocalTranslation().
  • Создание материала синего цвета.
  • Применение синего материала к геометрии первого куба.
    Box box1 = new Box(1,1,1);
    Geometry blue = new Geometry("Box", box1);
    blue.setLocalTranslation(new Vector3f(1,-1,1));
    Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    blue.setMaterial(mat1);

Создание геометрии для второго куба.

  • Создание формы и геометрии для второго куба.
  • Перемещение второго куба в координаты (1,3,1) с помощью метода setLocalTranslation().
  • Создание материала красного цвета.
  • Применение красного материала к геометрии второго куба.
    Box box2 = new Box(1,1,1);
    Geometry red = new Geometry("Box", box2);
    red.setLocalTranslation(new Vector3f(1,3,1));
    Material mat2 = new Material(assetManager,
      "Common/MatDefs/Misc/Unshaded.j3md");
    mat2.setColor("Color", ColorRGBA.Red);
    red.setMaterial(mat2);

Создание узла (node) pivot

  • Называем узел "pivot".
  • По умолчанию узел размещается в координатах (0,0,0).
  • Прикрепляем узел (node) к rootNode.
  • Узел (Node) не имеет видимого отображения на сцене.
        Node pivot = new Node("pivot");
        rootNode.attachChild(pivot);
Если запустить приложение на этом этапе, то сцена будет пустой. Это обусловлено тем, что узлы (node) невидимы, а видимые геометрии еще не были присоединены к rootNode.

Прикрепление двух кубов к узлу(node) pivot

        pivot.attachChild(blue);
        pivot.attachChild(red);
Запустив приложение на этом этапе можно увидеть два куба один над другим.

Поворот узла(node) pivot.

        pivot.rotate( 0.4f , 0.4f , 0.0f );
Теперь при запуске приложения можно увидеть два куба, один над другим, оба наклоненные на один угол.

Подробнее о точках поворота (pivot node)

Геометрии можно трансформировать (например поворачивать) либо вокруг их собственного центра либо вокруг указанных пользователем центральных точек. Пользовательские центральные точки для одной или нескольких геометрий принято называть pivot.
В этом примере вы использовали узел(node), названный pivot, для группировки двух геометрий. Также вы использовали этот узел(node) чтобы повернуть две геометрии вокруг общего центра. Поворот узла приводит к повороту всех прикрепленных к нему объектов с использованием этого узла в качестве центра, вокруг которого осуществляется поворот. Прежде чем прикреплять геометрии к узлу(node), рекомендуется удостовериться что он расположен в координатах (0,0,0). Трансформация родительского узла (parent node) с целью трансформации всех прикрепленных пространственных объектов(spatials) это распространенное действие - вы часто будете пользоваться этим способом в ваших играх.
Примеры: совместное перемещение машины и водителя, планеты и ее спутников.
Совсем другой результат можно получить, выполняя трансформацию геометрии без использования дополнительных узлов(nodes). В таком случае все вычисления выполняются в локальных координатах геометрии т.е. относительно ее центра координат.
Примеры: если повернуть каждый куб индивидуально (например так red.rotate(0.1f , 0.2f , 0.3f); blue.rotate(0.5f , 0.0f , 0.25f); ), то каждый куб будет повернут вокруг своего собственного центра, не меняя положения в пространстве. Очень похоже на вращение планеты вокруг своей оси.

Заполнение сцены (примеры)

Создание пространственного объекта (spatial)

Создаем форму и геометрию на ее основе, применяем материал:
Box mesh = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry thing = new Geometry("thing", mesh); 
Material mat = new Material(assetManager,
   "Common/MatDefs/Misc/ShowNormals.j3md");
thing.setMaterial(mat);

Отображение объекта на сцене

Прикрепление объекта к rootNode. Аналогично можно прикреплять объекты к любому узлу(node) и если тот в свою очередь прикреплен к rootNode, то объект будет отображен на сцене.
rootNode.attachChild(thing);

Удаление объекта со сцены

Так можно удалить привязку дочернего объекта или сразу всех дочерних объектов и даже отвязать объект, не зная его родительского объекта:
rootNode.detachChild(thing);

rootNode.detachAllChildren();

thing.removeFromParent();

Поиск объекта по имени, id, позиции в иерархии.

Spatial thing = rootNode.getChild("thing");

Spatial twentyThird = rootNode.getChild(22);

Spatial parent = myNode.getParent();

Начальная загрузка сцены

Все, инициализированное и прикрепленное к rootNode в методе simpleInitApp() будет частью сцены в начале игры.

Трансформация пространственных объектов (Spatials)

Существует три вида 3D трансформации объектов: перемещение(translation), поворот(rotation) и масштабирование(scaling).

Translation

Чтобы задать новое положение объекта в пространстве, указав новые координаты, можно использовать метод:
thing.setLocalTranslation( new Vector3f( 0.0f, 40.2f, -2.0f ) );
Для относительного смещения объекта в пространстве можно использовать следующий метод:
thing.move( 0.0f, 40.2f, -2.0f );

Scaling

Изменить масштаб объекта можно с помощью следующих методов:
thing.scale( 10.0f, 0.1f, 1.0f );
thing.setLocalScale( 10.0f, 0.1f, 1.0f );
Значение 1.0f соответствует 100%, таким образом значения между 0.0f и 1.0f уменьшает объект, а больше 1.0f - увеличивает. Можно указать разное масштабирование для каждой оси.
Отличие между методами scale() и setLocalScale() в том, что второй задает фиксированный масштаб, а первый - изменяет масштаб с учетом текущего.

Rotation

Поворот объектов в пространстве это отдельная тема, которой даже посвящена целая статья, но она еще не переведена.
Простой поворот осуществляется по трем осям с помощью следующего метода:
thing.rotate( 0f , 0f , 180*FastMath.DEG_TO_RAD );
Константа FastMath.DEG_TO_RAD используется в вычислениях для перевода градусов в радианы.
В случае использования большого кол-ва поворотов рекомендуется применять квартернионы(quaternion).
thing.setLocalRotation( 
  new Quaternion().fromAngleAxis(180*FastMath.DEG_TO_RAD, new Vector3f(1,0,0)));
Этой теме также посвящена отдельная статья, которую еще предстоит перевести.
Квартернионы сложны в понимании, но обеспечивают более эффективное хранение и обработку данных при поворотах и некоторые дополнительные возможности.

Решение проблем с пространственными объектами (Spatials)

Ниже приведен список распространенных ошибок и пути их решения:

Геометрия не появляется на сцене

Прикрепили ли вы геометрию к rootNode?
Назначен ли геометрии материал?
Не выходят ли координаты геометрии за отображаемую область?
Не находится ли геометрия за камерой или другими геометриями?
Не являются ли размеры геометрии слишком большими или слишком маленькими чтобы их увидеть?
Не находится ли геометрия слишком далеко от камеры? Можно воспользоваться методом cam.setFrustumFar(111111f); чтобы увеличить дальность отрисовки (рекомендуется не злоупотреблять увеличенем дальности отрисовки).

Поворот пространственного объекта(spatial) дает неожиданные результаты

Используете ли вы радианы для указания углов поворота? Не забывайте умножать значение в градусах на FastMath.DEG_TO_RAD чтобы получить радианы.
Был ли пространственный объект(spatial) создан в начале координат (Vector.ZERO)?
Действительно ли поворот выполняется вокруг правильного узла(node)?
Правильно ли выбрана ось поворота?

Геометрия отображается с неожиданным цветом или материалом

Не использовали ли вы общий материал для нескольких геометрий? В таком случае изменения в свойствах материала дял одной геометрии отразится и на всех остальных геометриях, использующих этот материал. В решении этой проблемы может помочь клонирование материала с помощью mat2 = mat.clone(); или создание нового материала вручную (по сути клонирование это просто создание нового материала с теми-же свойствами что и указанный).

Добавление пользовательских данных к пространственным объектам (Spatials)

Многие пространственные объекты(spatials) представляют игровых персонажей или другие сущности, с которыми игрок может взаимодействовать.
В зависимости от игры, сущности (game entities) не только меняют размер, поворачиваются или перемещаются в пространстве - у игровых объектов также могут быть особые параметры, такие как, например, запас здоровья, инвентарь, вооружение для персонажа или прочность корпуса и запас топлива для космического корабля.
Основным способом представления таких данных в Java является использование полей класса (переменных класса). Впрочем, JME3 позволяет задавать пользовательские данные любому узлу(node) или геометрии без необходимости использовать механизм наследования и объявления новых переменных. Вместо этого используется следующее:
pivot.setUserData( "pivot id", 42 );
Чтобы получить сохраненные данные в другом месте можно использовать следующее:
int id = pivot.getUserData( "pivot id" ); 
Используя различные строковые ключи (в данном случае в качестве ключа выступала строка "pivot id") можно сохранить в пространственных объектах(spatials) любые данные, какие только нужно. Однако стоит учитывать что только классы, реализующие интерфейс Savable могут быть сохранены на диск и загружены стандартными средствами JME3. Еще одна вещь, о которой стоит помнить: для более сложных систем может потребоваться более совершенная система хранения пользовательских данных, которую придется либо писать самостоятельно либо использовать одну из готовых реализаций.

Заключение

Вы должны были узнать что сцена состоит из пространственных объектов(spatials): видимых геометрий и невидимых узлов(nodes). Что пространственные объекты можно трансформировать и прикреплять друг к другу. Также теперь вы должны знать о простейшем способе хранения пользовательских данных для пространственных объектов.
Стандартные формы вроде куба и сферы быстро надоедают, так что очередь за загрузкой 3D-моделей. Так что следующий туториал будет посвящен загрузке ассетов(assets).
Владеющим английским языком может быть интересен первоисточник. А также продолжение на английском языке.


Просмотров: 3 552

prog #1 - 5 лет назад 1
Да простят меня души всех погибших от нежелания осваивать геймдев, но оставить здесь комментарий это единственный способ поднять в ленте ресурс, который был создан пару дней назад и редактировался в скрытом состоянии.
Doc #2 - 5 лет назад 1
сожержит
Удаление объекта со сцены
Можно добавить еще про Spatial.removeFromParent()
thing.scale( 10.0f, 0.1f, 1.0f );
Тут лучше бы упомянуть и про setLocalScale()
А еще лучше было бы добавить это и в оригинал.
prog #3 - 5 лет назад (отредактировано ) 0
Doc, увы, я пока чайник в работе с JME3, так что спасибо за дополнение - посмотрю как работает и добавлю. Что касается оригинала - это надо писать разработчикам.
сожержит
fixed
Doc #4 - 5 лет назад 0
Достаточно залогиниться на сайте, чтобы редактировать вики-статьи.
prog #5 - 5 лет назад 0
Doc:
Удаление объекта со сцены
Можно добавить еще про Spatial.removeFromParent()
thing.scale( 10.0f, 0.1f, 1.0f );
Тут лучше бы упомянуть и про setLocalScale()
done, в оригинал статьи не полезу пока.
ScorpioT1000 #6 - 5 лет назад 0
рекомендуется применять квартернионы
я бы сказал, вы обязаны использовать матрицы или кватернионы, ну ладно)))