4주차 개인 과제 트러블 슈팅 내용
1. 2D 타일맵 밖을 벗어나지 않는 카메라 구현
카메라의 시야가 맵 밖을 비추지 않게끔 하려면 아주 간단히
맵 돌아다니면서 좌표를 찍어서 제한두면 되긴 하나 이래서는 매번 새로 만들어주어야 합니다.
노가다로 해결하기보단 스마트하게 자동으로 맵 밖을 벗어나지 않는 코드를 작성하고 싶었습니다.
하지만 개념적으로 막막해서 방법론적으로 챗 gpt 도움을 받았습니다.
개념은 아주 간단했습니다.
1) 맵의 경계 좌표
2) 카메라가 보여주는 화면의 가로, 세로 길이
이것만 알 수 있다면 간단한 더하기 빼기 정도로 구할 수 있습니다.
제시해준 방법 둘을 조합하여 하나의 코드를 작성했으며 이해한 내용은 주석을 달아두었습니다.
그리고 비효율적인 부분들을 개선하여 아래의 코드와 같이 작성하였습니다.
using UnityEngine;
using UnityEngine.Tilemaps;
public class CameraMove : MonoBehaviour
{
[SerializeField, Tooltip("카메라가 따라다닐 대상")] Transform target;
[SerializeField, Tooltip("경계로 삼을 타일맵\n카메라 시야가 해당 타일맵 영역을 벗어나지 않음")] Tilemap mapEdge;
float zPos; // 카메라 Z 좌표
// 카메라 이동 한계 좌표
// 0: 왼쪽 아래
// 1: 오른쪽 위
Vector2[] posLimits;
void Start()
{
// 카메라가 보고 있는 영역의 크기 받아오기
// 2D일 때, 카메라 컴포넌트의 Size가 화면 직교 경계까지의 길이
float camera_half_height = Camera.main.orthographicSize;
// 화면 비율에 따라 height로부터 width 산출
float camera_half_width = camera_half_height * Camera.main.aspect;
// 맵: 타일맵 경계 구하기
Bounds bounds = mapEdge.localBounds;
// 타일맵 왼쪽 아래 끝 좌표
Vector2 bounds_min = bounds.min;
// 타일맵 오른쪽 위 끝 좌표
Vector2 bounds_max = bounds.max;
// 카메라가 이동할 수 있는 좌표의 한계값 산출
posLimits = new Vector2[2]
{
// 맵 경계로부터 카메라가 보여줄 수 있는 너비, 높이의 절반만큼 이격을 준 값
new Vector2(bounds_min.x + camera_half_width, bounds_min.y + camera_half_height),
new Vector2(bounds_max.x - camera_half_width, bounds_max.y - camera_half_height)
};
// 카메라 z축 초기 위치
zPos = transform.position.z;
}
// 이동은 보통 Update() 혹은 FixedUpdate()에서 처리하기에
// 타겟의 이동이 모두 끝난 뒤에 카메라를 움직이기 위해 LateUpdate() 사용
private void LateUpdate()
{
// 타겟의 x,y 좌표에 clamp로 계산해둔 경계값만큼 최소, 최대 제한을 걸어 경계를 벗어나는 이동은 하지 않도록
// 각 줄은 x,y,z 좌표
transform.position = new Vector3
(
Mathf.Clamp(target.position.x, posLimits[0].x, posLimits[1].x),
Mathf.Clamp(target.position.y, posLimits[0].y, posLimits[1].y),
zPos
);
}
}
2. 미니게임을 가져올 때 클래스 명칭이 겹치는 문제 해결
해당 프로젝트에서 미니게임을 가져올 때
무수히 많은 에러가 반기는 광경을 목도할 수 있었습니다.
그 이유는 동일한 명칭을 가진 클래스의 중복!
다른 프로젝트에서 똑같은 기능을 하는 스크립트는 똑같은 명칭을 쓰기 쉽상입니다.
그러면 매번 이름을 다르게 바꿔주어야 하는 수고를 하여야 할까요?
정답은 아닙니다.
게임을 만들어 보면서 이런 걸 가장 먼저 접할 만한 사례가 아마도 아래 예시일 것입니다.
Random 이라는 명칭을 가진 클래스가
UnityEngine, System 두 네임스페이스에 각각 있어
어떤 것을 참조할지 모른다는 에러입니다.
해당 문제는 어떤 네임스페이스의 것을 참조할지 명확하게 써주면 해결됩니다.
여기서 알 수 있는 점은,
클래스들을 포함하는 더 큰 그룹인 네임스페이스는 각각 별도로 구분되어 있어,
다른 네임스페이스에서 쓴 클래스 명칭을 사용하는 데 아무 지장이 없다는 것입니다.
아주 간단히 이미 다른 메서드에서 쓴 지역 변수 명칭을
다시 쓰는 건 문제가 안된다는 것과 같다고 보시면 됩니다.
이걸 활용하면 이름을 짓다 프로그래머가 아닌 작명가가 되는 불상사는 피할 수 있게 됩니다.
기능별로 묶기 위해 각 미니게임의 스크립트들을
해당 미니게임의 네임스페이스 별로 분리했습니다.
3. 점프 구현
저는 이 과제 내용을 처음 들었을 때 처음에 아주 복잡하게 생각했습니다.
진짜 물리적으로 점프를 구현하려 했는데
점프 버튼을 눌렀을 때,
(1) 리지드바디에 up 방향 속력을 가하여 점프
(2) 중력 적용
(3) 점프 시작 높이에 트리거를 생성하여 플레이어의 y축 이동에 대응하여 함께 움직이게끔
(4) 점프 해제 트리거에 닿으면 점프 종료, 중력 적용 해제, y축 방향 속력 0 등
이렇게 만들다 문득 메타버스에서는 그렇게 동작하면 안되는 게 아닌가 생각이 들었습니다.
다른 플레이어가 생성한 점프 해제 트리거에 닿아서 점프가 해제된다던가 하는 불상사가 생기기도 하고요.
그래서 수업을 듣는 메타버스 zep에 접속하여 직접 움직여 보면서 확인해 보았습니다.
오른쪽 책상에는 콜라이더가 있어서 캐릭터는 해당 물체를 뚫고 지나갈 수 없습니다.
이 상태에서 오른쪽 방향키를 누르며 점프를 하여 해당 책상을 넘어갈 수 있는지 테스트를 해보았습니다.
그 결과는 지나갈 수 없었습니다.
이 결과가 말해주는 것은
점프할 때 캐릭터 외형을 나타내는 부분만 위아래로 잠시 움직이고
실제 물리적인 충돌을 담당하는 리지드바디, 콜라이더를 포함한 부분은 제자리에 그대로 있다는 것이었습니다.
생각을 완전히 잘못했었네요.
이 방법이면 정말 간단히 해결할 수 있습니다.
Player 오브젝트 안에 리지드바디, 콜라이더
Sprite 오브젝트 안에 스프라이트 랜더러
이렇게 물리적 연산/외형을 담당할 오브젝트를 따로 만들어 각각 따로 움직일 수 있게끔 구조를 만들어두고
아래의 코드를 통해 점프를 구현했습니다.
점프하여 위아래로 외형이 이동한 후 로컬 좌표가 점프 전과 동일함을 확인!
오차가 누적되어 문제가 생길 여지도 없습니다.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerControl : MonoBehaviour
{
SpriteRenderer spriteRenderer;
float jumpHeight = 2f, jumpTime = 0.5f;
Vector3 sprite_origin, sprite_jumpMax;
Vector2 moveDir;
bool isJumping = false;
int half;
private void Awake()
{
spriteRenderer = GetComponentInChildren<SpriteRenderer>();
// 외형 오브젝트의 초기 로컬 좌표
sprite_origin = spriteRenderer.transform.localPosition;
// 외형 오브젝트의 점프 최대 높이만큼 올랐을 때의 로컬 좌표
sprite_jumpMax = sprite_origin + jumpHeight * Vector3.up;
// jumpTime에 따른 코루틴에서의 루프 실행 횟수(jumpTime 초 동안 점프 수행)
int loopCount = (int)(jumpTime / Time.fixedDeltaTime);
// 그 절반을 나누어 올라갈 때/내려갈 때 사용할 루프 횟수
half = loopCount / 2;
}
// 스페이스를 누르면 점프 호출
void OnJump()
{
if (!isJumping)
StartCoroutine(Jump());
}
// 잽 참고: 점프해도 바로 옆의 장애물을 넘지 못함
// 점프는 스프라이트만 위아래로 잠시 왔다갔다 하게끔(리지드바디가 위아래로 이동하지 않음)
IEnumerator Jump()
{
isJumping = true;
// 점프하여 위로 올라가기
for (int i = 1; i <= half; i++)
{
spriteRenderer.transform.localPosition = Vector3.Lerp(sprite_origin, sprite_jumpMax, (float)i / half);
yield return new WaitForFixedUpdate();
}
// 다시 아래로 떨어지기 >> 원래 좌표로
for (int i = 1; i <= half; i++)
{
spriteRenderer.transform.localPosition = Vector3.Lerp(sprite_jumpMax, sprite_origin, (float)i / half);
yield return new WaitForFixedUpdate();
}
isJumping = false;
}