老規矩先上圖:
最近在做一個做一個遊戲,繩子纏繞在一起然後需要把繩子解開方能贏得遊戲。因為需要用到一個繩子的效果,網了查了不少資料一方面是用外掛Obi Rope 或 Megafiers 都可以實現,另一方面比較硬核的可以使用自己的演演算法也可以用關節什麼的。
但由於IOS14以上版本對程式碼稽核非常嚴格,很多外掛是無法使用的,只能自己寫演演算法了,網上很多方法都是使用關節實現的繩子效果,繩子並非一條連貫的繩子,以下分享一實現的方式:
一、使用鉸鏈關節(Hinge Joint)把球體串起,保留Sphere Collider和Rigidbody。
二、隨便找下物體掛上下面指令碼,使物體可以被拖拽。
using UnityEngine;
public class MousePosHandle : MonoBehaviour
{
#region 公有變數
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
#endregion
#region 私有變數
//------------------------------------------------------------------------------------
private Transform dragGameObject;
private Vector3 offset;
private bool isPicking;
private Vector3 targetScreenPoint;
//------------------------------------------------------------------------------------
#endregion
#region 公有方法
#endregion
#region 私有方法
//------------------------------------------------------------------------------------
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (CheckGameObject())
{
offset = dragGameObject.transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetScreenPoint.z));
}
}
if (isPicking)
{
//當前滑鼠所在的螢幕座標
Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetScreenPoint.z);
//把當前滑鼠的螢幕座標轉換成世界座標
Vector3 curWorldPoint = Camera.main.ScreenToWorldPoint(curScreenPoint);
Vector3 targetPos = curWorldPoint + offset;
dragGameObject.position = targetPos;
}
if (Input.GetMouseButtonUp(0))
{
isPicking = false;
if (dragGameObject != null)
{
dragGameObject = null;
}
}
}
//------------------------------------------------------------------------------------
/// <summary>
/// 檢查是否點選到cbue
/// </summary>
/// <returns></returns>
bool CheckGameObject()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo, 1000f))
{
isPicking = true;
//得到射線碰撞到的物體
dragGameObject = hitInfo.collider.gameObject.transform;
targetScreenPoint = Camera.main.WorldToScreenPoint(dragGameObject.position);
return true;
}
return false;
}
//------------------------------------------------------------------------------------
#endregion
}
三、 建模一個圓柱體網格大概是醬子,節不能太少因為繩子會不柔順,也不能太多因為 效能會傷不起。
四、加了一個可愛的貼圖
五、然後把球和網格放在一起。
六、建立MonoBehaviour,新增指令碼並掛上對應物體,通過先找到所有球
public MeshFilter TargetMesh; //網格
public Transform BallGroup; //球體集合(關節上所有球放這)
private List<Transform> m_listBall; //存放所有球體
private List<MeshData> m_listMeshData; //節點資料
void Start()
{
m_listBall = new List<Transform>();
foreach (Transform tran in BallGroup)
{
m_listBall.Add(tran);
}
}
七、提供一個方法,幫所有網格節點找到對應的球節點,網格自動尋找離自己最近的球為目標。
private Transform __FindNearest(Vector3 v3)
{
if (m_listBall != null)
{
float MaxDis = 999999;
Transform MaxTran = null;
for (int i = 0; i < m_listBall.Count; i++)
{
float curDis = Vector3.Distance(m_listBall[i].localPosition, v3);
if (curDis < MaxDis)
{
MaxDis = curDis;
MaxTran = m_listBall[i];
}
}
return MaxTran;
}
return null;
}
八、提供記錄節點資料的資料結構
public class MeshData
{
public int Index; //索引
public Transform target; //目標球球
public Vector3 offset; //與目標球球位置差距
}
九、在Start中呼叫以下程式碼找到各節點的目標與位差資訊
m_listMeshData = new List<MeshData>();
int totleMeshPoint = TargetMesh.mesh.vertices.Length;
for (int i = 0; i < totleMeshPoint; i++)
{
MeshData data = new MeshData();
data.Index = i;
data.target = __FindNearest(TargetMesh.mesh.vertices[i]);
if (data.target == null) Debug.Log("有空的");
data.offset = TargetMesh.mesh.vertices[i] - data.target.localPosition;
m_listMeshData.Add(data);
}
十、然後在Update呼叫以下方法,並拖動。你會得到下面的效果:
private void MoveMeshPoint()
{
Vector3[] v3 = TargetMesh.mesh.vertices;
for (int i = 0; i < m_listMeshData.Count; i++)
{
MeshData curData = m_listMeshData[i];
v3[i] = curData.target.localPosition+ curData.offset;
}
TargetMesh.mesh.vertices = v3;
}
十一、跟著動了對嗎?顯然這還不是我們要的最終效果,優化一下加上這句,
讓位置著跟著球球旋轉一下:
Vector3 dir = curData.target.transform.TransformDirection(curData.offset);
變成這樣:
private void MoveMeshPoint()
{
Vector3[] v3 = TargetMesh.mesh.vertices;
for (int i = 0; i < m_listMeshData.Count; i++)
{
MeshData curData = m_listMeshData[i];
Vector3 dir = curData.target.transform.TransformDirection(curData.offset);
v3[i] = curData.target.localPosition + dir;
}
TargetMesh.mesh.vertices = v3;
}
十二、完整程式碼:
using System.Collections.Generic;
using UnityEngine;
public class TestMono : MonoBehaviour
{
public MeshFilter TargetMesh; //網格
public Transform BallGroup; //球體集合(關節上所有球放這)
private List<Transform> m_listBall; //存放所有球體
private List<MeshData> m_listMeshData; //節點資料
void Start()
{
m_listBall = new List<Transform>();
foreach (Transform tran in BallGroup)
{
m_listBall.Add(tran);
}
m_listMeshData = new List<MeshData>();
int totleMeshPoint = TargetMesh.mesh.vertices.Length;
for (int i = 0; i < totleMeshPoint; i++)
{
MeshData data = new MeshData();
data.Index = i;
data.target = __FindNearest(TargetMesh.mesh.vertices[i]);
if (data.target == null) Debug.Log("有空的");
data.offset = TargetMesh.mesh.vertices[i] - data.target.localPosition;
m_listMeshData.Add(data);
}
}
// Update is called once per frame
void Update()
{
MoveMeshPoint();
}
private void MoveMeshPoint()
{
Vector3[] v3 = TargetMesh.mesh.vertices;
for (int i = 0; i < m_listMeshData.Count; i++)
{
MeshData curData = m_listMeshData[i];
Vector3 dir = curData.target.transform.TransformDirection(curData.offset);
v3[i] = curData.target.localPosition + dir;
}
TargetMesh.mesh.vertices = v3;
}
private Transform __FindNearest(Vector3 v3)
{
if (m_listBall != null)
{
float MaxDis = 999999;
Transform MaxTran = null;
for (int i = 0; i < m_listBall.Count; i++)
{
float curDis = Vector3.Distance(m_listBall[i].localPosition, v3);
if (curDis < MaxDis)
{
MaxDis = curDis;
MaxTran = m_listBall[i];
}
}
return MaxTran;
}
return null;
}
}
public class MeshData
{
public int Index; //索引
public Transform target; //目標球球
public Vector3 offset; //與目標球球位置差距
}
點贊關注,謝謝。
有問題請與我取得聯絡。