LineRenderer模拟橡皮筋弹跳的物理现象,手拖拽橡皮筋然后松开,橡皮筋会来回跳动,并最终静止在原位置,主要思路就是实时计算点的位置,给LineRenderer的点赋值,实现动态弹跳效果
代码里主要分了两步:
- 从松开手的位置,回弹到原来位置
- 然后开始来回跳动,调动幅度越来越小,直到停止不动
该Demo里没有添加手拖拽皮筋的代码,如果有需要可自行实现,仅需要在LineRenderer 点位的起点与终点之间,添加一个中点,然后实时修改中点位置与屏幕鼠标位置一致即可(注意坐标转换~)
还有一个待实现功能,因为实际项目中没有这个需求,原谅楼主太懒没有写出代码··· 目前line从手松开点回弹时,仅用了三个点位,line显示就是两条线段,会有一个明显的夹角,如果要更加自然一点,可以多增加一些点位,利用正弦曲线值乘一个系数,原理与弹跳原理一样
在场景中创建一个空物体,添加LineRenderer 组件,设置合适的参数(材质、宽度等),然后挂载该脚本就OK啦~
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Line : MonoBehaviour
{
//取样点的数量,数量越多,线条越圆滑
[SerializeField] int pointCount = 20;
//回弹速度
[SerializeField, Range(0, 1)] float lineGoBackSpeed = 0.3f;
//弹跳衰减参数
[SerializeField, Range(0, 0.2f)] float bounceReduceParame = 0.1f;
//线条弹跳频率
[SerializeField, Range(0, 2.0f)] float bounceFrequencyParame = 0.8f;
//弹跳高度参数,最大弹跳高度
[SerializeField] float bounceHighParame = 0.05f;
//LineRenderer组件
LineRenderer lineRenderer;
//line起点终点
Vector2 startPoint, endPoint;
//回弹线条取样点,取正弦曲线上半部分,作为线条弯曲最大曲线,在此基础上,乘以动态系数,改变回弹幅度
List<float> pointList;
void Start()
{
//测试
Init(Vector2.zero, Vector2.one * 2);
BackToFormerPosition(new Vector2(1, 0));
}
void Init(Vector2 startPos, Vector2 endPos)
{
//获取组件
lineRenderer = GetComponent<LineRenderer>();
//设置向量起点与终点,保证向量在一二象限,即方向与Y轴正方向夹角不大于90度
if (startPos.y > endPos.y)
{
startPoint = endPos;
endPoint = startPos;
}
else
{
startPoint = startPos;
endPoint = endPos;
}
//获取0--π的sin值,即正弦函数上半部分,按指定数量均分,获取取样点,作为弹跳曲线的原型
if (pointList == null)
{
pointList = new List<float>();
for (float i = 0; i <= Mathf.PI; i += Mathf.PI / (pointCount - 1))
{
pointList.Add(Mathf.Sin(i));
}
pointList.Add(0);
}
}
//从给定点,回弹到原位置
public void BackToFormerPosition(Vector2 nowPoint)
{
//计算给定点,到原位置所在直线的距离,正负表示在向量的左右侧
float middlePointHigh = (nowPoint.y - startPoint.y) * (nowPoint.x - endPoint.x) - (nowPoint.y - endPoint.y) * (nowPoint.x - startPoint.x);
//计算弹跳高度,设置最大、最小幅度,避免高度太大或太小
float bounceHigh = middlePointHigh * bounceHighParame;
if (bounceHigh > 1)
bounceHigh = 1;
else if (bounceHigh < -1)
bounceHigh = -1;
else if (bounceHigh > -0.2f & bounceHigh < 0f)
bounceHigh = -0.2f;
else if (bounceHigh < 0.2f & bounceHigh >= 0)
bounceHigh = 0.2f;
//
StartCoroutine(SetLineOriginal(nowPoint, bounceHigh));
}
//从给定点回弹到原位置
IEnumerator SetLineOriginal(Vector2 middlePoint, float bounceHigh)
{
//目标点为原位置的中点,中间点从给定位置移动到目标点
Vector2 targetPoint = (startPoint + endPoint) / 2;
//设置lineRenderer参数
lineRenderer.positionCount = 3;
lineRenderer.SetPosition(0, startPoint);
lineRenderer.SetPosition(2, endPoint);
lineRenderer.SetPosition(1, middlePoint);
float distance = Vector2.Distance(middlePoint, targetPoint);
float speed = distance * lineGoBackSpeed;
//持续移动lineRenderer中点,直到与很接近原位置
while (distance > 0.05f)
{
middlePoint = Vector2.MoveTowards(middlePoint, targetPoint, speed);
lineRenderer.SetPosition(1, middlePoint);
distance = Vector2.Distance(middlePoint, targetPoint);
yield return new WaitForSeconds(0.02f);
}
//恢复直线状态
lineRenderer.positionCount = 2;
lineRenderer.SetPosition(0, startPoint);
lineRenderer.SetPosition(1, endPoint);
//回弹到原位置后,开始线条跳动效果
StartCoroutine(SetLineBounce(bounceHigh));
}
//线条跳动效果
IEnumerator SetLineBounce(float bounceHigh)
{
//起点与终点在X/Y方向的距离
float distanceX = endPoint.x - startPoint.x;
float distanceY = endPoint.y - startPoint.y;
//向量与水平方向的夹角,单位为弧度
float vectorAngle = Mathf.Acos(Vector2.Dot((endPoint - startPoint).normalized, Vector2.right));
//设置lineRenderer点数量
lineRenderer.positionCount = pointCount;
//angle累加,计算其正弦值,得到正弦曲线,0 -> 1 -> 0 -> -1 -> 0 ,依次作为line弹跳幅度系数,正负表示方向
float angle = 0;
//衰减数值,影响弹跳幅度,数值越来越小,弹跳幅度越来越小,直到弹跳停止
float reduce = 1;
while (reduce >= 0.02f)
{
//angle累加,bounceFrequencyParame会决定弹跳幅度的变化速度
angle += bounceFrequencyParame;
//插值计算reduce值
reduce = Mathf.Lerp(reduce, 0, bounceReduceParame);
//设置lineRenderer点位,线条弹起高度 = 幅度系数 * 衰减系数 * 高度系数
SetBounceLine(Mathf.Sin(angle) * reduce * bounceHigh, distanceX, distanceY, vectorAngle);
yield return new WaitForSeconds(0.02f);
}
//恢复直线状态
lineRenderer.positionCount = 2;
lineRenderer.SetPosition(0, startPoint);
lineRenderer.SetPosition(1, endPoint);
}
//偏移line的点
void SetBounceLine(float highParam, float distanceX, float distanceY, float vectorAngle)
{
//计算每个点的坐标位置
//计算公式:
//偏移点(x1,y1),向量起点(x0,y0),起点到终点的向量与X轴夹角 a
//在一二象限
//x1 = x0 - sin(a), y1 = y0 + cos(a)
//在三四象限
//x1 = x0 + sin(a), y1 = y0 + cos(a)
//所以在初始化的时候,互换起点与终点,保证向量在一二象限,可以方便计算
float xx, yy;
for (int i = 0; i < pointCount; i++)
{
xx = startPoint.x + distanceX * i / (pointCount - 1) - pointList[i] * highParam * Mathf.Sin(vectorAngle);
yy = startPoint.y + distanceY * i / (pointCount - 1) + pointList[i] * highParam * Mathf.Cos(vectorAngle);
lineRenderer.SetPosition(i, new Vector2(xx, yy));
}
}
}