当前位置:   article > 正文

【Unity小技巧】Unity中实现带有Sprite Shape的2D水效果(附项目源码)_unity中带有sprite shape的2d水教程

unity中带有sprite shape的2d水教程

先看实现的最终效果

在这里插入图片描述

前言

本文是自己的学习笔记,最近发现一个很有意思的2d水效果,所以把它的实现过程写下来分享给大家。

当在 Unity 中实现带有 Sprite Shape 的 2D 水效果时,首先需要理解 Sprite Shape 和水效果的基本概念和工作原理。Sprite Shape 是 Unity 提供的一种 2D 图形工具,用于创建基于轮廓的精灵形状,并可以根据路径进行变形和填充。而 2D 水效果通常涉及模拟水体的行为,包括波纹、浪花、浮力等物理特性的表现。

总的来说,结合 Sprite Shape 和水效果需要综合运用 Unity 中的图形技术、物理模拟和动画效果,以达到模拟逼真的水体效果。这样的设计可以为游戏场景增添视觉上的沉浸感和互动性,为玩家带来更加生动的游戏体验。

模拟水面的波动效果

新增WaterSpring

public class WaterSpring : MonoBehaviour
{
    public float velocity = 0;
    public float force = 0;
    // 当前高度
    public float height = 0f;
    // 目标高度
    public float target_height = 0f;

