GameFrameWork的热更新分包加载(按需下载)功能的实现

写在最前面,逻辑实现可以最后看,可以先看下面的配置,ab配置和配置表使用这些

逻辑实现

思考:分包加载的话主要是利用了ReourceGroup的概念,如果是其他的框架,就是指定ab包来加载的,简单来说就是对ab包进行分组;

1.一般来说,可以将ab包根据模块划分,比如音频,模型,字体这种;

2.或者是按照独立功能来分一个玩法一个ab包;

我选择,相互结合的方式,将通用的资源提取出来放在一个共用文件夹中,分组名叫Base(Common),这都随便,然后因为我们不同的场景过多,属于不同的模块,把他当成关卡来处理,每一关的都打包到一起,这样就是有多少个关卡就会有多少个分组+通用分组。

PS:有个特殊 情况,因为Unity机制的问题,Scene场景不能和其他资源放一起,所以我把所有的Scene放在一个ab里面,因为Scene主要是一份关联信息表,除非没有拆分的资源,否则他不会打进这个ab,如果不拆分的话,那么相关的资源也会打进这个ab包,这个ab就会很大,一般来说这个scene不会很大,哪怕你有几十个场景。

逻辑实现

核心的修改在于框架里面的这个类ProcedureResourcesUpdate:

在这里插入图片描述

可以看到在他的OnEnter里面有这样一段代码

StartUpdateResources(null);

我们在回顾下之前的流程:

在这里插入图片描述

如果判断你有资源需要更新那么他就会进入这个下载资源的逻辑,刚开始这个参数是null

在这里插入图片描述

跟踪进去能看到这里有俩个同名函数,一个带参数,一个没有参数,如果不传入参数,他就会下载默认的资源组,因为我们一般很少会指定资源组,默认是通过名字/路径来加载使用。我们利用的就是下面的那个方法,传入指定资源组,这样在刚开始的时候我们就让他下载比如通用资源组,然后在进入其他玩法的时候指定资源组下就行了。

在这里插入图片描述

逻辑修改如下

StartUpdateResources(“Base”);//Base是我默认的资源组的名字,其他的资源组我为了方便就让他跟玩法的名字保持一致了,这个随便

这样修改之后我们刚开始进入的时候只会下载通用资源了,可以通过HFS和下载的路径来观察是不是只下了我们指定的资源组(可以参考我下面的ab包的分组,使用了其他的插件工具。)

改完这里先测试以为没问题,后来发现校验出了问题,就是procedureResourceCheck那里,他的逻辑如下:

private void OnCheckResourcesComplete(int movedCount, int removedCount, int updateCount, long updateTotalLength, long updateTotalCompressedLength)
  {
      m_CheckResourcesComplete = true;
      m_NeedUpdateResources = updateCount > 0;
      m_UpdateResourceCount = updateCount;
      m_UpdateResourceTotalCompressedLength = updateTotalCompressedLength;
      Log.Info("Check resources complete, '{0}' resources need to update, compressed length is '{1}', uncompressed length is '{2}'.", updateCount.ToString(), updateTotalCompressedLength.ToString(), updateTotalLength.ToString());
  }

可以看到他是会验证所有的资源组,我们只需要他验证我们指定的那个默认资源组,代码如下

private void OnCheckResourcesComplete(int movedCount, int removedCount, int updateCount, long updateTotalLength, long updateTotalCompressedLength)
{
    IResourceGroup resourceGroup = GameEntry.Resource.GetResourceGroup("Base");
    if (resourceGroup == null)
    {
        Log.Error("has no resource group '{Base}',", "Base");
        return;
    }

    m_CheckResourcesComplete = true;
    m_NeedUpdateResources = !resourceGroup.Ready;
    m_UpdateResourceCount = resourceGroup.TotalCount - resourceGroup.ReadyCount;
    m_UpdateResourceTotalCompressedLength = updateTotalCompressedLength;
    Log.Info("Check resources complete, '{0}' resources need to update,  unzip length is '{1}'.", m_UpdateResourceCount.ToString(), (resourceGroup.TotalLength - resourceGroup.ReadyLength).ToString());
}

