This sample demonstrates a complete file-based save system using Newtonsoft JSON within Luna. It shows how to implement a concrete save manager that works seamlessly with Luna's UI components.
Refer to Save System Framework to learn about the UI-agnostic save system architecture.
Key Features: Pluggable serializers, secure type allow-lists, fast metadata loading, and complete UI integration.
GameSaveManagerExample — concrete manager handling files, folders, and JSON IOGameSaveDataExample — example player/save data modelGameSaveMetadataExample — lightweight metadata for lists and previewsInitializeSerializerExample — safe serializer initialization with converters and type allow-listAdd the serializer initializer to your bootstrap scene:
_serializationManager.Initialize(
new Type[] {
typeof(GameSaveMetadata),
typeof(GameSaveMetadataExample),
typeof(GameSaveDataExample),
typeof(Inventory),
typeof(InventoryItem),
typeof(Potion),
typeof(Equipment),
},
new JsonConverter[] {
new Vector2IntConverter(),
new GenericDictionaryConverter()
},
new PrivateSetterContractResolver()
);Warning: Keep the type allow-list tight for security when using
TypeNameHandlingfeatures.
public class MyGameSaveData : IGameSaveData {
[JsonProperty(Order = -100)]
public GameSaveMetadata Metadata { get; set; }
public string PlayerName;
public int Gold;
public Inventory Inventory;
public MyGameSaveData() {
PlayerName = "Player";
Gold = 0;
Inventory = new Inventory();
}
public GameSaveMetadata CreateMetadata(string saveVersion, bool isAutosave) {
return new MyGameSaveMetadata {
SaveVersion = saveVersion,
SaveDate = DateTime.Now,
IsAutosave = isAutosave,
Gold = Gold,
};
}
public void LoadFrom(IGameSaveData other, int slot) {
var o = (MyGameSaveData)other;
Metadata = o.Metadata;
PlayerName = o.PlayerName;
Gold = o.Gold;
Inventory = o.Inventory;
}
}Define lightweight metadata for UI display:
public class MyGameSaveMetadata : GameSaveMetadata {
public int Gold;
}Info: Use
[JsonProperty(Order = -100)]onMetadataso it appears first in JSON for fast metadata reads.
Inherit from GameSaveManager<MyGameSaveData, MyGameSaveMetadata> and implement the JSON-specific methods:
public class MyGameSaveManager : GameSaveManager<MyGameSaveData, MyGameSaveMetadata> {
protected override string[] GetAllFileNames() {
// Enumerate .json files in save directory
return Directory.GetFiles(GetSaveDirectory(), "*.json")
.Select(Path.GetFileName)
.ToArray();
}
protected override MyGameSaveData GetNewSave(string saveVersion) {
return new MyGameSaveData();
}
protected override void OnSaveRequest(int saveSlot, string fileName,
MyGameSaveData data, bool autosave) {
// Serialize and write JSON file
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(Path.Combine(GetSaveDirectory(), fileName), json);
}
protected override MyGameSaveData LoadFromFile(string fileName) {
// Deserialize full save data
string json = File.ReadAllText(Path.Combine(GetSaveDirectory(), fileName));
return JsonConvert.DeserializeObject<MyGameSaveData>(json);
}
protected override MyGameSaveMetadata LoadMetadataFromFile(string fileName) {
// Fast path to read only metadata
using var reader = new JsonTextReader(new StringReader(
File.ReadAllText(Path.Combine(GetSaveDirectory(), fileName))));
while (reader.Read()) {
if (reader.TokenType == JsonToken.PropertyName &&
reader.Value?.ToString() == "Metadata") {
reader.Read();
return JsonConvert.DeserializeObject<MyGameSaveMetadata>(
reader.ReadAsString());
}
}
return null;
}
protected override void OnDeleteRequest(int saveSlot, string fileName) {
// Delete JSON file
File.Delete(Path.Combine(GetSaveDirectory(), fileName));
}
protected override string GetFileExtension() => "json";
protected override string GetSaveVersion() => "1.0";
}Wire your manager into Luna UI components by overriding the provider method:
public class MyMainMenuView : MainMenuView<MyGameSaveData, MyGameSaveMetadata> {
[SerializeField] private MyGameSaveManager saveManager;
protected override GameSaveManager<MyGameSaveData, MyGameSaveMetadata> GetSaveManager() {
return saveManager;
}
}Info: Once connected, UI buttons (Continue, Load, New Game, Delete) automatically work through your manager. No additional UI code needed!
Application.persistentDataPath/saves/.json (configured by your manager)GetSaveVersion() methodAdd encryption/decryption in your manager's save/load methods:
protected override void OnSaveRequest(int saveSlot, string fileName,
MyGameSaveData data, bool autosave) {
string json = JsonConvert.SerializeObject(data, Formatting.Indented);
byte[] encrypted = Encrypt(json); // Your encryption logic
File.WriteAllBytes(Path.Combine(GetSaveDirectory(), fileName), encrypted);
}
protected override MyGameSaveData LoadFromFile(string fileName) {
byte[] encrypted = File.ReadAllBytes(Path.Combine(GetSaveDirectory(), fileName));
string json = Decrypt(encrypted); // Your decryption logic
return JsonConvert.DeserializeObject<MyGameSaveData>(json);
}Replace file IO with cloud API calls while keeping the same UI interface:
protected override void OnSaveRequest(int saveSlot, string fileName,
MyGameSaveData data, bool autosave) {
string json = JsonConvert.SerializeObject(data);
CloudStorage.Upload(fileName, json); // Your cloud API
}
protected override MyGameSaveData LoadFromFile(string fileName) {
string json = CloudStorage.Download(fileName); // Your cloud API
return JsonConvert.DeserializeObject<MyGameSaveData>(json);
}Warning: No save files found
Ensure
Application.persistentDataPath/savesexists and the platform has write permissions.
Warning: Metadata shows null
Ensure
Metadatais serialized first with[JsonProperty(Order = -100)]and created before saving.
Warning: Deserialization errors
Update the serializer allow-list and add required converters for custom types.
Warning: UI buttons disabled
Verify your manager returns valid metadata from
GetLastMetadata()and the serializer is properly initialized.
[JsonProperty(Order = -100)] on Metadata for fast metadata readsInfo: Remember: The UI remains completely unchanged when you swap between JSON, binary, cloud, or encrypted storage. Only your manager implementation changes.
Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction