GameFrameWork框架(Unity3D)使用笔记(八) 实现场景加载进度条

这篇具有很好参考价值的文章主要介绍了GameFrameWork框架(Unity3D)使用笔记(八) 实现场景加载进度条。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

        游戏在转换场景的时候,需要花费时间来加载相关的资源。而这个过程往往因为游戏场景的规模和复杂度以及玩家电脑配置的原因花费一小段时间(虽然这个项目里用不到)。

        所以,如果这一小段时间,画面就卡在这里,啥也做不了,玩家也不知道啥时候能加载好。这个等待的时间实际上非常地影响玩家的使用体验。

        目前大多数游戏在转换关卡这种时候都会有个加载界面,显示加载进度。这样玩家可以对啥时候能加载好有个心理预估(判断要不要因为加载太久浪费时间不如卸载游戏(开个玩笑))。

        一般加载场景显示进度条的方法搜搜就有了,就是利用Unity自带的异步加载函数SceneManager.LoadSceneAsync()加载场景,并且通过AsyncOperation跟踪加载进度,从而设置进度条之类的。

        不过,在GameFramework框架下,加载场景的模块被进一步封装,那怎么在UGF下实现加载的进度条就是本篇的主要内容。


一、实现过程讲解

        我看过一些非GF的加载场景的方案,大多数都是:对于从场景a-->场景b的过程,将其变为从场景a-->场景c-->场景b

        其中,场景c里面主要就只有一个加载界面,主要用来显示进度条等内容。这样的话,从a->c可以非常快速地跳转(因为c中就只有个UI所以即便配置不高也能很快跳转)然后玩家在c中观看进度条的时候,后台异步加载场景b,加载完毕后立刻转到场景b。

        但是,在GF框架下,有个不同的地方。就是GF框架预制体所在的Launcher场景是一直存在的,并且框架的UI统一在这个场景里管理。

        所以在GF里实现进度条的功能,我的方案是直接在ProcedureChangeScene流程里面加载新场景的同时显示进度条UI,并且在加载完成后关闭UI。

        此外,由于一些原因(参考这一篇:http://t.csdn.cn/65FDe),想要测试这个进度条的真正效果,需要把当前工程Build再运行测试。


一、拼制UI

 在场景里面拼个进度条UI:

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

然后写UI的逻辑脚本,这里实现控制UI进度条的函数方便之后调用:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace ShadowU
{
    public class LoadingForm: UGuiForm
    {
        public GameObject mask;     //进度条的遮罩
        public Text loadingText;    //加载中的文字

        protected override void OnClose(bool isShutdown, object userData)
        {
            base.OnClose(isShutdown, userData);
        }

        protected override void OnInit(object userData)
        {
            base.OnInit(userData);
        }

        protected override void OnOpen(object userData)
        {
            base.OnOpen(userData);
            loadingText.text = "加载中.  .  .  .  .  .  ";
        }

        protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(elapseSeconds, realElapseSeconds);
            //控制加载中文字后的句号浮动
            string temp = loadingText.text;
            temp.Insert(3, temp[temp.Length - 1].ToString()).Remove(temp.Length - 1);
            loadingText.text = temp;
        }
        void SetProcess(float a)
        {
            a = Mathf.Clamp(a,0f,1.0f);//将数值控制在0-1
            mask.transform.SetLocalScaleX(a);
        }
    }
}

接上脚本,设置好public参数,做成预制体:

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#


二、修改ProcedureChangeScene流程代码

         ProcedureChangeScene开始的时候,加载出Loading界面,同时开始异步加载新场景。

        通过LoadSceneUpdateEvent事件来更新加载界面进度条的进度。

        最后,在场景加载完成后关闭Loading界面即可。

        新的ProcedureChangeScene代码如下:


using GameFramework.Fsm;
using GameFramework.Event;
using GameFramework.DataTable;
using UnityGameFramework.Runtime;
using UnityEngine;
using UnityEngine.SceneManagement;
using System;

namespace ShadowU
{
    public class ProcedureChangeScene : ProcedureBase
    {
        private const int MenuSceneId = 1;   //菜单场景的ID 

        private bool m_ChangeToMenu = false; //是否转换为菜单
        private bool m_IsChangeSceneComplete = false; //转换场景是否结束

        private LoadingForm m_LoadingForm;    //加载界面
        public override bool UseNativeDialog
        {
            get
            {
                return true;
            }
        }

