【小松教你手游开发】【系统模块开发】ngui做聊天系统

用ngui做聊天系统有个简单的方法是用教程Exampl12里的TextList来做聊天系统。

但显然一个UILabel做的聊天系统拓展性不高,并且要做特殊点击事件会变得很麻烦。

所以我们还是用一个UIScrollView下挂一个UITable,把UILabel和其他东西封装成一个Prefab一个个加载到UITable来实现。

如果不考虑其他因素就是一个简单的UIScrollView相信大家都没什么问题。

不过项目中会有两个技术难点。

1.要实现图文混排

2.如果对话多的话(比如世界频道,每个玩家都可以发信息,很容易就一百条以上),肯定不能每个都实例化一个Prefab,所以UIScrollView要做循环加载。

1.关于图文混排在之前的文章中就讲到怎么实现

http://www.cnblogs.com/chrisfxs/p/5737607.html

所以本文主要讲的是第二点.循环加载

循环加载的意思就是,比如一个聊天系统,在可显示的范围内,用最少的Prefab,通过改里面的数值去显示不同数据,而不是每一个数据实例化一个Prefab。

在ngui的教程里就有教你怎么用ngui的UIScrollView里实现循环加载。

但是这里有一个前提是。它用的是UIScrollView下挂UIGrid,UIGrid下挂Prefab。且每个Prefab的高度一致。

这样可以准确的计算出最少需要多少个Prefab,现在滑动到哪个Prefab,且现在每个Prefab应该插入什么数据。

但是像聊天这样的系统,每个人发的每句话字数都不一样,也就是每个UILabel的高度不一样。这样导致了上面的计算都无法实现。

所以本来做好的UIScrollView的循环加载就需要调整。

首先是不用UIGrid用UITable。因为UITable不要求每一行的间距统一

接着就是写一个继承UIScrollView的类来实现循环加载。我这里叫CUIChatScrollView: UIScrollView

因为代码比较复杂,在最后的时候在一次过放上代码,这里先讲思路

首先按一般的循环加载我们要做的是知道这个UISCrollView最多同时要显示多少个Prefab,我们总共有多少数据。每个Prefab多高。当最上一块或最下一个滑出边框就把它移到另一边。

而由于现在高度不一,所以上面除了总共有多少数据是知道的其他都不能确定。

所以我们只能换种方式。

我们实例化足够的数,通过设定两个上线临界值来判断是否需要把Prefab移到另一边,而不通过边框计算。

通过索引index而不通过计算滑动位置与这个的比例来计算应该插入的数据的索引。

所以对于第一种可以做到的,最多同屏显示6条数据的话,我们只用实例化7个Prefab,这种最优方案我们没法使用。

我们只能预估一个值,实例化一个合适的数,比如每一个聊天语句只有一行的话,整个界面有多少条,然后再比这个数多一点。

在我的项目中上下最多显示3条,不过我这里也需要6条来实现循环加载。也就是实例化10个Prefab。

如果是文字只有一行的话我的像素是25,我的上下临界值设为250.

(这些数值都还可以再优化,我只是随便取了一些数)

在原来一般的循环加载逻辑下,实例化10个Prefab,当向下滑动时的Prefab超过下边框就移到另一边,向上一样。

而当前的Prefab应该插入什么数据不再通过计算当前点击position除于每个Prefab的高度interver来计算出index来插入数据。而是记录下index,如果把上面的Prefab移到下面就index++。如果下面的移到上面就index--。

而因为index作为索引如果从0开始,而最下面的index就是index加实例化的Prefab数,我的就是10.

而原本计算目标position的方式也不能用了。原本滑动到顶端把prefab移到低端通过最下面的prefab加高度interver就能知道。

现在最下面的要通过先把文字放到UILabel,通过NGUI的函数NGUIMath.CalculateRelativeWidgetBounds计算UILabel的宽高,加上最下面的Prefab的position来确定位置。

大概思路是这样

下面贴上代码

