文章目錄
- 前言
- 開始
- 1. 實現簡單的抓勾效果
- 2. 高階鉤爪效果
- 源碼
- 參考
- 完結
前言
歡迎閱讀本文,本文將向您介紹如何使用Unity游戲引擎來實現一個簡單而有趣的2D抓勾效果,類似于蜘蛛俠的獨特能力。抓勾效果是許多動作游戲和平臺游戲中的常見元素,給玩家帶來了無限的想象和挑戰。
不需要擔心,即使您是一位新手,也可以輕松跟隨本文學習。我們將從頭開始,從創建一個新的Unity項目開始,一直到最終的完成效果。
借助本文提供的步驟和技巧,您將能夠為您的游戲增添一個的特色。希望您能享受這個過程,并從中獲得靈感,探索更多關于游戲開發的樂趣。
照例,我們先來看看本文實現的最終效果,以決定你是否繼續往下看
源碼我放在文章末尾了
開始
1. 實現簡單的抓勾效果
新建一個2d項目,添加一個2對象作為我們的角色物體,并掛載rigidbody 2d、碰撞器、Distance Joint 2d、Line Renderer(記得配置好材質和線寬)
書寫腳本代碼,代碼已經加了詳細的解釋了,這里就不得過多介紹了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;// Grappler類,用于處理角色的抓取動作
public class Grappler : MonoBehaviour
{// 主攝像機public Camera mainCamera;// 線渲染器,用于渲染抓取線public LineRenderer _lineRenderer;// 距離關節,用于處理抓取物體的物理效果public DistanceJoint2D _distanceJoint;void Start(){// 初始化時,禁用距離關節_distanceJoint.enabled = false;}void Update(){// 檢測鼠標左鍵是否按下if (Input.GetKeyDown(KeyCode.Mouse0)){// 獲取鼠標在世界坐標中的位置Vector2 mousePos = (Vector2)mainCamera.ScreenToWorldPoint(Input.mousePosition);// 設置線渲染器的起始和結束位置_lineRenderer.SetPosition(0, mousePos);_lineRenderer.SetPosition(1, transform.position);// 設置距離關節的連接點_distanceJoint.connectedAnchor = mousePos;// 啟用距離關節和線渲染器_distanceJoint.enabled = true;_lineRenderer.enabled = true;}// 檢測鼠標左鍵是否松開else if (Input.GetKeyUp(KeyCode.Mouse0)){// 禁用距離關節和線渲染器_distanceJoint.enabled = false;_lineRenderer.enabled = false;}// 如果距離關節啟用if (_distanceJoint.enabled){// 更新線渲染器的結束位置_lineRenderer.SetPosition(1, transform.position);}}
}
掛載腳本和綁定對象
簡單配置一下環境
運行效果
可以看到,如果連線時碰撞會出現問題,如果你先實現好的碰撞效果,可以勾選Distance Joint 2d的Enable Collision及開啟碰撞
效果
2. 高階鉤爪效果
在場景中創建GameObject如下(由父對象到子對象一一進行講解):
Player:(示例中是一個 圓形的sprite)對其添加RigidBody2D、Circle Collider2D 、Spring Joint2D組件(跟前面一樣Spring Joint2D組件開啟Enable Collision碰撞)
Gunpivot:鉤鎖槍的錨點,其為空對象,位置設置在Player的中心即0.0位置(后續用于實現鉤鎖槍隨著鼠標旋轉的效果)
GrapplingGun:(示例中為一個長方形的sprite)鉤鎖槍,用于后期實現發射鉤鎖,僅添加Box Collider2D即可
FirePoint:鉤鎖的發射點,空對象,即鉤鎖發射的起始位置,設置在鉤鎖槍的邊緣即可
Rope:在空對象上添加LineRenderer并適當改變寬度和材質即可。
下有兩個腳本分別添加給GrapplingGun和Rope即可
腳本1:Perfecter_Grapple添加給GrapplingGun
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Perfecter_Grapple : MonoBehaviour
{[Header("腳本引用:")]public Grappling_Rope grappleRope;[Header("層設置:")][SerializeField] private bool grappleToAll = false;[SerializeField] private int grappableLayerNumber = 9;[Header("主攝像機:")]public Camera m_camera;[Header("變換引用:")]public Transform gunHolder;public Transform gunPivot;public Transform firePoint;[Header("物理引用:")]public SpringJoint2D m_springJoint2D;public Rigidbody2D m_rigidbody;[Header("旋轉:")][SerializeField] private bool rotateOverTime = true;[Range(0, 60)] [SerializeField] private float rotationSpeed = 4;[Header("距離:")][SerializeField] private bool hasMaxDistance = false;[SerializeField] private float maxDistnace = 20;private enum LaunchType //發射類型{Transform_Launch,Physics_Launch}[Header("發射:")][SerializeField] private bool launchToPoint = true;[SerializeField] private LaunchType launchType = LaunchType.Physics_Launch;[SerializeField] private float launchSpeed = 1;[Header("無發射點")][SerializeField] private bool autoConfigureDistance = false;[SerializeField] private float targetDistance = 3;[SerializeField] private float targetFrequncy = 1;[HideInInspector] public Vector2 grapplePoint;[HideInInspector] public Vector2 grappleDistanceVector;private void Start() //開始{grappleRope.enabled = false;m_springJoint2D.enabled = false;}private void Update() //更新函數,控制輸入{if (Input.GetKeyDown(KeyCode.Mouse0)) //通過Input的順序設定函數的執行順序,先進行鉤爪選取點的定位{SetGrapplePoint(); }else if (Input.GetKey(KeyCode.Mouse0)) //從上一步的定位中把grapplerope啟用{if (grappleRope.enabled){RotateGun(grapplePoint, false); //進行鉤鎖槍的旋轉}else{Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);RotateGun(mousePos, true);}if (launchToPoint && grappleRope.isGrappling) //如果選擇點對點發射且正在鉤中目標{if (launchType == LaunchType.Transform_Launch) //如果發射類型是使用Transform類型發射{Vector2 firePointDistnace = firePoint.position - gunHolder.localPosition;Vector2 targetPos = grapplePoint - firePointDistnace;gunHolder.position = Vector2.Lerp(gunHolder.position, targetPos, Time.deltaTime * launchSpeed); //采用插值的形式,模擬繩索命中的物理效果}}}else if (Input.GetKeyUp(KeyCode.Mouse0)) //若抬起左鍵,則將一切啟用的相關布爾置否,恢復原狀{grappleRope.enabled = false;m_springJoint2D.enabled = false;m_rigidbody.gravityScale = 1;}else //時刻獲取鼠標的屏幕信息位置{Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);RotateGun(mousePos, true);}}void RotateGun(Vector3 lookPoint, bool allowRotationOverTime) //實現繩索槍根據鼠標進行旋轉功能{Vector3 distanceVector = lookPoint - gunPivot.position; //定義三維距離向量=朝向點-槍錨點位置float angle = Mathf.Atan2(distanceVector.y, distanceVector.x) * Mathf.Rad2Deg; //定義一個角度,其值等于距離向量tan所對應的弧度值*弧度值轉化為角度值的常量if (rotateOverTime && allowRotationOverTime) //當采用根據時間延遲旋轉時,采用四元數的插值旋轉,在原本的旋轉角和獲得的繞軸的新角度中進行隨時間{gunPivot.rotation = Quaternion.Lerp(gunPivot.rotation, Quaternion.AngleAxis(angle, Vector3.forward), Time.deltaTime * rotationSpeed);}else{gunPivot.rotation = Quaternion.AngleAxis(angle, Vector3.forward); //不采用時間插值變化時時,直接讓強旋轉角角度等于計算出的角度繞軸的四元數即可}}void SetGrapplePoint() //設定鉤取點(主要是位置的計算和注意某些添加的限定條件){Vector2 distanceVector = m_camera.ScreenToWorldPoint(Input.mousePosition) - gunPivot.position; //設置一個二維向量distance用于記錄鼠標點擊的點和槍錨點之間的距離if (Physics2D.Raycast(firePoint.position, distanceVector.normalized)) //發射一條射線,起始點為開火點,方向為distance的方向向量{RaycastHit2D _hit = Physics2D.Raycast(firePoint.position, distanceVector.normalized); //保存剛才的射線為hitif (_hit.transform.gameObject.layer == grappableLayerNumber || grappleToAll) //選擇是否選中任意的可抓取圖層或是某一指定圖層{if (Vector2.Distance(_hit.point, firePoint.position) <= maxDistnace || !hasMaxDistance) //當命中點和開火電站之間的距離小于最大距離或者不限定最大距離時{grapplePoint = _hit.point; //將命中點位置賦予抓取點位置grappleDistanceVector = grapplePoint - (Vector2)gunPivot.position; //抓鉤的距離向量等于鉤鎖點減去鉤鎖槍的錨點位置grappleRope.enabled = true; //打開繩索變量}}}}public void Grapple() //鉤鎖執行(真正決定移動) {m_springJoint2D.autoConfigureDistance = false; //設定彈簧關節組建的自動計算距離屬性為假if (!launchToPoint && !autoConfigureDistance) //當對點發射和自動計算距離均為假時,將目標距離和目標頻率賦給彈簧組件的屬性{m_springJoint2D.distance = targetDistance;m_springJoint2D.frequency = targetFrequncy;}if (!launchToPoint) //如果僅為不對點發射{if (autoConfigureDistance) //若自動計算距離{m_springJoint2D.autoConfigureDistance = true;m_springJoint2D.frequency = 0; //彈簧組件頻率屬性為0,該值越大,彈簧越硬}m_springJoint2D.connectedAnchor = grapplePoint; //不自動計算距離且不對點發射時m_springJoint2D.enabled = true;}else //對點發射時,選擇發射類型,有物理類發射和Transform類發射{switch (launchType){case LaunchType.Physics_Launch:m_springJoint2D.connectedAnchor = grapplePoint; //當使用物理發射時,將鉤取點賦予彈簧的連接錨點Vector2 distanceVector = firePoint.position - gunHolder.position; //長度變量等于開火點距離減去持槍距離m_springJoint2D.distance = distanceVector.magnitude; //將長度變量賦給彈簧組建的距離屬性,保證鉤爪拉到盡頭時有一定的距離m_springJoint2D.frequency = launchSpeed; //彈簧頻率(強度)等于發射速度m_springJoint2D.enabled = true; //打開彈簧組件,進行拉伸break;case LaunchType.Transform_Launch:m_rigidbody.gravityScale = 0; //當使用Transform發射時,將物體的重力設置為0m_rigidbody.velocity = Vector2.zero; //啟動鉤爪時,將物體速度清零break;}}}private void OnDrawGizmosSelected() //始終在場景中繪制可視的Gizmo,On方法{if (firePoint != null && hasMaxDistance){Gizmos.color = Color.green;Gizmos.DrawWireSphere(firePoint.position, maxDistnace);}}}
腳本2:Grappling_Rope 添加給Rope即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Grappling_Rope : MonoBehaviour
{[Header("一般引用:")]public Perfecter_Grapple grapplingGun; //抓鉤槍public LineRenderer m_lineRenderer; //線渲染器[Header("一般設置:")][SerializeField] private int percision = 40; //精度[Range(0, 20)] [SerializeField] private float straightenLineSpeed = 5; //直線速度[Header("繩索動畫設置:")]public AnimationCurve ropeAnimationCurve; //繩索動畫曲線[Range(0.01f, 4)] [SerializeField] private float StartWaveSize = 2; //起始波動大小float waveSize = 0; //波動大小[Header("繩索進度:")]public AnimationCurve ropeProgressionCurve; //繩索進度曲線[SerializeField] [Range(1, 50)] private float ropeProgressionSpeed = 1; //繩索進度速度float moveTime = 0; //移動時間[HideInInspector] public bool isGrappling = true; //是否正在抓取bool strightLine = true; //是否為直線private void OnEnable() //啟用時執行{moveTime = 0;m_lineRenderer.positionCount = percision;waveSize = StartWaveSize;strightLine = false;LinePointsToFirePoint(); //線點對準發射點m_lineRenderer.enabled = true;}private void OnDisable() //禁用時執行{m_lineRenderer.enabled = false;isGrappling = false;}private void LinePointsToFirePoint() //線點對準發射點{for (int i = 0; i < percision; i++){m_lineRenderer.SetPosition(i, grapplingGun.firePoint.position); //繪制連接抓取點和抓鉤槍位置的繩子}}private void Update() //更新函數{moveTime += Time.deltaTime;DrawRope(); //繪制繩索}void DrawRope() //繪制繩索{if (!strightLine){if (m_lineRenderer.GetPosition(percision - 1).x == grapplingGun.grapplePoint.x){strightLine = true;}else{DrawRopeWaves(); //繪制繩索波動}}else{if (!isGrappling){grapplingGun.Grapple(); //抓取isGrappling = true;}if (waveSize > 0){waveSize -= Time.deltaTime * straightenLineSpeed;DrawRopeWaves(); //繪制繩索波動}else{waveSize = 0;if (m_lineRenderer.positionCount != 2) { m_lineRenderer.positionCount = 2; }DrawRopeNoWaves(); //繪制無波動的繩索}}}void DrawRopeWaves() //繪制繩索波動{for (int i = 0; i < percision; i++){float delta = (float)i / ((float)percision - 1f);Vector2 offset = Vector2.Perpendicular(grapplingGun.grappleDistanceVector).normalized * ropeAnimationCurve.Evaluate(delta) * waveSize; //計算偏移量Vector2 targetPosition = Vector2.Lerp(grapplingGun.firePoint.position, grapplingGun.grapplePoint, delta) + offset; //目標位置Vector2 currentPosition = Vector2.Lerp(grapplingGun.firePoint.position, targetPosition, ropeProgressionCurve.Evaluate(moveTime) * ropeProgressionSpeed); //當前位置m_lineRenderer.SetPosition(i, currentPosition); //設置線的位置}}void DrawRopeNoWaves() //繪制無波動的繩索{m_lineRenderer.SetPosition(0, grapplingGun.firePoint.position); //設置線的起始位置m_lineRenderer.SetPosition(1, grapplingGun.grapplePoint); //設置線的結束位置}
}
注意
:腳本賦予對象后注意賦值,在Rope中的函數曲線繪制以及添加點時一定要注意不能逾越
(0,0)以及(1,1)否則會出現鉤鎖無法發射的問題。
代碼賦值部分參數可以參照:
繩索發射曲線樣式參照:
最終效果
源碼
https://gitcode.net/unity1/unity2d-clawhook
參考
【視頻】https://www.youtube.com/watch?v=dnNCVcVS6uw
完結
贈人玫瑰,手有余香!如果文章內容對你有所幫助,請不要吝嗇你的點贊評論和關注
,以便我第一時間收到反饋,你的每一次支持
都是我不斷創作的最大動力。當然如果你發現了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奮斗的開發者,出于興趣愛好,于是最近才開始自習unity。如果你遇到任何問題,也歡迎你評論私信找我, 雖然有些問題我可能也不一定會,但是我會查閱各方資料,爭取給出最好的建議,希望可以幫助更多想學編程的人,共勉~