Unity 极致高效的IM设计方案教程

发布时间:2026/7/16 0:49:32

Unity 极致高效的IM设计方案教程
Unity 高效的 IM即时通讯设计方案原理教程概述本教程基于一个使用NGUI protobuf-net构建的 Unity IM 聊天系统深入分析其核心设计原理。该系统在消息协议定义、组件复用、对象池、虚拟滚动四个方面做到了高度优化适合作为 Unity 中长列表聊天框的参考架构。UGUI的版本也有实现一模一样的效果这里就不赘述原理是一样的。一、消息协议层基于 Protocol Buffers 的多类型定义1.1 Proto 文件定义位置Protocol/IMMsg.proto系统使用 protobuf-net 将.proto文件编译为 C# 类。消息类型通过枚举MsgType严格区分enum MsgType { Text 1; // 文本消息 Image 2; // 图片消息 Audio 3; // 语音消息 Time 4; // 时间提示消息 JoinOrLeave 5; // 加入/离开提示 World_Share 6; // 世界坐标分享 UserData 7; // 用户数据 }1.2 ChatMessage 的统一结构ChatMessage是核心消息体采用optional 字段 多类型承载的方式将多种消息类型统一为一个数据结构message ChatMessage { required int32 id 1; required int32 chatTime 2; required ChatChannel channel 3; required MsgType msgType 4; optional int32 roomId 5; optional User sender 6; optional string msg 7; // 文本类型 optional VoiceMsg voiceMsg 8; // 语音类型 optional PicMsg picMsg 9; // 图片类型 optional SiteMsg siteMsg 10; // 位置类型 }高效之处所有消息文本、图片、语音、位置、时间、系统提示共享同一个ChatMessage结构通过msgType区分避免为每种消息定义独立的网络包结构。配合 protobuf 的optional字段未使用的子消息体不占网络带宽。1.3 消息状态机枚举MsgStatus定义了消息的完整生命周期状态Created → Sending → Sent → Read ↘ SendingFailed这为后续的消息发送重试、未读计数、已读回执等业务提供了清晰的状态基础。二、组件继承体系极致的复用设计整个系统的 UI 组件采用三层继承结构每一层解决一个维度的通用性问题。2.1 层级结构MonoBehaviour ├── MsgBaseComponent (已废弃/预留基类) └── MsgBaseItem (核心基类所有消息 Item 的父类) ├── PTMsgItem (图文消息文本 图片) ├── TipMsgItem (提示消息时间 加入离开) └── VoiceMsgItem (语音消息)2.2 MsgBaseItem所有消息 Item 的根基位置MsgBaseItem.cs这是整个系统的核心抽象基类定义了每个消息 Item 必须拥有的属性和行为publicclassMsgBaseItem:MonoBehaviour{[RequireComponent(typeof(UIWidget))]// 布局属性 publicfloatHeight{get;set;}// 当前 Item 高度publicfloatStartPosY{get;set;}// 当前 Item Y 坐标publicstringItemPrefabName{get;set;}// 对应的 Prefab 名称用于对象池查找// 索引体系 publicintItemId{get;set;}// 全局唯一 ID由对象池递增分配publicintItemIndex{get;set;}// 在数据列表中的索引publicintViewIndex{get;set;}// 在 ScrollView 可见队列中的索引// 虚方法 publicvirtualvoidAwake()// 获取 UIWidget 组件引用publicvirtualvoidSetItemSize()// 根据内容计算并设置尺寸publicvirtualvoidFillItem(protocol.ChatMessagemsgData,intindex)// 填充数据publicvirtualvoidReset()// 重置到初始状态回收时调用}设计亮点所有 Item 强制要求UIWidget组件通过RequireComponent这是 ChatPanel 统一计算布局的前提ItemPrefabName关联对象池使得不同类型 Item 可以被统一管理三维索引ItemIdItemIndexViewIndex分别服务于对象池追踪、数据定位、视图排序职责分明2.3 PTMsgItem图文消息组件位置PTMsgItem.cs处理文本(Text)和图片(Image)两种消息类型。内部持有以下 UI 引用publicUISpriteheadIcon;// 头像publicUILabellblName;// 发送者名称publicUILabellblMessage;// 消息文本publicUISpritebg;// 气泡背景publicUISpriteline;// 翻译分隔线publicUIButtonbtnTranslate;// 翻译按钮publicUITexturetetPic;// 图片publicUISpritetranslated;// 已翻译标记复用技巧同一个 PrefabPTMsgItem通过FillItem内部的分支逻辑可以渲染两种完全不同的 UI 形态Text 模式激活bglblMessagebtnTranslate关闭tetPicImage 模式激活tetPic关闭lblMessagebtnTranslatebg这意味着编辑器里只需要拖拽配置一个Prefab就能展示文本和图片两种消息不需要创建两个独立的 Prefab。翻译功能实现了动态高度调整翻译完成后重新计算SetItemSize()然后调用ParentView.RePosition(this, deltaHeight)通知 ChatPanel 刷新下方所有 Item 的位置。2.4 TipMsgItem提示消息组件位置TipMsgItem.cs处理时间(Time)和加入/离开(JoinOrLeave)两种系统提示消息。逻辑极简publicoverridevoidFillItem(ChatMessagemsgData,intindex){if(msgData.msgTypeMsgType.JoinOrLeave)lblMsaage.textmsgData.msg;elseif(msgData.msgTypeMsgType.Time)lblMsaage.textConvertTimeIntervalToDayOfWeekAndTime(msgData.chatTime);bg.widthlblMsaage.width10;// 背景自适应文本宽度bg.heightlblMsaage.height10;SetItemSize();}复用技巧同样是一个 TipMsgItem通过msgType分支显示时间文本或用户行为文本。两种提示共用一套居中布局不需要分开。2.5 VoiceMsgItem语音消息组件位置VoiceMsgItem.cs处理语音(Audio)消息。特色在于气泡宽度随语音时长动态变化publicoverridevoidFillItem(ChatMessagemsgData,intindex){lblTime.textstring.Format({0},msgData.voiceMsg.duration);intdurationMathf.Clamp(msgData.voiceMsg.duration,3,50);bg.widthMathf.FloorToInt(Mathf.Lerp(105f,500f,(duration/50f)));SetItemSize();}Mathf.Lerp(105f, 500f, duration / 50f)将 3-50 秒的语音时长映射到 105-500 像素宽度给用户直观的时长视觉暗示。三、对象池系统消除运行时 GC位置ChatPanel.cs 中的ItemPool类3.1 为什么需要池IM 聊天框是高频动态列表新消息不断加入用户滚动时旧消息不断离开视野。如果每次都用Instantiate/Destroy会产生大量 GC Alloc在移动设备上导致卡顿。3.2 两级缓存设计publicclassItemPool{ListMsgBaseItemmTmpPooledItemListnewListMsgBaseItem();// 一级缓存临时ListMsgBaseItemmPooledItemListnewListMsgBaseItem();// 二级缓存就绪}工作流程GetItem() RecycleItem() 用户 ──────────→ ┌─────────┐ 滚动出视野 ──────────→ ┌──────────┐ │ 一级缓存 │ │ 一级缓存 │ │ (Temp) │ │ (Temp) │ └────┬────┘ └────┬─────┘ │ 为空时 fallback │ ClearTmpRecycledItem() ↓ ↓ ┌──────────┐ ┌──────────┐ │ 二级缓存 │ │ 二级缓存 │ │ (Ready) │ │ (Ready) │ └────┬─────┘ └──────────┘ │ 为空时创建新实例 ↓ GameObject.Instantiate一级缓存TempItem 刚被滚动移出视野时放入此处下一帧被ClearAllTmpRecycledItem()转移到二级缓存二级缓存Ready完全回收就绪的 Item可立即被GetItem()取出复用这种设计避免了频繁的Instantiate/Destroy所有 Item 实例在整个生命周期中只创建一次反复循环使用。3.3 预创建机制publicclassItemPrefabConfData{publicGameObjectmItemPrefabnull;publicintmInitCreateCount0;// 初始预创建数量}初始化时每种 Item 按mInitCreateCount预先创建 N 个实例并直接回收到池中。这样用户首次滑动时Item 直接从池中取出零延迟。3.4 池的生命周期管理// 初始化pool.Init(data.mItemPrefab,data.mInitCreateCount,mContainerTrans);// 取出从池或新建MsgBaseItemitempool.GetItem();// 回收回到一级缓存pool.RecycleItem(item);// 清理一级缓存转移到二级缓存pool.ClearTmpRecycledItem();// 销毁所有pool.DestroyAllItem();3.5 ChatPanel 中的池管理// 按 Prefab 名称索引的池字典Dictionarystring,ItemPoolmItemPoolDictnewDictionarystring,ItemPool();publicMsgBaseItemNewMsgItem(stringitemPrefabName){ItemPoolpoolmItemPoolDict[itemPrefabName];varitempool.GetItem();item.Reset();// 重置到初始状态item.ParentViewthis;// 关联父视图returnitem;}NewMsgItem是 ChatPanel 对外暴露的唯一获取 Item 的方法。上层ChatDemo只需传入 Prefab 名称不需要关心池的内部逻辑。四、虚拟滚动Loop Scroll引擎万条消息不卡顿位置ChatPanel.cs 的OnPanelLoopClipMove方法4.1 核心思想不将全部消息生成为 GameObject而是只生成视野可见区域 前后缓冲区中的 Item。滚动时移出视野的 Item 被回收新进入视野的消息复用池中 Item。不管数据列表有多少条100 还是 10000场景中同时存在的 Item 数量基本恒定。4.2 四标志位追踪系统使用 4 个MsgBaseItem引用追踪视图状态// {最上面的prefab, 视野范围内最上面prefab, 视野范围内最下面prefab, 最下面prefab}MsgBaseItem[]mFlagItemsnewMsgBaseItem[]{null,null,null,null};数据索引增大方向新消息在下方 ──────────────────────────────→ mFlagItems[0] ← 当前已生成的最上面 Item | mFlagItems[1] ← 视野中可见的最上面 Item | ↑ 视口可见区域 ↑ mFlagItems[2] ← 视野中可见的最下面 Item | mFlagItems[3] ← 当前已生成的最下面 Item为什么要 4 个这 4 个标志位让系统的判定条件非常精确mFlagItems[3].ItemIndex mChatMsgs.Count - 1底部还有未加载的数据mFlagItems[1].ItemIndex - mFlagItems[0].ItemIndex CacheMsgPrefabNum上方缓存过多需要回收mFlagItems[3].ItemIndex - mFlagItems[2].ItemIndex CacheMsgPrefabNum下方缓存不足需要加载4.3 滑动检测与回收策略voidOnPanelLoopClipMove(UIPanelpanel){floatmoveDismScrollView.transform.localPosition.y-svLastPos;if(Mathf.Abs(moveDis)DetectionDis)// 移动距离超过阈值才处理{boolisUpmoveDis0;if(isUp)// 向上滑动看新消息{// 如果上方缓存过多 → 回收顶部 Itemif(mFlagItems[1].ItemIndex-mFlagItems[0].ItemIndexCacheMsgPrefabNum){varfirstItemmItemList[0];mItemList.RemoveAt(0);RecycleItemTmp(firstItem);}// 如果下方可见区域即将空白 → 加载新的底部 Itemif(mFlagItems[3].ItemIndex-mFlagItems[2].ItemIndexCacheMsgPrefabNum){varitemLoadOneItemAtLast(mFlagItems[3].ItemIndex1,...);mFlagItems[3]item;}}else// 向下滑动看旧消息{// 对称逻辑回收底部加载顶部}svLastPosmScrollView.transform.localPosition.y;}}DetectionDis默认 0.1控制灵敏度值越小判定越精确但每帧计算次数越多是性能与精度的权衡参数。4.4 缓冲区的意义[Header(上下不可见区域缓存消息的数量)]publicintCacheMsgPrefabNum4;CacheMsgPrefabNum 4意味着视野上下方各多保留 4 个 Item 不回收。这样在快速滑动时新 Item 能够无缝衔接不会出现白屏再出现的视觉断层。4.5 首次加载策略voidFirstLoadItems(){// 从第一条消息开始加载直到铺满一屏幕 CacheMsgPrefabNumfor(inti0;imChatMsgs.Count;i){LoadOneItemAtLast(i,i);// 检查是否已超出视口高度if(mCurrentLastPosY-ViewPortHeight)break;}// 如果所有消息都不够一屏关闭循环滚动if(mCurrentLastPosY-ViewPortHeight)mLoopScrollfalse;}如果消息总数很少不够一屏幕自动关闭循环滚动避免不必要的判定开销。五、组件与数据的解耦回调工厂模式5.1 OnGetItemByIndex 委托System.FuncChatPanel,int,MsgBaseItemmOnGetItemByIndex;ChatPanel 不关心消息数据长什么样也不关心 Item 是哪种类型。当它需要某个索引的 Item 时调用这个委托去获取MsgBaseItemGetNewItemByIndex(intindex){varnewItemmOnGetItemByIndex(this,index);newItem.ItemIndexindex;returnnewItem;}5.2 ChatDemo 中的实现publicclassChatDemo:MonoBehaviour{publicChatPanelmChatPanel;voidStart(){mChatPanel.InitListView(ChatMsgDataMgr.Instance.ChatMsgList,OnGetItemByIndex);}MsgBaseItemOnGetItemByIndex(ChatPanellistView,intindex){varitemDataChatMsgDataMgr.Instance.GetChatMsgByIndex(index);stringprefabName;switch(itemData.msgType){caseprotocol.MsgType.Audio:prefabName(itemData.senderTypeSelf)?RightVoiceItem:LeftVoiceItem;break;caseprotocol.MsgType.JoinOrLeave:caseprotocol.MsgType.Time:prefabNameTipItem;break;caseprotocol.MsgType.Text:caseprotocol.MsgType.Image:prefabName(itemData.senderTypeSelf)?RightItem:LeftItem;break;}varitemmChatPanel.NewMsgItem(prefabName);item.FillItem(itemData,index);returnitem;}}设计价值ChatPanel 完全不知道ChatMessage数据结构只知道有一个索引ChatDemo 完全不知道对象池和滚动的存在只知道根据数据类型返回正确 Prefab添加新消息类型只需要在 switch 中添加 case → 不需要修改 ChatPanel 一行代码六、水平对比总结对比维度传统实现本系统实现效率提升消息结构每种消息独立网络包统一 ChatMessage 可选子消息减少重复序列化代码proto 带宽小Item 创建每消息一个 PrefabInstantiate对象池复用两级缓存消除 GC Alloc帧率稳定列表渲染全部生成 GameObject虚拟滚动只渲染可见 ± 缓存万条消息内存恒定组件复用文本/图片分两个 Prefab一个 PTMsgItem 处理两种Prefab 数量减半代码耦合ListView 持有数据类型委托回调解耦添加消息类型不修改引擎高度计算固定高度动态计算 RePosition支持翻译展开、图文混排七、架构总览图┌────────────────────────────────────────────────────────────┐ │ ChatDemo │ │ (入口初始化 ChatPanel实现 OnGetItemByIndex 回调) │ └──────────┬─────────────────────────────────┬───────────────┘ │ InitListView(callback) │ OnGetItemByIndex ▼ ▼ ┌──────────────────────┐ ┌──────────────────────────────┐ │ ChatPanel │ │ ChatMsgDataMgr │ │ (Loop Scroll 引擎) │ │ (Singleton数据管理) │ │ │ │ │ │ ┌────────────────┐ │ │ 持有 ListChatMessage │ │ │ ItemPool Dict │ │ │ 持有 Dictionaryid, User │ │ │ │ │ └──────────────────────────────┘ │ │ RightItem → │ │ │ │ LeftItem → │ │ ┌──────────────────────────────┐ │ │ TipItem → │ │ │ IMMsg (protobuf) │ │ │ RightVoice → │ │ │ │ │ │ LeftVoice → │ │ │ ChatMessage, User, VoiceMsg,│ │ └────────────────┘ │ │ PicMsg, SiteMsg, MsgType │ │ │ └──────────────────────────────┘ │ mItemList: 可见队列 │ │ mFlagItems[0..3]: │ ┌──────────────────────────────┐ │ 四标志位追踪器 │ │ MsgBaseItem │ └──────────────────────┘ │ (基类) │ │ ↑ 继承 ↑ ↑ 继承 ↑ │ │ PTMsgItem TipMsgItem │ │ (文本图片) (时间提示) │ │ VoiceMsgItem │ │ (语音) │ └──────────────────────────────┘八、关键启发协议先行使用 protobuf 定义统一的消息结构optional 字段使得多类型消息共享带宽继承复用MsgBaseItem定义了所有 Item 的共同契约尺寸、索引、填充、重置子类只需差异逻辑一个 Prefab 多种形态PTMsgItem通过激活/禁用子控件同时满足文本和图片两种消息双缓冲对象池一级临时 二级就绪既支持批量清理又保证取用速度虚拟滚动四标志位精确追踪视图边界用最少的 Item 实例覆盖无限的聊天数据委托解耦ChatPanel 不依赖具体数据类型后续接入新协议或本地消息历史零改动

