오늘은 제가 유니티를 공부하던 중 배웠던 것을 포스팅 할 것 입니다.


바로 '유니티2D 해상도' 입니다.


저 같은 경우는 유니티로 2D 게임을 만들때 가장 어려웠던 부분 중 하나가 해상도입니다. 설정한 해상도 (예를 들어 1280x720)에 2D Sprite를 유니티로 배치하면 정한 해상도와는 크기가 다르게 보여집니다.


오늘은 이 것을 해결 해 볼 것 입니다.


우선 Camera 컴포넌트를 보겠습니다.




우리가 봐야할 곳은 1번 Projection과 2번 Size 입니다.


1번 Projection

Projection 항목에는 두 가지가 있습니다.


- Perspective보통 3D에서 쓰입니다. 현실 세계속 카메라와 비슷한 카메라라고 생각하시면 됩니다. 

원근감이 적용돼 카메라와의 거리가 멀수록 오브젝트의 크기가 작게 보입니다.

- Orthographic : 보통 2D에서 쓰입니다. Perspective와는 다르게 원근감이 없는 카메라 입니다. 

원근감이 적용되지 않아 카메라와의 거리가 멀든 가깝든 오브젝트는 항상 똑같은 크기로 보여집니다. 


일단 우리는 2D 게임을 제작하니 Orthographic 으로 셋팅 하겠습니다.


2번 Size

Size 항목은 Projection 항목이 Orthographic으로 셋팅 되어야만 활성화 되는 항목입니다. 

Size는 카메라 확대에 관한 것이라고 보면 되겠습니다. 


Size의 숫치가


- 작으면 : 화면이 확대됩니다..

- 커지면 : 화면이 축소됩니다.


제가 위에서 Orthographic 뷰는 원근감이 없어 카메라와의 거리 상관없이 크기는 항상 똑같다고 했죠?

Size는 Orthographic에서의 카메라 줌이라고 생각하시면 됩니다.


유니티 카메라를 정한 해상도와 똑같이 보이게 할려면 Size 항목을 조절하시면 됩니다.


일단 게임을 만드실 때 정해둔 해상도가 있으실 것 입니다. 저 같은 경우는 1280x720 으로 하겠습니다.


Size 항목에 자신이 정해둔 해상도 세로 사이즈의 반(저 같은 경우 '360') 을 넣어 주시면 여러분이 생각하신 해상도와 똑같이 카메라가 보여지게 됩니다. 


아직 끝난 것이 아닙니다. Unity의 띄울 Sprite도 옵션을 셋팅 해줘야합니다.


Sprite를 눌러보시면 Inspector가 다음과 같이 보입니다.


우리가 봐야할 곳은 Pixels Per Unit입니다.


Pixels Per Unit

Sprite의 픽셀과의 비율입니다.  기본적으로 100으로 설정되어 있을 것 입니다.

예를 들어 64x64 사이즈의 Sprite가 있으면 유니티에서는 Scale XY를 100으로 맞춰주어야만 실제 Sprite 사이즈로 보여지는 것입니다.


Scale을 바꾸지 않고 Pixels Per Unit을 1로 바꾸면 실제 Sprite 픽셀 사이즈와 똑같이 보여지게 됩니다.


아래는 결과 화면입니다.



Camera 컴포넌트의 Size 보시면 720의 반인 360 으로 셋팅 되어있는 것을 볼 수 있습니다.

그리고 Pixels Per Unit도 1로 설정되어 있습니다.


그럼 카메라에 보여지는 것이 실제 해상도와 같은지 포토샵에서 같은 해상도로 보여드리겠습니다.



둘이 캡처의 크기가 달라서 조금 달라 보일 수 도 있지만 

둘의 크기는 정확히 똑같습니다. 유니티 카메라에서 실제 해상도와 똑같이 보이는 것입니다.

안녕하세요. 오늘은 C# Delegate라는 것을 알아볼 것 입니다.


C# Delegate는 한 마디로 요약하자면 메소드(함수)를 변수로 저장하는 것 입니다.

C/C++을 해보셨던 분들에게는 함수 포인터와 비슷하다고 생각하시면 될 것 입니다.


일단 아래는 C# 델리게이트를 선언하는 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 델리게이트는 아래와 같이 선언합니다.
public delegate void NoneReturn_NoneParam_Delegate();
static void NoneReturn_NoneParam_Func()
{
    Console.WriteLine("NoneReturn_NoneParam_Func");
}
 
public delegate int IntReturn_NoneParam_Delegate();
static int IntReturn_NoneParam_Func()
{
    Console.WriteLine("IntReturn_NoneParam_Func");
    return 10;
}
 
public delegate float FloatReturn_NoneParam_Delegate();
static float FloatReturn_NoneParam_Func()
{
    Console.WriteLine("FloatReturn_NoneParam_Func");
    return 10.0f;
}
 
public delegate void NoneReturn_OneIntParam_Delegate(int i);
static void NoneReturn_OneIntParam_Func(int i)
{
    Console.WriteLine("NoneReturn_OneIntParam_Func");
}
 
public delegate int IntReturn_OnefloatParam_Delegate(float i);
static int IntReturn_OnefloatParam_Func(float f)
{
    Console.WriteLine("IntReturn_OnefloatParam_Func");
    return 0;
}
 
public delegate float FloatReturn_OneIntParam_Delegate(int i);
static float FloatReturn_OneIntParam_Func(int i)
{
    return 10f;
}
 
