"Snap" 기능은 일반적으로 게임 오브젝트나 요소를 그리드나 다른 오브젝트에 정확하게 맞추는 기능을 의미합니다.
프로젝트를 진행하던 도중 Snap기능이 필요해서 에셋스토어를 뒤져봐도 원하는 스냅기능은 없어서 만들기로 했습니다
구현할 스냅기능은 그리드를 기준으로 맟추는게 아닌 오브젝트 기준으로 맞추기로 했습니다.

public bool SnapToWalls(LayerMask wallMask, Transform argObj, Vector3 argPos, float argDetectionRadius)
		{
			Vector3 startPos = argPos.SetY(.1f);
			float angleIncrement = 45;
			angleIncrement = Mathf.Clamp(angleIncrement, 5, 90);
			float angle = 0;
			Vector3 _nearPos = Vector3.zero;
			List<RaycastHit> allHits = new List<RaycastHit>();
			BoxCollider _argObjCol = argObj.GetComponent<BoxCollider>();

			while (angle <= 360)
			{
				Vector3 dir = Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward;

				RaycastHit[] hits = Physics.RaycastAll(startPos - dir * 0.1f, dir, argDetectionRadius, wallMask);
				angle += angleIncrement;
				
				allHits.AddRange(hits);
			}

			RaycastHit selected = default(RaycastHit);
			allHits = allHits.OrderBy(h => Vector3.Distance(h.point, startPos)).ToList();

			foreach (RaycastHit hit in allHits)
			{
				if(hit.transform.position != argObj.position)
                {
					selected = hit;
					_nearPos = selected.collider.ClosestPointOnBounds(argObj.transform.position);
					//Debug.Log(_nearPos);
					break;
				}
			}

			
			if (selected.point != Vector3.zero)
			{
				Bounds _bound = selected.collider.bounds;
				Debug.Log(selected.transform.name);

				///충돌체와 충돌대상 오브젝트 사이의 각도와 방향
				Vector3 _v = argObj.transform.position - selected.transform.position;
				Vector3 _dir = _v.normalized;

				///충돌된 오브젝트 모서리 각도를 구하기 위한 코드
				Vector3 _edgePosRightTop = new Vector3(selected.transform.position.x + _bound.extents.x, selected.transform.position.y, selected.transform.position.z + _bound.extents.z);
				Vector3 _edgeVectorRightTop = (_edgePosRightTop - selected.transform.position).normalized;

				Vector3 _edgePosRightBottom = new Vector3(selected.transform.position.x + _bound.extents.x, selected.transform.position.y, selected.transform.position.z - _bound.extents.z);
				Vector3 _edgeVectorRightBottom = (_edgePosRightBottom - selected.transform.position).normalized;

				Vector3 _edgePosLeftTop = new Vector3(selected.transform.position.x - _bound.extents.x, selected.transform.position.y, selected.transform.position.z + _bound.extents.z);
				Vector3 _edgeVectorLeftTop = (_edgePosLeftTop - selected.transform.position).normalized;

				Vector3 _edgePosLeftBottom = new Vector3(selected.transform.position.x - _bound.extents.x, selected.transform.position.y, selected.transform.position.z - _bound.extents.z);
				Vector3 _edgeVectorLeftBottom = (_edgePosLeftBottom - selected.transform.position).normalized;

				float dotResultForTop1 = Vector3.Dot(_edgeVectorRightTop - _dir, _dir - _edgeVectorLeftTop);
				float dotResultForTop2 = Vector3.Dot(_edgeVectorLeftTop - _dir, _dir - _edgeVectorRightTop);

				float dotResultForRight1 = Vector3.Dot(_edgeVectorRightTop - _dir, _dir - _edgeVectorRightBottom);
				float dotResultForRight2 = Vector3.Dot(_edgeVectorRightBottom - _dir, _dir - _edgeVectorRightTop);

				float dotResultForLeft1 = Vector3.Dot(_edgeVectorLeftTop - _dir, _dir - _edgeVectorLeftBottom);
				float dotResultForLeft2 = Vector3.Dot(_edgeVectorLeftBottom - _dir, _dir - _edgeVectorLeftTop);

				float dotResultForBottom1 = Vector3.Dot(_edgeVectorRightBottom - _dir, _dir - _edgeVectorLeftBottom);
				float dotResultForBottom2 = Vector3.Dot(_edgeVectorLeftBottom - _dir, _dir - _edgeVectorRightBottom);



				if (dotResultForTop1 > 0 && dotResultForTop2 > 0)
				{
					argObj.transform.position = new Vector3(argObj.transform.position.x, argObj.transform.position.y, _nearPos.z + _argObjCol.bounds.extents.z);
					Debug.Log("나는 위에있다");
					return true;
				}
				else if (dotResultForRight1 > 0 && dotResultForRight2 > 0)
				{
					argObj.transform.position = new Vector3(_nearPos.x + _argObjCol.bounds.extents.x, argObj.transform.position.y, argObj.transform.position.z);
					Debug.Log("나는 오른쪽에있다");
					return true;
				}
				else if (dotResultForLeft1 > 0 && dotResultForLeft2 > 0)
				{
					argObj.transform.position = new Vector3(_nearPos.x - _argObjCol.bounds.extents.x, argObj.transform.position.y, argObj.transform.position.z);
					Debug.Log("나는 왼쪽에있다");
					return true;
				}
				else if (dotResultForBottom1 > 0 && dotResultForBottom2 > 0)
				{
					argObj.transform.position = new Vector3(argObj.transform.position.x, argObj.transform.position.y, _nearPos.z - _argObjCol.bounds.extents.z);
					Debug.Log("나는 아래쪽에있다");
					return true;
				}

				
			}
			return false;
		}

