상속이 필요한이유


프로그램을 코딩할때 , 보통 데이터가 묶여있는 클래스와 그 데이터를 이용하여 기능의 처리를담당하는 클래스를 따로 작성하는데 , 이때 기능의 처리를 담당하는 클래스를 컨트롤 ( control ) 클래스 혹은 핸들러 ( handler ) 클래스라고한다. 이때 데이터가 묶여있는 클래스에 새로운 멤버가 생기는등의 변경사항이 있을때 , 컨트롤클래스의 수정이 불가피해지며 , 그 결과 클래스를 전부 수정해야하는 경우가 생긴다. 이러한 불편을 줄이기위해서 소프트설계에있어서 중요시하는것은 " 요구사항의 변경에 따른 프로그램의 유연성 " , " 기능의 추가에따른 프로그램의 확장성 " 이다. 이를위하여 상속의 개념이 도입되었다.


상속이란


B 클래스가 A 클래스를 상속한다면, B 클래스는 A가 가지고있는 모든 멤버를 물려받게된다. 이때 public 상속이냐 , private 상속이냐 , protect 상속이냐에 따라 멤버의 접근권한이 달라진다.


세가지 형태의 상속


class UnivStudent : public Person // public 상속 : Person의 멤버중 Public보다 접근의 범위가 넓은 멤버는 public으로 변경시켜서 상속 ( 그대로 상속 )

class UnivStudnet : private Person // private 상속 : Person 의 멤버중 private 보다 접근의 범위가 넓은 멤버는 private로 변경시켜서 상속 ( Person의 모든멤버를 Private 접근권한으로 상속 ) 

class UnivStudnet : protected Person // protect 상속 : Person 의 멤버중 protect 보다 접근의 범위가 넓은 멤버는 protceted로 변경시켜서 상속 ( Person의 public멤버를 protected 접근권한으로 상속 ) 

거의 대부분의 상속은 public 상속으로 이루어진다


protected 로 선언된 멤버가 허용하는 접근범위


protected 로 선언된 멤버변수는 이를 상속하는 유도클래스에서 접근이 가능하다. 이 키워드를 이용하면 유도클래스에게만 제한적으로 접근을 허용하여 유용하게 사용될 수 있지만, 기본적으로는 기초 클래스와 이를 상속하는 유도클래스 사이에서도 정보은닉이 지켜지는것이 좋다.


상속을 위한 조건


상속을 위한 기본조건인 IS - A 관계


ex ) 

무선전화기 is a 전화기

노트북컴퓨터 is a 컴퓨터


위와같이 상속관계로 묶고자하는 두 클래스가 IS - A 관계로 표현되지 않는다면 적절한상속의 관계가 아닐확률이 높다.


HAS - A 관계도 상속의 조건은되지만 복합관계로 이를 대신하는것이 일반적이다.


ex)

경찰 has a 총


위와같이 소유의 관계를 상속으로 ( 총클래스를 경찰클래스가 상속 ) 해결할 수도 있지만, 이때 경찰이 곤봉을 든다고 가정했을때 , 클래스의 수정이 어려워진다. 따라서 이런경우는 상속이외의 다른방법으로 해결하는것이 좋다.


위 두 상황을 제외하면 , 상속이 형성될 상황은 없다고봐도 무방하다.


상속의 방법


include<iostream>

include<cstring>

using namespace std;


class Person

{

private :

int age;

char name[50];

public :

Person(int myage , char* myname) : age(myage)

{

strcpy(name,myname);

}

void WhatYourName() const

{

cout << "My name is  : " <<name<<endl;

}

void HowOldAreYou() const

{

cout << My age : " << age << endl;

}

};

class UnivStudent : public Person // Person 클래스를 public 으로 상속

{

private :

char major[50]

public :

UnivStudent(char * myname , int myage, char * mymajor) : Person(myage,myname) // 이니셜라이저의 형태로 Person생성자 호출

{

strcpy(major,mymajor);

}

void WhoAreYou()

{

WhatYourName();

HowOldAreYou();

cout << "My major : "<<major <<endl;

}

};


int main (void)

{

UnivStudent ustd1("LEE",22,"Computer eng."); // UnivStudent클래스와 Person 클래스의 생성자 둘다호출 , 초기화

ustd1.WhoAreYou();

return 0;

}


이때 , 기초클래스인 Person의 private 멤버에는 유도클래스인 UnivStudent 에서도 접근이 제한된다. 이는 접근제한의 기준이 객체가아닌 클래스이기때문이다. 따라서 Person에 정의된 Public 함수를 이용해 Person의 private 멤버에 접근해야한다 . 정보의 은닉은 객체내에서도 진행된다. 


유도클래스의 객체 생성과정


유도클래스의 객체생성에서 기초클래스의 생성자는 100% 호출되며 , 유도클래스의 생성자에서 기초클래스의 생성자 호출을 명시하지않으면 기초클래스의 void 생성자가 호출된다.

-> 기초클래스의 생성자와 유도클래스의 생성자는 반드시 모두 호출된다 -> 클래스의 멤버는 해당 클래스의 생성자를 통해서 초기화해야한다.


생성자의 호출은 기초클래스 -> 유도클래스 순으로 호출되며, 소멸자의 호출은 그 반대인 유도클래스 -> 기초클래스의 순으로 진행된다.

-> 소멸자 또한 기초클래스와 유도클래스 모두 호출된다 -> 만약 생성자에서 동적할당한 멤버가 있다면 , 그 메모리공간은 각 클래스의 소멸자에서 해제해야한다.


ex)


class Person

{

private : 

char * name;

public :

Person ( char * myname);

{

name = new char[strlen(myname)+1]; // 동적할당

strcpy(name,myname);

}

~Person ()

{

delete[] name; // 해당클래스에서 메모리해제

}

};


const 객체와 const 객체의 특성


const SoSimple sim(20);


위와 같이 객체에 const 선언을 하게되면 이 객체를 대상으로는 cosnt 멤버함수만 호출이 가능하다. 따라서, 값을 변경하지 않는 멤버함수를 정의할때는 const 객체에서도 호출이 가능하도록 할 필요가 있다.


const와 함수오버로딩


void SimpleFunc(){ ,,, }

void SimpleFunc() const {,,,}


const 키워드의 선언유무도 함수오버로딩의 조건이된다. const 객체를 대상으로 함수 호출시 const 멤버함수가 , 일반 객체를 대상으로 함수호출시 일반 멤버함수가 호출된다. 


클래스의 friend 선언


A클래스가 B클래스를 대상으로 friend 선언을 하면 , B클래스는 A클래스의 private 멤버에 직접접근이 가능하다.

A클래스가 B클래스의 private 멤버에 접근하려면 , B클래스가 A 클래스를대상으로 friend 선언을 해야한다.


include <iostream>

using namespace std;


class Girl; // Girl 이 클래스라는 선언

class Boy

{

private:

int height;

friend class Girl; // Girl 을 대상으로 friend 선언 , friend 선언은 클래스 내부 어디에들어가도 상관없으며 , 그 자체로 Class Girl; 선언이 들어가있기때문에 Class Girl; 선언을 하지않아도된다.

public:

Boy(int len) : height(len)

{}

void ShowYourFriendInfo(Girl &frn); // Girl의 정보가 없기때문에 함수정의는 뒤로뺀다

};

class Girl

{

private:

char phNum[20];

public:

Girl(char* num)

{

strcpy(phNum,num);

}

voidShowYourFriendInfo(Boy &frn); // friend class Boy 선언이 컴파일되기전이기때문에 , Boy의 정보를 알 수 없으므로 정의를 뒤로뺀다

friend class Boy; 

};

void Boy::ShowYourFriendInfo( Girl &frn )

{

cout<<"Her Phone Number: " <<frn.phNum<<endl;

}

void Girl::ShowYourFriendInfo( Boy &frn)

{

cout<<"His height: "<<frn.heignt<<endl;

}


int main(void)

{

Boy boy(170);

Girl girl("010-1111-1111");

boy.ShowYourFriendInfo(girl);

girl.ShowYourFrinedInfo(boy);

return 0;

}


friend 선언은 지나치면 아주 위험할 수 있으므로 , friend 선언은 필요한 상황에서 극히 소극적으로 사용해야한다.


함수를 대상으로한 friend 선언


friend 선언은 전역함수를 대상으로도 ,클래스의 멤버함수를 대상으로도 선언이 가능하며 , 이때 선언된 함수는 선언된 클래스의 private 영역에 접근이가능하다. 


