안녕하세요! 여러분들과 함께 게임 개발을 공부하는 베르입니다!

이번 영상에서는 개발할 때 활용할 수 있는 정규화라는 개념에 대해서 알아봅시다!

 

타임라인

0:00 인트로

0:10 정규화란?

0:47 벡터의 정규화

3:21 좌표의 정규화

5:12 아웃트로

인트로

안녕하세요. 여러분들과 함께 게임 개발을 공부하는 베르입니다.

이번 시간에는 게임 개발에서 사용할 수 있는 정규화라는 개념에 대해서 알아보도록 하겠습니다.

정규화란?

먼저 정규화라는 용어에 대해서 알아보겠습니다.

위키백과를 참고해보면 정규화는 어떤 대상을 일정한 규칙이나 기준에 따르는 정규적인 상태로 바꾸거나 비정상적인 대상을 정상적으로 돌리는 과정을 뜻한다고 합니다.

영어로는 Nomalization이라고 합니다.

이 정규화라는 개념은 여러 분야에서 각 분야에 맞게 사용되고 있습니다.

가장 대표적인 것은 데이터베이스 분야의 정규화로 데이터베이스를 설계할 때 여러 테이블에서 중복되는 데이터를 최소화시키는 프로세스를 가리킵니다.

게임이나 프로그램을 개발할 때도 여러 곳에서 이러한 정규화를 적용할 수 있습니다.

벡터의 정규화

앞에서 정규화를 영어로 하면 Noralization이라고 했을 때 짐작하신 분도 있었겠지만 역시 가장 먼저 이야기 해볼 것은 바로 벡터의 정규화 입니다.

벡터에 대해서는 [좌표와 속도를 다루는 도구 - 벡터] 영상에서 한 번 다룬 적이 있습니다.

벡터에 대한 자세한 내용은 해당 영상에서 확인할 수 있습니다.

벡터의 정규화는 어떤 한 벡터를 벡터의 길이로 나누어서 그 벡터의 길이를 1로 만드는 것입니다.

이렇게 길이가 1이 된 벡터를 단위 벡터라고 부르죠.

이런 식으로 정규화를 통해서 길이를 1이 된 벡터는 캐릭터의 이동과 방향에 주로 사용됩니다.

보통 키보드로 조작하는 방식의 게임은 W키와 S키를 이용해서 앞/뒤로 움직이고 A키와 D키를 이용해서 좌/우로 움직입니다.

그리고 W키를 누르면 수직 입력 값을 1로 받고 S키를 누르면 수직 입력 값을 -1로 받게 됩니다.

물론 손을 떼면 0이고 W키와 S키를 동시에 누르면 값이 상쇄되어서 0이 됩니다.

A키와 D키 역시 마찬가지로 수평 입력 값에 대해 동일하게 동작합니다.

그리고 W키와 S키로 동작하는 수직 입력과 A키와 D키로 동작하는 수평 입력을 묶어서 2차원 벡터로 이동 입력을 표현하게 됩니다.

여기서 이동 입력 키를 하나만 눌렀을 때의 입력 벡터의 길이는 언제나 1이라는 것을 알 수 있습니다.

이처럼 플레이어가 이동하고자 하는 입력 방향 벡터의 길이가 언제나 1이 되어야 캐릭터를 이동시킬 때, 입력 받은 방향에 캐릭터의 이동 속도를 곱하는 방식으로 쉽게 구현할 수 있게 됩니다.

그런데 캐릭터를 대각선 방향으로 이동시키기 위해서 W키와 A키, W키와 D키, A키와 S키, D키와 S키 같은 조합으로 동시에 누르게 되면 문제가 발생합니다.

만약 오른쪽 정면을 향해 이동하기 위해 W키와 D키를 동시에 눌렀다면, 정면 방향 벡터인 (0,1)과 오른쪽 방향 벡터인 (1,0)이 합쳐져서 (1,1)이 됩니다.

벡터 (1,1)의 길이는 밑변과 높이가 각각 1인 삼각형의 빗변의 길이로 나타내집니다.

이 길이는 루트 2로 약 1.414가 됩니다.

그래서 입력된 대각선 이동 벡터에 곧바로 이동 속도를 곱해서 캐릭터를 이동시키면 앞, 뒤, 왼쪽, 오른쪽 각 단일 방향으로 움직일 때보다 약 1.4배의 속도로 더 빠르게 움직일 수 있게 되는 겁니다.

이런 문제를 막기 위해서 이동 벡터의 길이가 1보다 길어지면 정규화를 통해서 이동 벡터의 길이를 1로 맞춰주는 방식을 자주 사용하게 됩니다.

이런 캐릭터의 이동 문제 외에도 속력과 방향을 정의되는 벡터에서 순수하게 방향만을 남겨서 사용하고자 하는 경우에는 이러한 벡터의 정규화 개념을 사용하게 됩니다.

