Top Banner
GPG 1.1 객객 객객객 객객객객객객 객객객객 NHN NEXT 객객객
58

NHN NEXT GPG Study

Aug 13, 2015

Download

Software

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: NHN NEXT GPG Study

GPG 1.1 객체 지향적 프로그래밍과 설계기법NHN NEXT 김승현

Page 2: NHN NEXT GPG Study

목차• C++ 의 게임 프로그래밍에서의 장점

• 코딩 스타일

• 클래스 설계

클래스 계통 구조의 설계

설계 패턴 - 싱글톤 , 퍼사드 , 스테이트 , 팩토리

Page 3: NHN NEXT GPG Study

C++ 의 게임 프로그래밍에서의 장점- C 로부터 전해진 높은 이식성과 효율성을 가지고 있다 .

- 큰 규모의 프로젝트일수록 코드의 재사용성이 요구되기 때문에 ,

재사용 가능한 라이브러리와 게임 엔진의 작성이 불가결하다 .

이를 위해 기본적인 OOP 원칙들과 개발 기법들을 제대로 이해하고 사용해야 한다 .

이 장에서 주로 다룰 내용이 이에 대한 설계 기법들이다 .

Page 4: NHN NEXT GPG Study

코딩 스타일

Page 5: NHN NEXT GPG Study

코딩 스타일

- 헝가리식 표기법

Page 6: NHN NEXT GPG Study

코딩 스타일

- 클래스 이름 접두어

클래스의 용도에 따라 C 로 시작하는 클래스는 구체 (Concrete) 클래스 ,

I 는 인터페이스 클래스 . 즉 , 설계 템플릿으로 쓰일 클래스들을 뜻하게 한다 .

기능에 따라 UI 등의 접두어를 붙일 수도 있다 .

Page 7: NHN NEXT GPG Study

클래스 설계

Page 8: NHN NEXT GPG Study

클래스 설계

- 클래스에서 메서드 이름에 관한 명시적 규칙이 있는 것은

생성자와 소멸자 뿐이다 .

이에 대한 적절한 규약을 만들어두면 여러모로 도움된다 .

- 우측의 경우 , 일단 생성자가 매우 직관적이다 .

그리고 객체 생성 시점 이외의 시점에서도 클래스 변수를

초기화할 수 있게 되었다 .

이런식으로 객체의 초기화를 생성자로부터 분리하면 ,

Create(), Destroy() 를 여러번 호출하는 것으로

메모리 할당 , 해제의 반복을 줄일 수 있다 .

또한 기본생성자와 달리 반환값을 가질 수 있다는 이점도 있다 .

Page 9: NHN NEXT GPG Study

클래스 계통 구조의 설계

Page 10: NHN NEXT GPG Study

클래스 계통 구조의 설계

- 객체지향에서 코드 재사용성을 높이는 데 가장 중요한 것은 상속 개념이다 .

이를 위해 클래스를 연결하는 기본적인 방법은 상속과 레이어링이 있다 .

( 레이어링은 합성 , 포함 , 내포 등을 의미 )

상속은 하나의 클래스에서 다른 클래스를 파생하는 것이며 ,

레이어링은 하나의 객체가 다른 객체를 자신의 멤버로 가지는 것을 의미한다 .

- 간단한 규칙으로 , is-a 관계라면 public 상속을 이용하며 ,

has-a 관계는 레이어링을 사용하라고 한다 .

이 책에선 이에 대한 구현 방식을 자세히 설명하지 않고 , 개념적인 차이만 설명해주고 있음 .

자세히 알고 싶다면 effective c++ 책으로 공부할 것을 추천함 .

Page 11: NHN NEXT GPG Study

설계 패턴

Page 12: NHN NEXT GPG Study

설계 패턴 - 싱글톤 패턴

용도

- 여러 클래스 / 모듈 들이 , 전역적으로 접근할 수 있는 , 단일 객체가 필요할 때 쓰인다 .

아래는 기본적인 구현방법의 하나이다 .

Page 13: NHN NEXT GPG Study

설계 패턴 - 싱글톤 패턴

- 아래는 기본적인 싱글톤 개념에 , 확장 가능하도록 상속 개념을 추가한 구현이다 .

Page 14: NHN NEXT GPG Study

설계 패턴 - 퍼사드 패턴

용도

