先看实现的最终效果
前言
本文是自己的学习笔记,最近发现一个很有意思的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);
}
}
新建一个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);
}
}
新建个空物体,挂载脚本并配置参数
一个球的效果
复制多个球模拟水面波动
效果
制作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;
}
}
}
给球挂载脚本,添加触发器和配置区域
修改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;
}
}
}
挂载脚本,并配置参数
效果
实现物体落入水中互动效果
修改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;
}
}
新增FallingObject脚本,控制物体下落
public class FallingObject : MonoBehaviour
{
[Header("物体下落的速度")]
public float forceAmount = 5f;
private Rigidbody2D rb; // 物体的刚体组件
void Start()
{
rb = GetComponent<Rigidbody2D>();
// 将物体的速度设置为向下的 forceAmount 速度
rb.velocity = Vector3.down * forceAmount;
}
}
新建个box物体,挂载脚本,配置参数,记得修改物体标签为FallingObject
效果
给水面添加浮力效果
添加Buoyancy Effector 2D(浮力效应器)和多边形碰撞器。浮力效应器用于模拟游戏对象在水中受到的浮力影响,编辑多边形碰撞器配置覆盖整个水体
效果
最终效果
源码
整理好后我会放上了
参考
如果觉得本文实现的效果不错的话,非常推荐大家去支持一下原作者
【视频】https://www.youtube.com/watch?v=69sBjqMtZCc
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net文章来源:https://www.toymoban.com/news/detail-833585.html
一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
文章来源地址https://www.toymoban.com/news/detail-833585.html
到了这里,关于【Unity小技巧】Unity中实现带有Sprite Shape的2D水效果(附项目源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!