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

Andry

Опубликованно: AndryAndry16.02.2019, 21:51
Последняя редакция, Andry: 16.02.2019 21:51

Параметры для ParticleController:

Название  Название, используемое для геометрии в графе сцены
mesh  Используемая сетка (обычно это PointMesh или QuadMesh)
maxParticles  Максимальное количество частиц, позволяющих активно в любой момент
lifeMin  Минимальное количество времени (в секундах), в течение которого живет каждая частица
lifeMax  Максимальное количество времени (в секундах), в течение которого живет каждая частица
source  Источник, из которого появляются частицы
emissionController  Частота и время появления частиц. Если ноль, то никакие частицы не создаются автоматически, и их нужно запускать вручную, используя emitNextParticle () или emitAllParticles ()
influencers  Ноль или более ParticleInfluencers, каждый из которых меняет поведение частиц.

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

Javadoc для системы можно найти по адресу http://www.zero-separation.com/particles/javadoc.

Сетка(Mesh)

Параметры сетки(mesh):

PointMesh  Самый быстрый и эффективный, но также и самый ограниченный
QuadMesh  Гораздо более гибкая, чем точечная сетка, все частицы представлены в виде двумерных квадратов
TemplateMesh  Позволяет частицам быть полными трехмерными объектами, причем сетка для каждой частицы генерируется из одного из любого числа шаблонных сеток. Это позволяет полностью трехмерные частицы и принимает координаты текстуры и даже (если требуется) цвета вершин и нормали из исходной сетки, преобразуя их по мере необходимости.

Источник

Варианты источника:

PointSource  Генерирует все частицы из определенной точки со случайной скоростью. Сама точка является пространственной, поэтому ее можно прикрепить к графу сцены и перемещать вместе с ней.
MeshSource  Генерирует все частицы из случайно выбранной точки на данной сетке. Выбирается случайный треугольник, а затем излучатель частиц из случайной точки в пределах этого треугольника вдоль вектора нормали треугольника.
WeightedMeshSource  Обеспечивает ту же функциональность, что и MeshSource, но взвешивает треугольники на основе их относительного размера, более крупные треугольники будут иметь тенденцию испускать больше частиц. Это обеспечивает более равномерное распределение, но использует больше ресурсов и нуждается в обновлении при изменении сетки.
ParticleParticleSource  Излучает частицы из другого ParticleController. Частица испускается из случайно выбранной активной частицы, и новая частица начинается с идентичной скоростью, вращением и т. Д. Как частица, из которой она испускается.

EmissionControllers

NULL  NULL EmissionController не испускает какие-либо частицы автоматически, они должны испускаться извне путем вызова ParticleController emitNextParticle () или emitAllParticles (). Обратите внимание, что если ParticleController не используется в течение длительного периода времени, рекомендуется для сохранения ресурсов приостановить его, отключив контроллер или удалив геометрию из графа сцены.
RegularEmission  Этот EmissionController просто испускает частицы с регулярными интервалами, он будет испускать несколько частиц в одном кадре, если прошло более одного интервала с момента предыдущего кадра.

Influencers

ColorInfluencer  Изменить цвет частицы с течением времени
GravityInfluencer  Применить устойчивое ускорение в указанном направлении с течением времени
MultiColorInfluencer  Изменить цвет частицы с помощью нескольких цветов с течением времени
PreferredDestinationInfluencer  Переместить частицу к указанной точке
PreferredDirectionInfluencer  Поверните скорость частиц в заданном направлении во времени
RandomImpulseInfluencer  Применить случайный импульс к частице либо при инициализации, либо в каждом кадре
RandomSpriteInfluencer  Выберите случайный спрайт для частицы из тех, которые доступны при инициализации
RotationInfluencer  Вращайте частицу, выбирая начальную скорость вращения наугад, а затем сохраняя ее
SizeInfluencer  Изменить размер частицы с течением времени
SpatialDestinationInfluencer  Переместите частицу к заданному пространству, она будет пытаться достичь текущего местоположения пространства к концу жизненного цикла частицы.
SpeedInfluencer  Изменить скорость частицы с течением времени
SpriteAnimationInfluencer  Анимируй частицу через доступные спрайты со временем

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

Это система частиц для jME3, выложена для обзора и обсуждения. Это даст возможность для людей комментировать и запрашивать изменения в API или внутренних функций системы. Код для этой системы частиц может быть найден jMonkeyEngine-Contributions

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

Титры

На эти излучатели частиц я был вдохновлён излучателем частиц t0neg0ds, и использую часть кода оттуда.

Он же, в свою очередь, были основаны на оригинальной системе частиц jME3 Кирилла Вайнера

Большая картина

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

Простой способ увидеть, что вам нужно, — это создать новый ParticleController, а затем посмотреть на конструктор, вы можете увидеть какие параметры необходимо там указать.

Название  Название, используемое для геометрии в графе сцены
mesh  Используемая сетка (обычно это PointMesh или QuadMesh)
maxParticles  Максимальное количество частиц, которые могут быть активны в любой момент
lifeMin  Минимальное количество времени (в секундах), в течение которого живет каждая частица
lifeMax  Максимальное количество времени (в секундах), в течение которого живет каждая частица
source  Источник, из которого появляются частицы
emissionController  Частота и время появления частиц. Если ноль, то никакие частицы автоматически не создаются, и их нужно запускать вручную, используя emitNextParticle() или emitAllParticles()
influencers  Ноль или более ParticleInfluencers, каждый из которых меняет поведение частиц.

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

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

Полный список доступных стандартных опций смотрите на странице ссылок.

Простой огонь

public class HelloParticles1_SimpleFire extends SimpleApplication {

public static void main(String[] args){
HelloParticles1_SimpleFire app = new HelloParticles1_SimpleFire();
app.start(); // старт игры
}

@Override
public void simpleInitApp() {

// Создадим новый ParticleController
ParticleController pCtrl = new ParticleController(
// Назовём излучатель
"SimpleFire",
// Используем простую point mesh (самый быстрый, но наиболее ограниченный тип сетки) с заданным
// изображением (из jME3-testdata). Изображение на самом деле содержит сетку спрайтов 2х2.
new PointMesh(assetManager, "Effects/Explosion/flame.png", 2, 2),
// Разрешим не более 32 частиц в любой момент времени
32,
// Частицы существую не менее 2 секунд
2,
// И максимум 3 секунды
3,
// Точечные источники всегда генерируют частицы в месте расположения источника,
// частицам даётся случайная скорость между двумя заданными значениями.
new PointSource(new Vector3f(-3, 0, -3), new Vector3f(3, 0, 3)),
// Испускать частицы через равные промежутки времени, 10 частиц каждую секунду
new RegularEmission(10),
// ** Influencers начинаются здесь
// Выберите случайный спрайт из 4 доступных для каждой частицы
new RandomSpriteInfluencer(),
// Частицы начинаются с размера 0,5 единиц и заканчиваются радиусом 0,1
new SizeInfluencer(0.5f, 0.1f),
// Частицы начинаются желтого цвета и полностью непрозрачными и исчезают с красным
// с очень низкой непрозрачностью
new ColorInfluencer(new ColorRGBA(1,1,0.2f,1), new ColorRGBA(1,0,0,0.1f)),
// Независимо от того, с какой скоростью запускаются частицы, они начнут двигаться вверх.
new PreferredDirectionInfluencer(new Vector3f(0, 1, 0), 0.25f));

// Наконец, прикрепите геометрию к rootNode, чтобы запустить частицы в вашу сцену
rootNode.attachChild(pCtrl.getGeometry());
}
}

