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

RomaGZ

Опубликованно: RomaGZRomaGZ26.09.2015, 0:57
Последняя редакция, AdiDOS: 05.05.2016 5:13

Вышла новая версия JME 3.1 SDK дата выхода (21 августа 2015 года),  в этой версии добавлено много нововведений а также исправлены недостатки , пока очень мало информации потому что  реализация еще зеленая , но собственно есть о чем написать.

1

ЕДИНАЯ АРХИТЕКТУРА ВИДЕО ОБРАБОТКИ

Версия 3.0 поддерживала много платформ такие как Android, Windows, Mac OS, Linux , но для каждой платформы была разная реализация вывода визуализации графики , что и приводило к дублированию кода а также это влияло на производительность системы.

Новая архитектура  визуализации позволяет обойти все эти неудобства , теперь с помощью класса «GLRenderer» который занимается обработкой графики , возможно собирать приложения на одной платформе и при этом будет работать и на остальных без всякой программной корректировки , что значительно облегчает вносить какие либо изменения , да и положительно влияет на производительность графики.

А также были изменения в конвеере визуализации что привело к уменьшению бесполезной работы движка и повысило производительность графики.

АППАРАТНАЯ ТЕССЕЛЯЦИЯ

Добавлена поддержка аппаратной тесселяции , но при этом ваше устройство или видеокарта  должна поддерживать данную технологию , эта технология появилась с выходом OpenGL 4.0.

ГРАФ СЦЕНЫ ОПТИМИЗАЦИЯ

????????????????????????????????

ОСВЕЩЕНИЕ

????????????????????????????????

ИМПОРТ FBX (beta!)

В новой версии добавлена возможность импорта  FBX файлов , но к сожалению пока нет поддержки импорта скелетной анимации , она должна заменить полу-функциональную поддержку OgreXML.

ГЕОМЕТРИЯ INSTANCING

При создании некого 3D мира допустим с очень большим количеством растений или деревьев это очень влияет на производительность системы , так вот в новой версии JME появилась технология Instancing , данная технология позволяет ускорить процесс визуализации повторяющих моделей на сцене с помощью нового класса InstancedNode , но эта технология требует аппаратной поддержки OpenGL 3.0 и выше.

ПОТОКОВОЕ АУДИО

?????????????????????????????????????????

HIGH DYNAMIC RANGE RENDERING

Появилась возможность использовать технологию HDR , данная технология позволяет улучшить отображение моделей на сцене , детальней можете почитать здесь.

ПРОФИЛИРОВАНИЕ ПРИЛОЖЕНИЙ

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

IOS УЛУЧШЕНИЯ
Загрузка текстур исправлена ​​проблема.
Поддержка аудио в настоящее время включена.

ANDROID УЛУЧШЕНИЯ

Исправлено декодирование текстур с помощью С++ кода ,  что также  ускорило загрузку текстур.
Исправлено декодирование OGG аудио файлов используя С++ код , это позволяет использовать родную библиотеку OpenAL , появилась возможность использовать 3D звук, эффект Доплера, и реверберации.

ИМПОРТ БЛЕНДЕР

Улучшена поддержка анимированных  моделей (инверсной кинематики).
Улучшена загрузка  .blend файлов.

РЕДАКТОР JME 3.1

Добавлены инструменты масштабирования и поворота.

Добавлена новая тема под названием DarkMonkey  , вы можете включить её Сервис>Параметры>Внешний Вид>Вид и рабочие свойства.

2

От автора 

А также как написано на официальном сайте JMonkeyengine исправлено еще 1 триллион багов прошлой версии , так что надо использовать , только так можно увидеть все прелести движка JME 3.1.

Спасибо за внимание!!!

Давайте представим что есть у нас некая игра написанная на движке JME3 , а стандартное окно запуска нам не очень нравится , в таком случае появляться желание сделать свое меню настроек допустим настройки графики , звука и прочее , собственно  в этой статье мы рассмотрим как с помощью движка JME можно получить доступ к настройкам системы.

package test;

import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.DisplayMode;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;

public class SettingsTest extends SimpleApplication {

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

 @Override
 public void simpleInitApp() {
   flyCam.setDragToRotate(true);
   flyCam.setMoveSpeed(20);
   viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));

   createLight();
   createScene();
 }
 //Создание сцены
 private void createScene(){
   assetManager.registerLocator("E:/AssetsTest/jME3-testdata/town.zip", ZipLocator.class);
   Spatial sceneModel = assetManager.loadModel("main.scene");
   sceneModel.setLocalScale(2f);
   rootNode.attachChild(sceneModel);
 }
 //Создание источника света
 private void createLight() {
   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);
 }

 @Override
 public void start(){
   AppSettings settings = new AppSettings(true);
   settings.setWidth(640);//Ширина экрана в пикселях
   settings.setHeight(480);//Высота экрана в пикселях
   settings.setFrameRate(120);//Частота кадров (fps)
   settings.setVSync(false);//Вертикальная синхронизация
   settings.setBitsPerPixel(24);//Установка глубина цвета
   settings.setRenderer(AppSettings.LWJGL_OPENGL3);//Версия рендеринга OpenGL
   settings.setFrequency(60);//Частота обновления
   settings.setSamples(16);//Сглаживание
   settings.setAudioRenderer(AppSettings.LWJGL_OPENAL);
   //Текущее разрешение экрана
   Dimension resolution = new Dimension(Toolkit.getDefaultToolkit().getScreenSize());
   settings.setResolution(resolution.width, resolution.height);
   settings.setFullscreen(true);//Полно-Экранный режим

   //Установка настроек
   this.setSettings(settings);
   this.createCanvas(); 

   //Создание контекста отображения
   JmeCanvasContext ctx = (JmeCanvasContext) getContext();
   ctx.setSystemListener(this);
   ctx.getCanvas().setPreferredSize(new Dimension(640, 480));

   //Создание формы
   JFrame frame = new JFrame();
   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   frame.setSize(1024, 748);
   frame.setLocationRelativeTo(null);

   frame.getContentPane().add(ctx.getCanvas());
   frame.setVisible(true);

   //Получаем список разрешений
   DisplayMode[] modes = null;
 try {
   modes = org.lwjgl.opengl.Display.getAvailableDisplayModes();
 } catch (LWJGLException e) {
   e.printStackTrace();
 }

 for(DisplayMode mode : modes){
   System.out.println("> "+mode.getWidth()+" "+mode.getHeight()+" "+ mode.getBitsPerPixel());
  }
 }
}

