http://forum.china.unity3d.com/thread-16044-1-1.html

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

最终效果

本教程要实现的最终效果如下:

设置水管理器

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

然后还要跟踪所有节点的位置、速度及加速度。这些信息使用数组来存储,在类的最上面添加以下代码:

[C#] 纯文本查看 复制代码
?
float[]
xpositions;
float[]
ypositions;
float[]
velocities;
float[]
accelerations;
LineRenderer
Body;

LineRenderer用来保存所有节点及水体的轮廓。接下来使用网格来实现水体,还需创建游戏对象来使用这些网格。添加以下代码:

[C#] 纯文本查看 复制代码
?
GameObject[]
meshobjects;
Mesh[]
meshes;

为了让物体可以与水交互,还需为每个游戏对象添加碰撞器:

[C#] 纯文本查看 复制代码
?
GameObject[]
colliders;

还要定义一些常量:

[C#] 纯文本查看 复制代码
?
constfloat

springconstant = 0.02f;
constfloat

damping = 0.04f;
constfloat

spread = 0.05f;
constfloat

z = -1f;

前三个常量用来控制水流速度、衰减度及传播速度,最后的z值用于控制水体的显示层次,这里设为-1表示会显示在对象前面。大家也可根据自己的需求进行调整。



还要设置一些值:

[C#] 纯文本查看 复制代码
?
floatbaseheight;
floatleft;
floatbottom;

这三个变量定义了水的维度。



还要定义一些可以在编辑器中修改的公共变量,首先是制作水波四溅效果所需的粒子系统:

[C#] 纯文本查看 复制代码
?
publicGameObject
splash:

接下来是用于Line Renderer的材质:

[C#] 纯文本查看 复制代码
?
publicMaterial
mat:

还有用于模拟水体的网格:

[C#] 纯文本查看 复制代码
?
publicGameObject
watermesh:

这些资源均可在工程中获取。另外还需要一个管理器,保存所有数据并在游戏过程中生成水体。下面创建SpwanWater()函数来实现该功能。



该函数的参数分别为水体四周的边长:

[C#] 纯文本查看 复制代码
?
publicvoid

SpawnWater(
floatLeft,
floatWidth,
floatTop,
floatBottom)
{}

创建节点

下面决定总共需要的节点数量:

[C#] 纯文本查看 复制代码
?
intedgecount
= Mathf.RoundToInt(Width) * 5;
intnodecount
= edgecount + 1;

这里对每单位宽度的水体使用5个节点,让整个水体运动看起来更平滑。你也可以自己权衡性能与平滑效果来选择合适的节点数量。这样就能得到所有的边数了,顶点数在此基础上加1。



下面使用LineRenderer组件来渲染水体:

[C#] 纯文本查看 复制代码
?
Body
= gameObject.AddComponent<LineRenderer>();
Body.material
= mat;
Body.material.renderQueue
= 1000;
Body.SetVertexCount(nodecount);
Body.SetWidth(0.1f,
0.1f);

同时这里还通过渲染队列将材质的渲染顺序设为比水体更高。设置了节点总数,并将线段宽度设为0.1。



你也可以自己设置线段宽度,SetWidth()函数有两个参数,分别是线段的起始宽度和结束宽度,设为一样就表示线段宽度固定。



节点创建好后初始化上面声明的变量:

[C#] 纯文本查看 复制代码
?
positions
=
newfloat[nodecount];
ypositions
=
newfloat[nodecount];
velocities
=
newfloat[nodecount];
accelerations
=
newfloat[nodecount];
  
meshobjects
=
newGameObject[edgecount];
meshes
=
newMesh[edgecount];
colliders
=
newGameObject[edgecount];
  
baseheight
= Top;
bottom
= Bottom;
left
= Left;

现在所有的数组都初始化好,也拿到了所需的数据。下面就为各数组赋值,从节点开始:

[C#] 纯文本查看 复制代码
?
for(inti
= 0; i < nodecount; i++)
{
    ypositions[i]
= Top;
    xpositions[i]
= Left + Width * i / edgecount;
    accelerations[i]
= 0;
    velocities[i]
= 0;
    Body.SetPosition(i,newVector3(xpositions[i],
ypositions[i], z));
}

将所有的y坐标设为水体上方,让水体各部分紧密排列。速度和加速度都为0表示水体是静止的。



循环结束后就通过LineRenderer将各节点设置到正确的位置。



创建网格

现在有了水波线段,下面就使用网格来实现水体。先添加以下代码:

[C#] 纯文本查看 复制代码
?
for(inti
= 0; i < edgecount; i++)
{
    meshes[i]
=
newMesh();
}

网格中也保存了一堆变量,第一个就是所有的顶点。

上图展示了网格片段的理想显示效果。第一个片段的顶点高亮显示,共有4个。

[C#] 纯文本查看 复制代码
?
Vector3[]
Vertices =
newVector3[4];
Vertices[0]
=
newVector3(xpositions[i],
ypositions[i], z);
Vertices[1]
=
newVector3(xpositions[i
+ 1], ypositions[i + 1], z);
Vertices[2]
=
newVector3(xpositions[i],
bottom, z);
Vertices[3]
=
newVector3(xpositions[i+1],
bottom, z);

数组的四个元素按顺序分别表示左上角、右上角、左下角和右下角的顶点位置。



网格所需的第二个数据就是UV坐标。UV坐标决定了网格用到的纹理部分。这里简单的使用纹理左上角、右上角、左下角及右下角的部分作为网格显示内容。

[C#] 纯文本查看 复制代码
?
Vector2[]
UVs =
newVector2[4];
UVs[0]
=
newVector2(0,
1);
UVs[1]
=
newVector2(1,
1);
UVs[2]
=
newVector2(0,
0);
UVs[3]
=
newVector2(1,
0);

现在需要用到之前定义的数据。网格是由三角形组成的,而一个四边形可由两个三角形组成,所以这里要告诉网格如何绘制三角形。

按节点顺序观察各角,三角形A由节点0、1、3组成,三角形B由节点3、2、0组成。所以定义一个顶点索引数组顺序包含这些索引:

[C#] 纯文本查看 复制代码
?
int[]
tris =
newint[6]
{ 0, 1, 3, 3, 2, 0 };

四边形定义好了,下面来设置网格数据。

[C#] 纯文本查看 复制代码
?
meshes[i].vertices
= Vertices;
meshes[i].uv
= UVs;
meshes[i].triangles
= tris;

网格设置好了,还需添加游戏对象将其渲染到场景中。利用工程中的watermesh预制创建游戏对象,其中包含Mesh Renderer和Mesh Filter 组件。

[C#] 纯文本查看 复制代码
?
meshobjects[i]
= Instantiate(watermesh,Vector3.zero,Quaternion.identity)
asGameObject;
meshobjects[i].GetComponent<MeshFilter>().mesh
= meshes[i];
meshobjects[i].transform.parent
= transform;

将网格对象设为水管理器的子对象以便于管理。



创建碰撞器

下面添加碰撞器:

[C#] 纯文本查看 复制代码
?
colliders[i]
=
newGameObject();
colliders[i].name
=
"Trigger";
colliders[i].AddComponent<BoxCollider2D>();
colliders[i].transform.parent
= transform;
colliders[i].transform.position
=
newVector3(Left
+ Width * (i + 0.5f) / edgecount, Top - 0.5f, 0);
colliders[i].transform.localScale
=
newVector3(Width
/ edgecount, 1, 1);
colliders[i].GetComponent<BoxCollider2D>().isTrigger
=
true;
colliders[i].AddComponent<WaterDetector>();

添加盒状碰撞器并统一命名以便于管理,同样将其设为管理器子对象。将碰撞器坐标设为节点中间,设置好大小并添加WaterDetector类。



下面添加函数来控制水体网格的移动:

[C#] 纯文本查看 复制代码
?
voidUpdateMeshes()
    {
        for(inti
= 0; i < meshes.Length; i++)
        {
  
            Vector3[]
Vertices =
newVector3[4];
            Vertices[0]
=
newVector3(xpositions[i],
ypositions[i], z);
            Vertices[1]
=
newVector3(xpositions[i+1],
ypositions[i+1], z);
            Vertices[2]
=
newVector3(xpositions[i],
bottom, z);
            Vertices[3]
=
newVector3(xpositions[i+1],
bottom, z);
  
            meshes[i].vertices
= Vertices;
        }
    }

该函数与上面的几乎一样,只是不需再设置三角形和UV。



下一步是在FixedUpdate()函数中添加物理特性让水体可以自行流动。

[C#] 纯文本查看 复制代码
?
voidFixedUpdate()
{}

添加物理特性

首先是结合胡克定律和欧拉方法获取水体新的坐标、加速度及速度。



胡克定律即 F = kx,F是指由水浪产生的力(这里的水体模型就是由一排水浪组成),k指水体强度系数,x是偏移距离。这里的偏移距离就是各节点的y坐标减去节点的基本高度。



接下来添加一个与速度成比例的阻尼因子形成水面的阻力。

[C#] 纯文本查看 复制代码
?
for(inti
= 0; i < xpositions.Length ; i++)
        {
            floatforce
= springconstant * (ypositions[i] - baseheight) + velocities[i]*damping ;
            accelerations[i]
= -force;
            ypositions[i]
+= velocities[i];
            velocities[i]
+= accelerations[i];
            Body.SetPosition(i,newVector3(xpositions[i],
ypositions[i], z));
        }

欧拉方法很简单,就是在每帧用加速度更新速度然后用速度更新位置。



注意这里每个节点的作用力原子数量为1,你也可以改为其它值,这样加速度就是:

[C#] 纯文本查看 复制代码
?
accelerations[i]
= -force/mass;

下面实现水浪的传播效果。

[C#] 纯文本查看 复制代码
?
float[]
leftDeltas =
newfloat[xpositions.Length];
float[]
rightDeltas =
newfloat[xpositions.Length];

这里创建了两个数组,对于每个节点,都要对比前一个节点与当前节点的高度差并将差值存入leftDeltas。



然后还要比较后一个节点与当前节点的高度差并将差值存入rightDeltas。还需将所有的差值乘以传播速度常量。

[C#] 纯文本查看 复制代码
?
for(intj
= 0; j < 8; j++)
{
    for(inti
= 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(inti
= 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#] 纯文本查看 复制代码
?
publicvoid

Splash(
floatxpos,
floatvelocity)
{}

首先需要确定水波飞溅的位置是在水体范围内:

[C#] 纯文本查看 复制代码
?
if(xpos
>= xpositions[0] && xpos <= xpositions[xpositions.Length-1])
{}

然后改变水波的x坐标以获取飞溅位置与水体起始位置间的相对坐标:

[C#] 纯文本查看 复制代码
?
expos
-= xpositions[0];

然后找到落水物体碰撞的节点。计算方法如下:

[C#] 纯文本查看 复制代码
?
intindex
= Mathf.RoundToInt((xpositions.Length-1)*(xpos / (xpositions[xpositions.Length-1] - xpositions[0])));

步骤如下:

首先获取飞溅位置与水体左边界的坐标差(xpos)。



然后将该差值除以水体宽度。



这样就得到了飞溅发生位置的分数,例如飞溅发生在水体宽度的3/4处就会返回0.75。



将该分数乘以边数后取整,就得到了离飞溅位置最近的节点索引。

[C#] 纯文本查看 复制代码
?
velocities[index]
= velocity;

下面将入水物体的速度赋给该物体所碰撞的节点,这样节点会被物体压入水体。



注意:你可以按自己的需求来更改上面的代码。例如,你可以将节点速度与物体速度相加,或者使用动量除以节点的作用原子数量而非直接使用速度。

下面实现产生水花的粒子系统。将该对象命名为“splash”,别跟Splash()搞混了,后者是一个函数。



首先,我们需要设置飞溅的参数,这个参数是受撞击物体的速度影响的。

[C#] 纯文本查看 复制代码
?
floatlifetime
= 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 =
newVector3(xpositions[index],ypositions[index]-0.35f,5);
Quaternion
rotation = Quaternion.LookRotation(
newVector3(xpositions[Mathf.FloorToInt(xpositions.Length
/ 2)], baseheight + 8, 5) - position);

Shuriken粒子在与物体碰撞后不会立即被摧毁,所以要确保粒子不会显示在物体前方,有两种办法:



1.将它们固定在背景上,例如将其坐标的z值设为5。



2.让粒子系统总是朝向水体中心,这样就不会飞溅到边缘以外。



第二行代码获取坐标中点,稍微上移,并让粒子发射器指向该点。如果你的水体够宽,就不需要进行该设置。如果你的水体是室内游泳池就需要用到该脚本。

[C#] 纯文本查看 复制代码
?
GameObject
splish = Instantiate(splash,position,rotation)
asGameObject;
Destroy(splish,
lifetime+0.3f);

现在添加了飞溅对象,该对象会在粒子被摧毁后一段时间再消失,因为粒子系统发射了大量爆裂的粒子,所以粒子消失所需时间至少是Time.time + lifetime,最后的爆裂的粒子甚至需要更久。



碰撞检测

最后还需对物体进行碰撞检测,之前为所有的碰撞器都添加了WaterDetector脚本,在该脚本中添加下面的函数:

[C#] 纯文本查看 复制代码
?
voidOnTriggerEnter2D(Collider2D
Hit)
{}

在OnTriggerEnter2D()中实现2D Rigid Body与水体碰撞产生的效果。传入Collider2D类型的参数可获取更多关于碰撞物体的信息。需要该物体带有Rigidbody2D组件:

[C#] 纯文本查看 复制代码
?
if(Hit.rigidbody2D
!=
null)
{
      transform.parent.GetComponent<Water>().Splash(transform.position.x,
Hit.rigidbody2D.velocity.y*Hit.rigidbody2D.mass / 40f);
    }
}

所有碰撞器都是water manager的子对象。所以直接从碰撞器父节点获取Water组件并调用Splash()函数。如果希望物理效果更精确,可以使用动量而非速度。注意在这里也该为对应的属性即可。如果要获取物体动量,就将其速度乘以mass。如果只用速度,就将代码中的mass删掉。



在Start()函数中调用SpawnWater():

[C#] 纯文本查看 复制代码
?
voidStart()
{
    SpawnWater(-10,20,0,-10);
}

到此就完成了,所有带有rigidbody2D和碰撞器的物体都可以撞击水面并产生水波飞溅的效果,并且水波也会正常流动。

加分练习

在SpawnWater()函数中添加以下代码:

[C#] 纯文本查看 复制代码
?
gameObject.AddComponent<BoxCollider2D>();
gameObject.GetComponent<BoxCollider2D>().center
=
newVector2(Left
+ Width / 2, (Top + Bottom) / 2);
gameObject.GetComponent<BoxCollider2D>().size
=
newVector2(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

使用Unity实现动态2D水效果的更多相关文章

  1. 使用Unity创造动态的2D水体效果

    者:Alex Rose 在本篇教程中,我们将使用简单的物理机制模拟一个动态的2D水体.我们将使用一个线性渲染器.网格渲染器,触发器以及粒子的混合体来创造这一水体效果,最终得到可运用于你下款游戏的水纹和 ...

  2. Unity 4.3 2D 教程:新手上路

    这篇文章译自 Christopher LaPollo 先生的 Unity 4.3 2D 教程的第一部分 Unity 4.3 2D Tutorial: Getting Started 感谢这套优秀教程的 ...

  3. 介绍用C#和VS2015开发基于Unity架构的2D、3D游戏的技术

    [Unity]13.3 Realtime GI示例 摘要: 分类:Unity.C#.VS2015 创建日期:2016-04-19 一.简介 使用简单示例而不是使用实际示例的好处是能让你快速理解光照贴图 ...

  4. canvas动态小球重叠效果

    前面的话 在javascript运动系列中,详细介绍了各种运动,其中就包括碰壁运动.但是,如果用canvas去实现,却是另一种思路.本文将详细介绍canvas动态小球重叠效果 效果展示 静态小球 首先 ...

  5. Canvas 动态小球重叠效果

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  6. Android 动态Tab分页效果实现

    当前项目使用的是TabHost+Activity进行分页,目前要做个报表功能,需要在一个Tab页内进行Activity的切换.比方说我有4个Tab页分别为Tab1,Tab2,Tab3,Tab4,现在的 ...

  7. 【转】提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果

    原文网址:http://www.zhimengzhe.com/IOSkaifa/37910.html MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显 ...

  8. jQuery动态星级评分效果实现方法

    本文实例讲述了jQuery动态星级评分效果实现方法.分享给大家供大家参考.具体如下: 这里的jQuery星级评分代码,是大家都很喜欢的功能,目前广泛应用,本星级评分加入了动画效果,注意,如果要真正实现 ...

  9. axure rp教程(四)动态面板滑动效果

    转载自: http://www.iaxure.com/74.html 实现目标: 1.  点击登录滑出登录面板 2.  点击确定滑出动态面板 最终效果如下: 这种效果可以通过两种方法实现: 首先准备需 ...

随机推荐

  1. DB Migrations更新数据库命令

    在项目迭代的过程中,数据库结构常常需要跟随业务需求的变化做出调整,尤其在迭代的初期阶段,加一个字段减一个字段的需求更是家常便饭.在小型团队中,往往是负责开发功能模块的程序员在完成本地开发环境数据库的变 ...

  2. jforum二次开发教程

    环境准备: 一.Tomcat服务器     首先需要在本地搭建tomcat.tomcat搭建过程本人博客中有,不再重复纪录.因为开始没有搭建成功,浪费了一定时间.   二.Mysql服务器     在 ...

  3. matlab 在机器视觉中常用的函数

    ~ triangulate() 三角化(获得距离)匹配点 ~ undistortImage() 去除相机畸变并生成图像

  4. [2018-08-25]模板引擎Razor Engine 用法示例

    好久没写博客了,回宁波后最近几个月一直忙些线下的事情. 敲代码方面脱产有阵子了,生疏了,回头一看,这行业果然更新飞快. 最近线下的事情基本忙完,准备开始干回老本行,最重要的一件事就是升级abplus库 ...

  5. 【ES6】更易于继承的类语法

    和其它面向对象编程语言一样,ES6 正式定义了 class 类以及 extend 继承语法糖,并且支持静态.派生.抽象.迭代.单例等,而且根据 ES6 的新特性衍生出很多有趣的用法. 一.类的基本定义 ...

  6. Ctags快速入门

    Ctags快速入门 在vim下阅读代码,特别是阅读不熟悉的代码时,ctags是一个提高效率的强大的工具. 1. ctags是什么? ctags可以将代码中的函数.方法.类.变量和其他的标识符进行索引, ...

  7. 一篇文章了解相见恨晚的 Android Binder 进程间通讯机制【转】

    本文转载自:https://blog.csdn.net/freekiteyu/article/details/70082302 Android-Binder进程间通讯机制 概述 最近在学习Binder ...

  8. 一.编译nginx

    前往nginx.org下载需要的nginx版本,解压之后目录如下: auto : 主要存放辅助configure脚本执行时的文件, 例如判定nginx支持的模块,操作系统可供nginx使用的特性等. ...

  9. sublime 3好用快捷键

    sublime 3好用快捷键 自己常用 删除行 [ { "keys": ["ctrl+shift+d"], "command": " ...

  10. linux命令学习笔记(52):ifconfig命令

    许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改.Linux系统拥有 一个类似的工具,也就是ifconfig (interfaces config).通 ...