class Point; // 컴파일을 위해 Point 가 class 라는 선언

class PointOP

{

private : 

int opcnt;

public:

PointOP () : opcnt(0)

{}

Point PointAdd( const Point& , const Point&);

}

class Point

{

private:

 int x;

 int y;

pubilc:

Point(const int &xpos , const int & ypos) : x(xpos),y(ypos)

{}

friend Point PointOP::PointAdd(const Point& , const Point&);

friend void ShowPointPos(const Point&); // 함수정의생략 , 이 선언시, void ShowPointPos(const Point&) 함수원형선언이 포함되므로 , 별도의 함수원형을 선언할 필요 없다.

};

Point PointOP :: PointAdd(const Point& pnt1, const Point & pnt2)

{

opcnt++; 

return Point (pnt1.x + pnt2.x , pnt1.y+pnt2.y); // Point 클래스 내에서 friend 선언된 멤버함수이기때문에 , Point 클래스의 private 멤버에 접근가능

}


C언어에서의 static


전역변수에 선언된 static 의의미 -> 선언된 파일 내에서만 참조를 허용한다는 의미

함수내에 선언된 static 의 의미 -> 프로그램실행시 한번만 초기화되고 , 지역변수와 달리 함수를 빠져나가도 소멸되지않는다.


C++ 에서의 static


C++에서는 클래스 내에 static 멤버변수를 선언할 수있으며 , 이 변수는 객체마다가 아닌 클래스당 하나만 생성되며 , 객체에 종속되지않고 , 선언된 클래스나 객체를 통해서만 접근이 가능하다. ( private 로 선언되면 클래스 내에서만 접근가능하고 , public으로 선언되면 클래스의 이름이나 객체의 이름을통해 어디서든 접근 가능하다 )

static 변수는 생성자가아닌 함수외부에서 초기화를 진행해야하는데 , 이는 생성자에서 초기화시 , 객체를 생성할때마다 static 변수가 초기화되기때문이다.


int SoSimple :: SimObjCnt =0; 


이런식으로 클래스 밖에서 초기화해주어야한다.


static 멤버함수의 특성


선언된 클래스의 모든 객체가 공유한다

public으로 선언이되면 , 클래스의 이름을 사용해서 호출이 가능하다

객체의 멤버로 존재하는것이 아니다


객체의 멤버로 존재하지 않기때문에 , static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능하다


class SoSimple

{

private:

int num1;

static num2;

public:

SoSiple(int n) : num1(n)

{}

static void Adder(int n)

{

num1+=n; // num1 이 static 변수가 아니기때문에 컴파일에러발생

num2 +=n;

}

};


const static 멤버


클래스 내에 선언된 const 멤버변수의 초기화는 이니셜라이저를 통해야만하지만, const static 으로 선언되는 멤버변수는 선언과 동시에 초기화가 가능하다.


class ContryArea

{

public:

const static int RUSSIA = 1707540;

...

};


mutable 키워드


mutable int num;


mutable 키워드가 선언된 멤버는 cosnt 함수 내에서의 값의 변경을 예외적으로 허용한다

mutable 키워드의 과도한사용은 C++ 에 있어서 중요성을 인정받은 const 키워드의 선언을 의미없게 만들어버리므로 , 최대한 사용을 자제해야한다.


'C++언어 > 열혈C++' 카테고리의 다른 글

C++ : Chapter -8 상속과 다형성  (0) 2018.05.22
C++ : Chapter -7 상속의 이해  (0) 2018.05.20
C++ : Chapter -5 복사생성자  (0) 2018.05.10
C++ : Chapter -4 클래스 PART2  (0) 2018.05.02
C++ : Chapter -3 클래스 PART1  (0) 2018.04.26

게임 개발단계


프로토타입 : 필요한 최소한의 것을 갖춘 테스트 전용제품으로 , 게임의 가치 (재미) 가 있는지를 판단하기위해 짧은기간에 게임 자체의 본질적인 요소를 만들어서 가설 (게임 아이디어) 를 입증하는것. 