    // 带有阻尼的弹簧更新
    public void WaveSpringUpdate(float springStiffness, float dampening)
    {
        height = transform.localPosition.y; // 获取当前高度

        // 计算弹簧的最大拉伸距离
        var x = height - target_height;

        // 计算阻尼力
        var loss = -dampening * velocity;

        // 计算作用力
        force = -springStiffness * x + loss;

        // 激活物理引擎
        velocity += force;

        // 将物体位置调整为新的高度
        var y = transform.localPosition.y;
        transform.localPosition = new Vector3(transform.localPosition.x, y + velocity, transform.localPosition.z);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

新建一个2d球挂载上去
在这里插入图片描述

新增WaterShapeController

[SerializeField]
private float springstiffness = 0.1f; // 弹簧刚度系数
[SerializeField]
private List<WaterSpring> springs = new List<WaterSpring>(); // 所有水弹簧的列表
[SerializeField]
private float dampening = 0.03f; // 阻尼系数
public float spread = 0.006f; // 弹簧之间的间隔
 void FixedUpdate()
{
    // 对所有水弹簧进行更新
    foreach (WaterSpring waterSpringComponent in springs)
    {
        waterSpringComponent .WaveSpringUpdate(springstiffness, dampening);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

新建个空物体,挂载脚本并配置参数
在这里插入图片描述

一个球的效果
在这里插入图片描述
复制多个球模拟水面波动
在这里插入图片描述
效果
在这里插入图片描述

制作2d水面

安装插件
在这里插入图片描述
新增
在这里插入图片描述
配置4个节点
在这里插入图片描述
最终效果
在这里插入图片描述
修改WaterSpring

public class WaterSpring : MonoBehaviour
{
    private int waveIndex = 0; // 当前水泉所在的曲线上的节点索引
    [SerializeField]
    private static SpriteShapeController spriteShapeController = null; // 水面曲线对应的SpriteShapeController组件
    [System.NonSerialized]
    public float velocity = 0; // 当前水泉的速度
    private float force = 0; // 当前水泉的力
    [System.NonSerialized]
    public float height = 0f; // 当前水泉的高度
    private float target_height = 0f; // 目标高度
    private float resistance = 30f; // 抵抗力,表示当有物体落入水面时,水泉速度增加的系数

    // 初始化函数,用于设置当前水泉所在的曲线节点索引、SpriteShapeController组件以及初始化速度、高度
    public void Init(SpriteShapeController ssc)
    {
        var index = transform.GetSiblingIndex();
        waveIndex = index + 1;
        spriteShapeController = ssc;

        velocity = 0;
        height = transform.localPosition.y;
        target_height = transform.localPosition.y;
    }

    // 更新函数,用于更新水泉的状态,传入参数为弹性系数和阻尼系数
    public void WaveSpringUpdate(float springStiffness, float dampening)
    {
        height = transform.localPosition.y;
        // 最大伸长距离
        var x = height - target_height;
        var loss = -dampening * velocity;

        force = -springStiffness * x + loss;
        velocity += force;
        var y = transform.localPosition.y;
        transform.localPosition = new Vector3(transform.localPosition.x, y + velocity, transform.localPosition.z);
    }

    // 更新水面曲线上的节点高度
    public void WavePointUpdate()
    {
        if (spriteShapeController != null)
        {
            Spline waterSpline = spriteShapeController.spline;
            Vector3 wavePosition = waterSpline.GetPosition(waveIndex);
            waterSpline.SetPosition(waveIndex, new Vector3(wavePosition.x, transform.localPosition.y, wavePosition.z));
        }
    }
    private void OnTriggerEnter2D(Collider2D other)
    {
        Debug.Log(2222);
        if (other.gameObject.tag.Equals("FallingObject"))
        {
            FallingObject fallingObject = other.gameObject.GetComponent<FallingObject>();
            Rigidbody2D rb = fallingObject.GetComponent<Rigidbody2D>();
            var speed = rb.velocity;

            velocity += speed.y / resistance;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

给球挂载脚本,添加触发器和配置区域
在这里插入图片描述
修改WaterShapeController,当一个脚本类被标记为 [ExecuteAlways] 时,它的 Update、LateUpdate 和 FixedUpdate 等方法会在编辑模式下以及运行时都被执行,而不仅仅是在播放模式下执行。

[ExecuteAlways]
public class WaterShapeController : MonoBehaviour
{
    private int CorsnersCount = 2; // 水形状的边角数
    [SerializeField]
    private SpriteShapeController spriteShapeController; // 水形状控制器
    [SerializeField, Header("波浪点预设物体")]
    private GameObject wavePointPref;
    [SerializeField, Header("波浪点父物体")]
    private GameObject wavePoints;

    [SerializeField, Header("波浪数量")]
    [Range(1, 100)]
    private int WavesCount;
    private List<WaterSpring> springs = new(); // 存储水波浪效果的弹簧节点
    [Header("弹簧的强度常数")]
    public float springStiffness = 0.1f;
    [Header("阻力,用于减缓弹簧的运动")]
    public float dampening = 0.03f;
    [Header("扩散常数,用于将节点的影响传递给它附近的节点")]
    public float spread = 0.006f;

    void Start()
    {

    }
    void OnValidate()
    {
        // 清空水波浪点和弹簧
        StartCoroutine(CreateWaves());
    }
    IEnumerator CreateWaves()
    {
        foreach (Transform child in wavePoints.transform)
        {
            StartCoroutine(Destroy(child.gameObject));
        }
        yield return null;
        SetWaves();
        yield return null;
    }
    IEnumerator Destroy(GameObject go)
    {
        yield return null;
        DestroyImmediate(go);
    }
    private void SetWaves()
    {
        Spline waterSpline = spriteShapeController.spline; // 获取水形状的样条曲线
        int waterPointsCount = waterSpline.GetPointCount(); // 获取水形状的点数

        // 移除中间点,只保留边角的两个点
        // 每次移除第一个点,则第二个点成为新的第一个点
        for (int i = CorsnersCount; i < waterPointsCount - CorsnersCount; i++)
        {
            waterSpline.RemovePointAt(CorsnersCount);
        }

        Vector3 waterTopLeftCorner = waterSpline.GetPosition(1); // 水形状左上角的顶点位置
        Vector3 waterTopRightCorner = waterSpline.GetPosition(2); // 水形状右上角的顶点位置
        float waterWidth = waterTopRightCorner.x - waterTopLeftCorner.x; // 水形状的宽度

        float spacingPerWave = waterWidth / (WavesCount + 1); // 计算每个波浪之间的间隔
                                                              // 在水形状中添加波浪点
        for (int i = WavesCount; i > 0; i--)
        {
            int index = CorsnersCount;

            float xPosition = waterTopLeftCorner.x + (spacingPerWave * i);
            Vector3 wavePoint = new Vector3(xPosition, waterTopLeftCorner.y, waterTopLeftCorner.z);
            waterSpline.InsertPointAt(index, wavePoint);
            waterSpline.SetHeight(index, 0.1f);
            waterSpline.SetCorner(index, false);
            waterSpline.SetTangentMode(index, ShapeTangentMode.Continuous);
        }

        springs = new();
        for (int i = 0; i <= WavesCount + 1; i++)
        {
            int index = i + 1;

            Smoothen(waterSpline, index); // 平滑水形状的曲线

            GameObject wavePoint = Instantiate(wavePointPref, wavePoints.transform, false);
            wavePoint.transform.localPosition = waterSpline.GetPosition(index);

            WaterSpring waterSpring = wavePoint.GetComponent<WaterSpring>();
            waterSpring.Init(spriteShapeController);
            springs.Add(waterSpring);
        }
    }
    private void Smoothen(Spline waterSpline, int index)
    {
        Vector3 position = waterSpline.GetPosition(index); // 获取节点位置
        Vector3 positionPrev = position;
        Vector3 positionNext = position;
        if (index > 1)
        {
            positionPrev = waterSpline.GetPosition(index - 1); // 获取上一个节点的位置
        }
        if (index - 1 <= WavesCount)
        {
            positionNext = waterSpline.GetPosition(index + 1); // 获取下一个节点的位置
        }

        Vector3 forward = gameObject.transform.forward;

        float scale = Mathf.Min((positionNext - position).magnitude, (positionPrev - position).magnitude) * 0.33f;

        Vector3 leftTangent = (positionPrev - position).normalized * scale;
        Vector3 rightTangent = (positionNext - position).normalized * scale;

        SplineUtility.CalculateTangents(position, positionPrev, positionNext, forward, scale, out rightTangent, out leftTangent);

        waterSpline.SetLeftTangent(index, leftTangent); // 设置左切向量
        waterSpline.SetRightTangent(index, rightTangent); // 设置右切向量
    }
    void FixedUpdate()
    {
        foreach (WaterSpring waterSpringComponent in springs)
        {
            waterSpringComponent.WaveSpringUpdate(springStiffness, dampening); // 更新弹簧效果
            waterSpringComponent.WavePointUpdate(); // 更新节点位置
        }

        UpdateSprings(); // 更新所有弹簧的速度

    }

    private void UpdateSprings()
    {
        int count = springs.Count;
        float[] left_deltas = new float[count];
        float[] right_deltas = new float[count];

        for (int i = 0; i < count; i++)
        {
            if (i > 0)
            {
                left_deltas[i] = spread * (springs[i].height - springs[i - 1].height);
                springs[i - 1].velocity += left_deltas[i];
            }
            if (i < springs.Count - 1)
            {
                right_deltas[i] = spread * (springs[i].height - springs[i + 1].height);
                springs[i + 1].velocity += right_deltas[i];
            }
        }
    }
    private void Splash(int index, float speed)
    {
        if (index >= 0 && index < springs.Count)
        {
            springs[index].velocity += speed;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157

挂载脚本,并配置参数
在这里插入图片描述
效果
在这里插入图片描述

实现物体落入水中互动效果

修改WaterSpring

//。。。

 // 当有物体进入水面时的触发检测函数,如果该物体被标记为FallingObject,则将水泉速度增加一定值
 private void OnTriggerEnter2D(Collider2D other)
 {
     if (other.gameObject.tag.Equals("FallingObject"))
     {
         FallingObject fallingObject = other.gameObject.GetComponent<FallingObject>();
         Rigidbody2D rb = fallingObject.GetComponent<Rigidbody2D>();
         var speed = rb.velocity;

         velocity += speed.y / resistance;
     }
 } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

新增FallingObject脚本,控制物体下落

public class FallingObject : MonoBehaviour
{
    [Header("物体下落的速度")]
    public float forceAmount = 5f;
    private Rigidbody2D rb; // 物体的刚体组件

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        // 将物体的速度设置为向下的 forceAmount 速度
        rb.velocity = Vector3.down * forceAmount;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

新建个box物体,挂载脚本,配置参数,记得修改物体标签为FallingObject
在这里插入图片描述
效果
在这里插入图片描述

给水面添加浮力效果

添加Buoyancy Effector 2D(浮力效应器)和多边形碰撞器。浮力效应器用于模拟游戏对象在水中受到的浮力影响,编辑多边形碰撞器配置覆盖整个水体
在这里插入图片描述

效果
在这里插入图片描述

最终效果

在这里插入图片描述

源码

整理好后我会放上了

参考

如果觉得本文实现的效果不错的话,非常推荐大家去支持一下原作者

【视频】https://www.youtube.com/watch?v=69sBjqMtZCc

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号