(CGP) 10장. 하트담기 게임


하트담기 게임 이전 장에서 제작한 툴과 연동하기 위한 최소한 형식이 적용된 게임이다.

 

 

하하 유튜브 동영상 강의 주소

 

(1) http://youtu.be/8W1gqSXAOhQ

(2) http://youtu.be/F_BhmOCXriM

(3) http://youtu.be/-xe8xzJvCbQ

(4) http://youtu.be/Htoms-ZgsF0

  


10.1 기획

 

■ 스토리

 

하트 바구니에서 떨어지는 하트를 막대기로 담아라.

 

■ 게임 방식

 

하트 바구니는 j, l키로 좌우 이동을 하며 스테이지마다 정해진 목표 하트 수만큼 막대기로 받아야 한다. 제한 시간은 없으며 하트가 다 떨어지면 하트 바구니는 자동으로 멈춘다.

 

■ 제한 사항

 

툴에서 저장한 데이터를 읽어 각 스테이지에 적용하고 스테이지마다 막대기 길이와 하트가 떨어지는 시간 간격, 그리고 이동 시간을 다르게 적용한다.

 

■ 기획 화면

 

[그림 10-1] 게임 기획 화면

 

 


10.2 실행 화면

 

 

 

 

  

[그림 10-2] 메인 화면

 

[그림 10-3] 스테이지 화면

 

[그림 10-4] 게임 진행 화면

 

[그림 10-5] 미션 성공 화면

 

[그림 10-6] 미션 실패 화면

 

[그림 10-7] 결과 화면

 

10.3 게임 제작 로드맵

 

하트 담기 게임은 앞장에서 제작한 슛 골인 게임과 유사하다.

먼저 하트 바구니가 레일 위를 이동하는 것은 슛 골인 게임에서 골대가 이동하는 원리와 동일하다. 그리고 하트와 막대기의 충돌은 공과 골대의 충돌과 동일한 내용이다.

그러므로 앞서 제작한 슛 골인 게임을 상기하면서 아래의 게임 로드맵만으로 먼저 게임 제작을 해 보고 단계별 프로그래밍을 따라 제작해 보자.

 

[STEP 01]

 

[STEP 02]

 

[STEP 03]

 

[STEP 04]

 

[STEP 05]

[그림 10-8] 게임 제작 로드맵

 

 


10.4 단계별 프로그래밍

 

 

[STEP 01]

 

[그림 10-9] 1단계 제작 로드맵

 

■ 파일 읽기

 

툴에서 저장한 정보 파일은 게임의 스테이지를 초기화할 때 설정하기 위한 파일이다.

파일을 읽기 부분은 앞장에서 툴을 제작할 때 이미 구현했던 부분이다.

툴과 게임에서 파일 읽기의 차이점이 있다면 툴은 데이터를 입력, 수정, 삭제를 하기 위해 링크드 리스트를 사용하는 반면에 게임은 이미 툴에서 편집된 고정 데이터를 사용하므로 링크드 리스트와 같은 메모리 관리가 필요 없다.

그러므로 파일의 데이터만큼 메모리를 할당하고 데이터를 읽어 게임에 적용하면 된다.

 

[실습 예제 10-1]

 

9장 하트 게임 툴에서 저장한 파일을 읽어 [그림 10-10]과 같이 출력하는 프로그램을 작성해 보자. 참고로 툴에서 저장한 파일 형식은 아래와 같다.

 

 

typedef struct _STAGE

{        

       int nBasketX;                 // Note: 하트 바구니의 x 좌표

       int nBasketY;                      // Note: 하트 바구니의 y 좌표        

       int nHeartCount;          // Note: Stage별 총 하트 수

       int nGoalHeartCount;              // Note: 목표 하트 수

       int nBarLength;           // Note: 막대기 길이

       clock_t BasketMoveTime;      // Note: 하트 바구니가 움직이는 이동 시간 간격

       clock_t BasketDownHeartTime; // Note: 하트가 떨어지는 시간 간격

} STAGE;

 

[소스 10-1] 스테이지 정보 정의

 

[그림 10-10] 파일 데이터 출력 화면

 

 

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

