如何在Unity中实现一根有弹性的鱼竿?
如何在Unity中实现一根有弹性的鱼竿? 问题可以简化, 变成一个数学问题. (UE4用着熟, 就拿这个当例子, 参考资料是Unity的)对于鱼竿来说, 其实我们只需要保证杆尾这个点是弹性运动即可, 那么问题就是变成了直杆与弯杆的端点位置的弹性运动, 这个只用一个PhysicsConstraint就可以搞定了:
在这里我只用一个角度约束
那剩下的问题就简单了, 由三点确定一条曲线. 根据Euler-Bernoulli beam theory, 这个在工程力学中常用的理论, 参考其中的Cantilever beams(悬臂梁)弯曲算法, 很容易就能得出我们想要的曲线:
因为我们只是想求弯杆在白杆与黄杆之间插值的比率w, 那么把公式进一步简化为:
w = x^2 * (3 * L- x) / (2 * L^3).其中x是离起点的距离, L是杆长度.
用BP实现一下就是:
这样我们就把曲线上的细分点位置求解出来了:
至此已经解答完毕题主关于"弹性"的问题了. 至于"鱼竿"的表现部分, 我也想到两种实现方法, 一种是SplineMesh, 一种是SkeletalMesh. 下面使用比较通用的蒙皮骨骼模型实现一下看看效果.
Maya里做根简单的鱼杆, 绑定上一串骨骼:
那导入引擎之后, 就是需要把之前计算的曲线上的点, 设置到骨骼上就可以了:
原作者为了保持杆的长度不变多加了一步, 把直线插值变成了弧形插值, 这个实现起来也很容易, 就不再做试验了:
参考资料:
https://www.youtube.com/watch?v=SYSeoTwG_qk 先上最终效果
*最后在挥动方面的角度计算仍有问题,有问题的代码放在最后请大手子们指点一下。
===================================================
昨晚看到了这题,但是觉得可能会上头导致失眠,所以没写,结果还是失眠了……
如果要我个人说的话,我建议让美术制作“轻度弯曲”“中度弯曲”“重度弯曲”三个动画,然后让程序根据挥动力度在这三者间切换就好。
现在我写的是另一个解决方案,即通过代码更新Mesh来实现。
废话不多说,关键代码如下:
首先是计算插值的代码,也是照搬的逍遥剑客的算法:
private float GetCantileverW(float x, float L)
{
return Mathf.Pow(x, 2f) * (3 * L - x) / (2 *Mathf.Pow(L, 3f));
}
其次是更新Mesh用到的代码,代码都在注释里我就不废话解释了:
public void RefreshRod(float distance)
{
CantileverDistance = distance;
//第一步,制造一个鱼竿的Mesh
//1.0,清理数据
RodMesh.Clear();
colors.Clear();
vertices.Clear();
triangles.Clear();
//1.1,拿到每个节点的长度
//1.1.1,计算弯曲之后的鱼竿节点
List<Vector3> rodeList = new List<Vector3>();
for (int i = 0; i < RodeCount; i++)
{
rodeList.Add(new Vector3(0, RodeLength * i, CantileverDistance * GetCantileverW(RodeLength * i, RodeLength * (RodeCount -1))));
}
//1.1.2,由于弯曲之后鱼竿长度会变相增长,所以进行一次缩放
float realLength = 0f;
for (int i = 1; i < RodeCount; i++)
{
realLength += (rodeList - rodeList).magnitude;
}
for (int i = 1; i < RodeCount; i++)
{
rodeList *= RodeLength * (RodeCount - 1) / realLength;
}
//1.1.3*,绘制顶端到钓块的线
Debug.DrawLine(
fish.transform.position,
transform.TransformPoint(rodeList)
);
//1.2,根据每个节点长度,计算出面片
float unitEuler = 2 * Mathf.PI / (float) SplitCount;
for (int i = 1; i < RodeCount; i++)
{
for (int j = 0; j < SplitCount; j++)
{
float x1 = Radius * Mathf.Sin(unitEuler * j);
float x2 = Radius * Mathf.Sin(unitEuler * (j + 1));
float z1 = Radius * Mathf.Cos(unitEuler * j);
float z2 = Radius * Mathf.Cos(unitEuler * (j + 1));
AddQuad(
new Vector3(x1, rodeList.y, z1 + rodeList.z),
new Vector3(x2, rodeList.y, z2 + rodeList.z),
new Vector3(x1, rodeList.y, z1 + rodeList.z),
new Vector3(x2, rodeList.y, z2 + rodeList.z)
);
AddQuadColor(Color.gray, Color.white);
}
}
//1.3,计算上下顶面
for (int j = 0; j < SplitCount; j++)
{
float x1 = Radius * Mathf.Sin(unitEuler * j);
float x2 = Radius * Mathf.Sin(unitEuler * (j + 1));
float z1 = Radius * Mathf.Cos(unitEuler * j);
float z2 = Radius * Mathf.Cos(unitEuler * (j + 1));
AddTriangle(
new Vector3(x2, rodeList.y, z2 + rodeList.z),
new Vector3(x1, rodeList.y, z1 + rodeList.z),
new Vector3(0, rodeList.y, 0f)
);
AddTriangleColor(Color.white);
AddTriangle(
new Vector3(x1, rodeList.y, z1 + rodeList.z),
new Vector3(x2, rodeList.y, z2 + rodeList.z),
new Vector3(0, rodeList.y, rodeList.z)
);
AddTriangleColor(Color.gray);
}
//1.4,给Mesh赋值
RodMesh.vertices = vertices.ToArray();
RodMesh.triangles = triangles.ToArray();
RodMesh.colors = colors.ToArray();
RodMesh.RecalculateBounds();
RodMesh.RecalculateNormals();
}
效果如下:
(所以如果不缩放长度,弯曲程度越大则钓竿实际长度会无止境的增加)
这个函数只提供了单一方向的挥动,所以挥动方向由父物体提供,我们要另写一个新的挥杆者计算挥杆方向:
……
//由于FPSController的问题,不能从鼠标位置正确的得到旋转的速度,所以直接从GameObject的Rotation上拿
public Transform PlayerTrans;
public Transform CameraTrans;
public FishingRod Rod;
//挥动系数
public float Force;
//偏移累计量
public Vector2 Offset;
//目前偏移量
public Vector2 CurOffset;
//偏移回振力度
public float Resistance;
//偏移方向
public Vector3 OffsetDir;
//上次的角度
public Vector2 LastAngle;
public float test;
这里有一个问题,那就是我使用的是默认的FpsController,导致不能正确的用Input.mousePosition拿到旋转的方向和速度大小,所以这里我从Player的y轴上取到水平方向的旋转分量,再从Camera的x轴上取到垂直旋转的分量。
// Update is called once per frame
void Update ()
{
//获得当前挥动角度
Vector2 nowAngle = new Vector2(PlayerTrans.localRotation.y, CameraTrans.localRotation.x);
if (nowAngle != LastAngle)
{
//加入最大偏移限制,刚进入游戏会有一个1000+的巨额偏移
Offset += ((nowAngle - LastAngle).magnitude > 10f ? Vector2.zero : (nowAngle - LastAngle));
}
//对偏移累积量进行修正
float temp = 1 - (Resistance / Offset.magnitude);
Offset *= (temp >= 0 ? temp : 0);
//目前偏移量向累积量进行修正
Vector2 offsetBetweenTotalAndCurrent = Offset - CurOffset;
if (offsetBetweenTotalAndCurrent.magnitude < Resistance)
CurOffset = Offset;
else
CurOffset += offsetBetweenTotalAndCurrent.normalized * Resistance;
//根据得到的偏移量之模刷新Mesh
Rod.RefreshRod(CurOffset.magnitude * Force);
Rod.transform.localRotation = Quaternion.Euler(new Vector3(0f, - Vector2Angle(CurOffset) - 90f,0f));
LastAngle = nowAngle;
}
好吧,下面就是有问题的代码了,拿到这水平和垂直的旋转分量后我应该能计算出鱼竿的y轴欧拉角,不过不知道为啥总有一个方向挥不动,请大手子指教一下:
private float Vector2Angle(Vector2 e)
{
//e.x是水平旋转分量 e.y是垂直旋转分量
float res;
if (e.y == 0f)
{
res = e.x > 0 ? 0 : 180f;
print(res);
return res;
}
res = (float) Math.Atan(e.y / e.x);
res = res / Mathf.PI * 180;
if (e.y > 0)
res = Mathf.Abs(res);
else
res = -Mathf.Abs(res);
print(res);
return res;
}
我太累了,失眠+写完代码的空虚感……我先出去吃个饭然后睡会,下午还要写自己的东西……
P.S.顺带一提,实际上作为挥动钓竿的约束条件的CantileverDistance,实际上可以用正切函数从挥动角度上拿到的,因为挥动角度更好量化而挥动距离确不行。
P.S.2.还有就是实际上为了保证鱼竿在视野内的效果,在弯曲程度小的时候应该抬高鱼竿,在弯曲程度大的时候应放低鱼竿,保证鱼竿都在视野范围内。
P.S.N.还有一堆的东西,不说了,吃饭了……
===================================================
感谢 @逍遥剑客 提供的公式
感谢Unity C# Tutorials
感谢腾讯GAD的翻译 果断插件实现 可以用贝塞尔曲线实现,修改模型的mesh顶点:
GitHub:
https://github.com/dxxia/TestFishRod_BezierMesh 翻不了墙,看不了youtub,只能根据猜测回答题目
根据我模型的经验是
鱼竿模型的面数要给足
动画绑定的时候骨骼给足
应对多种情况要做出相应的动画片段
交给程序猿
玩家使用什么操作,就播放相应的动画 没看视频
实现方法两种 一种用代码实现动态骨骼 类似辫子和衣服摆件 控制硬度就可以
另外使用IK机制也可以模拟拉线弯曲 可以分享一下这个小Demo吗 我选择让美术做
页:
[1]