학교 수업/게임프로그래밍

제2강. console 게임 제작

식혜드식혜 2024. 4. 18. 20:51
#include<Windows.h>
#include <iostream>
using namespace std;

void Gotoxy(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //출력핸들?
	COORD cursorPos = { x,y };
	SetConsoleCursorPosition(handle, cursorPos);

}

void SetColor(int color)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(handle, color);
	
}
int main()
{
	int x = 0;
	int y = 0;
	for (int i = 0; i <= 15; ++i)
	{
		SetColor(i);
		cout << "color number: " << i;
		Gotoxy(x++, y++);
	}

}

<1. 메모리와 빌드>

1. 코드 영역

- 상수 등

2. 데이터(data) 영역 

- 전역 변수, 정적 변수
- 프로그램 시작 ~ 종료까지 존재

3. 스택(stack) 영역

- 지역, 매개 변수 등
- 함수의 시작~ 종료까지 존재
※ Stack Overflow
 

4. 힙(heap)영역

- 동적할당
- 사용자에 의해 설정
★ 사용자가 반드시 관리해야 하는 영역
※ Heap Overflow
 
 

2. 빌드 과정

 

빌드(build) 과정
(=컴파일 + 링킹)

1. 전처리

- 주석 제거: 컴퓨터는 필요 없음.
- 헤더 파일 삽입: #include 복사
=> cpp에 있는 함수 원형은 링킹할 때 오브젝트 파일과 결합함.
- 매크로 치환: #define 매크로 적용

2. 컴파일

컴파일러는 최종 소스를 기계어(어셈블리어)로 변환하여 오브젝트 파일(object)을 만든다.
- 문법 검사
- Data 영역 메모리 할당

3. 링크

- obj파일 묶어서 실행 파일 제작
- 라이브러리 연결
 
※ 유의사항
- cpp 파일들은 각각 독립적으로 컴파일 되며, 컴파일이 완료된 cpp파일들을 링킹한다.
- #include 하는 헤더파일은 컴파일이 아니라 cpp파일로 복사될 뿐이다.
 

3. 헤더(.h)와 소스파일(.cpp)을 분리하자.

1)#include ???

< >: 표준으로 사용하는 것
" ": 내가 정의한 것.(경로에서 찾는다.)
=> 헤더 파일 분리 방법: 선언은 헤더파일에, 정의는 cpp파일로 정리한다.
 
Q) 헤더 파일을 사용 및 분리하는 이유
- 파일 하나에 길게 코딩하면 복잡한 프로그램을 제작할 때 힘들기 때문에 분리한다.
- 다른 소스 파일에 포함시킬 목적으로 재사용하기 위해 분리한다.

2) 헤더 가드

- 헤더 가드 종류

1. #ifdef, #endif

2. #pragma once

:C++ 공식 언어는 아님, MS에서 지원 

 

- 헤더 가드(#pragma once)가 필요한 이유

=> 정의가 중복되면 안되기 때문에!

main.cpp에서 Add.h를 두번 참조하고있다.

 

<2. 아스키 아트와 사운드>

1) 아스키 아트

※ 참고 사이트

- 원하는 폰트로 글을 직접 입력하여 사용하는 사이트

: https://patorjk.com/software/taag
- 만들어진것들 키워드별로 찾아 사용하는 사이트

: https://ascii.co.uk/art
- 직접 그린것을 아스키아트로 만들어주는 사이트

: https://www.i2symbol.com/ascii-art-generator-for-facebook-comments
- 파일을 첨부하면 아스키아트를 생성해주는 사이트

: https://wepplication.github.io/tools/asciiArtGen/
- TEXT-IMAGE(첨부파일o)

: https://www.text-image.com/convert/ascii.html
 

※ 인코딩이란?

: 컴퓨터에서 인코딩은 사람의 언어(문자 집합)에서 컴퓨터 언어(0,1)로 변환하는 과정을 통틀어 의미. 코드화,
암호화를 의미.
컴퓨터는 on/off(1, 0)의 2진 신호로 데이터를  처리하게 되어있고, 따라서 숫자는 진법 변환으로 인간과 컴퓨터가 같이 이해를 할 수 있다. 그러나 문자는 그렇지 못하기 때문에 특정 문자와 숫자(코드)를 연결하는 코드가 필요해지는데 이것을 문자 인코딩이라고 한다.
 

2)멀티바이트 문자집합과 유니코드 문자집합의 차이점

- 멀티바이트

: 한 문자에 할당하는 공간이 일정하지 않다
(영어 : 1바이트, 다국어 2바이트)
ex. char str = "문자열"; 
cout, cin는 멀티바이트 형식을 사용한다.
 

- 유니코드

