Unity에서 2D 카메라 마스터하기: 게임 개발자를 위한 튜토리얼

게시 됨: 2022-03-11

개발자에게 카메라는 게임 개발 프로세스의 초석 중 하나입니다. 체스 앱에서 게임 뷰를 보여주는 것부터 영화 같은 효과를 얻기 위해 3D AAA 게임에서 카메라 움직임을 능숙하게 지시하는 것까지, 카메라는 실제로 "카메라"라고 불리기 전에도 이제까지 만들어진 모든 비디오 게임에서 기본적으로 사용됩니다.

이 기사에서는 2D 게임용 카메라 시스템을 설계하는 방법을 설명하고 가장 인기 있는 게임 엔진 중 하나인 Unity에서 이를 구현하는 방법에 대한 몇 가지 사항도 설명합니다.

2D에서 2.5D로: 확장 가능한 카메라 시스템

우리가 함께 설계할 카메라 시스템은 모듈식이며 확장 가능합니다. 기본 기능을 보장하는 여러 구성 요소로 구성된 기본 코어와 상황에 따라 선택적으로 사용할 수 있는 다양한 구성 요소/효과가 있습니다.

여기서 구축하고 있는 카메라 시스템은 2D 플랫폼 게임을 대상으로 하지만 다른 유형의 2D 게임, 2.5D 게임 또는 3D 게임까지 쉽게 확장할 수 있습니다.

Unity에서 2D 카메라 마스터하기: 게임 개발자를 위한 튜토리얼

Unity에서 2D 카메라 마스터하기: 게임 개발자를 위한 튜토리얼
트위터

카메라 기능을 카메라 추적과 카메라 효과의 두 가지 주요 그룹으로 나눌 것입니다.

추적

여기에서 수행할 대부분의 카메라 움직임은 추적을 기반으로 합니다. 이것이 게임 장면에서 움직이는 다른 개체를 추적하는 개체(이 경우 카메라)의 기능입니다. 우리가 구현할 추적 유형은 2D 플랫폼 게임에서 발생하는 몇 가지 일반적인 시나리오를 해결할 것이지만 다른 특정 시나리오에 대한 새로운 유형의 추적으로 확장할 수 있습니다.

효과

카메라 흔들림, 카메라 줌, 카메라 페이드 및 색상 오버레이와 같은 멋진 효과를 구현할 것입니다.

시작하기

Unity에서 새 2D 프로젝트를 만들고 표준 자산, 특히 RobotBoy 캐릭터를 가져옵니다. 다음으로 접지 상자를 만들고 캐릭터 인스턴스를 추가합니다. 현재 장면에서 캐릭터와 함께 걷고 점프할 수 있어야 합니다. 카메라가 직교 모드로 설정되어 있는지 확인하십시오(기본적으로 원근으로 설정되어 있음).

목표 추적

다음 스크립트는 기본 카메라에 기본 추적 동작을 추가합니다. 스크립트는 장면의 기본 카메라에 구성 요소로 연결되어야 하며 추적할 대상 개체를 할당하기 위한 필드를 노출합니다. 그런 다음 스크립트는 카메라의 x 및 y 좌표가 추적하는 개체와 동일한지 확인합니다. 이 모든 처리는 업데이트 단계에서 수행됩니다.

 [SerializeField] protected Transform trackingTarget; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x, trackingTarget.position.y, transform.position.z); }

주인공을 추적할 수 있도록 다음 동작에 의해 노출된 "추적 대상" 필드 위로 장면 계층 구조에서 RobotBoy 캐릭터를 드래그합니다.

오프셋 추가

다 좋습니다. 하지만 곧바로 한계를 알 수 있습니다. 캐릭터는 항상 장면의 중앙에 있습니다. 우리는 일반적으로 우리가 관심을 갖지 않는 캐릭터 뒤에서 많은 것을 볼 수 있으며, 게임 플레이에 해로울 수 있는 캐릭터 앞에 있는 것을 너무 적게 볼 수 있습니다.

이 문제를 해결하기 위해 대상에서 오프셋으로 카메라의 위치를 ​​지정할 수 있는 몇 가지 새로운 필드를 스크립트에 추가하고 있습니다.

 [SerializeField] float xOffset; [SerializeField] float yOffset; // ... void Update() { transform.position = new Vector3(trackingTarget.position.x + xOffset, trackingTarget.position.y + yOffset, transform.position.z); }