56

57

58

59

#include <stdio.h>

#include <conio.h>

#include <malloc.h>

#include <time.h>

 

typedef struct _STAGE

{        

       int nBasketX;                 // Note: 하트 바구니의 x 좌표

       int nBasketY;                      // Note: 하트 바구니의 y 좌표      

       int nHeartCount;          // Note: Stage별 총 하트 수

       int nGoalHeartCount;              // Note: 목표 하트 수

       int nBarLength;           // Note: 막대기길이

       clock_t BasketMoveTime;      // Note: 하트 바구니가 움직이는 이동 시간 간격

       clock_t BasketDownHeartTime; // Note: 하트가 떨어지는 시간 간격

} STAGE;

 

typedef struct _STAGE_INFO

{       

       int nStageCount;

       STAGE *pStage;

} STAGE_INFO;

 

STAGE_INFO g_sStageInfo;

 

int _tmain(int argc, _TCHAR* argv[])

{

     int i;

     FILE *fp = fopen( "stage.txt", "r" );

     fscanf(fp, "%d\n", &g_sStageInfo.nStageCount );

     g_sStageInfo.pStage = (STAGE*)malloc( sizeof( STAGE) * g_sStageInfo.nStageCount );

     for( i = 0 ; i < g_sStageInfo.nStageCount ; i++ )

     {

        fscanf( fp, "%d %d %d %d %d %d %d\n",

                                &g_sStageInfo.pStage[i].BasketMoveTime,                                     &g_sStageInfo.pStage[i].BasketDownHeartTime,                                       &g_sStageInfo.pStage[i].nBasketX,                                           &g_sStageInfo.pStage[i].nBasketY,                                           &g_sStageInfo.pStage[i].nHeartCount,                                               &g_sStageInfo.pStage[i].nGoalHeartCount,                                     &g_sStageInfo.pStage[i].nBarLength );

                

           printf( "이동시간간격: %d  다운시간간격: %d x: %d  y: %d \n 총하트수: %d

                   목표하트 수: %d 막대길이: %d\n",

                           g_sStageInfo.pStage[i].BasketMoveTime,

                           g_sStageInfo.pStage[i].BasketDownHeartTime,

                           g_sStageInfo.pStage[i].nBasketX,

                           g_sStageInfo.pStage[i].nBasketY,

                           g_sStageInfo.pStage[i].nHeartCount,

                           g_sStageInfo.pStage[i].nGoalHeartCount,

                           g_sStageInfo.pStage[i].nBarLength); 

     }

        

     fclose( fp );

     free( g_sStageInfo.pStage );

     _getch();

     return 0;

}

 

[소스 10-2] 파일 읽기

 

17행부터 21행까지의 STAGE_INFO 는 전체 스테이지의 데이터 구조를 정의한 구조체이다. 이 구조체를 통해 파일로부터 6행에서부터 15행까지 정의된 STAGE 정보를 몇 번 읽을 것인가가 결정된다. 특히 20행의 *pStage를 통해 가변적으로 메모리를 생성하므로 파일에 저장된 STAGE 개수는 중요하다.

 

29행은 현재 파일에 몇 개의 스테이지 정보가 있는지 그 개수를 읽는 부분이다.

이 부분은 9장의 툴에서도 강조했던 것으로, 그 개수를 알고 있으면 ‘for 반복문’을 사용하여 데이터를 읽기가 쉽다.

 

30행은 스테이지 개수만큼 메모리를 할당하는 부분이다.

여기서도 스테이지의 개수는 중요하게 사용된다.

 

31행에서부터 51행까지는 파일로부터 개별 스테이지 데이터를 읽어 내는 부분이다.

20행을 보면 원래 g_sStageInfo.pStage는 포인터 변수인데, 34행 이상을 보면 모두 일차원 배열 형식으로 사용하고 있는 것을 볼 수 있다.

이것이 가능한 이유는 포인터를 배열의 형식으로 사용해도 컴파일된 코드는 동일하기 때문이다. 즉 연속적인 메모리 할당된 포인터의 경우 배열 형식과 같이 사용해도 된다. 오히려 포인터보다는 배열 형식을 사용하는 것이 코드를 직관적으로 볼 수 있으므로 이 형식을 많이 사용한다.

