[Unity2D independent/cooperative development] Realize the existence of recording items in different scenes, with: scene fade function and preload

learning target:

Hello everyone, I mean the truth. Today, let's talk about what you want to see, and learn how to record the existence of items in different scenes. What I want to express is that if an item disappears in the first scene , if you enter the second scene and go back to the first scene at this time, you will find that the disappeared items will return to their original places, this is because each time you load a scene, the scene when you run the game will change again. Instantiate it again, so what I have to do today is to use a data structure to give each item a unique GUID, and delete the data completely when it is destroyed.


Learning Content:

// Before the focus of this lesson, let's make a scene loading controller and create a new empty object

Create a new scene name under the Enums script:

public enum SceneName
{
    Scene1_Farm,
    Scene2_Field,
    Scene3_Cabin,

}

SceneControllerManager and give it the script of the same name, with the following content:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class SceneControllerManager : Singleton<SceneControllerManager>
{
    private bool isFading;
    [SerializeField] private float fadeDuration = 1f;
    [SerializeField] private CanvasGroup fadeCanvasGroup = null;
    [SerializeField] private Image fadeImage = null;
    public SceneName startingSceneName;

    private IEnumerator Start()
    {
        fadeImage.color = new Color(0f, 0f, 0f, 1f);
        fadeCanvasGroup.alpha = 1f;

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(startingSceneName.ToString()));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        StartCoroutine(FadeCoroutine(0f));
    }

    public void FadeAndLoadScene(string sceneName, Vector3 spawnPosition)
    {
        if (!isFading)
        {
            StartCoroutine(FadeAndSwitchScenesCoroutine(sceneName, spawnPosition));
        }
    }

    private IEnumerator FadeAndSwitchScenesCoroutine(string sceneName,Vector3 spawnPosition)
    {
        EventHandler.CallBeforeSceneUnloadFadeOutEvent();

        yield return StartCoroutine(FadeCoroutine(1f));

        SaveStoreManager.Instance.StoreCurrentSceneData();

        PlayerController.Instance.gameObject.transform.position = spawnPosition;

        EventHandler.CallBeforeSceneUnloadEvent();

        yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(sceneName));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        yield return StartCoroutine(FadeCoroutine(0f));

        EventHandler.CallAfterSceneLoadFadeInEvent();
    }

    private IEnumerator FadeCoroutine(float finalAlpha)
    {
        isFading = true;

        fadeCanvasGroup.blocksRaycasts = true;

        float fadeSpeed = Mathf.Abs(fadeCanvasGroup.alpha - finalAlpha) / fadeDuration;

        while (!Mathf.Approximately(fadeCanvasGroup.alpha, finalAlpha))
        {
            fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, finalAlpha, fadeSpeed * Time.deltaTime);

            yield return null;
        }


        isFading = false;

        fadeCanvasGroup.blocksRaycasts = false;
    }

    private IEnumerator LoadSceneAndSetActiveCoroutine(string sceneName)
    {
        yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

        Scene newlyLoadedScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);

        SceneManager.SetActiveScene(newlyLoadedScene);
    }
}

 For EvnetHandler, this is an event handler, which is suitable for our development mode: Observer mode. For details, please refer to the previous article. Then create the events to be used in this issue first, and then corresponding to the events that fade out before the scene is loaded. , the event before the scene is loaded, the event that fades in after the scene starts to load, and the event after the scene starts to load.

public static class EventHandler
{
   //上一期的事件


    //场景加载事件
    public static event Action BeforeSceneUnloadFadeOutEvent;

    public static void CallBeforeSceneUnloadFadeOutEvent()
    {
        if(BeforeSceneUnloadFadeOutEvent!= null)
        {
            BeforeSceneUnloadFadeOutEvent();
        }
    }

    public static event Action BeforeSceneUnloadEvent;

    public static void CallBeforeSceneUnloadEvent()
    {
        if (BeforeSceneUnloadEvent != null)
        {
            BeforeSceneUnloadEvent();
        }
    }

    public static event Action AfterSceneLoadEvent;

    public static void CallAfterSceneLoadEvent()
    {
        if (AfterSceneLoadEvent != null)
        {
            AfterSceneLoadEvent();
        }
    }

    public static event Action AfterSceneLoadFadeInEvent;

    public static void CallAfterSceneLoadFadeInEvent()
    {
        if (AfterSceneLoadFadeInEvent != null)
        {
            AfterSceneLoadFadeInEvent();
        }
    }

