2D게임을 만들 때 플레이어가 맵의 끝쪽으로 이동하면 카메라가 맵 밖을 비추게 된다. 게임 플레이에 크게 불편한 점은 없겠지만, 디테일을 살리기 위해 카메라 범위를 제한해서 맵 밖이 안보이도록 만들어보자!
현재 첫번째 사진처럼 카메라가 맵 밖을 비추는 상황인데, 두번째 사진처럼 맵밖을 비추지 않게 만드는 것이 목표이다.
먼저 카메라가 플레이어를 추적하는 기능을 구현하자. 사실 플레이어의 자식객체로 카메라를 두면 카메라가 플레이어를 따라다니지만, 이번엔 카메라에 여러 기능을 추가할 거기 때문에 스크립트를 통해 구현해보자.
// CameraController.cs
public class CameraController : MonoBehaviour
{
Vector3 cameraPosition = new Vector3(0, 0, -10);
Transform playerTransform;
void Start()
{
playerTransform = GameObject.Find("Player").GetComponent<Transform>();
}
void FixedUpdate()
{
tranform.position = playerTransform.position + cameraPosition;
// tranform.position(카메라 위치), playerTransform.position(플레이어 위치)
}
}
위 스크립트는 CameraController.cs로 Main Camera의 컴포넌트로 추가할 스크립트이다. 위 스크립트는 카메라의 위치를 캐릭터의 위치로 변경해주는 간단한 스크립트이다.
여기서 cameraPosition이라는 변수를 왜 더해주는지 의문일 수 있는데, 간단하게 설명하면 카메라가 찍는 대상보다 뒤에 있어야되기 때문이다. 카메라와 대상이 겹쳐져 있으면 찍을 수 없기 때문이다.
우리의 목표는 카메라의 범위를 제한하기 이전에 게임의 디테일을 살리는 것이다. 그래서 카메라의 범위를 제한하기 이전에 부드러운 카메라의 움직임을 구현해보자.
카메라가 즉각적으로 플레이어를 따라가는 것이 아니라 조금 늦게 플레이어를 따라가면서 부드러운 카메라 무빙을 연출할 수 있다. 그 방법은 선형 보간을 이용하는 것이다.
선형 보간이란? 끝점의 값이 주어졌을 때 그 사이에 위치한 값을 추정하기 위하여 직선 거리에 따라 선형적으로 계산하는 방법이다. 조금 더 쉽게 설명하자면 a와 b가 존재할 때 a와 b의 사이값을 구하는 것이다.
이러한 선형보간은 유니티가 지원하는 Vector3.Lerp()
함수를 통해 쉽게 구현할 수 있다.
Vector3.Lerp(Vector3 a, Vector3 b, float t);
Lerp 함수는 a + (a - b) t 라는 연산을 하는 함수이며, a에서 b위치로 t비율만큼의 선형보간을 뜻한다.
이 때 t는 0 ~ 1의 값을 가지는데 t가 0이라면 a, 1이라면 b를 반환하고 그 사이 값이라면 a부터 b사이에 있는 t비율의 값을 반환한다. 따라서 Time.deltaTime을 활용해서 t의 값을 실시간으로 늘려준다면 카메라의 부드러운 무빙을 연출할 수 있다.
아래는 부드러운 카메라 무빙을 Lerp함수를 활용해서 구현한 코드 예시이다.
transform.position = Vector3.Lerp(transform.position, playerTransform.position + cameraPosition, Time.deltaTime * cameraMoveSpeed); // a = 카메라 위치, b = 플레이어의 위치, t = 실시간으로 늘어나는 값
이제 마지막으로 카메라의 범위를 제한해보자.
유니티 에디터의 Scene view를 보면 아래 사진과 같이 가운데 카메라 그림이 있는 흰색 사각형을 볼 수 있다. 이 사각형이 카메라가 사용자에게 보여주는 영역을 표시한 것이며 Size에 따라 사각형의 크기가 결정된다.
Size는 카메라의 중앙에서 y축 끝지점까지의 거리를 의미한다. 즉 카메라가 (0,0)에 위치해있고, Size가 5라면, y값이 -5~5 사이의 화면만 보여준다는 의미이다. Size(높이의 절반)와 화면의 가로 세로 비율을 알면 중앙에서 x축 끝지점까지의 거리도 계산을 통해 구할 수 있다. size * (Screen.width/Screen.height) 이를 구현하면 아래와 같다.
height = Camera.main.orthographicSize; width = height * Screen.width / Screen.height;
이제 카메라가 비추는 가로, 세로의 길이를 구했으니 카메라가 비추는 영역의 크기를 지정해주어야 한다. 이는 맵의 크기라고 생각하면 편하다.
Vector2타입의 mapSize라는 변수를 만들어 x,y값을 저장하되 height, width와 같이 절반 값을 저장해준다.
이제 모든 준비는 끝났다. 카메라의 가로 세로 범위, 맵의 가로 세로 크기를 알았으니 맵 밖을 비추지 않으면서 카메라가 움직일 수 있는 영역은 아래와 같다.
가로 : [-(mapSize.x - width), (mapSize.x - width)]
세로 : [-(mapSize.y - height), (mapSize.y) - height]
이를 유니티에서 지원하는 Mathf.Clamp()
함수에 넣어주면 카메라의 범위를 제한할 수 있다.
*Mathf.Clamp()란? value라는 변수가 일정한 값을 벗어나지 못하도록 제한해주는 함수
Mathf.Clamp(float value, float min, float max);
value라는 변수를 min과 max를 벗어나지 못하게 만들어 준다는 의미이다. 아래는 Mathf.Clamp()를 사용해 카메라의 범위를 제한하는 코드이다.
float lx = mapSize.x - width;
float clampX = Mathf.Clamp(transform.position.x, center.x - lx, center.x + lx);
float ly = mapSize.y - height;
float clampY = Mathf.Clamp(transform.position.y, center.y - ly, center.y + ly);
이렇게 범위를 제한한 x,y값을 구해서 카메라의 위치를 이동시켜 주면 끝이다.
transform.position = new Vector3(clampX, clampY, -10f);
아래는 총 정리 코드이다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public Transform playerTransform;
public Vector3 cameraPosition;
public Vector2 center;
public Vector2 mapSize;
public float cameraMoveSpeed;
private float height;
private float width;
void Start()
{
playerTransform = GameObject.Find("Player").GetComponent<Transform>();
height = Camera.main.orthographicSize;
width = height * Screen.width / Screen.height;
}
void FixedUpdate()
{
LimitCameraArea();
}
void LimitCameraArea()
{
transform.position = Vector3.Lerp(transform.position,
playerTransform.position + cameraPosition,
Time.deltaTime * cameraMoveSpeed);
float lx = mapSize.x - width;
float clampX = Mathf.Clamp(transform.position.x, -lx + center.x, lx + center.x);
float ly = mapSize.y - height;
float clampY = Mathf.Clamp(transform.position.y, -ly + center.y, ly + center.y);
transform.position = new Vector3(clampX, clampY, -10f);
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(center, mapSize * 2);
}
}
OnDrawGizmos()
함수는 아래 그림과 같이 카메라가 움직일 수 있는 영역을 Scene창에 직관적으로 표현하는 함수이다.