1

Данный пример демонстрирует создание установку своих настроек , класс AppSettings он предназначен для хранения и установки настроек нашей игры или приложения ,  ну а далее описание методов

Установка ширины и высоты окна приложения.

setWidth(640);
setHeight(480);

Установка количества кадров в секунду (frame per second).

setFrameRate(120);

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

setVSync(false);

Установка глубины цвета , а точнее устанавливаем количество бит на пиксель по умолчанию 24.

setBitsPerPixel(24);

Даем понять движку какой версией OpenGL будет производится обработка графики.

setRenderer(AppSettings.LWJGL_OPENGL3);

Установка частоты обновления экрана в герцах , по умолчанию 60.

settings.setFrequency(60);

Установка сглаживания (anti-aliasing)  , эта технология позволяет сгладить эффекты зубчатости моделей на сцене , максимальное значение зависит от поддержки вашей видеокарте , на слабых видеокартах может сильно тормозить отображение , моя HD7790 поддерживает  х16.

setSamples(16);

Установка звуковой системы в движке , есть два варианта первый это использование библиотеки OpenAL и второй null это отключение использования звука.

setAudioRenderer(AppSettings.LWJGL_OPENAL);

Установка разрешения экрана в пикселях.

setResolution(resolution.width, resolution.height);

Получаем текущее разрешение экрана и установка разрешения.

Dimension resolution = Toolkit.getDefaultToolkit().getScreenSize();
setResolution(resolution.width, resolution.height);

Включение полно-экранного режима , эта функция не работает при создании отдельного контекста отображения , как в нашем случае.

setFullscreen(true);

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

DisplayMode[] modes = null;
 try {
   modes = org.lwjgl.opengl.Display.getAvailableDisplayModes();
 } catch (LWJGLException e) {
 e.printStackTrace();
 }

 for(DisplayMode mode : modes){
   System.out.println("> "+mode.getWidth()+" "+mode.getHeight()+" "+ mode.getBitsPerPixel());
 }

Этот пример демонстрирует не только установку настроек системы , а также мы здесь создаем свой контекст отображения , дабы не использовать стандартное окно настроек JME3 , я думаю все ясно модель сцены можете скачать здесь town.zip.

Спасибо за внимание !!!

В прошлом уроке мы научились создавать GUI приложение с помощью XML файла , а так же добавили панель с текстом , в этом уроке мы будем дела тоже самое но без использования XML файла , а точней мы все создавать будем  с помощью классов и методов java.

package test;

import com.jme3.app.SimpleApplication;
import com.jme3.niftygui.NiftyJmeDisplay;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.builder.ElementBuilder.Align;
import de.lessvoid.nifty.builder.ElementBuilder.VAlign;
import de.lessvoid.nifty.builder.LayerBuilder;
import de.lessvoid.nifty.builder.PanelBuilder;
import de.lessvoid.nifty.builder.ScreenBuilder;
import de.lessvoid.nifty.builder.TextBuilder;
import de.lessvoid.nifty.screen.DefaultScreenController;
import de.lessvoid.nifty.tools.Color;

public class NiftyGuiWoutXML extends SimpleApplication {

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

 @Override
 public void simpleInitApp() {
   flyCam.setEnabled(false);
   NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);

   Nifty nifty = niftyDisplay.getNifty();
   guiViewPort.addProcessor(niftyDisplay);

   nifty.loadStyleFile("nifty-default-styles.xml");
   nifty.loadControlFile("nifty-default-controls.xml");

   ScreenBuilder screen = new ScreenBuilder("screen");
   screen.controller(new DefaultScreenController());

   LayerBuilder layer = createLayer();
   PanelBuilder panel = createPanel();
   TextBuilder text = createText();

   panel.text(text);
   layer.panel(panel);
   screen.layer(layer);

   nifty.addScreen("screen", screen.build(nifty));
   nifty.gotoScreen("screen");
 }
 //Создание слоя
 private LayerBuilder createLayer() {
   LayerBuilder layer = new LayerBuilder("layer");
   layer.childLayoutCenter();
   layer.backgroundColor("#000f");

   return layer;
 }
 //Создание панели
 private PanelBuilder createPanel() {
   PanelBuilder panel = new PanelBuilder("panel");
   panel.width("35%");
   panel.height("25%");
   panel.align(Align.Center);
   panel.valign(VAlign.Center);
   panel.backgroundColor(new Color(0,1,0,1));
   panel.childLayoutCenter();
   panel.visibleToMouse(true);

   return panel;
 }
 //Создание текста
 private TextBuilder createText(){
   TextBuilder text = new TextBuilder("text");
   text.font("aurulent-sans-16.fnt");
   text.color("#000f");
   text.text("Hello from jME3");
   text.align(Align.Center);
   text.valign(VAlign.Center);

   return text;

 }
}

 

