(CGP) 15장. 탱크 맵툴 만들기

탱크 게임 고전 게임의 하나로 가장 친숙하고 쉬운 게임중 하나이다.

 

탱크 게임은 보드 게임과 같이 전체 맵을 보면서 게임을 하게 된다.

 

그러므로 탱크 게임을 만들기 이전에 이 장에서 소개하는 맵툴은 필수라고 할 수 있다.

 

대박유튜브 동영상 강의 주소

(1) http://youtu.be/YSEBsywRrH8

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

(3) http://youtu.be/JcpS3kXQob8

(4) http://youtu.be/SfJ_9mUnh18

(5) http://youtu.be/6jv_DNzWQnw

(6) http://youtu.be/ehGYWVTWx_s

 

 

 


 

15.1 툴 제작 개요

 

앞서 제작한 툴을 통해 툴이 게임 스테이지를 어떻게 다양화시킬 수 있는가를 살펴보았다.

게임의 제작 규모가 커지면 커질수록 툴은 더욱 중요한 역할을 한다.

 

현재 우리가 제작하는 게임은 프로그래머에 의해서 만들어지지만 실제 게임은 프로그래머, 기획, 그래픽등 전체 게임 제작팀에 의해서 개발되어 상용화된다.

그러므로 툴은 이런 다양한 팀원들이 쉽게 기술적인 부분을 사용할 수 있도록 제작되어야 한다. 이 장에서 제작하는 탱크 게임 맵 툴은 앞서 제작한 툴의 통합한 형태라고 할 수 있다.

 

 

 


 

15.2 기획

 

툴을 제작하기 위해서는 먼저 어떤 게임을 제작할 것인지에 대한 기획이 확실해야 한다.  왜냐하면 기획에 따라 툴의 기능이 바뀌기 때문이다.

 

■ 스토리

 

2900년 세계 5차 대전이 발생했다. 적들은 늦은 밤 12시를 기점으로 아군의 진영으로 침투하였다. 아군은 사력을 다해 적군을 섬멸하고 아군 보스를 지켜야 한다.

 

■ 게임 방식

 

주인공 탱크는 방향키로 이동하며 ‘s' 키는 대포를 발사한다.

전체 맵은 블록과 방호벽으로 구성되며 블록은 적과 주인공의 대포로 파괴되지만 방호벽은 파괴되지 않는다. 게임에서 미션 실패는 주인공 탱크가 보호하는 아군 보스가 적으로부터 공격을 받거나 주인공 탱크의 생명값이 0이 되면 미션은 실패된다.

 

■ 제한 사항

 

아군 탱크의 총알을 5발로 제한하되 일정한 간격으로 발사되게 한다.  

적 탱크가 지능적으로 이동과 공격을 하도록 인공지능 요소를 첨가 할 수 있겠지만 전체 게임의 구성에 집중하기 위해서 적 탱크의 지능적인 이동과 공격은 임의의 동작하게 한다.

적 캐릭터는 스테이지당 최대 30개까지 생성할 수 있다.

 

■ 기획 화면

 

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

 

 

 


 

15.3 툴 제작 가이드

 

툴을 제작하기 위해서는 먼저 게임에 사용되는 데이터를 분석해야 한다.

왜냐하면 툴은 게임을 실행하기 위한 것이 아니라 게임에 사용되는 데이터를 편집하기 위한 프로그램이기 때문이다.

 

15장의 탱크 게임 맵 툴은 캐릭터와 맵을 다루기 위한 툴이다. 그러므로 먼저 캐릭터 속성을 주인공과 적 캐릭터로 구분하여 분석하고 그 다음 맵 속성을 분석하면 게임에 사용되는 대부분의 데이터가 결정된다.

여기서 결정된 데이터는 실제 게임에서도 동일하게 사용되므로 코딩 및 데이터 설계에서 많은 시간과 노력을 절약하게 해준다.

 

앞에서는 메뉴를 선택할 때 해당 번호를 입력하여 메뉴를 실행했었다.

여기서는 방향키를 이용하여 메뉴를 선택하는 방식을 사용한다.

이와 같은 방식은 사용자의 편의를 고려하면서 사용자가 번호 선택의 실수를 하지 않게 해준다.

 

데이터 중에는 툴에서 삽입과 삭제, 입력, 수정을 자주해야 하는 데이터가 있다.

이런 경우에는 데이터를 링크드 리스트(Linked List)로 관리할 수 있겠지만 링크드 리스트의 구현이 쉽지 않으므로 여기서는 배열로 데이터 관리를 한다.

현재의 툴을 보면 삭제, 입력, 수정 기능만이 있으므로 이에 대한 데이터 배열을 충분히 생성하고 그중 일부분만을 사용하는 방식으로 툴을 제작하면 된다.

 

 

 


 

15.4 실행 화면 

 

[그림 15-2] 메인 메뉴 화면

 

[그림 15-3] 파일 읽기 화면

 

[그림 15-4] 맵 편집 화면

 

[그림 15-5] 적 캐릭터의 속성 메뉴 화면

 

 

[그림 15-6] 적 캐릭터의 종류 설정 화면

 

[그림 15-7] 적 캐릭터의 출현 시간 설정 화면

 

 

 

[그림 15-8] 적 캐릭터의 생성 위치 설정 화면

 

15.5 툴 제작 로드맵

 

툴 제작을 위한 4단계를 살펴보면 다음과 같다.

 

1단계는 주로 데이터 분석과 기본 구조를 만드는 단계이다.

이 단계에서 메뉴의 선택 방식을 바꿀 수 있는 함수를 제작하여 시범적으로 테스트한다.

 

2단계는 맵을 디자인하기 위한 단계이다.

맵은 11장에서 제작한 Snake 게임 툴과 유사하지만 맵 정보 외에 부가적으로 추가되는 정보가 있다. 맵 정보는 고정된 행과 열로 이루어진 맵 배열에 입력을 하지만 적 탱크의 생성 위치, 아군 탱크의 위치는 가변적이므로 마지막에 설정된 개수를 조사해야 한다.

그래서 [그림 15-3]의 2단계를 보면 ‘데이터 생성’이라는 메뉴가 있다.

 

3단계는 적 캐릭터에 관한 단계이다.

적 캐릭터는 최대 30개까지이며 그중 일부를 사용하게 된다.

 

4단계는 파일을 다루는 단계이다.

기본적으로 읽기와 저장이 있으며 여기서 저장된 데이터는 게임이 시작될 때 읽혀져 설정된다.

 

[STEP 01]

 

[STEP 02]

 

[STEP 03]

 

[STEP 04]

[그림 15-9] 툴 제작 로드맵

 

 

 


 

15.6 단계별 프로그래밍

 

 

[STEP 01]

 

 

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

 

■ 데이터 분석

 

탱크 게임의 데이터는 크게 3가지로 나눌 수 있다.

 

첫째, 스테이지에 관한 데이터이다.

각 스테이지를 생각해 보면 적 탱크 개수와 적 생성 위치, 아군 보스와 아군 탱크 위치, 적 탱크의 종류는 매번 다르게 적용되므로 이 데이터는 툴에서 생성해야 한다.

 

둘째, 맵에 관한 데이터이다.

탱크 게임의 맵은 2차원 배열로 구성할 수 있으며 이 2차원 배열의 행과 열은 화면의 좌표와 일대일 대응된다. 맵에 저장되는 값은 다음의 [표 15-1]과 같다.

 

 

0

1

2

3

4

5

의미

공백

블록

방호벽

적 탱크 생성 위치

보스 위치

아군 탱크

출력 문자

 

 

[표 15-1] 맵 정보

 

셋째,  적 탱크에 관한 데이터이다.

모든 적 탱크에 임의의 속성을 설정하는 것이 아니라 여러 종류의 적 탱크 속성을 정의해  놓고 인덱스를 통해 속성을 선택하여 설정한다.

그리고 적 탱크는 툴에서 정한 위치에서 생성되며 출현 시간은 오름차순으로 설정된다.

 

위의 세 가지 데이터는 아래 [소스 15-1]과 같이 정의할 수 있다.

 

 

스테이지

typedef struct _STAGE

{

        int nEnemyCount;          // 적 캐릭터 수

        int nEnemyPositionCount;    // 적 출현 위치 개수

        POS_XY *pEnemyPosition;   // 적 출현 위치 배열

        int nBossX, nBossY;         // 보스 초기 위치

        int nPlayerX, nPlayerY;      // 주인공 초기 위치

        int nEnemyTypeCount;       // 적 탱크 종류 개수

} STAGE;

적 탱크 종류

tpedef struct _ENEMY_TYPE

{       

        int  nLife;                        // 생명력

        int nMoveTime;   // 속력

        int nFireTime;      // 총알 발사 시간 간격  

} ENEMY_TYPE;

적 탱크 속성

typedef struct _ENEMY

{

        int nTypeIndex;              // 타입 인덱스

        int nPosIndex;               // 초기 위치 인덱스

          int nAppearanceTime;           // 출현 시간

 

          /* 실제 게임에서 사용되는 속성이다.

         int nX, nY;                    // 이동 좌표

         int nState;                 // 상태 전이

         int nLife;                            // 생명력

         int nDirect;                 // 방향      

         unsigned int nOldTime; // 이전 이동 시간

          */

} ENEMY;

 

[소스 15-1] 속성 정의

 

■ 메인 메뉴

 

메인 메뉴는 크게 3가지로 나눌 수 있다.

 

첫째, 파일에 관련된 사항이다.

파일 메뉴에는 기본적으로 읽고 저장하는 기능이 있으며 [소스 15-1]에서 정의한 데이터를 읽거나 저장한다.

 

둘째, 맵에 관련된 사항이다.

맵 메뉴에는 맵을 편집하기 위한 사항과 맵 전체를 초기화하는 기능이 있어야 한다.

맵을 보면서 유동적인 데이터(적 탱크의 개수, 적 탱크의 초기 출현 위치)를 결정할 수 있는 메뉴도 있어야 한다.

 

셋째는 적 탱크에 관한 사항이다.

적 탱크의 생성과 속성을 임의의 값으로 지정하는 것이 아니라 툴에서 직접 입력하거나 설정한다. 이때 적 탱크를 스테이지마다 개별적으로 설정할 수 있지만 몇 가지 탱크 속성을 결정해 놓고 그 종류 안에서 선택적으로 설정한다.

그러므로 적 탱크 종류, 출현 시간, 생성 위치를 지정해 줄 수 있는 메뉴가 있어야 한다.

 

■ 메인 메뉴 화면

 

[그림 15-11] 메인 메뉴

 

메인 메뉴 화면을 보면 이전 메뉴 선택과 차이점이 있는 것을 알 수 있다.

이전에는 각 메뉴마다 고유 번호가 있어서 번호 선택에 따라 서브 메뉴가 실행되었다.

이번에는 사용자 편의를 고려하여 방향키에 따라 메뉴 항목을 이동하는 커서를 두어 메뉴가 선택되도록 해보자.

커서를 이동하는 방법은 사실, 게임과 같이 지우고 그리기를 빠르게 반복하여 마치 커서가 이동하는 것 같이 보이도록 한 것에 불과하다.

이것은 눈의 착시 현상을 이용한 것으로 빠르게 움직이면 연속적인 동작으로 보이는 원리를  이용한 것이다.

 

[실습 예제 15-1]

 

메뉴 선택 방식을 번호 입력에서 커서 이동 방식으로 바꾸어 [그림 15-12]와 같이 출력이 되도록 프로그래밍해 보자.

그리고 엔터키가 입력되면 현재 선택한 메뉴의 인덱스를 함수에서 리턴 되게 한다.

단, 커서 이동 메뉴는 함수로 제작하고 선택된 항목을 알 수 있도록 출력해 보자.

 

(메뉴 문자열 다루기)

 

메뉴와 같은 여러 개의 문자열을 다룰 때는 포인터 배열을 이용하면 보다 쉽게 문자열을 다룰 수 있다. 그 예로 하나의 문자열은 선언할 때에 포인터와 배열을 이용하면 쉽게 할당하여 사용할 수 있다.

 

(예 1) char cStr[20] = "메뉴“ ; 또는 char *pStr = "메뉴”;

 

하지만 이런 문자열이 여러 개이면 각 문자열의 메모리 주소를 저장하는 포인터 배열을 사용하는 것이 휠씬 유용하다.

 

