Develop/Unity

[Unity] 조이스틱 만들기

LKM0222 2025. 5. 22. 11:44
728x90
반응형

이 글을 참고해서 작성하였습니다.

 

[Unity3D] Programming - 모바일 가상 조이스틱 구현하기

Programming - 모바일 가상 조이스틱 구현하기 작성 기준 버전 :: 2019.2 - 2019.4 [이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다] 요즘에는 오락실이 별로 많지 않지만 제가 어렸을 때 오

wergia.tistory.com


 

대부분의 게임에선 캐릭터의 움직임을 구현해야한다.

 

어떤 게임은 마우스, 또 다른 게임은 키보드 조이패드 등 여러 입력법이 있지만, 이번엔 모바일 환경에서 조이스틱을 구현 해, 입력을 받으려고 한다.

 

이런식으로 이미지 두개를 겹치고, back 아래에 front가 오도록 배치했다. (다른 원형 이미지를 써도 무방하다.)

그리고, JoyStick_Front를 좌측 하단에서부터 계산해주기 위해 JoyStick_Back의 앵커를 좌측 하단으로 맞춰준다.

 

using UnityEngine;
using UnityEngine.EventSystems;

public class JoyStick : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("Drag Start");
    }

    public void OnDrag(PointerEventData eventData)
    {
        Debug.Log("Drag");
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("Drag End");
    }
}

스크립트를 만들어, IBeginDragHandler, IDragHandler, IEndDragHandler를 상속받아준다.

상속 받아준 후, 각 인터페이스를 구현해야한다.

 

각 핸들러는 EventSystems에 포함되어있는 핸들러이기 때문에 using선언을 해주어야한다.

그리고, 생성한 스크립트를 JoyStick_Back에 붙여주고, 드래그를 실험해본다.

 

이제, 레버를 클릭한 위치에 따라 레버를 이동시켜줘야한다.

using UnityEngine;
using UnityEngine.EventSystems;

public class JoyStick : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [SerializeField] private RectTransform lever; //추가
    private RectTransform rectTransform; //추가

    void Awake() //추가
    {
        rectTransform = GetComponent<RectTransform>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        var inputDir = eventData.position - rectTransform.anchoredPosition; //추가
        lever.anchoredPosition = inputDir; //추가
    }

    public void OnDrag(PointerEventData eventData)
    {
        var inputDir = eventData.position - rectTransform.anchoredPosition; //추가
        lever.anchoredPosition = inputDir; //추가
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        lever.anchoredPosition = Vector2.zero; //추가
    }
}

 

 

 

잘 움직이는걸 확인할 수 있다.

하지만, 레버가 back의 밖으로 나가는 현상이 발생한다.

 

코드를 다음과 같이 수정하자.

public class JoyStick : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [SerializeField] private RectTransform lever;
    private RectTransform rectTransform;

    [SerializeField, Range(10f, 150f)] private float leverRange; // 추가

    void Awake()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        var inputDir = eventData.position - rectTransform.anchoredPosition;
		
        // 추가
        var clampedDir = inputDir.magnitude < leverRange ? inputDir : inputDir.normalized * leverRange;

        lever.anchoredPosition = clampedDir;
    }

    public void OnDrag(PointerEventData eventData)
    {
        var inputDir = eventData.position - rectTransform.anchoredPosition;
		
        // 추가
        var clampedDir = inputDir.magnitude < leverRange ? inputDir : inputDir.normalized * leverRange;
        
        lever.anchoredPosition = clampedDir;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        lever.anchoredPosition = Vector2.zero;
    }
}

범위를 지정해줘서, 범위를 벗어난다면, nomalized 값에 range를 곱해 반환한다.

 

범위를 측정해보니, 레버가 나가지 않는 선은 25정도가 적당해보인다.

아까 선언해둔 변수를 25로 맞춰준다.

이후, 실행해보면 다음과 같다.

 

이렇게 범위를 넘어가지 않게 된다. (범위는 알아서 적당히 조절하면 된다.)

 

이제, 이 조이스틱의 입력값을 받아오자.

using UnityEngine;
using UnityEngine.EventSystems;

public class JoyStick : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [SerializeField] private RectTransform lever;
    private RectTransform rectTransform;

    [SerializeField, Range(10f, 150f)] private float leverRange;

	// 추가
    [SerializeField] private Vector2 inputVector;
    [SerializeField] private bool isInput;

    void Awake()
    {
        rectTransform = GetComponent<RectTransform>();
    }
    
    void Update() // 추가
    {
        if (isInput)
        {
            InputControlVector();
        }
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
    	// 추가
        ContolJoystickLever(eventData);
        isInput = true;
    }

    public void OnDrag(PointerEventData eventData)
    {
    	// 추가
        ContolJoystickLever(eventData);
        isInput = false;
    }
	
    // 추가
    public void ContolJoystickLever(PointerEventData eventData)
    {
        var inputDir = eventData.position - rectTransform.anchoredPosition;
        var clampDir = inputDir.magnitude < leverRange ? inputDir : inputDir.normalized * leverRange;
        lever.anchoredPosition = clampDir;
        // clampdir는 해상도 기반으로 만들어진 값이라, range로 나눠서 input에 넣어줌.
        inputVector = clampDir / leverRange;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        lever.anchoredPosition = Vector2.zero;
    }
    
    // 추가
    private void InputControlVector() 
    {
        // 입력값 전달.
        // 추후 캐릭터 오브젝트에 inputVector값 전달.
        Debug.Log(inputVector);
    }
}

 