Возможно я привык делать по старинке но такой подход как создание GUI приложений с использованием XML файлов мне не очень нравится , да что там говорить мне совсем не нравится , ну конечно если  не было альтернативы то пришлось использовать то что есть , но альтернатива есть и довольно не плохая в реализации , далее перейдем к описанию функций.

 

Установка размещения вложенных компонентов.

childLayoutCenter();
childLayout(ChildLayoutType.Center);

 

Установка цвета фона.

backgroundColor("#000f");
backgroundColor(new Color(0,1,0,1));

 

Ширина и высота в пикселях или в процентах.

width("35%");
height("25%");

 

Выравнивание.

alignCenter();
align(Align.Center);

 

Вертикальное выравнивание.

valignCenter();
valign(VAlign.Center);

 

Установка видимости мыши на компоненте.

visibleToMouse(true);

 

Установка шрифта.

font("aurulent-sans-16.fnt");

 

Установка цвета  , в нашем коде устанавливаем цвет текста.

color("#000f");

 

Установка текста.

text("Hello from jME3");

 

Создание слоя(Layer) входящий параметр в конструкторе идентификатор.

LayerBuilder layer = new LayerBuilder("layer");

 

Создание экрана(Screen) входящий параметр в конструкторе идентификатор.

ScreenBuilder screen = new ScreenBuilder("screen");

 

Создание текста входящий параметр в конструкторе идентификатор.

TextBuilder text = new TextBuilder("text");

 

С помощью метода layer() в Экране мы привязываем подготовленный слой , тоже и касается остальных методов привязываем друг к другу таким порядком , экран > слой > панель > текст.

panel.text(text);
layer.panel(panel);
screen.layer(layer);

 

Как по мне такой подход более удобен в разработке , хотя разные ситуации бывают допустим если вам нужно простым способом менять дизайн вашей программы без компиляции то естественно лучше будет использовать XML файлы.

1

Спасибо за внимание!!

В этом уроке мы рассмотрим как с помощью движка JME3 можно работать с клавиатурой , а также мы рассмотрим как добавить и удалить обработчики событий клавиатуры ну и кучу разной мелочи.


package test;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.ColorRGBA;

public class TestKeyboard extends SimpleApplication implements ActionListener, AnalogListener{
 BitmapText text;
 String strTemp = new String();

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

@Override
 public void simpleInitApp() {
   //Назначение кнопок клавиатуры
   inputManager.addMapping("key_s", new KeyTrigger(KeyInput.KEY_S));
   inputManager.addListener(this, "key_s");
   inputManager.addMapping("key_q_e", new KeyTrigger(KeyInput.KEY_Q), new KeyTrigger(KeyInput.KEY_E));
   inputManager.addListener(this, "key_q_e");
   inputManager.addMapping("key_enter", new KeyTrigger(KeyInput.KEY_RETURN));
   inputManager.addListener(this, "key_enter");
   inputManager.addMapping("key_clear", new KeyTrigger(KeyInput.KEY_SPACE));
   inputManager.addListener(this, "key_clear");

   inputManager.addMapping("key_delete", new KeyTrigger(KeyInput.KEY_DELETE));
   inputManager.addListener(this, "key_delete");
  //inputManager.addListener(this, new String[]{"key_s", "key_q_e", "key_enter", "key_clear", "key_delete"});
  createText();

 }

@Override
 public void onAction(String name, boolean isPressed, float tpf) {
 //Кнопка S
 if (name.equals("key_s") && isPressed){
   addText("s");
 }
 //Кнопки Q и Е
 if (name.equals("key_q_e") && isPressed){
   addText(name);
 }
 //Кнопка Энтер
 if (name.equals("key_enter") && isPressed){
   addText("\n");
 }
 //Кнопка Пробел
 if (name.equals("key_clear") && !isPressed){
   clearText();
 }
 //Кнопка Delete
 if (name.equals("key_delete") && !isPressed){
   inputManager.deleteMapping("key_s");
   inputManager.deleteMapping("key_q_e");
   inputManager.deleteMapping("key_enter");
   inputManager.deleteMapping("key_space");
   inputManager.deleteMapping("key_delete");

   inputManager.removeListener(this);
 }

 }
 //Создание текста
 private void createText(){
   BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
   text = new BitmapText(font, false);
   text.setText("JME3 Keyboard Test !!!!");
   text.setColor(ColorRGBA.Green);
   updatePosText();
   guiNode.attachChild(text);
 }
 //Добавление текста
 private void addText(String str){
   strTemp += ' ' + str;
   text.setText(strTemp);
   updatePosText();
 }
 //Очистка текста
 private void clearText(){
   strTemp = "";
   text.setText(strTemp);
   updatePosText();
 }
 //Обновление позиции текста
 private void updatePosText(){
   int w = settings.getWidth();
   int h = settings.getHeight();
   text.setLocalTranslation(w / 2 - text.getLineWidth() / 2, h / 2 + text.getLineHeight() / 2, 0);
 }

 @Override
 public void onAnalog(String name, float value, float tpf) {
   System.out.println("This analog event");
 }
}

Этот код демонстрирует обработку событий клавиатуры , перед тем как назначить обработчик событий мы должны назначить триггер в его входит константа которая указывает на кнопку клавиатуры , все константы описаны в интерфейсе KeyInput , далее с помощью метода addMapping мы добавляем имя и триггер , таким образом мы связываем определенное имя к определенной кнопке на клавиатуре , это нужно для того чтобы при определенных событиях кнопок мы получали не индекс клавиатуры а логическое имя.

Ну а далее идет назначение события с помощью метода  addListener , где первый входящий параметр интерфейс InputListener , на данный момент есть два наследника этого интерфейса это ActionListener и AnalogListener.

Метод onAction вызывается когда нажимаем кнопку или отжимаем , параметр name возвращает имя нажатой кнопки привязанное функцией addMapping , по втором параметру isPressed можно определять состояние кнопки нажата она или нет.