(예 2) char *pMenu[3] = { "메뉴“, ”적 캐릭터“, ”종료“ };

 

 

[그림 15-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

#include <stdio.h>

#include <windows.h>

#include <conio.h>

 

char *g_Menu[5] = {"파일  읽기", "파일  저장", "맵 정보", "적 캐릭터", "종    료" };

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int VerticalMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nLength, nMaxLength = 0;

    char cBlank[50];

 

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

           nMaxLength = nLength;

    }           

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[nMaxLength + 6 ] = 0; // 널 문자 처리

                 

    while( 1 )

    {   

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

        {       

             gotoxy( nX - 2, nY + i*2 );

             printf( "%s", cBlank );

             gotoxy( nX, nY + i*2 );

             printf( "%s", pMenu[i] );

        }

        gotoxy( nX - 2, nMenu*2 + nY );

        printf( "[" );

        gotoxy( nX + nMaxLength + 1, nMenu*2 + nY );

        printf( "]" );

 

        nKey = _getch();

        switch( nKey )

        {               

         case 72 : // 화살표 위쪽 키

                if( nMenu - 1 >= 0 )

                   nMenu--;

                 break;

         case 80 : // 화살표 아래 키

                if( nMenu + 1 < nCount )

                    nMenu++;

                break;                          

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int main(void)

{

    int nMenu;

 

    while( 1 )

    {

          nMenu = VerticalMenu( 6, 3, g_Menu, 5 );

        gotoxy( 6, 15 );

        printf( "%s 선택", g_Menu[nMenu] );

        _getch();

        gotoxy( 6, 15 );

        printf( "                      " );

    }

    return 0;

}

 

[소스 15-2] 메인 메뉴

 

19행부터 26행까지는 메뉴 문자열 중에서 최대 길이를 구하고 그 크기만큼 지우기 위한 공백 문자열을 만드는 부분이다.

메뉴의 출력은 지우고 그리기를 반복하는 것이지만 지울 때 화면 전체를 지우기보다는 해당되는 메뉴 항목만 지우는 것이 효과적이므로 이와 같이 공백 문자열을 만드는 것이다.

문자열의 마지막에는 항상 널문자가 있어야 하므로 26행에서는 문자열의 끝에 널문자를 넣어 주고 있다.

 

30행부터 36행까지는 메뉴를 출력하는 부분이다.

그중에서 32행과 33행은 지우는 부분이며 34행과 35행은 메뉴를 출력하는 부분이다.

 

■ 메뉴 선택 구조 만들기

 

메인 메뉴와 서브 메뉴간의 전체적인 구조는 [그림 15-13]과 같다.

 

[그림 15-13] 메뉴 선택 구조

 

위와 같이 메뉴는 큰 항목에서 작은 항목으로 나눠져 세부적인 처리를 한다.

 

[실습 예제 15-1]에서 제작한 메뉴 형식은 커서를 수직으로만 이동하여 메뉴를 선택하였다. 이와 같은 메뉴 형식은 메인 메뉴 형식에 적합하다.

서브 메뉴의 경우에는 메뉴만 보여주는 것이 아닌 맵과 같이 큰 화면을 차지하는 정보도 출력해야 하므로 수직 메뉴가 아닌 수평 메뉴 방식도 사용해야 한다.

[그림 15-8]에서 세부적인 메뉴에 해당하는 입력, 수정, 삭제 등은 수평적인 메뉴 방식을 사용해야 한다.

 

[실습 예제 15-2]

 

[그림 15-13]의 메뉴 항목을 수직 또는 수평으로 선택할 수 있도록 프로그래밍해 보자.

메인 메뉴와 적 캐릭터의 서브 메뉴는 수직 메뉴 선택 방식을 사용하고 나머지는 수평 메뉴 선택 방식이 되도록 한다.

 

[그림 15-14] 메인 메뉴

 

[그림 15-15] 적 캐릭터의 서브 메뉴

 

 

[그림 15-16] 적 캐릭터 출현 시간의 수평 메뉴

 

[그림 15-17] 적 캐릭터 출현 시간의 입력 메뉴가 출력된 화면

 

 

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

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

#include <stdio.h>

#include <windows.h>

#include <conio.h>

 

char *g_Menu[5] = {"파일  읽기", "파일  저장", "맵 정보", "적 캐릭터", "종    료" };

char *g_Sub[4] = { "입력", "수정", "삭제", "상위" };

char *g_Sub2[6] = { "입력", "수정", "삭제", "이전", "다음", "상위" };

char *g_Sub3[5] = { "입력", "수정", "이전", "다음", "상위" };

char *g_StageSubMenu[4] = { "적 캐릭터 종류", "적 캐릭터 출현 시간",

                            "적 캐릭터 출현 위치", "상위 메뉴로" };

char *g_MapSubMenu[4] = {"편집", "맵 전체 삭제", "데이터 생성", "상위 메뉴로" };

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int VerticalMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nLength, nMaxLength = 0;

    char cBlank[50];

 

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

            nMaxLength = nLength;

    }   

        

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[nMaxLength + 6 ] = 0; // 널 문자 처리

                 

    while( 1 )

    {   

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

        {       

             gotoxy( nX - 2, nY + i*2 );

             printf( "%s", cBlank );

             gotoxy( nX, nY + i*2 );

             printf( "%s", pMenu[i] );

        }

        gotoxy( nX - 2, nMenu*2 + nY );

        printf( "[" );

        gotoxy( nX + nMaxLength + 1, nMenu*2 + nY );

        printf( "]" );

 

        nKey = _getch();

        switch( nKey )

        {               

         case 72 : // 화살표 위쪽 키

                if( nMenu - 1 >= 0 )

                    nMenu--; 

                break;

         case 80 : // 화살표 아래 키

                if( nMenu + 1 < nCount )

                    nMenu++;

                break;                          

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int LineMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nMaxLength = 0, nLength;

    char cBlank[80];

        

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

            nMaxLength = nLength;

    }   

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[ (nMaxLength+2)*nCount+1] = 0; // 널 문자 처리

 

    while( 1 )

    {           

        gotoxy( nX - 2, nY ) ;

        printf( "%s", cBlank );

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

        {

              gotoxy( nX + i*(nMaxLength + 2), nY);

              printf( "%s", pMenu[i] );

        }

        gotoxy( nMenu*(nMaxLength + 2) + nX -1, nY ); printf( "[" );

        gotoxy( nMenu*(nMaxLength + 2) + nX + nMaxLength, nY); printf( "]" );

        

          nKey = _getch();

        switch( nKey )

        {               

         case 75 : // 화살표 왼쪽 키

                nMenu--;

                if( nMenu < 0 )

                    nMenu = 0;

                 break;

         case 77 : // 화살표 오른쪽 키

                nMenu++;

                if( nMenu == nCount )

                    nMenu = nCount - 1 ;                          

                break;

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int main(void)

{

    int nMenu;   

 

    while( 1 )

    {

        system( "cls" );

        nMenu = VerticalMenu( 17, 6, g_Menu, 5 );          

        if( nMenu == 4 )

            break;

 

        switch( nMenu )

        {

        case 0:

                system( "cls" );

                gotoxy( 17, 10 );

                printf( "파일 읽기 실행" );

                _getch();

                break;

        case 1:

                system( "cls" );

                gotoxy( 17, 10 );

                printf( "파일 저장 실행" );

                _getch();

                break;

        case 2:

                while( 1 )

                {

                   system( "cls" );

                   nMenu = VerticalMenu( 17, 6, g_MapSubMenu, 4 );

                   if( nMenu == 3 )

                       break;

 

                    switch( nMenu )

                    {

                     case 0:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "편집 상태" );

                          _getch();

                          break;

                     case 1:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "맵 전체 삭제" );

                          _getch();

                          break;

                     case 2:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "데이터 생성" );

                          _getch();

                          break;                                 

                    }

                }

                break;

        case 3:                         

                while( 1 )

                {

                    system( "cls" );

                    nMenu = VerticalMenu( 17, 6, g_StageSubMenu, 4 );

                    if( nMenu == 3 )

                        break;

                                        

                     switch( nMenu )

                     {  

                      case 0:

                            while( 1 )

                            {

                                 system( "cls" );

                                 nMenu = LineMenu( 5, 5, g_Sub, 4 );

                                 if( nMenu == 3 )

                                   break;

 

                                  switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "삭제 상태" );

                                        _getch();

                                        break;

                                }

                            }

                            break;

                      case 1:

                            while( 1 )

                            {   

                                  system( "cls" );

                                nMenu = LineMenu( 5, 5, g_Sub2, 6 );

                                if( nMenu == 5 )

                                    break;

 

                                switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "삭제 상태" );

                                        _getch();

                                        break;

                                case 3:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "이전 상태" );

                                        _getch();

                                        break;

                                case 4:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "다음 상태" );

                                        _getch();

                                        break;

                                }

                           }

                           break;

                      case 2:

                            while(1)

                            {

                                  system( "cls" );

                                nMenu = LineMenu( 5, 5, g_Sub3, 5 );

                                if( nMenu == 4 )

                                    break;

                                

                                switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "이전 상태" );

                                        _getch();

                                        break;

                                case 3:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "다음 상태" );

                                        _getch();

                                        break;

                                }

                            }

                            break;

                     }

                }

                break;          

        }

    }

    return 0;

}

 

[소스 15-3] 메뉴 선택 방식

 

 

 


 

[STEP 02] 

[그림 15-18] 2단계 제작 로드맵

 

■ 맵

 

탱크 맵은 12장에서 제작한 Snake 게임과 동일한 맵 구조를 사용한다.

맵 정보를 저장하기 위한 이차원 배열에는 [표 15-1]의 값이 저장되고 해당 문자가 출력된다. 이차원 배열의 행과 열은 화면 좌표와 일대일 대응되므로 행과 열의 개수는 화면에 출력할 맵 크기를 결정한다.

 

- 편집

 

맵 편집에는 입력, 수정, 삭제 과정이 포함되어 있다.

맵을 편집한다는 것은 결국 맵 데이터를 저장하는 이차원 배열의 값을 입력, 수정, 삭제하는 것이다. 이때 입력과 수정은 데이터를 대입하는 것이지만 삭제는 실제 이차원 배열의 요소를 삭제하는 것이 아니라 출력에서 제외시키기 위한 값을 입력하는 것이다.

현재 공백은 0일 때 출력하는 것이므로 0 값을 대입하는 것이 삭제이다.

 

[그림 15-19] 맵 편집 상태

 

- 맵 전체 삭제

 

맵 전체를 공백으로 채워주는 0을 행과 열 개수만큼 일일이 입력하는 것은 상당히 번거로운 일이다. 그래서 ‘맵 전체 삭제’ 메뉴를 선택하면 맵 전체가 0으로 채워지도록 [소스 15-4]의 memset() 함수를 사용하면 된다.

 

 

memset( g_nMap, 0, sizeof( int )*MAP_ROW*MAP_COL );

 

[소스 15-4] memset()

 

- 데이터 생성

 

[그림 15-19]를 보면 편집되는 데이터가 0부터 5까지의 값이라는 것을 알 수 있다.

이 값들 중에는 맵 데이터로만 있어야 하는 값과 맵 데이터를 다른 용도로 사용하기 위한 값이 있다. 예를 들면 블록, 방호벽은 맵 데이터로만 존재하면 되지만 적 탱크의 출현 위치와 아군 보스의 위치, 아군 탱크의 위치는 맵에 설정된 행과 열의 좌표를 얻어 다른 용도로 사용하기 위한 것이다.

물론 적 탱크의 출현 위치를 스테이지마다 임의로 생성할 수 있겠지만 맵에서 설정한다면  기획된 시나리오대로 적 탱크를 출력할 수 있게 된다. 또한 좌표를 코드로 직접 입력하는 것보다는 맵이 출력된 화면에서 설정하는 것이 훨씬 쉽기 때문에 이 방법을 사용한다.

아래 [소스 15-5] 스테이지 구조체에서 ①부터 ④까지의 변수에 맵 데이터가 좌표값으로 변환되어 저장된다.

 

 

typedef struct _STAGE_INFO

{

        int nEnemyCount;          // 적 캐릭터 수

        int nEnemyPositionCount;    // 적 캐릭터 위치 개수  ------------ ①

        POS_XY *pEnemyPosition;   // 적 캐릭터의 출현 위치 배열 --------- ②

        int nBossX, nBossY;         // 아군 보스의 초기 위치    ---------- ③

        int nPlayerX, nPlayerY;      // 주인공의 초기 위치 ----------- ④

        int nEnemyTypeCount;       // 적 탱크의 종류 개수

} STAGE_INFO;

 

[소스 15-5] 스테이지 구조체

 

편집 메뉴 중에 ‘데이터 생성’ 메뉴는 맵 데이터를 다른 용도로 사용하기 위한 변수에 값을 설정하기 위한 메뉴이다. 맵 편집을 다 한 후에는 반드시 실행시켜 변수를 초기화해야 한다. 물론 이 부분도 자동으로 설정되게 프로그래밍할 수 있겠지만 프로그래머의 수고를 조금 덜어 준다는 차원에서 이 방식을 사용한다.

 

[실습 예제 15-3]

 

