123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- = loading_screen
- :revnumber: 2.1
- :revdate: 2020/07/24
- == Nifty Loading Screen (Progress Bar)
- //There is a good tutorial about creating a nifty progress bar here:
- //link:http://sourceforge.net/apps/mediawiki/nifty-gui/index.php?title=Create_your_own_Control_%28//A_Nifty_Progressbar%29[http://sourceforge.net/apps/mediawiki/nifty-gui/index.php?title=Create_yo//ur_own_Control_%28A_Nifty_Progressbar%29]
- This example will use the existing hello terrain as an example.
- It will require these 2 images inside Assets/Interface/ (save them as border.png and inner.png respectively).
- image:gui/inner1.png[inner1.png,width="",height=""]
- image:gui/border1.png[border1.png,width="",height=""]
- You need to add the jme3-niftygui and xref:sdk:sample_code#jme3testdata-assets.adoc[jme3-test-data] libraries.
- You will need to set your projects source to JDK 8.
- This is the progress bar at 90%:
- image:gui/loadingscreen.png[loadingscreen.png,width="",height=""]
- nifty_loading.xml
- [source,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>
- ----
- === Understanding Nifty XML
- The progress bar and text is done statically using nifty XML.
- A custom control is created, which represents the progress bar.
- [source,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>
- ----
- This screen simply displays a button in the middle of the screen, which could be seen as a simple main menu UI.
- [source,xml]
- ----
- <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>
- ----
- This screen displays our custom progress bar control with a text control.
- [source,xml]
- ----
- <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>
- ----
- == Creating the bindings to use the Nifty XML
- There are 3 main ways to update a progress bar. To understand why these methods are necessary, an understanding of the graphics pipeline is needed.
- Something like this in a single thread will not work:
- [source,java]
- ----
- load_scene();
- update_bar(30%);
- load_characters();
- update_bar(60%);
- load_sounds();
- update_bar(100%);
- ----
- If you do all of this in a single frame, then it is sent to the graphics card only after the whole code block has executed. By this time the bar has reached 100% and the game has already begun – for the user, the progressbar on the screen would not have visibly changed.
- The 2 main good solutions are:
- . Updating explicitly over many frames
- . Multi-threading
- === Updating progress bar over a number of frames
- The idea is to break down the loading of the game into discrete parts.
- [source,java]
- ----
- 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;
- /**
- * This is the TestLoadingScreen Class of your Game. You should only do
- * initialization here. Move your Logic into AppStates or 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");
- }
- // methods for Controller
- @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) {
- }
- }
- ----
- NOTE: Try and add all controls near the end, as their update loops may begin executing.
- === Using multithreading
- For more info on multithreading: xref:app/multithreading.adoc[The jME3 Threading Model]
- Make sure to change the XML file to point the controller to TestLoadingScreen*1*.
- [source,java]
- ----
- 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) {
- //if we have not started loading, submit Callable to executor
- loadFuture = exec.submit(loadingCallable);
- }
- //check if the execution on the other thread is done
- if (loadFuture.isDone()) {
- //these calls have to be done on the update loop thread,
- //especially attaching the terrain to the rootNode
- //after it is attached, it's managed by the update loop thread
- // and may not be modified from any other thread anymore!
- nifty.gotoScreen("end");
- nifty.exit();
- guiViewPort.removeProcessor(niftyDisplay);
- flyCam.setEnabled(true);
- flyCam.setMoveSpeed(50);
- rootNode.attachChild(terrain);
- load = false;
- }
- }
- }
- //This is the callable that contains the code that is run on the other
- //thread.
- //Since the assetmanager is threadsafe, it can be used to load data from
- //any thread.
- //We do *not* attach the objects to the rootNode here!
- Callable<Void> loadingCallable = new Callable<Void>() {
- @Override
- public Void call() {
- 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 is thread safe (see below)
- 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) {
- //Since this method is called from another thread, we enqueue the
- //changes to the progressbar to the update loop thread.
- 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");
- }
- // methods for Controller
- @Override
- public boolean inputEvent(final NiftyInputEvent inputEvent) {
- return false;
- }
- @Override
- public void onFocus(boolean getFocus) {
- }
- @Override
- public void destroy() {
- super.destroy();
- shutdownAndAwaitTermination(exec);
- }
- //standard shutdown process for executor
- private void shutdownAndAwaitTermination(ExecutorService pool) {
- pool.shutdown(); // Disable new tasks from being submitted
- try {
- // Wait a while for existing tasks to terminate
- if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
- pool.shutdownNow(); // Cancel currently executing tasks
- // Wait a while for tasks to respond to being cancelled
- if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
- LOG.log(Level.SEVERE, "Pool did not terminate {0}", pool);
- }
- }
- } catch (InterruptedException ie) {
- // (Re-)Cancel if current thread also interrupted
- pool.shutdownNow();
- // Preserve interrupt status
- Thread.currentThread().interrupt();
- }
- }
- @Override
- public void bind(Nifty nifty, Screen screen, Element elmnt,
- Parameters prmtrs) {
- progressBarElement = elmnt.findElementById("progressbar");
- }
- @Override
- public void init(Parameters prmtrs) {
- }
- }
- ----
|