간단하게 넘어간 것들을 더 알아보고 싶어 공부하다가 4시간 넘게 이 둘만 잡고 있었다.
01. Skin Width
- 두 콜라이더가 서로 스킨 너비만큼 관통할 수 있다.
- Skin Width가 클 수록 지터링 현상이 감소한다. (보통 반지름의 10% 정도로 설정)
도저히 설명이 이해가 가지 않아 거의 한 시간 찾아봤다.
Skin Width 1
Skin Width 0.1
처음에는 Two Colliders can penerate each otehr as deep as their Skin Width 라는 표현 때문에,
Character Controller가 갖고 있는 자체적인 캡슐 모양의 콜라이더 '안쪽으로' 충돌을 허용하는 것인줄 알았으나
실험 결과 '충돌 감지 기준'이 바뀌는 것이다. 물리적 엔진의 동작 자체는 변화하지 않으나, 충돌의 감지 기준을
Skin Width만큼 살짝 밖으로 충돌 감지 기준이 확장되어서 Skin Width 범위 내에서 충돌을 조기에 종료한다.
이것이 지터링을 왜 바꿀 수 있느냐 !
우선 지터링(Jittering)은 충돌 감지 후 너무 강하게 튕겨나는 문제다.
이산적인 충돌 감지는 다음과 같은 과정을 통해 지터링을 만든다.
- 프레임마다 캐릭터가 중력으로 인해 바닥으로 떨어진다.
- 충돌 감지 시, 이미 조금 깊이 박혀 있는 상태에서 튕겨 나온다.
- 충돌 처리가 강하게 작용되어, 다시 위로 밀려나게 된다.
- 다음 프레임에서 다시 중력으로 내려오고, 또 충돌하고 튕겨 나간다.
- 이 과정을 반복하며 미세하게 떨리는 현상(지터링)이 발생한다.
다만, Skin Width를 적용하게 되면, 조금 겹쳐도 깊이 박히기 전에 충돌 처리가 종료된다(미리 감지하므로).
따라서 덜 깊이 박히기 때문에 튀어오르는 강한 반작용이 줄어들고 지터링이 줄어들게 된다.
콜라이더의 크기를 키우는 것과는 무엇이다르냐면, 콜라이더의 크기를 키우는 것은 물체의 크기가 커지는 것이므로
물리 엔진의 동작 자체가 변하는 것이고, Skin Width는 단순히 충돌 감지의 기준을 조절하는 개념이므로
게임 내 크기 변화 없이도 효과를 볼 수 있다. 즉, 콜라이더 크기를 키우면 상호작용 자체가 변화하지만
Skin Width는 충돌 판정 방식만 바꾸므로 지터링 해결에 최적이다.
실험을 해 보는 것이 가장 빠르게 "답을 찾기 위한 적절한 질문"을 도출해 내는 방법인듯 하다.
직접 실험을 하기 전까지는 명확하게 SkinWidth가 CharacterController의 자체 콜라이더 바깥쪽인지, 안쪽인지도
확실하지 않았는데, 실험을 하고 나니 명확해졌고, 이를 바탕으로 좋은 질문을 할 수 있었다.
02. 카메라 구현
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class CameraController : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField] private float rotationSpeed = 100f;
// 타겟과 카메라 사이의 거리
[SerializeField] private float distance = 5f;
// 직각 삼각형 ( 서 있는 )각도
private float _azimuthAngle = 0f;
//누워 있는 직각 삼각형의 각도
private float _polarAngle = 45f;
private void Start()
{
var cartesianPosition = GetCameraPosition(distance, _azimuthAngle, _polarAngle);
var cameraPosition = target.position - cartesianPosition;
transform.position = cameraPosition;
transform.LookAt(target);
}
private void LateUpdate()
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
//Debug.Log($"Mouse X : {mouseX} Mouse Y : {mouseY}");
_polarAngle -= mouseY * rotationSpeed * Time.deltaTime;
_azimuthAngle += mouseX * rotationSpeed * Time.deltaTime;
_polarAngle = Mathf.Clamp(_polarAngle, 10f, 45f);
var cartesianPosition = GetCameraPosition(distance, _azimuthAngle, _polarAngle);
var cameraPosition = target.position - cartesianPosition;
transform.position = cameraPosition;
transform.LookAt(target);
}
// 두 개의 각을 넣고, 거리를 넣으면 좌표값을 얻어 올 수 있는 함수
Vector3 GetCameraPosition(float r, float azimuthAngle, float polarAngle)
{
// 코싸인 함수는 Rad로 각을 변환해 주어야 함.
float b = r * Mathf.Cos( polarAngle * Mathf.Deg2Rad);
// b는 밑변의 길이를 구했다.
float z = b * Mathf.Cos(azimuthAngle * Mathf.Deg2Rad);
float y = r * Mathf.Sin(polarAngle * Mathf.Deg2Rad) * -1;
Debug.Log(y);
float x = b * Mathf.Sin(azimuthAngle * Mathf.Deg2Rad);
return new Vector3(x, y, z);
}
}
전체 코드는 다음과 같다. 2D 입력 ( 마우스 입력 )을 3D 좌표계인 구면 좌표계로 전환할 것이다.
우선 마우스 입력을 통해 PolarAnlge ( 수직 각 ) , AzimuthAngle (수평 각)을 구해준다.
이 때 PolarAngle에 -를 해 주는 이유는 마우스 입력 방향을 반전시키기 위해서이다.
일반적으로 Input.GetAxis("Mouse Y")값은 위로 올리면 +값, 아래로 내리면 -값이 나온다.
하지만 일반적으로 3D 게임에서는 마우스를 위로 올리면 카메라가 아래를 내려다봐야 한다.
따라서 -처리를 해 줌으로써 입력 방향을 반전시켜준다.
이 수직각과 수평각, 그리고 카메라로부터의 거리인 Distance (r)을 사용해서 구면 좌표계로 전환할 것이다.
r이 의미하는 바를 먼저 설명하자면, r은 타겟(중심)에서 카메라부터의 거리다. 따라서 구면 좌표계에서 보면 빗변이 된다.
이제, 빗변과 Polar Angle을 사용해서 밑변을 구해준다.
Cos(PolarAngle) = 밑변 / 빗변 , 따라서 밑변 = Cos(PolarAngle) * 빗변
즉, b = r * Cos(PolarAngle)
2) Y 좌표 구하기
우리가 구하고자 하는 Y좌표는 '높이'이다. 따라서 Sin(PolarAngle)을 활용해서 구할 수 있다.
Sin(PolarAngle) = 높이 / 빗변 , 따라서 높이 = Sin(PolarAngle) * 빗변
즉, Y = r * Sin(PolarAngle)
위에서 -로 마우스의 입력 방향을 반전시켜 주었지만 또 다시 -를 해 주는 이유는 현재 카메라 위치를 구하는 방법때문이다.
위 영상에서 분명 똑같이 마우스를 올리고 있음에도, 특정 구간에서는 카메라가 올라가는 듯한 효과를
특정 구간에서는 카메라가 내려가는 듯한 효과가 나타나고 있다. 이는 Sin(PolarAngle)의 특성 때문이다.
-를 곱하지 않은 일반적인 공식을 생각해보자.
Y = r * Sing(PolarAngle)
이 공식에서 중요한 점은:
- 𝜃 = 0° → sin(0°)=0\sin(0°) = 0, Y = 0 (시작점)
- 𝜃 = 90° → sin(90°)=1\sin(90°) = 1, Y = r (최대 높이)
- 𝜃 = 180° → sin(180°)=0\sin(180°) = 0, Y = 0 (다시 원점)
즉, 각도가 증가할수록 Y가 증가하다가 다시 감소한다는 것이다.
그러므로 카메라 이동의 방향을 고정하기 위해서는, PolarAngle값을 특정 각도에 맞춰 한정시키고,
그 각도 내에서 증가와 감소할 때의 Y값을 부호를 통해 올바르게 세팅해 주는 것이 필요하다.
그 코드가 바로 다음 코드이다.
_polarAngle = Mathf.Clamp(_polarAngle, 10f, 45f);
10f ~ 45f로 polarAnlge의 값이 고정되어 있으므로, 이에 맞는 부호를 통해 원하는 동작을 구현하면 된다.
현재 마우스를 아래로 내리면 polarAngle값이 증가하게 되고, 우리가 의도하는 바는 캐릭터가 땅을 보는 것,
즉 카메라가 위로 이동하는 것을 뜻한다.
그렇게 된다면 PolarAngle값이 증가할 때, Y값도 같이 증가해야 하는것이 맞다.
다만, 고려해야 할 것은 우리가 현재 카메라 위치를 다음과 같이 구하고 있다는 것이다.
cameraPosition=targetPosition−cartesianPosition
카메라는 타겟 기준으로 "뒤쪽"으로 이동한 위치에 있고, cartesianPosition을 빼 준 Vector3값에 위치한다.
따라서 PolarAngle값이 증가할 때 Y값이 같이 증가하기 위해서는 -1을 곱해주어 타겟을 기준으로 '반대 방향'으로
이동해야 한다.
3) X, Z좌표 구하기
위의 삼각형과는 다른 , XZ축으로 존재하는 삼각형을 생각하면 된다.
이 삼각형에서는 X축의 거리가 높이, Z축의 거리가 밑변, b가 빗변이 된다.
다음 그림을 참고하면 알 수 있다.
즉 X = b * Sin(PolarAngle)
Z = b * Cos(PolarAngle)이 된다.
마찬가지로 왼쪽을 보면 카메라가 오른쪽으로, 오른쪽을 보면 카메라가 왼쪽으로 이동하는 것은
카메라 위치를 타겟 위치에서 구한 포지션을 빼 주어서 정하고 있기 때문이다.
'시행착오' 카테고리의 다른 글
네트워크 관련 ( 포톤 퓨전 , HasAuthority ) (0) | 2025.05.04 |
---|---|
Tunneling 문제를 해결하기 위한 Raycast 활용 (0) | 2025.04.14 |
Unity < - > Node.js Socket.IO 데이터 교환 (0) | 2025.03.18 |
02.25 회고 + Scroll View 응용 (0) | 2025.02.26 |
Scroll View With ObjectPool (0) | 2025.02.25 |