좌표의 정규화

두 번째로 다뤄볼 정규화는 좌표의 정규화입니다.

게임을 개발하면서 우리는 여러가지 종류의 공간을 다루게 됩니다.

대표적으로는 게임을 플레이하면서 돌아다니게 되는 월드 공간과 UI가 그려지는 UI 공간이 있습니다.

이 두 공간의 좌표를 오가는 기능으로는 월드 공간에 존재하는 오브젝트를 UI 공간에서 나타내기 위한 기능으로 지도 기능이 있습니다.

플레이어나 오브젝트들이 움직이는 월드 공간의 크기는 기획에 따라 달라지기 마련이고 UI 공간은 디스플레이의 해상도에 따라 달라집니다.

간단하게 예를 들어서 3800x3800의 넓이를 가지는 월드 공간에서 움직이는 오브젝트를 UI 공간에서 800x800의 지도 UI에서 표시하고 싶다고 가정해봅시다.

오브젝트가 (2000, 1500)의 위치에 있을 때 지도 UI에 자신의 좌표를 그대로 보내버리면 800x800인 지도의 한참 바깥에 위치를 표시하게 됩니다.

그렇기 때문에 월드 공간에 존재하는 오브젝트의 위치는 좌표를 정규화시켜서 지도에 전달해야 합니다.

오브젝트 좌표의 X값과 Y값을 각각 월드 공간에 속하는 맵의 가로 폭과 세로 폭으로 나눠줍니다.

그러면 오브젝트의 좌표는 (0.52631, 0.39473)으로 변환되면서 좌표의 모든 값이 0에서 1사이 값의 형태를 가지게 됩니다.

이렇게 정규화된 좌표 값을 지도 UI에 넘겨주면 지도 UI에서는 이 값에 지도의 너비와 높이를 곱해줍니다.

그러면 오브젝트의 지도 위에서 좌표 값이 (421.05, 315.78)이 되면서 월드 상의 위치와 지도 상의 위치가 거의 흡사하게 표시되는 것을 볼 수 있습니다.

이런 식으로 서로 다른 공간에 있는 오브젝트의 위치를 표현할 때 좌표 값을 정규화해서 넘겨주는 것으로 동일한 위치에 표시할 수 있게 되는 것입니다.

이러한 좌표의 정규화 개념은 월드 공간과 UI 공간을 넘어서, 현실 공간에서 센서같은 장비로 계측된 위치를 월드 공간으로 옮기는 방식으로도 적용할 수 있습니다.

아웃트로

이번 영상에서는 정규화라는 개념과 게임 개발에서 사용되는 아주 간단한 예시만을 알아보았습니다.

이 외에도 정규화라는 개념은 게임 개발의 여러 분야에서 유용하게 사용할 수 있습니다.

이상 베르의 게임 개발 유튜브였습니다. 감사합니다.

반응형

'게임 제작 > 게임 수학' 카테고리의 다른 글

[Math] 정규화 (1) - 벡터와 좌표의 정규화  (1) 2021.10.04
[Math] 비트 연산  (0) 2021.06.22
  1. 일등하이 2021.10.05 08:25 신고

    잘보고가요^^ 깔끔한 정리입니다👍

Programming 

스크린 공간과 월드 공간

 

작성 기준 버전 :: 2019.2

 

[본 포스트는 유튜브 영상으로도 시청하실 수 있습니다]

 

이번 섹션에서는 스크린 공간와 월드 공간에 대해서 알아보자.

 

유니티의 좌표계 글에서 게임에서 사용되는 월드 좌표계와 로컬 좌표계에 대해서 알아보았다.

 

스크린 공간(Screen space)

 

유니티 엔진에서는 게임 속에서 사용되는 월드 좌표계와 로컬 좌표계로 대표되는 월드 공간 외에도 스크린 공간이 있다.

 

스크린 공간은 단어 그대로, 게임 화면 상에서의 좌표 공간을 의미한다. 그리고 일반적인 UI의 위치와 마우스 위치는 이 스크린 공간의 좌표를 기준으로 동작한다.

 

 

 

에디터에서는 게임 뷰 화면이 스크린 공간이고, 만약 전체화면인 게임이라면 모니터 전체가 스크린 공간이 된다.

 

 

우선 스크린 공간의 좌표를 테스트 하기 전에 게임 뷰의 Free Aspect라고 적힌 드롭다운 메뉴를 클릭해서 드롭다운 메뉴를 열어보자. 이것은 게임 뷰에서 보일 게임 화면의 해상도를 설정할 수 있는 곳이다. + 버튼을 누르면 해상도를 설정할 수 있는 대화상자가 표시된다.

 

 

1920 x 1080 해상도로 설정해보자.

 

 

설정하고나면 16:9 비율로 보이게 된다.

 