전체 소스코드
 

public bool SnapToWalls(LayerMask wallMask, Transform argObj, Vector3 argPos, float argDetectionRadius)

인자값으로 RayCast에 사용될 LayerMask와
Snap이 필요한 오브젝트의 Transform
오브젝트의 Position
Raycast의 탐지거리인 argDetectionRadius 가 사용됩니다
 

			Vector3 startPos = argPos.SetY(.1f);
			float angleIncrement = 45;
			angleIncrement = Mathf.Clamp(angleIncrement, 5, 90);
			float angle = 0;
			Vector3 _nearPos = Vector3.zero;
			List<RaycastHit> allHits = new List<RaycastHit>();
			BoxCollider _argObjCol = argObj.GetComponent<BoxCollider>();

			while (angle <= 360)
			{
				Vector3 dir = Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward;

				RaycastHit[] hits = Physics.RaycastAll(startPos - dir * 0.1f, dir, argDetectionRadius, wallMask);
				angle += angleIncrement;
				
				allHits.AddRange(hits);
			}

			RaycastHit selected = default(RaycastHit);
			allHits = allHits.OrderBy(h => Vector3.Distance(h.point, startPos)).ToList();
            
            foreach (RaycastHit hit in allHits)
			{
				if(hit.transform.position != argObj.position)
                {
					selected = hit;
					_nearPos = selected.collider.ClosestPointOnBounds(argObj.transform.position);
					//Debug.Log(_nearPos);
					break;
				}
			}

이후 360도가 될때까지 angleIncrement 만큼 회전하여 RaycastHit을 실행, 결과를 List<RaycastHit> allHit에 담아줍니다
allHit는 거리가 가까운 오브젝트 순으로 정렬됩니다
 
가장 가까운 오브젝트를 선언된 selected에 담아주고 snap이 필요한 오브젝트와 충돌된 오브젝트의 가장 가까운 점을
_nearPos에 담아줍니다
이때 레이캐스트가 자기 자신을 충돌체로 감지하기때문에 제외해주기 위해 Position을 비교해줍니다
 