[csharp] view plain copy

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public class CUIChatScrollView : UIScrollView
  5. {
  6. public delegate void OnMoveCallBack();
  7. /// <summary>
  8. /// 每次滑动结束的回调
  9. /// </summary>
  10. public OnMoveCallBack mOnMoveCallback;
  11. public delegate void OnMoveNextPage(int pageindex);
  12. public OnMoveNextPage mOnMoveNextPage;
  13. private float mTopOffSetValue = 250;
  14. private float mBottomOffSetValue = 250;
  15. /// <summary>
  16. /// 当前数据总数
  17. /// </summary>
  18. private int mTotalCount = 0;
  19. /// <summary>
  20. /// 子物体当前临时坐标
  21. /// </summary>
  22. private float mTempPosition = 0;
  23. /// <summary>
  24. /// 子物体当前临时相对坐标(与UIPanel偏移、DynamicComponentTran、自己本身坐标之和,如果UIpanel裁剪区域有偏移还需要加上UIPanel FinalClipRegion的偏移值(X或者Y))
  25. /// </summary>
  26. private float mBorderValue = 0;
  27. /// <summary>
  28. /// 当前显示出来的物体列表
  29. /// </summary>
  30. private List<CBaseComponent> mCurrentComList = new List<CBaseComponent>();
  31. /// <summary>
  32. /// 当前循环临时加载的列表(达到临界值需要移动的Components)
  33. /// </summary>
  34. private List<CBaseComponent> mTempComList = new List<CBaseComponent>();
  35. /// <summary>
  36. ///  挂载UIGrid的组件物体,是components父物体,UIPanel子物体
  37. /// </summary>
  38. private Transform DynamicComponentTran;
  39. /// <summary>
  40. /// 当前ScrollView依赖的UIPanel
  41. /// </summary>
  42. private UIPanel mUIPanel;
  43. /// <summary>
  44. /// UIPanel最终的绘制区域、坐标
  45. /// </summary>
  46. private Vector4 mPanelFinalClipRegion;
  47. /// <summary>
  48. /// 是否需要请求下一页数据(用于多数据分页加载情况)
  49. /// </summary>
  50. private bool bNeedReqNextPage;
  51. /// <summary>
  52. /// 每页的数量(用于分页)
  53. /// </summary>
  54. private int mPrePageNumber;
  55. /// <summary>
  56. /// 是否是排行榜模式
  57. /// </summary>
  58. private bool bIsRankModel;
  59. /// <summary>
  60. /// 当前自己的排名
  61. /// </summary>
  62. private int mCurrentMyRank;
  63. /// <summary>
  64. /// 缓存开关
  65. /// </summary>
  66. private Dictionary<int, bool> bHasRequestDic = new Dictionary<int, bool>();
  67. /// <summary>
  68. /// 用于比较的X坐标
  69. /// </summary>
  70. private float xPos;
  71. /// <summary>
  72. /// 用于比较的Y坐标
  73. /// </summary>
  74. private float yPos;
  75. /// <summary>
  76. /// 是否需要3D显示效果
  77. /// </summary>
  78. private bool bShow3DStyle = false;
  79. /// <summary>
  80. /// 居中的子物体
  81. /// </summary>
  82. public Transform mCenterTranform;
  83. /// <summary>
  84. /// 子物体的depath
  85. /// </summary>
  86. private UIPanel mTempUIPanel;
  87. /// <summary>
  88. ///  ScrollView 的UIPanel depath。
  89. /// </summary>
  90. private int mUIPanelDepth = 1;
  91. private int m_Index = 0;
  92. private int m_maxShowNum = 0;
  93. public void Init<T>(List<T> components, int maxShowNum, int totalNumber, Transform dyTransorm, Movement moveMent = Movement.Horizontal, bool needrequest = false, int prepagenum = 0, bool isrank = false, int myrank = 0, bool bshow3d = false) where T : CBaseComponent
  94. {
  95. m_Index = maxShowNum-1;
  96. m_maxShowNum = maxShowNum;
  97. mCurrentComList.Clear();
  98. for (int i = 0, length = components.Count; i < length; i++)
  99. {
  100. if (components[i] is CBaseComponent)
  101. {
  102. mCurrentComList.Add(components[i]);
  103. }
  104. }
  105. //mCurrentComList = components as List<CBaseComponent>;
  106. DynamicComponentTran = dyTransorm;
  107. mTotalCount = totalNumber;
  108. movement = moveMent;
  109. mPanelFinalClipRegion = (mUIPanel ?? (mUIPanel = gameObject.GetComponent<UIPanel>())).finalClipRegion;
  110. mUIPanelDepth = mUIPanel.depth + 10;//子物体固定高出父物体深度10个单位
  111. //if (movement == Movement.Vertical)
  112. //    mOffSetValue = mPanelFinalClipRegion.w / 2 + mItemIntervalValue / 2f;
  113. //else if (moveMent == Movement.Horizontal)
  114. //    mOffSetValue = mPanelFinalClipRegion.z / 2 + mItemIntervalValue / 2f;
  115. bNeedReqNextPage = needrequest;
  116. mPrePageNumber = prepagenum;
  117. bIsRankModel = isrank;
  118. mCurrentMyRank = myrank;
  119. bHasRequestDic.Clear();
  120. bShow3DStyle = bshow3d;
  121. momentumAmount = 35f;
  122. }
  123. public override void MoveRelative(Vector3 relative)
  124. {
  125. base.MoveRelative(relative);
  126. CheckScrollView(relative);
  127. }
  128. public override void AdjustSpring(Vector3 relative)
  129. {
  130. base.AdjustSpring(relative);
  131. CheckScrollView(relative);
  132. }
  133. private void CheckScrollView(Vector3 relative)
  134. {
  135. if (mCurrentComList == null || mCurrentComList.Count == 0)
  136. return;
  137. if (movement == Movement.Horizontal)
  138. {
  139. xPos = relative.x;
  140. mCurrentComList.Sort(delegate (CBaseComponent a, CBaseComponent b) { return a.LocalPosition.x.CompareTo(b.LocalPosition.x); });
  141. }
  142. else if (movement == Movement.Vertical)
  143. {
  144. yPos = relative.y;
  145. mCurrentComList.Sort(delegate (CBaseComponent a, CBaseComponent b) { return b.LocalPosition.y.CompareTo(a.LocalPosition.y); });
  146. }
  147. mTempComList.Clear();
  148. if ((yPos > 0 && movement == Movement.Vertical) || (xPos < 0 && movement == Movement.Horizontal))
  149. {
  150. Transform temptran = mCurrentComList[mCurrentComList.Count - 1].ComTransform;
  151. if (movement == Movement.Horizontal)
  152. mTempPosition = temptran.localPosition.x;
  153. else if (movement == Movement.Vertical)
  154. mTempPosition = temptran.localPosition.y;
  155. mTempPosition = Mathf.Abs(mTempPosition);
  156. if (true)
  157. {
  158. for (int i = 0, j = mCurrentComList.Count; i < j; i++)
  159. {
  160. temptran = mCurrentComList[i].ComTransform;
  161. if (movement == Movement.Horizontal)
  162. {
  163. mBorderValue = (temptran.localPosition.x*temptran.localScale.x + transform.localPosition.x + DynamicComponentTran.localPosition.x + Mathf.Abs(mPanelFinalClipRegion.x));
  164. if (mBorderValue > -mTopOffSetValue)
  165. {
  166. break;
  167. }
  168. }
  169. else if (movement == Movement.Vertical)
  170. {
  171. mBorderValue = (temptran.localPosition.y * temptran.localScale.y + transform.localPosition.y + DynamicComponentTran.localPosition.y + Mathf.Abs(mPanelFinalClipRegion.y));
  172. if (mBorderValue < mTopOffSetValue)
  173. {
  174. break;
  175. }
  176. }
  177. mTempComList.Add(mCurrentComList[i]);
  178. }
  179. }
  180. for (int i = 0, j = mTempComList.Count; i < j; i++)
  181. {
  182. if (m_Index >= mTotalCount - 1) break;
  183. m_Index++;
  184. if (movement == Movement.Vertical)
  185. {
  186. float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(transform, mCurrentComList[mCurrentComList.Count - 1].ComTransform, true).size.y;
  187. mTempComList[i].LocalPosition = new Vector3(0, -(spriteHeight +mTempPosition), 0);
  188. }
  189. //mTempComList[i].LocalPosition = new Vector3(0, -(mItemIntervalValue * (i + 1) + mTempPosition), 0);
  190. mTempComList[i].InitData(m_Index);
  191. }
  192. }
  193. else if ((yPos < 0 && movement == Movement.Vertical) || (xPos > 0 && movement == Movement.Horizontal))
  194. {
  195. Transform temptran = mCurrentComList[0].ComTransform;
  196. if (movement == Movement.Horizontal)
  197. mTempPosition = temptran.localPosition.x;
  198. else if (movement == Movement.Vertical)
  199. mTempPosition = temptran.localPosition.y;
  200. mTempPosition = Mathf.Abs(mTempPosition);
  201. if (true)
  202. {
  203. for (int i = mCurrentComList.Count - 1; i >= 0; i--)
  204. {
  205. temptran = mCurrentComList[i].ComTransform;
  206. if (movement == Movement.Horizontal)
  207. {
  208. mBorderValue = (temptran.localPosition.x + transform.localPosition.x + DynamicComponentTran.localPosition.x + Mathf.Abs(mPanelFinalClipRegion.x));
  209. if (mBorderValue <mBottomOffSetValue)
  210. {
  211. break;
  212. }
  213. }
  214. else if (movement == Movement.Vertical)
  215. {
  216. mBorderValue = (temptran.localPosition.y + transform.localPosition.y + DynamicComponentTran.localPosition.y + Mathf.Abs(mPanelFinalClipRegion.y));
  217. if (mBorderValue > -mBottomOffSetValue)
  218. {
  219. break;
  220. }
  221. }
  222. mTempComList.Add(mCurrentComList[i]);
  223. }
  224. }
  225. for (int i = 0, j = mTempComList.Count; i < j; i++)
  226. {
  227. if (m_Index <= m_maxShowNum-1) break;
  228. m_Index--;
  229. //mTempComList[i].LocalPosition = new Vector3(0, -(mTempPosition - mItemIntervalValue * (i + 1)), 0);
  230. mTempComList[i].InitData(m_Index - m_maxShowNum+1);
  231. if (movement == Movement.Vertical)
  232. {
  233. float spriteHeight = NGUIMath.CalculateRelativeWidgetBounds(transform, mTempComList[i].ComTransform, true).size.y;
  234. Vector3 newPosition;
  235. if (i == 0)
  236. newPosition = new Vector3(0, -(mTempPosition - spriteHeight), 0);
  237. else
  238. newPosition = new Vector3(0, -(Mathf.Abs(mTempComList[i - 1].LocalPosition.y) - spriteHeight), 0);
  239. mTempComList[i].LocalPosition = newPosition;
  240. }
  241. }
  242. }
  243. if (mOnMoveCallback != null)
  244. {
  245. mOnMoveCallback();
  246. }
  247. }
  248. }