public class ScreenSpaceTest : MonoBehaviour

    void Update()

    {

        Debug.Log(Input.mousePosition);

    }

}

 

그럼 이제 C# 스크립트를 하나 생성해서, 마우스 좌표를 출력하는 코드를 작성한다.

 

 
그리고 플레이 버튼을 누른 뒤 게임 뷰의 화면 위에서 마우스를 움직여 보면 왼쪽 아래로 갈수록 출력되는 좌표가 (0, 0)에 가까워지고 오른쪽 위로 갈수록 아까 설정한 화면 해상도에 가까운 (1920, 1080)에 가까워지는 것을 알 수 있다. 지금 볼 수 있는 화면 해상도로 표현되는 공간이 바로 스크린 공간이다.

 

처음에 언급했듯이 이것을 통해서 UI의 위치를 컨트롤 하거나 마우스의 위치를 알아낼 수 있다.

 

공간 전환

 

게임의 기능을 구현할 때는 이 스크린 공간의 좌표를 월드 공간의 좌표로 변환하거나 그 반대로 변환하는 방법 역시 자주 사용하게 된다.

 

스크린 공간 -> 월드 공간

 

먼저 스크린 공간의 좌표를 월드 공간의 좌표로 변경하는 법을 알아보자. 스크린 공간의 좌표를 월드 공간으로 전환하는 예시는 주로 마우스를 클릭한 위치로 캐릭터를 이동시킨다던가 하는 종류의 처리를 하는데 사용된다.

 

스크린 공간의 좌표를 월드 공간의 좌표로 전환하는 방법을 배우기 전에 씬을 세팅해보자.

 

 

씬에 평면 하나를 만들고 그 평면을 내려다 보도록 카메라를 배치한다.

 

 

그리고 하이어라키 뷰에 우클릭하고 스피어 게임 오브젝트를 하나 만든다.

 

 

그 다음 생성된 스피어 게임 오브젝트를 프로젝트 뷰에 드래그해서 프리팹으로 만든다. 그 다음엔 씬에 배치되어 있는 스피어 게임 오브젝트는 삭제한다.

 

public class ScreenToWorld : MonoBehaviour

{

    public GameObject prefab;

    void Update()

    {

        if(Input.GetMouseButtonDown(0))

        {

            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;

            if(Physics.Raycast(ray, out hit))

            {

                Instantiate(prefab, hit.point, Quaternion.identity);

            }

        }

    }

}

 

그리고 위와 같은 코드를 작성한다. 위 코드는 다음과 같은 과정으로 처리된다.

 

1. 카메라에 내장된 함수인 ScreenPointToRay() 함수로 클릭한 순간의 마우스 위치를 화면 공간의 좌표에서 카메라에서 쏘아지는 광선으로 변환

2. Physics.Raycast() 함수로 광선이 부딪힌 지점을 검출

3. 광선이 부딪힌 위치에 Instantiate() 함수로 게임 오브젝트를 생성

 

 

코드를 모두 작성하면 저장하고 에디터로 돌아가서 게임 오브젝트를 하나 배치한다. 그리고 그 게임 오브젝트에 작성한 ScreenToWorld 컴포넌트를 부착합니다. Prefab 프로퍼티에는 아까 만든 Sphere 프리팹을 할당한다.

 

 

 

플레이를 하고 게임 뷰에서 평면을 클릭해보면 마우스의 위치 좌표가 월드 공간의 좌표로 바뀌면서 클릭한 위치에 게임 오브젝트가 생성되는 것을 볼 수 있다.

 

월드 공간 -> 스크린 공간

 

스크린 공간에서 월드 공간으로 변환하는 것과 반대로 월드 공간에서 스크린 공간으로 변환하는 것 역시 게임에서 자주 사용된다. 대표적인 예로는 캐릭터의 머리 위에 HP UI를 띄우는 것이다.

 

유니티의 UI는 일반적으로 스크린 공간에 그려지는데, 월드 공간에서 움직이는 캐릭터를 따라가려면, 월드 공간의 캐릭터 좌표를 스크린 공간으로 전환해서 HP UI에게 알려줘야 한다.

 

테스트 하기 위한 씬을 세팅해보자.

 

 

하이어라키 뷰에 우클릭해서 스피어 하나와 [UI > Image]를 선택해서 이미지 UI를 하나 만들자.

 

 

그리고 이 이미지의 Width와 Height를 50으로 변경하고 색깔도 빨간색으로 변경한다.

 

public class WorldToScreen : MonoBehaviour

{

    public GameObject worldObject;

    void Update()

    {

        transform.position = Camera.main.WorldToScreenPoint(worldObject.transform.position);

    }

}

 

그 다음 WorldToScreen C# 스크립트 파일을 생성하고 코드를 작성한다.

 

 

