CObject 클래스를 통해 사각형 객체를 만들고
화면에 출력하였습니다.
이를 키 입력을 통해 움직일 수 있도록 하였죠.
이제는 CObject 클래스를 통해
플레이어 객체를 생성해 줄 자식 클래스를 만들겠습니다.
먼저 아래 코드에서 17번째 줄에서 보이듯이
update 함수를 순수 가상 함수로 만들어 주었습니다.
CObject 클래스를 상속받은 자식 클래스로 만든
오브젝트는 각 유형에 따라 고유의 update 함수를
갖게 할 것입니다. update 함수는 어떤 오브젝트냐에 따라
다르게 작동해야 하니까 말이죠.
가상 함수로 정의해 줌으로써
update 함수를 호출하면 부모 클래스의
update 함수가 아닌 자신의 update 함수를
호출할 수 있도록 하였습니다.
이 가상 함수를 또 순수 가상 함수로 정의하면서
부모 클래스인 CObject 클래스를 통해 객체를
만드는 일을 방지하고 CObject 클래스의
자식 클래스는 반드시 update 함수를
구현하고 있어야 하도록 했죠.
Object 필터 안에 Player 필터를 만들고
그 안에 CPlayer 클래스를 만들었습니다.
해당 클래스는 CObject 클래스를
상속받아 구성하였죠.
CPlayer 클래스의 헤더 파일을 보시면
update 함수가 있는 것이 보입니다.
부모 클래스에 update 함수가 순수 가상 함수로
정의되어 있으므로 자식 클래스에는 반드시
update 함수가 구현되어 있어야 오류가 발생하지 않습니다.
CPlayer 클래스의 소스 파일을 보시면
플레이어의 update 함수는
키 입력을 통해 오브젝트의 위치를
변경시키는 역할을 하고 있습니다.
GetPos 함수를 통해 부모 클래스에 있는
좌푯값을 지역 변수 vPos에 저장하고
키 입력이 있다면 vPos에 저장된 값을
수정한 후 SetPos 함수를 호출하여
수정된 값을 부모 클래스에 있는 멤버에
다시 적용시켜 주고 있습니다.
이러면 오직 CPlayer 클래스를 통해 만든 객체,
즉 플레이어 객체만이 키 입력을 받고 움직일 수 있는
상태가 될 것입니다.
만약 부모 클래스인 CObject 클래스에
위와 같은 update 함수를 구현하였다면
키 입력이 들어왔을 때 모든 객체가 움직이는
문제가 발생하겠죠.
이제 몬스터 객체도 만들어 보겠습니다.
아래 사진은 CObject 클래스를 상속 받은
CMonster 클래스입니다.
멤버로는 몬스터가 움직일 기준점이 되는
m_vCenterPos와 몬스터의 속도를 저장하는
m_fSpeed, 몬스터가 움직일 최대 거리를 저장하는
m_fMaxDistance, 몬스터가 움직일 방향을 결정하는
m_iDirection가 있습니다.
이 중 m_vCenterPos와 m_fSpeed, m_fMaxDistance 멤버는
해당 멤버의 값을 세팅하고 반환하는 Get, Set 함수가 존재하죠.
몬스터의 update 함수는
플레이어의 update 함수와 어떻게 다른지
살펴봅시다.
CMonster 클래스의 소스 파일입니다.
몬스터는 좌우로 왔다 갔다 움직이게 할 겁니다.
CMonster 클래스의 생성자를 보시면 몬스터의
초기 속도는 100이며 움직일 거리는 50으로 되어 있으며,
오른쪽부터 움직이도록 설정되어 있습니다.
m_iDirection의 값이 1이면
오른쪽 방향을, -1이면 왼쪽 방향을 의미합니다.
우선 GetPos를 통해 몬스터의 위치 좌푯값을
지역 변수 vPos에 저장합니다.
그리고 매 프레임마다 설정된 속도와 방향, Delta Time을 곱하고
기존의 몬스터의 x 좌푯값과 더하여 이를 다시
몬스터의 x 좌푯값으로써 저장합니다.
처음에는 22번째 줄에 의해 몬스터가 초당 100 픽셀을
이동하는 속도로, 오른쪽으로 이동하겠죠.
그렇게 오른쪽으로 쭉 이동하다가
m_fMaxDistance값만큼 이동하면
m_iDirection값에 -1을 곱해 방향을 전환해야겠죠?
이때 중요한 것이 24번째 줄에 있는 변수 fDist입니다.
몬스터가 움직일 기준점의 x 좌푯값에서
현재 몬스터가 위치한 x 좌푯값을 빼고
연산 결과를 abs 함수를 통해 절댓값으로 바꿉니다.
여기에서 몬스터가 움직일 최대 거리로
설정한 값을 빼고 fDist에 저장합니다.
26번째 줄에 있는 if 문을 통해
fDist의 값이 0보다 크면, 즉 양수라면
28번째 코드에 의해 방향을 바꾸고
fDist의 부호를 바꿉니다.
현재 몬스터가 위치한 x 좌푯값과 fDist를 더해
다시 x 좌푯값에 저장합니다.
즉 fDist는 우리가 설정한 거리를 기준으로
얼마나 이동했는지에 대한 값을 저장하고
몬스터가 설정한 거리보다 더 움직였다면
더 움직인 만큼 몬스터의 위치를 빼주어
몬스터의 위치를 조정해 줍니다.
몬스터가 설정한 거리보다 덜 움직였다면
fDist에 음수가 저장될 것이고
if 문에 걸리지 않아 마저 움직이게 될 겁니다.
예를 들어보면
몬스터가 움직일 기준점은
윈도우상 x 좌푯값을 640으로 하고,
설정한 최대 이동 거리는 50으로 했다고 합시다.
그럼 몬스터는 x 좌표 640을 기준으로
좌우로 움직일 것이며 처음에는 오른쪽부터
가게 되겠죠. 그리고 x 좌푯값이 50만큼 이동한
690가 되면 m_iDirection에 -1을 곱해
방향 전환이 발생할 겁니다.
근데 여기서 순간적으로 DeltaTime이 확 내려가
실행 속도가 빨라져서 몬스터가 710 지점까지
움직이고 말았습니다. 이대로 방향 전환을 하여
왼쪽으로 50만큼 이동하면 660 지점까지 이동하다가
다시 방향 전환이 되면서 몬스터가 이상하게 움직일 겁니다.
이를 방지하기 위한 24번째 줄에 있는
코드에 값을 대입해 보면
abs(640 - 710) - 50 = 20이
fDist에 대입되겠죠.
양수 20이 fDist에 대입되었다는 것은
몬스터가 원래 도착해야 할 지점보다
20 픽셀 더 갔다는 것을 의미합니다.
이 경우 26번째 줄의 if 문에 걸려
방향 전환을 하여 그만 이동하게 하고
710 + 20 * -1 = 690의 값을
몬스터의 x 좌푯값에 대입함으로써
강제로 몬스터를 정상 위치에 옮겨 놓은 후에
다시 움직일 수 있도록 하는 겁니다.
만약 아래 그림처럼
순간적으로 Delta Time이 확 올라가
몬스터가 1초에 690 지점까지 가야 하는데
1초 동안 670 지점까지밖에 가지 못했습니다.
그러면
abs(640 - 670) - 50 = -20이
fDist에 대입되겠죠.
-20이 fDist에 대입되었다는 것은
몬스터가 원래 도착해야 할 지점보다
20 픽셀 덜 갔다는 것을 의미합니다.
그래서 if 문에 걸리지 않고
다음 프레임에 update 함수가 호출될 때
22번째 줄에 있는 코드가 실행되면서
남은 픽셀을 더 움직이게 되는 것이죠.
움직일 방향과 거리가 다 정해지고 난 후에야
33번째 줄에 있는 SetPos 함수가
호출되어 몬스터가 움직이기 위한
값들이 CObject 부모 클래스의 멤버에
이때 실제로 적용이 됩니다.
몬스터 오브젝트를 Start 씬에 출력하도록 하죠.
CScene_Start 클래스의 소스 파일에서
Start 씬이 실행되었을 때 초기 상태를
설정해 주는 Enter 함수를 보겠습니다.
22번째 줄에 있는 변수 CreateMonsterCount는
Start 씬에 존재할 몬스터의 개수를 나타냅니다.
CreateMonsterMoveDistance는 존재하는 몬스터가
움직일 거리를 나타냅니다.
CreateMonsterScale는 몬스터의 크기를 나타내죠.
CreateMonsterCount가 5이므로 Start 씬에는
5개의 몬스터 오브젝트가 존재하는데 y 축 기준 동일 선상에,
x 축 기준으로는 일정한 간격을 두고 출력할 것입니다.
이 간격을 계산하기 위해 27번째 줄에서 vResolution에
CCore 클래스로 만든 코어 매니저가 갖고 있는 해상도를
대입하는 것이죠. 즉 프로그램이 실행되면 띄워지는
윈도우의 해상도를 갖고 오는 겁니다.
이때 GetResolution 함수의 반환형은 POINT이므로
Vec2형인 vResolution에 대입하려고 하면 오류가 발생하죠.
그래서 아래 코드처럼
구조체 Vec2의 구현 코드에서
8번째 줄에 대입 연산자 오버로드를 위해
operator를 추가했습니다.
반환형은 Vec2 레퍼런스로
복사가 발생하지 않고 바로
참조할 수 있도록 하였죠.
인자로 받은 것을 const POINT형 레퍼런스로
하여 레퍼런스의 멤버 x, y를 Vec2의 멤버 x, y에
대입하고 있습니다. POINT의 멤버 x, y는
long형이므로 Vec2의 float형 멤버인 x, y에
대입하기 위해 float으로 형변환해 주는 모습입니다.
하지만 대입 연산자를 오버로딩하여도
오류가 사라지지 않습니다.
이유는 27번째 줄에서 Vec2형 변수 vResolution을
선언함과 동시에 대입을 하고 있기 때문이죠.
선언과 동시에 대입을 하면 이를 대입으로 보지 않고
초기화로 인식합니다. 즉 Vec2형 vResolution가
생성됨과 동시에 우항에 있는 값으로 Vec2 생성자
을 통해 이니셜라이저를 진행하는 것입니다.
이를 위해 29번째 줄에 존재하는 생성자를 보시면
Vec2형 객체가 생성될 때 인자로 POINT형을 받으면
각 멤버를 인자의 각 멤버로 초기화하도록 하는 것을
보실 수 있습니다.
변수 fTerm에 몬스터 간의 간격값이 계산되어 대입됩니다.
위 그림은 윈도우의 가로 해상도가 1280이고
50 크기의 몬스터가 50만큼 좌우로 이동하는
프로그램인 경우입니다.
몬스터가 처음에는 회색 사각형에 존재하였다가
왼쪽으로 50만큼 이동하여 검은색 사각형 위치에 도달하겠죠.
이때 가장 왼쪽에 존재하는 몬스터가 움직이면서 화면 밖으로
나가지 않게, 딱 붙을 때까지만 이동하는 위치에
생성하고 싶다면
몬스터가 이동하는 거리와 몬스터의 크기의 절반을
합한 값을 가장 왼쪽 몬스터의 x 좌푯값으로 하면 됩니다.
50 + 50 / 2 이므로 75가
가장 왼쪽 몬스터의 x 좌푯값이 되겠네요.
이 가장 왼쪽에 위치한 몬스터는 왼쪽으로
50만큼 이동하여도 잘리지 않고 화면 끝에
딱 걸치도록 움직일 것입니다.
가장 왼쪽에 존재하는 몬스터의 좌푯값을
구했으니 나머지 몬스터들의 좌푯값을 구해줄 수 있죠.
( 이때 모든 몬스터는 일정한 크기로
일정한 거리를, 같은 방향으로 움직이며
일정한 간격을 두고 생성된다고 가정 )
여러 몬스터의
간격을 구하고자 한다면
일단 윈도우의 가로 해상도에서 몬스터가 이동하는 거리와
몬스터의 크기의 절반을 합한 값에 2를 곱한 값을
빼주면 됩니다.
1280 - ( 50 + 25 * 2 ) * 2 이므로 1130 입니다.
몬스터가 이동하는 거리와
몬스터의 크기의 절반을 합한 값에
2를 곱하는 이유는 가로 해상도에서
왼쪽과 오른쪽 모두 75만큼 빼주기
위해서입니다.
그리고 이 값을 몬스터 오브젝트의 개수에서
1을 뺀 값으로 나누어 줍니다.
몬스터 오브젝트의 개수에서
1을 뺀 값으로 나누어 주는 이유는
만약 오브젝트가 5개라면 사이 간격의
개수는 4개죠? 오브젝트가 10개면
사이 간격의 개수는 9개일 겁니다.
사이 간격의 개수는 오브젝트 개수에서
항상 1이 적은 개수이므로 1을 빼 주어
간격의 값을 구하는 겁니다.
1130에서 몬스터의 개수 1을
빼 준 값으로 나누어 주면 되므로
1130 / 4 = 282 입니다.
즉 각 몬스터의 간격은 282라는 것이죠.
32번째 줄에 있는 for 문을 통해
본격적으로 몬스터를 생성해 줄 겁니다.
30번째 줄을 보시면 CMonster형 포인터를 선언하였고
34번째 줄에서 동적 할당된 공간을 가리켰습니다.
CMonster형 포인터로 가리키는 이유는
만약 부모 클래스인 CObject형 포인터로
자식 클래스인 CMonster형 공간을 가리키게 되면
CMonster 공간에 존재하는 몬스터의 속도, 최대 이동 거리 등
멤버에 접근할 수 없게 됩니다.
부모 클래스는 자식 클래스에 접근할 수 없기 때문이죠.
35번째 줄에서 몬스터가 움직일 기준점을
SetCenterPos 함수를 통해 설정해 주고 있습니다.
i가 0일 때, 즉 첫 번째 몬스터의
움직일 기준점의 x 좌푯값은
( 50 + 50 / 2 ) + ( 0 * 282 ) 일 겁니다.
y 좌푯값은 고정 50입니다.
i가 증가할수록 첫 번째 몬스터에서 282만큼
멀어지는 위치에 몬스터가 생성되겠죠.
36번째 줄에서는 SetPos 함수를 통해
몬스터의 좌푯값을 설정해 주고 있습니다.
여기서 좌푯값은 몬스터의 중앙 좌푯값으로,
몬스터가 움직일 기준점과 같은 곳을
몬스터의 초기 좌푯값으로 설정해 주기 위해
GetCenterPos 함수의 반환값을 몬스터의 좌푯값으로
설정하고 있습니다.
37번째 줄에서 SetScale 함수를 통해
몬스터의 크기를 설정하고 있습니다.
SetScale 함수에 인자로
CreateMonsterScale값을 주어
몬스터의 크기는 가로, 세로 50으로
설정되었습니다.
38번째 줄에서 SetMaxMoveDistance 함수를 통해
몬스터가 이동할 최대 거리를 설정하고 있습니다.
CreateMonsterMoveDistance값을 인자로,
즉 몬스터는 최대 50 픽셀까지 이동하게 되죠.
39번째 줄에서 AddObject 함수를 통해서
CMonster형 포인터 pObjMonster가 가리키던
동적 할당된 공간에 존재하는 몬스터와
해당 몬스터가 속할 그룹값을 인자로 넘겨
씬이 보유하고 오브젝트가 모인 vector에
인자로 넘긴 몬스터를 vector에 삽입합니다.
이후 프로그램을 실행해 보면
5개의 몬스터(위쪽 사각형)가 일정한 간격으로 생성되어
좌우로 움직이는 것을 확인할 수 있으며
키 입력을 통해 플레이어(큰 사각형)를 움직일 수 있음을
확인할 수 있습니다.
또한 CPlayer 클래스와 CMonster 클래스는
똑같이 CObject 클래스를 상속받은 자식 클래스이지만
순수 가상 함수인 update 함수를 각 자식 클래스에서
다르게 구현하였기 때문에 키 입력이 발생하면
플레이어 오브젝트만 움직이고
몬스터 오브젝트에는 영향을 주지 않습니다.
강의 출처 : https://www.youtube.com/watch?v=fyNb6tK0vGU&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=17
'Win32 API > 기초' 카테고리의 다른 글
Win32 API 기초 : 기초 수학 (1) (0) | 2024.07.09 |
---|---|
Win32 API 기초 : Object (2) (0) | 2024.07.08 |
Win32 API 기초 : Scene Manager (2) (0) | 2024.07.05 |
Win32 API 기초 : Scene Manager (1) (0) | 2024.07.04 |
Win32 API 기초 : Key Manager (2) (2) | 2024.07.03 |