jMonkeyEngine 3 Tutorial (3) - Hello Assets

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

jMonkeyEngine 3 Tutorial (3) - Hello Assets

Небольшое примечание по терминологии: в оригинале используется два термина "scene graph" и "scene", первый обозначает виртуальное пространство, в котором происходит действие игры, а второй - просто композицию из объектов. При переводе, по старой привычке, я использовал термины "сцена" вместо "scene graph" и "композиция" вместо "scene".
Данный туториал посвящен загрузке моделей и текста при помощи jME Asset Manager и их размещению на сцене. Также будут рассмотрены вопросы организации путей и использования различных форматов.
Файл jme3-test-data.jar содержит все ассеты(assets), которые используются в этом и следующих туториалах - просто добавьте его в classpath. При использовании jMonkeyEngine SDK можно просто добавить к проекту преконфигурированную библиотеку jme3-test-data, для этого необходимо проделать следующее: правый клик по проекту, выбрать пункт свойства(Properties), библиотеки(Libraries), добавить библиотеку (Add Library).

Пример кода

package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
 
/** Пример 3 - загрузка OBJ моделей и OgreXML моделей, текстур, материалов или текста. */
public class HelloAssets extends SimpleApplication {
 
    public static void main(String[] args) {
        HelloAssets app = new HelloAssets();
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
 
        Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
        Material mat_default = new Material( 
            assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
        teapot.setMaterial(mat_default);
        rootNode.attachChild(teapot);
 
        // создание стены с простой текстурой из test_data
        Box box = new Box(Vector3f.ZERO, 2.5f,2.5f,1.0f);
        Spatial wall = new Geometry("Box", box );
        Material mat_brick = new Material( 
            assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_brick.setTexture("ColorMap", 
            assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
        wall.setMaterial(mat_brick);
        wall.setLocalTranslation(2.0f,-2.5f,0.0f);
        rootNode.attachChild(wall);
 
        // отображение текста с использованием стандартного шрифта
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText helloText = new BitmapText(guiFont, false);
        helloText.setSize(guiFont.getCharSet().getRenderedSize());
        helloText.setText("Hello World");
        helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
        guiNode.attachChild(helloText);
 
        // загрузка модели из test_data (OgreXML + материал + текстура)
        Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
        ninja.scale(0.05f, 0.05f, 0.05f);
        ninja.rotate(0.0f, -3.0f, 0.0f);
        ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
        rootNode.attachChild(ninja);
        // добавление освещения на сцену чтобы модель можно было разглядеть
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        rootNode.addLight(sun);
 
    }
}
Скомпилировав и запустив этот пример вы должны увидеть то-же что и на скриншоте в начале статьи.

Asset Manager

Ассетами(assets) называют все мультимедийные файлы, такие как модели, текстуры, композиции, шейдеры, файлы с музыкой или звуками, шрифты и тому подобное. В состав JME3 входит удобный инструмент - класс AssetManager, который упрощает работу с ассетами(assets). AssetManager может загружать файлы из следующих мест:
  • classpath (по умолчанию - корневая папка вашего проекта),
  • папка assets
  • указанные пользователем пути.
Рекомендуемая структура папок для хранения ассетов(assets) выглядит примерно так:
MyGame/assets/Interface/
MyGame/assets/MatDefs/
MyGame/assets/Materials/
MyGame/assets/Models/
MyGame/assets/Scenes/
MyGame/assets/Shaders/
MyGame/assets/Sounds/
MyGame/assets/Textures/
MyGame/build.xml            <-- билд-скрипт Ant (генерируется автоматически)
MyGame/src/...              <-- исходники на Java
MyGame/...
Это только рекомендуемая структура, которая получается при создании проекта в jMokeyEngine SDK. На практике обязательным является только факт наличия папки assets, а ее содержимое может быть произвольным.

Загрузка текстур

Поместите текстуры в папку assets/Textures/, после чего можно загружать текстуру, назначать ее материалу и назначать материал геометрии. Следующий фрагмент кода из метода simpleInitApp() загружает текстуру и создает с ее помощью простую кирпичную стену:
// создание стены с использованием текстуры
Box box = new Box(Vector3f.ZERO, 2.5f,2.5f,1.0f);
Spatial wall = new Geometry("Box", box );
Material mat_brick = new Material( 
    assetManager, "Common/MatDefs/Misc/"Unshaded.j3md"");
mat_brick.setTexture("ColorMap", 
    assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
wall.setMaterial(mat_brick);
wall.setLocalTranslation(2.0f,-2.5f,0.0f);
rootNode.attachChild(wall);
В данном случае был создан материал и применен к геометрии. Из этого примера также видно что материалы можно основывать на стандартных "шаблонах" для материалов, таких как "Unshaded.j3md" (другими словами - преднастроенных материалов). Возможно тут лучше пойдет перевод вида "в данном примере показано создание материала на основе преднастроенного Unshaded.j3md"

Загрузка текста и шрифтов

Данный пример позволяет отобразить текст "Hello World" стандартным шрифтом у нижней границы окна. Текст прикрепляется к guiNode – это специальный узел(node) для проских элементов, таких как UI. Приведенный фрагмент кода располагается в методе simpleInitApp().
// отображение текста стандартным шрифтом
guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText helloText = new BitmapText(guiFont, false);
helloText.setSize(guiFont.getCharSet().getRenderedSize());
helloText.setText("Hello World");
helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
guiNode.attachChild(helloText);
Замечание: чтобы скрыть существующий текст с экрана, можно отсоединить все дочерние объекты от guiNode.

Загрузка моделей

Экспортированные в OgreXML модели (.mesh.xml, .scene, .material, .skeleton.xml) размещаем в assets/Models/. Следующий фразмент кода из simpleInitApp() демонстрирует загрузку таких моделей.
//загрузка модели в формате OgreXML (с материалом и текстурой)
Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
ninja.scale(0.05f, 0.05f, 0.05f);
ninja.rotate(0.0f, -3.0f, 0.0f);
ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
rootNode.attachChild(ninja);
// добавление освещения на сцену чтобы модель можно было разглядеть
DirectionalLight sun = new DirectionalLight();
sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
rootNode.addLight(sun);
Обратите внимание, что нет необходимости создавать материал, если модель была экспортирована с назначенным материалом. Не забудьте что на сцене должно быть освещение иначе материал (а значит и модель) нельзя будет разглядеть.

Загрузка ассетов с использованием пользовательских путей

Бывает что игра использует файлы моделей, предоставленные пользователем и, соответственно, не включенные в состав игры. В таком случае необходимо использовать класс Locator, что позволяет загружать ассеты по произвольному пути.
Вот так с помощью класса ZipLocator можно загрузить содержимое файла town.zip из корневой папки проекта:
    assetManager.registerLocator("town.zip", ZipLocator.class);
    Spatial scene = assetManager.loadModel("main.scene");
    rootNode.attachChild(scene);
Вот так с помощью HttpZipLocator можно скачать и загрузить модели в zip архиве по указанному адресу:
    assetManager.registerLocator(
      "http://jmonkeyengine.googlecode.com/files/wildhouse.zip", 
      HttpZipLocator.class);
    Spatial scene = assetManager.loadModel("main.scene");
    rootNode.attachChild(scene);
В состав JME3 входят ClasspathLocator, ZipLocator, FileLocator, HttpZipLocator и UrlLocator (подробнее в пакете com.jme3.asset.plugins).

Создание моделей и композиций

Для создания 3D моделей и композиций необходим 3D редактор с возможностью экспортировать в OgreXML (в большинстве случаев с помощью соответствующего плагина). Например можно экспортировать оттекстурированные модели из Blender. Для создания композиций используется инструмент из JME3 SDK, позволяющий загружать и конвертировать модели, а также собирать из них композиции.
Если вы пользуетесь Blender, то экспорт модели и материала в Ogre XML выглядит как-то так:
  • ВыберитеМеню File > Export > OgreXML Exporter to open the exporter dialog.
  • В разделе экспорта материалов(Export Materials) укажите материалу такое-же название как у модели. Например, модель something.mesh.xml, материал something.material и (не обязательно) something.skeleton.xml плюс файлы текстур.
  • В разделе экспорта геометрии(Export Meshes) укажите папку в пределах assets/Models/, например assets/Models/something/.
  • Выберите следующие настройки:
    • Copy Textures: YES
    • Rendering Materials: YES
    • Flip Axis: YES
    • Require Materials: YES
    • Skeleton name follows mesh: YES
  • Нажмите "export".
В результате модель будет экспортирована сразу в папку, в которой ее увидит ваше приложение.
Помимо блендера существуют плагины для экспорта в формате OgreXML и для других 3D редакторов, например для 3ds Max.

Форматы файлов моделей

JME3 поддерживает Ogre XML модели с материалами, Ogre DotScenes, а также модели Wavefront OBJ+MTL. Метод loadModel() отлично работает с этими типами файлов когда вы запускаете приложение непосредственно из jMonkeyEngine SDK.
При построении исполняемого файла подобные модели не включаются в его состав, так что при вполне ожидаемо будут возникать ошибки вида:
com.jme3.asset.DesktopAssetManager loadAsset
WARNING: Cannot locate resource: Models/Ninja/Ninja.mesh.xml
com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.NullPointerException
Таким образом, загрузка XML/OBJ файлов допустима только на этапе разработки. Такая возможность сохранена для удобства работы - можно очень быстро просмотреть изменения с использованием свежих моделей от моделера или любые другие изменения в моделях.
Для тестирования и релиза предусмотрен формат моделей .j3o. J3o это оптимизированный бинарный формат моделей jME3.J3o файлы автоматически пакуются в JAR при компиляции. Для релизной или тестовой версии следует использовать SDK и конвертировать все .obj/.scene/.xml/.blend файлы в .j3o и загружать только их. стоит заранее подумать об автоматизации замены имен загружаемых файлов или хотябы центральном хранилище для них чтобы не выискивать потом вручную по всему коду
  • Откройте ваш JME3 проект в jMonkeyEngine SDK.
  • Сделайте правый клик по .Blend, .OBJ, или .mesh.xml файлу в окне проекта и выберите "convert to JME3 binary".
  • Файл .j3o появится возле .mesh.xml файла и будет иметь такое-же имя.
  • Измените все вызовы loadModel() соответствующим образом. Например:
    Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.j3o");
Если ваш исполняемый файл продолжает выдавать ошибки, убедитесь что вы конвертировали все модели в .j3o!

Загрузка моделей и композиций

Загрузка модели с материалами

Используем метод loadModel() из AssetManager-а, затем прикрепляем пространственный объект(spatial ) к rootNode.
Spatial elephant = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml");
rootNode.attachChild(elephant);
Spatial elephant = assetManager.loadModel("Models/Elephant/Elephant.j3o");
rootNode.attachChild(elephant);

Загрузка модели без материалов

Если у вашей модели нет материала, придется его ей назначить, в противном случае модель будет невозможно увидеть.
Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.j3o");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); // default material
teapot.setMaterial(mat);
rootNode.attachChild(teapot);

Загрузка композиции

Композиции загружаются точно так-же, как и любые другие модели:
Spatial scene = assetManager.loadModel("Scenes/town/main.scene");
rootNode.attachChild(scene);
Spatial scene = assetManager.loadModel("Scenes/town/main.j3o");
rootNode.attachChild(scene);

Упражнение - загрузка ассетов

As an exercise, let's try different ways of loading a scene. You will learn how to load the scene directly, or from a zip file.
Download the town.zip sample scene.
(Optional:) Unzip the town.zip to see the structure of the contained Ogre dotScene: You'll get a directory named town. It contains XML and texture files, and file called main.scene. (This is just for your information, you do not need to do anything with it.)
Place the town.zip file in the top level directory of your JME3 project, like so:
    jMonkeyProjects/MyGameProject/assets/
    jMonkeyProjects/MyGameProject/build.xml
    jMonkeyProjects/MyGameProject/src/
    jMonkeyProjects/MyGameProject/town.zip
    ...
Use the following method to load models from a zip file:
Verify town.zip is in the project directory.
Register a zip file locator to the project directory: Add the following code under simpleInitApp() {
        assetManager.registerLocator("town.zip", ZipLocator.class);
        Spatial gameLevel = assetManager.loadModel("main.scene");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
The loadModel() method now searches this zip directly for the files to load.
(This means, do not write loadModel(town.zip/main.scene) or similar!)
Clean, build and run the project.
You should now see the Ninja+wall+teapot standing in a town.
Tip: If you register new locators, make sure you do not get any file name conflicts: Don't name all scenes main.scene but give each scene a unique name.
Earlier in this tutorial, you loaded scenes and models from the asset directory. This is the most common way you will be loading scenes and models. Here is the typical procedure:
Remove the code that you added for the previous exercise.
Move the unzipped town/ directory into the assets/Scenes/ directory of your project.
Add the following code under simpleInitApp() {
        Spatial gameLevel = assetManager.loadModel("Scenes/town/main.scene");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
Note that the path is relative to the assets/… directory.
Clean, build and run the project. Again, you should see the Ninja+wall+teapot standing in a town.
Here is a third method you must know, loading a scene/model from a .j3o file:
Remove the code from the previous exercise.
If you haven't already, open the SDK and open the project that contains the HelloAsset class.
In the projects window, browse to the assets/Scenes/town directory.
Right-click the main.scene and convert the scene to binary: The jMoneyPlatform generates a main.j3o file.
Add the following code under simpleInitApp() {
        Spatial gameLevel = assetManager.loadModel("Scenes/town/main.j3o");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
Again, note that the path is relative to the assets/… directory.
Clean, Build and run the project.
Again, you should see the Ninja+wall+teapot standing in a town.

Заключение

Now you know how to populate the scenegraph with static shapes and models, and how to build scenes. You have learned how to load assets using the assetManager and you have seen that the paths start relative to your project directory. Another important thing you have learned is to convert models to .j3o format for the executable JARs etc.
Let's add some action to the scene and continue with the Update Loop!