Запустите этот код, и результат должен выглядеть примерно так:

particles1

Простой огонь и дым

    @Override
public void simpleInitApp() {

// Создадим новый ParticleController
ParticleController pCtrl = new ParticleController(
// Назовём излучатель
"SimpleFire",
// Используем простую point mesh (самый быстрый, но наиболее ограниченный тип сетки) с заданным
// изображением (из jME3-testdata). Изображение на самом деле содержит сетку спрайтов 2х2.
new PointMesh(assetManager, "Effects/Explosion/flame.png", 2, 2),
// Разрешим не более 32 частиц в любой момент времени, частицы теперь будут живить дольше
// опому что нам их надо будет больше на экране
50,
// Частицы существую не менее 4 секунд
4,
// И максимум 5 секунд
5,
// Точечные источники всегда генерируют частицы в месте расположения источника,
// частицам даётся случайная скорость между двумя заданными значениями.
new PointSource(new Vector3f(-3, 0, -3), new Vector3f(3, 0, 3)),
// Испускать частицы через равные промежутки времени, 10 частиц каждую секунду
new RegularEmission(10),
// ** Influencers начинаются здесь
// Выберите случайный спрайт из 4 доступных для каждой частицы
new RandomSpriteInfluencer(),
// Частицы начинаются с размера 0,5 единиц и заканчиваются радиусом 0,1
new SizeInfluencer(0.5f, 0.25f),
// Частицы начинаются желтого цвета и полностью непрозрачными и исчезают с красным
// с очень низкой непрозрачностью
new MultiColorInfluencer(
new MultiColorInfluencer.Stage(0, new ColorRGBA(1, 1, 0.1f, 1)),
new MultiColorInfluencer.Stage(0.15f, new ColorRGBA(1, 0, 0, 0.25f)),
new MultiColorInfluencer.Stage(0.3f, new ColorRGBA(1f, 1f, 1f, 0.5f)),
new MultiColorInfluencer.Stage(1, new ColorRGBA(1f,1f,1f,0f))
),
// Независимо от того, с какой скоростью запускаются частицы, они начнут двигаться вверх.
new PreferredDirectionInfluencer(new Vector3f(0, 1, 0), 0.25f));

// Наконец, прикрепите геометрию к rootNode, чтобы запустить частицы в вашу сцену
rootNode.attachChild(pCtrl.getGeometry());
}

Вы можете видеть, что единственное изменение состояло в том, чтобы заставить частицы жить немного дольше и изменить ColorInfluencer для MultiColorInfluencer, и все же результат выглядят совершенно иным:

Это еще не очень убедительный огонь, но его очень легко получить и запустить. Одна из проблем этого подхода состоит в том, что частицы сделаны с использованием альфа-аддитивного материала, то есть они могут делать вещи только освещёнными, но не могут их затенять. Это не идеально для дыма, который должен делать вещи затенёнными. Мы еще вернемся к этому позже, но сейчас перейдем к различным типам сеток(mesh).

Quad Meshes и Billboarding

Point Meshe(Точечные сетки) очень быстрые, но у них есть ряд ограничений. Основные из них заключаются в том, что спрайты всегда должны быть направлены к экрану, а на некоторых видеокартах максимальное количество пикселей, которое спрайт может занимать на экране, ограничено.

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

Изображение пламени которое мы создавали ранее будет использоваться для второго излучателя, а первый излучатель будет использовать изображение ниже, которое вы можете скачать для использования в своём коде:
runecircle
    @Override
public void simpleInitApp() {

// Создадим новый ParticleController чтобы обеспечить сам эффект рунного заклинания
ParticleController pCtrl = new ParticleController(
// Назовём излучатель
"SpellRunes",
// Use a Quad Mesh, this image is available for download on this page. The texture file contains
// a single image so there are no sprite columns and rows to set up. The BillboardStrategy is how
// the particles should be oriented, in this case it uses the particle rotation.
new QuadMesh(QuadMeshBillboardStrategy.USE_PARTICLE_ROTATION, assetManager, "Textures/runeCircle.png"),
// Allow at most 9 particles at any time
9,
// Particles always last for 4 seconds
4,
4,
// We want to generate all particles from the same location with the same velocity.
new PointSource(new Vector3f(0, 1f, 0), new Vector3f(0, 1f, 0)),
// Emit particles at regular intervals, 4 particles every second
new RegularEmission(2),
// ** Influencers start here
// These particles should be size 3 and stay the same size
new SizeInfluencer(3, 3),
// Start the particles at full opacity blue and then fade them out to 0 opacity cyan.
new ColorInfluencer(ColorRGBA.Blue, new ColorRGBA(0, 1, 1, 0)),
// Rotate all particles by the same amount. The units are radians-per-second
new RotationInfluencer(
new Vector3f(0, FastMath.QUARTER_PI, 0),
new Vector3f(0, FastMath.QUARTER_PI, 0), false));

// Finally attach the geometry to the rootNode in order to start the particles running
rootNode.attachChild(pCtrl.getGeometry());

// Construct a new ParticleController to provide the central glow effect
pCtrl = new ParticleController(
// The name of the emitter
"SpellBase",
// Use a simple point mesh (the fastest but most limitted mesh type) with the specified
// image (from jME3-testdata). The image actually contains a 2x2 grid of sprites.
new PointMesh(assetManager, "Textures/flame.png", 2, 2),
// Allow at most 76 particles at any time
76,
// Particles last for at least 5 seconds
5,
// And at most 5 seconds
5,
// Point sources always generate particles at the location of the source, the particles
// are given a random velocity between the two given.
new PointSource(new Vector3f(-1f, 0, -1f), new Vector3f(1f, 0.5f, 1f)),
// Emit particles at regular intervals, 15 particles every second
new RegularEmission(15),
// ** Influencers start here
// Select a random sprite from the 4 available for each particle
new RandomSpriteInfluencer(),
// Particles start red with some blue and green and fade towards blue zero opacity
// Because particles are rendered using an additive blend then any area where a lot
// of particles overlap will end up white.
new ColorInfluencer(new ColorRGBA(1,0.25f,0.25f,0.25f), new ColorRGBA(0,0,1,0f)));

// Finally attach the geometry to the rootNode in order to start the particles running
rootNode.attachChild(pCtrl.getGeometry());

cam.setLocation(new Vector3f(0, 10, -10));
cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
}

Результат должен выглядеть примерно так:

Видео: Пример 1 излучателя частиц

Использование сетки в качестве источника частиц

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

    @Override
public void simpleInitApp() {

Node monkey = (Node) assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml");
rootNode.attachChild(monkey);

DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-0.1f,-0.7f,-1).normalizeLocal());
dl.setColor(new ColorRGBA(0.88f, 0.60f, 0.60f, 1.0f));
rootNode.addLight(dl);

AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White);
rootNode.addLight(al);

Результат должен выглядеть примерно так:

