最終效果
本教程要實現的最終效果如下:

設置水管理器
第一步就是使用Unity的線段渲染器(Line Renderer)和一些節點來實現水浪的形狀。如下圖:

然后還要跟蹤所有節點的位置、速度及加速度。這些信息使用數組來存儲,在類的最上面添加以下代碼:
[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();
}
|

上圖展示了網格片段的理想顯示效果。第一個片段的頂點高亮顯示,共有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);
|

按節點順序觀察各角,三角形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;
|
注意:你可以按自己的需求來更改上面的代碼。例如,你可以將節點速度與物體速度相加,或者使用動量除以節點的作用原子數量而非直接使用速度。

下面實現產生水花的粒子系統。將該對象命名為“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);
}
|

加分練習
在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