cs


보시면 

"접근 한정자 + delegate + 함수 타입 " 방식으로 선언합니다.


예를 들어 

2 번째 줄에 선언된 delegate의 타입 이름은 NoneReturn_NoneParam_Delegate 이며

저 타입에 들어갈 수 있는 함수의 형식은 반환 값과 매개변수가 없는 함수만 저 형식에

저장 될 수 있는 것이죠.


그럼 이제 저 delegate를 사용하는 코드를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 선언한 델리게이트를 사용한 변수입니다.
NoneReturn_NoneParam_Delegate noneReturn_NoneParam 
    = NoneReturn_NoneParam_Func;
 
IntReturn_NoneParam_Delegate intReturn_NoneParam 
    = IntReturn_NoneParam_Func;
 
FloatReturn_NoneParam_Delegate floatReturn_NoneParam 
    = FloatReturn_NoneParam_Func;
 
NoneReturn_OneIntParam_Delegate noneReturn_OneIntParam 
    = NoneReturn_OneIntParam_Func;
 
IntReturn_OnefloatParam_Delegate intReturn_OnefloatParam 
    = IntReturn_OnefloatParam_Func;
 
FloatReturn_OneIntParam_Delegate floatReturn_OneIntParam 
    = FloatReturn_OneIntParam_Func;
 
cs


선언한 델리게이트를 사용하는 법은 일반 변수랑 같습니다.


예를들어 4 번째 줄을 보시면


NoneReturn_NoneParam_Delegate noneReturn_NoneParam 

                = NoneReturn_NoneParam_Func;


"델리게이트 타입 이름 + 변수 명 + 대입할 함수 이름" 입니다.


그럼 저렇게 변수에 저장 된 함수를 호출해볼까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 호출 방법 1
if (noneReturn_NoneParam != null)
    noneReturn_NoneParam();
 
if (intReturn_NoneParam != null)
    intReturn_NoneParam();
 
if (floatReturn_NoneParam != null)
    floatReturn_NoneParam();
 
if (noneReturn_OneIntParam != null)
    noneReturn_OneIntParam(5);
 
if (intReturn_OnefloatParam != null)
    intReturn_OnefloatParam(5f);
 
if (floatReturn_OneIntParam != null)
    floatReturn_OneIntParam(5);
 
 
 
// 호출 방법 2
// '?' 은 만약 변수가 NULL이 아니라면 뒤 함수를 호출한다는 뜻입니다.
noneReturn_NoneParam?.Invoke();
intReturn_NoneParam?.Invoke();
floatReturn_NoneParam?.Invoke();
 
noneReturn_OneIntParam?.Invoke(5);
intReturn_OnefloatParam?.Invoke(5f);
floatReturn_OneIntParam?.Invoke(5);
 
cs


호출 방법 1


함수를 저장해놓은 변수가 NULL 인지 체크하고 일반 함수를 호출하는 것과 같은 방식으로 변수에 저장되어있는 함수를 호출합니다.


항상 NULL 체크를 하는 것이 좋습니다. 만약 체크를 안하고 NULL인 델리게이트 변수의 저장된 함수를 호출하면 예외가 터집니다.


호출 방법2


이 방식은 델리게이트 타입의 변수에는 자동으로 Invoke라는 함수가 생기게 됩니다. 

Invoke를 호출하면 해당 함수를 호출하는 것이지요.


근데


변수 이름 옆에 붙은 '?' 의 의미는 해당 변수가 NULL 이 아닐 경우(변수가 초기화 된 경우) Invoke를 호출하라는 의미입니다. 간단하죠? 만약 변수가 NULL 이면 Invoke가 호출되지 않습니다. 저 '?' 같은 경우는 델리게이트 뿐만 아니라 다른 클래스에도 사용 가능 하니 한번 사용해보세요.



안녕하세요. 오늘은 Unity에서 Resource.Load로 Sprite 로딩 시 NULL이 리턴되는 문제를 다뤄볼려고 합니다.

경로가 틀려서 NULL이 리턴되는 경우는 너무 당연 한 것이기 때문에 다루지 않겠습니다.


다음 코드에 경우 리턴된 Sprite는 NULL 입니다. (Path가 올바른 경로라고 가정)


1
2
Sprite sprite = Resources.Load("Path"as Sprite;
 
cs


분명 보기에는 이상한 것이 없습니다. 


저 코드에 문제는 바로 "Resources.Load" 함수에서 Object Type으로 반환한 Sprite 객체를

"as Sprite"로 형 변환을 했기 때문입니다. 왜 그런지는 저도 잘 모르겠습니다.


그러므로 아래와 같이 수정하면 올바르게 Sprite를 반환 할 것 입니다.


1
2
Sprite sprite = Resources.Load<Sprite>("Path");
 
cs


'programing > Unity' 카테고리의 다른 글

Unity2D Sprite 실제 픽셀 사이즈와 맞추기  (2) 2019.11.03

오늘은 C#의 예외처리에 대해 알아보겠습니다. 


예외를 적절하게 사용하면 프로그램 실행 중간에 에러가 터져도 프로그램이 꺼지지 않고 넘길 수 있습니다. 하지만 굳이 예외가 필요없는 곳인데도 불구하고 과도하게 사용 할 경우 성능 저하를 불러 올 수 있습니다.


다음과 같은 형식으로 예외 처리를 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 생성자에 Message를 넣을 수 있습니다.
        throw new Exception("예외를 던집니다.");
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        // 예외 클래스의 Message 변수로 예외처리 
        //변수의 생성자에 넣은 메시지를 얻어 올 수 있습니다.
        Console.WriteLine(ex.Message);
    }
}
 