 We load the finished scene into the persistent scene as Additive, and then go to

BuildSettings add three scenes 

 

 

 When you first start running the game, only the first scene will be loaded, and now the fade in and out are implemented, so how do you go to the second scene?

Here we use Trigger to write a portal, create an empty object called SceneTeleport and then the script with the same name

 

The content of the script is as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]
public class SceneTeleport : MonoBehaviour
{
    [SerializeField] private SceneName sceneNameToGo = SceneName.Scene1_Farm;
    [SerializeField] private Vector3 scenePositionToGo = new Vector3();

    private void OnTriggerStay2D(Collider2D collision)
    {
        if(collision.TryGetComponent<PlayerController>(out PlayerController player))
        {
            Debug.Log("成功与玩家触发条件");
            float xPosition = Mathf.Approximately(scenePositionToGo.x, 0) ? player.transform.position.x : scenePositionToGo.x;
            float yPosition = Mathf.Approximately(scenePositionToGo.y, 0) ? player.transform.position.y : scenePositionToGo.y;
            float zPosition = Mathf.Approximately(scenePositionToGo.z, 0) ? player.transform.position.z : scenePositionToGo.z;

            SceneControllerManager.Instance.FadeAndLoadScene(sceneNameToGo.ToString(), new Vector3(xPosition,yPosition,zPosition));

        }
    }
}

Don't forget to make this object a Prefab, so we find the location where the second scene is to be teleported, and then add it. The teleportation point of each scene must be set.

 

Don't forget to Unload these three scenes every time you run the game, only let PersistenceScene load

 

 

 If you have succeeded, then you still have to congratulate, and then comes our main event

 


Realize and record the existence status of items in different scenarios:

To the point

  First of all, in the face of different scenarios, their item positions and states are different, so we need to record their positions and states first.

  Start by writing a script from the location

  

[System.Serializable]

public class Vector3Serializable
{
    public float x;
    public float y;
    public float z;

    public Vector3Serializable(float x,float y,float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vector3Serializable()
    {

    }
}

Then go to the Enums script to create a new enumeration 

public enum InventoryLocation
{
    player,
    chest,
    count,
}

public enum ToolEffect
{
    none,
    watering,
}

public enum Direction
{
    up,
    down,
    right,
    left,
}

public enum ItemType
{
    Seed,
    Commodity,
    Watering_tool,
    Hoeing_tool,
    Chopping_tool,
    Breaking_tool,
    Reping_tool,
    Collecting_tool,
    Reapable_scenery,
    Furinture,
    None,
    Count,

}

Create another SceneItem


[System.Serializable]
public class SceneItem 
{
    public int itemCode;
    public Vector3Serializable position;
    public string itemName;

    public SceneItem()
    {
        position = new Vector3Serializable();
    }
}

 Next is GenerateGUID which can add unique modifiers


using UnityEngine;

[ExecuteAlways]
public class GenerateGUID : MonoBehaviour
{
    [SerializeField] private string _gUID = "";

    public string GUID
    {
        get => _gUID;
        set => _gUID = value;
    }

    private void Awake()
    {
        if (!Application.IsPlaying(gameObject))
        {
            if(_gUID == "")
            {

                //登记GUID
                _gUID = System.Guid.NewGuid().ToString();
            }
        }
    }
}

   A newly learned feature here
[ExecuteAlways]

This means that even if the script is added in the state of the Unity editor, it will be executed according to the content after the game runs,

!Application.IsPlaying(gameObject); The exclamation mark is opposite, indicating that when the game is not running,

For items in different scenes, we use a linked list of classes to store SceneItems belonging to a scene

using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class SceneSave 
{
    public List<SceneItem> listSceneItem;
}

Then we store the data of these three scenes, SceneSave, in a dictionary and write an overload

using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class GameObjectSave 
{
    public Dictionary<string, SceneSave> sceneData;

    public GameObjectSave()
    {
        sceneData = new Dictionary<string, SceneSave>();
    }

    public GameObjectSave(Dictionary<string,SceneSave> sceneData)
    {
        this.sceneData = sceneData;
    }
}

 

We also need an interface to realize the storage of items, re-storage, GUID of items, items in the registration scene, and removal of items in the registration scene

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISaveable 
{
    string ISaveableUniqueID { get; set; }

    GameObjectSave GameObjectSave { get; set; }

    void ISaveableRegister();

    void ISaveableDeregister();

    void ISaveableStoreScene(string sceneName);

    void ISaveableRestoreScene(string sceneName);
}