public interface ActionListener extends InputListener {
  public void onAction(String name, boolean isPressed, float tpf);

Метод onAnalog вызывается постоянно при нажатой кнопки в зависимости от ваших кадров в секунду при 60 fps и удерживании кнопки клавиатуры 1 секунду метод вызывается 60 раз , первый параметр как и в onAction имя нажатой кнопки , а второй параметр value он определяет время между вызовом метода , допустим при частоте 60fps параметр value должен быть равен 1/60=0.016 сек.

public interface AnalogListener extends InputListener {
   public void onAnalog(String name, float value, float tpf);
}

Добавление триггера под именем «key_s» и кнопкой S.

  addMapping("key_s", new KeyTrigger(KeyInput.KEY_S));

Здесь добавляем сразу два триггера под одним именем «key_q_e» и двумя кнопками Q и E.

  addMapping("key_q_e", new KeyTrigger(KeyInput.KEY_Q), new KeyTrigger(KeyInput.KEY_E));

Добавление обработчика события параметр this говорит о том что интерфейсы ActionListener и AnalogListener  находятся в текущем классе, второй параметр указываем имя триггера .

  addListener(this, "key_enter");

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

  inputManager.addListener(this, new String[]{"key_s", "key_q_e", "key_enter", "key_clear", "key_delete"});

В коде я при нажатии кнопок выполняю определенные действия с выводом текста на экране .

По нажатию кнопки S мы добавляем текст на экран

//Кнопка S
 if (name.equals("key_s") && isPressed){
   addText("s");
 }

По нажатию кнопки Q или E мы добавляем текст на экран

//Кнопки Q и Е
 if (name.equals("key_q_e") && isPressed){
  addText(name);
 }

По нажатию кнопки Enter мы добавляем перенос строки

//Кнопка Энтер
 if (name.equals("key_enter") && isPressed){
  addText("\n");
 }

По нажатию кнопки Пробел очищаем текст на экране

//Кнопка Пробел
 if (name.equals("key_clear") && !isPressed){
   clearText();
 }

По нажатию кнопки Delete удаляем все триггеры и интерфейс обработчика событий .

//Кнопка Delete
 if (name.equals("key_delete") && !isPressed){
   inputManager.deleteMapping("key_s");
   inputManager.deleteMapping("key_q_e");
   inputManager.deleteMapping("key_enter");
   inputManager.deleteMapping("key_space");
   inputManager.deleteMapping("key_delete");

   inputManager.removeListener(this);
  }
 }

1

2

Как вы видите все довольно просто и на мое мнение удобно , если что то не ясно лучше взять и попробовать.

Спасибо за внимание !!!

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

package test;

import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.cinematic.MotionPath;
import com.jme3.cinematic.events.MotionEvent;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.math.Spline.SplineType;

public class TestMovingParticle extends SimpleApplication implements ActionListener {

 MotionEvent motionControl;
 MotionPath path;
 boolean enDebugShape = false;

 public static void main(String[] args) {
   new TestMovingParticle().start();

 }

 @Override
 public void simpleInitApp() {
   flyCam.setMoveSpeed(20);
   ParticleEmitter emit = new ParticleEmitter("Emitter", Type.Triangle, 300);
   emit.setGravity(0, 0, 0);
   emit.getParticleInfluencer().setVelocityVariation(1);
   emit.setLowLife(1);
   emit.setHighLife(1);
   emit.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
   emit.setImagesX(15);
   emit.setStartSize(1);
   emit.setEndSize(6);
   Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
   mat.setTexture("Texture", assetManager.loadTexture("Effects/Smoke/Smoke.png"));
   emit.setMaterial(mat);

   rootNode.attachChild(emit);
   //Назначение нажатия кнопки X
   inputManager.addListener(this, "speed");
   inputManager.addMapping("speed", new KeyTrigger(KeyInput.KEY_X));
   //Назначение нажатия кнопки C
   inputManager.addListener(this, "debugshape");
   inputManager.addMapping("debugshape", new KeyTrigger(KeyInput.KEY_C));

   createMotionPath(emit);
 }
 //Создание пути
 private void createMotionPath(Geometry moveEffects){
   path = new MotionPath();

   //Добавление точек
   path.addWayPoint(new Vector3f(10, 3, 0));
   path.addWayPoint(new Vector3f(10, 3, 10));
   path.addWayPoint(new Vector3f(-40, 3, 10));
   path.addWayPoint(new Vector3f(-40, 3, 0));
   path.addWayPoint(new Vector3f(-40, 8, 0));
   path.addWayPoint(new Vector3f(10, 8, 0));
   path.addWayPoint(new Vector3f(10, 8, 10));
   path.addWayPoint(new Vector3f(15, 8, 10));
   path.addWayPoint(new Vector3f(10, 3, 0));

   path.setPathSplineType(SplineType.CatmullRom);

   path.enableDebugShape(assetManager, rootNode);

   motionControl = new MotionEvent(moveEffects,path);
   motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation);
   motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y));

   motionControl.setInitialDuration(20f);
   motionControl.setSpeed(1f);
   motionControl.setLoopMode(LoopMode.Loop);
   motionControl.play();

 }

 @Override
 public void onAction(String name, boolean isPressed, float tpf) {
   //Переключение скорости движения
   if ("speed".equals(name) && isPressed) {
     if (motionControl.getSpeed() == 1)
       motionControl.setSpeed(5);
     else
       motionControl.setSpeed(1);
   }
   //Включение и отключение отображения линий и точек пути
   if ("debugshape".equals(name) && isPressed) {
     enDebugShape = !enDebugShape;
     if (enDebugShape)
       path.disableDebugShape();
     else
       path.enableDebugShape(assetManager, rootNode);

   }

  }

}