코드를 모두 작성하면 에디터로 돌아가 이미지 게임 오브젝트에 WorldToScreen 컴포넌트를 추가하고 wolrdObject 프로퍼티에 씬에 배치한 게임 오브젝트를 할당한다.

 

 

모든 세팅을 마치고 플레이 버튼을 누른 뒤, 씬 뷰에서 스피어 게임 오브젝트를 선택하고 움직여보면 스크린 공간에 있는 이미지가 월드 공간의 게임 오브젝트를 정상적으로 따라다니는 모습을 볼 수 있다.

반응형

Vector 

좌표와 속도를 다루기 위한 도구

 

작성 기준 버전 :: 2019.2

 

[유튜브 영상에서도 본 포스트의 내용을 시청하실 수 있습니다.]

 

이번 섹션에서는 유니티 엔진의 벡터에 대해서 알아보자.

 

게임 속에 존재하는 모든 오브젝트들은 어느 위치에 존재하거나, 어딘가를 향해서 이동한다. 아무것도 움직이지 않는 게임은 거의 존재하지 않는다. 그렇기 때문에 이렇게 오브젝트의 위치와 이동 등에 대해서 다루는 도구가 필요한데, 그것을 위한 도구가 바로 벡터이다.

 

벡터에 관한 이번 포스트를 읽기 전에 유니티의 좌표계에 대한 글을 읽어보는 것도 좋다.

 

벡터(Vector)

 

벡터에 대해서 수학이나 물리학에서는 여러 복잡한 정의가 존재하는데 간단하게 요약하자면 특정한 공간에서의 방향과 크기를 표현하는 도구로서 주로 화살표로 표시되는 개념이다.

 

[그림 1]

 

[그림 1]을 예시로 들면 오른쪽으로 뻗어나가는 X축과 위쪽으로 뻗어나가는 Y축, 두 개의 축으로 구성된 2차원 평면 공간이 있고, (2, 2) 위치를 가리키고 있는 V1 벡터와 (-1, -2) 위치를 가리키고 있는 V2 벡터, 두 개의 벡터가 2차원 공간 속에 존재하는 것을 볼 수 있다.

 

즉, 여기서는 2차원 평면이 "특정한 공간"이며, 벡터는 그 공간 안에서 방향과 크기를 표현하는 도구라고 말했듯이, 벡터를 표시한 화살표를 보는 것만으로도 이 벡터가 어느 방향을 가리키고 있고 얼마만큼의 길이를 가지는지 알 수 있다.

 

그리고 이 "특정한 공간"은 몇 차원이든 가능하지만 대부분은 사람이 쉽게 인지할 수 있는 2차원 혹은 3차원으로 정의된다. 유니티에서도 2차원과 3차원의 벡터만 사용한다.

 

벡터의 활용

 

벡터는 방향과 크기를 표현하는 도구라고 이야기했는데, 이것을 이용해 여러 가지 용도로 활용된다.

 

첫 번째 활용법은 방향이다. [그림 1]과 같이 평면에 벡터가 표시하는 좌표까지 화살표를 그리는 것으로 방향을 쉽게 알 수 있다.

 

[그림 2]

 

두 번째 활용법은 속도이다. V1과 V2가 [그림 1]에서는 서로 다른 방향을 가리키고 있는데, [그림 2]와 같이 두 벡터를 같은 방향으로 놓고 보면, V1이 V2보다 긴 것을 확인할 수 있다. 1초 동안 각 벡터의 길이만큼 이동한다고 가정하면, 당연히 같은 시간동안 V1만큼 이동한 것이 V2만큼 이동한 것보다 멀리 이동할 수 있고, 벡터의 방향 자체가 이동 방향의 의미를 가지기 때문에, 이동하고자하는 방향과 속력, 즉, 속도의 개념으로 활용되는 것이다.

 

세 번째 활용법은 좌표이다. [그림 1]에서 X축과 Y축이 교차하는 지점을 공간의 중심이라고 가정했을 때, V1 위치에 어떤 오브젝트가 있다면, 그 오브젝트는 공간의 중심으로부터 (2, 2)의 위치에 있다고 말할 수 있다.

 

벡터의 계산

 

벡터에 대한 여러가지 계산들이 존재하는데 이러한 계산 방법들을 잘 활용하면 게임의 기능을 구현하는데 유용하게 사용할 수 있다.

 

벡터의 덧셈과 뺄셈

 

두 개의 벡터는 서로 더하거나 뺄 수 있다.

 

[그림 3]

 

우선 벡터의 덧셈을 살펴보자. [그림 3]을 보면 (1, 2)의 V1과 (2, 1)의 V2를 더하면 (3, 3)의 V3가 나오는 것을 볼수 있다. V1 지점에서 V2 만큼 이동했다고 여기면 된다.

 

[그림 4]

 

