2.
목차
• C++의 게임 프로그래밍에서의 장점
• 코딩 스타일
• 클래스 설계
클래스 계통 구조의 설계
설계 패턴 - 싱글톤, 퍼사드, 스테이트, 팩토리
3.
C++의 게임 프로그래밍에서의 장점
- C 로부터 전해진 높은 이식성과 효율성을 가지고 있다.
- 큰 규모의 프로젝트일수록 코드의 재사용성이 요구되기 때문에,
재사용 가능한 라이브러리와 게임 엔진의 작성이 불가결하다.
이를 위해 기본적인 OOP 원칙들과 개발 기법들을 제대로 이해하고 사용해야 한다.
이 장에서 주로 다룰 내용이 이에 대한 설계 기법들이다.
8.
클래스 설계
- 클래스에서 메서드 이름에 관한 명시적 규칙이 있는 것은
생성자와 소멸자 뿐이다.
이에 대한 적절한 규약을 만들어두면 여러모로 도움된다.
- 우측의 경우, 일단 생성자가 매우 직관적이다.
그리고 객체 생성 시점 이외의 시점에서도 클래스 변수를
초기화할 수 있게 되었다.
이런식으로 객체의 초기화를 생성자로부터 분리하면,
Create(), Destroy()를 여러번 호출하는 것으로
메모리 할당, 해제의 반복을 줄일 수 있다.
또한 기본생성자와 달리 반환값을 가질 수 있다는 이점도 있다.
10.
클래스 계통 구조의 설계
- 객체지향에서 코드 재사용성을 높이는 데 가장 중요한 것은 상속 개념이다.
이를 위해 클래스를 연결하는 기본적인 방법은 상속과 레이어링이 있다.
( 레이어링은 합성, 포함, 내포 등을 의미 )
상속은 하나의 클래스에서 다른 클래스를 파생하는 것이며,
레이어링은 하나의 객체가 다른 객체를 자신의 멤버로 가지는 것을 의미한다.
- 간단한 규칙으로, is-a 관계라면 public 상속을 이용하며,
has-a 관계는 레이어링을 사용하라고 한다.
이 책에선 이에 대한 구현 방식을 자세히 설명하지 않고, 개념적인 차이만 설명해주고 있음.
자세히 알고 싶다면 effective c++ 책으로 공부할 것을 추천함.
12.
설계 패턴 - 싱글톤 패턴
용도
- 여러 클래스/모듈 들이, 전역적으로 접근할 수 있는, 단일 객체가 필요할 때 쓰인다.
아래는 기본적인 구현방법의 하나이다.
13.
설계 패턴 - 싱글톤 패턴
- 아래는 기본적인 싱글톤 개념에, 확장 가능하도록 상속 개념을 추가한 구현이다.
14.
설계 패턴 - 퍼사드 패턴
용도
- 클래스들 사이의 상호의존성(커플링)을 최소화하기 위해서 사용한다.
이를 위해 각각의 서브시스템들을 대표하는 관리자 클래스를 만든다.
15.
설계 패턴 - 퍼사드 패턴
커플링을 줄여야 하는 이유
- 한 서브 시스템에서 큰 변화가 생겼을 때,
서브 시스템의 클래스들과 커플링 된 외부의 클래스들도 수정해야함.
당연히 커플링이 적을수록 작업량이 적다.
16.
설계 패턴 - 상태 패턴
이른바 state 패턴.
용도
- 게임의 규모가 클 때, 다양한 곳에서 다루는 상태를 관리하기 위해 사용한다.
혹은 상태의 구조가 복잡할 때도 사용하면 좋다.
- 우측과 같이 상태를 바꾸는 로직은 관리자역의 클래스에서
하도록 하는 것이 관리하기가 쉽다.
17.
설계 패턴 - 팩토리 패턴
- 하나의 클래스가 다양한 종류의 객체의 생성을 책임지게 하는 방식으로,
메모리 할당을 모아서 하거나, 객체들을 자원 관리자에 넣는 등
공통적인 작업을 한번에 수행하기 쉬워진다.
- 한 객체 모델에서 파생된, 서로 다른 수많은 객체들을 생성해야 된다면,
팩토리 패턴을 사용하는 것이 이롭다.
AI, 텍스처, 사운드 등의 자원은 물론 추상적인 객체도 적용 대상이 될 것이다.
18.
설계 패턴 - 팩토리 패턴
- 상속 개념을 통해 확장성과 유연성에 이득을 볼수도 있다.
클래스 ID를 받아 객체를 생성하고, 기반 클래스에 대한 포인터를 돌려주는 방식으로
팩토리 패턴으로 다룰 수 있는 객체의 종류를 추가/제거하는 작업을 쉽게 할 수 있다.
22.
assert 의 기본
- 코드상 임의의 가정이 부합하는지 감시하는 용도.
( 넘어온 인자가 nullptr 이 아니라고 가정할 때 등 )
- 다만 위 예제의 경우, 릴리즈 모드에서 강제종료 될 수 있음에 주의.
if 문으로 처리할 지 assert 를 쓸지 상황에 따라 생각해봐야 한다.
23.
assert 의 동작
- 실행 중 가정이 어긋나면(false 이면) 프로그램을 일시 정지시킨다.
이 때 사용자는 프로그램을 계속 돌릴지,
종료하고 문제가 생긴 코드로 이동할 지 결정할 수 있다.
( 근데 실제로 해보거나 구글해보면, 무조건 abort() 를 호출하도록 되어있다. vs 버전에 따라 다
르거나 assert 표준이 바뀐듯. )
- 릴리즈 모드에서 assert 구문이 들어간 코드는 컴파일되지 않으며, 작동하지 않는다.
때문에 assert 안에서 프로그램의 상태를 변화시킬 경우,
릴리즈 모드와 디버깅 모드의 동작이 달라지는 문제가 발생할 수 있다.
24.
assert 의 동작
- assert 안의 코드를 콘솔이나 메세지 박스에 출력하며,
해당 소스가 있는 파일과 line 도 알려준다.
- 혹은 다시 시도 버튼을 누른 후 중단시켜
중단된 시점의 call 스택과 변수의 상태를 확인하여
에러의 원인을 쉽게 찾을 수 있다.
25.
assert 의 용도
- 코드상 임의의 가정이 부합하는지 감시하는 용도.
- 제대로 동작했다면 논리적으로 올 수 없는 flow 에 도달했는지 검사하는 용도.
- 매우 빠르게 수행되어야 하는 저레벨의 함수를 작성할 때,
어떤 문제가 발생했는지 점검하는 용도.
( 릴리즈에서 동작하지 않기 때문에 약간의 가속효과를 얻을 수 있다. )
- 하지만 assert 를 사용하는 것은 일종의 디버깅 테크닉일 뿐으로,
예외처리와 구분하여 사용해야 한다. 예외처리를 해야하는 경우엔 if 문을 쓰자.
26.
assert 의 용도
- 예외처리를 해야되는 경우와 assert 를 사용해도 되는 경우의 구분
- 예외처리
> 외부 입력(소켓, 키보드, 파일 등)으로부터 저장된 변수의 경우
- assert 사용
> 설계상 반드시 조건을 갖춰야 하는 변수의 경우( NULL 이 아니라던지, 0 보다 커야 하던지)
> enum 값을 매개변수나 리턴 값으로 사용하는 경우
> enum 을 사용한 switch 문의 default 에서, 필요하다면 assert(0)
> if else chain 에서 맨 마지막 else 에서, 필요하다면 assert(0)
> 리소스를 해제할 때 메모리 릭이 발생하는지 확인할 때
27.
비법 #1 : 더 많은 정보를 넣는 법
- assert(src != 0) 이 실패하면, “src != 0” 이라는 메시지가 표시된 대화상자가 나타난다.
( 근데 설정이나 버전에 따라 나타나는 방식에 차이가 있는듯. 제껀 콘솔창에 표시됨.. )
다음과 같이 어떤 문제인지 구체적으로 적는다면, 디버그 시간을 단축할수도 있다.
혹은 assert(0) 을 사용하는 대신에,
이런 코드를 사용할수도 있다.
28.
비법 #2 : 좀더 편하게
- 매크로를 사용하여 비법1을 사용하기 편하게 할 수 있다.
29.
비법 #3 : 커스텀 assert 만들기
- 표준 C assert 에는 귀찮은 문제가 하나 있다.
디버깅 중 조건이 실패할 때, 소스 파일에 작성한 assert 문이 아니라,
assert.c 파일 안의 assert 문으로 유도된다는 점이다.
( 근데 내가 쓸땐 잘만 되는데, 이역시 vs 버전에 따라 다른 것 같음. )
- 오른쪽과 같은 커스텀 assert 를
만들어서 중단점이 소스 파일의
assert 로 유도되도록 할 수 있다.
30.
비법 #4 : 이것도 중요!
- 커스텀 assert 를 만들 때, assert 대화상자에 “다음부터 항상 무시” 옵션을 추가하는 것이 좋다.
assert 가 프레임마다 호출되는 등 귀찮은 경우가 있기 때문.
우측은 구현 예의 하나.
31.
비법 #5 : 초고수만 볼 것
- assert 의 또다른 문제로,
1. assert 가 함수의 내부에 있고
2. 함수가 받은 매개변수가 assert 의 발생 원인이 될 수 있는 경우
디버거를 같이 쓰지 않는 한, 왜 assert 가 발생했는지 쉽게 추적할 수 없다는 것이다.
때문에 거의 디버거를 붙여서 실행하지 않는, 테스터를 통한 도움을 받기 힘들다.
이에 대한 간단한 해결책은, assert 대화상자 안에 스택 상태를 표시하는 것이다.
아니면 assert 가 발생하면 스택의 상태를 클립보드에 복사되게 하거나.
35.
A*(에이 스타) 알고리즘
- 정확하면서 효율적인 길찾기 알고리즘의 하나.
- 대부분의 상황에서 최적의 수행시간을 제공하며,
몇가지 조건만 갖춘다면 다른 어떤 알고리즘보다도 빠르게 찾을 수 있다.
36.
동작 방식
- 각 지점(node)에 목표지점까지의 직선거리(h)를 저장한다.
두 가지 경로 목록을 관리한다.( Open list ), ( Closed list )
시작 지점을 Open list 에 넣으며 시작하며, 아래의 작업을 반복수행하는 것으로
최적의 경로를 구한다.
> 시작지점부터 현재 탐색중인 지점까지 지나온 경로의 weight 를 합한 값(g)을 구한다.
시작 지점은 이 값이 0.
> Open list 에 있는 지점 중 비용(g+h)이 가장 작은 값을 선택한다.
> 선택한 지점을 Closed list 에 추가하며, 인접한 지점을 Open list 에 추가한다.
> 목표지점에 도착했다면 지나온 경로를 리턴하며 빠져나온다.
37.
비용
- 바퀴가 달린 전차는 다른 길보다 도로에서 더 빠를것이다.
수륙 양용의 차라면 바다도 건널 수 있을 것.
시작지점이 평지고 도착지점이 언덕인 경우와 그 반대인 경우는
경로의 비용에 차이가 있을것이다.
이처럼 유닛의 종류나 이동의 양 끝 위치를 인자로 받아야 하는 경우도 있다.
- 보드게임처럼 바닥이 격자로 된 경우, 직선거리(h)는 최소로 목표지점까지 이동하는 칸 수가
될 것이다.
38.
비용
- weighted 그래프의 경우 추정 직선거리(h)가 실제 최단거리보다 커질 수 있다.
이 경우 최단 경로가 정확하지 않을 수 있다.
보다 정확한 최단 경로를 구하기 위해서는 h 를 설정할 때는 직선거리의 값을
가장 weigh가 작은 한 경로와 같은 비율이 되게 해야한다.
- 다만 값을 너무 작게 할 경우 올바른 방향으로 가기보다
‘안가봤던 방향’으로 향하는 경우가 더 많아져 비효율적이 될 수 있다.
40.
효율적이고 자연스러운 길찾기를 위한 그래프
- 시작위치와 도착지점에 맞아떨어지는 지점(node)은 존재하지 않을 수 있다.
때문에 그에 대한 지점은 검색이 일어날 때 추가해야 한다.
혹은 각각 가장 인접한 지점을 시작, 도착 지점으로 하던지.
- 도달하지 못하는 장소가 없도록 충분히 설정하되, 너무 많아선 안된다.
유닛이 좀더 지능적으로 이동하는 것처럼 보이려면, 되도록 지점을 지그재그로 배치하지 말자.
45.
다룰 내용
- 다양한 크기의 많은 객체들이 돌아다니는 게임에서
충돌 판정의 횟수를 줄이기 위한 방법.
- 이런 로직을 설계하지 않았다면 O(n^2) 지옥을 볼수있다.
46.
쉬운 문제부터 접근해보자.
- 다양한 크기라는 조건을 뺀다고 가정한다.
동일한 크기의 적당히 큰 격자맵을 둔다.
각 격자는 그 칸에 중점을 두고 있는 객체들을 담은 링크드 리스트를 관리한다.
어떤 객체에 대해 충돌을 검사하는 경우, 그 객체가 속한 칸과 주변 칸의 리스트
안에 담긴 객체들 하고만 검사하면 된다.
47.
쉬운 문제부터 접근해보자.
- 모든 객체들에 서로의 충돌여부를 검사하는 경우.
좌상단의 첫번째 칸부터 진행.
각 칸의 리스트 내 객체를 순회하며 순서대로 모든 객체를 한번씩 선택함.
선택한 객체의 칸과 동쪽, 남동쪽, 남쪽의 세 칸에 대해서만 검사해주면 된다.
( 책에서는 이렇게 말했지만 남서쪽도 확인해줘야 할 듯. )
그 외의 칸들은 이전에 확인했던 객체를 통해 검사가 끝났기 때문.
이 방법으로 모든 객체들에 대한 충돌검사를 빠르게 처리할 수 있다.
48.
이제 본 문제.
- 객체의 크기가 다양한 경우 이 방법을 그대로 적용하긴 어렵다.
( 객체가 격자 한칸보다 크다거나. )
물론 격자의 크기를 가장 큰 객체의 크기만큼 늘리면 충돌은 제대로 검사하지만,
그만큼 효율이 떨어진다.
- 이에 대한 쉬운 해결책 중 하나는, 객체의 중점을 포함하는 칸 뿐만 아니라,
실제로 객체가 닿는 모든 격자의 리스트에 객체를 추가하는 것이다.
구현은 간단하지만 그리 깔끔한 방법은 아니다.( 계산량이 오히려 많아질수도 있음 )
49.
다해상도 맵
- 저자가 제시하는 해결책은 다음과 같다.
격자를 하나만 사용하지 않고, 칸의 크기에 차이가 있는 여러개의 격자를 사용.
칸의 크기는 2의 제곱수로 제한( 최적화를 위해 )
각 객체는 크기에 따라 적절한 격자맵을 선택하고,
중점이 속한 칸의 링크드 리스트에 추가한다.
여기서 적절한 크기란, 객체가 한 격자의 안에 들어갈 수 있는 격자 중 최소인 크기.
50.
다해상도 맵
- 충돌을 검출하는 방식은 앞서 설명한 방식과 유사하다.
먼저 자신과 주변 칸을 검사한다.
그리고 서로 다른 격자맵을 가진 객체간의 충돌도 검출해야 한다.
이를 위해 객체가 속한 칸이 더 큰 격자맵에서 어떤 격자에 속하는지 구하고,
주변의 더 큰 객체와의 충돌을 검출한다.
( 자신보다 작은 객체가 자신과의 충돌을 검출하기 때문에 )
더 작은 객체에 대해서는 확인할 필요가 없다.
51.
다해상도 맵
- 그럼 맵을 몇개나 만들어야 할까?
일단 가장 작은 격자의 크기는 가장 작은 객체를 포함할 수 있는 2의 제곱수여야 한다.
그로부터 한 단계씩 줄여 해상도 1의 맵 전체를 포함하는 격자맵을 만든다면,
어떤 크기의 객체가 추가되어도( 화면 전체를 뒤덮는 폭발 효과 라던지 ) 대처할 수 있다.
격자맵이 많을수록 부하가 심할 것처럼 느낄 수 있겠지만,
한 단계마다 격자의 수는 ¼ 로 줄어들기 때문에 그리 큰 부담이 아니다.
물론 가장 큰 객체가 확실히 정해졌다면 해상도의 하한을 결정할 수 있다.
가장 효율적인 방법은 개발 도중에는 하한을 두지 말고, 완료 직전에 결정하는 것이다.
Parece que tem um bloqueador de anúncios ativo. Ao listar o SlideShare no seu bloqueador de anúncios, está a apoiar a nossa comunidade de criadores de conteúdo.
Odeia anúncios?
Atualizámos a nossa política de privacidade.
Atualizámos a nossa política de privacidade de modo a estarmos em conformidade com os regulamentos de privacidade em constante mutação a nível mundial e para lhe fornecer uma visão sobre as formas limitadas de utilização dos seus dados.
Pode ler os detalhes abaixo. Ao aceitar, está a concordar com a política de privacidade atualizada.