时间: 2024-08-08 01:27:27

【小松教你手游开发】【系统模块开发】ngui做聊天系统的相关文章

【小松教你手游开发】【unity实用技能】角色头部跟随镜头旋转

这个在端游上比较场景,在角色展示的时候,当摄像头在角色身边上下左右旋转时,角色头部跟随镜头旋转.如天涯明月刀等. 这个在手游上比较少见,不过实现也没什么区别. 首先一般情况下,找到模型的头部节点,直接用lookAt指向camera就可以了,不过一般需求不会这么简单. 比如说,超过头部扭动极限,头部需要插值回到原始点:当镜头从外部回到极限内,需要插值回来.这时候lookat就没法使用. 更有情况,头部本身坐标系不在世界坐标轴上, 可能旋转了90多或者输出的prefab就是歪的等等,这些情况都没办法

【小松教你手游开发】【unity系统模块开发】热更

现在的手游项目如果没个热更新迭代根本跟不上, 特别是像我们项目做mmo的更是需要经常改动代码. 而现在的项目一般会选择用lua的方式实现热更新 不过我们项目由于历史原因没有使用,用的是另外一种方案 在项目里的所有GameObject都不挂脚本(NGUI脚本就通过代码的方式挂上),自己写的脚本都不继承Mono并打成dll,然后通过一个启动脚本去打开这些dll. 不过这样就有个问题,ios不能热更... 不管怎么样,先来讲讲这种方案要怎么做. 首先有两部分,一部分是打包,一部分是解包. 而包又分为资

