Unity游戏开发-游戏热更新以及登录流程

【Unity】热更新流程详解 Unity热更新步骤解析 阅读详情

本篇主要分享基于热更新的游戏初始化方案。

整体初始化的流程大致为:检查是否需要解压资需要则解压,之后再检查是否存在需要热更新的资源文件需要则更新,更新完成后则初始化结束可进入登录界面。

关于登录这块的处理还涉及到了一个状态机有兴趣可以参考这里《unity游戏开发-简易的状态机》

其中热更新完整的方案本身包含了以下四个部分:

  1. 资源文件打包
  2. 资源文件比较
  3. 资源文件下载
  4. 资源文件更新

其中资源文件打包可以参考这里《Unity游戏开发-Assetbundle打包》

本篇主要会涉及到资源文件比较和更新。

另外关于资源文件的下载会放到这里《Unity游戏开发-客户端Http下载》

整体设计

简单解释下这张泳道流程图

主要的逻辑在就是中间登录流程这张图,一共分三步:解压bundle=>检查并处理热更=>登录就绪

其中关于资源解压的所有逻辑放在了左边流程图对应的模块处理,热更新的所有逻辑放在了右边的流程图对应的模块处理。

另外对于整个游戏内容来说,我们的登录为一个逻辑模块。外部的UI展示界面可以直接实例化一个登录逻辑模块,然后将一个显示用的回调函数传入该模块即可。

逻辑模块内部将解压、更新时的一些关键性数据传入到回调函数中,即可让外部处理进度条等等的展示了。

这样做的好处是,登录的UI界面功能和逻辑模块变成了两个互相独立的模块耦合性更低。

而且将业务相关的UI界面和更接近于框架层面的逻辑分离,有利于多项目迁移。

登录流程模块

根据我们所画的流程图可以看出登录流程并没有直接处理任何具体的事情,只是一个单纯的流程控制模块。

因此我采用了状态机的形式来处理该模块。

内部实际仅仅只初始化了解压和热更两个状态模块,并将之关联起来,最后所有流程结束就调用就绪方法。

利用状态机的形式来处理登录逻辑流程主要为了解压和热更模块之间的衔接关系更清楚的展示再登录流程中也更利于流程的改动。

如果直接模块之间相互调用则每次修改流程会非常的麻烦,每次都需要重新阅读代码找到衔接点修改调用的方法。

如果用事件来衔接各个模块其实也挺麻烦,如果流程状态很多的情况下,事件会零零散散的分布在各个模块,其实也不利于阅读和修改流程。

// 登录流程
public class LoginMachine : IStateMachine
{
    // 资源包解压模块
    private UnPackAssetBundle mUnPackBundle = new UnPackAssetBundle();
    // 热更新模块
    private HotUpdate mHotUpdate = new HotUpdate();

    // 流程结束回调
    private Action mFnOnEnd;

    // 初始化
    public void Init(Action<string, float> fnLoginShow, Action fnOnEnd)
    {
        // 初始化状态
        mUnPackBundle.Init(fnLoginShow, this);
        mHotUpdate.Init(fnLoginShow, this);
        mFnOnEnd = fnOnEnd;

        // 注册流程
        AddTransition(mUnPackBundle, mHotUpdate, "end");
    }

    // 启动流程
    public void Start()
    {
        mUnPackBundle.DoStart(null);
    }

    protected override void DoEnd(string key)
    {
        if ("hotError".Equals(key))
        {
            FDebug.LogError("热更新出错,流程结束!");
        }
        else
        {
            mFnOnEnd?.Invoke();
        }
    }
}

其中有一个fnLoginShow,是留给UI界面展示用的回调。

另外AddTransition(a,b,c)是一个状态迁移的注册方法,其中a代表前一个状态,b代表下一个状态,c代表条件。即a在以c方式结束的情况下迁移到b状态。

在这里即解压模块在以"end"的方式结束的情况下调起热更模块。

最后整个流程结束时若发生了需要处理的错误"hotError"则处理下异常,否则调用mFnOnEnd即我们设计的就绪。

解压模块

设计方案:

中间判定是否需要解压简单的判定一下我们存放资源的路径(Application.persistentDataPath + "/Bundles")下是否存在版本清单文件即可。

