开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:1582

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 2424|回复: 112

[成都大红花] Unity3D游戏开发之“预加载场景,瞬间过图”

  [复制链接]  [移动端链接]
排名
2526
昨日变化
6

47

主题

150

帖子

1491

积分

Rank: 9Rank: 9Rank: 9

UID
135388
好友
12
蛮牛币
1564
威望
0
注册时间
2016-1-22
在线时间
262 小时
最后登录
2017-2-21

专栏作家

发表于 2016-12-20 19:53:08 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 成都大红花 于 2016-12-20 19:54 编辑

为什么需要预加载场景?
一般我们的过图的时候很少考虑到预加载场景,所以我们在过图的时候经常会显示加载进度条等待场景加载完成,那么加载一些很大的场景的话就会让玩家等待很久的时间。但是在某一些项目有瞬间过图的需求的时候,那么我们就需要预加载场景了。

场景过图包括2种方式。1、unity内置过图场景  2、AB资源场景过图
1、unity内置场景预加载
像我们做mmo的时候内置场景是很少的,只有初始化场景、登录场景等一些很简单的场景,其他的场景全部是AB资源场景方式。
在内置场景做预加载的时候我们必须使用Application.LoadLevelAsync(levelName)异步加载场景的方式,在把返回的AsyncOperation对象存起来并把allowSceneActivation设置为false
当你需要切换场景的时候再把allowSceneActivation设置成true就成功进行了场景切换。

示例代码:
[C#] 纯文本查看 复制代码
//unity内置的场景加载
public class UnityBuiltSceneLoadAsync : SceneLoadAsync {
    private AsyncOperation sceneAsync = null;

    public UnityBuiltSceneLoadAsync(string scene_name, bool allowSceneActivation, Action<float> progressCallBack, Action loadCompleted) {
        this.sceneName = scene_name;
        this.progressCallBack = progressCallBack;
        this.loadCompleted = loadCompleted;
        this.allowSceneActivation = allowSceneActivation;

        string assetPath = string.Format("res/scenes/{0}.scene", scene_name);
        Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(scene_name, assetPath));
    }

    protected override IEnumerator LoadLevelBundle(string levelName, string assetPath) {
        Global.coroutine.Execute(base.LoadLevelBundle(levelName, assetPath));
        sceneAsync = Application.LoadLevelAsync(levelName);
        sceneAsync.allowSceneActivation = allowSceneActivation;
        float progress = 0;
        while (!sceneAsync.isDone) {
            progress = sceneAsync.progress;
            if (progress >= 0.9f) {
                progress = 1;
                assetProgresCallBacks[assetPath](assetPath, progress);

                if (this.loadCompleted != null)
                    this.loadCompleted();
                this.isDone = true;
                LOG.Log("[UnityBuiltSceneLoadAsync-LoadLevelBundle] sceneAsync load isDone");
                allowSceneActivation = true;
                yield break;
            }
            yield return null;
        }
    }

    public override void Unload(bool unloadAllLoadedObjects) {
        base.Unload(unloadAllLoadedObjects);

        sceneAsync = null;
    }

    public override bool allowSceneActivation {
        get {
            return base.allowSceneActivation;
        }
        set {
            base.allowSceneActivation = value;
            LOG.Log("[UnityBuiltSceneLoadAsync-allowSceneActivation] allowSceneActivation=" + value);
            if (sceneAsync != null) {
                sceneAsync.allowSceneActivation = value;
                if (value) {
                    if (onSceneActiveBegin != null)
                        onSceneActiveBegin(sceneName);
                    LOG.Log("[ActiveScene] " + sceneName);
                }
            }
        }
    }

    protected override void OnLoadSceneProgress(string assetPath,float progress) {
        if (this.progressCallBack != null)
            this.progressCallBack(progress);
    }
}



2、AB资源场景预加载
AB资源场景顾名思义就是把场景打包成AB资源然后通过加载AB资源来达到加载场景的目的。
首先需要把场景打包成AB资源,然后在切换场景的时候先加载场景AB资源再通过Application.LoadLevel(sceneName)接口来切换我们的场景。
在做预加载的时候我们需要缓存加载好的场景AB资源,在切换场景的时候再调用Application.LoadLevel(sceneName),这样就会瞬间切换。
特别注意:这里有一个大坑就是,在这种方式的时候我们只能保存场景AB资源,切换的时候再调用Application.LoadLevel(sceneName)接口。不能加载AB资源后保存调用Application.LoadLevelAsync(levelName)返回的异步接口并设置allowSceneActivation为false,等切换的时候在设置allowSceneActivation为true。在unity4.7这种方式会导致我们的工程的所有协程停止运行,不知道是unity的bug还是什么,unity5.X的没有试过,不知道有没有这种情况。
为了优化更多的内存占用,我们需要在加载完AB场景后需要把AB资源的内存镜像给删除掉调用assetBundle.Unload(false);