1

2

В этом коде я добавил два обработчика событий , один на кнопку Y — переключение скорости движения и Y — включение и отключение отображение линий пути , ну поешали к описанию функций.

 

Ну естественно сбрасываем гравитацию в ноль она нам не нужна , по умолчанию так (0.0f, 0.1f, 0.0f).


setGravity(0, 0, 0);

Скорость изменения частиц , если завысить этот параметр у нас получится что то типа само наводящей ракеты.


setVelocityVariation(1);

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


 emit.setLowLife(1);
 emit.setHighLife(1);

 

Начального скорость анимации частиц , что то подобие сбившего самолета так что лучше больше не ставить.


setInitialVelocity(new Vector3f(0, .5f, 0));

 

Ширина изображения ,  наша текстура состоит из 15 частей.

 setImagesX(15); 

 

Начальный и конечный размер анимации, я использовал чтоб увеличить в размерах.

 emit.setStartSize(1);
 emit.setEndSize(6);

 

Я думаю все ясно данный пример можно использовать в многих вещах такие как  выхлоп газа из машины , турбина самолета , шлейф ракеты и прочие.

Спасибо за внимание!!!

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

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


package test;

import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;

public class HelloEffects extends SimpleApplication {

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

@Override
public void simpleInitApp() {
  //Создание эффекта огня
  ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
  //Создание материала
  Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
  //Загрузка текстур
  mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
  fire.setMaterial(mat_red);
  fire.setImagesX(2);
  fire.setImagesY(2);
  fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red
  fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
  fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0));
  fire.setStartSize(1.5f);
  fire.setEndSize(0.1f);
  fire.setGravity(0, 1, 0);
  fire.setLowLife(1f);
  fire.setHighLife(3f);
  fire.getParticleInfluencer().setVelocityVariation(0.3f);
  rootNode.attachChild(fire);

 }
}

Данный пример демонстрирует эффект огня , класс ParticleEmitter  отвечает за создание систему частиц , а далее перейдем к описанию методов.

Конструктор класса первый входящий параметр имя эффекта , а второй определяет тип частиц и может принимать 2 параметра.

Point — Частицы состоят из точек.

Triangle — Каждая частица состоит из двух треугольников в совокупности образуют квадрат.

А последний параметр это максимальное количество частиц.


ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 50);

 

Функция установки материала частицы.


 setMaterial(mat_red);

 

Установка ширины изображения.


 setImagesX(2); 

 

Установка высоты изображения.


setImagesY(2);

 

Установка конечного цвета частиц.


setEndColor(new ColorRGBA(1f, 0f, 0f, 1f));

 

Установка начального цвета частиц.


 setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f));

 

Начального скорость анимации частиц.


setInitialVelocity(new Vector3f(0, 2, 0));

 

Начальный размер частиц.


setStartSize(1.5f);

 

Конечный размер частиц.


setEndSize(0.1f);

 

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


setGravity(0, 0.5f, 0);

 

Низкое значение жизни анимации.


setLowLife(1f);

 

Высокое значение жизни анимации , время анимации определяется случайным выбором между низким и высоким значением.


setHighLife(3f);

 

Скорость изменения частиц.


setVelocityVariation(0.3f);

Текстура используемая в примере.

flame

Вот что получилось.

2

1

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

Спасибо за внимание!!!

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

Что касается физики движения игрока на сцене я использовал стандартную физику JME а точнее JBullet , весь код я поместил в отдельный класс PlaerCollisionControl  , а также анимация взрыва в классе ExplosionGeometry , код этих двух классов  размещать здесь я не буду , первая причина размер а вторая для этого созданы отдельные темы физика и эффекты , ну в общем мы рассмотрим их функции а также функции касающиеся выстрелов и уничтожения объектов.

Начнем наверно с физики а точнее класса PlaerCollisionControl   у него всего 2 функции.

Первый параметр это stateManager он нужен для привязки физики к сцене , inputManager естественно для назначение событий мыши и клавиатуры для управления игроком, камера (cam) привязывается к физическому объекту для передвижения по сцене, ну и последний параметр это сцена на которой физика будет производить вычисления столкновений а простыми слова по которой мы будем двигаться.

 init(stateManager, inputManager, cam, (Node) scene.scene);

Этот метод физика производит расчеты позиции где мы находимся и мы  перемещаем камеру.

update();

Теперь опишу класс эффектов.

Тоже инициализация как и с физикой но с другими параметрами первый нужен для загрузки текстур анимации которые входят в стандартный пакет JME , ну а остальные нужны для привязки эффекта к сцене.

init(assetManager, rootNode, renderManager);

Этот метод нужен для вызова прорисовки эффекта.

draw();

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

Принцип такой передаем параметры камеры лучу и проверяем если наш результат больше 0  а точнее он есть , и если наш объект под именем «Box»  то с помощью функции detachChild убираем коробку из сцену таким образом мы имитируем что то подобное убийства коробки  , ну а далее с помощью функции setLocalTranslation мы перемещаем наш эффект на позицию убитой коробки и с помощью функции draw рисуем .

private void parseRay(){
  CollisionResults results = new CollisionResults();
  Ray ray = new Ray(cam.getLocation(), cam.getDirection());
  rootNode.collideWith(ray, results);

  if (results.size() > 0 && results.getCollision(0).getGeometry().getName().equals("Box")) {
    rootNode.detachChild(results.getCollision(0).getGeometry());
    ExplosionGeometry.explosionEffect.setLocalTranslation(
                      results.getCollision(0).getGeometry().getLocalTranslation());
    ExplosionGeometry.draw();

  }
} 

