카메라 구현에 있어서 Cinemachine을 선택한 이유
- 기본 카메라를 사용하게 되면 직접 스크립트를 짜서 기능을 구현해야 하는 경우가 많다.
- 구현해야 할 기능들이 많아지면 많아질수록 이는 부담으로 다가온다.
- 하지만 Cinemachine을 사용하면 Cinemachine이 제공하는 강력한 기능들을 손쉽게 프로젝트에 적용할 수 있다.
구현해야 하는 것
- 2D 모바일 환경에서 드래그로 맵을 둘러볼 수 있는 기능.
- 이때 카메라는 특정 구역 바깥으로 나가지 않아야 한다.
구현 방법
1. UI로 DragPanel 만들고, 스크립트 짜기
- UI로 DragPanel을 만들어 터치할 부분에 오브젝트가 있어도 UI의 특성을 활용해 드래그가 가능하게 했다.
- 또한 DragPanel 밑 오브젝트를 클릭해야 할 경우도 있으므로 Canvas의 Graphic raycastTarget 속성을 조절하는 방식으로 처리를 해 줬다.
- (UI가 아닌 오브젝트에 IPointer Handler를 작동시키려면 Physics RayCaster 컴포넌트가 메인 카메라에 붙어 있어야 한다.)
- 이때 IPointerDownHandler, IPointerUpHandler, IDragHandler 인터페이스를 사용하여 드래그 동작을 구현했다.
UI_DragPanel.cs
더보기
더보기
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class UI_DragPanel : UI_Base, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
// 자연스러운 움직임을 위해 빈 오브젝트를 Cinemachine 카메라가 따라가는 형식으로 구현
// 드래그인지 아닌지 판단하여, 오브젝트에 이벤트를 전달할 수 있도록 한다
[Header("Components")]
public Camera mainCamera; // mainCam
public BoxCollider2D confiner; // 카메라 가두리
public Transform target; // 빈 오브젝트
[Header("Drag Settings")]
public float dragSpeed = 0.005f; // 화면 드래그 스피드
public float dragThreshold = 10f; // 드래그 판단 임계치 (픽셀)
private Vector2 lastTouchPos;
private bool isDragging = false;
private Graphic graphic; // raycast target on/off 용도
private void Awake()
{
mainCamera = GameObject.Find("Cameras").GetComponentInChildren<Camera>();
target = GameObject.Find("Cameras").transform.Find("Target");
confiner = GameObject.Find("Map").GetComponent<BoxCollider2D>();
Managers.Game.Confiner = confiner;
graphic = GetComponent<Graphic>();
}
public override bool Init()
{
if (base.Init() == false)
return false;
return true;
}
public void OnDrag(PointerEventData eventData)
{
// 드래그 시작 여부 판단
if (!isDragging)
{
if(Vector2.Distance(lastTouchPos, eventData.position) > dragThreshold)
isDragging = true;
}
// 드래그 중이면 카메라 타겟 이동 처리
if (isDragging)
{
Vector2 delta = eventData.delta;
Vector3 worldDelta = mainCamera.ScreenToWorldPoint(new Vector3(0, 0, mainCamera.nearClipPlane))
- mainCamera.ScreenToWorldPoint(new Vector3(delta.x, delta.y, mainCamera.nearClipPlane));
Vector3 newPos = target.position + worldDelta * dragSpeed;
// 카메라의 중심이 Confiner를 벗어나지 않게 조정
if (confiner != null)
{
Bounds bounds = confiner.bounds;
// 화면의 왼쪽 하단, 오른쪽 상단을 월드 좌표로 반환
Vector3 worldMin = mainCamera.ViewportToWorldPoint(new Vector3(0, 0, mainCamera.nearClipPlane));
Vector3 worldMax = mainCamera.ViewportToWorldPoint(new Vector3(1, 1, mainCamera.nearClipPlane));
float halfX = (worldMax.x - worldMin.x) / 2;
float halfY = (worldMax.y - worldMin.y) / 2;
newPos.x = Mathf.Clamp(newPos.x, bounds.min.x + halfX, bounds.max.x - halfX);
newPos.y = Mathf.Clamp(newPos.y, bounds.min.y + halfY, bounds.max.y - halfY);
}
target.position = newPos;
}
}
public void OnPointerDown(PointerEventData eventData)
{
lastTouchPos = eventData.position;
isDragging = false;
}
public void OnPointerUp(PointerEventData eventData)
{
// 드래그가 아니었다면 터치로 간주하여 아래 오브젝트에 이벤트 전달
if (!isDragging)
{
// UI 패널이 터치 이벤트를 가로채지 않도록 잠시 RayCast Target 비활성화
graphic.raycastTarget = false;
// 현재 이벤트 정보를 바탕으로 Drag Panel 밑에 있는 오브젝트 찾음
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, results);
// 자신을 제외한 첫번째 결과에 클릭 이벤트 전달
foreach (var result in results)
{
if (result.gameObject != gameObject)
{
// 클릭 이벤트 실행
ExecuteEvents.Execute(result.gameObject, eventData, ExecuteEvents.pointerClickHandler);
break;
}
}
// Raycast Target 활성화
graphic.raycastTarget = true;
}
isDragging = false;
}
}
2. Confiner 설정
맵 반경을 결정지을 Collider를 만들어 카메라가 비추는 영역이 그 바깥으로 나가지 않게 구역을 정해 준다.
이후 코드에서 Collider 바깥으로 나가지 않게 구현한다.
3. Cinemachine이 Follow할 빈 오브젝트 만들기
빈 오브젝트를 만들어 화면을 Drag할 시 빈 오브젝트가 움직이게끔 하고, Cinemachine의 Follow 기능을 이용해 카메라가 빈 오브젝트를 따라 비추게 한다.
결과
'Unity' 카테고리의 다른 글
[Unity 2D] 장애물과 겹치지 않게 랜덤하게 움직이기 (0) | 2025.03.05 |
---|---|
Behavior Tree 개념 / 적용해 보기 (0) | 2025.01.24 |
용량이 큰 사운드 리소스를 로드할 때 - Streaming (0) | 2025.01.20 |