C++ Component System KASA 2008-06-22 김성헌
Component System? 재사용 가능한 모듈(컴포넌트) 기반으로 프로그램을 조립하듯이 만드는 것 이미 Visual Basic, Delphi 등 RAD 툴로 익숙한 개념 OOP 에서 상속의 개념으로 이러한 모듈의 조립식 프로그래밍이 가능한 것처럼 말하지만, C++ 에서는 문제가 많다는데…
Agenda 복잡한 최근 게임과 OOP C++ 상속 리뷰 C++ 상속 문제점 살펴보기 Component System 소개 Q&A
최근 게임 경향 매우 다양한 게임 오브젝트 출현 주인공과 게임 오브젝트간의 풍부한 인터랙션 (이런 것 까지 될 줄이야!) 전문화 고도화되는 비 프로그래머의 게임 레벨 설정 (툴 만들어줘~) 어쨌거나 이런 요구 사항들을 구현해 내야 하는데, 소프트웨어 설계 능력이 중요하겠죠? 일단은 OOP 적인 접근은 필수…
Object-oriented Programming 주요 OOP 기법들: Modularity Encapsulation Polymorphism Inheritance !! 이론적으로는 문제가 없습니다. C++ 이라서 생기는 문제를 알아봅시다. Encapsulation: 코딩시 메소드 호출 중심으로 멤버 변수를 감추는 것 Modularity: 클래스라는 것을 쓴다. Polymorphism: 오버로딩이나 virtual 함수 Inheritance: 이전에 만들어 놓은 기능을 쉽게 가져다 쓰는 것
C++ Inheritance Base class Base class Derived class Derived class
C++ Inheritance Base class Base class Derived class Derived class
C++ Multiple Inheritance BaseA BaseB Derived
C++ Multiple Inheritance BaseA BaseB Derived
C++ Multiple Inheritance Base Classes: Components Derived class
C++ Multiple Inheritance 약간 복잡한 실세계: 2단계 이상 상속 Object Movable Drawable Edible Car Apple Chicken
C++ Multiple Inheritance 약간 복잡한 실세계: 2단계 이상 상속 !! Object Movable Drawable Edible Car Apple Chicken
The Diamond of Death Class B Class C A A A B C B C Class D A 이렇게 되기를 바라지만… B C D D
The Diamond of Death Class B Class C A A A B C B C A A Class D A B B C 이것이 현실..
The Diamond of Death A B C D
The Diamond of Death A B C D : error C2385: ambiguous access of 'FuncA' 1> could be the 'FuncA' in base 'A' 1> or could be the 'FuncA' in base 'A'
Virtual Inheritance ?
C++ Inheritance 평가 다중 상속이 문제일 뿐, 실세계 모델링 방법으로 여전히 훌륭함 Java나 C# 등 다중상속이 금지된 언어들도 잘 쓰이는 것을 보면 상속 자체가 나쁜 것은 아님 하지만 상속 기법으로 프로젝트를 크게 진행하다 보면 다음과 같은 문제점들을 만나게 된다. (해결해버리면 되지만 많은 노력과 시행 착오가 필요)
Template Method Pattern C++ template<> 과는 관계 없음 Base class 는 함수 interface 를 정의하고 Derived class 에서 함수 본체를 구현 변수만이 아닌 메소드 들이 포함된 상위 클래스를 상속하는 경우, 이런 식의 디자인이 많음
Template Method Pattern
Template Method Pattern
Template Method Pattern
Interface Bloat 프로그램이 복잡해 질 수록 상위 Base 클래스는 빈 virtual function 들로 가득 차는 경향이 있다. 문제가 되는 것은 아니지만 코드를 보고 있으면 정신이 혼란스럽다. 머리 속이 복잡하면 버그 유발
Pass-thru Enforcement 생성자와 소멸자는 클래스 상속 순서에 맞게 자동으로 chain을 이루어 호출되지만, 일반 메소드 (virtual 이건 아니건) 는 상황에 적당한 한 개만 호출된다. 팀 내부적으로 룰을 만들어 일반 메소드 작성 가이드 라인을 만드는데.. 실수로 안지키면 버그 유발
Pass-thru Enforcement 직접 상위클래스의 메소드를 호출하지 않는 빈 함수들이지만, 클래스 C 의 인스턴스가 생성, 소멸될 때 호출되는 메소드 순서는… (virtual ~A(); 가 무슨 효과가 있는지는 다들 아시죠?)
Pass-thru Enforcement 클래스 설계자의 의도: Initialize 메소드는 생성자 호출 chain 처럼 parent class 의 같은 메소드를 호출하시오.
Pass-thru Enforcement 프로그래머의 작은 실수: 함수틀을 Base 클래스에서 copy&paste 하다보니 Base::Initialize() 호출을 까먹음 그러나 Compiler 는 자동으로 chain 호출을 만들어주지도 않고 에러도 안낸다. 동작 중 버그를 일찍 발견하기를 바라는 수밖에..
Unintended Consequences 특정 오브젝트의 오동작 버그의 원인을 찾아 메소드를 고쳤는데, 고칠 때는 미처 몰랐던 다른 오브젝트에서 그 클래스를 상속하고 있었다. 잘 되던 것이 이상해짐…
그러나 진짜 큰 문제는.. 지속적인 기능 추가 요구 만들어 놨더니 빼달라고 요구 뺐더니 다시 넣자고.. 완전히 독립적인 클래스의 추가는 쉽겠지만, 다른 클래스와 기능이 겹치면.. (^C,^V 금지;;) 만들어 놨더니 빼달라고 요구 빼려고 하는 요소가 클래스 단위가 아니라면.. 뺐더니 다시 넣자고.. 소스컨트롤 롤백만으로 쉽게 되진 않고.. 상속에 의한 모듈들의 관계는 의존성이 너무 커서, 코드의 한 두 군데만 고쳐서 해결되는 경우가 참 적습니다.
Component System 상속으로 인한 문제점을 피하면서 컴포넌트 조립식 프로그래밍을 하려면...? “포함” 기법 사용 간단히, 필요한 클래스들을 멤버 변수로 잡아 포함할 수도 있겠지만 (하드코딩)… 다수의 컴포넌트 클래스들을 포함할 수 있고, 컴포넌트 간의 통신이 가능할 수 있도록 표준적인 방법을 구축하는 것… 컴포넌트 시스템 (GPG 6권 4.6)
이런 개념.. 포함을 하기 때문에 abstract 클래스가 아닌 한 문제 없음 User Object Component A Component B Component C Component D Component E 컴포넌트들을 포함하고 있는 오브젝트를 가리켜 Entity 라고도 함
CS 세부사항 기능상 비슷하지만 똑같지 않은 컴포넌트들을 Family (Category) 로 분류할 수 있다. 같은 분류의 컴포넌트는 다른 것으로 교체 가능해야 한다. (한 분류 내에 후보가 유일하여 무언가 교체해볼 여지가 없다면 컴포넌트 프로그래밍이라 할 수 없을 듯…) 컴포넌트를 interface 로 정의하고 ID 를 부여한다. Entity는 ID 당 1개의 컴포넌트만 가질 수 있다. 동시에 두 개 이상 컴포넌트가 필요하다면 그들을 포함하는 새로운 컴포넌트를 개발한다.
CS 세부사항 컴포넌트간의 통신… 딱히 기발한 해결책은 없고 모든 컴포넌트가 Owner (Parent) 의 포인터를 저장해서 활용함 (예제 코드 참조)
GPG 코드 보기
GameObject 상속 없이 GameObject 클래스 1개로 게임내 다양한 오브젝트를 표현 가지고 있는 컴포넌트의 종류에 따라 오브젝트의 성격이 변함 상속의 경우 해당 클래스를 가지고 있는지 dynamic_cast 를 썼겠지만, 포함된 컴포넌트는 Family ID에 따라 바로 static_cast (C스타일 캐스팅)로 변환하여 쓸 수 있다.
GameObject GPG 예제의 구현 방법: 생각해 볼 문제 String ID 와 map<string, component*> 로 씀 생각해 볼 문제 String ID 는 Debug 시에 도움을 줄 수 있지만, 성능 면에서 정수 ID 사용도 고려해 볼만함 컴포넌트 종류가 적다면 정수 ID 와 array 를 써서 map lookup 을 피할 수도 있음
Component 인터페이스
gocVisualSphereAndCube Component Hierarchy GOComponent gocVisual gocAI gocHealth familyID: … gocVisualSphere gocVisualCube … componentID: gocVisualSphereAndCube
특정(Visual) Family 인터페이스
Family내 특정 컴포넌트 구현
컴포넌트의 사용
컴포넌트간 통신
Component System 응용 게임 에디터와 연동 오브젝트 초기화 게임 오브젝트 타입이 상속으로 분화되지 않기 때문에, 화면에 표시하고 속성(컴포넌트)을 편집하는 에디터를 만들기 좋다. 오브젝트 초기화 상속의 경우, 최말단 클래스 타입이 무엇인지 판단하여 생성 후 데이터 초기화를 해야 하고, 초기화 코드의 분화가 어렵지만 컴포넌트 방식은 일단 생성 후, 점진적으로 개별 컴포넌트들을 추가하며 초기화를 할 수 있고, 컴포넌트 별로 초기화 코드를 골라 쓰기 쉽다. 데이터 파일로부터 읽어 초기화하기에 수월하다.
그러나 여전히… 오브젝트 표현 방법일 뿐 당연하게도, 구체적인 코딩 스킬을 향상시켜 주지는 않는다. 어떠한 Family 로 컴포넌트들을 나누고 각 컴포넌트간의 조합으로 로직을 만드는 능력은 경험으로 터득하는 수 밖에 없습니다;;
끝 – Q&A