前言

最近在自己做开发的时候突然想在2D游戏做一个扔出锁链的效果,但是在网上感觉相关的实现教程都略深奥了一点(=v=、),于是自己研究了一下,也借鉴了社区里的一些讲解和教程,终于搞明白了实现方法,在这里和大家分享一下~

HingeJoint2D铰链关节的使用要点

HingeJoint2D(铰链关节)是Unity自带的物理关节组件之一,主要用于实现两个刚体之间相互勾连旋转的效果(比如钟摆的效果、行星绕恒星旋转的效果等等),除此之外,Unity中还带有FixedJoint(固定关节)、SpringJoint(弹簧关节)、CharacterJoint(角色关节)等多种关节组件,具体用法可以阅读官方文档查看,这里就不详细介绍了。

这一节主要讲一些在HingeJoint2D组件在实现锁链过程中需要注意的一些用法。

在本节中HingeJoint2D面板上主要需要注意的属性有三个:

  • Connected Rigidody:连接的目标刚体,选定物体后本物体会绕着目标物体的锚点为轴心转动(公转,可以类比钟摆的固定点)

  • Anchor:自身锚点,自身在与目标物体的连接之外若受到其他的力则自身会绕着自身锚点旋转(自转的轨迹中心),默认在自身刚体中心,采用local坐标系,以物体本身位置为(0,0)计算

  • Connected Anchor:目标物体锚点,可以设置公转的中心点位于目标刚体的哪个位置,默认在目标刚体中心,采用local坐标系,以物体本身位置为(0,0)计算

  • Auto Configure Connected Anchor:如果勾选这一项的话Connected Anchor始终保持在目标物体RigidBody的位置,无法修改坐标值

除此之外在锁链的实现方法中还需要注意:

  • 锁片的长宽尺寸大小:在设置锁链间距的时候需要计算锁链大小,可以通过SpriteRender组件中的bound变量(变量类型为Vector2)获取当前scale下Sprite的实际长宽;

  • 由于anchor采用local坐标系,因此anchor坐标的数值不受transform中scale的影响,而是始终基于Sprite原本大小计算,因此在计算两个锁片之间的最大长度时,应该使用

     最大距离=锁环1的anchor坐标长度 * 锁环1的scale +锁环2的anchor坐标长度 *  锁环2的scale
    

2D锁链的实现方案

虽然Unity中很难实现完全的柔性材质,但是锁链本质上是由多个环状链节构成的刚体组合。因此实现锁链的关键在于模拟单个链节之间的勾连效果和旋转关系。

基于HingeJoint2D模拟刚体间转轴施力的效果,我们可以通过以下方案模拟链节之间的相互受力:

  1. 对每个单个链节的尾部添加HingeJoint2D,并将该链节的前一个链节的头部作为相互链接的影响对象(Connected Anchor),以此模拟锁链运动端对锁链固定端的施力作用,如下图所示:

  2. 其中锁链的起点链接在世界坐标系下事先设置的起点坐标(Connect RigidBody设为空):

  3. 尾部每生成一个新的锁片,将其尾部一端链接到移动端的位置坐标,头部一端与前一个生成的链节的尾部形成铰链关节的指定关系:

    由此通过HingeJoint2D的单向传递性设置实现锁链中链节相互施力、相互影响的效果。

实现代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//专门设置一个父物体来管理整条锁链
public class ChainController : MonoBehaviour
{//单个链节的数据结构,用来引用链节对应的物体和Joint组件 public class Node {public HingeJoint2D headJoint, tailJoint;//单个链节对象的头部铰链和尾部铰链public GameObject node;public Node(GameObject self){node = self;headJoint = node.GetComponents<HingeJoint2D>()[0];tailJoint = node.GetComponents<HingeJoint2D>()[1];}}public GameObject nodeFront, nodeSide;//正面的链节和横放的链节预制体List<Node> chain;//所有链节以队列的形式保存引用int maxLength;//链节最大数量Vector3 headPos, tailPos;//锁链两端的位置float nodeWidth, nodeHeight;//链节的尺寸数据float anchorBios;//锚点偏移Sprite中心的横向距离(初步定是0.55f)float scale = 0.3f;private void Awake(){  anchorBios = 0.55f;maxLength = 15;chain = new List<Node>();}void Start(){tailPos = new Vector3(0, 0, 0);headPos = new Vector3(0, 0, 0);nodeWidth = nodeFront.GetComponent<SpriteRenderer>().bounds.size.x;}void FixedUpdate(){//鼠标按下位置为锁链起点if (Input.GetMouseButtonDown(0)){tailPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);tailPos = new Vector3(tailPos.x, tailPos.y, 0);}//如果鼠标处于按下状态则实时更新锁链信息(拖动)if (Input.GetMouseButton(0)){headPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);headPos = new Vector3(headPos.x, headPos.y, 0);FixChainList();}//松开鼠标锁链消失if (Input.GetMouseButtonUp(0)){//ClearAllChain();}}int nodeCount;//当前拖动距离下链节的数量//按住鼠标移动时锁链实时变化void FixChainList(){//大致计算锁链的链节数量float tempCount = ((headPos - tailPos).magnitude - anchorBios * scale * 2) / (anchorBios * scale * 2) + 1;nodeCount =  (int)tempCount;nodeCount = nodeCount < 0 ? 0 : nodeCount;//确保不为负数nodeCount = nodeCount > maxLength ? maxLength : nodeCount;//确保小于锁链最大长度//计算当前数目的链节拉直时另一端的位置,以此修正锁链移动端的位置headPos = tailPos + (headPos - tailPos).normalized * (anchorBios * scale * 2 * nodeCount+0.0005f);//调整当前生成链节数量while (chain.Count < nodeCount){AddNodeToChain(chain);}while (chain.Count > nodeCount){DeleteHeadNode(chain);}//鼠标控制锁链延伸if (chain.Count > 0){chain[chain.Count - 1].headJoint.enabled = true;chain[chain.Count - 1].headJoint.connectedAnchor = headPos;}}//在锁链移动端创建新链节GameObject newNode;void AddNodeToChain(List<Node> list){float theta = Mathf.Atan2(headPos.y - tailPos.y, headPos.x - tailPos.x) + Mathf.PI;//确认锁链方向//创建首个链节if (list.Count == 0){newNode = Instantiate(nodeFront, this.transform).gameObject;newNode.transform.position = new Vector3(tailPos.x - anchorBios * scale * Mathf.Cos(theta), tailPos.y - anchorBios * scale * Mathf.Sin(theta), 0);newNode.transform.rotation = Quaternion.Euler(0, 0, theta * Mathf.Rad2Deg);list.Add(new Node(newNode));//设置第一个链节时,首尾分别链接起始位置和鼠标位置list[0].tailJoint.autoConfigureConnectedAnchor = false;list[0].tailJoint.connectedAnchor = tailPos;list[0].headJoint.autoConfigureConnectedAnchor = false;list[0].headJoint.connectedAnchor = headPos;newNode.SetActive(true);//预制体的Active设置为false,确保设置位置后在再显示return;}//在移动端生成新链节newNode = list.Count % 2 == 0 ? nodeFront : nodeSide;Vector3 temp = list[list.Count - 1].node.transform.position - new Vector3(anchorBios * scale * 2 * Mathf.Cos(theta), anchorBios * scale * 2 * Mathf.Sin(theta), 0);//设置新链节的位置和角度list[list.Count - 1].headJoint.enabled = false;//如果进行双向链接设置需要将这一句注释掉newNode = Instantiate(newNode, temp, Quaternion.Euler(0, 0, theta * Mathf.Rad2Deg)).gameObject;newNode.transform.parent = this.transform;list.Add(new Node(newNode));//进行链接关系的初始设置list[list.Count - 1].tailJoint.connectedBody = list[list.Count - 2].node.GetComponent<Rigidbody2D>();list[list.Count - 1].tailJoint.autoConfigureConnectedAnchor = false;list[list.Count - 1].tailJoint.connectedAnchor = new Vector2(-1 * anchorBios, 0);//这个取决于锁片与上一个锁片之间旋转关系的位置list[list.Count - 1].headJoint.connectedBody = null;list[list.Count - 1].headJoint.autoConfigureConnectedAnchor = false;list[list.Count - 1].headJoint.connectedAnchor = headPos;newNode.SetActive(true);//预制体的Active设置为false,确保设置位置后在再显示//双向设置也不会影响锁链的运动效果//list[list.Count - 2].headJoint.connectedBody = list[list.Count - 1].node.GetComponent<Rigidbody2D>(); ;//list[list.Count - 2].headJoint.autoConfigureConnectedAnchor = false;//list[list.Count - 2].headJoint.connectedAnchor = new Vector2(anchorBios, 0);}//锁链缩短时优先删除移动端链节,并将前一个链节链接到移动端位置void DeleteHeadNode(List<Node> list){Node delete = list[list.Count - 1];list.Remove(delete);GameObject.Destroy(delete.node);if (list.Count > 0){list[list.Count - 1].headJoint.connectedBody = null;list[list.Count - 1].headJoint.autoConfigureConnectedAnchor = false;list[list.Count - 1].headJoint.connectedAnchor = list[list.Count - 1].node.transform.InverseTransformPoint(headPos);chain[chain.Count - 1].headJoint.enabled = true;}}//松开鼠标时清除所有锁链,这里最好使用对象池(懒人躺倒)void ClearAllChain(){while (chain.Count > 0){DeleteHeadNode(chain);}}
}

实现效果

锁链的摆动效果:

拖动锁链自动生成:

初步的物理效果:

结语

这里只是简单考虑了一下锁链的基础物理逻辑和实现,锁链这件表现上还有很多需要注意的细节要处理,后面有时间应该会继续研究一下碰撞等更加细节的表现,希望这些知识对各位有帮助,谢谢~!

Unity2D锁链效果实现(一)——HingeJoint2D组件的使用相关推荐

  1. Vue实现跑马灯效果以及封装为组件发布

    Vue 实现跑马灯效果 前言 最近做活动需要做跑马灯效果,其他同事也有实现,本来打算复制他们代码,发现都是使用setInterval实现了,也没有封装为组件,所以自己用CSS3实现了一下跑马灯效果,并 ...

  2. 前端悬浮窗效果_Flutter自绘组件:微信悬浮窗(一)

    看微信公众号的时候时常会想退出去回复消息,但又不想放弃已经阅读一半的文章,因为回复信息后再从公众号找到该篇文章之间有不必要的时间花费,微信悬浮窗的出现解决了这个烦恼,回复完消息之后只需要点击悬浮窗就可 ...

  3. Flutter - 一个fultter练习项目(仿写微信UI、实现一些常用效果、封装通用组件和工具类)

    demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新,请前往github查看最新代码 pwd:123456 代码不定期更新 注:Flut ...

  4. 微信小程序实现3d轮播图效果(非swiper组件)

    首先上一下效果图: 在做的时候首先想到的是用swiper组件但是发现swiper组件放进去图片的时候会没有3d的效果,原因是swiper组件自带的style属性限制了3d效果所需要的属性,所以单独写了 ...

  5. php广告轮播效果,使用swiper组件实现轮播广告效果

    这次给大家带来使用swiper组件实现轮播广告效果,使用swiper组件实现轮播广告效果的注意事项有哪些,下面就是实战案例,一起来看一下. 1.安装swipernpm install swiper@3 ...

  6. vue 数字动画递增_数字滚动动画效果 vue组件化

    主要思路是利用css属性writing-mode:vertical-lr:通过设定最大字符长度,补零,去循环,然后添加style translate和transition来完成想要的效果: 子组件根据 ...

  7. Unity2D 入门教程

    Unity2D 入门教程 常用操作 通用操作 游戏状态进行保存 使用 TileMap 绘制地图 实现碰撞体效果 移动 Bug 的修复 实现人物控制 动画系统 镜头控制 脚本控制 Cinemachine ...

  8. Ant Design源码分析(三):Wave组件

    Wave组件效果预览 在上一篇文章Button组件的源码分析中遇到了一个Wave组件, Wave组件在Ant design中提供了通用的表单控件点击效果,在自己阅读源码之前,也并没有过更多留心过在这些 ...

  9. 小程序 - 效果处理之技巧合集(更新中...)

    巧用Console.log(event) Event::事件对象,方法在执行的时候,当前环境携带的一些信息 可以打印出来.获取很多信息,根据这些信息再度寻找你需要的信息的路径.如小程序里的event. ...

最新文章

  1. IT行业: 嵌入式工程师的进阶之路
  2. 完整的聚合支付中心设计方案
  3. Java面向对象(1) —— 封装
  4. feeder link
  5. 爱立信数据分析解决方案抓住物联网发展机遇
  6. 小米捐赠5亿启动“小米青年学者”项目 5年支持100所高校
  7. 马云创造的就业机会多还是毁掉的就业机会多?
  8. HTTP报文格式详解
  9. 西北大学计算机课表,西北大学课表
  10. linux主机安装sctp协议栈
  11. .com与.top域名有什么区别
  12. 洁厕灵的工作原理、使用方法和危害 //2021-1-27
  13. 输入日期判断这一年的第几天
  14. 07年7月仲夏在红瓦绿树下的青岛散心~
  15. Monte-Carlo算法(基本原理,理论基础,应用实践)
  16. php mysql插入的数据有引号,PHP引号转义中解决POST,GET,Mysql数据自动转义问题
  17. 评测i9 13900hx和​​R9 7940HS选哪个 酷睿i913900hx和​​锐龙R97940HS对比
  18. 滴滴当年重创的安全事件,也会重创货拉拉吗?
  19. MATLAB 求任意一个数的所有因子
  20. php文件伪装成jsp,Content-Type伪装 - 将jsp伪装成css

热门文章

  1. 数据传输:XML JSON 你不得不知道的知识
  2. 全球与中国防护和海洋涂料市场深度研究分析报告
  3. python实现 基于邻域的算法之协同过滤(电影推荐实战)
  4. 加班多无聊过来看看,只有程序员才懂的幽默
  5. 【XBMC教程/kodi】有关XBMC软解、硬解以及外挂播放的详细解答
  6. 【GB】国标查询网站
  7. 百度、清华研发盲人搜索 为盲人提供无障碍信息服务
  8. [英语歌曲]Just one last dance
  9. 京东模拟登陆,仅实现登陆功能(仅交流学习使用,爬虫起点)
  10. 小程序,大世界-web点播直播入门-代码的自我修养-进阶的直梯