또 다른 관점에서 살펴보면 두 벡터의 합은 서로 다른 방향의 두 힘이 충돌했을 때의 그 합쳐진 힘의 진행 방향으로 해석될 수도 있다.

 

[그림 5]

 

벡터의 뺄셈은 역시 (1, 2)인 V1에서 (2, 1)인 V2를 빼면 (-1, 1)인 V3가 나온다. 다만 여기서 기억할 점은 이렇게 결과 값으로 나온 V3가 V2 지점에서 시작하여 V1 방향으로 이동하는 V3'와 길이와 방향이 모두 같다는 것이다. 이것을 이용하면 V1과 V2 사이의 거리, V2에서 V1으로 가는 방향등을 구할 수 있다.

 

벡터와 스칼라의 곱셈

 

벡터는 좌표로서 공간 상에서 방향과 크기를 의미하지만 스칼라는 크기 만을 가지는 값이다. 벡터가 차원 축을 따라서 (2, 0), (1, 2, 3)과 같은 값을 가지는 것에 비해 1, 10, 24 등의 단일한 값을 의미한다.

 

[그림 6]

 

(1, 1)인 V1에 4인 스칼라 S를 곱하면 V1의 각 원소에 S를 곱하여 (4, 4)인 V2를 얻게 된다. 이렇게 벡터에 스칼라를 곱하는 계산은 주로 벡터의 크기를 원하는대로 늘이거나 줄이는 등의 계산이나, 벡터에 -1을 곱해서 현재 벡터의 반대 방향을 구하는데 주로 사용된다.

 

벡터의 길이

 

[그림 2]에서는 벡터를 나란히 놓는 원시적인 방식으로 벡터의 길이를 비교했다. 이런 방식 외에 계산적인 방식으로도 벡터의 길이를 구할 수 있다.

 

 

벡터의 길이를 구하는 공식은 위와 같다. 벡터의 각 원소를 제곱한 뒤, 모두 더하여 제곱근을 구하면, 벡터의 길이가 나온다.

 

[그림 1]

 

다시 [그림 1]을 보자. V1의 좌표는 (2, 2)이다. 이것을 공식에 대입하면 : 

 

 

위와 같은 값이 나온다. 이것은 사실 피타고라스의 정리에서 유도된 빗변의 길이를 구하는 공식이다. 벡터는 항상 각 축을 대상으로 직각을 이루는 직각 삼각형의 형태이기 때문에 이 공식으로 길이를 구할 수 있는 것이다.

 

벡터의 길이는 즉, 거리로, 벡터의 뺄셈을 이용하면 두 벡터 간의 거리를 구할 수 있다.

 

 

3차원 벡터에 대한 공식은 2차원 공식에 z축 좌표를 추가해서 계산하면 된다.

 

벡터의 정규화(Normalize)

 

벡터의 정규화는 현재 벡터의 방향을 유지한 채로 벡터의 길이를 1로 만드는 것을 의미하며, 이를 단위 벡터라고 부른다.

 

 

단위 벡터를 구하려면 벡터의 각 원소를 벡터의 길이로 나누어 주면 된다. 예를 들어 [그림 1]의 V1을 단위 벡터로 만들려면 V1의 각 원소 (2, 2)를 각각 벡터의 길이인 2.828427로 나누어주면 된다. 그러면 각 원소의 값은 (0.707106..., 0.707106...)이 된다. 이렇게 나온 단위 벡터에 대해서 다시 벡터의 길이를 구해보면 1의 근사값이 나올 것이다.

 

이런 단위벡터는 정확히 벡터의 방향만을 추출하고자 할 때 사용되는데, 주 사용처를 이야기 해보자면, 만약 캐릭터를 마우스의 방향으로 이동시키려고 할 때, (마우스 위치) - (캐릭터 위치)로 캐릭터에서 마우스 위치 방향을 찾아낼 것이다. 그런데 이 벡터를 그대로 사용하면 마우스의 거리가 멀어지면 멀어질 수록 이 이동 벡터의 길이가 길어지기 때문에 마우스와 캐릭터의 거리가 멀면 캐릭터가 빨라지고 가까워지면 캐릭터가 느려지는 문제가 발생할 것이다.

 

(마우스 위치) - (캐릭터 위치)로 찾아낸 방향을 정규화해서 단위 벡터로 만든 다음에 캐릭터의 이동 속도만 곱해주면 캐릭터가 일정한 속도로 이동한다.

 

벡터의 내적(Dot Product)

 

점곱(dot product), 내적(inner product)이라고 부르는 계산으로 계산 결과 값으로 벡터가 아닌 단일 값, 즉 스칼라 값을 내는 계산이다. 때문에 스칼라 곱이라고도 부르는데, 벡터와 스칼라의 곱셈과 헷갈려서는 안된다.

 

 

계산 공식은 위와 같다. 벡터의 각 원소끼리 곱한 뒤, 모두 더하는 것으로 벡터의 내적을 구할 수 있다.

 

