最终效果
本教程要实现的最终效果如下:
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/142512a364s4uh7w6p7t78.gif)
设置水管理器
第一步就是使用Unity的线段渲染器(Line Renderer)和一些节点来实现水浪的形状。如下图:
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/142513ruanubehbu10kbji.png.thumb.jpg)
然后还要跟踪所有节点的位置、速度及加速度。这些信息使用数组来存储,在类的最上面添加以下代码:
[C#] 纯文本查看 复制代码
float []
xpositions; float []
ypositions; float []
velocities; float []
accelerations; LineRenderer
Body; |
[C#] 纯文本查看 复制代码
GameObject[]
meshobjects; Mesh[]
meshes; |
[C#] 纯文本查看 复制代码
GameObject[]
colliders; |
[C#] 纯文本查看 复制代码
const float
springconstant = 0.02f; const float
damping = 0.04f; const float
spread = 0.05f; const float
z = -1f; |
还要设置一些值:
[C#] 纯文本查看 复制代码
float baseheight; float left; float bottom; |
还要定义一些可以在编辑器中修改的公共变量,首先是制作水波四溅效果所需的粒子系统:
[C#] 纯文本查看 复制代码
public GameObject
splash: |
[C#] 纯文本查看 复制代码
public Material
mat: |
[C#] 纯文本查看 复制代码
public GameObject
watermesh: |
该函数的参数分别为水体四周的边长:
[C#] 纯文本查看 复制代码
public void
SpawnWater( float Left,
float Width,
float Top,
float Bottom) {} |
下面决定总共需要的节点数量:
[C#] 纯文本查看 复制代码
int edgecount
= Mathf.RoundToInt(Width) * 5; int nodecount
= edgecount + 1; |
下面使用LineRenderer组件来渲染水体:
[C#] 纯文本查看 复制代码
Body
= gameObject.AddComponent<LineRenderer>(); Body.material
= mat; Body.material.renderQueue
= 1000; Body.SetVertexCount(nodecount); Body.SetWidth(0.1f,
0.1f); |
你也可以自己设置线段宽度,SetWidth()函数有两个参数,分别是线段的起始宽度和结束宽度,设为一样就表示线段宽度固定。
节点创建好后初始化上面声明的变量:
[C#] 纯文本查看 复制代码
positions
= new float [nodecount]; ypositions
= new float [nodecount]; velocities
= new float [nodecount]; accelerations
= new float [nodecount]; meshobjects
= new GameObject[edgecount]; meshes
= new Mesh[edgecount]; colliders
= new GameObject[edgecount]; baseheight
= Top; bottom
= Bottom; left
= Left; |
[C#] 纯文本查看 复制代码
for ( int i
= 0; i < nodecount; i++) { ypositions[i]
= Top; xpositions[i]
= Left + Width * i / edgecount; accelerations[i]
= 0; velocities[i]
= 0; Body.SetPosition(i, new Vector3(xpositions[i],
ypositions[i], z)); } |
循环结束后就通过LineRenderer将各节点设置到正确的位置。
创建网格
现在有了水波线段,下面就使用网格来实现水体。先添加以下代码:
[C#] 纯文本查看 复制代码
for ( int i
= 0; i < edgecount; i++) { meshes[i]
= new Mesh(); } |
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/142513i8ig8r4q0pjlwj08.png.thumb.jpg)
上图展示了网格片段的理想显示效果。第一个片段的顶点高亮显示,共有4个。
[C#] 纯文本查看 复制代码
Vector3[]
Vertices = new Vector3[4]; Vertices[0]
= new Vector3(xpositions[i],
ypositions[i], z); Vertices[1]
= new Vector3(xpositions[i
+ 1], ypositions[i + 1], z); Vertices[2]
= new Vector3(xpositions[i],
bottom, z); Vertices[3]
= new Vector3(xpositions[i+1],
bottom, z); |
网格所需的第二个数据就是UV坐标。UV坐标决定了网格用到的纹理部分。这里简单的使用纹理左上角、右上角、左下角及右下角的部分作为网格显示内容。
[C#] 纯文本查看 复制代码
Vector2[]
UVs = new Vector2[4]; UVs[0]
= new Vector2(0,
1); UVs[1]
= new Vector2(1,
1); UVs[2]
= new Vector2(0,
0); UVs[3]
= new Vector2(1,
0); |
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/142513re0407tev82ztvtv.png.thumb.jpg)
按节点顺序观察各角,三角形A由节点0、1、3组成,三角形B由节点3、2、0组成。所以定义一个顶点索引数组顺序包含这些索引:
[C#] 纯文本查看 复制代码
int []
tris = new int [6]
{ 0, 1, 3, 3, 2, 0 }; |
[C#] 纯文本查看 复制代码
meshes[i].vertices
= Vertices; meshes[i].uv
= UVs; meshes[i].triangles
= tris; |
[C#] 纯文本查看 复制代码
meshobjects[i]
= Instantiate(watermesh,Vector3.zero,Quaternion.identity) as GameObject; meshobjects[i].GetComponent<MeshFilter>().mesh
= meshes[i]; meshobjects[i].transform.parent
= transform; |
创建碰撞器
下面添加碰撞器:
[C#] 纯文本查看 复制代码
colliders[i]
= new GameObject(); colliders[i].name
= "Trigger" ; colliders[i].AddComponent<BoxCollider2D>(); colliders[i].transform.parent
= transform; colliders[i].transform.position
= new Vector3(Left
+ Width * (i + 0.5f) / edgecount, Top - 0.5f, 0); colliders[i].transform.localScale
= new Vector3(Width
/ edgecount, 1, 1); colliders[i].GetComponent<BoxCollider2D>().isTrigger
= true ; colliders[i].AddComponent<WaterDetector>(); |
下面添加函数来控制水体网格的移动:
[C#] 纯文本查看 复制代码
void UpdateMeshes() { for ( int i
= 0; i < meshes.Length; i++) { Vector3[]
Vertices = new Vector3[4]; Vertices[0]
= new Vector3(xpositions[i],
ypositions[i], z); Vertices[1]
= new Vector3(xpositions[i+1],
ypositions[i+1], z); Vertices[2]
= new Vector3(xpositions[i],
bottom, z); Vertices[3]
= new Vector3(xpositions[i+1],
bottom, z); meshes[i].vertices
= Vertices; } } |
下一步是在FixedUpdate()函数中添加物理特性让水体可以自行流动。
[C#] 纯文本查看 复制代码
void FixedUpdate() {} |
添加物理特性
首先是结合胡克定律和欧拉方法获取水体新的坐标、加速度及速度。
胡克定律即 F = kx,F是指由水浪产生的力(这里的水体模型就是由一排水浪组成),k指水体强度系数,x是偏移距离。这里的偏移距离就是各节点的y坐标减去节点的基本高度。
接下来添加一个与速度成比例的阻尼因子形成水面的阻力。
[C#] 纯文本查看 复制代码
for ( int i
= 0; i < xpositions.Length ; i++) { float force
= springconstant * (ypositions[i] - baseheight) + velocities[i]*damping ; accelerations[i]
= -force; ypositions[i]
+= velocities[i]; velocities[i]
+= accelerations[i]; Body.SetPosition(i, new Vector3(xpositions[i],
ypositions[i], z)); } |
注意这里每个节点的作用力原子数量为1,你也可以改为其它值,这样加速度就是:
[C#] 纯文本查看 复制代码
accelerations[i]
= -force/mass; |
[C#] 纯文本查看 复制代码
float []
leftDeltas = new float [xpositions.Length]; float []
rightDeltas = new float [xpositions.Length]; |
然后还要比较后一个节点与当前节点的高度差并将差值存入rightDeltas。还需将所有的差值乘以传播速度常量。
[C#] 纯文本查看 复制代码
for ( int j
= 0; j < 8; j++) { for ( int i
= 0; i < xpositions.Length; i++) { if (i
> 0) { leftDeltas[i]
= spread * (ypositions[i] - ypositions[i-1]); velocities[i
- 1] += leftDeltas[i]; } if (i
< xpositions.Length - 1) { rightDeltas[i]
= spread * (ypositions[i] - ypositions[i + 1]); velocities[i
+ 1] += rightDeltas[i]; } } } |
[C#] 纯文本查看 复制代码
for ( int i
= 0; i < xpositions.Length; i++) { if (i
> 0) { ypositions[i-1]
+= leftDeltas[i]; } if (i
< xpositions.Length - 1) { ypositions[i
+ 1] += rightDeltas[i]; } } |
这里将所有代码放在一个循环,共运行八次。这样做的目的是希望多次运行但计算量小,而非计算量过大从而导致效果不够流畅。
添加水波飞溅的效果
现在已经实现了水的流动,下面来实现水波飞溅的效果。添加函数Splash()用于检测水波的x坐标及入水物体接触水面时的速度。将该函数设为公有的以供后续的碰撞器调用。
[C#] 纯文本查看 复制代码
public void
Splash( float xpos,
float velocity) {} |
[C#] 纯文本查看 复制代码
if (xpos
>= xpositions[0] && xpos <= xpositions[xpositions.Length-1]) {} |
[C#] 纯文本查看 复制代码
expos
-= xpositions[0]; |
[C#] 纯文本查看 复制代码
int index
= Mathf.RoundToInt((xpositions.Length-1)*(xpos / (xpositions[xpositions.Length-1] - xpositions[0]))); |
首先获取飞溅位置与水体左边界的坐标差(xpos)。
然后将该差值除以水体宽度。
这样就得到了飞溅发生位置的分数,例如飞溅发生在水体宽度的3/4处就会返回0.75。
将该分数乘以边数后取整,就得到了离飞溅位置最近的节点索引。
[C#] 纯文本查看 复制代码
velocities[index]
= velocity; |
注意:你可以按自己的需求来更改上面的代码。例如,你可以将节点速度与物体速度相加,或者使用动量除以节点的作用原子数量而非直接使用速度。
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/151039ditsnsv5rz6bttg5.png.thumb.jpg)
下面实现产生水花的粒子系统。将该对象命名为“splash”,别跟Splash()搞混了,后者是一个函数。
首先,我们需要设置飞溅的参数,这个参数是受撞击物体的速度影响的。
[C#] 纯文本查看 复制代码
float lifetime
= 0.93f + Mathf.Abs(velocity)*0.07f; splash.GetComponent<ParticleSystem>().startSpeed
= 8+2*Mathf.Pow(Mathf.Abs(velocity),0.5f); splash.GetComponent<ParticleSystem>().startSpeed
= 9 + 2 * Mathf.Pow(Mathf.Abs(velocity), 0.5f); splash.GetComponent<ParticleSystem>().startLifetime
= lifetime; |
上面设置两次startSpeed的原因是,这里使用Shuriken来实现的粒子系统,它设定粒子的起始速度是两个随机常量之间,但我们通过脚本无法操作Shuriken中的更多内容,所以这里设置两次startSpeed。
下面增加的几行代码可能不是必须的:
[C#] 纯文本查看 复制代码
Vector3
position = new Vector3(xpositions[index],ypositions[index]-0.35f,5); Quaternion
rotation = Quaternion.LookRotation( new Vector3(xpositions[Mathf.FloorToInt(xpositions.Length
/ 2)], baseheight + 8, 5) - position); |
1.将它们固定在背景上,例如将其坐标的z值设为5。
2.让粒子系统总是朝向水体中心,这样就不会飞溅到边缘以外。
第二行代码获取坐标中点,稍微上移,并让粒子发射器指向该点。如果你的水体够宽,就不需要进行该设置。如果你的水体是室内游泳池就需要用到该脚本。
[C#] 纯文本查看 复制代码
GameObject
splish = Instantiate(splash,position,rotation) as GameObject; Destroy(splish,
lifetime+0.3f); |
碰撞检测
最后还需对物体进行碰撞检测,之前为所有的碰撞器都添加了WaterDetector脚本,在该脚本中添加下面的函数:
[C#] 纯文本查看 复制代码
void OnTriggerEnter2D(Collider2D
Hit) {} |
[C#] 纯文本查看 复制代码
if (Hit.rigidbody2D
!= null ) { transform.parent.GetComponent<Water>().Splash(transform.position.x,
Hit.rigidbody2D.velocity.y*Hit.rigidbody2D.mass / 40f); } } |
在Start()函数中调用SpawnWater():
[C#] 纯文本查看 复制代码
void Start() { SpawnWater(-10,20,0,-10); } |
![](http://forum.china.unity3d.com/data/attachment/forum/201603/23/142514klup7fb77bb3tf2d.png.thumb.jpg)
加分练习
在SpawnWater()函数中添加以下代码:
[C#] 纯文本查看 复制代码
gameObject.AddComponent<BoxCollider2D>(); gameObject.GetComponent<BoxCollider2D>().center
= new Vector2(Left
+ Width / 2, (Top + Bottom) / 2); gameObject.GetComponent<BoxCollider2D>().size
= new Vector2(Width,
Top - Bottom); gameObject.GetComponent<BoxCollider2D>().isTrigger
= true ; |
添加OnTriggerStay2D()函数同样带有一个Collider2D类型的参数,用与之前一样的方式检测物体的作用力原子数量,然后为rigidbody2D添加力或速度让物体漂流在水中。
总结
本教程主要教大家使用Unity 2D模拟简单的2D水效果,用到了一点简单的物理知识以及Line Renderer、Mesh Renderer、触发器和粒子。教程不难,但理论知识都是适用的,希望大家发挥自己的想象力将其用到实际项目中。
原文链接:http://gamedevelopment.tutsplus. ... edtutorials_sidebar
原文作者:Alex Rose
本文版权归Unity官方中文论坛所有,转载请注明来源(forum.china.unity3d.com)。
http://download.csdn.net/download/onafioo/9966532