• Unity Xlua热更新框架(六):场景管理


    10. 实体管理与场景管理

    10-1. 实体管理与场景管理

    实体、场景、lua、声音等管理逻辑一样:1、xxxManager在Unity中管理这些模块,包括在Hierarchy中获取分组等,xxxManager最重要的作用是当gamestart.cs打开main.bytes执行的时候,给main.bytes提供接口(OpenUI等);2、xxxLogic运行该模块的逻辑,该模块只有框架,核心部分的实现都在xx.lua中实现,比如Update映射到lua的OnUpdate

    实体管理部分:
    在Scripts/Framework/Manager中创建EntityManager,Scripts/Framework/Behaviour中创建EntityLogic

    private static EntityManager _entity;
    public static EntityManager Entity
    {
    	get { return _entity; }
    }
    
    private static MySceneManager _scene;
    public static MySceneManager Scene
    {
        get { return _scene; }
    }
    
    public void Awake()
    {
        _resource = this.gameObject.AddComponent<ResourceManager>();
        _lua = this.gameObject.AddComponent<LuaManager>();
        _ui = this.gameObject.AddComponent<UIManager>();
        _entity = this.gameObject.AddComponent<EntityManager>();
        _scene = this.gameObject.AddComponent<MySceneManager>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    添加Root-Entity节点

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class EntityManager : MonoBehaviour
    {
        //缓存实体
        Dictionary<string, GameObject> m_Entities = new Dictionary<string, GameObject>();
        //缓存实体分组的transform
        Dictionary<string, Transform> m_Groups = new Dictionary<string, Transform>();
    
        private Transform m_EntityParent;
    
        private void Awake()
        {
            m_EntityParent = this.transform.parent.Find("Entity");
        }
    
        /// 
        /// 给Lua提供接口,方便添加实体分组
        /// 
        /// 要添加的UI层级名称的list
        public void SetEntityGroup(List<string> groups)
        {
            for (int i = 0; i < groups.Count; i++)
            {
                GameObject group = new GameObject("Group-" + groups[i]);
                group.transform.SetParent(m_EntityParent, false);
                m_Groups[groups[i]] = group.transform;
            }
        }
    
        /// 
        /// 返回指定层级的transform
        /// 
        /// 分组名称
        /// 返回字典中对应分组名称的transform
        Transform GetGroup(string group)
        {
            if (!m_Groups.ContainsKey(group))
            {
                Debug.LogError("group is not exist");
            }
            return m_Groups[group];
        }
    
        /// 
        /// 传入一个实体名字和lua名字,自动给ui预制体绑定C#脚本,自动执行lua脚本
        /// 
        /// 实体名字
        /// lua名字
        public void ShowEntity(string name, string group, string luaName)
        {
            GameObject entity = null;
            //如果prefab已经加载过了(从ab包取出放到Dictionary中),就只执行OnShow(Start),不在执行Init(Awake)
            if (m_Entities.TryGetValue(name, out entity))
            {
                EntityLogic logic = entity.GetComponent<EntityLogic>();
                logic.OnShow();
                return;
            }
    
            Manager.Resource.LoadPrefab(name, (UnityEngine.Object obj) =>
            {
                entity = Instantiate(obj) as GameObject;
                Transform parent = GetGroup(group);
                entity.transform.SetParent(parent, false);
                m_Entities.Add(name, entity);
                EntityLogic entityLogic = entity.AddComponent<EntityLogic>();
                entityLogic.Init(luaName);//Init对应Awake,OnShow对应Start
                entityLogic.OnShow();
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    LoadPrefab用来加载不同的预制体,预制体包含多种类型,effect,model等,需要根据传进来的内容按类型返回找到的资源

    //因为prefab类型太多,必须保证传进来的就是指定类型的路径,例如effect的路径等
    public void LoadPrefab(string assetName, Action<UnityEngine.Object> action = null)
    {
        LoadAsset(assetName, action);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    public class EntityLogic : LuaBehaviour
    {
        Action m_LuaOnShow;
        Action m_LuaOnHide;
        public override void Init(string luaName)
        {
            base.Init(luaName);
            m_ScriptEnv.Get("OnShow", out m_LuaOnShow);
            m_ScriptEnv.Get("OnHide", out m_LuaOnHide);
        }
    
        public void OnOpen()
        {
            m_LuaOnShow?.Invoke();
        }
        public void OnClose()
        {
            m_LuaOnHide?.Invoke();
        }
        protected override void Clear()
        {
            base.Clear();
            m_LuaOnShow = null;
            m_LuaOnHide = null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    场景管理部分:

    • 场景加载
      • 场景切换、叠加、激活
    • 场景卸载

    Scripts-Framework-Behaviour创建SceneLogic,Scripts-Framework-Manager创建SceneManager

    public class MySceneManager : MonoBehaviour
    {
        private string m_LogicName = "[SceneLogic]";
    
        private void Awake()
        {
            //对应下面设置激活场景时候的监听
            SceneManager.activeSceneChanged += OnActiveSceneChanged;
        }
    
        /// 
        /// 切换场景的回调,从前面s1切换激活到s2场景
        /// 
        /// 之前激活,现在被取消激活隐藏的场景
        /// 现在新激活的场景
        private void OnActiveSceneChanged(Scene s1, Scene s2)
        {
            if (!s1.isLoaded || !s2.isLoaded)
                return;
            SceneLogic logic1 = GetSceneLogic(s1);
            SceneLogic logic2 = GetSceneLogic(s2);
    
            logic1?.OnInActive();
            logic2?.OnActive();
        }
    
        /// 
        /// 激活指定场景,对应SceneLogic的OnActive的调用
        /// 
        /// 
        public void SetActive(string sceneName)
        {
            Scene scene = SceneManager.GetSceneByName(sceneName);
            SceneManager.SetActiveScene(scene);
            //激活一个场景的时候,之前的场景会被隐藏,因此需要创建一个事件,用来监听哪个场景被取消激活了
        }
    
        /// 
        /// 叠加加载场景的接口
        /// 
        /// 
        /// 
        public void LoadScene(string sceneName, string luaName)
        {
            //场景是.unity,ab包解包完成后异步加载场景
            Manager.Resource.LoadScene(sceneName, (UnityEngine.Object obj) =>
            {
                StartCoroutine(StartLoadScene(sceneName, luaName, LoadSceneMode.Additive));
            });
        }
        /// 
        /// 切换场景的接口
        /// 
        /// 
        /// 
        public void ChangeScene(string sceneName, string luaName)
        {
            //场景是.unity,ab包解包完成后异步加载场景
            Manager.Resource.LoadScene(sceneName, (UnityEngine.Object obj) =>
            {
                //只加载单一的场景
                StartCoroutine(StartLoadScene(sceneName, luaName, LoadSceneMode.Single));
            });
        }
    
        /// 
        /// 卸载场景
        /// 
        /// 
        public void UnLoadSceneAsync(string sceneName)
        {
            StartCoroutine(UnLoadScene(sceneName));
        }
    
        /// 
        /// 检测场景是否已经加载
        /// 
        /// 场景名
        /// 
        private bool IsLoadedScene(string sceneName)
        {
            Scene scene = SceneManager.GetSceneByName(sceneName);
            return scene.isLoaded;
        }
        /// 
        /// 加载场景
        /// 
        /// 
        /// 
        /// 
        /// 
        IEnumerator StartLoadScene(string sceneName, string luaName, LoadSceneMode mode)
        {
            //检测场景是否加载
            if (IsLoadedScene(sceneName))
                yield break;
    
            //异步操作协同程序
            AsyncOperation async = SceneManager.LoadSceneAsync(sceneName, mode);
            //allowSceneActivation允许场景一旦准备好就被激活,设置false的话,场景只加载90%,留下10%等待
            async.allowSceneActivation = true;
            yield return async;
    
            //获取加载成功的场景
            Scene scene = SceneManager.GetSceneByName(sceneName);
            //创建挂载场景逻辑脚本的物体,,每一个场景加载好,都会创建一个物体挂载SceneLogic脚本并移动到该场景中
            GameObject go = new GameObject(m_LogicName);
            //有可能加载了多个场景,把这个挂载场景逻辑脚本的物体移动到激活的场景中
            SceneManager.MoveGameObjectToScene(go, scene);
    
            SceneLogic logic = go.AddComponent<SceneLogic>();
    		logic.SceneName = sceneName;
            logic.Init(luaName);
            logic.OnEnter();
        }
    
        /// 
        /// 
        /// 
        /// 
        /// 
        private IEnumerator UnLoadScene(string sceneName)
        {
            Scene scene = SceneManager.GetSceneByName(sceneName);
            if (!scene.isLoaded)
            {
                Debug.LogError("scene not isloaded");
                yield break;
            }
            //因为由多个场景,要找个挂载SceneLogic脚本的物体到底在哪个物体身上
            SceneLogic logic = GetSceneLogic(scene);
            logic?.OnQuit(); //场景卸载调用lua的quit方法,,先调用lua的退出场景函数,再异步卸载场景
            AsyncOperation async = SceneManager.UnloadSceneAsync(scene);
            yield return async;
        }
    
        private SceneLogic GetSceneLogic(Scene scene)
        {
            GameObject[] gameObjects = scene.GetRootGameObjects();
            foreach (GameObject go in gameObjects)
            {
                if (go.name.CompareTo(m_LogicName) == 0)
                {
                    SceneLogic logic = go.GetComponent<SceneLogic>();
                    return logic;
                }
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    public class SceneLogic : LuaBehaviour
    {
    	//lua会调用MySceneManager接口根据名字设置该场景激活
        public string SceneName;
    
        Action m_LuaActive;
        Action m_LuaInActive;
        Action m_LuaOnEnter;
        Action m_LuaOnQuit;
        public override void Init(string luaName)
        {
            base.Init(luaName);
            m_ScriptEnv.Get("OnActive", out m_LuaActive);//场景加载是否激活
            m_ScriptEnv.Get("OnInActive", out m_LuaInActive);
            m_ScriptEnv.Get("OnEnter", out m_LuaOnEnter);//第一次加载可以认为是首次进入场景Enter
            m_ScriptEnv.Get("OnQuit", out m_LuaOnQuit);
        }
    
        public void OnActive()
        {
            m_LuaActive?.Invoke();
        }
        public void OnInActive()
        {
            m_LuaInActive?.Invoke();
        }public void OnEnter()
        {
            m_LuaOnEnter?.Invoke();
        }
        public void OnQuit()
        {
            m_LuaOnQuit?.Invoke();
        }
        protected override void Clear()
        {
            base.Clear();
            m_LuaActive = null;
            m_LuaInActive = null;
            m_LuaOnEnter = null;
            m_LuaOnQuit = null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在场景中的Entity创建一个capsule,改名player,添加Character Controller,放进model作为预制体,,随便创建另外一个场景Test01放什么物体都行,保存到scene文件夹,,新建另一个场景Test02放一个地面
    在luaScripts文件夹下面创建entity和scene文件夹,创建PlayerEntity.bytes,Scene01.bytes,Scene02.bytes
    main.bytes加载TestUI时让TestUI运行TestUI.bytes,TestUI.bytes的OnOpen方法执行Manager.Scene:LoadScene加载场景Test01绑定执行Scene01.bytes并Manager.Scene:SetActive激活自己,这个Scene01.bytes又加载场景Test02绑定执行Scene02.bytes,,Scene02.bytes又加载ShowEntity加载了player.prefab并绑定了PlayerEntity.bytes

    Manager = CS.Manager --引用C#里面定义的类
    PathUtil = CS.PathUtil
    Vector3 = CS.UnityEngine.Vector3
    Input = CS.UnityEngine.Input
    KeyCode = CS.UnityEngine.KeyCode
    Time = CS.UnityEngine.Time
    
    --定义UI层级
    local ui_group = 
    {
      "Main",
      "UI",
      "Box",
    }
    
    local entity_group = 
    {
      "Player",
      "Monster",
      "Effect",
    }
    
    Manager.UI:SetUIGroup(ui_group)
    Manager.Entity:SetEntityGroup(entity_group)
    function Main()
      print("hello main")
      Manager.UI:OpenUI("TestUI", "UI", "ui.TestUI")
    
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    function OnInit()
        print("lua OnInit")
    end
    
    function OnOpen()
        print("lua OnOpen")
        Manager.Scene:LoadScene("Test01","scene.Scene01")
    end
    
    function Update()
        print("lua Update")
    end
    
    function OnClose()
        print("lua OnClose")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    function OnInit()
        print("lua OnInit")
    end
    
    function OnActive()
        print("lua OnActive")
    end
    
    function OnEnter()
        print("lua OnEnter Scene01")
        Manager.Scene:LoadScene("Test02","scene.Scene02")
        Manager.Scene:SetActive(self.SceneName)
    end
    
    function Update()
        print("lua Update")
    end
    
    function OnInActive()
        print("lua OnInActive")
    end
    
    function OnQuit()
        print("lua OnQuit")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    function OnInit()
        print("lua OnInit")
    end
    
    function OnActive()
        print("lua OnActive")
    end
    
    function OnEnter()
        print("lua OnEnter")
        Manager.Entity:ShowEntity(PathUtil.GetModelPath("Player"),"Player","entity.PlayerEntity")
    end
    
    function Update()
        print("lua Update")
    end
    
    function OnInActive()
        print("lua OnInActive")
    end
    
    function OnQuit()
        print("lua OnQuit")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    function OnInit()
        print("lua OnInit")
    end
    
    function OnShow()
        print("lua OnShow")
        self.transform.localPosition = Vector3(0,2,0)
    end
    
    function Update()
        print("lua Update")
        if Input.GetKey(KeyCode.W) then
            self.transform:Translate(self.transform.forward * Time.deltaTime * 5)
        end
        if Input.GetKey(KeyCode.S) then
            self.transform:Translate(-self.transform.forward * Time.deltaTime * 5)
        end
        if Input.GetKey(KeyCode.A) then
            self.transform:Translate(-self.transform.right * Time.deltaTime * 5)
        end
        if Input.GetKey(KeyCode.D) then
            self.transform:Translate(self.transform.right * Time.deltaTime * 5)
        end
    end
    
    function OnHide()
        print("lua OnHide")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image.png给两个Test场景添加进去
    Model-mat文件夹创建材质球,给他们赋材料,运行程序
    image.png20221023_101243.gif

    10-2. 场景管理测试

    上面的实体测试实在GameMode == EditorMode模式下测试的,本框架为热更框架,需要进行测试
    选择PackageMode测试,构建bundle,运行

    InvalidOperationException: This method cannot be used on a streamed scene AssetBundle.
    UnityEngine.AssetBundle.LoadAssetAsync报错
    AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync(assetName);
    结论:如果是场景scene.unity不能用这类loadxxx方法加载。
    原因: 任何一个包含*.unity的资源包都属于动态场景资源包,对应assetBundle.isStreamedSceneAssetBundle = true,动态场景资源包是不允许手动去访问其内部的资源的。
    重点: 如果你的资源需要通过代码访问,那么在打包时不要与*.unity文件捆绑为一个文件。

    经过测试,Build Settings设置了Scene,并且AssetBundle也加载了Scene,使用SceneManager.LoadSceneAsync会优先加载AssetBundle中的Scene

    而我们的项目加载了场景,但是并没有用一个obj接受这个场景,因此直接屏蔽调这句话即可。尽管没有接收这个obj,但是使用SceneManager在Editor模式下加载Build Settings中的场景,在Update模式下优先加载AssetBundle里的scene。scene加载测试,见博文

    AssetBundle动态加载Scene场景_我是刘咩咩的博客

    IEnumerator LoadBundleAsync(string assetName,Action<UObject> action = null)
    {
        string bundleName = m_BundleInfos[assetName].BundleName;
        //这个是小写的bundle.ab的路径名
        string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
        List<string> dependences = m_BundleInfos[assetName].Dependeces;
        if(dependences != null && dependences.Count > 0)
        {
            //递归加载依赖bundle,因为依赖的资源目录名就是bundle资源名
            for (int i = 0; i < dependences.Count; i++)
            {
                yield return LoadBundleAsync(dependences[i]);
            }
        }
        //创建异步加载bundle申请
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
        yield return request;
    
        if (assetName.EndsWith(".unity"))
        {
            action?.Invoke(null);
            yield break;
        }
        //从bundle申请加载指定路径名的文件,例如prefab
        AssetBundleRequest bundleRequest = request.assetBundle.LoadAssetAsync(assetName);
        yield return bundleRequest;
    
        //如果回调和request都不为空,语法糖
        action?.Invoke(bundleRequest?.asset);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

  • 相关阅读:
    C++设计模式_06_Decorator 装饰模式
    ATFX邀请码揭秘,尊享服务的神秘邀请码
    基于PSO的UAV三维路径规划(Matlab代码实现)
    HCQ1-1300-D【故障笔记】
    技术分享 | orchetrator--安装一个高可用 orchestrator
    shell中分支语句,循环语句,函数
    uniapp集成windicss的流程
    【SQL】MySQL中的索引,索引优化
    Hive性能调优行之有效的优化方法
    前端常用的版本管理工具
  • 原文地址:https://blog.csdn.net/weixin_42264818/article/details/128211329