效果、源码下载
视频展示,源码及demo下载
gif演示
(千束是刻意起飞的,不是bug)
前言
我之前在看到网上有人实现了网页版的sakana,感觉超级有意思,于是动手用unity实现了一下。
整个代码就弹簧效果上实现起来稍微麻烦一点,用了点数学物理的知识。Unity原本有个弹簧关节 (Spring Joint)插件可以实现弹簧效果。但是我试了一下,用不会、不好用,还是自己查资料、动手写来的方便、自由。
虽然这个效果看起来很简单,但是做起来实在是容易踩坑。
另外,此项目的代码是对弹簧进行简单的模拟,简化版的弹簧运动,仅用到了高中物理或者说大物的知识,不是硬核地模拟真实的弹簧。
本文对代码思路进行简单地阐述,源代码已经写了很多注释了。
效果拆分
我把最终效果拆分成一下几个部分:
- 鼠标拖动
- 物体相对弹簧的径向运动
- 物体相对弹簧的摆动
- 物体朝向
void FixedUpdate()
{
OnMouseDown();
Look2D();
OnMouseUp();
if (start)
{
SpringMove();
SpringSwing();
}
}
有个小细节,我把流程代码放在FixedUpdate而不是Update里面,是因为不同平台运行代码时候帧率不一样,通过Time.deltaTime计算出来的运动效果会有差别。FixedUpdate是固定时间执行一次,就能在不同平台达到相近的效果。
鼠标拖动
使用协程OnMouseDow来检测鼠标的动作,这个方法只会检测挂了此脚本的物体。
屏幕坐标和世界坐标之间的转换
另外添加了limit限制拖动范围
IEnumerator OnMouseDown()
{
Vector3 targetScreenPos = Camera.main.WorldToScreenPoint(transform.position);
var offset = transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetScreenPos.z));
while (Input.GetMouseButton(0))
{
start = false;
Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetScreenPos.z);
var targetPos = Camera.main.ScreenToWorldPoint(mousePos) + offset;
if (Vector3.Distance(targetPos, oriPosition) <= limit)
{
transform.position = targetPos;
}
yield return new WaitForFixedUpdate();
}
}
物体相对弹簧地径向运动
物体直线运动公式
x = v * dt + 0.5f * a * dt * dt (代码里面不要写1 / 2,因为 1 / 2 等于0)
代码中的time是Time.fixedDeltaTime,相当于公式中的dt,另外,本段代码的运算采用的是矢量,所以速度用了个向量。
在SpringMove()中先计算当前速度的位移,在AddForce()中计算力作用下的位移,力来源于弹簧,设弹簧的劲度系数为k, scalarF = k *(dAB.magnitude - L),设置了一个bottom点,表示弹簧的另外一段(没有连接运动物体的那一段)(这个bottom还会用来计算摆动)。
void SpringMove()
{
transform.position += v * time;
v *= aS;
Vector3 dAB = transform.position - bottom;
float scalarF = k * (dAB.magnitude - L);
AddForce(-dAB.normalized * scalarF);
}
void AddForce(Vector3 force)
{
Vector3 a = force / rb.mass;
v += a * time;
transform.position += 0.5f * a * Mathf.Pow(time, 2);
}
物体相对弹簧的摆动
摆动我是让物体绕着一个点做摆动,再用transform.RotateAround方法来计算,参数是旋转轴,旋转点,旋转位移。
向量求叉积可以判断当前物体运动到原点左边还是右边,所以求了旋转轴和一个axis,这样能计算运动方向。
void SpringSwing()
{
float r = Vector3.Distance(transform.position, targetObject.position);
float l = Vector3.Distance( new Vector3(targetObject.position.x, transform.position.y, transform.position.z)
, transform.position);
Vector3 axis = Vector3.Cross( transform.position - targetObject.position, Vector3.down);
if( Vector3.Dot(axis, rotateAxi) < 0)
{
l = -l;
}
float alpha = (-g) * l / Mathf.Pow(r, 2);
ow += alpha * time;
ow *= aR;
float thelta = ow * time * 180.0f / Mathf.PI / 2;
transform.RotateAround(targetObject.position, rotateAxi, thelta);
}
物体朝向
为了让物体看起来不那么生硬,更有弹簧旋转起来的效果,我让物体始终朝向bottom点。
在3d里面有LookAt直接用,但是2d要自己实现一下.
用物体的坐标与bottom(被看向的点)两个点的出一个向量,让物体的旋转性保持这个向量的方向即可。
void Look2D()
{
Vector3 v = targetObject.position - transform.position;
v.z = 0;
Quaternion rotation = Quaternion.FromToRotation(Vector3.up, -v);
transform.rotation = rotation;
}
其他代码
Rigidbody2D rb;
Vector3 v;
float p;
Vector3 bottom;
float k;
float L;
float aS;
float aR;
bool start;
public Transform targetObject;
float g = 10000.8f;
float ow = 0;
Vector3 rotateAxi;
float time;
float limit;
Vector3 oriPosition;
AudioSource audio;
void Start()
{
rb = gameObject.GetComponent<Rigidbody2D>();
bottom = targetObject.transform.position;
k = 300f;
L = gameObject.transform.position.y - bottom.y;
limit = 0.8f*L;
oriPosition = transform.position;
aS = 0.9999f;
aR = 0.97f;
start = false;
rotateAxi = Vector3.Cross((transform.position - targetObject.position), Vector3.down);
rb.gravityScale = 0;
time = Time.fixedDeltaTime;
audio = GetComponent<AudioSource>();
}
IEnumerator OnMouseUp()
{
if (Input.GetMouseButtonUp(0))
{
start = true;
audio.Play();
yield return new WaitForFixedUpdate();
}
}
|