        protected override void OnEnter(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner)
        {
            base.OnEnter(procedureOwner);

            Debug.Log("ChangeScene!");
            m_IsChangeSceneComplete = false; //如果为true的话 则在update中会切换流程

            //打开加载界面
            GameEntry.UI.OpenUIForm(AssetUtility.GetUIFormAsset("LoadingForm"), "Default", 1, this);

            //注册事件
            GameEntry.Event.Subscribe(LoadSceneSuccessEventArgs.EventId,OnLoadSceneSuccess);
            GameEntry.Event.Subscribe(LoadSceneFailureEventArgs.EventId, OnLoadSceneFailure);
            GameEntry.Event.Subscribe(LoadSceneUpdateEventArgs.EventId, OnLoadSceneUpdate);
            GameEntry.Event.Subscribe(LoadSceneDependencyAssetEventArgs.EventId, OnLoadSceneDependencyAsset);
            GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId,OnOpenUIFormSuccess);

            //隐藏所有实体
            GameEntry.Entity.HideAllLoadingEntities();
            GameEntry.Entity.HideAllLoadedEntities();

            //卸载所有场景
            string[] loadedSceneAssetNames = GameEntry.Scene.GetLoadedSceneAssetNames();

            foreach (string sn in loadedSceneAssetNames)
                GameEntry.Scene.UnloadScene(sn);

