Game Math] Portal System 카메라 상대적 동기화
- 김영호
- 2022년 7월 27일
- 2분 분량
최종 수정일: 2023년 4월 25일
원통 좌표계를 통해 포탈과 플레이어의 상대적 위치를 알아낸 것으로 반대쪽 포탈과 포탈 이미지를 그려줄 카메라의 위치를 계산하여 포탈들이 각 글로벌 상에서 어느 각도로 있더라도 동기화가 가능하도록 만들어 주었다.
구현
동기화를 위한 값, 계산 순서
현재 포탈의 right Vector와 포탈->플레이어 Vector간의 사이각
타겟 포탈의 right Vector와 글로벌상의 right Vector(1, 0, 0)간의 각도 차이
1 + 2를 한것이 글로벌상에서 동기화된 카메라의 각도
각 포탈을 기준으로 플레이어가 전방이라면 카메라는 후방에 있어야함
현재 포탈과 타겟 포탈의 각도 차이
public void SyncCamToPlayer_Relative()
{
//1. 현재 포탈의 right Vector와 포탈->플레이어 Vector간의 사이각
float angle_Cur_PortalToPlayer = Vector3.Angle(portal_Current.right, Vector_PortalToCamera());
if (!Over180(portal_Current.right, Vector_PortalToCamera()))
angle_Cur_PortalToPlayer += 180f; //포탈의 후방에 있음
//2. 타겟 포탈의 right Vector와 글로벌상의 right Vector(1, 0, 0)간의 각도 차이
float angle_TarPortal = Vector3.Angle(portal_Target.right, Vector3.right);
angle_TarPortal = (float)((int)((angle_TarPortal + 0.005) * 100f) / 100f); //소수점 제거
if (Over180(portal_Target.right, Vector3.right))
angle_TarPortal = 360 - angle_TarPortal;
//3. 1 + 2를 한것이 글로벌상에서 동기화된 카메라의 각도
float angle_SyncCam = angle_Cur_PortalToPlayer + angle_TarPortal;
//4. 각 포탈을 기준으로 플레이어가 전방이라면 카메라는 후방에 있어야함
angle_SyncCam = ((angle_SyncCam + 180f) % 360f) * Mathf.Deg2Rad; //+180도, angle <= 360이므로 % 360, Radian으로 변환
//현재 포탈 <-> 플레이어 간의 거리
float dist = Vector3.Distance(portal_Current.position, cam_Player.parent.position);
//카메라의 위치 동기화
NewPosition(angle_SyncCam, dist);
//5. 현재 포탈과 타겟 포탈의 각도 차이
float angle_Portals = Vector3.Angle(portal_Current.right, portal_Target.right);
if (Over180(portal_Current.right, portal_Target.right))
angle_Portals = 360 - angle_Portals;
//카메라 회전 동기화
SyncRotaion(angle_Portals);
}
void NewPosition(...) : 원통 좌표계를 받아와 직교 좌표계로 변환 해주는 함수
//포탈간 차이각으로 계산
void NewPosition(float offset_angle, float dist)
{
//포탈과 플레이어간의 원통 좌표계를 직교 좌표계로 변환
float x = Mathf.Cos(offset_angle); float z = Mathf.Sin(offset_angle);
//타겟 포탈을 기준으로 카메라가 있어야 할 위치(포탈과 카메라간의 offset)
Vector3 calcPos = new Vector3(x, 0, z) * dist;
//타겟 포탈의 위치 + 포탈과 카메라 간의 offset
Vector3 newPos = portal_Target.position + calcPos;
//Y축으로 내려다 본 상황에서 x, z는 위치가 동일하면 되지만 y(시야 높이)는 player와 동일해야 하기 때문에 y값을 덮어씌운다)
newPos = new Vector3(newPos.x, cam_Player.position.y, newPos.z);
//카메라의 위치 수정
cam_Target.position = newPos;
}
void SyncRotation(...)
void SyncRotaion(float angle_Portals)
{
//카메라는 플레이어와 반대방향을 봐야하므로 +180
Quaternion portalRotationalDiff = Quaternion.AngleAxis(angle_Portals + 180f, Vector3.up);
//플레이어 카메라의 정면에 방향을 곱해서 타겟 카메라의 Rotation을 정함
Vector3 newCamDirection = portalRotationalDiff * cam_Player.forward;
//타겟 카메라가 새로 만들어진 방향을 보게함
cam_Target.rotation = Quaternion.LookRotation(newCamDirection, Vector3.up);
}
bool Over180
public bool Over180(Vector3 v1, Vector3 v2)
{
//v1, v2의 외적
Vector3 cross = Vector3.Cross(v1, v2);
if (cross.y < 0)
return true;
return false;
}
함수 bool Over180(...)은 Vector의 외적을 사용해서 전후방을 확인하기 위한 함수이다.
Vector의 각도(내적)는 어느 방향(시계 방향, 반시계 방향)이든 기준 Vector로부터 0~180의 값으로만 나온다.
그래서 포탈의 전후방(내적 시작 기준 Vector = 포탈의 right)을 구분하지 않으면 포탈의 차이각 계산에 오류가 발생하게 된다.