-
유니티) 벡터의 내적 (cos을 이용한 간단한 회전)Unity유니티/회전 2022. 6. 10. 14:46
벡터의 내적을 이용해서, 캐릭터의 좌우회전을 구현해보기로했다
Vector A , B를 예로들어
내적공식 => Dot(A,B) = Cosθ|A||B|
유니티에서 제공하는 함수중에
Vector3.Dot(a,b) 두벡터 ab의 내적값 반환해줌(내적은 벡터가 아닌 스칼라값을 반환함)
Vector3.Magnitude(Vector A) 인자(벡터)의 길이를 반환해줌
Mahf.Acos(float a) 역함수로 인자로 코싸인θ을 받고 코싸인각도를 반환해줌
먼저 탑뷰에서 바라보는 형식으로 하는게 만들기 편하다 생각이들었다, y축 회전(위에서 바라볼떄 좌우회전)으로 구현하기로 했다
1) Update
비헤이버 업데이트는 매프레임마다 반복하는데, 매프레임마다 회전값을 구하며 회전을 하는게 별로라 생각이들었다
그래서 RotThroughCos()란 함수를 업데이트문에 넣고 실행을 하는데
3가지의 예외처리를 해보았다1. if (target != _aim.transform.position) : 타겟은 멤버변수인데, _aim의 변한 위치값을 캐싱해두는 용도였다
그래서, 굳이 바라볼 대상(_aim)의 위치가 변하지 않으면 함수를 실행하지않도록했다
2.if (target == _aim.transform.position && _Co == null) { return; } : 현재 진행할 회전없으면 돌아가기3.(_Co != null && _isSameDir == false && target == _aim.transform.position) { return; } :
목표방향이 변경이 없으면서 또 현재 회전을 진행중이면 다시 뒤로
_Co는 코루틴실행및 기억해두려 만든 멤버변수다
그래서 코루틴으로 회전을 개별로 처리하기로했고 만약 코루틴이 실행중이며 회전방향이 바뀌었다면두 조건이 만족할때만 저 긴 업데이트문을 실행하는것으로하였다 (아니면 return으로 업데이트 빠져나오기)
먼저 _aim멤버변수는 회전할방향이 되어줄 오브젝트다(유니터 에디트씬에서 오브젝트 2개 깔아둠)
멤버변수 target이 있는데, 그저 당장 회전을 실행해야한다면 _aim의 값을 캐싱해두는 용도로 급하게 만들었다굳이 캐싱용 변수를 만든건, 목표위치가 바뀌면 캐싱해두었던 위치와 다른것을 판별할수있었고
그런 쉬운 판별로 다시 코루틴을 재실행하게하려는 의도였다(코루틴에 회전기능을 두어서)
( 보통 _표시로 멤버변수 표기를하는데, 끝에 만든 변수여서 깜빡했다!)
내적을 하기위하여 필요한게 3가지였다
1. 회전을 할 캐릭터의 z축 , 전방방향 (Y축 회전으로 만드려해서 그렇다)
현재 회전을 할 obj(이 스크립트를 참조할 객체)의 전방Z축방향이 필요하다
그런데 월드공간상에서 회전할OBJ의 양수Z축이 전방벡터가 되기에Vector3 forwardDir = this.transform.forward;로 작성했다
2. 회전할 방향
먼저 나같은경우 테스트를 위해, 캐릭터와 바라볼 방향 obj두개를 에디터에 깔았다
그러면 현재 나의 캐릭터Forward위치에서 바라볼 오브젝트로 향하는 벡터는
Vector3(a-b) => b에서 a로 향하는 방향으로 쉽게 구할수있다
Vector3 dir = target - this.transform.position;
3. 내적함수를 이용하여 코사인을 구하고 역함수로 코사인각도구하기
유니티 제공함수기준으로
Vector3.Dot(forwardDir, dir) ==
Vector3.Magnitude(forwardDir) * Vector3.Magnitude(dir) * Mathf.Cos(cos); 이 된다위를 응용해보면
float cos ==Vector3.Dot(forwardDir, dir) / (Vector3.Magnitude(forwardDir) * Vector3.Magnitude(dir) )
이 된다!!
또 Mathf.Cos()를 쓰지 않는 이유는, 저기 위 "float cos" 자체가 Mathf.Cos 값이다
Mathf.Cos(cos)할 필요가 없단것이다
(주의할점 벡터의 길이가 0이하가 되면 에러가 뜨니 신경써야됨, 난 y축으로 1증가시킴)
Mathf.Acos()는 역함수로 코사인각도를 반환해준다
단 Acos으로 반환되는 값은 라디안이다(각도법을 쓰려면 변환해줘야함)
호도법(라디안) -> 각도법 : 1° = (180 * rad) / π
각도법 -> 호도법 : 1rad = (π * 1° ) / 180그러나 이럴 필요없이 유니티에서 제공해주는걸 쓰자! Mathf.Rad2Deg(이름이 말한다, 라디안을 각도로 바꾸고싶다라고!)
Mathf.Rad2Deg를 곱하여 우리에게 익숙한 degree 각도법으로 변환해준다
끝으로 간편하게 식별하고싶어서 디버그로그로 콘솔창에 또 출력하였다
그러나!!
단순히 전방벡터와 바라볼 방향벡터만 구해선 회전을 할수가 없다
이런 1차적인 내적으론 물체(바라볼)가 나의 앞에있는지 뒤에있는지 구분만 가능하기떄문이다그래서 회전할 캐릭터가 지닌 또 다른 기저벡터 Right벡터를 이용하기로했다!
먼저 RdotD는 이름에서 말하듯, Right벡터와 Direction(방향, 위에서 전방벡터와 내적했던 파트너)를 내적한 값이다
왼손좌표계를 사용하는 유니티에서, Right벡터는 언제나 포워드방향기준 오른쪽에 있을것이다
Rcos는 Right벡터와 내적한 결과물을 각도법으로 다시 변환한것이다 (Right벡터에서 방향벡터까지의 각도)그리고 degree각도가 360도 넘으면 -360를 해주는건 지워도 상관없는데 내가 왜 놔뒀을까?
(저건 빠르게 손으로 마구잡이로 버그내보겠다고 하다 각도가 틀어질때 대비해서 해둔건데, 지워도 차이가없다)
정리
변수 역할
degree 현재 캐릭터의 +Z축 방향
cos 현재 캐릭터의 +Z축 방향과 바라볼방향벡터 사이의 코사인값
Rcos 캐릭터의 Right벡터와 바라볼방향벡터 사이의 각도캐릭터(회전해야할놈) , 물체(우리 캐릭터의 회전해야할 방향) 이렇게 두개가 있다
전방벡터와 내적한 값 cos는 코사인값이다 코사인 그래프를 보면 더 직관적인데
cos는 -1 ~ 1사이의 범위를 가진다왼쪽이 그래프다
x축은 각도를 의미하며(정확히는 라디안)
y축은 그로인한 cos값이다cos값이 0이상이라면 현재 캐릭터 전방벡터기준 좌우로 90도까지가 양수의 영역이다
현재 내 에디터씬속 모습인데
파란색 방향이 z축 전방방향이다그리고 빨간색 구간이 cos값이 양수가 나오는 구간이다
즉 캐릭터의 앞면에 해당한다
그리고 Rcos는 아까 말했든 Right벡터와 물체사이의
코사인값을 각도로 변화시킨것이다그리고 전방벡터(캐릭터의 +z축)와
Right벡터(캐릭터의 +x축)은 서로 직교한다 90도 각을 가지고있다
여기서 물체가 캐릭터의 정면방향에 있다는걸
cos >= 0으로 알수가 있으며
왼쪽, 오른쪽으로 회전할지는90 - Rcos각도로 구할수가 있다!
왼쪽으로 회전한다면
현재 그림은 캐릭터가 물체를 바라보고있는 그림이다, 그림속 각도 표기는 회전하기전 각도를 그린것이다
아무튼 12시가 forward벡터면, 3시방향이 right벡터가된다, 여기서 왼쪽 11시쯤에 위치한것이 물체바라볼 방향이라 가정시
왼쪽그림대로, Right벡터와 물체사이의 외적을 한다 그후 MathF.Acos함수로 각도를 구한다
참고로 난 y축회전으로 진행하엿는데
인스펙터창에서 확인해보면
y축회전은 왼쪽으로 갈수록 음수
오른쪽으로 갈수록 양수가 된다
그래서 결론은.
캐릭터의 정면과 물체의 코사인각도(degree변수)만큼 우리의 캐릭터는 회전을 해야하는데
방향이 왼쪽이냐 오른쪽이냐 그것이다
나는 그걸 Right벡터와 내적한 결과로 찾는 방식을 골랐다
right벡터와 다시 내적하여 90도를 넘는지확인 ( 90이상은 왼쪽 , 이하는 오른쪽 방향)
그래서 반환할 degree는 degree = 90.0f - Rcos;이 된다물체가 내 왼쪽일시, Rcos값이 90도를 넘게되고 degree = 90 - (90이넘는 Rcos); 결국 -degree만큼(왼쪽방향)회전하고
물체가 오른쪽일시 Rcos값이 90도를 안넘게 될테니 degree = 90.0f - Rcos; 로 오른쪽 회전을 하면된다
if (cos >= 0)
degree = 90.0f - Rcos;
반대로 cos값이 음수의 영역이라면, 물체가 나의 뒷편에 있는것이된다
정리를 해야겠단 생각이 많이든다;;
음수(뒷편)방향으로 회전시, degree가 180도가 나오는 경우 반대로 360-degree만큼 회전하는게 맞다 생각들었다
그림이 상당히 지저분하다..
만약 나의 캐릭터 전방방향기준으로 회전할 degree가 200도가 나왔다면 오른쪽으로 200회전을 한단건
왼쪽으로 -160도만큼 회전한것과 같은 결과다
그런데 오른쪽으로 200회전보단
왼쪽으로 -160만큼회전하는게 짧고 간편해보인다if (degree > 180.0f && Rcos > 90.0f)
{ degree = degree - 360.0f; }이렇게 작성을 하였고 Rcos >90 의 경우는 안전장치개념으로 추가 작성하였다
그러면 degree가 180도 이하로 나왔다면?
degree = degree;
degree값 자체로 회전시키면 된다! 간편하다!
약간의 특수한경우 , Rcos가 반대로 내적을 구해버리는경우
else if (degree < 180.0f && Rcos > 90.0f)
{ degree = -degree; }예외처리용으로 작성하였다
끝으로
회전할목표 변수 a에 값을 넣고 코루틴을 실행하였다
RotCoru함수
반복문으로 회전을 계속 진행시키기로 했다
특별한 이유는 없고, 그것이 제일 간편하다 생각했다
CalcuDir함수를 내부에서 계속 진행하는데
CalcuDir함수를 보면, Lerp보간으로 쿼터니언값을 델타타임만큼 보간하고있다
그런데 Lerp같은 변환을 할떄 아쉬운점이 하나있다면
시간에 비례해서 보간을 하다보면 후반부터는
아주 미세하게 값이 변하는데 많은 시간을 쏟는다
왜냐면 this.transform.rotation값이 angle과 큰차이가 없는 상태에서 보간을하다보니 그런것이다
그래서 나는 각도차이가 미세하다면
육안으로 식별도 안되는 차이라 강제로 맞추기로했다
(실제로 에디터에서 봐도 강제로 맞추어지는 부분이 육안으로 식별이 안된다)
이렇게 회전이 끝나면 _isSameDir을 트루로 바꿔주고
RotCoru속 와일문을 빠져나와 코루틴도 같이 빠져나와준다
반복자까지 null로 바꾸어주어 업데이트문에서
다시 순환이 시작된다
아쉬운점 : 일부러 버그를 내려 마구잡이로 흔들다보면
각이 살짝 틀어질떄가 있다 다행인점은 1초안에 다시 회전이 자리를 잡긴하는데 약간 찝찝한부분이긴하다
그리고 왼쪽 회전, 오른쪽 회전 정상적으로 작동하지만
시작 순간에만! 앞방향 왼쪽에 놓으면 회전이 엄청 빠르다
그부분만 그러는데 왜그런건지 잘모르겠다..
다행히 실시간으로 회전을 시키어보면 그런게 없어 다행이긴한데 아무튼 그것도 찝찝하다
다음엔 전에 Atan2으로 만든 회전을 내가 다시볼겸 작성해봐야겠다'Unity유니티 > 회전' 카테고리의 다른 글
유니티) 역탄젠트 Mathf.Atan2를 이용한 회전 (0) 2022.06.14 유니티) 벡터의 외적을 이용한 회전 (0) 2022.06.12