Теперь давайте подожжем обезьяну! (Никакие

В первой части мы имели дело с настройкой и некоторым грубым скелетом и просто кораблем на экране, но все еще без системы сущностей. Ну, мы сделали настройку, но ещё не использовали систему сущностей. В этой части мы действительно будем её использовать. Обещаю!

Мы хотим захватчиков

Действительно, игра про захватчиков без захватчиков не является игрой о захватчиках, поэтому снова запустите ваш blender и смоделируйте захватчика. Конечно, у меня есть один для вас на моем Google диске, чтобы поделиться с вами. Я назвал его BasicInvader.

А теперь, мой друг, мы будем использовать систему сущностей для хранения данных группы захватчиков и звездного корабля на базе. Что нам нужно, чтобы указать корабль и захватчиков, которые должны появиться на экране? Нам нужна позиция и модель, чтобы иметь возможность напечатать ее на экране. Хорошо, обычно вы начинаете с какого-то игрового объекта и наследуете от него свой корабль, своего захватчика и так далее, и так далее. Возможно, вы бы злоупотребили JME Spatial, чтобы даже придерживаться некоторой игровой логики, и довольно скоро начали бы создавать беспорядочные спагетти, подобные архитектуре. По крайней мере, это происходит со мной. Поэтому очень хорошая идея отделить логику от визуализации. С системой сущностей вы делаете это в экстремальных условиях. С помощью системы управления сущностями мы можем в любое время расширить наш корабль с помощью любого необходимого нам компонента, не затрагивая большую часть существующего кода, в основном ни одного. Даже данные и методы разделены, что полностью противоположно объектно-ориентированному программированию, и если вы глубоко в объектно-ориентированном программировании, этот подход может вас или не может сбить с толку. Но будь терпелив со мной и с самим собой.

Какова моя позиция

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

package mygame;

import com.jme3.math.Vector3f;
import com.simsilica.es.EntityComponent;

public class Position implements EntityComponent {

private final Vector3f location;

public Position(Vector3f location) {
this.location = location;
}

public Vector3f getLocation() {
return location;
}

@Override
public String toString() {
return getClass().getSimpleName() + "[" + location + "]";
}
}

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

Кто, черт возьми, я

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

package mygame;

import com.simsilica.es.EntityComponent;

public class Model implements EntityComponent {
private final String name;
public final static String SpaceShip = "SpaceShip";
public final static String BasicInvader = "BasicInvader";

public Model(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public String toString() {
return "Model[" + name + "]";
}
}

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

Генерировать энтиты

Наконец, мы будем использовать систему сущностей для хранения данных захватчиков и корабля, в данный момент графического представления нет. Сущности просто больше ничего. Вы должны добавить компоненты к этой сущности, чтобы придать ей смысл. А затем вы запрашиваете сущности с конкретными компонентами, в нашем случае Position и Model. Это как база данных. Итак, что нам сейчас нужно, так это GameAppState, в котором мы выстраиваем наши уровни, осуществляем переходы от начала к игре, от игры к мертвому и от мертвого к возрожденному и тому подобное. Но давайте сначала сделаем это глупо простым, а потом уточним.

package mygame;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.math.Vector3f;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;

public class GameAppState extends AbstractAppState {

private EntityData ed;
private SimpleApplication app;

@Override
public void initialize(AppStateManager stateManager, Application app) {

this.app = (SimpleApplication) app;
this.app.setPauseOnLostFocus(true);
this.app.setDisplayStatView(true);

this.ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();

EntityId ship = ed.createEntity();
this.ed.setComponents(ship,
new Position(new Vector3f(0, -20, 0)),
new Model(Model.SpaceShip));
for (int x = -20; x < 20; x += 4) {
for (int y = 0; y < 20; y += 4) {
EntityId invader = ed.createEntity();
this.ed.setComponents(invader,
new Position(new Vector3f(x, y, 0)),
new Model(Model.BasicInvader));
}
}
}

@Override
public void cleanup() {
}

@Override
public void update(float tpf) {
}

}

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

Покажи что у тебя есть

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

package mygame;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.light.DirectionalLight;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.simsilica.es.Entity;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;
import com.simsilica.es.EntitySet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class VisualAppState extends AbstractAppState {

private SimpleApplication app;
private EntityData ed;
private EntitySet entities;
private final Map<EntityId, Spatial> models;
private ModelFactory modelFactory;

public VisualAppState() {
this.models = new HashMap<>();
}

@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = (SimpleApplication) app;

ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();
entities = ed.getEntities(Position.class, Model.class);

app.getCamera().lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
app.getCamera().setLocation(new Vector3f(0, 0, 60));

DirectionalLight light = new DirectionalLight();
light.setDirection(new Vector3f(1, 1, -1));
this.app.getRootNode().addLight(light);

modelFactory = new ModelFactory(this.app.getAssetManager());
}

@Override
public void cleanup() {
entities.release();
entities = null;
}

@Override
public void update(float tpf) {
if (entities.applyChanges()) {
removeModels(entities.getRemovedEntities());
addModels(entities.getAddedEntities());
updateModels(entities.getChangedEntities());
}
}

private void removeModels(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = models.remove(e.getId());
s.removeFromParent();
}
}

private void addModels(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = createVisual(e);
models.put(e.getId(), s);
updateModelSpatial(e, s);
this.app.getRootNode().attachChild(s);
}
}

private void updateModels(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = models.get(e.getId());
updateModelSpatial(e, s);
}
}

private void updateModelSpatial(Entity e, Spatial s) {
Position p = e.get(Position.class);
s.setLocalTranslation(p.getLocation());
}

private Spatial createVisual(Entity e) {
Model model = e.get(Model.class);
return modelFactory.create(model.getName());
}
}

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

Подробности пожалуйста

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

        ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();
entities = ed.getEntities(Position.class, Model.class);

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

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

    public void update(float tpf) {
if (entities.applyChanges()) {
removeModels(entities.getRemovedEntities());
addModels(entities.getAddedEntities());
updateModels(entities.getChangedEntities());
}
}

Мы проверяем, есть ли изменения в наборе сущностей, а затем обрабатываем удаленные сущности, добавленные сущности и измененные сущности. Все пространственные объекты и соответствующий идентификатор сущности управляются с помощью «локальной» хэш-карты

    private final Map<EntityId, Spatial> models;

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

    private void removeModels(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = models.remove(e.getId());
s.removeFromParent();
}
}

Пространство удерживается в хэш-карте моделей и получается с идентификатором удаленного объекта, поэтому я могу удалить его из визуального узла. Это все. Хорошо, прежде чем мы можем удалить что-то, это должно быть добавлено некоторое время, прежде чем, конечно. Добавленные объекты обрабатываются методом addModels

    private void addModels(Set<Entity> entities) {
for (Entity e : entities) {
Spatial s = createVisual(e);
models.put(e.getId(), s);
updateModelSpatial(e, s);
this.app.getRootNode().attachChild(s);
}
}

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

UpdateModel довольно прост и просто обновляет позицию пространства, в которой изменяются соответствующие сигналы идентификатора объекта.

В конце вы должны зарегистрировать все эти новые состояния приложения в Main, как это

    public Main() {
super(new VisualAppState(),
new GameAppState(),
new EntityDataState());
}

Вот и все. Ваш первый программный продукт, управляемый ES.


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

Начнем с главной части …

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;

public class Main extends SimpleApplication {

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

    public Main() {
    }

    @Override
    public void simpleInitApp() {
    }

    @Override
    public void simpleUpdate(float tpf) {
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
}

Нажмите F6 и начните игру. После выбора разрешения вы видите только черный экран. Не очень страшно на самом деле.

Говорят все дело в системе сущностей, поэтому давайте настроим и инициализируем Zay ES, прежде чем мы перейдем к самой игре.

package mygame;

import com.jme3.app.state.AbstractAppState;
import com.simsilica.es.EntityData;
import com.simsilica.es.base.DefaultEntityData;

public class EntityDataState extends AbstractAppState {
    private EntityData entityData;