이 형식을 사용할 때 주의할 점은 위와 같이 연속적인 메모리가 할당되었을 때만 사용해야 한다는 것이다. 왜냐하면 배열의 인덱스는 순차적으로 메모리에 접근하게 하기 때문이다. 다음의 코드가 이를 간단히 설명하고 있다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#include <stdio.h>

#include <malloc.h>

#include <conio.h>

 

int main(void)

{

    int *pData, i;

 

    pData = (int*)malloc( sizeof( int ) * 7 );

 

    for( i = 0 ; i < 7 ; i++ )

    {

        pData[i] = i;

        printf( "%d \n", pData[i] );

    }

  

    return 0;

}

 

[소스 10-3] 포인터를 배열 형식으로 사용

 

[그림 10-11] 포인터를 배열 형식으로 출력한 결과

 

■ 막대기 구조 설계 및 설정

  

- 속성

 

하트 게임에서 막대기는 단순히 여러 개의 블록을 연결한 것에 불과하지만 스테이지마다 변화를 줄 수 있는 요소가 되므로 좌표와 길이라는 속성을 가진다.

길이 정보에 따라 최대 7개의 블록 중에서 일부를 사용하게 된다.

 

또한 키 입력에 따라 막대기가 즉시 이동하는 것이 아니라 일정한 시간 간격으로 이동하므로 이동 시간 간격과 이전 이동 시각 속성이 있어야 한다.

그러므로 이와 같은 사항을 정리하면 아래 [표 10-1]과 같다.

 

 

① 막대기 길이

② x 좌표 7개

③ y 좌표

④ 이동 시간 간격

⑤ 이전 이동 시각

 

[표 10-1] 속성

 

위의 막대기 속성을 구조체로 정의하면 다음과 같다.

 

 

typedef struct _BAR

{

       int nBarLength;    // Note: 막대기 길이

       int nX[7], nY ;    // Note: 좌표    

       clock_t  MoveTime;

       clock_t  OldMoveTime; 

} BAR;

 

[소스 10-4] 속성 정의

 

- 이동 및 키보드 처리

 

이동은 j, l 키에 따라 좌우로 이동하도록 하지만 이동은 일정한 시간 간격으로 이동한다.

 

[실습 예제 10-2]

 

툴에서 저장한 데이터를 읽고 첫 번째 스테이지 데이터를 적용하여 좌우로 막대기가 이동하도록 게임 프레임워크를 적용하여 프로그래밍해 보자.

 

[그림 10-12] 이동하는 막대기

 

 

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

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

#include <stdio.h>

#include <windows.h>

#include <time.h>

#include <conio.h>

#include "Screen.h"

 

typedef struct _BAR

{

       int nBarLength;    // 막대기 길이

       int nX[7], nY ;    // 좌표    

       clock_t  MoveTime;

       clock_t  OldMoveTime; 

} BAR;

 

typedef struct _STAGE

{       

       int nBasketX;                  //  하트 바구니의 x 좌표

       int nBasketY;                       //  하트 바구니의 y 좌표  

       int nHeartCount;           //  Stage당 총 하트 수

       int nGoalHeartCount;               //  목표 하트 수

       int nBarLength;            //  막대기 길이

       clock_t BasketMoveTime;       //  하트 바구니가 움직이는 이동 시간의 간격

       clock_t BasketDownHeartTime;  //  하트가 떨어질 시간 간격

} STAGE;

 

typedef struct _STAGE_INFO

{       

        int nStageCount;            // 스테이지 개수

        STAGE *pStage;            // 스테이지 정보

} STAGE_INFO;

 

BAR g_Bar;

STAGE_INFO g_sStageInfo;

 

void Init()

