| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- = loading_screen
- :author:
- :revnumber:
- :revdate: 2016/03/17 20:48
- :relfileprefix: ../../
- :imagesdir: ../..
- ifdef::env-github,env-browser[:outfilesuffix: .adoc]
- == 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_%28A_Nifty_Progressbar%29[http://sourceforge.net/apps/mediawiki/nifty-gui/index.php?title=Create_your_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:jme3/advanced/inner1.png[inner1.png,with="",height=""]
- image:jme3/advanced/border1.png[border1.png,with="",height=""]
- This is the progress bar at 90%:
- image:jme3/advanced/loadingscreen.png[loadingscreen.png,with="",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.niftygui.NiftyJmeDisplay;
- import de.lessvoid.nifty.Nifty;
- import de.lessvoid.nifty.elements.Element;
- 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 com.jme3.app.SimpleApplication;
- import com.jme3.material.Material;
- import com.jme3.renderer.Camera;
- import com.jme3.terrain.geomipmap.TerrainLodControl;
- import com.jme3.terrain.heightmap.AbstractHeightMap;
- import com.jme3.terrain.geomipmap.TerrainQuad;
- import com.jme3.terrain.heightmap.ImageBasedHeightMap;
- import com.jme3.texture.Texture;
- import com.jme3.texture.Texture.WrapMode;
- import de.lessvoid.nifty.controls.Controller;
- import de.lessvoid.nifty.elements.render.TextRenderer;
- import de.lessvoid.xml.xpp3.Attributes;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Properties;
- import jme3tools.converters.ImageToAwt;
- 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 = new NiftyJmeDisplay(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").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(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<Camera>();
- 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").findElementByName("progressbar");
- }
- // methods for Controller
- @Override
- public boolean inputEvent(final NiftyInputEvent inputEvent) {
- return false;
- }
- @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) {
- }
- public void onFocus(boolean getFocus) {
- }
- }
- ----
- Note:
- * Try and add all controls near the end, as their update loops may begin executing
- === Using multithreading
- For more info on multithreading: <<jme3/advanced/multithreading#,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.niftygui.NiftyJmeDisplay;
- import de.lessvoid.nifty.Nifty;
- import de.lessvoid.nifty.elements.Element;
- 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 com.jme3.app.SimpleApplication;
- import com.jme3.material.Material;
- import com.jme3.renderer.Camera;
- import com.jme3.terrain.geomipmap.TerrainLodControl;
- import com.jme3.terrain.heightmap.AbstractHeightMap;
- import com.jme3.terrain.geomipmap.TerrainQuad;
- import com.jme3.terrain.heightmap.ImageBasedHeightMap;
- import com.jme3.texture.Texture;
- import com.jme3.texture.Texture.WrapMode;
- import de.lessvoid.nifty.controls.Controller;
- import de.lessvoid.nifty.elements.render.TextRenderer;
- import de.lessvoid.xml.xpp3.Attributes;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Properties;
- import java.util.concurrent.Callable;
- import java.util.concurrent.Future;
- import java.util.concurrent.ScheduledThreadPoolExecutor;
- import jme3tools.converters.ImageToAwt;
- 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 ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(2);
- private Future loadFuture = null;
- private TextRenderer textRenderer;
- public static void main(String[] args) {
- TestLoadingScreen1 app = new TestLoadingScreen1();
- app.start();
- }
- @Override
- public void simpleInitApp() {
- flyCam.setEnabled(false);
- niftyDisplay = new NiftyJmeDisplay(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 yet, submit the Callable to the 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 assetmananger 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>() {
- 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 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<Camera>();
- 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(new Callable() {
- public Object call() throws Exception {
- 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").findElementByName("progressbar");
- }
- // methods for Controller
- @Override
- public boolean inputEvent(final NiftyInputEvent inputEvent) {
- return false;
- }
- @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) {
- }
- public void onFocus(boolean getFocus) {
- }
-
-
- @Override
- public void stop() {
- super.stop();
- //the pool executor needs to be shut down so the application properly exits.
- exec.shutdown();
- }
- }
- ----
|