0
\$\begingroup\$

At this point, I don't have any idea of what could the issue be. So, I was trying to make a project wide loading screen. Through an editor window, I assigned the loading scene, which was picked up via script and assigned to the code base, from where it was loaded each time the loading screen was called.

First version I wrote worked almost perfectly. The problem was that for some strange reason it didn't work in the builds. I never got to the root of the problem so I decided to use another method. Now the problem got worse, because now it didn't work neither in the builds nor in the editor.

I knew working with scenes on editor was a very difficulty task, so I tried to use a custom serializable scene field class (This one, This one, and this one). None of them worked. Problem was that the scene I was trying to retrieve from editor (the loading scene) was always null. The only thing that could make sense in this case is that for some reason, these classes could not be serialized.

I thought maybe the Scriptableobject that had the fields referenced was the one that was null, but after some testing I was able to verify that this was not the case. So instead of using the custom class, I decided to just call the scene by its index. To my surprise, this didn't work either. While the scene was now not null (for obvious reasons), it was now invalid (as if the scene didn't exist).

The funny thing about all this is that actually, this scene is not the only field that I am referencing in the editor, in fact, I am referencing a scriptable object that for some reason has not given me any errors, unlike the scene (As far as I know, working with scriptable objects in the editor is even more tedious).

I'm here because I'm out of options, I have no idea what the problem is at this point.

These are my scripts: What's the problem with them?

// First script
public class SceneManagementProjectSettings : ScriptableObject
{
    private const string k_DefaultEventChannelPath = "Assets/Scripts/Project Wide/SceneManagement/CoroutineCallback.asset";

    [SerializeField] private int m_loadingSceneIndex;
    [SerializeField] private CoroutineChannel m_coroutineChannel;

    public int LoadingSceneIndex { get { return m_loadingSceneIndex; } }
    public CoroutineChannel CoroutineChannel { get { return m_coroutineChannel; } }

    public SceneManagementManager Manager => _manager;
    private SceneManagementManager _manager = new SceneManagementManager();

    internal static SceneManagementProjectSettings GetOrCreateSettings()
    {
        var settings = AssetDatabase.LoadAssetAtPath<SceneManagementProjectSettings>(SceneManagementSettingsProvider.k_SettingsPath);
        if (settings == null)
        {
            settings = CreateInstance<SceneManagementProjectSettings>();
            settings.m_loadingSceneIndex = 0;
            settings.m_coroutineChannel = AssetDatabase.LoadAssetAtPath<CoroutineChannel>(k_DefaultEventChannelPath);
            AssetDatabase.CreateAsset(settings, SceneManagementSettingsProvider.k_SettingsPath);
            AssetDatabase.SaveAssets();
        }
        return settings;
    }

    internal static SerializedObject GetSerializedSettings()
    {
        return new SerializedObject(GetOrCreateSettings());
    }
}

class SceneManagementSettingsProvider : SettingsProvider
{
    public const string k_SettingsPath = "Assets/Resources/SceneManagementSettings.asset";
    private SerializedObject m_SMSettings;

    class Styles {
        public static GUIContent LoadingScreenField = new GUIContent("Loading Scene Index");
        public static GUIContent CoroutineEventField = new GUIContent("Coroutine Channel");
    }

    public SceneManagementSettingsProvider(string path, SettingsScope scope = SettingsScope.Project) : base(path, scope) { }
    public static bool IsSettingsAvailable() {
        return File.Exists(k_SettingsPath);
    }

    public override void OnActivate(string searchContext, VisualElement rootElement)
    {
        m_SMSettings = SceneManagementProjectSettings.GetSerializedSettings();
    }

    public override void OnGUI(string searchContext)
    {
        m_SMSettings.Update();

        EditorGUILayout.PropertyField(m_SMSettings.FindProperty("m_loadingSceneIndex"), Styles.LoadingScreenField);
        EditorGUILayout.PropertyField(m_SMSettings.FindProperty("m_coroutineChannel"), Styles.CoroutineEventField);

        m_SMSettings.ApplyModifiedProperties();
    }

    [SettingsProvider]
    public static SettingsProvider CreateSceneManagementSettingsProvider()
    {
        if (IsSettingsAvailable())
        {
            var provider = new SceneManagementSettingsProvider("Project/Scene Management", SettingsScope.Project);

            // Automatically extract all keywords from the Styles.
            provider.keywords = GetSearchKeywordsFromGUIContentProperties<Styles>();
            return provider;
        }

        // Settings Asset doesn't exist yet; no need to display anything in the Settings window.
        return null;
    }
}


// Second script
public static class SceneManagement
{
    public const string k_SettingsPath = "Assets/Resources/SceneManagementSettings.asset";

    public static SceneManagementProjectSettings ProjectSettings =>
        _projectSettings;
    private static SceneManagementProjectSettings _projectSettings;

    public static SceneManagementManager Manager =>
        _manager ?? (_manager = ProjectSettings.Manager);
    private static SceneManagementManager _manager = new SceneManagementManager();

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
    static void Init()
    {
        if (ProjectSettings != null)
        {
            Manager.SetLoadingScene(ProjectSettings.LoadingSceneIndex);
            Manager.SetCoroutineChannel(ProjectSettings.CoroutineChannel);
        }
        else 
        {
            _projectSettings = GetProjectSettings();
            if (ProjectSettings == null) 
            {
                Debug.Log("<color=red>Project Settings Not Found</color>");
            }
        }

        Application.quitting += CleanUp;
    }

