效果、源码下载
视频展示,源码及demo下载
gif演示
(千束是刻意起飞的,不是bug)

讯享网
前言
我之前在看到网上有人实现了网页版的sakana,感觉超级有意思,于是动手用unity实现了一下。
整个代码就弹簧效果上实现起来稍微麻烦一点,用了点数学物理的知识。Unity原本有个弹簧关节 (Spring Joint)插件可以实现弹簧效果。但是我试了一下,用不会、不好用,还是自己查资料、动手写来的方便、自由。
虽然这个效果看起来很简单,但是做起来实在是容易踩坑。
另外,此项目的代码是对弹簧进行简单的模拟,简化版的弹簧运动,仅用到了高中物理或者说大物的知识,不是硬核地模拟真实的弹簧。
本文对代码思路进行简单地阐述,源代码已经写了很多注释了。
效果拆分

我把最终效果拆分成一下几个部分:
- 鼠标拖动
- 物体相对弹簧的径向运动
- 物体相对弹簧的摆动
- 物体朝向
void FixedUpdate() {
//鼠标拖动 OnMouseDown(); //物体朝向 //3D项目用transform.LookAt就可以简单实现朝向,2D的话就得自己手动来了 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还会用来计算摆动)。
//x = v * dt + 0.5f * a * dt * dt //沿弹簧方向的运动 void SpringMove() {
transform.position += v * time;//速度产生的位移 v *= aS;//空气阻力会使速度减小 //AddForce(new Vector3(0, 9.8f, 0));//重力 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); //水平l 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;//衰减 //求角位移(乘以180/PI 是为了将弧度转换为角度) float thelta = ow * time * 180.0f / Mathf.PI / 2; //绕targetObject圆点,rotateAxi旋转轴,旋转位移thelta transform.RotateAround(targetObject.position, rotateAxi, thelta); //print("ow:" + ow + " alpha:"+alpha + "r:"+ r + " l:"+l); }
物体朝向
为了让物体看起来不那么生硬,更有弹簧旋转起来的效果,我让物体始终朝向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; // Start is called before the first frame update 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; //PC,Update时候 //aS = 0.9995f; //aR = 0.995f; aS = 0.9999f; aR = 0.97f; //An,因为帧率的影响,这是放在update时候 //aS = 0.9999f; //aR = 0.9f; start = false; //targetObject = GameObject.Find("Bottom1").transform; //旋转轴 //注意!!!,gameobjec和target在世界中需要错开一点角度,如果都在一条竖线上的出来的旋转轴是0向量,无法继续计算 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(); } }
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/14569.html