【Unity教程】2D水物理模拟

【Unity教程】2D水物理模拟

 视频地址

介绍

  • 当角色进入水中时,水会根据角色下降的速度大小受力,进而让水面下降

  • 当角色跳出水中时,水会根据角色出水的速度大小受力,水面会有少数上升

  • 角色再水中移动时,会对周围水面产生影响,类似出水时的受力

  • 水面会根据受力,上下波动,并且随着时间波动衰减,类似橡皮筋

  • 水面波动会向周围扩散,并衰减,最终趋于平静

第一部分 绘制水面

绘制组件(unity内置的组件)

水面和绘制

  • 1.绘制水上面部分的顶点和下面部分的顶点

 //顶点,length是水面长度,quality是顶点数量
         float interval_length = length/quality;
         //分开设置数组前一部分是上顶点,后一半是下顶点
         //上面顶点
         for (int i = 0; i < quality; i++) {
             vertices[i] = new Vector3(i * interval_length, width, 0);
         }
         //下面顶点
         for (int i = quality; i < 2*quality; i++) {
             vertices[i] = new Vector3((i-quality) * interval_length, 0, 0);
         }
  • 2.如果看了上面mesh的博客,就知道需要把这些点连接成一个顺时针的三角形,这样才能再正面显示出来

  • 从下顶点遍历,绘制三角形,每个下顶点顺时针绘制两个三角形

  • 3.然后UV采样

  • UV坐标是x范围(0,1)y的范围(0,1)

  • 所以绘制的顶点均进行均匀在uv图上采样

 Vector2[] uvs = new Vector2[vertices.Length];
         for (int i = 0; i < vertices.Length; i++) {
             uvs[i] = new Vector2(vertices[i].x/length,vertices[i].y/width);
         }

img

完整绘制水面代码

  
//绘制水
     public void DrawWater(float length,float width,int quality) {
         //材质
         gameObject.GetComponent<MeshRenderer>().material = mat;
         //顶点
         float interval_length = length/quality;
         //分开设置数组前一部分是上顶点,后一半是下顶点
         //上面顶点
         for (int i = 0; i < quality; i++) {
             vertices[i] = new Vector3(i * interval_length, width, 0);
         }
         //下面顶点
         for (int i = quality; i < 2*quality; i++) {
             vertices[i] = new Vector3((i-quality) * interval_length, 0, 0);
         }
         //设置三角形
         int anglescount = (quality*2-2)*3;
         triangles = new int[anglescount];
 ​
         int current=0;
         for (int i = quality; i < 2 * quality - 1; i++) {
 ​
             triangles[current++] = i;
             triangles[current++] = i-quality;
             triangles[current++] = i-quality+1;
 ​
             triangles[current++] = i;
             triangles[current++] = i - quality + 1;
             triangles[current++] = i + 1;
         }
 ​
         Vector2[] uvs = new Vector2[vertices.Length];
         for (int i = 0; i < vertices.Length; i++) {
             uvs[i] = new Vector2(vertices[i].x/length,vertices[i].y/width);
         }
 ​
         Mesh mesh = GetComponent<MeshFilter>().mesh;
         mesh.Clear();
         mesh.RecalculateNormals();
         mesh.RecalculateTangents();
         mesh.vertices = vertices;
         mesh.triangles = triangles;
         mesh.uv = uvs;
     }

第二部分 物理模拟

物理模拟原理

  • 因为水面是由很多个顶点组成,所以只需要对水面的顶点进行变换,在重新绘制顶点即可模拟水面的变化。

  • 在这里每个顶点移动只在Y轴(上下)进行偏移,不进行左右移动

  • 所以只需要模拟每个顶点的运动规律就可以实现水面动画

实现思路

  • 1.因为只对水面部分的顶点进行模拟,设置一个水面顶点变化速度的数组

  • 2.当物体落入水中,跳出水中,在水中移动时,根据距离和力的大小赋予数组初速度。

  • 3.然后每帧让顶点位置+对应变化速度,进行顶点偏移

  • 4.速度数组会时刻收到与速度相反的力,可以理解为水面张力,目的就是让水面恢复平静,所以在设置每个位置对应的加速度,与速度方向相反

  • 5.速度受加速度和阻力的影响持续衰减,最终减少为0

  • 6.扩散,从左遍历,每一个顶点变换速度+上相邻右边顶点的差值,再从右边遍历,每一个顶点变换速度+上相邻左边顶点的差值

