使用Unity实现动态2D水效果
http://forum.china.unity3d.com/thread-16044-1-1.html
在这片教程里面我们将会用简单的物理效果来模拟动态的2D水效果。我们将会使用Line Renderer,Mesh Renderer,触发器(Trigger)和粒子来创造这个水效果。最终的的效果将会包含波浪和水花溅起的特效,你可以直接加入自己的游戏中。你可以在文章的结尾下载此工程。当然,本文中使用的制作原理可以应用于任何游戏引擎之中。
最终效果
本教程要实现的最终效果如下:

设置水管理器
第一步就是使用Unity的线段渲染器(Line Renderer)和一些节点来实现水浪的形状。如下图:

然后还要跟踪所有节点的位置、速度及加速度。这些信息使用数组来存储,在类的最上面添加以下代码:
float [] float [] float [] float [] LineRenderer |
LineRenderer用来保存所有节点及水体的轮廓。接下来使用网格来实现水体,还需创建游戏对象来使用这些网格。添加以下代码:
GameObject[] Mesh[] |
为了让物体可以与水交互,还需为每个游戏对象添加碰撞器:
GameObject[] |
还要定义一些常量:
const float
const float
const float
const float
|
前三个常量用来控制水流速度、衰减度及传播速度,最后的z值用于控制水体的显示层次,这里设为-1表示会显示在对象前面。大家也可根据自己的需求进行调整。
还要设置一些值:
float baseheight; float left; float bottom; |
这三个变量定义了水的维度。
还要定义一些可以在编辑器中修改的公共变量,首先是制作水波四溅效果所需的粒子系统:
public GameObject |
接下来是用于Line Renderer的材质:
public Material |
还有用于模拟水体的网格:
public GameObject |
这些资源均可在工程中获取。另外还需要一个管理器,保存所有数据并在游戏过程中生成水体。下面创建SpwanWater()函数来实现该功能。
该函数的参数分别为水体四周的边长:
public void
float Left, float Width, float Top, float Bottom) {} |
创建节点
下面决定总共需要的节点数量:
int edgecount int nodecount |
这里对每单位宽度的水体使用5个节点,让整个水体运动看起来更平滑。你也可以自己权衡性能与平滑效果来选择合适的节点数量。这样就能得到所有的边数了,顶点数在此基础上加1。
下面使用LineRenderer组件来渲染水体:
Body Body.material Body.material.renderQueue Body.SetVertexCount(nodecount); Body.SetWidth(0.1f, |
同时这里还通过渲染队列将材质的渲染顺序设为比水体更高。设置了节点总数,并将线段宽度设为0.1。
你也可以自己设置线段宽度,SetWidth()函数有两个参数,分别是线段的起始宽度和结束宽度,设为一样就表示线段宽度固定。
节点创建好后初始化上面声明的变量:
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 bottom left |
现在所有的数组都初始化好,也拿到了所需的数据。下面就为各数组赋值,从节点开始:
for ( int i { ypositions[i] xpositions[i] accelerations[i] velocities[i] Body.SetPosition(i, new Vector3(xpositions[i], } |
将所有的y坐标设为水体上方,让水体各部分紧密排列。速度和加速度都为0表示水体是静止的。
循环结束后就通过LineRenderer将各节点设置到正确的位置。
创建网格
现在有了水波线段,下面就使用网格来实现水体。先添加以下代码:
for ( int i { meshes[i] new Mesh(); } |
网格中也保存了一堆变量,第一个就是所有的顶点。

上图展示了网格片段的理想显示效果。第一个片段的顶点高亮显示,共有4个。
Vector3[] new Vector3[4]; Vertices[0] new Vector3(xpositions[i], Vertices[1] new Vector3(xpositions[i Vertices[2] new Vector3(xpositions[i], Vertices[3] new Vector3(xpositions[i+1], |
数组的四个元素按顺序分别表示左上角、右上角、左下角和右下角的顶点位置。
网格所需的第二个数据就是UV坐标。UV坐标决定了网格用到的纹理部分。这里简单的使用纹理左上角、右上角、左下角及右下角的部分作为网格显示内容。
Vector2[] new Vector2[4]; UVs[0] new Vector2(0, UVs[1] new Vector2(1, UVs[2] new Vector2(0, UVs[3] new Vector2(1, |
现在需要用到之前定义的数据。网格是由三角形组成的,而一个四边形可由两个三角形组成,所以这里要告诉网格如何绘制三角形。

按节点顺序观察各角,三角形A由节点0、1、3组成,三角形B由节点3、2、0组成。所以定义一个顶点索引数组顺序包含这些索引:
int [] new int [6] |
四边形定义好了,下面来设置网格数据。
meshes[i].vertices meshes[i].uv meshes[i].triangles |
网格设置好了,还需添加游戏对象将其渲染到场景中。利用工程中的watermesh预制创建游戏对象,其中包含Mesh Renderer和Mesh Filter 组件。
meshobjects[i] as GameObject; meshobjects[i].GetComponent<MeshFilter>().mesh meshobjects[i].transform.parent |
将网格对象设为水管理器的子对象以便于管理。
创建碰撞器
下面添加碰撞器:
colliders[i] new GameObject(); colliders[i].name "Trigger" ; colliders[i].AddComponent<BoxCollider2D>(); colliders[i].transform.parent colliders[i].transform.position new Vector3(Left colliders[i].transform.localScale new Vector3(Width colliders[i].GetComponent<BoxCollider2D>().isTrigger true ; colliders[i].AddComponent<WaterDetector>(); |
添加盒状碰撞器并统一命名以便于管理,同样将其设为管理器子对象。将碰撞器坐标设为节点中间,设置好大小并添加WaterDetector类。
下面添加函数来控制水体网格的移动:
void UpdateMeshes() { for ( int i { Vector3[] new Vector3[4]; Vertices[0] new Vector3(xpositions[i], Vertices[1] new Vector3(xpositions[i+1], Vertices[2] new Vector3(xpositions[i], Vertices[3] new Vector3(xpositions[i+1], meshes[i].vertices } } |
该函数与上面的几乎一样,只是不需再设置三角形和UV。
下一步是在FixedUpdate()函数中添加物理特性让水体可以自行流动。
void FixedUpdate() {} |
添加物理特性
首先是结合胡克定律和欧拉方法获取水体新的坐标、加速度及速度。
胡克定律即 F = kx,F是指由水浪产生的力(这里的水体模型就是由一排水浪组成),k指水体强度系数,x是偏移距离。这里的偏移距离就是各节点的y坐标减去节点的基本高度。
接下来添加一个与速度成比例的阻尼因子形成水面的阻力。
for ( int i { float force accelerations[i] ypositions[i] velocities[i] Body.SetPosition(i, new Vector3(xpositions[i], } |
欧拉方法很简单,就是在每帧用加速度更新速度然后用速度更新位置。
注意这里每个节点的作用力原子数量为1,你也可以改为其它值,这样加速度就是:
accelerations[i] |
下面实现水浪的传播效果。
float [] new float [xpositions.Length]; float [] new float [xpositions.Length]; |
这里创建了两个数组,对于每个节点,都要对比前一个节点与当前节点的高度差并将差值存入leftDeltas。
然后还要比较后一个节点与当前节点的高度差并将差值存入rightDeltas。还需将所有的差值乘以传播速度常量。
for ( int j { for ( int i { if (i { leftDeltas[i] velocities[i } if (i { rightDeltas[i] velocities[i } } } |
可以根据高度差立即改变速度,但此时只需保存坐标差即可。如果立即改变第一个节点的坐标,同时再去计算第二个节点时第一个坐标已经移动了,这样会影响到后面所有节点的计算。
for ( int i { if (i { ypositions[i-1] } if (i { ypositions[i } } |
到此就获得了所有的高度数据,可以应用到最终效果了。由于最左与最右的节点不会动,所以需要改变坐标是第一个至倒数第二个节点。
这里将所有代码放在一个循环,共运行八次。这样做的目的是希望多次运行但计算量小,而非计算量过大从而导致效果不够流畅。
添加水波飞溅的效果
现在已经实现了水的流动,下面来实现水波飞溅的效果。添加函数Splash()用于检测水波的x坐标及入水物体接触水面时的速度。将该函数设为公有的以供后续的碰撞器调用。
public void
float xpos, float velocity) {} |
首先需要确定水波飞溅的位置是在水体范围内:
if (xpos {} |
然后改变水波的x坐标以获取飞溅位置与水体起始位置间的相对坐标:
expos |
然后找到落水物体碰撞的节点。计算方法如下:
int index |
步骤如下:
首先获取飞溅位置与水体左边界的坐标差(xpos)。
然后将该差值除以水体宽度。
这样就得到了飞溅发生位置的分数,例如飞溅发生在水体宽度的3/4处就会返回0.75。
将该分数乘以边数后取整,就得到了离飞溅位置最近的节点索引。
velocities[index] |
下面将入水物体的速度赋给该物体所碰撞的节点,这样节点会被物体压入水体。
注意:你可以按自己的需求来更改上面的代码。例如,你可以将节点速度与物体速度相加,或者使用动量除以节点的作用原子数量而非直接使用速度。

下面实现产生水花的粒子系统。将该对象命名为“splash”,别跟Splash()搞混了,后者是一个函数。
首先,我们需要设置飞溅的参数,这个参数是受撞击物体的速度影响的。
float lifetime splash.GetComponent<ParticleSystem>().startSpeed splash.GetComponent<ParticleSystem>().startSpeed splash.GetComponent<ParticleSystem>().startLifetime |
这里已经设置了粒子系统,并设定好生命周期,以免在物体撞击水面后粒子消失过早,并将粒子速度设置为撞击速度的立方(加上一个常数,这样较小力度的飞溅也会有效果)。
上面设置两次startSpeed的原因是,这里使用Shuriken来实现的粒子系统,它设定粒子的起始速度是两个随机常量之间,但我们通过脚本无法操作Shuriken中的更多内容,所以这里设置两次startSpeed。
下面增加的几行代码可能不是必须的:
Vector3 new Vector3(xpositions[index],ypositions[index]-0.35f,5); Quaternion new Vector3(xpositions[Mathf.FloorToInt(xpositions.Length |
Shuriken粒子在与物体碰撞后不会立即被摧毁,所以要确保粒子不会显示在物体前方,有两种办法:
1.将它们固定在背景上,例如将其坐标的z值设为5。
2.让粒子系统总是朝向水体中心,这样就不会飞溅到边缘以外。
第二行代码获取坐标中点,稍微上移,并让粒子发射器指向该点。如果你的水体够宽,就不需要进行该设置。如果你的水体是室内游泳池就需要用到该脚本。
GameObject as GameObject; Destroy(splish, |
现在添加了飞溅对象,该对象会在粒子被摧毁后一段时间再消失,因为粒子系统发射了大量爆裂的粒子,所以粒子消失所需时间至少是Time.time + lifetime,最后的爆裂的粒子甚至需要更久。
碰撞检测
最后还需对物体进行碰撞检测,之前为所有的碰撞器都添加了WaterDetector脚本,在该脚本中添加下面的函数:
void OnTriggerEnter2D(Collider2D {} |
在OnTriggerEnter2D()中实现2D Rigid Body与水体碰撞产生的效果。传入Collider2D类型的参数可获取更多关于碰撞物体的信息。需要该物体带有Rigidbody2D组件:
if (Hit.rigidbody2D null ) { transform.parent.GetComponent<Water>().Splash(transform.position.x, } } |
所有碰撞器都是water manager的子对象。所以直接从碰撞器父节点获取Water组件并调用Splash()函数。如果希望物理效果更精确,可以使用动量而非速度。注意在这里也该为对应的属性即可。如果要获取物体动量,就将其速度乘以mass。如果只用速度,就将代码中的mass删掉。
在Start()函数中调用SpawnWater():
void Start() { SpawnWater(-10,20,0,-10); } |
到此就完成了,所有带有rigidbody2D和碰撞器的物体都可以撞击水面并产生水波飞溅的效果,并且水波也会正常流动。

加分练习
在SpawnWater()函数中添加以下代码:
gameObject.AddComponent<BoxCollider2D>(); gameObject.GetComponent<BoxCollider2D>().center new Vector2(Left gameObject.GetComponent<BoxCollider2D>().size new Vector2(Width, 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
使用Unity实现动态2D水效果的更多相关文章
- 使用Unity创造动态的2D水体效果
者:Alex Rose 在本篇教程中,我们将使用简单的物理机制模拟一个动态的2D水体.我们将使用一个线性渲染器.网格渲染器,触发器以及粒子的混合体来创造这一水体效果,最终得到可运用于你下款游戏的水纹和 ...
- Unity 4.3 2D 教程:新手上路
这篇文章译自 Christopher LaPollo 先生的 Unity 4.3 2D 教程的第一部分 Unity 4.3 2D Tutorial: Getting Started 感谢这套优秀教程的 ...
- 介绍用C#和VS2015开发基于Unity架构的2D、3D游戏的技术
[Unity]13.3 Realtime GI示例 摘要: 分类:Unity.C#.VS2015 创建日期:2016-04-19 一.简介 使用简单示例而不是使用实际示例的好处是能让你快速理解光照贴图 ...
- canvas动态小球重叠效果
前面的话 在javascript运动系列中,详细介绍了各种运动,其中就包括碰壁运动.但是,如果用canvas去实现,却是另一种思路.本文将详细介绍canvas动态小球重叠效果 效果展示 静态小球 首先 ...
- Canvas 动态小球重叠效果
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- Android 动态Tab分页效果实现
当前项目使用的是TabHost+Activity进行分页,目前要做个报表功能,需要在一个Tab页内进行Activity的切换.比方说我有4个Tab页分别为Tab1,Tab2,Tab3,Tab4,现在的 ...
- 【转】提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果
原文网址:http://www.zhimengzhe.com/IOSkaifa/37910.html MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显 ...
- jQuery动态星级评分效果实现方法
本文实例讲述了jQuery动态星级评分效果实现方法.分享给大家供大家参考.具体如下: 这里的jQuery星级评分代码,是大家都很喜欢的功能,目前广泛应用,本星级评分加入了动画效果,注意,如果要真正实现 ...
- axure rp教程(四)动态面板滑动效果
转载自: http://www.iaxure.com/74.html 实现目标: 1. 点击登录滑出登录面板 2. 点击确定滑出动态面板 最终效果如下: 这种效果可以通过两种方法实现: 首先准备需 ...
随机推荐
- cocos2d-x中对象的位置,旋转,缩放
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/cuit/article/details/26729633 分为两种: 缓动.IntervalActi ...
- .htaccess技巧: URL重写(Rewrite)与重定向(Redirect) (转)
目录 Table of Contents 一.准备开始:mod_rewrite 二.利用.htaccess实现URL重写(rewrite)与URL重定向(redirect) 将.htm页面映射到.ph ...
- hadoop —— teragen & terasort
这两个类所在目录: hadoop-examples-0.20.2-cdh3u6.jar 中: 代码: TeraGen.java: /** * Licensed to the Apache Softwa ...
- POJ 之 WERTYU
WERTYU Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8371 Accepted: 4007 Descriptio ...
- 51nod 1040
题目 题解:我们要求的是这个式子: $ \sum\limits_{i = 1}^n {\gcd (n,i)} $ (下面式子中的d都是n的因子) 变形下 $ \sum\limits_{d = 1} ...
- 算法(Algorithms)第4版 练习 1.3.21
方法实现: //1.3.21 /** * find if some node in the list has key as its item field * * @param list the lin ...
- plugin scala is incompatible with current installation
源文链接:http://stackoverflow.com/questions/31927516/plugin-scala-is-incompatible-with-this-installation ...
- python的上下文管理器
直接上代码: f = open('123.txt','w') try: f.write('hello world') except Exception: pass finally: f.close() ...
- hdu-5805 NanoApe Loves Sequence(线段树+概率期望)
题目链接: NanoApe Loves Sequence Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/131072 ...
- leetcode 130 Surrounded Regions(BFS)
Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured ...