Forum rules - please read before posting.

XBOX - is there any info on game saves, heard it is not player prefs on XBOX?

XBOX - is there any info on game saves, heard it is not player prefs on XBOX?

Comments

  • Details of the console platform's save system are provided by Microsoft once you've agreed to their developer NDA.

  • And to hook it up not using layer prefs? How does a that involved for AC please?
  • You can set the file handler for both save games, and options data, through script, e.g.:

    SaveSystem.SaveFileHandler = new SaveFileHandler_SystemFile ();
    Options.OptionsFileHandler = new OptionsFileHandler_SystemFile ();
    

    The above classes are included with AC. You can replace these with your own for custom behaviour.

    See the Manual's "Custom save formats and handling" chapter for details.

  • Hi Chris, let me elaborate further where we are and what are issue is:

    Hi Chris,

    We’re running into an issue integrating a custom iSaveFileHandler for our Xbox GDK build. We're on Adventure Creator v1.79 and are overriding the save system to work with platform-specific storage (rather than using PlayerPrefs). Our class implements iSaveFileHandler and works correctly on other platforms like Steam and PlayStation.

    In our setup:

    The game starts in an initial scene called XboxStartNew, which handles sign-in and runtime setup.

    • Adventure Creator is not present in this scene.

    • The Credits scene is the first scene that contains Adventure Creator.

    • In the Credits scene, we assign our custom save handler via this script:

      SaveSystem.SaveFileHandler = new XboxSaveFileHandler();

    This happens inside a script called InitXboxSaveHandler, which runs on Awake() and is placed in the scene.

    There is no autosave or save/load logic in the Credits scene itself.

    The first actual save call (via an ActionList) happens in the following scene — once the player starts the game from the menu.

    Despite this, the SavesList UI only shows a single black/empty slot where the autosave should be, and clicking it does nothing. Debug logs confirm that XboxSaveFileHandler is being assigned — but it seems like Adventure Creator isn’t recognizing or using it.

    Is it possible that AC’s PersistentEngine and save system are initializing before SaveSystem.SaveFileHandler is assigned — even though this happens in the same scene on Awake? Is there a better way to ensure the custom handler is registered before AC needs it?

    Thanks in advance for any help you can offer!

    Best,
    James

  • SaveFileHandler is a static variable - you should be able to set it even before AC is present in any scene.

    But you're saying this same setting is recognised in other platforms?

    Be aware that the Options handling uses PlayerPrefs by default as well - and it includes the Profile data that each save file is associated with. If your platform needs to rely on its own storage method instead of PlayerPrefs, you'll need to override that as well.

  • Hi Chris,

    Thanks — yes, to clarify:

    “But you're saying this same setting is recognised in other platforms?”

    Correct — on other platforms (Steam, PS4, PS5), Adventure Creator saving and loading works fine using the same SaveFileHandler override logic, but they rely on PlayerPrefs for Options and Profile handling, which we haven't overridden yet.

    On Xbox, since PlayerPrefs isn't supported for persistent storage, we're now overriding both:

    SaveSystem.SaveFileHandler with our XboxSaveFileHandler (for game saves)

    Options.OptionsFileHandler with a new XboxOptionsFileHandler (for profiles/options)

    We suspect the missing UI slots on Xbox were due to not overriding the Options handling, meaning AC couldn’t read the active profile and therefore returned no save slots. We're now testing with the options handler override in place to confirm if that resolves the issue.

    Will update shortly once confirmed!

    Thanks again,
    James

  • Quick update — we’ve now confirmed that both SaveFileHandler and OptionsFileHandler are being registered correctly on Xbox, and both are reading/writing data successfully via the Xbox GameSave system (confirmed via debug logs).

    However, even with both handlers in place, the SavesList menu still shows no available save slots on Xbox. The same system works fine on other platforms (PC, PS4, PS5), likely because they fall back to PlayerPrefs.

    Our current theory is that AC might still internally rely on PlayerPrefs for things like GetNumSlots() or profile resolution — even after custom handlers are set.

    Is there anything else we should be overriding or forcing to ensure AC fully respects the custom save and options handlers?

    Thanks again,
    James
  • Our current theory is that AC might still internally rely on PlayerPrefs for things like GetNumSlots() or profile resolution — even after custom handlers are set.

    The only references AC makes to PlayerPrefs directly is within the SaveFileHandler_PlayerPrefs and OptionsFileHandler scripts. You can insert logs into these to see if this is the case.

  • Hi Chris,

    Thanks for the pointer. We've been doing some detailed testing on Xbox using a custom save system built around the Xbox GDK’s GameSave API, integrated with AC's iSaveFileHandler interface.

    We’ve verified that the only references to it are in SaveFileHandler_PlayerPrefs and OptionsFileHandler, and we've overridden both via:

    A custom XboxSaveFileHandler.cs that implements iSaveFileHandler

    A custom XboxOptionsFileHandler.cs implementing iOptionsFileHandler

    ❗ What Works
    Save and load work fine during the same session.

    AC menus populate with the correct save slot data (label and ID).

    Screenshot saving is implemented, though not yet confirmed visible due to Xbox texture readback limits.

    🚫 What Fails
    After fully quitting the game and restarting, no save files are found.

    We suspect Xbox sandbox or save container persistence is the root cause, and are waiting on confirmation from Microsoft.

    Let us know if there’s anything else we should be checking within AC. The integration seems to behave correctly in-session, so we're fairly confident that the issue lies in the Xbox GDK save environment between sessions.

    Best,

  • Hi Chris,

    Here’s some updated info. We’ve been doing some in-depth testing on Xbox using a custom save system built around the Xbox GDK’s GameSave API, integrated with AC’s iSaveFileHandler interface.

    We’ve confirmed the only references to the default system are in SaveFileHandler_PlayerPrefs and OptionsFileHandler, and we’ve overridden both via:
    • A custom XboxSaveFileHandler.cs implementing iSaveFileHandler
    • A custom XboxOptionsFileHandler.cs implementing iOptionsFileHandler

    What’s Working:
    • Save and load function correctly during the same session
    • AC menus populate as expected with save slot labels and IDs
    • Screenshot saving is implemented (though visibility is still pending due to Xbox texture readback constraints)

    What’s Not Working:
    • After a full quit and relaunch of the game, no save files are found by AC

    We’ve verified that the blobs persist via the Xbox dashboard and the xbstorage export tool, so the data is being saved and retained—just not detected on reload. We suspect this may be related to Xbox’s sandbox or container behavior on relaunch and are awaiting further confirmation from Microsoft.

    If there’s anything you think we should be reviewing within AC itself—particularly around how save files are gathered or expected metadata—do let us know. In-session behavior appears correct, so we’re leaning toward an Xbox-side persistence issue.
  • What's your AC version? There were some minor fixes made related to the display of save-games in v1.83, but it does sound like your particular issue is outside of AC itself.

  • You may want to test the latest release, but you're likely all right with that version. IIRC the fixes I'm referring to were due to changes made in v1.80.

  • edited May 16
    Hi Chris,

    We’ve successfully implemented Xbox GDK support in our Unity game using a custom XboxSaveFileHandler and XboxOptionsFileHandler, both tied into Adventure Creator.

    What’s Working:
    • We are using a custom implementation of iSaveFileHandler (XboxSaveFileHandler) and iOptionsFileHandler (XboxOptionsFileHandler) to support Xbox Game Save via the Unity GDK.
    • Save files persist correctly across reboots. We can load, delete, and enumerate save blobs from the Xbox GameSave container (MainSaves), and they persist after quitting the game.
    • We are successfully registering the handlers via code after the PersistentEngine is initialized.

    The Issue:
    • Our custom XboxOptionsFileHandler successfully saves options to a blob (e.g., options_0) and logs confirm that submission to GameSave is successful.
    • However, after rebooting the game, the options are not restored. AC appears to fall back to default settings unless we manually call LoadOptionsAsync() and re-assign preferences in code.

    What We Think:

    From debugging, we believe the issue is that:
    • Options.LoadPrefs() calls LoadOptions() (sync) which checks an in-memory Dictionary<int, string> (profileCache) for saved data.
    • This dictionary is empty on reboot since the save is persisted in GameSave, not PlayerPrefs or local memory.
    • Therefore, the options file is saved, but not reloaded into memory unless we explicitly reload it async before Options.LoadPrefs() is called.

    Current Workaround (Experimental):

    We added this logic after sign-in

    Options.OptionsFileHandler = new XboxOptionsFileHandler();
    Options.OptionsFileHandler.LoadOptionsAsync(0, true, (loadedData) =>
    {
    Options.SetActiveProfileID(0);
    Options.LoadPrefs(); // Force AC to repopulate options from cache
    });

    This successfully restores the correct values, but we’re not sure if this is the recommended or safest method within AC’s framework.



    Our Question:

    Could you please advise:
    1. Is this the correct approach to support loading AC options from a custom handler after reboot?
    2. Is there a better point in the AC initialization flow to inject the async loading and repopulate preferences?
    3. Would it be possible in a future version of AC to allow async LoadOptions behavior or provide a hook before options are first accessed?

    We’d really appreciate your thoughts and validation, as this is the final piece in our Xbox save system integration.

    Many thanks,
    James
    Lightfoot Bros Games
  • I'm not following the need to wait until AC is initialised before manually populating your dictionary from the GameSave. You can define the handler instance earlier, do what's necessary, and then - in a later scene - load AC:

    var optionsFileHandler = new XboxOptionsFileHandler();
    optionsFileHandler.Initialise ();
    //
    Options.OptionsFileHandler = optionsFileHandler;
    
  • Hi Chris,

    Just wanted to share a final update with where we’ve landed:

    We’ve now got the Xbox save system fully working using our custom XboxSaveFileHandler, built around Xbox GDK’s GameSave API, and integrated with Adventure Creator v1.79.3.

    What works:

    Save/load/delete all work as expected in-session and across reboots.

    Save data is confirmed to persist using the Xbox dashboard and xbstorage.

    We're using a custom iSaveFileHandler and iOptionsFileHandler to handle both game saves and profile/options storage.

    AC menus correctly show save slot IDs and allow loading in-session.

    ** What’s left:**

    After reboot, slot labels are missing (e.g. Main Menu, scene name, date).

    These labels are generated at runtime using GetDefaultSaveLabel.

    We suspect AC may be calling GatherSaveFiles() before any metadata has been loaded or cached.

    Below is our current working implementation of XboxSaveFileHandler.cs:

    #if UNITY_GDK
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using UnityEngine;
    using Unity.XGamingRuntime;
    using AC;
    
    namespace WoolleyMountain
    {
        public class XboxSaveFileHandler : iSaveFileHandler
        {
            private const string ContainerName = "MainSaves";
    
            public void Save(SaveFile saveFile, string dataString, Action<bool> callback)
            {
                if (GdkXboxManager.Instance?.SaveManager == null)
                {
                    Debug.LogWarning("[XboxSaveFileHandler] SaveManager is null");
                    callback?.Invoke(false);
                    return;
                }
    
                byte[] data = Encoding.UTF8.GetBytes(dataString);
                string blobName = saveFile.saveID.ToString();
    
                GdkXboxManager.Instance.SaveManager.SaveGame(ContainerName, blobName, data, hr =>
                {
                    bool success = HR.SUCCEEDED(hr);
                    Debug.Log("[XboxSaveFileHandler] Save " + (success ? "successful" : "failed") + " for blob " + blobName);
                    callback?.Invoke(success);
                });
            }
    
            public void Load(SaveFile saveFile, bool showLog, Action<SaveFile, string> callback)
            {
                if (GdkXboxManager.Instance?.SaveManager == null)
                {
                    Debug.LogWarning("[XboxSaveFileHandler] SaveManager is null");
                    callback?.Invoke(saveFile, string.Empty);
                    return;
                }
    
                string blobName = saveFile.saveID.ToString();
    
                GdkXboxManager.Instance.SaveManager.LoadGame(ContainerName, blobName, (hr, data) =>
                {
                    if (HR.SUCCEEDED(hr) && data != null)
                    {
                        string dataString = Encoding.UTF8.GetString(data);
                        callback?.Invoke(saveFile, dataString);
                    }
                    else
                    {
                        if (showLog)
                            Debug.LogWarning("[XboxSaveFileHandler] Failed to load save for blob: " + blobName);
                        callback?.Invoke(saveFile, string.Empty);
                    }
                });
            }
    
            public void Delete(SaveFile saveFile, Action<bool> callback)
            {
                if (GdkXboxManager.Instance?.SaveManager == null)
                {
                    Debug.LogWarning("[XboxSaveFileHandler] SaveManager is null");
                    callback?.Invoke(false);
                    return;
                }
    
                string blobName = saveFile.saveID.ToString();
    
                GdkXboxManager.Instance.SaveManager.DeleteBlob(ContainerName, blobName, hr =>
                {
                    bool success = HR.SUCCEEDED(hr);
                    Debug.Log("[XboxSaveFileHandler] Delete " + (success ? "successful" : "failed") + " for blob " + blobName);
                    callback?.Invoke(success);
                });
            }
    
            public List<SaveFile> GatherSaveFiles(int profileID)
            {
                List<SaveFile> saves = new List<SaveFile>();
                var blobs = GdkXboxManager.Instance?.SaveManager?.QueryContainerBlobs();
    
                if (blobs != null)
                {
                    foreach (var blob in blobs)
                    {
                        if (int.TryParse(blob.Name, out int saveID))
                        {
                            var save = new SaveFile(saveID, saveID, blob.Name, GetDefaultSaveLabel(saveID), null, string.Empty, 0);
                            saves.Add(save);
                        }
                    }
                }
    
                return saves;
            }
    
            public SaveFile GetSaveFile(int profileID, int saveID)
            {
                return new SaveFile(saveID, saveID, saveID.ToString(), GetDefaultSaveLabel(saveID), null, string.Empty, 0);
            }
    
            public List<SaveFile> GatherImportFiles(int profileID, int maxFiles, string extension, string label)
            {
                return new List<SaveFile>();
            }
    
            public string GetDefaultSaveLabel(int saveID)
            {
                string sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
                string friendlyName = GetFriendlySceneName(sceneName);
                string dateTime = System.DateTime.Now.ToString("dd/MM/yyyy HH:mm");
    
                if (saveID == 0)
                {
                    return $"AUTOSAVE - {friendlyName.ToUpper()} [{dateTime}]";
                }
    
                return $"{friendlyName.ToUpper()} [{dateTime}]";
            }
    
            private string GetFriendlySceneName(string sceneName)
            {
                if (sceneName == "Title Screen 1") return "Main Menu";
                if (sceneName == "Start Screen") return "Woolley Mountain";
                if (sceneName == "Start Screen Part 3") return "Woolley Mountain Part 3";
                if (sceneName == "Control Room Land Ahoy") return "Control Room Part 2";
                if (sceneName.StartsWith("Bertie's Lair")) return "Bertie's Lair";
                return sceneName;
            }
    
            public void DeleteAll(int profileID)
            {
                var blobs = GdkXboxManager.Instance?.SaveManager?.QueryContainerBlobs();
                if (blobs != null)
                {
                    foreach (var blob in blobs)
                    {
                        GdkXboxManager.Instance.SaveManager.DeleteBlob(ContainerName, blob.Name, hr =>
                        {
                            Debug.Log("[XboxSaveFileHandler] Delete result: 0x" + hr.ToString("X") + " for blob " + blob.Name);
                        });
                    }
                }
            }
    
            public bool SupportsSaveThreading() => false;
    
            public void SaveScreenshot(SaveFile saveFile) { /* Disabled */ }
        }
    }
    #endif
    

    Let us know if you have any advice on how we can get save labels to persist across sessions. At the moment, the system works perfectly in-game — labels are generated and displayed correctly — but once the game is closed and reopened, the slots revert to just their save IDs (e.g. 0, 1, 2). We're not sure if this is something AC expects us to cache manually, or if we need to build a manifest/blob system for labels, or even request guidance from Xbox directly.

    Any tips or recommendations would be hugely appreciated.

    Thanks again for all your help!

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.