    static SceneManagementProjectSettings GetProjectSettings() {
        SceneManagementProjectSettings settings;

       #if UNITY_EDITOR
        var allSettings =
            AssetDatabase.FindAssets($"t:{nameof(SceneManagementProjectSettings)}").
                Select(AssetDatabase.GUIDToAssetPath).
                Select(AssetDatabase.LoadAssetAtPath<SceneManagementProjectSettings>).ToArray();
        if (allSettings.Length == 0)
        {
            Debug.Log("<color=red>Error</color>");
            return null;
        }
        else 
        {
            settings = allSettings.First();
        }
        #else
        settings = Resources.FindObjectsOfTypeAll<SceneManagementProjectSettings>().FirstOrDefault();
        #endif
        if (settings == null)
        {
            Debug.Log("<color=red>Unable to retrieve or create Project Settings. Something went wrong during the process!</color>");
        }
        else 
        {
            Debug.Log("<color=green>Project Settings retrieved succesfully!</color>");
        }

        return settings;
    }

    static void CleanUp()
    {
        _manager = null;
    }
}
\$\endgroup\$
14
  • \$\begingroup\$ I don't fully get the situation you describe but are you trying to reference some scene hierarchy (incl components related) from a field in a project asset? \$\endgroup\$
    – Nikaas
    Feb 28, 2022 at 6:57
  • \$\begingroup\$ @Nikaas I'm trying to reference a particular scene from a field in a project asset. Since you can't easily use scenes as fields, I used some custom classes that allowed me to do that, but it didn't work (at least not with the project asset, it worked on other things). So I ended up using an int field as the scene index, but this also didn't work. \$\endgroup\$
    – YoshGJ
    Feb 28, 2022 at 8:21
  • \$\begingroup\$ Did you remember to add all the scenes to the build? \$\endgroup\$
    – Philipp
    Feb 28, 2022 at 8:56
  • \$\begingroup\$ @Philipp Yes, the scene is added to the build. \$\endgroup\$
    – YoshGJ
    Feb 28, 2022 at 9:05
  • 1
    \$\begingroup\$ I would try to minimize the moving parts. It seems like you have a lot of heavily OOP architected stuff in your script and other features which make it hard to follow the control flow. Have you tried seeing if you can just load a scene (that you've included in the build) by index? Without any managers, providers, Coroutines, event callbacks, or third party libraries. If that works, then you can begin adding your abstractions and other logic back on top one at a time, testing with every incremental layer of abstraction to pinpoint when things break. \$\endgroup\$
    – Charly
    Feb 28, 2022 at 9:48

1 Answer 1

1
\$\begingroup\$

I think this approach might be an anti-pattern to how Unity handles scenes. Scenes are introduced to Unity via Build Settings and are meant to be accessed via the SceneManagement API of Unity. Plus, the scene asset has nothing to do with the scene managed (loaded/unloaded...etc.) in Unity during gameplay. Direct access to scene files could be meant only for editor usage.

So, instead of a project-wide reference to scene assets, I suggest defining a project-wide reference to the identifier of the scene (i.e. build index or name) and using SceneManager.LoadScene(sceneName) to load the scene. You can also make use of the relevant API get the Scene object (i.e. SceneManager.GetSceneByBuildIndex() and SceneManager.GetSceneByName()).

Since scene indexes can change a lot during development, I achieve this by creating an Enum of scene names that matches the names of scene assets and using SceneManager.LoadScene() anywhere in code.

// Same as the names of scene assets.
public enum SceneName
{
    ...
    LoadingScreenScene,
    MainMenuScene,
    Level1Scene,
    Level2Scene,
    ...
}

public class MyBehaviour : MonoBehaviour
{
    ...
    
    private void LoadOtherScene()
    {
        // This line works anywhere in the project.
        SceneManager.LoadScene(
            SceneName.LoadingScreenScene.ToString(), LoadSceneMode.Single);
    }
    
    ...
}

Then, you can write your own scene manager that displays the loading screen and loads the next screen in the background.

Edit: Appending an alternative to prevent using ToString() every time you refer to a scene name. Still I prefer using enum so that it is possible to expose it to editor (e.g. an editor script that launches the scene you pick).

// Same as the names of scene assets.
public struct SceneName
{
    ...
    public static string LoadingScreenScene => nameof(LoadingScreenScene);
    public static string MainMenuScene => nameof(MainMenuScene);
    ...
}

public class MyBehaviour : MonoBehaviour
{
    ...
    
    private void LoadOtherScene()
    {
        // This line works anywhere in the project.
        SceneManager.LoadScene(
            SceneName.LoadingScreenScene, LoadSceneMode.Single);
    }
    
    ...
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ This seems to be a reasonable conclusion. Interestingly, I had already replaced the reference to the scene asset with an indexer, and the problem still persisted (loading scene was not found). I think the only option left is to completely discard the project asset, although based on my testing this really isn't the root of the problem. \$\endgroup\$
    – YoshGJ
    Mar 1, 2022 at 20:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .