• Unity制作简单3D图表


    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!


    开篇废话:


    在大学时稍微自学过一段时间Unity3D,虽然现在在做安卓,但一直对游戏开发很感兴趣,所以平时偶尔有空也会稍微看看,不过水平还是未入门菜鸟级的。

    下面这个demo是看了雨松MOMO大神所写的几篇基础文章后,写的一个练习demo,用来展示简易的3D图表。

    这个Demo非常初级,纯粹是为了练习知识点,但是所应用到的知识点非常基础,非常重要,适合初学者学习。截图如下:


     

     


    简介:


    这个Demo可以分为4个部分

    第一部分——建立3D坐标系:

    外框首先由四个平面所围成的一个“3D坐标系”,和X、Y、Z三个“坐标”组成。

    1.四个平面

    这四个平面所用的就是系统自带的cube,创建并调整cube的大小和方向组成上面的形状。


    2.创建坐标系prefab

    创建一个预设,并将左平面,右平面和后平面添加到下平面中最后添加到预设里,这样他们就变成了一个整体。如下图所示:



    3.调整prefab在世界中的位置和他的scale

    由于要设立坐标点,所以需要使刚才建立的坐标系的零点对应世界坐标的零点。

    在本例中:假设每个单位长度为1,X坐标需要分成12份(12个月)坐标系scale的x值为24(每两份代表一个刻度)。Y轴代表百分比,分为5份(每份一个刻度)。Z轴同理。


    通过上面的3个步骤,3D坐标系的样子已经出来了,下面开始为这个坐标系添加“刻度名称”。



    第二部分——为坐标添加刻度名称:

    刻度名称使用两个知识点:

    1.世界坐标转换为屏幕坐标:

    添加刻度名称其实就是将世界坐标的坐标值(因为我们已经在上一步中将坐标系和世界坐标同步了)转换为屏幕坐标值。


    2.使用GUI.Label写出坐标名称:

    通过刚才算出的屏幕坐标处使用GUI.Label函数写出当前坐标点的名称。


    下面是本例中这个方法的代码:

    1. // isY代表是不是Y轴  
    2. void drawCoordinate (Vector3 point, string name, bool isY) {  
    3.     // 将世界坐标转换为屏幕坐标  
    4.     Vector2 position = camera.WorldToScreenPoint (point);  
    5.     position = new Vector2 (position.x, position.y);  
    6.   
    7.     // 设置刻度的大小和颜色  
    8.     Vector2 nameSize = GUI.skin.label.CalcSize (new GUIContent(name));  
    9.     GUI.color  = Color.yellow;  
    10.     // 根据X,Y轴的不同加上适当偏移量画出刻度  
    11.     if (isY) {  
    12.         GUI.Label(new Rect(position.x-nameSize.x-coordOffset ,Screen.height-position.y,nameSize.x,nameSize.y), name);  
    13.     }  
    14.     else {  
    15.         GUI.Label(new Rect(position.x-nameSize.x ,Screen.height-position.y,nameSize.x,nameSize.y), name);  
    16.     }  
    17. }  
    调用方法如下:
    1. void OnGUI()  
    2. {  
    3.     drawCoordinate (new Vector3 (0 , 0, 0), "0.00%"true);  
    4.     drawCoordinate (new Vector3 (2 , 0, 0), "Jan"false);  
    5. }  

    最后我贴上从网上找的关于Unity中坐标系的知识点(原文实在找不到了我就不贴连接了):

    【Unity中四种坐标系】
    1、World Space(世界坐标):
    我们在场景中添加物体(如:Cube),他们都是以世界坐标显示在场景中的。
    transform.position可以获得该位置坐标。

    2、Screen Space(屏幕坐标):
    以像素来定义的,以屏幕的左下角为(0,0)点,右上角为(Screen.width,Screen.height),Z的位置是以相机的世界单位来衡量的。
    Screen.width = Camera.pixelWidth
    Screen.height = Camera.pixelHeigth

    鼠标位置坐标属于屏幕坐标,Input.mousePosition可以获得该位置坐标,
    手指触摸屏幕也为屏幕坐标,Input.GetTouch(0).position可以获得单个手指触摸屏幕坐标。

    3、ViewPort Space(视口坐标):
    视口坐标是标准的和相对于相机的。相机的左下角为(0,0)点,右上角为(1,1)点,Z的位置是以相机的世界单位来衡量的。

    4、绘制GUI界面的坐标系:
    这个坐标系与屏幕坐标系相似,不同的是该坐标系以屏幕的左上角为(0,0)点,右下角为(Screen.width,Screen.height)。

    【四种坐标系的转换】
    1、世界坐标→屏幕坐标:

    camera.WorldToScreenPoint(transform.position);
    这样可以将世界坐标转换为屏幕坐标。其中camera为场景中的camera对象。

    2、屏幕坐标→视口坐标:
    camera.ScreenToViewportPoint(Input.GetTouch(0).position);
    这样可以将屏幕坐标转换为视口坐标。其中camera为场景中的camera对象。

    3、视口坐标→屏幕坐标:
    camera.ViewportToScreenPoint();

    4、视口坐标→世界坐标:
    camera.ViewportToWorldPoint();


    第三部分——在3D坐标系中画折线

    坐标系建立好以后我们就可以在这个坐标系中画折线来啦。

    坐标系已经和世界坐标系同步,所以每个坐标的坐标点我们已经知道,下面的问题就是如何将点连成线。


    Unity提供了LineRenderer来画线,可以通过如下两种方法创建它:

    一.Unity编辑器的方式:

    1.Unity -> GameObject -> Create Empty 创建一个空的对象,我命名为line。

    2.然后点击 Component -> Effects-> Line Renderer 给line添加一个线渲染器的属性


    二.脚本的方式:

    下面是本例的使用代码:

    1. public class lineScript1 : MonoBehaviour {  
    2.   
    3.     private Color c1 = Color.red;  
    4.     private Color c2 = Color.red;  
    5.     private LineRenderer lineRenderer;  
    6.   
    7.     private Vector3 v0 = new Vector3(0.0f,0.0f,3.0f);  
    8.     private Vector3 v1 = new Vector3(2.0f,2.0f,0.0f);  
    9.     private Vector3 v2 = new Vector3(4.0f,3.0f,1.0f);  
    10.     private Vector3 v3 = new Vector3(10.0f,4.0f,0.0f);  
    11.     private Vector3 v4 = new Vector3(15.0f,6.0f,2.0f);  
    12.     private Vector3 v5 = new Vector3(20.0f,8.6f,0.0f);  
    13.   
    14.     void Start () {   
    15.         lineRenderer = lineRenderer = gameObject.AddComponent<LineRenderer>();  
    16.         lineRenderer.SetColors(c1, c2);  
    17.         lineRenderer.SetWidth(0.2f,0.2f);  
    18.         lineRenderer.SetVertexCount(6);  
    19.     }  
    20.   
    21.     void Update () {  
    22.         lineRenderer.SetPosition (0, v0);  
    23.         lineRenderer.SetPosition (1, v1);  
    24.         lineRenderer.SetPosition (2, v2);  
    25.         lineRenderer.SetPosition (3, v3);  
    26.         lineRenderer.SetPosition (4, v4);  
    27.         lineRenderer.SetPosition (5, v5);  
    28.     }  
    29. }  

    LineRenderer的详细使用请参考雨松momo的文章:Unity3D研究院之游戏对象的访问绘制线与绘制面详解(十七)



    第四部分——通过手势/鼠标来放大、缩小、旋转坐标系

    简单概括为两个要点:

    1.手势和鼠标的识别

    在Unity中手势识别有一个插件:FingerGestures

    在本例中当然不需要做的这么复杂,只需要判断触摸点来进行一些简单计算就可以。

    无论是Android应用还是Unity手势判断最常用的就是简单的通过记录之前的触摸点位置和之后的触摸点位置然后再进行计算。

    计算触摸点Unity提供如下几个方法:

    Input.touchCount可以判断当前触摸点的数量,所以可以通过这个方法来判断是单点触摸还是多点触摸

    Input.GetTouch(0).phase方法会返回一个表示当前触摸类型的枚举(TouchPhase)。

    TouchPhase有如下几种类型:

    • 手指已触摸屏幕。
    • 手指在屏幕上移动。
    • 手指触摸屏幕,但并没有移动。
    • 手指从屏幕上移开。这是一个触摸的最后状态。
    • 系统取消跟踪触摸,如用户把屏幕放到他脸上或超过五个接触同时发生。这是一个触摸的最后状态。

    更多关于Input的内容强烈建议去看Unity的脚本手册,英文不好的可以去Unity圣典看中文的。


    2.控制物体的放大,缩小,旋转

    在Unity中放大,缩小,旋转某个物体其实是通过拉近,拉远,旋摄像机来实现的(自动脑补)。


    对于如何将触摸判断和对物体控制的结合我推荐看雨松momo的这篇文章——Unity3D研究院之IOS触摸屏手势控制镜头旋转与缩放(八)


    最后附上本例的这段代码,雨松文章里用的是js的我这里给改成c#并且加上了鼠标滚轮的放大缩小:

    1. public class controll : MonoBehaviour {  
    2.   
    3.     public Transform target;  
    4.   
    5.     private float distance = 50.0f;  
    6.     private float xSpeed = 250.0f;  
    7.     private float ySpeed = 120.0f;  
    8.   
    9.     private int yMinLimit = -20;  
    10.     private int yMaxLimit = 80;  
    11.   
    12.     private float x = 0.0f;  
    13.     private float y = 0.0f;  
    14.   
    15.     private int MouseWheelSensitivity = 5;  
    16.   
    17.     private int zoomMin = 10;  
    18.     private int zoomMax = 50;  
    19.   
    20.     private Vector2 oldPosition1;  
    21.     private Vector2 oldPosition2;  
    22.   
    23.     void Start () {  
    24.         Vector3 angles = transform.eulerAngles;  
    25.         x = angles.y;  
    26.         y = angles.x;  
    27.     }  
    28.       
    29.     void Update () {  
    30.         // 由于要支持鼠标和触摸,所以还需要加一个鼠标的计算  
    31.         if (Input.GetMouseButton (0)) {  
    32.             //根据触摸点计算X与Y位置  
    33.             if (Mathf.Abs(Input.GetAxis("Mouse X") * xSpeed * 0.02f) < 50) {  
    34.                 x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;  
    35.             }  
    36.             if (Mathf.Abs(Input.GetAxis("Mouse Y") * ySpeed * 0.02f) < 50) {  
    37.                 y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;  
    38.             }  
    39.         }  
    40.   
    41.         if(Input.touchCount == 1)  
    42.         {  
    43.             //触摸类型为移动触摸  
    44.             if(Input.GetTouch(0).phase==TouchPhase.Moved)  
    45.             {  
    46.                 //根据触摸点计算X与Y位置  
    47.                 if (Mathf.Abs(Input.GetAxis("Mouse X") * xSpeed * 0.02f) < 50) {  
    48.                     x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;  
    49.                 }  
    50.                 if (Mathf.Abs(Input.GetAxis("Mouse Y") * ySpeed * 0.02f) < 50) {  
    51.                     y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;  
    52.                 }     
    53.             }  
    54.         }  
    55.   
    56.         //判断触摸数量为多点触摸  
    57.         if(Input.touchCount >1 )  
    58.         {  
    59.             //前两只手指触摸类型都为移动触摸  
    60.             if(Input.GetTouch(0).phase==TouchPhase.Moved||Input.GetTouch(1).phase==TouchPhase.Moved)  
    61.             {  
    62.                 //计算出当前两点触摸点的位置  
    63.                 Vector2 tempPosition1 = Input.GetTouch(0).position;  
    64.                 Vector2 tempPosition2 = Input.GetTouch(1).position;  
    65.                 //函数返回真为放大,返回假为缩小  
    66.                 if(isEnlarge(oldPosition1,oldPosition2,tempPosition1,tempPosition2))  
    67.                 {  
    68.                     //放大系数超过3以后不允许继续放大  
    69.                     //这里的数据是根据我项目中的模型而调节的,大家可以自己任意修改  
    70.                     if(distance > zoomMin)  
    71.                     {  
    72.                         distance -= 0.5f;  
    73.                     }  
    74.                 }else  
    75.                 {  
    76.                     //缩小洗漱返回18.5后不允许继续缩小  
    77.                     //这里的数据是根据我项目中的模型而调节的,大家可以自己任意修改  
    78.                     if(distance < zoomMax)  
    79.                     {  
    80.                         distance += 0.5f;  
    81.                     }  
    82.                 }  
    83.                 //备份上一次触摸点的位置,用于对比  
    84.                 oldPosition1=tempPosition1;  
    85.                 oldPosition2=tempPosition2;  
    86.             }  
    87.         }  
    88.       
    89.         // 鼠标滚轮  
    90.         if (Input.GetAxis("Mouse ScrollWheel") != 0)  
    91.         {  
    92.             if (distance >= zoomMin && distance <= zoomMax)  
    93.             {  
    94.                 distance -= Input.GetAxis("Mouse ScrollWheel") * MouseWheelSensitivity;  
    95.             }  
    96.             if (distance < zoomMin)  
    97.             {  
    98.                 distance = zoomMin;  
    99.             }  
    100.             if (distance > zoomMax)  
    101.             {  
    102.                 distance = zoomMax;  
    103.             }  
    104.         }  
    105.     }  
    106.   
    107.     public bool isEnlarge(Vector2 oP1, Vector2 oP2, Vector2 nP1, Vector2 nP2) {  
    108.         //函数传入上一次触摸两点的位置与本次触摸两点的位置计算出用户的手势  
    109.         float leng1 =Mathf.Sqrt((oP1.x-oP2.x)*(oP1.x-oP2.x)+(oP1.y-oP2.y)*(oP1.y-oP2.y));  
    110.         float leng2 =Mathf.Sqrt((nP1.x-nP2.x)*(nP1.x-nP2.x)+(nP1.y-nP2.y)*(nP1.y-nP2.y));  
    111.         if(leng1<leng2)  
    112.         {  
    113.             //放大手势  
    114.             return true;  
    115.         }else  
    116.         {  
    117.             //缩小手势  
    118.             return false;  
    119.         }  
    120.     }  
    121.   
    122.     void LateUpdate(){  
    123.         if (target) {          
    124.             //重置摄像机的位置  
    125.             y = ClampAngle(y, yMinLimit, yMaxLimit);  
    126.   
    127.             Quaternion rotation = Quaternion.Euler(y, x, 0);  
    128.             Vector3 position = rotation * new Vector3(0.0f, 0.0f, -distance) + target.position;  
    129.               
    130.             transform.rotation = rotation;  
    131.             transform.position = position;  
    132.         }  
    133.     }  
    134.   
    135.     public float ClampAngle (float angle, float min,float max ) {  
    136.         if (angle < -360)  
    137.             angle += 360f;  
    138.         if (angle > 360)  
    139.             angle -= 360f;  
    140.         return Mathf.Clamp (angle, min, max);  
    141.     }  
    142. }  


    写在最后:


    这篇文章由于十分简单,而且我觉得逻辑讲的也算清楚,代码也贴了几段,所以源码我就不上传了。


    写这个Demo的目的其实是为了下一篇文章,我在9月份的时候参加了一个网站的比赛,做了一个Unity和Android结合的3D语音的天气预报(可惜没获奖快哭了),过几天准备给它分享出来。因为我本来就是Unity菜鸟,而且好几个月没看了所以就拿这个demo先热热身。。。


    PS:我Unity就会一点基础,挺怕写相关文章的,有错请见谅并恳请指正。。。


  • 相关阅读:
    Java并发编程:线程池的使用
    java动态代理实现与原理详细分析
    Springmvc 横向源码原理解析(原创)
    spring事物配置,声明式事务管理和基于@Transactional注解的使用
    Spring IoC的原理为什么是反射而不是new
    Mybatis源码分析(原创)
    Springboot中做定时任务 和 Springboot API 分页
    python程序入门 基础教程
    本地的jar包添加到maven库中 jdbc举例
    基于接口隔离原则的依赖注入实现
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6168039.html
Copyright © 2020-2023  润新知