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


바로 '유니티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 에서는 벡터를 변환하는 함수인


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


게임을 만들던 도중  STL list로 관리하는 오브젝트가 삭제될때 list 오류가 나는것이다.. 


나는 list를 잘 사용을 못해 진짜 꼼수로 오류를 막았는데 그 방법이



오류코드

for (auto Iter : m_objects)

{

for (auto Iter02 = (*Iter.second).begin(); Iter02 != (*Iter.second).end();)

{

if ((*Iter02)->GetDelete())

{

SAFE_DELETE(*Iter02);

         (Iter.second)->erase(Iter02);

}

else

Iter02++;

}

}

여기서 m_objects는 STL map 이다. map<int key, list<cGameObject*>*> 이런 방식이다

예를 들면

 



위 코드의 문제는 만약 GetDelete함수가 참이되는 오브젝트를 삭제하고나면 그 Iter02는 삭제되어 다음 object를 가르키는 iterator가 없어지는 것이다 그리고 나서 삭제된 Iterator의 접근하니 오류가 날 수 밖에 없는 코드이다

 

 나는 list를 잘몰랐기 때문에 하나를 삭제하고 break 를 써서 안쪽 루프를 탈출하는 방법을 쓰고있었다.


바로 이렇게


꼼수식 해결코드(꼼수라고도 못한다)

for (auto Iter : m_objects)

{

for (auto Iter02 = (*Iter.second).begin(); Iter02 != (*Iter.second).end();)

{

if ((*Iter02)->GetDelete())

{

SAFE_DELETE(*Iter02);

         (Iter.second)->erase(Iter02);

 break;

}

else

Iter02++;

}

}


위 코드는 문제하나가있다 하나가 삭제될때마다 삭제된 오브젝트와 같은 key값의 list의 저장되는 object들은 현재 프레임에서 삭제를 못하고 다음 프레임에서 삭제를 해야하는 경우가 발생한다.(그냥 삭제된 오브젝트와 같은 키값 list의 저장된 오브젝트는 삭제를 계속 미루는 것이다 이게 계속 쌓여서 삭제가 느리게되는것이다.

이 문제는 내가 뭔가 위 코드가 이상하다고 느껴서 총알 발사 속도를 엄청나게 빠르게해놓고 계속 발사하다보닌까 삭제가 늦게되는 총알이 발생한다는것을 알게되었다.


인터넷의 이유를 검색하닌까 이유를 알게되었다 그 이유는


위에서도 설명했지만 삭제가 실행되는 Iterator가 삭제되면 다음 값을 가르키는 Iterator가 삭제되는 것이기 때문에 다음값의 접근을못하고 삭제된 iterator의 접근을해서 오류가 나는것이다.


이것의 해결방법은 

만약 list.erase() 를 이용하여 삭제되면 erase함수는 삭제된 Iterator의 다음 Iterator를 리턴한다. 이것을 이용하면


for (auto Iter : m_objects)

{

for (auto Iter02 = (*Iter.second).begin(); Iter02 != (*Iter.second).end();)

{

if ((*Iter02)->GetDelete())

{

SAFE_DELETE(*Iter02);

         Iter02 = (Iter.second)->erase(Iter02);

}

else

Iter02++;

}

}



이렇게만 해주면 삭제할때마다 break를 써서 탈출하여 삭제할 오브젝가 쌓이지 않고 계속 삭제된 오브젝트와 같은 list의 저장된 object들도 조건이 참이되면 계속 삭제가 될 수 있다.




개인이 공부하는 목적으로 올린 포스트이기 때문에 틀린 내용이 있을 수도 있습니다 댓글로 알려주세요



'programing > c_c++' 카테고리의 다른 글

Win32프로젝트에서 콘솔창 이용하기  (0) 2018.02.15

Win32로 프로젝트를 진행하다 보면 여러가지 상태의 변경상황을 확인해야하는데 이런 것을 디버그로 확인 하다보면 정말 불편하다 멈추고 확인하고 멈추고 확인하고... 계속 반복하다 보면 정말 귀찮다 그리고 프로그램의 특정 상황(게임같이 계속 프레임이 돌아가는 경우) 에서 확인 해야 한다면 거의 불가능한 수준이다. 


그래서 Win32에서도 콘솔창을 띄울 수 있는 방법이 있다


프로그램 시작시 이런 코드를 추가해주면 된다


#pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console") 


하지만 또 확인해야 할 것이 있는데 자신의 프로그램이 멀티바이트를 사용하는 프로그램인가 유니코드를 사용하는 프로그램인가에 따라서 코드가 조금 다르다


유니코드 사용시

#pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console") 


멀티바이트 코드 사용시

#pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console")  


이렇게 코드를 달리 해주면 된다


근데 이걸 또 프로젝트 생성시 마다 확인해주고 다르게 작성하기 귀찮으니

c/c++인 경우 배운 전처리 지시기를 이용하면 편리하다


                                                                                                           

#ifdef UNICODE                                                                                      

      #pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console") 

#else                                                                                                    

      #pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console")   

                                                                                                           


이렇게 해주면 확인해줄 필요도없고 또 확경이 바뀌어도 알아서 해주니 편리하다


그리도 대부분 디버그 모드에서만 활용하니


#ifdef _DEBUG

                                                                                                           

#ifdef UNICODE                                                                                      

      #pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console") 

#else                                                                                                    

      #pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console")   

#endif                                                                                                   

#endif                                                                                                   



이렇게 해주면 된다 그리고 프로그램 상에서 cout이나 printf 를 사용하면된다


(개인이 공부목적으로 쓰는 글이기 때문에 틀린 부분이 있을 수 있습니다)

'programing > c_c++' 카테고리의 다른 글

STL list 원소삭제법.  (0) 2018.02.24

+ Recent posts