cs


출력 결과.


예외를 던집니다.

계속하려면 아무 키나 누르십시오 . . .


만약 throw 뒤에 다른 코드가 있다면 어떻게 될까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        throw new Exception("예외를 던집니다.");
 
        Console.WriteLine("출력");
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
 
cs


출력 결과.


예외를 던집니다.

계속하려면 아무 키나 누르십시오 . . .


출력 결과를 보면 알 수 있듯이 try문 내에 예외가 던져진 곳 다음 코드는 실행이 안되는 것을 알 수 있습니다.


다음과 같은 상황에서는 어느 catch문이 실행될까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void TestFunc()
    try
    {
        throw new Exception("예외");
    }
    catch (Exception ex)
    {
        Console.WriteLine("TestFunc " + ex.Message);
    }
 
}
 
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 예외처리 구문이 있는 함수를 호출합니다.
        TestFunc();
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine("MainFunc" + ex.Message);
    }
}
 
cs


출력 결과.


TestFunc 예외

계속하려면 아무 키나 누르십시오 . . .


TestFunc 함수 내에 예외가 출력되었습니다. 결과를 보면 가장 가까운 catch (가까운 스택) 문에 점프하는 걸 볼 수 있습니다. 


근데 제가 처음에 너무 과도하게 사용하면 성능 저하가 있을 수도 있다고 적어놨습니다. 왜 그럴까요?


아래 코드를 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void TestFunc01()
{
    TestFunc02();
 
}
 
static void TestFunc02()
{
    TestFunc03();
 
}
 
static void TestFunc03()
{
    TestFunc04();
}
 
static void TestFunc04()
{
    throw new Exception("예외");
}
 
 
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 예외처리 구문이 있는 함수를 호출합니다.
        TestFunc01();
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
 
cs


위 코드의 콜 스택은 어떻게 될까요.


예외 발생 전

TestFunc01 -> TestFunc02 -> TestFunc03 -> TestFunc04 -> 예외 발생!


예외 발생 후

Test04 -> Test03 -> Test02 -> Test01 -> catch 문!.


예외가 발생하면 catch문이 있는 곳 까지 콜 스택이 되돌려집니다.

물론 위와 같은 코드는 한 번만 실행되기 때문에 성능 저하가 없을 수도 있습니다.


하지만 


서버 같이 항상 실행되는 경우 저런 예외처리 구문이 많으면 성능 저하가 생길 수 도 있습니다.


단 많은 .Net Framework에서는 예외처리를 사용하여 에러를 확인해 할 때가 있습니다.

예를 들면 C# System.Net에 서버 API들은 대부분의 오류를 예외처리로 던져줍니다. 


그렇기 때문에 신중하게 사용하는 것이 중요합니다.



이번에는 C#에서 열을 부분적으로 참조하는 법을 알아봅시다.


ArraySegment 를 이용하면 편리합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class TestClass
{
    /* 배열 참조하기 위한 정보가 들어있는 구조체이다.
     * 구조체 내부에는 
     * Array  : 버퍼를 참조하는 배열 변수.(제네릭으로 전달한 타입의 배열)
     * Offset : 그 배열의 참조 시작 위치를 알리는 int 형 변수
     * Count  : 몇개의 index를 참조하는지를 나타내는 int 형 변수.
    */
    ArraySegment<byte> _arraySegment;
    
    
    public void SetArray (byte[] inBuffer, int inOffset, int inCount)
    {
        _arraySegment = new ArraySegment<byte>(inBuffer, inOffset, inCount);
    }
 
    public void SetValue()
    {
        byte value = 0;
 
        // 참조한 배열의 시작 위치부터 참조의 끝까지 값을 세팅합니다.
        for (int i = _arraySegment.Offset; i < _arraySegment.Offset + _arraySegment.Count; ++i, ++value)
        {
            _arraySegment.Array[i] = value;
        }
    }
 
    public void ParintVluae()
    {
        // 참조한 배열의 시작 위치부터 참조의 끝까지 값을 출력합니다.
        for (int i = _arraySegment.Offset; i < _arraySegment.Offset + _arraySegment.Count; ++i)
        {
            Console.WriteLine(_arraySegment.Array[i]);
        }
    }
}
 
class Program
{
  