- 클래스들 사이의 상호의존성 ( 커플링 ) 을 최소화하기 위해서 사용한다 .

이를 위해 각각의 서브시스템들을 대표하는 관리자 클래스를 만든다 .

Page 15: NHN NEXT GPG Study

설계 패턴 - 퍼사드 패턴

커플링을 줄여야 하는 이유

- 한 서브 시스템에서 큰 변화가 생겼을 때 ,

서브 시스템의 클래스들과 커플링 된 외부의 클래스들도 수정해야함 .

당연히 커플링이 적을수록 작업량이 적다 .

Page 16: NHN NEXT GPG Study

설계 패턴 - 상태 패턴

이른바 state 패턴 .

용도

- 게임의 규모가 클 때 , 다양한 곳에서 다루는 상태를 관리하기 위해 사용한다 .

혹은 상태의 구조가 복잡할 때도 사용하면 좋다 .

- 우측과 같이 상태를 바꾸는 로직은 관리자역의 클래스에서

하도록 하는 것이 관리하기가 쉽다 .

Page 17: NHN NEXT GPG Study

설계 패턴 - 팩토리 패턴

- 하나의 클래스가 다양한 종류의 객체의 생성을 책임지게 하는 방식으로 ,

메모리 할당을 모아서 하거나 , 객체들을 자원 관리자에 넣는 등

공통적인 작업을 한번에 수행하기 쉬워진다 .

- 한 객체 모델에서 파생된 , 서로 다른 수많은 객체들을 생성해야 된다면 ,

팩토리 패턴을 사용하는 것이 이롭다 .

AI, 텍스처 , 사운드 등의 자원은 물론 추상적인 객체도 적용 대상이 될 것이다 .

Page 18: NHN NEXT GPG Study

설계 패턴 - 팩토리 패턴

- 상속 개념을 통해 확장성과 유연성에 이득을 볼수도 있다 .

클래스 ID 를 받아 객체를 생성하고 , 기반 클래스에 대한 포인터를 돌려주는 방식으로

팩토리 패턴으로 다룰 수 있는 객체의 종류를 추가 / 제거하는 작업을 쉽게 할 수 있다 .

Page 19: NHN NEXT GPG Study

설계 패턴 - 팩토리 패턴

- 상속을 활용한 구현 예

Page 20: NHN NEXT GPG Study

Page 21: NHN NEXT GPG Study

GPG 1.12 assert 의 비법들NHN NEXT 김승현

Page 22: NHN NEXT GPG Study

assert 의 기본- 코드상 임의의 가정이 부합하는지 감시하는 용도 .

( 넘어온 인자가 nullptr 이 아니라고 가정할 때 등 )

- 다만 위 예제의 경우 , 릴리즈 모드에서 강제종료 될 수 있음에 주의 .

if 문으로 처리할 지 assert 를 쓸지 상황에 따라 생각해봐야 한다 .

Page 23: NHN NEXT GPG Study

assert 의 동작- 실행 중 가정이 어긋나면 (false 이면 ) 프로그램을 일시 정지시킨다 .

이 때 사용자는 프로그램을 계속 돌릴지 ,

종료하고 문제가 생긴 코드로 이동할 지 결정할 수 있다 .

( 근데 실제로 해보거나 구글해보면 , 무조건 abort() 를 호출하도록 되어있다 . vs 버전에 따라 다

르거나 assert 표준이 바뀐듯 . )

- 릴리즈 모드에서 assert 구문이 들어간 코드는 컴파일되지 않으며 , 작동하지 않는다 .

때문에 assert 안에서 프로그램의 상태를 변화시킬 경우 ,

릴리즈 모드와 디버깅 모드의 동작이 달라지는 문제가 발생할 수 있다 .

Page 24: NHN NEXT GPG Study

assert 의 동작- assert 안의 코드를 콘솔이나 메세지 박스에 출력하며 ,

해당 소스가 있는 파일과 line 도 알려준다 .

- 혹은 다시 시도 버튼을 누른 후 중단시켜

중단된 시점의 call 스택과 변수의 상태를 확인하여

에러의 원인을 쉽게 찾을 수 있다 .

Page 25: NHN NEXT GPG Study

assert 의 용도- 코드상 임의의 가정이 부합하는지 감시하는 용도 .

- 제대로 동작했다면 논리적으로 올 수 없는 flow 에 도달했는지 검사하는 용도 .