[그림 15-19]와 같이 맵 편집과 맵 전체 초기화, 데이터 생성이 가능하도록 [실습 예제 15-2]의 구조 안에서 프로그래밍해 보자.

맵 이동은 방향키(상 72, 하 80, 좌 77, 우 75 )를 이용하며 1부터 6까지의 입력을 받아 맵 데이터를 입력을 받거나 수정한다. 

 

 

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

case 2 : // 맵 정보

     nPositionCount = nRow = nCol = 0;

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

if( g_nMap[i][j] == 3 )

nPositionCount++;

}

}

gotoxy( 52, 1 ); printf( "★ 메뉴 항목 " );

gotoxy( 50, 2 ); printf( "┏━━━━━━━━┓" );

gotoxy( 50, 3 ); printf( "┃ ┃" );

gotoxy( 50, 4 ); printf( "┃ ┃" );

gotoxy( 50, 5 ); printf( "┃ ┃" );

gotoxy( 50, 6 ); printf( "┃ ┃" );

gotoxy( 50, 7 ); printf( "┃ ┃" );

gotoxy( 50, 8 ); printf( "┃ ┃" );

gotoxy( 50, 9 ); printf( "┃ ┃" );

gotoxy( 50, 10 ); printf( "┗━━━━━━━━┛" );

gotoxy( 52, 12 ); printf( "1: 블록 " );

gotoxy( 52, 13 ); printf( "2: 방호벽 " );

gotoxy( 52, 14 ); printf( "3: 적 탱크 출현 위치 " );

gotoxy( 52, 15 ); printf( "4: 아군 보스의 위치 설정 " );

gotoxy( 52, 16 ); printf( "5: 주인공 위치 설정 " );

gotoxy( 52, 17 ); printf( "6: 삭제" );

gotoxy( 52, 18 ); printf( "7: 메뉴로" );

while( 1 )

{

Box();

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

gotoxy( j*2 + 2, i+1);

printf( "%s", g_Title[ g_nMap[i][j] ] );

 }

}

nMenu = VerticalMenu( 54, 3, g_MapSubMenu, 4 );

if( nMenu == 3 )

{

system("cls" );

break;

}

 

switch( nMenu )

{

case 0 : // 편집 상태

while( 1 )

{

gotoxy( nCol*2 + 2, nRow+1 );

nKey = _getch();

if( nKey == '7' )

break;

switch( nKey )

{

case 72 : // 위쪽

if( nRow - 1 >= 0 )

nRow--;

break;

case 80 : // 아래

if( nRow + 1 < MAP_ROW )

nRow++;

break;

case 77 : // 오른쪽

if( nCol + 1 < MAP_COL )

nCol++;

break;

case 75 : // 왼쪽

if( nCol - 1 >= 0 )

nCol--;

break;

case '1' : // 블록

g_nMap[nRow][nCol] = 1;

break;

case '2' : // 방호벽

g_nMap[nRow][nCol] = 2;

break;

case '3' : // 적 탱크의 출현 위치

g_nMap[nRow][nCol] = 3;

nPositionCount++;

break;

case '4' : // 아군 보스 설정

g_nMap[nRow][nCol] = 4;

g_Stage.nBossX = nCol;

g_Stage.nBossY = nRow;

break;

case '5' : // 주인공 설정

g_nMap[nRow][nCol] = 5;

g_Stage.nPlayerX = nCol;

g_Stage.nPlayerY = nRow;

break;

case '6' : // 삭제

if( g_nMap[nRow][nCol] == 3 )

nPositionCount--;

g_nMap[nRow][nCol] = 0;

break;

 }

gotoxy( nCol*2 + 2, nRow + 1); // x, y

printf( "%s", g_Title[ g_nMap[nRow][nCol] ] );

}

break;

case 1 :

// 맵 전체 삭제

memset( g_nMap, 0, sizeof( int )*MAP_ROW*MAP_COL );

break;

case 2 :

// 데이터 생성

g_Stage.nEnemyPositionCount = nPositionCount;

if( g_Stage.pEnemyPosition != NULL )

free( g_Stage.pEnemyPosition );

g_Stage.pEnemyPosition =

        (POS_XY*)malloc( sizeof(POS_XY)*nPositionCount );

nPositionIndex = 0;

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

if( g_nMap[i][j] == 3 )

{

 g_Stage.pEnemyPosition[nPositionIndex].nX = j;

g_Stage.pEnemyPosition[nPositionIndex++].nY = i; }

}

}

BoxXY( 5, 5, 4 );

gotoxy( 7, 7 ); printf( "데이터 생성 완료" );

_getch();

break;

}

}

break;

 

 [소스 15-6] 맵 편집 소스

 

109행부터 123행까지는 적 탱크의 출현 위치를 저장하기 위한 배열에 정보를 저장하는 부분이다.

 

 


[STEP 03]

 

[그림 15-20] 3단계 제작 로드맵

 

■ 적 캐릭터

 

적 캐릭터는 한 스테이지당 30개로 제한되어 있다.

적 캐릭터 속성을 스테이지마다 임의로 입력할 수 있겠지만 기획 의도를 충분히 반영하기 위해서 적 캐릭터 속성은 툴에서 입력해야 한다.

위의 [그림 15-20]을 보면 적 캐릭터의 데이터를 종류, 출현 시간, 생성 위치에 따라 입력, 수정, 삭제를 하게 된다.

 

- 종류

 

적 캐릭터 종류를 미리 생성해 놓고 선택적으로 사용하는 이유는 최대 30개의 적 캐릭터를 전부 다르게 설정하기 보다는 미리 종류를 설정해 놓고, 선택적으로 속성을 설정하는 방법이 적 캐릭터 설정을 쉽게 할 수 있기 때문이다.

 

 

[그림 15-21] 적 캐릭터 종류

 

‘적 캐릭터 종류’ 항목에는 생명력, 이동 시간 간격, 총알 발사 시각이 있다.

‘적 캐릭터 종류’ 항목은 아래 [소스 15-7]의 구조체를 설정한다.

 

 

typedef struct _ENEMY_TYPE

{       

        int  nLife;                 // 생명력

        unsigned int nMoveTime;   // 속력

        unsigned int nFireTime;    // 총알 발사 시간 간격   

} ENEMY_TYPE;

 

[소스 15-7] 적 캐릭터 종류의 속성 정의

 

- 출현 시간

 

출현 시간은 게임을 더욱 생동감 있게 만드는 요소 중의 하나이다.

서로 다른 위치에서 서로 다른 종류의 적 캐릭터 4대가 동시에 출력하게 하려면 적 캐릭터의 출현 시간을 동일하게 하고 서로 다른 종류의 적 캐릭터를 설정하면 된다.

이와 같이 출현 시간을 개별적으로 설정하면 더욱 다양하게 캐릭터를 출력할 수 있다.

 

[그림 15-22] 적 캐릭터의 출현 시간 설정

 

출현 시간이 설정된 개수만큼 적 캐릭터는 생성되어야 하며 적 캐릭터의 개수는 출현 시간의 설정 개수에 따라 결정된다.

  

- 출현 위치

 

일정한 위치에서 적 캐릭터가 출현하기 보다는 다양한 위치에서 출현하는 것이 게임을 더욱 흥미롭게 한다. 출현 위치를 설정하기 위해, 사용자가 맵에서 위치를 파악하고 좌표를 일일이 입력하는 방법보다 맵상의 커서를 통해 출현 위치로 이동하여 설정하는 것이 식별하기가  쉽다. 툴은 이와 같은 편의 사항을 사용자에게 제공해야 한다.

 

[그림 15-23] 맵상에서 적 캐릭터의 출현 위치 설정

 

[실습 예제 15-4]

 

입력과 삭제 기능은 scanf()와 gets()와 같은 함수를 이용하면 쉽게 해결이 된다. 하지만 수정의 경우는 기존 데이터를 화면에 출력한 후에 수정할 수 있어야 하므로 제공되는 함수만의 기능으로는 해결하기가 어렵다. 그래서 항목을 보여 주고 입력을 받거나 백스페이스 키를 이용하여 데이터를 지울 수 있는 함수를 제작해 보자.

 

[그림 15-24] 기존 데이터 출력

 

[그림 15-25] 수정 데이터 출력

 

 

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

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

#include <conio.h>

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int IntScanf( int x, int y, int nNumber )

{

    char strTemp[20], cMunja;

    int nIndex = 0, i, nLength;

         

    itoa( nNumber, strTemp, 10 );  

    nLength = strlen( strTemp ); // 기존 문자의 길이

    // 원문 출력

    gotoxy( x, y ); printf( "%s", strTemp );

    gotoxy( x, y );      

    while( 1 )

    {           

        cMunja = _getch();

        switch( cMunja )

        {

        case 13 : // 엔터키

                return atoi( strTemp );

        case 8 : // 백스페이스 처리

                 if( nIndex - 1 < 0 )

                   nIndex = 0;

                else

                {

                   nIndex--;

                   strTemp[nIndex] = 0; 

                 }                               

                break;

        default :

                if( nIndex + 1 < 19 ) // 18문자 이상 들어 오면 입력 받지 않는다.

                {                                       

                    strTemp[nIndex++] = cMunja;

                    strTemp[nIndex] = 0; // 널문자

                }

        }

 

        // Note: 길이만큼 화면을 지운다.

        gotoxy( x, y );

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

             printf(" ");                

                                

        gotoxy( x, y ); printf( "%s", strTemp );

        nLength = strlen( strTemp );

    }

}

 

int main(void)

{

    int nVal = 200;

 

    nVal = IntScanf( 3, 1, nVal );

    printf( "\n 수정된 값 : %d", nVal );

    _getch();

    return 0;

}

 

[소스 15-8] 정수값 편집 함수

 

[실습 예제 15-5]

 

적 캐릭터 메뉴에 해당되는 종류, 출현 시간, 출현 위치 항목에서 입력, 수정, 삭제, 이전, 다음 메뉴에 의해 실제 처리가 되도록 프로그래밍해 보자. 기본적인 구조는 [실습 예제 15-2]를 사용하며 수정할 때에는 [실습 예제 15-4]에서 제작한 함수를 사용한다.

 

 

 

 

