팀프로젝트/R&D

2020.06.13 모바일 조이스틱 + 터치 화면 회전(카메라 컨트롤)

dev_sr 2020. 6. 13. 15:43

구현한 것

1. 조이스틱으로 캐릭터를 움직이고

2. 화면 터치로 카메라를 움직인다 ( 수평 yaw -캐릭터 주위를 공전, 수직 pitch - 일정 각도까지만 회전)

 

조이스틱까진 수월하게 구현했는데 터치로 카메라를 제어하는 것이 어려웠다.

 

모바일 환경에서 터치로 카메라를 어떻게 조종하는가 찾아보면 대부분 getmouseButton 에 raycast를 쏘는 방식으로

구현하는지 터치 구조체를 이용하는 방법은 잘 나오지 않았다

 

거기에 한손으로 이동 조작을 하면서 다른 손가락 터치로 카메라를 어떻게 제어하는지 코드나 방법을 찾아보았는데

대부분 카메라가 플레이어를 따라다니는 것까지만 구현하는 게 가장 많았고 내가 필요했던 방법은 나오질 않았다..

 

여차저차 열심히 구글링해서 나오는대로 터치 구조체를 이용하지 않고 getmounsebuttondown(0) 나 IPointer인터페이스로 어찌어찌 만들어보았다.

유니티3D에선 마우스 입력으로 원하는대로 잘 나왔는데 모바일로 빌드하면 영 다른 결과가 나왔다.

위 두방법 사용하면 어딜 누르든 다 터치되는 걸로 인식이 되어서 조이스틱만 눌러봐도 화면이 돌아가는 문제가 생김ㅋㅋ

 

결국 해결해야하는 문제가 2가지 있었는데

1. 조이스틱 같은 UI를 터치할 시 화면 회전이 되어서는 안된다

2. 조이스틱을 터치하고 드래그하면 계속 조이스틱 입력이 진행되는데

   모바일 조이스틱 특성상 조이스틱 범위를 벗어나도 입력되어야하며 그 입력에 화면이 회전되어서는 안된다.

 

 

이거 해결하려고 github에서 제스처도 찾아보고 구글링 열심히 해봤는데 코드가 너무 어려웠다...

나중에 더 공부해봐야지..

 

결국 이것저것 더 검색하다가 유튜브에서 unity fps controller를 검색해서

터치에 관련해서 잘 설명해놓은 FPS 1인칭 시점 구현 영상을 찾아서 따라해보면서 만들어보았다.

 

따라해보면서 알게된 2가지 문제를 해결하는 방법

1. 조이스틱 같은 UI를 터치할 시 화면 회전이 되어서는 안된다

-> if(!EventSystem.current.IsPointerOverGameObject(i))  : UI면 true를 반환 / 아니면 false를 반환 

2. 조이스틱을 터치하고 드래그하면 계속 조이스틱 입력이 진행되는데

   모바일 조이스틱 특성상 조이스틱 범위를 벗어나도 입력되어야하며 그 입력에 화면이 회전되어서는 안된다.

-> 화면을 반 나눠서 화면 오른쪽에서만 카메라가 회전되게 만듦

 

 

CameraController 코드 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
 
public class CameraController : MonoBehaviour
{
    [SerializeField] private GameObject player;
    [SerializeField] private float speed;
 
    private int rightFingerId;
    float halfScreenWidth;  //화면 절반만 터치하면 카메라 회전
    private Vector2 prevPoint;
 
    private Vector3 originalPos;
    public Button btn;  // 시점(yaw)을 원상태로 되돌리는 버튼
 
    public Transform cameraTransform;
    public float cameraSensitivity;
 
    private Vector2 lookInput;
    private float cameraPitch; //pitch 시점
 
 
    void Start()
    {
        this.rightFingerId = -1;    //-1은 추적중이 아닌 손가락
        this.halfScreenWidth = Screen.width / 2;
        this.originalPos = new Vector3(000);
        this.cameraPitch = 35f;
 
        this.btn.onClick.AddListener(() =>
        {
            this.transform.eulerAngles = this.originalPos;
 
        });
    }
 
    // Update is called once per frame
    void Update()
    {
        this.transform.position = Vector3.Lerp(this.transform.position, this.player.transform.position + new Vector3(0,this.transform.position.y,0), this.speed);
 
        GetTouchInput();        
    }
 
    private void GetTouchInput()
    {
        //몇개의 터치가 입력되는가
        for (int i = 0; i < Input.touchCount; i++)
        {
            Touch t = Input.GetTouch(i);
 
            switch (t.phase)
            {
                case TouchPhase.Began:
 
                    if (t.position.x > this.halfScreenWidth && this.rightFingerId == -1)
                    {
                        this.rightFingerId = t.fingerId;
                        Debug.Log("오른쪽 손가락 입력");
                    }
                    break;

                case TouchPhase.Moved:
 
                    //이것을 추가하면 시점 원상태 버튼을 누를 때 화면이 돌아가지 않는다
                    if (!EventSystem.current.IsPointerOverGameObject(i))
                    {
                        if (t.fingerId == this.rightFingerId)
                        {
                           
                            //수평
                            this.prevPoint = t.position - t.deltaPosition;
                            this.transform.RotateAround(this.player.transform.position, Vector3.up, -(t.position.x - this.prevPoint.x) * 0.2f);
                            this.prevPoint = t.position;
 
 
                            //수직
                            this.lookInput = t.deltaPosition * this.cameraSensitivity * Time.deltaTime;
                            this.cameraPitch = Mathf.Clamp(this.cameraPitch - this.lookInput.y, 10f, 35f);
                            this.cameraTransform.localRotation = Quaternion.Euler(this.cameraPitch, 00);
                        }
                    }
                    break;
 
                case TouchPhase.Stationary:
 
                    if (t.fingerId == this.rightFingerId)
                    {
                        this.lookInput = Vector2.zero;
 
                    }
                    break;

                case TouchPhase.Ended:
 
                    if (t.fingerId == this.rightFingerId)
                    {
                        this.rightFingerId = -1;
                        Debug.Log("오른쪽 손가락 끝");
 
                    }
                    break;
 
                case TouchPhase.Canceled:
 
                    if (t.fingerId == this.rightFingerId)
                    {
                        this.rightFingerId = -1;
                        Debug.Log("오른쪽 손가락 끝");
 
                    }
                    break;
            }
        }
    }
 
   
}

 

터치 단계는 열거형으로 5단계로 구성되어 있는데 각 단계마다 어떻게 할 것인지 구성하면 된다.

 

1. Began - 터치가 시작되었을 때

2. Moved - 터치하면서 움직일 때(드래그 할때)

3. Stationary - 터치는 하는데 한 지점에 머무를 때

4. Ended - 터치하다 손 땠을 때

5. Canceled - 전화가 오거나 다른 어플이 켜지는 등 상황에서 터치가 입력되지 못할 때

 

 

기타

t.position : 터치가 현재 입력되어있는 좌표

t.deltaPosition : 현재 터치와 이전 터치 좌표의 차이

 

Mathf.Clamp(this.cameraPitch - this.lookInput.y, 10f, 35f);

특정값이 최소값과 최대값을 넘어지가지 않도록 제한한다.

 

 

하이어라키 구성

 

동영상 

 

 

 

 

참고 

 

 

 

Unity - 스크립팅 API: Touch

Devices can track a number of different pieces of data about a touch on a touchscreen, including its phase (ie, whether it has just started, ended or moved), its position and whether the touch was a single contact or several taps. Furthermore, the continui

docs.unity3d.com