{

     int i;

 

     // Note: 파일 읽기

     FILE *fp = fopen( "stage.txt", "r" );

     fscanf(fp, "%d\n", &g_sStageInfo.nStageCount );

     g_sStageInfo.pStage = (STAGE*)malloc( sizeof( STAGE) * g_sStageInfo.nStageCount );

 

     for( i = 0 ; i < g_sStageInfo.nStageCount ; i++ )

     {

        fscanf( fp, "%d %d %d %d %d %d %d\n",

                               &g_sStageInfo.pStage[i].BasketMoveTime,

                               &g_sStageInfo.pStage[i].BasketDownHeartTime,

                               &g_sStageInfo.pStage[i].nBasketX,

                               &g_sStageInfo.pStage[i].nBasketY,

                               &g_sStageInfo.pStage[i].nHeartCount,

                               &g_sStageInfo.pStage[i].nGoalHeartCount,

                               &g_sStageInfo.pStage[i].nBarLength );

     }

        

     fclose( fp );

 

      // Note: 막대기 초기화       

      g_Bar.nY = 22;

      g_Bar.nBarLength = g_sStageInfo.pStage[0].nBarLength; // 막대기의 길이

      g_Bar.OldMoveTime = clock();

      g_Bar.MoveTime = 40;

    

     for( i = 0 ; i < g_Bar.nBarLength ; i++ )

     {          

        g_Bar.nX[i] = 15 + 2*(i+1); // 15 컬럼부터          

     }

}

 

void Update()

{

}

 

void Render()

{

     int i;

 

     ScreenClear();

 

     for( i = 0 ; i < g_Bar.nBarLength ; i++ )

        ScreenPrint( g_Bar.nX[i], g_Bar.nY, "▣" );

        

     ScreenFlipping();

}

 

void Release()

{

     free( g_sStageInfo.pStage );

}

 

int main()

{

    int nKey, i;

    clock_t CurTime;

 

    ScreenInit();

    Init();

 

    while( 1 )

    {

        CurTime = clock();

 

        if( _kbhit() )

        {

            nKey = _getch();

            switch( nKey )

            {

             case 'j' :

                    if( CurTime - g_Bar.OldMoveTime > g_Bar.MoveTime )

                    {

                        g_Bar.OldMoveTime = CurTime;

                        for( i = 0 ; i < g_Bar.nBarLength ; i++ )

                             g_Bar.nX[i] -= 1;

                    }

                      break;

              case 'l' :

                      if( CurTime - g_Bar.OldMoveTime > g_Bar.MoveTime )

                      {

                         g_Bar.OldMoveTime = CurTime;

                         for( i = 0 ; i < g_Bar.nBarLength ; i++ )

                              g_Bar.nX[i] += 1;

                       }

                       break;

               }

        }

 

        Update();

        Render();       

     }

     Release();

     ScreenRelease();

 

     return 0;

}

 

[소스 10-5] 막대기 이동

 

40행에서부터 56행까지는 파일 읽기 부분으로 9장의 [소스 9-18]과 같은 코드이다.

파일에 관련된 코드는 이미 툴에서 검증된 코드이므로 동일하게 복사하여 사용하면 된다.

 

66행은 특수 문자를 출력할 때 2컬럼 단위로 출력하기 위한 부분이다.

 

42행에서 스테이지 개수만큼 메모리가 할당되었으므로 프로그램 종료할 때에는 반드시 88행과 같이 메모리를 해제시켜 주어야 한다.

 

109행과 117행은 이동키가 입력되어도 즉시 이동하지 못하게 하기 위해 이동 시간 간격을 체크하는 부분이다.

 

 


STEP 02

 

[그림 10-13] 2단계 제작 로드맵

 

■ 하트 바구니

 

- 속성

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

 

하트 바구니는 좌우로 스스로 이동하면서 경계 영역과 충돌하면 이동 방향을 바꾼다.

5장의 슛 골인 게임에서 골대 방향을 바꾸기 위해서 -1을 사용했듯이 하트 게임에서도 이 방법을 사용하면 쉽게 이동 방향을 바꿀 수 있다.

나머지 이동 시간 간격에 따른 이동은 막대기의 이동과 동일하므로 [소스 10-2]의 109행과 117행을 참고하도록 하자.

 

하트 바구니는 일정한 시간 간격으로 하트를 출력해야 하므로 출력하기 위한 시간 간격과 이전 출력 시각에 대한 속성이 있어야 한다.