【小松教你手游开发】【unity实用技能】根据上一个GameObject坐标生成的tips界面

开发游戏,特别是mmo手游的时候经常需要开发的一个需求是,点击某个装备,在它附近的位置生成一个tips界面,介绍装备功能和各种信息. 像上面红色框框里的这个. 这个主要的问题是 根据点击的GameObject对应生成这个详情界面时,详情界面位置需要合理摆放(不能显示不到,不能遮挡等) 基本的思路是, 首先找到GameObject的position, 把手机屏幕大概分成四个象限,知道这个GameObject大概在这个屏幕的哪个象限(左上,左下,右上,右下) 根据象限来判断详情界面应该在GameOb

【小松教你手游开发】【unity系统模块开发】Unity Assetbundle打包笔记

*最近项目更新了Unity5.5.2,顺便更新了项目的ui打包,也更新一下这边的笔记 首先打包分为两部分,一部分是打包成Assetbundle包,一部分是从Assetbundle包解包出来成为可用的资源. 首先说第一部分 打包 所有资源都可以打包,甚至不是资源(一些数据)也可以打包,只要你需要. 打包出来的东西都可以直接用,一个字体,一个Texture,一个Prefab,一个场景,都是一打出来成Assetbundle包就可以直接用,但是为什么大家还是要各自开发自己的打包流程呢? 最重要的原因就是

