2025年Lycoris Recoil再现!Unity实现Sakana~,代码思路解析,代码开源,Unity弹簧效果

Lycoris Recoil再现!Unity实现Sakana~,代码思路解析,代码开源,Unity弹簧效果效果 源码下载 视频展示 源码及 demo 下载 gif 演示 千束是刻意起飞的 不是 bug 前言 我之前在看到网上有人实现了网页版的 sakana 感觉超级有意思 于是动手用 unity 实现了一下 整个代码就弹簧效果上实现起来稍微麻烦一点 用了点数学物理的知识

大家好,我是讯享网,很高兴认识大家。

效果、源码下载

视频展示,源码及demo下载

gif演示

(千束是刻意起飞的,不是bug)
在这里插入图片描述
讯享网

前言

我之前在看到网上有人实现了网页版的sakana,感觉超级有意思,于是动手用unity实现了一下。

整个代码就弹簧效果上实现起来稍微麻烦一点,用了点数学物理的知识。Unity原本有个弹簧关节 (Spring Joint)插件可以实现弹簧效果。但是我试了一下,用不会、不好用,还是自己查资料、动手写来的方便、自由。

虽然这个效果看起来很简单,但是做起来实在是容易踩坑。

另外,此项目的代码是对弹簧进行简单的模拟,简化版的弹簧运动,仅用到了高中物理或者说大物的知识,不是硬核地模拟真实的弹簧。

本文对代码思路进行简单地阐述,源代码已经写了很多注释了。

效果拆分

在这里插入图片描述
我把最终效果拆分成一下几个部分:

  1. 鼠标拖动
  2. 物体相对弹簧的径向运动
  3. 物体相对弹簧的摆动
  4. 物体朝向
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(); } } 
小讯
上一篇 2025-01-29 15:25
下一篇 2025-03-13 23:37

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/14569.html