    static void Main(string[] args)
    {
        TestClass testClass = new TestClass();
        
        // 원본 버퍼를 생성한다.
        byte[] array = new byte[100];
 
        // 이렇게 할 경우 TestClass. _arraySegment는 array[50] 부터 10개만큼 참조한다.
        testClass.SetArray(array, 5010);
 
        testClass.SetValue();
 
        testClass.ParintVluae();
    }
 
cs


방법은 굉장히 간단합니다.


참조할려는 배열과 참조 시작 위치, 참조할 index의 갯수를 ArraySegment에 넘기면 됩니다.


사용할 때는 ArraySegment에 Offset과 Count를 활용해야합니다. 

*(TestClass. SetValue와 TestClass. PrintValue 참조)*


Offset : 참조 시작 위치.

Count : 참조 시작 위치 부터 몇개의 인덱스를 참조 할 것인가. 


*주의 할 점*

Offset과 Count를 잘 확인해서 배열의 참조한 부분보다 더 많은 부분을 접근하지 않도록 해야합니다.. 


만약 위 사항을 주의하지않고 다른 부분의 접근해버리면 다음과 같은 문제가 생깁니다.


1. 만약 배열의 크기가 넉넉하지 않고 잘못 접근한 부분이 배열의 할당된 것보다 크면 메모리 예외가 던져집니다. (따로 예외 처리를 안했을 경우 프로그램이 터져 버릴 수도 있습니다.)


2. 배열의 크기가 넉넉하더라고 만약 바로 잘못 접근한 부분을 다른 곳에서 참조하고있으면 그 부분의 원래 데이터가 회손될 수 있습니다.





'programing > C# Programming' 카테고리의 다른 글

C#Programming - Delegate  (0) 2019.10.07
C#Programming - C#의 예외처리(exception)  (0) 2019.09.23
C# Programming - C#에서의 배열 복사  (0) 2019.09.20

C# 에서의 배열 복사를 알아볼 것 입니다.

배열 복사를 할 땐 항상 배열의 인덱스의 범위를 벗어나서 복사 하지 않는지 주의해야합니다.


1. Array.CopyTo(Array destArray, int index) 

// srcArray의 데이터를 인자로 전달한 destArray에 저장합니다.

// destArray의 마지막 인자의 인덱스 부터 저장됩니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
byte[] sourceArray = new byte[10]; // 복사를 할 배열입니다.
byte[] destinationArray = new byte[10]; // 복사를 당할 배열입니다.
 
// 복사 할 배열의 데이터를 삽입 합니다.
for (int i = 0; i < 10++i)
    sourceArray[i] = (byte)i;
 
// 복사하기전 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 0 0 0 0 0 0 0 0 0
 
// 복사합니다.
// 복사를 당할 배열, 복사할 크기 (index의 갯수)
sourceArray.CopyTo(destinationArray, 0);
 
Console.WriteLine();
 
// 복사한 후 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 1 2 3 4 5 6 7 8 9
 
Console.WriteLine();
 
cs


2. Array.Copy(Array srcArray, int srcCopyStartIndex, Array destArray, int destCopyStartIndex, int CopyLength)

// 예) srcArray[srcCopyStartIndex]에서 부터 CopyLength 까지 destArray에 복사합니다.

// destArray의 destCopyStartIndex부터 저장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
byte[] sourceArray = new byte[10]; // 복사를 할 배열입니다.
byte[] destinationArray = new byte[10]; // 복사를 당할 배열입니다.
 
// 복사 할 배열의 데이터를 삽입 합니다.
for (int i = 0; i < 10++i)
    sourceArray[i] = (byte)i;
 
// 복사하기전 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 0 0 0 0 0 0 0 0 0
 
/* 복사합니다.
복사할 배열, 복사할 배열의 복사가 시작되는 위치,
복사를 당할 배열, 복사를 당할 배열의 저장 시작 위치,
크기 (index 갯수)
*/
Array.Copy(sourceArray, 0, destinationArray, 010);
Console.WriteLine();
 
// 복사한 후 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} " );
// 출력 결과 : 0 1 2 3 4 5 6 7 8 9
 
Console.WriteLine();
 
cs


3. Array.Clone()

// 이 함수는 복사할 배열과 똑같은 복사본을 만듭니다. 할당도 알아서해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
byte[] sourceArray = new byte[10]; // 복사를 할 배열입니다.
 
// 복사 할 배열의 데이터를 삽입 합니다.
for (int i = 0; i < 10++i)
    sourceArray[i] = (byte)i;
 
// sourceArray의 복사본을 똑같이 만듭니다. 할당도 알아서 해줍니다.
byte[] destinationArray = sourceArray.Clone() as byte[];
 
// 복사한 후 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 1 2 3 4 5 6 7 8 9
 
Console.WriteLine();
 
cs


4. Buffer.BlockCopy(Array srcArray, int srcOffset, Array destArray, int destOffset, int Length);

// Offset : 시작 위치입니다. 어디서부터 복사할지 결정 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
byte[] sourceArray = new byte[10]; // 복사를 할 배열입니다.
byte[] destinationArray = new byte[10]; // 복사를 당할 배열입니다.
 
// 복사 할 배열의 데이터를 삽입 합니다.
for (int i = 0; i < 10++i)
    sourceArray[i] = (byte)i;
 
// 복사하기전 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 0 0 0 0 0 0 0 0 0
 
/* 복사합니다. 
인자 (복사 할 배열, 복사할 배열의 복사 시작 위치, 복사를 당할 배열, 
복사를 당할 배열의 복사 시작 위치, 복사할 사이즈(인덱스의 갯수))*/
Buffer.BlockCopy(sourceArray, 0, destinationArray, 010);
 
Console.WriteLine();
 
// 복사한 후 복사를 당할 배열의 데이터를 출력해봅니다.
for (int i = 0; i < 10++i)
    Console.Write($"{destinationArray[i]} ");
// 출력 결과 : 0 1 2 3 4 5 6 7 8 9
 
Console.WriteLine();
 
cs


틀린 부분이 있을 수도 있습니다. 댓글로 알려주세요

저번에 했던 플레이어 회전을 오일러에서 쿼터니온으로 변경하였다. 

이유는.


DirectX에서 구면 선형 보간 함수인 D3DXQuaternionSLerp 함수를 제공해주기 때문이다


D3DXQUATERNION *D3DXQuaternionSlerp(      

    D3DXQUATERNION *pOut,     CONST D3DXQUATERNION *pQ1,     CONST D3DXQUATERNION *pQ2,     FLOAT t );

참고 : Microsoft DirectX 9.0



일단 영상을 보자




구면선형보간과 그냥 선형보간의 다른 점을 찾아봤는데 

만약 정원에 있는 


두 점을 


선형보간을하게 되면 원의 표면을 따라가는 곡선이 아니라 원이 관통되어 직선이 그어진다


구면 선형 보간을 하게되면 원의 표면을 따라가는 곡선이 그어져 더 부드럽게 보간된다고 한다.


나는 카메라가 그냥 직선으로 보간되어 회전되는것 보단 구면 선형 보간을 사용해 원을 그리듯이 회전하면 더욱 부드러울꺼 같아서 사용했다.


일단 PlayerAIrPlane 클래스에 부모클래스인 AirPlane 클래스에다가 쿼터니온을 사용하여 비행기를 회전하는 함수를 만들었다 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//AirPlane.cpp
//Quaternion == D3DXQUATERNION
void AirPlane::YawRotation(float fAngle)
{
    Quaternion qRot;
    D3DXQuaternionRotationAxis(&qRot, &vAxis[E_AXIS_UP], fAngle);
    transform->qRot = transform->qRot * qRot;
    
    Matrix matRot;
    D3DXMatrixRotationQuaternion(&matRot, &qRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_RIGHT], &vAxis[E_AXIS_RIGHT], &matRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_FORWARD], &vAxis[E_AXIS_FORWARD], &matRot);
}
void AirPlane::RollRotation(float fAngle)
{
    Quaternion qRot;
    D3DXQuaternionRotationAxis(&qRot, &vAxis[E_AXIS_FORWARD], fAngle);
    transform->qRot = transform->qRot * qRot;
    Matrix matRot;
    D3DXMatrixRotationQuaternion(&matRot, &qRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_RIGHT], &vAxis[E_AXIS_RIGHT], &matRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_UP], &vAxis[E_AXIS_UP], &matRot);
}
void AirPlane::PitchRotation(float fAngle)
{
    Quaternion qRot;
    D3DXQuaternionRotationAxis(&qRot, &vAxis[E_AXIS_RIGHT], fAngle);
    transform->qRot = transform->qRot * qRot;
    Matrix matRot;
    D3DXMatrixRotationQuaternion(&matRot, &qRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_FORWARD], &vAxis[E_AXIS_FORWARD], &matRot);
    D3DXVec3TransformNormal(&vAxis[E_AXIS_UP], &vAxis[E_AXIS_UP], &matRot);
}
void AirPlane::SetAirPlaneMatrix()
{
    if (fRollAngle)
        AirPlane::RollRotation(fRollAngle);
    if (fYawAngle)
        AirPlane::YawRotation(fYawAngle);
    if (fPitchAngle)
        AirPlane::PitchRotation(fPitchAngle);
 
    if (fRollAngle || fYawAngle || fPitchAngle)
        fRollAngle = fYawAngle = fPitchAngle = 0.f;
 
    D3DXMatrixTranslation(&transform->matPos, transform->pos.x, transform->pos.y, transform->pos.z);
    D3DXMatrixScaling(&transform->matScale, transform->scale.x, transform->scale.y, transform->scale.z);
}
 