            //还原游戏速度
            GameEntry.Base.ResetNormalGameSpeed();

            
            //获取下一个场景的ID
            int sceneId = procedureOwner.GetData<VarInt32>("NextSceneId");
            m_ChangeToMenu = sceneId == MenuSceneId; //判断是否是转到菜单
            IDataTable<DRScene> dtScene = GameEntry.DataTable.GetDataTable<DRScene>();
            DRScene drScene = dtScene.GetDataRow(sceneId); //获取数据表行
            if(drScene == null)
            {
                Log.Warning("Can not load scene '{0}' from data table.", sceneId.ToString());
                return;
            }
            GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName),Constant.AssetPriority.SceneAsset,this);
        }



        protected override void OnLeave(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner, bool isShutdown)
        {
            base.OnLeave(procedureOwner, isShutdown);

            GameEntry.Event.Unsubscribe(LoadSceneSuccessEventArgs.EventId, OnLoadSceneSuccess);
            GameEntry.Event.Unsubscribe(LoadSceneFailureEventArgs.EventId, OnLoadSceneFailure);
            GameEntry.Event.Unsubscribe(LoadSceneUpdateEventArgs.EventId, OnLoadSceneUpdate);
            GameEntry.Event.Unsubscribe(LoadSceneDependencyAssetEventArgs.EventId, OnLoadSceneDependencyAsset);
            GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId,OnOpenUIFormSuccess);

            //关闭UI
            if (m_LoadingForm != null)
            {
                m_LoadingForm.Close(isShutdown);
                m_LoadingForm = null;
            }
        }

        protected override void OnUpdate(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner, float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);


            if (!m_IsChangeSceneComplete)   
            {
                return;  //还没完成场景切换
            }

            if (m_ChangeToMenu)
            {
                ChangeState<ProcedureMenu>(procedureOwner);   //菜单
            }else
            {
                ChangeState<ProcedureMain>(procedureOwner);   //游戏运行流程
            }
        }
        
        private void OnLoadSceneSuccess(object sender,GameEventArgs e)
        {
            LoadSceneSuccessEventArgs ne = (LoadSceneSuccessEventArgs)e;
            if(ne.UserData != this)
            {
                return;
            }
            Log.Info("Load scene '{0}' OK.", ne.SceneAssetName);
            m_IsChangeSceneComplete = true;
        }
        private void OnLoadSceneFailure(object sender, GameEventArgs e)
        {
            LoadSceneFailureEventArgs ne = (LoadSceneFailureEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Error("Load scene '{0}' failure, error message '{1}'.", ne.SceneAssetName, ne.ErrorMessage);
        }

        private void OnLoadSceneUpdate(object sender, GameEventArgs e)
        {
            LoadSceneUpdateEventArgs ne = (LoadSceneUpdateEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Info("Load scene '{0}' update, progress '{1}'.", ne.SceneAssetName, ne.Progress.ToString("P2"));
            //更新加载界面的进度条
            if(m_LoadingForm != null)
                m_LoadingForm.SetProcess(ne.Progress);
        }

        private void OnLoadSceneDependencyAsset(object sender, GameEventArgs e)
        {
            LoadSceneDependencyAssetEventArgs ne = (LoadSceneDependencyAssetEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Info("Load scene '{0}' dependency asset '{1}', count '{2}/{3}'.", ne.SceneAssetName, ne.DependencyAssetName, ne.LoadedCount.ToString(), ne.TotalCount.ToString());
        }
        private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
        {
            OpenUIFormSuccessEventArgs ne = (OpenUIFormSuccessEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            m_LoadingForm = (LoadingForm)ne.UIForm.Logic;
        }
    }
}

 三、测试效果

        直接编辑器模式运行的话看场景加载并不是异步的,所以要测试这个进度条的真正效果需要打包运行。

        关于打包的过程之后的文章里会讲,这里就先跳过了。

        测试:

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

 进度条的功能是完成了!

但是。。。又出了新的问题:菜单界面在加载界面之上。

不过编辑器模式运行并没有这个问题:

    gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

但是没有问题只是看上去的。。。如果我立即暂停后观察,其实这个模式下也会两个UI同屏,只不过相对来说时间比较短暂。我经过一段时间分析后,想到UGuiForm里面在关闭和打开界面的时候都有淡入淡出的逻辑,于是作出如下假设:

加载界面看到菜单界面并不是渲染层级先后的问题,而是它们都在淡入或淡出过程中。所以说,他们都处于半透明状态所以能都看得见。

(因为基于这个假设能解决问题那我就不去证明我这个假设到底对不对了嘿嘿~) 

那解决办法就很灵活了。比如可以判断取消打开加载界面的淡入效果。不过我觉得这样改结构会很不美观。

于是我打算这样处理:让加载界面至少停留一秒。同时减小淡入淡出的时间。

在UGuiForm.cs里面修改淡入淡出时间

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

修改一下ProcedureChangeScene的代码(增加了和loadingTimer变量有关的部分):


using GameFramework.Fsm;
using GameFramework.Event;
using GameFramework.DataTable;
using UnityGameFramework.Runtime;
using UnityEngine;
using UnityEngine.SceneManagement;
using System;

namespace ShadowU
{
    public class ProcedureChangeScene : ProcedureBase
    {
        private const int MenuSceneId = 1;   //菜单场景的ID 

        private bool m_ChangeToMenu = false; //是否转换为菜单
        private bool m_IsChangeSceneComplete = false; //转换场景是否结束

        private LoadingForm m_LoadingForm;    //加载界面
        private float loadingTimer;           //加载界面停留的计时器
        public override bool UseNativeDialog
        {
            get
            {
                return true;
            }
        }

        protected override void OnEnter(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner)
        {
            base.OnEnter(procedureOwner);

            Debug.Log("ChangeScene!");
            m_IsChangeSceneComplete = false; //如果为true的话 则在update中会切换流程

            //打开加载界面
            GameEntry.UI.OpenUIForm(AssetUtility.GetUIFormAsset("LoadingForm"), "Default", 1, this);
            loadingTimer = 1.0f;         //加载界面至少停留一秒

            //注册事件
            GameEntry.Event.Subscribe(LoadSceneSuccessEventArgs.EventId,OnLoadSceneSuccess);
            GameEntry.Event.Subscribe(LoadSceneFailureEventArgs.EventId, OnLoadSceneFailure);
            GameEntry.Event.Subscribe(LoadSceneUpdateEventArgs.EventId, OnLoadSceneUpdate);
            GameEntry.Event.Subscribe(LoadSceneDependencyAssetEventArgs.EventId, OnLoadSceneDependencyAsset);
            GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId,OnOpenUIFormSuccess);

            //隐藏所有实体
            GameEntry.Entity.HideAllLoadingEntities();
            GameEntry.Entity.HideAllLoadedEntities();

            //卸载所有场景
            string[] loadedSceneAssetNames = GameEntry.Scene.GetLoadedSceneAssetNames();

            foreach (string sn in loadedSceneAssetNames)
                GameEntry.Scene.UnloadScene(sn);

            //还原游戏速度
            GameEntry.Base.ResetNormalGameSpeed();

            
            //获取下一个场景的ID
            int sceneId = procedureOwner.GetData<VarInt32>("NextSceneId");
            m_ChangeToMenu = sceneId == MenuSceneId; //判断是否是转到菜单
            IDataTable<DRScene> dtScene = GameEntry.DataTable.GetDataTable<DRScene>();
            DRScene drScene = dtScene.GetDataRow(sceneId); //获取数据表行
            if(drScene == null)
            {
                Log.Warning("Can not load scene '{0}' from data table.", sceneId.ToString());
                return;
            }
            GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName),Constant.AssetPriority.SceneAsset,this);
        }



        protected override void OnLeave(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner, bool isShutdown)
        {
            base.OnLeave(procedureOwner, isShutdown);

            GameEntry.Event.Unsubscribe(LoadSceneSuccessEventArgs.EventId, OnLoadSceneSuccess);
            GameEntry.Event.Unsubscribe(LoadSceneFailureEventArgs.EventId, OnLoadSceneFailure);
            GameEntry.Event.Unsubscribe(LoadSceneUpdateEventArgs.EventId, OnLoadSceneUpdate);
            GameEntry.Event.Unsubscribe(LoadSceneDependencyAssetEventArgs.EventId, OnLoadSceneDependencyAsset);
            GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId,OnOpenUIFormSuccess);

            //关闭UI
            if (m_LoadingForm != null)
            {
                m_LoadingForm.Close(isShutdown);
                m_LoadingForm = null;
            }
        }

        protected override void OnUpdate(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner, float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);


            if (!m_IsChangeSceneComplete)   
            {
                return;  //还没完成场景切换
            }

            m_LoadingForm.SetProcess(1);

            if(loadingTimer > 0)  
            {
                loadingTimer -= elapseSeconds;//更新计时器
                return;
            }

            if (m_ChangeToMenu)
            {
                ChangeState<ProcedureMenu>(procedureOwner);   //菜单
            }else
            {
                ChangeState<ProcedureMain>(procedureOwner);   //游戏运行流程
            }
        }
        
        private void OnLoadSceneSuccess(object sender,GameEventArgs e)
        {
            LoadSceneSuccessEventArgs ne = (LoadSceneSuccessEventArgs)e;
            if(ne.UserData != this)
            {
                return;
            }
            Log.Info("Load scene '{0}' OK.", ne.SceneAssetName);
            m_IsChangeSceneComplete = true;
        }
        private void OnLoadSceneFailure(object sender, GameEventArgs e)
        {
            LoadSceneFailureEventArgs ne = (LoadSceneFailureEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Error("Load scene '{0}' failure, error message '{1}'.", ne.SceneAssetName, ne.ErrorMessage);
        }

        private void OnLoadSceneUpdate(object sender, GameEventArgs e)
        {
            LoadSceneUpdateEventArgs ne = (LoadSceneUpdateEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Info("Load scene '{0}' update, progress '{1}'.", ne.SceneAssetName, ne.Progress.ToString("P2"));
            //更新加载界面的进度条
            if(m_LoadingForm != null)
                m_LoadingForm.SetProcess(ne.Progress);
        }

        private void OnLoadSceneDependencyAsset(object sender, GameEventArgs e)
        {
            LoadSceneDependencyAssetEventArgs ne = (LoadSceneDependencyAssetEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            Log.Info("Load scene '{0}' dependency asset '{1}', count '{2}/{3}'.", ne.SceneAssetName, ne.DependencyAssetName, ne.LoadedCount.ToString(), ne.TotalCount.ToString());
        }
        private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
        {
            OpenUIFormSuccessEventArgs ne = (OpenUIFormSuccessEventArgs)e;
            if (ne.UserData != this)
            {
                return;
            }

            m_LoadingForm = (LoadingForm)ne.UIForm.Logic;
        }
    }
}

此外还有个细节:就是进度条到90就不动了。其实场景加载到90%的时候就已经加载完了,最后的10%就在于有没有把场景显示出来。那为了看起来不膈应,我上面的代码里在加载完场景后手动把进度条设置为100:

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

打包看下最终效果:

gamefromwork框架,GameFrameWork使用笔记,GameFrameWork,UGF,unity,c#

 效果挺理想的,那就这样了!

        文章来源地址https://www.toymoban.com/news/detail-785633.html

到了这里,关于GameFrameWork框架(Unity3D)使用笔记(八) 实现场景加载进度条的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Unity3D框架】Unity Package Manager自定义包管理实践

            在公司开发的前两个项目,虽然搭建了基础的框架,有一些目录划分,但是当项目复杂度增长到一定程度,以及后续新开了一些新的项目之后,对于基础框架的管理就遇到了一些挑战,主要体现在以下几个方面:         1、多项目之间拷贝了类似的基础框架,但是

    2024年02月03日
    浏览(60)
  • Unity3D 基础——使用 Vector3.Lerp 实现缓动效果

     让一个物体从当前位置移动到另一个位置   Vector3-Lerp - Unity 脚本 API https://docs.unity.cn/cn/current/ScriptReference/Vector3.Lerp.html 1.在场景中新建两个 Cube 立方体,在 Scene 视图中将两个 Cude的位置错开。  2.新建 C# 脚本 MoveToTarget.cs(写完记得保存) 3.将脚本绑定到 Cude 上,然后将其

    2024年02月06日
    浏览(30)
  • Unity3d使用Netcode实现Hololens、Android和Pc端通信

    在Unity3d中使用现有的Netcode/Mirror组件实现Hololens、Android和Pc端的三方通信,可以指定一平台为服务器端,其他的两平台多为客户端,不过通常是进行两个平台的通信即可。在这片文章中,可以实现平移、缩放以及旋转的同步 具体步骤如下: 版本:unity2020.3LTS、VS2019 一、配置

    2024年02月09日
    浏览(34)
  • 【Unity3D日常开发】Unity3D中协程的使用

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 最近有小伙伴问协程怎么用、怎么写,我也是会用会写,但是原理不是很明白。 学习了一下,总结出

    2024年02月12日
    浏览(41)
  • 我的框架-Unity3d中的用户数据储存模块UserDB

    前言:         我们在开发一些小游戏的时候,不可能将所有的数据都上传到服务器里去储存,有很多数据是需要储存到用户本地的。比如一些简单的用户设置,一些只需要打开一次的用户提示记录等等。当所需储存的数据比较少的时候,我们可以直接用 PlayerPrefs.SetString 直

    2023年04月14日
    浏览(28)
  • Unity3D学习笔记8——GPU实例化(3)

    在前两篇文章《Unity3D学习笔记6——GPU实例化(1)》《Unity3D学习笔记6——GPU实例化(2)》分别介绍了通过简单的顶点着色器+片元着色器,以及通过表面着色器实现GPU实例化的过程。而在Unity的官方文档Creating shaders that support GPU instancing里,也提供了一个GPU实例化的案例,这里就详

    2023年04月22日
    浏览(28)
  • 用Unity3D制作FPS游戏的学习笔记————人物移动、利用鼠标实现视角转动和人物跳跃(含人物悬空不掉落修复)

    前言: 这是我第一次发布文章,此文章仅供参考,我也是刚学习接触untiy,在制作项目的过程中将有用的写下来记一记,以便自己之后能回头看看,各位大佬轻点喷,若有错误请麻烦积极提谢谢各位。该文章参考自B站UP主蔡先森_rm-rf发布的 【第一人称射击游戏教程2.0【已完结

    2024年04月27日
    浏览(36)
  • Unity3D实现给3D物体添加文本标签

    本教程将向您展示如何通过编写脚本来实现在Unity3D中给3D物体添加文本标签的通用方法。了解如何创建文本标签预制体,并通过编写脚本将其添加到3D物体上,以提供更多信息或标识物体。

    2024年02月04日
    浏览(83)
  • 【unity3D】退出游戏功能的实现

    💗 未来的游戏开发程序媛,现在的努力学习菜鸡 💦本专栏是我关于游戏开发的学习笔记 🈶本篇是unity的退出游戏功能的实现 新建一个button按钮,在Hierachy面板点击右键,然后UI—Button。 调节Button位置和Button下面Text文本的内容和大小。 新建一个脚本 新建一个空物体,然后

    2024年02月11日
    浏览(40)
  • 【Unity3D】实现UI点击事件穿透

              注意:EventSystem.current.RaycastAll获取到的对象列表是能够接受事件的,假如你的按钮Button自己身上没有Image,而是Button的子物体有,那么你就要给这个子物体也加上标签Tag才能响应到。 如果ExecuteEvents.Execute不管用,可以试试ExecuteEvents.ExecuteHierarchy

    2024年02月04日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包