Unity繩子/繩索效果

2020-10-25 09:00:28

老規矩先上圖:
在這裡插入圖片描述

最近在做一個做一個遊戲,繩子纏繞在一起然後需要把繩子解開方能贏得遊戲。因為需要用到一個繩子的效果,網了查了不少資料一方面是用外掛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;          //與目標球球位置差距
}

點贊關注,謝謝。
有問題請與我取得聯絡。