GameSaveViewList<TSaveData, TSaveMetadata> renders the slot list for the GameSave system — load / overwrite / delete buttons, autosave/manual filter toggles, gamepad-friendly InputPrompts, and overwrite/delete confirmations through a global Confirmation Popup navigation destination. Subclass it for your save types and provide two slot-content slots (SlotOne, SlotTwo) per row.

The component requires both a PanelRenderer and a GameSaveView on the same GameObject ([RequireComponent] enforces this). The PanelRenderer delivers its tree asynchronously, so all element lookups run at the UILoaded milestone (via GameSaveView.WhenUILoaded) — no UIDocument is involved. The UXML must contain the named elements the abstract class queries:
| Element | Name | Purpose |
|---|---|---|
Button | NewSave | Create a new save slot. |
Toggle | ShowAuto | Filter: include autosaves. |
Toggle | ShowManual | Filter: include manual saves. |
InputPrompt | Load | Load selected slot. |
InputPrompt | Overwrite | Overwrite selected slot. |
InputPrompt | Delete | Delete selected slot. |
ListView | LoadList | Slot list. Items rendered from _listEntryTemplate. |
Also serialize a VisualTreeAsset _listEntryTemplate — Luna instantiates it per row and your SlotOne/SlotTwo overrides populate it.
Overwrite and Delete are destructive, so both go through a confirm popup. The inspector field _confirmDest (a CatalogKey into the nav-destination catalog) is required: the view pushes that destination with per-call ChoicePopupArgs ("Overwrite Save 3" / "Delete Save 3") via LunaNavigation.PushAsync<int> and acts only when the affirmative choice (index 0) comes back; Escape or a backdrop dismiss is a no-op. The acted-on slot is captured before the push, so changing the list selection while the popup is open can't retarget the action. Point it at one global ChoicePopupController destination — see Confirmation Popup.
Leaving _confirmDest empty is a configuration error: the view logs an error (at Awake and again on the button press) and refuses the overwrite/delete instead of acting unconfirmed. The editor also warns via OnValidate while the field is unset.
namespace CupkekGames.GameSave.Luna;
[RequireComponent(typeof(PanelRenderer))]
[RequireComponent(typeof(GameSaveView))]
public abstract class GameSaveViewList<TSaveData, TSaveMetadata> : MonoBehaviour
where TSaveData : IGameSaveData, IData, new()
where TSaveMetadata : GameSaveMetadata| Method | Description |
|---|---|
GetSaveManager() | Return your GameSaveManager<TSaveData, TSaveMetadata> instance. |
IsInGame() | Return true if a save can be written from here. Disables NewSave/Overwrite when false. |
SlotOne(int index, GameSaveMetadataWithSlot<TSaveMetadata> metadata) | Build the left-hand row content. |
SlotTwo(int index, GameSaveMetadataWithSlot<TSaveMetadata> metadata) | Build the right-hand row content. |
OnLoadButtonClicked() | Load the selected slot. Use GetSelectedMetadata() to read the row. |
GetTooltipController() | Return the scene's TooltipController (passed to each slot row for its tooltips). |
| Property | Type | Description |
|---|---|---|
GameSaveManager | GameSaveManager<TSaveData, TSaveMetadata> | Resolved during Awake(). |
GameSaveView | GameSaveView | The sibling view component on the same GameObject. |
using UnityEngine.UIElements;
using UnityEngine;
using CupkekGames.GameSave;
using CupkekGames.GameSave.Luna;
using CupkekGames.Luna;
using CupkekGames.SceneManagement;
using CupkekGames.Services;
using System;
namespace CupkekGames.Luna.Demo.Game.Full
{
public class GameSaveViewListExample : GameSaveViewList<GameFullSaveData, GameFullSaveMetadata>
{
protected override GameSaveManager<GameFullSaveData, GameFullSaveMetadata> GetSaveManager()
=> ServiceLocator.Get<GameFullSaveManager>();
protected override bool IsInGame()
=> !SceneLoader.Instance.IsLoaded(1); // MainMenu scene index
protected override VisualElement SlotOne(int index, GameSaveMetadataWithSlot<GameFullSaveMetadata> meta)
{
VisualElement container = new();
container.AddToClassList("flex-row");
VisualElement left = new();
VisualElement right = new();
container.Add(left);
container.Add(right);
left.Add(new Label("Index: " + index));
left.Add(new Label("File: " + meta.SaveSlot));
right.Add(new Label(meta.Metadata.SaveDate.ToString()));
right.Add(new Label(meta.Metadata.IsAutosave ? "Autosave" : "Manual Save"));
container.Add(new Label("Save Version: " + meta.Metadata.SaveVersion));
return container;
}
protected override VisualElement SlotTwo(int index, GameSaveMetadataWithSlot<GameFullSaveMetadata> meta)
=> new Label("Gold: " + meta.Metadata.Gold);
protected override void OnLoadButtonClicked()
{
var meta = GetSelectedMetadata();
if (!meta.HasValue) return;
try
{
// SetSessionSave (not a bare CurrentSave.Data assignment) also marks
// the save as chosen for this play session — see GameSave docs.
GameSaveManager.SetSessionSave(GameSaveManager.GetSave(meta.Value.SaveSlot));
}
catch (Exception e) { Debug.LogException(e, this); return; }
SceneLoader.Instance.LoadScene(2,
SceneTransitionDatabase.Instance.Transitions.GetValue("Fade"), true);
GameSaveView.ReturnClicked(); // pops the save/load view off the nav back stack
}
protected override TooltipController GetTooltipController()
=> Luna.Sample.Essentials.TooltipDatabaseExample.Instance.TooltipController;
}
}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