save_and_load.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]--><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Asciidoctor 1.5.4"><meta name="keywords" content="convert, j3o, models, load, save, documentation, serialization, import, export, spatial, node, mesh, geometry, scenegraph, sdk"><title>Saving and Loading Games (.j3o)</title><link rel="stylesheet" href="./asciidoctor.css">
  2. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
  3. <link rel="stylesheet" href="./coderay-asciidoctor.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css"><link rel="stylesheet" href="/home/travis/build/jMonkeyEngine/wiki/build/asciidoc/html5/jme3/advanced/twemoji-awesome.css"></head><body class="article toc2 toc-left"><div id="header"><div id="toolbar"><a href="https://github.com/jMonkeyEngine/wiki/edit/master/src/docs/asciidoc/jme3/advanced/save_and_load.adoc"><i class="fa fa-pencil-square" aria-hidden="true"></i></a><a href="https://github.com/jMonkeyEngine/wiki/new/master/src/docs/asciidoc/jme3/advanced/"><i class="fa fa-plus-square" aria-hidden="true"></i></a><input dir="auto" style="position: relative; vertical-align: top;" spellcheck="false" autocomplete="off" class="searchbox__input aa-input" id="doc-search" name="search" placeholder="Search in the doc" required="required" type="search"></div><h1>Saving and Loading Games (.j3o)</h1><div class="details"><span class="author" id="author"></span><br><span id="revnumber">version ,</span> <span id="revdate">2016/03/17 20:48</span></div><div id="toc" class="toc2"><div id="toctitle">Table of Contents</div><ul class="sectlevel1"><li><a href="#sample-code">Sample Code</a></li><li><a href="#saving-a-node">Saving a Node</a></li><li><a href="#loading-a-node">Loading a Node</a></li><li><a href="#custom-savable-class">Custom Savable Class</a></li><li><a href="#default-value">Default Value</a></li></ul></div></div><div id="content"><div id="preamble"><div class="sectionbody"><div class="paragraph"><p>Spatials (that is Nodes and Geometries) can contain audio and light nodes, particle emitters, controls, and user data (player score, health, inventory, etc). For your game distribution, you must convert all original models to a faster binary format. You save individual Spatials as well as scenes using <code>com.jme3.export.binary.BinaryExporter</code>.</p></div>
  4. <div class="paragraph"><p>The jMonkeyEngine&#8217;s binary file format is called <code>.j3o</code>. You can convert, view and edit .j3o files and their materials in the jMonkeyEngine <a href="../../sdk.html">SDK</a> and compose scenes (this does not include editing meshes). For the conversion, you can either use the BinaryExporters, or a context menu in the SDK.</p></div>
  5. <div class="admonitionblock important"><table><tr><td class="icon"><i class="fa icon-important" title="Important"></i></td><td class="content"><div class="paragraph"><p>The jMonkeyEngine&#8217;s serialization system is the <code>com.jme3.export.Savable</code> interface. JME3&#8217;s BinaryExporter can write standard Java objects, JME3 objects, and primitive data types that are included in a <a href="../../jme3/advanced/spatial.html">spatial&#8217;s user data</a>. If you use custom game data classes, see below how to make them “Savable.</p></div></td></tr></table></div>
  6. <div class="paragraph"><p>There is also a com.jme3.export.xml.XMLExporter and com.jme3.export.xml.XMLImporter that similarly converts jme3 spatials to an XML format. But you wouldn&#8217;t use that to load models at runtime (quite slow).</p></div></div></div>
  7. <div class="sect1"><h2 id="sample-code">Sample Code</h2><div class="sectionbody"><div class="ulist"><ul><li><p><a href="https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java">TestSaveGame.java</a></p></li></ul></div></div></div>
  8. <div class="sect1"><h2 id="saving-a-node">Saving a Node</h2><div class="sectionbody"><div class="paragraph"><p>The following example overrides <code>stop()</code> in SimpleApplication to save the rootNode to a file when the user quits the application. The saved rootNode is a normal .j3o binary file that you can open in the <a href="../../sdk.html">SDK</a>.</p></div>
  9. <div class="admonitionblock warning"><table><tr><td class="icon"><i class="fa icon-warning" title="Warning"></i></td><td class="content"><div class="paragraph"><p>Note that when you save a model that has textures, the references to those textures are stored as absolute paths, so when loading the j3o file again, the textures have to be accessible at the exact location (relative to the assetmanager root, by default the <code>assets</code> directory) they were loaded from. This is why the SDK manages the conversion on the project level.</p></div></td></tr></table></div>
  10. <div class="listingblock"><div class="content"><pre class="CodeRay highlight"><code data-lang="java"> <span class="comment">/* This is called when the user quits the app. */</span>
  11. <span class="annotation">@Override</span>
  12. <span class="directive">public</span> <span class="type">void</span> stop() {
  13. <span class="predefined-type">String</span> userHome = <span class="predefined-type">System</span>.getProperty(<span class="string"><span class="delimiter">&quot;</span><span class="content">user.home</span><span class="delimiter">&quot;</span></span>);
  14. BinaryExporter exporter = BinaryExporter.getInstance();
  15. <span class="predefined-type">File</span> file = <span class="keyword">new</span> <span class="predefined-type">File</span>(userHome+<span class="string"><span class="delimiter">&quot;</span><span class="content">/Models/</span><span class="delimiter">&quot;</span></span>+<span class="string"><span class="delimiter">&quot;</span><span class="content">MyModel.j3o</span><span class="delimiter">&quot;</span></span>);
  16. <span class="keyword">try</span> {
  17. exporter.save(rootNode, file);
  18. } <span class="keyword">catch</span> (<span class="exception">IOException</span> ex) {
  19. <span class="predefined-type">Logger</span>.getLogger(Main.class.getName()).log(<span class="predefined-type">Level</span>.SEVERE, <span class="string"><span class="delimiter">&quot;</span><span class="content">Error: Failed to save game!</span><span class="delimiter">&quot;</span></span>, ex);
  20. }
  21. <span class="local-variable">super</span>.stop(); <span class="comment">// continue quitting the game</span>
  22. }</code></pre></div></div></div></div>
  23. <div class="sect1"><h2 id="loading-a-node">Loading a Node</h2><div class="sectionbody"><div class="paragraph"><p>The following example overrides <code>simpleInitApp()</code> in SimpleApplication to load <code>Models/MyModel.j3o</code> when the game is initialized.</p></div>
  24. <div class="listingblock"><div class="content"><pre class="CodeRay highlight"><code data-lang="java"> <span class="annotation">@Override</span>
  25. <span class="directive">public</span> <span class="type">void</span> simpleInitApp() {
  26. <span class="predefined-type">String</span> userHome = <span class="predefined-type">System</span>.getProperty(<span class="string"><span class="delimiter">&quot;</span><span class="content">user.home</span><span class="delimiter">&quot;</span></span>);
  27. assetManager.registerLocator(userHome, FileLocator.class);
  28. Node loadedNode = (Node)assetManager.loadModel(<span class="string"><span class="delimiter">&quot;</span><span class="content">Models/MyModel.j3o</span><span class="delimiter">&quot;</span></span>);
  29. loadedNode.setName(<span class="string"><span class="delimiter">&quot;</span><span class="content">loaded node</span><span class="delimiter">&quot;</span></span>);
  30. rootNode.attachChild(loadedNode);
  31. }</code></pre></div></div>
  32. <div class="admonitionblock tip"><table><tr><td class="icon"><i class="fa icon-tip" title="Tip"></i></td><td class="content"><div class="paragraph"><p>Here you see why we save user data inside spatials – so it can be saved and loaded together with the .j3o file. If you have game data outside Spatials, you have to remember to save() and load(), and get() and set() it yourself.</p></div></td></tr></table></div></div></div>
  33. <div class="sect1"><h2 id="custom-savable-class">Custom Savable Class</h2><div class="sectionbody"><div class="paragraph"><p>JME&#8217;s BinaryExporter can write standard Java objects (String, ArrayList, buffers, etc), JME objects (Savables, such as Material), and primitive data types (int, float, etc). If you are using any custom class together with a Spatial, then the custom class must implement the <code>com.jme3.export.Savable</code> interface. There are two common cases where this is relevant:</p></div>
  34. <div class="ulist"><ul><li><p>The Spatial is carrying any <a href="../../jme3/advanced/custom_controls.html">Custom Controls</a>.<br>
  35. Example: You used something like <code>mySpatial.addControl(myControl);</code></p></li><li><p>The Spatial&#8217;s user data can contain a custom Java object.<br>
  36. Example: You used something like <code>mySpatial.setUserData("inventory", myInventory);</code></p></li></ul></div>
  37. <div class="paragraph"><p>If your custom classes (the user data or the Controls) do not implement Savable, then the BinaryImporter/BinaryExporter cannot save the Spatial!</p></div>
  38. <div class="paragraph"><p>So every time you create a custom Control or custom user data class, remember to implement Savable:</p></div>
  39. <div class="listingblock"><div class="content"><pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">import</span> <span class="include">com.jme3.export.InputCapsule</span>;
  40. <span class="keyword">import</span> <span class="include">com.jme3.export.JmeExporter</span>;
  41. <span class="keyword">import</span> <span class="include">com.jme3.export.JmeImporter</span>;
  42. <span class="keyword">import</span> <span class="include">com.jme3.export.OutputCapsule</span>;
  43. <span class="keyword">import</span> <span class="include">com.jme3.export.Savable</span>;
  44. <span class="keyword">import</span> <span class="include">com.jme3.material.Material</span>;
  45. <span class="keyword">import</span> <span class="include">java.io.IOException</span>;
  46. <span class="directive">public</span> <span class="type">class</span> <span class="class">MyCustomClass</span> <span class="directive">implements</span> Savable {
  47. <span class="directive">private</span> <span class="type">int</span> someIntValue; <span class="comment">// some custom user data</span>
  48. <span class="directive">private</span> <span class="type">float</span> someFloatValue; <span class="comment">// some custom user data</span>
  49. <span class="directive">private</span> Material someJmeObject; <span class="comment">// some custom user data</span>
  50. ...
  51. <span class="comment">// your other code...</span>
  52. ...
  53. public <span class="type">void</span> write(JmeExporter ex) <span class="directive">throws</span> <span class="exception">IOException</span> {
  54. OutputCapsule capsule = ex.getCapsule(<span class="local-variable">this</span>);
  55. capsule.write(someIntValue, <span class="string"><span class="delimiter">&quot;</span><span class="content">someIntValue</span><span class="delimiter">&quot;</span></span>, <span class="integer">1</span>);
  56. capsule.write(someFloatValue, <span class="string"><span class="delimiter">&quot;</span><span class="content">someFloatValue</span><span class="delimiter">&quot;</span></span>, <span class="float">0f</span>);
  57. capsule.write(someJmeObject, <span class="string"><span class="delimiter">&quot;</span><span class="content">someJmeObject</span><span class="delimiter">&quot;</span></span>, <span class="keyword">new</span> Material());
  58. }
  59. <span class="directive">public</span> <span class="type">void</span> read(JmeImporter im) <span class="directive">throws</span> <span class="exception">IOException</span> {
  60. InputCapsule capsule = im.getCapsule(<span class="local-variable">this</span>);
  61. someIntValue = capsule.readInt( <span class="string"><span class="delimiter">&quot;</span><span class="content">someIntValue</span><span class="delimiter">&quot;</span></span>, <span class="integer">1</span>);
  62. someFloatValue = capsule.readFloat( <span class="string"><span class="delimiter">&quot;</span><span class="content">someFloatValue</span><span class="delimiter">&quot;</span></span>, <span class="float">0f</span>);
  63. someJmeObject = capsule.readSavable(<span class="string"><span class="delimiter">&quot;</span><span class="content">someJmeObject</span><span class="delimiter">&quot;</span></span>, <span class="keyword">new</span> Material());
  64. }
  65. }</code></pre></div></div>
  66. <div class="paragraph"><p>To make a custom class savable:</p></div>
  67. <div class="olist arabic"><ol class="arabic"><li><p>Implement <code>Savable</code> and add the <code>write()</code> and <code>read()</code> methods as shown in the example above.</p></li><li><p>Do the following for each non-temporary class field:</p><div class="ulist"><ul><li><p>Add one line that <code>write()</code>s the data to the JmeExport output capsule.</p><div class="ulist"><ul><li><p>Specify the variable to save, give it a String name (can be the same as the variable name), and specify a default value.</p></li></ul></div></li><li><p>Add one line that <code>read…()</code>s the data to the JmeImport input capsule.</p><div class="ulist"><ul><li><p>On the left side of the assignment, specify the class field that you are restoring</p></li><li><p>On the right side, use the appropriate <code>capsule.read…()</code> method for the data type. Specify the String name of the variable (must be the same as you used in the <code>write()</code> method), and again specify a default value.</p></li></ul></div></li></ul></div></li></ol></div>
  68. <div class="admonitionblock important"><table><tr><td class="icon"><i class="fa icon-important" title="Important"></i></td><td class="content"><div class="paragraph"><p>As with all serialization, remember that if you ever change data types in custom classes, the updated read() methods will no longer be able to read your old files. Also there has to be a constructor that takes no Parameters.</p></div></td></tr></table></div></div></div>
  69. <div class="sect1"><h2 id="default-value">Default Value</h2><div class="sectionbody"><div class="paragraph"><p>The default value plays an important role in what data is saved to file.</p></div>
  70. <div class="listingblock"><div class="title">write()</div>
  71. <div class="content"><pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">void</span> write(<span class="type">int</span> value, <span class="predefined-type">String</span> name, <span class="type">int</span> defVal) <span class="directive">throws</span> <span class="exception">IOException</span> {
  72. <span class="keyword">if</span> (value == defVal)
  73. <span class="keyword">return</span>;
  74. writeAlias(name, BinaryClassField.INT);
  75. write(value);
  76. }</code></pre></div></div>
  77. <div class="paragraph"><p>The write methods of the <a href="https://github.com/jMonkeyEngine/jmonkeyengine/blob/445f7ed010199d30c484fe75bacef4b87f2eb38e/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java">BinaryOutputCapsule.java </a> class do not write the <code>defVal</code> to file. Instead, they check to see if <code>value</code> is equal to <code>defVal</code>, and if so, will not write anything at all.</p></div>
  78. <div class="paragraph"><p>There are very good reasons to do this.</p></div>
  79. <div class="olist arabic"><ol class="arabic"><li><p>It takes less space if everything is a default value.</p></li><li><p>You may decide on new defaults later and your objects will automatically upgrade if they didn’t have specifically overridden values.</p></li></ol></div>
  80. <div class="listingblock"><div class="title">read()</div>
  81. <div class="content"><pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">int</span> readInt(<span class="predefined-type">String</span> name, <span class="type">int</span> defVal) <span class="directive">throws</span> <span class="exception">IOException</span> {
  82. BinaryClassField field = cObj.nameFields.get(name);
  83. <span class="keyword">if</span> (field == <span class="predefined-constant">null</span> || !fieldData.containsKey(field.alias))
  84. <span class="keyword">return</span> defVal;
  85. <span class="keyword">return</span> ((<span class="predefined-type">Integer</span>) fieldData.get(field.alias)).intValue();
  86. }</code></pre></div></div>
  87. <div class="paragraph"><p>When reading your saved file, the <a href="https://github.com/jMonkeyEngine/jmonkeyengine/blob/445f7ed010199d30c484fe75bacef4b87f2eb38e/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java">BinaryInputCapsule.java</a> class will see that the <code>name</code> field is <code>null</code> and this is when the defVal is set.</p></div>
  88. <div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content"><div class="paragraph"><p>If you rely on the compiler to initialize class or instance variables for you, this can lead too unintended consequences.</p></div>
  89. <div class="paragraph"><p>For example:</p></div>
  90. <div class="listingblock"><div class="content"><pre class="CodeRay highlight"><code data-lang="java">capsule.write(someIntValue, <span class="string"><span class="delimiter">&quot;</span><span class="content">someIntValue</span><span class="delimiter">&quot;</span></span>, <span class="integer">1</span>);</code></pre></div></div>
  91. <div class="paragraph"><p>If you let the compiler initialize <code>someIntValue</code>, it will initialize to zero and if it&#8217;s not changed after initialization, zero will be written to file.</p></div>
  92. <div class="listingblock"><div class="content"><pre class="CodeRay highlight"><code data-lang="java">someIntValue = capsule.readInt( <span class="string"><span class="delimiter">&quot;</span><span class="content">someIntValue</span><span class="delimiter">&quot;</span></span>, <span class="integer">1</span>);</code></pre></div></div>
  93. <div class="paragraph"><p>Now when <code>read</code> is called, it will see the &#8220;someIntValue&#8221; name and set the <code>someIntValue</code> variable to zero. Not one, as you were expecting. Keep this in mind when using Savable.</p></div></td></tr></table></div></div></div></div><div id="footer"><div id="footer-text">Version <br>Last updated 2019-12-20 23:30:51 +00:00</div></div><script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script><script>docsearch({
  94. apiKey: 'a736b6d93de805e26ec2f49b55013fbd',
  95. indexName: 'jmonkeyengine',
  96. inputSelector: '#doc-search',
  97. debug: false // Set debug to true if you want to inspect the dropdown
  98. });</script></body></html>