case 3 : // 적 캐릭터 정보                   

        while( 1 )

        { 

             Box();

             gotoxy( 19, 2 );

             printf( "%s", "적 캐릭터" );

             nMenu = VerticalMenu( 13, 6, g_StageSubMenu, 4 );

             if( nMenu == 3 ) // 상위 메뉴로

                break;

             

              switch( nMenu )

              {

                case 0 : // 적캐릭터 종류 별 속성 설정

                        while( 1 )

                        {

                             Box();

                             gotoxy( 2, 4 );

                                   printf( "----------------------------");

                             gotoxy( 2, 5 );

                                printf("인덱스 생명력 이동시간간격 총알발사시간");

                             gotoxy( 2, 6 );

                                   printf( "----------------------------");

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

                             {

                                gotoxy( 3, 7+i);

                                printf( "  %d    %2d     %4d     %4d  ",

                                                  i,  g_EnemyType[i].nLife,

                                                  g_EnemyType[i].nMoveTime,

                                                   g_EnemyType[i].nFireTime );

                             }

                             nMenu = LineMenu( 5, 2, 43, g_Sub, 4 );

                             if( nMenu == 3 )

                                break;

                                                                

                             switch( nMenu )

                             {

                              case 0 : // 입력

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( "인덱스 : ");

                                    gotoxy( 7, 7 ); printf( "생명력 : " );

                                    gotoxy( 7, 8 ); printf("이동시간 간격 : " );

                                    gotoxy( 7, 9 );printf("총알발사 시간간격:");

                                    gotoxy( 17, 6 );

                                    scanf( "%d", &nIndex ); fflush( stdin );

                                    gotoxy( 17, 7 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nLife );

                                    fflush( stdin );

                                    gotoxy( 25, 8 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nMoveTime );

                                    fflush( stdin );

                                    gotoxy( 29, 9 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nFireTime );

                                    fflush( stdin );           

                                    g_Stage.nEnemyTypeCount++;

                                    break;

                              case 1 : // 수정

                                    BoxXY( 5, 5, 6 );

                                    gotoxy( 7, 6 );

                                    printf( "수정 인덱스 : ");

                                    scanf( "%d", &nIndex );

                                    fflush( stdin );

                                    gotoxy( 7, 7 );

                                    printf( "생명력 : %d" ,

                                    g_EnemyType[ nIndex ].nLife );

                                    gotoxy( 7, 8 );

                                    printf( "이동 시간 간격 : %d",

                                    g_EnemyType[ nIndex ].nMoveTime );

                                    gotoxy( 7, 9 );

 

                                    printf( "총알 발사 시간 간격 : %d",

                                               g_EnemyType[ nIndex ].nFireTime );

                                    g_EnemyType[ nIndex ].nLife =

                                          IntScanf(6, 7, g_EnemyType[ nIndex ].nLife );

                                    g_EnemyType[ nIndex ].nMoveTime =

                                          IntScanf(24, 8, g_EnemyType[ nIndex ].nMoveTime);

                                    g_EnemyType[ nIndex ].nFireTime =

                                          IntScanf(29,9,g_EnemyType[ nIndex ].nFireTime );

                                    break;

                        case 2 : // 삭제

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 );

                                    printf( " 삭제 인덱스:");

                                    scanf( "%d", &nIndex );

                                    memset( &g_EnemyType[nIndex], 0,

                                                          sizeof( ENEMY_TYPE ) );            

                                    g_Stage.nEnemyTypeCount--;

                                    break;

                             }

                        }

                        break;

                case 1 :  // 적 캐릭터 출현 시간 설정

                         nPage = 0;

                         while( 1 )

                         {

                            Box();

                            gotoxy( 2, 4 );

                                  printf( "----------------------------");

                            gotoxy( 2, 5 );

                                  printf( " 순번      종류         출현 시간" );

                            gotoxy( 2, 6 );

                                  printf( "----------------------------");

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

                            {

                                 gotoxy( 3, 7 + i );

                                 printf( " %d         %d        %d", i + nPage*10,

                                         g_Enemy[i + nPage*10].nTypeIndex,

                                      g_Enemy[i + nPage*10].nAppearanceTime );

                            }

                            nMenu = LineMenu( 5, 2, 45, g_Sub2, 6 );

                            if( nMenu == 5 )

                                break;

 

                            switch( nMenu )

                            {

                             case 0 : // 입력

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( "순번 : ");

                                    gotoxy( 7, 7 ); printf( "종류 : " );

                                    gotoxy( 7, 8 ); printf( "출현 시간 : " );

                                    gotoxy( 14, 6 );

                                            scanf( "%d", &nIndex );

                                            fflush( stdin );

                                    gotoxy( 14, 7 );

                                    scanf( "%d", &g_Enemy[ nIndex ].nTypeIndex );

                                    fflush( stdin );

                                    gotoxy( 19, 8 );

                                    scanf( "%d",&g_Enemy[ nIndex ].nAppearanceTime );

                                    fflush( stdin );                  

                                    g_Stage.nEnemyCount++;

                                    break;

                             case 1 : // 수정

                                    BoxXY( 5, 5, 6 );

                                    gotoxy( 7, 6 ); printf( "순번 : ");

                                    scanf( "%d", &nIndex ); fflush( stdin );

                                    gotoxy( 7, 7 );

                                    printf( "종류:%d",g_Enemy[ nIndex ].nTypeIndex );

                                    gotoxy( 7, 8 );

                                    printf( "출현 시간 : %d",

                                    g_Enemy[ nIndex ].nAppearanceTime );

                                    g_Enemy[ nIndex ].nTypeIndex = IntScanf( 14, 7,

                                    g_Enemy[ nIndex ].nTypeIndex );

                                    g_Enemy[ nIndex ].nAppearanceTime = 

                                    IntScanf( 19, 8, g_Enemy[ nIndex ].nAppearanceTime );

                                    break;

                             case 2 : // 삭제

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( " 삭제 인덱스 : ");

                                    scanf( "%d", &nIndex );

                                    memset( &g_Enemy[nIndex], 0, sizeof( ENEMY ) );

                                    g_Stage.nEnemyCount--;

                                    break;

                             case 3:

                                    if( nPage - 1 < 0 )

                                        nPage = 0;

                                    else

                                        nPage--;

                                    break;      

                             case 4 :

                                    if( nPage + 1 > 2 )

                                        nPage = 2;

                                    else

                                        nPage++;

                                    break;    

                            }

                       }

                       break;

                case 2 : // 적 캐릭터 초기 위치 설정

                     nPage = 0;

                     while( 1 )

                     {

                        system( "cls" );

                       // Note: 적 캐릭터 생성 위치 출력

                        Box();

                        for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

                        {

                           gotoxy( g_Stage.pEnemyPosition[i].nX*2 + 2,

                                                         g_Stage.pEnemyPosition[i].nY+1);

                           printf( "%s:%d", g_Title[3], i );  

                         }          

                         gotoxy( 50, 1 ); 

                         printf( "----------------------------");

                         gotoxy( 50, 2 ); printf( "순번 | 출현시간 | 인덱스" );

                         gotoxy( 50, 3 );

                         printf( "------------------------");

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

                         {

                            gotoxy( 50, 4 + i );

                            printf( " %d     %6d         %d", i + nPage*10,

                                  g_Enemy[i + nPage*10].nAppearanceTime,

                                 g_Enemy[i +Page*10].nPosIndex );

                

                            nMenu = LineMenu( 3, 21, 60, g_Sub3, 5 );

                            if( nMenu == 4 ) // 상위

                            {

                                system( "cls" );

                                break;

                            }

 

                            switch( nMenu )

                            {

                             case 0 : // 입력

                                BoxXY( 5, 5, 3);

                                gotoxy( 7, 6 ); printf( "순번 : ");

                                gotoxy( 7, 7 ); printf( "인덱스 : " ); 

                                gotoxy( 14, 6 );

                                scanf( "%d", &nIndex ); fflush( stdin );

                                gotoxy( 16, 7 );

                                scanf( "%d", &g_Enemy[ nIndex ].nPosIndex );

                                fflush( stdin );

                                break;

                             case 1 : // 수정

                                BoxXY( 5, 5, 3 );

                                gotoxy( 7, 6 ); printf( "순번 : ");

                                scanf( "%d", &nIndex ); fflush( stdin );

                                gotoxy( 7, 7 );

                                printf( "인덱스:%d" , g_Enemy[ nIndex ].nPosIndex );

                                g_Enemy[ nIndex ].nPosIndex = IntScanf( 16, 7,

                                                              g_Enemy[ nIndex ].nPosIndex );

                                break;

                             case 2 : // 이전

                                if( nPage - 1 < 0 )

                                    nPage = 0;

                                else

                                    nPage--;

                                break;

                             case 3 : // 다음

                                if( nPage + 1 > 2 )

                                    nPage = 2;

                                else

                                    nPage++;

                                break;

                             }

                        }                                    

                        break;

              }                                 

        }

        break;

 

[소스 15-9] 적 캐릭터의 메뉴 항목에 대한 처리

 

 

 


[STEP 04]

 

[그림 5-26] 4단계 제작 로드맵

 

파일로 저장하거나 읽는 데이터는 [소스 15-1]에서 이미 정의한 것이다.

파일 읽기나 저장을 할 때에는 저장하는 프로그램을 먼저 작성하고 읽기 프로그램을 작성하자. 그 이유는 저장된 데이터가 있어야 읽을 수 있기 때문이다.

 

■ 파일 저장

 

파일 저장 형식은 텍스트 파일 형식으로 한다.

이와 같이 하는 이유는 실제 데이터가 정확히 저장되었는지를 간단한 에디터(메모장)를 통해 살펴보기 위해서이다.

 

[그림 5-27] 저장된 맵 데이터

 

 

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

case 1 : // 파일 저장

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 저장 : " );

        scanf( "%s", strFileName ); fflush( stdin );

        fp = fopen( strFileName, "wt" );

        fprintf( fp, "적 캐릭터수 : %d\n", g_Stage.nEnemyCount );

        fprintf( fp, "적 캐릭터가 출현할 위치 개수 : %d\n",

                                       g_Stage.nEnemyPositionCount );

                        

        for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

            fprintf( fp, "%d %d\n", g_Stage.pEnemyPosition[i].nX,

                                      g_Stage.pEnemyPosition[i].nY );

        fprintf( fp, "보스 초기 위치 : %d %d\n", g_Stage.nBossX, g_Stage.nBossY );

        fprintf( fp, "플레이어 초기 위치 : %d %d\n", g_Stage.nPlayerX ,

                                             g_Stage.nPlayerY );

        fprintf( fp, "적 캐릭터 종류 개수 : %d\n", g_Stage.nEnemyTypeCount );

        for( i = 0 ; i < g_Stage.nEnemyTypeCount ; i++ )

        fprintf( fp, "생명력: %d 이동시간 간격: %d 총알: %d\n", g_EnemyType[i].nLife,

                  g_EnemyType[i].nMoveTime, g_EnemyType[i].nFireTime );

        for( i = 0 ; i < g_Stage.nEnemyCount ; i++ )

        fprintf( fp, "타입인덱스: %d 적캐릭터 생성 인덱스: %d 출현 시간: %d\n",

                g_Enemy[i].nTypeIndex, g_Enemy[i].nPosIndex,

        g_Enemy[i].nAppearanceTime );

 

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

        {

             for( j = 0 ; j < MAP_COL ; j++ )

             {

                   fprintf( fp, "%d ", g_nMap[i][j] );

                }

                fprintf( fp, "\n");

        }

        fclose( fp );

 

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 저장 완료" );

        _getch(); 

        fflush( stdin );

        break;

 

[소스 15-10] 파일 저장

 

■ 파일 읽기

 

파일 저장을 통해 저장된 데이터를 확인했으므로 fscanf()를 이용하여 데이터를 읽어 툴에서 확인할 수 있도록 프로그래밍을 하면 된다.

 

 

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

case 0 : // 파일 읽기

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 읽기 : " );

        scanf( "%s", strFileName ); fflush( stdin );

        fp = fopen( strFileName, "rt" );

        if( fp == NULL )

        {

            BoxXY( 5, 5, 4 );

            gotoxy( 7, 7 ); printf( "파일이 없습니다!" );

            _getch();

            fflush( stdin );

        }else{

            fscanf( fp, "적 캐릭터수 : %d\n", &g_Stage.nEnemyCount );

            fscanf(fp, "적 캐릭터가 출현할 위치 개수 : %d\n",

 &g_Stage.nEnemyPositionCount);

            if( g_Stage.pEnemyPosition != NULL )

               free( g_Stage.pEnemyPosition );

            g_Stage.pEnemyPosition =

                  (POS_XY*)malloc( sizeof( POS_XY )*g_Stage.nEnemyPositionCount );

            for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

                fscanf( fp, "%d %d \n", &g_Stage.pEnemyPosition[i].nX,

                                           &g_Stage.pEnemyPosition[i].nY );

            fscanf( fp, "보스 초기 위치 : %d %d\n", &g_Stage.nBossX,

 &g_Stage.nBossY );

            fscanf( fp, "플레이어 초기 위치 : %d %d\n", &g_Stage.nPlayerX ,

                                                         &g_Stage.nPlayerY );

            fscanf( fp, "적 캐릭터 종류 개수 : %d\n", &g_Stage.nEnemyTypeCount );

            for( i = 0 ; i < g_Stage.nEnemyTypeCount ; i++ )

                fscanf( fp, "생명력: %d 이동시간 간격: %d 총알: %d\n",

                                 &g_EnemyType[i].nLife, &g_EnemyType[i].nMoveTime,

                                 &g_EnemyType[i].nFireTime );

 

            for( i = 0 ; i < g_Stage.nEnemyCount ; i++ )

                fscanf( fp, "타입인덱스: %d 적캐릭터 생성 인덱스: %d 출현 시간: %d\n",

                                       &g_Enemy[i].nTypeIndex, &g_Enemy[i].nPosIndex,

                                         &g_Enemy[i].nAppearanceTime );

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

            {

                 for( j = 0 ; j < MAP_COL ; j++ )

                 {

                    fscanf( fp, "%d ", &g_nMap[i][j] );

                }

            }

            fclose( fp );

            system( "cls" );

            BoxXY( 5, 5, 4 );

            gotoxy( 7, 7 ); printf( "파일 읽기 완료" );

            _getch();

            fflush( stdin );

        }

        break;

 

[소스 15-11] 파일 읽기

 

여기까지 살펴본 내용을 기반으로 하여 탱크 게임 맵 툴을 완성하여 보자.

 

 

 

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

[출처] https://nowcampus.tistory.com/entry/CGP-15%EC%9E%A5-%ED%83%B1%ED%81%AC-%EB%A7%B5?category=655340

 

 

 

 

(CGP) 15장. 탱크 맵툴 만들기

탱크 게임 고전 게임의 하나로 가장 친숙하고 쉬운 게임중 하나이다.

 

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

탱크 게임은 보드 게임과 같이 전체 맵을 보면서 게임을 하게 된다.

 

그러므로 탱크 게임을 만들기 이전에 이 장에서 소개하는 맵툴은 필수라고 할 수 있다.

 

대박유튜브 동영상 강의 주소

(1) http://youtu.be/YSEBsywRrH8

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

(3) http://youtu.be/JcpS3kXQob8

(4) http://youtu.be/SfJ_9mUnh18

(5) http://youtu.be/6jv_DNzWQnw

(6) http://youtu.be/ehGYWVTWx_s

 

 

 


 

