-
[Unity][ClickEvent] 버튼 클릭 이벤트를 처리하는 n가지 방법앱 개발/Unity, C# 2024. 2. 18. 00:12
나 이제 웹 개발 해.... 그래서 웹 개발 관련 정리하기 전에 호다닥 네이버에 정리해둔 거를 여기에 업데이트 하려 한다.
확실히 티스토리가 구글에 노출이 잘 돼서 아예 버릴 순 없을듯.
역시 이번에도 네이버에 있는 내용 + 알파.
1. [MonoBehaviour] Update()에서 Input.GetMouseButtonDown(0)
using UnityEngine; public class ClickEventTest : MonoBehaviour { void Update() { if (Input.GetMouseButtonDown(0)) { // 마우스 왼쪽 버튼을 눌렀을 때 수행할 작업 } } }
가장 기본이 되는 방법.
다들 알고 있듯이 GetMouseButtonDown은 마우스 버튼을 눌렀을 때, GetMouseButton은 마우스 버튼을 누르는 동안, 그리고 GetMouseButtonUp은 누르던 마우스 버튼을 뗐을 때 true를 반환한다.
인수로 넣는 0, 1, 2는 각각 마우스 왼쪽 버튼, 마우스 오른쪽 버튼, 그리고 마우스 휠과 대응된다.
마우스 드래그 작업을 다루기 위해 코드를 아래와 같이 활용할 수 있다.
using UnityEngine; public class ClickEventTest : MonoBehaviour { private bool isDragging = false; void Update() { if (Input.GetMouseButtonDown(0)) { isDragging = true; } if (Input.GetMouseButtonUp(0)) { isDragging = false; } if (isDragging) { // 마우스 왼쪽 버튼을 드래그할 때 수행할 작업 // Input.mousePosition으로 마우스 커서 위치를 받아올 수 있다. // 다만 이렇게 받아온 커서 위치는 스크린 상의 좌표로 표시되기 때문에 // 3D 상의 오브젝트와 인터랙션 하기 위해서는 변환이 필요하다. } } }
Input은 GetMouseButton 이외에도 GetKey와 같은 다양한 메소드나 mousePosition 같은 값을 제공한다.
이번 포스팅에서는 마우스 이벤트만 다루기로 했으므로 나머지는 아래의 링크에서 둘러보는걸로..!
2. [MonoBehaviour] Collider을 가진 객체가 정의하는 OnMouseDown()
using UnityEngine; public class ClickEventTest : MonoBehaviour { void OnMoueDown() { Debug.Log("mouse pressed on the object(collider)"); } void OnMouseDrag() { Debug.Log("mouse dragging the object(collider)"); } void OnMouseEnter() { Debug.Log("mouse entered the object(collider)"); } void OnMouseExit() { Debug.Log("mouse exit the object(collider)"); } void OnMouseOver() { Debug.Log("mouse is over the object(collider)"); } void OnMouseUp() { Debug.Log("mouse released on object(collider)"); } }
1번의 경우, 마우스가 특정 객체와 인터랙션 해야 할 경우 코드를 짜기 번거로울 수 있다.
만약 그 특정 객체가 Collider을 가지고 있을 경우, 위의 코드처럼 Monobehaviour의 콜백 함수를 활용해보자.
3. [MonoBehaviour] GUI Button 객체에서 onClick에 함수 할당
using UnityEngine; using UnityEngine.UI; public class ClickEventTest : MonoBehaviour { [SerializeField] private Button m_firstButton; [SerializeField] private Button m_secondButton; [SerializeField] private Button m_thirdButton; void Start() { m_firstButton.onClick.AddListener(OnClickButton); m_secondButton.onClick.AddListener(() => OnClickButtonWithParameters(2)); m_secondButton.onClick.AddListener(delegate { Debug.Log("clicked second button"); }); m_thirdButton.onClick.AddListener(OnClickButton); } void OnClickButton() { Debug.Log("clicked button"); } void OnClickButtonWithParameters(int buttonNo) { Debug.Log("clicked button with number " + buttonNo); } }
GUI 버튼의 경우, 인스펙터 창에서 직접 함수를 추가해줄 수도 있다.
인스펙터 창의 OnClick() 리스트에 + 버튼을 눌러, 실행하고 싶은 함수가 정의된 스크립트를 가지고 있는
게임 오브젝트를 추가한 후 함수를 지정해주면 된다.
4. [UIElements] Button에서 clickable.clicked에 함수 할당
using UnityEngine; using UnityEngine.UIElements; public class ClickEventTest : MonoBehaviour { [SerializeField] private UIDocument m_UIDocument; private Button m_firstButton; private Button m_secondButton; private Button m_thirdButton; private int cnt = 0; void Start() { var rootElement = m_UIDocument.rootVisualElement; m_firstButton = rootElement.Q<Button>("FirstButton"); m_secondButton = rootElement.Q<Button>("SecondButton"); m_thirdButton = rootElement.Q<Button>("ThirdButton"); // connect handler with visual elements m_firstButton.clickable.clicked += OnClickButton; m_secondButton.clickable.clicked += () => { OnClickButtonWithParameters(2); }; m_thirdButton.clickable.clicked += delegate { cnt += 1; Debug.Log("now count is " + cnt); }; } private void OnClickButton() { Debug.Log("clicked button"); } private void OnClickButtonWithParameters(int buttonNo) { Debug.Log("clicked button with number " + buttonNo); } }
코드로 보면 3번과 비슷해보인다. 원리가 다른듯 비슷한 코드의 세계...
이 정도면 clikable.clicked에 할당할 수 있는 콜백 함수의 형태는 다 보여주지 않았나...
5. [UIElements] VisualElement에서 RegisterCallback<PointerDownEvent>()
using UnityEngine; using UnityEngine.UIElements; public class ClickEventTest : MonoBehaviour { [SerializeField] private UIDocument m_UIDocument; private VisualElement m_draggingArea; private bool isDragging = false; void Start() { var rootElement = m_UIDocument.rootVisualElement; m_draggingArea = rootElement.Q<VisualElement>("DraggingArea"); m_draggingArea.RegisterCallback<PointerDownEvent>(StartDrag); m_draggingArea.RegisterCallback<PointerMoveEvent>(MoveDrag); m_draggingArea.RegisterCallback<PointerUpEvent>(StopDrag); m_draggingArea.RegisterCallback<WheelEvent>(MoveWheel); } private void StartDrag(PointerDownEvent evt) { Debug.Log("start dragging " + evt.position); isDragging = true; } private void MoveDrag(PointerMoveEvent evt) { if (!isDragging) return; switch (evt.pressedButtons) { case 1: // Left Button Debug.Log("dragging left button " + evt.position); break; case 2: // Right Button Debug.Log("dragging right button " + evt.position); break; case 4: // Middle Button Debug.Log("dragging middle button(wheel) " + evt.position); break; default: break; } } private void StopDrag(PointerUpEvent evt) { isDragging = false; } private void MoveWheel(WheelEvent evt) { Debug.Log("moving mouse wheel " + (evt.delta * wheelDirection)); return; } private void OnDestroy() { m_draggingArea.UnregisterCallback<PointerDownEvent>(StartDrag); m_draggingArea.UnregisterCallback<PointerMoveEvent>(MoveDrag); m_draggingArea.UnregisterCallback<PointerUpEvent>(StopDrag); m_draggingArea.UnregisterCallback<WheelEvent>(MoveWheel); } }
이게 또 재미있더라.
4번의 Button 또한 VisualElement의 한 종류이기에, 더 원론적인 방법이라고 볼 수 있다.
Pointer~Event 말고 Mouse~Event 써도 된다. 근데 나는 포인터가 더 귀여워(?) 보여서 이걸 썼다. 더 흥미롭달까.
더 흥미로웠던 건, 앞서 다뤘던 Input의 경우 인자로 어떤 버튼인지 가르쳐줬다면,
UIElement 이벤트에서는 속성으로 pressedButtons라는 값을 가지고 있다는 점이다.
마찬가지로 숫자에 버튼이 하나씩 대응되는데, 2의 거듭제곱에 대응되는 게 변태같다.
1, 2, 4가 각각 마우스 왼쪽 버튼, 마우스 오른쪽 버튼, 마우스 휠 버튼에 대응되니까, 이들의 합으로 모든 상황을 나타낼 수 있다.
예시로, pressedButtons의 값이 3이라면 마우스 왼쪽 버튼과 오른쪽 버튼을 동시에 누른 상황이라는 말이다.
이외에도 clickCount와 같은 속성을 잘 활용하면 UIElement의 VisualElement에서 더블 클릭을 다룰 수도 있을 것 같다.
여기도 참고자료는 유니티 공홈.
유니티 공홈이 진짜 알고 보면 있을 게 다 있는데, 처음 보면 뭔 말인지 하나도 모르겠달까...
그래도 이제 참고하면서 코드를 직접 써봐야 이해도 잘 되고 실력도 느는 것 같다.
6. Custom Raycaster 작성(추가)
이건 UIToolkit -> Nova UI Asset(Unity UI)로 migration하면서, 기존의 객체 선택 방법을 사용할 수 없어져서 새로 발견한 방법이다.
사실 이게 근본이라고 생각한다.
하지만 왠지 계산을 많이 잡아 먹고 성능에 문제가 생길 것만 같은 느낌에 작성하기 꺼려진달까...
심지어 hover 상태도 체크해야 한다면... 캬... (근데 내가 그러고 있음)
근데 위에 방법이 다 안 되면 어떡해 그냥 써야지!
대신 Layer이랑 같이 쓰자. (아래의 LayerValue.CUSTOM_MASK 변경)
[RequireComponent(typeof(UnityCamera))] public class CustomRaycaster : MonoBehaviour { #region Variables private UnityCamera _camera; private UnityCamera Camera { get { if (_camera == null) { _camera = GlobalObjectManager.MainCamera.GetComponent<UnityCamera>(); } return _camera; } } private RaycastHit _hitInfo; private CustomController _hoveredController; private CustomController _selectedController; #endregion Variables #region Unity Methods private void Update() => RaycastProp(); #endregion Unity Methods private void RaycastProp() { _hitInfo = new RaycastHit(); var raycastHits = Physics.RaycastAll(Camera.ScreenPointToRay(Input.mousePosition), float.MaxValue, LayerValue.CUSTOM_MASK); if (raycastHits.Length > 0) { // 거리가 가까운 순으로 정렬 Array.Sort(raycastHits, (x,y) => x.distance.CompareTo(y.distance)); var index = Array.FindIndex(raycastHits, CheckCurrentCollider); _hitInfo = raycastHits[index < 0 ? 0 : index]; if (_hitInfo.collider.gameObject.TryGetComponent<CustomController>(out var customController)) { // _hoveredController 설정 AssignHoveredController(customController); } else { // _hoveredController 해제 AssignHoveredController(null); } } else { // _hoveredController 해제 AssignHoveredController(null); } // 아래의 메서드에서 마우스 클릭 처리 // _hoveredController가 있고 _selectedController와 다르면 _selectedController 변경 // 나머지는 로직에 따라 달라짐 SetCustomController(); } }
사실 굉장히 복잡하게 짜여진 클래슨데, 핵심만 뽑아왔다. 여러분이라면 충분히 응용하실 수 있을 거라고 생각함..!
이 방법의 장점은, 선택된 객체가 다른 객체 뒤에 있어도 조작할 수 있도록 코드를 작성할 수 있다는 것이다. (그 부분은 복잡해서 생략함)
내 상황에서는, UI 뒤에 있는 객체를 선택해야 했고 또 여러 개의 객체가 겹쳐있을 때 현재 조작 중인 객체를 우선적으로 처리해주어야 했는데, 이 작업을 모두 할 수 있는 방법이 이 방법 뿐이었다.
물론 이 컴포넌트만 작성해서 카메라에 붙인다고 끝나는 건 아님.
객체에 Collider이 붙어 있고, Custom layer로 설정되어 있어야 한다.
나는 Hover 상태도 체크해서 시각적으로 보여주기 위해 Update마다 Raycast 하고 가장 아래에서 클릭 처리를 해주었는데,
그냥 클릭했는지만 판단해도 된다면 클릭 판단하는 조건문을 가장 위에 넣고 클릭 안 했을 땐 리턴하도록 하자.
--
유니티에는 워낙 꼼수가 많다고 해야 하나 여튼 하나를 구현하기 위한 방법이 여러 갈래라
내가 모르는 방법이 있거나 아니면 새로 생기는 방법이 있을 것 같아서 제목을 n가지 방법이라고 했다.
실제로 작성하다가 한 가지 방법이 더 생각나서 추가하기도 했고... + 티스토리 쓰면서 하나 더 추가했지롱
여튼 나중에 뭔가 바뀌면 포스팅 업데이트하러 와야겠다.
끗~
'앱 개발 > Unity, C#' 카테고리의 다른 글
[Unity] 커서 모양 바꾸기 + 커서 회전하기 [How to rotate custom cursor texture] (2) 2024.02.18 [Unity][UI Toolkit] 버튼 누르는 동안 동작 실행 (0) 2024.02.18 [Unity][UI Toolkit] UI Document를 생성하고 Visual Tree Asset(uxml 파일) 적용시키기 (0) 2022.09.06 [Unity][UI Toolkit] vector 형식(svg 형식)으로 아이콘을 불러오는 방법 (0) 2022.09.04 [Unity][UI Builder] 그림(아이콘)과 텍스트(라벨)가 둘 다 있는 버튼 만들기 (0) 2022.08.26