하트 개수는 [소스 10-1]의 스테이지 정보에 있는 nHeartCount로부터 데이터를 가져와 출력한다. 여기까지 살펴본 하트 바구니에 대한 속성을 정의하면 아래 [표 10-2]와 같다.

 

 

① 좌표 ( x, y )

② 이동 거리

③ 이전 이동 시각

④ 이동 시간 간격

⑤ 하트를 출력한 이전 시각

⑥ 하트를 출력하는 시간 간격

 

[표 10-2] 하트 바구니 속성

 

 

typedef struct _BASKET

{

        int nX, nY;

          int nDist;      

        clock_t OldMoveTime;

        clock_t MoveTime;         

          clock_t OldDownHeartTime;

        clock_t DownHeartTime;    

} BASKET;

 

[소스 10-6] 하트 바구니 속성 정의

 

[실습 예제 10-3]

 

위에 정의한 BASKET 구조체를 이용하여 하트 바구니가 일정하게 좌우로 이동하는 프로그램을 게임 프레임워크에서 프로그래밍해 보자.

 

[그림 10-14] 좌우로 이동하는 하트 바구니

 

 

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

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

#include <stdio.h>

#include <windows.h>

#include <time.h>

#include "Screen.h"

 

typedef struct _BASKET

{

       int nX, nY;

       int nDist;         

       clock_t OldMoveTime;

       clock_t MoveTime;          

       clock_t OldDownHeartTime;

       clock_t DownHeartTime;     

} BASKET;

 

BASKET g_sBasket;

int g_nHeartDownCount = 0;

 

void Init()

{

     // Note: 하트 바구니 초기화

     g_sBasket.nX = 20;

     g_sBasket.nY = 2;

     g_sBasket.OldMoveTime = clock();

     g_sBasket.MoveTime = 200;

     g_sBasket.nDist = 1;

     g_nHeartDownCount = 0;

}

 

void Update()

{

      clock_t CurTime = clock();

 

     if( (CurTime - g_sBasket.OldMoveTime) > g_sBasket.MoveTime )

     {

        g_sBasket.OldMoveTime = CurTime;

        g_sBasket.nX += g_sBasket.nDist;

 

        if( g_sBasket.nX < 3 || g_sBasket.nX + 1 > 53 ) // 경계 영역

            g_sBasket.nDist = -1 * g_sBasket.nDist;

      }

}

 

void Render()

{

     ScreenClear();

 

     ScreenPrint( g_sBasket.nX, g_sBasket.nY, "▦" );

 

     ScreenFlipping();

}

 

void Release()

{

}

 

int main(void)

{

 

    ScreenInit();

    Init();

 

    while( 1 )

    {

        Update();

        Render();       

    }

 

    Release();

    ScreenRelease();     

    return 0;

}

 

[소스 10-7] 하트 바구니의 이동 소스

 

40행은 39행의 경계 영역에 하트 바구니가 닿았을 때 이동 방향을 바꾸기 위한 부분이다.

항등수인 -1은 크기에 주지 않고 방향을 바꾸는 역할을 한다.

 

■ 하트

  

- 속성 

 

하트 바구니에서 뿌려지는 하트에도 출력하고자 하는 좌표 정보가 있어야 한다.

또한 하트는 막대기와 충돌하거나 경계 영역에 닿게 되면 소멸 상태가 되어야 하므로 생명이라는 속성이 있어야 한다.

이와 같은 속성을 정리하면 아래 [표 10-3]과 같다.

 

 

① 생명

② 좌표

③ 이동 거리

④ 이동 시간 간격

⑤ 이전 이동 시각

 

[표 10-3] 하트 속성

 

위의 사항을 구조체로 정의하면 아래와 같다.

 

 

typedef struct _HEART

{

        int nLife;                   // 생명

        int nX, nY;         // 좌표

        int nDist;              // 이동거리

        clock_t MoveTime;     // 이동 시간 간격

        clock_t OldMoveTime;  // 이전 이동 시각

} HEART;

 

[소스 10-8] 하트 속성 정의

 