    public EntityDataState() {
        this(new DefaultEntityData());
    }

    public EntityDataState( EntityData ed ) {
        this.entityData = ed;
    }

    public EntityData getEntityData() {
        return entityData;
    }

    @Override
    public void cleanup() {
        entityData.close();
        entityData = null; // не может быть повторно использован
    }
}

Существует не так много, способов чтобы описать это состоянии приложения. Это как если мозг просто мёртв.

Черный экран? Я имею в виду … привет ?! На самом деле играть без визуального представления не очень весело. Кроме того, так мы всегда можем видеть что-то на экране и иметь обратную связь, что поддерживает нас счастливыми и мотивированными.

Звездный корабль. Нам нужны модели, которые мы могли бы сделать с помощью Blender и импортировать их в JME3.

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

Если у вас есть модель, то поместите blender файл в папку игровых ресурсов assets/Models, затем просто щелкните [ПК мыши] blender модель в JME3 и выберите Convert to JME Binary(Преобразовать в двоичный j3o файл).

Фабрика моделей. Решение проблемы создания моделей с помощью Blender, чтобы получать spatial-ы в JME3, была бы отличной, это упрощает ситуацию.

package mygame;

import com.jme3.asset.AssetManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
 
public class ModelFactory {
    private final AssetManager assetManager;
 
    public ModelFactory(AssetManager assetManager) {
        this.assetManager = assetManager;
    }

    public Spatial create(String name) {
        Node visual = new Node("Visual");
        Node model = (Node) assetManager.loadModel("Models/" + name + ".j3o");
        visual.attachChild(model);
        return visual;
    }
}

Это подразумевает, что у нас есть наши модели в Project Assets (на самом деле это папка с именем assets) и там в папке Models. Я думаю, что не нужно много объяснений, я позволю говорить коду самому за себя. Конечно, вы можете сделать свою фабрику лучше, больше ОО или нет, мне все равно.

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

package mygame;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;

public class VisualAppState extends AbstractAppState {
    private SimpleApplication app;
    private ModelFactory modelFactory;
 
    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        this.app = (SimpleApplication) app;
        app.getCamera().lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
        app.getCamera().setLocation(new Vector3f(0, 0, 60));
        DirectionalLight light = new DirectionalLight();
        light.setDirection(new Vector3f(1, 1, -1));
        this.app.getRootNode().addLight(light);
        modelFactory = new ModelFactory(this.app.getAssetManager());
        Spatial myVisual = modelFactory.create("SpaceShip");
        this.app.getRootNode().attachChild(myVisual);
    }

    @Override
    public void cleanup() {
    }

    @Override
    public void update(float tpf) {
    }
}

Я буду считать, что ваш космический корабль хранится в файле assets/Models/SpaceShip.j3o.

Если у вашей модели есть материал, то вам понадобится свет, я взял направленный свет(directional light) здесь. И зарегистрируйте его в классе Main следующим образом.

    public Main() {
        super(new VisualAppState(),
                new EntityDataState(),);
    }

Вот и все, запустите свою игру нажав F6 и посмотрите. Я согласен, что здесь ES пока не так много, но мы теперь готовы сделать первую реальную систему сущностей, на самом деле нам нужно добавить немного плоти в VisualAppState и добавить что-то вроде GameAppState.


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

Добро пожаловать в вики Zay-ES!

Чтобы впервые начать работу с Zay-ES, сначала посмотрите страницу С Чего Начать.

Для более глубокого понимания здесь есть раздел документации: Документация.

Или взгляните на некоторые примеры в разделе Примеры из практики Zay Es.

Для дальнейшего чтения на Entity-Component-Systems проверьте раздел Интересные ссылки.

оригинал


О Christian Liesch

Меня зовут Christian Liesch, и я далёк от обычных программистов игр и занимаюсь этим как хобби, в дождливую погоду и в бурные ночи.

Мне слишком часто приходилось перебарывал себя что бы завершить даже простые игры. Но потом я увидел эту систему сущностей где-то в вики jME3. Сначала я понятия не имел, как это должно быть встроено в игру, и как вы отображаете свои игровые элементы на экране. Поэтому я изучил asteroid panic от Paul Speed и начал понимать. Система сущностей вернула меня в прошло в день взлома ZX81 когда я был 13-летним мальчиком более 30 лет назад. Я нашел способ четко разделить логику, данные и представление. С помощью системы сущностей я смог написать простую игру менее чем за 24 часа, что потребовало бы от меня недели и месяцы классическим способом. Потому что система сущностей дала мне структуру, которую я так отчаянно искал. Простая архитектура, с «модулями», где все ясно чётко, и не надо задумываться о наследовании, а объединяют все это, только сущности, компоненты и системы.

Моё тематическое исследование — это моя первая игра на основе системы сущностей, которую я написал для вас. Я многому научился и хочу поделиться с вами своими выводами и извлеченными уроками. В игре Invaders(Захватчики). Тематическое исследование основанное на идее, что в итоге у вас будет простая запущенная игра, и даже больше, вы можете попробовать все шаги и получить немедленную обратную связь.

оригинал


Документация

Когда нибудь возможно здесь что то будет.

Эмпирические правила

Обдумывание концепции Сущность-Компонент-Система(Entity-Component-System) может быть затруднено. Многолетний опыт объектно-ориентированного программирования может затруднить мышление в новом режиме. Ниже приведены некоторые общие правила, которые следует учитывать при разработке собственного приложения ECS дизайна:

  • Компоненты только данные.
  • Две системы не должны производить один и тот же тип компонента для одних и тех же сущностей.
  • это еще не всё

API Javadoc

Всегда стоит ознакомиться с javadocs проекта и держать эти ссылки под рукой:

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

оригинал


С Чего Начать


Получение библиотеки

Загрузите последнюю версию ядра (и/или сети) отсюда: Релизы

Или сконфигурируйте ваш инструмент сборки так, чтобы они автоматически вытягивались (gradle/maven/итп.). Jar-ы Zay-ES доступны в открытом репозитории jcenter.

Вот пример раздела из файла build.gradle, который включает в себя как ядро Zay-ES, так и сетевую библиотеку:

dependencies {
    compile "com.simsilica:zay-es:1.2.1"
    compile "com.simsilica:zay-es-net:1.2.1"
}

Требуемые зависимости

Если вы не используете современный инструмент для сборки, который автоматически разбирается с зависимостями, то тогда вам понадобится скачать несколько дополнительных jar-файлов:

  1. Guava версия 12 или новее
  2. slf4j версии 1.7.5 или новее, а также адаптер для предпочитаемой вами среды ведения журнала отладки. (Как минимум, вам понадобится API jar, что-то вроде: slf4j-api-1.7.5.jar)

Создайте главный объект EntityData

EntityData — это главный класс, который будет содержать все компоненты. Создать базовую версию «в памяти» так же просто, как:

    EntityData ed = new DefaultEntityData();

Создать Сущность

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

    EntityId myEntity = ed.createEntity();

Задание Компонент

Обычно приложение создает свои собственные классы реализации EntityComponent, но Zay-ES поставляется с парой стандартных. Следующий код иллюстрирует задание названия ранее созданному объекту.

    ed.setComponent(new Name("My Entity"));

Наблюдение за изменениями (игровой цикл)