프리프로덕션 : 프로토타입 단계에서 게임이 재미있다고 증명한후 , 게임제작의 워크플로를 증명하는 단계. 이때 워크폴로는 , 누가 언제 어디서 무엇을 어떻게 만들것인지 , 한정된 예산과 시간안에 이 게임을 만들 수 있는지 등을 확인하는 단계이다 . 이 단계에서 많은 개발회사는 버티컬 슬라이스를 수행하는데 , 이는 게임의 주요 기능이 모두 탑재된 스테이지 하나를 최종 제품과 같은 품질로 만드는 것이다.  프리 프로덕션 단계에서 만들어진 프로그램 , 툴 , 파이프라인 , 워크플로는 모두 프로덕션 단계까지 이어져야한다. 


* 버티컬슬라이스 : 모든 스테이지에서 체험하게되는 경험을 하나의 스테이지에 담는다. 예를들어 1~4스테이지는 정글 , 5~8 스테이지는 설산 , 9~10 스테이지는 바다라고 했을때 , 한 스테이지에 정글 , 설산 , 바다 를 모두 담는 것을 말한다. 


프로덕션 : 인력을 추가하여 프로토타입과 프리프로덕션에서 정한 만들어야할것 , 만드는 방법을 이용하여 제작체제를 스케일업 (확대)하고 , 게임을 완성한다.


워크플로


워크플로의 원칙은 상류의 공정이 완료될때까지 하류의 공정은 하지않는것이다 . 이를 반대로말하면 하류의 공정이 시작되었다면 상류의 공정을 최대한 변경하지 않아야 한다는것이다. 이때 , 상류와 하류는 재작업을 줄이기 위해서 만든것이지 , 절대 계갑을 나타내는 것이 아니므로 서로의 전문 영역에 영향력을 행사하려는 행동은 자중해야한다.


그레이박스


좋은그레이박스(게임의 콘텐츠의 중심)를 만들기 위해서는 그레이박스를 만든후 , 이터레이션 ( 개선할 부분을 찾고 수정하는 과정 )을 통해 완성도를 높여가야한다.이터레이션을 빠르게 만들기 위해서는 좋은 툴과 파이프라인이 필요한데 , 이때 , 레벨디자이너가 방대한 지식을 가지고있다면 혼자서 다양한 테스트를 할수 있으므로 , 빠른 이터레이션이 가능하다.


스테이지 제작 워크플로의 예


플롯


일단 스테이지의 플롯(구상) 을 만든다 . 이를통해 게임을 진행하면서 사용자가 스테이지에서 무엇을 보고 듣고 체험할것인지  (재미요소)를 결정한다.


컨셉아트


플롯을 기반으로 컨셉아트를 만든다. 컨셉아트란 디자인 , 아이디어 , 분위기 등을 최종 제품으로 만들어내기 전에 시각적으로 표현해서 전달하는 것을 목적으로하는 일러스트의 한 형태이다. 보통의 경우 컨셉아티스트가 플롯을 시각화하는데 , 컨셉아티스트가 없더라도 시각적이미지를 통일해야 사람마다 다른 형태의 게임을 이미지하는 우를 범하지않을수있다.

플레이 가능한 프로토타입 제작


상류 ( 플롯 , 컨셉아트 ) 에서 만들어진 정보를 바탕으로 레벨디자이너가 플레이 가능한 스테이지를 설정한다. 이때 원기둥 ,직육면체와 같은 간단한 메시를 사용해서 스테이지를 만들고 게임플레이를 테스트하는데 이 과정을 그레이박싱이라하고 , 만들어진 결과물을 그레이박스라고한다. 명심해야할것은 그레이박스단계에서 '게임을 어떻게 플레이할것인가' 를 완성해야한다는것이다. 하류공정 ( 비주얼요소 , 사운드요소등 )을 하는도중 이를 바꾸게되면 자원소모가 엄청나므로 , 반드시 시간을 더들여서라도 최대한 완벽하게 만들어야한다. 

그리고 그레이박스 과정에서는 게임역학과 관련된 내용을 충분히 완성해야한다. ( 캐릭터의 점프력등 ) 게임에서 직접 시도해봐야 완성할 수 있는 부분이 있는경우 , 그레이박스를 만들며 함께 진행하는것이좋다 .

