정보공간_1

OpenGL로 만드는 2D 비행슈팅 게임. 본문

IT 놀이터/IT Storehouse

OpenGL로 만드는 2D 비행슈팅 게임.

알 수 없는 사용자 2011. 10. 8. 03:28

2D 슈팅게임 (1945)로써, 처음 게임을 제작하는 사람들에게 많은 예제가 되고 간편하게 접근할 수 있는 게임 이고, 게임을 개발함에 있어 필요한 노하우를 제공하는 것을 주제로 잡아 진행하고 있습니다.

-------------------------------------------------------------------------------------------
// chapter 1. 오픈GL 셋팅하기
-------------------------------------------------------------------------------------------



  1. #include <glut.h>                    
  2. #include <gl.h>                          
  3. #include <glu.h>   
  4. #include <glaux.h>    //텍스쳐 파일 불러오는데 필요한 라이브러리   
  5. #pragma comment(lib, "glaux.lib")   
  6.   
  7. int main(int argc, char** argv) {   
  8.     glutInit(&argc,argv);                  
  9.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);      
  10.        
  11.     glutInitWindowSize(600, 700);          
  12.     glutInitWindowPosition(0, 0);    
  13.     glutCreateWindow("1945 OpenGL");   
  14.     glClearColor (0.0, 0.0, 0.0, 1.0);   
  15.   
  16.     //OpenGL 셋팅   
  17.     glMatrixMode(GL_PROJECTION);       
  18.     glLoadIdentity();       
  19.     glOrtho(0.0, 60.0, 0.0, 70.0, -30.0, 30.0);   
  20.   
  21.     glClearDepth(1.0f);   
  22.     glEnable(GL_DEPTH_TEST);   
  23.     glDepthFunc(GL_LEQUAL);   
  24.        
  25.     glEnable(GL_TEXTURE_2D);   
  26.     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   
  27.   
  28.     glutDisplayFunc(Render);    
  29.     glutKeyboardFunc(MyKeyboard);   
  30.     glutIdleFunc(FrameMove);   
  31.        
  32.     glutMainLoop();   
  33.   
  34.     return 0;      
  35. }  



기본적인 OpenGL 셋팅에서 게임을 만들때 꼭 필요한 콜백 함수는 그림을 그려주는 glutDisplayFunc
glutIdleFunc 입니다.

DisplayFunc에서 화면에 보여지는 모든 그림들을 그리는 작업을 하고, 

IdleFunc에서 위치이동, 연산 등 게임에서 돌아가는 모든 연산 작업이 이루어진다고 생각하시면 됩니다.

그래서 모든 객체들의 클래스들의 구조의 기본형태는 다음과 같습니다.

  1. class CGame {   
  2. public:   
  3.     CGame(void);   
  4.     ~CGame(void);   
  5.   
  6.     HRESULT Init();   
  7.     HRESULT Render();   
  8.     int FrameMove(float delta);   
  9.     HRESULT Destroy();   
  10. }  
 
게임 객체들은 초기화 과정을 생성자에서 하지 않는 이유는 캐릭터 같은경우 죽었을 때 초기화 값을 다시 넣어주기 위해서,
초기화 작업을 다시 해야하기 때문에 다시 생성자를 호출 할 수 없는 클래스 특성상 초기화 작업은 따로 메소드를 생성하여
작업하게 됩니다.

그리고 그리는 Render 메소드와 연산작업을하는 FrameMove 메소드 생성한 것을 다시 해제해 줄 수 있는 Destory를 
기본형태의 클래스 구조를 가지게 됩니다.


-------------------------------------------------------------------------------------------
// chapter 2. IdleFunc에서 게임 연산
-------------------------------------------------------------------------------------------

OpenGL의 glutIdleFunc 을 이용하여 연산을 하는 이유는 타이머 콜백을 이용하여  연산을 할 경우,
각 컴퓨터들의 성능에 따라 연산량이 많아졌을 때 타이머가 제시간에 작동하지 않는 오류를 줄여 주기 위하여
타이머를 이용하지 않고 IDLE을 이용하여 연산하게 됩니다.

이때 타이머가 아니라 IDLE을 이용하여 연산을 하기 위해서는 IDLE 함수 루프에 동작시간을 구해야 합니다.
한번 돌때의 루프 시간을 구하여 그 시간을 내가 원하는 상수에 곱해주기 되면, 내가 지정한 시간에 원하는 이동
포인트 만큼을 이동시킬 수 있습니다.


  1. //타임 관련 변수   
  2. float lastTime = (float)timeGetTime();   
  3. float timeDelta = 0.0f;   
  4.   
  5. void FrameMove() //Idle   
  6. {   
  7.     //루프 한번 도는데 걸리는 시간을 측정   
  8.     float currTime = (float)timeGetTime();   
  9.     timeDelta = (currTime - lastTime) * 0.001f;   
  10.   
  11.     //연산작업   
  12.   
  13.   
  14.   
  15.   
  16.     //   
  17.     lastTime = currTime;   
  18. }  


