Save system with Instantiated Prefabs

Hello, I’ve been working on a game where you can trade out AI companions to help you in battle, but I had some issues with saving them between scenes. The solution I came up with was to make a ScriptableObject that will have the AI roles (Active/Inactive for now) and an array of their SaveableEntity UUIDs stored in a Dictionary<Role, List.

When a new NPC is generated, the Prefab’s SaveableEntity UUID is blank, so it generates a new one and saves itself into the SO. When switching scenes, the LevelManager Awake() method will load up the SO and spawn in the active AI and set the UUID, which then gets picked up by the SavingSystem and restores the modified state. This seems like it will work for keeping a list of all the generated NPCs, although it’ll be a bit clunky to pass around lists and remove/add to the SO.

What are some other approaches to solving the problem of “Instantiate an NPC and make it persistent across multiple scenes”? The second part is to allow for saving copies of the same prefab independently, which you wouldn’t get by “equipping” a prefab to the player (I think).

I made a small combat prototype that uses the SO/Dictionary method, although I haven’t scripted out multiple NPCs yet. The NPC saves the equipment, the stamina/mana, and modifiers to the ATB gauge. You can see how it works here:

NPC Scriptable Object script
[CreateAssetMenu(fileName = "FollowerCollection", menuName = "Stats/Follower/Collection", order = 2)]
public class FollowerCollection : ScriptableObject
{
    [SerializeField] FollowerTypeList[] followers;

    Dictionary<FollowerPosition, FollowerRole> lookupTable = null;

    public FollowerRole GetFollowerIdentifier(FollowerPosition position)
    {
        BuildLookup();

        return lookupTable[position];
    }

    public void AddNewFollower(FollowerPosition position, FollowerRole role)
    {
        BuildLookup();

        foreach (FollowerTypeList followerType in followers)
        {
            if(followerType.Position == position)
            {
                followerType.Role.FollowerClass = role.FollowerClass;
                followerType.Role.Identifier = role.Identifier;
                lookupTable[position] = followerType.Role;
                return;
            }
        }
    }

    private void BuildLookup()
    {
        if(lookupTable != null) return;

        lookupTable = new Dictionary<FollowerPosition, FollowerRole>();

        foreach (FollowerTypeList followerType in followers)
        {
            string s = followerType.Role.Identifier;
            lookupTable[followerType.Position] = followerType.Role;
        }
    }
}

[System.Serializable]
class FollowerTypeList
{
    public FollowerPosition Position;
    public FollowerRole Role;
}

[System.Serializable]
public struct FollowerRole
{
    public CharacterClass FollowerClass;
    public string Identifier;
}
NPC Spawner Script
public class FollowerSpawner : MonoBehaviour
{
    [SerializeField] FollowerCollection followers;
    [SerializeField] BaseStats[] followerPrefabs;
    [SerializeField] Transform HUDTransform;

    GameObject followerGO = null;

    private void Awake()
    {
        FollowerRole companionToSpawn = new FollowerRole();
        companionToSpawn = followers.GetFollowerIdentifier(FollowerPosition.Combat);
        string followerUUID = companionToSpawn.Identifier;

        // If UUID is blank, spawn a random NPC from the prefabs and register it
        if(followerUUID == string.Empty)
        {
            int randomFollowerIndex = UnityEngine.Random.Range(0, followerPrefabs.Length);
            BaseStats selectedFollower = followerPrefabs[randomFollowerIndex];
            followerGO = Instantiate(selectedFollower, HUDTransform).gameObject;

            FollowerRole newCompanion = new FollowerRole();
            newCompanion.FollowerClass = followerGO.GetComponent<BaseStats>().GetClass();
            newCompanion.Identifier = followerGO.GetComponent<SaveableEntity>().GenerateNewUniqueIdentifier();

            followers.AddNewFollower(FollowerPosition.Combat, newCompanion);
        }
        else // Spawn NPC from SO Dictionary
        {
            foreach (BaseStats healClass in followerPrefabs)
            {
                if(companionToSpawn.FollowerClass == healClass.GetClass())
                {
                    followerGO = Instantiate(healClass, HUDTransform).gameObject;
                    followerGO.GetComponent<SaveableEntity>().SetUniqueIdentifier(followerUUID);
                }
            }
        }
    }
}
New Methods in SaveableEntity
public string GenerateNewUniqueIdentifier()
{
    if(uniqueIdentifier != "") return uniqueIdentifier;
    if (string.IsNullOrEmpty(uniqueIdentifier) || !IsUnique(uniqueIdentifier))
    {
        uniqueIdentifier = System.Guid.NewGuid().ToString();
    }

    globalLookup[uniqueIdentifier] = this;

    return uniqueIdentifier;
}

public void SetUniqueIdentifier(string newIdentifier)
{
    uniqueIdentifier = newIdentifier;
}
1 Like

I think this system will work for you. Make sure before you create/load a new companion that you SavingSystem.Save() the game first to lock in the stats on the old companion. This would be similar to what we do in a Portal jump… Save(), Load(), then Save() again.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.