- 매우 빠르게 수행되어야 하는 저레벨의 함수를 작성할 때 ,

어떤 문제가 발생했는지 점검하는 용도 .

( 릴리즈에서 동작하지 않기 때문에 약간의 가속효과를 얻을 수 있다 . )

- 하지만 assert 를 사용하는 것은 일종의 디버깅 테크닉일 뿐으로 ,

예외처리와 구분하여 사용해야 한다 . 예외처리를 해야하는 경우엔 if 문을 쓰자 .

Page 26: NHN NEXT GPG Study

assert 의 용도- 예외처리를 해야되는 경우와 assert 를 사용해도 되는 경우의 구분

- 예외처리

> 외부 입력 ( 소켓 , 키보드 , 파일 등 ) 으로부터 저장된 변수의 경우

- assert 사용

> 설계상 반드시 조건을 갖춰야 하는 변수의 경우 ( NULL 이 아니라던지 , 0 보다 커야 하던지 )

> enum 값을 매개변수나 리턴 값으로 사용하는 경우

> enum 을 사용한 switch 문의 default 에서 , 필요하다면 assert(0)

> if else chain 에서 맨 마지막 else 에서 , 필요하다면 assert(0)

> 리소스를 해제할 때 메모리 릭이 발생하는지 확인할 때

Page 27: NHN NEXT GPG Study

비법 #1 : 더 많은 정보를 넣는 법- assert(src != 0) 이 실패하면 , “src != 0” 이라는 메시지가 표시된 대화상자가 나타난다 .

( 근데 설정이나 버전에 따라 나타나는 방식에 차이가 있는듯 . 제껀 콘솔창에 표시됨 .. )

다음과 같이 어떤 문제인지 구체적으로 적는다면 , 디버그 시간을 단축할수도 있다 .

혹은 assert(0) 을 사용하는 대신에 ,

이런 코드를 사용할수도 있다 .

Page 28: NHN NEXT GPG Study

비법 #2 : 좀더 편하게- 매크로를 사용하여 비법 1 을 사용하기 편하게 할 수 있다 .

Page 29: NHN NEXT GPG Study

비법 #3 : 커스텀 assert 만들기- 표준 C assert 에는 귀찮은 문제가 하나 있다 .

디버깅 중 조건이 실패할 때 , 소스 파일에 작성한 assert 문이 아니라 ,

assert.c 파일 안의 assert 문으로 유도된다는 점이다 .

( 근데 내가 쓸땐 잘만 되는데 , 이역시 vs 버전에 따라 다른 것 같음 . )

- 오른쪽과 같은 커스텀 assert 를

만들어서 중단점이 소스 파일의

assert 로 유도되도록 할 수 있다 .

Page 30: NHN NEXT GPG Study

비법 #4 : 이것도 중요 !

- 커스텀 assert 를 만들 때 , assert 대화상자에 “다음부터 항상 무시” 옵션을 추가하는 것이 좋다 .

assert 가 프레임마다 호출되는 등 귀찮은 경우가 있기 때문 .

우측은 구현 예의 하나 .

Page 31: NHN NEXT GPG Study

비법 #5 : 초고수만 볼 것- assert 의 또다른 문제로 ,

1. assert 가 함수의 내부에 있고

2. 함수가 받은 매개변수가 assert 의 발생 원인이 될 수 있는 경우

디버거를 같이 쓰지 않는 한 , 왜 assert 가 발생했는지 쉽게 추적할 수 없다는 것이다 .

때문에 거의 디버거를 붙여서 실행하지 않는 , 테스터를 통한 도움을 받기 힘들다 .

이에 대한 간단한 해결책은 , assert 대화상자 안에 스택 상태를 표시하는 것이다 .

아니면 assert 가 발생하면 스택의 상태를 클립보드에 복사되게 하거나 .

Page 32: NHN NEXT GPG Study

비법 #5 : 초고수만 볼 것

Page 33: NHN NEXT GPG Study

끗 !

Page 34: NHN NEXT GPG Study

GPG 3.3 A* 길찾기 알고리즘의 기초NHN NEXT 김승현

Page 35: NHN NEXT GPG Study

A*( 에이 스타 ) 알고리즘- 정확하면서 효율적인 길찾기 알고리즘의 하나 .