cs

각각 비행기의 YawRotation RollRotation PitchRotation 함수를 구현해놨다. 


AirPlane 클래스에 멤버변수에는


1
2
3
    float fYawAngle;
    float fPitchAngle;
    float fRollAngle;
cs


이 값을 변경해주면 


1
2
3
4
5
6
7
8
9
10
11
12
void AirPlane::SetAirPlaneMatrix()
{
    if (fRollAngle)
        AirPlane::RollRotation(fRollAngle);
    if (fYawAngle)
        AirPlane::YawRotation(fYawAngle);
    if (fPitchAngle)
        AirPlane::PitchRotation(fPitchAngle);
 
    if (fRollAngle || fYawAngle || fPitchAngle)
        fRollAngle = fYawAngle = fPitchAngle = 0.f;
 
cs

이 함수에서 위 변수의 값으로 각각의 회전함수를 써서 비행기를 회전한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//PlayerAirPlane.cpp
void PlayerAirplane::Update()
{
    InputMouse();
    InputKeyboard();
 
    if (fSpeed > fMaxSpeed)
        fSpeed -= PlayerUnAccel;
 
    transform->pos += vAxis[E_AXIS_FORWARD] * (fSpeed * Et);
 
    AirPlane::SetAirPlaneMatrix();
 
    if (KEYDOWN(VK_SPACE))
    {
        Vector3 LeftFirePos = Vector3(-20.f, 0.f, 30.f);
        Vector3 RightFirePos = Vector3(20.f, 0.f, 30.f);
 
        D3DXMATRIX matRot;
        D3DXMatrixRotationQuaternion(&matRot, &transform->qRot);
        memcpy(&matRot._41, transform->pos, sizeof(Vector3));
 
        D3DXVec3TransformCoord(&LeftFirePos, &LeftFirePos, &matRot);
        D3DXVec3TransformCoord(&RightFirePos, &RightFirePos, &matRot);
 
        OBJECT.AddObject<PlayerBullet>()
            ->SetBullet(LeftFirePos, transform->qRot, 2000.f, 1.f);
        OBJECT.AddObject<PlayerBullet>()
            ->SetBullet(RightFirePos, transform->qRot, 2000.f, 1.f);
    }
 
    CamreaSetting();
}
 
void PlayerAirplane::InputMouse()
{
    Vector2 vGap(INPUT.GetMouseGap());
 
    float XGap = D3DXToRadian(vGap.x) * 0.05f;
    float YGap = D3DXToRadian(vGap.y) * 0.05f;
 
    if (XGap)
        fRollAngle = (XGap * -1);
    if (YGap)
        fPitchAngle = (YGap * -1);
}
 
void PlayerAirplane::InputKeyboard()
{
    if (KEYPRESS(VK_UP))
        fPitchAngle = PlayerPitchAngle;
    if (KEYPRESS(VK_DOWN))
        fPitchAngle = -PlayerPitchAngle;
    
    if (KEYPRESS(VK_LEFT))
        fRollAngle = PlayerRollAngle;
    if (KEYPRESS(VK_RIGHT))
        fRollAngle = -PlayerRollAngle;
 
    if (KEYPRESS('A'))
        fYawAngle = -PlayerYawAngle;
    if (KEYPRESS('D'))
        fYawAngle = PlayerYawAngle;
 
    if (KEYPRESS('C'))
        bCameraBack = true;
    else if (KEYPRESS(VK_MBUTTON))
        bCameraBack = true;
    else
        bCameraBack = false;
 
    if (KEYPRESS('W'))
    {
        if(fSpeed < fMaxSpeed)
            fSpeed += PlayerAccel;
    }
    
    if (KEYPRESS('S'))
    {
        if(fSpeed > 0.f)
            fSpeed -= PlayerUnAccel;
    }
 
    if (KEYPRESS(VK_LSHIFT))
        fMaxSpeed = 800.f;
    else
        fMaxSpeed = 500.f;    
}
 
void PlayerAirplane::CamreaSetting()
{
    Vector3 vCameraDir = Vector3(0.f, 0.f, bCameraBack ? 1.f : -1.f);
 
    D3DXQuaternionSlerp(&qCameraRot, &qCameraRot, &transform->qRot, 0.1f);
 
    D3DXMATRIX matCamreaRot;
    D3DXMatrixRotationQuaternion(&matCamreaRot, &qCameraRot);
    
    
    //vCameraPos;
    D3DXMATRIX matInitCameraRot;
    D3DXMatrixRotationX(&matInitCameraRot, bCameraBack ? -fCameraAngle : fCameraAngle);
 
    D3DXVec3TransformNormal(&vCameraDir, &vCameraDir, &matInitCameraRot);
    D3DXVec3TransformNormal(&vCameraDir, &vCameraDir, &matCamreaRot);
    vCameraPos = transform->pos + vCameraDir * fCameraDistance;
 
    //Up
    vCameraUp = Vector3(0.f, 1.f, 0.f);
    D3DXVec3TransformNormal(&vCameraUp, &vCameraUp, &matCamreaRot);
 
    //LookAt
    vCameraLookAt = Vector3(0.f, 50.f, 0.f);
    memcpy(&matCamreaRot._41, &transform->pos, sizeof(Vector3));
    
    D3DXVec3TransformCoord(&vCameraLookAt, &vCameraLookAt, &matCamreaRot);
    
 
    CAMERA.SetCameraPos(vCameraPos, false0.f);
    CAMERA.SetCameraLookAt(vCameraLookAt, false0.f);
    CAMERA.SetCameraUp(vCameraUp, false0.f);
}
cs


PlayerAirPlane 클래스를 보면 입력에 따라 fYawAngle, fPitchAngle, fRollAngle 변수를 변경해서


80 번째줄 AirPlane::SetAirPlaneMatrix(); 으로 회전을 반영시켰다.


그리고


162번째 줄 

1
void PlayerAirplane::CamreaSetting()
cs


함수에서 구면보간 함수인 D3DXQuaternionSlerp 함수를 써서 카메라의 회전을 구면 보간하는것을 알 수있다.






 

드디어 오늘 플레이어 이동 & 카메라 구현을 완료했다. 


처음 만드는 3D게임이여서 어려웠던거 같다. 

그래도 만들면서 카메라 회전이나 회전행렬등을 이해할 수 있었다



구현 영상

조작은 마우스로도 할 수 있고 키보드 방향키로도 할 수 있다


구현 코드 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void PlayerAirplane::InputMouse()
{
    // vGap : 전프레임과 현재 프레임의 마우스 위치의 차이.
    Vector2 vGap(INPUT.GetMouseGap());
 
    //fForwardAngle : Forward축으로 돌릴 회전값
    float fForwardAngle = -(D3DXToRadian(vGap.x) * 0.05f);
    //fRightAngle : Right축으로 돌릴 회전값
    float fRightAngle = -(D3DXToRadian(vGap.y) * 0.05f);
 
    //키보드 사용시 
    if (KEYPRESS(VK_UP))
        fRightAngle += D3DXToRadian(3.f);
    if (KEYPRESS(VK_DOWN))
        fRightAngle -= D3DXToRadian(3.f);
 
    if (KEYPRESS(VK_RIGHT))
        fForwardAngle -= D3DXToRadian(3.f);
    if (KEYPRESS(VK_LEFT))
        fForwardAngle += D3DXToRadian(3.f);
 
    // 각 축의 회전행렬과 초기화
    D3DXMATRIX matForwardRot;
    D3DXMatrixIdentity(&matForwardRot);
 
    D3DXMATRIX matRightRot;
    D3DXMatrixIdentity(&matRightRot);
 
 
#pragma region AIRPLANE ROTATION
    // 플레이어를 회전 코드
 
    if (fForwardAngle)
    {
        // Forward 축으로 회전한 Matrix를 다른축에다 적용시켜준다 
        D3DXMatrixRotationAxis(&matForwardRot, &vAxis[E_AXIS_FORWARD], fForwardAngle);
        
        D3DXVec3TransformNormal(&vAxis[E_AXIS_RIGHT], &vAxis[E_AXIS_RIGHT], &matForwardRot);
        D3DXVec3TransformNormal(&vAxis[E_AXIS_UP], &vAxis[E_AXIS_UP], &matForwardRot);
    }
 
    if (fRightAngle)
    {
        // Right 축으로 회전한 Matrix를 다른축에다 적용시켜준다 
        D3DXMatrixRotationAxis(&matRightRot, &vAxis[E_AXIS_RIGHT], fRightAngle);
        
        D3DXVec3TransformNormal(&vAxis[E_AXIS_FORWARD], &vAxis[E_AXIS_FORWARD], &matRightRot);
        D3DXVec3TransformNormal(&vAxis[E_AXIS_UP], &vAxis[E_AXIS_UP], &matRightRot);
    }
 
    if (fRightAngle != 0 || fForwardAngle != 0)
        transform->matRot = transform->matRot * matForwardRot * matRightRot;    
 
#pragma endregion AIRPLANE ROTATION
 
#pragma region CAMERA_SETTING    
    // 카메라를 회전 코드
    // 구한 회전 행렬을 플레이어와 카메라위치, 카메라LookAt위치에 대한 방향에 적용시켜준다. 
    if (fForwardAngle)
    {
        D3DXVec3TransformNormal(&vCameraLookAtDir, &vCameraLookAtDir, &matForwardRot);
        D3DXVec3TransformNormal(&vCameraDir, &vCameraDir, &matForwardRot);
    }
 
    if (fRightAngle)
    { 
        D3DXVec3TransformNormal(&vCameraLookAtDir, &vCameraLookAtDir, &matRightRot);
        D3DXVec3TransformNormal(&vCameraDir, &vCameraDir, &matRightRot);
    }
    
    //카메라 위치 셋팅
    vCameraPos = transform->pos + (vCameraDir * fCameraDistance);
    vCameraLookAt = transform->pos + (vCameraLookAtDir * fCameraLookAtDistance);
 
    CAMERA.SetCameraInfo(vCameraPos, vCameraLookAt, vAxis[E_AXIS_UP]);
#pragma endregion CAMERA_SETTING
 
}
 
void PlayerAirplane::InputKeyboard()
{
    if (KEYPRESS('W'))
        transform->pos += vAxis[E_AXIS_FORWARD] * (200 * Et);
    if (KEYPRESS('S'))
        transform->pos -= vAxis[E_AXIS_FORWARD] * (200 * Et);
 
}
 
 
 
cs

각 코드의 설명은 주석으로 작성해놨다.


알아두고 가야할 요소들.


* 값의 변화를 잘 알아두고 작업하자 


144줄과 150줄 사이에서 Forward벡터를 축으로한 회전이 이루어진다

그리고 153줄과 160줄 사이에서 Right축으로한 회전이 이루어지면서

Forward벡터가 변경되는데

여기서 입력이 동시에 이루어진다면 Forward벡터는 변경되기 전 값으로 

계속 먼저 회전되고나서 값의 변경이 이루어진다. 

이 값이 계속 누적되면 카메라가 플레이어를 똑바로 바라보지않고 오차가 생기게된다


--------------------------------------------------------------------------

만들때는 어려웠지만 만들고 보닌까 이해가 됐다. 



DirectX 에서는 벡터를 변환하는 함수인


1
2
3
4
5
D3DXVECTOR3* WINAPI D3DXVec3TransformCoord
    ( D3DXVECTOR3 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM );
 
D3DXVECTOR3* WINAPI D3DXVec3TransformNormal
    ( D3DXVECTOR3 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM );
cs

함수가 있다.


일단 이 두함수의 공통점은 2번째 인자인 벡터와 3번째 인자인 행렬을 곱하는 함수라는 것이다.


우선 3차원 벡터와 4x4 행렬은 곱해질 수 없다 왜냐하면 행렬을 곱할때는 앞 행렬의 열과 뒤 행렬의

행이 같아야지만 곱할 수 있다


그래서 이 함수는 3차원 벡터를 4차원 벡터로 변환하여 행렬과 곱해주는데 여기서 차이점이 발생한다


3차원 벡터 가 있다면


이 3차원 벡터를


D3DXVec3TransformCoord 함수는 

로 바꿔주고


D3DXVec3TranformNormal 함수는

로 바꿔준다.


둘의 차이는 4차원 벡터의 4번째 요소  가 다르다는 점인데 


 이라면 포인터(좌표) 개념이 되어 이동이 적용되고

 이라면 벡터의(방향) 개념이 되어 이동이 적용되지 않는다


이유는 


DirectX 의 4x4 행렬의 마지막 열은 이동을 담당하는것을 알고 있다면 이해하기 쉽다


------------------------------------------------------------ 

이 식에서 


이라면

  이므로 이동이 적용되는걸 알 수 있다.


 이라면 

      이므로 이동이 적용 안되는 걸 알 수 있다.


-------------------------------------------------------------

그러므로


 벡터와 행렬을 곱하여 


 이동, 회전, 스케일, 을 변환하고 싶다면 D3DXVec3TransformCoord를 쓰고

 이동은 변환하고싶지 않고 회전,스케일만 변환하고 싶다면 D3DXVec3TransformNormal을 쓰면된다








'programing > directX' 카테고리의 다른 글

LPD3DXMESH 쓰는법  (0) 2018.10.13
LPD3DXFONT  (0) 2018.02.13

전체소스코드는 맨 아래있습니다.


DirectX 3D를 공부하던 중 Obj 모델링을 로드하고 그 정보를 저장하기 위해 LPD3DXMESH를 사용했다


LPD3DXMESH를 사용하기 위해 해야할 것들은...


1. LPD3DXMESH 초기화 해주기


D3DXCreateMeshFVF(

삼각형의 갯수,

정점의 갯수,

D3DXMESH_MANAGED,

FVF 포멧,

device,

LPD3DXMESH의 포인터);     위 함수를 이용하여 메쉬를 초기화 한다.


2. VertexBuffer 초기화 하기

일단 정점들을 저장한 배열이나 STL::Vector에 저장한다.

vector<CustomVertex> vVertexs;


버퍼 포인터를 받을 void 포인터 변수를 선언한다.

void * Vertices = nullptr;


락을 걸어준다.

lpD3DXMesh->LockVertexBuffer(0, Vertices);


그리고 배열이나 STL::Vector의 저장된 정점들을 복사해준다.

memcpy(Vertices, &vVertexs[0], sizeof(CustomVertex) * vVertexs.size());


락을 풀어준다.

lpD3DXMesh->UnlockVertexBuffer();


2. IndexBuffer 초기화 하기.

초기화할 인덱스 정보를 배열이나 STL:Vector에 저장한다.

vector<WORD> vIndexs;


버퍼 포인터를 받을 void 포인터 변수를 선언한다

void * Indices = nullptr;


락을 걸어준다.

lpD3DXMesh->LockIndexBuffer(0, &Indices))