15.1 툴 제작 개요

 

앞서 제작한 툴을 통해 툴이 게임 스테이지를 어떻게 다양화시킬 수 있는가를 살펴보았다.

게임의 제작 규모가 커지면 커질수록 툴은 더욱 중요한 역할을 한다.

 

현재 우리가 제작하는 게임은 프로그래머에 의해서 만들어지지만 실제 게임은 프로그래머, 기획, 그래픽등 전체 게임 제작팀에 의해서 개발되어 상용화된다.

그러므로 툴은 이런 다양한 팀원들이 쉽게 기술적인 부분을 사용할 수 있도록 제작되어야 한다. 이 장에서 제작하는 탱크 게임 맵 툴은 앞서 제작한 툴의 통합한 형태라고 할 수 있다.

 

 

 


 

15.2 기획

 

툴을 제작하기 위해서는 먼저 어떤 게임을 제작할 것인지에 대한 기획이 확실해야 한다.  왜냐하면 기획에 따라 툴의 기능이 바뀌기 때문이다.

 

■ 스토리

 

2900년 세계 5차 대전이 발생했다. 적들은 늦은 밤 12시를 기점으로 아군의 진영으로 침투하였다. 아군은 사력을 다해 적군을 섬멸하고 아군 보스를 지켜야 한다.

 

■ 게임 방식

 

주인공 탱크는 방향키로 이동하며 ‘s' 키는 대포를 발사한다.

전체 맵은 블록과 방호벽으로 구성되며 블록은 적과 주인공의 대포로 파괴되지만 방호벽은 파괴되지 않는다. 게임에서 미션 실패는 주인공 탱크가 보호하는 아군 보스가 적으로부터 공격을 받거나 주인공 탱크의 생명값이 0이 되면 미션은 실패된다.

 

■ 제한 사항

 

아군 탱크의 총알을 5발로 제한하되 일정한 간격으로 발사되게 한다.  

적 탱크가 지능적으로 이동과 공격을 하도록 인공지능 요소를 첨가 할 수 있겠지만 전체 게임의 구성에 집중하기 위해서 적 탱크의 지능적인 이동과 공격은 임의의 동작하게 한다.

적 캐릭터는 스테이지당 최대 30개까지 생성할 수 있다.

 

■ 기획 화면

 

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

 

 

 


 

15.3 툴 제작 가이드

 

툴을 제작하기 위해서는 먼저 게임에 사용되는 데이터를 분석해야 한다.

왜냐하면 툴은 게임을 실행하기 위한 것이 아니라 게임에 사용되는 데이터를 편집하기 위한 프로그램이기 때문이다.

 

15장의 탱크 게임 맵 툴은 캐릭터와 맵을 다루기 위한 툴이다. 그러므로 먼저 캐릭터 속성을 주인공과 적 캐릭터로 구분하여 분석하고 그 다음 맵 속성을 분석하면 게임에 사용되는 대부분의 데이터가 결정된다.

여기서 결정된 데이터는 실제 게임에서도 동일하게 사용되므로 코딩 및 데이터 설계에서 많은 시간과 노력을 절약하게 해준다.

 

앞에서는 메뉴를 선택할 때 해당 번호를 입력하여 메뉴를 실행했었다.

여기서는 방향키를 이용하여 메뉴를 선택하는 방식을 사용한다.

이와 같은 방식은 사용자의 편의를 고려하면서 사용자가 번호 선택의 실수를 하지 않게 해준다.

 

데이터 중에는 툴에서 삽입과 삭제, 입력, 수정을 자주해야 하는 데이터가 있다.

이런 경우에는 데이터를 링크드 리스트(Linked List)로 관리할 수 있겠지만 링크드 리스트의 구현이 쉽지 않으므로 여기서는 배열로 데이터 관리를 한다.

현재의 툴을 보면 삭제, 입력, 수정 기능만이 있으므로 이에 대한 데이터 배열을 충분히 생성하고 그중 일부분만을 사용하는 방식으로 툴을 제작하면 된다.

 

 

 


 

15.4 실행 화면 

 

[그림 15-2] 메인 메뉴 화면

 

[그림 15-3] 파일 읽기 화면

 

[그림 15-4] 맵 편집 화면

 

[그림 15-5] 적 캐릭터의 속성 메뉴 화면

 

 

[그림 15-6] 적 캐릭터의 종류 설정 화면

 

[그림 15-7] 적 캐릭터의 출현 시간 설정 화면

 

 

 

[그림 15-8] 적 캐릭터의 생성 위치 설정 화면

 

15.5 툴 제작 로드맵

 

툴 제작을 위한 4단계를 살펴보면 다음과 같다.

 

1단계는 주로 데이터 분석과 기본 구조를 만드는 단계이다.

이 단계에서 메뉴의 선택 방식을 바꿀 수 있는 함수를 제작하여 시범적으로 테스트한다.

 

2단계는 맵을 디자인하기 위한 단계이다.

맵은 11장에서 제작한 Snake 게임 툴과 유사하지만 맵 정보 외에 부가적으로 추가되는 정보가 있다. 맵 정보는 고정된 행과 열로 이루어진 맵 배열에 입력을 하지만 적 탱크의 생성 위치, 아군 탱크의 위치는 가변적이므로 마지막에 설정된 개수를 조사해야 한다.

그래서 [그림 15-3]의 2단계를 보면 ‘데이터 생성’이라는 메뉴가 있다.

 

3단계는 적 캐릭터에 관한 단계이다.

적 캐릭터는 최대 30개까지이며 그중 일부를 사용하게 된다.

 

4단계는 파일을 다루는 단계이다.

기본적으로 읽기와 저장이 있으며 여기서 저장된 데이터는 게임이 시작될 때 읽혀져 설정된다.

 

[STEP 01]

 

[STEP 02]

 

[STEP 03]

 

[STEP 04]

[그림 15-9] 툴 제작 로드맵

 

 

 


 

15.6 단계별 프로그래밍

 

 

[STEP 01]

 

 

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

 

■ 데이터 분석

 

탱크 게임의 데이터는 크게 3가지로 나눌 수 있다.

 

첫째, 스테이지에 관한 데이터이다.

각 스테이지를 생각해 보면 적 탱크 개수와 적 생성 위치, 아군 보스와 아군 탱크 위치, 적 탱크의 종류는 매번 다르게 적용되므로 이 데이터는 툴에서 생성해야 한다.

 

둘째, 맵에 관한 데이터이다.

탱크 게임의 맵은 2차원 배열로 구성할 수 있으며 이 2차원 배열의 행과 열은 화면의 좌표와 일대일 대응된다. 맵에 저장되는 값은 다음의 [표 15-1]과 같다.

 

 

0

1

2

3

4

5

의미

공백

블록

방호벽

적 탱크 생성 위치

보스 위치

아군 탱크

출력 문자

 

 

[표 15-1] 맵 정보

 

셋째,  적 탱크에 관한 데이터이다.

모든 적 탱크에 임의의 속성을 설정하는 것이 아니라 여러 종류의 적 탱크 속성을 정의해  놓고 인덱스를 통해 속성을 선택하여 설정한다.

그리고 적 탱크는 툴에서 정한 위치에서 생성되며 출현 시간은 오름차순으로 설정된다.

 

위의 세 가지 데이터는 아래 [소스 15-1]과 같이 정의할 수 있다.

 

 

스테이지

typedef struct _STAGE

{

        int nEnemyCount;          // 적 캐릭터 수

        int nEnemyPositionCount;    // 적 출현 위치 개수

        POS_XY *pEnemyPosition;   // 적 출현 위치 배열

        int nBossX, nBossY;         // 보스 초기 위치

        int nPlayerX, nPlayerY;      // 주인공 초기 위치

        int nEnemyTypeCount;       // 적 탱크 종류 개수

} STAGE;

적 탱크 종류

tpedef struct _ENEMY_TYPE

{       

        int  nLife;                        // 생명력

        int nMoveTime;   // 속력

        int nFireTime;      // 총알 발사 시간 간격  

} ENEMY_TYPE;

적 탱크 속성

typedef struct _ENEMY

{

        int nTypeIndex;              // 타입 인덱스

        int nPosIndex;               // 초기 위치 인덱스

          int nAppearanceTime;           // 출현 시간

 

          /* 실제 게임에서 사용되는 속성이다.

         int nX, nY;                    // 이동 좌표

         int nState;                 // 상태 전이

         int nLife;                            // 생명력

         int nDirect;                 // 방향      

         unsigned int nOldTime; // 이전 이동 시간

          */

} ENEMY;

 

[소스 15-1] 속성 정의

 

■ 메인 메뉴

 

메인 메뉴는 크게 3가지로 나눌 수 있다.

 

첫째, 파일에 관련된 사항이다.

파일 메뉴에는 기본적으로 읽고 저장하는 기능이 있으며 [소스 15-1]에서 정의한 데이터를 읽거나 저장한다.

 

둘째, 맵에 관련된 사항이다.

맵 메뉴에는 맵을 편집하기 위한 사항과 맵 전체를 초기화하는 기능이 있어야 한다.

맵을 보면서 유동적인 데이터(적 탱크의 개수, 적 탱크의 초기 출현 위치)를 결정할 수 있는 메뉴도 있어야 한다.

 

셋째는 적 탱크에 관한 사항이다.

적 탱크의 생성과 속성을 임의의 값으로 지정하는 것이 아니라 툴에서 직접 입력하거나 설정한다. 이때 적 탱크를 스테이지마다 개별적으로 설정할 수 있지만 몇 가지 탱크 속성을 결정해 놓고 그 종류 안에서 선택적으로 설정한다.

그러므로 적 탱크 종류, 출현 시간, 생성 위치를 지정해 줄 수 있는 메뉴가 있어야 한다.

 

■ 메인 메뉴 화면

 

[그림 15-11] 메인 메뉴

 

메인 메뉴 화면을 보면 이전 메뉴 선택과 차이점이 있는 것을 알 수 있다.

이전에는 각 메뉴마다 고유 번호가 있어서 번호 선택에 따라 서브 메뉴가 실행되었다.

이번에는 사용자 편의를 고려하여 방향키에 따라 메뉴 항목을 이동하는 커서를 두어 메뉴가 선택되도록 해보자.

커서를 이동하는 방법은 사실, 게임과 같이 지우고 그리기를 빠르게 반복하여 마치 커서가 이동하는 것 같이 보이도록 한 것에 불과하다.

이것은 눈의 착시 현상을 이용한 것으로 빠르게 움직이면 연속적인 동작으로 보이는 원리를  이용한 것이다.

 

[실습 예제 15-1]

 

메뉴 선택 방식을 번호 입력에서 커서 이동 방식으로 바꾸어 [그림 15-12]와 같이 출력이 되도록 프로그래밍해 보자.

그리고 엔터키가 입력되면 현재 선택한 메뉴의 인덱스를 함수에서 리턴 되게 한다.

단, 커서 이동 메뉴는 함수로 제작하고 선택된 항목을 알 수 있도록 출력해 보자.

 

(메뉴 문자열 다루기)

 

메뉴와 같은 여러 개의 문자열을 다룰 때는 포인터 배열을 이용하면 보다 쉽게 문자열을 다룰 수 있다. 그 예로 하나의 문자열은 선언할 때에 포인터와 배열을 이용하면 쉽게 할당하여 사용할 수 있다.

 

(예 1) char cStr[20] = "메뉴“ ; 또는 char *pStr = "메뉴”;

 

하지만 이런 문자열이 여러 개이면 각 문자열의 메모리 주소를 저장하는 포인터 배열을 사용하는 것이 휠씬 유용하다.

 