因为新用户首次打开游戏的时候还没有做过解压,因此可写路径下不会存在我们的资源文件和版本清单文件。而执行完一次解压之后我们就会把资源文件解压到该路径下,并且生成版本清单文件,因此该判定是有效的。

解压的时候这边是考虑开了一个线程来处理,因为解压是一个比较耗时的过程,玩家肯定不希望在这么长的时间内看到游戏界面是卡主的,因此不能阻塞主线程。

整体处理:

// 资源初始化解压模块
public class UnPackAssetBundle : ILoginState
{
    // 当前正在解压的文件编号
    private int mUnPackIdx = 0;
    // 版本文件
    private AssetVersion version;
    // 进度显示回调
    private Action<string, float> mFnLoginShow;

    #region 流程
    public override void OnStart(string key)
    {
        if (CheckInited())
        {
            DoEnd("end");
        }
        else
        {
            InitAsset();
        }
    }

    public override void DoUpdate()
    {
        string title = "解压文件中:" + mUnPackIdx.ToString() + "/" + version.files.Count.ToString();
        float progress = (float)mUnPackIdx / version.files.Count;
        mFnLoginShow?.Invoke(title, progress);
    }

    public override void Init(Action<string, float> fnLoginShow, IStateMachine machine)
    {
        mFnLoginShow = fnLoginShow;
        Init(machine);
    }
    #endregion

    // 检查资源文件是否初始化过了
    public bool CheckInited()
    {
        FDebug.Log("资源包文件路径:" + PathMgr.instance.AssetBundlePath);
        if (!System.IO.Directory.Exists(PathMgr.instance.AssetBundlePath)
            || !System.IO.File.Exists(PathMgr.instance.AssetBundlePath + "/version"))
        {
            mUnPackIdx = 0;
            // 初始化版本文件
            FileTool.TruncateDirectory(PathMgr.instance.AssetBundlePath);
            version = DataWriteRead.ReadJson<AssetVersion>(PathMgr.instance.StreamingPath + "/version");
            return false;
        }
        return true;
    }

    // 初始化资源文件
    public void InitAsset()
    {
        // 开线程异步执行
        Thread thread = new Thread(UnCompressAsset);
        thread.IsBackground = true;
        thread.Start();
    }

    // 解压所有的资源
    private void UnCompressAsset()
    {
        for(; mUnPackIdx < version.files.Count; mUnPackIdx++)
        {
            string fileName = version.files[mUnPackIdx].fileName;
            FileTool.DecompressFileLZMA(PathMgr.instance.StreamingPath + "/" + fileName, PathMgr.instance.AssetBundlePath + "/" + fileName);
        }
        // 写入版本文件
        DataWriteRead.SaveJson(PathMgr.instance.AssetBundlePath + "/version", version);
    }
}

代码中间又一个块用region包裹起来的"流程",该部分处理的都是状态机中状态的接口实现。

DoUpdate对应了unity的update,里面主要将当前解压文件的进度文字和进度数值传递到mFnLoginShow中供界面展示使用。

其他实现没有什么特别的地方。

热更模块

热更模块相对于解压来说要复杂一些,因为要处理从服务器获取版本清单文件,文件对比校验,下载,解压。

设计方案:

检查是否需要热更:

设计方案:

首先从服务器拉取最新的版本清单文件。

将获取的清单文件逐一和本地做比较,若本地文件md5和服务器不一致则添加到热更列表。

最后若热更列表数量大于0则表示需要热更。

