오늘 학습 진행 내용
게임 개발자를 위한 C++ 문법
챕터1-6 : 객체지향 프로그래밍
상속
상속 대상이 되는 클래스를 기본 클래스(부모 클래스)라고 정의
상속 받아 새로운 클래스를 정의하면 이를 파생 클래스라고 정의
생성자는 멤버 초기화 리스트를 통해 멤버 변수들을 초기화
파생 클래스(자식 클래스)의 생성자는 부모 클래스의 생성자를 호출 가능
탈것, 자전거, 트럭 상속 예제
#include <iostream>
#include <string>
using namespace std;
class Vehicle{
protected: //private와 동일하게 내부 접근만 허용하지만 상속받은 자식 클래스에서 접근 가능하다는 점 차이.
string color;
int speed;
public:
Vehicle(string c, int s) : color(c), speed(s) {} //기본 생성자
void move() {
cout << "The vehicle is moving at " << speed <<" km/h." << endl;
}
void setColor(string c){
color = c;
}
string getColor(){
return color;
}
};
class Bicycle : public Vehicle{ //Vehicle 부모클래스의 상속 클래스 ByCycle
private:
bool hasBasket; //Vehicle 클래스의 바구니 유무 매개 변수
public:
Bicycle(string c, int s, bool basket) : Vehicle(c,s), hasBasket(basket){} //기본 생성자(부모 클래스 생성자 포함)
void ringBell() {
cout << "Bicycle bell : Ring Ring!" << endl;
}
};
class Truck : public Vehicle{ //Vehicle 부모클래스의 상속 클래스 Truck
private:
int cargoCapacity; //Truck 클래스의 적재용량 매개 변수
public:
Truck(string c, int s, int capacity)
: Vehicle(c,s), cargoCapacity(capacity){
}
void loadCargo(){
cout<< "Truck loading cargo. Capacity: " << cargoCapacity << "tons." <<endl;
}
};
int main(){
Bicycle b("Yellow",30,true);
Truck t("Blue",40,95);
b.ringBell();
b.move(); //부모 클래스의 move 매개함수도 가져다가 사용 가능
b.getColor(); //부모 클래스의 getClor 매개함수도 가져다가 사용가능
t.loadCargo();
t.move();
t.getColor();
}
다형성
다형성은 기본이 되는 클래스(부모 클래스)를 만들어 함수의 인터페이스 정의하고 실제 구현은 파생 클래스에서 담당하는기법
동적 바인딩을 통해 호출된 객체의 타입에 따라 적절한 파생 클래스의 함수가 실해되도록 함수 앞에 virtual 키워드를 붙임
virtual 키워드 붙이면 가상 함수 테이블 생성
기본 클래스(부모 클래스)에서 virtual 선언된 함수가 파생 클래스에서 재정의된 경우 그 위치 기록되어 동적 디스패치 가능
다형성 적용 예제
#include <iostream>
#include <string>
using namespace std;
//다형성이 적용되지 않은 예시
//새로운 동물 생길 때마다 관리해야될 클래스가 많아지는 케이스
//다형성 기본이 되는 클래스 만들어 함수 인터페이스 정의, 실제 구현은 파생 클래스에서 담당하는 기법
//파생 클래스 함수 앞에 virtual 키워브 붙이면 실행 가능
// class Lion{
// public:
// Lion(string word):m_word(word){}
// void bark() { cout<<"Lion" << " " << m_word << endl;}
// private:
// string m_word;
// };
// class Wolf{
// public:
// Wolf(string word) : m_word(word){}
// void bark() { cout<<"Wolf"<< " " <<m_word<<endl;}
// private:
// string m_word;
// };
// class Dog{
// public:
// Dog(string word) : m_word(word){}
// void bark() {cout<<"Dog"<< " " << m_word<<endl;}
// private:
// string m_word;
// };
// void print(Lion lion){
// lion.bark();
// }
// void print(Wolf wolf){
// wolf.bark();
// }
// void print(Dog dog){
// dog.bark();
// }
// 다형성 적용 예시
//기본 클래스 : Animal
class Animal{
public:
Animal() {} //기본 생성자
virtual void bark() {}; //파생 클래스에서 함수 재정의를 위한 가상 함수 지정
virtual void makeSound() = 0; //가상 함수 : 지식 클래스에서 재정의 가능, 순수 가상함수 =0 과 같은 것들은 파생 클래스에 재정의 하지 않으면 파생 클래스가 추상 클래스(객체 생성 불가)로 됨.
};
class Lion : public Animal{
public:
Lion(string word) : m_word(word){}
void bark() {cout<<"Lion"<< " "<<m_word<<endl;}
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
private:
string m_word;
};
class Wolf : public Animal{
public:
Wolf(string word) : m_word(word){}
void bark(){cout<<"Wolf"<<" "<<m_word<<endl;}
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
private:
string m_word;
};
class Dog : public Animal{
public:
Dog(string word) : m_word(word){}
void bark(){cout<<"Dog"<< " "<<m_word<<endl;}
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
private:
string m_word;
};
class Cat : public Animal{
public:
void makeSound() {
cout << "Cat meows: Meow! Meow!" << endl;
}
};
void print(Animal* animal){ //다형성을 적용하려면 반드시 포인터 또는 참조를 사용
//실제 객체타입의 bark() 호출 하기 위해서
animal->bark();
}
int main(){
Animal* myAnimal;
Lion lion("Ahaaaaa!");
Wolf wolf("ohhhhhh");
Dog myDog("oooooooops");
Cat myCat;
print(&lion);
print(&wolf);
print(&myDog);
myAnimal = &myDog;
myAnimal->makeSound(); //Dog의 makeSound()호출 포인터여서 -> 이걸로 호출
myAnimal = &myCat;
myAnimal->makeSound(); //Cat의 makeSound() 호출 포인터여서 -> 이걸로 호출
return 0;
}
객체 지향 프로그래밍 실습
다형성을 활용한 게임스킬 사용 프로그램
다형성을 이용해 다양한 직업을 가진 모험가들이 각기 다른 스킬을 사용하는 프로그램을 구현합니다.
기본 클래스
Adventure라는 기본 클래스를 정의하세요.useSkill()이라는 순수가상함수를 선언하세요.파생 클래스
Warror, Mage, Archer라는 세 가지 파생 클래스를 만드세요.useSkill 함수를 재정의 해서 아래와 같이 출력하세요.Warror : Warror uses Slash!Mage : Mage casts Fireball!Archer : Archer shoots an Arrow!다형성 구현
Adventure*타입의 포인터를 사용하여 여러 모험가 객체를 가리키고,실습 코드
#include <iostream>
#include <vector>
using namespace std;
class Adventurer{
public:
virtual void useSkill() = 0;
virtual ~Adventurer() {} //상속 받아서 다형성(virtual 함수)를 쓸 경우 기반 클래스 소멸자는 무조건 virtual로 선언
//~클래스 이름 : 소멸자(동적 메모리 해제, 파일 닫기, 네트워크 연결 종료, 스마트 포인터 내부 정리 작업을 진행) 메모리 누수 방지를 위해서 사용
};
class Warrior : public Adventurer {
public:
void useSkill(){
cout<<"Warror uses Slash!"<<endl;
}
};
class Mage : public Adventurer {
public:
void useSkill(){
cout<<"Mage casts Fireball!"<<endl;
}
};
class Archer : public Adventurer {
public:
void useSkill(){
cout<<"Archer shoots an Arrow!"<<endl;
}
};
int main(){
Warrior myWarrior; //Warrior 객체 선언 myWarrior
Mage myMage; //Mage 객체 선언 myMage
Archer myArcher; //Archer 객체 선언 myArcher
Adventurer* myjobs[3] = {&myWarrior, &myMage, &myArcher}; //배열에 이미 존재하는 객체들 포인터만 저장
for(Adventurer* i : myjobs){
i->useSkill(); //해당 포인터로 가리키는 객체의 useSkill 함수 호출
*i.useSkill(); // 해당 포인터로 가리키는 객체의 useSkill 함수 호출
}
// 다형성 사용
vector<Adventurer*> adventurers; //Adventurer 포인터 벡터(벡터 크기 가변적이어서 개수 제한 없이 늘리는 것 가능)
adventurers.push_back(new Warrior()); //adventurer 벡터에 Warrior 객체 추가
adventurers.push_back(new Mage()); //adventurer 벡터에 Mage 객체 추가
adventurers.push_back(new Archer()); //adventurer 벡터에 Archer 객체 추가
// 각 모험가의 스킬 사용
for (size_t i = 0; i < adventurers.size(); ++i) {
adventurers[i]->useSkill(); //벡터 내 객체 포인터가 가리키는 객체의 useSkill 함수 호출
}
// 메모리 해제
for (size_t i = 0; i < adventurers.size(); ++i) {
delete adventurers[i]; //new로 할당할 경우에 반드시 delete 필요, 안 하면 메모리 누수, 예외 발생 가능성 존재
}
return 0;
}
CH2 학습 가이드
미니 실습
실습 제출 코드
#include <iostream>
//Sleep()을 사용하면 표시하는 시간에 딜레이를 줄 수 있어요!
#include <Windows.h> //Sleep()을 사용하려면, 이 헤더가 필요해요
#include <map>
using namespace std;
//가짜 포맷을 진행하는 코드
//가짜 포맷 안내 및 1번 선택하면 문제 진행
//선택 안할 경우 가짜 포맷 보여주고 종료
//1번 선택한 경우 3문제 차례대로 보기 주어지고 정답 입력받게 함
//정답 입력 시 다음 문제, 오답 시 가짜 포맨 보여줌
//3문제 모두 맞출 경우 장난 메세지와 함께 종료
void FakeFormat(){ //Fake 포맷 진행률 표시 출력 함수
cout<<"당신의 컴퓨터는 포맷이 진행됩니다!!"<<endl;
//진행률 표시(가짜 포맷)
cout<<"|";
for(int i=0; i<50; i++){
cout<<"=";
Sleep(100);
}
cout<<"|"<<endl;
cout<<"포맷이 완료되었습니다!"<<endl; //포맷 완료 메세지 출력
}
int main(){
cout<<"삐빅! 당신의 컴퓨터는 바이러스에 걸렸습니다!"<<endl;
cout<<"지금부터 답변을 제대로 하지 않으면 당신의 컴퓨터는 포맷됩니다...!"<<endl;
cout<<"총 3문제를 맞추면 포맷은 취소가 됩니다."<<endl;
cout<<"진행을 원하시면 1을 입력하시오."<<endl;
cout<<"진행을 안할 경우 강제로 포맷이 진행됩니다!!"<<endl;
int choice;
cin>>choice;
if(choice!=1){
FakeFormat();
return 0;
}
else{
//답안 배열 corretAnswer 선언
int correctAnswer[3] = {4,2,3};
//문제와 보기 저장한 questeions Map
map<string, string> questions = {{"세계에서 가장 넓은 면적을 가진 나라는?","1. 중국\n 2. 미국\n 3. 러시아\n 4. 캐나다"},
{"사과가 떨어지는 것을 보고 만유인력을 발견한 과학자는?","1. 아인슈타인\n 2. 뉴턴\n 3. 갈릴레오\n 4. 파스칼"},
{"다음 중 바다가 아닌 것은?","1. 동해\n 2. 지중해\n 3. 홍해\n 4. 알프스"}};
int i=0;
int answer;
for(auto& pair : questions){
cout<<pair.first<<endl;
cout<<pair.second<<endl;
cin>>answer;
if(answer==correctAnswer[i]){
cout<<"정답입니다!"<<endl;
}
else{
cout<<"오답입니다!"<<endl;
FakeFormat();
return 0;
}
i++;
}
cout<<"축하합니다 모든 문제를 맞췄습니다."<<endl;
cout<<"장난이었습니다! 놀랬죠! 포맷은 가짜입니다!"<<endl;
return 0;
}
}
도전 실습
//공백과 함께 한 줄을 입력받으려면, 아래의 코드가 필요해요.
#include <string>
#include <Windows.h>
#include <iostream>
using namespace std;
int main(){
string text;
getline(cin, text); // 입력을 받는 부분
for(int i=0; i<text.length(); i++){
if(isspace(static_cast<unsigned char>(text[i]))){ //char 혀태의 text의 한 글자 공백 검사했을 때 True인 경우
Sleep(500); //0.5초 대기
cout<<text[i]; //글자 출력
}
else{
if(text[i]==text[i+1]&& text[i]==text[i+2] &&text[i]=='.' && i+2<text.length()){ //text 변수 내 3글자 연속 범위(text.legnth() 안에서) 3글자 연속한 경우 중 모두 '.'이랑 일치하는경우
cout<<endl; //줄바꿈 처리
i+=2; //'...' 줄바꿈 처리했기 때문에 나머지 두 글자 pass
}
else{
cout<<text[i]; //글자 출력
}
}
}
}
도전X도전 실습
실습 제출 코드
#include <iostream>
using namespace std;
const int TILE_EMPTY = 0; // 빈칸
const int TILE_PLAYER = 1; // 플레이어
const int TILE_MON1 = 2; // 몬스터 A
const int TILE_MON2 = 3; // 몬스터 B
const int TILE_MON3 = 4; // 몬스터 C
const int TILE_BOSS = 5; // 보스
const char DISP_EMPTY = '-';
const char DISP_PLAYER = 'P';
const char DISP_MON1 = 'a';
const char DISP_MON2 = 'b';
const char DISP_MON3 = 'c';
const char DISP_BOSS = 'B';
// 시야 밖(#) 은 별도로 사용
const char DISP_OUTSIDE = '#';
const int WIDTH = 20;
const int HEIGHT = 10;
int map_data[WIDTH * HEIGHT] =
{
// 0행(0~19)
0,0,0,0,0, 2,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0,
// 1행(20~39)
0,0,3,0,0, 0,0,0,0,0, 0,0,0,0,4, 0,0,0,0,0,
// 2행(40~59)
0,0,0,0,0, 0,0,2,0,0, 0,0,0,0,0, 0,0,0,0,0,
// 3행
0,0,0,0,0, 0,0,0,0,3, 0,0,0,0,0, 0,0,0,0,0,
// 4행
0,0,0,0,0, 0,0,0,1,0, 0,0,0,0,0, 0,0,2,0,0,
// 5행
0,0,0,0,0, 4,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0,
// 6행
0,3,0,0,0, 0,0,0,0,0, 0,0,0,2,0, 0,0,0,0,0,
// 7행
0,0,0,0,0, 0,0,4,0,0, 0,0,0,0,0, 0,3,0,0,0,
// 8행
0,0,0,0,0, 0,0,0,0,0, 0,2,0,0,0, 0,0,0,0,0,
// 9행
0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,5,0,
};
void disp_map(int num){
if(num == TILE_EMPTY){
cout << DISP_EMPTY;
}else if(num == TILE_PLAYER){
cout << DISP_PLAYER;
}else if(num == TILE_MON1){
cout << DISP_MON1;
}else if(num == TILE_MON2){
cout << DISP_MON2;
}else if(num == TILE_MON3){
cout << DISP_MON3;
}else if(num == TILE_BOSS){
cout << DISP_BOSS;
}else{
cout << DISP_OUTSIDE;
}
}
bool check_monster(int num){
if(num == TILE_BOSS || num == TILE_MON1 || num == TILE_MON2 || num == TILE_MON3 || num == -1){
cout << "몬스터 혹은 벽으로 플레이어가 이동할 수 없는 위치입니다." << endl;
return false; // 이동 불가능
}else{
return true; // 이동 가능
}
}
int main(){
int input_move = 0;
// 맵 상의 플레이어 위치 (4행 8열 위치에 1이 있으므로 그에 맞춤)
int player_location[2] = {8, 4}; // {x, y}
bool can_move = true;
while(input_move != 5){
// 현재 시야 출력
for(int j = player_location[1] - 2; j <= player_location[1] + 2; j++){
for(int i = player_location[0] - 2; i <= player_location[0] + 2; i++){
if(0 <= i && i < WIDTH && 0 <= j && j < HEIGHT){
int location = i + j * WIDTH;
disp_map(map_data[location]);
} else {
disp_map(-1); // 시야 밖
}
}
cout << endl;
}
cout << endl;
cout << "어디로 움직이겠습니까?" << endl;
cout << "1)상 2)하 3)좌 4)우 5)종료" << endl;
cin >> input_move;
if (input_move == 5) break;
int new_x = player_location[0];
int new_y = player_location[1];
if(input_move == 1){ // 상
new_y -= 1;
}else if(input_move == 2){ // 하
new_y += 1;
}else if(input_move == 3){ // 좌
new_x -= 1;
}else if(input_move == 4){ // 우
new_x += 1;
}else{
cout << "잘못된 입력입니다." << endl;
continue;
}
// 범위 밖 체크
int tile_value = -1;
if(0 <= new_x && new_x < WIDTH && 0 <= new_y && new_y < HEIGHT){
int location = new_x + new_y * WIDTH;
tile_value = map_data[location];
}else{
tile_value = -1; // 벽/맵 밖
}
can_move = check_monster(tile_value);
if(can_move){
// 기존 위치를 빈칸으로, 새 위치를 플레이어로 바꾸고 싶다면:
int old_index = player_location[0] + player_location[1] * WIDTH;
map_data[old_index] = TILE_EMPTY;
int new_index = new_x + new_y * WIDTH;
map_data[new_index] = TILE_PLAYER;
// 논리 좌표 갱신
player_location[0] = new_x;
player_location[1] = new_y;
}
}
return 0;
}