 The preparations are complete, so how do we implement these interfaces so that the scene data and the item information in the scene can be stored and retrieved for you?

  Just create an empty object called SceneItemManager and create a script with the same name to it

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(GenerateGUID))] //需要组件GenerateGUID
public class SceneItemsManager : Singleton<SceneItemsManager>, ISaveable //继承ISaveable并实现方法
{
    private Transform parentItem;
    [SerializeField] private GameObject itemPrefab = null;

    private string _iSaveableUniqueId;

    public string ISaveableUniqueID { get => _iSaveableUniqueId; set => _iSaveableUniqueId = value; } //GUID属性,每帧更新

    private GameObjectSave _gameObjectSave;
    public GameObjectSave GameObjectSave { get => _gameObjectSave; set => _gameObjectSave = value; } //scneneData属性,更新

    protected override void Awake()
    {
        base.Awake();

        ISaveableUniqueID = GetComponent<GenerateGUID>().GUID;
        GameObjectSave = new GameObjectSave();
    }

    private void OnEnable()
    {
        ISaveableRegister();
        EventHandler.AfterSceneLoadEvent += AfterSceneLoaded;
    }

    private void OnDisable()
    {
        ISaveableDeregister();
        EventHandler.AfterSceneLoadEvent -= AfterSceneLoaded;
    }

    /// <summary>
    /// 在AfterSceneLoadEvent触发后,才能执行parentItem的实例化
    /// </summary>
    public void AfterSceneLoaded()
    {
        parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

    /// <summary>
    /// 销毁所有场景中的物品
    /// </summary>
    private void DestorySceneItems()
    {
        Item[] itemInScenes = GameObject.FindObjectsOfType<Item>();

        for (int i = itemInScenes.Length -1; i > -1; i--)
        {
            Destroy(itemInScenes[i].gameObject);
        }
    }

    /// <summary>
    /// 重新加载场景中的所有物品,先销毁所有物品游戏对象再初始化
    /// </summary>
    /// <param name="sceneName"></param>
    public void ISaveableRestoreScene(string sceneName)
    {
        if(GameObjectSave.sceneData.TryGetValue(sceneName,out SceneSave sceneSave))//找到特定的sceneName字符串,如果找到了就销毁该场景的
        {
            if(sceneSave.listSceneItem!= null )
            {
                DestorySceneItems();

                InstantiateSceneItems(sceneSave.listSceneItem);
            }
        }
    }

    /// <summary>
    /// 初始化SceneItem链表,并给所有Item重新赋值
    /// </summary>
    /// <param name="sceneItemList"></param>
    private void InstantiateSceneItems(List<SceneItem> sceneItemList)
    {
        GameObject itemGameObject;
        foreach (SceneItem sceneItem in sceneItemList)
        {
            itemGameObject = Instantiate(itemPrefab, new Vector3(sceneItem.position.x, sceneItem.position.y, sceneItem.position.z), Quaternion.identity, parentItem);

            Item item = itemGameObject.GetComponent<Item>();

            item.ItemCode = sceneItem.itemCode;
            item.name = sceneItem.itemName;

        }
    }

    public void InstantiateSceneItem(int itemCode,Vector3 itemPosition)
    {
        GameObject itemGameObject = Instantiate(itemPrefab, itemPosition, Quaternion.identity, parentItem);
        Item item = itemGameObject.GetComponent<Item>();
        item.Init(itemCode);        
    }

    /// <summary>
    /// 将场景中的所有Item存储起来
    /// </summary>
    /// <param name="sceneName"></param>
    public void ISaveableStoreScene(string sceneName)
    {
        GameObjectSave.sceneData.Remove(sceneName); //先清空该场景

        List<SceneItem> sceneItemLists = new List<SceneItem>();
        Item[] itemsInScene = FindObjectsOfType<Item>();

        foreach (Item item in itemsInScene)
        {
            SceneItem sceneItem = new SceneItem();
            sceneItem.itemCode = item.ItemCode;
            sceneItem.position = new Vector3Serializable(item.transform.position.x, item.transform.position.y, item.transform.position.z);
            sceneItem.itemName = item.name;

            sceneItemLists.Add(sceneItem);
        }

        SceneSave sceneSave = new SceneSave();
        sceneSave.listSceneItem = sceneItemLists;

        GameObjectSave.sceneData.Add(sceneName, sceneSave);
    }

    /// <summary>
    /// 移除SaveStoreManager.Instance.iSaveableObjectLists链表的该数据
    /// </summary>
    public void ISaveableDeregister()
    {
        SaveStoreManager.Instance.iSaveableObjectLists.Remove(this);
    }


    /// <summary>
    /// 添加SaveStoreManager.Instance.iSaveableObjectLists链表的该数据
    /// </summary>
    public void ISaveableRegister()
    {
        SaveStoreManager.Instance.iSaveableObjectLists.Add(this);
    }
}

You also need a SceneLodeManager game object and a script of the same name, which is called when the scene is loaded


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SaveStoreManager : Singleton<SaveStoreManager>
{
    public List<ISaveable> iSaveableObjectLists;

    protected override void Awake()
    {
        base.Awake();

        iSaveableObjectLists = new List<ISaveable>();
    }

    public void StoreCurrentSceneData()
    {
        foreach (ISaveable iSaveableObjectList in iSaveableObjectLists)
        {
            iSaveableObjectList.ISaveableStoreScene(SceneManager.GetActiveScene().name);
        }
    }

    public void RestoreCurrentSceneData()
    {
        foreach (ISaveable iSaveableObjectList in iSaveableObjectLists)
        {
            iSaveableObjectList.ISaveableRestoreScene(SceneManager.GetActiveScene().name);
        }
    }
}

Back to the scene loading controller script, we can eliminate all the content of the error.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class SceneControllerManager : Singleton<SceneControllerManager>
{
    private bool isFading;
    [SerializeField] private float fadeDuration = 1f;
    [SerializeField] private CanvasGroup fadeCanvasGroup = null;
    [SerializeField] private Image fadeImage = null;
    public SceneName startingSceneName;

    private IEnumerator Start()
    {
        fadeImage.color = new Color(0f, 0f, 0f, 1f);
        fadeCanvasGroup.alpha = 1f;

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(startingSceneName.ToString()));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        StartCoroutine(FadeCoroutine(0f));
    }