相关新闻

影刀RPA 数据去重实战:找出Excel中的重复记录

影刀RPA 数据去重实战:找出Excel中的重复记录

2026/7/16 0:39:32

title: “影刀RPA 数据去重实战:找出Excel中的重复记录” date: 2026-07-01 author: 林焱 影刀RPA 数据去重实战:找出Excel中的重复记录 从多个系统导出的数据,同一条记录可能出现多次。手动查重靠肉眼或COUNTIF公式,数据一大效率…

美团开发岗高频算法题清单

美团开发岗高频算法题清单

2026/7/16 0:39:32

美团开发岗的算法难度整体不一定最卷,但有一个很鲜明的特点:题目很贴近后台开发常见的数据结构能力。 也就是说,美团更像是在通过算法题看你是不是一个扎实的后台工程师,而不是单纯看你刷了多少偏题怪题。 美团算法面试特点 偏经…

国产PLC通信避坑:C#解决信捷PLC数据丢包与断线重连实战

国产PLC通信避坑:C#解决信捷PLC数据丢包与断线重连实战

2026/7/16 0:39:32

前言:国产PLC的"隐形门槛" 在工控圈子里,西门子、三菱的通信库被研究得透透的,随便搜一下就是现成的轮子。但当你接手一个使用信捷XC/XD系列PLC的项目时,往往会发现:文档少、社区小、坑还特别多。 去年我们给…