아래에서 두 개의 새 필드에 대한 가능한 구성을 볼 수 있습니다.

평활화

카메라 움직임은 꽤 뻣뻣하며 일부 플레이어는 환경의 지속적으로 감지되는 움직임으로 인해 현기증을 일으키기도 합니다. 이 문제를 해결하기 위해 선형 보간을 사용하여 카메라 추적에 약간의 지연을 추가하고 캐릭터가 위치를 변경하기 시작한 후 카메라가 제자리에 얼마나 빨리 위치하는지 제어하는 ​​새 필드를 추가합니다.

 [SerializeField] protected float followSpeed; // ... protected override void Update() { float xTarget = trackingTarget.position.x + xOffset; float yTarget = trackingTarget.position.y + yOffset; float xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); float yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); transform.position = new Vector3(xNew, yNew, transform.position.z); } 

현기증 중지: 축 잠금

캐릭터와 함께 카메라가 시시각각 오르락 내리락 하는 모습을 보는 것이 뇌에 좋지 않기 때문에 축 잠금을 도입하고 있습니다. 이는 추적을 하나의 축으로만 제한할 수 있음을 의미합니다. 그런 다음 추적 코드를 축 독립 추적으로 분리하고 새 잠금 플래그를 고려합니다.

 [SerializeField] protected bool isXLocked = false; [SerializeField] protected bool isYLocked = false; // ... float xNew = transform.position.x; if (!isXLocked) { xNew = Mathf.Lerp(transform.position.x, xTarget, Time.deltaTime * followSpeed); } float yNew = transform.position.y; if (!isYLocked) { yNew = Mathf.Lerp(transform.position.y, yTarget, Time.deltaTime * followSpeed); } 

레인 시스템

이제 카메라는 플레이어를 수평으로만 추적하므로 한 화면 높이로 제한됩니다. 캐릭터가 사다리를 오르거나 이보다 높게 점프하면 따라가야 합니다. 이를 수행하는 방법은 차선 시스템을 사용하는 것입니다.

다음 시나리오를 상상해 보십시오.

캐릭터는 처음에 낮은 차선에 있습니다. 캐릭터가 이 레인의 경계 내에 있는 동안 카메라는 설정할 수 있는 레인 특정 높이 오프셋에서만 수평으로 이동합니다.

캐릭터가 다른 레인에 진입하는 즉시 카메라는 해당 레인으로 전환하고 다음 레인 변경이 발생할 때까지 거기에서 계속 수평으로 이동합니다.

플레이어에게 혼란을 줄 수 있는 점프와 같은 동작 중에 빠른 레인 전환을 방지하기 위해 레인 설계에 주의를 기울여야 합니다. 레인은 플레이어의 캐릭터가 잠시 동안 그 위에 머물러 있을 경우에만 변경되어야 합니다.

레인의 레벨은 디자이너의 특정 요구 사항에 따라 게임 레벨 전체에서 변경되거나 완전히 중단되어 다른 카메라 추적 시스템이 그 자리를 대신할 수 있습니다. 따라서 차선 영역을 지정하기 위한 몇 가지 리미터가 필요합니다.

구현

가능한 구현은 장면에서 레인을 간단한 개체로 추가하는 것입니다. 위의 추적 스크립트에서 Y 오프셋과 쌍을 이루는 Y 위치 좌표를 사용하여 시스템을 구현합니다. 따라서 X 및 Z 좌표에서의 위치는 중요하지 않습니다.

추적 클래스와 함께 카메라에 LaneSystem 클래스를 추가하고 제공된 배열에 레인 객체를 할당합니다. 또한 참조 필드에 플레이어 캐릭터를 할당합니다. 기준이 레인과 다른 레인 사이에 위치하므로 둘 중 더 낮은 것이 카메라 위치를 지정하는 데 사용됩니다.

그리고 LaneSystem 클래스는 기준 위치를 기준으로 레인 사이에서 카메라를 이동하는 작업을 처리합니다. FollowSpeed는 차선 전환이 너무 갑작스러운 것을 방지하기 위해 위치 보간을 위해 다시 사용됩니다.

 [SerializeField] Transform reference; [SerializeField] List<Transform> lanes; [SerializeField] float followSpeed = 5f; // ... void Update() { float targetYCoord = transform.position.y; if (lanes.Count > 1) { int i = 0; for (i = 0; i < lanes.Count - 1; ++i) { if ((reference.position.y > lanes[i].position.y) && (reference.position.y <= lanes[i + 1].position.y)) { targetYCoord = lanes[i].position.y; break; } } if (i == lanes.Count - 1) targetYCoord = lanes[lanes.Count - 1].position.y; } else { targetYCoord = lanes[0].position.y; } float yCoord = Mathf.Lerp(transform.position.y, targetYCoord, Time.deltaTime * followSpeed); transform.position = new Vector3(transform.position.x, yCoord, transform.position.z); }