: 항상 2바이트 할당. 외국어 윈도우에서도 한글이 깨지지 않고, 다국어버전을 만들기 쉽다
ex. wchar_t str = L"문자열";  //유니코드
wcout, wcin은 유니코드 형식을 사용한다.
 

구분 유니코드(_UNICODE) 멀티바이트(_MBCS)
기본 자료형 whcar_t char
단일 문자의 크기 2Byte (16bit) 1Byte ~ 2Byte
(영문, 숫자를 포함한 ASCII는 1바이트로 표현되고 나머지 한글, 한자, 일본 가나 등은 2바이트로 표현)
포함하는 문자셋 와이드 문자 및  utf-16으로 인코딩된 문자열 유니코드를 제외한 문자셋(ANSI UTF-8등)

 

5. 아스키 아트 출력을 위한 함수들

1) _setmode()

: mode에서 제공하는 파일 변환 모드인 fd로 설정됨.
_O_TEXT를 mode로 전달하면 텍스트(즉, 변환된) 모드가 설정됨.
#include<io.h> 필요: input. output 헤더
- 성공하면 이전 변환 모드를 반환함.

 
 
 
 
_FileHandle: 파일 설명자, _Mode: 새 변환 모드
 
ex. _setmode(_fileno(stdout), _O_U16TEXT);
=> 알파벳 O임!!
 

2) _fileno

: 스트림에 연결된 파일 설명자를 가져온다.

#include<stdio.h> 필요 //_fileno(File *_Stream)
#include<fnctl.h> 필요: file control options 헤더 => file형태 //_O_U16TEXT
 

6. Sound 생성

: 컴퓨터 게임에서 출력되는 화면도 중요하지만 사운드의 역할도 굉장히 중요하다. 우리가 자주 듣는 파일은 mp3파일이지만, 콘솔용 게임이 주로 나왔던 시기에는 mp3 보다는 스피커에서 간단한 톤을 생성해서 그 톤으로 음악을 만들었다. 여기서 그러한 사운드를 만들기 위한 간단한 방법에 대해서 이야기해 보고 샘플로 '반짝반짝 작은 별'을 스피커음으로 들어 보자.

※ Beep함수

: 마이크로소프트제작
- 함수의 원형:

- dwFreq: 헤르츠에서 소리의 주파수, 이 매개변수는 37에서 32,767(0x25 ~ 0x7FFF) 범위에 있어야 한다.
- dwDuration: 소리의 지속 시간(단위: ms)
 
- 음에 대한 기준 주파수는 440Hz이다. 이것은 1834년 독일 자연 연구회의에서 결정된 표준 음고로 계명은 A4(라)에 해당한다. 피아노 건반은 한 옥타브당 12개이고 한 옥타브 올라가는 데 2배 주파수의 톤이 나온다.
 

예제 게임

<1. 절대 음감 게임>

- 게임 설명:
1. 8음계를 Beep함수를 이용하여 먼저 차례대로 들려준다.
2. 난수를 발생해서 선택된 한 음을 들려준다.
3. 플레이어는 그 음이 무엇인지 선택하고, 맞거나 틀린 정보를 보여준다.

7. 콘솔 함수 다루기(console.h, console.cpp)

1) 콘솔 창 관련

1. 제목 설정

- system("title 제목"); ex. system("title test");
- SetConsoleTitle("제목"); ex. SetConsoleTitle(L"미로 게임"); SetConsoleTitle("my game");

2. 콘솔 창 크기 설정

- system("mode con cols=가로길이 lines=세로길이);
=> 응용 system("mode con cols= 가로길이 lines=세로길이 | title 제목");
ex.     system("mode con cols=50 lines=50 | title test");
※ 가로가 세로보다 짧음. 가로를 50문자가 출력될 정도, 세로를 50줄이 출력될 정도. 띄어쓰기 주의!!
※ 핸들이란? 리소스의 메모리 주소를 정수로 치환한 값
 
 

3. 콘솔 창 전체화면 설정

: 지정한 표준 디바이스에 대한 핸들을 검색함.
※  STD_OUTPUT_HANDLE: 표준 출력 디바이스
ex. void Fullscreen()
//CONSOLE_FULLSCREE_MODE가 전체화면모드, CONSOLE_WINDOWED_MODE가 윈도우모드
  //SetConsoleDisplayMode(GetStdHandle(STD_OUTPUT_HANDLE), CONSOLE_FULLSCREEN_MODE, 0);
          ShowWindow(GetConsoleWindow(), SW_MAXIMIZE);
}
 

2) 프로그램 관련

- 종료: exit(0);
- 일시정지: system("pause");
 

3) 커서 제어

※ 콘솔 좌표계

1. 좌표 이동 함수(Gotoxy)

: "콘솔의 핸들 값"과 "좌표 값"을 받아서, 해당 위치로 콘솔의 커서를 이동시키는 함수.

