1.首先

可可有限公司“椰子技术谈话”我使用了 LT 文章作为参考。
谢谢你。

本文使用的 Unity 版本为“Unity 2021.3.1f1”。

2. 为什么不使用单例

综上所述,如果您只想在场景之间保留信息,则不需要使用单例。是。
首先使用单例的原因是因为

  • 我要确保只有一个实例
  • 我想获得来自不同类的引用

等等。

在游戏开发中,你可能会遇到使用单例更方便的情况。

“这个 `Manager'' 是一个我经常使用的类,所以让我们让它成为一个单例。”

如果你用一个简单的想法让它成为一个单身人士,比如具有无数引用的紧耦合类完成了。

如果你不小心使用它,可读性会很差,并且设计会变得相互依赖。
另外,实际上可以说,当事实证明拥有多个实例更方便时,影响范围非常大。

3. 实施细节

在每个场景的根对象中放置一个继承 AbstractScene 的组件。
Unityでシングルトンを使わずにシーン間でデータを保持する。

基类 AbstractScene 的详细信息。
这个放置在每个场景中并使用它接收到的变量。

抽象场景.cs
using UnityEngine;
using Cysharp.Threading.Tasks;

public abstract class AbstructScene : MonoBehaviour
{
    protected SceneOperator _sceneOperator;

    private void Awake()
    {
        OnAwake();
    }
    protected virtual void OnAwake() { }

    /// <summary>
    /// SceneOperatorのLoadSceneを非同期で呼ぶ
    /// </summary>
    public async void LoadScene(string sceneName,string message)
    {
        //最初に一度インスタンスを初期化
        if (_sceneOperator == null)
        {
            _sceneOperator = new SceneOperator(message);
        }
        await _sceneOperator.LoadScene(sceneName);
    }

    /// <summary>
    /// 前のシーンからSceneOperatorを引き継ぐための関数
    /// </summary>
    public void SetOperator(SceneOperator appOperator)
    {
        _sceneOperator = appOperator;
    }
    
    /// <summary>
    /// ロード時に呼ばれる
    /// </summary>
    public abstract UniTask OnLoad(string message);
    /// <summary>
    /// ロードされた後に呼ばれる
    /// </summary>
    public abstract void OnOpen();
    /// <summary>
    /// シーンの破棄時に呼ばれる
    /// </summary>
    public abstract UniTask OnUnLoad();
}

异步加载一个场景并将值传递给下一个场景的 AbstractScene。

场景操作员.cs
using UnityEngine;
using System.Threading;
using UnityEngine.SceneManagement;
using Cysharp.Threading.Tasks;

public class SceneOperator
{
    /// <summary>
    /// シーン間で保持する文字列
    /// </summary>
    string _message;

    public SceneOperator(string message)
    {
        SetUp(message);
    }

    public void SetUp(string message)
    {
        _message = message;
    }

    public async UniTask LoadScene(string sceneName)
    {
        //シーンを破棄
        await GetActiveAbstructScene(SceneManager.GetActiveScene()).UnLoad();

        //シーンをロード
        await SceneManager.LoadSceneAsync(sceneName);

        //ロード先のAbstructSceneを取得
        var absScene = GetActiveAbstructScene(SceneManager.GetSceneByName(sceneName));
        absScene.SetOperator(this);

        //ロード時の処理を呼ぶ
        await absScene.Load(_message);
        absScene.Open();
    }

    /// <summary>
    /// シーン内のRootObjectsからAbstructSceneを返す
    /// </summary>
    AbstructScene GetActiveAbstructScene(Scene scene)
    {
        AbstructScene abstructScene = null;
        foreach (var obj in scene.GetRootGameObjects())
        {
            if (obj.TryGetComponent(out AbstructScene getAbstructScene))
            {
                abstructScene = getAbstructScene;
                break;
            }
        }
        if (abstructScene)
        {
            return abstructScene;
        }
        else
        {
            throw new System.ArgumentNullException($"{scene.name}のRootObjectsにAbstructSceneがアタッチされたオブジェクトが含まれていません。");
        }
    }
}

AbstractScene 的实现示例。

InGameManager.cs
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;

public class InGameManager : AbstructScene
{
    /// <summary>シーン内で使用する文字列</summary>
    protected string _message = "";

    [Header("コンポーネント")]
    [SerializeField,Tooltip("押下時にロードが呼ばれるボタン")]
    Button _loadButton;

    [SerializeField,Tooltip("押下時に次のシーンに渡したい文字列を決定するボタン")]
    Button _inputButton;

    [SerializeField,Tooltip("次のシーンに渡したい文字列を入力するフィールド")]
    protected InputField _inputField = null;

    [SerializeField,Tooltip("現在保持されている変数を表示するText")]
    protected Text _text;

    [Header("ロード関連")]
    [SerializeField,Tooltip("ロード先のシーン名")]
    string _sceneName;

    protected override void OnAwake()
    {
        //押下時に指定のシーンにロードする。
        _loadButton.onClick.AddListener(() => { LoadScene(_sceneName, _inputField.text); });

        //押下時に次のシーンに渡す文字列を変更する
        _inputButton.onClick.AddListener(() => {
            if (_sceneOperator == null)
            {
                _sceneOperator = new SceneOperator(_inputField.text);
            }
            else
            {
                _sceneOperator.SetUp(_inputField.text);
            }         
        });
    }
    public override async UniTask Load(string message)
    {
        _message = message;
        await UniTask.Yield();
    }

    /// <summary>
    /// シーンがロードされた後に呼ばれる
    /// </summary>
    public override void Open()
    {
        _text.text = _message;
    }

    public override async UniTask UnLoad()
    {
        await UniTask.Yield();
    }
}

这是我运行它时的样子:
按下中间按钮切换场景,上面显示的文本会跨场景保留。
您可以更改 InputField 中的值。
Unityでシングルトンを使わずにシーン間でデータを保持する。

需要注意的是,根据 AbstractScene,没有源或子,因此在使用该值的类中需要一些独创性。 ,

4.总结

顺便说一句,我从不反对单例 w (输入等往往是单例)
单例是一种在 GoF 和其他地方被采用和使用的设计模式,但使用起来可能有点棘手。

这一次,我尝试实现它的目的是增加设计抽屉的数量。
这似乎可以防止使用单例往往发生的紧密耦合。

这是我第一次在Qiita上发表文章,所以如果有任何错误或错误,请告诉我。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308631851.html

相关文章:

  • 2021-11-18
  • 2021-12-04
  • 2021-12-10
  • 2021-06-14
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-28
猜你喜欢
  • 2021-10-22
  • 2022-12-23
  • 2021-08-15
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案