(예 2) char *pMenu[3] = { "메뉴“, ”적 캐릭터“, ”종료“ };

 

 

[그림 15-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

#include <stdio.h>

#include <windows.h>

#include <conio.h>

 

char *g_Menu[5] = {"파일  읽기", "파일  저장", "맵 정보", "적 캐릭터", "종    료" };

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int VerticalMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nLength, nMaxLength = 0;

    char cBlank[50];

 

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

           nMaxLength = nLength;

    }           

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[nMaxLength + 6 ] = 0; // 널 문자 처리

                 

    while( 1 )

    {   

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

        {       

             gotoxy( nX - 2, nY + i*2 );

             printf( "%s", cBlank );

             gotoxy( nX, nY + i*2 );

             printf( "%s", pMenu[i] );

        }

        gotoxy( nX - 2, nMenu*2 + nY );

        printf( "[" );

        gotoxy( nX + nMaxLength + 1, nMenu*2 + nY );

        printf( "]" );

 

        nKey = _getch();

        switch( nKey )

        {               

         case 72 : // 화살표 위쪽 키

                if( nMenu - 1 >= 0 )

                   nMenu--;

                 break;

         case 80 : // 화살표 아래 키

                if( nMenu + 1 < nCount )

                    nMenu++;

                break;                          

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int main(void)

{

    int nMenu;

 

    while( 1 )

    {

          nMenu = VerticalMenu( 6, 3, g_Menu, 5 );

        gotoxy( 6, 15 );

        printf( "%s 선택", g_Menu[nMenu] );

        _getch();

        gotoxy( 6, 15 );

        printf( "                      " );

    }

    return 0;

}

 

[소스 15-2] 메인 메뉴

 

19행부터 26행까지는 메뉴 문자열 중에서 최대 길이를 구하고 그 크기만큼 지우기 위한 공백 문자열을 만드는 부분이다.

메뉴의 출력은 지우고 그리기를 반복하는 것이지만 지울 때 화면 전체를 지우기보다는 해당되는 메뉴 항목만 지우는 것이 효과적이므로 이와 같이 공백 문자열을 만드는 것이다.

문자열의 마지막에는 항상 널문자가 있어야 하므로 26행에서는 문자열의 끝에 널문자를 넣어 주고 있다.

 

30행부터 36행까지는 메뉴를 출력하는 부분이다.

그중에서 32행과 33행은 지우는 부분이며 34행과 35행은 메뉴를 출력하는 부분이다.

 

■ 메뉴 선택 구조 만들기

 

메인 메뉴와 서브 메뉴간의 전체적인 구조는 [그림 15-13]과 같다.

 

[그림 15-13] 메뉴 선택 구조

 

위와 같이 메뉴는 큰 항목에서 작은 항목으로 나눠져 세부적인 처리를 한다.

 

[실습 예제 15-1]에서 제작한 메뉴 형식은 커서를 수직으로만 이동하여 메뉴를 선택하였다. 이와 같은 메뉴 형식은 메인 메뉴 형식에 적합하다.

서브 메뉴의 경우에는 메뉴만 보여주는 것이 아닌 맵과 같이 큰 화면을 차지하는 정보도 출력해야 하므로 수직 메뉴가 아닌 수평 메뉴 방식도 사용해야 한다.

[그림 15-8]에서 세부적인 메뉴에 해당하는 입력, 수정, 삭제 등은 수평적인 메뉴 방식을 사용해야 한다.

 

[실습 예제 15-2]

 

[그림 15-13]의 메뉴 항목을 수직 또는 수평으로 선택할 수 있도록 프로그래밍해 보자.

메인 메뉴와 적 캐릭터의 서브 메뉴는 수직 메뉴 선택 방식을 사용하고 나머지는 수평 메뉴 선택 방식이 되도록 한다.

 

[그림 15-14] 메인 메뉴

 

[그림 15-15] 적 캐릭터의 서브 메뉴

 

 

[그림 15-16] 적 캐릭터 출현 시간의 수평 메뉴

 

[그림 15-17] 적 캐릭터 출현 시간의 입력 메뉴가 출력된 화면

 

 

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

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

#include <stdio.h>

#include <windows.h>

#include <conio.h>

 

char *g_Menu[5] = {"파일  읽기", "파일  저장", "맵 정보", "적 캐릭터", "종    료" };

char *g_Sub[4] = { "입력", "수정", "삭제", "상위" };

char *g_Sub2[6] = { "입력", "수정", "삭제", "이전", "다음", "상위" };

char *g_Sub3[5] = { "입력", "수정", "이전", "다음", "상위" };

char *g_StageSubMenu[4] = { "적 캐릭터 종류", "적 캐릭터 출현 시간",

                            "적 캐릭터 출현 위치", "상위 메뉴로" };

char *g_MapSubMenu[4] = {"편집", "맵 전체 삭제", "데이터 생성", "상위 메뉴로" };

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int VerticalMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nLength, nMaxLength = 0;

    char cBlank[50];

 

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

            nMaxLength = nLength;

    }   

        

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[nMaxLength + 6 ] = 0; // 널 문자 처리

                 

    while( 1 )

    {   

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

        {       

             gotoxy( nX - 2, nY + i*2 );

             printf( "%s", cBlank );

             gotoxy( nX, nY + i*2 );

             printf( "%s", pMenu[i] );

        }

        gotoxy( nX - 2, nMenu*2 + nY );

        printf( "[" );

        gotoxy( nX + nMaxLength + 1, nMenu*2 + nY );

        printf( "]" );

 

        nKey = _getch();

        switch( nKey )

        {               

         case 72 : // 화살표 위쪽 키

                if( nMenu - 1 >= 0 )

                    nMenu--; 

                break;

         case 80 : // 화살표 아래 키

                if( nMenu + 1 < nCount )

                    nMenu++;

                break;                          

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int LineMenu( int nX, int nY, char** pMenu, int nCount )

{

    int nKey, i, nMenu = 0, nMaxLength = 0, nLength;

    char cBlank[80];

        

    // 최대 메뉴 항목의 최대 길이와 지우기 위한 공백

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

    {

        nLength = strlen( pMenu[i] );

        if( nLength > nMaxLength )

            nMaxLength = nLength;

    }   

    memset( cBlank, ' ', sizeof( cBlank ) );

    cBlank[ (nMaxLength+2)*nCount+1] = 0; // 널 문자 처리

 

    while( 1 )

    {           

        gotoxy( nX - 2, nY ) ;

        printf( "%s", cBlank );

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

        {

              gotoxy( nX + i*(nMaxLength + 2), nY);

              printf( "%s", pMenu[i] );

        }

        gotoxy( nMenu*(nMaxLength + 2) + nX -1, nY ); printf( "[" );

        gotoxy( nMenu*(nMaxLength + 2) + nX + nMaxLength, nY); printf( "]" );

        

          nKey = _getch();

        switch( nKey )

        {               

         case 75 : // 화살표 왼쪽 키

                nMenu--;

                if( nMenu < 0 )

                    nMenu = 0;

                 break;

         case 77 : // 화살표 오른쪽 키

                nMenu++;

                if( nMenu == nCount )

                    nMenu = nCount - 1 ;                          

                break;

         case 13 : // 엔터키 (CR)                         

                return nMenu;                            

        }

    }   

}

 

int main(void)

{

    int nMenu;   

 

    while( 1 )

    {

        system( "cls" );

        nMenu = VerticalMenu( 17, 6, g_Menu, 5 );          

        if( nMenu == 4 )

            break;

 

        switch( nMenu )

        {

        case 0:

                system( "cls" );

                gotoxy( 17, 10 );

                printf( "파일 읽기 실행" );

                _getch();

                break;

        case 1:

                system( "cls" );

                gotoxy( 17, 10 );

                printf( "파일 저장 실행" );

                _getch();

                break;

        case 2:

                while( 1 )

                {

                   system( "cls" );

                   nMenu = VerticalMenu( 17, 6, g_MapSubMenu, 4 );

                   if( nMenu == 3 )

                       break;

 

                    switch( nMenu )

                    {

                     case 0:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "편집 상태" );

                          _getch();

                          break;

                     case 1:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "맵 전체 삭제" );

                          _getch();

                          break;

                     case 2:

                          system( "cls" );

                          gotoxy( 17, 10 );

                          printf( "데이터 생성" );

                          _getch();

                          break;                                 

                    }

                }

                break;

        case 3:                         

                while( 1 )

                {

                    system( "cls" );

                    nMenu = VerticalMenu( 17, 6, g_StageSubMenu, 4 );

                    if( nMenu == 3 )

                        break;

                                        

                     switch( nMenu )

                     {  

                      case 0:

                            while( 1 )

                            {

                                 system( "cls" );

                                 nMenu = LineMenu( 5, 5, g_Sub, 4 );

                                 if( nMenu == 3 )

                                   break;

 

                                  switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 종류 실행" );

                                        gotoxy( 17, 10 );

                                        printf( "삭제 상태" );

                                        _getch();

                                        break;

                                }

                            }

                            break;

                      case 1:

                            while( 1 )

                            {   

                                  system( "cls" );

                                nMenu = LineMenu( 5, 5, g_Sub2, 6 );

                                if( nMenu == 5 )

                                    break;

 

                                switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "삭제 상태" );

                                        _getch();

                                        break;

                                case 3:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "이전 상태" );

                                        _getch();

                                        break;

                                case 4:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 시간");

                                        gotoxy( 17, 10 );

                                        printf( "다음 상태" );

                                        _getch();

                                        break;

                                }

                           }

                           break;

                      case 2:

                            while(1)

                            {

                                  system( "cls" );

                                nMenu = LineMenu( 5, 5, g_Sub3, 5 );

                                if( nMenu == 4 )

                                    break;

                                

                                switch( nMenu )

                                {

                                case 0:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "입력 상태" );

                                        _getch();

                                        break;

                                case 1:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "수정 상태" );

                                        _getch();

                                        break;

                                case 2:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "이전 상태" );

                                        _getch();

                                        break;

                                case 3:

                                        system( "cls" );

                                        gotoxy( 17, 8 );

                                        printf( "적 캐릭터 출현 위치" );

                                        gotoxy( 17, 10 );

                                        printf( "다음 상태" );

                                        _getch();

                                        break;

                                }

                            }

                            break;

                     }

                }

                break;          

        }

    }

    return 0;

}

 

[소스 15-3] 메뉴 선택 방식

 

 

 


 

[STEP 02] 

[그림 15-18] 2단계 제작 로드맵

 

■ 맵

 

탱크 맵은 12장에서 제작한 Snake 게임과 동일한 맵 구조를 사용한다.

맵 정보를 저장하기 위한 이차원 배열에는 [표 15-1]의 값이 저장되고 해당 문자가 출력된다. 이차원 배열의 행과 열은 화면 좌표와 일대일 대응되므로 행과 열의 개수는 화면에 출력할 맵 크기를 결정한다.

 

- 편집

 

맵 편집에는 입력, 수정, 삭제 과정이 포함되어 있다.

맵을 편집한다는 것은 결국 맵 데이터를 저장하는 이차원 배열의 값을 입력, 수정, 삭제하는 것이다. 이때 입력과 수정은 데이터를 대입하는 것이지만 삭제는 실제 이차원 배열의 요소를 삭제하는 것이 아니라 출력에서 제외시키기 위한 값을 입력하는 것이다.

현재 공백은 0일 때 출력하는 것이므로 0 값을 대입하는 것이 삭제이다.

 

[그림 15-19] 맵 편집 상태

 

- 맵 전체 삭제

 

맵 전체를 공백으로 채워주는 0을 행과 열 개수만큼 일일이 입력하는 것은 상당히 번거로운 일이다. 그래서 ‘맵 전체 삭제’ 메뉴를 선택하면 맵 전체가 0으로 채워지도록 [소스 15-4]의 memset() 함수를 사용하면 된다.

 

 

memset( g_nMap, 0, sizeof( int )*MAP_ROW*MAP_COL );

 

[소스 15-4] memset()

 

- 데이터 생성

 

[그림 15-19]를 보면 편집되는 데이터가 0부터 5까지의 값이라는 것을 알 수 있다.

이 값들 중에는 맵 데이터로만 있어야 하는 값과 맵 데이터를 다른 용도로 사용하기 위한 값이 있다. 예를 들면 블록, 방호벽은 맵 데이터로만 존재하면 되지만 적 탱크의 출현 위치와 아군 보스의 위치, 아군 탱크의 위치는 맵에 설정된 행과 열의 좌표를 얻어 다른 용도로 사용하기 위한 것이다.

물론 적 탱크의 출현 위치를 스테이지마다 임의로 생성할 수 있겠지만 맵에서 설정한다면  기획된 시나리오대로 적 탱크를 출력할 수 있게 된다. 또한 좌표를 코드로 직접 입력하는 것보다는 맵이 출력된 화면에서 설정하는 것이 훨씬 쉽기 때문에 이 방법을 사용한다.

아래 [소스 15-5] 스테이지 구조체에서 ①부터 ④까지의 변수에 맵 데이터가 좌표값으로 변환되어 저장된다.

 

 

typedef struct _STAGE_INFO

{

        int nEnemyCount;          // 적 캐릭터 수

        int nEnemyPositionCount;    // 적 캐릭터 위치 개수  ------------ ①

        POS_XY *pEnemyPosition;   // 적 캐릭터의 출현 위치 배열 --------- ②

        int nBossX, nBossY;         // 아군 보스의 초기 위치    ---------- ③

        int nPlayerX, nPlayerY;      // 주인공의 초기 위치 ----------- ④

        int nEnemyTypeCount;       // 적 탱크의 종류 개수

} STAGE_INFO;

 

[소스 15-5] 스테이지 구조체

 

편집 메뉴 중에 ‘데이터 생성’ 메뉴는 맵 데이터를 다른 용도로 사용하기 위한 변수에 값을 설정하기 위한 메뉴이다. 맵 편집을 다 한 후에는 반드시 실행시켜 변수를 초기화해야 한다. 물론 이 부분도 자동으로 설정되게 프로그래밍할 수 있겠지만 프로그래머의 수고를 조금 덜어 준다는 차원에서 이 방식을 사용한다.

 