【小松教你手游开发】【系统模块开发】做一个3d旋转菜单

在unity做一个3d旋转菜单,像乱斗西游2的这种: 暂时有两种方法可以实现: 一.当做是2d界面,通过定义几个固定点的坐标.大小.透明度,还有每个点的panel depth大小,把数据存储下来,在手机滑动的过程中计算滑动划过的距离和这个panel大小的比值,乘以两个点之间的距离,获得坐标点移动的距离,通过改变x轴改变位置,同理改变大小和透明度. 这个方法我自己做2d游戏的时候实现过,做起来比较简单,没有什么可拓展性可言,并且会有很多限制,比如拖动过程中很难转变方向.要自己实现运动中的弹性(这里

【小松教你手游开发】【系统模块开发】unity 数据储存到本地为二进制文件(聊天记录本地储存)

unity游戏开发中有很多需要把数据储存到本地,官方有好几个方式可以使用,下面简单介绍一下. 一.Stream::Write,Stream::WriteLine 这个方法是打开数据流就开始写字符串,可以指定长度写,也可以一行一行的写.具体参考http://blog.csdn.net/dingxiaowei2013/article/details/19084859和雨松大神的http://www.xuanyusong.com/archives/1069 这种方法最简单,一行一行的写,一行一行的读,

【小松教你手游开发】【unity系统模块开发】unity网络层读写

在unity做强联网游戏的时候一般会选择用tcp来做通信(据说有一种udp的传输具有更高的效率),而接收信息的方法上一般会选择新建一个线程循环读取. 今天在我们项目上看到另外的一种方法.这里记录一下. 首先建立tcp连接 #using System.Net.Sockets; TcpClient tcpClient = new TcpClient(); tcpClient .BeginConnect(address,port,new AsyncCallback(this.OnConnect),re

【小松教你手游开发】【unity系统模块开发】Unity5.5.2UI打包AssetBundle

之前已经有几篇文章写打包AssetBundle,但毕竟没有实际在项目中写过都写的比较浅. 刚好最近项目更新Unity5.5.2就顺便由我来更新ui打包流程 这里就把这次的经验写一下 这里还是稍微解释一下打包的基本目的: 打包ui就是把你做的界面打包出来成assetbundle包,讲道理你就把每个界面打成bundle包在游戏中你就可以直接加载来用,但是这样子的话你的每个bundle包就会非常的大,为什么呢,是因为这样子每个界面的bundle包里都包含这个界面用到的字体,贴图atlas,textur

【小松教你手游开发】【unity实用技能】拓展函数(给系统代码添加可直接使用的接口)

拓展函数的意思是给一些没有源码的脚本添加上你自己写的接口并可以直接调用. using UnityEngine; using System.Collections; namespace ExtensionMethods { public static class MyExtensions { public static void SetLocalPositionX(this Transform transform, float x) { Vector3 newPosition = new Vect