"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

Prefab으로 만들어둔 버튼 UI가 있는데 마우스커서 가 오버랩 되면 정보를 띄어주는 UI가 오버레이 되는 기능을 구현하고 싶었다...

게임을 예로 들면 인벤토리 내의 아이템위에 커서를 위치하면 아이템의 정보창이 뜨는 그런 기능,,,

 

기존의 RayCastHit 으로 가져올려는데 아무리 해봐도 안됨....

 

근데 당연히 안됬던 것!

 

UI는 Rect transform 인데 transform으로 가져올려니까 안됬던 것.

찾아보니 UI는 GraphicRaycaster를 따로 사용하는데 인자값도 다르다...

PointerEventData 와 List<RayCastResult>를 인자로 사용하는데

List<RayCastResult>는 결과값을 저장하기 위한 용도로 인식이 되지만 PointerEventData는 감이 잡히질 않아

찾아보면 UI 요소와의 상호작용 및 터치/마우스 입력 이벤트 처리에 사용된다고 기술되어있다

https://docs.unity3d.com/2019.1/Documentation/ScriptReference/EventSystems.PointerEventData.html

 

Unity - Scripting API: PointerEventData

You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see: You've told us there are code samples on this page which don't work. If you know ho

docs.unity3d.com

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;

public class GraphicRaycasterRaycasterExample : MonoBehaviour
{
    GraphicRaycaster m_Raycaster;
    PointerEventData m_PointerEventData;
    EventSystem m_EventSystem;

    void Start()
    {
        //Fetch the Raycaster from the GameObject (the Canvas)
        m_Raycaster = GetComponent<GraphicRaycaster>();
        //Fetch the Event System from the Scene
        m_EventSystem = GetComponent<EventSystem>();
    }

    void Update()
    {
        //Check if the left Mouse button is clicked
        if (Input.GetKey(KeyCode.Mouse0))
        {
            //Set up the new Pointer Event
            m_PointerEventData = new PointerEventData(m_EventSystem);
            //Set the Pointer Event Position to that of the mouse position
            m_PointerEventData.position = Input.mousePosition;

            //Create a list of Raycast Results
            List<RaycastResult> results = new List<RaycastResult>();

            //Raycast using the Graphics Raycaster and mouse click position
            m_Raycaster.Raycast(m_PointerEventData, results);

            //For every result returned, output the name of the GameObject on the Canvas hit by the Ray
            foreach (RaycastResult result in results)
            {
                Debug.Log("Hit " + result.gameObject.name);
            }
        }
    }
}

유니티 공식 문서에 있는 예제코드

 

GraphicRaycaster가 Canvas에서 오브젝트를 체크해야되기에 당연히 스크립트가 Canvas 오브젝트에 붙어있다는 가정하에 작성된 코드였지만 이거 못보고 30분동안 null값만 들어와서 해맷다....

 

결론

GraphicRaycaster를 굳이 사용하지않고

인벤토리를 예시로 하자면, 버튼 UI위에 아이템 GameObject를 생성시키고 일반적인 RayCaster로 Hit된 아이템 오브젝트를 가져와서 그 아이템이 가지고 있는 정보들을 표시해주는 것이 가장 좋을 것 같다.

 

 

'Unity' 카테고리의 다른 글