그리고 배열이나 STL::Vector의 저장된 정점들을 복사해준다

memcpy(Indices, &vIndexs[0], sizeof(WORD) * vIndexs.size());


락을 풀어준다.

lpD3DXMesh->UnlockIndexBuffer();


2. AttributeBuffer 초기화 하기.

초기화할 속성 정보를 배열이나 STL:Vector에 저장한다.

vector<DWORD> vAttribute;


버퍼 포인터를 받을 DWRD 포인터 변수를 선언한다

DWRD * Attribute= nullptr;


락을 걸어준다.

lpD3DXMesh->LockAttributeBuffer(0, &dwAttribute))


그리고 배열이나 STL::Vector의 저장된 정점들을 복사해준다

memcpy(dwAttribute, &vAttribute[0], sizeof(DWORD) * vAttribute.size());


락을 풀어준다.

    lpD3DXMesh->UnlockAttributeBuffer();



이렇게 설정해주고 그릴때 lpD3DXMesh->DrawSubset(SubsetNum); 이렇게 그려주면 메쉬가 그려진다.


전체 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
bool cMeshRenderer::CreateMesh()
{
    if (FAILED(D3DXCreateMeshFVF(
        vVertexs.size() / 3
        vVertexs.size(), 
        D3DXMESH_MANAGED, 
        CustomFVF, 
        g_device, 
        &lpD3DXMesh)))
    {
        MessageBox(nullptr, L"Failed CreateMesh ", L"Error", MB_OK);
        return false;
    }
 
    void * Vertices = nullptr;
 
    if (FAILED(lpD3DXMesh->LockVertexBuffer(0&Vertices)))
    {
        MessageBox(nullptr, L"Failed Mesh->LockVertexBuffer", L"Error", MB_OK);
        return false;
    }
    
    memcpy(Vertices, &vVertexs[0], sizeof(CustomVertex) * vVertexs.size());
 
    lpD3DXMesh->UnlockVertexBuffer();
 
    void * Indices = nullptr;
 
    if (FAILED(lpD3DXMesh->LockIndexBuffer(0&Indices)))
    {
        MessageBox(nullptr, L"Failed Mesh->LockIndexBuffer", L"Error", MB_OK);
        return false;
    }
    
    memcpy(Indices, &vIndexs[0], sizeof(WORD) * vIndexs.size());
 
    lpD3DXMesh->UnlockIndexBuffer();
 
    DWORD * dwAttribute = nullptr;
 
    if (FAILED(lpD3DXMesh->LockAttributeBuffer(0&dwAttribute)))
    {
        MessageBox(nullptr, L"FAiled Mesh->LockAttributeBuffer", L"Error", MB_OK);
        return false;
    }
 
    memcpy(dwAttribute, &vAttribute[0], sizeof(DWORD) * vAttribute.size());
 
    lpD3DXMesh->UnlockAttributeBuffer();
 
    return true;
}
 
cs



틀린 것이나 조언할것이 있다면 댓글로 해주세요 감사합니다

'programing > directX' 카테고리의 다른 글

DirectX D3DXVec3TransformCoord와 D3DXVec3TransformNormal 함수  (0) 2018.10.15
LPD3DXFONT  (0) 2018.02.13

+ Recent posts