: 커서의 위치를 저장하는 구조체.
=> #include<Windows.h> 필요
 참고글: https://geundung.dev/14
ex. void Gotoxy(int x, int y)
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 콘솔창 핸들
COORD Cur = {x,y }; // {x*2, y};처럼 게임마다 2칸씩
SetConsoleCursorPosition(hOut, Cur);
}

2. 깜빡거리는 커서 보이지 않게 하기

: 지정된 콘솔 화면에 대한 커서의 크기와, 표시 유형을 설정함.

: 콘솔 커서의 정보를 저장하는 구조체.
ex. void Setcursor(bool _bVis, DWORD _size)
{
CONSOLE_CURSOR_INFO curinfo;
curinfo.dwSize = _size; // 커서 굵기(1~100)
curinfo.bVisible = _bVis; // True: 보임, FALSE: 숨김
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curinfo);
}
 

4) 색깔 칠하기

: “콘솔의 핸들 값”과 “색상 값”을 받아서 글자 색깔을 변경해주는
함수.
ex. void Setcolor(int color, int bgcolor)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (bgcolor << 4) | color);
}
- 글자 배경색은 글자색의 16배이다. ex. 글자색의 2와 글자 배경색의 32는 같은 색깔
- WORD는 short형(2바이트=16비트)임. 배경색상을 바꾸려면 5~8번째 4개의 비트를 조작해야 함.

 

ex. 겜프는 허니잼 : 밝은 흰색 바탕에 연한 녹색 글씨 => 0000 0000 1010 1111

※ 색상표 => 아래와 같이 enum class 활용!

< 예제 2 Gotoxy()와 Setcolor() 함수로 아래 화면을 만들어보세요. >

실행 후 첫 화면

 

3초 후 화면

 

5) 키보드 처리

1. _getch(), _putch()

↑: -32 -> 72

←: -32 -> 75

→: -32 -> 77
↓: -32 -> 80
※ 스페이스바: 32, 엔터키: 13
=> 두 개 이상의 키를 한번에 입력받지 못해 대각선을 못함. Q) _kbhit()는 왜 써야 할까??
=> _getch()로 입력을 받는 과정에서 키보드가 입력되지 않으면 키보드 입력을 기다리면서 입력할 때까지 게
임이 진행되지 않는 문제점이 생김.

 

2. GetAsyncKeyState

:동시에 입력받을 때 사용. ex. 대각선 이동

: 인자 값으로 키보드의 가상키코드를 받음.

- 키가 눌려진 상태에서는 최상위 비트(0x8000)이 1이 되며, 처음 입력되었을 때는 0x8001 비트가 1임.

ex. if(GetAsyncKeyState(VK_UP) & 0x8000)

- 가상 키코드

 

가상키 코드 설명
0x01 VK_LBUTTON 마우스 왼쪽 버튼
0x02 VK_RBUTTON 마우스 오른쪽 버튼
0x04 VK_MBUTTON 마우스 가운데 버튼
0x08 VK_BACK Backspace
0x09 VK_TAB Tab

0x0D VK_RETURN Enter
0x10 VK_SHIFT Shift
0x11 VK_CONTROL Ctrl
0x12 VK_MENU Alt
0x1B VK_ESCAPE Esc
0x20 VK_SPPACE Space Bar
0x21 VK_PRIOR Page Up
0x22 VK_NEXT Page Down
0x23 VK_END End
0x24 VK_HOME Home
0x25 VK_LEFT
0x26 VK_UP
0x27 VK_RIGHT
0x28 VK_DOWN
반환 값 설명
0(0x0000) 이전에 누른 적이 없고 호출 시점에서 안눌린 상태
0x8000 이전에 누른 적이 없고 호출 시점에서 눌린 상태
0x8001 이전에 누른 적이 있고 호출 시점에서 눌린 상태
1(0x0001) 이전에 누른 적이 있고 호출 시점에서 안눌린 상태

=> 이 시점을 판단하는 기준은! 이전 GetAsyncKeyState 후 ~ 호출시의 GetAsyncKeyState의 바로 전까지의
기간임. 

 

※ 시간 관련 함수

clock()

 : c 함수(#include<time.h>), 응용 프로그램이 시작된 이후 CPU 틱수를 반환함.(ms)

GetTickCount64()

: win32 api 함수(#include<Windows.h>), 시스템이 시작된 이후 경과된 시간을 반환함. 
1초에 1000씩 틱 카운트를 증가시킴.

※ 64가 붙은 이유: 윈도우가 시작되고 1초에 1000틱씩 카운트를 증가시키는데, 카운트는 32비트 값이라. 최대
49.7일간만 유지할 수 있어 이후에는 오버플로우라 0으로 초기화됨. 64비트로 증가하여 오버플로우 시점이 약 5억 8천정도로늘어남.