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)

猜你喜欢

转载自blog.csdn.net/h824612113/article/details/129448181