본문 바로가기
게임개발/유니티 엔진

모바일 가상 조이스틱 구현

by do_ng 2024. 4. 20.
public class VirtualJoystick : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [SerializeField]
    private RectTransform lever;
    private RectTransform rectTransform;

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

    private Vector3 inputDirection;
    public Vector3 InputDirection { get { return inputDirection; } }    

    public void OnBeginDrag(PointerEventData eventData)
    {
        Managers.Game.GetPlayer().GetComponent<PlayerController>().PlayerState = Defines.State.Moving;
        ControlJoystickLever(eventData);        
    }
    
    public void OnDrag(PointerEventData eventData)
    {
        ControlJoystickLever(eventData);
    }

    void ControlJoystickLever(PointerEventData eventData)
    {
        Vector2 inputPos = eventData.position - rectTransform.anchoredPosition;        
        Vector2 inputVector = inputPos.magnitude < leverRange ? inputPos : inputPos.normalized * leverRange;
        lever.anchoredPosition = inputVector;
        inputDirection = inputVector.normalized;
    }

}

 

 

1. 조이스틱 레버의 위치 파악

Vector2 inputPos = eventData.position - rectTransform.anchoredPosition;

 

조이스틱은 (0,0) 위치에 고정되어 있고, 레버의 초기 위치는 (0, 0)인데 드래그를 해서 다음과 같이 변한다고 가정해 보자

 

조이스틱의 범위 안에서 레버의 위치를 구하기 위해서는 "드래그 한 위치(레버의 벡터값)" - "조이스틱이 그려진 위치(조이스틱 벡터값)"를 계산해 주면 된다. 

여기서 조이스틱은 (0,0)을 기준으로 그려지기 때문에 그 기준점을 바탕으로 레버가 조이스틱으로부터 어디까지 드래그 됬는지를 구할 수 있다.

 

 

2. 레버가 조이스틱의 범위밖을 빠져나가지 않도록 체크

Vector2 inputVector = inputPos.magnitude < leverRange ? inputPos : inputPos.normalized * leverRange;

 

 

레버가 최대로 움직일 수 있는 거리(leverRange)를 10으로 고정했을 때, 

레버가 위치한 벡터의 크기가 10보다 큰 경우 normalized(벡터를 크기가 1인 단위 벡터로 변경)를 한 다음에 leverRange를 곱해서 조이스틱의 범위를 넘지 않게 조절한다. 

레버의 위치가 10보다 큰 경우 그냥 크기가 10인 벡터로 고정시키면 되지 않을까 생각할 수 있는데 레버가 위치한 방향을 알아야 되기 때문에 normalized를 통해서 크기가 1이면서 방향을 나타내는 벡터를 구하였다.

 

3. 레버의 방향에 따라서 캐릭터를 이동

lever.anchoredPosition = inputVector;
inputDirection = inputVector.normalized;

 

// 조이스틱의 y축 -> 3D 공간에서 z축으로 이동하게 변경(3D 공간에서 y축이 깊이이므로)
Vector3 direction = new Vector3(_playerController.Joystick.InputDirection.x, 0, _playerController.Joystick.InputDirection.y);        
transform.position += direction * _playerController.Stat.MoveSpeed * Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), 20 * Time.deltaTime);

 

레버의 벡터값을 그대로 캐릭터가 이동하는 방향에 넣어주지 않은 이유는 레버를 조금 움직이거나 크게 움직이는 경우에 따라서 벡터의 크기가 변하기 때문에 그 값에 따라 캐릭터의 이동이 들쑥날쑥해진다.

정규화(noramlized)를 통해서 크기가 1인 단위 벡터로 만들고 고정된 값으로 이동시키게 만들었다.

 

4. 3D에서 캐릭터를 깊이(y)가 아니라 "상하좌우"로만 이동시키는 경우

 

조이스틱의 경우 UI이기 때문에 2D(x축과 y축 좌표를 가지고 있는 2차원 벡터)를 3D 형식에 맞춰서 변환이 필요하다.

3D는 x, y, z 3차원 벡터로 구성되는데 y축이 깊이 부분을 담당하고 있기 때문에 y축은 0으로 고정시키고 UI에 있는 y축 방향을 z 축으로 이동하였다.