Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    Документация jMonkeyEngine
        jMonkeyEngine Уроки и Документация
            Документация для продвинутых пользователей
                Nifty - Экран Загрузки (Индикатор выполнения(Progress bar))

Nifty — Экран Загрузки (Индикатор выполнения(Progress bar))

Опубликованно: 24.08.2017, 14:37
Последняя редакция, Andry: 10.10.2017 23:27

В этом примере будет использован существующий hello terrain в качестве примера. Так же потребуются разместить эти два изображения внутри Assets/Interface/ (сохранить их как border.png и inner.png)

inner1 border1

Вам нужно добавить библиотеки jme3-niftygui и jme3-test-data.

Вам нужно будет задать вашему проекту исходники JDK 8.

Это индикатор(progress bar) выполнен на 90%:

loadingscreen

nifty_loading.xml

<?xml version="1.0" encoding="UTF-8"?>
<nifty>
    <useStyles filename="nifty-default-styles.xml" />
    <useControls filename="nifty-default-controls.xml" />

    <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
        <image filename="Interface/border.png" childLayout="absolute"
               imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
            <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px" height="100%"
                   imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15" />
        </image>
    </controlDefinition>

    <screen id="start" controller = "jme3test.TestLoadingScreen">
        <layer id="layer" childLayout="center">
            <panel id = "panel2" height="30%" width="50%" align="center" valign="center" childLayout="vertical"
                   visibleToMouse="true">
                <control id="startGame" name="button" backgroundColor="#0000" label="Load Game" align="center">
                    <interact onClick="showLoadingMenu()" />
                </control>
            </panel>
        </layer>
    </screen>

    <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
        <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
            <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center" height="32px" width="70%">
                <control name="loadingbar" align="center" valign="center" width="100%" height="100%" />
                <control id="loadingtext" name="label" align="center"
                         text="                                                  "/>
            </panel>
        </layer>
    </screen>

    <screen id="end" controller = "jme3test.TestLoadingScreen">
    </screen>

</nifty>

Понимание Nifty XML

Индикатор выполнения(progress bar) и текст выполняются статически, используя nifty XML. Создается пользовательский элемент управления(control), представляющий индикатор выполнения.

    <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
        <image filename="Interface/border.png" childLayout="absolute"
               imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
            <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px" height="100%"
                   imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15"/>
        </image>
    </controlDefinition>

Этот экран просто отображает кнопку в середине экрана, которая может рассматриваться как простой UI главного меню.

    <screen id="start" controller = "jme3test.TestLoadingScreen">
        <layer id="layer" childLayout="center">
            <panel id = "panel2" height="30%" width="50%" align="center" valign="center" childLayout="vertical"
                   visibleToMouse="true">
                <control id="startGame" name="button" backgroundColor="#0000" label="Load Game" align="center">
                    <interact onClick="showLoadingMenu()" />
                </control>
            </panel>
        </layer>
    </screen>

На этом экране отображается наш пользовательский progress bar control с text control

    <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
        <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
            <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center" height="32px" width="400px">
                <control name="loadingbar" align="center" valign="center" width="400px" height="32px" />
                <control id="loadingtext" name="label" align="center"
                          text="                                                  "/>
            </panel>
        </layer>
    </screen>

Создание привязок для использования Nifty XML

Существует три основных способа обновления индикатора выполнения(progress bar). Чтобы понять, почему эти методы необходимы, требуется понимание графического конвейера.

Что-то вроде этого в одном потоке не будет работать:

load_scene();
update_bar(30%);
load_characters();
update_bar(60%);
load_sounds();
update_bar(100%);

Если вы делаете все это в одном кадре, то его отправляют на видеокарту только после выполнения всего кода. К этому времени индикатор уже достигнет 100%, и игра уже начнется — пользователь не заметит никаких существенных изменений в индикаторе выполнения на экране.