이런식으로 수정해준다.

InputControlVector 함수에서 캐릭터 오브젝트에게 움직임을 전달해주면 된다. 

 


여기까지가 조이스틱을 만드는 과정이다.

 

여기서 한단계 나아가서, 조이스틱이 안보이다가, 클릭하는 지점에 조이스틱이 생성되고, 움직임을 전달한 후, 드레그를 종료하면, 조이스틱이 사라지게 만들고싶다.

 

간단하게 생각해보면, 클릭 위치를 받아내서, 조이스틱 오브젝트의 위치를 클릭한 위치로 이동시키고, 손가락을 떼면, 오브젝트를 비활성화하면 해결될 것 같다.

 

일단, 오브젝트의 계층구조를 다음과 같이 변경해준다.

기존에 만들어뒀던 조이스틱을, 터치영역을 감지할 오브젝트 안에 배치해준다. 그리고 터치 영역에 기존에 만들어둔 스크립트를 부착한다.

터치영역은 화면이 어떤 크기이던, 꽉 차야되기 때문에, 

Anchor Presets를 stretch로 맞춰준다.

 

그리고, 조이스틱에 붙어있던 스크립트는 모두 삭제해준다.

화면이 클릭될때마다 나타나게 하기 위해서 비활성화 처리 해두었다.

 

그리고 스크립트를 수정해준다.

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

public class JoyStick : MonoBehaviour, IPointerUpHandler, IDragHandler, IPointerDownHandler
{
    [SerializeField] private RectTransform joystick;
    [SerializeField] private RectTransform lever;

    [SerializeField, Range(10f, 150f)] private float leverRange;


    [SerializeField] private Vector2 inputVector;
    [SerializeField] private bool isInput;

    // LifeCycle
    void Update()
    {
        if (isInput)
        {
            InputControlVector();
        }
    }

    // interface
    public void OnPointerDown(PointerEventData eventData)
    {
        joystick.position = eventData.position;
        joystick.gameObject.SetActive(true);
        ContolJoystickLever(eventData);
        isInput = true;
    }
    public void OnDrag(PointerEventData eventData) // 드래그 중
    {
        ContolJoystickLever(eventData);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        inputVector = Vector2.zero;
        lever.anchoredPosition = Vector2.zero;
        joystick.gameObject.SetActive(false);
        isInput = false;
    }


    // Method
    public void ContolJoystickLever(PointerEventData eventData)
    {
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(joystick, eventData.position, eventData.pressEventCamera, out localPoint);

        Vector2 clamped = Vector2.ClampMagnitude(localPoint, leverRange);
        lever.anchoredPosition = clamped;
        inputVector = clamped / leverRange;
    }

    private void InputControlVector()
    {
        //입력값 전달.
        // Debug.Log(inputVector);
    }
}

 

코드가 많이 수정되었는데, 일단 BeginDrag, EndDrag핸들러를 각각 PointerDown, PointerUP으로 바꿔줬다.

클릭이 시작되면서 바로, 조이스틱이 나타나고 계산할 준비가 되어야하기 때문이다.

클릭이 종료되면, 조이스틱은 비활성화 처리해주고, 다음 계산을 위해 각 값들을 0으로 만들어준다.

 

그리고 새로 짜둔 코드에서 주의깊게 봐야될 코드는 RectTransformUtility인것같다.

UGUI에서 RectTransform의 월드좌표 스크린좌표를 연동시킬 때 주로 쓰는 클래스인것 같다.

자세한 설명은 아래 링크를 참고하자.

 

[Unity] RectTransformUtility 팁

https://docs.unity3d.com/ScriptReference/RectTransformUtility.html Unity - Scripting API: RectTransformUtility Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggeste

yeobi27.tistory.com

 

ScreenPointToLocalPointInRectangle 함수는 화면 공간 지점을 RectTransform의 로컬 공간에서 사각형 평면에 있는 위치로 변환하는 함수이다.

이 함수로, 좌표를 변환한 후, 레버의 위치를 클릭한 지점부터 계산하도록 설정했다.

그 후, ClampMagnitude를 사용해 미리 지정한 범위를 넘지 않는 값을 계속 반환하도록 했다.

 

나머지 코드는 이전 조이스틱과 같다.

 

 

이렇게 어디를 클릭하던 조이스틱이 튀어나오도록 만들 수 있다.


1. 조이스틱 만들기 (이미지는 아무거나)

2. 조이스틱 배경의 Anchor Presets를 좌측 하단으로 설정

3. 클릭을 위한 배경 만들기 AnchorPresets를 Stretch로 설정

4. 코드를 배경에 넣기

5. 조이스틱 레버가 조이스틱 배경을 벗어나지 못하는 지점으로 범위를 설정하기

 

728x90
반응형