这样他就只会验证默认资源了,然后选择进入到ProcedureResourceUpdate的流程,还是正常的逻辑流程,正常来说第一次之后都是正常的逻辑流程,这个每个人不一样,我这里是加载热更流程,然后切换场景ProcedureChangeScene,我加了一个ProcedureGame的流程,因为是热更工程,这里的状态机需要自己手动创建,流程就是GameHotFixEntry→ProcedurePreload→ProcedureChangeScene <→ProcedureGame

因为场景太多,我也不想每个场景一个流程这样太复杂了,所以所有的玩法只有一个状态,然后根据不同的场景id,我来加载对应的玩法,指定资源组下载.然后又发现了新的问题,因为资源组下载走的是更新,编辑器读取的是编辑器路径下的资源不能直接使用资源组的逻辑,我就让编辑器模式下直接查找资源了,这样编辑器运行不至于报错,否则汇报ResrouceGroup相关的错误,主要修改了ProcedureChangeScene下面的OnEnter里面的逻辑,上面的订阅和关闭逻辑自己看着修改:

#if UNITY_EDITOR
  {
      sceneAssetIsCompleted = true;
      GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset, this);
  }            
  #else
  {
      sceneAssetIsCompleted = GetCurResourceGroupIsLocalComplete(drScene.AssetName);
      if (!sceneAssetIsCompleted)
      {
          //下载呗
          GameEntry.Resource.UpdateResources(drScene.AssetName, OnUpdateResourcesComplete);
      }
      else
      {
          GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset, this);   
      }
  } 
  #endif

我这里封装了一个方法,判定这个资源组是不是下载了,如果已经下载了到本地了,那么就直接加载了走LoadScene,否则就在下载成功之后在加载LoadScene,代码如下:

//判定bending资源组是否已经资源完整
private bool GetCurResourceGroupIsLocalComplete(string resourceGourpName)
{
    bool flag = false;
    List<IResourceGroup> results = new List<IResourceGroup>();
    GameEntry.Resource.GetAllResourceGroups(results);
    for (int i = 0; i < results.Count; i++)
    {
        if (results[i].Name == resourceGourpName)
        {
            if (results[i].Ready)
            {
                flag = true;
                break;   
            }
        }   
    }

    return flag;
}
//根据场景的id获取当前的场景信息
private DRScene GetDrSceneBySceneId(int sceneId)
{
    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 null;
    }

    return drScene;
}
//这个是下载成功之后的监听
private void OnUpdateResourcesComplete(IResourceGroup resourceGroup, bool result)
{
    if (result)
    {
        // SetDownFinishState();
        DRScene drScene = GetDrSceneBySceneId(m_cuSceneId);
        GameEntry.Scene.LoadScene(AssetUtility.GetSceneAsset(drScene.AssetName), Constant.AssetPriority.SceneAsset, this);
        Log.Info("OnUpdateResourcesComplete -加载场景---Scene---"+m_cuSceneId+"Update resources complete with no errors.");
    }
    else
    {
        Log.Error("OnUpdateResourcesComplete  Scene---"+m_cuSceneId+"Update resources complete with errors.");
    }
}

最后我在ProcedureGame里面加了事件监听,用的也是GF的Event模块

protected override void OnEnter(IFsm<IProcedureManager> procedureOwner)
{
    base.OnEnter(procedureOwner);
    m_ProcedureOwner = procedureOwner;
    Game.GameEntry.Event.Subscribe(PlayerEventArgs.EventId,OnPlayerEvent);
    
}

