├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── CHANGELOG.md.meta
├── CODE_OF_CONDUCT.md
├── CODE_OF_CONDUCT.md.meta
├── Editor.meta
├── Editor
├── SaveAsyncMenu.cs
└── SaveAsyncMenu.cs.meta
├── LICENSE.md
├── LICENSE.md.meta
├── README.md
├── README.md.meta
├── Runtime.meta
├── Runtime
├── BUCK.SaveAsync.Runtime.asmdef
├── BUCK.SaveAsync.Runtime.asmdef.meta
├── Encryption.cs
├── Encryption.cs.meta
├── FileHandler.cs
├── FileHandler.cs.meta
├── ISaveable.cs
├── ISaveable.cs.meta
├── SaveManager.cs
├── SaveManager.cs.meta
├── Singleton.cs
└── Singleton.cs.meta
├── Samples~
└── SaveAsyncExample
│ ├── Benchmarks.cs
│ ├── Benchmarks.cs.meta
│ ├── CacheDataExample.cs
│ ├── CacheDataExample.cs.meta
│ ├── Files.cs
│ ├── Files.cs.meta
│ ├── Game State Async Sample.unity.meta
│ ├── GameDataExample.cs
│ ├── GameDataExample.cs.meta
│ ├── GameManagerExample.cs
│ ├── GameManagerExample.cs.meta
│ ├── GameStateBenchmarks.cs.meta
│ ├── Save Async Sample.unity
│ └── Save Async Sample.unity.meta
├── package.json
└── package.json.meta
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Asset meta data should only be ignored when the corresponding asset is also ignored
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # Autogenerated Jetbrains Rider plugin
20 | [Aa]ssets/Plugins/Editor/JetBrains*
21 |
22 | # Visual Studio cache directory
23 | .vs/
24 |
25 | # Gradle cache directory
26 | .gradle/
27 |
28 | # Autogenerated VS/MD/Consulo solution and project files
29 | ExportedObj/
30 | .consulo/
31 | *.csproj
32 | *.unityproj
33 | *.sln
34 | *.suo
35 | *.tmp
36 | *.user
37 | *.userprefs
38 | *.pidb
39 | *.booproj
40 | *.svd
41 | *.pdb
42 | *.mdb
43 | *.opendb
44 | *.VC.db
45 |
46 | # Unity3D generated meta files
47 | *.pidb.meta
48 | *.pdb.meta
49 | *.mdb.meta
50 |
51 | # Unity3D generated file on crash reports
52 | sysinfo.txt
53 |
54 | # Builds
55 | *.apk
56 | *.unitypackage
57 |
58 | # Crashlytics generated file
59 | crashlytics-build.properties
60 |
61 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.4.3] - 2024-08-20
4 |
5 | - Converted the SaveManager.Exists method and the default FileHandler method to be async. This isn't necessary for typical file I/O but can be useful in the case of custom file handlers that have a wait time associated with checking for file availability.
6 |
7 | ## [0.4.2] - 2024-07-30
8 |
9 | - Added the ability to disable background threads to help debug issues.
10 |
11 | ## [0.4.1] - 2024-07-16
12 |
13 | - Added additional exception handling for malformed JSON data.
14 |
15 | ## [0.4.0] - 2024-07-16
16 |
17 | - The ISaveable interface Guid property ISaveable.Guid has been replaced with a string property called ISaveable.Key. This is a breaking change, but it enables the option to use either a unique string key or store a Guid as a string, rather than using Guids and serialized byte arrays exclusively.
18 | - Added some much needed error checking to validate ISaveable keys when loading data. Previously, if a key was not found in the registered saveables dictionary, it would throw an unhandled exception.
19 |
20 | ## [0.3.2] - 2024-07-09
21 |
22 | - The default FileHandler now inherits from ScriptableObject and can be overridden. This can be useful in scenarios where files should not be saved using local file IO (such as cloud saves) or when a platform-specific save API must be used.
23 |
24 | ## [0.3.1] - 2024-01-20
25 |
26 | - Renamed the project to "Save Async"
27 |
28 | ## [0.3.0] - 2023-12-11
29 |
30 | - Fixed lots of potential concurrency issues and race conditions.
31 | - Fixed an issue where spamming output could cause errors.
32 | - Simplified DataManager internals to be more DRY.
33 | - Added XML comments to all public DataManager API methods.
34 | - Improved loading performance by reducing thread swapping.
35 | - FileHandler methods now return a Task rather than an Awaitable which is necessary to support the async File API methods.
36 | - Added more error checking throughout.
37 | - Removed AES encryption for now until more work and testing can be done.
38 |
39 | ## [0.2.0] - 2023-11-28
40 |
41 | - Added basic support for JSON writing and reading using Newtonsoft Json.NET
42 | - Basic XOR encryption has been added. AES is still WIP.
43 |
44 | ## [0.1.0] - 2023-11-22
45 |
46 | - Initial commit.
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: db46a03c91de1c349aed695872a6ddc9
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official email address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | [INSERT CONTACT METHOD].
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series
87 | of actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or
94 | permanent ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within
114 | the community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.0, available at
120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available
127 | at [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
135 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: edc42b1cd322fd3439847569db6c88de
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5e167a5aac73f434abaa17e6889f4b01
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/SaveAsyncMenu.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using UnityEngine;
4 | using UnityEditor;
5 | using System.Diagnostics;
6 | using System.IO;
7 |
8 | namespace Buck.SaveAsync
9 | {
10 | public class SaveAsyncMenu
11 | {
12 | [MenuItem("Tools/Save Async/Open Persistent Data Path", false, 0)]
13 | static void OpenPersistentDataPath()
14 | {
15 | // Get the path to the persistent data directory
16 | string path = Application.persistentDataPath;
17 |
18 | // Open the directory in the file explorer
19 | Process.Start(path);
20 | }
21 |
22 | [MenuItem("Tools/Save Async/Clear Persistent Data Path", false, 1)]
23 | static void ClearPersistentDataPath()
24 | {
25 | // Get the path to the persistent data directory
26 | string path = Application.persistentDataPath;
27 |
28 | // Confirm with the user that they want to delete all data
29 | if (EditorUtility.DisplayDialog("Clear Persistent Data",
30 | "Are you sure you want to delete all data in the persistent data path?",
31 | "Yes", "No"))
32 | {
33 | // Delete all files and directories at the path
34 | DirectoryInfo directory = new DirectoryInfo(path);
35 | foreach (FileInfo file in directory.GetFiles())
36 | file.Delete();
37 |
38 | foreach (DirectoryInfo dir in directory.GetDirectories())
39 | dir.Delete(true);
40 |
41 | // Optional: Show a confirmation message
42 | EditorUtility.DisplayDialog("Persistent Data Cleared",
43 | "All data in the persistent data path has been deleted.",
44 | "Ok");
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Editor/SaveAsyncMenu.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2a2c79a6a6d8eaf458da8154535764b1
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 BUCK Design LLC [buck-co](https://github.com/buck-co)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b6aef760eb32c3848a1599ca53cd0991
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Save Async
4 | _Save Async_ is [BUCK](https://buck.co)'s Unity package for asynchronously saving and loading data in the background using Unity's [`Awaitable`](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/Awaitable) class. It includes a simple API that makes it easy to capture and restore state without interrupting Unity's main render thread. That means smoother framerates and fewer load screens!
5 |
6 | ### Features
7 | - :watch: **Asynchronous**: All methods are asynchronously "awaitable" meaning the game can continue running while it waits for a response from the storage device, even if that storage device is slow (like HDDs, external storage, and some consoles)
8 | - :thread: **Background Threading**: All file I/O occurs on background threads which helps avoid dips in framerate
9 | - :zap: **SaveManager API**: Simple API that can be called from anywhere with methods like `SaveManager.Save()`
10 | - :floppy_disk: **ISaveable Interface**: Implement this interface on any class to give it the ability to be saved and loaded
11 | - :ledger: **JSON Serialization**: Data is saved to JSON automatically using Unity's own [Newtonsoft Json Unity Package](https://docs.unity3d.com/Packages/com.unity.nuget.newtonsoft-json@3.2/manual/index.html)
12 | - :game_die: **Battle-Tested**: Used in production on shipped games including [_The Electric State: Kid Cosmo_](https://apps.apple.com/us/app/the-electric-state-kid-cosmo/id6475495298)
13 |
14 | # Getting Started
15 | > [!NOTE]
16 | > This package works with **Unity 2023.1 and above** as it requires Unity's [`Awaitable`](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/Awaitable.html) class which is not available in earlier versions.>
17 |
18 | 📺 [Watch the Save Async Tutorial video](https://vimeo.com/buck/save-async) or follow the instructions below.
19 |
20 | ### Install the _Save Async_ Package
21 |
22 | 1. Copy the git URL of this repository: `https://github.com/buck-co/save-async.git`
23 | 2. In Unity, open the Package Manager from the menu by going to `Window > Package Manager`
24 | 3. Click the plus icon in the upper left and choose `Add package from git URL...`
25 | 4. Paste the git URL into the text field and click the `Add` button.
26 |
27 | ### Install the _Unity Converters for Newtonsoft.Json_ Package (strongly recommended)
28 |
29 | 1. Copy this git URL: `https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters.git#upm`
30 | 2. In Unity, open the Package Manager from the menu by going to `Window > Package Manager`
31 | 3. Click the plus icon in the upper left and choose `Add package from git URL...`
32 | 4. Paste the git URL into the text field and click the `Add` button.
33 |
34 | Save Async depends on Unity's Json.NET package for serializing data, which is already included as a package dependency and will be installed automatically. However, Unity types like Vector3 don't serialize to JSON very nicely, and can include ugly recursive property loops, like this:
35 |
36 | ```json
37 | {
38 | "x": 0,
39 | "y": 1,
40 | "z": 0,
41 | "normalized": {
42 | "x": 0,
43 | "y": 1,
44 | "z": 0,
45 | "normalized": {
46 | "x": 0,
47 | "y": 1,
48 | "z": 0,
49 | "normalized": {
50 | "x": 0,
51 | "y": 1,
52 | "z": 0,
53 | "normalized": {
54 | ...
55 | }
56 | }
57 | }
58 | }
59 | }
60 | ```
61 |
62 | _Yikes!_ Installing the [Unity Converters for Newtonsoft.Json](https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters) package takes care of these issues, as well as many more.
63 |
64 | Once you've done this, Json.NET should be able to convert Unity's built-in types. In the future, we'll try to include this as a package dependency, but currently the Unity Package Manager only allows packages to have dependencies that come from the official Unity registry.
65 |
66 | ### Basic Workflow
67 | After installing the package...
68 |
69 | 1. Add the `SaveManager` component to a GameObject in your scene.
70 | 2. Implement the `ISaveable` interface on at least one class (more detail on how to do this is available below).
71 | 3. Register the ISaveable by calling `SaveManager.RegisterSaveable(mySaveableObject);` This is usually done in `MonoBehaviour.Awake()`
72 | 4. Call SaveManager API methods like `SaveManager.Save()` from elsewhere in your project, such as from a Game Manager class. Do this _after_ all your ISaveable implementations are registered.
73 |
74 | ### Included Samples
75 |
76 | This package includes a sample project which you can install from the Unity Package Manager by selecting the package from the list and then selecting the `Samples` tab on the right. Then click `Import`. Examining the sample can help you understand how to use the package in your own project.
77 |
78 |
79 | # Implementing ISaveable and using the SaveManager API
80 |
81 | Any class that should save or load data needs to implement the [`ISaveable`](Runtime/ISaveable.cs) interface.
82 |
83 | - **Key Property**: Each `ISaveable` must have a globally unique string for distinguishing it when saving and loading data.
84 | - **Filename Property**: Each `ISaveable` must have a filename string that identifies which file it should be saved in.
85 | - **CaptureState Method**: This method captures and returns the current state of the object in a serializable format.
86 | - **RestoreState Method**: This method restores the object's state from the provided data.
87 |
88 | ## Example Implementation: `GameDataExample`
89 |
90 | 1. **Implement `ISaveable` in Your Class**
91 |
Your class should inherit from `ISaveable` from the `Buck.SaveAsync` namespace.
92 | ```csharp
93 | using Buck.SaveAsync
94 |
95 | public class YourClass : MonoBehaviour, ISaveable
96 | {
97 | // Your code here...
98 | }
99 | ```
100 |
101 | 2. **Choose a Filename**
102 |
This is the file name where this object's data will be saved.
103 | ```csharp
104 | public string Filename => Files.GameData;
105 | ```
106 |
107 | It is recommended to use a static class to store file paths as strings to avoid typos.
108 |
109 | ```csharp
110 | public static class Files
111 | {
112 | public const string GameData = "GameData.dat";
113 | public const string SomeFile = "SomeFile.dat";
114 | }
115 | ```
116 |
117 | 3. **Generate and Store a Unique Serializable Key**
118 |
Ensure that your class has a globally unique string key, such as "GameDataExample".
119 | ```csharp
120 | public string Key => "GameDataExample";
121 | ```
122 | Optionally, you can generate and use a serializable Guid to uniquely identify your objects. Use `SaveManager.GetSerializableGuid()` in MonoBehaviour.OnValidate() to get the Guid and then store it as a serialized byte array (since the System.Guid type itself cannot be serialized).
123 | ```csharp
124 | [SerializeField, HideInInspector] byte[] m_guidBytes;
125 | public string Key => new Guid(m_guidBytes).ToString();
126 | void OnValidate() => SaveManager.GetSerializableGuid(ref m_guidBytes);
127 | ```
128 |
129 | 4. **Register Your Object with `SaveManager`**
130 |
Register the object with `SaveManager`. Generally it's best to do this in your `Awake` method or during initialization. Make sure you do this before calling any save or load methods in the SaveManager class or your saveables won't be picked up!
131 | ```csharp
132 | void Awake()
133 | {
134 | SaveManager.RegisterSaveable(this);
135 | }
136 | ```
137 |
138 | 5. **Define Your Data Structure**
139 |
Create a struct or class that represents the data you want to save. This structure needs to be serializable.
140 | ```csharp
141 | [Serializable]
142 | public struct MyCustomData
143 | {
144 | // Custom data fields
145 | public string playerName;
146 | public int playerHealth;
147 | public Vector3 position;
148 | public Dictionary inventory;
149 | }
150 | ```
151 |
152 | 6. **Implement `CaptureState` and `RestoreState` Methods**
153 |
Implement the `CaptureState` method to capture and return the current state of your object. Then implement the `RestoreState` method to restore your object's state from the saved data. Both of these methods will be called by the `SaveManager` class when you call its save and load methods.
154 | ```csharp
155 | public object CaptureState()
156 | {
157 | return new MyCustomData
158 | {
159 | playerName = m_playerName,
160 | playerHealth = m_playerHealth,
161 | position = m_position,
162 | inventory = m_inventory
163 | };
164 | }
165 |
166 | public void RestoreState(object state)
167 | {
168 | var s = (MyCustomData)state;
169 |
170 | m_playerName = s.playerName;
171 | m_playerHealth = s.playerHealth;
172 | m_position = s.position;
173 | m_inventory = s.inventory;
174 | }
175 | ```
176 |
177 | For a complete example, check out [this ISaveable implementation](Samples~/SaveAsyncExample/GameDataExample.cs) in the sample project.
178 |
179 | ## SaveManager API
180 |
181 | [`SaveManager`](Runtime/SaveManager.cs) methods can be called anywhere in your game's logic that you want to save or load, such as in a Game Manager class or a main menu screen. You should add the SaveManager component to a GameObject in your scene. Below you'll find the public interface for interacting with the SaveManager class, along with short code examples.
182 |
183 | > [!NOTE]
184 | > The `SaveManager` class is in the `Buck.SaveAsync` namespace. Be sure to include this line at the top of any files that make calls to SaveManager methods or implement the ISaveable interface.
185 | ```csharp
186 | using Buck.SaveAsync
187 | ```
188 |
189 |
190 |
191 | ### Properties
192 |
193 | #### `bool IsBusy`
194 | Indicates whether or not the SaveManager class is currently busy with a file operation. This can be useful if you want to wait for one operation to finish before doing another, although because file operations are queued, this generally is only necessary for benchmarking and testing purposes.
195 |
196 |
197 | **Usage Example**:
198 | ```csharp
199 | while (SaveManager.IsBusy)
200 | await Awaitable.NextFrameAsync();
201 | ```
202 |
203 |
204 | ### Methods
205 |
206 | #### `void RegisterSaveable(ISaveable saveable)`
207 | Registers an ISaveable with the SaveManager class for saving and loading.
208 |
209 | **Usage Example**:
210 | ```csharp
211 | SaveManager.RegisterSaveable(mySaveableObject);
212 | ```
213 |
214 |
215 | #### `Awaitable Save(string[] filenames)`
216 | Asynchronously saves the files at the specified array of paths or filenames.
217 |
218 | **Usage Example**:
219 | ```csharp
220 | await SaveManager.Save(new string[] {"MyFile.dat"});
221 | ```
222 |
223 |
224 | #### `Awaitable Load(string[] filenames)`
225 | Asynchronously loads the files at the specified array of paths or filenames.
226 |
227 | **Usage Example**:
228 | ```csharp
229 | await SaveManager.Load(new string[] {"MyFile.dat"});
230 | ```
231 |
232 |
233 | #### `Awaitable Delete(string[] filenames)`
234 | Asynchronously deletes the files at the specified array of paths or filenames.
235 |
236 | **Usage Example**:
237 | ```csharp
238 | await SaveManager.Delete(new string[] {"MyFile.dat"});
239 | ```
240 |
241 |
242 | #### `Awaitable Erase(string[] filenames)`
243 | Asynchronously erases the files at the specified paths or filenames, leaving them empty but still on disk.
244 |
245 | **Usage Example**:
246 | ```csharp
247 | await SaveManager.Erase(new string[] {"MyFile.dat"});
248 | ```
249 |
250 |
251 | #### `byte[] GetSerializableGuid(ref byte[] guidBytes)`
252 | Sets the given Guid byte array to a new Guid byte array if it is null, empty, or an empty Guid. The `guidBytes` parameter is a byte array (passed by reference) that you would like to fill with a serializable guid.
253 |
254 | **Usage Example**:
255 | ```csharp
256 | [SerializeField, HideInInspector] byte[] m_guidBytes;
257 | public string Key => new Guid(m_guidBytes).ToString();
258 | void OnValidate() => SaveManager.GetSerializableGuid(ref m_guidBytes);
259 | ```
260 |
261 |
262 | ## Encryption
263 |
264 | If you want to prevent mischievous gamers from tampering with your save files, you can encrypt them using XOR encryption. To turn it on, use the encryption dropdown menu on the SaveManager component in your scene and create a password. XOR is very basic and can be hacked using brute force methods, but it is very fast. AES encryption is planned!
265 |
266 | # Additional Project Information
267 |
268 | ### Why did we build this?
269 | Figuring out how to save and load your game data can be tricky, but what's even more challenging is deciding _when_ to save your game data. Not only is there the issue of data serialization and file I/O, but in addition, save and load operations often end up happening synchronously on Unity's main thread which will cause framerate dips. That's because Unity's renderer is also on the main thread! Furthermore, while most desktops have fast SSDs, sometimes file I/O can take longer than the time it takes to render a frame, especially if you're running your game on a gaming console or a computer with an HDD.
270 |
271 | We hit these pain points on our game _[Let's! Revolution!](https://store.steampowered.com/app/2111090/Lets_Revolution/)_ and we wanted to come up with a better approach. By combining [`async`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/async) and [`await`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await) with Unity's [`Awaitable`](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/Awaitable.html) class (available in Unity 2023.1 and up), it is now possible to do file operations both asynchronously _and_ on background threads. That means you can save and load data in the background while your game continues to render frames seamlessly. Nice! However, there's still a good bit to learn about how multithreading works in the context of Unity and how to combine that with a JSON serializer and other features like encryption. The _Save Async_ package aims to take care of these complications and make asynchronous saving and loading data in Unity a breeze!
272 |
273 | ### Why not just use Coroutines?
274 | While Coroutines have served us well for many years, the Task-based asynchronous pattern (TAP) enabled by async/await and Unity's [`Awaitable`](https://docs.unity3d.com/2023.2/Documentation/ScriptReference/Awaitable) class has many advantages. Coroutines can execute piece by piece over time, but they still process on Unity's single main thread. If a Coroutine attempts a long-running operation (like accessing a file) it can cause the whole application to freeze for several frames. For a good overview of the differences between async/await and Coroutines, check out this Unite talk [Best practices: Async vs. coroutines - Unite Copenhagen](https://www.youtube.com/watch?v=7eKi6NKri6I&t=548s).
275 |
276 | # Contributing
277 |
278 | Found a bug or have a feature request? We'd love to hear from you!
279 |
280 | - [Open an issue](https://github.com/buck-co/save-async/issues) for problems or suggestions
281 | - [Create a pull request](https://github.com/buck-co/save-async/pulls) if you'd like to contribute code
282 | - Check our [contribution guidelines](CONTRIBUTING.md) before submitting
283 |
284 | # Authors
285 |
286 | * **Nick Pettit** - [nickpettit](https://github.com/nickpettit)
287 |
288 | See the full list of [contributors](https://github.com/buck-co/save-async/contributors).
289 |
290 | # Acknowledgments
291 |
292 | * Thanks to [Tarodev for this tutorial](https://www.youtube.com/watch?v=X9Dtb_4os1o) on using async and await in Unity using the Awaitable class. It gave me the idea for creating an async save system.
293 | * Thanks to [Dapper Dino for this tutorial](https://www.youtube.com/watch?v=f5GvfZfy3yk) which demonstrated how a form of the inversion of control design pattern could be used to make saving and loading easier.
294 | * Thanks to [Bronson Zgeb at Unity for this Unite talk](https://www.youtube.com/watch?v=uD7y4T4PVk0) which shows many of the pieces necessary for building a save system in Unity.
295 |
296 | # License
297 |
298 | MIT License - Copyright (c) 2025 BUCK Design LLC [buck-co](https://github.com/buck-co)
299 |
300 | ---
301 |
302 | _[BUCK](https://buck.co) is a global creative company that brings brands, stories, and experiences to life through art, design, and technology. If you're a Game Developer or Creative Technologist or want to get involved with our work, reach out and say hi via [Github](https://github.com/buck-co), [Instagram](https://www.instagram.com/buck_design/?hl=en) or our [Careers page](https://buck.co/careers). 👋_
303 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ad588e0dd412ae547819755d3d88aa7b
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b84342a4c4e28c7449fdc6163e5998a5
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/BUCK.SaveAsync.Runtime.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BUCK.SaveAsync.Runtime",
3 | "rootNamespace": "Buck.SaveAsync",
4 | "references": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [],
12 | "versionDefines": [],
13 | "noEngineReferences": false
14 | }
--------------------------------------------------------------------------------
/Runtime/BUCK.SaveAsync.Runtime.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ad4bea86cb6093347bcf3482d1635cc9
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Encryption.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using System;
4 |
5 | namespace Buck.SaveAsync
6 | {
7 | public enum EncryptionType
8 | {
9 | None,
10 | XOR
11 | }
12 |
13 | public class Encryption
14 | {
15 | public static string Encrypt(string content, string password, EncryptionType encryptionType)
16 | {
17 | switch (encryptionType)
18 | {
19 | case EncryptionType.None:
20 | return content;
21 | case EncryptionType.XOR:
22 | return EncryptDecryptXOR(content, password);
23 | default:
24 | throw new ArgumentOutOfRangeException(nameof(encryptionType), encryptionType, null);
25 | }
26 | }
27 |
28 | public static string Decrypt(string content, string password, EncryptionType encryptionType)
29 | {
30 | switch (encryptionType)
31 | {
32 | case EncryptionType.None:
33 | return content;
34 | case EncryptionType.XOR:
35 | return EncryptDecryptXOR(content, password);
36 | default:
37 | throw new ArgumentOutOfRangeException(nameof(encryptionType), encryptionType, null);
38 | }
39 | }
40 |
41 | static string EncryptDecryptXOR(string content, string password)
42 | {
43 | string newContent = "";
44 | for (int i = 0; i < content.Length; i++)
45 | newContent += (char)(content[i] ^ password[i % password.Length]);
46 | return newContent;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Runtime/Encryption.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c11360380f7d15c40a4944832d786a7e
--------------------------------------------------------------------------------
/Runtime/FileHandler.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using UnityEngine;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using System.IO;
7 |
8 | namespace Buck.SaveAsync
9 | {
10 | public class FileHandler : ScriptableObject
11 | {
12 | ///
13 | /// Stores the persistent data path for later use, which can only be accessed on the main thread.
14 | ///
15 | string m_persistentDataPath;
16 |
17 | protected virtual void OnEnable()
18 | => m_persistentDataPath = Application.persistentDataPath;
19 |
20 | ///
21 | /// Returns the full path to a file in the persistent data path using the given path or filename.
22 | ///
23 | /// File example: "MyFile.dat"
24 | /// Path example: "MyFolder/MyFile.dat"
25 | ///
26 | ///
27 | /// The path or filename of the file that will be combined with the persistent data path.
28 | protected virtual string GetPath(string pathOrFilename)
29 | => Path.Combine(m_persistentDataPath, pathOrFilename);
30 |
31 | ///
32 | /// Returns true if a file exists at the given path or filename.
33 | ///
34 | /// File example: "MyFile.dat"
35 | /// Path example: "MyFolder/MyFile.dat"
36 | ///
37 | ///
38 | /// The path or filename of the file to check.
39 | /// The cancellation token should be the same one from the calling MonoBehaviour.
40 | public virtual async Task Exists(string pathOrFilename, CancellationToken cancellationToken)
41 | => File.Exists(GetPath(pathOrFilename));
42 |
43 | ///
44 | /// Writes the given content to a file at the given path or filename.
45 | ///
46 | /// File example: "MyFile.dat"
47 | /// Path example: "MyFolder/MyFile.dat"
48 | ///
49 | ///
50 | /// The path or filename of the file to write.
51 | /// The string to write to the file.
52 | /// The cancellation token should be the same one from the calling MonoBehaviour.
53 | public virtual async Task WriteFile(string pathOrFilename, string content, CancellationToken cancellationToken)
54 | => await File.WriteAllTextAsync(GetPath(pathOrFilename), content, cancellationToken);
55 |
56 | ///
57 | /// Returns the contents of a file at the given path or filename.
58 | ///
59 | /// File example: "MyFile.dat"
60 | /// Path example: "MyFolder/MyFile.dat"
61 | ///
62 | ///
63 | /// The path or filename of the file to read.
64 | /// The cancellation token should be the same one from the calling MonoBehaviour.
65 | public virtual async Task ReadFile(string pathOrFilename, CancellationToken cancellationToken)
66 | {
67 | // If the file does not exist, return an empty string and log a warning.
68 | bool exists = await Exists(pathOrFilename, cancellationToken);
69 |
70 | if (!exists)
71 | {
72 | Debug.LogWarning($"FileHandler: File does not exist at path or filename: {pathOrFilename}" +
73 | $"\nReturning empty string and no data will be loaded.");
74 | return string.Empty;
75 | }
76 |
77 | string fileContent = await File.ReadAllTextAsync(GetPath(pathOrFilename), cancellationToken);
78 |
79 | // If the file is empty, return an empty string and log a warning.
80 | if (string.IsNullOrEmpty(fileContent))
81 | {
82 | Debug.LogWarning($"FileHandler: Attempted to load {pathOrFilename} but the file was empty.");
83 | return string.Empty;
84 | }
85 |
86 | return fileContent;
87 | }
88 |
89 | ///
90 | /// Erases a file at the given path or filename. The file will still exist on disk, but it will be empty.
91 | /// Use to remove the file from disk.
92 | ///
93 | /// File example: "MyFile.dat"
94 | /// Path example: "MyFolder/MyFile.dat"
95 | ///
96 | ///
97 | /// The path or filename of the file to erase.
98 | /// The cancellation token should be the same one from the calling MonoBehaviour.
99 | public virtual async Task Erase(string pathOrFilename, CancellationToken cancellationToken)
100 | => await WriteFile(pathOrFilename, string.Empty, cancellationToken);
101 |
102 | ///
103 | /// Deletes a file at the given path or filename. This will remove the file from disk.
104 | /// Use to fill the file with an empty string without removing it from disk.
105 | ///
106 | /// File example: "MyFile.dat"
107 | /// Path example: "MyFolder/MyFile.dat"
108 | ///
109 | ///
110 | /// The path or filename of the file to delete.
111 | public virtual void Delete(string pathOrFilename)
112 | => File.Delete(GetPath(pathOrFilename));
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Runtime/FileHandler.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3c877474a7940e54a98115088fb0d808
--------------------------------------------------------------------------------
/Runtime/ISaveable.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | namespace Buck.SaveAsync
4 | {
5 | ///
6 | /// Allows an object to be saved and loaded via the SaveManager class.
7 | ///
8 | public interface ISaveable
9 | {
10 | ///
11 | /// This is a unique string used to identify the object when saving and loading.
12 | /// If you choose to use a Guid, it is recommended that it is backed by a
13 | /// serialized byte array that does not change.
14 | ///
15 | public string Key { get; }
16 |
17 | ///
18 | /// This is the file name where this object's data will be saved.
19 | /// It is recommended to use a static class to store file paths as strings to avoid typos.
20 | ///
21 | public string Filename { get; }
22 |
23 | ///
24 | /// This is used by the SaveManager class to capture the state of a saveable object.
25 | /// Typically this is a struct defined by the ISaveable implementing class.
26 | /// The contents of the struct could be created at the time of saving, or cached in a variable.
27 | ///
28 | object CaptureState();
29 |
30 | ///
31 | /// This is used by the SaveManager class to restore the state of a saveable object.
32 | /// This will be called any time the game is loaded, so you may want to consider
33 | /// also using this method to initialize any fields that are not saved (i.e. "resetting the object").
34 | ///
35 | void RestoreState(object state);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Runtime/ISaveable.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 45d6f3ec2fe1dcd4a9f11a4bd0a3f7a3
--------------------------------------------------------------------------------
/Runtime/SaveManager.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using UnityEngine;
8 | using Newtonsoft.Json;
9 |
10 | namespace Buck.SaveAsync
11 | {
12 | [AddComponentMenu("SaveAsync/SaveManager")]
13 | public class SaveManager : Singleton
14 | {
15 | [SerializeField, Tooltip("Generally you should keep this enabled and only disable it if you believe " +
16 | "that it's causing unexpected behavior on a target platform.")]
17 | bool m_useBackgroundThread = true;
18 |
19 | [SerializeField, Tooltip("Enables encryption for save data. " +
20 | "XOR encryption is basic but extremely fast. Support for AES encryption is planned." +
21 | "Do not change the encryption type once the game has shipped!")]
22 | EncryptionType m_encryptionType = EncryptionType.None;
23 |
24 | [SerializeField, Tooltip("The password used to encrypt and decrypt save data. This password should be unique to your game. " +
25 | "Do not change the encryption password once the game has shipped!")]
26 | string m_encryptionPassword = "password";
27 |
28 | [SerializeField, Tooltip("This field can be left blank. SaveAsync allows the FileHandler class to be overridden." +
29 | "This can be useful in scenarios where files should not be saved using local file IO" +
30 | "(such as cloud saves) or when a platform-specific save API must be used. " +
31 | "If you want to use a custom file handler, create a new class that inherits from FileHandler and assign it here.")]
32 | FileHandler m_customFileHandler;
33 |
34 | enum FileOperationType
35 | {
36 | Save,
37 | Load,
38 | Delete,
39 | Erase
40 | }
41 |
42 | struct FileOperation
43 | {
44 | public FileOperationType Type;
45 | public string[] Filenames;
46 |
47 | public FileOperation(FileOperationType operationType, string[] filenames)
48 | {
49 | Type = operationType;
50 | Filenames = filenames;
51 | }
52 | }
53 |
54 | static FileHandler m_fileHandler;
55 | static Dictionary m_saveables = new();
56 | static List m_loadedSaveables = new();
57 | static Queue m_fileOperationQueue = new();
58 | static HashSet m_files = new();
59 |
60 | static bool m_isInitialized;
61 |
62 | static readonly JsonSerializerSettings m_jsonSerializerSettings = new()
63 | {
64 | Formatting = Formatting.Indented,
65 | ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
66 | TypeNameHandling = TypeNameHandling.Auto
67 | };
68 |
69 | [Serializable]
70 | public class SaveableObject
71 | {
72 | public string Key;
73 | public object Data;
74 | }
75 |
76 | void Awake()
77 | => Initialize();
78 |
79 | static void Initialize()
80 | {
81 | if (m_isInitialized)
82 | return;
83 |
84 | // If there is a user-defined FileHandler, use it. Otherwise, create a new FileHandler.
85 | m_fileHandler = Instance.m_customFileHandler == null
86 | ? ScriptableObject.CreateInstance()
87 | : Instance.m_customFileHandler;
88 |
89 | m_isInitialized = true;
90 | }
91 |
92 | #region SaveAsync API
93 |
94 | ///
95 | /// Boolean indicating whether or not a file operation is in progress.
96 | ///
97 | public static bool IsBusy { get; private set; }
98 |
99 | ///
100 | /// Registers an ISaveable and its file for saving and loading.
101 | ///
102 | /// The ISaveable to register for saving and loading.
103 | public static void RegisterSaveable(ISaveable saveable)
104 | {
105 | if (m_saveables.TryAdd(saveable.Key, saveable))
106 | m_files.Add(saveable.Filename);
107 | else
108 | Debug.LogError($"Saveable with Key {saveable.Key} already exists!");
109 | }
110 |
111 | ///
112 | /// Checks if a file exists at the given path or filename.
113 | ///
114 | /// File example: "MyFile.dat"
115 | /// Path example: "MyFolder/MyFile.dat"
116 | ///
117 | ///
118 | /// The path or filename to check for existence.
119 | public static async Task Exists(string filename)
120 | => await m_fileHandler.Exists(filename, Instance.destroyCancellationToken);
121 |
122 | ///
123 | /// Saves the files at the given paths or filenames.
124 | ///
125 | /// File example: "MyFile.dat"
126 | /// Path example: "MyFolder/MyFile.dat"
127 | ///
128 | ///
129 | /// The array of paths or filenames to save.
130 | public static async Awaitable Save(string[] filenames)
131 | => await DoFileOperation(FileOperationType.Save, filenames);
132 |
133 | ///
134 | /// Saves the file at the given path or filename.
135 | ///
136 | /// File example: "MyFile.dat"
137 | /// Path example: "MyFolder/MyFile.dat"
138 | ///
139 | ///
140 | /// The path or filename to save.
141 | public static async Awaitable Save(string filename)
142 | => await Save(new[] {filename});
143 |
144 | ///
145 | /// Loads the files at the given paths or filenames.
146 | ///
147 | /// File example: "MyFile.dat"
148 | /// Path example: "MyFolder/MyFile.dat"
149 | ///
150 | ///
151 | /// The array of paths or filenames to load.
152 | public static async Awaitable Load(string[] filenames)
153 | => await DoFileOperation(FileOperationType.Load, filenames);
154 |
155 | ///
156 | /// Loads the file at the given path or filename.
157 | ///
158 | /// File example: "MyFile.dat"
159 | /// Path example: "MyFolder/MyFile.dat"
160 | ///
161 | ///
162 | /// The path or filename to load.
163 | public static async Awaitable Load(string filename)
164 | => await Load(new[] {filename});
165 |
166 | ///
167 | /// Deletes the files at the given paths or filenames. Each file will be removed from disk.
168 | /// Use to fill each file with an empty string without removing it from disk.
169 | ///
170 | /// File example: "MyFile.dat"
171 | /// Path example: "MyFolder/MyFile.dat"
172 | ///
173 | ///
174 | /// The array of paths or filenames to delete.
175 | public static async Awaitable Delete(string[] filenames)
176 | => await DoFileOperation(FileOperationType.Delete, filenames);
177 |
178 | ///
179 | /// Deletes the file at the given path or filename. The file will be removed from disk.
180 | /// Use to fill the file with an empty string without removing it from disk.
181 | ///
182 | /// File example: "MyFile.dat"
183 | /// Path example: "MyFolder/MyFile.dat"
184 | ///
185 | ///
186 | /// The path or filename to delete.
187 | public static async Awaitable Delete(string filename)
188 | => await Delete(new[] {filename});
189 |
190 | ///
191 | /// Erases the files at the given paths or filenames. Each file will still exist on disk, but it will be empty.
192 | /// Use to remove the files from disk.
193 | ///
194 | /// File example: "MyFile.dat"
195 | /// Path example: "MyFolder/MyFile.dat"
196 | ///
197 | ///
198 | /// The array of paths or filenames to erase.
199 | public static async Awaitable Erase(string[] filenames)
200 | => await DoFileOperation(FileOperationType.Erase, filenames);
201 |
202 | ///
203 | /// Erases the file at the given path or filename. The file will still exist on disk, but it will be empty.
204 | /// Use to remove the file from disk.
205 | ///
206 | /// File example: "MyFile.dat"
207 | /// Path example: "MyFolder/MyFile.dat"
208 | ///
209 | ///
210 | /// The path or filename to erase.
211 | public static async Awaitable Erase(string filename)
212 | => await Erase(new[] {filename});
213 |
214 | ///
215 | /// Sets the given Guid byte array to a new Guid byte array if it is null, empty, or an empty Guid.
216 | /// This method can be useful for creating unique keys for ISaveables.
217 | ///
218 | /// The byte array (passed by reference) that you would like to fill with a serializable guid.
219 | /// The same byte array that contains the serializable guid, but returned from the method.
220 | public static byte[] GetSerializableGuid(ref byte[] guidBytes)
221 | {
222 | // If the byte array is null, return a new Guid byte array.
223 | if (guidBytes == null)
224 | {
225 | Debug.LogWarning("Guid byte array is null. Generating a new Guid.");
226 | guidBytes = Guid.NewGuid().ToByteArray();
227 | }
228 |
229 | // If the byte array is empty, return a new Guid byte array.
230 | if (guidBytes.Length == 0)
231 | {
232 | Debug.LogWarning("Guid byte array is empty. Generating a new Guid.");
233 | guidBytes = Guid.NewGuid().ToByteArray();
234 | }
235 |
236 | // If the byte array is not empty, but is not 16 bytes long, throw an exception.
237 | if (guidBytes.Length != 16)
238 | throw new ArgumentException("Guid byte array must be 16 bytes long.");
239 |
240 | // If the byte array is not an empty Guid, return a new Guid byte array.
241 | // Otherwise, return the given Guid byte array.
242 | Guid guidObj = new Guid(guidBytes);
243 |
244 | if (guidObj == Guid.Empty)
245 | {
246 | Debug.LogWarning("Guid is empty. Generating a new Guid.");
247 | guidBytes = Guid.NewGuid().ToByteArray();
248 | }
249 |
250 | return guidBytes;
251 | }
252 |
253 | #endregion
254 |
255 | static async Awaitable DoFileOperation(FileOperationType operationType, string[] filenames)
256 | {
257 | Initialize();
258 |
259 | // If the cancellation token has been requested at any point, return
260 | while (!Instance.destroyCancellationToken.IsCancellationRequested)
261 | {
262 | if (m_saveables.Count == 0)
263 | {
264 | Debug.LogError("No saveables have been registered! You must call RegisterSaveable on your" +
265 | " ISaveable classes before using save, load, erase, or delete methods.", Instance.gameObject);
266 | return;
267 | }
268 |
269 | // Create the file operation struct and queue it
270 | m_fileOperationQueue.Enqueue(new FileOperation(operationType, filenames));
271 |
272 | // If we are already doing file I/O, return
273 | if (IsBusy)
274 | return;
275 |
276 | // Prevent duplicate file operations from processing the queue
277 | IsBusy = true;
278 |
279 | // Switch to a background thread to process the queue
280 | if (Instance.m_useBackgroundThread)
281 | await Awaitable.BackgroundThreadAsync();
282 |
283 | while (m_fileOperationQueue.Count > 0)
284 | {
285 | m_fileOperationQueue.TryDequeue(out FileOperation fileOperation);
286 | switch (fileOperation.Type)
287 | {
288 | case FileOperationType.Save:
289 | await SaveFileOperationAsync(fileOperation.Filenames);
290 | break;
291 | case FileOperationType.Load:
292 | await LoadFileOperationAsync(fileOperation.Filenames);
293 | break;
294 | case FileOperationType.Delete:
295 | await DeleteFileOperationAsync(fileOperation.Filenames);
296 | break;
297 | case FileOperationType.Erase:
298 | await DeleteFileOperationAsync(fileOperation.Filenames, true);
299 | break;
300 | default:
301 | throw new ArgumentOutOfRangeException();
302 | }
303 | }
304 |
305 | // Switch back to the main thread before accessing Unity objects and setting IsBusy to false
306 | if (Instance.m_useBackgroundThread)
307 | await Awaitable.MainThreadAsync();
308 |
309 | // If anything was populated in the loadedDataList, restore state
310 | // This is done here because it's better to process the whole queue before switching back to the main thread.
311 | if (m_loadedSaveables.Count > 0)
312 | {
313 | // Restore state for each ISaveable
314 | foreach (SaveableObject wrappedData in m_loadedSaveables)
315 | {
316 | if (wrappedData.Key == null)
317 | {
318 | Debug.LogError("The key for an ISaveable is null. JSON data may be malformed. " +
319 | "The data will not be restored. ", Instance.gameObject);
320 | continue;
321 | }
322 |
323 | // Try to get the ISaveable from the dictionary
324 | if (m_saveables.ContainsKey(wrappedData.Key) == false)
325 | {
326 | Debug.LogError("The ISaveable with the key " + wrappedData.Key + " was not found in the saveables dictionary. " +
327 | "The data will not be restored. This could mean that the string Key for the matching object has " +
328 | "changed since the save data was created.", Instance.gameObject);
329 | continue;
330 | }
331 |
332 | // Get the ISaveable from the dictionary
333 | var saveable = m_saveables[wrappedData.Key];
334 |
335 | // If the ISaveable is null, log an error and continue to the next iteration
336 | if (saveable == null)
337 | {
338 | Debug.LogError("The ISaveable with the key " + wrappedData.Key + " is null. "
339 | + "The data will not be restored.", Instance.gameObject);
340 | continue;
341 | }
342 |
343 | // Restore the state of the ISaveable
344 | saveable.RestoreState(wrappedData.Data);
345 | }
346 | }
347 |
348 | // Clear the list before the next iteration
349 | m_loadedSaveables.Clear();
350 |
351 | IsBusy = false;
352 |
353 | // Return, otherwise we will loop forever
354 | return;
355 | }
356 | }
357 |
358 | static async Awaitable SaveFileOperationAsync(string[] filenames)
359 | {
360 | // Get the ISaveables that correspond to the files, convert them to JSON, and save them
361 | foreach (string filename in filenames)
362 | {
363 | List saveablesToSave = new();
364 |
365 | // Gather all of the saveables that correspond to the file
366 | foreach (ISaveable saveable in m_saveables.Values)
367 | if (saveable.Filename == filename)
368 | saveablesToSave.Add(saveable);
369 |
370 | string json = SaveablesToJson(saveablesToSave);
371 | json = Encryption.Encrypt(json, Instance.m_encryptionPassword, Instance.m_encryptionType);
372 | await m_fileHandler.WriteFile(filename, json, Instance.destroyCancellationToken);
373 | }
374 | }
375 |
376 | static async Awaitable LoadFileOperationAsync(string[] filenames)
377 | {
378 | // Load the files
379 | foreach (string filename in filenames)
380 | {
381 | string fileContent = await m_fileHandler.ReadFile(filename, Instance.destroyCancellationToken);
382 |
383 | // If the file is empty, skip it
384 | if (string.IsNullOrEmpty(fileContent))
385 | continue;
386 |
387 | string json = Encryption.Decrypt(fileContent, Instance.m_encryptionPassword, Instance.m_encryptionType);
388 |
389 | // Deserialize the JSON data to List of SaveableDataWrapper
390 | List jsonObjects = null;
391 |
392 | try
393 | {
394 | jsonObjects = JsonConvert.DeserializeObject>(json, m_jsonSerializerSettings);
395 | }
396 | catch (Exception e)
397 | {
398 | Debug.LogError("Error deserializing JSON data. JSON data may be malformed. Exception message: " + e.Message, Instance.gameObject);
399 | continue;
400 | }
401 |
402 | if (jsonObjects != null)
403 | m_loadedSaveables.AddRange(jsonObjects);
404 | }
405 | }
406 |
407 | static async Awaitable DeleteFileOperationAsync(string[] filenames, bool eraseAndKeepFile = false)
408 | {
409 | // Delete the files from disk
410 | foreach (string filename in filenames)
411 | if (eraseAndKeepFile)
412 | await m_fileHandler.Erase(filename, Instance.destroyCancellationToken);
413 | else
414 | m_fileHandler.Delete(filename);
415 | }
416 |
417 | static string SaveablesToJson(List saveables)
418 | {
419 | if (saveables == null)
420 | throw new ArgumentNullException(nameof(saveables));
421 |
422 | SaveableObject[] wrappedSaveables = new SaveableObject[saveables.Count];
423 |
424 | for (var i = 0; i < saveables.Count; i++)
425 | {
426 | var s = saveables[i];
427 | var data = s.CaptureState();
428 |
429 | wrappedSaveables[i] = new SaveableObject
430 | {
431 | Key = s.Key.ToString(),
432 | Data = data
433 | };
434 | }
435 |
436 | return JsonConvert.SerializeObject(wrappedSaveables, m_jsonSerializerSettings);
437 | }
438 | }
439 | }
440 |
--------------------------------------------------------------------------------
/Runtime/SaveManager.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f3c390e20d837bf409b6716c3b995a22
--------------------------------------------------------------------------------
/Runtime/Singleton.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using UnityEngine;
4 |
5 | namespace Buck.SaveAsync
6 | {
7 | ///
8 | /// Inherit from this base class to create a singleton.
9 | /// e.g. public class MyClassName : Singleton {}
10 | ///
11 | public class Singleton : MonoBehaviour where T : MonoBehaviour
12 | {
13 | // Check to see if we're about to be destroyed.
14 | static bool m_ShuttingDown = false;
15 | static object m_Lock = new object();
16 | static T m_Instance;
17 |
18 | ///
19 | /// Access singleton instance through this propriety.
20 | ///
21 | public static T Instance
22 | {
23 | get
24 | {
25 | if (m_ShuttingDown)
26 | {
27 | Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
28 | "' already destroyed. Returning null.");
29 | return null;
30 | }
31 |
32 | lock (m_Lock)
33 | {
34 | if (m_Instance == null)
35 | {
36 | // Search for existing instance.
37 | m_Instance = (T)FindAnyObjectByType(typeof(T));
38 |
39 | // Create new instance if one doesn't already exist.
40 | if (m_Instance == null)
41 | {
42 | // Need to create a new GameObject to attach the singleton to.
43 | var singletonObject = new GameObject();
44 | m_Instance = singletonObject.AddComponent();
45 | singletonObject.name = typeof(T).ToString() + " (Singleton)";
46 |
47 | // Make instance persistent.
48 | DontDestroyOnLoad(singletonObject);
49 | }
50 | }
51 |
52 | return m_Instance;
53 | }
54 | }
55 | }
56 |
57 | void OnApplicationQuit()
58 | => m_ShuttingDown = true;
59 |
60 | void OnDestroy()
61 | => m_ShuttingDown = true;
62 | }
63 | }
--------------------------------------------------------------------------------
/Runtime/Singleton.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 226594b9c92a5f14e84f0f3a1f63a649
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Benchmarks.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | using System;
4 | using UnityEngine;
5 | using Buck.SaveAsync;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.Text;
9 | using UnityEngine.UI;
10 |
11 | namespace Buck.SaveAsyncExample
12 | {
13 | public enum BenchmarkType
14 | {
15 | SaveGameData,
16 | LoadGameData,
17 | EraseGameData,
18 | DeleteGameData
19 | }
20 |
21 | public class Benchmarks : MonoBehaviour
22 | {
23 | [SerializeField] Text m_debugText;
24 | List m_debugOutput = new ();
25 | StringBuilder m_stringBuilder = new ();
26 |
27 | void AddDebugOutput(string output)
28 | {
29 | // If the list is too long, remove the oldest entry
30 | if (m_debugOutput.Count > 30)
31 | m_debugOutput.RemoveAt(0);
32 |
33 | // Append the current time to the output
34 | output = "> " + DateTime.Now.ToString("HH:mm:ss.fff") + ": " + output;
35 |
36 | m_debugOutput.Add(output);
37 | m_stringBuilder.Clear();
38 | foreach (var t in m_debugOutput)
39 | m_stringBuilder.AppendLine(t);
40 |
41 | m_debugText.text = m_stringBuilder.ToString();
42 | }
43 |
44 | async Awaitable RunBenchmark(BenchmarkType benchmarkType, string[] filenames)
45 | {
46 | while (!destroyCancellationToken.IsCancellationRequested)
47 | {
48 | // Create a new GUID to track which save test is which
49 | Guid guid = Guid.NewGuid();
50 | string shortGuid = guid.ToString().Substring(0, 4);
51 |
52 | Stopwatch stopwatch = new();
53 | stopwatch.Start();
54 | AddDebugOutput("Starting " + benchmarkType + "() " + shortGuid + " ...");
55 |
56 | try
57 | {
58 | switch (benchmarkType)
59 | {
60 | case BenchmarkType.SaveGameData:
61 | await SaveManager.Save(filenames);
62 | break;
63 | case BenchmarkType.LoadGameData:
64 | await SaveManager.Load(filenames);
65 | break;
66 | case BenchmarkType.EraseGameData:
67 | await SaveManager.Erase(filenames);
68 | break;
69 | case BenchmarkType.DeleteGameData:
70 | await SaveManager.Delete(filenames);
71 | break;
72 | }
73 |
74 | // Switch back to the main thread while waiting
75 | Awaitable.MainThreadAsync();
76 |
77 | while (SaveManager.IsBusy)
78 | await Awaitable.NextFrameAsync();
79 |
80 | AddDebugOutput(benchmarkType + "() " + shortGuid + " completed in " + stopwatch.ElapsedMilliseconds +
81 | "ms");
82 | }
83 | catch (Exception e)
84 | {
85 | Awaitable.MainThreadAsync();
86 |
87 | AddDebugOutput("" + benchmarkType + "() " + shortGuid + " failed: " + e.Message +
88 | "");
89 | }
90 |
91 | return;
92 | }
93 | }
94 |
95 | public async Awaitable SaveGameData(string[] filenames)
96 | => await RunBenchmark(BenchmarkType.SaveGameData, filenames);
97 |
98 | public async Awaitable LoadGameData(string[] filenames)
99 | => await RunBenchmark(BenchmarkType.LoadGameData, filenames);
100 |
101 | public async Awaitable EraseGameData(string[] filenames)
102 | => await RunBenchmark(BenchmarkType.EraseGameData, filenames);
103 |
104 | public async Awaitable DeleteGameData(string[] filenames)
105 | => await RunBenchmark(BenchmarkType.DeleteGameData, filenames);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Benchmarks.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1acd81244f60d6d4da7c7aee8f73e010
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/CacheDataExample.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | /*
4 | * This example implementation of the ISaveable interface shows how data can be cached manually.
5 | * The CaptureState method returns the cached data. Contrast this with the GameDataExample class,
6 | * which shows how to create a snapshot of current values.
7 | *
8 | * In Unity, use the "Cache Data State" context menu item to update the cached data.
9 | */
10 |
11 | using System;
12 | using Buck.SaveAsync;
13 | using UnityEngine;
14 |
15 | namespace Buck.SaveAsyncExample
16 | {
17 | public class CacheDataExample : MonoBehaviour, ISaveable
18 | {
19 | // ISaveable needs a unique string "Key" which is used to identify the object in the save data.
20 | // This is can optionally be a serialized byte array that does not change.
21 | // Use OnValidate to ensure that your ISaveable's Guid has a value when the MonoBehaviour is created.
22 | // For an example that uses a string, see GameDataExample.cs.
23 | [SerializeField, HideInInspector] byte[] m_guidBytes;
24 | public string Key => new Guid(m_guidBytes).ToString();
25 | void OnValidate() => SaveManager.GetSerializableGuid(ref m_guidBytes);
26 | public string Filename => Files.SomeFile;
27 |
28 | // Your game data should go in a serializable struct
29 | [Serializable]
30 | public struct MySaveData
31 | {
32 | public string myString;
33 | public int myInt;
34 | public float myFloat;
35 | }
36 |
37 | [SerializeField] string m_myString = "Some string!";
38 | [SerializeField] int m_myInt = 5;
39 | [SerializeField] float m_myFloat = 2f;
40 |
41 |
42 | MySaveData m_CachcedSaveDataState;
43 |
44 | void Awake()
45 | {
46 | // Register this ISaveable
47 | SaveManager.RegisterSaveable(this);
48 | }
49 |
50 | // Use the "Cache Data State" context menu item to update the cached data.
51 | // This is to simulate a situation where the data is cached at a specific time
52 | // that is not necessarily when the game is saved.
53 | // (e.g. when the player reaches a checkpoint or makes changes to their inventory)
54 | [ContextMenu("Cache Data State")]
55 | public void CacheDataState()
56 | {
57 | m_CachcedSaveDataState = new MySaveData
58 | {
59 | myString = m_myString,
60 | myInt = m_myInt,
61 | myFloat = m_myFloat
62 | };
63 | }
64 |
65 | // Every ISaveable must implement the CaptureState and RestoreState method
66 | // CaptureState is called when the game is saved with SaveManager.Save()
67 | // RestoreState is called when the game is loaded with SaveManager.Load()
68 | public object CaptureState() => m_CachcedSaveDataState;
69 |
70 | public void RestoreState(object state)
71 | {
72 | var s = (MySaveData)state;
73 |
74 | m_myString = s.myString;
75 | m_myInt = s.myInt;
76 | m_myFloat = s.myFloat;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/CacheDataExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f66940caddea28c4db60761f8c77d861
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Files.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | namespace Buck.SaveAsyncExample
4 | {
5 | public static class Files
6 | {
7 | public const string GameData = "GameData.dat";
8 | public const string SomeFile = "SomeFile.dat";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Files.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 93d7789fda94e76489e8e145d76b03d2
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Game State Async Sample.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4bb607f4f9046a141bf03d17b210024d
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/GameDataExample.cs:
--------------------------------------------------------------------------------
1 | // MIT License - Copyright (c) 2024 BUCK Design LLC - https://github.com/buck-co
2 |
3 | /*
4 | * This example implementation of the CaptureState method shows how the CaptureState method
5 | * can be used to create an instant snapshot of the current values of the object's fields.
6 | * Contrast this with the CacheDataExample class, which shows how data can be cached manually.
7 | */
8 |
9 | using System;
10 | using System.Collections.Generic;
11 | using Buck.SaveAsync;
12 | using UnityEngine;
13 |
14 | namespace Buck.SaveAsyncExample
15 | {
16 | public class GameDataExample : MonoBehaviour, ISaveable
17 | {
18 | // ISaveable needs a unique string "Key" which is used to identify the object in the save data.
19 | // For an example that uses a Guid, see CacheDataExample.cs.
20 | public string Key => "GameDataExample";
21 | public string Filename => Files.GameData;
22 |
23 | // Your game data should go in a serializable struct
24 | [Serializable]
25 | public struct MyCustomData
26 | {
27 | public string playerName;
28 | public int playerHealth;
29 | public Vector3 position;
30 | public Dictionary inventory;
31 | }
32 |
33 | [SerializeField] string m_playerName = "The Player Name";
34 | [SerializeField] int m_playerHealth = 100;
35 | [SerializeField] Vector3 m_position = new Vector3(1f, 2f, 3f);
36 |
37 | public struct Item
38 | {
39 | public string m_name;
40 | public int m_quantity;
41 | }
42 |
43 | Dictionary m_inventory = new()
44 | {
45 | {1, new Item() { m_name = "ItemOne", m_quantity = 5 }},
46 | {2, new Item() { m_name = "ItemTwo", m_quantity = 6 }},
47 | {3, new Item() { m_name = "ItemThree", m_quantity = 7 }}
48 | };
49 |
50 | void Awake()
51 | {
52 | // Register this ISaveable
53 | SaveManager.RegisterSaveable(this);
54 | }
55 |
56 | // Every ISaveable must implement the CaptureState and RestoreState method
57 | // CaptureState is called when the game is saved with SaveManager.Save()
58 | // RestoreState is called when the game is loaded with SaveManager.Load()
59 |
60 | public object CaptureState()
61 | {
62 | return new MyCustomData
63 | {
64 | playerName = m_playerName,
65 | playerHealth = m_playerHealth,
66 | position = m_position,
67 | inventory = m_inventory
68 | };
69 | }
70 |
71 | public void RestoreState(object state)
72 | {
73 | var s = (MyCustomData)state;
74 |
75 | m_playerName = s.playerName;
76 | m_playerHealth = s.playerHealth;
77 | m_position = s.position;
78 | m_inventory = s.inventory;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/GameDataExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f2b492ad60c16564880e711051b0caf4
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/GameManagerExample.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using Buck.SaveAsync;
3 |
4 | namespace Buck.SaveAsyncExample
5 | {
6 | public class GameManagerExample : MonoBehaviour
7 | {
8 | [SerializeField] bool m_runBenchmarks = true;
9 |
10 | string[] m_filenames =
11 | {
12 | Files.GameData,
13 | Files.SomeFile
14 | };
15 |
16 | Benchmarks m_benchmarks;
17 |
18 | void Awake()
19 | {
20 | m_benchmarks = GetComponent();
21 | }
22 |
23 | public async void SaveGameData()
24 | {
25 | if (m_runBenchmarks)
26 | await m_benchmarks.SaveGameData(m_filenames);
27 | else
28 | await SaveManager.Save(m_filenames);
29 | }
30 |
31 | public async void LoadGameData()
32 | {
33 | if (m_runBenchmarks)
34 | await m_benchmarks.LoadGameData(m_filenames);
35 | else
36 | await SaveManager.Load(m_filenames);
37 | }
38 |
39 | public async void EraseGameData()
40 | {
41 | if (m_runBenchmarks)
42 | await m_benchmarks.EraseGameData(m_filenames);
43 | else
44 | await SaveManager.Erase(m_filenames);
45 | }
46 |
47 | public async void DeleteGameData()
48 | {
49 | if (m_runBenchmarks)
50 | await m_benchmarks.DeleteGameData(m_filenames);
51 | else
52 | await SaveManager.Delete(m_filenames);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/GameManagerExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8fc2051fe91f7384ea2c6164a45be913
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/GameStateBenchmarks.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e04f08679f12d2a43be4aa35ab5d7768
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Save Async Sample.unity:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!29 &1
4 | OcclusionCullingSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_OcclusionBakeSettings:
8 | smallestOccluder: 5
9 | smallestHole: 0.25
10 | backfaceThreshold: 100
11 | m_SceneGUID: 00000000000000000000000000000000
12 | m_OcclusionCullingData: {fileID: 0}
13 | --- !u!104 &2
14 | RenderSettings:
15 | m_ObjectHideFlags: 0
16 | serializedVersion: 10
17 | m_Fog: 0
18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
19 | m_FogMode: 3
20 | m_FogDensity: 0.01
21 | m_LinearFogStart: 0
22 | m_LinearFogEnd: 300
23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
26 | m_AmbientIntensity: 1
27 | m_AmbientMode: 0
28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
30 | m_HaloStrength: 0.5
31 | m_FlareStrength: 1
32 | m_FlareFadeSpeed: 3
33 | m_HaloTexture: {fileID: 0}
34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
35 | m_DefaultReflectionMode: 0
36 | m_DefaultReflectionResolution: 128
37 | m_ReflectionBounces: 1
38 | m_ReflectionIntensity: 1
39 | m_CustomReflection: {fileID: 0}
40 | m_Sun: {fileID: 0}
41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
42 | m_UseRadianceAmbientProbe: 0
43 | --- !u!157 &3
44 | LightmapSettings:
45 | m_ObjectHideFlags: 0
46 | serializedVersion: 12
47 | m_GISettings:
48 | serializedVersion: 2
49 | m_BounceScale: 1
50 | m_IndirectOutputScale: 1
51 | m_AlbedoBoost: 1
52 | m_EnvironmentLightingMode: 0
53 | m_EnableBakedLightmaps: 1
54 | m_EnableRealtimeLightmaps: 0
55 | m_LightmapEditorSettings:
56 | serializedVersion: 12
57 | m_Resolution: 2
58 | m_BakeResolution: 40
59 | m_AtlasSize: 1024
60 | m_AO: 0
61 | m_AOMaxDistance: 1
62 | m_CompAOExponent: 1
63 | m_CompAOExponentDirect: 0
64 | m_ExtractAmbientOcclusion: 0
65 | m_Padding: 2
66 | m_LightmapParameters: {fileID: 0}
67 | m_LightmapsBakeMode: 1
68 | m_TextureCompression: 1
69 | m_ReflectionCompression: 2
70 | m_MixedBakeMode: 2
71 | m_BakeBackend: 1
72 | m_PVRSampling: 1
73 | m_PVRDirectSampleCount: 32
74 | m_PVRSampleCount: 512
75 | m_PVRBounces: 2
76 | m_PVREnvironmentSampleCount: 256
77 | m_PVREnvironmentReferencePointCount: 2048
78 | m_PVRFilteringMode: 1
79 | m_PVRDenoiserTypeDirect: 1
80 | m_PVRDenoiserTypeIndirect: 1
81 | m_PVRDenoiserTypeAO: 1
82 | m_PVRFilterTypeDirect: 0
83 | m_PVRFilterTypeIndirect: 0
84 | m_PVRFilterTypeAO: 0
85 | m_PVREnvironmentMIS: 1
86 | m_PVRCulling: 1
87 | m_PVRFilteringGaussRadiusDirect: 1
88 | m_PVRFilteringGaussRadiusIndirect: 5
89 | m_PVRFilteringGaussRadiusAO: 2
90 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5
91 | m_PVRFilteringAtrousPositionSigmaIndirect: 2
92 | m_PVRFilteringAtrousPositionSigmaAO: 1
93 | m_ExportTrainingData: 0
94 | m_TrainingDataDestination: TrainingData
95 | m_LightProbeSampleCountMultiplier: 4
96 | m_LightingDataAsset: {fileID: 0}
97 | m_LightingSettings: {fileID: 0}
98 | --- !u!196 &4
99 | NavMeshSettings:
100 | serializedVersion: 2
101 | m_ObjectHideFlags: 0
102 | m_BuildSettings:
103 | serializedVersion: 3
104 | agentTypeID: 0
105 | agentRadius: 0.5
106 | agentHeight: 2
107 | agentSlope: 45
108 | agentClimb: 0.4
109 | ledgeDropHeight: 0
110 | maxJumpAcrossDistance: 0
111 | minRegionArea: 2
112 | manualCellSize: 0
113 | cellSize: 0.16666667
114 | manualTileSize: 0
115 | tileSize: 256
116 | buildHeightMesh: 0
117 | maxJobWorkers: 0
118 | preserveTilesOutsideBounds: 0
119 | debug:
120 | m_Flags: 0
121 | m_NavMeshData: {fileID: 0}
122 | --- !u!1 &132441561
123 | GameObject:
124 | m_ObjectHideFlags: 0
125 | m_CorrespondingSourceObject: {fileID: 0}
126 | m_PrefabInstance: {fileID: 0}
127 | m_PrefabAsset: {fileID: 0}
128 | serializedVersion: 6
129 | m_Component:
130 | - component: {fileID: 132441562}
131 | - component: {fileID: 132441565}
132 | - component: {fileID: 132441564}
133 | - component: {fileID: 132441563}
134 | m_Layer: 5
135 | m_Name: Load Game Data
136 | m_TagString: Untagged
137 | m_Icon: {fileID: 0}
138 | m_NavMeshLayer: 0
139 | m_StaticEditorFlags: 0
140 | m_IsActive: 1
141 | --- !u!224 &132441562
142 | RectTransform:
143 | m_ObjectHideFlags: 0
144 | m_CorrespondingSourceObject: {fileID: 0}
145 | m_PrefabInstance: {fileID: 0}
146 | m_PrefabAsset: {fileID: 0}
147 | m_GameObject: {fileID: 132441561}
148 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
149 | m_LocalPosition: {x: 0, y: 0, z: 0}
150 | m_LocalScale: {x: 1, y: 1, z: 1}
151 | m_ConstrainProportionsScale: 0
152 | m_Children:
153 | - {fileID: 520882672}
154 | m_Father: {fileID: 1533276811}
155 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
156 | m_AnchorMin: {x: 0, y: 0}
157 | m_AnchorMax: {x: 0, y: 0}
158 | m_AnchoredPosition: {x: 0, y: 0}
159 | m_SizeDelta: {x: 0, y: 0}
160 | m_Pivot: {x: 0.5, y: 0.5}
161 | --- !u!114 &132441563
162 | MonoBehaviour:
163 | m_ObjectHideFlags: 0
164 | m_CorrespondingSourceObject: {fileID: 0}
165 | m_PrefabInstance: {fileID: 0}
166 | m_PrefabAsset: {fileID: 0}
167 | m_GameObject: {fileID: 132441561}
168 | m_Enabled: 1
169 | m_EditorHideFlags: 0
170 | m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
171 | m_Name:
172 | m_EditorClassIdentifier:
173 | m_Navigation:
174 | m_Mode: 3
175 | m_WrapAround: 0
176 | m_SelectOnUp: {fileID: 0}
177 | m_SelectOnDown: {fileID: 0}
178 | m_SelectOnLeft: {fileID: 0}
179 | m_SelectOnRight: {fileID: 0}
180 | m_Transition: 1
181 | m_Colors:
182 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
183 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
184 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
185 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
186 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
187 | m_ColorMultiplier: 1
188 | m_FadeDuration: 0.1
189 | m_SpriteState:
190 | m_HighlightedSprite: {fileID: 0}
191 | m_PressedSprite: {fileID: 0}
192 | m_SelectedSprite: {fileID: 0}
193 | m_DisabledSprite: {fileID: 0}
194 | m_AnimationTriggers:
195 | m_NormalTrigger: Normal
196 | m_HighlightedTrigger: Highlighted
197 | m_PressedTrigger: Pressed
198 | m_SelectedTrigger: Selected
199 | m_DisabledTrigger: Disabled
200 | m_Interactable: 1
201 | m_TargetGraphic: {fileID: 132441564}
202 | m_OnClick:
203 | m_PersistentCalls:
204 | m_Calls:
205 | - m_Target: {fileID: 638370369}
206 | m_TargetAssemblyTypeName: Buck.SaveAsyncExample.GameManagerExample, Assembly-CSharp
207 | m_MethodName: LoadGameData
208 | m_Mode: 1
209 | m_Arguments:
210 | m_ObjectArgument: {fileID: 0}
211 | m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
212 | m_IntArgument: 0
213 | m_FloatArgument: 0
214 | m_StringArgument:
215 | m_BoolArgument: 0
216 | m_CallState: 2
217 | --- !u!114 &132441564
218 | MonoBehaviour:
219 | m_ObjectHideFlags: 0
220 | m_CorrespondingSourceObject: {fileID: 0}
221 | m_PrefabInstance: {fileID: 0}
222 | m_PrefabAsset: {fileID: 0}
223 | m_GameObject: {fileID: 132441561}
224 | m_Enabled: 1
225 | m_EditorHideFlags: 0
226 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
227 | m_Name:
228 | m_EditorClassIdentifier:
229 | m_Material: {fileID: 0}
230 | m_Color: {r: 1, g: 1, b: 1, a: 1}
231 | m_RaycastTarget: 1
232 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
233 | m_Maskable: 1
234 | m_OnCullStateChanged:
235 | m_PersistentCalls:
236 | m_Calls: []
237 | m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
238 | m_Type: 1
239 | m_PreserveAspect: 0
240 | m_FillCenter: 1
241 | m_FillMethod: 4
242 | m_FillAmount: 1
243 | m_FillClockwise: 1
244 | m_FillOrigin: 0
245 | m_UseSpriteMesh: 0
246 | m_PixelsPerUnitMultiplier: 1
247 | --- !u!222 &132441565
248 | CanvasRenderer:
249 | m_ObjectHideFlags: 0
250 | m_CorrespondingSourceObject: {fileID: 0}
251 | m_PrefabInstance: {fileID: 0}
252 | m_PrefabAsset: {fileID: 0}
253 | m_GameObject: {fileID: 132441561}
254 | m_CullTransparentMesh: 1
255 | --- !u!1 &330585543
256 | GameObject:
257 | m_ObjectHideFlags: 0
258 | m_CorrespondingSourceObject: {fileID: 0}
259 | m_PrefabInstance: {fileID: 0}
260 | m_PrefabAsset: {fileID: 0}
261 | serializedVersion: 6
262 | m_Component:
263 | - component: {fileID: 330585546}
264 | - component: {fileID: 330585545}
265 | - component: {fileID: 330585544}
266 | - component: {fileID: 330585547}
267 | m_Layer: 0
268 | m_Name: Main Camera
269 | m_TagString: MainCamera
270 | m_Icon: {fileID: 0}
271 | m_NavMeshLayer: 0
272 | m_StaticEditorFlags: 0
273 | m_IsActive: 1
274 | --- !u!81 &330585544
275 | AudioListener:
276 | m_ObjectHideFlags: 0
277 | m_CorrespondingSourceObject: {fileID: 0}
278 | m_PrefabInstance: {fileID: 0}
279 | m_PrefabAsset: {fileID: 0}
280 | m_GameObject: {fileID: 330585543}
281 | m_Enabled: 1
282 | --- !u!20 &330585545
283 | Camera:
284 | m_ObjectHideFlags: 0
285 | m_CorrespondingSourceObject: {fileID: 0}
286 | m_PrefabInstance: {fileID: 0}
287 | m_PrefabAsset: {fileID: 0}
288 | m_GameObject: {fileID: 330585543}
289 | m_Enabled: 1
290 | serializedVersion: 2
291 | m_ClearFlags: 2
292 | m_BackGroundColor: {r: 0.5019608, g: 0.5019608, b: 0.5019608, a: 0}
293 | m_projectionMatrixMode: 1
294 | m_GateFitMode: 2
295 | m_FOVAxisMode: 0
296 | m_Iso: 200
297 | m_ShutterSpeed: 0.005
298 | m_Aperture: 16
299 | m_FocusDistance: 10
300 | m_FocalLength: 50
301 | m_BladeCount: 5
302 | m_Curvature: {x: 2, y: 11}
303 | m_BarrelClipping: 0.25
304 | m_Anamorphism: 0
305 | m_SensorSize: {x: 36, y: 24}
306 | m_LensShift: {x: 0, y: 0}
307 | m_NormalizedViewPortRect:
308 | serializedVersion: 2
309 | x: 0
310 | y: 0
311 | width: 1
312 | height: 1
313 | near clip plane: 0.3
314 | far clip plane: 1000
315 | field of view: 60
316 | orthographic: 1
317 | orthographic size: 5
318 | m_Depth: -1
319 | m_CullingMask:
320 | serializedVersion: 2
321 | m_Bits: 4294967295
322 | m_RenderingPath: -1
323 | m_TargetTexture: {fileID: 0}
324 | m_TargetDisplay: 0
325 | m_TargetEye: 3
326 | m_HDR: 1
327 | m_AllowMSAA: 1
328 | m_AllowDynamicResolution: 0
329 | m_ForceIntoRT: 0
330 | m_OcclusionCulling: 1
331 | m_StereoConvergence: 10
332 | m_StereoSeparation: 0.022
333 | --- !u!4 &330585546
334 | Transform:
335 | m_ObjectHideFlags: 0
336 | m_CorrespondingSourceObject: {fileID: 0}
337 | m_PrefabInstance: {fileID: 0}
338 | m_PrefabAsset: {fileID: 0}
339 | m_GameObject: {fileID: 330585543}
340 | serializedVersion: 2
341 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
342 | m_LocalPosition: {x: 0, y: 1, z: -10}
343 | m_LocalScale: {x: 1, y: 1, z: 1}
344 | m_ConstrainProportionsScale: 0
345 | m_Children: []
346 | m_Father: {fileID: 0}
347 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
348 | --- !u!114 &330585547
349 | MonoBehaviour:
350 | m_ObjectHideFlags: 0
351 | m_CorrespondingSourceObject: {fileID: 0}
352 | m_PrefabInstance: {fileID: 0}
353 | m_PrefabAsset: {fileID: 0}
354 | m_GameObject: {fileID: 330585543}
355 | m_Enabled: 1
356 | m_EditorHideFlags: 0
357 | m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
358 | m_Name:
359 | m_EditorClassIdentifier:
360 | m_RenderShadows: 1
361 | m_RequiresDepthTextureOption: 2
362 | m_RequiresOpaqueTextureOption: 2
363 | m_CameraType: 0
364 | m_Cameras: []
365 | m_RendererIndex: -1
366 | m_VolumeLayerMask:
367 | serializedVersion: 2
368 | m_Bits: 1
369 | m_VolumeTrigger: {fileID: 0}
370 | m_VolumeFrameworkUpdateModeOption: 2
371 | m_RenderPostProcessing: 0
372 | m_Antialiasing: 0
373 | m_AntialiasingQuality: 2
374 | m_StopNaN: 0
375 | m_Dithering: 0
376 | m_ClearDepth: 1
377 | m_AllowXRRendering: 1
378 | m_AllowHDROutput: 1
379 | m_UseScreenCoordOverride: 0
380 | m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
381 | m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
382 | m_RequiresDepthTexture: 0
383 | m_RequiresColorTexture: 0
384 | m_Version: 2
385 | m_TaaSettings:
386 | quality: 3
387 | frameInfluence: 0.1
388 | jitterScale: 1
389 | mipBias: 0
390 | varianceClampScale: 0.9
391 | contrastAdaptiveSharpening: 0
392 | --- !u!1 &346742329
393 | GameObject:
394 | m_ObjectHideFlags: 0
395 | m_CorrespondingSourceObject: {fileID: 0}
396 | m_PrefabInstance: {fileID: 0}
397 | m_PrefabAsset: {fileID: 0}
398 | serializedVersion: 6
399 | m_Component:
400 | - component: {fileID: 346742333}
401 | - component: {fileID: 346742332}
402 | - component: {fileID: 346742331}
403 | - component: {fileID: 346742330}
404 | m_Layer: 5
405 | m_Name: Canvas
406 | m_TagString: Untagged
407 | m_Icon: {fileID: 0}
408 | m_NavMeshLayer: 0
409 | m_StaticEditorFlags: 0
410 | m_IsActive: 1
411 | --- !u!114 &346742330
412 | MonoBehaviour:
413 | m_ObjectHideFlags: 0
414 | m_CorrespondingSourceObject: {fileID: 0}
415 | m_PrefabInstance: {fileID: 0}
416 | m_PrefabAsset: {fileID: 0}
417 | m_GameObject: {fileID: 346742329}
418 | m_Enabled: 1
419 | m_EditorHideFlags: 0
420 | m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
421 | m_Name:
422 | m_EditorClassIdentifier:
423 | m_IgnoreReversedGraphics: 1
424 | m_BlockingObjects: 0
425 | m_BlockingMask:
426 | serializedVersion: 2
427 | m_Bits: 55
428 | --- !u!114 &346742331
429 | MonoBehaviour:
430 | m_ObjectHideFlags: 0
431 | m_CorrespondingSourceObject: {fileID: 0}
432 | m_PrefabInstance: {fileID: 0}
433 | m_PrefabAsset: {fileID: 0}
434 | m_GameObject: {fileID: 346742329}
435 | m_Enabled: 1
436 | m_EditorHideFlags: 0
437 | m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
438 | m_Name:
439 | m_EditorClassIdentifier:
440 | m_UiScaleMode: 1
441 | m_ReferencePixelsPerUnit: 100
442 | m_ScaleFactor: 1
443 | m_ReferenceResolution: {x: 1920, y: 1080}
444 | m_ScreenMatchMode: 0
445 | m_MatchWidthOrHeight: 0
446 | m_PhysicalUnit: 3
447 | m_FallbackScreenDPI: 96
448 | m_DefaultSpriteDPI: 96
449 | m_DynamicPixelsPerUnit: 1
450 | m_PresetInfoIsWorld: 0
451 | --- !u!223 &346742332
452 | Canvas:
453 | m_ObjectHideFlags: 0
454 | m_CorrespondingSourceObject: {fileID: 0}
455 | m_PrefabInstance: {fileID: 0}
456 | m_PrefabAsset: {fileID: 0}
457 | m_GameObject: {fileID: 346742329}
458 | m_Enabled: 1
459 | serializedVersion: 3
460 | m_RenderMode: 0
461 | m_Camera: {fileID: 0}
462 | m_PlaneDistance: 100
463 | m_PixelPerfect: 0
464 | m_ReceivesEvents: 1
465 | m_OverrideSorting: 0
466 | m_OverridePixelPerfect: 0
467 | m_SortingBucketNormalizedSize: 0
468 | m_VertexColorAlwaysGammaSpace: 1
469 | m_AdditionalShaderChannelsFlag: 25
470 | m_UpdateRectTransformForStandalone: 0
471 | m_SortingLayerID: 0
472 | m_SortingOrder: 0
473 | m_TargetDisplay: 0
474 | --- !u!224 &346742333
475 | RectTransform:
476 | m_ObjectHideFlags: 0
477 | m_CorrespondingSourceObject: {fileID: 0}
478 | m_PrefabInstance: {fileID: 0}
479 | m_PrefabAsset: {fileID: 0}
480 | m_GameObject: {fileID: 346742329}
481 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
482 | m_LocalPosition: {x: 0, y: 0, z: 0}
483 | m_LocalScale: {x: 0, y: 0, z: 0}
484 | m_ConstrainProportionsScale: 0
485 | m_Children:
486 | - {fileID: 2120769809}
487 | m_Father: {fileID: 0}
488 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
489 | m_AnchorMin: {x: 0, y: 0}
490 | m_AnchorMax: {x: 0, y: 0}
491 | m_AnchoredPosition: {x: 0, y: 0}
492 | m_SizeDelta: {x: 0, y: 0}
493 | m_Pivot: {x: 0, y: 0}
494 | --- !u!1 &385040791
495 | GameObject:
496 | m_ObjectHideFlags: 0
497 | m_CorrespondingSourceObject: {fileID: 0}
498 | m_PrefabInstance: {fileID: 0}
499 | m_PrefabAsset: {fileID: 0}
500 | serializedVersion: 6
501 | m_Component:
502 | - component: {fileID: 385040792}
503 | - component: {fileID: 385040795}
504 | - component: {fileID: 385040794}
505 | - component: {fileID: 385040793}
506 | m_Layer: 5
507 | m_Name: Save Game Data
508 | m_TagString: Untagged
509 | m_Icon: {fileID: 0}
510 | m_NavMeshLayer: 0
511 | m_StaticEditorFlags: 0
512 | m_IsActive: 1
513 | --- !u!224 &385040792
514 | RectTransform:
515 | m_ObjectHideFlags: 0
516 | m_CorrespondingSourceObject: {fileID: 0}
517 | m_PrefabInstance: {fileID: 0}
518 | m_PrefabAsset: {fileID: 0}
519 | m_GameObject: {fileID: 385040791}
520 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
521 | m_LocalPosition: {x: 0, y: 0, z: 0}
522 | m_LocalScale: {x: 1, y: 1, z: 1}
523 | m_ConstrainProportionsScale: 0
524 | m_Children:
525 | - {fileID: 907649185}
526 | m_Father: {fileID: 1533276811}
527 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
528 | m_AnchorMin: {x: 0, y: 0}
529 | m_AnchorMax: {x: 0, y: 0}
530 | m_AnchoredPosition: {x: 0, y: 0}
531 | m_SizeDelta: {x: 0, y: 0}
532 | m_Pivot: {x: 0.5, y: 0.5}
533 | --- !u!114 &385040793
534 | MonoBehaviour:
535 | m_ObjectHideFlags: 0
536 | m_CorrespondingSourceObject: {fileID: 0}
537 | m_PrefabInstance: {fileID: 0}
538 | m_PrefabAsset: {fileID: 0}
539 | m_GameObject: {fileID: 385040791}
540 | m_Enabled: 1
541 | m_EditorHideFlags: 0
542 | m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
543 | m_Name:
544 | m_EditorClassIdentifier:
545 | m_Navigation:
546 | m_Mode: 3
547 | m_WrapAround: 0
548 | m_SelectOnUp: {fileID: 0}
549 | m_SelectOnDown: {fileID: 0}
550 | m_SelectOnLeft: {fileID: 0}
551 | m_SelectOnRight: {fileID: 0}
552 | m_Transition: 1
553 | m_Colors:
554 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
555 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
556 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
557 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
558 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
559 | m_ColorMultiplier: 1
560 | m_FadeDuration: 0.1
561 | m_SpriteState:
562 | m_HighlightedSprite: {fileID: 0}
563 | m_PressedSprite: {fileID: 0}
564 | m_SelectedSprite: {fileID: 0}
565 | m_DisabledSprite: {fileID: 0}
566 | m_AnimationTriggers:
567 | m_NormalTrigger: Normal
568 | m_HighlightedTrigger: Highlighted
569 | m_PressedTrigger: Pressed
570 | m_SelectedTrigger: Selected
571 | m_DisabledTrigger: Disabled
572 | m_Interactable: 1
573 | m_TargetGraphic: {fileID: 385040794}
574 | m_OnClick:
575 | m_PersistentCalls:
576 | m_Calls:
577 | - m_Target: {fileID: 638370369}
578 | m_TargetAssemblyTypeName: Buck.SaveAsyncExample.GameManagerExample, Assembly-CSharp
579 | m_MethodName: SaveGameData
580 | m_Mode: 1
581 | m_Arguments:
582 | m_ObjectArgument: {fileID: 0}
583 | m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
584 | m_IntArgument: 0
585 | m_FloatArgument: 0
586 | m_StringArgument:
587 | m_BoolArgument: 0
588 | m_CallState: 2
589 | --- !u!114 &385040794
590 | MonoBehaviour:
591 | m_ObjectHideFlags: 0
592 | m_CorrespondingSourceObject: {fileID: 0}
593 | m_PrefabInstance: {fileID: 0}
594 | m_PrefabAsset: {fileID: 0}
595 | m_GameObject: {fileID: 385040791}
596 | m_Enabled: 1
597 | m_EditorHideFlags: 0
598 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
599 | m_Name:
600 | m_EditorClassIdentifier:
601 | m_Material: {fileID: 0}
602 | m_Color: {r: 1, g: 1, b: 1, a: 1}
603 | m_RaycastTarget: 1
604 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
605 | m_Maskable: 1
606 | m_OnCullStateChanged:
607 | m_PersistentCalls:
608 | m_Calls: []
609 | m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
610 | m_Type: 1
611 | m_PreserveAspect: 0
612 | m_FillCenter: 1
613 | m_FillMethod: 4
614 | m_FillAmount: 1
615 | m_FillClockwise: 1
616 | m_FillOrigin: 0
617 | m_UseSpriteMesh: 0
618 | m_PixelsPerUnitMultiplier: 1
619 | --- !u!222 &385040795
620 | CanvasRenderer:
621 | m_ObjectHideFlags: 0
622 | m_CorrespondingSourceObject: {fileID: 0}
623 | m_PrefabInstance: {fileID: 0}
624 | m_PrefabAsset: {fileID: 0}
625 | m_GameObject: {fileID: 385040791}
626 | m_CullTransparentMesh: 1
627 | --- !u!1 &520882671
628 | GameObject:
629 | m_ObjectHideFlags: 0
630 | m_CorrespondingSourceObject: {fileID: 0}
631 | m_PrefabInstance: {fileID: 0}
632 | m_PrefabAsset: {fileID: 0}
633 | serializedVersion: 6
634 | m_Component:
635 | - component: {fileID: 520882672}
636 | - component: {fileID: 520882674}
637 | - component: {fileID: 520882675}
638 | m_Layer: 5
639 | m_Name: Text (TMP)
640 | m_TagString: Untagged
641 | m_Icon: {fileID: 0}
642 | m_NavMeshLayer: 0
643 | m_StaticEditorFlags: 0
644 | m_IsActive: 1
645 | --- !u!224 &520882672
646 | RectTransform:
647 | m_ObjectHideFlags: 0
648 | m_CorrespondingSourceObject: {fileID: 0}
649 | m_PrefabInstance: {fileID: 0}
650 | m_PrefabAsset: {fileID: 0}
651 | m_GameObject: {fileID: 520882671}
652 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
653 | m_LocalPosition: {x: 0, y: 0, z: 0}
654 | m_LocalScale: {x: 1, y: 1, z: 1}
655 | m_ConstrainProportionsScale: 0
656 | m_Children: []
657 | m_Father: {fileID: 132441562}
658 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
659 | m_AnchorMin: {x: 0, y: 0}
660 | m_AnchorMax: {x: 1, y: 1}
661 | m_AnchoredPosition: {x: 0, y: 0}
662 | m_SizeDelta: {x: 0, y: 0}
663 | m_Pivot: {x: 0.5, y: 0.5}
664 | --- !u!222 &520882674
665 | CanvasRenderer:
666 | m_ObjectHideFlags: 0
667 | m_CorrespondingSourceObject: {fileID: 0}
668 | m_PrefabInstance: {fileID: 0}
669 | m_PrefabAsset: {fileID: 0}
670 | m_GameObject: {fileID: 520882671}
671 | m_CullTransparentMesh: 1
672 | --- !u!114 &520882675
673 | MonoBehaviour:
674 | m_ObjectHideFlags: 0
675 | m_CorrespondingSourceObject: {fileID: 0}
676 | m_PrefabInstance: {fileID: 0}
677 | m_PrefabAsset: {fileID: 0}
678 | m_GameObject: {fileID: 520882671}
679 | m_Enabled: 1
680 | m_EditorHideFlags: 0
681 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
682 | m_Name:
683 | m_EditorClassIdentifier:
684 | m_Material: {fileID: 0}
685 | m_Color: {r: 0, g: 0, b: 0, a: 1}
686 | m_RaycastTarget: 1
687 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
688 | m_Maskable: 1
689 | m_OnCullStateChanged:
690 | m_PersistentCalls:
691 | m_Calls: []
692 | m_FontData:
693 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
694 | m_FontSize: 20
695 | m_FontStyle: 0
696 | m_BestFit: 0
697 | m_MinSize: 2
698 | m_MaxSize: 40
699 | m_Alignment: 4
700 | m_AlignByGeometry: 0
701 | m_RichText: 1
702 | m_HorizontalOverflow: 0
703 | m_VerticalOverflow: 0
704 | m_LineSpacing: 1
705 | m_Text: Load Game Data
706 | --- !u!1 &638370368
707 | GameObject:
708 | m_ObjectHideFlags: 0
709 | m_CorrespondingSourceObject: {fileID: 0}
710 | m_PrefabInstance: {fileID: 0}
711 | m_PrefabAsset: {fileID: 0}
712 | serializedVersion: 6
713 | m_Component:
714 | - component: {fileID: 638370370}
715 | - component: {fileID: 638370369}
716 | - component: {fileID: 638370371}
717 | m_Layer: 0
718 | m_Name: Game Manager Example
719 | m_TagString: Untagged
720 | m_Icon: {fileID: 0}
721 | m_NavMeshLayer: 0
722 | m_StaticEditorFlags: 0
723 | m_IsActive: 1
724 | --- !u!114 &638370369
725 | MonoBehaviour:
726 | m_ObjectHideFlags: 0
727 | m_CorrespondingSourceObject: {fileID: 0}
728 | m_PrefabInstance: {fileID: 0}
729 | m_PrefabAsset: {fileID: 0}
730 | m_GameObject: {fileID: 638370368}
731 | m_Enabled: 1
732 | m_EditorHideFlags: 0
733 | m_Script: {fileID: 11500000, guid: 8fc2051fe91f7384ea2c6164a45be913, type: 3}
734 | m_Name:
735 | m_EditorClassIdentifier:
736 | m_runBenchmarks: 1
737 | --- !u!4 &638370370
738 | Transform:
739 | m_ObjectHideFlags: 0
740 | m_CorrespondingSourceObject: {fileID: 0}
741 | m_PrefabInstance: {fileID: 0}
742 | m_PrefabAsset: {fileID: 0}
743 | m_GameObject: {fileID: 638370368}
744 | serializedVersion: 2
745 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
746 | m_LocalPosition: {x: 0, y: 0, z: 0}
747 | m_LocalScale: {x: 1, y: 1, z: 1}
748 | m_ConstrainProportionsScale: 0
749 | m_Children: []
750 | m_Father: {fileID: 0}
751 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
752 | --- !u!114 &638370371
753 | MonoBehaviour:
754 | m_ObjectHideFlags: 0
755 | m_CorrespondingSourceObject: {fileID: 0}
756 | m_PrefabInstance: {fileID: 0}
757 | m_PrefabAsset: {fileID: 0}
758 | m_GameObject: {fileID: 638370368}
759 | m_Enabled: 1
760 | m_EditorHideFlags: 0
761 | m_Script: {fileID: 11500000, guid: 1acd81244f60d6d4da7c7aee8f73e010, type: 3}
762 | m_Name:
763 | m_EditorClassIdentifier:
764 | m_debugText: {fileID: 1339772109}
765 | --- !u!1 &665154986
766 | GameObject:
767 | m_ObjectHideFlags: 0
768 | m_CorrespondingSourceObject: {fileID: 0}
769 | m_PrefabInstance: {fileID: 0}
770 | m_PrefabAsset: {fileID: 0}
771 | serializedVersion: 6
772 | m_Component:
773 | - component: {fileID: 665154988}
774 | - component: {fileID: 665154987}
775 | m_Layer: 0
776 | m_Name: Save Manager
777 | m_TagString: Untagged
778 | m_Icon: {fileID: 0}
779 | m_NavMeshLayer: 0
780 | m_StaticEditorFlags: 0
781 | m_IsActive: 1
782 | --- !u!114 &665154987
783 | MonoBehaviour:
784 | m_ObjectHideFlags: 0
785 | m_CorrespondingSourceObject: {fileID: 0}
786 | m_PrefabInstance: {fileID: 0}
787 | m_PrefabAsset: {fileID: 0}
788 | m_GameObject: {fileID: 665154986}
789 | m_Enabled: 1
790 | m_EditorHideFlags: 0
791 | m_Script: {fileID: 11500000, guid: f3c390e20d837bf409b6716c3b995a22, type: 3}
792 | m_Name:
793 | m_EditorClassIdentifier:
794 | m_encryptionType: 0
795 | m_encryptionPassword: password
796 | --- !u!4 &665154988
797 | Transform:
798 | m_ObjectHideFlags: 0
799 | m_CorrespondingSourceObject: {fileID: 0}
800 | m_PrefabInstance: {fileID: 0}
801 | m_PrefabAsset: {fileID: 0}
802 | m_GameObject: {fileID: 665154986}
803 | serializedVersion: 2
804 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
805 | m_LocalPosition: {x: 0, y: 0, z: 0}
806 | m_LocalScale: {x: 1, y: 1, z: 1}
807 | m_ConstrainProportionsScale: 0
808 | m_Children: []
809 | m_Father: {fileID: 0}
810 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
811 | --- !u!1 &687334401
812 | GameObject:
813 | m_ObjectHideFlags: 0
814 | m_CorrespondingSourceObject: {fileID: 0}
815 | m_PrefabInstance: {fileID: 0}
816 | m_PrefabAsset: {fileID: 0}
817 | serializedVersion: 6
818 | m_Component:
819 | - component: {fileID: 687334403}
820 | - component: {fileID: 687334402}
821 | m_Layer: 0
822 | m_Name: Cache Data Example
823 | m_TagString: Untagged
824 | m_Icon: {fileID: 0}
825 | m_NavMeshLayer: 0
826 | m_StaticEditorFlags: 0
827 | m_IsActive: 1
828 | --- !u!114 &687334402
829 | MonoBehaviour:
830 | m_ObjectHideFlags: 0
831 | m_CorrespondingSourceObject: {fileID: 0}
832 | m_PrefabInstance: {fileID: 0}
833 | m_PrefabAsset: {fileID: 0}
834 | m_GameObject: {fileID: 687334401}
835 | m_Enabled: 1
836 | m_EditorHideFlags: 0
837 | m_Script: {fileID: 11500000, guid: f66940caddea28c4db60761f8c77d861, type: 3}
838 | m_Name:
839 | m_EditorClassIdentifier:
840 | m_guidBytes: b75d06ceb0c50449bc28ec00feb4fb21
841 | m_myString: Some string!
842 | m_myInt: 5
843 | m_myFloat: 2
844 | --- !u!4 &687334403
845 | Transform:
846 | m_ObjectHideFlags: 0
847 | m_CorrespondingSourceObject: {fileID: 0}
848 | m_PrefabInstance: {fileID: 0}
849 | m_PrefabAsset: {fileID: 0}
850 | m_GameObject: {fileID: 687334401}
851 | serializedVersion: 2
852 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
853 | m_LocalPosition: {x: 0, y: 0, z: 0}
854 | m_LocalScale: {x: 1, y: 1, z: 1}
855 | m_ConstrainProportionsScale: 0
856 | m_Children: []
857 | m_Father: {fileID: 0}
858 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
859 | --- !u!1 &707914462
860 | GameObject:
861 | m_ObjectHideFlags: 0
862 | m_CorrespondingSourceObject: {fileID: 0}
863 | m_PrefabInstance: {fileID: 0}
864 | m_PrefabAsset: {fileID: 0}
865 | serializedVersion: 6
866 | m_Component:
867 | - component: {fileID: 707914463}
868 | - component: {fileID: 707914465}
869 | - component: {fileID: 707914466}
870 | m_Layer: 5
871 | m_Name: Text (TMP)
872 | m_TagString: Untagged
873 | m_Icon: {fileID: 0}
874 | m_NavMeshLayer: 0
875 | m_StaticEditorFlags: 0
876 | m_IsActive: 1
877 | --- !u!224 &707914463
878 | RectTransform:
879 | m_ObjectHideFlags: 0
880 | m_CorrespondingSourceObject: {fileID: 0}
881 | m_PrefabInstance: {fileID: 0}
882 | m_PrefabAsset: {fileID: 0}
883 | m_GameObject: {fileID: 707914462}
884 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
885 | m_LocalPosition: {x: 0, y: 0, z: 0}
886 | m_LocalScale: {x: 1, y: 1, z: 1}
887 | m_ConstrainProportionsScale: 0
888 | m_Children: []
889 | m_Father: {fileID: 1995397477}
890 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
891 | m_AnchorMin: {x: 0, y: 0}
892 | m_AnchorMax: {x: 1, y: 1}
893 | m_AnchoredPosition: {x: 0, y: 0}
894 | m_SizeDelta: {x: 0, y: 0}
895 | m_Pivot: {x: 0.5, y: 0.5}
896 | --- !u!222 &707914465
897 | CanvasRenderer:
898 | m_ObjectHideFlags: 0
899 | m_CorrespondingSourceObject: {fileID: 0}
900 | m_PrefabInstance: {fileID: 0}
901 | m_PrefabAsset: {fileID: 0}
902 | m_GameObject: {fileID: 707914462}
903 | m_CullTransparentMesh: 1
904 | --- !u!114 &707914466
905 | MonoBehaviour:
906 | m_ObjectHideFlags: 0
907 | m_CorrespondingSourceObject: {fileID: 0}
908 | m_PrefabInstance: {fileID: 0}
909 | m_PrefabAsset: {fileID: 0}
910 | m_GameObject: {fileID: 707914462}
911 | m_Enabled: 1
912 | m_EditorHideFlags: 0
913 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
914 | m_Name:
915 | m_EditorClassIdentifier:
916 | m_Material: {fileID: 0}
917 | m_Color: {r: 0, g: 0, b: 0, a: 1}
918 | m_RaycastTarget: 1
919 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
920 | m_Maskable: 1
921 | m_OnCullStateChanged:
922 | m_PersistentCalls:
923 | m_Calls: []
924 | m_FontData:
925 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
926 | m_FontSize: 20
927 | m_FontStyle: 0
928 | m_BestFit: 0
929 | m_MinSize: 2
930 | m_MaxSize: 40
931 | m_Alignment: 4
932 | m_AlignByGeometry: 0
933 | m_RichText: 1
934 | m_HorizontalOverflow: 0
935 | m_VerticalOverflow: 0
936 | m_LineSpacing: 1
937 | m_Text: Delete Game Data
938 | --- !u!1 &752201904
939 | GameObject:
940 | m_ObjectHideFlags: 0
941 | m_CorrespondingSourceObject: {fileID: 0}
942 | m_PrefabInstance: {fileID: 0}
943 | m_PrefabAsset: {fileID: 0}
944 | serializedVersion: 6
945 | m_Component:
946 | - component: {fileID: 752201905}
947 | - component: {fileID: 752201906}
948 | m_Layer: 5
949 | m_Name: Text Padding
950 | m_TagString: Untagged
951 | m_Icon: {fileID: 0}
952 | m_NavMeshLayer: 0
953 | m_StaticEditorFlags: 0
954 | m_IsActive: 1
955 | --- !u!224 &752201905
956 | RectTransform:
957 | m_ObjectHideFlags: 0
958 | m_CorrespondingSourceObject: {fileID: 0}
959 | m_PrefabInstance: {fileID: 0}
960 | m_PrefabAsset: {fileID: 0}
961 | m_GameObject: {fileID: 752201904}
962 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
963 | m_LocalPosition: {x: 0, y: 0, z: 0}
964 | m_LocalScale: {x: 1, y: 1, z: 1}
965 | m_ConstrainProportionsScale: 0
966 | m_Children:
967 | - {fileID: 1339772106}
968 | m_Father: {fileID: 1156166954}
969 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
970 | m_AnchorMin: {x: 0, y: 0}
971 | m_AnchorMax: {x: 1, y: 1}
972 | m_AnchoredPosition: {x: 0, y: 0}
973 | m_SizeDelta: {x: -50, y: -50}
974 | m_Pivot: {x: 0.5, y: 0.5}
975 | --- !u!222 &752201906
976 | CanvasRenderer:
977 | m_ObjectHideFlags: 0
978 | m_CorrespondingSourceObject: {fileID: 0}
979 | m_PrefabInstance: {fileID: 0}
980 | m_PrefabAsset: {fileID: 0}
981 | m_GameObject: {fileID: 752201904}
982 | m_CullTransparentMesh: 1
983 | --- !u!1 &907649184
984 | GameObject:
985 | m_ObjectHideFlags: 0
986 | m_CorrespondingSourceObject: {fileID: 0}
987 | m_PrefabInstance: {fileID: 0}
988 | m_PrefabAsset: {fileID: 0}
989 | serializedVersion: 6
990 | m_Component:
991 | - component: {fileID: 907649185}
992 | - component: {fileID: 907649187}
993 | - component: {fileID: 907649188}
994 | m_Layer: 5
995 | m_Name: Text (TMP)
996 | m_TagString: Untagged
997 | m_Icon: {fileID: 0}
998 | m_NavMeshLayer: 0
999 | m_StaticEditorFlags: 0
1000 | m_IsActive: 1
1001 | --- !u!224 &907649185
1002 | RectTransform:
1003 | m_ObjectHideFlags: 0
1004 | m_CorrespondingSourceObject: {fileID: 0}
1005 | m_PrefabInstance: {fileID: 0}
1006 | m_PrefabAsset: {fileID: 0}
1007 | m_GameObject: {fileID: 907649184}
1008 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
1009 | m_LocalPosition: {x: 0, y: 0, z: 0}
1010 | m_LocalScale: {x: 1, y: 1, z: 1}
1011 | m_ConstrainProportionsScale: 0
1012 | m_Children: []
1013 | m_Father: {fileID: 385040792}
1014 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1015 | m_AnchorMin: {x: 0, y: 0}
1016 | m_AnchorMax: {x: 1, y: 1}
1017 | m_AnchoredPosition: {x: 0, y: 0}
1018 | m_SizeDelta: {x: 0, y: 0}
1019 | m_Pivot: {x: 0.5, y: 0.5}
1020 | --- !u!222 &907649187
1021 | CanvasRenderer:
1022 | m_ObjectHideFlags: 0
1023 | m_CorrespondingSourceObject: {fileID: 0}
1024 | m_PrefabInstance: {fileID: 0}
1025 | m_PrefabAsset: {fileID: 0}
1026 | m_GameObject: {fileID: 907649184}
1027 | m_CullTransparentMesh: 1
1028 | --- !u!114 &907649188
1029 | MonoBehaviour:
1030 | m_ObjectHideFlags: 0
1031 | m_CorrespondingSourceObject: {fileID: 0}
1032 | m_PrefabInstance: {fileID: 0}
1033 | m_PrefabAsset: {fileID: 0}
1034 | m_GameObject: {fileID: 907649184}
1035 | m_Enabled: 1
1036 | m_EditorHideFlags: 0
1037 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
1038 | m_Name:
1039 | m_EditorClassIdentifier:
1040 | m_Material: {fileID: 0}
1041 | m_Color: {r: 0, g: 0, b: 0, a: 1}
1042 | m_RaycastTarget: 1
1043 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1044 | m_Maskable: 1
1045 | m_OnCullStateChanged:
1046 | m_PersistentCalls:
1047 | m_Calls: []
1048 | m_FontData:
1049 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
1050 | m_FontSize: 20
1051 | m_FontStyle: 0
1052 | m_BestFit: 0
1053 | m_MinSize: 2
1054 | m_MaxSize: 40
1055 | m_Alignment: 4
1056 | m_AlignByGeometry: 0
1057 | m_RichText: 1
1058 | m_HorizontalOverflow: 0
1059 | m_VerticalOverflow: 0
1060 | m_LineSpacing: 1
1061 | m_Text: Save Game Data
1062 | --- !u!1 &1056661431
1063 | GameObject:
1064 | m_ObjectHideFlags: 0
1065 | m_CorrespondingSourceObject: {fileID: 0}
1066 | m_PrefabInstance: {fileID: 0}
1067 | m_PrefabAsset: {fileID: 0}
1068 | serializedVersion: 6
1069 | m_Component:
1070 | - component: {fileID: 1056661432}
1071 | - component: {fileID: 1056661434}
1072 | - component: {fileID: 1056661433}
1073 | m_Layer: 5
1074 | m_Name: Output Log Container
1075 | m_TagString: Untagged
1076 | m_Icon: {fileID: 0}
1077 | m_NavMeshLayer: 0
1078 | m_StaticEditorFlags: 0
1079 | m_IsActive: 1
1080 | --- !u!224 &1056661432
1081 | RectTransform:
1082 | m_ObjectHideFlags: 0
1083 | m_CorrespondingSourceObject: {fileID: 0}
1084 | m_PrefabInstance: {fileID: 0}
1085 | m_PrefabAsset: {fileID: 0}
1086 | m_GameObject: {fileID: 1056661431}
1087 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1088 | m_LocalPosition: {x: 0, y: 0, z: 0}
1089 | m_LocalScale: {x: 1, y: 1, z: 1}
1090 | m_ConstrainProportionsScale: 0
1091 | m_Children:
1092 | - {fileID: 1533276811}
1093 | - {fileID: 1156166954}
1094 | m_Father: {fileID: 2120769809}
1095 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1096 | m_AnchorMin: {x: 0.5, y: 0.5}
1097 | m_AnchorMax: {x: 0.5, y: 0.5}
1098 | m_AnchoredPosition: {x: 0, y: 0}
1099 | m_SizeDelta: {x: 1513.5, y: 848.1}
1100 | m_Pivot: {x: 0.5, y: 0.5}
1101 | --- !u!114 &1056661433
1102 | MonoBehaviour:
1103 | m_ObjectHideFlags: 0
1104 | m_CorrespondingSourceObject: {fileID: 0}
1105 | m_PrefabInstance: {fileID: 0}
1106 | m_PrefabAsset: {fileID: 0}
1107 | m_GameObject: {fileID: 1056661431}
1108 | m_Enabled: 1
1109 | m_EditorHideFlags: 0
1110 | m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
1111 | m_Name:
1112 | m_EditorClassIdentifier:
1113 | m_Padding:
1114 | m_Left: 0
1115 | m_Right: 0
1116 | m_Top: 0
1117 | m_Bottom: 0
1118 | m_ChildAlignment: 4
1119 | m_Spacing: 0
1120 | m_ChildForceExpandWidth: 1
1121 | m_ChildForceExpandHeight: 1
1122 | m_ChildControlWidth: 1
1123 | m_ChildControlHeight: 0
1124 | m_ChildScaleWidth: 0
1125 | m_ChildScaleHeight: 0
1126 | m_ReverseArrangement: 0
1127 | --- !u!222 &1056661434
1128 | CanvasRenderer:
1129 | m_ObjectHideFlags: 0
1130 | m_CorrespondingSourceObject: {fileID: 0}
1131 | m_PrefabInstance: {fileID: 0}
1132 | m_PrefabAsset: {fileID: 0}
1133 | m_GameObject: {fileID: 1056661431}
1134 | m_CullTransparentMesh: 1
1135 | --- !u!1 &1156166953
1136 | GameObject:
1137 | m_ObjectHideFlags: 0
1138 | m_CorrespondingSourceObject: {fileID: 0}
1139 | m_PrefabInstance: {fileID: 0}
1140 | m_PrefabAsset: {fileID: 0}
1141 | serializedVersion: 6
1142 | m_Component:
1143 | - component: {fileID: 1156166954}
1144 | - component: {fileID: 1156166956}
1145 | - component: {fileID: 1156166955}
1146 | m_Layer: 5
1147 | m_Name: Panel
1148 | m_TagString: Untagged
1149 | m_Icon: {fileID: 0}
1150 | m_NavMeshLayer: 0
1151 | m_StaticEditorFlags: 0
1152 | m_IsActive: 1
1153 | --- !u!224 &1156166954
1154 | RectTransform:
1155 | m_ObjectHideFlags: 0
1156 | m_CorrespondingSourceObject: {fileID: 0}
1157 | m_PrefabInstance: {fileID: 0}
1158 | m_PrefabAsset: {fileID: 0}
1159 | m_GameObject: {fileID: 1156166953}
1160 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1161 | m_LocalPosition: {x: 0, y: 0, z: 0}
1162 | m_LocalScale: {x: 1, y: 1, z: 1}
1163 | m_ConstrainProportionsScale: 0
1164 | m_Children:
1165 | - {fileID: 752201905}
1166 | m_Father: {fileID: 1056661432}
1167 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1168 | m_AnchorMin: {x: 0, y: 0}
1169 | m_AnchorMax: {x: 0, y: 0}
1170 | m_AnchoredPosition: {x: 0, y: 0}
1171 | m_SizeDelta: {x: 0, y: 749.1}
1172 | m_Pivot: {x: 0.5, y: 0.5}
1173 | --- !u!114 &1156166955
1174 | MonoBehaviour:
1175 | m_ObjectHideFlags: 0
1176 | m_CorrespondingSourceObject: {fileID: 0}
1177 | m_PrefabInstance: {fileID: 0}
1178 | m_PrefabAsset: {fileID: 0}
1179 | m_GameObject: {fileID: 1156166953}
1180 | m_Enabled: 1
1181 | m_EditorHideFlags: 0
1182 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
1183 | m_Name:
1184 | m_EditorClassIdentifier:
1185 | m_Material: {fileID: 0}
1186 | m_Color: {r: 1, g: 1, b: 1, a: 1}
1187 | m_RaycastTarget: 1
1188 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1189 | m_Maskable: 1
1190 | m_OnCullStateChanged:
1191 | m_PersistentCalls:
1192 | m_Calls: []
1193 | m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
1194 | m_Type: 1
1195 | m_PreserveAspect: 0
1196 | m_FillCenter: 1
1197 | m_FillMethod: 4
1198 | m_FillAmount: 1
1199 | m_FillClockwise: 1
1200 | m_FillOrigin: 0
1201 | m_UseSpriteMesh: 0
1202 | m_PixelsPerUnitMultiplier: 1
1203 | --- !u!222 &1156166956
1204 | CanvasRenderer:
1205 | m_ObjectHideFlags: 0
1206 | m_CorrespondingSourceObject: {fileID: 0}
1207 | m_PrefabInstance: {fileID: 0}
1208 | m_PrefabAsset: {fileID: 0}
1209 | m_GameObject: {fileID: 1156166953}
1210 | m_CullTransparentMesh: 1
1211 | --- !u!1 &1339772105
1212 | GameObject:
1213 | m_ObjectHideFlags: 0
1214 | m_CorrespondingSourceObject: {fileID: 0}
1215 | m_PrefabInstance: {fileID: 0}
1216 | m_PrefabAsset: {fileID: 0}
1217 | serializedVersion: 6
1218 | m_Component:
1219 | - component: {fileID: 1339772106}
1220 | - component: {fileID: 1339772108}
1221 | - component: {fileID: 1339772109}
1222 | m_Layer: 5
1223 | m_Name: Debug Text
1224 | m_TagString: Untagged
1225 | m_Icon: {fileID: 0}
1226 | m_NavMeshLayer: 0
1227 | m_StaticEditorFlags: 0
1228 | m_IsActive: 1
1229 | --- !u!224 &1339772106
1230 | RectTransform:
1231 | m_ObjectHideFlags: 0
1232 | m_CorrespondingSourceObject: {fileID: 0}
1233 | m_PrefabInstance: {fileID: 0}
1234 | m_PrefabAsset: {fileID: 0}
1235 | m_GameObject: {fileID: 1339772105}
1236 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1237 | m_LocalPosition: {x: 0, y: 0, z: 0}
1238 | m_LocalScale: {x: 1, y: 1, z: 1}
1239 | m_ConstrainProportionsScale: 0
1240 | m_Children: []
1241 | m_Father: {fileID: 752201905}
1242 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1243 | m_AnchorMin: {x: 0, y: 0}
1244 | m_AnchorMax: {x: 1, y: 1}
1245 | m_AnchoredPosition: {x: 0, y: 0}
1246 | m_SizeDelta: {x: 0, y: 0}
1247 | m_Pivot: {x: 0.5, y: 0.5}
1248 | --- !u!222 &1339772108
1249 | CanvasRenderer:
1250 | m_ObjectHideFlags: 0
1251 | m_CorrespondingSourceObject: {fileID: 0}
1252 | m_PrefabInstance: {fileID: 0}
1253 | m_PrefabAsset: {fileID: 0}
1254 | m_GameObject: {fileID: 1339772105}
1255 | m_CullTransparentMesh: 1
1256 | --- !u!114 &1339772109
1257 | MonoBehaviour:
1258 | m_ObjectHideFlags: 0
1259 | m_CorrespondingSourceObject: {fileID: 0}
1260 | m_PrefabInstance: {fileID: 0}
1261 | m_PrefabAsset: {fileID: 0}
1262 | m_GameObject: {fileID: 1339772105}
1263 | m_Enabled: 1
1264 | m_EditorHideFlags: 0
1265 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
1266 | m_Name:
1267 | m_EditorClassIdentifier:
1268 | m_Material: {fileID: 0}
1269 | m_Color: {r: 0, g: 0, b: 0, a: 1}
1270 | m_RaycastTarget: 1
1271 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1272 | m_Maskable: 1
1273 | m_OnCullStateChanged:
1274 | m_PersistentCalls:
1275 | m_Calls: []
1276 | m_FontData:
1277 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
1278 | m_FontSize: 20
1279 | m_FontStyle: 0
1280 | m_BestFit: 0
1281 | m_MinSize: 2
1282 | m_MaxSize: 40
1283 | m_Alignment: 0
1284 | m_AlignByGeometry: 0
1285 | m_RichText: 1
1286 | m_HorizontalOverflow: 0
1287 | m_VerticalOverflow: 0
1288 | m_LineSpacing: 1
1289 | m_Text: Press a button to start a test!
1290 | --- !u!1 &1533276810
1291 | GameObject:
1292 | m_ObjectHideFlags: 0
1293 | m_CorrespondingSourceObject: {fileID: 0}
1294 | m_PrefabInstance: {fileID: 0}
1295 | m_PrefabAsset: {fileID: 0}
1296 | serializedVersion: 6
1297 | m_Component:
1298 | - component: {fileID: 1533276811}
1299 | - component: {fileID: 1533276813}
1300 | - component: {fileID: 1533276812}
1301 | m_Layer: 5
1302 | m_Name: Save Buttons
1303 | m_TagString: Untagged
1304 | m_Icon: {fileID: 0}
1305 | m_NavMeshLayer: 0
1306 | m_StaticEditorFlags: 0
1307 | m_IsActive: 1
1308 | --- !u!224 &1533276811
1309 | RectTransform:
1310 | m_ObjectHideFlags: 0
1311 | m_CorrespondingSourceObject: {fileID: 0}
1312 | m_PrefabInstance: {fileID: 0}
1313 | m_PrefabAsset: {fileID: 0}
1314 | m_GameObject: {fileID: 1533276810}
1315 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1316 | m_LocalPosition: {x: 0, y: 0, z: 0}
1317 | m_LocalScale: {x: 1, y: 1, z: 1}
1318 | m_ConstrainProportionsScale: 0
1319 | m_Children:
1320 | - {fileID: 385040792}
1321 | - {fileID: 132441562}
1322 | - {fileID: 1779326921}
1323 | - {fileID: 1995397477}
1324 | m_Father: {fileID: 1056661432}
1325 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1326 | m_AnchorMin: {x: 0, y: 0}
1327 | m_AnchorMax: {x: 0, y: 0}
1328 | m_AnchoredPosition: {x: 0, y: 0}
1329 | m_SizeDelta: {x: 0, y: 150}
1330 | m_Pivot: {x: 0.5, y: 0.5}
1331 | --- !u!114 &1533276812
1332 | MonoBehaviour:
1333 | m_ObjectHideFlags: 0
1334 | m_CorrespondingSourceObject: {fileID: 0}
1335 | m_PrefabInstance: {fileID: 0}
1336 | m_PrefabAsset: {fileID: 0}
1337 | m_GameObject: {fileID: 1533276810}
1338 | m_Enabled: 1
1339 | m_EditorHideFlags: 0
1340 | m_Script: {fileID: 11500000, guid: 8a8695521f0d02e499659fee002a26c2, type: 3}
1341 | m_Name:
1342 | m_EditorClassIdentifier:
1343 | m_Padding:
1344 | m_Left: 0
1345 | m_Right: 0
1346 | m_Top: 0
1347 | m_Bottom: 0
1348 | m_ChildAlignment: 4
1349 | m_StartCorner: 0
1350 | m_StartAxis: 0
1351 | m_CellSize: {x: 200, y: 50}
1352 | m_Spacing: {x: 50, y: 50}
1353 | m_Constraint: 0
1354 | m_ConstraintCount: 2
1355 | --- !u!222 &1533276813
1356 | CanvasRenderer:
1357 | m_ObjectHideFlags: 0
1358 | m_CorrespondingSourceObject: {fileID: 0}
1359 | m_PrefabInstance: {fileID: 0}
1360 | m_PrefabAsset: {fileID: 0}
1361 | m_GameObject: {fileID: 1533276810}
1362 | m_CullTransparentMesh: 1
1363 | --- !u!1 &1664646199
1364 | GameObject:
1365 | m_ObjectHideFlags: 0
1366 | m_CorrespondingSourceObject: {fileID: 0}
1367 | m_PrefabInstance: {fileID: 0}
1368 | m_PrefabAsset: {fileID: 0}
1369 | serializedVersion: 6
1370 | m_Component:
1371 | - component: {fileID: 1664646201}
1372 | - component: {fileID: 1664646202}
1373 | m_Layer: 0
1374 | m_Name: Game Data Example
1375 | m_TagString: Untagged
1376 | m_Icon: {fileID: 0}
1377 | m_NavMeshLayer: 0
1378 | m_StaticEditorFlags: 0
1379 | m_IsActive: 1
1380 | --- !u!4 &1664646201
1381 | Transform:
1382 | m_ObjectHideFlags: 0
1383 | m_CorrespondingSourceObject: {fileID: 0}
1384 | m_PrefabInstance: {fileID: 0}
1385 | m_PrefabAsset: {fileID: 0}
1386 | m_GameObject: {fileID: 1664646199}
1387 | serializedVersion: 2
1388 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
1389 | m_LocalPosition: {x: 0, y: 0, z: 0}
1390 | m_LocalScale: {x: 1, y: 1, z: 1}
1391 | m_ConstrainProportionsScale: 0
1392 | m_Children: []
1393 | m_Father: {fileID: 0}
1394 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1395 | --- !u!114 &1664646202
1396 | MonoBehaviour:
1397 | m_ObjectHideFlags: 0
1398 | m_CorrespondingSourceObject: {fileID: 0}
1399 | m_PrefabInstance: {fileID: 0}
1400 | m_PrefabAsset: {fileID: 0}
1401 | m_GameObject: {fileID: 1664646199}
1402 | m_Enabled: 1
1403 | m_EditorHideFlags: 0
1404 | m_Script: {fileID: 11500000, guid: f2b492ad60c16564880e711051b0caf4, type: 3}
1405 | m_Name:
1406 | m_EditorClassIdentifier:
1407 | m_guidBytes: 91e44137b6dcb7448635eb7c175ea754
1408 | m_playerName: The Player Name
1409 | m_playerHealth: 100
1410 | m_position: {x: 1, y: 2, z: 3}
1411 | --- !u!1 &1665658484
1412 | GameObject:
1413 | m_ObjectHideFlags: 0
1414 | m_CorrespondingSourceObject: {fileID: 0}
1415 | m_PrefabInstance: {fileID: 0}
1416 | m_PrefabAsset: {fileID: 0}
1417 | serializedVersion: 6
1418 | m_Component:
1419 | - component: {fileID: 1665658485}
1420 | - component: {fileID: 1665658487}
1421 | - component: {fileID: 1665658488}
1422 | m_Layer: 5
1423 | m_Name: Text (TMP)
1424 | m_TagString: Untagged
1425 | m_Icon: {fileID: 0}
1426 | m_NavMeshLayer: 0
1427 | m_StaticEditorFlags: 0
1428 | m_IsActive: 1
1429 | --- !u!224 &1665658485
1430 | RectTransform:
1431 | m_ObjectHideFlags: 0
1432 | m_CorrespondingSourceObject: {fileID: 0}
1433 | m_PrefabInstance: {fileID: 0}
1434 | m_PrefabAsset: {fileID: 0}
1435 | m_GameObject: {fileID: 1665658484}
1436 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
1437 | m_LocalPosition: {x: 0, y: 0, z: 0}
1438 | m_LocalScale: {x: 1, y: 1, z: 1}
1439 | m_ConstrainProportionsScale: 0
1440 | m_Children: []
1441 | m_Father: {fileID: 1779326921}
1442 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1443 | m_AnchorMin: {x: 0, y: 0}
1444 | m_AnchorMax: {x: 1, y: 1}
1445 | m_AnchoredPosition: {x: 0, y: 0}
1446 | m_SizeDelta: {x: 0, y: 0}
1447 | m_Pivot: {x: 0.5, y: 0.5}
1448 | --- !u!222 &1665658487
1449 | CanvasRenderer:
1450 | m_ObjectHideFlags: 0
1451 | m_CorrespondingSourceObject: {fileID: 0}
1452 | m_PrefabInstance: {fileID: 0}
1453 | m_PrefabAsset: {fileID: 0}
1454 | m_GameObject: {fileID: 1665658484}
1455 | m_CullTransparentMesh: 1
1456 | --- !u!114 &1665658488
1457 | MonoBehaviour:
1458 | m_ObjectHideFlags: 0
1459 | m_CorrespondingSourceObject: {fileID: 0}
1460 | m_PrefabInstance: {fileID: 0}
1461 | m_PrefabAsset: {fileID: 0}
1462 | m_GameObject: {fileID: 1665658484}
1463 | m_Enabled: 1
1464 | m_EditorHideFlags: 0
1465 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
1466 | m_Name:
1467 | m_EditorClassIdentifier:
1468 | m_Material: {fileID: 0}
1469 | m_Color: {r: 0, g: 0, b: 0, a: 1}
1470 | m_RaycastTarget: 1
1471 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1472 | m_Maskable: 1
1473 | m_OnCullStateChanged:
1474 | m_PersistentCalls:
1475 | m_Calls: []
1476 | m_FontData:
1477 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
1478 | m_FontSize: 20
1479 | m_FontStyle: 0
1480 | m_BestFit: 0
1481 | m_MinSize: 2
1482 | m_MaxSize: 40
1483 | m_Alignment: 4
1484 | m_AlignByGeometry: 0
1485 | m_RichText: 1
1486 | m_HorizontalOverflow: 0
1487 | m_VerticalOverflow: 0
1488 | m_LineSpacing: 1
1489 | m_Text: Erase Game Data
1490 | --- !u!1 &1779326920
1491 | GameObject:
1492 | m_ObjectHideFlags: 0
1493 | m_CorrespondingSourceObject: {fileID: 0}
1494 | m_PrefabInstance: {fileID: 0}
1495 | m_PrefabAsset: {fileID: 0}
1496 | serializedVersion: 6
1497 | m_Component:
1498 | - component: {fileID: 1779326921}
1499 | - component: {fileID: 1779326924}
1500 | - component: {fileID: 1779326923}
1501 | - component: {fileID: 1779326922}
1502 | m_Layer: 5
1503 | m_Name: Erase Game Data
1504 | m_TagString: Untagged
1505 | m_Icon: {fileID: 0}
1506 | m_NavMeshLayer: 0
1507 | m_StaticEditorFlags: 0
1508 | m_IsActive: 1
1509 | --- !u!224 &1779326921
1510 | RectTransform:
1511 | m_ObjectHideFlags: 0
1512 | m_CorrespondingSourceObject: {fileID: 0}
1513 | m_PrefabInstance: {fileID: 0}
1514 | m_PrefabAsset: {fileID: 0}
1515 | m_GameObject: {fileID: 1779326920}
1516 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1517 | m_LocalPosition: {x: 0, y: 0, z: 0}
1518 | m_LocalScale: {x: 1, y: 1, z: 1}
1519 | m_ConstrainProportionsScale: 0
1520 | m_Children:
1521 | - {fileID: 1665658485}
1522 | m_Father: {fileID: 1533276811}
1523 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1524 | m_AnchorMin: {x: 0, y: 0}
1525 | m_AnchorMax: {x: 0, y: 0}
1526 | m_AnchoredPosition: {x: 0, y: 0}
1527 | m_SizeDelta: {x: 0, y: 0}
1528 | m_Pivot: {x: 0.5, y: 0.5}
1529 | --- !u!114 &1779326922
1530 | MonoBehaviour:
1531 | m_ObjectHideFlags: 0
1532 | m_CorrespondingSourceObject: {fileID: 0}
1533 | m_PrefabInstance: {fileID: 0}
1534 | m_PrefabAsset: {fileID: 0}
1535 | m_GameObject: {fileID: 1779326920}
1536 | m_Enabled: 1
1537 | m_EditorHideFlags: 0
1538 | m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
1539 | m_Name:
1540 | m_EditorClassIdentifier:
1541 | m_Navigation:
1542 | m_Mode: 3
1543 | m_WrapAround: 0
1544 | m_SelectOnUp: {fileID: 0}
1545 | m_SelectOnDown: {fileID: 0}
1546 | m_SelectOnLeft: {fileID: 0}
1547 | m_SelectOnRight: {fileID: 0}
1548 | m_Transition: 1
1549 | m_Colors:
1550 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
1551 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
1552 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
1553 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
1554 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
1555 | m_ColorMultiplier: 1
1556 | m_FadeDuration: 0.1
1557 | m_SpriteState:
1558 | m_HighlightedSprite: {fileID: 0}
1559 | m_PressedSprite: {fileID: 0}
1560 | m_SelectedSprite: {fileID: 0}
1561 | m_DisabledSprite: {fileID: 0}
1562 | m_AnimationTriggers:
1563 | m_NormalTrigger: Normal
1564 | m_HighlightedTrigger: Highlighted
1565 | m_PressedTrigger: Pressed
1566 | m_SelectedTrigger: Selected
1567 | m_DisabledTrigger: Disabled
1568 | m_Interactable: 1
1569 | m_TargetGraphic: {fileID: 1779326923}
1570 | m_OnClick:
1571 | m_PersistentCalls:
1572 | m_Calls:
1573 | - m_Target: {fileID: 638370369}
1574 | m_TargetAssemblyTypeName: Buck.SaveAsyncExample.GameManagerExample, Assembly-CSharp
1575 | m_MethodName: EraseGameData
1576 | m_Mode: 1
1577 | m_Arguments:
1578 | m_ObjectArgument: {fileID: 0}
1579 | m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
1580 | m_IntArgument: 0
1581 | m_FloatArgument: 0
1582 | m_StringArgument:
1583 | m_BoolArgument: 0
1584 | m_CallState: 2
1585 | --- !u!114 &1779326923
1586 | MonoBehaviour:
1587 | m_ObjectHideFlags: 0
1588 | m_CorrespondingSourceObject: {fileID: 0}
1589 | m_PrefabInstance: {fileID: 0}
1590 | m_PrefabAsset: {fileID: 0}
1591 | m_GameObject: {fileID: 1779326920}
1592 | m_Enabled: 1
1593 | m_EditorHideFlags: 0
1594 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
1595 | m_Name:
1596 | m_EditorClassIdentifier:
1597 | m_Material: {fileID: 0}
1598 | m_Color: {r: 1, g: 1, b: 1, a: 1}
1599 | m_RaycastTarget: 1
1600 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1601 | m_Maskable: 1
1602 | m_OnCullStateChanged:
1603 | m_PersistentCalls:
1604 | m_Calls: []
1605 | m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
1606 | m_Type: 1
1607 | m_PreserveAspect: 0
1608 | m_FillCenter: 1
1609 | m_FillMethod: 4
1610 | m_FillAmount: 1
1611 | m_FillClockwise: 1
1612 | m_FillOrigin: 0
1613 | m_UseSpriteMesh: 0
1614 | m_PixelsPerUnitMultiplier: 1
1615 | --- !u!222 &1779326924
1616 | CanvasRenderer:
1617 | m_ObjectHideFlags: 0
1618 | m_CorrespondingSourceObject: {fileID: 0}
1619 | m_PrefabInstance: {fileID: 0}
1620 | m_PrefabAsset: {fileID: 0}
1621 | m_GameObject: {fileID: 1779326920}
1622 | m_CullTransparentMesh: 1
1623 | --- !u!1 &1815900018
1624 | GameObject:
1625 | m_ObjectHideFlags: 0
1626 | m_CorrespondingSourceObject: {fileID: 0}
1627 | m_PrefabInstance: {fileID: 0}
1628 | m_PrefabAsset: {fileID: 0}
1629 | serializedVersion: 6
1630 | m_Component:
1631 | - component: {fileID: 1815900019}
1632 | - component: {fileID: 1815900021}
1633 | - component: {fileID: 1815900022}
1634 | m_Layer: 5
1635 | m_Name: Save Async - Header Text
1636 | m_TagString: Untagged
1637 | m_Icon: {fileID: 0}
1638 | m_NavMeshLayer: 0
1639 | m_StaticEditorFlags: 0
1640 | m_IsActive: 1
1641 | --- !u!224 &1815900019
1642 | RectTransform:
1643 | m_ObjectHideFlags: 0
1644 | m_CorrespondingSourceObject: {fileID: 0}
1645 | m_PrefabInstance: {fileID: 0}
1646 | m_PrefabAsset: {fileID: 0}
1647 | m_GameObject: {fileID: 1815900018}
1648 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1649 | m_LocalPosition: {x: 0, y: 0, z: 0}
1650 | m_LocalScale: {x: 1, y: 1, z: 1}
1651 | m_ConstrainProportionsScale: 0
1652 | m_Children: []
1653 | m_Father: {fileID: 2120769809}
1654 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1655 | m_AnchorMin: {x: 0, y: 1}
1656 | m_AnchorMax: {x: 1, y: 1}
1657 | m_AnchoredPosition: {x: 0, y: -69}
1658 | m_SizeDelta: {x: 0, y: 138}
1659 | m_Pivot: {x: 0.5, y: 0.5}
1660 | --- !u!222 &1815900021
1661 | CanvasRenderer:
1662 | m_ObjectHideFlags: 0
1663 | m_CorrespondingSourceObject: {fileID: 0}
1664 | m_PrefabInstance: {fileID: 0}
1665 | m_PrefabAsset: {fileID: 0}
1666 | m_GameObject: {fileID: 1815900018}
1667 | m_CullTransparentMesh: 1
1668 | --- !u!114 &1815900022
1669 | MonoBehaviour:
1670 | m_ObjectHideFlags: 0
1671 | m_CorrespondingSourceObject: {fileID: 0}
1672 | m_PrefabInstance: {fileID: 0}
1673 | m_PrefabAsset: {fileID: 0}
1674 | m_GameObject: {fileID: 1815900018}
1675 | m_Enabled: 1
1676 | m_EditorHideFlags: 0
1677 | m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
1678 | m_Name:
1679 | m_EditorClassIdentifier:
1680 | m_Material: {fileID: 0}
1681 | m_Color: {r: 1, g: 1, b: 1, a: 1}
1682 | m_RaycastTarget: 1
1683 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1684 | m_Maskable: 1
1685 | m_OnCullStateChanged:
1686 | m_PersistentCalls:
1687 | m_Calls: []
1688 | m_FontData:
1689 | m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
1690 | m_FontSize: 100
1691 | m_FontStyle: 0
1692 | m_BestFit: 0
1693 | m_MinSize: 1
1694 | m_MaxSize: 199
1695 | m_Alignment: 4
1696 | m_AlignByGeometry: 0
1697 | m_RichText: 1
1698 | m_HorizontalOverflow: 0
1699 | m_VerticalOverflow: 0
1700 | m_LineSpacing: 1
1701 | m_Text: Save Async
1702 | --- !u!1 &1995397476
1703 | GameObject:
1704 | m_ObjectHideFlags: 0
1705 | m_CorrespondingSourceObject: {fileID: 0}
1706 | m_PrefabInstance: {fileID: 0}
1707 | m_PrefabAsset: {fileID: 0}
1708 | serializedVersion: 6
1709 | m_Component:
1710 | - component: {fileID: 1995397477}
1711 | - component: {fileID: 1995397480}
1712 | - component: {fileID: 1995397479}
1713 | - component: {fileID: 1995397478}
1714 | m_Layer: 5
1715 | m_Name: Delete Game Data
1716 | m_TagString: Untagged
1717 | m_Icon: {fileID: 0}
1718 | m_NavMeshLayer: 0
1719 | m_StaticEditorFlags: 0
1720 | m_IsActive: 1
1721 | --- !u!224 &1995397477
1722 | RectTransform:
1723 | m_ObjectHideFlags: 0
1724 | m_CorrespondingSourceObject: {fileID: 0}
1725 | m_PrefabInstance: {fileID: 0}
1726 | m_PrefabAsset: {fileID: 0}
1727 | m_GameObject: {fileID: 1995397476}
1728 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
1729 | m_LocalPosition: {x: 0, y: 0, z: 0}
1730 | m_LocalScale: {x: 1, y: 1, z: 1}
1731 | m_ConstrainProportionsScale: 0
1732 | m_Children:
1733 | - {fileID: 707914463}
1734 | m_Father: {fileID: 1533276811}
1735 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1736 | m_AnchorMin: {x: 0, y: 0}
1737 | m_AnchorMax: {x: 0, y: 0}
1738 | m_AnchoredPosition: {x: 0, y: 0}
1739 | m_SizeDelta: {x: 0, y: 0}
1740 | m_Pivot: {x: 0.5, y: 0.5}
1741 | --- !u!114 &1995397478
1742 | MonoBehaviour:
1743 | m_ObjectHideFlags: 0
1744 | m_CorrespondingSourceObject: {fileID: 0}
1745 | m_PrefabInstance: {fileID: 0}
1746 | m_PrefabAsset: {fileID: 0}
1747 | m_GameObject: {fileID: 1995397476}
1748 | m_Enabled: 1
1749 | m_EditorHideFlags: 0
1750 | m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
1751 | m_Name:
1752 | m_EditorClassIdentifier:
1753 | m_Navigation:
1754 | m_Mode: 3
1755 | m_WrapAround: 0
1756 | m_SelectOnUp: {fileID: 0}
1757 | m_SelectOnDown: {fileID: 0}
1758 | m_SelectOnLeft: {fileID: 0}
1759 | m_SelectOnRight: {fileID: 0}
1760 | m_Transition: 1
1761 | m_Colors:
1762 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
1763 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
1764 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
1765 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
1766 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
1767 | m_ColorMultiplier: 1
1768 | m_FadeDuration: 0.1
1769 | m_SpriteState:
1770 | m_HighlightedSprite: {fileID: 0}
1771 | m_PressedSprite: {fileID: 0}
1772 | m_SelectedSprite: {fileID: 0}
1773 | m_DisabledSprite: {fileID: 0}
1774 | m_AnimationTriggers:
1775 | m_NormalTrigger: Normal
1776 | m_HighlightedTrigger: Highlighted
1777 | m_PressedTrigger: Pressed
1778 | m_SelectedTrigger: Selected
1779 | m_DisabledTrigger: Disabled
1780 | m_Interactable: 1
1781 | m_TargetGraphic: {fileID: 1995397479}
1782 | m_OnClick:
1783 | m_PersistentCalls:
1784 | m_Calls:
1785 | - m_Target: {fileID: 638370369}
1786 | m_TargetAssemblyTypeName: Buck.SaveAsyncExample.GameManagerExample, Assembly-CSharp
1787 | m_MethodName: DeleteGameData
1788 | m_Mode: 1
1789 | m_Arguments:
1790 | m_ObjectArgument: {fileID: 0}
1791 | m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
1792 | m_IntArgument: 0
1793 | m_FloatArgument: 0
1794 | m_StringArgument:
1795 | m_BoolArgument: 0
1796 | m_CallState: 2
1797 | --- !u!114 &1995397479
1798 | MonoBehaviour:
1799 | m_ObjectHideFlags: 0
1800 | m_CorrespondingSourceObject: {fileID: 0}
1801 | m_PrefabInstance: {fileID: 0}
1802 | m_PrefabAsset: {fileID: 0}
1803 | m_GameObject: {fileID: 1995397476}
1804 | m_Enabled: 1
1805 | m_EditorHideFlags: 0
1806 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
1807 | m_Name:
1808 | m_EditorClassIdentifier:
1809 | m_Material: {fileID: 0}
1810 | m_Color: {r: 1, g: 1, b: 1, a: 1}
1811 | m_RaycastTarget: 1
1812 | m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
1813 | m_Maskable: 1
1814 | m_OnCullStateChanged:
1815 | m_PersistentCalls:
1816 | m_Calls: []
1817 | m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
1818 | m_Type: 1
1819 | m_PreserveAspect: 0
1820 | m_FillCenter: 1
1821 | m_FillMethod: 4
1822 | m_FillAmount: 1
1823 | m_FillClockwise: 1
1824 | m_FillOrigin: 0
1825 | m_UseSpriteMesh: 0
1826 | m_PixelsPerUnitMultiplier: 1
1827 | --- !u!222 &1995397480
1828 | CanvasRenderer:
1829 | m_ObjectHideFlags: 0
1830 | m_CorrespondingSourceObject: {fileID: 0}
1831 | m_PrefabInstance: {fileID: 0}
1832 | m_PrefabAsset: {fileID: 0}
1833 | m_GameObject: {fileID: 1995397476}
1834 | m_CullTransparentMesh: 1
1835 | --- !u!1 &2019224793
1836 | GameObject:
1837 | m_ObjectHideFlags: 0
1838 | m_CorrespondingSourceObject: {fileID: 0}
1839 | m_PrefabInstance: {fileID: 0}
1840 | m_PrefabAsset: {fileID: 0}
1841 | serializedVersion: 6
1842 | m_Component:
1843 | - component: {fileID: 2019224796}
1844 | - component: {fileID: 2019224795}
1845 | - component: {fileID: 2019224797}
1846 | m_Layer: 0
1847 | m_Name: EventSystem
1848 | m_TagString: Untagged
1849 | m_Icon: {fileID: 0}
1850 | m_NavMeshLayer: 0
1851 | m_StaticEditorFlags: 0
1852 | m_IsActive: 1
1853 | --- !u!114 &2019224795
1854 | MonoBehaviour:
1855 | m_ObjectHideFlags: 0
1856 | m_CorrespondingSourceObject: {fileID: 0}
1857 | m_PrefabInstance: {fileID: 0}
1858 | m_PrefabAsset: {fileID: 0}
1859 | m_GameObject: {fileID: 2019224793}
1860 | m_Enabled: 1
1861 | m_EditorHideFlags: 0
1862 | m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
1863 | m_Name:
1864 | m_EditorClassIdentifier:
1865 | m_FirstSelected: {fileID: 0}
1866 | m_sendNavigationEvents: 1
1867 | m_DragThreshold: 10
1868 | --- !u!4 &2019224796
1869 | Transform:
1870 | m_ObjectHideFlags: 0
1871 | m_CorrespondingSourceObject: {fileID: 0}
1872 | m_PrefabInstance: {fileID: 0}
1873 | m_PrefabAsset: {fileID: 0}
1874 | m_GameObject: {fileID: 2019224793}
1875 | serializedVersion: 2
1876 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
1877 | m_LocalPosition: {x: 0, y: 0, z: 0}
1878 | m_LocalScale: {x: 1, y: 1, z: 1}
1879 | m_ConstrainProportionsScale: 0
1880 | m_Children: []
1881 | m_Father: {fileID: 0}
1882 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1883 | --- !u!114 &2019224797
1884 | MonoBehaviour:
1885 | m_ObjectHideFlags: 0
1886 | m_CorrespondingSourceObject: {fileID: 0}
1887 | m_PrefabInstance: {fileID: 0}
1888 | m_PrefabAsset: {fileID: 0}
1889 | m_GameObject: {fileID: 2019224793}
1890 | m_Enabled: 1
1891 | m_EditorHideFlags: 0
1892 | m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
1893 | m_Name:
1894 | m_EditorClassIdentifier:
1895 | m_SendPointerHoverToParent: 1
1896 | m_HorizontalAxis: Horizontal
1897 | m_VerticalAxis: Vertical
1898 | m_SubmitButton: Submit
1899 | m_CancelButton: Cancel
1900 | m_InputActionsPerSecond: 10
1901 | m_RepeatDelay: 0.5
1902 | m_ForceModuleActive: 0
1903 | --- !u!1 &2120769808
1904 | GameObject:
1905 | m_ObjectHideFlags: 0
1906 | m_CorrespondingSourceObject: {fileID: 0}
1907 | m_PrefabInstance: {fileID: 0}
1908 | m_PrefabAsset: {fileID: 0}
1909 | serializedVersion: 6
1910 | m_Component:
1911 | - component: {fileID: 2120769809}
1912 | - component: {fileID: 2120769810}
1913 | m_Layer: 5
1914 | m_Name: Container
1915 | m_TagString: Untagged
1916 | m_Icon: {fileID: 0}
1917 | m_NavMeshLayer: 0
1918 | m_StaticEditorFlags: 0
1919 | m_IsActive: 1
1920 | --- !u!224 &2120769809
1921 | RectTransform:
1922 | m_ObjectHideFlags: 0
1923 | m_CorrespondingSourceObject: {fileID: 0}
1924 | m_PrefabInstance: {fileID: 0}
1925 | m_PrefabAsset: {fileID: 0}
1926 | m_GameObject: {fileID: 2120769808}
1927 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
1928 | m_LocalPosition: {x: 0, y: 0, z: 0}
1929 | m_LocalScale: {x: 1, y: 1, z: 1}
1930 | m_ConstrainProportionsScale: 0
1931 | m_Children:
1932 | - {fileID: 1815900019}
1933 | - {fileID: 1056661432}
1934 | m_Father: {fileID: 346742333}
1935 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1936 | m_AnchorMin: {x: 0, y: 0}
1937 | m_AnchorMax: {x: 1, y: 1}
1938 | m_AnchoredPosition: {x: 0, y: 0}
1939 | m_SizeDelta: {x: 0, y: 0}
1940 | m_Pivot: {x: 0.5, y: 0.5}
1941 | --- !u!222 &2120769810
1942 | CanvasRenderer:
1943 | m_ObjectHideFlags: 0
1944 | m_CorrespondingSourceObject: {fileID: 0}
1945 | m_PrefabInstance: {fileID: 0}
1946 | m_PrefabAsset: {fileID: 0}
1947 | m_GameObject: {fileID: 2120769808}
1948 | m_CullTransparentMesh: 1
1949 | --- !u!1660057539 &9223372036854775807
1950 | SceneRoots:
1951 | m_ObjectHideFlags: 0
1952 | m_Roots:
1953 | - {fileID: 330585546}
1954 | - {fileID: 665154988}
1955 | - {fileID: 638370370}
1956 | - {fileID: 1664646201}
1957 | - {fileID: 687334403}
1958 | - {fileID: 346742333}
1959 | - {fileID: 2019224796}
1960 |
--------------------------------------------------------------------------------
/Samples~/SaveAsyncExample/Save Async Sample.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d043a47d93c8b65489f6e24c7a1acac5
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "co.buck.saveasync",
3 | "version": "0.4.3",
4 | "displayName": "Save Async",
5 | "description": "Save Async is BUCK's Unity package for asynchronously saving and loading data in the background using Unity's Awaitable class. Capture and restore state without interrupting Unity's main render thread.",
6 | "unity": "2023.1",
7 | "documentationUrl": "https://github.com/buck-co/unity-pkg-data-management",
8 | "keywords": [
9 | "BUCK",
10 | "Save",
11 | "Load",
12 | "Files",
13 | "I/O",
14 | "Data",
15 | "Async"
16 | ],
17 | "repository": {
18 | "url": "https://github.com/buck-co/unity-pkg-data-management.git",
19 | "type": "git"
20 | },
21 | "author": {
22 | "name": "BUCK",
23 | "url": "https://buck.co/"
24 | },
25 | "dependencies": {
26 | "com.unity.nuget.newtonsoft-json": "3.2.1"
27 | },
28 | "enableLockFile": true,
29 | "resolutionStrategy": "highest",
30 | "samples": [
31 | {
32 | "displayName": "Save Async Example",
33 | "description": "Contains a simple example scene that demonstrates the use of the SaveManager class and two implementations of the ISaveable interface.",
34 | "path": "Samples~/SaveAsyncExample"
35 | }
36 | ]
37 | }
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 661f9a28025884248b98a61f80bcf9d6
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------