loading_screen.adoc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. = loading_screen
  2. :author:
  3. :revnumber:
  4. :revdate: 2016/03/17 20:48
  5. :relfileprefix: ../../
  6. :imagesdir: ../..
  7. ifdef::env-github,env-browser[:outfilesuffix: .adoc]
  8. == Nifty Loading Screen (Progress Bar)
  9. There is a good tutorial about creating a nifty progress bar here:
  10. 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]
  11. This example will use the existing hello terrain as an example.
  12. It will require these 2 images inside Assets/Interface/ (save them as border.png and inner.png respectively)
  13. image:jme3/advanced/inner1.png[inner1.png,with="",height=""]
  14. image:jme3/advanced/border1.png[border1.png,with="",height=""]
  15. This is the progress bar at 90%:
  16. image:jme3/advanced/loadingscreen.png[loadingscreen.png,with="",height=""]
  17. nifty_loading.xml
  18. [source,xml]
  19. ----
  20. <?xml version="1.0" encoding="UTF-8"?>
  21. <nifty>
  22. <useStyles filename="nifty-default-styles.xml" />
  23. <useControls filename="nifty-default-controls.xml" />
  24. <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
  25. <image filename="Interface/border.png" childLayout="absolute"
  26. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
  27. <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px" height="100%"
  28. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15" />
  29. </image>
  30. </controlDefinition>
  31. <screen id="start" controller = "jme3test.TestLoadingScreen">
  32. <layer id="layer" childLayout="center">
  33. <panel id = "panel2" height="30%" width="50%" align="center" valign="center" childLayout="vertical"
  34. visibleToMouse="true">
  35. <control id="startGame" name="button" backgroundColor="#0000" label="Load Game" align="center">
  36. <interact onClick="showLoadingMenu()" />
  37. </control>
  38. </panel>
  39. </layer>
  40. </screen>
  41. <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
  42. <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
  43. <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center" height="32px" width="70%">
  44. <control name="loadingbar" align="center" valign="center" width="100%" height="100%" />
  45. <control id="loadingtext" name="label" align="center"
  46. text=" "/>
  47. </panel>
  48. </layer>
  49. </screen>
  50. <screen id="end" controller = "jme3test.TestLoadingScreen">
  51. </screen>
  52. </nifty>
  53. ----
  54. === Understanding Nifty XML
  55. The progress bar and text is done statically using nifty XML.
  56. A custom control is created, which represents the progress bar.
  57. [source,xml]
  58. ----
  59. <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
  60. <image filename="Interface/border.png" childLayout="absolute"
  61. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
  62. <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px" height="100%"
  63. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15"/>
  64. </image>
  65. </controlDefinition>
  66. ----
  67. This screen simply displays a button in the middle of the screen, which could be seen as a simple main menu UI.
  68. [source,xml]
  69. ----
  70. <screen id="start" controller = "jme3test.TestLoadingScreen">
  71. <layer id="layer" childLayout="center">
  72. <panel id = "panel2" height="30%" width="50%" align="center" valign="center" childLayout="vertical"
  73. visibleToMouse="true">
  74. <control id="startGame" name="button" backgroundColor="#0000" label="Load Game" align="center">
  75. <interact onClick="showLoadingMenu()" />
  76. </control>
  77. </panel>
  78. </layer>
  79. </screen>
  80. ----
  81. This screen displays our custom progress bar control with a text control
  82. [source,xml]
  83. ----
  84. <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
  85. <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
  86. <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center" height="32px" width="400px">
  87. <control name="loadingbar" align="center" valign="center" width="400px" height="32px" />
  88. <control id="loadingtext" name="label" align="center"
  89. text=" "/>
  90. </panel>
  91. </layer>
  92. </screen>
  93. ----
  94. == Creating the bindings to use the Nifty XML
  95. 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.
  96. Something like this in a single thread will not work:
  97. [source,java]
  98. ----
  99. load_scene();
  100. update_bar(30%);
  101. load_characters();
  102. update_bar(60%);
  103. load_sounds();
  104. update_bar(100%);
  105. ----
  106. 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.
  107. The 2 main good solutions are:
  108. . Updating explicitly over many frames
  109. . Multi-threading
  110. === Updating progress bar over a number of frames
  111. The idea is to break down the loading of the game into discrete parts
  112. [source,java]
  113. ----
  114. package jme3test;
  115. import com.jme3.niftygui.NiftyJmeDisplay;
  116. import de.lessvoid.nifty.Nifty;
  117. import de.lessvoid.nifty.elements.Element;
  118. import de.lessvoid.nifty.input.NiftyInputEvent;
  119. import de.lessvoid.nifty.screen.Screen;
  120. import de.lessvoid.nifty.screen.ScreenController;
  121. import de.lessvoid.nifty.tools.SizeValue;
  122. import com.jme3.app.SimpleApplication;
  123. import com.jme3.material.Material;
  124. import com.jme3.renderer.Camera;
  125. import com.jme3.terrain.geomipmap.TerrainLodControl;
  126. import com.jme3.terrain.heightmap.AbstractHeightMap;
  127. import com.jme3.terrain.geomipmap.TerrainQuad;
  128. import com.jme3.terrain.heightmap.ImageBasedHeightMap;
  129. import com.jme3.texture.Texture;
  130. import com.jme3.texture.Texture.WrapMode;
  131. import de.lessvoid.nifty.controls.Controller;
  132. import de.lessvoid.nifty.elements.render.TextRenderer;
  133. import de.lessvoid.xml.xpp3.Attributes;
  134. import java.util.ArrayList;
  135. import java.util.List;
  136. import java.util.Properties;
  137. import jme3tools.converters.ImageToAwt;
  138. public class TestLoadingScreen extends SimpleApplication implements ScreenController, Controller {
  139. private NiftyJmeDisplay niftyDisplay;
  140. private Nifty nifty;
  141. private Element progressBarElement;
  142. private TerrainQuad terrain;
  143. private Material mat_terrain;
  144. private float frameCount = 0;
  145. private boolean load = false;
  146. private TextRenderer textRenderer;
  147. public static void main(String[] args) {
  148. TestLoadingScreen app = new TestLoadingScreen();
  149. app.start();
  150. }
  151. @Override
  152. public void simpleInitApp() {
  153. flyCam.setEnabled(false);
  154. niftyDisplay = new NiftyJmeDisplay(assetManager,
  155. inputManager,
  156. audioRenderer,
  157. guiViewPort);
  158. nifty = niftyDisplay.getNifty();
  159. nifty.fromXml("Interface/nifty_loading.xml", "start", this);
  160. guiViewPort.addProcessor(niftyDisplay);
  161. }
  162. @Override
  163. public void simpleUpdate(float tpf) {
  164. if (load) { //loading is done over many frames
  165. if (frameCount == 1) {
  166. Element element = nifty.getScreen("loadlevel").findElementByName("loadingtext");
  167. textRenderer = element.getRenderer(TextRenderer.class);
  168. mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
  169. mat_terrain.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
  170. setProgress(0.2f, "Loading grass");
  171. } else if (frameCount == 2) {
  172. Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
  173. grass.setWrap(WrapMode.Repeat);
  174. mat_terrain.setTexture("Tex1", grass);
  175. mat_terrain.setFloat("Tex1Scale", 64f);
  176. setProgress(0.4f, "Loading dirt");
  177. } else if (frameCount == 3) {
  178. Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
  179. dirt.setWrap(WrapMode.Repeat);
  180. mat_terrain.setTexture("Tex2", dirt);
  181. mat_terrain.setFloat("Tex2Scale", 32f);
  182. setProgress(0.5f, "Loading rocks");
  183. } else if (frameCount == 4) {
  184. Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
  185. rock.setWrap(WrapMode.Repeat);
  186. mat_terrain.setTexture("Tex3", rock);
  187. mat_terrain.setFloat("Tex3Scale", 128f);
  188. setProgress(0.6f, "Creating terrain");
  189. } else if (frameCount == 5) {
  190. AbstractHeightMap heightmap = null;
  191. Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
  192. heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
  193. heightmap.load();
  194. terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
  195. setProgress(0.8f, "Positioning terrain");
  196. } else if (frameCount == 6) {
  197. terrain.setMaterial(mat_terrain);
  198. terrain.setLocalTranslation(0, -100, 0);
  199. terrain.setLocalScale(2f, 1f, 2f);
  200. rootNode.attachChild(terrain);
  201. setProgress(0.9f, "Loading cameras");
  202. } else if (frameCount == 7) {
  203. List<Camera> cameras = new ArrayList<Camera>();
  204. cameras.add(getCamera());
  205. TerrainLodControl control = new TerrainLodControl(terrain, cameras);
  206. terrain.addControl(control);
  207. setProgress(1f, "Loading complete");
  208. } else if (frameCount == 8) {
  209. nifty.gotoScreen("end");
  210. nifty.exit();
  211. guiViewPort.removeProcessor(niftyDisplay);
  212. flyCam.setEnabled(true);
  213. flyCam.setMoveSpeed(50);
  214. }
  215. frameCount++;
  216. }
  217. }
  218. public void setProgress(final float progress, String loadingText) {
  219. final int MIN_WIDTH = 32;
  220. int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().getWidth() - MIN_WIDTH) * progress);
  221. progressBarElement.setConstraintWidth(new SizeValue(pixelWidth + "px"));
  222. progressBarElement.getParent().layoutElements();
  223. textRenderer.setText(loadingText);
  224. }
  225. public void showLoadingMenu() {
  226. nifty.gotoScreen("loadlevel");
  227. load = true;
  228. }
  229. @Override
  230. public void onStartScreen() {
  231. }
  232. @Override
  233. public void onEndScreen() {
  234. }
  235. @Override
  236. public void bind(Nifty nifty, Screen screen) {
  237. progressBarElement = nifty.getScreen("loadlevel").findElementByName("progressbar");
  238. }
  239. // methods for Controller
  240. @Override
  241. public boolean inputEvent(final NiftyInputEvent inputEvent) {
  242. return false;
  243. }
  244. @Override
  245. public void bind(Nifty nifty, Screen screen, Element elmnt, Properties prprts, Attributes atrbts) {
  246. progressBarElement = elmnt.findElementByName("progressbar");
  247. }
  248. @Override
  249. public void init(Properties prprts, Attributes atrbts) {
  250. }
  251. public void onFocus(boolean getFocus) {
  252. }
  253. }
  254. ----
  255. Note:
  256. * Try and add all controls near the end, as their update loops may begin executing
  257. === Using multithreading
  258. For more info on multithreading: <<jme3/advanced/multithreading#,The jME3 Threading Model>>
  259. Make sure to change the XML file to point the controller to TestLoadingScreen*1*
  260. [source,java]
  261. ----
  262. package jme3test;
  263. import com.jme3.niftygui.NiftyJmeDisplay;
  264. import de.lessvoid.nifty.Nifty;
  265. import de.lessvoid.nifty.elements.Element;
  266. import de.lessvoid.nifty.input.NiftyInputEvent;
  267. import de.lessvoid.nifty.screen.Screen;
  268. import de.lessvoid.nifty.screen.ScreenController;
  269. import de.lessvoid.nifty.tools.SizeValue;
  270. import com.jme3.app.SimpleApplication;
  271. import com.jme3.material.Material;
  272. import com.jme3.renderer.Camera;
  273. import com.jme3.terrain.geomipmap.TerrainLodControl;
  274. import com.jme3.terrain.heightmap.AbstractHeightMap;
  275. import com.jme3.terrain.geomipmap.TerrainQuad;
  276. import com.jme3.terrain.heightmap.ImageBasedHeightMap;
  277. import com.jme3.texture.Texture;
  278. import com.jme3.texture.Texture.WrapMode;
  279. import de.lessvoid.nifty.controls.Controller;
  280. import de.lessvoid.nifty.elements.render.TextRenderer;
  281. import de.lessvoid.xml.xpp3.Attributes;
  282. import java.util.ArrayList;
  283. import java.util.List;
  284. import java.util.Properties;
  285. import java.util.concurrent.Callable;
  286. import java.util.concurrent.Future;
  287. import java.util.concurrent.ScheduledThreadPoolExecutor;
  288. import jme3tools.converters.ImageToAwt;
  289. public class TestLoadingScreen1 extends SimpleApplication implements ScreenController, Controller {
  290. private NiftyJmeDisplay niftyDisplay;
  291. private Nifty nifty;
  292. private Element progressBarElement;
  293. private TerrainQuad terrain;
  294. private Material mat_terrain;
  295. private boolean load = false;
  296. private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(2);
  297. private Future loadFuture = null;
  298. private TextRenderer textRenderer;
  299. public static void main(String[] args) {
  300. TestLoadingScreen1 app = new TestLoadingScreen1();
  301. app.start();
  302. }
  303. @Override
  304. public void simpleInitApp() {
  305. flyCam.setEnabled(false);
  306. niftyDisplay = new NiftyJmeDisplay(assetManager,
  307. inputManager,
  308. audioRenderer,
  309. guiViewPort);
  310. nifty = niftyDisplay.getNifty();
  311. nifty.fromXml("Interface/nifty_loading.xml", "start", this);
  312. guiViewPort.addProcessor(niftyDisplay);
  313. }
  314. @Override
  315. public void simpleUpdate(float tpf) {
  316. if (load) {
  317. if (loadFuture == null) {
  318. //if we have not started loading yet, submit the Callable to the executor
  319. loadFuture = exec.submit(loadingCallable);
  320. }
  321. //check if the execution on the other thread is done
  322. if (loadFuture.isDone()) {
  323. //these calls have to be done on the update loop thread,
  324. //especially attaching the terrain to the rootNode
  325. //after it is attached, it's managed by the update loop thread
  326. // and may not be modified from any other thread anymore!
  327. nifty.gotoScreen("end");
  328. nifty.exit();
  329. guiViewPort.removeProcessor(niftyDisplay);
  330. flyCam.setEnabled(true);
  331. flyCam.setMoveSpeed(50);
  332. rootNode.attachChild(terrain);
  333. load = false;
  334. }
  335. }
  336. }
  337. //this is the callable that contains the code that is run on the other thread.
  338. //since the assetmananger is threadsafe, it can be used to load data from any thread
  339. //we do *not* attach the objects to the rootNode here!
  340. Callable<Void> loadingCallable = new Callable<Void>() {
  341. public Void call() {
  342. Element element = nifty.getScreen("loadlevel").findElementByName("loadingtext");
  343. textRenderer = element.getRenderer(TextRenderer.class);
  344. mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
  345. mat_terrain.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
  346. //setProgress is thread safe (see below)
  347. setProgress(0.2f, "Loading grass");
  348. Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
  349. grass.setWrap(WrapMode.Repeat);
  350. mat_terrain.setTexture("Tex1", grass);
  351. mat_terrain.setFloat("Tex1Scale", 64f);
  352. setProgress(0.4f, "Loading dirt");
  353. Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
  354. dirt.setWrap(WrapMode.Repeat);
  355. mat_terrain.setTexture("Tex2", dirt);
  356. mat_terrain.setFloat("Tex2Scale", 32f);
  357. setProgress(0.5f, "Loading rocks");
  358. Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
  359. rock.setWrap(WrapMode.Repeat);
  360. mat_terrain.setTexture("Tex3", rock);
  361. mat_terrain.setFloat("Tex3Scale", 128f);
  362. setProgress(0.6f, "Creating terrain");
  363. AbstractHeightMap heightmap = null;
  364. Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
  365. heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
  366. heightmap.load();
  367. terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
  368. setProgress(0.8f, "Positioning terrain");
  369. terrain.setMaterial(mat_terrain);
  370. terrain.setLocalTranslation(0, -100, 0);
  371. terrain.setLocalScale(2f, 1f, 2f);
  372. setProgress(0.9f, "Loading cameras");
  373. List<Camera> cameras = new ArrayList<Camera>();
  374. cameras.add(getCamera());
  375. TerrainLodControl control = new TerrainLodControl(terrain, cameras);
  376. terrain.addControl(control);
  377. setProgress(1f, "Loading complete");
  378. return null;
  379. }
  380. };
  381. public void setProgress(final float progress, final String loadingText) {
  382. //since this method is called from another thread, we enqueue the changes to the progressbar to the update loop thread
  383. enqueue(new Callable() {
  384. public Object call() throws Exception {
  385. final int MIN_WIDTH = 32;
  386. int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().getWidth() - MIN_WIDTH) * progress);
  387. progressBarElement.setConstraintWidth(new SizeValue(pixelWidth + "px"));
  388. progressBarElement.getParent().layoutElements();
  389. textRenderer.setText(loadingText);
  390. return null;
  391. }
  392. });
  393. }
  394. public void showLoadingMenu() {
  395. nifty.gotoScreen("loadlevel");
  396. load = true;
  397. }
  398. @Override
  399. public void onStartScreen() {
  400. }
  401. @Override
  402. public void onEndScreen() {
  403. }
  404. @Override
  405. public void bind(Nifty nifty, Screen screen) {
  406. progressBarElement = nifty.getScreen("loadlevel").findElementByName("progressbar");
  407. }
  408. // methods for Controller
  409. @Override
  410. public boolean inputEvent(final NiftyInputEvent inputEvent) {
  411. return false;
  412. }
  413. @Override
  414. public void bind(Nifty nifty, Screen screen, Element elmnt, Properties prprts, Attributes atrbts) {
  415. progressBarElement = elmnt.findElementByName("progressbar");
  416. }
  417. @Override
  418. public void init(Properties prprts, Attributes atrbts) {
  419. }
  420. public void onFocus(boolean getFocus) {
  421. }
  422. @Override
  423. public void stop() {
  424. super.stop();
  425. //the pool executor needs to be shut down so the application properly exits.
  426. exec.shutdown();
  427. }
  428. }
  429. ----