Unity의 모바일 터치 입력 조이스틱

모바일 게임에서 제어 가능한 캐릭터를 만들려면 화면에 시각적으로 표현되고 터치 입력에 반응하는 버튼을 추가해야 합니다.

이 튜토리얼에서는 주로 터치스크린 휴대폰의 이동 제어에 적합한 Unity에 조이스틱과 같은 버튼을 만드는 방법을 보여 드리겠습니다.

아래 예를 사용하여 더 많은 버튼과 조이스틱을 추가할 수도 있습니다. 그럼 시작해보자!

1단계: 필요한 모든 스크립트 만들기

모바일Joystick_UI.cs

using UnityEngine;
using UnityEngine.UI;

public class MobileJoystick_UI : MonoBehaviour
{

    //Mobile controller graphics
    public Sprite navigationCircle;
    public Sprite navigationButton;
    //Use this in your movement script for the input control
    public Vector2 moveDirection;
    //Joystick components size
    int circleSize = 120;
    int buttonSize = 100;
    //How far the joystick should be placed from the side of the screen
    int marginLeft = 100;
    //How far the joystick should be placed from the bottom of the screen
    int marginBottom = 100;

    Canvas mainCanvas;

    //Mobile movement
    [System.Serializable]
    public class JoystickButton
    {
        public Image backgroundCircle;
        public Image mainButton;
        public Rect defaultArea;
        public Vector2 touchOffset;
        public Vector2 currentTouchPos;
        public int touchID;
        public bool isActive = false;
    }

    //Move joystick data
    JoystickButton moveTouch = new JoystickButton();

    public static MobileJoystick_UI instance;

    // Start is called before the first frame update
    void Start()
    {
        if (instance != null)
        {
            //There is another instance already present, remove this one
            Destroy(gameObject);
            return;
        }
        //Assign this instance to a static variable so you can access the movement direction directly at MobileJoystick_UI.instance.moveDirection
        instance = this;

        //This function will initialize canvas element along with the joystick button
        GameObject tmpObj = new GameObject("Canvas");
        tmpObj.transform.position = Vector3.zero;
        mainCanvas = tmpObj.AddComponent<Canvas>();
        mainCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
        mainCanvas.pixelPerfect = true;

        //Add Canvas Scaler component
        CanvasScaler canvasScaled = tmpObj.AddComponent<CanvasScaler>();
        canvasScaled.scaleFactor = 1;
        canvasScaled.referencePixelsPerUnit = 100;

        //Add Graphic Raycaster element
        tmpObj.AddComponent<GraphicRaycaster>();

        //Setup navigation background
        GameObject cntrlTmpObj = new GameObject("Movement Circle");
        cntrlTmpObj.transform.position = Vector3.zero;
        cntrlTmpObj.transform.parent = tmpObj.transform;
        moveTouch.backgroundCircle = cntrlTmpObj.AddComponent<Image>();
        moveTouch.backgroundCircle.sprite = navigationCircle;
        moveTouch.backgroundCircle.rectTransform.anchorMin = new Vector2(0, 0);
        moveTouch.backgroundCircle.rectTransform.anchorMax = new Vector2(0, 0);
        moveTouch.backgroundCircle.rectTransform.sizeDelta = new Vector2(circleSize, circleSize);
        moveTouch.backgroundCircle.rectTransform.pivot = new Vector2(0, 0);
        moveTouch.backgroundCircle.rectTransform.position = new Vector3(marginLeft, marginBottom, 0);

        //Navigation button
        cntrlTmpObj = new GameObject("Movement Button");
        cntrlTmpObj.transform.position = Vector3.zero;
        cntrlTmpObj.transform.parent = tmpObj.transform;
        moveTouch.mainButton = cntrlTmpObj.AddComponent<Image>();
        moveTouch.mainButton.sprite = navigationButton;
        moveTouch.mainButton.rectTransform.anchorMin = new Vector2(0, 0);
        moveTouch.mainButton.rectTransform.anchorMax = new Vector2(0, 0);
        moveTouch.mainButton.rectTransform.sizeDelta = new Vector2(buttonSize, buttonSize);
        moveTouch.mainButton.rectTransform.pivot = new Vector2(0, 0);
        moveTouch.mainButton.rectTransform.position = new Vector3(marginLeft + (circleSize - buttonSize) / 2, marginBottom + (circleSize - buttonSize) / 2, 0);

        //Save the default location of the joystick button to be used later for input detection
        moveTouch.defaultArea = new Rect(moveTouch.mainButton.rectTransform.position.x,
            moveTouch.mainButton.rectTransform.position.y,
            moveTouch.mainButton.rectTransform.sizeDelta.x,
            moveTouch.mainButton.rectTransform.sizeDelta.y);
    }

    // Update is called once per frame
    void Update()
    {
        //Handle joystick movement
#if (UNITY_ANDROID || UNITY_IOS || UNITY_WP8 || UNITY_WP8_1) && !UNITY_EDITOR
        //Mobile touch input
        for (var i = 0; i < Input.touchCount; ++i)
        {
            Touch touch = Input.GetTouch(i);

            if (touch.phase == TouchPhase.Began)
            {
                MobileButtonsCheck(new Vector2(touch.position.x, Screen.height - touch.position.y), touch.fingerId);
            }

            if (touch.phase == TouchPhase.Moved )
            {
                if(moveTouch.isActive && moveTouch.touchID == touch.fingerId)
                {
                    moveTouch.currentTouchPos = touch.position;
                }
            }

            if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
            {
                MobileButtonStop(touch.fingerId);
            }
        }
#else
        //Desktop mouse input for editor testing
        if (Input.GetMouseButtonDown(0))
        {
            MobileButtonsCheck(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y), -1);
        }