다음 공식을 보면 이해가 조금 더 쉽게 될 수 있습니다.

게임을 설계 할때 유저 비행기의 속도를 한 루프당 + 1px 만큼 움직이겠다고 정의하는 것이 아니라.
1초에 20px을 움직일 것이다라고 초 단위의 이동 량을 정의 합니다.

이렇게 할 경우 케릭터 위치를

위치 = 20 * 루프한번도는데 걸리는 시간 ;

으로 연산하게 되면 루프한번 도는데 걸리는 시간이 0.0005초 일때

20 * 0.0005 이기 때문에 1초가 됬을 때 정확히 20만큼 움직이는 연산을 할 수 있습니다.
( 이제 부터 모든 오브젝트의 연산은 TimeDelta 값을 곱하는 방식으로 진행 되게 됩니다. )


-------------------------------------------------------------------------------------------
// chapter 3. Render Frame 제한
-------------------------------------------------------------------------------------------

다음으로 그림을 그려주는 Frame률을 제한하는 작업에 대해 설명해 드리겠습니다.
사람이 눈으로 확인 할 수 있는 프레임은 평균적으로 30 프레임 이라고 합니다. 보통 평균 화질의 
영상이 30프레임률을 가지고 재생되고, 현재 3D 게임의 최고 프레임률을 60프레임정도로 제한 해서 
그려지고 있습니다. 이와 같이 Render의 경우에는 사람에 눈에 보일때 무리 없을 정도만 보여주는게
성능향상에 도움이 되기 때문에 프레임률을 제한 하는 것이 좋습니다.


  1. //프레임 제어   
  2. int timeCount = 0;   
  3. DWORD ctime;   
  4.   
  5.     //프레임률 제어   
  6.     if(timeCount == 0)   
  7.     {   
  8.         ctime = timeGetTime();   
  9.         timeCount++;   
  10.     }   
  11.     //현재시간과 처음받아온 시간을 비교하여   
  12.     //60프레임 보다 크면 랜더하고 타임을 다시 받아옴   
  13.     if(ctime + (1000/90) <= timeGetTime())   
  14.     {   
  15.         FrameCnt++;   
  16.         glutPostRedisplay(); //그림을 그려주세요.   
  17.         timeCount = 0;   
  18.     }  
 
위에 소스와 같이 시간을 이용하여 프레임 률을 제한 하여 Render를 호출 할 수 있습니다.

-------------------------------------------------------------------------------------------
// chapter 4. Scene 관리
-------------------------------------------------------------------------------------------


Render 함수에 모든 소스를 작성하기엔 가독성에 매우 좋지 않다.
재사용성을 위해서라도 클래스로 나누는 작업을 하기 위해 처음으로 각 Scene들을 정의한다.

게임 로딩화면, 메인화면, 선택화면, 플레잉화면, 에필로그 등 각 장면들을 각각 하나의 Scene으로 정의한다.


  1. class CScene   
  2. {   
  3. public:   
  4.     CScene(void);   
  5.     virtual ~CScene(void);   
  6.   
  7.     virtual HRESULT Init() = 0; //게임에 필요한 생성   
  8.     virtual HRESULT Render() = 0;   
  9.     virtual int FrameMove(float delta) = 0;   
  10.     virtual HRESULT Destroy() = 0; //게임에 필요한 소멸   
  11. };  



  1. class CGameScene :   
  2.     public CScene   
  3. {   
  4. public:   
  5.     CGameScene(void);   
  6.     ~CGameScene(void);   
  7.   
  8.     HRESULT Init();   
  9.     HRESULT Render();   
  10.     int FrameMove(float delta);   
  11.     HRESULT Destroy();   
  12. };  


각 해당하는 씬을 만들고 씬의 기본형을 상속받아 재정의 하여 사용 한다.

이 Scene들은 Scene 매니져 를 만들어 관리하고 사용하게 만들면 좋다. 일단 Scene을 상속 받은

GameScene을 가지고 게임을 플레이 하는 환경을 세팅 한다.

맨처음 설명했던 것과 마찬가지로 Render()와 FrameMove()를 이용하여 각 객체들을 동작하는 메소드를

각 Scene에 맞게 정의 하여 준다.


-------------------------------------------------------------------------------------------
// chapter 5. Key Input
-------------------------------------------------------------------------------------------