이렇게 나온 계산 결과의 의미는 내적의 값이 0이면 두 벡터의 각이 90도이고, 0보다 크면 두 벡터 사이의 각도가 90도보다 작고, 0보다 작으면 각도가 90도보다 크다는 의미이다. 여기에 삼각함수를 이용하면 두 벡터 사이의 각도를 구할 수 있다.

 

벡터의 외적(Cross Product)

 

가위곱(cross product), 외적(outer product)이라고 부르는 계산으로 두 벡터와 모두 직교하는, 즉 두 벡터와의 각이 모두 90도를 이루는 벡터를 결과값으로 내며, 3차원 공간에서만 성립하는 계산이다.

 

 

외적의 계산 공식은 위와 같다. 

 

[그림 7]

 

 V1과 V2에 대해서 직교하는 벡터는 V3, V4 둘 다 될 수 있는데, 이 결과는 왼손 좌표계를 사용하느냐, 오른손 좌표계를 사용하느냐에 따라 결과가 달라진다. 왼손 좌표계에서 V2 X V1의 결과값은 V3로 나오고 오른손 좌표계에서는 V4로 나온다. 이렇게 나온 벡터를 법선 벡터(Normal Vector)라고 부른다.

 

[그림 8]

 

그래서 벡터의 외적은 [그림 8]과 같이 한 면이 바라보는 방향을 구하는 용도로 주로 사용된다.

 

 

 

 

유니티 엔진의 벡터

 

그러면 이제 유니티 엔진에서의 벡터에 대해서 알아보자.

 

Vector2 vector2 = new Vector2();

 

vector2.x = 1f;

vector2.y = 1f;

 

Vector3 vector3 = new Vector3();

 

vector3.x = 1f;

vector3.y = 1f;

vector3.z = 1f;

 

위의 코드 예시는 유니티에서 사용되는 Vector2 구조체와 Vector3 구조체이다. Vector2 구조체는 2차원 평면 공간에 속하는 벡터로 X축의 좌표를 표시하기 위한 x 변수와, Y축의 값을 표시하기 위한 y 변수를 가진다. Vector3 클래스는 여기에 더해 Z축을 표시하는 z 변수까지 가진다.

 

Vector2는 대부분 UI 같은 2D 공간이나 2D 게임을 제작할 때 사용되고, Vector3는 일반적인 3D 공간에서 사용된다.

 

자, 그렇다면 우리는 벡터와 관련된 기능을 전부 직접 구현해서 사용해야 할까? 아니다. 유니티에서는 이러한 벡터와 관련된 기능들을 모두 제공한다.

 

벡터의 덧셈과 뺄셈

 

Vector3 v1 = new Vector3(1f, 2f, 0f);

Vector3 v2 = new Vector3(2f, 1f, 0f);

// 벡터의 덧셈

Debug.Log("v1 + v2 = " + (v1 + v2));

// 벡터의 뺄셈

Debug.Log("v1 - v2 = " + (v1 - v2));

 

 

벡터의 덧셈과 뺄셈은 간단하게 일반 덧셈과 뺄셈을 하듯이 연산자를 사용하면 바로 구할 수 있다.

 

벡터와 스칼라의 곱셈

 

// 벡터와 스칼라의 곱셈

Vector3 v1 = new Vector3(1f, 1f, 0f);

int scalar = 4;

Debug.Log("v1 * scalar = " + (v1 * scalar));

 

 

벡터와 스칼라, 단일 정수 혹은 단일 실수와의 곱셈 역시 일반 계산과 같이 간단하다.

 

벡터의 길이

 

Vector3 v1 = new Vector3(2f, 2f, 0f);

// 벡터의 길이

Debug.Log("length of v1 = " + (v1.magnitude));

 

 

벡터의 길이는 Vector3 구조체에 포함되어 있는 magnitude 프로퍼티를 통해서 가져올 수 있다. 이것은 Vector2에서도 똑같다.

 

벡터의 정규화

 

Vector3 v1 = new Vector3(2f, 2f, 0f);

// 정규화된 단위 벡터

Vector3 normalizedVector = v1.normalized;

Debug.Log("||v1|| = " + normalizedVector);

// 단위 벡터의 길이 확인

Debug.Log("length of ||v1|| = " + normalizedVector.magnitude);

 

 

Vector3 구조체의 normalized 프로퍼티를 이용하면 정규화된 단위 벡터를 가져올 수 있다. magnitude 프로퍼티를 다시 사용해보면 단위 벡터의 길이가 1임을 확인할 수 있다.

 

벡터의 내적

 

[그림 9]

 

아래 코드 예시에 정의된 각 벡터는 [그림 9]와 같다.

 

Vector3 v1 = new Vector3(2f, 0f, 0f);

Vector3 v45 = new Vector3(1f, 1f, 0f);

