MOMO与图灵合作的《Unity 3D游戏开发》没想到开售不到1个月就售罄了,心里实在是太高兴了。不过高兴归高兴,学习生活工作我是不能耽误的,加油哇咔咔。 NGUI系列的文章在上一章提到的活灵活现ListView相信大家已经熟练掌握,但是这些是满足不了程序猿的欲望的,哇咔咔~~NGUI中提供了两种Scroll View 一种是通过手指或鼠标滑动视图时移动平面物体,另一种则是直接移动摄像机,他们各有各的好处。但是NGUI提供的Scroll View很难实现类似Android 与 IOS 中的Scroll View 滚动相册的那种效果,至少MOMO没有很快的找到方法,不过程序猿的力量是伟大无穷的。虽然不能用它提供的API做出来,但是我们可以通过另外的手打巧妙的实现。不用担心MOMO会在这篇文章仔细向大家介绍如何实现自制Scroll View实现滚动相册。
如下图所示,这是我们的工程页面,程序的实现原理是将相册在Unity3D世界中呈横向队列,摄像机固定的照射在第一个Item相册,当手指发生滑动事件时,计算向左滑动还是向右滑动,此时整体移动相册队列,而摄像机不动。为了让滑动效果更加好看我们需要使用插值计算滑动的时间,使滑动队列不是直接移动过去,而是以一定惯性移动过去。相册下方我们制作一个小白点用来记录当前滑动的位置,在做几个灰色的点表示队列一共的长度,滑动时下方的小白点也会跟随移动,这样就更想高级控件啦。当然小白点与小灰点是要根据item的数量居中显示的喔。
注解1:滚动相册一般可分为两种,第一种为数量已知的情况,第二种为数量未知的情况。因为第一种比较简单所以我们主要探讨第二种。
Script historyInit.cs: 该脚本用于相册队列的初始化工作。在这里初始化相册队列的数量,计算完毕让队列以横向线性的排列方式在Unity3D中。
Prefab item:每个相册的预设,我这里每个相册上还会有一些文字的描述,我需要动态的修改它们的内容。大家也可根据自己的情况制作自己的相册item。
Prefabhui:相册滚动时下方用来记录相册队列总数的灰色小点。
Prefabbai :相册滚动时下方用来记录当前滚动页面ID的白色小点。
Point :因为灰色、白色的点不能和相册队列在一个面板之上,否则会跟着相册队列移动而移动,所以这里将灰色白色的点放在两外一个面板之上。
注解2:这个面板上的4个item就是我们通过historyinit脚本初始化时动态生成赋值的、当界面发生触摸事件时,会整体移动该面板让自对象的相册队列跟随移动。
注解3:button0 – button3 是下方的Tabar。bai(Clone)表示白色的小点,hui(Clone)表示灰色的小点,它们的位置是需要根据滑动事件而改变的。
因为我们需要监听每一个Item的滑动事件,所以肯定要在每一个item预设之上绑定监听事件的脚本,如下图所示。
注解1:因为需要监听触摸滑动事件,所以肯定要绑定Box Collider组件,这个是NGUI的标准用法。
注解2:Move脚本用来监听向左滑动 向右滑动 点击事件。
注解3:这个就是每一个相册的item,在上图中挂在historyInit脚本之上。
historyInit.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
using UnityEngine; using System.Collections; using System.Collections.Generic; public class historyInit :MonoBehaviour { //相册列表的每一个item public GameObject prefab; //灰色的小点 public GameObject prefabhui; //白色的小点 public GameObject prefabbai; //另外一个显示面板 //用来放置灰色、白色小点 public Transform ponit; //白色小点的临时对象 private GameObject bai; //链表,用来记录每一个相册中的一些用户信息 List<UserData> users = new List<UserData>(); //灰色、白色小点下方的起始位置。 int start; void Start () { //将当前面板对象储存在全局静态变量中 Globe.ListPanel = gameObject; loadSQL (); initItem(); } //以前是读取数据库 //写例子程序就没必要使用数据库了 //这里直接写4个死值,当然数量是灵活使用的 void loadSQL () { //表示一共向U3D世界中添加横向4个相册队列 for(int i =0; i< 4; i ++) { //简单的对象储存 string name = "momo =" + i; string age = "26 = " + i; string height = "183 ="+ i; users.Add(new UserData(name,age,height)); } } void initItem() { //因为下方灰色 白色的小点需要根据相册列表的数量来计算居中显示 int size = users.Count; //乘以16表示计算所有小点加起来的宽度 int length = (size - 1) * 16; //得到下方灰色 白色 小点的居中起始位置 start = (-length) >>1; for(int i=0; i< size; i++) { //把每一个相册加入相册列表 GameObject o =(GameObject) Instantiate(prefab); //设置这个对象的父类为 当前面板 o.transform.parent = transform; //设置相对父类的坐标,这些值可根据自己的情况而设定, //总之就是设置相册列表中每一个item的坐标,让它们横向的排列下来就行 o.transform.localPosition = new Vector3(25 + i* 243,-145f,-86f); //设置相对父类的缩放 o.transform.localScale= new Vector3(0.7349999f,0.66f,0.7349999f); //得到每一个user的信息 UserData data = users[i]; //遍历每一个相册对象的子组件, UILabel []label = o.GetComponentsInChildren<UILabel>(); //拿到UILabel并且设置它们的数据 label[0].text = data.age; label[1].text = data.height; label[2].text = data.name; //把每一个灰色小点加入3D世界 GameObject hui =(GameObject) Instantiate(prefabhui); //设置灰色小点的父类为另外一个面板 hui.transform.parent = ponit; //设置每一个灰色小点的位置与缩放,总之让它们居中排列显示在相册列表下方。 hui.transform.localPosition = new Vector3(start + i* 16,-120f,0f); hui.transform.localScale= new Vector3(8,8,1); //深度 因为是先在屏幕下方绘制4个灰色的小点, 然后在灰色上面绘制白色小点 //表示当前的窗口ID 所以深度是为了设置白色小点在灰色小点之上绘制 hui.GetComponent<UISprite>().depth = 0; } //下面的数据是把当前初始化的数据放在一个static类中 //在Move脚本中就可以根据这里的数据来判断了。 //滑动列表的长度 Globe.list_count = size -1; //相册每一项的宽度 Globe.list_offset = 243; //当前滑动的索引 Globe.list_currentIndex = 0; //点击后打开的新游戏场景 Globe.list_go_name= "LoadScene"; //把白色小点也加载在3D世界中 bai =(GameObject) Instantiate(prefabbai); //设置它的深度高于 灰色小点,让白色小点显示在灰色小点之上 bai.GetComponent<UISprite>().depth = 1; //设置白色小点的位置 setBaiPos(); } void Update() { //当用户滑动界面 //在Update方法中更新 //当前白色小点的位置 setBaiPos(); } void setBaiPos() { //Globe.list_currentIndex 就是当前界面的ID //根据ID 重新计算白色小点的位置 bai.transform.parent = ponit; bai.transform.localPosition = new Vector3(start + Globe.list_currentIndex* 16,-120f,-10f); bai.transform.localScale= new Vector3(8,8,1); } } |
如下图所示,我们可以看出Tabbar 、 下方记录界面的灰色、白色小点、摄像机 它们是不会发生改变的。唯一改变的就是面板之上的相册队列。为了让滑动界面的效果更加连贯,我们需要以插值的形式来计算真个相册面板的坐标。
触摸的事件全都写在Move.cs脚本中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
using UnityEngine; using System.Collections; public class Move : MonoBehaviour { //是否触摸 bool isTouch = false; //是否向左滑动 bool isRight = false; //是否向右滑动 bool isLeft = false; //是否正在滑动中 bool isOnDrag = false; //滑动中事件 void OnDrag (Vector2 delta) { //为了避免事件冲突 //这里只判断一个滑动的事件 if(!isTouch) { if(delta.x > 0.5) { //向左滑动 isRight = true; isOnDrag = true; }else if(delta.x < -0.5) { //向右滑动 isLeft = true; isOnDrag = true; } isTouch = true; } } //滑动后松手调用OnPress事件 void OnPress() { //重新计算当前界面的ID if(Globe.list_currentIndex < Globe.list_count && isLeft) { Globe.list_currentIndex++; } if(Globe.list_currentIndex >0 && isRight) { Globe.list_currentIndex--; } //表示一次滑动事件结束 isTouch = false; isLeft = false; isRight = false; } void Update() { //这个方法就是本节内容的核心 //Globe.ListPanel 这个对象是我们在historyInit脚本中得到的,它用来当面相册面板对象使用插值,让面板有惯性的滑动。 //Vector3.Lerp() 这个是一个插值的方法, 参数1 表示开始的位置 参数2 表示结束的位置 参数3 表示一共所用的时间, 在Time.deltaTime * 5 时间以内 Update每一帧中得到插值当前的数值,只到插值结束 //-(Globe.list_currentIndex * Globe.list_offset) 得到当前需要滑动的目标点。 //请大家仔细看这个方法。 Globe.ListPanel.transform.localPosition =Vector3.Lerp(Globe.ListPanel.transform.localPosition, new Vector3(-(Globe.list_currentIndex * Globe.list_offset),0,0), Time.deltaTime * 5); } void OnClick () { //当点击某个item时进入这里 if(!isOnDrag) { //如果不是在拖动中 进入另一个场景 Application.LoadLevel(Globe.list_go_name); } else { //否则等待用户重新发生触摸事件 isOnDrag = false; } } } |
还有两个辅助的类,我也贴出来。
UserData.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using UnityEngine; using System.Collections; public class UserData{ public string name; public string age; public string height; public string hand; public UserData(string _name, string _age,string _height) { age = _age; height = _height; name = _name; } } |
Globe.cs 这个静态类用来共享记录多个脚本甚至多个场景所需的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using System.Collections.Generic; public class Globe { public static GameObject ListPanel; public static int list_count; public static int list_currentIndex; public static int list_offset; public static string list_go_name; } |
写到这里,本篇博文就写的差不多了。这篇文章我是用NGUI来实现的触摸滚动效果,仔细想想,其实不用NGUI完全也能实现这样的效果。在脚本中完全可以通过射线 以及 触摸的时间去计算当前用户操作的事件,这篇文章里的工程最后我是打包在Android上面的,效果挺不错,滑动的效果图不好截取我也不截取了,主要还是文章的书写内容,希望大家学习愉快,雨松MOMO祝大家晚安,哇咔咔,啦啦啦。
- 本文固定链接: https://www.xuanyusong.com/archives/1465
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
雨松大神,插值方法的第二个参数的y坐标为啥是0呢
可以看看你的源码吗306891402@qq.com,谢谢
??
有没有资源包啊
在Move.cs文件中的OnPress函数结尾应该加句isOnDrag = false;要不在一次滑动事件之后得点击第二下才能响应按键事件了
雨松大大 这个项目有源文件么?希望带上源文件
雨松老师 有没有 2dtookit 的呢?
请问item(clone)和hui(clone)是什么啊?他们是什么类型的GameObject?而且希望能求到工程源文件,麻烦发到214137022@qq.com,谢谢了。。。
是预制prefab。
雨松老师讲的真好,有些地方有点不懂。求工程源文件呀。我的邮箱是365206763@qq.com
为什么 我的OnDrag 和 OnPress 没有反应
我也购买了博主大人的《unity3d 游戏开发》,真是入门的好教材,谢谢博主大人!另外博主大人这篇ScrollView的完整工程能发给我看一下吗?我的邮箱是m_iSnow@hotmail.com再次感谢!
请问如果scrollview的范围设置得比较大,但是里面的内容比较少,就会造成内容显示在区域中央,有没有方法当内容不够的时候,让内容显示在顶部?
修改UIPanelpublic bool isTop=false;public Vector3 CalculateConstrainOffset (Vector2 min, Vector2 max) { float offsetX = clipRange.z * 0.5f; float offsetY = clipRange.w * 0.5f; Vector2 minRect = new Vector2(min.x, min.y); Vector2 maxRect = new Vector2(max.x, max.y); Vector2 minArea = new Vector2(clipRange.x – offsetX, clipRange.y – offsetY); Vector2 maxArea = new Vector2(clipRange.x + offsetX, clipRange.y + offsetY); if (clipping == UIDrawCall.Clipping.SoftClip) { minArea.x += clipSoftness.x; minArea.y += clipSoftness.y; maxArea.x -= clipSoftness.x; maxArea.y -= clipSoftness.y; } Vector2 v2 =NGUIMath.ConstrainRect(minRect, maxRect, minArea, maxArea); if(isTop&&(max.y-min.y)
代码不全,能重新发一份吗
修改UIPanelpublic bool isTop=false;public Vector3 CalculateConstrainOffset (Vector2 min, Vector2 max){float offsetX = clipRange.z * 0.5f;float offsetY = clipRange.w * 0.5f;Vector2 minRect = new Vector2(min.x, min.y);Vector2 maxRect = new Vector2(max.x, max.y);Vector2 minArea = new Vector2(clipRange.x – offsetX, clipRange.y – offsetY);Vector2 maxArea = new Vector2(clipRange.x offsetX, clipRange.y offsetY);if (clipping == UIDrawCall.Clipping.SoftClip){minArea.x = clipSoftness.x;minArea.y = clipSoftness.y;maxArea.x -= clipSoftness.x;maxArea.y -= clipSoftness.y;}Vector2 v2 =NGUIMath.ConstrainRect(minRect, maxRect, minArea, maxArea);if(isTop&&(max.y-min.y)
老师,能给个源代码么,谢谢啦~我的邮箱是244157539@qq.com
看了你的书,但觉得自己还处于一个小小有基础的人,这个能给个源代码吗,谢谢谢。我的邮箱1975209805@qq.com
雨松可以给下这个的源码吗?看你做的挺好看的。资源也是难求的。呵呵。谢谢。sjl_leaf@163.com