애니메이션을 저장하고 있는
오브젝트의 Component인 Animator를 생성해 보겠습니다.
우선 대입을 통해 새로운 오브젝트를 생성한다고 할 때,
인자로 들어온 오브젝트에 애니메이터를
가리키는 멤버 m_pAnimator의 값이 존재한다면
이를 가져와야겠죠. 그래서 34번째 줄에 있는
if 문에 들어가면 새로운 동적 할당을 통해
인자로 받은 오브젝트가 갖고 있는 m_pAnimator와
같은 값을 가진 공간을 만듭니다.
그리고 그 공간에 있는 Animator가 새로 생성한
오브젝트를 가리키도록 하죠.
오브젝트가 component_render 함수를 통해
컴포넌트를 렌더링할 때,
이제는 m_pAnimator가 가리키는
애니메이터 컴포넌트도 render를 해야겠죠.
CreateAnimator 함수를 통해 본격적으로
Animator를 생성해 줍니다.
CAnimator 클래스에는 새로운 멤버 m_pCurAnimation가
추가되었습니다.
현재 애니메이터가 실행하는 애니메이션을
가리키는 포인터 변수이죠.
애니메이터의 update와 render 함수의 호출은
곧, 현재 실행되고 있는 애니메이션에 대한
update 함수와 render 함수를 호출하는 겁니다.
CreateAnimation 함수를 통해
Animation을 만들게 되는데,
FindAnimation 함수에 _strName를 넣어
반환받은 값을 지역 변수인 anim이라는
애니메이션 포인터가 가리키도록 합니다.
FindAnimation 함수는 인자로 받은
문자열을 통해 다양한 애니메이션을 저장하고 있는
멤버 m_mapAnimation을 탐색합니다.
해당 애니메이션이 이미 존재한다면
해당 애니메이션을 가리키는 포인터를 반환,
m_mapAnimation에 존재하지 않는다면 nullptr을 반환합니다.
그리고 다시 CreateAnimation 함수로 돌아와
anim에 저장된 값이 nullptr이면 계속 진행하고,
그렇지 않고 anim에 특정 값이 들어와 있다면,
즉 이미 해당 애니메이션이 존재한다면
경고 창을 띄웁니다.
anim에 저장된 값이 nullptr인 경우
동적 할당한 후 그 공간에 CAnimation형 객체를
만듭니다.
그리고 인자로 받은 문자열을 인자로,
SetName 함수를 통해 동적 할당된 공간에 존재하는
CAnimation형 객체에 이름을 붙여 줍니다.
이후 Create 함수를 호출하는데
해당 함수는 한 프레임에 출력할 이미지를
어떤 식으로 출력할지에 대한 정보를 세팅해 줍니다.
애니메이션의 이름과 위 과정을 통해
값이 세팅된 anim을 pair 형태로 만들어
insert 함수를 통해 m_mapAnimation에
삽입합니다.
CAnimation 클래스의 Create 함수를
더 살펴보겠습니다.
그 전에 CAnimation 클래스의
멤버 변수들과 구조체 tAnimFrame를
먼저 보겠습니다.
멤버 m_strName에는 애니메이션의 이름이 저장됩니다.
또한 애니메이션이 어떤 애니메이터의 소속인지
멤버 m_pAnimator에 저장되어 있죠.
애니메이션은 멤버 m_pTexture를 통해 텍스처를 가리키고 있습니다.
여기서 텍스처는 아래 그림과 같이
오브젝트의 여러 모습을
하나의 이미지로 만든 것입니다.
그럼 이 텍스처를 어떤 식으로
잘라서 오브젝트에 씌울 것인가에 대한
정보가 필요할 겁니다.
그 정보를 하나로 묶은게
구조체 tAnimFrame 입니다.
만약 오브젝트가 아래로 움직이는
애니메이션을 구현한다고 가정해 봅시다.
그럼 빨간 테두리 안에 있는 부분이 필요하겠죠?
빨간 테두리 안에 있는 그림 중
가장 왼쪽에 있는 그림의 좌푯값을
얻고 해당 그림의 크기만큼 x 좌표를 이동하면
나머지 그림도 얻어낼 수 있을 겁니다.
이렇게 저장한 그림을 화면에 순서대로
그려 주면 마치 움직이는 것처럼 보일 겁니다.
그럼 우선 왼쪽 상단 좌푯값부터 알아내야 합니다.
텍스처의 크기를 보니 너비가 1200,
높이가 1040 픽셀이네요.
아래로 걷는 모션을 보면 가로로 10개의 모션이
나열되어 있으므로 한 모션의 가로 크기는
1200 / 10 = 120 픽셀이네요.
또한 모든 모션이 8개의 행으로 구분되어 있으므로
한 모션의 세로 크기는 1040 / 8 = 130 픽셀입니다.
5번째 행에 위치한 아래로 걷는 모션이 필요하죠?
아래로 걷는 모션이 존재하는 행의 좌푯값은
130 * 5 = 650이므로 ( 0, 650 ) 임을 알 수 있습니다.
이 값을 구조체 tAnimFrame가 갖고 있는
멤버 m_vLeftTop에 저장하는 겁니다.
또한 한 모션의 가로 길이는 120,
세로 길이는 130이죠? 즉 한 모션을 얻기
위해서 큰 텍스처 이미지를 120 X 130 사이즈로
잘라내면 된다는 것이죠.
이 값을 멤버 m_vSliceSize에 저장합니다.
출력된 모션을 얼마나( 시간 ) 출력할 것인지
설정해 주어야 하는데 이 설정값을
m_fDuration에 저장하게 됩니다.
CAnimation 클래스의
멤버 m_vAnimInfo는 이런
구조체 tAnimFrame로 이루어진
모션 하나하나의 정보를 모아
vector에 모은 것이죠.
다시 Create 함수로 돌아와서
인자로 받은 텍스처를
애니메이션의 멤버 m_pTexture를 통해 가라킵니다.
tAnimFrame형 객체를 하나 만들고
for 문을 돌면서 tAnimFrame형 객체의
값을 세팅해 주는 겁니다.
for 문을 _iFrameCount의 값만큼 돌게 되어 있습니다.
위 텍스처에서 아래로 걷는 모션은
10개이므로 10번 for 문을 돌아
아래로 걷는 모션 하나하나의
좌푯값, 사이즈, 출력 시간을
하나로 묶어서 m_vAnimInfo에 저장합니다.
여기서 중요한 점은 tAnimFrame형 객체인 frm의
멤버 m_vLeftTop의 값입니다.
가장 왼쪽에 있는 모션에 대한 정보를 저장했다면
다음에는 그 모션으로부터 오른쪽에 있는 모션에 대한
정보를 순차적으로 저장해야겠죠.
그래서 Create 함수 호출 당시, 인자로 받은
vStep이라는 값과 i 값을 곱하여 저장하는 모습입니다.
vStep은 얼마만큼 이동해야 다음 모션이 존재하는지
계산하기 위한 값으로, 다음 모션을 얻기 위해서는
가로로 120, 세로로 0만큼 이동하면 되기 때문에
vStep의 값은 ( 120, 0 ) 입니다.
아래로 걷는 모션 중 가장 왼쪽에
존재하는 모션의 좌푯값은 ( 0, 650 )이므로
m_vLeftTop의 값도 ( 0, 650 ) 입니다.
vStep의 값은 ( 120, 0 )이고
i값은 0이므로 첫 모션이 존재하는 위치의 좌푯값은
( 0, 650 ) + ( 120 * 0, 0 * 0) = ( 0, 650 ) 입니다.
이후 i값이 1 증가하면 다음 모션이 존재하는 위치의
좌푯값이 ( 0, 650 ) + ( 120 * 1, 0 * 1) = ( 120, 650 ) 인 것을
알 수 있죠.
그렇게 쭉 가면 마지막 모션이 존재하는 위치의
좌푯값은 ( 0, 650 ) + ( 120 * 9, 0 * 9) = ( 1080, 650 ) 인 것을
알 수 있겠죠?
여기서 Vec2 타입인 _vStep과
정수 타입인 _i의 곱셈이 가능한 이유는
Vec2 구조체에 곱하기 연산을 오버로딩했기 때문이죠.
Vec2 타입 변수와 정수 타입의 변수 간의 곱셈이 발생하면
Vec2 타입 변수가 갖고 있는 멤버 x, y에 각각 정수를 곱하도록 하였습니다.
여기까지 진행하고 CPlayer 클래스 생성자,
즉 플레이어가 생성될 때
LordTexture 함수를 통해 외부에 있는 텍스처 파일을
가져오고 CreateAnimator 함수를 통해
애니메이터를 만듭니다.
그리고 만든 애니메이터를 가져와
CreateAnimation 함수를 호출합니다.
CreateAnimation 함수의 인자로
가져온 텍스처, 애니메이션의 이름, 텍스처로부터 잘라올 부분의 좌상단 좌푯값,
한 모션의 크기, 다음 모션을 얻기 위해 이동해야 할 좌푯값, 모션이 출력될 시간,
모션의 개수를 넣으면 됩니다.
이전에 작성한 코드 중 문제가 발생할 수 있는
부분이 있어 수정을 하도록 하겠습니다.
우선 모든 리소스는 CRes 클래스를 상속받고 있습니다.
텍스처도 마찬가지로 CRes 클래스를 상속받고 있죠.
그래서 부모 클래스인 CRes 클래스의
소멸자에 virtual 키워드를 붙여 주어야 합니다.
CRes형 포인터가, 텍스처 등
자식 클래스가 있는 동적 할당된 공간을
가리키고 있다고 해 봅시다.
그리고 프로그램이 종료될 때
delete 키워드를 통해 CRes형 포인터가
가리키는 동적 할당된 공간의 메모리 해제를
진행하도록 하였습니다.
이때 부모 클래스인 CRes 클래스의
소멸자에 virtual 키워드를 붙이지 않으면
오직 CRes 클래스의 소멸자만 호출이 되고
자식 클래스의 소멸자가 호출되지 않아
메모리 누수가 발생할 수 있죠.
delete 키워드를 통해 지워야 하는 것은
자식 클래스가 있는 공간이니까요.
하지만 부모 클래스인 CRes 클래스의
소멸자에 virtual 키워드를 붙이면
알아서 CRes형 포인터가 가리키는
공간에 있는 자식 클래스의 소멸자도
호출이 되죠.
그리고 기존 코드는
리소스 매니저의 소멸자가 호출될 때
오류가 발생했습니다.
리소스 매니저의 소멸자에서는
Safe_Delete_Map 함수를 호출하고 있는데,
Safe_Delete_Map 함수는 인자로 받은 map형 객체를
delete하여 메모리를 해제해 주는 함수입니다.
근데 m_mapTexture라는 객체가
map<wstring, CTexture*>형인 게 문제였죠.
왜냐하면 CTexture의 소멸자는
private로 선언되어 있기 때문에
friend로 처리해 준 CResMgr 클래스를 제외하면
함부로 호출할 수 없는 상태였는데, 이를
전역 함수로 접근하려고 하니 오류가 발생하는 것이죠.
이를 해결하기 위해서 m_mapTexture의 타입을
map<wstring, CRes*>로 바꾸었습니다.
CRes 클래스의 소멸자는 public으로 선언하였기에
전역 함수를 통해서 접근할 수 있습니다.
또한 CTexture 클래스는 CRes 클래스를 상속받은
클래스이기 때문에 부모 클래스형 포인터로 가리켜도
문제가 없습니다.
다만 CResMgr 클래스의 FindTexture 함수에서
43번째 줄처럼 m_mapTexture 타입이 바뀌었기 때문에
이를 받는 iterator의 타입도 변경을 해주어야겠죠.
그리고 m_mapTexture가 갖고 있는 노드들을
탐색하여 second값을 반환하는, 즉 텍스처를 포인터로
반환하는 FindTexture 함수였으나 second값의 타입이
CRes형 포인터로 바뀌었기 때문에 CTexture형 포인터로
형 변환한 후 반환해주어야겠죠.
강의 출처 : https://www.youtube.com/watch?v=wHsOvvHcjqw&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=37
'Win32 API > 기초' 카테고리의 다른 글
Win32 API 기초 : Animation (4) (0) | 2024.09.01 |
---|---|
Win32 API 기초 : Animation (3) (0) | 2024.09.01 |
Win32 API 기초 : Animation (1) (0) | 2024.08.19 |
Win32 API 기초 : Object (1) (0) | 2024.08.18 |
Win32 API 기초 : Scene (1) (0) | 2024.08.18 |