Визуализация
В первой части мы имели дело с настройкой и некоторым грубым скелетом и просто кораблем на экране, но все еще без системы сущностей. Ну, мы сделали настройку, но ещё не использовали систему сущностей. В этой части мы действительно будем её использовать. Обещаю!
Мы хотим захватчиков
Действительно, игра про захватчиков без захватчиков не является игрой о захватчиках, поэтому снова запустите ваш 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
Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.