Двумя основными хорошими решениями являются:

  1. Явное обновление по многим кадрам
  2. Многопоточность

Обновление индикатор выполнения по нескольким кадрам

Идея состоит в том, чтобы разбить загрузку игры на отдельные части

package jme3test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.niftygui.NiftyJmeDisplay;
import static com.jme3.niftygui.NiftyJmeDisplay.newNiftyJmeDisplay;
import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.Controller;
import de.lessvoid.nifty.controls.Parameters;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.SizeValue;
import java.util.ArrayList;
import java.util.List;

/**
 * Это класс TestLoadingScreen вашей игры. Здесь вы должны выполнить 
 * инициализацию. Переместите свою Логику в AppStates или Controls
 *
 * @author normenhansen
 */

public class TestLoadingScreen extends SimpleApplication implements
        ScreenController, Controller {

    private NiftyJmeDisplay niftyDisplay;
    private Nifty nifty;
    private Element progressBarElement;
    private TerrainQuad terrain;
    private Material mat_terrain;
    private float frameCount = 0;
    private boolean load = false;
    private TextRenderer textRenderer;

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

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

        nifty.fromXml("Interface/nifty_loading.xml", "start", this);

        guiViewPort.addProcessor(niftyDisplay);
    }

    @Override
    public void simpleUpdate(float tpf) {

        if (load) { //loading is done over many frames
            if (frameCount == 1) {
                Element element = nifty.getScreen("loadlevel").findElementById(
                        "loadingtext");
                textRenderer = element.getRenderer(TextRenderer.class);

                mat_terrain = new Material(assetManager,
                        "Common/MatDefs/Terrain/Terrain.j3md");
                mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                        "Textures/Terrain/splat/alphamap.png"));
                setProgress(0.2f, "Loading grass");

            } else if (frameCount == 2) {
                Texture grass = assetManager.loadTexture(
                        "Textures/Terrain/splat/grass.jpg");
                grass.setWrap(WrapMode.Repeat);
                mat_terrain.setTexture("Tex1", grass);
                mat_terrain.setFloat("Tex1Scale", 64f);
                setProgress(0.4f, "Loading dirt");

            } else if (frameCount == 3) {
                Texture dirt = assetManager.loadTexture(
                        "Textures/Terrain/splat/dirt.jpg");

                dirt.setWrap(WrapMode.Repeat);
                mat_terrain.setTexture("Tex2", dirt);
                mat_terrain.setFloat("Tex2Scale", 32f);
                setProgress(0.5f, "Loading rocks");

            } else if (frameCount == 4) {
                Texture rock = assetManager.loadTexture(
                        "Textures/Terrain/splat/road.jpg");

                rock.setWrap(WrapMode.Repeat);

                mat_terrain.setTexture("Tex3", rock);
                mat_terrain.setFloat("Tex3Scale", 128f);
                setProgress(0.6f, "Creating terrain");

            } else if (frameCount == 5) {
                AbstractHeightMap heightmap = null;
                Texture heightMapImage = assetManager.loadTexture(
                        "Textures/Terrain/splat/mountains512.png");
                heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

                heightmap.load();
                terrain = new TerrainQuad("my terrain", 65, 513, heightmap.
                        getHeightMap());
                setProgress(0.8f, "Positioning terrain");

            } else if (frameCount == 6) {
                terrain.setMaterial(mat_terrain);

                terrain.setLocalTranslation(0, -100, 0);
                terrain.setLocalScale(2f, 1f, 2f);
                rootNode.attachChild(terrain);
                setProgress(0.9f, "Loading cameras");

            } else if (frameCount == 7) {
                List<Camera> cameras = new ArrayList<>();
                cameras.add(getCamera());
                TerrainLodControl control = new TerrainLodControl(terrain,
                        cameras);
                terrain.addControl(control);
                setProgress(1f, "Loading complete");

            } else if (frameCount == 8) {
                nifty.gotoScreen("end");
                nifty.exit();
                guiViewPort.removeProcessor(niftyDisplay);
                flyCam.setEnabled(true);
                flyCam.setMoveSpeed(50);
            }

            frameCount++;
        }
    }

    public void setProgress(final float progress, String loadingText) {
        final int MIN_WIDTH = 32;
        int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().
                getWidth() - MIN_WIDTH) * progress);
        progressBarElement.setConstraintWidth(new SizeValue(pixelWidth + "px"));
        progressBarElement.getParent().layoutElements();

        textRenderer.setText(loadingText);
    }

    public void showLoadingMenu() {
        nifty.gotoScreen("loadlevel");
        load = true;
    }

    @Override
    public void onStartScreen() {
    }

    @Override
    public void onEndScreen() {
    }

    @Override
    public void bind(Nifty nifty, Screen screen) {
        progressBarElement = nifty.getScreen("loadlevel").findElementById(
                "progressbar");
    }

    // методы для Контроллера
    @Override
    public boolean inputEvent(final NiftyInputEvent inputEvent) {
        return false;
    }

    @Override
    public void onFocus(boolean getFocus) {
    }

    @Override
    public void bind(Nifty nifty, Screen screen, Element elmnt,
            Parameters prmtrs) {
        progressBarElement = elmnt.findElementById("progressbar");
    }

    @Override
    public void init(Parameters prmtrs) {
    }

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

