百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

在Unity中创建2D动态水体效果

bigegpt 2024-08-10 12:15 7 浏览

在中创建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

相关推荐

C#.NET Autofac 详解(c# autoit)

简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...

webapi 全流程(webapi怎么部署)

C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...

.NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式

一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...

C# 使用SemanticKernel调用本地大模型deepseek

一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...

C#.NET 中间件详解(.net core中间件use和run)

简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...

IoC 自动注入:让依赖注册不再重复劳动

在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...

C#.NET 依赖注入详解(c#依赖注入的三种方式)

简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...

C#从零开始实现一个特性的自动注入功能

在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...

C#.NET 仓储模式详解(c#仓库货物管理系统)

简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...

C#.NET 泛型详解(c# 泛型 滥用)

简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...

数据分析-相关性分析(相关性 分析)

相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...

geom_smooth()函数-R语言ggplot2快速入门18

在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...

规范申报易错要素解析(规范申报易错要素解析)

为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...

「Eurora」海关编码归类 全球海关编码查询 关务服务

  海关编码是什么?  海关编码即HS编码,为编码协调制度的简称。  其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...

9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法

全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...