        if (Input.GetMouseButtonUp(0))
        {
            MobileButtonStop(-1);
        }

        moveTouch.currentTouchPos = Input.mousePosition;
#endif

        //Moving
        if (moveTouch.isActive)
        {
            moveTouch.mainButton.rectTransform.position = new Vector3(moveTouch.currentTouchPos.x - moveTouch.touchOffset.x, moveTouch.currentTouchPos.y - moveTouch.touchOffset.y);
            moveDirection.x = moveTouch.mainButton.rectTransform.position.x - moveTouch.defaultArea.x;
            moveDirection.y = moveTouch.mainButton.rectTransform.position.y - moveTouch.defaultArea.y;

            if (Mathf.Abs(moveDirection.x) < 19)
            {
                moveDirection.x = 0;
            }
            else
            {
                moveDirection.x = Mathf.Clamp(moveDirection.x / 75.000f, -1.000f, 1.000f);
            }

            if (Mathf.Abs(moveDirection.y) < 19)
            {
                moveDirection.y = 0;
            }
            else
            {
                moveDirection.y = Mathf.Clamp(moveDirection.y / 75.000f, -1.000f, 1.000f);
            }
        }
        else
        {
            moveTouch.mainButton.rectTransform.position = new Vector3(moveTouch.defaultArea.x, moveTouch.defaultArea.y);
            moveDirection = Vector2.zero;
        }
    }

    //Here we check if the clicked/tapped position is inside the joystick button
    void MobileButtonsCheck(Vector2 touchPos, int touchID)
    {
        //Move controller
        if (moveTouch.defaultArea.Contains(new Vector2(touchPos.x, Screen.height - touchPos.y)) && !moveTouch.isActive)
        {
            moveTouch.isActive = true;
            moveTouch.touchOffset = new Vector2(touchPos.x - moveTouch.defaultArea.x, Screen.height - touchPos.y - moveTouch.defaultArea.y);
            moveTouch.currentTouchPos = new Vector2(touchPos.x, Screen.height - touchPos.y);
            moveTouch.touchID = touchID;
        }
    }

    //Here we release the previously active joystick if we release the mouse button/finger from the screen
    void MobileButtonStop(int touchID)
    {
        if (moveTouch.isActive && moveTouch.touchID == touchID)
        {
            moveTouch.isActive = false;
            moveTouch.touchOffset = Vector2.zero;
            moveTouch.touchID = -1;
        }
    }
}

TouchPlayerController.cs

using UnityEngine;

public class TouchPlayerController : MonoBehaviour
{

    // Update is called once per frame
    void Update()
    {
        //Move Front/Back
        if (MobileJoystick_UI.instance.moveDirection.y != 0)
        {
            transform.Translate(transform.forward * Time.deltaTime * 2.45f * MobileJoystick_UI.instance.moveDirection.y, Space.World);
        }

        //Rotate Left/Right
        if (MobileJoystick_UI.instance.moveDirection.x != 0)
        {
            transform.Rotate(new Vector3(0, 14, 0) * Time.deltaTime * 4.5f * MobileJoystick_UI.instance.moveDirection.x, Space.Self);
        }
    }
}

2단계: 위의 스크립트를 사용하여 간단한 장면 설정

  • 새 장면 만들기
  • 새로운 GameObject를 생성하고 호출합니다. '_TouchInput'
  • MobileJoystick_UI 스크립트를 첨부하세요.
  • 'Navigation Circle' 및 'Navigation button' 변수를 할당합니다.

아래 스프라이트를 사용하거나 여기를 클릭할 수 있습니다.

  • Unity로 가져온 후 텍스처 유형을 다음으로 변경하세요. 'Sprite (2D and UI)'

3단계: 플레이어 인스턴스 설정

마지막으로 플레이어 인스턴스를 설정합니다(제 경우에는 내부에 원통이 있는 간단한 GameObject가 됩니다).

  • 새로운 GameObject를 생성하고 호출합니다. 'MobilePlayer'
  • TouchPlayerController 스크립트를 연결하세요.
  • 새 원통을 만들고 거의 평평해 보일 때까지 높이를 줄입니다. (제 경우에는 크기가 (x: 1 y: 0.0142 z: 1)입니다. )
  • 'MobilePlayer' GameObject 내부에서 원통을 이동합니다.
  • 테스트 목적으로 기본 카메라를 'MobilePlayer' 내부로 이동하고 회전하여 다음과 같이 플레이어를 향하도록 할 수도 있습니다.

이제 Play를 누르고 모든 것이 제대로 작동하는지 확인할 시간입니다.

Sharp Coder 비디오 플레이어

모든 것이 예상대로 작동합니다! 플레이어는 조이스틱 버튼을 움직여 제어됩니다.

MobileJoystick_UI 스크립트는 모바일 터치 입력과 마우스 클릭(에디터에서 플레이하는 경우)을 모두 지원합니다.

추천 기사
Unity 모바일 터치 컨트롤을 만드는 방법
Unity용 헬리콥터 컨트롤러
Unity에서 크레인 제어를 만드는 방법
Unity용 자동차 컨트롤러
Unity에서 플레이어 움직임 만들기
Unity의 2D 플랫폼 게임 캐릭터 컨트롤러에 이중 점프 지원 추가
캐릭터 컨트롤러 Unity에서 강체를 푸시하는 기능을 추가하는 방법