代码实现:

    // 获取服务器版本文件
    HttpMgr.instance.HttpPost(HttpEventId.HTTP_POST_HOT_VERSION);

    // 收到服务器版本消息回调
    private void OnRecvVersion(int resCode, string jsonData)
    {
        if (resCode == 200)
        {
            mServerVersion = LitJson.JsonMapper.ToObject<AssetVersion>(jsonData);
            if (CheckHotUpdate(mServerVersion))
            {
                StartHotUpdate();
            }
            else
            {
                DoEnd();
            }
        }
        else
        {
            FDebug.LogError("服务器版本文件获取失败");
            DoEnd("hotError");
        }
    }
    
    // 检查是否需要热更新
    public bool CheckHotUpdate(AssetVersion serverVersion)
    {
        if (serverVersion == null)
        {
            return false;
        }

        AssetVersion version = DataWriteRead.ReadJson<AssetVersion>(PathMgr.instance.AssetBundlePath + "/version");
        // 本地版本不存在则更新所有文件
        if (version == null)
        {
            foreach (AssetFile serverFile in serverVersion.files)
            {
                HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                mHotFiles.Add(hotFile);
            }
            FDebug.Log("本地热更新文件不存在");
        }
        // 本地版本存在,则更新本地没有的文件或者md5不一致的文件
        else
        {
            foreach (AssetFile serverFile in serverVersion.files)
            {
                if (version.TryGetAssetFile(serverFile.fileName, out var localFile))
                {
                    if (!localFile.md5.Equals(serverFile.md5))
                    {
                        HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                        mHotFiles.Add(hotFile);
                        FDebug.Log(serverFile.fileName + "md5不一致=>本地md5:" + localFile.md5 + "。服务器md5:" + serverFile.md5);
                    }
                    else
                    {
                        FDebug.Log(serverFile.fileName + "md5一致");
                    }
                }
                else
                {
                    HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                    mHotFiles.Add(hotFile);
                    FDebug.Log(serverFile.fileName + "本地文件不存在");
                }
            }
        }

        return mHotFiles.Count > 0;
    }

public class HotFile
{
    public string serverFile;
    public string md5;
    public int size;
    public string localFile;
    public string fileName;
    public HttpDownPack downPack;
}

代码实现比较简单没啥好说的。。。

中间有一个HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);

是将资源文件信息的数据结构转换为热更新需要的数据结构。

HotFile结构中的HttpDownPack为下载文件需要的数据结构,该结构会在调用下载的时候由下载模块返回回来。我们就可以通过读取HttpDownPack中的各项数据来显示下载进度了。

serverFile为服务器的下载路径,localFile为本地需要保存的路径。

执行热更新:

执行热更简单来讲就是将热更文件列表中的文件逐一拿去调用下载模块。

中间如果出错就直接以hotError的形式结束热更模块。

中途下载中的时候则不停的回调mFnLoginShow来展示下载的进度。

设计方案:

代码实现:

// 开始下载流程
    private void StartHotUpdate()
    {
        // 设置下载总大小
        foreach (var oneHotFile in mHotFiles)
        {
            mTotalSize += oneHotFile.size;
        }
        // 下载第一个文件
        mDownIdx = 0;
        var hotFile = mHotFiles[mDownIdx];
        hotFile.downPack = HttpMgr.instance.HttpDownLoad(hotFile);
        // 修改状态为下载状态
        mState = EHotState.HotUpdate;
    }

    // 下载状态时定时回调
    private void DoHotUpdate()
    {
        var curHotFile = mHotFiles[mDownIdx];
        HttpDownState state = curHotFile.downPack.state;
        if (state == HttpDownState.Complete)
        {
            // 记录已下载的大小
            mNowDownSize += curHotFile.size;
            // 下载下一个
            mDownIdx++;
            if (mDownIdx < mHotFiles.Count)
            {
                var hotFile = mHotFiles[mDownIdx];
                hotFile.downPack = HttpMgr.instance.HttpDownLoad(hotFile);
            }
            // 结束下载
            else
            {
                StartUnPack();
            }
        }
        else if (state == HttpDownState.Error ||
            state == HttpDownState.Md5Failed)
        {
            DoEnd("hotError");
        }
        else
        {
            int curSize = mNowDownSize + curHotFile.downPack.downSize;
            string title = "热更新中:" + CommonTool.GetMBByteSize(curSize) + "/" + CommonTool.GetMBByteSize(mTotalSize);
            float progress = (float)curSize / mTotalSize;
            mFnLoginShow?.Invoke(title, progress);
        }
    }

代码实现中有一块地方是设置mState = EHotState.HotUpdate;

原因是整个热更模块中有两个小的子模块:下载、解压。

这两个模块中间某些地方都需要被Update调用,因此用了一个状态标记。

再热更模块的Update中根据不同的状态调用不同的子模块逻辑。

执行解压:

热更的解压部分和解压模块非常的相似,都是启动一个线程然后一个一个的解压文件。

update里面不停的获取解压的进度反馈到界面即可。

