日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Unity 2D 自定义碰撞系统(一)

發布時間:2023/12/20 windows 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity 2D 自定义碰撞系统(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

很久之前就想要用Unity實現一個比較復古的碰撞效果。
但是由于Unity的剛體是基于物理運算的,在發生碰撞的時候,會出現反彈等我們不希望出現的效果。
所以通過查看了一些類似的插件和官方的一些項目作為參考,實現了一個沒有力的概念的碰撞系統。

效果


可以看出手感已經很平滑了,而且對于較為邊緣的碰撞,也會自動向外部偏移,這個主要利用了切線的方向,后面會詳細提到。
這一節主要討論碰撞的算法實現,后續可能會更新一套完整的自定義物理系統的架構,可以擴展出許多好玩的效果比如:



推箱子

減速帶

減速區

自定義碰撞算法

1.核心函數

Rigidbody2D.Cast(Vector2 direction, ContactFilter2D contactFilter, List<RaycastHit2D> results, float distance = Mathf.Infinity);

該函數會預先檢測移動范圍內所有的碰撞體。
該函數并不會導致物體移動。
物體移動使用Rigidbody2D.position。

參數和返回值

該函數返回值為int類型,表示檢測到的碰撞物體的數量。
direction:表示運動方向。
contactFilter:表示碰撞設置,是一個struct,可以設置碰撞的layer,是否忽略trigger等。
results:返回發生碰撞的信息,也是一個struct,包括碰撞點,碰撞法線,碰撞點到物體的距離等信息。
distance:移動的距離。

Collider2D也擁有Cast函數,與Rigidbody2D的Cast方法的參數和返回值是相同的,不過兩個函數也有不同點。

Rigidbody2D.Cast應該在FixedUpdate中調用,同時最終的移動采用Rigidbody.position進行移動。
Collider2D.Cast應該在Update中調用,最終的移動采用transform.Translate進行移動。

Rigidbody2D.Cast會檢測該物體下所有碰撞體集合的信息。
Collider2D.Cast只會檢測當前碰撞體的碰撞信息。

2.初始化

[RequireComponent(typeof(Collider2D))] public class PhysicalObject : MonoBehaviour {private const float MIN_MOVE_DISTANCE = 0.001f;private new Rigidbody2D rigidbody2D;private ContactFilter2D contactFilter2D;private readonly List<RaycastHit2D> raycastHit2DList = new List<RaycastHit2D>();public LayerMask layerMask;public Vector2 velocity;void Start(){rigidbody2D = GetComponent<Rigidbody2D>();if (rigidbody2D == null)rigidbody2D = gameObject.AddComponent<Rigidbody2D>();rigidbody2D.hideFlags = HideFlags.NotEditable;rigidbody2D.bodyType = RigidbodyType2D.Kinematic;rigidbody2D.simulated = true;rigidbody2D.useFullKinematicContacts = false;rigidbody2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;rigidbody2D.sleepMode = RigidbodySleepMode2D.NeverSleep;rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;rigidbody2D.constraints = RigidbodyConstraints2D.FreezeRotation;rigidbody2D.gravityScale = 0;contactFilter2D = new ContactFilter2D{useLayerMask = true,useTriggers = false,layerMask = layerMask};}private void OnValidate(){contactFilter2D.layerMask = layerMask;} }

這里我們將Rigidbody2D的bodyType設置為Kinematic類型,并且useFullKinematicContacts為false。
Kinematic類型的剛體會返回碰撞信息,但是并不會對剛體造成物理影響,這正是我們需要的。
useFullKinematicContacts之所以設置為false,是因為我們采用Cast的方式進行碰撞檢測而不是采用OnCollisionXX函數,所以為了減少不必要的性能消耗。

3.碰撞檢測

private void Update() {velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); }private void FixedUpdate() {Movement(velocity * Time.deltaTime * 5f); }private void Movement(Vector2 deltaPosition) {if (deltaPosition == Vector2.zero)return;Vector2 updateDeltaPosition = Vector2.zero;float distance = deltaPosition.magnitude;Vector2 direction = deltaPosition.normalized;if (distance <= MIN_MOVE_DISTANCE)distance = MIN_MOVE_DISTANCE;rigidbody2D.Cast(direction, contactFilter2D, raycastHit2DList, distance);Vector2 finalDirection = direction;float finalDistance = distance;foreach (var hit in raycastHit2DList){//DoSth}updateDeltaPosition += finalDirection * finalDistance;}

我們在FixedUpdate中調用Movement方法,并且檢測所有的碰撞信息。
在執行完Cast方法后,該方法會將所有的碰撞信息保存在raycastHit2DList中。
在Unity5.6版本之前,使用Foreach遍歷每一幀都會GC,所以5.6之前對List的遍歷需要使用For循環或者枚舉器。(詳細:https://www.jianshu.com/p/03760933e2fa)
個人測試Foreach的效率比For要好,大概2:2.5的樣子,所以這里用的Foreach,測試版本Unity2018.3和2019.1。

接下來就是重頭戲了,碰撞檢測的方法。
不過在那之前,我們先明確一些東西。
首先傳入Cast方法的Direction和Distance是固定的,這就意味著這次檢測的方向是固定的,同時也就說明,最終移動的方向一定是檢測的方向,否則這次檢測將失去意義。
所以對于最終移動的方向和距離,我們只需要關心它的距離就好了。
并且最終的距離一定會小于Distance。

foreach (var hit in raycastHit2DList) {float moveDistance = hit.distance;if (moveDistance < finalDistance){finalDistance = moveDistance;}}

在距離上,我們遍歷所有的碰撞信息,并且找出最短的那個,這說明這個物體是距離我們最近的,同時也是我們應該停止的地方。

但是當我們發生碰撞的時候,無論什么方向,都無法進行移動了。
Debug一下

foreach (var hit in raycastHit2DList) {float moveDistance = hit.distance;Debug.DrawLine(hit.point, hit.point + hit.normal, Color.white);Debug.DrawLine(hit.point, hit.point + direction, Color.yellow);Debug.Log(hit.distance);if (moveDistance < finalDistance){finalDistance = moveDistance;}}



黃色線代表移動方向,白色線代表碰撞法線的方向。
這時候我們發現無論我們朝著哪個方向進行移動,碰撞的法線方向始終是固定的,并且發生碰撞時,兩物體之間的距離始終是0。
所以我們得出的結論就是,碰撞法線和距離是固定,與我們要移動的方向無關。
既然碰撞法線和距離是一定的,也就是說,無論我們朝著什么方向移動,他都不會改變,所以此時我們需要判斷移動的方向與碰撞法線的方向,來決定該如何移動。

foreach (var hit in raycastHit2DList) {float moveDistance = hit.distance;Debug.DrawLine(hit.point, hit.point + hit.normal, Color.white);Debug.DrawLine(hit.point, hit.point + direction, Color.yellow);Debug.Log(hit.distance);float projection = Vector2.Dot(hit.normal, direction);if (projection >= 0){moveDistance = distance;}if (moveDistance < finalDistance){finalDistance = moveDistance;}}


此時,我們加入一個點乘判斷,這個判斷表示,如果我們想要移動的方向與碰撞法線的方向基本相反(projection < 0),moveDistance = hit.distance,也就是0,表示無法移動。
如果想要移動的方向與碰撞法線方向呈90度(projection == 0)或者基本相同(projection > 0)說明我們要移動的方向并不會收到碰撞的限制,所以此時將moveDistance = distance,也就是初始運動的距離。

但是還有一個問題就是,雖然我們能夠正確的進行移動了,但是當我們靠近物體的時候,只有在兩方向夾角小于等于90度的時候才能夠進行移動,如果我希望即使靠近物體,仍舊能夠取得移動距離在可移動方向的分量怎么辦?
可能有點繞,直白一點可以表示為,貼著墻摩擦前進。

于是我們需要引入切線來達到這個目的。
那么如何獲取正確的需要移動的切線方向呢?

首先我們需要明確的是,切線方向是碰撞法線的切線方向,而不是移動方向的
其次切線的方向要保持和移動方向基本相同,因為是移動方向的分量。
(切線用品紅色表示)

foreach (var hit in raycastHit2DList) {float moveDistance = hit.distance;Debug.DrawLine(hit.point, hit.point + hit.normal, Color.white);Debug.DrawLine(hit.point, hit.point + direction, Color.yellow);float projection = Vector2.Dot(hit.normal, direction);if (projection >= 0){moveDistance = distance;}else{Vector2 tangentDirection = new Vector2(hit.normal.y, -hit.normal.x);float tangentDot = Vector2.Dot(tangentDirection, direction);if (tangentDot < 0){tangentDirection = -tangentDirection;tangentDot = -tangentDot;}float tangentDistance = tangentDot * distance;Debug.DrawLine(hit.point, hit.point + tangentDirection, Color.magenta);}if (moveDistance < finalDistance){finalDistance = moveDistance;} }

所以我們首先通過法線方向獲取切線的方向,然后判斷切線方向與移動方向點積,來確定切線的方向(實際上這里用切線表述并不準確,但是為了簡單明了還是采用切線來表述,實際表述應該是移動方向在切線方向的分量)
并且確定移動距離在切線方向的分量,也就是切線方向需要移動的距離。

在獲得這些信息之后,我們還需要做一次Cast檢測,來確定切線方向的移動會不會碰撞到物體

//Class private readonly List<RaycastHit2D> tangentRaycastHit2DList = new List<RaycastHit2D>();{//Addif (tangentDot != 0){rigidbody2D.Cast(tangentDirection, contactFilter2D, tangentRaycastHit2DList, tangentDistance);foreach (var tangentHit in tangentRaycastHit2DList){Debug.DrawLine(tangentHit.point, tangentHit.point + tangentDirection, Color.magenta);if (Vector2.Dot(tangentHit.normal, tangentDirection) >= 0)continue;if (tangentHit.distance < tangentDistance)tangentDistance = tangentHit.distance;}updateDeltaPosition += tangentDirection * tangentDistance;} }

首先我們需要在Class中聲明一個盛放切線碰撞信息的容器。
然后進行切線方向的碰撞檢測。
同樣切線方向的移動也需要判斷與法線的點積來確定如何移動。

最終效果。
如果想要做邊緣自動偏移的話,將BoxCollider2D的Edge Radius設置一下就好了,這樣正方形會變成圓角的正方形。

至此,關于碰撞檢測算法已經基本完成了。
在此之前,我曾經嘗試過很多種實現方式,但是最后采用這種方式,并且這種方式足夠優雅~
只用了為數不多的代碼。
并且實際上很多問題已經解決掉了,所以對這些東西感興趣的朋友也可以自己嘗試一下,這里也是提供一個參考。

完整代碼:

using System.Collections; using System.Collections.Generic; using UnityEngine;[RequireComponent(typeof(Collider2D))] public class PhysicalObject : MonoBehaviour {private const float MIN_MOVE_DISTANCE = 0.001f;private new Collider2D collider2D;private new Rigidbody2D rigidbody2D;private ContactFilter2D contactFilter2D;private readonly List<RaycastHit2D> raycastHit2DList = new List<RaycastHit2D>();private readonly List<RaycastHit2D> tangentRaycastHit2DList = new List<RaycastHit2D>();public LayerMask layerMask;[HideInInspector]public Vector2 velocity;void Start(){collider2D = GetComponent<Collider2D>();rigidbody2D = GetComponent<Rigidbody2D>();if (rigidbody2D == null)rigidbody2D = gameObject.AddComponent<Rigidbody2D>();rigidbody2D.hideFlags = HideFlags.NotEditable;rigidbody2D.bodyType = RigidbodyType2D.Kinematic;rigidbody2D.simulated = true;rigidbody2D.useFullKinematicContacts = false;rigidbody2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;rigidbody2D.sleepMode = RigidbodySleepMode2D.NeverSleep;rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;rigidbody2D.constraints = RigidbodyConstraints2D.FreezeRotation;rigidbody2D.gravityScale = 0;contactFilter2D = new ContactFilter2D{useLayerMask = true,useTriggers = false,layerMask = layerMask};}private void OnValidate(){contactFilter2D.layerMask = layerMask;}private void Update(){velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));}private void FixedUpdate(){Movement(velocity * Time.deltaTime * 5f);}private void Movement(Vector2 deltaPosition){if (deltaPosition == Vector2.zero)return;Vector2 updateDeltaPosition = Vector2.zero;float distance = deltaPosition.magnitude;Vector2 direction = deltaPosition.normalized;if (distance <= MIN_MOVE_DISTANCE)distance = MIN_MOVE_DISTANCE;rigidbody2D.Cast(direction, contactFilter2D, raycastHit2DList, distance);Vector2 finalDirection = direction;float finalDistance = distance;foreach (var hit in raycastHit2DList){float moveDistance = hit.distance;Debug.DrawLine(hit.point, hit.point + hit.normal, Color.white);Debug.DrawLine(hit.point, hit.point + direction, Color.yellow);float projection = Vector2.Dot(hit.normal, direction);if (projection >= 0){moveDistance = distance;}else{Vector2 tangentDirection = new Vector2(hit.normal.y, -hit.normal.x);float tangentDot = Vector2.Dot(tangentDirection, direction);if (tangentDot < 0){tangentDirection = -tangentDirection;tangentDot = -tangentDot;}float tangentDistance = tangentDot * distance;if (tangentDot != 0){rigidbody2D.Cast(tangentDirection, contactFilter2D, tangentRaycastHit2DList, tangentDistance);foreach (var tangentHit in tangentRaycastHit2DList){Debug.DrawLine(tangentHit.point, tangentHit.point + tangentDirection, Color.magenta);if (Vector2.Dot(tangentHit.normal, tangentDirection) >= 0)continue;if (tangentHit.distance < tangentDistance)tangentDistance = tangentHit.distance;}updateDeltaPosition += tangentDirection * tangentDistance;}}if (moveDistance < finalDistance){finalDistance = moveDistance;}}updateDeltaPosition += finalDirection * finalDistance;rigidbody2D.position += updateDeltaPosition;} }

總結

以上是生活随笔為你收集整理的Unity 2D 自定义碰撞系统(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧美日韩成人在线视频 | 玖玖爱国产| 羞羞涩涩视频 | 免费黄色av | 欧美一级淫片免费视频黄 | 懂色av蜜臀av粉嫩av | 日韩中文字幕有码 | 久久精品美女 | wwwxxx日韩 | 夜夜撸影院| 国产一区二区三区四区五区美女 | 华丽的外出在线 | 国产96在线 | 日本精品在线观看 | 欧美一区二不卡视频 | 99视频免费在线观看 | 国产精品久久久99 | 天堂中文在线最新 | av官网在线观看 | 精品国产人妻一区二区三区 | 久久电影一区二区 | 国产一二三精品 | 深夜福利视频网站 | 漂亮人妻被黑人久久精品 | 亚洲欧美日韩久久 | 久草资源在线视频 | 青草视频免费看 | 欧美日韩一区二区三区免费 | 黄色片在线免费观看 | 一区二区三区在线观看免费 | 51久久久| 国产性―交一乱―色―情人 | 久久久久久久久久久av | 99热青青草| 亚洲少妇网站 | 欧美成人不卡视频 | 网站黄在线观看 | 欧美电影一区 | 2019国产在线 | 国产精品福利网站 | 欧美jizz19性欧美 | 精品免费av| 国产精品日韩一区二区三区 | 精品国产乱码久久久久久婷婷 | 国产一区日韩精品 | 一二三区免费视频 | 在线视频久久 | 日韩一道本| 黄色大片黄色大片 | 日本不卡中文字幕 | 久久伊人影院 | 人妻少妇精品一区二区三区 | www.爆操 | 日韩中文字幕av在线 | 一级黄色免费 | 国产一区二区三区免费在线观看 | 中文不卡av | 亚洲欧美日韩专区 | 一级做a爱片久久毛片 | 日韩九九九 | 日韩精品第一页 | www.youjizz.com在线观看 | 久久精品一区二区国产 | 内射国产内射夫妻免费频道 | av黄色免费在线观看 | 国产精品一区二区电影 | 欧美大黄视频 | 朝桐光在线观看 | 后进极品美女白嫩翘臀 | 欧美一区二区三区电影 | 天海翼av| 成人天堂噜噜噜 | 香蕉网久久 | 18性xxxxx性猛交 | 成人羞羞国产免费动态 | 一本色道久久88加勒比—综合 | 97公开免费视频 | 成年人在线免费观看网站 | 日韩干 | 特级西西444www大精品视频免费看 | 污视频网站在线 | 欧美大片在线观看 | 怡春院国产 | 欧美三级日本三级 | 色综合中文综合网 | 中文字字幕在线中文乱码 | 99久久亚洲精品 | 美女赤身免费网站 | 欧美色乱 | 亚洲综合av一区二区三区 | 成人做爰69片免费看 | 1024亚洲 | 亚洲偷偷 | 蜜桃视频久久一区免费观看入口 | 国产91欧美 | 日韩av片在线看 | 好男人在线观看 | 你懂的在线免费观看 | 91chinese在线|