Carry object across scenes - How to prevent duplicates when scene loads

I have a few cubes that can be carried across scenes by the player.
Currently:

  1. When player pick up cube - move cube to persistent scene
  2. if released, check if original scene is loaded - move cube to original, else move to another scene(not persistent)
  3. if no other scene beside persistent scene is loaded (probably never the case by design) then cube stays in persistent scene

Problem:

  1. Cube duplicates when moving back to original scene, tried to use instance and destroy if there are replicates. This works with one moveable cube, but can’t find a way to work with multiple.
  2. I tried to create a list to reference the moveable cubes but I don’t know how to match two of the same cubes when a scene unloads then reloads again…

Example:


3 cubes in original scene

Player moves out of original scene and cube is held in persistent scene temporarily until player releases cube.

Player moves back to original scene with cube in hand, but a duplicate spawns because it doesn’t know it already exists.

What should happen is Cube1 should destroy itself when the same cube held by the player is moved back into scene.

OR if cube1 is in another scene, then also destroy itself. In other words if cube1 has left original scene in any way, then it should always destroy itself when player comes back.

I’m stuck trying to identify cube1 globally, because if I can tell the game that this object exists already somewhere in game, then it’ll apply to every other object that can be moved across scenes.

I’m trying not to leave a cluster of objects in persistent scene because I don’t know how many objects can be carried in the future, and since persistent scene always loads, it’ll cluster very quickly if say I pick up 1000 cubes and never touch 900 of them. Thank you.

One practical way to control it is give each of those objects a persistent unique identifier of some kind, then have a utility that manages the presence / absence of those items based on that unique identifier.

That manager could observe when scenes are loaded and decide what needs to be made, or if it’s already present.

1 Like

Are these cubes actually a part of the saved scene? If so, I’d consider making them prefabs. You instantiate the prefab when needed and if it does not already exist. You don’t instantiate if either it is not needed or it does already exist. So there should only be 1 of each at most.

1 Like

I’ll try out this method and see where it goes. I did think about assigning a GUID to it but I can’t wrap my head around how I’d tell the manager to notify the object to destroy/not instantiate itself when it’s in another scene that’s unloaded. Would keeping a bool on each item (bool hasInstantiatedOnceInLifetimeOfGame) be a good idea?

That makes sense, how would I keep track or notify this specific cube that there is an instance of this globally? I can get it to work with one cube, which simply destroys itself when there is one copy in the world. But how would I execute the same behavior with 3 of the same cube and destroy say index [1] of this list of moveable cubes?

When you place or instantiate an object into the game scene, a LOT of game designs will consider that object’s mere presence to be part of the game state, e.g, part of the game world.

This is usually fine for tons of applications.

But when things can persist longer, you need to separately track and reconcile these long-lived items.

Personally I like to destroy everything in the scene when I change scenes, then create the long-lived items afresh in the new scene, but like I said this requires a long lived items manager of some kind. Your game should be engineered to have a central repository of knowing what items should exist when.

2 Likes

You could keep a reference to the instantiated object on some singleton which keeps track of them for you, then you check with the singleton whether the object already exists or not.

You could keep static references to each object, so you just need to check if the static reference is null as to whether the object already exists or not.

You could give each object a unique tag, so you just do FindWithTag and if the result null it does not exist.

I’m sure there are other ways as well.

1 Like

Thanks for the feedback, this is what I have so far. Works but kind of hacky solution… Is there a way to compare if two gameobject from a list contains the same specific value? I’m searching for a way to compare the index.GUID in the list, and remove the duplicated index.GUID.gameObject. Not finding any results elsewhere…