示例代码:
[C#] 纯文本查看 复制代码
//ab资源的场景加载
public class AssetBundleSceneLoadAsync : SceneLoadAsync {
    public class SceneAssetData {
        private UnityEngine.Object mainAsset_ = null;
        private AssetBundle assetBundle_ = null;

        public string sceneName { get; set; }
        public string assetName { get; set; }
        public Coroutine assetCoroutine { get; set; }
        public AssetBundle assetBundle {
            get { return assetBundle_; }
            set {
                if (value != null) {
                    assetBundle_ = value;

                    mainAsset_ = assetBundle.Load(sceneName,typeof(GameObject));
                    if (mainAsset_ != null) {
                        if ((mainAsset_ as GameObject) == null) {
                            LOG.LogError(string.Format("场景加载出错,加载的AB资源{0}中资源{1}[{2}]的类型错误,请美术检查相应AB资源"
                                , assetName, sceneName, mainAsset_.GetType().Name));
                        }
                    }
                    else if (!assetName.EndsWith(".scene"))
                        LOG.LogError(string.Format("场景加载出错,加载的AB资源{0}中无{1}资源,请美术检查相应AB资源"
                            , assetName, sceneName));
                }
            }
        }
        public UnityEngine.Object mainAsset {
            get {
                return mainAsset_;
            }
        }

        public SceneAssetData(string sceneName, string assetName, Coroutine assetCoroutine, AssetBundle assetBundle) {
            this.sceneName = sceneName;
            this.assetName = assetName;
            this.assetCoroutine = assetCoroutine;
            this.assetBundle = assetBundle;
        }

        public void Unload(bool unloadAllLoadedObjects) {
            if (assetCoroutine != null) {
                Global.coroutine.Stop(assetCoroutine);
                assetCoroutine = null;
            }
            if (assetBundle != null)
                assetBundle.Unload(unloadAllLoadedObjects);

            //unloadAllLoadedObjects=false表示的是过完场景后对场景AB资源进行卸载
            //unloadAllLoadedObjects=true表示对整个场景的预加载的卸载
            if (!unloadAllLoadedObjects && mainAsset != null 
                && mainAsset is GameObject && !assetName.EndsWith(".scene")) {
                GameObject obj = UnityEngine.Object.Instantiate(mainAsset) as GameObject;
                CombineAssetStatic(obj.transform);
            }
        }

        private void CombineAssetStatic(Transform root) {
            for (int i = 0; i < root.childCount; i++) {
                if (root.GetChild(i).name == "static") {
                    StaticBatchingUtility.Combine(root.GetChild(i).gameObject);
                }
            }
        }
    }
    private Map<string, SceneAssetData> sceneAssets = new Map<string, SceneAssetData>();
    private Map<string, float> assetsProgressMap = new Map<string, float>();

    public AssetBundleSceneLoadAsync(int scene_id,bool allowSceneActivation, Action<float> progressCallBack, Action loadCompleted) {
        MapReference mapref = Global.map_mgr.GetReference(scene_id);
        this.sceneName = mapref.FileName;
        this.progressCallBack = progressCallBack;
        this.loadCompleted = loadCompleted;
        this.allowSceneActivation = allowSceneActivation;

        string assetPath = string.Format("res/scenes/{0}.scene", sceneName);
        Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(sceneName, assetPath));
        sceneAssets.Add(assetPath, new SceneAssetData(sceneName,assetPath, coroutine, null));

        if (!string.IsNullOrEmpty(mapref.ResPath)) {
            assetPath = string.Format("res/scenes/{0}.go", mapref.ResPath);
            coroutine = Global.coroutine.Execute(LoadLevelBundle(sceneName, assetPath));
            sceneAssets.Add(assetPath, new SceneAssetData(sceneName, assetPath, coroutine, null));
        }
    }

    protected override IEnumerator LoadLevelBundle(string levelName, string assetPath) {
        Global.coroutine.Execute(base.LoadLevelBundle(levelName, assetPath));
        WWW loadSceneWWW = new WWW(CDirectory.MakeFullPath(assetPath));
        while (!loadSceneWWW.isDone) {
            assetProgresCallBacks[assetPath](assetPath, loadSceneWWW.progress);
            yield return null;
        }
        assetProgresCallBacks[assetPath](assetPath, 1.0f);
        yield return loadSceneWWW;
        if (loadSceneWWW.error != null) {
            LOG.LogError("WWW download:" + loadSceneWWW.error + "  path :  " + loadSceneWWW.url);
            yield break;
        }
        if (loadSceneWWW.isDone) {
            sceneAssets[assetPath].assetBundle = loadSceneWWW.assetBundle;
            loadSceneWWW.Dispose();
            if (isSceneLoadCompleted()) {
                if (loadCompleted != null)
                    loadCompleted();
                isDone = true;
                allowSceneActivation = sceneActive;
            }
        }
    }

    private bool isSceneLoadCompleted() {
        int complateCount = 0;
        for (sceneAssets.Begin(); sceneAssets.Next(); ) {
            if (sceneAssets.Value.assetBundle != null)
                complateCount++;
        }

        return complateCount == sceneAssets.Count && complateCount != 0;
    }

    public override void Unload(bool unloadAllLoadedObjects) {
        base.Unload(unloadAllLoadedObjects);

        for (sceneAssets.Begin(); sceneAssets.Next(); ) {
            sceneAssets.Value.Unload(unloadAllLoadedObjects);
        }
        sceneAssets.Clear();
        assetsProgressMap.Clear();
    }

    public override bool allowSceneActivation {
        get {
            return base.allowSceneActivation;
        }
        set {
            base.allowSceneActivation = value;

            if (value && isSceneLoadCompleted()) {
                if (onSceneActiveBegin != null)
                    onSceneActiveBegin(sceneName);
                Application.LoadLevel(sceneName);
                LOG.Log("[ActiveScene] " + sceneName);
            }
        }
    }

    protected override void OnLoadSceneProgress(string assetPath,float progress) {
        if (assetsProgressMap.ContainsKey(assetPath))
            assetsProgressMap[assetPath] = progress;
        else
            assetsProgressMap.Add(assetPath, progress);

        float tempProgress = 0f;
        for (assetsProgressMap.Begin(); assetsProgressMap.Next(); ) {
            tempProgress += assetsProgressMap.Value;
        }

        if (this.progressCallBack != null) {
            this.progressCallBack(tempProgress / assetsProgressMap.Count);
        }
    }
}