Одно из основных отличий между Zay-ES и другими системами ECS заключается в том, что вас не принуждают к конкретной «системной» модели. То, как вы пишете свои системы и как вы используете сущности, более или менее зависит от вас. Таким образом, вместо подхода Инверсии управления, в котором вы реализуете какой-то системный класс и фреймворк вызывается вами, вы можете просто запрашивать нужные сущности, когда захотите. Zay-ES предоставляет внутренние механизмы, чтобы сделать это эффективным.

Основной цикл опроса:

    EntitySet namedEntities = ed.getEntities(Name.class);
    while( true ) {
        namedEntities.applyChanges();
        for( Entity e : namedEntities ) {
            // делать что то каждый кадр
        }
    }

Более эффективный цикл опроса, где код запускается только при изменении сущностей:

    EntitySet namedEntities = ed.getEntities(Name.class);
    while( true ) {
        if( namedEntities.applyChanges() ) {
            // сделать что-то с новыми соответствующими объектами
            addSomeStuff(namedEntities.getAddedEntities());

            // сделать что-то с сущностями, которые больше не соответствуют
            removeSomeStuff(namedEntities.getRemovedEntities());

            // сделать что-то с сущностями, которые просто изменились
            changeSomeStuff(namedEntities.getChangedEntities());
        }
    }

Для дальнейшего чтения смотрите:

оригинал


Zay-ES Case Studies

Сначала вы должны загрузить JME 3.1+ и заставить его работать, это довольно просто. Далее скачиваем также Zay ES и его зависимости guava и slf4j-api. После этого запустите новый игровой проект в JME, выполнив следующие действия: Создать проект ▸ JME3 ▸ BasicGame

Jar-ы могут быть добавлены с помощью: Щелкните [ПК мыши] ваш проект Свойства ▸ Библиотеки ▸ Добавить файл JAR/папку. Вы можете разместить их там, где захотите, лично у меня они находятся в папке lib в моем проекте JME3.

Автор этих тематических исследований Christian Liesch.

Invaders(Захватчики)

Простой игровой сервер

  • Мой первый игровой сервер
  • Мой первый игровой клиент

оригинал


Интересные ссылки

Этот раздел для интересных ссылок, связанных с Системами Сущностей(Entity Systems).

Мое первоначальное знакомство с ESes было в серии статей в блоге на сайте t-machines … сейчас они, по-видимому, уже не существуют, но некоторые основные оригинальные материалы можно найти в этих трех статьях:

оригинал


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

Библиотека основанного на физике, рендеринга неба.

cloudimg4

Файл пример

Выберите файл TestSky для начала.

Лицензия

Библиотека находится в папке src, под лицензией BSD 3-Clause.