//check if scene is loaded, then see if there is already item in the list
        if (SceneManager.GetSceneByName("TestLoading2").isLoaded && holdItemPrefabs.Count != 0)
        {
            //loop through the list and remove all the destroyed gameobjects
            for (int i = 0; i < holdItemPrefabs.Count; i++)
            {
                if(holdItemPrefabs[i] == null)
                {
                    holdItemPrefabs.Remove(holdItemPrefabs[i]);
                }
            }
        }

        //check if scene is loaded, then see if it's less than the number of max spawn
        if (SceneManager.GetSceneByName("TestLoading2").isLoaded && holdItemPrefabs.Count < 4)
        {
            SceneLoaded = SceneLoad.isLoaded;
            //loop through the num of max spawn amount and instantiate
            for (int i = 0; i < 4; i++)
            {
                //create offset for the spawnposition of object
                offset = new Vector3(0, i, 0);
                //instantiate the object
                GameObject cubeClone = Instantiate(holditemPrefab, spawnLocation.position + offset, Quaternion.identity);

                //set each item to it's own GUID
                cubeClone.GetComponent<HoldItemObject>().GUID = i;

                //move the cube to the loaded scene
                transform.parent = null;
                SceneManager.MoveGameObjectToScene(cubeClone, SceneManager.GetSceneByName("TestLoading2"));

                //add the clone to the list tracker
                holdItemPrefabs.Add(cubeClone);
              
            }
        }

        if(SceneManager.GetSceneByName("TestLoading2").isLoaded && holdItemPrefabs.Count > 4)
        {
           
//now here I need to remove the item that has duplicated GUID....
                //if(holdItemPrefabs[i] has GUID that equals another holdItemPrefabs[i])
                //remove itself from the list
            Destroy(holdItemPrefabs[holdItemPrefabs.Count - 1]);
        }

Current situation:
1) a persistent scene has a datamanager script that keeps a list of moveable cubes (empty at start)
2) cube instantiates when first entering scene, gets moved to the scene that’s loaded, and populate the list.
3) upon leaving, the list remains, but the cubes that are destroyed due to scene unloading will now be null in list
4) when reentering, the list remove all the null(destroyed cubes) and instantiate new ones
5) if cubes exceed the max amount, destroy the last index (preferably destroy the one with same GUID somehow…)

it works… but not a clean approach. I have a few concerns…
1) You said to keep a reference to see if object exists, but I need the cubes to be in a scene that will unload eventually as player moves around - unloading destroys the object, and the reference is no longer valid in global datamanager, so how do I keep track?
2) when I use a static reference, all the changes become unified, so I can’t destroy one cube specifically without destroying all instances. (Not that I know how…) So keeping a list/array that has unique static variable on each cube is one way i can think of…
3) using tag does work with GameObject.FindWithTag, but now I’d need to create thousands of tags for thousands of cubes. Then using FindWithTag to go through thousands of tags to compare them…surely that doesn’t sound appealing

I just thought of another shortcut way to do this is just make a single object marked as DontDestroyOnLoad() by a little script.

Every time you make a cube, parent it to this object.

None of those cubes will go away when scenes change.

At end of game (or start of new game) you can just explicitly destroy that CubeContainer object and they all go away.

That object would by definitely have the list of all cubes: as it’s children in the Transform… easy access!

In fact, it took about 60 seconds to write from my singleton example. Here is what I mean:

using UnityEngine;

public class CubeContainer : MonoBehaviour
{
    private static CubeContainer _Instance;
    public static CubeContainer Instance
    {
        get
        {
            if (!_Instance)
            {
                _Instance = new GameObject().AddComponent<CubeContainer>();
                _Instance.name = _Instance.GetType().ToString();
                DontDestroyOnLoad(_Instance.gameObject);
            }
            return _Instance;
        }
    }

    public void Register(GameObject go)
    {
        go.transform.SetParent(transform);
    }

    public void Unregister(GameObject go)
    {
        go.transform.SetParent(null);
    }

    public void GameOver()
    {
        Destroy(gameObject);
        _Instance = null;
    }
}

When you make a cube, Register() it, or Unregister() it, and when the game is over, call GameOver.

Just move the cubes around however you want, leaving them parented to the CubeContainer.

While we’re talking about lifetimes, these are my super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with Prefab used for predefined data:

These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}
1 Like

Thanks Kurt, I am aware I can keep all moveable cubes that way. It would’ve been in the persistent scene/DontDestroyOnLoad() if nothing else worked.

I mentioned above how this method may lead to future problems as there is no loading screen for this game except menu to in game. All loading happens in elevators/stairs/behind walls. The scenes are like zones in the map. I’m using world streaming as seen here. Perhaps they didn’t load scenes load/unload but rather gameobject enable/disable. Again, if I do that I’m essentially putting everything in one giant scene to load all at once (That’d solve any reference problem).

If using multiple scenes isn’t doable, then I guess the moveable cubes will have to be kept in the persistent scene.

