저번 까지는 화면에 우주선을 움직이게 까지 했다.
이번에는 게임의 하이라이트 충돌이다.

물체간에 부딛히는 것을 인식하게 하는 것이다.
여기에는 실제로 많은 물리 지식과 수학적 지식이 필요하다.
나도 책을 여러번 읽고 이해를 할 수 있었다.
여기에 충돌에 관한 물리적 내용을 다루지는 않을 것이다.
실제 자세한 구현과 코드의 내용을 보려면 직접 책을 참고하는 것이 좋을 것이다!
(2d 게임 프로그래밍, 찰스 켈리)

이 글의 목적은 게임 상의 우주선이 게임 화면 안에서 갇혀있는 것이다.
우주선이 왔다리 갔다리 하는데 화면 끝으로 이동했을 때 넘어가는 것이 아니라
공이 벽에 튕기듯이 튕겨서 게임 화면 안에서 계속 돌아댕기는 프로그램을 만들 것이다.

충돌을 다루기 위해 벡터를 사용해야한다.
DirectX에서 제공하는 벡터 타입이다.
벡터 연산들도 제공한다.

그러나 이름이 길고 쓰기 불편하므로 우리가 이해하기 쉽게 간단한 이름으로

캡슐화 시켰다.


(벡터 재정의)



(벡터 연산)


위와 같이 벡터 타입과, 벡터 연산들을 우리가 사용하기 편하게 함수안에 넣어두었다.

그 다음 충돌을 다루게 하기 위해 Entity 라는 이름의 클래스를 만들어 볼 것이다.
Entity는 해석하면 객체이지만, 여기서는 고유명사처럼 Entity라고 말할 것이다.

Entity는 우리가 충돌을 인식하게 하기 위해 우리가 원하는 객체들을 Entity 클래스로 선언할 것이다.

그리고 각종 충돌에 관한 함수들을 집어 넣을 것이다.



(entity 이름 영역)


Entity의 이름 영역에는 충돌타입과 중력상수가 적혀있다.
충돌 타입은
원형 충돌
사각형 충돌
회전 사각형 충돌
복합 충돌
으로 구분할 수 있다.

그러한 것들을 이름영역에 정의해두었다.

다음은 Entity 클래스의 속성 변수들을 볼 것이다.



(속성 변수)


가지고 있는 속성 변수에는 위와 같다.

그리고 protected 영역에 선언된 함수들이다.



(protected 함수)


위 함수들은 다른 클래스 밖에서 호출되지 않고 내부에서만 호출되게 하기 위해 protected 안에서 선언되어있다.

이제 public 영역의 함수들을 볼 것이다.



(생성자)


생성자이다.
초기값들을 설정하는 부분이다.

다음으로는 초기화 함수이다.



(초기화 함수)


초기화 함수가 조금 특이하다.
첫번째 인자가 게임포인터라는 것만 빼면 Image의 초기화 함수랑 똑같다.
심지어 내부에서 Image의 초기화 함수를 호출한다.

게임 포인터에서 입력을 input으로 저장해둔다.
게임 중 입력 받아놓은 지역변수 값들을 가져와서 저장해두는 역할을 한다.

(activate() & update())


activate() 함수는 Entity 객체가 충돌관련 연산이 일어나게 할지 활성화 여부를 활성화 시키는 것이다.

update 함수는 계산된 속도 변화를 적용시킨다.
이전에 충돌과 관련된 계산이 끝난 후 변화 속도를 deltaV에 저장해 두고 여기서 적용시킨다.

그 후 Image의 update를 호출한다.


(ai 함수)


AI 함수는 인공지능에 관련된 함수이다.
지금 현재 우리의 객체에는 인공지능이 들어가지 않는다.
그렇지만 Entity 클래스의 객체를 인스턴스화 할 수 있게 하기 위해 빈 함수로 제공해둔다.

이렇게 하면 Entity 클래스에서 객체를 직접 생성할 수 있다.



(충돌 함수)


충돌에 관련된 함수이다.
위 함수는 각 객체의 충돌 타입을 확인 한 후 각 객체의 충돌 타입에 맞게 다시 충돌탐지 함수들을 호출한다.
각 충돌 탐지에 관한 함수들, 내용들은
시간이 된다면 따로 정리해서 올릴 예정이다.