全部示例代码SceneManager.cs:
可能示例代码你不能完全运行,不过思想全部在代码里面了,只需要看懂思想稍微改变就能成为你的。
游客,如果您要查看本帖隐藏内容请回复



如果觉得我的帖子对你有帮助:请投“成都大红花”一票  传送门:http://www.manew.com/tools/expert

如果有什么问题请加群交流:
技术交流群:153091222
成都unity3d技术圈:422809511



本帖被以下淘专辑推荐:


回复

使用道具 举报

7日久生情
3487/5000
排名
41
昨日变化

0

主题

242

帖子

3487

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
14133
好友
0
蛮牛币
5002
威望
0
注册时间
2014-2-12
在线时间
1295 小时
最后登录
2017-2-17
发表于 2016-12-21 09:24:59 | 显示全部楼层
很好,不错的分享。

回复

使用道具 举报

排名
19903
昨日变化
21

0

主题

14

帖子

69

积分

Rank: 2Rank: 2

UID
29260
好友
0
蛮牛币
129
威望
0
注册时间
2014-6-11
在线时间
44 小时
最后登录
2017-2-13
发表于 2016-12-21 09:31:38 | 显示全部楼层
谢谢,非常不错的分享

回复 支持 反对

使用道具 举报

3偶尔光临
269/300
排名
7351
昨日变化
94

8

主题

65

帖子

269

积分

Rank: 3Rank: 3Rank: 3

UID
172976
好友
1
蛮牛币
28
威望
0
注册时间
2016-9-30
在线时间
92 小时
最后登录
2017-2-25
发表于 2016-12-21 09:32:04 | 显示全部楼层
感谢分享

回复

使用道具 举报

6蛮牛粉丝
1007/1500
排名
3337
昨日变化
19

4

主题

308

帖子

1007

积分

Rank: 6Rank: 6Rank: 6

