在中创建2D动态水体效果
在本教程中,通过简单的物理学模拟动态2D的水体。我们将混合使用线条渲染器,网格渲染器,触发器和粒子系统来实现效果。最终效果还配有浪花和飞溅,为下一个游戏做好准备。一个Unity(Unity3D)的实例已经实现了,但是你应该能在任何游戏引擎中使用相同的原理来实现。
相关文章
Make a Splash With Dynamic 2D Water Effects
How to Create a Custom 2D Physics Engine: The Basics and Impulse Resolution
Adding Turbulence to a Particle System
最终结果
这就是我们最终要的东西。你需要Unity浏览器插件试试。
https://ssl-webplayer.unity3d.com/download_webplayer-3.x/3.0/co/.application?installer=https%3A%2F%2Fssl-webplayer.unity3d.com%2Fdownload_webplayer-3.x%2FUnityWebPlayer.exe
建立水体管理
在Michael Hoffman的教程中讲述了如何通过溅射模拟水体表面。
我们打算让水体使用统一的渲染器,使用如此之多的节点作为连续的波浪出现。
我们需要跟踪每个节点的位置,速度和加速度。要做到这一点就要使用数组。所以在类的顶部要添加这些变量:
float[] xpositions;
float[] ypositions;
float[] velocities;
float[] accelerations;
LineRenderer Body;
LineRenderer将存储所有节点和水体轮廓,即使仍然需要水体本身;
我们将会使用网格进行创建,同时需要对象来持有这些网格。
GameObject[] meshobjects;
Mesh[] meshes;
我们也需要碰撞器以便与水体进行交互:
GameObject[] colliders;
我们将会存储所有的常量:
const float springconstant = 0.02f;
const float damping = 0.04f;
const float spread = 0.05f;
const float z = -1f;
这些常量与 Michael discussed所说的一样,通过一个参数z-即水体在z方向的偏移。我们将会使用-1以便展示对象。(你可能想要改变它,这都取决于想要出现在前面还是后面;需要通过坐标z来决定精灵放置在哪。)
下一步,我们将要持有一些变量:
Float baseheight;
float left;
float bottom;
这些都是水体的面积。
我们需要在编辑器中设置一些公共变量。首先,将粒子系统用在溅射上:
public GameObject splash:
接下来,我们将对线性渲染器使用材质(例如想要复用酸、熔岩、化学材料或者别的):
public Material mat:
另外,这种网格是水的主体:
public GameObject watermesh:
这些都是组合式的,都包含在the source files中。
public void SpawnWater(float Left, float Width, float Top, float Bottom)
我们需要一个游戏对象来保存所有这些数据,作为管理者,同时产生游戏世界中的水体。要做到这一点,我们将编写一个SpawnWater()函数。
这个函数的输入有水体的左边宽度,顶部和底部。
(尽管这似乎不一致,但在构建时展现了快速的设计水准)。
创建节点
现在要找出我们需要多少个节点:
int edgecount = Mathf.RoundToInt(Width) * 5;
int nodecount = edgecount + 1;
我们需要五个单位宽度达到光滑的运动要求。(你可以改变平滑的效率。)这里有所有行,我们需要额外节点 + 1 。
首先要做的是决定水体使用的 LineRenderer 组件:
Body = gameObject.AddComponent<LineRenderer>();
Body.material = mat;
Body.material.renderQueue = 1000;
Body.SetVertexCount(nodecount);
Body.SetWidth(0.1f, 0.1f);
还要做的就是选择材质,通过选择在渲染队列中的位置设置它在水面之上。我们已经设置正确的节点数,线的宽度设置为0.1。
可以根据你想要的线段来改变需求。你可能已经注意到,SetWidth()方法接受两个参数,这些都是在开始和结束的行宽。我们想要宽度不变。
既然我们已经创建了所有节点,那就先进行初始化操作:
xpositions = 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;
现在我们有了各个数组及其数据。
现在设置了数组实际的值,然后将以以下节点开始:
for (int i = 0; i < nodecount; i++)
{
ypositions = Top;
xpositions = Left + Width * i / edgecount;
accelerations = 0;
velocities = 0;
Body.SetPosition(i, new Vector3(xpositions, ypositions, z));
}
这里,我们设置了所有水体y轴的坐标,然后逐步添加所有节点。最初水体的速度和加速度为零。
通过设置每个节点LineRenderer (Body)的正确位置完成循环。
创建网格
这里开始变得棘手了。
我们已经有了线条,但是没有水体本身。我们可以使用网格,这里要先创建这些:
for (int i = 0; i < edgecount; i++)
{
meshes = new Mesh();
现在,网格存储了一堆变量。第一个变量是非常简单的:它包含所有的顶点(或角)。
图中显示了我们希望网格的样子。第一段,顶点高亮。我们总共想要四个。
Vector3[] Vertices = new Vector3[4];
Vertices[0] = new Vector3(xpositions, ypositions, z);
Vertices[1] = new Vector3(xpositions[i + 1], ypositions[i + 1], z);
Vertices[2] = new Vector3(xpositions, bottom, z);
Vertices[3] = new Vector3(xpositions[i+1], bottom, z);
现在,可以在这里看到左上的顶点是0,1是右上角的,2是左下角的,3是右上角的。我们需要记住它们。
第二个网格属性需要的是UVs。网格需要纹理, UVs选择了我们需要的部分。这种情况下,我们只需要纹理的左上角,右上角,左下角和右下角。
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。因此我们想要创建一个六个整数的数组,如下所示:
int[] tris = new int[6] { 0, 1, 3, 3, 2, 0 };
这里创建了四边形,现在可以设值了。
meshes.vertices = Vertices;
meshes.uv = UVs;
meshes.triangles = tris;
现在有了网格,但没有游戏对象渲染场景。所以我们要从watermesh预制中进行创建,这包含了一个网格渲染器和网格过滤器。
meshobjects = Instantiate(watermesh,Vector3.zero,Quaternion.identity) as GameObject;
meshobjects.GetComponent<MeshFilter>().mesh = meshes;
meshobjects.transform.parent = transform;
我们设置了网格和水体管理器的子集,从而将事物整合。
创建碰撞
现在加入碰撞器:
colliders[i] = new GameObject();
colliders.name = "Trigger";
colliders.AddComponent<BoxCollider2D>();
colliders.transform.parent = transform;
colliders.transform.position = new Vector3(Left + Width * (i + 0.5f) / edgecount, Top - 0.5f, 0);
colliders.transform.localScale = new Vector3(Width / edgecount, 1, 1);
colliders.GetComponent<BoxCollider2D>().isTrigger = true;
colliders.AddComponent<WaterDetector>();
这里创建了盒体碰撞器,进行命名将会让代码更加清爽,子集同理。我们设置了中间节点,同时设置了大小,加入了一个 WaterDetector 类。
现在有了网格,我们需要一个函数来更新水体的移动:
void UpdateMeshes()
{
for (int i = 0; i < meshes.Length; i++)
{
Vector3[] Vertices = new Vector3[4];
Vertices[0] = new Vector3(xpositions, ypositions, z);
Vertices[1] = new Vector3(xpositions[i+1], ypositions[i+1], z);
Vertices[2] = new Vector3(xpositions, bottom, z);
Vertices[3] = new Vector3(xpositions[i+1], bottom, z);
meshes.vertices = Vertices;
}
}
您可能注意到这个函数只使用我们之前写的代码。唯一不同的是这次不需要设置tris和UVs,因为这些是保持不变的。
我们的下一个任务是使水体开始工作。我们将使用 FixedUpdate() 以增量的方式修改它们。
void FixedUpdate()
原文标题:Creating Dynamic 2D Water Effects in Unity
原文链接:https://gamedevelopment.tutsplus.com/tutorials/creating-dynamic--water-effects-in-unity--gamedev-14143