代码实现:

    // 开始解压
    private void StartUnPack()
    {
        mState = EHotState.UnPack;
        mUnPackIdx = 0;
        // 开启线程解压
        System.Threading.Thread unPack = new System.Threading.Thread(UnPackTask);
        unPack.IsBackground = true;
        unPack.Start();
    }

    // 解压任务执行
    private void UnPackTask()
    {
        for (; mUnPackIdx < mHotFiles.Count; )
        {
            var hotFile = mHotFiles[mUnPackIdx];
            FileTool.DecompressFileLZMA(hotFile.localFile, PathMgr.instance.AssetBundlePath + "/" + hotFile.fileName);
            FileTool.DeleteFile(hotFile.localFile);
            mUnPackIdx++;
        }
    }

    // 解压时定时器获取进度
    private void DoUnPack()
    {
        // 回调显示
        string title = "热更文件解压中:" + mUnPackIdx.ToString() + "/" + mHotFiles.Count.ToString();
        mFnLoginShow?.Invoke(title, (float)mUnPackIdx / mHotFiles.Count);

        if (mUnPackIdx >= mHotFiles.Count)
        {
            // 将服务器版本文件写入本地
            DataWriteRead.SaveJson<AssetVersion>(PathMgr.instance.AssetBundlePath + "/" + "version", mServerVersion);
            DoEnd();
        }
    }

完整代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HotFile
{
    public string serverFile;
    public string md5;
    public int size;
    public string localFile;
    public string fileName;
    public HttpDownPack downPack;
}

// 热更新
public class HotUpdate : ILoginState
{
    // 热更状态
    private enum EHotState
    { 
        None,
        HotUpdate,
        UnPack,
    }
    private EHotState mState;
    // 服务器版本文件
    private AssetVersion mServerVersion;
    // 需要热更的文件
    private List<HotFile> mHotFiles = new List<HotFile>();
    // 总大小
    private int mTotalSize;
    // 当前已下载完成大小
    private int mNowDownSize;
    // 正在下载的文件编号
    private int mDownIdx;
    // 正在解压的文件编号
    private int mUnPackIdx;
    // 显示回调
    private Action<string, float> mFnLoginShow;

    #region 流程
    public override void OnStart(string key)
    {
        mState = EHotState.None;
        // 注册回调
        HttpEvent.instance.AddEvent<int, string>(HttpEventId.HTTP_POST_HOT_VERSION, OnRecvVersion);
        // 获取服务器版本文件
        HttpMgr.instance.HttpPost(HttpEventId.HTTP_POST_HOT_VERSION);
    }

    public override void DoUpdate()
    {
        if (mState == EHotState.HotUpdate)
        {
            DoHotUpdate();
        }
        else if (mState == EHotState.UnPack)
        {
            DoUnPack();
        }
    }

    protected override void OnEnd()
    {

    }

    public override void Init(Action<string, float> fnLoginShow, IStateMachine machine)
    {
        mFnLoginShow = fnLoginShow;
        Init(machine);
    }
    #endregion

    #region 逻辑
    // 收到服务器版本消息
    private void OnRecvVersion(int resCode, string jsonData)
    {
        if (resCode == 200)
        {
            mServerVersion = LitJson.JsonMapper.ToObject<AssetVersion>(jsonData);
            if (CheckHotUpdate(mServerVersion))
            {
                StartHotUpdate();
            }
            else
            {
                DoEnd();
            }
        }
        else
        {
            FDebug.LogError("服务器版本文件获取失败");
            DoEnd("hotError");
        }
    }

    // 检查是否需要热更新
    public bool CheckHotUpdate(AssetVersion serverVersion)
    {
        if (serverVersion == null)
        {
            return false;
        }

        AssetVersion version = DataWriteRead.ReadJson<AssetVersion>(PathMgr.instance.AssetBundlePath + "/version");
        // 本地版本不存在则更新所有文件
        if (version == null)
        {
            foreach (AssetFile serverFile in serverVersion.files)
            {
                HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                mHotFiles.Add(hotFile);
            }
            FDebug.Log("本地热更新文件不存在");
        }
        // 本地版本存在,则更新本地没有的文件或者md5不一致的文件
        else
        {
            foreach (AssetFile serverFile in serverVersion.files)
            {
                if (version.TryGetAssetFile(serverFile.fileName, out var localFile))
                {
                    if (!localFile.md5.Equals(serverFile.md5))
                    {
                        HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                        mHotFiles.Add(hotFile);
                        FDebug.Log(serverFile.fileName + "md5不一致=>本地md5:" + localFile.md5 + "。服务器md5:" + serverFile.md5);
                    }
                    else
                    {
                        FDebug.Log(serverFile.fileName + "md5一致");
                    }
                }
                else
                {
                    HotFile hotFile = GetHotFile(serverFile, serverVersion.downPath);
                    mHotFiles.Add(hotFile);
                    FDebug.Log(serverFile.fileName + "本地文件不存在");
                }
            }
        }

        return mHotFiles.Count > 0;
    }