그레이박스의 테스트 ( 프로토타입 )시 자신이 플레이하는것이 프로토타입임을 인지하고 , 이는 최소한의 것으로 게임의 가치(재미)를 판단하는것임을 잊어선안된다 . 타격감을위해 소리를추가한다는등의 수정은 게임의 재미를 확인하는 본질을 흐릴수있다.


메싱


그레이박스에서 스테이지에 놓은 임시 지오메트리를 정식에셋으로 바꿔서 배치하는 과정이다. 현대에는 물체를 작은 부분으로 나누고 그것을 스테이지상에서 조립하고 배치하는 과정을 사용한다 ( 레고블록과 비슷 ) . 레고블록구조를 활용하기위해서는 규칙을 정해야한다 .예를들어 벽을 구성하는 기본적인 맵의 부품의 크기규칙(벽은 2m X 2m X 10m로 규격을 정한다 ), fps 게임에서 엄폐공간의 높이규칙등을 정하는 것이다. 이를 레귤레이션이라고 한다.

이때 , 레고블록처럼 재사용을 목적으로하지않고 한번만 등장하는 애셋을 '히어로애셋' 이라고 하는데 이 애셋을 만들땐 다른 레고블록애셋과의 연결을 생각할 필요없고 , 레귤레이션에 얽메이지않아도된다.


그레이박스의 일부유지


그레이박스를 정식애셋으로 교체하는 메싱과정에서 그레이박스로 확정시킨 게임방식이 깨져버리는 경우가있다. (통로가 좁아지거나 히트정보가 바뀌는등)

이때, 그레이박스를 제거하지않은채 눈에 보이지 않는 히트로 남겨두고 내부에 정식배경애셋을 배치하는 방법을 많이사용한다 . 이때 , 문제가 생길수있는데 ( 공중에서막히는 탄환등) 이를 해결하기위해 많은게임에서는 여러종류의 히트판정을 복합해서 사용한다 .

이러한경우를 초기부터 막기위해서 그레이박스와 메싱단계에서 외형이 크게 변경되는 부분은 미리 애셋을 만들어서 레벨디자이너에게 제공하는것이 좋다.


레벨수정


메싱작업으로 게임플레이에 문제가 발생될수 있고 , 이를 아티스트는 알아챌수 없으므로 메싱공정에서 다른공정으로 넘어가기전에 레벨디자이너가 확인과 수정을 할 시간을 가져야한다.


라이팅


라이트를 배치하고, 조정하는 과정이다. 그레이박스에서 추가한 가상라이트를 손보는 과정이다. 어두운스테이지에서는 워크플로에서 문제가 있을 수 있으므로 그레이박스단계부터 레벨디자이너와 소통하며 작업하는것이좋다.


폴리시 


메시의 외형 또는 배치를 유지한채로 전체적인 품질을 향상시켜가는 단계이다. 완성된 라이팅을 기반으로 전체적인 화면의 디테일을 올리고 , 파티클 이벤트와 사운드등을 배치한다. 


워크플로 반복


완성된게임을 책임자가 체크하는 기간을가져야한다. 상류공정의 정밀도를 최대한으로 높인다고해도 원하는 수준에 한번에 다다를수는 없으므로 , 이러한 워크플로반복을 2번정도 한다고 생각하고 일정을 잡는것이 좋다. 


최적화


레벨디자이너 , 엔지니어 , 아티스트 , 테크니컬 아티스트가 모두 처리부하를 신경쓰면서 게임을 만든다 . 이때 , 문제를 찾으면서 최적화하는것은 어려운일이므로 , 문제를 찾고 이후에 최적화를하는 형태를 채택하는 편이 좋을것이다. 아트디렉터가 어설프게 최적화를 신경쓰며 작업할시 , 아트가 목표수준에 도달할 수 없으므로 아트를 목표수준까지 높여두고 게임을 완성한후 프로파일러등을 사용해 부하가 큰 부분을 찾아 큰 부분부터 최적화하는것이 좋다.

큰 부하가 걸릴것으로 예상했던 부분이 실제 게임실행시 걸리지않는 경우도 많으므로 , 그때그때 그것들을 처리한다면 시간낭비가된다.

애셋 제작단계에서는 메시의 폴리곤카운트(트라이앵글의수), 뼈의수 , 머티리얼의 스탭수등에 제한을 두는 정도면 충분한다.

+ Recent posts