完整代码

 
/****************************************************
     文件:WaterMeshRender.cs
   作者:别离或雪
     邮箱: [email protected]
     日期:#CreateTime#
   功能:水面模拟
 *****************************************************/
 ​
 using UnityEngine;
 ​
 [RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
 public class WaterMeshRender : MonoBehaviour 
 {
 ​
     [Header("水")]
     public float length;//水面长度
     public float width;//水深
     public int quality;//顶点数量
 ​
     public Material mat;//材质
     [Header("组件")]
     private BoxCollider2D boxCollider;
     private Bounds bounds;
     Vector3[] vertices;//顶点
     int[] triangles;//三角形
 ​
     float[] leftdetails;//向左扩散速度
     float[] rightdetails;//向右扩散速度
     float[] velocitys;//velocityY方向的速度
     float[] accelerated;//加速度
 ​
     public float damping=0.2f;//速度对恢复波动的影响力
     public float heighspeed=2f;//高度对恢复波动的影响力
     public float spread;//传播衰减值
 ​
     //public float MaxOffsetTop=1;//水面最大高度
     //public float MaxOffsetBottom=1;//水面最低深度
 ​
     public float collForce=0.1f;//物体落水力影响值
     private void Start() {
         vertices = new Vector3[quality*2];
         velocitys = new float[quality];
         accelerated = new float[quality * 2];
         leftdetails = new float[quality];
         rightdetails = new float[quality];
         DrawWater(length, width, quality);
         boxCollider = GetComponent<BoxCollider2D>();
         boxCollider.offset = new Vector2(4.8f, 0.74f);
         boxCollider.size = new Vector2(length, width);
         bounds = boxCollider.bounds;
 ​
         //可以自己波动范围限制
         //MaxOffsetTop = width+2;
         //MaxOffsetBottom = 0.5f;
 ​
         MeshRenderer meshRenderer = transform.GetComponent<MeshRenderer>();
         meshRenderer.sortingOrder = 1;
     }
     private void Update() {
         //持续绘制
         //物理
         for (int i = 0; i < quality; i++) {
             //加速度的力
             float force =heighspeed*(vertices[i].y-width)+velocitys[i]*damping;
             //顶点变换
             vertices[i].y +=velocitys[i]
             //这里可以设置力的最大最小范围
                 ;
             accelerated[i] = -force;
             //速度收到加速度和阻力衰减(这个数值自行设置,这里我设置速度大小的1/5)
             velocitys[i] += accelerated[i]-velocitys[i]/5;
         }
         //水扩散和衰减
         for (int i = 0; i < quality; i++) {
             if (i > 0) {
                 leftdetails[i] = spread * (vertices[i].y - vertices[i - 1].y);
                 velocitys[i - 1] += leftdetails[i];
             }
             if (i < quality - 1) {
                 rightdetails[i] = spread * (vertices[i].y - vertices[i + 1].y);
                 velocitys[i + 1] += rightdetails[i];
             }
         }
         //重新绘制顶点
         ReDraw(vertices);
     }
     //绘制水
     public void DrawWater(float length,float width,int quality) {
         //材质
         gameObject.GetComponent<MeshRenderer>().material = mat;
         //顶点
         float interval_length = length/quality;
         //分开设置数组前一部分是上顶点,后一半是下顶点
         //上面顶点
         for (int i = 0; i < quality; i++) {
             vertices[i] = new Vector3(i * interval_length, width, 0);
         }
         //下面顶点
         for (int i = quality; i < 2*quality; i++) {
             vertices[i] = new Vector3((i-quality) * interval_length, 0, 0);
         }
         //设置三角形
         int anglescount = (quality*2-2)*3;
         triangles = new int[anglescount];
 ​
         int current=0;
         for (int i = quality; i < 2 * quality - 1; i++) {
 ​
             triangles[current++] = i;
             triangles[current++] = i-quality;
             triangles[current++] = i-quality+1;
 ​
             triangles[current++] = i;
             triangles[current++] = i - quality + 1;
             triangles[current++] = i + 1;
         }
         //对应坐标UV设置
         Vector2[] uvs = new Vector2[vertices.Length];
         for (int i = 0; i < vertices.Length; i++) {
             uvs[i] = new Vector2(vertices[i].x/length,vertices[i].y/width);
         }
 ​
         Mesh mesh = GetComponent<MeshFilter>().mesh;
         mesh.Clear();
         mesh.RecalculateNormals();
         mesh.RecalculateTangents();
         mesh.vertices = vertices;
         mesh.triangles = triangles;
         mesh.uv = uvs;
     }
     public void ReDraw(Vector3[] currentvertices) {
         //设置三角形
         int anglescount = (quality * 2 - 2) * 3;
         triangles = new int[anglescount];
 ​
         int current = 0;
         for (int i = quality; i < 2 * quality - 1; i++) {
 ​
             triangles[current++] = i;
             triangles[current++] = i - quality;
             triangles[current++] = i - quality + 1;
 ​
             triangles[current++] = i;
             triangles[current++] = i - quality + 1;
             triangles[current++] = i + 1;
         }
         Vector2[] uvs = new Vector2[vertices.Length];
         for (int i = 0; i < vertices.Length; i++) {
             uvs[i] = new Vector2(vertices[i].x / length, Mathf.Max(vertices[i].y / width,1));
         }
 ​
         Mesh mesh = GetComponent<MeshFilter>().mesh;
         mesh.Clear();
         mesh.RecalculateNormals();
         mesh.RecalculateTangents();
         mesh.vertices = currentvertices;
         mesh.triangles = triangles;
         mesh.uv = uvs;
     }
 ​
     private void OnTriggerEnter2D(Collider2D col) {
         Vector2 velocity = col.GetComponent<Rigidbody2D>().velocity;
         FallIn(col, velocity.y*collForce);
         Debug.Log("Enter Water");
     }
     private void OnTriggerStay2D(Collider2D col) {
         Vector2 velocity = col.GetComponent<Rigidbody2D>().velocity;
         //持续呆在水中力为上方向,这里设置速度的绝对值
         StayWater(col, Mathf.Abs(velocity.x) * collForce);
     }
     private void OnTriggerExit2D(Collider2D col) {
         Vector2 velocity = col.GetComponent<Rigidbody2D>().velocity;
         JumpOut(col,velocity.y*collForce);
         Debug.Log("Out Water");
     }
     //落入水中
     private void FallIn(Collider2D col,float force) {
         //原点,因为绘制的顶点并不是世界坐标,需要加上原点的位置才是世界坐标
         Vector2 originalpos = transform.position;
         //计算物体包围盒宽度
         float radius = col.bounds.max.x - col.bounds.min.x;
         //计算中心点
         Vector2 center = new Vector2(col.bounds.center.x,width);
         //计算每个受力的大小,赋予初始速度
         for (int i = 0; i < quality; i++) {
             //只计算X距离
             float dis = Vector2.Distance(new Vector2(originalpos.x+vertices[i].x,width), center);
             if (dis < radius) {
                 velocitys[i] = force * (radius - dis) / radius;
             }
         }
     }
     //跃出水面
     private void JumpOut(Collider2D col,float force) {
         Vector2 originalpos = transform.position;
         float radius = col.bounds.max.x - col.bounds.min.x;
         Vector2 center = new Vector2(col.bounds.center.x, width);
         for (int i = 0; i < quality; i++) {
             float dis = Vector2.Distance(new Vector2(originalpos.x + vertices[i].x, width), center);
             if (dis < radius) {
                 velocitys[i] = force * (radius - dis) / radius;
             }
         }
     }
     //水中移动
     private void StayWater(Collider2D col,float force) {
         //水中移动影响当前速度,不是赋予初始速度
         Vector2 originalpos = transform.position;
         float radius = col.bounds.max.x - col.bounds.min.x;
         Vector2 center = new Vector2(col.bounds.center.x, width);
         for (int i = 0; i < quality; i++) {
             float dis = Vector2.Distance(new Vector2(originalpos.x + vertices[i].x, width), center);
             if (dis < radius) {
                 velocitys[i] += (force) * dis /5;
             }
         }
     }
 }

项目地址

2946952974/WaterMeshRender: 水面模拟 (github.com)

猜你喜欢

转载自blog.csdn.net/leave_snow/article/details/125601212
今日推荐