- 이동과 출력

 

하트 바구니 속성을 살펴보면 하트를 출력하기 위한 이동 시간 간격이라는 속성이 있다.

이 속성에 의해 일정한 시간 간격으로 하트가 출력되며 이때 하트 바구니의 x, y 좌표는 곧 하트의 초기 x, y 좌표가 된다.

 

스테이지마다 개수가 다른 하트를 출력하기 위해 메모리를 사용하는 방법에는 두 가지가 있다.

 

첫째, 최대 메모리를 미리 확보해 놓고 일부를 사용하는 방법이다.

둘째, 적당하게 메모리를 확보해 놓고 필요할 때마다 사용하지 않는 메모리를 검색하여  사용하는 방법이다.

 

위의 두 가지 방법에는 장단점이 있다.

첫째 방법은 사용하지 않는 메모리가 발생할 수 있으므로 메모리 낭비가 발생할 수 있다는 단점이 있지만 사용하기가 쉽다는 장점도 있다.

둘째 방법은 첫째 방법에 비해 메모리 낭비는 발생하지 않지만 사용하지 않는 메모리를 검색하기 위한 시간이 걸릴 수 있다는 단점이 있다.

 

이 두 가지 방법 중에 이 게임에서는 첫째 방법을 사용한다.

 

[실습 예제 10-4]

 

좌우로 이동하는 하트 바구니에서 하트가 [그림 10-15]와 같이 떨어지도록 프로그래밍해 보자. 단, 최대 30개의 하트 중에서 20개만이 출력되도록 한다.

이 실습 예제는 [실습 예제 10-3]에 하트만을 추가하여 작성하면 된다.

 

[그림 10-15] 출력되는 하트

 

 

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

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

#include <stdio.h>

#include <windows.h>

#include <time.h>

#include "Screen.h"

 

typedef struct _BASKET

{

       int nX, nY;

       int nDist;         

       clock_t OldMoveTime;

       clock_t MoveTime;          

       clock_t OldDownHeartTime;

       clock_t DownHeartTime;     

} BASKET;

 

typedef struct _HEART

{

        int nLife;                    // 생명

        int nX, nY;          // 좌표

        int nDist;                // 이동거리

        clock_t MoveTime;       // 이동 시간 간격

        clock_t OldMoveTime;     // 이전 이동 시각

} HEART;

 

HEART  g_sHeart[50];

BASKET g_sBasket;

int g_nHeartDownCount = 0;

 

void Init()

{

     // Note: 하트 바구니 초기화

     g_sBasket.nX = 20;

     g_sBasket.nY = 2;

     g_sBasket.OldMoveTime = clock();

     g_sBasket.MoveTime = 200;

     g_sBasket.DownHeartTime = 500;

     g_sBasket.OldDownHeartTime = clock();

     g_sBasket.nDist = 1;

 

     g_nHeartDownCount = 0;               

}

 

void Update()

{

     int i;

     clock_t CurTime = clock();

 

     if( (CurTime - g_sBasket.OldMoveTime) > g_sBasket.MoveTime )

     {

        g_sBasket.OldMoveTime = CurTime;

        g_sBasket.nX += g_sBasket.nDist;

 

        if( g_sBasket.nX < 3 || g_sBasket.nX > 53 )

            g_sBasket.nDist = -1 * g_sBasket.nDist;

      }

 

      // 하트 초기화

     if( g_nHeartDownCount < 30 )

     {

         if( (CurTime - g_sBasket.OldDownHeartTime) > g_sBasket.DownHeartTime )

         {

             g_sBasket.OldDownHeartTime = CurTime;

             g_sHeart[g_nHeartDownCount].nLife = 1;

             g_sHeart[g_nHeartDownCount].nDist = 1;

             g_sHeart[g_nHeartDownCount].nX = g_sBasket.nX;

             g_sHeart[g_nHeartDownCount].nY = g_sBasket.nY + 1;

             g_sHeart[g_nHeartDownCount].OldMoveTime = CurTime;

               // 최소 100, 최대 399 밀리세컨드

             g_sHeart[g_nHeartDownCount].MoveTime = rand() % 300 + 100;

             g_nHeartDownCount++;

        }

      }

 

      for( i = 0 ; i < g_nHeartDownCount ; i++ )

      {

        if( g_sHeart[i].nLife == 1 )

        {

            if( CurTime - g_sHeart[i].OldMoveTime > g_sHeart[i].MoveTime )

            {

                g_sHeart[i].OldMoveTime = CurTime;

                g_sHeart[i].nY += g_sHeart[i].nDist;

                  if( g_sHeart[i].nY >= 22 ) // 하단의 경계 영역과 충돌

                  g_sHeart[i].nLife = 0; 

             }

         }

      }

}       

 

