loading_screen.adoc 21 KB

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