- 대부분의 상황에서 최적의 수행시간을 제공하며 ,

몇가지 조건만 갖춘다면 다른 어떤 알고리즘보다도 빠르게 찾을 수 있다 .

Page 36: NHN NEXT GPG Study

동작 방식- 각 지점 (node) 에 목표지점까지의 직선거리 (h) 를 저장한다 .

두 가지 경로 목록을 관리한다 .( Open list ), ( Closed list )

시작 지점을 Open list 에 넣으며 시작하며 , 아래의 작업을 반복수행하는 것으로

최적의 경로를 구한다 .

> 시작지점부터 현재 탐색중인 지점까지 지나온 경로의 weight 를 합한 값 (g) 을 구한다 .

시작 지점은 이 값이 0.

> Open list 에 있는 지점 중 비용 (g+h) 이 가장 작은 값을 선택한다 .

> 선택한 지점을 Closed list 에 추가하며 , 인접한 지점을 Open list 에 추가한다 .

> 목표지점에 도착했다면 지나온 경로를 리턴하며 빠져나온다 .

Page 37: NHN NEXT GPG Study
Page 38: NHN NEXT GPG Study

비용- 바퀴가 달린 전차는 다른 길보다 도로에서 더 빠를것이다 .

수륙 양용의 차라면 바다도 건널 수 있을 것 .

시작지점이 평지고 도착지점이 언덕인 경우와 그 반대인 경우는

경로의 비용에 차이가 있을것이다 .

이처럼 유닛의 종류나 이동의 양 끝 위치를 인자로 받아야 하는 경우도 있다 .

- 보드게임처럼 바닥이 격자로 된 경우 , 직선거리 (h) 는 최소로 목표지점까지 이동하는 칸 수가 될 것이다 .

Page 39: NHN NEXT GPG Study

비용- weighted 그래프의 경우 추정 직선거리 (h) 가 실제 최단거리보다 커질 수 있다 .

이 경우 최단 경로가 정확하지 않을 수 있다 .

보다 정확한 최단 경로를 구하기 위해서는 h 를 설정할 때는 직선거리의 값을

가장 weigh 가 작은 한 경로와 같은 비율이 되게 해야한다 .

- 다만 값을 너무 작게 할 경우 올바른 방향으로 가기보다

‘ 안가봤던 방향’으로 향하는 경우가 더 많아져 비효율적이 될 수 있다 .

Page 40: NHN NEXT GPG Study

비용

Page 41: NHN NEXT GPG Study

효율적이고 자연스러운 길찾기를 위한 그래프- 시작위치와 도착지점에 맞아떨어지는 지점 (node) 은 존재하지 않을 수 있다 .

때문에 그에 대한 지점은 검색이 일어날 때 추가해야 한다 .

혹은 각각 가장 인접한 지점을 시작 , 도착 지점으로 하던지 .

- 도달하지 못하는 장소가 없도록 충분히 설정하되 , 너무 많아선 안된다 .

유닛이 좀더 지능적으로 이동하는 것처럼 보이려면 , 되도록 지점을 지그재그로 배치하지 말자 .

Page 42: NHN NEXT GPG Study

그래프를 만드는 다양한 방법

Page 43: NHN NEXT GPG Study

A* 의 약점- 시작지점에서 목표까지의 경로가 존재하지 않을 경우

모든 지점을 조사하고나서야 그것을 알 수 있다 .

이에 대한 타개책으로 , 독립된 구역에 대해 ( 섬 이라든지 ) 같은 구역 내에서만 길찾기를 허용하는 것도 하나의 방법이다 .

Page 44: NHN NEXT GPG Study

끗 !

Page 45: NHN NEXT GPG Study

GPG 4.6 다해상도 맵을 이용한 충돌 판정NHN NEXT 김승현

Page 46: NHN NEXT GPG Study

다룰 내용- 다양한 크기의 많은 객체들이 돌아다니는 게임에서

충돌 판정의 횟수를 줄이기 위한 방법 .

- 이런 로직을 설계하지 않았다면 O(n^2) 지옥을 볼수있다 .

Page 47: NHN NEXT GPG Study

쉬운 문제부터 접근해보자 .

- 다양한 크기라는 조건을 뺀다고 가정한다 .

동일한 크기의 적당히 큰 격자맵을 둔다 .