void Render()

{

     int i;

     char str[100];

     ScreenClear();

 

     ScreenPrint( g_sBasket.nX, g_sBasket.nY, "▦" );

     for( i = 0 ; i < g_nHeartDownCount ; i++ )

     {

        if( g_sHeart[i].nLife == 1 )

            ScreenPrint( g_sHeart[i].nX, g_sHeart[i].nY, "♡" );

      }

        

     sprintf( str, "출력된 하트 수 : %d", g_nHeartDownCount );

     ScreenPrint( 0, 0, str );

 

     ScreenFlipping();

}

 

void Release()

{

}

 

int main(void)

{

 

    ScreenInit();

    Init();

 

    while( 1 )

    {

        Update();

        Render();       

    }

 

    Release();

    ScreenRelease();     

    return 0;

}

 

[소스 10-9] 하트 출력 소스

 

 


STEP 03

  

[그림 10-16] 3단계 제작 로드맵

 

■ 충돌 체크

 

- 하트와 막대기 충돌

 

하트와 막대기 충돌은 앞장에서 제작한 슛 골인 게임과 동일하다. 먼저 막대기의 길이를 조사하고 막대기에 설정된 최상위 좌표와 최하위 좌표를 찾아 충돌 체크를 한다.

주의할 점은 하트와 막대기는 특수 문자이므로 항상 2컬럼의 크기를 가지므로 충돌 체크를 할 때 아래 그림과 같이 하트를 기준으로 막대기의 충돌 범위 양쪽을 체크해야 한다.

[그림 10-17] 하트의 왼쪽과 막대기 충돌 범위

 

[그림 10-18] 하트의 오른쪽과 막대기 충돌 범위

 

이와 같이 하는 이유는 하트의 일부가 아래 [그림 10-19]와 같이 충돌하는 경우가 발생하기 때문이다.

 

[그림 10-19] 하트의 일부가 막대기와 충돌한 경우

 

아래 [소스 10-10]은 하트와 막대기가 충돌하는 소스이다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

nLength = g_sStageInfo.pStage[0].nBarLength; // 0 스테이지인 경우

for( i = 0 ; i < 20 ; i++ )

{

   if( g_sHeart[i].nLife == 1 )

   {    
      if( g_sHeart[i].nY >= g_Bar.nY )

      {

        if( ( g_sHeart[i].nX >= g_Bar.nX[0] &&

               g_sHeart[i].nX <= (g_Bar.nX[nLength-1] + 1) )

          || ( ( g_sHeart[i].nX + 1 ) >= g_Bar.nX[0] &&

                  ( g_sHeart[i].nX + 1 ) <= (g_Bar.nX[nLength-1] + 1 )) )     

          {

             g_sHeart[i].nLife = 0; 

          }

      }

    }

}

 

[소스 10-10] 하트와 막대기의 충돌 소스

 

1행의 막대기 길이는 스테이지가 시작할 때 이미 결정되는 부분이다.

 

6행에서부터 15행까지는 막대기의 최상위 좌표와 최하위 좌표를 구해 하트와 충돌 체크를 하는 부분이다. 이 코드는 [그림 10-17]과 [그림 10-18]과 같이 하트의 일부가 포함되는 것까지 충돌 체크를 한다.

 

- 경계 영역과 충돌

 

하트 바구니의 좌표는 하트가 처음 출력될 때 초기 좌표가 된다. 이때 하트 바구니는 경계 영역 안에서 좌우로 이동하므로 하트 좌표도 좌우 경계 영역을 넘어서지 않는다.