게임에서 키들의 메세지를 받기 위해서 여러가지 방법이 있다. Window에서 제공해주는, 콜백 함수를 이용해도
되지만 메세지 방식을 이용하게 되면, 한 메세지에서 한키의 입력값 밖에 받아 올 수 없기 때문에,
게임엔 적합하지 않다.  그래서 다음과 같이 KeyState 값을 가지고 & 연산을 이용하여 키의 눌림여부를 판별한다.


  1. //0x8000 : 키가 눌렸다   
  2.     BOOL up = ((GetAsyncKeyState(VK_UP) & 0x8000) == 0x8000);   
  3.     BOOL down = ((GetAsyncKeyState(VK_DOWN) & 0x8000) == 0x8000);   
  4.     BOOL bleft = ((GetAsyncKeyState(VK_LEFT) & 0x8000) == 0x8000);   
  5.     BOOL bright = ((GetAsyncKeyState(VK_RIGHT) & 0x8000) == 0x8000);  

다음 BOOL 변수에 키가 눌렸을 때 TRUE 값을 반환하게 하는 함수 이다.

이 BOOL 변수들로 키의 눌림 여부를 판별하여 각 키에 따른 동작을 정의하게 되면, 여러키를 동시에 처리 할 수 있다.

-------------------------------------------------------------------------------------------
// chapter 6. Render 와 Animation
-------------------------------------------------------------------------------------------

2D 게임의 기본적인 오브젝트를 그리는 방식은 사각형을 그리고 그 위에 텍스쳐를 씌워 그리는 방식을 사용한다.

다음은 OpenGL을 사용하여 오브젝트를 그리는 방법을 설명하겠다.





  1. glEnable(GL_BLEND);   
  2. glBlendFunc(GL_DST_COLOR, GL_ZERO);   
  3. glBindTexture(GL_TEXTURE_2D, MyTextureObject);   
  4.   
  5. glBegin(GL_POLYGON);   
  6. glColor3f(1.0f, 1.0f, 1.0f);   
  7. glTexCoord2f(((int)animation + 0) / 3.0f, 0.51f);  glVertex3f(m_rPos.left-4.0f, m_rPos.bottom, 0.0);   
  8. glTexCoord2f(((int)animation + 1) / 3.0f, 0.51f);  glVertex3f(m_rPos.right, m_rPos.bottom, 0.0);   
  9. glTexCoord2f(((int)animation + 1) / 3.0f, 0.99f);  glVertex3f(m_rPos.right, m_rPos.top, 0.0);   
  10. glTexCoord2f(((int)animation + 0) / 3.0f, 0.99f);  glVertex3f(m_rPos.left-4.0f, m_rPos.top, 0.0);   
  11. glEnd();   
  12.   
  13.   
  14. glBlendFunc(GL_ONE, GL_ONE);   
  15.        
  16. glBegin(GL_POLYGON);   
  17. glColor3f(1.0f, 1.0f, 1.0f);   
  18. glTexCoord2f(((int)animation + 0) / 3.0f, 0.01f);  glVertex3f(m_rPos.left-4.0f, m_rPos.bottom, 0.0);   
  19. glTexCoord2f(((int)animation + 1) / 3.0f, 0.01f);  glVertex3f(m_rPos.right, m_rPos.bottom, 0.0);   
  20. glTexCoord2f(((int)animation + 1) / 3.0f, 0.49f);  glVertex3f(m_rPos.right, m_rPos.top, 0.0);   
  21. glTexCoord2f(((int)animation + 0) / 3.0f, 0.49f);  glVertex3f(m_rPos.left-4.0f, m_rPos.top, 0.0);   
  22. glEnd();   
  23.   
  24. glDisable(GL_BLEND);  


glVertex3f 를 이용 삼각형 두개를 그려 사각형을 만들고 그앞에 TexCoord를 이용하여 텍스쳐의 좌표를 지정하여

텍스쳐를 그리는 OpenGL의 전형적인 방법이다.

여기서 같은 사각형을 블랜드 옵션을 변경하여 두번 그리는 이유는 Bitmap 그림파일을 원하는 부분만 그리기 위해서는

마스크 값을 이용하여 합산하여 원하는 부위만 그리는 방식을 사용할 수 있다.

일단 이미지의 윗부분을 그려 마스크를 두고, 원본 이미지와 합산하여 원하는 부위의 이미지만을 출력할 수있다.

여기서 Texcoord의 좌표를 원하는 시간마다 변경하여 케릭터의 모습을 애니메이션 시킬 수 있다.

- 신촌 멤버십 3D시그 작품전! -
공정훈님 작품



김소영님 작품




김정인님 작품


박성진님 작품




곽동현님 작품(게임공모전 준비로 공모전 출품작으로 대처)



김수진님 작품




김근직님 작품



이지연님 작품