각 격자는 그 칸에 중점을 두고 있는 객체들을 담은 링크드 리스트를 관리한다 .

어떤 객체에 대해 충돌을 검사하는 경우 , 그 객체가 속한 칸과 주변 칸의 리스트

안에 담긴 객체들 하고만 검사하면 된다 .

Page 48: NHN NEXT GPG Study
Page 49: NHN NEXT GPG Study

쉬운 문제부터 접근해보자 .

- 모든 객체들에 서로의 충돌여부를 검사하는 경우 .

좌상단의 첫번째 칸부터 진행 .

각 칸의 리스트 내 객체를 순회하며 순서대로 모든 객체를 한번씩 선택함 .

선택한 객체의 칸과 동쪽 , 남동쪽 , 남쪽의 세 칸에 대해서만 검사해주면 된다 .

( 책에서는 이렇게 말했지만 남서쪽도 확인해줘야 할 듯 . )

그 외의 칸들은 이전에 확인했던 객체를 통해 검사가 끝났기 때문 .

이 방법으로 모든 객체들에 대한 충돌검사를 빠르게 처리할 수 있다 .

Page 50: NHN NEXT GPG Study

이제 본 문제 .

- 객체의 크기가 다양한 경우 이 방법을 그대로 적용하긴 어렵다 .

( 객체가 격자 한칸보다 크다거나 . )

물론 격자의 크기를 가장 큰 객체의 크기만큼 늘리면 충돌은 제대로 검사하지만 ,

그만큼 효율이 떨어진다 .

- 이에 대한 쉬운 해결책 중 하나는 , 객체의 중점을 포함하는 칸 뿐만 아니라 ,

실제로 객체가 닿는 모든 격자의 리스트에 객체를 추가하는 것이다 .

구현은 간단하지만 그리 깔끔한 방법은 아니다 .( 계산량이 오히려 많아질수도 있음 )

Page 51: NHN NEXT GPG Study

다해상도 맵- 저자가 제시하는 해결책은 다음과 같다 .

격자를 하나만 사용하지 않고 , 칸의 크기에 차이가 있는 여러개의 격자를 사용 .

칸의 크기는 2 의 제곱수로 제한 ( 최적화를 위해 )

각 객체는 크기에 따라 적절한 격자맵을 선택하고 ,

중점이 속한 칸의 링크드 리스트에 추가한다 .

여기서 적절한 크기란 , 객체가 한 격자의 안에 들어갈 수 있는 격자 중 최소인 크기 .

Page 52: NHN NEXT GPG Study
Page 53: NHN NEXT GPG Study

다해상도 맵- 충돌을 검출하는 방식은 앞서 설명한 방식과 유사하다 .

먼저 자신과 주변 칸을 검사한다 .

그리고 서로 다른 격자맵을 가진 객체간의 충돌도 검출해야 한다 .

이를 위해 객체가 속한 칸이 더 큰 격자맵에서 어떤 격자에 속하는지 구하고 ,

주변의 더 큰 객체와의 충돌을 검출한다 .

( 자신보다 작은 객체가 자신과의 충돌을 검출하기 때문에 )

더 작은 객체에 대해서는 확인할 필요가 없다 .

Page 54: NHN NEXT GPG Study

다해상도 맵- 그럼 맵을 몇개나 만들어야 할까 ?

일단 가장 작은 격자의 크기는 가장 작은 객체를 포함할 수 있는 2 의 제곱수여야 한다 .

그로부터 한 단계씩 줄여 해상도 1 의 맵 전체를 포함하는 격자맵을 만든다면 ,

어떤 크기의 객체가 추가되어도 ( 화면 전체를 뒤덮는 폭발 효과 라던지 ) 대처할 수 있다 .

격자맵이 많을수록 부하가 심할 것처럼 느낄 수 있겠지만 ,

한 단계마다 격자의 수는 ¼ 로 줄어들기 때문에 그리 큰 부담이 아니다 .

물론 가장 큰 객체가 확실히 정해졌다면 해상도의 하한을 결정할 수 있다 .

가장 효율적인 방법은 개발 도중에는 하한을 두지 말고 , 완료 직전에 결정하는 것이다 .

Page 55: NHN NEXT GPG Study

코드

Page 56: NHN NEXT GPG Study

코드

Page 57: NHN NEXT GPG Study

코드

Page 58: NHN NEXT GPG Study

끗 !