Дополнительные библиотеки, которые могут также использоваться, расположены в:

  • src-hyg Каталог HYG Star (https://github.com/astronexus/HYG-Database)
  • src-nuta Функция Нутации (лицензия программного обеспечения SOFA), используйте если вас интересует точный расчет небесных тел относительно времени и местоположения наблюдателя. Возможность использования функции нутации.
    Space.NutationFunction = new NutationFunction();
  • src-vsop Местоположение Планет в Солнечной системе (GNU Affero General Public Лицензия, версия 3)
    Space.EarthPosition = new Earth();
    //например:
    Space.getEarthPos(jullianDate, observerLon, observerLan);
    //или
    Space.EarthPosition.getLon(jullianDate);

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

Но по-прежнему забавно проверять, что CSG может делать динамически. Достаточно простой тестовый вариант состоит в том, чтобы иметь две фигуры с общим центром, вращающиеся в противоположных направлениях. Затем отобразите их пересечение. С помощью простых кубов (12 треугольников в кубе) вы получаете довольно хороший результат. Замените один из этих кубов на, до некоторой степени гладкий цилиндр, и частота кадров снижается. Вы можете сделать опрос смешанной фигуры после регенерации, чтобы получать количество наносекунд, необходимых для создания фигуры.

Настройка выполняется напрямую. Создайте две фигуры и соедините их с третьей. В цикле обновления примените вращение к двум примитивам, и затем регенерируйте смешанную фигуру. Код CSG перестроит смешение с нуля при каждом вызове регенерации.
скачать видео (которое идёт рывками из-за активной записи, но это происходит довольно плавно в ​​реальной жизни)

Прогрессивная Перестройка

Более практичная форма полуавтоматически применяет прогрессивные изменения к одной целевой фигуре. Эти изменения могут быть вызваны действием пользователя или могут быть непрерывными, двигаясь с любой скоростью, которую может обеспечить ваше железо. CSG структурирована так, чтобы вы могли легко выполнять прогрессивные изменения. Ключевым в этом является то, что возвратом вызова .regenerate() является CSGShape. Эта фигура является единственным результатом предыдущего смешивания и готова к использованию.

Типичным для структуры дизайна является сохранение результата первоначальной .regenerate() в переменной «priorResult». Действие обновления состоит в том, чтобы сначала удалить все фигуры из вашей целевой фигуры, смешать в priorResult, смешать любые новые изменения и затем сохранить результат этого нового .regenerate() в priorResult. Этот цикл обновления можно запустить повторно, включая любые новые изменения в каждом цикле.

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

Многопоточность

Для улучшения поддержки анимации и прогрессивной перестройки процесс сборки CSG был сделан thread aware. Регенерация была разбита на два отдельных шага. Первый включает в себя весё тяжелую работу анализа фигур смешивания, разрешения конфликтов и принятия решения о структуре конечного результата. Второй шаг — применить окончательную фигуру к активной визуальной сцене.

По умолчанию .regenerate() выполняет оба шага. Но вы можете использовать:

 myCSGSpatial.deferSceneChanges( true );

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

myCSGSpatial.applySceneChanges();

который обновляет базовую сцену. .regenerate() и .applySceneChanges() являются thread-aware и должным образом синхронизируются. Это означает, что вы можете управлять регенерацией (шаг нагрузки на CPU) из фонового потока и вызывать .applySceneChanges() из стандартного потока jme3 .update(). .applySceneChanges() возвращает true, если работа была выполнена, false если обновления не были применены.

Как часть являющаяся thread-aware, главный внутренние циклы обработки монитор Thread.interrupted(). Если такое прерывание обнаружено, процесс регенерации прерывается и генерируется исключение CSGConstructionException.

Обратите внимание, что CSGConstructionException является RuntimeException, поэтому вызывающему не требуется явно писать код обработки исключений. java.lang.InterruptedException не используется в обработке CSG.

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

Я начал трудится над CSG, чтобы удовлетворить свои потребность в программных инструментах построения, которые я могу использовать в других экспериментах. Для достижения этой цели пришлось поддержке CSG несколько выйти за минимальные рамки потребности в объединение/вычитании/пересечении фигур. Ниже перечислены только некоторые возможности, реализованные для удовлетворения моих других личных требований.

Освещение

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

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

Так как локальное освещение применяются только к элементам в пределах данного Spatial, я понял, что могу создать некоторый уровень освещения только для интерьера, если я смогу создать узел как часть процесса смешивания. Поддержка нескольких Материалов уже знает, как связать поверхность конечного продукта с исходной примитивной фигурой. Эта процедура была легко расширена, чтобы отслеживать локальное освещение, а также материалы. Конечный результат включает в себя Узел, который содержит локальное освещение, и все элементы поверхности Сетки(Mesh), которые освещаются таким освещением.

Например, если я вычитаю цилиндр из куба, я могу реализовать освещение на сам цилиндр. Это освещение распространяется только на конечные элементы внутренней поверхности, и более нигде. Хотя, конечно, это не комплексное решение того как правильно будут тени, это позволяет мне создавать некоторые хорошие эффекты. Это также хорошо работает с группами под-фигур. Чтобы создать коридор в форме арки, я группирую вместе половину-высоты Куба для дна и Цилиндр для верха. Затем я могу вычесть эту группу из внешней Куба. Добавление света в группу приводит к приятному внутреннему освещению. Добавление локального освещения к внешнему кубу означает, что я могу осветить его снаружи, без просачивания света внутрь.

CorridorLights

По умолчанию jme3-поведение состоит в том, что все освещения расположены в мировом пространстве, даже если они реализованы локально. Целью CSG является создание сложной фигуры из простых примитивов. Когда добавляются освещения, можно было бы ожидать, что преобразование в окончательную фигуру сохранит правильное освещение. Но если освещения расположены глобально, преобразование в окончательную фигуру означает, что свет больше не освещает там, где можно было бы ожидать. Jme3 решает эту проблему через LightControl, который может изменить положение Освещения, поскольку связанный с ним Spatial преобразуется. По умолчанию CSG создаст и назначит соответствующий Control всем локальным освещениям, встретившимся во время смешивания. Он использует настраиваемый CSGLightControl, который лучше поддерживает базовую структуру CSG. Но вы можете отключить эту функцию или предоставить любой другой Control для управления освещением.

Физика

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

Для этого Физики добавляются так же, как освещения. Физики применяются только к сегменту конечной сетки, соответствующему смешанному примитиву. Поэтому, если вы создаете комнату, вычитая маленький куб из куба большего размера, и вам нужны упругие стены, но инертный(неактивный) пол, вы можете применять перед-гранью физики на разных поверхностях. Окончательная смесь держит все точно для вас.

2D-Поверхности

Основным направлением CSG является смешивание твердых тел. Но я столкнулся с ситуацией, когда захотел поместить твёрдое тело на своем 2D полу без смешивания пола. Разумеется, нет необходимости собирать пол как твердое тело, и я надеялся использовать jme3 Поверхность(Surface) и Ландшафт(Terrain). Обработчик IOB отлично справляется с обнаружением наложения и выбором правильных треугольников для окончательной смешанной фигуры. Вычитание твердого тела и пола дает мне хорошее отверстие, в которые должно пройти твердое тело. Пересечение твердого тела и пола показывает мне, где именно происходит наложение. И объединение дает мне броские фигуры, я не уверен, что это очень полезно (но выглядит корректно).

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


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

Общий

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

Простые проверки

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

  • Если вызывается необязательная функция (например, функция расширения OpenGL), LWJGL проверяет и выдает исключение NullPointerException, если оно недоступно.
  • Если параметр указателя функции или член структуры никогда не должен быть равен NULL, LWJGL проверяет и выдает исключение NullPointerException, если оно есть.
  • Если параметр буфер функции или член структуры должен иметь определенный минимальный размер, LWJGL проверяет и выдает исключение IllegalArgumentException, если оно есть.

Эти проверки, как правило, оказывают низкую нагрузку на систему и потому не должны оказывать заметного влияния на производительность, поэтому они включены по умолчанию, и их отключение не рекомендуется, особенно во время разработки. Для обеспечения оптимальной производительности в конечных сборках их можно отключить с помощью -Dorg.lwjgl.util.NoChecks=true или Configuration.DISABLE_CHECKS.set(true).

Отключенные LWJGL проверки не оказывают нагрузку во время выполнения. Но, судя по Java утверждениям (assertion), они увеличивают размер байт-кода окружающих методов. В редких случаях это может повлиять на принятие решений JVM и в конечном итоге повлиять на производительность. Если такая проблема была обнаружена, то в этом случае LWJGL можно собрать с удалением всех проверок, задав binding.DISABLE_CHECKS в config/build-bindings.xml в true.

Режим отладки

LWJGL выполняет дополнительные, более дорогие проверки, когда режим отладки включен с -Dorg.lwjgl.util.Debug=true или Configuration.DEBUG.set(true). Это должен быть первый вариант, который можно использовать при столкновении с LWJGL. Режим отладки также создает дополнительный выход (по умолчанию stderr, может быть переопределен с Configuration.DEBUG_STREAM) и требуется некоторыми другими инструментами устранения неполадок.

Когда вы сообщаете о проблеме LWJGL, настоятельно рекомендуется включить режим отладки и включить её вывод с описанием проблемы.

Режим отладки загрузчика общей библиотеки

Одной из наиболее распространенных проблем, с которой сталкиваются новые пользователи LWJGL, является их проект, так что общие библиотеки загружаются правильно. Включение этого режима с помощью -Dorg.lwjgl.util.DebugLoader = true или Configuration.DEBUG_LOADER.set (true) создает дополнительный вывод в DEBUG_STREAM, что очень полезно при определении проблем с загрузкой библиотеки.

Также должен быть включен режим отладки.

Режим отладки распределителя памяти

Родные библиотеки означают неактивную память, что означает повышенный риск утечки памяти по сравнению со стандартным кодом Java. Включение этого режима с помощью -Dorg.lwjgl.util.DebugAllocator = true или Configuration.DEBUG_MEMORY_ALLOCATOR.set (true) делает LWJGL отслеживать все распределения вне кучи, выполняемые через org.lwjgl.system.MemoryUtil. Когда процесс JVM завершается, любые распределения, которые не были явно освобождены, будут сообщены в DEBUG_STREAM.

Этот режим может оказать очень негативное влияние на производительность.

Режим отладки в стеке памяти
Использование org.lwjgl.system.MemoryStack прост, но может быть источником сложной идентификации проблем. Включение этого режима с помощью -Dorg.lwjgl.util.DebugStack = true или Configuration.DEBUG_MEMORY_DEBUG_STACK.set (true) делает LWJGL сообщать о любом стеке pop без соответствующего нажатия в том же методе.

Этот режим может оказать очень негативное влияние на производительность.

Привязки

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

EGL

EGL-реализации могут поддерживать расширение KHR_debug. См. OpenGL ниже для получения дополнительной информации.

GLFW

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

// custom
glfwSetErrorCallback((error, description) -> {
	System.err.println("GLFW error [" + Integer.toHexString(error) + "]: " + GLFWErrorCallback.getDescription(description));
});
// or shortcut that prints to DEBUG_STREAM
GLFWErrorCallback.createPrint().set();
// or shortcut that throws an exception on error
GLFWErrorCallback.createThrow().set();

// easy clean-up
glfwSetErrorCallback(null).free();

OpenAL

OpenAL-Soft, реализация OpenAL в комплекте с LWJGL, может быть настроена с переменными окружения.

Переменная ALSOFT_LOGLEVEL определяет количество протоколирования. OpenAL Soft выписывает:

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

OpenCL

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

CLContextCallback contextCB = CLContextCallback.create((errinfo, private_info, cb, user_data) -> {
    System.err.println("cl_context_callback info: " + memUTF8(errinfo));
});

long context = clCreateContext(ctxProps, device, contextCB), NULL, errcode_ret);

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

OpenGL

Современные версии OpenGL поддерживают очень мощный режим отладки для сообщения об ошибках и предупреждениях. Этот режим стал стандартным в OpenGL 4.3, но есть расширения, которые обеспечивают такую же функциональность в более ранних версиях (KHR_debug, ARB_debug_output, AMD_debug_output). Пример кода:

