loading_screen.adoc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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_%28//A_Nifty_Progressbar%29[http://sourceforge.net/apps/mediawiki/nifty-gui/index.php?title=Create_yo//ur_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,width="",height=""]
  14. image:jme3/advanced/border1.png[border1.png,width="",height=""]
  15. You need to add the jme3-niftygui and <<sdk/sample_code#jme3testdata-assets#,jme3-test-data>> libraries.
  16. You will need to set your projects source to JDK 8.
  17. This is the progress bar at 90%:
  18. image:jme3/advanced/loadingscreen.png[loadingscreen.png,width="",height=""]
  19. nifty_loading.xml
  20. [source,xml]
  21. ----
  22. <?xml version="1.0" encoding="UTF-8"?>
  23. <nifty>
  24. <useStyles filename="nifty-default-styles.xml" />
  25. <useControls filename="nifty-default-controls.xml" />
  26. <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
  27. <image filename="Interface/border.png" childLayout="absolute"
  28. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
  29. <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px"
  30. height="100%" imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15" />
  31. </image>
  32. </controlDefinition>
  33. <screen id="start" controller = "jme3test.TestLoadingScreen">
  34. <layer id="layer" childLayout="center">
  35. <panel id = "panel2" height="30%" width="50%" align="center" valign="center"
  36. childLayout="vertical" visibleToMouse="true">
  37. <control id="startGame" name="button" backgroundColor="#0000" label="Load Game"
  38. align="center">
  39. <interact onClick="showLoadingMenu()" />
  40. </control>
  41. </panel>
  42. </layer>
  43. </screen>
  44. <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
  45. <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
  46. <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center"
  47. height="32px" width="70%">
  48. <control name="loadingbar" align="center" valign="center" width="100%"
  49. height="100%" />
  50. <control id="loadingtext" name="label" align="center"
  51. text=" "/>
  52. </panel>
  53. </layer>
  54. </screen>
  55. <screen id="end" controller = "jme3test.TestLoadingScreen">
  56. </screen>
  57. </nifty>
  58. ----
  59. === Understanding Nifty XML
  60. The progress bar and text is done statically using nifty XML.
  61. A custom control is created, which represents the progress bar.
  62. [source,xml]
  63. ----
  64. <controlDefinition name = "loadingbar" controller = "jme3test.TestLoadingScreen">
  65. <image filename="Interface/border.png" childLayout="absolute"
  66. imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15">
  67. <image id="progressbar" x="0" y="0" filename="Interface/inner.png" width="32px"
  68. height="100%" imageMode="resize:15,2,15,15,15,2,15,2,15,2,15,15"/>
  69. </image>
  70. </controlDefinition>
  71. ----
  72. This screen simply displays a button in the middle of the screen, which could be seen as a simple main menu UI.
  73. [source,xml]
  74. ----
  75. <screen id="start" controller = "jme3test.TestLoadingScreen">
  76. <layer id="layer" childLayout="center">
  77. <panel id = "panel2" height="30%" width="50%" align="center" valign="center"
  78. childLayout="vertical" visibleToMouse="true">
  79. <control id="startGame" name="button" backgroundColor="#0000" label="Load Game"
  80. align="center"> <interact onClick="showLoadingMenu()" />
  81. </control>
  82. </panel>
  83. </layer>
  84. </screen>
  85. ----
  86. This screen displays our custom progress bar control with a text control.
  87. [source,xml]
  88. ----
  89. <screen id="loadlevel" controller = "jme3test.TestLoadingScreen">
  90. <layer id="loadinglayer" childLayout="center" backgroundColor="#000000">
  91. <panel id = "loadingpanel" childLayout="vertical" align="center" valign="center"
  92. height="32px" width="400px">
  93. <control name="loadingbar" align="center" valign="center" width="400px"
  94. height="32px" />
  95. <control id="loadingtext" name="label" align="center"
  96. text=" "/>
  97. </panel>
  98. </layer>
  99. </screen>
  100. ----
  101. == Creating the bindings to use the Nifty XML
  102. 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.
  103. Something like this in a single thread will not work:
  104. [source,java]
  105. ----
  106. load_scene();
  107. update_bar(30%);
  108. load_characters();
  109. update_bar(60%);
  110. load_sounds();
  111. update_bar(100%);
  112. ----
  113. 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.
  114. The 2 main good solutions are:
  115. . Updating explicitly over many frames
  116. . Multi-threading
  117. === Updating progress bar over a number of frames
  118. The idea is to break down the loading of the game into discrete parts.
  119. [source,java]
  120. ----
  121. package jme3test;
  122. import com.jme3.app.SimpleApplication;
  123. import com.jme3.material.Material;
  124. import com.jme3.niftygui.NiftyJmeDisplay;
  125. import static com.jme3.niftygui.NiftyJmeDisplay.newNiftyJmeDisplay;
  126. import com.jme3.renderer.Camera;
  127. import com.jme3.terrain.geomipmap.TerrainLodControl;
  128. import com.jme3.terrain.geomipmap.TerrainQuad;
  129. import com.jme3.terrain.heightmap.AbstractHeightMap;
  130. import com.jme3.terrain.heightmap.ImageBasedHeightMap;
  131. import com.jme3.texture.Texture;
  132. import com.jme3.texture.Texture.WrapMode;
  133. import de.lessvoid.nifty.Nifty;
  134. import de.lessvoid.nifty.controls.Controller;
  135. import de.lessvoid.nifty.controls.Parameters;
  136. import de.lessvoid.nifty.elements.Element;
  137. import de.lessvoid.nifty.elements.render.TextRenderer;
  138. import de.lessvoid.nifty.input.NiftyInputEvent;
  139. import de.lessvoid.nifty.screen.Screen;
  140. import de.lessvoid.nifty.screen.ScreenController;
  141. import de.lessvoid.nifty.tools.SizeValue;
  142. import java.util.ArrayList;
  143. import java.util.List;
  144. /**
  145. * This is the TestLoadingScreen Class of your Game. You should only do
  146. * initialization here. Move your Logic into AppStates or Controls
  147. *
  148. * @author normenhansen
  149. */
  150. public class TestLoadingScreen extends SimpleApplication implements
  151. ScreenController, Controller {
  152. private NiftyJmeDisplay niftyDisplay;
  153. private Nifty nifty;
  154. private Element progressBarElement;
  155. private TerrainQuad terrain;
  156. private Material mat_terrain;
  157. private float frameCount = 0;
  158. private boolean load = false;
  159. private TextRenderer textRenderer;
  160. public static void main(String[] args) {
  161. TestLoadingScreen app = new TestLoadingScreen();
  162. app.start();
  163. }
  164. @Override
  165. public void simpleInitApp() {
  166. flyCam.setEnabled(false);
  167. niftyDisplay = newNiftyJmeDisplay(assetManager,
  168. inputManager,
  169. audioRenderer,
  170. guiViewPort);
  171. nifty = niftyDisplay.getNifty();
  172. nifty.fromXml("Interface/nifty_loading.xml", "start", this);
  173. guiViewPort.addProcessor(niftyDisplay);
  174. }
  175. @Override
  176. public void simpleUpdate(float tpf) {
  177. if (load) { //loading is done over many frames
  178. if (frameCount == 1) {
  179. Element element = nifty.getScreen("loadlevel").findElementById(
  180. "loadingtext");
  181. textRenderer = element.getRenderer(TextRenderer.class);
  182. mat_terrain = new Material(assetManager,
  183. "Common/MatDefs/Terrain/Terrain.j3md");
  184. mat_terrain.setTexture("Alpha", assetManager.loadTexture(
  185. "Textures/Terrain/splat/alphamap.png"));
  186. setProgress(0.2f, "Loading grass");
  187. } else if (frameCount == 2) {
  188. Texture grass = assetManager.loadTexture(
  189. "Textures/Terrain/splat/grass.jpg");
  190. grass.setWrap(WrapMode.Repeat);
  191. mat_terrain.setTexture("Tex1", grass);
  192. mat_terrain.setFloat("Tex1Scale", 64f);
  193. setProgress(0.4f, "Loading dirt");
  194. } else if (frameCount == 3) {
  195. Texture dirt = assetManager.loadTexture(
  196. "Textures/Terrain/splat/dirt.jpg");
  197. dirt.setWrap(WrapMode.Repeat);
  198. mat_terrain.setTexture("Tex2", dirt);
  199. mat_terrain.setFloat("Tex2Scale", 32f);
  200. setProgress(0.5f, "Loading rocks");
  201. } else if (frameCount == 4) {
  202. Texture rock = assetManager.loadTexture(
  203. "Textures/Terrain/splat/road.jpg");
  204. rock.setWrap(WrapMode.Repeat);
  205. mat_terrain.setTexture("Tex3", rock);
  206. mat_terrain.setFloat("Tex3Scale", 128f);
  207. setProgress(0.6f, "Creating terrain");
  208. } else if (frameCount == 5) {
  209. AbstractHeightMap heightmap = null;
  210. Texture heightMapImage = assetManager.loadTexture(
  211. "Textures/Terrain/splat/mountains512.png");
  212. heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
  213. heightmap.load();
  214. terrain = new TerrainQuad("my terrain", 65, 513, heightmap.
  215. getHeightMap());
  216. setProgress(0.8f, "Positioning terrain");
  217. } else if (frameCount == 6) {
  218. terrain.setMaterial(mat_terrain);
  219. terrain.setLocalTranslation(0, -100, 0);
  220. terrain.setLocalScale(2f, 1f, 2f);
  221. rootNode.attachChild(terrain);
  222. setProgress(0.9f, "Loading cameras");
  223. } else if (frameCount == 7) {
  224. List<Camera> cameras = new ArrayList<>();
  225. cameras.add(getCamera());
  226. TerrainLodControl control = new TerrainLodControl(terrain,
  227. cameras);
  228. terrain.addControl(control);
  229. setProgress(1f, "Loading complete");
  230. } else if (frameCount == 8) {
  231. nifty.gotoScreen("end");
  232. nifty.exit();
  233. guiViewPort.removeProcessor(niftyDisplay);
  234. flyCam.setEnabled(true);
  235. flyCam.setMoveSpeed(50);
  236. }
  237. frameCount++;
  238. }
  239. }
  240. public void setProgress(final float progress, String loadingText) {
  241. final int MIN_WIDTH = 32;
  242. int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().
  243. getWidth() - MIN_WIDTH) * progress);
  244. progressBarElement.setConstraintWidth(new SizeValue(pixelWidth + "px"));
  245. progressBarElement.getParent().layoutElements();
  246. textRenderer.setText(loadingText);
  247. }
  248. public void showLoadingMenu() {
  249. nifty.gotoScreen("loadlevel");
  250. load = true;
  251. }
  252. @Override
  253. public void onStartScreen() {
  254. }
  255. @Override
  256. public void onEndScreen() {
  257. }
  258. @Override
  259. public void bind(Nifty nifty, Screen screen) {
  260. progressBarElement = nifty.getScreen("loadlevel").findElementById(
  261. "progressbar");
  262. }
  263. // methods for Controller
  264. @Override
  265. public boolean inputEvent(final NiftyInputEvent inputEvent) {
  266. return false;
  267. }
  268. @Override
  269. public void onFocus(boolean getFocus) {
  270. }
  271. @Override
  272. public void bind(Nifty nifty, Screen screen, Element elmnt,
  273. Parameters prmtrs) {
  274. progressBarElement = elmnt.findElementById("progressbar");
  275. }
  276. @Override
  277. public void init(Parameters prmtrs) {
  278. }
  279. }
  280. ----
  281. NOTE: Try and add all controls near the end, as their update loops may begin executing.
  282. === Using multithreading
  283. For more info on multithreading: <<jme3/advanced/multithreading#,The jME3 Threading Model>>
  284. Make sure to change the XML file to point the controller to TestLoadingScreen*1*.
  285. [source,java]
  286. ----
  287. package jme3test;
  288. import com.jme3.app.SimpleApplication;
  289. import com.jme3.material.Material;
  290. import com.jme3.niftygui.NiftyJmeDisplay;
  291. import static com.jme3.niftygui.NiftyJmeDisplay.newNiftyJmeDisplay;
  292. import com.jme3.renderer.Camera;
  293. import com.jme3.terrain.geomipmap.TerrainLodControl;
  294. import com.jme3.terrain.geomipmap.TerrainQuad;
  295. import com.jme3.terrain.heightmap.AbstractHeightMap;
  296. import com.jme3.terrain.heightmap.ImageBasedHeightMap;
  297. import com.jme3.texture.Texture;
  298. import com.jme3.texture.Texture.WrapMode;
  299. import de.lessvoid.nifty.Nifty;
  300. import de.lessvoid.nifty.controls.Controller;
  301. import de.lessvoid.nifty.controls.Parameters;
  302. import de.lessvoid.nifty.elements.Element;
  303. import de.lessvoid.nifty.elements.render.TextRenderer;
  304. import de.lessvoid.nifty.input.NiftyInputEvent;
  305. import de.lessvoid.nifty.screen.Screen;
  306. import de.lessvoid.nifty.screen.ScreenController;
  307. import de.lessvoid.nifty.tools.SizeValue;
  308. import java.util.ArrayList;
  309. import java.util.List;
  310. import java.util.concurrent.Callable;
  311. import java.util.concurrent.ExecutorService;
  312. import java.util.concurrent.Executors;
  313. import java.util.concurrent.Future;
  314. import java.util.concurrent.ScheduledExecutorService;
  315. import java.util.concurrent.TimeUnit;
  316. import java.util.logging.Level;
  317. import java.util.logging.Logger;
  318. public class TestLoadingScreen1 extends SimpleApplication implements
  319. ScreenController, Controller {
  320. private NiftyJmeDisplay niftyDisplay;
  321. private Nifty nifty;
  322. private Element progressBarElement;
  323. private TerrainQuad terrain;
  324. private Material mat_terrain;
  325. private boolean load = false;
  326. private ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
  327. private Future loadFuture = null;
  328. private TextRenderer textRenderer;
  329. private static final Logger LOG = Logger.getLogger(TestLoadingScreen1.class.
  330. getName());
  331. public static void main(String[] args) {
  332. TestLoadingScreen1 app = new TestLoadingScreen1();
  333. app.start();
  334. }
  335. @Override
  336. public void simpleInitApp() {
  337. flyCam.setEnabled(false);
  338. niftyDisplay = newNiftyJmeDisplay(assetManager,
  339. inputManager,
  340. audioRenderer,
  341. guiViewPort);
  342. nifty = niftyDisplay.getNifty();
  343. nifty.fromXml("Interface/nifty_loading.xml", "start", this);
  344. guiViewPort.addProcessor(niftyDisplay);
  345. }
  346. @Override
  347. public void simpleUpdate(float tpf) {
  348. if (load) {
  349. if (loadFuture == null) {
  350. //if we have not started loading, submit Callable to executor
  351. loadFuture = exec.submit(loadingCallable);
  352. }
  353. //check if the execution on the other thread is done
  354. if (loadFuture.isDone()) {
  355. //these calls have to be done on the update loop thread,
  356. //especially attaching the terrain to the rootNode
  357. //after it is attached, it's managed by the update loop thread
  358. // and may not be modified from any other thread anymore!
  359. nifty.gotoScreen("end");
  360. nifty.exit();
  361. guiViewPort.removeProcessor(niftyDisplay);
  362. flyCam.setEnabled(true);
  363. flyCam.setMoveSpeed(50);
  364. rootNode.attachChild(terrain);
  365. load = false;
  366. }
  367. }
  368. }
  369. //This is the callable that contains the code that is run on the other
  370. //thread.
  371. //Since the assetmananger is threadsafe, it can be used to load data from
  372. //any thread.
  373. //We do *not* attach the objects to the rootNode here!
  374. Callable<Void> loadingCallable = new Callable<Void>() {
  375. @Override
  376. public Void call() {
  377. Element element = nifty.getScreen("loadlevel").findElementById(
  378. "loadingtext");
  379. textRenderer = element.getRenderer(TextRenderer.class);
  380. mat_terrain = new Material(assetManager,
  381. "Common/MatDefs/Terrain/Terrain.j3md");
  382. mat_terrain.setTexture("Alpha", assetManager.loadTexture(
  383. "Textures/Terrain/splat/alphamap.png"));
  384. //setProgress is thread safe (see below)
  385. setProgress(0.2f, "Loading grass");
  386. Texture grass = assetManager.loadTexture(
  387. "Textures/Terrain/splat/grass.jpg");
  388. grass.setWrap(WrapMode.Repeat);
  389. mat_terrain.setTexture("Tex1", grass);
  390. mat_terrain.setFloat("Tex1Scale", 64f);
  391. setProgress(0.4f, "Loading dirt");
  392. Texture dirt = assetManager.loadTexture(
  393. "Textures/Terrain/splat/dirt.jpg");
  394. dirt.setWrap(WrapMode.Repeat);
  395. mat_terrain.setTexture("Tex2", dirt);
  396. mat_terrain.setFloat("Tex2Scale", 32f);
  397. setProgress(0.5f, "Loading rocks");
  398. Texture rock = assetManager.loadTexture(
  399. "Textures/Terrain/splat/road.jpg");
  400. rock.setWrap(WrapMode.Repeat);
  401. mat_terrain.setTexture("Tex3", rock);
  402. mat_terrain.setFloat("Tex3Scale", 128f);
  403. setProgress(0.6f, "Creating terrain");
  404. AbstractHeightMap heightmap = null;
  405. Texture heightMapImage = assetManager.loadTexture(
  406. "Textures/Terrain/splat/mountains512.png");
  407. heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
  408. heightmap.load();
  409. terrain = new TerrainQuad("my terrain", 65, 513, heightmap.
  410. getHeightMap());
  411. setProgress(0.8f, "Positioning terrain");
  412. terrain.setMaterial(mat_terrain);
  413. terrain.setLocalTranslation(0, -100, 0);
  414. terrain.setLocalScale(2f, 1f, 2f);
  415. setProgress(0.9f, "Loading cameras");
  416. List<Camera> cameras = new ArrayList<>();
  417. cameras.add(getCamera());
  418. TerrainLodControl control = new TerrainLodControl(terrain, cameras);
  419. terrain.addControl(control);
  420. setProgress(1f, "Loading complete");
  421. return null;
  422. }
  423. };
  424. public void setProgress(final float progress, final String loadingText) {
  425. //Since this method is called from another thread, we enqueue the
  426. //changes to the progressbar to the update loop thread.
  427. enqueue(() -> {
  428. final int MIN_WIDTH = 32;
  429. int pixelWidth = (int) (MIN_WIDTH + (progressBarElement.getParent().
  430. getWidth() - MIN_WIDTH) * progress);
  431. progressBarElement.setConstraintWidth(new SizeValue(pixelWidth
  432. + "px"));
  433. progressBarElement.getParent().layoutElements();
  434. textRenderer.setText(loadingText);
  435. return null;
  436. });
  437. }
  438. public void showLoadingMenu() {
  439. nifty.gotoScreen("loadlevel");
  440. load = true;
  441. }
  442. @Override
  443. public void onStartScreen() {
  444. }
  445. @Override
  446. public void onEndScreen() {
  447. }
  448. @Override
  449. public void bind(Nifty nifty, Screen screen) {
  450. progressBarElement = nifty.getScreen("loadlevel").findElementById(
  451. "progressbar");
  452. }
  453. // methods for Controller
  454. @Override
  455. public boolean inputEvent(final NiftyInputEvent inputEvent) {
  456. return false;
  457. }
  458. @Override
  459. public void onFocus(boolean getFocus) {
  460. }
  461. @Override
  462. public void destroy() {
  463. super.destroy();
  464. shutdownAndAwaitTermination(exec);
  465. }
  466. //standard shutdown process for executor
  467. private void shutdownAndAwaitTermination(ExecutorService pool) {
  468. pool.shutdown(); // Disable new tasks from being submitted
  469. try {
  470. // Wait a while for existing tasks to terminate
  471. if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
  472. pool.shutdownNow(); // Cancel currently executing tasks
  473. // Wait a while for tasks to respond to being cancelled
  474. if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
  475. LOG.log(Level.SEVERE, "Pool did not terminate {0}", pool);
  476. }
  477. }
  478. } catch (InterruptedException ie) {
  479. // (Re-)Cancel if current thread also interrupted
  480. pool.shutdownNow();
  481. // Preserve interrupt status
  482. Thread.currentThread().interrupt();
  483. }
  484. }
  485. @Override
  486. public void bind(Nifty nifty, Screen screen, Element elmnt,
  487. Parameters prmtrs) {
  488. progressBarElement = elmnt.findElementById("progressbar");
  489. }
  490. @Override
  491. public void init(Parameters prmtrs) {
  492. }
  493. }
  494. ----