Hi! I’m so sorry, this has probably been covered a lot - but I can’t find anything to help me break through days of trial and error on this. I want to apologize in advance for any misunderstandings of terms in C# and Unity as I am a functional JS developer and this OOP stuff is HARD! I also lose cognitive function when I get frustrated, so I may be a bit incoherent… I super appreciate any of you taking a look at my question.
I created a GameManager using the Singleton pattern to manage state and am currently just saving state into Lists and what not in that instance. My question is: How do I rehydrate the scene when I come back to it after leaving?
This forum answer beautifully details the steps required to save data between scenes. My question is based on Step 2 of the load process outlined in that answer:
“Step 2 - iterate all data, reinject it into the running game. This might take multiple steps: iterate some data to figure out what scene(s) to load, then load that scene(s), then iterate the rest of the data to inject it into the running scene, possibly even creating additional GameObjects in the scene based on the save data.”
How do I “reinject” the data into the running game? Currently, I’m struggling because when the scene loads, all the game objects already exist and I have no way of referencing these existing game objects and adding the data back into them. (The game objects were created in the Unity GUI, I am not creating them programmatically.) So, the expected scene loads, but the game objects in that scene are not aware of the data that their previous incarnations had.
I have tried adding DontDestroyOnLoad(gameObject)
into each game object’s Awake
method, but this doesn’t let the properties of the attached class persist.
I have tried saving a reference to the game object instance ID and then attempting to find the game object instance ID when the scene changes, but it seems that these IDs change or perhaps the game objects change themselves in between scenes.
I have tried finding game objects by name (they all have unique names that I set in the Unity GUI) and then adding the data in from there, but nothing seems to happen when I attempt this (code below).
Here is some code that might be of interest to you:
Singleton GameManager
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public GameDataManager GameState;
public static event Action<GameStateTrigger> OnGameStateChanged;
void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
void Start()
{
GameState = new GameDataManager();
GameObject[] CollectablePrefabs = Resources.LoadAll<GameObject>("Prefabs");
GameState.SetupCollectables(CollectablePrefabs);
UpdateGameState(GameStateTrigger.Setup);
}
// ---------STATE UPDATES---------- //
public void UpdateGameState(GameStateTrigger newState)
{
GameState.UpdateState(newState);
switch (GameState.CurrentGameState)
{
case GameStateTrigger.Setup:
break;
case GameStateTrigger.PostSetup:
break;
case GameStateTrigger.PlayerCollectItem:
GameState.CheckCollectionCompleted();
break;
case GameStateTrigger.Win:
// show closing UI.
break;
default:
throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
}
OnGameStateChanged?.Invoke(newState);
}
// ---------HELPER CLASSES---------- //
public class GameDataManager
{
public GameStateTrigger CurrentGameState { get; private set; }
public List<SpawnableItem> AvailableCollectables { get; private set; }
public List<string> GoalItems { get; private set; }
public List<SpawnableItem> PlayerCollectedItems { get; private set; }
public Dictionary<string, ItemSpawnerData> spawners {get; private set; }
public GameDataManager()
{
GoalItems = new List<string> { "Star Fish", "Clown Fish", "Angel Fish" };
PlayerCollectedItems = new List<SpawnableItem>();
spawners = new Dictionary<string, ItemSpawnerData>();
CurrentGameState = GameStateTrigger.Initialized;
}
public void UpdateState(GameStateTrigger newState)
{
CurrentGameState = newState;
}
// ---------ACTIONS---------- //
public void SetupCollectables(GameObject[] CollectablePrefabs)
{
List<SpawnableItem> items = new List<SpawnableItem>();
foreach (var prefab in CollectablePrefabs)
{
items.Add(new SpawnableItem(prefab.name, prefab));
}
AvailableCollectables = items;
}
public void CollectItem(SpawnableItem item)
{
PlayerCollectedItems.Add(item);
}
public void CheckCollectionCompleted()
{
bool allFound = AvailableCollectables.All(item => PlayerCollectedItems.Contains(item));
if (allFound)
{
GameManager.Instance.UpdateGameState(GameStateTrigger.Win);
}
}
public void AddItemSpawner( GameObject spawner, GameObject Sparkles, SpawnableItem item, bool found)
{
spawners[spawner.name] = new ItemSpawnerData(spawner, Sparkles, item, found);
}
public ItemSpawnerData FindItemSpawner(string name)
{
ItemSpawnerData matchingSpawner = spawners[name];
return matchingSpawner;
}
}
public class ItemSpawnerData
{
public GameObject spawner;
public GameObject Sparkles;
public bool found;
public SpawnableItem item;
public ItemSpawnerData(GameObject spawner, GameObject Sparkles, SpawnableItem item, bool found)
{
this.spawner = spawner;
this.Sparkles = Sparkles;
this.item = item;
this.found = found;
}
}
}
// ---------ENUMS & INTERFACES ---------- //
public enum GameStateTrigger
{
Initialized,
Setup,
PostSetup,
PlayerCollectItem,
Win,
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEditor;
using System.Linq;
using UnityEngine.SceneManagement;
public class ItemSpawner : MonoBehaviour
{
System.Guid uuid;
GameObject Sparkles;
SpawnableItem item;
bool inCollision;
bool found;
// dev must assign Canvas GameObject as uiBox to each ItemSpawner.
public GameObject uiBox;
// dev must assign Canvas->Image->Button to acceptItemButton in each ItemSpawner.
public Button acceptItemButton;
void Awake()
{
GameManager.OnGameStateChanged += GameManager_OnGameStateChanged;
SceneManager.sceneLoaded += OnSceneLoaded;
DontDestroyOnLoad(gameObject);
}
void GameManager_OnGameStateChanged(GameStateTrigger newState)
{
switch (newState)
{
case GameStateTrigger.Setup:
uuid = System.Guid.NewGuid();
GameObject SparklesPrefab = Resources.Load<GameObject>("Prefabs/Sparkles");
Sparkles = Instantiate(SparklesPrefab, transform.position, Quaternion.identity);
item = GetRandomSpawnable(GameManager.Instance.GameState.AvailableCollectables);
found = false;
uiBox.SetActive(false);
GameManager.Instance.GameState.AddItemSpawner(gameObject, Sparkles, item, found);
GameManager.Instance.GameState.UpdateState(GameStateTrigger.PostSetup);
break;
case GameStateTrigger.PlayerCollectItem:
GameManager.Instance.GameState.CollectItem(item);
break;
}
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// Can't figure this part out. How can I load the data into each game object that has this script?
if (GameManager.Instance != null)
{
if (GameManager.Instance.GameState != null)
{
foreach (KeyValuePair<string, GameManager.ItemSpawnerData> kvp in GameManager.Instance.GameState.spawners)
{
Debug.Log($"Key: {kvp.Key}, Value: {kvp.Value.item.ItemName}");
}
Debug.Log($"current id: {uuid}");
GameManager.ItemSpawnerData spawner = GameManager.Instance.GameState.FindItemSpawner(gameObject.name);
// I think I am missing how to attach this spawner data to the real game object.
if (spawner != null)
{
Sparkles = spawner.Sparkles;
found = spawner.found;
item = spawner.item;
}
}
}
}
void Update()
{
if (Input.GetButtonDown("Submit") && inCollision && !found)
{
DisplayUIBox(item);
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Player")
{
inCollision = true;
}
}
public SpawnableItem GetRandomSpawnable(List<SpawnableItem> findableItems)
{
int random = Random.Range(0, findableItems.Count);
return findableItems.ElementAt(random);
}
void DisplayUIBox(SpawnableItem item)
{
uiBox.SetActive(true);
Image foundItemUI = GameObject.Find("FoundItemUI").GetComponent<Image>();
foundItemUI.sprite = item.Prefab.GetComponent<SpriteRenderer>().sprite;
GameObject foundItemText = GameObject.Find("FoundItemText");
foundItemText.GetComponent<TextMeshProUGUI>().text = "Would you like to take this " + item.ItemName + "?";
acceptItemButton.onClick.RemoveAllListeners();
acceptItemButton.onClick.AddListener(() => triggerCollectItem(item));
}
void triggerCollectItem(SpawnableItem item)
{
Debug.Log("triggered1");
GameManager.Instance.UpdateGameState(GameStateTrigger.PlayerCollectItem);
inCollision = false;
Sparkles.SetActive(false);
found = true;
uiBox.SetActive(false);
}
}
I super appreciate any pointers on where to look, what to Google, or how to think about this.