// before context creation
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);

// after context creation
glfwMakeContextCurrent(window);
GL.createCapabilities();
Callback debugProc = GLUtil.setupDebugMessageCallback(); // may return null if the debug mode is not available

// cleanup
if ( debugProc != null )
    debugProc.free();

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

OpenGL ES

Подобно OpenGL, OpenGL ES поддерживает режим отладки, начиная с OpenGL ES 3.2. Расширение KHR_debug также может быть доступно в более ранних версиях.

Vulkan

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

Как и в случае с Vulkan, код для такой проверки не является тривиальным. Не забудьте проверить LunarG Vulkan SDK и образец HelloVulkan LWJGL для примера.


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

Каковы различные компоненты нативных привязок?

Чтобы вызвать нативную функцию, LWJGL требует как Java, так и нативный код. Давайте используем функцию glShaderSource, которая была введена в версии 2.0 спецификации OpenGL:

void glShaderSource(GLuint shader, GLsizei count, const GLchar **strings, const GLint *length)

Это void функция, которая принимает целое число без знака, целое число со знаком, указатель на массив строк и указатель на массив целых чисел со знаком. В LWJGL это преобразование происходит следующим образом:

public static native void nglShaderSource(int shader, int count, long strings, long length)

Обратите внимание на префикс ‘n’ и сопоставления типов: Как со знаком, так и беззнаковые целочисленные параметры были сопоставлены с Java int и оба указателя, сопоставлены с Java long. Мы обсудим эти сопоставления позже.

Префикс ‘n’ не является абсолютно необходимым, но добавляется для того что бы автозавершение кода в среде IDE позволяло разделить нативные и не нативные методы.

В дополнение к нативному методу, так же включены несколько обычных Java-методов:

public static void glShaderSource(int shader, PointerBuffer strings, IntBuffer length)
public static void glShaderSource(int shader, CharSequence... strings)
public static void glShaderSource(int shader, CharSequence string)

Это дружественные Java методы, которые разработчики обычно используют. Первый специализирует типы указателей и сбрасывает аргумент count, второй заменяет все с помощью параметра CharSequence vararg, а третий с одним CharSequence.

С нативной стороны все очень просто. Это реализация C указателя функции:

typedef void (APIENTRY *glShaderSourcePROC) (jint, jint, const intptr_t, const intptr_t);

и реализация функции JNI:

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_GL20_nglShaderSource(
    JNIEnv *__env, jclass clazz, // unsed
    jint shader, jint count, jlong stringsAddress, jlong lengthAddress
) {
	glShaderSourcePROC glShaderSource = /* получить указатель функции */;
	const intptr_t strings = (const intptr_t)stringsAddress;
	const intptr_t length = (const intptr_t)lengthAddress;
	UNUSED_PARAM(clazz)
	glShaderSource(shader, count, strings, length);
}
Используемые C типы не соответствуют исходной функции, но они бинарно совместимы.

LWJGL осуществляет здесь очень важный выбор о реализации; нативный код ничего не делает, кроме параметров заливки для соответствующих типов и вызывает нативную функцию. Все сложности и сопоставление с удобными для пользователя параметрами для корректных нативных данных, обрабатываются в Java-коде. Все указатели передаются как примитивные long-и, примитивные значения передаются без изменений и это все. Параметры JNIEnv и jclass никогда не затрагиваются, и никакого взаимодействия с JVM нет; никакие jobject параметры не передаются, и нет вызовов C-к-Java. Нативные методы также public, что позволяет пользователям создавать свои удобные для них методы поверх исходных, и они могут быть спокойны, что никаких проблем не будет происходит при вызове функции JNI.

Двигаясь дальше, нынешний дизайн будет делать принятие Project Panama легче для LWJGL, который будет доступен на Java 10.

Вышеупомянутое относится ко всем привязкам LWJGL. Большинство пользователей никогда не будут иметь дело с нативными методами напрямую, и они не должны, если нет веской причины; их использование по сути является небезопасным. Более опытные разработчики могут делать интересные вещи с помощью исходных указателей арифметики и утилиты org.lwjgl.system.MemoryUtil.

Что такое параметр __functionAddress, который требуется многим нативным методам?

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

Префикс ‘__’ позволяет сделать более очевидным, что это синтетический параметр.

Если нативные методы не используются напрямую, пользователям редко приходится явно передавать адрес функции. LWJGL использует специальные механизмы что бы делать это автоматически. Например, методы OpenGL используют локальную память потока для текущего контекста, а методы Vulkan используют параметр dispatchable handle для получения корректного указателя функции.

Как сопоставляются примитивные значения?

Сопоставления обычно является простым, за исключением без знаковых типов и непрозрачных указателей:

  • Без знаковые целые типы сопоставляются в Java соответствующему типу со знаком. Может показаться что это звучит как с ошибкой, но в большинстве случаев это не так. Они могут быть легко преобразованы в, и из целых чисел с высокой точностью, используя при этом простые бинарные операции. Основные арифметические операции, кроме деления, работают одинаково как для целых чисел со знаком, так и без знака. Java 8 также предоставляет полезные утилиты для их обработки.
  • Непрозрачные указатели сопоставляются с Java через long.
Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним API. Значения таких указателей просто передаются в, и выходят из API и никогда не используются для прямого доступа к памяти.

Давайте посмотрим на некоторые примеры. Следующие объявления функций:

void glDepthMask(GLboolean mask)
void glAlphaFunc(GLenum func, GLfloat ref)
void glClear(GLbitfield mask)
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)
void glVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3)
void glVertexAttrib4Nub(GLuint dinex, GLubyte x, GLubyte y, GLubyte z, GLubyte w)
void glfwSwapBuffers(GLFWwindow* window)

сопоставляется со следующими Java-методами:

public static void glDepthMask(boolean flag)
public static void glAlphaFunc(int func, float ref)
public static void glClear(int mask)
public static void glCopyPixels(int x, int y, int width, int height, int type)
public static void glVertexAttrib4s(int index, short v0, short v1, short v2, short v3)
public static void glVertexAttrib4Nub(int index, byte x, byte y, byte z, byte w)
public static void glfwSwapBuffers(long window)

Как сопоставляются указатели на примитивные значения?

Данная нативная функции объявляется так:

void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);

мы имеем следующие методы Java:

public static native void nglUniform4fv(int location, int count, long value) // A
public static void glUniform4fv(int location, FloatBuffer value) // B

В этом случае мы имеем float указатель и явно заданный параметр count, который определяет, сколько векторов vec4 следует читать из этого указателя.

В GLSL vec4 представляет собой вектор из 4 float значений. Поэтому указатель должен указывать на массив count * 4 * 4 байт.

В методе A, который является исходным методом JNI, параметр count и адрес указателя являются явными.

В методе B произошли две трансформации; a) параметр value теперь является FloatBuffer, соответствующим типу нативного указателя, и b) параметр count теперь неявный, замененный value.remaining() / 4. На 4 потому, что каждый count представляет собой вектор из 4-х значений.