그 후 이 전까지와 달라진 점이 있다.
이젠 Entity를 사용해 각 객체를 호출 할 것이다.
그러기 전에 행성과 우주선이라는 클래스를 따로 만들 것이다.
그리고 이 우주선과 행성 클래스는 Entity 클래스를 상속한다.
우주선과 행성 클래스를 따로 만드는 이유는 각 객체의 값들을 따로 관리하는게 좋기 때문이다.
여기저기 섞여 있는 것보다는!

planet 부터 보자!


(planet 헤더)


헤더 파일에 planet 의 속성들이 들어있다.



(planet 생성자)


생성자는 간단히 이름영역에서 설정한 값들을
변수에 초기값으로 설정하는 역할을 한다.

다음은 ship 클래스이다.


(ship 헤더)


planet 과 같이 속성 설정값들이 있다. ( 이 값들은 애니메이션과 관련된 설정들도 있고 지난 글들에서 다루어왔던 값들이므로 설명은 생략하겠다. )

planet과 다른점은 update 함수가 있다.

이 update 함수는 우주선이 벽에 부딛히고 튕겨져 나오는 것을 작성하기 위해 여기서 상속받아서 작성하였다.


(ship의 생성자)


planet과 마찬가지로 ship의 생성자 또한 이름영역에서 설정된 값들을 가지고 초기값을 설정하며 만들어진다.



(update 함수)


update 함수이다.
이 함수에서는 맨 먼저 Entity 의 update가 실행된다. Entity의 update에서 충돌 결과의 움직임이 적용이 되고 Image의 update가 실행이 된 후이다. 마지막으로 화면에 그려지기 전에 이루어지는 update이다.

Entity의 update에서 적용된 속도 값들을 이용하여 해당 우주선의 화면상 위치를 잡는 모습이 보이고
추가적으로 우주선이 천천히 뱅글뱅글 돌아가도록 spriteData의 angle 값을 바꾸고 있는 모습도 보인다.

그 뒤로 이제 화면에 부딛히는 과정을 체크한 후 부딛히면 날라온 방향에서 부딛힌 축에 뒤집히게 -1을 곱하여 반대로 뒤집어준다.

이제 실제 spacewar 파일에서 우주선과 행성 등을 다시 선언해주어야한다.
이 전까지는 Image로 선언했기 때문이다.



(재선언)


이 때 우주선은 Ship으로 행성은 Planet으로 선언했다.
둘 모두 Entity를 상속했고 Entity이고 Entity 는 Image를 상속했다.

실행해보면 우주선이 게임화면에서 계속 튕기면서 돌아댕기는 모습을 볼 수 있다.



(우주선 표류)



(우주선 표류)


다음에는 두 객체가 충돌하는 것에 관련해서 알아볼 것이다.
아까 Entity에서 충돌에 관련해 만들어두었지만 아직 안썼었다.

(* 참조 - 2d 게임 프로그래밍, 찰스 켈리)

게임 프로그램에 이미지를 그리는 것을 해볼 것이다.

우리가 사용할 것은 스프라이트이다.
스프라이트는 2D 이미지를 그리기 위해 설계된 DirectX 그래픽스 API의 일부이다.
(꼭 2D 이미지만을 위한 것은 아니지만 거의 주로 2D이미지에 사용된다.)

그렇다면 스프라이트를 이용하기 위해 스프라이트 포인터를 자주 사용하므로 간단하게 재정의한다.


(스프라이트 재정의)


앞으로 LP_SPRITE로 사용할 것이다.

그리고 스프라이트 포인터를 담고 있을 변수도 추가해준다.

스프라이트는 그래픽을 그리는 일을 하므로 그래픽 클래스에 변수를 만들것이다.


(스프라이트 포인터 변수)


그렇다면 이제 할 일은 스프라이트를 생성하고 그 스프라이트 주소를 우리가 만든 스프라이트 포인터 변수에 담아야 한다. (만들어야 사용할 수 있으므로 당연한 사고의 흐름이다!)



(스프라이트 생성)


스프라이트 생성함수는 위와 같다.
D3DXCreateSprite를 사용하고 첫번째 인자에 디바이스 인터페이스를 넘겨주고, 두 번째 인자는 생성될 스프라이트 객체의 주소를 담을 포인터 변수이다.

게임 엔진을 통해 수행되는 모든 스프라이트 그리기 작업은 하나의 DirectX 스프라이트로 처리된다. 앞으로 지금 만든 스프라이트를 이용해 그리기 작업을 수행할 것이다.

