开篇废话:
在9月份时参加了一个网站的比赛,比赛的题目是需要使用第三方平台提供的服务做出创意的作品。
于是我选择使用语音服务,天气服务,Unity3D,Android来制作一个3D语音天气预报,我给它起名叫做3D语音天气球(好土。。。)
虽然没获奖但我觉得这个项目中还是有些东西比较有创意的,所以打算分享出来,或许有人会用到。
下面简单看下效果图:
左边是Unity做出后在电脑上运行效果图
右边是Unity结合Android和语音控制之后在手机运行的效果图(手机不会做GIF):
瞅着还不错吧。。。
项目简介:
项目结构:
首先这个项目的开发分为Android端和Unity3D端:
Android端:
Android端主要负责的是语音控制模块和4个按钮,并将语音处理后的结果传递到Unity端中做处理
Unity3D端:
Unity端负责接收Android端语音处理后传递过来的信息,和4个按键的反馈。
并根据不同的省市情况实时的从网上获取天气信息,解析后显示在我们制作的3D球上。
Build:
最后将Android端的代码以插件的形式放入Unity端中,然后在Unity端Build成apk文件在手机中运行。
业务简介:
初始界面:
在初始界面最上方有个简易的动态栏可以显示一些提示信息。
动态栏之下是一个省市的名称标题和平均气温。
最中间的“动态球”显示全国各省份的分布。并且会根据该省市的平均气温设置不同的颜色来大体反映出全国天气情况(越红表示温度越高,越蓝表示越低)。
详细页面:
在选中一个省后(可以鼠标或手势选中,如果在手机上可以语音选中),点击详细可以进入到详细页面。
在详细页面中显示当前省内所有城市的天气信息,并且“动态球”上会显示出天气趋势的小图片。
如果选中某个城市后再次点击详细则会在屏幕中上方显示该城市的详细天气信息,包括风速紫外线等等信息。
功能划分:
这个项目虽然功能比较简单,但是代码也写了不少,关键好多地方都是为了快速完成功能,也并没有考虑一些框架和结构,因此逻辑看起来特别乱。
为了思路清晰,我准备分几篇文章把这个项目详细介绍一下。因为我觉得这样分块进行不仅思路清晰,还可以按需求观看,而且也避免都写在一篇文章中导致又臭又长逻辑混乱。
我准备分四篇文章来介绍这个项目:
一:创建可旋转的“3D球”:3D语音天气球(源码分享)——创建可旋转的3D球
二:通过天气服务,从网络获取时实天气信息并动态生成“3D球”:3D语音天气球(源码分享)——通过天气服务动态创建3D球
三:Android语音服务和Unity的消息传递:3D语音天气球(源码分享)——在Unity中使用Android语音服务
四:Unity3D端和Android端的结合:3D语音天气球(源码分享)——完结篇
今天就先来介绍一下如何创建这个可旋转的“3D球”:
3D球的创建
下面是我将其他所有模块的代码都去掉后,一个纯净版的效果图,而今天的任务就是完成它:
如图可示,
在这个“3D”球中平均分布这很多“小块”。
本例中每一块显示的是一个标题和一行详细信息。
当然也可以在每块中显示图片,显示3D模型。而且还可以有更多创意,比如3D球中的每个小块又是一个3D球,如此递归循环下去这样是不是就是我们的宇宙了(想多了,赶紧跑回来。。。)
原理构成:
先来看下面3个图示:
相信通过上面的图示,大家就应该差不多知道这个3D球和球面上的"小块"是如何构成的了。
没错,这个3D球是由一个大球和若干个小球组成的,小球附着在大球上,大球负责控制转动,小球也就相应的转动了。
再稍微详细些:
大球是一个“透明/隐形”的一个物体,他有物理碰撞器属性,所以可以转动。
所有的小球均匀分布在大球之上,小球同样是“透明/隐形”,但小球上会附着文字和图片等,小球永远随着大球转动且文字方向永远面向摄像机。
把上面这些弄明白之后配合Unity最简单的几个基础语法和知识点就可以完成这个可动态旋转的3D球了!
代码详解:
下面的内容解需要一定Unity基础,Unity入门很容易。我的习惯是先看最基本的视频教程(一定要国人出的,一定要简单无脑),之后看书看官网文档,再去搜几个大神的blog看看。
这里强烈推荐两个大神的Blog:雨松MOMO研究院,墨半成霜的博客
1.创建大球
首先需要建立大球
通过:菜单-GameObject-create other-sphere来创建一个球体(sphere)
调整大球的大小,位置。点掉他的Mash Renderer,这样大球就“隐身”了。
3. 创建小球预设
小球是由代码动态生成的,但是我们需要先做一个小球的预设。
方法和制作大球相同,不过需要给小球增加一个Text Mesh来显示文字,如果除了显示文字还需要在小球身上显示一些其他东西,可以建一个空的GameObject添加相应的组件,然后将这个GameObject加入到小球组成一个整体(prefab预设)。
这个例子中是显示城市名和简单的天气信息,城市名直接加在小球上,简单的天气信息放在另一个GameObject上,调整大小后放入小球中组成一个整体。
4.动态生成小球
这个我认为是这个步骤是整个项目里最难的地方,他的难度在于要根据小球数量计算出分布在大球身上的坐标。
简单来说就是在球体表面平均分割点,并求出每个点的三维坐标。
如果我能算出来就是大神了。。。还好有Google,我在Google中搜到了一个算法如下:
float inc = Mathf.PI * (3 - Mathf.Sqrt (5)); float off = 2 / N; for (int k = 0; k < (N); k++) { float y = k * off - 1 + (off / 2); float r = Mathf.Sqrt (1 - y * y); float phi = k * inc; Vector3 pos = new Vector3 ((Mathf.Cos (phi) * r * size), y * size, Mathf.Sin (phi) * r * size); }N为需要平均分为多少份。size为分割小球的大小,r为大球半径。
for循环中的Vector3类型的pos就是计算所得的三维坐标。我也不知道这个算法是什么原理,数学好的希望给讲解一下。
更多的在球体上分割点的算法可以在Google上试着搜下关键词:distributed points on sphere
通过算法求出每个点的坐标后,通过下面代码就可以创建出小球:
GameObject text = (GameObject)Instantiate (textObject, pos, Quaternion.identity);
textObject是我们之前做的小球预设, pos是刚刚求出的小球坐标。最后循环创建所有小球的效果如下:
5. 设置小球文字和颜色
上面创建好名为text的GameObject对象就是我们的小球。下面就要为每个小球设置父亲,颜色,大小,中心文字,详细信息。
// 将大球设置为小球的父亲(将小球放在大球身上) text.transform.parent = gameObject.transform; // 获取小球的TextMesh组件 TextMesh tm = (TextMesh)text.GetComponent<TextMesh> (); // 设置文字 tm.text = city.getName (); // 设置颜色 tm.color = city.getWeatherColor (); // 设置大小 text.transform.localScale = city.getSize (); // 循环获取小球的孩子(也就是小球里面添加的GameObject,这里只有一个显示详细信息) foreach (Transform child in text.transform) { TextMesh tm2 = (TextMesh)child.GetComponent<TextMesh> (); tm2.text = city.getTempture (); tm2.color = city.getWeatherColor (); }
6. 设置小球朝向,透明度,颜色
因为整个大球是不停在旋转,所以大球上的小球们的朝向,透明度,和颜色都会不停的变化。所以需要实时的调整小球的各种属性。
在update()函数中:
// 遍历大球中的小球,设置小球的朝向,透明度,颜色 foreach (Transform child in gameObject.transform) { // 小球永远朝向摄像机 child.LookAt (new Vector3 (cameraTarget.position.x, cameraTarget.position.y, -cameraTarget.position.z)); // 通过距离设置小球的透明度,产生越远越透明的效果 float dis = Vector3.Distance (child.position, cameraTarget.transform.position); Color c = child.renderer.material.color; float alpha = Mathf.Abs (dis - r) / 5f + 0.1f; // 设置小球和它子gameobject的颜色 child.renderer.material.color = new Color (c.r, c.g, c.b, alpha); foreach (Transform cc in child.transform) { cc.renderer.material.color = new Color (c.r, c.g, c.b, alpha); } }
3D球的旋转:
上面我们已经基本设置后大球和其身上的小球们(现在他们是一个整体)。
下面我们就需要让他们旋转起来,这时我们需要为大球添加碰撞器,这样我们才能点击它并旋转它。
Component-Physics-Sphere Collider
通过下面代码可以进行手势/鼠标旋转(如果快速转动会有一个逐渐变慢到最终停止的效果)
初始化:
//是否被拖拽// private bool onDrag = false; //旋转速度// private float speed = 3f; //阻尼速度// private float tempSpeed; //鼠标沿水平方向移动的增量// private float axisX; //鼠标沿竖直方向移动的增量// private float axisY; //滑动距离(鼠标) private float cXY;
重写鼠标按下和拖拽函数:
//鼠标移动的距离 void OnMouseDown () { //接受鼠标按下的事件// axisX = 0f; axisY = 0f; } //鼠标拖拽时的操作 void OnMouseDrag () { onDrag = true; axisX = -Input.GetAxis ("Mouse X"); axisY = Input.GetAxis ("Mouse Y"); cXY = Mathf.Sqrt (axisX * axisX + axisY * axisY); //计算鼠标移动的长度// if (cXY == 0f) { cXY = 1f; } }
计算阻尼(为了实现上面提到的越转越慢效果)
//计算阻尼速度 float Rigid () { if (onDrag) { tempSpeed = speed; } else { if (tempSpeed > 0) { //通过除以鼠标移动长度实现拖拽越长速度减缓越慢 if (cXY != 0) { tempSpeed -= speed * 2 * Time.deltaTime / cXY; } } else { tempSpeed = 0; } } return tempSpeed; }
// 根据计算出的阻尼和X,Y轴的偏移来旋转大球 gameObject.transform.Rotate (new Vector3 (axisY, axisX, 0) * Rigid (), Space.World); // 如果鼠标离开屏幕则标记为已经不再拖拽 if (!Input.GetMouseButton (0)) { onDrag = false; }
写在最后:
上面所有的代码都只是一个思路和一些必要的步骤。如果大家有兴趣,可以下载它的源码来看看。
GitHub地址:https://github.com/a396901990/3D_Sphere
一:创建可旋转的“3D球”:3D语音天气球(源码分享)——创建可旋转的3D球
二:通过天气服务,从网络获取时实天气信息并动态生成“3D球”:3D语音天气球(源码分享)——通过天气服务动态创建3D球
三:Android语音服务和Unity的消息传递:3D语音天气球(源码分享)——在Unity中使用Android语音服务
四:Unity3D端和Android端的结合:3D语音天气球(源码分享)——完结篇