Sorry, took me a little bit to figure out what you’re talking about with GUID’s. They don’t really sound like Guaranteed Unique ID’s if they are not necessarily unique :stuck_out_tongue:

I see what is probably a bug in the loop where you are removing null GameObjects from the list. You are iterating over a list you are modifying. This will cause the index numbers of the list to shift as you iterate. When I do that, I iterate over a copy of the list instead of the actual list to avoid that issue. Actually I usually just use .ToArray() to iterate over an array copy, just cause it is super easy.

But I just thought of a way that may be simpler. Just check if List.Contains(null) is true, if so do List.Remove(null), repeat until List.Contains(null) returns false. I don’t think I’ve ever done that before, but it probably works. I don’t know how it performs compared to iterating over the list, but if this is during a scene load it probably doesn’t matter even if it is slightly slower.

As far as all your GetComponent calls, you can avoid them by making the list of type HoldItemObject instead of GameObject. You just GetComponent once when you instantiate and you add that to the list. When you destroy you just destroy its .gameObject instead of itself.

As far as my earlier suggestions, I was under the mistaken impression you were dealing with literally 3 total cubes, not a variable number of them. With just 3 cubes the tags suggestion as well as individual static references is practical and easy to do, not so much in what you’re actually doing though. So forget that.

Back on GUID’s though, if they really are supposed to be “unique” I wouldn’t do it this way at all. Instead have each assign its own unique ID that is guaranteed unique.

public ulong GUID;
private static ulong lastGUID = 0UL;

void Awake
{
    lastGUID++;
    GUID = lastGUID;
}

I like to use ulong for these, because with int type it isn’t impossible you’ll hit max int and end up flipping negative if your game is ridiculous enough. But the same code works fine as type int instead.

You assign your GUID’s this way, in the HoldItemObject script, then you don’t even need to bother going through the list looking for duplicates, because there are no GUID duplicates. If you can’t keep yourself from changing the GUID’s in your other scripts, then maybe change it to this: :wink:

private ulong GUID;
private static ulong lastGUID = 0UL;

void Awake
{
    lastGUID++;
    GUID = lastGUID;
}

public ulong GetGUID()  //Other scripts check GUID here, so you can't be tempted to change them :p
{
    return GUID;
}

I may be misunderstanding, but it seems like you don’t need to move cubes to other scenes at all, just re-create them upon loading the next scene. The overall manager would be a static jagged array of bools: scene index, ints for cubes existing in that scene or not. If the int = -1, it doesn’t exist; otherwise, it’s the index of the type of cube you want (if there is more than one kind).
Maybe?

Thanks for all the feedback.
Update:

  1. tried HashSet, and Distinct().ToList() to remove duplicates, turns out they were never duplicates because instantiating makes them all unique. So doesn’t remove anything upon reentry/reload of scene
  2. tried @Joe-Censored 's GUID but not what this system needs. instantiate makes them generate GUID, yes but I don’t need a new GUID every time the scene reload/reentry of player. the GUID causes no object to be destroyed because game will never think there are duplicates even when player brings the same cube back.
  3. holdItemPrefabs.RemoveAll(item=>item == null); lambda works for removing null in list. List.Remove(null) does not.
  4. @seejayjames I do need to move the cubes to other scene because the player is actively holding the cube, moving it across different scenes. Unless there is a way to seamlessly re-create the cube player is holding at the exact same transform upon loading next scene, then that’s not viable.
  5. found this - in short, recommend save/load method. But using this for every time a scene(zone in this case) loads, unsure…

Currently:
I created a static holdingGUID to keep track of which cube the player is holding/held and I destroy the correct cube upon entry.
Destroy(holdItemPrefabs[HoldItemObject.holdingGUID + 1]);

While this works for moving one item outside the scene and back, it doesn’t work for moving more than one object outside the scene because it uses a naive solution to destroy the specific index cube due to how the logic works.

I’m wondering how Skyrim allows player to carry objects across the world and reloads it back where the player drops it when scene changes…will keep trying.

The single-scene solution might be good and would solve most of these problems (though of course there are drawbacks too). You can get pretty creative with parent/child stuff in the Hierarchy to keep things organized. Each “scene” could be a top-level object with everything in it, and any sub-objects could also be hidden/shown etc., as you no doubt have worked with. “Carrying” cubes to other “scenes” would just mean re-parenting.