public virtual void OnPlayerEvent(object sender, GameEventArgs e)
{
    var args = e as PlayerEventArgs;
    switch (args.EventType)
    {
        case PlayerEventType.Event_ReplaceScene:
        {
            int sceneId = (int)args.EventData;
            ReplaceScene(sceneId);
        }
            break;
        case PlayerEventType.Event_GetCurSceneId:
        {
            int sceneId = (int)args.EventData;
            ReplaceScene(sceneId);
        }
            break;
    }
}

private void ReplaceScene(int sceneId)
{
    DRScene drScene = GetDrSceneBySceneId(sceneId);
    Debug.Log("当前场景是==="+drScene.Id+"--名称是--"+drScene.AssetName);
    m_ProcedureOwner.SetData<VarInt32>("NextSceneId", sceneId);
    ChangeState<ProcedureChangeScene>(m_ProcedureOwner);
}

这些完成基本就实现了GF框架上的分包逻辑实现,也就是我们说的按需加载。也实现了包体缩减的修改。这里只说实现的逻辑,具体ab这里可能一下子说不了很清楚。

当然如果我们想整包加载怎么办,需要吧代码改回去吗?

当然不用,这个时候我们修改ab包的资源分组就好,比如所有的ab分组改成Base,这样就是整包下载了。

AB包处理

GF本身有自己的ab插件,不过需要自己去配置表里面改,我觉得不是很方便就用了大佬的插件。用法参考我之前的那个GameFrameWork打包文章就好了,这里就看下截图,看看资源组的命名就好了。

在这里插入图片描述
在这里插入图片描述

依赖的处理

之前查资料准备自己写一个依赖查找的工具,后来发现GF自带一个查依赖的工具,我就使用这个工具+自己检查,做了一个大概的分类,这个我已场景为单位,然后把每个场景用到的资源放到自己的ab包文件夹里面,零散的和共用的都先放到公用的文件夹了,这个文件多的时候,左下角可以输入名字过滤,比如点击一个场景过去会看到下面的图:

在这里插入图片描述

配置表的处理

加载场景的使用目前主要用Scene.txt和DefaultScene.txt,这个就是吧自己的场景和信息添加进去,代碼调用的时候注意自己的路径要对应的修改(配置参考starforce就可以了。)

工具使用

1.GF自带工具的Resource Analyzer,查看依赖,帮助ab包分包,移动的话需要自己手动

2.插件ResourceRuleEditor ,进行ab包资源组分组

3.插件DataTableGenerator,datatable插件生成。

4.ResourceBuilder进行ab包的打包。

ps:欢迎大家进q群交流游戏开发的问题(632313288)

世界地图矢量数据可以通过多种网站进行下载。以下是一些提供免费下载世界地图矢量数据的网站: 1. Open Street Map (https://www.openstreetmap.org/): 这个网站可以根据输入的经纬度或手动选定范围来导出目标区域的矢量图。导出的数据格式为osm格式,但只支持矩形范围的地图下载。 2. Geofabrik (http://download.geofabrik.de/): Geofabrik提供按洲际和国家快速下载全国范围的地图数据。数据格式支持shape文件格式,包含多个独立图层,如道路、建筑、水域、交通、土地利用分类、自然景观等。数据每天更新一次。 3. bbbike (https://download.bbbike.org/osm/): bbbike提供全球主要的200多个城市的地图数据下载,也可以按照bbox进行下载。该网站还提供全球的数据,数据格式种类齐全,包括geojson、shp等。 4. GADM (https://gadm.org/index.html): GADM提供按国家或全球下载地图数据的服务。该网站提供多种格式的数据下载。 5. L7 AntV (https://l7.antv.antgroup.com/custom/tools/worldmap): L7 AntV是一个提供标准世界地图矢量数据免费下载的网站。支持多种数据格式下载,包括GeoJSON、KML、JSON、TopJSON、CSV和高清SVG格式等。可以下载中国省、市、县的矢量边界和世界各个国家的矢量边界数据。 以上这些网站都提供了世界地图矢量数据的免费下载服务,你可以根据自己的求选择合适的网站进行下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值