이 구현은 WYSIWYG 구현이 아니며 독자를 위한 연습으로 남겨둡니다.

잠금 노드 시스템

카메라가 차선을 이동하는 것은 좋지만 때때로 게임 장면에서 관심 지점(POI)인 무언가에 카메라를 고정해야 할 때가 있습니다.

이는 장면에서 이러한 POI를 구성하고 트리거 충돌기를 연결하여 달성할 수 있습니다. 캐릭터가 트리거 충돌기에 들어갈 때마다 카메라를 이동하고 POI에 머뭅니다. 캐릭터가 이동한 다음 POI의 트리거 충돌기를 벗어나면 다른 유형의 추적, 일반적으로 표준 추적 동작으로 돌아갑니다.

카메라 추적을 잠금 노드로 전환했다가 다시 전환하는 것은 간단한 전환이나 추적 모드가 푸시 및 팝되는 스택 시스템에 의해 수행될 수 있습니다.

구현

잠금 노드를 구성하려면 개체(비어 있거나 아래 스크린샷과 같이 스프라이트일 수 있음)를 만들고 여기에 큰 Circle Collider 2D 구성 요소를 연결하여 카메라가 카메라를 켤 때 플레이어가 있을 영역을 표시합니다. 노드에 초점을 맞춥니다. 모든 유형의 충돌기를 선택할 수 있습니다. 여기서는 Circle을 예로 선택하겠습니다. 또한 "CameraNode"와 같이 쉽게 확인할 수 있는 태그를 만들어 이 개체에 할당합니다.

카메라의 추적 스크립트에 다음 속성을 추가합니다.

 public Transform TrackingTarget { get { return trackingTarget; } set { trackingTarget = value; } }