S7-1200编程进阶:活用数组Array实现高效数据管理与间接寻址

S7-1200编程进阶:活用数组Array实现高效数据管理与间接寻址

2026/7/16 2:19:36

1. 数组在S7-1200中的核心价值我第一次接触S7-1200的数组功能是在一个包装生产线项目里。当时需要处理200多个传感器的实时状态数据,如果每个传感器都单独定义变量,不仅编程繁琐,后期维护更是噩梦。直到发现数组这个"数据集装箱"功…

2026年跨行业转行面试信服力构建:AI可迁移能力映射法——从「为什么转行」到「为什么选你」的4段式回答

2026年跨行业转行面试信服力构建:AI可迁移能力映射法——从「为什么转行」到「为什么选你」的4段式回答

2026/7/16 2:19:36

文章目录一、「你为什么想转行?」——面试官没说的三个真实顾虑1.1 转行面试在结构化面试体系中的特殊位置1.2 首因效应的特殊影响1.3 三种最致命的转行动机回答1.4 传统准备方式 vs AI辅助方式二、测评方法论:5个硬核标准2.1 测评维度定义2.2 评分标准三…

中国限定宝可梦魔方解析:玩法、收藏与购买全攻略

中国限定宝可梦魔方解析:玩法、收藏与购买全攻略

