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

Визуализация

Опубликованно: 10.01.2019, 15:34
Последняя редакция, Andry: 10.01.2019 21:20

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

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

Действительно, игра про захватчиков без захватчиков не является игрой о захватчиках, поэтому снова запустите ваш 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

Добавить комментарий

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