[Unity #] Snap 기능 구현??  (0) 2024.03.11
[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

Font-awesome?

Font Awesome는 웹 개발에서 아이콘을 사용하기 위한 오픈 소스 아이콘 폰트 및 CSS 프레임워크

https://fontawesome.com/

 

Font Awesome

The internet's icon library + toolkit. Used by millions of designers, devs, & content creators. Open-source. Always free. Always awesome.

fontawesome.com

 

사용에 앞서 TextMeshPro가 설치되어있는지 Package Manager을 통해 확인하고 미설치시 설치해줍니다

 

https://fontawesome.com/docs/desktop/setup/get-started

 

Get Started on the Desktop

The internet's icon library + toolkit. Used by millions of designers, devs, & content creators. Open-source. Always free. Always awesome.

fontawesome.com

사용할 폰트를 Font-awesome 사이트에서 다운로드 해줍니다, 무료버전을 사용했습니다

 

다운로드 받은 폰트를 선택해주고 위의 사진과 같이 설정값을 바꿔줍니다.

 

사용할 Font-awesome icon의 유니코드를 복사하여 Character Sequence에 붙여넣어줍니다.

 

Generate Font Atlas를 눌러주면 해당 유니코드의 아이콘들이 표시됩니다.

 

 

Atlas Resolution 설정을 과도하게 높은 해상도로 설정해준다면 UI에서 크기를 작게할 시에 흐리게 보일 수 있습니다

해당사진은 4096 x 4096으로 Generate 했을 때의 흐려진 UI입니다

 

생성된 font Asset을 TMP에 적용시켜주고 사용할 유니코드 앞에 \u 를 붙여 사용해주면 위의 그림과 같이 아이콘이 표시됩니다.

 

https://gilchr.ist/blog/using-fontawesome-in-unity

 

Using FontAwesome in Unity — Craig Gilchrist

Ever wanted to use FontAwesome in Unity apps? Well here’s a quick tutorial about how to use the desktop fonts with TextMesh Pro.

gilchr.ist

 

'Unity' 카테고리의 다른 글

[Unity #] Snap 기능 구현??  (0) 2024.03.11
[Unity #] UI 에서 RayCast  (2) 2024.02.13
[Unity #] Rigidbody? Collider?  (1) 2024.01.23
[Unity] SerializeField ? 직렬화 ?  (0) 2024.01.22
[Unity C#] Vector3  (0) 2024.01.22

https://docs.unity3d.com/kr/2021.3/Manual/class-Rigidbody.html

 

리지드바디 - Unity 매뉴얼

Rigidbody 는 GameObject 가 물리 제어로 동작하게 합니다. 리지드바디는 힘과 토크를 받아 오브젝트가 사실적으로 움직이도록 해줍니다. 리지드바디가 포함된 모든 게임 오브젝트는 중력의 영향을

docs.unity3d.com

Rigidbody는 물리 엔진과 상호 작용할 수 있도록 게임 오브젝트에 물리적 특성을 부여하는 컴포넌트이다.

Rigidbody를 사용하면 게임 오브젝트는 물리적인 힘과 중력 등에 영향을 받게 되어 움직임 및 충돌과 같은 물리 시뮬레이션을 구현할 수 있다

Rigidbody가 포함된 모든 게임 오브젝트는 중력의 영향을 받아야 하며 스크립팅을 통해 가해진 힘으로 움직이거나 NVIDIA PhysX 물리 엔진을 통해 다른 오브젝트와 상호 작용해야 한다고 공식 매뉴얼에 기술되어 있다.

 

 

Transform 조작과 RIgidbody 조작의 가장 큰 차이점은 힘의 사용이다.

Rigidbody는 힘과 토크를 받을 수 있지만Transform은 그렇지 않고 트랜스폼도 회전할 수는 있지만 물리를 사용할 때와는 다르다.

Rigidbody에 힘/토크를 더하면 오브젝트 Transform 컴포넌트의 포지션과 회전값을 바꾼다.

그러므로 둘 중에서 하나만을 사용해야 하며 Transform을 바꾸면서 물리를 사용하면 충돌 및 기타 연산에 문제가 발생할 수 있다.

원하는 대로 정확하게 동작시키려면 Collider 나 조인트를 추가해야한다.

콜라이더(Colliders)

콜라이더는 충돌이 일어나게 만들기 위해 Rigidbody에 함께 추가해야 하는 또 다른 유형의 컴포넌트, 두 개의 Rigidbody가 서로 충돌하더라도 두 오브젝트 모두 콜라이더가 추가되어 있지 않으면 물리 엔진은 충돌을 연산하지 않고 콜라이더가 없는 리지드바디는 물리 시뮬레이션 동안 서로를 지나쳐가기만 한다.

 

가장 간단한(그리고 프로세서에 부하를 주지 않는) 콜라이더는 기본 콜라이더 타입입니다. 3D에서는 박스 콜라이더스피어 콜라이더캡슐 콜라이더가 바로 이 타입입니다. 2D에서는 박스 콜라이더 2D와 써클 콜라이더 2D를 사용할 수 있습니다. 복합 콜라이더 를 만들기 위해 이러한 콜라이더를 단일 게임 오브젝트에 원하는 만큼 추가할 수 있습니다

 

일반적으로  Mesh 콜라이더는 서로 충돌하지 않습니다. Mesh 콜라이더가 Convex 로 지정되면 다른 Mesh 콜라이더와 충돌할 수 있습니다. 일반적인 해결책은 움직이는 모든 오브젝트에는 기본 콜라이더를 사용하고 정적 배경 오브젝트에는 Mesh 콜라이더를 쓰는 것입니다

 

연속 충돌 검사(Collision Detection)

연속 충돌 검사는 빠르게 움직이는 콜라이더가 서로 통과하는 것을 방지하는 기능입니다. 일반적인(Discrete) 충돌 검사를 사용할 때 이런 일이 발생할 수 있는데 한 프레임에서 오브젝트 하나가 콜라이더의 이쪽 편에 있고 다음 프레임에서 이미 콜라이더를 지나쳐버리는 것입니다. 빨리 움직이는 오브젝트의 리지드바디에서 연속 충돌 검사를 활성화하면 문제를 해결할 수 있습니다. 충돌 검사 모드를 Continuous 로 설정해 리지드바디가 다른 정적 (즉, 리지드바디가 아닌)메시 콜라이더를 통과하지 않게 합니다. Continuous Dynamic 으로 설정해도 리지드바디가 Continuous 또는 Continuous Dynamic 으로 충돌 검사 모드가 설정된 리지드바디를 통과하지 않습니다.

연속 충돌 검사는 박스-, 구체- 및 캡슐 콜라이더 용으로 지원됩니다. 연속 충돌 검사는 오브젝트가 서로 통과하는 경우에 충돌을 잡기 위한 안전망 역할을 하나 물리적으로 정확한 충돌 결과를 내지는 않습니다. 그러므로 빠르게 움직이는 오브젝트와 관련해 문제가 생기면 TimeManager 인스펙터의 고정 타임 스텝 값을 줄여 보다 정확한 시뮬레이션을 구현하는 방안을 고려할 수 있습니다.

 

  • 두 리지드바디의 상대 Mass 는 둘이 서로 충돌했을 때 어떻게 반응할지를 결정합니다.
  • 한 리지드바디의 Mass 를 다른 리지드바디보다 크게 만들어도 자유 낙하 시 더 빨리 낙하하지 않습니다. 이 경우 Drag 를 사용해야 합니다.
  • Drag 값이 작으면 오브젝트가 무거워 보이고 값이 크면 가벼워 보입니다. Drag 의 일반적인 값은 .001(단단한 금속 덩어리)과 10(깃털) 사이입니다.
  • 오브젝트의 Transform 컴포넌트를 직접 조작하면서도 물리 연산이 필요하다면 리지드바디를 추가하고 키네마틱으로 만듭니다.
  • Transform 컴포넌트를 통해 게임 오브젝트를 이동시키면서도 충돌/트리거 메시지를 받고자 한다면 이동하는 오브젝트에 리지드바디를 추가해야 합니다.
  • 앵글 드래그를 무한대로 설정하는 것 만으로는 오브젝트의 회전을 멈추게할 수는 없습니다.

 

'Unity' 카테고리의 다른 글

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

https://docs.unity3d.com/ScriptReference/SerializeField.html

 

Unity - Scripting API: SerializeField

When Unity serializes your scripts, it only serializes public fields. If you also want Unity to serialize your private fields you can add the SerializeField attribute to those fields. Unity serializes all your script components, reloads the new assemblies,

docs.unity3d.com

SerializeField는 Unity에서 직렬화할 때 필요한 프라이빗 변수를 인스펙터에서 접근 가능하게 만드는 속성(Attribute)이다

이 어트리뷰트는 주로 MonoBehaviour 클래스의 멤버 변수들에 사용되며, 해당 변수를 인스펙터에서 수정할 수 있도록한다.

일반적으로 Unity에서는 인스펙터에서만 값을 지정할 수 있는데, 이를 위해 멤버 변수를 public 으로 선언하는 경우가 있다.

그런데 이는 객체 지향 프로 그래밍의 캡슐화 원칙을 위배하거나 보안상의 이슈를 일으킬 수 있도 있다고 한다. 

SerializeField를 사용하면 멤버 변수를 private로 유지하면서도 해당 변수의 값을 인스펙터에서 설정할 수 있습니다.

이를 통해 변수에 직접 접근하지 않고도 Unity의 직렬화 시스템이 값을 올바르게 처리할 수 있습니다.

using UnityEngine;

public class Example : MonoBehaviour
{
    // SerializeField 어트리뷰트를 사용하여 private 변수를 인스펙터에서 수정 가능하게 함
    [SerializeField]
    private int myPrivateVariable = 42;

    // 다른 코드
}


이렇게 하면 `myPrivateVariable`은 `private`이지만 인스펙터에서 해당 변수에 접근하여 값을 설정할 수 있다.


 

직렬화(Serialization)는 데이터를 다른 형식으로 변환하여 저장하거나 전송하는 과정을 나타낸다. Unity에서 주로 사용되는 직렬화는 두 가지가 존재한다.

데이터 저장 및 로드: Unity에서는 게임 오브젝트의 상태를 저장하고 나중에 불러오기 위해 데이터를 파일에 저장하는 경우가 많은데 이때, 데이터를 파일에 쓰거나 읽을 때 직렬화가 사용된다. 일반적으로 XML, JSON, 또는 binary 형식으로 데이터를 직렬화하여 저장하며, Unity는 `JsonUtility`나 `BinaryFormatter`와 같은 도구를 제공한다.

인스펙터와의 상호 작용: Unity에서는 인스펙터 창에서 게임 오브젝트 및 스크립트의 변수를 조작하고 실시간으로 변경할 수 있다.

이때도 직렬화가 사용되며, `SerializeField` 속성을 통해 특정 변수를 인스펙터에서 수정 가능하도록 제공한다.

Unity는 이러한 직렬화를 통해 게임 상태를 영구적으로 저장하거나 인스펙터에서 손쉽게 수정할 수 있도록 지원하여, 게임의 개발 및 유지보수를 용이하게 합니다.

'Unity' 카테고리의 다른 글

[Unity #] 유니티에서 Font-awesome 사용하기  (0) 2024.01.29
[Unity #] Rigidbody? Collider?  (1) 2024.01.23
[Unity C#] Vector3  (0) 2024.01.22
2024-01-10  (0) 2024.01.10
2024_01_08  (0) 2024.01.08

https://docs.unity3d.com/kr/current/Manual/VectorCookbook.html
https://docs.unity3d.com/kr/530/ScriptReference/Vector3.html 

 

UnityEngine.Vector3 - Unity 스크립팅 API

Representation of 3D vectors and points.

docs.unity3d.com

 

중요 클래스 - Vector - Unity 매뉴얼

벡터는 방향과 크기를 설명할 수 있는 기본적인 수학적 개념입니다. 게임과 앱에서 벡터는 캐릭터의 포지션, 오브젝트의 이동 속도 또는 두 오브젝트 간 거리와 같은 기본적인 프로퍼티를 설명

docs.unity3d.com

 


3D 벡터와 점의 표현.

이 구조는 유니티 전체에서 3D 위치와 방향을 전달하는 데 사용됩니다. 그것은 또한 일반적인 벡터 작업을 수행하기 위한 기능을 포함한다.

아래에 나열된 기능 외에도, 다른 클래스도 벡터와 포인트를 조작하는 데 사용할 수 있습니다. 예를 들어 쿼터니언과 Matrix4x4 클래스는 벡터와 점을 회전하거나 변환하는 데 유용합니다.


C# 에서의 Vector3는 Class가 아닌 구조체이다

오브젝트는 Class라 적혀있는 반면 Vector에 대한 설명은 struct라 기술되어 있다

 

`Vector3`는 Unity의 `UnityEngine` 네임스페이스에 속한 구조체(struct)이다. 이 구조체는 다음과 같이 정의되어 있다

namespace UnityEngine {
    public struct Vector3 {
        public float x;
        public float y;
        public float z;

        // Constructors, methods, and other members are also part of the Vector3 struct.
    }
}


Vector3 구조체는 세 개의 float 형을 가지는 `x`, `y`, `z`를 가지며, 3D 벡터의 속성을 나타낸다.
이러한 구조체는 Value Type으로 new를 사용해도 힙 영역에 할당되지 않고 메모리의 스택 영역에 할당되기 때문에 참조가 아닌 직접적인 값으로 복사되는데 이는 메모리 관리 측면에서 유리하다고 되어있다.

힙에 할당되지 않는데 왜 new를 사용하는 거지…? 라는 의문에 답이 밑 링크 블로그에 적혀 있었다.
또한 Vector3에 new를 사용한다고 가비지가 생기지 않는다고도 적혀있다.

new로 할당한다고 하더라도 구조체는 여전히 스택에 생성되는 값 타입이다. C#이 이 문법을 지원하는 이유는 통일성과 생성자 호출을 위해서이다.

출처: https://3dmpengines.tistory.com/1566 [3DMP:티스토리]

Vector3에는 많은 함수들이 포함되어있다.
일부 에셋들의 스크립트를 훑어본 결과 대부분의 함수들이 사용되고 있었기에 어떠한 기능의 함수들이 존재하는지 정도는 알고 있는 것이 좋을 것 같다. 

Angle(from, to) from과 to 벡터 간의 각도 계산
ClampMagnitude(vector, maxLength) 벡터의 크기를 최대 길이 maxLength로 제한한 복사본 반환
Cross(lhs, rhs) lhs와 rhs 벡터의 외적 계산
Distance(a, b) 두 벡터 a와 b 간의 거리 반환
Dot(lhs, rhs) lhs와 rhs 벡터의 내적 계산
Lerp(from, to, t) from에서 to로 선형 보간, 보간 계수는 t
LerpUnclamped(from, to, t) from에서 to로 제한 없는 선형 보간, 보간 계수는 t
Max(lhs, rhs) lhs와 rhs 벡터의 각 요소 중 큰 값으로 이루어진 벡터 반환
Min(lhs, rhs) lhs와 rhs 벡터의 각 요소 중 작은 값으로 이루어진 벡터 반환
MoveTowards(current, target, maxDistanceDelta) 현재 위치 current에서 목표 위치 target로 이동, 최대 이동 거리 제한은 maxDistanceDelta
Normalize(vector) 대상 벡터 vector의 크기를 1로 만듦
OrthoNormalize(normalized, tangent) 벡터를 정규화하고 서로 수직으로 만듦: 정규화된 벡터 normalized, 수직 벡터 tangent
Project(vector, onNormal) 대상 벡터 vector를 다른 벡터 onNormal에 투영
ProjectOnPlane(vector, planeNormal) 대상 벡터 vector를 평면 법선 벡터 planeNormal에 투영
Reflect(vector, planeNormal) 대상 벡터 vector를 평면 법선 벡터 planeNormal을 기준으로 반사
RotateTowards(current, target, maxRadiansDelta, maxMagnitudeDelta) 현재 벡터 current를 목표 벡터 target로 회전, 최대 각도 변화량 제한은 maxRadiansDelta, 최대 크기 변화량 제한은 maxMagnitudeDelta
Scale(lhs, rhs) 두 벡터 lhs와 rhs를 각 성분별로 곱함
SignedAngle(from, to, axis) 시작 벡터 from과 대상 벡터 to 간의 부호 있는 각도 계산, 축 벡터는 axis
Slerp(from, to, t) 두 벡터 from과 to 사이를 구면 선형 보간, 보간 계수는 t
SlerpUnclamped(from, to, t) 두 벡터 from과 to 사이를 제한 없는 구면 선형 보간, 보간 계수는 t
SmoothDamp(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) 현재 벡터 current를 목표 벡터 target로 부드럽게 이동, 현재 속도는 currentVelocity, 부드러운 이동에 걸리는 시간은 smoothTime, 최대 속도는 maxSpeed, 프레임 간격은 deltaTime

 

'Unity' 카테고리의 다른 글

[Unity #] Rigidbody? Collider?  (1) 2024.01.23
[Unity] SerializeField ? 직렬화 ?  (0) 2024.01.22
2024-01-10  (0) 2024.01.10
2024_01_08  (0) 2024.01.08
2024-01-04// 옵셔널 체이닝, Rect Transform, Bounds  (0) 2024.01.04

유니티 에디터의 Scene뷰와 Game뷰 에는 오브젝트 조작과 가시성의 편의를 위해 Gizmo란 것이 존재하는데

기존엔 Scene 뷰에서 조작이 가능할뿐 런타임 중에는 Gizmo를 활용할 수 없었지만 Github에 좋은게 있다.

https://github.com/HiddenMonk/Unity3DRuntimeTransformGizmo

 

GitHub - HiddenMonk/Unity3DRuntimeTransformGizmo: A runtime transform gizmo similar to unitys editor so you can translate (move,

A runtime transform gizmo similar to unitys editor so you can translate (move, rotate, scale) objects at runtime. - GitHub - HiddenMonk/Unity3DRuntimeTransformGizmo: A runtime transform gizmo simil...

github.com

21.3.25 버젼 기준 잘 동작하며 적용법도 아주 간편한데 메인이 되는 카메라에 TransformGizmo 스크립트만 넣어주면 작동이 된다

 

RunTime 중에도 기즈모가 표시되고 Position, Rotation, Scale 등 조작이 가능하다

또한 에디터 뿐만아니라 빌드 이후의 실행파일에서도 동작한다.

 

맨 아래 Selection Mask 항목을 통해서 선택하고 싶은 LayerMask도 설정할 수 있다.

 

해당 소스에서는 UnityEngine.GL 라이브러리를 이용하여 기즈모와 같이 그려주고

카메라가 모든 오브젝트의 렌더링을 마친 후에 호출되는 함수 OnPostRender() 에서 호출해주고 있었다.

void DrawQuads(List<Vector3> lines, Color color)
		{
			if(lines.Count == 0) return;

			GL.Begin(GL.QUADS);
			GL.Color(color);

			for(int i = 0; i < lines.Count; i += 4)
			{
				GL.Vertex(lines[i]);
				GL.Vertex(lines[i + 1]);
				GL.Vertex(lines[i + 2]);
				GL.Vertex(lines[i + 3]);
			}

			GL.End();
		}

 

OpenGL 사용에 대한 블로그

https://bloodstrawberry.tistory.com/705

 

유니티 GL로 화면에 그림 그리기

Unity 전체 링크 참고 - 안드로이드 OpenGL 설정 - OpenGL로 화면에 선 그리기 - OpenGL로 화면 터치 이펙트 만들기 - OpenGL로 멀티 터치 이펙트 만들기 GL은 로우 레벨(Low-level) 그래픽 라이브러리다. 아래

bloodstrawberry.tistory.com

 

'Unity' 카테고리의 다른 글

[Unity] SerializeField ? 직렬화 ?  (0) 2024.01.22
[Unity C#] Vector3  (0) 2024.01.22
2024_01_08  (0) 2024.01.08
2024-01-04// 옵셔널 체이닝, Rect Transform, Bounds  (0) 2024.01.04
Unity2D) 기본캐릭터 이동조작  (0) 2022.09.05

해당 에셋의 일부 소스입니다

https://assetstore.unity.com/packages/tools/level-design/home-designer-2023-realtime-interior-designer-178561

 

Home Designer 2023 - Realtime Interior Designer | 레벨 디자인 | Unity Asset Store

Get the Home Designer 2023 - Realtime Interior Designer package from exoa and speed up your game development process. Find this & other 레벨 디자인 options on the Unity Asset Store.

assetstore.unity.com

유료 에셋이라 코드내용을 가려야 할 것 같다.....

 

// Class InfoPopup 
public static UnityEvent OnClickMove = new UnityEvent();
public static UnityEvent OnClickDelete = new UnityEvent();

override protected void Awake()
        {

            Instance = this;

            thumb = contentGo.GetComponentInChildren<RawImage>();
            text = contentGo.GetComponentInChildren<TMP_Text>();

            trashBtn?.onClick.AddListener(() =>
            {
                OnClickDelete.Invoke();
                Hide();
            });
            rotateBtn?.onClick.AddListener(() =>
            {
                OnClickDelete.Invoke();
                Hide();
            });
            moveBtn?.onClick.AddListener(() =>
            {
                OnClickMove.Invoke();
                Hide();
            });
            variantsBtn?.onClick.AddListener(() =>
            {
                MaterialPopup.Instance.ShowMode(MaterialPopupUI.Mode.Module, CurrentTarget.gameObject);
                Hide();
            });
            base.Awake();
        }

 

UnityEvent는 Unity에서 이벤트를 구현하기 위한 클래스이고,

OnClickMove는 이벤트의 인스턴스로, UI상의 버튼 클릭과 같은 상황에서 호출할 함수들을 등록할 수 있도록 한다

이 코드에서는 클릭 이벤트가 발생할 때 호출될 함수들을 OnClickMove에 등록하고 있다.

 

도대체가 OnclickDelete와 OnClickMove는 뭐가 어떻게 작동되고 있는걸까, 너무 궁금햇다.

 

열심히 Hierarchy에서 뒤적거린 결과 메인으로 작동되는 소스코드에서 해당 이벤트를 정의해주고 있었다

        InfoPopup.OnClickDelete.AddListener(DeleteSelectedObject);
	InfoPopup.OnClickMove.AddListener(OnClickMove);
        
        private void DeleteSelectedObject()
        {
          코드 내용
        }



        /// <summary>
        ///  Clicking the "move" button on the popup
        ///  will recreate a ghost object that is editable
        /// </summary>
        private void OnClickMove()
        {
     		코드 내용
        }

그니까

InfoPopup 에 선언된 이벤트(OnClickDelete)에 AddListner()로 이벤트에 사용될 함수를 등록해주었고,

Awake() 함수에서 프로그램이 실행 되었을 때, UI상의 버튼 클릭 시 발생할 이벤트를 등록해주었다.

 

RayCastHit 의 구조

barycentricCoordinate 삼각형에서의 충돌 지점의 바리켄트릭 좌표입니다. 바리켄트릭 좌표는 삼각형 내의 점을 나타내는 좌표 시스템입니다.
collider 충돌한 오브젝트의 콜라이더입니다.
distance 레이의 시작점에서 충돌 지점까지의 거리입니다
lightmapCoord 충돌 지점에서의 라이트맵 좌표입니다.
normal 충돌 지점에서의 표면의 법선 벡터입니다.
point 충돌한 지점의 월드 좌표입니다.
rigidbody 충돌한 콜라이더가 속한 리지드바디입니다. 콜라이더가 리지드바디에 속해있지 않으면 null입니다.
textureCoord 충돌 지점에서의 텍스처 좌표입니다.
textureCoord2 충돌 지점에서의 보조 텍스처 좌표입니다.
transform 충돌한 콜라이더나 리지드바디의 트랜스폼입니다.
triangleIndex 충돌한 삼각형의 인덱스입니다.

 

 

 

'Unity' 카테고리의 다른 글

[Unity] SerializeField ? 직렬화 ?  (0) 2024.01.22
[Unity C#] Vector3  (0) 2024.01.22
2024-01-10  (0) 2024.01.10
2024-01-04// 옵셔널 체이닝, Rect Transform, Bounds  (0) 2024.01.04
Unity2D) 기본캐릭터 이동조작  (0) 2022.09.05

+ Recent posts