[실습 예제 15-3]

 

[그림 15-19]와 같이 맵 편집과 맵 전체 초기화, 데이터 생성이 가능하도록 [실습 예제 15-2]의 구조 안에서 프로그래밍해 보자.

맵 이동은 방향키(상 72, 하 80, 좌 77, 우 75 )를 이용하며 1부터 6까지의 입력을 받아 맵 데이터를 입력을 받거나 수정한다. 

 

 

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

case 2 : // 맵 정보

     nPositionCount = nRow = nCol = 0;

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

if( g_nMap[i][j] == 3 )

nPositionCount++;

}

}

gotoxy( 52, 1 ); printf( "★ 메뉴 항목 " );

gotoxy( 50, 2 ); printf( "┏━━━━━━━━┓" );

gotoxy( 50, 3 ); printf( "┃ ┃" );

gotoxy( 50, 4 ); printf( "┃ ┃" );

gotoxy( 50, 5 ); printf( "┃ ┃" );

gotoxy( 50, 6 ); printf( "┃ ┃" );

gotoxy( 50, 7 ); printf( "┃ ┃" );

gotoxy( 50, 8 ); printf( "┃ ┃" );

gotoxy( 50, 9 ); printf( "┃ ┃" );

gotoxy( 50, 10 ); printf( "┗━━━━━━━━┛" );

gotoxy( 52, 12 ); printf( "1: 블록 " );

gotoxy( 52, 13 ); printf( "2: 방호벽 " );

gotoxy( 52, 14 ); printf( "3: 적 탱크 출현 위치 " );

gotoxy( 52, 15 ); printf( "4: 아군 보스의 위치 설정 " );

gotoxy( 52, 16 ); printf( "5: 주인공 위치 설정 " );

gotoxy( 52, 17 ); printf( "6: 삭제" );

gotoxy( 52, 18 ); printf( "7: 메뉴로" );

while( 1 )

{

Box();

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

gotoxy( j*2 + 2, i+1);

printf( "%s", g_Title[ g_nMap[i][j] ] );

 }

}

nMenu = VerticalMenu( 54, 3, g_MapSubMenu, 4 );

if( nMenu == 3 )

{

system("cls" );

break;

}

 

switch( nMenu )

{

case 0 : // 편집 상태

while( 1 )

{

gotoxy( nCol*2 + 2, nRow+1 );

nKey = _getch();

if( nKey == '7' )

break;

switch( nKey )

{

case 72 : // 위쪽

if( nRow - 1 >= 0 )

nRow--;

break;

case 80 : // 아래

if( nRow + 1 < MAP_ROW )

nRow++;

break;

case 77 : // 오른쪽

if( nCol + 1 < MAP_COL )

nCol++;

break;

case 75 : // 왼쪽

if( nCol - 1 >= 0 )

nCol--;

break;

case '1' : // 블록

g_nMap[nRow][nCol] = 1;

break;

case '2' : // 방호벽

g_nMap[nRow][nCol] = 2;

break;

case '3' : // 적 탱크의 출현 위치

g_nMap[nRow][nCol] = 3;

nPositionCount++;

break;

case '4' : // 아군 보스 설정

g_nMap[nRow][nCol] = 4;

g_Stage.nBossX = nCol;

g_Stage.nBossY = nRow;

break;

case '5' : // 주인공 설정

g_nMap[nRow][nCol] = 5;

g_Stage.nPlayerX = nCol;

g_Stage.nPlayerY = nRow;

break;

case '6' : // 삭제

if( g_nMap[nRow][nCol] == 3 )

nPositionCount--;

g_nMap[nRow][nCol] = 0;

break;

 }

gotoxy( nCol*2 + 2, nRow + 1); // x, y

printf( "%s", g_Title[ g_nMap[nRow][nCol] ] );

}

break;

case 1 :

// 맵 전체 삭제

memset( g_nMap, 0, sizeof( int )*MAP_ROW*MAP_COL );

break;

case 2 :

// 데이터 생성

g_Stage.nEnemyPositionCount = nPositionCount;

if( g_Stage.pEnemyPosition != NULL )

free( g_Stage.pEnemyPosition );

g_Stage.pEnemyPosition =

        (POS_XY*)malloc( sizeof(POS_XY)*nPositionCount );

nPositionIndex = 0;

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

{

for( j = 0 ; j < MAP_COL ; j++ )

{

if( g_nMap[i][j] == 3 )

{

 g_Stage.pEnemyPosition[nPositionIndex].nX = j;

g_Stage.pEnemyPosition[nPositionIndex++].nY = i; }

}

}

BoxXY( 5, 5, 4 );

gotoxy( 7, 7 ); printf( "데이터 생성 완료" );

_getch();

break;

}

}

break;

 

 [소스 15-6] 맵 편집 소스

 

109행부터 123행까지는 적 탱크의 출현 위치를 저장하기 위한 배열에 정보를 저장하는 부분이다.

 

 


[STEP 03]

 

[그림 15-20] 3단계 제작 로드맵

 

■ 적 캐릭터

 

적 캐릭터는 한 스테이지당 30개로 제한되어 있다.

적 캐릭터 속성을 스테이지마다 임의로 입력할 수 있겠지만 기획 의도를 충분히 반영하기 위해서 적 캐릭터 속성은 툴에서 입력해야 한다.

위의 [그림 15-20]을 보면 적 캐릭터의 데이터를 종류, 출현 시간, 생성 위치에 따라 입력, 수정, 삭제를 하게 된다.

 

- 종류

 

적 캐릭터 종류를 미리 생성해 놓고 선택적으로 사용하는 이유는 최대 30개의 적 캐릭터를 전부 다르게 설정하기 보다는 미리 종류를 설정해 놓고, 선택적으로 속성을 설정하는 방법이 적 캐릭터 설정을 쉽게 할 수 있기 때문이다.

 

 

[그림 15-21] 적 캐릭터 종류

 

‘적 캐릭터 종류’ 항목에는 생명력, 이동 시간 간격, 총알 발사 시각이 있다.

‘적 캐릭터 종류’ 항목은 아래 [소스 15-7]의 구조체를 설정한다.

 

 

typedef struct _ENEMY_TYPE

{       

        int  nLife;                 // 생명력

        unsigned int nMoveTime;   // 속력

        unsigned int nFireTime;    // 총알 발사 시간 간격   

} ENEMY_TYPE;

 

[소스 15-7] 적 캐릭터 종류의 속성 정의

 

- 출현 시간

 

출현 시간은 게임을 더욱 생동감 있게 만드는 요소 중의 하나이다.

서로 다른 위치에서 서로 다른 종류의 적 캐릭터 4대가 동시에 출력하게 하려면 적 캐릭터의 출현 시간을 동일하게 하고 서로 다른 종류의 적 캐릭터를 설정하면 된다.

이와 같이 출현 시간을 개별적으로 설정하면 더욱 다양하게 캐릭터를 출력할 수 있다.

 

[그림 15-22] 적 캐릭터의 출현 시간 설정

 

출현 시간이 설정된 개수만큼 적 캐릭터는 생성되어야 하며 적 캐릭터의 개수는 출현 시간의 설정 개수에 따라 결정된다.

  

- 출현 위치

 

일정한 위치에서 적 캐릭터가 출현하기 보다는 다양한 위치에서 출현하는 것이 게임을 더욱 흥미롭게 한다. 출현 위치를 설정하기 위해, 사용자가 맵에서 위치를 파악하고 좌표를 일일이 입력하는 방법보다 맵상의 커서를 통해 출현 위치로 이동하여 설정하는 것이 식별하기가  쉽다. 툴은 이와 같은 편의 사항을 사용자에게 제공해야 한다.

 

[그림 15-23] 맵상에서 적 캐릭터의 출현 위치 설정

 

[실습 예제 15-4]

 

입력과 삭제 기능은 scanf()와 gets()와 같은 함수를 이용하면 쉽게 해결이 된다. 하지만 수정의 경우는 기존 데이터를 화면에 출력한 후에 수정할 수 있어야 하므로 제공되는 함수만의 기능으로는 해결하기가 어렵다. 그래서 항목을 보여 주고 입력을 받거나 백스페이스 키를 이용하여 데이터를 지울 수 있는 함수를 제작해 보자.

 

[그림 15-24] 기존 데이터 출력

 

[그림 15-25] 수정 데이터 출력

 

 

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

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

#include <conio.h>

 

void gotoxy( int x, int y )

{

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), CursorPosition );

}

 

int IntScanf( int x, int y, int nNumber )

{

    char strTemp[20], cMunja;

    int nIndex = 0, i, nLength;

         

    itoa( nNumber, strTemp, 10 );  

    nLength = strlen( strTemp ); // 기존 문자의 길이

    // 원문 출력

    gotoxy( x, y ); printf( "%s", strTemp );

    gotoxy( x, y );      

    while( 1 )

    {           

        cMunja = _getch();

        switch( cMunja )

        {

        case 13 : // 엔터키

                return atoi( strTemp );

        case 8 : // 백스페이스 처리

                 if( nIndex - 1 < 0 )

                   nIndex = 0;

                else

                {

                   nIndex--;

                   strTemp[nIndex] = 0; 

                 }                               

                break;

        default :

                if( nIndex + 1 < 19 ) // 18문자 이상 들어 오면 입력 받지 않는다.

                {                                       

                    strTemp[nIndex++] = cMunja;

                    strTemp[nIndex] = 0; // 널문자

                }

        }

 

        // Note: 길이만큼 화면을 지운다.

        gotoxy( x, y );

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

             printf(" ");                

                                

        gotoxy( x, y ); printf( "%s", strTemp );

        nLength = strlen( strTemp );

    }

}

 

int main(void)

{

    int nVal = 200;

 

    nVal = IntScanf( 3, 1, nVal );

    printf( "\n 수정된 값 : %d", nVal );

    _getch();

    return 0;

}

 

[소스 15-8] 정수값 편집 함수

 

[실습 예제 15-5]

 

적 캐릭터 메뉴에 해당되는 종류, 출현 시간, 출현 위치 항목에서 입력, 수정, 삭제, 이전, 다음 메뉴에 의해 실제 처리가 되도록 프로그래밍해 보자. 기본적인 구조는 [실습 예제 15-2]를 사용하며 수정할 때에는 [실습 예제 15-4]에서 제작한 함수를 사용한다.

 

 

 

 