Использование многопоточности

Для получения дополнительной информации о многопоточности: Модель Потоков JME3

Обязательно измените XML файл, чтобы указать контроллеру на TestLoadingScreen*1*

package jme3test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.niftygui.NiftyJmeDisplay;
import static com.jme3.niftygui.NiftyJmeDisplay.newNiftyJmeDisplay;
import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.Controller;
import de.lessvoid.nifty.controls.Parameters;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.SizeValue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestLoadingScreen1 extends SimpleApplication implements
        ScreenController, Controller {

    private NiftyJmeDisplay niftyDisplay;
    private Nifty nifty;
    private Element progressBarElement;
    private TerrainQuad terrain;
    private Material mat_terrain;
    private boolean load = false;
    private ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
    private Future loadFuture = null;
    private TextRenderer textRenderer;
    private static final Logger LOG = Logger.getLogger(TestLoadingScreen1.class.
            getName());

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

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

        nifty.fromXml("Interface/nifty_loading.xml", "start", this);

        guiViewPort.addProcessor(niftyDisplay);
    }

    @Override
    public void simpleUpdate(float tpf) {
        if (load) {
            if (loadFuture == null) {
                //если мы еще не начали загрузку, отправьте Callable исполнителю
                loadFuture = exec.submit(loadingCallable);
            }
            //проверьте выполненные ли другие потоки
            if (loadFuture.isDone()) {
                 // эти вызовы должны выполняться в потоке цикла обновления,
                 // особенно присоединение рельеф к rootNode
                 // после присоединения он управляется потоком цикла обновления
                 // и больше не может быть изменен из любого другого потока!
                nifty.gotoScreen("end");
                nifty.exit();
                guiViewPort.removeProcessor(niftyDisplay);
                flyCam.setEnabled(true);
                flyCam.setMoveSpeed(50);
                rootNode.attachChild(terrain);
                load = false;
            }
        }
    }
    //этот callable который  содержит код, который запускается в другом потоке.
    //поскольку assetmananger является потокобезопасным, его можно использовать для загрузки данных из любого потока
    //мы *не* прикрепляем объекты к rootNode здесь!
    Callable<Void> loadingCallable = new Callable<Void>() {

        public Void call() {

            Element element = nifty.getScreen("loadlevel").findElementByName("loadingtext");
            textRenderer = element.getRenderer(TextRenderer.class);

            mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
            mat_terrain.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
            //setProgress является потокобезопасным (см. Ниже)
            setProgress(0.2f, "Loading grass");

            Texture grass = assetManager.loadTexture(
                    "Textures/Terrain/splat/grass.jpg");
            grass.setWrap(WrapMode.Repeat);
            mat_terrain.setTexture("Tex1", grass);
            mat_terrain.setFloat("Tex1Scale", 64f);
            setProgress(0.4f, "Loading dirt");

            Texture dirt = assetManager.loadTexture(
                    "Textures/Terrain/splat/dirt.jpg");

            dirt.setWrap(WrapMode.Repeat);
            mat_terrain.setTexture("Tex2", dirt);
            mat_terrain.setFloat("Tex2Scale", 32f);
            setProgress(0.5f, "Loading rocks");

            Texture rock = assetManager.loadTexture(
                    "Textures/Terrain/splat/road.jpg");

            rock.setWrap(WrapMode.Repeat);

            mat_terrain.setTexture("Tex3", rock);
            mat_terrain.setFloat("Tex3Scale", 128f);
            setProgress(0.6f, "Creating terrain");

            AbstractHeightMap heightmap = null;
            Texture heightMapImage = assetManager.loadTexture(
                    "Textures/Terrain/splat/mountains512.png");
            heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

            heightmap.load();
            terrain = new TerrainQuad("my terrain", 65, 513, heightmap.
                    getHeightMap());
            setProgress(0.8f, "Positioning terrain");

            terrain.setMaterial(mat_terrain);

            terrain.setLocalTranslation(0, -100, 0);
            terrain.setLocalScale(2f, 1f, 2f);
            setProgress(0.9f, "Loading cameras");

            List<Camera> cameras = new ArrayList<>();
            cameras.add(getCamera());
            TerrainLodControl control = new TerrainLodControl(terrain, cameras);
            terrain.addControl(control);
            setProgress(1f, "Loading complete");

            return null;
        }
    };

    public void setProgress(final float progress, final String loadingText) {
        //поскольку этот метод вызывается из другого потока, мы вставляем 
        //изменения в индикатор выполнения в поток цикла обновления
        enqueue(() -> {
            final int MIN_WIDTH = 32;
            int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().
                    getWidth() - MIN_WIDTH) * progress);
            progressBarElement.setConstraintWidth(new SizeValue(pixelWidth
                    + "px"));
            progressBarElement.getParent().layoutElements();

            textRenderer.setText(loadingText);
            return null;
        });

    }

    public void showLoadingMenu() {
        nifty.gotoScreen("loadlevel");
        load = true;
    }

    @Override
    public void onStartScreen() {
    }

    @Override
    public void onEndScreen() {
    }

    @Override
    public void bind(Nifty nifty, Screen screen) {
        progressBarElement = nifty.getScreen("loadlevel").findElementById(
                "progressbar");
    }

    // методы для Контроллера
    @Override
    public boolean inputEvent(final NiftyInputEvent inputEvent) {
        return false;
    }

    @Override
    public void onFocus(boolean getFocus) {
    }

    @Override
    public void destroy() {
        super.destroy();
        shutdownAndAwaitTermination(exec);
    }

    //стандартный процесс выключения для executor
    private void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // Отключить новые задачи from being submitted
        try {
            // Подождите, пока существующие задачи завершатся
            if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
                pool.shutdownNow(); // Отмена в настоящее время выполняемой задачи
                // Подождите, пока задачи будут откликаться на отмену
                if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
                    LOG.log(Level.SEVERE, "Pool did not terminate {0}", pool);
                }
            }
        } catch (InterruptedException ie) {
            // (Повторно-)Отменить, если текущий поток также прерван
            pool.shutdownNow();
            // Сохранять статус прерывания
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void bind(Nifty nifty, Screen screen, Element elmnt, Properties prprts, Attributes atrbts) {
        progressBarElement = elmnt.findElementByName("progressbar");
    }

    @Override
    public void init(Properties prprts, Attributes atrbts) {
    }

}

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

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

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