鼎鼎大名的贝塞尔曲线相信大家都耳熟能详。这两天因为工作的原因需要将贝塞尔曲线加在工程中,那么MOMO迅速的研究了一下成果就分享给大家了哦。贝塞尔曲线的原理是由两个点构成的任意角度的曲线,这两个点一个是起点,一个是终点。在这条曲线之上还会有两个可以任意移动的点来控制贝塞尔曲线的角度。如下图所示,点1 和点4 就是起点和终点,点2 和点3 就是控制曲线角度的两个动态点。
如下图所示。使用拖动条来让曲线发生旋转,大家会看的更加清晰。目前我们看到的被塞尔曲线是在平面中完成的,其实贝塞尔曲线是完全支持3D中完成,这里是为了让大家看的更加清楚MOMO将忽略Z曲线的Z轴。UnityAPI文档中有贝塞尔曲线的方法,可是只支持编辑器中使用,也就是说无法在程序中使用。那么本篇文章我们利用贝塞尔曲线的数学原理以及LineRenderer组件来完成在Unity中使用贝塞尔曲线。
创建一个U3D的工程,创建一个新游戏对象,绑定LineRenderer组件。
Bezier.cs 这里是贝塞尔曲线的公式C#版本
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 133 134 135 |
using UnityEngine; [System.Serializable] public class Bezier : System.Object { public Vector3 p0; public Vector3 p1; public Vector3 p2; public Vector3 p3; public float ti = 0f; private Vector3 b0 = Vector3.zero; private Vector3 b1 = Vector3.zero; private Vector3 b2 = Vector3.zero; private Vector3 b3 = Vector3.zero; private float Ax; private float Ay; private float Az; private float Bx; private float By; private float Bz; private float Cx; private float Cy; private float Cz; // Init function v0 = 1st point, v1 = handle of the 1st point , v2 = handle of the 2nd point, v3 = 2nd point // handle1 = v0 + v1 // handle2 = v3 + v2 public Bezier( Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3 ) { this.p0 = v0; this.p1 = v1; this.p2 = v2; this.p3 = v3; } // 0.0 >= t <= 1.0 public Vector3 GetPointAtTime( float t ) { this.CheckConstant(); float t2 = t * t; float t3 = t * t * t; float x = this.Ax * t3 + this.Bx * t2 + this.Cx * t + p0.x; float y = this.Ay * t3 + this.By * t2 + this.Cy * t + p0.y; float z = this.Az * t3 + this.Bz * t2 + this.Cz * t + p0.z; return new Vector3( x, y, z ); } private void SetConstant() { this.Cx = 3f * ( ( this.p0.x + this.p1.x ) - this.p0.x ); this.Bx = 3f * ( ( this.p3.x + this.p2.x ) - ( this.p0.x + this.p1.x ) ) - this.Cx; this.Ax = this.p3.x - this.p0.x - this.Cx - this.Bx; this.Cy = 3f * ( ( this.p0.y + this.p1.y ) - this.p0.y ); this.By = 3f * ( ( this.p3.y + this.p2.y ) - ( this.p0.y + this.p1.y ) ) - this.Cy; this.Ay = this.p3.y - this.p0.y - this.Cy - this.By; this.Cz = 3f * ( ( this.p0.z + this.p1.z ) - this.p0.z ); this.Bz = 3f * ( ( this.p3.z + this.p2.z ) - ( this.p0.z + this.p1.z ) ) - this.Cz; this.Az = this.p3.z - this.p0.z - this.Cz - this.Bz; } // Check if p0, p1, p2 or p3 have changed private void CheckConstant() { if( this.p0 != this.b0 || this.p1 != this.b1 || this.p2 != this.b2 || this.p3 != this.b3 ) { this.SetConstant(); this.b0 = this.p0; this.b1 = this.p1; this.b2 = this.p2; this.b3 = this.p3; } } } |
MyBezier.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 |
using UnityEngine; public class MyBezier : MonoBehaviour { //贝塞尔曲线算法类 public Bezier myBezier; //曲线的对象 public GameObject Yellowline; //曲线对象的曲线组件 private LineRenderer YellowlineRenderer; //拖动条用来控制贝塞尔曲线的两个点 public float hSliderValue0; public float hSliderValue1; void Start() { //得到曲线组件 YellowlineRenderer = Yellowline.GetComponent<LineRenderer>(); //为了让曲线更加美观,设置曲线由100个点来组成 YellowlineRenderer.SetVertexCount(100); } void OnGUI() { //拖动条得出 -5.0 - 5.0之间的一个数值 hSliderValue0 = GUI.HorizontalSlider(new Rect(25, 25, 100, 30), hSliderValue0, -5.0F, 5.0F); hSliderValue1 = GUI.HorizontalSlider(new Rect(25, 70, 100, 30), hSliderValue1, -5.0F, 5.0F); } void Update() { //在这里来计算贝塞尔曲线 //四个参数 表示当前贝塞尔曲线上的4个点 第一个点和第四个点 //我们是不需要移动的,中间的两个点是由拖动条来控制的。 myBezier = new Bezier( new Vector3( -5f, 0f, 0f ), new Vector3( hSliderValue1, hSliderValue0 , 0f ), new Vector3( hSliderValue1, hSliderValue0, 0f ), new Vector3( 5f, 0f, 0f ) ); //循环100遍来绘制贝塞尔曲线每个线段 for(int i =1; i <= 100; i++) { //参数的取值范围 0 - 1 返回曲线没一点的位置 //为了精确这里使用i * 0.01 得到当前点的坐标 Vector3 vec = myBezier.GetPointAtTime( (float)(i *0.01) ); //把每条线段绘制出来 完成白塞尔曲线的绘制 YellowlineRenderer.SetPosition(i -1,vec); } } } |
OK 这里贝塞尔曲线的原理就已经完毕。下面我们学习在NGUI中如何使用贝塞尔曲线。刚刚我们说过贝塞尔曲线是由2个固定点 加两个动态点来完成的,其实我们在开发中往往只需要3个点。1 起点 2 中间点 3 结束点 拖动这三个点都可以重新计算曲线的轨迹这样才比较完美。如下图所示,这三个点都是可以任意拖动的,拖动结束后,黑色的线为用户拖拽点连接的直角线段,我们根据这三个点组成的直角线段计算它们之间的贝塞尔曲线,也就是图中黄色的线段。
简单的进行拖拽一下,是不是感觉贝塞尔曲线很酷炫呢?哇咔咔。
我们来看看代码实现的部分,其实原理和上面完全一样。
BallMove.cs绑定在这三个可以拖拽的点上,让拖动小球后小球可跟随手指移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using UnityEngine; using System.Collections; public class BallMove : MonoBehaviour { void OnDrag (Vector2 delta) { float movex = transform.localPosition.x + (delta.x / 3); float movey = transform.localPosition.y + (delta.y / 3); //避免越界操作,这里可以进行一些判断 transform.localPosition = new Vector3(movex,movey ,transform.localPosition.z); } } |
如此一来触摸小球后,小球将跟随用户手指移动。下面我们将监听用户触摸小球后的坐标来计算它们三点之间的贝塞尔曲线。
BallInit.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 |
using UnityEngine; using System.Collections; public class BallInit : MonoBehaviour { //黑色直角线段 LineRenderer lineRenderer0; LineRenderer lineRenderer1; //贝塞尔曲线 LineRenderer BezierRenderer; //三个小球触摸对象 public GameObject mark0; public GameObject mark1; public GameObject mark2; //算法公式类 private Bezier myBezier; void Start () { //分别得到黑色直角线段 与黄色贝塞尔曲线的 线段组件 lineRenderer0 = GameObject.Find("line0").GetComponent<LineRenderer>(); lineRenderer1 = GameObject.Find("line1").GetComponent<LineRenderer>(); BezierRenderer = GameObject.Find("Bezier").GetComponent<LineRenderer>(); //黑色直角是有两个线段组成 lineRenderer0.SetVertexCount(2); lineRenderer1.SetVertexCount(2); //为了让贝塞尔曲线细致一些 设置它有100个点组成 BezierRenderer.SetVertexCount(100); } void Update () { //mark0 表示中间的小球 //mark1 表示右边的小球 //mark2 表示左边的小球 //中间的标志点分别减去左右两边的标志点,计算出曲线的X Y 的点 float y = (mark0.transform.position.y - mark2.transform.position.y) ; float x = (mark0.transform.position.x - mark2.transform.position.x) ; //因为我们是通过3个点来确定贝塞尔曲线, 所以参数3 设置为0 即可。 //这样参数1 表示起点 参数2表示中间点 参数3 忽略 参数4 表示结束点 myBezier = new Bezier( mark2.transform.position, new Vector3(x,y,0f), new Vector3(0f,0f,0f), mark1.transform.position ); //绘制贝塞尔曲线 for(int i =1; i <= 100; i++) { Vector3 vec = myBezier.GetPointAtTime( (float)(i * 0.01) ); BezierRenderer.SetPosition(i -1,vec); } //绘制直角黑色标志线段 lineRenderer0.SetPosition(0,mark0.transform.position); lineRenderer0.SetPosition(1,mark2.transform.position); lineRenderer1.SetPosition(0,mark0.transform.position); lineRenderer1.SetPosition(1,mark1.transform.position); } } |
NGUI部分的源码就不放出来,MOMO将第一个例子的源码放出来,最近生活与工作都有点郁闷,哎~~~ 日子是熬出来,程序也是写出来的,走一步看一步,加油!雨松MOMO祝大家学习愉快,哇咔咔。
下载地址:http://vdisk.weibo.com/s/aceMi
参考文章:http://forum.unity3d.com/threads/5082-Bezier-Curve
- 本文固定链接: https://www.xuanyusong.com/archives/1548
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
大神的公式有错(把4个点放在同一直线可以验证得到的线是错的) 不过给了我思路 可以用的待定系数法把1-3阶的贝塞尔曲线都推导出来~
请问如果我直接用dotween之类的插件和这个比,效率哪个高呢?
代码被吞掉了
这公式和不化简贝塞尔公式算出来的值不一样
cx = 3 * ( x1 – x0 )bx = 3 * ( x2 – x1 ) – cxax = x3 – x0 – cx – bxcy = 3 * ( y1 – y0 )by = 3 * ( y2 – y1 ) – cyay = y3 – y0 – cy – by四点式
http://zh.wikipedia.org/wiki/貝茲曲線 维基百科上讲的还是挺清楚的,上文推导出来的常量Ax明显是不正确的,画出来的曲线形状也不对,可以自己推导看看,虽说有点小错误,但是文章还是很棒的,学习了
用到了..感谢分享
有个错误 ! MyBezier.cs脚本 不是绑在摄像机上 而是绑在 有LineRenderer控件的物体上
update里不断new是几个意思? 为什么不在Awake()里面new?
生活的意义在于创造,MOMO做的非常好了,每天来这里逛逛,感觉不错,哈哈哈。
谢谢哈。。
日子是熬出来,程序也是写出来的。同感,原来大牛也有工作和生活上的烦恼哈
请问 this.Cx = 3f * ( ( this.p0.x this.p1.x ) – this.p0.x ); 为什么不直接等于3f * this.p1.x?你这个曲线的数学公式原型是什么?望赐教啊~
请问 this.Cx = 3f * ( ( this.p0.x + this.p1.x ) – this.p0.x ); 为什么不直接等于3f * this.p1.x?你这个曲线的数学公式原型是什么?望赐教啊~
雨松大神,求解答啊,困惑好久了
http://zh.wikipedia.org/wiki/%E8%B2%9D%E8%8C%B2%E6%9B%B2%E7%B7%9A 维基百科上讲的还是挺清楚的,上文推导出来的常量Ax明显是不正确的,画出来的曲线形状也不对,可以自己推导看看,虽说有点小错误,但是文章还是很棒的,学习了
雨大问你个事 曲线的数组长度和 后面Vector3 vec = myBezier.GetPointAtTime( (float)(i * 0.01) );这句里面的.01有什么关系,因为曲线的代码没注释 也不懂,所以没有研究Bezier 类,如果要把数组长度定为50后面.01要改成多少,还请不吝赐教
private void SetConstant() 中 this.Cx = 3f * ( ( this.p0.x this.p1.x ) – this.p0.x );==》this.Cx = 3f * this.p1.x ; this.Cy = 3f * ( ( this.p0.y this.p1.y ) – this.p0.y );==》this.Cy = 3f * this.p1.y ; this.Cz = 3f * ( ( this.p0.z this.p1.z ) – this.p0.z );==》this.Cz = 3f * this.p1.z ; 是不?假设画一个圆,那么4个点应该是怎样的?应该是两组吧?假设r为半径,圆心(0,0)。谢谢
private void SetConstant() 中 this.Cx = 3f * ( ( this.p0.x + this.p1.x ) – this.p0.x );==》this.Cx = 3f * this.p1.x ; this.Cy = 3f * ( ( this.p0.y + this.p1.y ) – this.p0.y );==》this.Cy = 3f * this.p1.y ; this.Cz = 3f * ( ( this.p0.z + this.p1.z ) – this.p0.z );==》this.Cz = 3f * this.p1.z ; 是不?假设画一个圆,那么4个点应该是怎样的?应该是两组吧?假设r为半径,圆心(0,0)。谢谢
上面的系数怎么算出来的
你好请问[System.Serializable], [System.NonSerialized]到底是什么意思?我一直都不太明白,谢谢
非常感谢,但是怎么得到贝塞尔曲线方向向量,比如我有一个弓箭使用被塞尔曲线来做曲线运动,但是箭头肯定要指向运动的切线方向,请问这个怎么得到?
雨松老师 请问一下可以多个贝塞尔曲线来组成一条不规则的路径吗 具体思路是什么
感谢经验分享,祝好人一生平安!
注意 尺寸。。。
非常有帮助,感谢
客气了 嚯嚯
最近一段时间没有看U3D哈
直接让美术改一下不就得了
为什么我直接copy过来就总是显示错误Assets/JS/BallMove.cs(11,23): warning CS0219: The variable `movey’ is assigned but its value is never used等等
是不是脚本没有绑上呢?
momo大神,可以给个用UIFilledSprite做技能冷却的教程么?哇咔咔
NGUI中能有方法把彩色的图片转换为黑白图像的方法吗?
谢谢哈
虾米!!!
楼主,ngui在IPAD3导出显示问题,怎么解决?
好久没来啦,支持下MOMO,嘿嘿,3D有贝塞尔,过些天我也出个安卓版的贝塞尔文章,哈哈…..利用你说的原理,盗用学习!!!
嗯哈 小马 蛤蛤
牛人啊
过奖了
哈哈,有NGUI的教程吗。最近买了一本你的书,感觉真不错啊
系谢啦。
出一本NGUI的书撒,自学真心无力啊。
额。。。。最近心情比较乱, 也不知道怎么办。。
心情不好就叫同学去打球,游泳,出去玩。嘿嘿- -.有个问题问下你。progress Bar 在game里面拖动,有好多阴影,是什么回事啊。
牛~ 顶一个
感谢小妖