Ну по сути все просто при выстреле вызываем метод parseRay и он делает свое дело , ну далее перейдем к главному классу.


package test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;

import weapons.AK47;

public class SceneControl extends SimpleApplication {
  AK47 player;
  Scene scene;
  PlaerCollisionControl pcc = new PlaerCollisionControl();
  ExplosionGeometry eg = new ExplosionGeometry();

public static void main(String[] args) {
  new SceneControl().start();

}
//Инициализация
@Override
public void simpleInitApp() {

  flyCam.setMoveSpeed(30);
  //flyCam.setDragToRotate(true);
  setDisplayStatView(false);
  scene = new Scene(assetManager, rootNode, viewPort); //Создание сцены неба ландшафта и прочее
  scene.createScene();

  createCube(1000);

  player = new AK47(assetManager, cam, inputManager, settings, guiNode, rootNode);//Создание оружия
  rootNode.attachChild(player);

  pcc.init(stateManager, inputManager, cam, (Node) scene.scene);
  eg.init(assetManager, rootNode, renderManager);

}

@Override
public void simpleUpdate(float tpf) {
  pcc.update();
  player.update();
  super.simpleUpdate(tpf);

}
//Хаотичное добавление моделей на сцену
public void createCube(int count){
  for (int i = 0; i < count; i++) {
    Box cube1Mesh = new Box(FastMath.rand.nextFloat()*10,FastMath.rand.nextFloat()*10,
    FastMath.rand.nextFloat()*10);
    Geometry cube1Geo = new Geometry("Box", cube1Mesh);
    //cube1Geo.getMesh().setMode(Mode.LineStrip);
    cube1Geo.setLocalTranslation(new Vector3f(-3f,1.1f,0f));
    Material cube1Mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    Texture cube1Tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
    cube1Mat.setTexture("ColorMap", cube1Tex);
    cube1Geo.setMaterial(cube1Mat);

    cube1Geo.setLocalTranslation(FastMath.rand.nextFloat()*300, FastMath.rand.nextFloat()*300,
    FastMath.rand.nextFloat()*300);
    rootNode.attachChild(cube1Geo);

    }
  }

}

Единственное что я могу дополнить это класс он хаотично создает кубики на сцене где его входящий параметр count определяет количество кубиков.

createCube(int count)

1

Исходники тут.

Спасибо за внимание!!

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

Да и потому что наш код растет и размещать его не очень актуально с точки зрения читабельности , я буду описывать новые функции и изменения частично.

Исходники и все ресурсы вы сможете скачать ниже.

ИЗМЕНЕНИЯ

Добавлен код анимации в методе update принцип примерно такой при включается переменная enableShoots и в этом методе плавно автомат начинает двигаться таким образом имитируется отдача при стрельбе.

 /**
 * Функция расчета и масштабирования оружия с камерой и анимации
 */
 public void update(){
   //Перемещение оружия по координатам камеры
   Vector3f vectorDifference = new Vector3f(cam.getLocation().subtract(weapon.getWorldTranslation()));
   this.setLocalTranslation(vectorDifference.addLocal(this.getLocalTranslation()));
   Quaternion worldDiff = new Quaternion(cam.getRotation().mult(this.getWorldRotation().inverse()));
   this.setLocalRotation(worldDiff.multLocal(this.getLocalRotation()));
   //Обработка анимации выстрелов
   if (enableShoots) {
     counterAnim++;
   if (counterAnim == lenghtShotsAnim)
     animMove.x -= speedShotsAnim;
   if (counterAnim == (lenghtShotsAnim*2))
     animMove.x += speedShotsAnim;
   if (counterAnim > (lenghtShotsAnim*2)+1)
     counterAnim = 0;
   }
   //Позиционирование оружия
   this.move(cam.getDirection().mult(move.x+animMove.x));
   this.move(cam.getUp().mult(move.y+animMove.y));
   this.move(cam.getLeft().mult(move.z+animMove.z));
   this.rotate(rotate.x,rotate.y, rotate.z);
 }

В методе initKeys добавлен обработчик событий при нажатии левой кнопки мыши .