그 다음으로 이미지 파일로 부터 이미지를 가져와 텍스처를 만들것이다.
텍스처?
텍스처는 그래픽스에서 사용하는 프리미티브 타입에 적용할 수 있는 그림이다. 이 경우 텍스처는 스프라이트이다. ( 텍스처에 관해 궁금하신 분들은 구굴링! )
텍스처는 여기서 쉽게 말해 그릴 그림이라고 생각하고 넘어가도 된다. ( 2D 이미지를 그리는 경우 )

쉽게 정리해서
파일로부터 이미지를 가져와 그릴 수 있는 텍스쳐로 만든 후
스프라이트를 이용해 그 텍스쳐를 화면에 그린다!

그렇다면 파일로부터 이미지를 가져와 텍스처를 만드는 것을 계속해보자!



(텍스처 가져오기)


D3DXCreateTextureFromFileEx 함수를 이용해 파일로 부터 이미지를 가져오고 그 이미지로 텍스처를 만드는 작업을 한다. 맨 마지막 인자에 포인터 변수가 들어가는데 그 변수에 만들어진 텍스처가 저장된다.

중간 인자 중 텍스처의 폭과 높이가 있는데 이 정보를 파일로 부터 가져올 수 있다.
D3DXGetImageInfoFromFile을 사용하면 되는데 이 것을 포함해 텍스처를 로드하는 함수인

loadTexture의 완성 코드는 아래와 같다.


(loadTexture)


loadTexture함수는
파일로 부터 폭과 높이를 가져오고
그 후 텍스처를 만드는 함수이다.

이제 스프라이트를 그리는 작업을 살펴볼 것이다.
스프라이트는 신 사이에서 그려야한다.
신 시작 - (그리기) - 신 끝
위 처럼 신의 시작과 끝 사이에서 그려져야한다.

위의 원칙을 적용해 renderGame 함수를 적어보면 아래와 같다.


(renderGame)


renderGame함수는 게임 아이템을 그리는 함수이다.
렌더링 시작할 때 그래픽 함수에서 신을 시작하는 함수를 호출하고
그 후에 render() 를 호출한다. (여기서 그리기 작업이 들어가는 것이다. )
그리고 다 그렸으면 신을 종료하는 함수를 호출하여 그리는 작업을 마무리한다.
(지금까지 그리는 것들은 백버퍼에 그려지고 있는 것이다.)

그 후 백 버퍼를 화면에 표시해준다.

신을 시작하고 종료하는 함수는 아래와 같다.



(신 시작과 끝함수)


beginScene 함수는 신을 시작하는 함수이다.
신을 시작하기 전에 백버퍼를 지워 준 후 신을 시작한다.
endScene 함수는 신을 종료하는 함수이다.
내부적으로 디바이스 인터페이스에서 BeginScene과 EndScene을 호출한다.

신 시작 함수 호출과 스프라이트 그리기 함수 사이에서 한가지 작업이 더 들어가야한다.
그리기 전에 그릴 텍스처에 대한 설정이다. 크기 조정, 회전 및 위치를 지정하는 작업이다.

이 작업은 행렬 수학을 이용해 계산이 되는데 우리는 직접 계산해서 하는 것이 아니라 DirectX의 함수를 이용할 것이다.


(행렬 적용 및 그리기)


D3DXMatrixTransformation2D 함수를 사용하면 크기 조정, 회전 및 위치를 지정할 수 있는 변환 행렬을 구할 수 있다. 첫번째 인자로 만들어진 행렬이 저장된다.

만들어진 행렬을 SetTransform 함수에 넣어 호출해주면 해당 행렬을 스프라이트에 적용하게 되는 것이다.

그 후 Draw 함수를 이용해 그리기 작업이 들어가는 것이다.

이렇게 그리는 텍스처에 대한 속성값들이 있다. 이것들을 위한 구조체를 만들어서 정리하면 편하다.


(스프라이트 속성)


스프라이트 속성들을 모아서 구조체로 만들었다.

아래는 스프라이트 속성들을 설정하는 코드이다.


(스프라이트 속성 설정)


위 코드는 drawSprite 함수의 윗부분에 해당하고 이 코드들이 실행이 된 후 속성이 다 설정이 되면 위에서 봤던 행렬을 만들어준 후, 적용하고 그리는 단계로 drawSprite 함수가 이루어져 있다.