2026/7/16 2:19:36

最近在逛宝可梦周边时,发现了一款特别有意思的产品——Tingman推出的中国限定版宝可梦魔方。作为宝可梦粉丝和魔方爱好者,这种跨界联名产品立刻引起了我的兴趣。更让人惊喜的是,这款魔方竟然是中国地区专属限定,其他地方都买不到&…

2026年基层岗竞聘主管表达升级指南:AI管理行为识别法——4步把执行经验翻译成管理能力

2026年基层岗竞聘主管表达升级指南:AI管理行为识别法——4步把执行经验翻译成管理能力

2026/7/16 2:19:36

文章目录一、「你有管理经验吗?」——基层求职者最怕的面试问题1.1 核心数据:管理岗面试的行为面试占比1.2 传统方式 vs AI辅助方式对比1.3 三种典型翻车回答二、测评方法论:5个硬核标准2.1 测评维度定义2.2 评分标准三、4款工具逐一深度测评…

知识表示的方法(3)——状态空间表示法在路径规划中的应用

知识表示的方法(3)——状态空间表示法在路径规划中的应用

2026/7/16 2:19:36

1. 状态空间表示法基础概念状态空间表示法就像给问题画了一张"地图",把复杂问题拆解成一个个可操作的步骤。想象你在玩华容道游戏,每个棋子的位置组合就是一种"状态",移动棋子的操作就是"算子",从初…