결국 경계 영역의 충돌은 하단의 y 좌표에 대해서만 발생하므로 아래 [소스 10-11]의 5행부터 8행까지가 충돌 체크를 하는 부분이다.

 

 

1

2

3

4

5

6

7

8

9

10

for( i = 0 ; i < 20 ; i++ )

{

   if( g_sHeart[i].nLife )

   {

      if( g_sHeart[i].nY > 22 )

      {

        g_sHeart[i].nLife = 0;    

      }

   }

}

 

[소스 10-11] 경계 영역과 충돌 소스

 

 


STEP 04

[그림 10-20] 4단계 제작 로드맵

 

■ 게임 진행 제어와 기타

 

게임 진행을 하기 위한 스테이지 정보는 이미 툴에서 정의하였으므로 여기서는 툴에서 저장한 파일의 내용을 읽어 게임을 초기화하고 앞서 제작한 게임과 동일한 흐름으로 진행되게 제작하면 된다.

 

여기까지 툴과 게임을 연계시켜 살펴보았듯이 툴과 게임이 무관한 관계가 아닌 서로 데이터와 코드를 공유하는 관계라는 것을 알 수 있었다.

그리고 툴에서 작성한 검증된 코드는 게임에서 오히려 쉽게 사용되는 것을 알 수 있었다.

이제 단계별로 제작한 내용과 파일을 연관시켜 하트 게임을 완성해 보자.

 

좋은하루강의가 도움이 되셨습니까? 손가락 꾸욱 눌러주는 센스 ~~ 

[출처] https://nowcampus.tistory.com/entry/10%EC%9E%A5-%ED%95%98%ED%8A%B8-%EA%B2%8C%EC%9E%84?category=655340

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
108 [Xamarin] Xamarin.Forms. Android 실행/ 디버깅시에 에뮬리이터 배포오류 Why am I getting this error in Xamarin.Forms using Visual Studio? file 졸리운_곰 2021.12.01 22
107 [Xamarin] Visual Studio 2019 를 설치하고 Xmarin.forms 빌드시 에러 : I am just download and start Visual Studio (Xamarin Project). But there is an ERROR NU1101. file 졸리운_곰 2021.12.01 53
106 [게임개발] How to Create Smarter NPCs in Games file 졸리운_곰 2021.08.31 23
105 (CGP)16장. 탱크 게임 file 졸리운_곰 2021.06.28 802
104 (CGP) 15장. 탱크 맵툴 만들기 file 졸리운_곰 2021.06.28 225
103 (CGP) 14장 Sogo 게임 file 졸리운_곰 2021.06.28 21
102 (CGP)13장. 패턴 뷰어 file 졸리운_곰 2021.06.28 19
101 (CGP) 12장 Snake 게임 file 졸리운_곰 2021.06.28 23
100 (CGP) 11장 Snake 게임 툴 만들기 file 졸리운_곰 2021.06.28 50
» (CGP) 10장. 하트담기 게임 file 졸리운_곰 2021.06.28 25
98 (CGP) 9장. 하트 툴 만들기 file 졸리운_곰 2021.06.28 22
97 (CGP) 8장. 벽돌깨기 게임 file 졸리운_곰 2021.06.28 48
96 (CGP)7장. 짝 맞추기 게임 file 졸리운_곰 2021.06.28 131
95 (CGP) 6장. 두더지 잡기 게임 file 졸리운_곰 2021.06.28 113
94 (CGP) 5장. 슛골인 게임 file 졸리운_곰 2021.06.27 151
93 (CGP) 4장. 사운드 file 졸리운_곰 2021.06.27 78
92 (CGP) 3장. 게임의 기본 구조 file 졸리운_곰 2021.06.27 126
91 (CGP) 2장. 함수 file 졸리운_곰 2021.06.27 36
90 (CGP) 1장. C언어 file 졸리운_곰 2021.06.27 60
89 (CGP) 0장. C를 이용한 게임프로그래밍 강좌를 시작하기 전에 file 졸리운_곰 2021.06.27 173
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED