在做一個遊戲時一個地方需要鎖鏈,就在這記錄一下
主要考慮方面:
1.鎖鏈生成的主要輸入:
鎖鏈的兩端位置
2.鎖片總數限制
通過鎖鏈兩端位置、單個鎖片的長度、鎖片間的重疊比例,決定鎖片總數
3.鎖鏈長度實時變化
這裏考慮的是,一條動態長度的鎖鏈,當輸入值發生變化時,鎖鏈長度將自動改變。
4.誤差過濾
在生成最後一塊鎖片時,應考慮 鉸連接描點 的位置。因爲在生成指定長度的鎖鏈生成時,鎖鏈總長度不可能等於輸入的指定長度。
提示: 2D關節 顧名思義,關節將遊戲物體連接在一起。
不清楚的建議去Unity官方文檔看看,將所有關節儘可能的都用一遍。
5.鎖鏈兩端是否固定在空間某點或某物體上
需要注意鉸連接的一特點:鉸連接的剛體連接(Connected Rigid Body)爲空的話“ 鉸鏈連接” 的另一端將固定到“ 連接錨點”設置所定義的空間中的一點。
可由此實現固定於空間一點,至於固定到某物體上,只需將目標物體的剛體作爲本鉸連接的剛體連接(Connected Rigid Body)即可。
基本實現
公開方法
//在兩點之間生成片段
public void DrawLength(Vector2 l,Vector2 r)
//清除不使用的片段,清除內存池中的過多對象
public void Clear()
調用案例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MTest{
public class Manager : MonoBehaviour {
public Transform left;
public Transform right;
public Detent detent;
void Update () {
detent.DrawLength(left.position,right.position);
}
}
}
鎖鏈類
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Detent : MonoBehaviour {
//單個鎖片 信息記錄
class Point{
public static Point left;
public static Point right;
public bool active{get{return root == null ? false : root.activeSelf;}}
public GameObject root;
public HingeJoint2D joint;
public HingeJoint2D joint2;
public Rigidbody2D rigidbody;
public Point(GameObject obj){
root = obj;
HingeJoint2D[] joints = obj.GetComponents<HingeJoint2D>();
joint = joints[0];
joint2 = joints[1];
rigidbody = obj.GetComponent<Rigidbody2D>();
}
public void Init(string name,Vector3 pos,Quaternion qua){
root.name = name;
root.transform.position = pos;
root.transform.rotation = qua;
rigidbody.velocity = Vector2.zero;
joint2.enabled = false;
Show();
}
public void Show(bool show = true){
if(show != root.activeSelf) root.SetActive(show);
}
}
public Transform content; //片段存放目標
private List<Point> mem = new List<Point>(); //片段內存池
public SpriteRenderer[] prefabs; //鎖片 預製體
//當考慮生成的初始鎖鏈爲曲線時可自行改動長度並重新設計輸入參數形式,這裏沒實現只了預留了下
public bool[] HangSegment; //鎖片兩端 是否固定與空間中
public bool useBendLimit = false; //使用片段彎曲限制
public int bendLimit = 45; //片段彎曲限制角度
public bool useBreakForce=false; //使用片段斷裂界限
public float BreakForce = 100; //片段斷裂峯值 --力
private Vector2 leftP; //左端點位置
private Vector2 rightP; //右端點位置
private Vector2 rightOff; //右端點偏移
[Range(-0.5f,0.5f)] public float overlapFactor; //重疊比例
[Range(0,0.5f)] public float minError = 0.25f; //允許的最小誤差
public int maxLength = 50; //最大片段數
float segmentHeight; //片段長度
float yScale; //片段長度縮放
private void Awake() { Init(); }
//初始化
public void Init(){
yScale = prefabs[0].transform.localScale.y;
segmentHeight = prefabs[0].bounds.size.y * (1 + overlapFactor);
}
//在兩點之間生成片段
public void DrawLength(Vector2 l,Vector2 r){
if(leftP != l || (rightP + rightOff)!= r){
leftP = l;
rightP = r;
Debug.DrawLine(leftP,rightP);
}
else return;
int i = 0;
float distance = Vector2.Distance(rightP,leftP);
int segmentCount = (int)(distance / segmentHeight);
float error = distance - segmentCount * segmentHeight;
bool fixError = error > minError * segmentHeight || segmentCount == 0;
int length = segmentCount;
if(maxLength <= segmentCount){
length = maxLength;
fixError = false;
rightOff = rightP - ((rightP-leftP).normalized * maxLength * segmentHeight + leftP);
rightP -= rightOff;
}
else{
rightOff = Vector2.zero;
}
for (; i < length; i++) AddPoint(i);
if(fixError) AddPoint(i++,true);
if(mem.Count > 0 && i > 0) Point.right = mem[i-1];
for (; i < mem.Count; i++) mem[i].Show(false);
if(!HangSegment[0]){
Point.left.joint.enabled = false;
}
if(HangSegment[1]){
Point.right.joint2.autoConfigureConnectedAnchor = false;
Point.right.joint2.connectedBody = null;
if(fixError){
Point.right.joint2.anchor = Point.right.root.transform.InverseTransformPoint(rightP);
}
else{
Point.right.joint2.anchor = new Vector2(0, segmentHeight / 2);
}
Point.right.joint2.connectedAnchor = rightP;
Point.right.joint2.enabled = true;
}
}
//清除不使用的片段
public void Clear(){
int i = 0,sum = mem.Count;
for (; i < sum; i++) if(!mem[i].active) break;
for (int j = i; j < sum; j++){
Destroy(mem[i].root);
mem.RemoveAt(i);
}
}
//添加片段 <i>片段順序 <fix>是否消除誤差
private void AddPoint(int i,bool fix = false){
Point point;
if(i < mem.Count){
point = mem[i];
}
else{
GameObject obj = Instantiate(prefabs[i % 2],content).gameObject;
point = new Point(obj);
mem.Add(point);
}
float theta = Mathf.Atan2(rightP.y - leftP.y, rightP.x - leftP.x);
float dtheta = theta * Mathf.Rad2Deg;
if (dtheta > 180) dtheta -= 360;
else if (dtheta < -180) dtheta += 360;
float dx = segmentHeight * Mathf.Cos(theta);
float dy = segmentHeight * Mathf.Sin(theta);
float startX = leftP.x + dx / 2;
float startY = leftP.y + dy / 2;
point.Init(
"Segment_" + i+(fix ? "_fix":""),
new Vector3(startX + dx * i, startY + dy * i),
Quaternion.Euler(0, 0, theta * Mathf.Rad2Deg - 90)
);
if(i == 0){
Point.left = point;
point.joint.connectedAnchor = leftP;
point.joint.anchor = new Vector2(0, -segmentHeight / 2);
}
else{
AddJoint(dtheta, segmentHeight/ yScale, i-1, point);
}
}
//爲片段添加關節
private void AddJoint(float dthetaT, float segmentHeightT, int index, Point point)
{
point.joint.connectedBody = mem[index].rigidbody;
point.joint.anchor = new Vector2(0, -segmentHeightT / 2);
point.joint.connectedAnchor = new Vector2(0, segmentHeightT / 2);
}
//使用片段角度限制與片段斷裂峯值
private void LimitAndBreak(float dthetaT,Point point){
if (useBendLimit)
{
point.joint.useLimits = true;
point.joint.limits = new JointAngleLimits2D()
{
min = dthetaT - bendLimit,
max = dthetaT + bendLimit
};
}
if (useBreakForce) point.joint.breakForce = BreakForce;
}
}
效果參數 設置,或者可自行導入 package