Текущий Buffer.position() влияет на адрес указателя, который будет передан в нативную функцию. Если FloatBuffer обертывает указатель с адресом x, а текущая позиция равна 2, будет использоваться адрес x + 8.
Текущий Buffer.limit() определяет, сколько значений будет считано или записано в буфер. В сочетании с примечанием о Buffer.position() выше это соответствует тому, как буферы java.nio обычно используются в JDK. Единственное различие заключается в том, что LWJGL никогда не изменяет текущую позицию или ограничение(limit) буфера. Это уменьшает необходимость flip() или rewind() буферов.

Как обрабатываются параметры вывода(output)?

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

Классическим примером является glGetIntegerv, очень полезная функция OpenGL:

void glGetIntegerv(GLenum pname, GLint *data)

LWJGL включает следующие методы для этой функции:

public static native void nglGetIntegerv(int pname, long params) // A
public static void glGetIntegerv(int pname, IntBuffer params) // B
public static int glGetInteger(int pname) // C

Методы A и B аналогичны тем что были описаны выше, за исключением того, что нет явного параметра count. Если бы был параметр count, то такое же преобразование было бы применено в случае B (params.remaining() будет использоваться неявно). Для этой функции пользователь должен убедиться, что для возвращаемых данных запрошенного параметра, достаточно места.

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

Метод C является интересным. Его можно использовать, когда запрос возвращает одно значение. Без него пользователь должен будет выделить одно значение буферу, вызвать метод, а затем прочитать значение из буфера, сложная процедура. Вместо этого LWJGL сбрасывает параметр params и изменяет тип возвращаемого метода из void в int. Это делает его более естественным и удобным в использовании.

LWJGL использует org.lwjgl.system.MemoryStack для реализации таких методов, как C. Такие методы можно безопасно использовать из нескольких контекстов одновременно.

Это круто. Можно ли это сделать для единичных-значений параметров ввода(input)?

Конечно, если это полезно для какой то конкретной функции. Например:

void glDeleteTextures(GLsizei n, const GLuint *textures)

может использоваться с любым из них:

public static native void nglDeleteTextures(int n, long textures)
public static void glDeleteTextures(IntBuffer textures)
public static void glDeleteTextures(int texture) // одно значение!

Есть ли удобный способ использования String или CharSequence?

Да, LWJGL упрощает обработку текстовых данных. Соглашением для текста является то, что параметры ввода сопоставляются с CharSequence, а вывода (возвращенные) значения сопоставляются с String. LWJGL корректно обрабатывает кодировки символов, поддерживает кодирование/декодирование ASCII, UTF-8 и UTF-16 и будет использовать правильный вариант в зависимости от используемой функции. Наконец, он может так же легко обрабатывать нуль-терминированные строки и строки с точной длиной. Некоторые примеры:

GLint glGetAttribLocation(GLuint program, const GLchar *name) // A) нуль-терминированный ввод
GLubyte *glGetString(GLenum name) // B) нуль-терминированный вывод
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) // C) вывод w/ точной длинны

и Java методы:

// A)
public static native int nglGetAttribLocation(int program, long name)
public static int glGetAttribLocation(int program, ByteBuffer name)
public static int glGetAttribLocation(int program, CharSequence name)
// B)
public static native long nglGetString(int name)
public static String glGetString(int name)
// C)
public static native void nglGetProgramInfoLog(int program, int maxLength, long length, long infoLog)
public static void glGetProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog)
public static String glGetProgramInfoLog(int program, int maxLength)
public static String glGetProgramInfoLog(int program)

Бонусная функция: последний glGetProgramInfoLog будет использовать glGetProgrami(program, GL_INFO_LOG_LENGTH) внутри для автоматического выделения достаточного количества память для возврата лога текста!

Как можно настроить обратные вызовы из нативного кода?

Каждый тип нативного обратного вызова сопоставляется с парой interface/abstract класса. Подобно CharSequence и String, когда тип обратного вызова является параметром ввода, он сопоставляется с interface и когда вывода (возвращаемое значение) относится к классу abstract. Это важно по двум причинам:

  • Лямбда SAM преобразования применимы только к интерфейсам. Все интерфейсы обратного вызова являются функциональными интерфейсами по определению.
  • Нативные функции обратного вызова генерируются во время выполнения. Это означает, что есть нативные ресурсы, которые должны быть сохранены как состояние и должны быть явно освобождены, если их больше не используют. Только абстрактный класс может иметь состояние и метод free().

Примером функции обратного вызова, которая может быть зарегистрирована в контексте OpenGL, является:

// Сигнатура функции:
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id,
 GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
// Регистрация обратного вызова:
void glDebugMessageCallback(GLDEBUGPROC callback, const void *userParam)

Они сопоставляются с:

public static native void nglDebugMessageCallback(long callback, long userParam)
public static void glDebugMessageCallback(GLDebugMessageCallbackI callback, long userParam)

и интерфейс org.lwjgl.opengl.GLDebugMessageCallbackI, который реализует один метод:

void invoke(int source, int type, int id, int severity, int length, long message, long userParam);

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

// Способ 1. Сохраните эту ссылку в переменной Java.
GLDebugMessageCallback cb = GLDebugMessageCallback.create((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
});
glDebugMessageCallback(cb, NULL);
// потом...
cb.free();

// Способ 2: используйте glGetPointer для получения обратного вызова
glDebugMessageCallback((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
}, NULL);
// потом...
GLDebugMessageCallback.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION)).free();

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

Существует ли специальное сопоставление для нативных структур?

Да, каждый тип структуры сопоставляется с классом. Класс содержит методы создания/распределения и геттеры/сеттеры для элементов структуры. Компоновка структуры (смещения поля, sizeof, alignof) доступеа как static final int поле.

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

Каждый класс структуры также имеет внутренний класс Buffer, который представляет массив этих структур. Класс Buffer имеет API, который совместим с буферами java.nio, а также геттеры/сеттеры, которые могут использоваться для доступа к массиву структур с приспособленец (flyweight pattern).

Пример:

typedef struct GLFWimage
{
    int width;
    int height;
    unsigned char* pixels;
} GLFWimage;
GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images);

Вышеуказанные значения сопоставляются с:

public class GLFWImage extends Struct implements NativeResource

public static long nglfwCreateCursor(long image, int xhot, int yhot)
public static long glfwCreateCursor(GLFWImage image, int xhot, int yhot) // A

public static void nglfwSetWindowIcon(long window, int count, long images)
public static void glfwSetWindowIcon(long window, GLFWImage.Buffer images) // B

GLFWImage имеет следующие статические поля:

GLFWImage.SIZEOF // sizeof(GLFWimage)
GLFWImage.ALIGNOF // alignof(GLFWimage)
GLFWImage.WIDTH // offsetof(GLFWimage, width)
GLFWImage.HEIGHT // offsetof(GLFWimage, height)
GLFWImage.PIXELS // offsetof(GLFWimage, pixels)

и следующие геттеры и сеттеры:

img.width()
img.height()
img.pixels()
Существуют также «небезопасные» статические геттеры и сеттеры, которые могут использоваться для доступа к ячейкам памяти, которые не обёрнуты в класс структуры или структурный буфер.

В приведенных выше методах A просто. Результат image.address() передается в нативный код. В B images представляет собой массив структуры GLFWimage. Обратите внимание, что он был сопоставлен с GLFWImage.Buffer, и параметр count был удален, как и в других методах, которые принимают автоматический размер java.nio буферов.

Поддержка памяти структур и структурные буферы могут быть любой памятью без кучи. MemoryStack, MemoryUtil и BufferUtil могут использоваться для распределения экземпляров объектов структуры, отвечающие характеристикам удобства использования/производительности.

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

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