    // 开始下载流程
    private void StartHotUpdate()
    {
        // 设置下载总大小
        foreach (var oneHotFile in mHotFiles)
        {
            mTotalSize += oneHotFile.size;
        }
        // 下载第一个文件
        mDownIdx = 0;
        var hotFile = mHotFiles[mDownIdx];
        hotFile.downPack = HttpMgr.instance.HttpDownLoad(hotFile);
        // 修改状态为下载状态
        mState = EHotState.HotUpdate;
    }

    // 下载状态时定时回调
    private void DoHotUpdate()
    {
        var curHotFile = mHotFiles[mDownIdx];
        HttpDownState state = curHotFile.downPack.state;
        if (state == HttpDownState.Complete)
        {
            // 记录已下载的大小
            mNowDownSize += curHotFile.size;
            // 下载下一个
            mDownIdx++;
            if (mDownIdx < mHotFiles.Count)
            {
                var hotFile = mHotFiles[mDownIdx];
                hotFile.downPack = HttpMgr.instance.HttpDownLoad(hotFile);
            }
            // 结束下载
            else
            {
                StartUnPack();
            }
        }
        else if (state == HttpDownState.Error ||
            state == HttpDownState.Md5Failed)
        {
            DoEnd("hotError");
        }
        else
        {
            int curSize = mNowDownSize + curHotFile.downPack.downSize;
            string title = "热更新中:" + CommonTool.GetMBByteSize(curSize) + "/" + CommonTool.GetMBByteSize(mTotalSize);
            float progress = (float)curSize / mTotalSize;
            mFnLoginShow?.Invoke(title, progress);
        }
    }

    // 开始解压
    private void StartUnPack()
    {
        mState = EHotState.UnPack;
        mUnPackIdx = 0;
        // 开启线程解压
        System.Threading.Thread unPack = new System.Threading.Thread(UnPackTask);
        unPack.IsBackground = true;
        unPack.Start();
    }

    // 解压任务执行
    private void UnPackTask()
    {
        for (; mUnPackIdx < mHotFiles.Count; )
        {
            var hotFile = mHotFiles[mUnPackIdx];
            FileTool.DecompressFileLZMA(hotFile.localFile, PathMgr.instance.AssetBundlePath + "/" + hotFile.fileName);
            FileTool.DeleteFile(hotFile.localFile);
            mUnPackIdx++;
        }
    }

    // 解压时定时器获取进度
    private void DoUnPack()
    {
        // 回调显示
        string title = "热更文件解压中:" + mUnPackIdx.ToString() + "/" + mHotFiles.Count.ToString();
        mFnLoginShow?.Invoke(title, (float)mUnPackIdx / mHotFiles.Count);

        if (mUnPackIdx >= mHotFiles.Count)
        {
            // 将服务器版本文件写入本地
            DataWriteRead.SaveJson<AssetVersion>(PathMgr.instance.AssetBundlePath + "/" + "version", mServerVersion);
            DoEnd();
        }
    }

    private HotFile GetHotFile(AssetFile assetFile, string downPath)
    {
        HotFile hotFile = new HotFile();
        hotFile.serverFile = downPath + assetFile.fileName;
        hotFile.md5 = assetFile.md5;
        hotFile.size = assetFile.fileSize;
        hotFile.fileName = assetFile.fileName;
        hotFile.localFile = PathMgr.instance.DownTmpPath + "/" + assetFile.fileName;
        return hotFile;
    }
    #endregion
}

 

Unity游戏开发客户端面经——热更新(初级) Unity游戏开发客户端面经 之 热更新 篇 阅读详情