Vector3 v90 = new Vector3(0f, 2f, 0f);

Vector3 v135 = new Vector3(-1f, 1f, 0f);

 

// 벡터의 내적

Debug.Log("v1 . v45 = " + Vector3.Dot(v1, v45));

Debug.Log("v1 . v90 = " + Vector3.Dot(v1, v90));

Debug.Log("v1 . v135 = " + Vector3.Dot(v1, v135));

 

// 두 벡터 사이의 각도

Debug.Log("v1 . v45 = " + Vector3.Angle(v1, v45));

Debug.Log("v1 . v90 = " + Vector3.Angle(v1, v90));

Debug.Log("v1 . v135 = " + Vector3.Angle(v1, v135));

 

 

벡터의 내적을 구하려면 Vector3 클래스의 정적 함수인 Dot 함수를 사용하면 된다. 벡터의 내적에 대해서 설명했듯이, v1과 v45 사이의 각도는 90도 보다 작기 때문에 0보다 큰 값이 나왔고, v1과 v90 사이 각은 정확히 90도이기 때문에 0, v1과 v135 사이 각은 90도보다 커서 0보다 작은 값이 나온 것을 확인할 수 있다.

 

덤으로 Vector3의 또 다른 정적 함수인 Angle 함수를 사용하면 두 벡터 사이의 각을 얻을 수 있다.

 

벡터의 외적

 

Vector3 v1 = new Vector3(2f, 0f, 0f);

Vector3 v2 = new Vector3(0f, 0f, 2f);

 

// 벡터의 외적

Debug.Log("v1 X v2 = " + Vector3.Cross(v1, v2));

 

 

벡터의 외적은 Vector3 클래스의 정적 함수인 Cross 함수를 통해서 구할수 있다.

 

이번 섹션에서는 벡터와 그 계산법을 알아보고 유니티 엔진에서는 어떻게 사용되는지 알아보았습니다.

반응형
  1. 료용 2020.02.03 02:14 신고

    프로그래머가 기본수학을 알아야하는이유... 유투브에 수포자를위한 프로그래머 수학보고 아 그렇구나 이러기만하고 적용을 못시키겠더라고요

    • wergia 2020.02.03 03:52 신고

      사실 저도 수포자입니다 ㅋㅋ 다만 벡터는 눈에 보이기 쉬운 개념이라 금방 배울 수 있더라구요. 벡터로 하는 작업이 조금 어려울 때는 종이에 화살표 그려보면서 하면 잘 되더라구요.

Tutorial (5)

 

유니티의 좌표계

 

작성 기준 버전 :: 2018.3.1f1

 

[본 튜토리얼의 내용은 유튜브 영상으로도 시청할 수 있습니다]

 

이번 섹션에서는 유니티의 좌표계에 대해서 알아보자.

 

 

좌표계란?

 

좌표계란 공간 내에서 특정한 위치를 나타내기 위한 방식이다.

 

어떤 공간에서 위치를 찾고자 하는 것인지 기준을 잡기 위해서 축이라는 것을 사용하는데, X라는 하나의 축을 사용하면 수직선 상에서의 점의 위치를 찾아낼 수 있게 된다.

 

 

X축과 Y축, 2개의 축을 이용하면 평면 상의 중심으로부터의 점의 위치를 알 수 있다.

 

 

X축과 Y축 그리고 Z축까지 3개의 축을 사용하면 3차원 공간 상의 점의 위치를 알아낼 수 있게 된다.

 

 유니티 엔진에서는 씬이라는 공간 안에서 오브젝트의 위치를 표현하기 위해서 좌표계를 이용한다.

 

수직선을 이용한 1차원 상의 공간을 사용하는 게임은 별로 없고 대부분은 2D 좌표계나 3D 좌표계를 사용한다.

 

 


2D 좌표계를 사용하는 게임으로는 슈퍼 마리오 브라더스를 예로 들 수 있고, 3D 좌표계를 사용하는 게임으로는 하프라이프를 예로 들 수 있다. 2D 좌표계를 사용하는 게임은 움직임이 상하좌우 또는 전후좌우로 움직임이 제한되지만 3D 좌표계를 사용하는 게임은 전후좌우 뿐만 아니라 상하의 움직임까지 가능하다.

 

 

왼손 좌표계와 오른손 좌표계

 

좌표의 축을 정하는 방법은 여러 가지가 있는데 그 중 대표적인게 바로 왼손 좌표계와 오른손 좌표계이다.

 

 

우선 오른손 좌표계는 엄지 손가락이 X축, 검지 손가락이 Y축, 중지 손가락이 Z축이라고 가정하고 엄지를 종이 위에 수직선을 그었을 때 양수의 방향, 즉 오른쪽을 향하게 하고 검지를 X축과 직교하는 위 방향으로 향하게 했을 때, 중지가 나를 바라보는 방향이 되게 XYZ축을 정의하는 방식이다. 일반적인 수학에서는 이 오른손 좌표계를 표준으로 사용한다.

 

 