case 3 : // 적 캐릭터 정보                   

        while( 1 )

        { 

             Box();

             gotoxy( 19, 2 );

             printf( "%s", "적 캐릭터" );

             nMenu = VerticalMenu( 13, 6, g_StageSubMenu, 4 );

             if( nMenu == 3 ) // 상위 메뉴로

                break;

             

              switch( nMenu )

              {

                case 0 : // 적캐릭터 종류 별 속성 설정

                        while( 1 )

                        {

                             Box();

                             gotoxy( 2, 4 );

                                   printf( "----------------------------");

                             gotoxy( 2, 5 );

                                printf("인덱스 생명력 이동시간간격 총알발사시간");

                             gotoxy( 2, 6 );

                                   printf( "----------------------------");

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

                             {

                                gotoxy( 3, 7+i);

                                printf( "  %d    %2d     %4d     %4d  ",

                                                  i,  g_EnemyType[i].nLife,

                                                  g_EnemyType[i].nMoveTime,

                                                   g_EnemyType[i].nFireTime );

                             }

                             nMenu = LineMenu( 5, 2, 43, g_Sub, 4 );

                             if( nMenu == 3 )

                                break;

                                                                

                             switch( nMenu )

                             {

                              case 0 : // 입력

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( "인덱스 : ");

                                    gotoxy( 7, 7 ); printf( "생명력 : " );

                                    gotoxy( 7, 8 ); printf("이동시간 간격 : " );

                                    gotoxy( 7, 9 );printf("총알발사 시간간격:");

                                    gotoxy( 17, 6 );

                                    scanf( "%d", &nIndex ); fflush( stdin );

                                    gotoxy( 17, 7 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nLife );

                                    fflush( stdin );

                                    gotoxy( 25, 8 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nMoveTime );

                                    fflush( stdin );

                                    gotoxy( 29, 9 );

                                    scanf( "%d", &g_EnemyType[ nIndex ].nFireTime );

                                    fflush( stdin );           

                                    g_Stage.nEnemyTypeCount++;

                                    break;

                              case 1 : // 수정

                                    BoxXY( 5, 5, 6 );

                                    gotoxy( 7, 6 );

                                    printf( "수정 인덱스 : ");

                                    scanf( "%d", &nIndex );

                                    fflush( stdin );

                                    gotoxy( 7, 7 );

                                    printf( "생명력 : %d" ,

                                    g_EnemyType[ nIndex ].nLife );

                                    gotoxy( 7, 8 );

                                    printf( "이동 시간 간격 : %d",

                                    g_EnemyType[ nIndex ].nMoveTime );

                                    gotoxy( 7, 9 );

 

                                    printf( "총알 발사 시간 간격 : %d",

                                               g_EnemyType[ nIndex ].nFireTime );

                                    g_EnemyType[ nIndex ].nLife =

                                          IntScanf(6, 7, g_EnemyType[ nIndex ].nLife );

                                    g_EnemyType[ nIndex ].nMoveTime =

                                          IntScanf(24, 8, g_EnemyType[ nIndex ].nMoveTime);

                                    g_EnemyType[ nIndex ].nFireTime =

                                          IntScanf(29,9,g_EnemyType[ nIndex ].nFireTime );

                                    break;

                        case 2 : // 삭제

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 );

                                    printf( " 삭제 인덱스:");

                                    scanf( "%d", &nIndex );

                                    memset( &g_EnemyType[nIndex], 0,

                                                          sizeof( ENEMY_TYPE ) );            

                                    g_Stage.nEnemyTypeCount--;

                                    break;

                             }

                        }

                        break;

                case 1 :  // 적 캐릭터 출현 시간 설정

                         nPage = 0;

                         while( 1 )

                         {

                            Box();

                            gotoxy( 2, 4 );

                                  printf( "----------------------------");

                            gotoxy( 2, 5 );

                                  printf( " 순번      종류         출현 시간" );

                            gotoxy( 2, 6 );

                                  printf( "----------------------------");

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

                            {

                                 gotoxy( 3, 7 + i );

                                 printf( " %d         %d        %d", i + nPage*10,

                                         g_Enemy[i + nPage*10].nTypeIndex,

                                      g_Enemy[i + nPage*10].nAppearanceTime );

                            }

                            nMenu = LineMenu( 5, 2, 45, g_Sub2, 6 );

                            if( nMenu == 5 )

                                break;

 

                            switch( nMenu )

                            {

                             case 0 : // 입력

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( "순번 : ");

                                    gotoxy( 7, 7 ); printf( "종류 : " );

                                    gotoxy( 7, 8 ); printf( "출현 시간 : " );

                                    gotoxy( 14, 6 );

                                            scanf( "%d", &nIndex );

                                            fflush( stdin );

                                    gotoxy( 14, 7 );

                                    scanf( "%d", &g_Enemy[ nIndex ].nTypeIndex );

                                    fflush( stdin );

                                    gotoxy( 19, 8 );

                                    scanf( "%d",&g_Enemy[ nIndex ].nAppearanceTime );

                                    fflush( stdin );                  

                                    g_Stage.nEnemyCount++;

                                    break;

                             case 1 : // 수정

                                    BoxXY( 5, 5, 6 );

                                    gotoxy( 7, 6 ); printf( "순번 : ");

                                    scanf( "%d", &nIndex ); fflush( stdin );

                                    gotoxy( 7, 7 );

                                    printf( "종류:%d",g_Enemy[ nIndex ].nTypeIndex );

                                    gotoxy( 7, 8 );

                                    printf( "출현 시간 : %d",

                                    g_Enemy[ nIndex ].nAppearanceTime );

                                    g_Enemy[ nIndex ].nTypeIndex = IntScanf( 14, 7,

                                    g_Enemy[ nIndex ].nTypeIndex );

                                    g_Enemy[ nIndex ].nAppearanceTime = 

                                    IntScanf( 19, 8, g_Enemy[ nIndex ].nAppearanceTime );

                                    break;

                             case 2 : // 삭제

                                    BoxXY( 5, 5, 5);

                                    gotoxy( 7, 6 ); printf( " 삭제 인덱스 : ");

                                    scanf( "%d", &nIndex );

                                    memset( &g_Enemy[nIndex], 0, sizeof( ENEMY ) );

                                    g_Stage.nEnemyCount--;

                                    break;

                             case 3:

                                    if( nPage - 1 < 0 )

                                        nPage = 0;

                                    else

                                        nPage--;

                                    break;      

                             case 4 :

                                    if( nPage + 1 > 2 )

                                        nPage = 2;

                                    else

                                        nPage++;

                                    break;    

                            }

                       }

                       break;

                case 2 : // 적 캐릭터 초기 위치 설정

                     nPage = 0;

                     while( 1 )

                     {

                        system( "cls" );

                       // Note: 적 캐릭터 생성 위치 출력

                        Box();

                        for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

                        {

                           gotoxy( g_Stage.pEnemyPosition[i].nX*2 + 2,

                                                         g_Stage.pEnemyPosition[i].nY+1);

                           printf( "%s:%d", g_Title[3], i );  

                         }          

                         gotoxy( 50, 1 ); 

                         printf( "----------------------------");

                         gotoxy( 50, 2 ); printf( "순번 | 출현시간 | 인덱스" );

                         gotoxy( 50, 3 );

                         printf( "------------------------");

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

                         {

                            gotoxy( 50, 4 + i );

                            printf( " %d     %6d         %d", i + nPage*10,

                                  g_Enemy[i + nPage*10].nAppearanceTime,

                                 g_Enemy[i +Page*10].nPosIndex );

                

                            nMenu = LineMenu( 3, 21, 60, g_Sub3, 5 );

                            if( nMenu == 4 ) // 상위

                            {

                                system( "cls" );

                                break;

                            }

 

                            switch( nMenu )

                            {

                             case 0 : // 입력

                                BoxXY( 5, 5, 3);

                                gotoxy( 7, 6 ); printf( "순번 : ");

                                gotoxy( 7, 7 ); printf( "인덱스 : " ); 

                                gotoxy( 14, 6 );

                                scanf( "%d", &nIndex ); fflush( stdin );

                                gotoxy( 16, 7 );

                                scanf( "%d", &g_Enemy[ nIndex ].nPosIndex );

                                fflush( stdin );

                                break;

                             case 1 : // 수정

                                BoxXY( 5, 5, 3 );

                                gotoxy( 7, 6 ); printf( "순번 : ");

                                scanf( "%d", &nIndex ); fflush( stdin );

                                gotoxy( 7, 7 );

                                printf( "인덱스:%d" , g_Enemy[ nIndex ].nPosIndex );

                                g_Enemy[ nIndex ].nPosIndex = IntScanf( 16, 7,

                                                              g_Enemy[ nIndex ].nPosIndex );

                                break;

                             case 2 : // 이전

                                if( nPage - 1 < 0 )

                                    nPage = 0;

                                else

                                    nPage--;

                                break;

                             case 3 : // 다음

                                if( nPage + 1 > 2 )

                                    nPage = 2;

                                else

                                    nPage++;

                                break;

                             }

                        }                                    

                        break;

              }                                 

        }

        break;

 

[소스 15-9] 적 캐릭터의 메뉴 항목에 대한 처리

 

 

 


[STEP 04]

 

[그림 5-26] 4단계 제작 로드맵

 

파일로 저장하거나 읽는 데이터는 [소스 15-1]에서 이미 정의한 것이다.

파일 읽기나 저장을 할 때에는 저장하는 프로그램을 먼저 작성하고 읽기 프로그램을 작성하자. 그 이유는 저장된 데이터가 있어야 읽을 수 있기 때문이다.

 

■ 파일 저장

 

파일 저장 형식은 텍스트 파일 형식으로 한다.

이와 같이 하는 이유는 실제 데이터가 정확히 저장되었는지를 간단한 에디터(메모장)를 통해 살펴보기 위해서이다.

 

[그림 5-27] 저장된 맵 데이터

 

 

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

case 1 : // 파일 저장

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 저장 : " );

        scanf( "%s", strFileName ); fflush( stdin );

        fp = fopen( strFileName, "wt" );

        fprintf( fp, "적 캐릭터수 : %d\n", g_Stage.nEnemyCount );

        fprintf( fp, "적 캐릭터가 출현할 위치 개수 : %d\n",

                                       g_Stage.nEnemyPositionCount );

                        

        for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

            fprintf( fp, "%d %d\n", g_Stage.pEnemyPosition[i].nX,

                                      g_Stage.pEnemyPosition[i].nY );

        fprintf( fp, "보스 초기 위치 : %d %d\n", g_Stage.nBossX, g_Stage.nBossY );

        fprintf( fp, "플레이어 초기 위치 : %d %d\n", g_Stage.nPlayerX ,

                                             g_Stage.nPlayerY );

        fprintf( fp, "적 캐릭터 종류 개수 : %d\n", g_Stage.nEnemyTypeCount );

        for( i = 0 ; i < g_Stage.nEnemyTypeCount ; i++ )

        fprintf( fp, "생명력: %d 이동시간 간격: %d 총알: %d\n", g_EnemyType[i].nLife,

                  g_EnemyType[i].nMoveTime, g_EnemyType[i].nFireTime );

        for( i = 0 ; i < g_Stage.nEnemyCount ; i++ )

        fprintf( fp, "타입인덱스: %d 적캐릭터 생성 인덱스: %d 출현 시간: %d\n",

                g_Enemy[i].nTypeIndex, g_Enemy[i].nPosIndex,

        g_Enemy[i].nAppearanceTime );

 

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

        {

             for( j = 0 ; j < MAP_COL ; j++ )

             {

                   fprintf( fp, "%d ", g_nMap[i][j] );

                }

                fprintf( fp, "\n");

        }

        fclose( fp );

 

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 저장 완료" );

        _getch(); 

        fflush( stdin );

        break;

 

[소스 15-10] 파일 저장

 

■ 파일 읽기

 

파일 저장을 통해 저장된 데이터를 확인했으므로 fscanf()를 이용하여 데이터를 읽어 툴에서 확인할 수 있도록 프로그래밍을 하면 된다.

 

 

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

case 0 : // 파일 읽기

        system( "cls" );

        BoxXY( 5, 5, 4 );

        gotoxy( 7, 7 ); printf( "파일 읽기 : " );

        scanf( "%s", strFileName ); fflush( stdin );

        fp = fopen( strFileName, "rt" );

        if( fp == NULL )

        {

            BoxXY( 5, 5, 4 );

            gotoxy( 7, 7 ); printf( "파일이 없습니다!" );

            _getch();

            fflush( stdin );

        }else{

            fscanf( fp, "적 캐릭터수 : %d\n", &g_Stage.nEnemyCount );

            fscanf(fp, "적 캐릭터가 출현할 위치 개수 : %d\n",

 &g_Stage.nEnemyPositionCount);

            if( g_Stage.pEnemyPosition != NULL )

               free( g_Stage.pEnemyPosition );

            g_Stage.pEnemyPosition =

                  (POS_XY*)malloc( sizeof( POS_XY )*g_Stage.nEnemyPositionCount );

            for( i = 0 ; i < g_Stage.nEnemyPositionCount ; i++ )

                fscanf( fp, "%d %d \n", &g_Stage.pEnemyPosition[i].nX,

                                           &g_Stage.pEnemyPosition[i].nY );

            fscanf( fp, "보스 초기 위치 : %d %d\n", &g_Stage.nBossX,

 &g_Stage.nBossY );

            fscanf( fp, "플레이어 초기 위치 : %d %d\n", &g_Stage.nPlayerX ,

                                                         &g_Stage.nPlayerY );

            fscanf( fp, "적 캐릭터 종류 개수 : %d\n", &g_Stage.nEnemyTypeCount );

            for( i = 0 ; i < g_Stage.nEnemyTypeCount ; i++ )

                fscanf( fp, "생명력: %d 이동시간 간격: %d 총알: %d\n",

                                 &g_EnemyType[i].nLife, &g_EnemyType[i].nMoveTime,

                                 &g_EnemyType[i].nFireTime );

 

            for( i = 0 ; i < g_Stage.nEnemyCount ; i++ )

                fscanf( fp, "타입인덱스: %d 적캐릭터 생성 인덱스: %d 출현 시간: %d\n",

                                       &g_Enemy[i].nTypeIndex, &g_Enemy[i].nPosIndex,

                                         &g_Enemy[i].nAppearanceTime );

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

            {

                 for( j = 0 ; j < MAP_COL ; j++ )

                 {

                    fscanf( fp, "%d ", &g_nMap[i][j] );

                }

            }

            fclose( fp );

            system( "cls" );

            BoxXY( 5, 5, 4 );

            gotoxy( 7, 7 ); printf( "파일 읽기 완료" );

            _getch();

            fflush( stdin );

        }

        break;

 

[소스 15-11] 파일 읽기

 

여기까지 살펴본 내용을 기반으로 하여 탱크 게임 맵 툴을 완성하여 보자.

 

 

 

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

[출처] https://nowcampus.tistory.com/entry/CGP-15%EC%9E%A5-%ED%83%B1%ED%81%AC-%EB%A7%B5?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
» (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
99 (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