相关推荐

Unity游戏开发必备:HybridCLR热更新实战教程(含iOS/Android适配指南)

本文详细介绍了HybridCLR在Unity游戏开发中的热更新实战教程,包括iOS/Android平台适配指南。通过环境配置、项目结构规划、热更新工作流实现等步骤,帮助开发者高效掌握C#热更新技术,解决移动游戏快速迭代的痛点问题。

weixin_29011395的博客 441

Unity 热更新方案和流程

unity 热更新

s178435865的专栏 2579

Unity游戏开发实战:YooAsset资源管理从配置到热更新的完整流程

本文详细解析了Unity游戏开发中YooAsset资源管理系统的完整实战流程。从项目初始化、环境配置、资源打包策略与性能优化,到运行时异步加载、引用计数管理与生命周期控制,最后深入探讨了基于差分更新的热更新系统设计与高级容错策略。旨在帮助开发者构建高效、稳定的商业级游戏资源管理体系,提升开发效率与游戏运营稳定性。

weixin_29230865的博客 615

Unity 浅谈热更新

介绍了C#热更新方案的优缺点,就说一下这篇文章的主角,HybridCLR: C#热更新方案。HybridCLR是一个特性完整、零成本、高性能、低内存的近乎完美的c#热更新方案。HybridCLR提供一个非常完整的跨平台 CLR 运行时,不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以 AOTUnity开发者从此可以完全使用c#开发,并且零成本使用几乎所有的c#特性,再也不需要使用各种脚本或者不完整的热更新方案了。

q1295006114的博客 1055

Unity商业级游戏开发框架TEngine全解析:模块化、热更新与AI工作流

在Unity游戏开发中,模块化设计与资源管理是构建可维护、高性能项目的核心基础。通过高内聚低耦合的架构,开发者能够将系统拆分为独立的功能模块,如资源、UI、事件等,有效降低代码复杂度并提升团队协作效率。结合YooAsset等企业级资源管理方案,可以实现高效的资源打包、加载与生命周期管理,避免内存泄漏。这些技术为商业级游戏开发提供了工业化、标准化的解决方案,尤其适用于需要快速迭代和热更新的移动游戏与跨平台项目。本文以TEngine框架为例,深入探讨了其如何整合HybridCLR热更新与AI辅助开发工作流,为U

weixin_33713350的博客 338

Unity大型游戏开发全流程实战:从架构设计到性能优化

在游戏开发领域,构建一个稳定、可扩展的大型项目是一项系统工程,它要求开发者深入理解从概念到落地的完整技术链条。其核心原理在于通过严谨的工程方法论,将复杂的游戏系统解耦为清晰的数据层、逻辑层与表现层,并借助事件驱动等设计模式实现模块间通信。这种架构的价值在于显著提升代码的可维护性、团队协作效率以及项目的长期生命力。在实际应用场景中,这具体体现为对渲染管线(如URP/HDRP)的合理选型、使用Addressable Asset System进行精细化的资源与内存管理,以及建立基于Profiler的性能基线监控体

weixin_33816300的博客 384

ET学习笔记之五星麻将0

五星麻将0客户端登录界面前言准备工作配置报错热更新模式热更新程序集HotfixAssembly热更新启动GotoHotfix登录Login登录通道LoginAisle登录界面LoginPanel作者的话 前言 HexMap无限地图已经悄无声息的完成了,之所以悄无声息的完成,是因为弃用了ECS框架,弃用的原因在于不够成熟,而我要开发的游戏需要成熟的解决方案。既然不成熟,那么当初为啥要入坑呢?岂不是浪...

CloudHuCoding 1522

Unity大型手游代码热更新(Hotfix)终极指南

能动不包,能补不重,能灰不炸,能稳不坑!

qq_41179365的博客 1548

一图看懂kbengine版球球大作战之登录、大厅、房间匹配流程

clientkbeAvatar.baseHalls.baseRoom.baseRoom.cellHallsAvatar.cellloginonClientEnabled()Halls.enterRoom(self, cell.pos, cell.dir, self.roomKey)注意:这里的self是Avatar.base,它的类型是一个EntityCallfindRoom()没找到房间会自动创...

优梦创客的专栏 916

CA(载波聚合)

文章目录CA基本概念1. CA分类2. Pcell、Scell以及Serving CellPcell(Primary Cell)Scell(Secondary Cell)3. Pcell、Scell部署场景场景1场景2场景3场景44. CA的协议控制4.1 Camping/Connetion Establishment:4.2 CC Management/SCell Addition/Release:4.3 Handover/Measurements:4.4 SCell激活 CA基本概念 CA也就是我们中文

你又来看我了,一起学习吧 4万+

90+个各种疾病相关医疗数据集

含新冠、传染病、医学图像等

2万+

ET篇:master消息机制介绍(一般消息的流转)

终于到了核心部分之一了——消息机制 既然讲到消息,我们要到消息的源头——客户端 来到Unity.Hotfix的Init文件,并定位到这一句 Game.EventSystem.Run(EventIdType.InitSceneStart); 他会执行这里的Run代码(不知道为何的去复习ETBook的EventSystem哦) 进入UILoginFactory,他是登录UI的工厂类,...

设计师养成记 1474

CanOpen PDO映射实战:从原理到配置的完整指南

本文详细解析了CanOpen PDO映射的原理与配置流程,从基础通信机制到实战配置步骤,涵盖禁用PDO、清空映射、逐项映射和使能PDO等关键环节。特别针对西门子G120变频器、台达伺服和倍福CX控制器等不同厂商设备,提供了具体的配置技巧和常见问题排查指南,帮助工程师快速掌握多厂商设备间的PDO通信配置。

weixin_30718391的博客 517

【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)

【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)

linxinfa的专栏 2万+

零中频架构实战:用Matlab模拟AD9361收发信号(附完整代码)

本文详细介绍了如何使用Matlab模拟AD9361射频收发器的零中频架构,解决镜像干扰等关键挑战。通过完整的代码示例和系统级建模方法,帮助工程师掌握RFIC设计中的自适应校准算法和性能优化技巧,特别适用于SDR系统开发。

weixin_30733003的博客 542

在Jetson orin NX上安装部署Yolov8

1、环境准备与依赖安装2、安装PyTorch和Torchvision:从NVIDIA官方论坛或开发者下载中心获取与你的JetPack版本兼容的PyTorch wheel文件。例如,对于JetPack 5.1.2,可以安装PyTorch v2.1.0 8。:需要编译安装与PyTorch版本匹配的Torchvision。这里自己编译没有成功,下载了一个现成的可以用torchvision-0.16.2+c6f3977-cp38-cp38-linux_aarch64.whl。

xxxcccvvvbbb的博客 776

如何使用GameFramework框架:打造高效Unity游戏开发流程的完整指南

GameFramework是一款基于Unity引擎的专业游戏框架,它通过封装开发过程中常用的游戏模块,在很大程度上标准化了开发流程,提升了开发速度并确保了产品质量。无论是新手开发者还是有经验的团队,都能通过本指南快速掌握GameFramework的核心用法,构建稳定高效的游戏项目。 ## 🚀 框架核心优势解析 GameFramework框架的设计理念是解决Unity开发中的常见痛点,其核心优

gitblog_00533的博客 853

[HCTF 2018]WarmUp 1学习记录

#[HCTF 2018]WarmUp 1学习笔记 最近做了做ctf,觉得真的很有趣,后悔当初没早点做,不过之前的学习也让我得到了不少经验基础,废话不多说,这次ctf虽然基本上靠了网上的大佬们(因为比较菜),但自己也做了些思考,应该对新手也比较友好吧。 ##1 首先怎么找源码,我承认我确实刚开始不知道在哪找,不过也很快找到了,相信也会有部分新手和我差不多吧 然后下面控制台会有个temp0,然后直接回车 后面我就受到启发,直接在链接后面加上source.php,进行页面跳转,然后就可以开始PHP代码审计了。

weixin_45869407的博客 684

C#与Lua热更新架构:原理、实现与Unity游戏开发实践

热更新技术是现代游戏与大型应用开发中的核心工程实践,它允许开发者在无需重新发布客户端安装包的情况下,动态更新应用逻辑与资源。其基本原理在于将稳定的核心框架与需要频繁变动的业务逻辑进行分离,通常采用C#等编译型语言构建底层引擎,而使用Lua等脚本语言实现动态层。这种架构的技术价值在于极大地缩短了版本迭代周期,能够快速修复线上BUG和发布新内容,从而提升用户体验并降低玩家流失风险。在应用场景上,它尤其适用于手游、网游等需要频繁更新和运营的活动型项目。本文将以Unity引擎为例,深入探讨如何通过C#与Lua的组合