AppScan实战:从零到一构建企业级Web应用安全扫描流程

AppScan实战:从零到一构建企业级Web应用安全扫描流程

2026/7/16 2:09:35

1. 为什么企业需要Web应用安全扫描第一次接触Web应用安全扫描时,我完全被各种专业术语搞晕了。直到亲眼看到黑客利用一个简单的SQL注入漏洞,在5分钟内拖走了整个用户数据库,才真正明白安全扫描的重要性。现在每次给新同事培训,我都…

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

2026/7/16 0:35:09

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator作为Unity游戏社区中最成熟的文本翻译解决方…

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

2026/7/13 20:43:19

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持 【免费下载链接】raspberrypi-kernel It provides openEuler kernel source for Raspberry Pi 项目地址: https://gitcode.com/openeuler/raspberrypi-kernel 前往项目官网免费下载&…

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

2026/7/15 0:26:43

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧 【免费下载链接】integration-test The repo contains test suits for system integration test 项目地址: https://gitcode.com/openeuler/integration-test 前往项目官网免费下载:…

Python实现跨境电商商品图批量翻译教程

Python实现跨境电商商品图批量翻译教程

2026/7/16 0:09:31

一、问题引入做跨境电商的卖家朋友,你是否遇到过这样的困扰?每次上架新品到亚马逊、Shopee或Lazada等平台,都需要处理大量商品图片的多语言版本。比如上架200款衣服,每款需要翻译成英语、日语、韩语等5种语言,这意味着…

跨境电商多语言商品图翻译方案实现

跨境电商多语言商品图翻译方案实现

2026/7/16 0:09:31

一、问题引入对于做跨境电商的卖家来说,多语言商品图的制作一直是令人头疼的环节。当你准备在亚马逊、Shopee、Lazada等多个平台同步上架新品时,首先遇到的就是图片翻译问题。以一位做家居用品的卖家为例,他需要将200张商品图片中的英文文案全…

Windows系统文件d3dx9_36.dll丢失找不到问题解决

Windows系统文件d3dx9_36.dll丢失找不到问题解决

2026/7/16 0:09:31

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…