모바일 게임을 개발하다 가장 중요한 카메라 이동, 줌인 줌아웃에 대해 글을 써보려고 한다.
접촉되어있는 손가락의 개수를 감지하는 Input클래스의 touchCount는 Input.touchCount 이렇게 사용한다.
내가 쓴 코드는 줌인 줌아웃과 카메라 이동 코드가 있기에 touchCount가 1일때와 2일때로 나누었다.
if (Input.touchCount == 1)
{
//터치 감지(카메라 이동)
}
화면에 접촉한 손가락의 순서를 감지하는 Input클래스의 GetTouch메소드는 Input.GetTouch(0) 이렇게 사용한다. (0), (1) …. 으로 첫번째, 두번째 순서를 나타낸다.
내가 쓴 코드는 오브젝트를 클릭하며 이동했을때 오브젝트가 회전하도록 구현했기에 TouchPhase 와 함께 사용했다.
TouchPhase에는 5가지가 존재한다.
| Began | 터치가 시작되었을 때 |
|---|---|
| Moved | 터치가 화면에서 움직일 때 |
| Stationary | 터치 후 움직이지 않았을 때 |
| Ended | 손가락을 뗐을 때(터치의 마지막 단계) |
| Cancled | 5개 이상의 터치 입력이 동시에 발생하여 추적을 취소했을 때 |
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
//터치가 시작된 후 실행할 코드
}
}
위에서 언급했듯이 오브젝트를 클릭한 채로 터치를 이동하면 오브젝트를 회전하게, 아무것도 클릭하지 않고 터치를 이동하면 카메라를 이동하게 구현했다.
그래서 Raycast를 사용해 ray가 화면상에 존재하는 객체와 충돌했는지 반환하여 회전하도록 구현했다.
if (touch.phase == TouchPhase.Began)
{
// 터치가 시작될 때 클릭된 오브젝트를 확인
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(touch.position);
if (Physics.Raycast(ray, out hit))
{
if (hit.transform == otherObjectTransform)
{
isObjectSelected = true;
}
else
{
isObjectSelected = false;
}
}
}
if (isObjectSelected)
{
// 오브젝트가 선택된 경우 오브젝트 회전
switch (touch.phase)
{
case TouchPhase.Moved:
Vector2 touchDelta = touch.deltaPosition;
if (touchDelta.x != 0 || touchDelta.y != 0)
{
float rotationY = touchDelta.x * rotateSpeed;
otherObjectTransform.Rotate(Vector3.up, rotationY, Space.World);
}
break;
}
}
else
{
// 오브젝트가 선택되지 않은 경우 카메라 이동
switch (touch.phase)
{
case TouchPhase.Moved:
Vector2 touchDelta = touch.deltaPosition;
if (touchDelta.x != 0 || touchDelta.y != 0)
{
transform.Translate(-touchDelta.x * moveSpeed, -touchDelta.y * moveSpeed, 0);
}
break;
}
}
하나씩 살펴보면 RaycastHit로 레이캐스트를 선언해주고 ScreenPointToRay 로 스크린 좌표를 인수로 넘겨주어 레이를 생성해준다.
Physics.Raycast는 ray가 화면상에 존재하는 객체와 충돌하면 true를 반환하고, out 파라미터로 RaycastHit를 리턴하며 객체 정보를 저장한다.
하지만 이 방법은 내가 원하던 방법을 구현하기엔 시간이 조금 걸릴 것 같아서 임시로 오브젝트가 회전하는 방법을 사용한 것이다.
→ 원래는 카메라가 오브젝트 주위를 회전하며 고정된 오브젝트를 다각도로 볼 수 있도록 구현하고 싶었다.
줌인 줌아웃 코드는 아래와 같이 작성했으며, 자세한 설명은 주석을 달아놨다
//접촉되어 있는 손가락 개수가 2일때(두 손가락을 터치했을때)
else if (Input.touchCount == 2)
{
//첫번째 터치 정보 저장
Touch touchZero = Input.GetTouch(0);
//두번째 터치 정보 저장
Touch touchOne = Input.GetTouch(1);
//각각 터치의 이전 위치를 계산한다.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
//(목적지-현재위치).magnitude : 남은 거리
//이전 위치와 현재 위치간의 거리를 계산한다
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
//두 손가락 간의 거리를 계산한다
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
//orthographic모드일때
if (GetComponent<Camera>().orthographic)
{
GetComponent<Camera>().orthographicSize += deltaMagnitudeDiff * orthoZoomSpeed;
GetComponent<Camera>().orthographicSize = Mathf.Max(GetComponent<Camera>().orthographicSize, 0.1f);
}
//fieldOfView모드일때
else
{
GetComponent<Camera>().fieldOfView += deltaMagnitudeDiff * perspectiveZoomSpeed;
GetComponent<Camera>().fieldOfView = Mathf.Clamp(GetComponent<Camera>().fieldOfView, 0.1f, 179.9f);
}
}
사용한 전체 코드는 아래와 같다. 이 또한 자세한 설명은 주석을 달아놨으니 참고!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchManager : MonoBehaviour
{
//줌 속도 변수
private float perspectiveZoomSpeed = 0.008f;
private float orthoZoomSpeed = 0.0001f;
//이동 속도
private float moveSpeed = 0.001f;
//회전 속도
private float rotateSpeed = 0.1f;
//회전할 오브젝트
public Transform otherObjectTransform;
private bool isObjectSelected = false; // 오브젝트가 선택되었는지 여부
void Update()
{
// 오브젝트 클릭 여부에 따라 처리
if (Input.touchCount == 1)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
// 터치가 시작될 때 클릭된 오브젝트를 확인
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(touch.position);
if (Physics.Raycast(ray, out hit))
{
//레이가 객체와 충돌했을때
if (hit.transform == otherObjectTransform)
{
isObjectSelected = true;
}
//충돌 X
else
{
isObjectSelected = false;
}
}
}
//충돌이 나면
if (isObjectSelected)
{
//터치 정보에 따라
switch (touch.phase)
{
//터치가 움직이면
case TouchPhase.Moved:
//터치의 이동량을 저장
Vector2 touchDelta = touch.deltaPosition;
//x또는 y가 하나라도 0이 아니면 회전(움직이면 회전)
if (touchDelta.x != 0 || touchDelta.y != 0)
{
//회전 수행
float rotationY = touchDelta.x * rotateSpeed;
otherObjectTransform.Rotate(Vector3.up, rotationY, Space.World);
}
break;
}
}
//충돌X
else
{
switch (touch.phase)
{
//터치가 이동하면
case TouchPhase.Moved:
Vector2 touchDelta = touch.deltaPosition;
//터치가 이동했는지 확인
if (touchDelta.x != 0 || touchDelta.y != 0)
{
//카메라 터치의 이동량에 비례해 x와 y방향으로 이동하고, 이동량은 moveSpeed에 비례한다.
//-를 붙인 이유는 유니티 좌표계에서 카메라가 움직이는 방향을 반대로 하기 위해서임
transform.Translate(-touchDelta.x * moveSpeed, -touchDelta.y * moveSpeed, 0);
}
break;
}
}
}
//접촉되어 있는 손가락 개수가 2일때(두 손가락을 터치했을때)
else if (Input.touchCount == 2)
{
//첫번째 터치 정보 저장
Touch touchZero = Input.GetTouch(0);
//두번째 터치 정보 저장
Touch touchOne = Input.GetTouch(1);
//각각 터치의 이전 위치를 계산한다.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
//(목적지-현재위치).magnitude : 남은 거리
//이전 위치와 현재 위치간의 거리를 계산한다
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
//두 손가락 간의 거리를 계산한다
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
//orthographic모드일때
if (GetComponent<Camera>().orthographic)
{
GetComponent<Camera>().orthographicSize += deltaMagnitudeDiff * orthoZoomSpeed;
GetComponent<Camera>().orthographicSize = Mathf.Max(GetComponent<Camera>().orthographicSize, 0.1f);
}
//fieldOfView모드일때
else
{
GetComponent<Camera>().fieldOfView += deltaMagnitudeDiff * perspectiveZoomSpeed;
GetComponent<Camera>().fieldOfView = Mathf.Clamp(GetComponent<Camera>().fieldOfView, 0.1f, 179.9f);
}
}
}
}