weixin_30732487的博客 315

黑猫带你学Nand第11篇:万字长文带你了解NandFlash的数据保持原理及特性(Data Retention)

TATTAT:Trap-assistedtunneling,陷阱辅助隧穿,也有叫法称为电荷陷阱现象。这是一种物理现象,它发生在非挥发性存储器,如NAND闪存。在这种效应中,存储在闪存单元浮动栅极上的电荷(电子)由于隧道氧化物中捕获的电荷形成的电场影响,导致电荷通过隧道效应从一个能级隧穿到另一个能级,从而造成电荷泄漏。这种电荷泄漏是导致闪存数据保持问题的主要原因之一,因为它可以改变存储在闪存单元中的电荷量,进而改变其阈值电压水平,最终可能导致数据错误。在NANDFlash中,电荷被存储在浮置栅极上。

黑猫奥利奥的博客 1099

基于STM32设计的老人防摔倒报警设备(OneNet)_65

本文设计了一种基于STM32单片机的智能老人防摔倒报警设备。该系统结合MPU6050陀螺仪传感器实时监测老人的姿态变化,检测摔倒事件;通过SIM800C通信模块发送短信通知紧急联系人,并将GPS定位信息上传至云端平台,确保家属能够及时了解老人的准确位置。

1万+

Unity游戏登录模块流程(需验证)

1.获取网络状态 unity判断网络连接:Application.internetReachability,返回值定义: NetworkReachability.NotReachable: 网络不可用 NetworkReachability.ReachableViaCarrierDataNe...

chuangpeng7633的博客 1887

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【unity游戏开发——网络】网络游戏通信方案——强联网游戏(Socket长连接)、 弱联网游戏(HTTP短连接)

本文介绍了联网游戏的类型划分与通信协议选择。游戏分为弱联网(如消消乐)和强联网(如王者荣耀),分别对应短连接和长连接。核心协议包括Socket(实时对战)、HTTP/S(回合制游戏)和FTP(资源下载)。文章通过对比表格和流程图,指导开发者根据游戏类型选择合适协议,并推荐了Unity游戏开发相关专栏资源。作者向宇提供了博客链接,欢迎交流学习。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 1826

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Unity游戏开发的终极框架:GameFramework完整指南

GameFramework是一个基于Unity游戏引擎的强大框架,专门为游戏开发者设计,用于封装开发过程中常用的游戏模块。这个开源框架通过标准化的开发流程,显著提高开发速度并确保产品质量。无论你是Unity游戏开发的新手还是经验丰富的开发者,GameFramework都能为你的项目提供坚实的架构基础。 ## 为什么选择GameFramework?🚀 GameFramework不仅仅是一个工具

gitblog_00806的博客 770

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

GameFramework架构解析:Unity游戏开发的模块化工程实践

GameFramework是一个基于Unity引擎的**企业级游戏开发框架**,通过19个核心模块的深度封装,为游戏开发者提供了一套完整的**架构级解决方案**。该框架不仅规范了Unity游戏开发流程,更在性能优化、内存管理、模块解耦等方面提供了专业级的工程实践,适用于从独立游戏到大型商业项目的全场景开发需求。 ## 一、分层架构设计与模块化工程 GameFramework采用**三层架构设计

gitblog_00797的博客 774

Unity游戏开发框架实战:GameFramework与YooAsset的完美融合方案

在Unity游戏开发过程中,你是否遇到过资源管理混乱、代码架构不清晰、热更新困难等痛点?GameFramework-at-YooAsset项目通过整合GameFramework游戏架构、YooAsset资源管理、Luban配置表工具、HybridCLR热更新以及UniTask异步编程,为开发者提供了一套完整的解决方案。本文将深入解析这个强大的框架组合,帮助你快速掌握其核心用法。 ## 架构设计理

gitblog_00896的博客 439
上一篇: Unity游戏开发-Assetbundle打包
下一篇: unity游戏开发-简易的状态机
古月三齐
博客等级 码龄10年 67粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值