UID
112674
好友
3
蛮牛币
597
威望
0
注册时间
2015-7-8
在线时间
409 小时
最后登录
2017-2-24
QQ
发表于 2016-12-21 09:32:17 | 显示全部楼层
很好,不错的分享

回复

使用道具 举报

3偶尔光临
232/300
排名
10693
昨日变化
5

1

主题

16

帖子

232

积分

Rank: 3Rank: 3Rank: 3

UID
130007
好友
0
蛮牛币
304
威望
0
注册时间
2015-11-26
在线时间
168 小时
最后登录
2017-2-25
QQ
发表于 2016-12-21 09:33:03 | 显示全部楼层
好威武支持有希望

回复 支持 反对

使用道具 举报

7日久生情
2963/5000
排名
86
昨日变化

3

主题

480

帖子

2963

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
32341
好友
2
蛮牛币
7448
威望
0
注册时间
2014-7-3
在线时间
710 小时
最后登录
2017-2-25
发表于 2016-12-21 09:35:25 | 显示全部楼层
好东西顶起来······感谢分享

回复 支持 反对

使用道具 举报

5熟悉之中
860/1000
排名
2751
昨日变化
1

9

主题

138

帖子

860

积分

Rank: 5Rank: 5

UID
59605
好友
0
蛮牛币
1974
威望
0
注册时间
2014-12-5
在线时间
369 小时
最后登录
2017-2-23
发表于 2016-12-21 09:43:31 | 显示全部楼层
谢谢分享

回复

使用道具 举报

3偶尔光临
223/300
排名
7010
昨日变化
89

0

主题

35

帖子

223

积分

Rank: 3Rank: 3Rank: 3

UID
134896
好友
0
蛮牛币
246
威望
0
注册时间
2016-1-17
在线时间
80 小时
最后登录
2017-2-24
发表于 2016-12-21 09:46:29 | 显示全部楼层
谢谢分享

回复

使用道具 举报

4四处流浪
486/500
排名
4049
昨日变化
20

0

主题

63

帖子

486

积分

Rank: 4

UID
70166
好友
1
蛮牛币
1444
威望
0
注册时间
2015-1-21
在线时间
189 小时
最后登录
2017-2-25
QQ
发表于 2016-12-21 09:49:59 | 显示全部楼层
厉害了谢谢分享

回复

使用道具 举报

7日久生情
1639/5000
排名
538
昨日变化
1

0

主题

170

帖子

1639

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
8109
好友
0
蛮牛币
2325
威望
0
注册时间
2013-11-15
在线时间
442 小时
最后登录
2017-2-23

游戏蛮牛QQ群会员活力之星

发表于 2016-12-21 09:52:43 | 显示全部楼层
感谢分享

回复

使用道具 举报

7日久生情
2618/5000
排名
198
昨日变化
1

0

主题

328

帖子

2618

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
2484
好友
2
蛮牛币
2027
威望
0
注册时间
2013-8-23
在线时间
852 小时
最后登录
2017-2-24
发表于 2016-12-21 09:56:04 | 显示全部楼层
很好的学习资料啊!感谢楼主这么慷慨!

回复 支持 反对

使用道具 举报

3偶尔光临
230/300
排名
8235
昨日变化
9

6

主题

101

帖子

230

积分

Rank: 3Rank: 3Rank: 3

UID
164720
好友
0
蛮牛币
1326
威望
0
注册时间
2016-11-17
在线时间
47 小时
最后登录
2017-2-21
QQ
发表于 2016-12-21 10:04:39 | 显示全部楼层
超赞~~~~~~~~~~~~~
[发帖际遇]: 瞌睡的猫 乐于助人,奖励 3 蛮牛币. 幸运榜 / 衰神榜

回复

使用道具 举报

7日久生情
1606/5000
排名
811
昨日变化
2

0

主题

233

帖子

1606

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
23916
好友
1
蛮牛币
3476
威望
0
注册时间
2014-5-5
在线时间
515 小时
最后登录
2017-2-25

VIP

发表于 2016-12-21 10:04:42 | 显示全部楼层
谢谢分享。
[发帖际遇]: 雨泥 发帖时在路边捡到 1 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复

使用道具 举报

7日久生情
1983/5000
排名
267
昨日变化

1

主题

179

帖子

1983

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
27813
好友
1
蛮牛币
4002
威望
0
注册时间
2014-6-3
在线时间
495 小时
最后登录
2017-2-24
发表于 2016-12-21 10:07:06 | 显示全部楼层
感谢分享!!

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表