    public void FadeAndLoadScene(string sceneName, Vector3 spawnPosition)
    {
        if (!isFading)
        {
            StartCoroutine(FadeAndSwitchScenesCoroutine(sceneName, spawnPosition));
        }
    }

    private IEnumerator FadeAndSwitchScenesCoroutine(string sceneName,Vector3 spawnPosition)
    {
        EventHandler.CallBeforeSceneUnloadFadeOutEvent();

        yield return StartCoroutine(FadeCoroutine(1f));

        SaveStoreManager.Instance.StoreCurrentSceneData();

        PlayerController.Instance.gameObject.transform.position = spawnPosition;

        EventHandler.CallBeforeSceneUnloadEvent();

        yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(sceneName));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        yield return StartCoroutine(FadeCoroutine(0f));

        EventHandler.CallAfterSceneLoadFadeInEvent();
    }

    private IEnumerator FadeCoroutine(float finalAlpha)
    {
        isFading = true;

        fadeCanvasGroup.blocksRaycasts = true;

        float fadeSpeed = Mathf.Abs(fadeCanvasGroup.alpha - finalAlpha) / fadeDuration;

        while (!Mathf.Approximately(fadeCanvasGroup.alpha, finalAlpha))
        {
            fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, finalAlpha, fadeSpeed * Time.deltaTime);

            yield return null;
        }


        isFading = false;

        fadeCanvasGroup.blocksRaycasts = false;
    }

    private IEnumerator LoadSceneAndSetActiveCoroutine(string sceneName)
    {
        yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

        Scene newlyLoadedScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);

        SceneManager.SetActiveScene(newlyLoadedScene);
    }
}

 Check if you have mounted the script

 

Change the execution order of scripts

 

 

 Back to the UIInventorySlot script, we need to instantiate after the event is fired. Change as follows:

private void OnEnable()
    {
        EventHandler.AfterSceneLoadEvent += SceneLoaded;
    }

    private void OnDisable()
    {
        EventHandler.AfterSceneLoadEvent -= SceneLoaded;
    }

    public void SceneLoaded()
    {
        itemParent = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

Learning output:

fade in and fade out 

 

When different scenes are switched, the data structure plays a role, so that the picked item is destroyed and no longer instantiated 

 

 

 

 

Guess you like

Origin blog.csdn.net/dangoxiba/article/details/126896980