/**
* Функция инициализация кнопок
*/
public void initKeys(){
  inputManager.addMapping("reload", new KeyTrigger(KeyInput.KEY_R));
  inputManager.addListener(this,"reload");
  inputManager.addMapping("mouseleft", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
  inputManager.addListener(this, "mouseleft");
}

Добавлена функция загрузки звука выстрелов все аналогично звука перезарядки , кроме вызова функции повторного воспроизведения setLooping(true).

/**
 * Функция загрузки звука выстрелов <br>
 * @param rootPath Путь к корневому каталогу
 * @param name Имя Файла
 * @param locatorClass Локатор
 */
 public void loadAudioShoots(String rootPath, String name, Class<? extends AssetLocator> locatorClass){
   assetManager.registerLocator(rootPath, locatorClass);
   shoots = new AudioNode(assetManager, name);
   shoots.setLooping(true);
 }

Добавлен обработчик события левой мыши для выстрелов , если вы заметили при нажатии мыши в коде так же проверяется переменная animComplete, это нужно для того что бы в момент перезарядки нельзя было стрелять .

 @Override
 public void onAction(String name, boolean isPressed, float tpf) {
 //Обработка событий по нажатию кнопки R
 if (animComplete && isPressed && name.equals("reload")){
   channel.setAnim("1");
   channel.setLoopMode(LoopMode.DontLoop);
   reload.play();
   shoots.stop();
 }
 //Обработка событий левой кнопки мыши
 if (animComplete && name.equals("mouseleft")){
   enableShoots = true;
   shoots.play();
 }
 //Если кнопка отжата сбрасываем все
 if (!isPressed){
   enableShoots = false;
   animMove.zero();
   counterAnim = 0;
   shoots.stop();
  }
 }

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


/**
*
* @return Возвращает значение скорости анимации перезарядки
*/
public float getSpeedShotsAnim() {
  return speedShotsAnim;
}
/**
*
* @param speedShotsAnim Установка скорости перезарядки по умолчанию 0.02
*/
public void setSpeedShotsAnim(float speedShotsAnim) {
  this.speedShotsAnim = speedShotsAnim;
}
/**
*
* @return Возвращает значение дальности анимации перезарядки
*/
public int getLenghtShotsAnim() {
  return lenghtShotsAnim;
}
/**
*
* @param lenghtShotsAnim Установка дальности перемещения анимации перезарядки
* по умолчанию 5
*/
public void setLenghtShotsAnim(int lenghtShotsAnim) {
  this.lenghtShotsAnim = lenghtShotsAnim;
} 

1

Конечно анимация не столь профессиональная но моя задача научить , ну я думаю в следующем уроке сделаем что нибудь по круче.

Исходники  , звук.

Спасибо за внимание!!!

Иногда для построения некой анимации мы представляем как она будет выглядеть но в данном случае у меня мыслей никаких нет, и я решил сделать небольшую анимации с помощью класса AnimationFactory , данная технология предоставляет очень удобный механизм построения анимации вращения , перемещения и масштабирования.

Ради наглядного примера я взял 2 бокса и поместил в узел  Node , и поставил для себя задачу мои кубики должны переместится по определенным осям и при этом один из боксов должен вращаться определенное время , ну перейдем к реализации.

 package test;

import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimationFactory;
import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;

public class TestAnimationFactory extends SimpleApplication {
Node model;

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

@Override
public void simpleInitApp() {
  flyCam.setMoveSpeed(20);
  createScene();
  createAmbienLight();
  createDirectionLight();

  //Создание анимации модели
  AnimationFactory animModel = new AnimationFactory(10, "anim_model", 25);
  //Добавление перемещение модели
  animModel.addTimeTranslation(5, new Vector3f(10, 0, 0));
  animModel.addTimeTranslation(10, new Vector3f(10, 5, 0));

  //Контроль анимации модели
  AnimControl contModel = new AnimControl();
  //Добавляем анимацию модели
  contModel.addAnim(animModel.buildAnimation());
  //Привязываем контроль анимации к модели
  model.addControl(contModel);

  //Создание анимации бокса
  AnimationFactory animBox1 = new AnimationFactory(8, "anim_box1", 25);
  //Добавление вращение бокса
  animBox1.addTimeRotation(8f,new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z));

  AnimControl contBox1 = new AnimControl();
  contBox1.addAnim(animBox1.buildAnimation());

  //Привязываем контроль анимации к бокса
  model.getChild("box1").addControl(contBox1);

  //Запуск анимации
  contModel.createChannel().setAnim("anim_model");
  contBox1.createChannel().setAnim("anim_box1");

}
//Создание боксов
private void createScene() {
  model = new Node();
  Box box1 = new Box(1, 1, 1);
  Geometry geom1 = new Geometry("box1", box1);
  geom1.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"));
  model.attachChild(geom1);

  Box box2 = new Box(1, 1, 1);
  Geometry geom2 = new Geometry("box2", box2);
  geom2.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m"));
  geom2.setLocalTranslation(0,0,4);
  geom2.updateModelBound();
  model.attachChild(geom2);
  rootNode.attachChild(model);
}

// Направленный свет
private void createDirectionLight() {
  DirectionalLight sun = new DirectionalLight();
  sun.setColor(ColorRGBA.White);
  sun.setDirection(new Vector3f(-1f, -1f, -3f).normalizeLocal());
  rootNode.addLight(sun);
}

// Рассеянный свет
private void createAmbienLight() {
  AmbientLight al = new AmbientLight();
  al.setColor(ColorRGBA.White.mult(1.5f));
  rootNode.addLight(al);
}

}

1

Принцип работы такой при добавлении новой точки анимации класс учитывает координаты модели , ну простыми словами допустим наша модель стоит по координатам (0,0,0) и если мы добавим addTimeTranslation(5, new Vector3f(10, 0, 0)); время 5 и позиция 10 , то наша модель будет перемещаться по оси X в течении заданного времени .

ОПИСАНИЕ ФУНКЦИЙ

Конструктор класса AnimationFactory  первый параметр это общее время анимации в секундах , второй  имя анимации и третий количество кадров в секунду (по умолчанию 30).


new AnimationFactory(10, "anim_model", 25);

Добавление точки трансформации первый параметр длительность анимации в секундах а второй координата перемещения.


addTimeTranslation(5, new Vector3f(10, 0, 0));

Добавление точки вращения первый параметр длительность анимации в секундах а второй угол вращения.


addTimeRotation(8f,new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z));

Добавление точки масштабирования первый параметр длительность анимации в секундах а второй координаты масштабирования.


addTimeScale(float time, Vector3f scale)

Добавление контроля анимации собственно привязка к модели анимацию.


model.addControl(contModel);

Добавление анимации в класс AnimControl где функция buildAnimation строит анимацию по заданным ранее параметрам.


addAnim(animModel.buildAnimation());

Создание канала анимации и плюс функция .setAnim(«anim_model») запускает ее по заданном имени.


contModel.createChannel().setAnim("anim_model");

Данная технология очень полезна для создания анимации лифтов, кранов, подъемников и прочее ну перечислять можно бесконечно.

Спасибо за внимание!!!

Сегодня решил посвятить немного времени  неплохой технологии под название MotionPath  — Motion (Движение) Path (Путь) простыми словами данная технология позволяет просто реализовать анимацию движения по определенном пути , перейдем наверно к коду.