그 다음 왼손 좌표계는 엄지와 검지의 방향을 오른손 좌표계와 같이 맞췄을 때 중지는 내가 바라보는 방향을 가리키게 되도록 XYZ축을 정의한다. 유니티에서는 이 왼손 좌표계를 기준으로 사용한다.

 

한마디로 왼손 좌표계와 오른손 좌표계의 차이는 Z축이 가리키는 방향이 달라진다는 것이다. 오른손 좌표계에서는 화면에서 바라보는 사람에게로 다가오는 방식으로 Z축이 가리키게 되지만, 왼손 좌표계는 화면을 바라보는 사람에게서 화면 방향으로 Z축이 가리키게 된다.

 

 

 

 

 

Y-Up과 Z-Up

 

좌표계를 정의할 때, X축은 기본적으로 첫 번째 수평 방향의 수직선을 기준으로 하기 때문에 대부분 같은 방향으로 고정되어 있다. 여기서 발생하는 문제는 두 번째 축인 Y축의 방향을 어떻게 정의하느냐이다.

 

 

여기에는 두 가지 관점이 있는데 위에서 내려다보는 시점으로 Y축을 앞으로 나가는 방향으로 정의하는 방식이 하나로, 이렇게하면 새로 추가되는 세 번째 축인 Z축이 높이 축이 되는 Z-Up 방식이 된다. 언리얼 엔진과 3D 모델링 툴인 3ds Max가 이 방식을 채택한다.

 

 

다른 방식으로는 옆에서 바라보는 시점에서 Y축을 위로 향하는 방향으로 정의하는 것이다. Y축이 높이 축이 되기 때문에 Y-Up이라고 하고 유니티 엔진은 이 방식을 채택한다.

 

 

월드 좌표와 로컬 좌표

 

바로 전 파트까지 좌표계란 무엇인지와 유니티 엔진에서는 어떤 방식의 좌표계를 채택했는지를 설명했다. 이번 파트에서 이야기할 내용은 월드 좌표와 로컬 좌표에 대한 이야기이다.

 

월드 좌표란 세상을 중심으로 어느 위치에 있느냐를 의미하는 것이고, 로컬 좌표는 나 혹은 어느 한 오브젝트를 중심으로 어느 위치에 있느냐 하는 것이다.

 

사실 실제 세상에서 세상을 중심으로 어떠한 객체가 어느 위치에 있느냐 하는 것은 그 세상의 중심이 어디인지는 사람마다 생각이 다르고 절대적이라고 할 수 있는 중심이 없기 때문에 세상의 중심을 기준으로 한 위치라는 것은 구할 수 없겠지만, 게임이나 유니티 엔진에서는 가능하다.

 

 

바로 씬 안의 의 위치가 바로 게임 안에서의 세상의 중심이 된다.

 

 

 

 

월드 좌표를 대상으로 봤을 때, 선택된 큐브는 {-6, 0, -4}의 위치에 존재한다.

 

그렇다면 로컬 좌표란 무엇인가? 왜 월드의 중심이 아닌 어느 한 오브젝트를 중심으로 위치를 측정해야하는 걸까?

 

 

 

위의 이미지를 보자. 스피어 오브젝트 하나가 월드 좌표를 설명할 때 사용했던 큐브 오브젝트보다 XZ좌표가 각각 1씩 월드의 중심에 가깝게 존재하고 있다. 큐브 오브젝트의 위치가 {-6, 0, -4}였으니, 스피어 오브젝트는 {-5, 0, -3}의 위치에 있다. 만약에 추가된 이 스피어 오브젝트를 큐브 스피어를 중심으로 공전하게 만들고 싶다면 어떻게 해야할까?

 

 

만약 월드 좌표만으로 처리하려고 한다면 위의 이미지와 같이 좌표가 복잡하게 바뀌는 것을 알 수 있다.

 

 

하지만 스피어 오브젝트를 큐브 오브젝트의 자식 오브젝트로 만들면 포지션이 월드의 중심 좌표를 기준으로한 월드 좌표인 {-5, 0, -3}이 아니라 큐브 오브젝트를 중심으로한 로컬 좌표 로 표시되는 것을 확인할 수 있다.

 

 

이렇게 하고 나면 간단하게 큐브 오브젝트를 회전시키는 것만으로도 궤도를 따라서 스피어 오브젝트가 간단하게 공전하는 것을 볼 수 있다. 물론 큐브도 함께 자전한다는 문제가 있기는 하지만 이런 문제는 간단하게 해결하고 스피어 오브젝트만 궤도를 따라서 공전하게도 만들 수 있다.

 

 

반응형

+ Recent posts