if (selected.point != Vector3.zero)
	{
		Bounds _bound = selected.collider.bounds;

		///충돌체와 충돌대상 오브젝트 사이의 각도와 방향
		Vector3 _v = argObj.transform.position - selected.transform.position;
		Vector3 _dir = _v.normalized;

		///충돌된 오브젝트 모서리 각도를 구하기 위한 코드
		Vector3 _edgePosRightTop = new Vector3(selected.transform.position.x + _bound.extents.x, selected.transform.position.y, selected.transform.position.z + _bound.extents.z);
		Vector3 _edgeVectorRightTop = (_edgePosRightTop - selected.transform.position).normalized;
        
		Vector3 _edgePosRightBottom = new Vector3(selected.transform.position.x + _bound.extents.x, selected.transform.position.y, selected.transform.position.z - _bound.extents.z);
		Vector3 _edgeVectorRightBottom = (_edgePosRightBottom - selected.transform.position).normalized;

		Vector3 _edgePosLeftTop = new Vector3(selected.transform.position.x - _bound.extents.x, selected.transform.position.y, selected.transform.position.z + _bound.extents.z);
        	Vector3 _edgeVectorLeftTop = (_edgePosLeftTop - selected.transform.position).normalized;

		Vector3 _edgePosLeftBottom = new Vector3(selected.transform.position.x - _bound.extents.x, selected.transform.position.y, selected.transform.position.z - _bound.extents.z);
		Vector3 _edgeVectorLeftBottom = (_edgePosLeftBottom - selected.transform.position).normalized;

		float dotResultForTop1 = Vector3.Dot(_edgeVectorRightTop - _dir, _dir - _edgeVectorLeftTop);
		float dotResultForTop2 = Vector3.Dot(_edgeVectorLeftTop - _dir, _dir - _edgeVectorRightTop);

		float dotResultForRight1 = Vector3.Dot(_edgeVectorRightTop - _dir, _dir - _edgeVectorRightBottom);
		float dotResultForRight2 = Vector3.Dot(_edgeVectorRightBottom - _dir, _dir - _edgeVectorRightTop);

		float dotResultForLeft1 = Vector3.Dot(_edgeVectorLeftTop - _dir, _dir - _edgeVectorLeftBottom);
		float dotResultForLeft2 = Vector3.Dot(_edgeVectorLeftBottom - _dir, _dir - _edgeVectorLeftTop);

		float dotResultForBottom1 = Vector3.Dot(_edgeVectorRightBottom - _dir, _dir - _edgeVectorLeftBottom);
		float dotResultForBottom2 = Vector3.Dot(_edgeVectorLeftBottom - _dir, _dir - _edgeVectorRightBottom);

		if (dotResultForTop1 > 0 && dotResultForTop2 > 0)
			{
				argObj.transform.position = new Vector3(argObj.transform.position.x, argObj.transform.position.y, _nearPos.z + _argObjCol.bounds.extents.z);
				Debug.Log("나는 위에있다");
				return true;
			}
			else if (dotResultForRight1 > 0 && dotResultForRight2 > 0)
			{
				argObj.transform.position = new Vector3(_nearPos.x + _argObjCol.bounds.extents.x, argObj.transform.position.y, argObj.transform.position.z);
				Debug.Log("나는 오른쪽에있다");
				return true;
			}
			else if (dotResultForLeft1 > 0 && dotResultForLeft2 > 0)
			{
				argObj.transform.position = new Vector3(_nearPos.x - _argObjCol.bounds.extents.x, argObj.transform.position.y, argObj.transform.position.z);
				Debug.Log("나는 왼쪽에있다");
				return true;
			}
			else if (dotResultForBottom1 > 0 && dotResultForBottom2 > 0)
			{
				argObj.transform.position = new Vector3(argObj.transform.position.x, argObj.transform.position.y, _nearPos.z - _argObjCol.bounds.extents.z);
				Debug.Log("나는 아래쪽에있다");
				return true;
			}	
		}
			return false;
     }


_dir :
Snap이 필요한 오브젝트 벡터 - 충돌된 오브젝트 벡터.normalized 로 충돌된 오브젝트에서의 Snap이 필요한 오브젝트 까지의 방향을 구해줍니다.
_edgeVector:
Snap이 필요한 오브젝트가 충돌된 오브젝트의 어느 면에 있는지 측정하기 위해서 충돌된 오브젝트 BoxCollider의 모서리 좌표들을 구해주고 모서리 좌표 - 충될된 오브젝트 위치.normalized 로 방향벡터를 구해줍니다.
_dotResult
벡터 _dir _edgeVector를 사용하여 내적을 구해줍니다. 내적의 결과가 0보다 크다면 범위안에 있는 것이므로
 
Snap이 필요한 오브젝트는 충돌된 오브젝트의 위치한 면에 딱 달라붙게 됩니다!

'Unity' 카테고리의 다른 글

[Unity #] UI 에서 RayCast  (2) 2024.02.13
[Unity #] 유니티에서 Font-awesome 사용하기  (0) 2024.01.29
[Unity #] Rigidbody? Collider?  (1) 2024.01.23
[Unity] SerializeField ? 직렬화 ?  (0) 2024.01.22
[Unity C#] Vector3  (0) 2024.01.22

+ Recent posts