그런 다음 플레이어에 다음 스크립트를 첨부하면 일시적으로 카메라의 대상을 설정한 잠금 노드로 전환할 수 있습니다. 스크립트는 플레이어가 트리거 영역을 벗어났을 때 다시 돌아갈 수 있도록 이전 대상도 기억합니다. 필요한 경우 이를 전체 스택으로 변환할 수 있지만 여러 잠금 노드를 겹치지 않기 때문에 이 작업을 수행할 수 있습니다. 또한 Circle Collider 2D의 위치를 ​​조정하거나 다른 종류의 충돌기를 다시 추가하여 카메라 잠금을 트리거할 수 있습니다. 이것은 단지 예일 뿐입니다.

 public class LockBehavior : MonoBehaviour { #region Public Fields [SerializeField] Camera camera; [SerializeField] string tag; #endregion #region Private private Transform previousTarget; private TrackingBehavior trackingBehavior; private bool isLocked = false; #endregion // Use this for initialization void Start() { trackingBehavior = camera.GetComponent<TrackingBehavior>(); } void OnTriggerEnter2D(Collider2D other) { if (other.tag == tag && !isLocked) { isLocked = true; PushTarget(other.transform); } } void OnTriggerExit2D(Collider2D other) { if (other.tag == tag && isLocked) { isLocked = false; PopTarget(); } } private void PushTarget(Transform newTarget) { previousTarget = trackingBehavior.TrackingTarget; trackingBehavior.TrackingTarget = newTarget; } private void PopTarget() { trackingBehavior.TrackingTarget = previousTarget; } } 

카메라 줌

카메라 줌은 사용자 입력에 따라 실행되거나 POI 또는 레벨 내의 좁은 영역과 같은 것에 초점을 맞추고 싶을 때 애니메이션으로 실행할 수 있습니다.

Unity 3D에서 2D 카메라 줌은 카메라의 orthographicSize를 조작하여 얻을 수 있습니다. 다음 스크립트를 구성 요소로 카메라에 연결하고 SetZoom 메서드를 사용하여 확대/축소 비율을 변경하면 원하는 효과를 얻을 수 있습니다. 1.0은 확대/축소 없음, 0.5는 두 번 확대, 2는 두 번 축소 등을 의미합니다.

 [SerializeField] float zoomFactor = 1.0f; [SerializeField] float zoomSpeed = 5.0f; private float originalSize = 0f; private Camera thisCamera; // Use this for initialization void Start() { thisCamera = GetComponent<Camera>(); originalSize = thisCamera.orthographicSize; } // Update is called once per frame void Update() { float targetSize = originalSize * zoomFactor; if (targetSize != thisCamera.orthographicSize) { thisCamera.orthographicSize = Mathf.Lerp(thisCamera.orthographicSize, targetSize, Time.deltaTime * zoomSpeed); } } void SetZoom(float zoomFactor) { this.zoomFactor = zoomFactor; }

화면 흔들림

게임에서 지진, 폭발 또는 기타 효과를 표시해야 할 때마다 카메라 흔들림 효과가 유용합니다.

이를 수행하는 방법의 구현 예는 GitHub: gist.github.com/ftvs/5822103에서 볼 수 있습니다. 구현은 상당히 간단합니다. 지금까지 다룬 다른 효과와 달리 약간의 임의성에 의존합니다.

페이드 및 오버레이

레벨이 시작되거나 끝날 때 페이드 인 또는 아웃 효과가 좋습니다. 화면 전체에 걸쳐 확장되는 패널에 상호작용할 수 없는 UI 텍스처를 추가하여 이를 구현할 수 있습니다. 처음에는 투명할 때 원하는 색상과 불투명도로 채우거나 애니메이션을 적용하여 원하는 효과를 얻을 수 있습니다.

다음은 해당 구성의 예입니다. UI 패널 개체가 Main Camera Object의 "Camera Overlay" 자식에 할당된다는 점에 유의하십시오. 카메라 오버레이는 다음을 특징으로 하는 오버레이라는 스크립트를 표시합니다.

 [SerializeField] Image overlay; // ... public void SetOverlayColor(Color color) { overlay.color = color; } 

페이드 인 효과를 내기 위해서는 다음 스크립트와 같이 SetOverlayColor로 설정한 대상 색상에 보간을 추가하여 오버레이 스크립트를 변경하고 패널의 초기 색상을 검정색(또는 흰색)으로 설정하고 대상 색상을 오버레이의 최종 색상으로 필요에 맞게 fadeSpeed를 변경할 수 있습니다. 0.8이 초보자에게 좋은 것 같습니다. fadeSpeed의 값은 시간 수정자로 작동합니다. 1.0은 여러 프레임에 걸쳐 발생하지만 1초 시간 프레임 내에 발생함을 의미합니다. 0.8은 실제로 완료하는 데 1/0.8 = 1.25초가 걸린다는 것을 의미합니다.

 public class Overlay : MonoBehaviour { #region Fields [SerializeField] Image overlay; [SerializeField] float fadeSpeed = 5f; [SerializeField] Color targetColor; #endregion void Update() { if (overlay.color != targetColor) { overlay.color = Color.Lerp(overlay.color, targetColor, Time.deltaTime * fadeSpeed); } } #region Public public void SetOverlayColor(Color color) { targetColor = color; } #endregion }

마무리

이 기사에서는 게임에 모듈식 2D 카메라 시스템을 설치하는 데 필요한 기본 구성 요소와 이를 설계하는 데 필요한 마음가짐을 보여주려고 했습니다. 당연히 모든 게임에는 특정 요구 사항이 있지만 여기에 설명된 기본 추적 및 간단한 효과를 사용하면 먼 길을 갈 수 있고 자신의 효과를 구현하기 위한 청사진도 얻을 수 있습니다. 그런 다음 더 나아가 다른 프로젝트에도 전송할 수 있는 재사용 가능한 Unity 3D 패키지에 모든 것을 포장할 수 있습니다.

카메라 시스템은 플레이어에게 적합한 분위기를 전달하는 데 매우 중요합니다. 내가 사용하고 싶은 좋은 비교는 고전 연극과 영화의 차이점을 생각할 때입니다. 카메라와 필름 자체가 장면에 너무 많은 가능성을 가져왔기 때문에 결국 그 자체로 하나의 예술로 발전했습니다. 따라서 다른 "Pong" 게임을 구현할 계획이 없다면 고급 카메라는 모든 게임 프로젝트에서 선택하는 도구여야 합니다. 지금부터 착수합니다.

관련: MVC를 사용한 Unity: 게임 개발 수준을 높이는 방법