Unity/Project : Cursed Treasure

Unity 팀프 21.05.25. 3rd Person Joystick - 1단계

HappyFrog 2021. 5. 25. 23:23

기존의 조이스틱의 방향성을 잘못잡았다. 기존의 조이스틱은 GTA나 레드데드리뎀션처럼 좌, 우, 후방으로 움직일 때 축을 기준으로 회전하며 방향을 바꾸기 때문에 모바일에서는 불편할 거라는 지적과 고찰이 있었다.

 

따라서 시장에 나와있는 모바일 게임들 중 조이스틱을 사용하고, 우리가 원하는 방향으로 조작할 수 있는 게임들을 찾아보니 검은사막 모바일, 제 5인격 등이 있었다.

 

해당 게임들의 조작방법의 특징은 카메라의 시점조작(화면 드래그)과 캐릭터의 움직임조작(조이스틱)이 완전히 분리되어있다는 점이었다.

ㄴ캐릭터를 어느방향으로 움직이던 카메라를 조작하기전까진 시점은 바뀌지 않는다.

ㄴ그리고 캐릭터의 전진방향은 언제나 카메라의 forward방향이며, 이는 Vector3.forward와는 다른 값일 수 있다.

 

따라서 총 4단계로 진행단계를 잡았다.

1단계 - 카메라의 시점조작

2단계 - 캐릭터의 이동조작 및 캐릭터가 움직이는 방향을 바라보게 하기.(이 때 조이스틱의 전진방향은 Vector3.forward)이다.

3단계 - 카메라와 캐릭터를 일직선으로 배치한 뒤 카메라의 forward를 구하기.

4단계 - 조이스틱(캐릭터)의 전진방향을 항상 카메라의 forward를 향하게 하기.

 

오늘은 1단계를 완성했으며, R&D를 한 메서드는 Transform.RotateAround이다.

RotateAround는 point를 중심점 삼아 axis축으로 angle만큼 회전하는 메서드이다. 

 

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

public class CameraTest : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
    public GameObject target;
    public Transform camera;

    bool isTouch;
    [SerializeField]
    float speed;
    [SerializeField]
    float highestAngle;
    [SerializeField]
    float lowestAngle;

    Vector2 lastPosition;

    public void OnDrag(PointerEventData eventData)
    {
        Vector3 value = eventData.position - lastPosition;
        value = value.normalized;
        if (value.x > 0)
        {
            this.camera.RotateAround(target.transform.position, Vector3.up, value.x * speed);
        }
        else if (value.x < 0)
        {
            this.camera.RotateAround(target.transform.position, Vector3.down, Mathf.Abs(value.x) * speed);
        }

        if (value.y > 0)
        {
            this.camera.Rotate(-value.y * 30 * speed * Time.deltaTime, 0, 0);
        }
        else if (value.y < 0)
        {
            this.camera.Rotate(Mathf.Abs(value.y) * 30 * speed * Time.deltaTime, 0, 0);
        }
        lastPosition = eventData.position;
        Debug.Log("Drag");
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        lastPosition = eventData.position;
        isTouch = true;
        Debug.Log("OnPointerDown");
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        lastPosition = Vector3.zero;
        isTouch = false;
        Debug.Log("OnPointerUp");
    }

    void Start()
    {
        speed = 3f;
        highestAngle = 15;
        lowestAngle = 0;
    }

    void Update()
    {
        var angle = camera.transform.eulerAngles;
        if (angle.x < 180 && angle.x > highestAngle)
        {
            camera.transform.eulerAngles = new Vector3(highestAngle, angle.y, angle.z);
        }
        if (angle.x < lowestAngle + 360 && angle.x > 180)
        {
            camera.transform.eulerAngles = new Vector3(lowestAngle, angle.y, angle.z);
        }
    }
}

eㄱㅂㅈㄷㄱㅈㄷㅂ이 스크립트에서는 좌, 우이동은 축을 이용하여 회전하고 상,하는 각조절을 한다. 따라서 좌, 우 회전만 RotateAround메서드를 사용했다. 그리고 value에 곱해지는 상수들과 speed는 속도조절을 위한 임의의 값이므로 조절이 가능하다.

Update에서는 카메라 상하각의 제한을 두었다. -> 제한이 없으면 빙글빙글 도는 조작이 되므로 많이 불편할 것이다.

이 때, angle.x를 180을 기준으로 나눈 이유는 angle.x > 0 && angle.x < 15라고 설정하려 했지만, 잘 되지 않아서 0~360이 아닌 -180~180을 기준으로 잡아보니 수월하게 되더라.

하지만 아래 조건문처럼 angle.x < lowestAngle + 360 && angle.x > 180은 앵글의 최저 값을 할당하면 angle.x>180과 달라서 조건문이 발동되지 않을것이다. 실제로도 작동을 하지 않아서 +360을 더함으로써 앵글의 '값'자체의 크기를 키웠을 뿐, 회전각의 출력값 자체는 달라지지 않기때문에 360이라는 상수를 더했다.