├── README.md └── Save Tutorial ├── ScriptedSaveSystem_Tutorial.md └── images ├── ScriptCanvas_NodePaletteOptionspng.png ├── ScriptCanvas_Restore.png └── ScriptCanvas_Save.png /README.md: -------------------------------------------------------------------------------- 1 | # O3DE_tutorials 2 | O3DE Game Development Tutorials 3 | 4 | Welcome! 5 | 6 | This repository contains tutorials for developing with [O3DE engine](http://o3de.org). 7 | 8 | ---- 9 | 10 | [Save Tutorial](https://github.com/ossls/O3DE_tutorials/blob/7d61c7c0e5dd93228bd4625989646968443acc60/Save%20Tutorial/ScriptedSaveSystem_Tutorial.md) 11 | 12 | This tutorial explains how you can create a JSON based file save system by combining Lua and Script Canvas. Currently Script Canvas does not have native JSON load/save/parsing functionality. In the meantime, it is possible to leverage Lua for JSON operations. 13 | 14 | It covers: 15 | 16 | - Creating Script Events 17 | - Using Script Events between Lua and Script Canvas 18 | - Using a Lua JSON parser to save/load JSON files 19 | - Sending the JSON data from Lua to Script Canvas to allow working in a visual environment 20 | 21 | ---- 22 | 23 | -------------------------------------------------------------------------------- /Save Tutorial/ScriptedSaveSystem_Tutorial.md: -------------------------------------------------------------------------------- 1 | # Creating a Save System with Script Canvas, Script Events, and Lua 2 | 3 | This tutorial covers an approach to save data from Script Canvas to disk. Currently there is no built-in way to accomplish this, but it can be done using a combination of features in Lumberyard. 4 | 5 | At its core we will use a Script Event as a way of having bilateral communcation between Script Canvas and Lua. 6 | 7 | ### Why Lua? 8 | 9 | Lua has libraries available to perform file operations, we will use these and an open source JSON library for Lua in order to save and restore our data. 10 | 11 | 12 | 13 | ## Script Event 14 | 15 | Create a new Script Event asset, name it: SaveEvents 16 | 17 | - Rename Category to: Save System 18 | - Add two events 19 | 20 | ### First Event 21 | 22 | - **Name:** Save 23 | - Add 3 parameters (you can add as many as you need, but for this tutorial we'll do 3). 24 | 25 | - **Parameter 0:** 26 | - Name: myNumber_1 27 | - Type: Number 28 | 29 | - **Parameter 1:** 30 | - Name: myString_2 31 | - Type: String 32 | 33 | - **Paramter 2:** 34 | - Name: myBool_3 35 | - Type: Boolean 36 | 37 | ### Second Event 38 | 39 | - **Name:** Restore 40 | - Add 3 parameters that match the ones from Save **(IMPORTANT: they need to be named differently than the ones in Save)** 41 | 42 | - **Parameter 0:** 43 | - Name: inNumber_1 44 | - Type: Number 45 | 46 | - **Parameter 1:** 47 | - Name: inString_2 48 | - Type: String 49 | 50 | - **Paramter 2:** 51 | - Name: inBool_3 52 | - Type: Boolean 53 | 54 | ## Notes: 55 | 56 | For this tutorial, we are making these two events pretty identical, the reason is to avoid using the same event to both Send and Receive as this can create an infinite loop situation. 57 | 58 | # Sending Script Event from Script Canvas 59 | 60 | This example script simply waits a few seconds before sending some data using a Script Canvas event. 61 | 62 | The Script Canvas event "SaveEvents" can be found on the Node Palette under the "Save System" folder (recall this is the category name we gave the Script Event we created in the first section of this tutorial). 63 | 64 | ![Script Canvas Save](./images/ScriptCanvas_NodePaletteOptionspng.png) 65 | 66 | # Lua 67 | 68 | ## json.lua 69 | 70 | We will use the open source https://github.com/rxi/json.lua as the JSON encoder/decoder. Download the file json.lua file and place it in **\Scripts** 71 | 72 | We will use this package within Lua using the require keyword: 73 | 74 | ```lua 75 | json = require "Scripts.json" 76 | ``` 77 | 78 | ## SaveSystem.lua 79 | 80 | For this tutorial we only need one Lua script, the first part of this script is to bind it to the Script Event asset. 81 | 82 | ```lua 83 | local SaveSystem = 84 | { 85 | Properties = 86 | { 87 | SaveSystemScriptEvents = { default=ScriptEventsAssetRef() }, 88 | }, 89 | } 90 | ``` 91 | 92 | This section of the code will add an asset entry to the Entity's component that uses this Lua script. You will need to specify the Script Event asset we created in the first section. 93 | 94 | Lua is able to connect to the new Script Event that was just created, it can handle events in a Script Event and it can broadcast events from the script. 95 | 96 | The first step will be to get the Script Event and connect to it in order to handle its events 97 | 98 | ```lua 99 | function SaveSystem:OnActivate() 100 | 101 | -- Connect to the Script Event (SaveEvents is the name of the ScriptEvent we specified in the ScriptEvent asset) 102 | self.theScriptEvent = SaveEvents.Connect(self) 103 | end 104 | ``` 105 | 106 | Having connected to the Script Event, the next step is to handle an event, we will only be handling the Save event in this Lua script, the Restore event will be handle in Script Canvas later. 107 | 108 | ```lua 109 | function SaveSystem:Save(myNumber1, myString2, myBool3) 110 | 111 | -- The code that saves the data received will be saved to file here 112 | 113 | end 114 | ``` 115 | 116 | Now, we need to look into how json.lua works, it's fairly straightforward, we will encode an object with all the value parameters provided, then we will use Lua's io library to save it to file. 117 | 118 | ```lua 119 | function SaveSystem:Save(myNumber1, myString2, myBool3) 120 | 121 | encodedJsonData = json.encode({ myNumber1, myString2, myBool3}) 122 | 123 | Debug.Log("JSON: " .. tostring(test)) -- prints: JSON: [432,"abcdef",true] 124 | 125 | -- Open a file for writing 126 | file = io.open ("mygamesavedata.json" , "w") 127 | 128 | -- Write the encoded JSON data 129 | file:write(tostring(encodedJsonData)) 130 | 131 | -- Close the file 132 | file:close() 133 | 134 | end 135 | ``` 136 | 137 | Complete Save Lua script: 138 | 139 | ```lua 140 | 141 | json = require "Scripts.json" 142 | 143 | local SaveSystem = 144 | { 145 | Properties = 146 | { 147 | SaveSystemScriptEvents = { default=ScriptEventsAssetRef() }, 148 | }, 149 | } 150 | 151 | function SaveSystem:OnActivate() 152 | 153 | -- Connect to the Script Event (SaveEvents is the name of the ScriptEvent we specified in the ScriptEvent asset) 154 | self.theScriptEvent = SaveEvents.Connect(self) 155 | end 156 | 157 | function SaveSystem:Save(myNumber1, myString2, myBool3) 158 | 159 | encodedJsonData = json.encode({ myNumber1, myString2, myBool3}) 160 | 161 | Debug.Log("JSON: " .. tostring(test)) -- prints: JSON: [432,"abcdef",true] 162 | 163 | -- Open a file for writing 164 | file = io.open ("mygamesavedata.json" , "w") 165 | 166 | -- Write the encoded JSON data 167 | file:write(tostring(encodedJsonData)) 168 | 169 | -- Close the file 170 | file:close() 171 | 172 | end 173 | 174 | function SaveSystem:OnDeactivate() 175 | end 176 | 177 | return SaveSystem 178 | 179 | ``` 180 | 181 | We will place the Restore system in its own Lua script, this will enable us to have a different entity that once activated, it will load the saved data and broadcast it out. 182 | 183 | ```lua 184 | json = require "Scripts.json" 185 | 186 | local RestoreSystem = 187 | { 188 | Properties = 189 | { 190 | SaveSystemScriptEvents = { default=ScriptEventsAssetRef() }, 191 | }, 192 | } 193 | 194 | function RestoreSystem:Load() 195 | 196 | -- Open the file we previously saved for Reading 197 | file = io.open("mygamesavedata.json", "r") 198 | 199 | -- Read its raw JSON data, this will be a string 200 | jsonData = file:read() 201 | 202 | -- Decode the JSON data 203 | decodedData = json.decode(jsonData) 204 | 205 | -- We will create a table using the decoded JSON data 206 | args = {} 207 | for k, v in pairs(decodedData) do 208 | table.insert(args, v) 209 | end 210 | 211 | -- Finally, we will broadcast Restore and pass in each of the elements in the table we just created 212 | 213 | -- The number of arguments sent to Restore must match the number of parameters in the Script Event's Restore method 214 | SaveEvents.Broadcast.Restore( 215 | args[1], -- myNumber1 216 | args[2], -- myString2 217 | args[3] -- myBool3 218 | ) 219 | 220 | end 221 | 222 | -- When the entity that owns this script is Activated, we will load the saved data and broadcast it 223 | function RestoreSystem:OnActivate() 224 | 225 | self.theScriptEvent = SaveEvents.Connect(self) 226 | 227 | -- For now, we will immediately load our data when this script is activated 228 | self.Load() 229 | end 230 | 231 | function RestoreSystem:OnDeactivate() 232 | end 233 | 234 | return RestoreSystem 235 | ``` 236 | 237 | # Script Canvas Save Script 238 | 239 | In order to save data from Script Canvas, we will drag a "Send Save" node from the Script Canvas Node Palette under the "Save System" category. 240 | 241 | You can then pass in the data you wish to save to the slots on the node and call the node as needed. 242 | 243 | ![Script Canvas Save](./images/ScriptCanvas_Save.png) 244 | 245 | # Script Canvas Restore Script 246 | 247 | Finally, when a Restore entity is activated, it will broadcast the Restore Script Event. We will handle this event in a separate script (for the purpose of this tutorial, but it's up to you how you wish to organize your scripts). 248 | 249 | We need to drag out a Receive Restore node from the Script Canvas Node Palette. Anytime the LoadSystem Lua script is activated, this node will receive the data saved from the JSON file. 250 | 251 | ![Script Canvas Restore](./images/ScriptCanvas_Restore.png) 252 | 253 | # Closing Thoughts 254 | 255 | - This system shows how you can use Script Canvas, Script Events, JSON and Lua to save and load custom data from file. It doesn't offer much in terms of error detection and reporting. 256 | 257 | - Currently the script is hardcoded to create a file called "mygamesavedata.json", it would be a good idea to make this a property that is provided in the entity's component properties. 258 | 259 | - The system will generate a file at the development root, which is not a great place when a game is ready to be relesed, or even during production, this can be improved by getting the user's "My Documents" folder in Lua: 260 | 261 | ```lua 262 | username = os.getenv('USERNAME') 263 | dir = 'C:\\users\\' .. username .. '\\Documents' 264 | ``` 265 | 266 | - Be careful to keep your Script Event file, both Save and Restore function's number of parameters and the Lua script files synchronized. You need to make sure that the same number of parameters are being sent and received. 267 | 268 | - This system was not tested extensively against all Script Events supported types, it is possible some of them do not encode correctly, and the example scripts above do not provide much in terms of error detection, however [lua.json](https://github.com/rxi/json.lua) does provide some features that could be leveraged to ensure correctness. 269 | -------------------------------------------------------------------------------- /Save Tutorial/images/ScriptCanvas_NodePaletteOptionspng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossls/O3DE_tutorials/32e52073bbba8de6e85cd7afa6c5f827d2815cb8/Save Tutorial/images/ScriptCanvas_NodePaletteOptionspng.png -------------------------------------------------------------------------------- /Save Tutorial/images/ScriptCanvas_Restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossls/O3DE_tutorials/32e52073bbba8de6e85cd7afa6c5f827d2815cb8/Save Tutorial/images/ScriptCanvas_Restore.png -------------------------------------------------------------------------------- /Save Tutorial/images/ScriptCanvas_Save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossls/O3DE_tutorials/32e52073bbba8de6e85cd7afa6c5f827d2815cb8/Save Tutorial/images/ScriptCanvas_Save.png --------------------------------------------------------------------------------