정리하면!
1. 그래픽 클래스에서 신을 시작한다.
2. 스프라이트의 Begin 함수를 호출
3. 스프라이트 속성 설정
4. 스프라이트 행렬 만들기
5. 스프라이트 행렬 적용
6. 스프라이트 그리기
7. 스프라이트의 End 함수 호출
8. 그래픽 클래스에서 신을 종료한다.


아래는 색상을 앞으로 이용하기 편하게 만들어 놓은 이름 공간이다.


(색상 namespace)


예를 들어 오렌지색을 사용하고 싶다면 코드에
graphicsNS::ORANGE라고만 입력하면 된다.

그래픽 클래스에 추가한 loadTexture 함수는 텍스처 데이터를 D3DPOOL_DEFAULT 메모리로 불러온다. 일반적으로 이 곳은 비디오 메모리인데, 다른 프로그램이 그래픽 디바이스를 사용하고 있다면 사용할 수 없거나, 로스트 상태가 된다. 그렇기에 로스트 상태가 되면 디바이스를 되찾는 과정이 필요하다.

게임에서 텍스처를 사용할 때 각 텍스처를 불러오고 관리하기 위해 TextureManager 객체를 만들 것이다.


(초기화함수)


초기화 함수에는 loadTexture를 이용해 해당 이미지를 가져와 텍스처를 생성하는 작업을 한다.

로스트 상태를 관리하기 위한 함수는 아래와 같다.


(로스트 관리)


이전 다른 디바이스와 마찬가지로 로스트 상태라면 해당 리소스를 해제해주어야하고 다시 얻어와야한다.

그 외 TextureManager 객체에 대한 데이터를 가져올 수 있는 함수들도 만들어준다.


(TextureManger 함수들)


이 함수들을 이용해 해당 텍스처에 대한 정보를 가져올 수 있다.
마지막으로 스프라이트를 그리고 관리하는데 필요한 모든 코드들을 Image 클래스로 통합할 것이다.

Image 클래스를 이용해 그리기는 작업을 할 것이다. (지금까지 만든것들을 Image 클래스에 통합하는 과정이다.)


(이미지 클래스 초기화함수)


초기화 함수에서는 필요한 객체를 가져오고, 해당 이미지의 텍스처 정보를 가져오는 과정이다.

이미지 클래스의 draw 함수이다.


(그리기 함수)


이 함수에서 그래픽의 drawSprite 함수를 호출한다. 그러면 우리가 지금까지 확인한 일련의 과정들이 실행되는 것이다.

draw 함수를 두개 만들었다. 하나는 위와 같고 다른 하나는 특정 sprite를 지정하여 그리는 함수로 아래와 같다.


(특정 스프라이트를 지정하여 그리는 함수)


그 외에 이미지 클래스의 데이터를 가져오고 설정하는 get과 set 함수들을 추가해준다.

그러면 지금까지 만든 내용들로 게임의 이미지를 그려볼 것이다.
성운 이미지를 배경으로 쓰고 행성 이미지를 그 위에 그리는 것이다.

Spacewar.h 함수에 게임 아이템 2개에 대한 텍스처와 이미지를 만들어준다.


(텍스처와 이미지)


그리고 초기화 과정에 텍스처 초기화과정과 이미지 초기화 과정을 넣어준다.


(게임 초기화 함수)


처음에 텍스처 초기화 과정이 이루어지면 그 텍스처매니저를 이미지 초기화 함수에 사용한다.
그리고 행성을 화면 중앙에 배치하는 코드를 넣었다.

이렇게 되면 그리는 작업의 준비는 끝난 것이다.

그리는 함수인 render() 함수에 해당 이미지를 그리는 함수를 호출하면된다.


(그리기)


위 render 함수 이전에 실행되는 것이 그래픽 클래스의 신 시작이고 render()함수 가 종료되고 실행되는 것이 크래픽 클래스의 신 종료 함수인 것을 기억해야한다.

그리는 것은 이미지 클래스의 draw(우리가 만든것) 을 이용한다.


(로스트 처리)


로스트 처리에 관한 부분도 넣어준다. 각각의 아이템마다 처리해준다.

그리고 빌드 후 실행해보자!


(실행 화면)



프로그램에 우리가 원하는 그림이 그려지는 걸 확인 할 수 있다.

(* 참조 - 2D 게임 프로그래밍, 찰스 켈리)

+ Recent posts