Create a script or multiple scripts that will handle all save menu functionality. For this example, we will create a single script called InfiniteSavesExample.cs. Start the script by adding the member variables that can be set from the inspector.
public class InfiniteSavesExample : MonoBehaviour
{
/// <summary>
/// The save slot prefab.
/// </summary>
[SerializeField]
private Button saveSlotPrefab;
/// <summary>
/// A button that creates a new save state.
/// </summary>
[SerializeField]
private Button createSaveButton;
/// <summary>
/// A button that deletes all save states.
/// </summary>
[SerializeField]
private Button deleteAllButton;
/// <summary>
/// Parent transform of save slots.
/// </summary>
[SerializeField]
private Transform content;
/// <summary>
/// Text that displays the time elapsed.
/// </summary>
[SerializeField]
private Text timeElapsedText;
/// <summary>
/// A toggle that controls the save/load mode. If toggled on, clicking on a save slot will load it. If toggled off,
/// clicking on a save slot will overwrite the save.
/// </summary>
[SerializeField]
private Toggle modeToggle;
}
Remember to add a GameObject to the scene and add this script to it. At this point, we should have everything to populate the inspector properties. Drag and drop each component to the relevant property.
Step 2: Other Script Members
We will create other members that won't be visible in the inspector.
public class InfiniteSavesExample : MonoBehaviour
{
/// <summary>
/// Time elapsed data ID.
/// </summary>
private const string timeElapsedID = "TimeElapsed";
/// <summary>
/// The save slot prefab.
/// </summary>
[SerializeField]
private Button saveSlotPrefab;
/// <summary>
/// A button that creates a new save state.
/// </summary>
[SerializeField]
private Button createSaveButton;
/// <summary>
/// A button that deletes all save states.
/// </summary>
[SerializeField]
private Button deleteAllButton;
/// <summary>
/// Parent transform of save slots.
/// </summary>
[SerializeField]
private Transform content;
/// <summary>
/// Text that displays the time elapsed.
/// </summary>
[SerializeField]
private Text timeElapsedText;
/// <summary>
/// A toggle that controls the save/load mode. If toggled on, clicking on a save slot will load it. If toggled off,
/// clicking on a save slot will overwrite the save.
/// </summary>
[SerializeField]
private Toggle modeToggle;
/// <summary>
/// A list of loaded save slots.
/// </summary>
private List<Button> slots = new();
/// <summary>
/// The amount of time elapsed.
/// </summary>
private float timeElapsed;
/// <summary>
/// If load mode is enabled.
/// </summary>
private bool loadMode { get => modeToggle.isOn; }
If load mode is true, we will make the save slots load a save. If it's false (save mode), we will make the save slots overwrite a save.
Step 3: Initialize ESave
You can either add the ESave Initializer component to your scene or add the code below to your script.
private void Awake()
{
// Initialize ESave
ESave.Initialize();
}
private void OnApplicationQuit()
{
// End database connection
ESave.Terminate();
// Unload active save if it exists
if (SaveState.active != null)
{
ESave.Unload();
}
}
The idea is just to initialize ESave on awake and terminate it on exit.
Step 4: Instantiate Existing Saves
When we enter play mode, we need to first load any existing saves that the player may have made in a previous session. We can do this in the Start method.
Also, save slot UI objects will need to be instantiated for this, so we'll create a method called CreateNewSaveSlot that will do just that.
private void Start()
{
// Get all save states
ESave.GetAllSaveStates().OnComplete(result =>
{
// Create slots for existing save states
foreach (var existingSave in result)
{
CreateNewSaveSlot(existingSave);
}
});
}
/// <summary>
/// Creates a save slot for a save state.
/// </summary>
/// <param name="saveState">The save state.</param>
public void CreateNewSaveSlot(SaveState saveState)
{
// Instantiate the save slot
var slot = Instantiate(saveSlotPrefab, content);
// Set the slot text
var slotText = slot.transform.GetChild(0).GetComponent<Text>();
slotText.text = $"Save Slot {slots.Count}";
// Move save creator button to the bottom
createSaveButton.transform.SetAsLastSibling();
// Add on-click event for loading
slot.onClick.AddListener(() => LoadOrOverwriteSave(saveState));
slots.Add(slot);
}
Step 5: Increment Time
In the Update method, we will increment the time elapsed. This will be the only data that will be saved and loaded for this example.
private void Update()
{
// Increment time per frame
timeElapsed += Time.deltaTime;
timeElapsedText.text = $"Time Elapsed: {timeElapsed:0.00}";
}
Step 6: Saving and Loading Data
We will create 3 methods for saving and loading. The first one will load the data from a save state.
/// <summary>
/// Loads a save.
/// </summary>
/// <param name="saveState">The save state.</param>
public void LoadSave(SaveState saveState)
{
saveState.GetData<float>(timeElapsedID).OnComplete(result =>
{
timeElapsed = result;
});
}
The second one will save (or overwrite) the data in the save file.
/// <summary>
/// Overwrites a save.
/// </summary>
/// <param name="saveState">The save state.</param>
public void OverwriteSave(SaveState saveState)
{
// Ensure it's the active save to allow editing
ESave.Load(saveState).OnComplete(result =>
{
// Save the time elapsed
result.AddData(timeElapsedID, timeElapsed).OnComplete(_ =>
{
// Confirm changes
ESave.Save(result);
});
});
}
The third one will be used by the save slots and will load or overwrite the data depending on the mode.
/// <summary>
/// Loads or overwrites the save state based on the active mode.
/// </summary>
/// <param name="saveState">The save state.</param>
public void LoadOrOverwriteSave(SaveState saveState)
{
if (loadMode)
{
LoadSave(saveState);
}
else
{
OverwriteSave(saveState);
}
}
Step 7: Create New Save
We have methods that require a save state, but no save state is being created yet. A save state should be created when the 'Create New Save' button is pressed, along with a save slot.
So, let's create a method for this.
/// <summary>
/// Creates a new save.
/// </summary>
public void CreateNewSave()
{
// Create a new save state
ESave.CreateSave().OnComplete(result =>
{
// Save the time elapsed
// Techincally, nothing is being overwitten at this stage since it is an empty save
OverwriteSave(result);
// Create the save slot
CreateNewSaveSlot(result);
});
}
Step 8: Delete All Saves
At this point, we can create saves, but we can't delete them. Let's add some code that will delete all saves that exist.
/// <summary>
/// Deletes all save states.
/// </summary>
public void DeleteAllSaves()
{
// Delete all save states
ESave.DeleteAllSaves().OnComplete(result =>
{
// Remove UI objects
foreach (var slot in slots)
{
Destroy(slot.gameObject);
}
slots.Clear();
});
}
Step 9: Final Button Events
We still need the create save button and the delete button to execute the correct methods on-click. This can be done by adding an Awake method. Exclude the initialization code if your using the ESave Initializer component.