import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.cinematic.MotionPath;
import com.jme3.cinematic.MotionPathListener;
import com.jme3.cinematic.events.MotionEvent;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Spline.SplineType;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.util.SkyFactory;

public class TestMotionPath extends SimpleApplication implements MotionPathListener {
 private Spatial teapot;
 private MotionPath path;
 MotionEvent motionControl;
 public static void main(String[] args) {
   new TestMotionPath().start();

 }

 @Override
 public void simpleInitApp() {
   flyCam.setMoveSpeed(20);
   createTeapot();
   createAmbienLight();
   createDirectionLight();
   createSkyBox();
   //Создание пути
   path = new MotionPath();
   //Тип построения линий маршрута
 
   //Добавление точек
   path.addWayPoint(new Vector3f(10, 3, 0));
   path.addWayPoint(new Vector3f(10, 3, 10));
   path.addWayPoint(new Vector3f(-40, 3, 10));
   path.addWayPoint(new Vector3f(-40, 3, 0));
   path.addWayPoint(new Vector3f(-40, 8, 0));
   path.addWayPoint(new Vector3f(10, 8, 0));
   path.addWayPoint(new Vector3f(10, 8, 10));
   path.addWayPoint(new Vector3f(15, 8, 10));

   path.setPathSplineType(SplineType.CatmullRom);
   //Визуальное отображение пути 
   path.enableDebugShape(assetManager, rootNode);
   //Добавляем обработчик событий
   path.addListener(this);
 
   //Класс отвечающий за движение анимации
   motionControl = new MotionEvent(teapot,path);
   //Говорим что наша модель будет двигатся и вращатся 
   motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation);
   //Поворачиваем на 180 градусов по оси Y
   motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y));
 
   //Продолжительность анимации на время в секундах
   motionControl.setInitialDuration(20f);
   //Скорость анимации
   motionControl.setSpeed(1f); 
   //Циклическая анимация
   motionControl.setLoopMode(LoopMode.Loop);
   //Старт анимации
   motionControl.play();
 
 }
 //Создание чайника
 private void createTeapot(){
   Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
   mat.setFloat("Shininess", 1f);
   mat.setBoolean("UseMaterialColors", true);
   mat.setColor("Ambient", ColorRGBA.Black);
   mat.setColor("Diffuse", ColorRGBA.DarkGray);
   mat.setColor("Specular", ColorRGBA.White.mult(0.6f));
 
   teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
   teapot.setName("Teapot");
   teapot.setLocalScale(3);
   teapot.setMaterial(mat);

   rootNode.attachChild(teapot);
 }
 
 // Направленный свет
 private void createDirectionLight() {
   DirectionalLight sun = new DirectionalLight();
   sun.setColor(ColorRGBA.White);
   sun.setDirection(new Vector3f(-1f, -1f, -1f).normalizeLocal());
   rootNode.addLight(sun);
 }

 // Рассеянный свет
 private void createAmbienLight() {
   AmbientLight al = new AmbientLight();
   al.setColor(ColorRGBA.White.mult(0.4f));
   rootNode.addLight(al);
 }

 // Небо
 private void createSkyBox() {
   Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
   sky.setLocalScale(350);
   rootNode.attachChild(sky);
 }
 @Override
 public void onWayPointReach(MotionEvent motionControl, int wayPointIndex) {
    System.out.println(wayPointIndex);
  }

}

2

1

Этот пример демонстрирует движение чайника по определенном пути , класс MotionPath его задача построить путь по нашим указанным точкам , с каждым добавлением точки класс генерирует нам маршрут с пред идущей точкой.

Данная функция определяет тип построения линий маршрута , поддерживает 2 параметра CatmullRom — линии строятся плавно с закруглениями на поворотах подобно кривым безье, Linear — строгие линии получаются острые углы на поворотах , её желательно вызывать после добавления точек так как при вызове строи маршрут .

setPathSplineType(SplineType.CatmullRom);

Функция добавления точек

 path.addWayPoint(new Vector3f(10, 3, 0));

Эта функция автоматически создает нам линии маршрута и на каждой точке ставит кубики , очень хорошая для визуальной оценки.

 path.enableDebugShape(assetManager, rootNode);

Тип направления при движении может принять 5 параметра

Non — Объекта всегда в стартовом направлении

Path — Объект  вращается с направлением пути.

Rotation — Движение пропорционально маршруту не поворачивает на изгибах.

PathAndRotation  — Объект вращается с направлением пути при использовании функции setRotation.

LookAt — Объект вращается но при указанной координате точки с помощью функции setLookAt.

setDirectionType(MotionEvent.Direction.PathAndRotation)

Продолжительность анимации.

 motionControl.setInitialDuration(20f);

Скорость движения.

motionControl.setSpeed(1f);

Есть 3 параметра этой функции

Loop — Анимация будет выполнятся повторно при достижении конечной точки начинать с первой.

DontLoop— Анимация будет выполнятся один раз до достижения конечной точки.

Cycle— Анимация будет выполнятся повторно при достижения конечной точки будет продолжать движение.

setLoopMode(LoopMode.Cycle);

Запуск анимации.

play();

Остановка анимации и при повторном вызове функции play модель перемещается на начальную точку.

stop();

Приостановка анимации и при повторном вызове функции play модель продолжает движение.

pause();

Я добавлю по поводу обработки  события onWayPointReach этот метод вызывается каждый раз при достижении объектом каждой точки и  переменная wayPointIndex возвращает индекс точки.

Что первое приходит в голову по поводу реализации это пример движения поезда можно создать маршрут и добавить допустим вокзалы , поезд доехал до определенной точки вокзала постоял немного с помощью функции pause набрал людей и поехал далее по команде play.

Спасибо за внимание !!!

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