꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 김포프

사실... 오늘이 제 기고일은 아니지만... 그냥 개인 블로그에 예전에 올린 글들을 좀 살펴보다가 그냥 여기에 올려도 나쁘지 않은 짧은 꼼수가 보여서... 올립니다.... (뭐 어차피 오늘 예약 걸린 글도 안보이더군요...)


제가 10년전에 문자열을 초기화 하던 방법은 다음과 같습니다.


char temp[64];

temp[0] = 0;


근데 2006년인가 캡콤 밴쿠버에서 일할 때, 동료 프로그래머 하나가 다음의 방법이 더 낫다고 귀뜸해줬지요.


char temp[64] = {0, };


그 친구가 이 방법이 더 나은 이유를 말해줬던거 같은데 사실 기억은 안나고... -_- 그냥 그 뒤로 줄곧 이 방법을 사용해왔죠. 뭐, 더 나은 방법이라니까.... 믿지 뭐..... 근데 최근(사실 첨 글쓴 시기로는 1년전.... -_-)에 우연히 이 방법이 왜 더 나은지를 알게되었습니다.


Xbox 360에서 프로파일 캡춰를 하던 도중 위의 코드가 컴파일되면 어떻게 변하는지를 찾아냈거든요. 다음과 같아요.


char temp[64];

memset(temp, 0, sizeof(char) * 64);


흥미로운 사실.. memset이 호출된다. 이 방법은 5~10 마이크로세컨드 정도가 걸리는데(물론 엑박360 하드웨어에서) 사실 뭐 그리 걱정할 정도는 아니고요. 근데 생각해보면 char 버퍼 선언뒤에 곧바로 strcpy 등의 함수를 호출한다면 굳이 위와 같은 초기화는 할 필요가 없겠네요. (뭐가되든 null 캐릭터만 들어가면 되니까요.)





반응형
,
Posted by 대마왕J

넵 11강.

이번 11강부터는 템포를 좀 줄일게요. 욕심부려서 한 회에 한 편을 완료했더니 아주 그냥 부담이 팍팍...
아니 평소같으면 괜찮습니다 사실. 어차피 퇴근하고 밤에는 뭔가 계속 깔짝대면서 공부하거나 글쓰니까요.
근데 요즘은 갑자기 몸이 고장이 난데다가 담주에 일주일간 여행을 갈 일도 있고 해서 하고 싶던 공부도 못하고 저녁에 그냥 쓰러지는 상태라.. OTL 템포를 조금 줄이도록 하겠습니다. 꾸준한게 중요하죠 꾸준한게!!!

이번 시간부터는 에또.. 봅시다. 그동안 10강을 하면서 꽤 많은 것을 했습니다. 특히 10강동안 중요하게 생각했던게, '텍스쳐 블렌딩' 쪽이었지요. 일단 그쪽이 제일 쉽구요. 그렇게해서 일단 익숙해지게 만드는게 우선이었습니다. 아무래도 그래픽 아티스트들에게는 shader가 그렇게 익숙한 것이 아니기 때문이지요

 

 

그래서 이렇게 맛을 들이고 난 후 ...

이번 시간부터가 shader의 진짜 능력인 'Lighting' 쪽을 건드려 보는 시간입니다.
뭐 라이팅이라고 해도 기본적인것만 합니다 기본적인것만. 아직 고급 라이팅을 가르칠 수준도 안되고 , 답답한 노드구조로 고급 라이팅 짜다가는 제가 빡칠 것 같아서 ... -_-;;;

 일단 제가 빡치면 재미있는 강의는 물건너 가는거잖아요 ㅋ

귀찮기도 하구요

 

뭐 어쨌건 오늘은 라이팅 기초 들어갑니다. 
여러 번 말씀드렸듯, 프로그래머들이 Shader 공부하는 것과 완전 반대 과정으로 가고 있어요 :) 공식 증명 같은것도 몰라요. 그냥 알기 쉽게, 제가 이해하는 수준으로 말씀드리고 말래요. 늘 그랬듯 말이죠 :)

 

라이트 개념잡기

3D 공간에서의 라이트는, 재미있는 속성을 가지고 있습니다.
오늘은 일단 라이트의 기본 개념부터 잡고 가도록 하죠.

기본 라이트는 몇 가지 종류가 있습니까?
그래요. 3D MAX좀 만져 보셨다면 다들 아시듯이, 라이트는 3가지 종류가 있습니다.
Directional , Omni, Spot 이렇게 말이죠. [각주:1] 

Directional light는 원통형으로 보통 표시됩니다. 직진하는 빛의 속성을 가지고 있고, 태양빛 등을 표현할때 보통 사용하죠.

Omni light는 사실 그래픽 아티스트만 알아듣는 용어입니다. 프로그래머들은 몰라요. 프로그래머들은 'Point light' 라고 주로 말합니다 . 그래서 게임 엔진에서도 Point light 라고 표현하고 있지요. 한글로는 '점광원' 이라고도 합니다.[각주:2]

Spot light는 말그대로 '스포트라이트' 입니다. 실생활에서 인위적인 조명기구에게서는 많이 볼 수 있는 조명 형태인데, 이상하게도 게임에서는 상대적으로 많이 사용하지 않지요? 후후후 

 

보통 3D 그래픽을 처음 시작하시는 그래픽 디자이너 분들이라면, 이 라이트를 가지고 공부하게 됩니다.

즉, '이게 전부이자 이게 실생활의 빛과 똑같은 것' 이라고 개념을 잡게 되지요.
그러다 보니까 실제 게임 엔진에서와, 실제 세상에서의 빛 차이 같은 경우를 별로 의식하지 않고 생활하는 경우가 있습니다.

하지만 아니예요! 게임에서의 라이트는 조금 달라요! 실생활에서의 라이트도 조금 다르고요!!!

그럼 한 번 그 차이중에서 아주 일반적인거 한 두개만 얘기해 볼까요? [각주:3]

 

우훗 ♡

 

자... 여기 전구가 있습니다.

 

그리고 이 전구 주변에 물체가 있다고 칩시다.  

하나는 가까이에 있고, 하나는 멀리에 있습니다.

이제 퀴즈입니다.

 

 

자, 전구 가까이 있는 물체 A가 밝을까요,
전구에서 멀리 떨어진 물체B가 밝을까요?

 

 

일반적인 학생들은 거의 대부분이 , 전구에서 가까운 물체 A가 밝다고 합니다.

 

 

그렇지만 3D 그래픽에서는 .... 
A와 B는 똑같거나 혹은 B가 더 밝습니다!!

이 개념은 나중에 설명할 라이팅에서 매우 중요하게 취급될 개념이기 때문에 꼭 알아야 하는 개념입니다.
일단 위 내용이 사실인지, 3D max 에서 보도록 하지요.

 

뷰포트에서 봐도 확실히 가까운 곳의 Plane이 더 어두워 보입니다.

렌더링 하면 더 확실해 집니다. 그림자는 어차피 켜지 않았으니까 그냥 빛이 통과된다고 생각하고 보면, 가까이 있는 plane이 멀리 있는 plane보다 더 어두운 것을 볼 수 있습니다.

이 이유는 무엇일까요?

 

 

자, 잘 모르시겠으면 다른 질문 하나 하죠.

지구에서 가장 더운 곳은 어디인가요?

적도지요???

그럼, 왜 봄 여름 가을 겨울 같은 계절이 생기는 걸까요?

넹 그건 지구가 기울었기 때문이지요?

 

무슨 말일까나... ?

 

그림출처 : 네이버 캐스트

 

 

넹 그러니까.. 태양으로부터 '직각' 으로 빛을 받는 면이 가장 덥다는 거지요.

다시 말해서 빛과 '직각' 으로 되어 있는 면이 가장 강한 빛을 받는다는 겁니다.

 

 

그 다음부터는 간단합니다.

Omni 라이트 (Point라이트) 는 빛을 어떻게 쏘아내지요?

 

이렇게 쏘아냅니다. 사방으로요.

 

그러니 위 그림에서 두 면이 받는 빛을 그려봅시다.

 

자! 어느 쪽 빛이 더 직각에 가까운가요?

아니 , 다시 말하지요.

어느 쪽 빛이 직각에 가까운 면적이 더 넓은가요?

넹! 멀리 있는 겁니다! 오브젝트가 멀리 있으면 멀리 있을수록 밝아지는 이유는 이거지요. 직각에 가까워 지기 때문!!!!

이게 진짜 빛의 기본 속성입니다. 3D 그래픽에서도 이 속성을 그대로 구현하고 있구요.

 

 

그런데 실제는 안그렇죠? 손전등을 비춰 봤는데 , 가까이 있는 주전자보다 창 밖에 있는 옆집 창문을 비추는게 더 밝다 ... 뭐 이런 사태는 안 일어나지 않습니까? 역시 3D 그래픽의 빛은 실제와는 다른 걸까요?

 

 

 

그럼 다시 다른 질문 해 보지요. 빛은 왜 잘 가다가 없어집니까? 감쇄 (attenuation) 현상이 왜 일어나는 겁니까?
여러분도 다 알고 있는 거예요. 먼지나 수분과 같은 입자가 공기중에 있고, 그 입자들이 빛을 사방으로 튕겨내기 때문 아닙니까? 다 알고 있으시잖아요. [각주:4]

그럼 여기서 하나 질문 들어가죠.

 

누가 3D Max 안에 먼지 넣어 놨어요?

 

네가 넣었구나.

 

넹, 3D max나 게임 엔진 안에 먼지는 없습니다. 완전한 클린 룸이예요.
그러니 빛이 가다가 감쇄 현상을 일으킬 리가 없지요. 물리학적으로 빛을 완전히 시뮬레이션 하는 것도 아니구요.
그래서 '일부러' 감쇄 현상을 만들어 주지 않는 한은 빛은 사실 '멀리 있을 수록 밝습니다'

 

 

넹 오늘은 진짜 여기까지. 하지만 이게 끝이 아닙니다.
다음 시간에 '그래픽 아티스트들이 잘 모르는 조명의 비밀' 이 계속 연재됩니다.
오늘 배운 얘기를 엎어버릴 얘기도 나오고 막 그러거든요.

내용은... ... 뭐 TA 급만 되시면 다 아실테지만... 저는 일단 다음 시간으로 이어서 연재하도록 하겠습니다 :)

다음 연재일인 3일을 기다려 주세요

 

  1. Area 라이트 같은거 말씀하시면 곤란... 포토매트릭 라이팅 말고 진짜 기본 라이팅 말하는 거니까요. [본문으로]
  2. 알아듣는 분도 물론 계십니다만, 일반적으로는 낮설어 하는 용어입니다 [본문으로]
  3. 전부 얘기하면 그것만 도데체 양이 얼마나 될지 알 수 없으므로... [본문으로]
  4. 물리학적으로는 다른 이유로도 감쇄가 일어난다고 합니다만 3D는 단순하니까.. [본문으로]
반응형
,
Posted by 알 수 없는 사용자

안녕하세요. 이번에 새로 3일자와 18일에 연재글을 맞게된 디퍼드H입니다.

간단한 제 소개를 해드린다면 현재 디지펜-계명대 복수학위 이수하면서 현재 미국에서 공부하고 졸업을 앞둔 학생입니다. 학교 커리큘럼에 맞추어 GAM프로젝트 (GAM100, GAM150(텍스트 게임), GAM200, GAM250(1년 2D 게임 프로젝트), GAM300, GAM350(1년 3D 프로젝트)) 겪었던 여러경험들을 여러분들과 공유해보고자 합니다.

이번에 제가 연재할 첫번째 내용은 '자기만의 게임엔진을 만들어보기' 입니다. 사실 여러분들 중 게임 프로그래머를 지망하는 학생분들도 많을거라고 생각됩니다. 전문적인 내용 예로 들어 저같은경우 그래픽(렌더링) 프로그래밍 지망생으로 디퍼드 셰이딩이라던가 여러 전문적인 렌더링 테크닉을 다루기 보다는 우선 게임을 쉽게 만들수 있도록 도와줄 연재글을 작성해보려고 합니다. (이부분에 대해서는 포프님이나 다른분들께서 다루어주실거라고 믿어 의심치 않습니다.)

'자기만의 게임엔진을 만들기부분에서 연재글이 몇화나 될지는 잘 모르겠지만 빨리빨리 넘어가기 보다는 프로그래밍을 막 시작하는 분들을 위한 초점으로 천천히 진행해보고자 합니다. 허나 혹시 여러분들중에서 C/C++에 대해서 전혀 모르신다면 미리 공부하고 저의 글을 보시는게 도움이 되실거라고 생각합니다. 저는 여러분들이 이미 C/C++언어에 대해서 잘아신다는 전제하에 진행하도록 하겠습니다.

자, 그럼 이제 오늘의 강의를 시작하도록하죠. 보통 게임엔진하면 대표적으로 언리얼 엔진, 크라이 엔진, 하복 엔진, 떠오르는 유니티 엔진등이 있습니다. '우와, 이런 게임엔진을 어떻게 만들어?' 물론 우리가 만들어나갈 게임 엔진은 이런 상용엔진은 아닙니다. 허나, 우리만의 게임엔진을 만들어 나가면서, 여러분들의 게임 프로그래밍에 대한 지식을 한층 더 높여줄거라고 확신합니다. 사실 프로그래밍 실력을 확실하고 빠르게 늘릴 수 있는 방법은 직접 프로그래밍을 많이 해보는 길입니다. 여러분들이 새로운 기술 또는 테크닉을 익혔을 경우 여러분들이 만든 엔진에서 구현을 해본다면 크게 도움이 될 것입니다. 

자자, 여러분들만의 게임 엔진 구현의 장점 나열은 여기까지하고 이제 여러분이 만드실 게임엔진에 대해 전체적 구조를 알아보도록 하죠. 아래의 그림을 보도록 합시다. 우리가 만들어갈 게임 엔진을 기본구조를 잘 표현하고 있는 그림입니다.

우선 그림 보면 CoreEngine 클래스가 보입니다. 이 클래스 우리가 만들 게임엔진에 핵심이 될 부분입니다. 이 클래스는 시스템과 관련된 메소드와 게임 루프 함수를 들고 있습니다. 게임 루프 함수는 게임 엔진에서 메인 심장이라고 보면되는데, 게임 루프는 CoreEngine 클래스에 속해있는 시스템들은 주기적으로 업데이트 시켜주는 메소드라고 생각하시면 됩니다. 자세한 내용은 후일 CoreEngine을 다룰때 알아보도록 합시다. CoreEngine 클래스 안에는 여러 시스템을 가지고 있는 컨테이너를 가지고 있는데, 그럼 시스템에는 무엇이 있겠는가 살펴보죠.

그림에서 보면 가장 처음으로 윈도우 시스템이 보입니다. 우선 윈도우 시스템에 대해서 간단히 설명하자면 윈도우 운영체제와 관련된 윈도우 창 만들기, 윈도우 메세지 제어등을 관리할 시스템이라고 생각하면 됩니다.

그 다음 시스템은 그래픽 시스템을 보도록 하죠. 그림을 보면, 어~! 그래픽 시스템에서는 뭔가 화살표 방향으로 Transform, Model, 또 Light로 연결이 되어있네요? 막 복잡하고 그렇게 보이지만, 간단히 말하자면 그냥 컴포넌트 클래스 입니다. 여기서 컴포넌트 클래스가 무엇인가 궁금해 하는 분들이 있을거라고 생각합니다. 잠깐 여기서 간단히 살펴보고 가봅시다. 아래의 그림을 봅시다.

그림에서 보면 GameObject 클래스가 있습니다. 이 게임 오브젝트 클래스는 우리가 흔히 게임상에서 말하는 적, 플레이어, 나무, 장애물, 추상적으로는 빛까지 이르는 오브젝트라고 말할 수 있습니다. 게임 오브젝트는 컴포넌트 컨테이너를 가지고 있는데, IComponent 클래스에서 상속받는 컴포넌트들로 구성이 되어있습니다. 사실 이 컴포넌트를 가장 쉽게 설명한다면 오브젝트들이 가지고 있는 유니크한 특징들을 객체 또는 클래스를 표현 했다고 생각한다는 것이 쉬울것 같습니다. 예로 들어 빛을 게임 오브젝트로 표현하고 싶다라고 한다면, 게임 오브젝트 클래스에 Light라는 컴포넌트를 가지고 있으면 빛 오브젝트를 정의할 수 있습니다. 한가지 더 해보도록 하죠. 만약 캐릭터 오브젝트를 표현하고 싶다라고 한다면, Transform과 Model 컴포넌트를 가지고 있다면 캐릭터 오브젝트를 만들 수 있죠. 이렇듯, 컴포넌트 구성요소에 의해 우리는 게임 오브젝트 종류를 설정할 수 있습니다. 여러 게임 오브젝트를 추가시킬때 우린 굳이 클래스를 따로 정의 할 필요가 없게 되는거죠. 이외 컴포넌트를 이용한 게임 오브젝트를 만들면서 또다른 여러가지 이점이 있지만 예로 들어 Data-Driven 구조로 Multi-threading 구현이 쉬워진다라는 등, 이외의 이점은 다음에 우리가 오브젝트와 컴포넌트를 실제로 구현할때 더 자세히 다루어 보도록 하겠습니다.

자, 다시 그래픽 시스템으로 돌아와서, 그래픽에서는 Light, Transform, 그리고 Model 컴포넌트가 연결되어 있습니다. 이게 무슨뜻일까요? 컴포넌트 기반의 게임엔진에서는 각 시스템에서 특정 컴포넌트들의 업데이트 담당하게 됩니다. 예로 들어 Transform 컴포넌트는 게임 오브젝트의 위치를 나타냅니다. 이 위치는 주로 그래픽 시스템에서 랜더링할때 담당하기때문에 그래픽에서 담당하게 됩니다. 빛 컴포넌트도 마찬가지입니다. 이 빛 컴포넌트는 빛 오브젝트에 속해질 컴포넌트로 오직 그래픽에서 랜더링할때 사용될거기 때문에 그래픽 시스템에 속해 질것입니다. 모델 컴포넌트로 마찬가지입니다. 정리해보면, 시스템들은 보통 1가지 이상의 컴포넌트 업데이트를 담당하게 됩니다. 또 다른 예를 들게 되면 여러분들이 쉽게 이해 되실겁니다. 자, RigidBody 컴포넌트가 있다고 가정합시다. 여러분들 중에 물리 엔진을 한번이라도 만져 보셨거나 직접 만들어 보신 분이라면 이 컴포넌트는 물리에 관련이 있다는 것을 바로 직감하셨을 겁니다. 그렇습니다 그럼 이 컴포넌트는 물리 시스템에서 관리를 하게 될것입니다. 이제 감이 오시나요? 혹시 난 아직 이해가 안된다 하시는 분들이 있다면 걱정마세요. 지금은 '아 이런게 있구나.' 라는 정도만 알아도 충분합니다. 또 자세히 돌아볼 기회가 있으니깐요.

지금까지 좀 많은걸 배운것 같은데 잠시 정리하고 지나가 봅시다.

- CoreEngine이란?

- 게임 루프 ; 게임의 심장

- 시스템들을 초기화와 관리한다.

- 컴포넌트란?

- 게임 오브젝트의 특징을 클래스 또는 객체화한 것이다.

- 컴포넌트가 모여 하나의 게임 오브젝트를 구성하게 된다.

- 각 시스템들은 하나 이상의 컴포넌트를 관리하게 된다.

- Data-Driven ; Multi-threading 구현

마지막으로 살펴볼 내용은 메세지 시스템입니다. 메세지 시스템이 왜 필요할까요? 윈도우 API에 있는 메세지 시스템으로 도 충분하지 않을까? 물론, 충분하지만, 커스텀 메세지 시스템 도입은 여러가지 이점들을 주는데 우선 대표적으로 클래스 사이 포인터 참조 사용을 줄이므로써 클래스 간의 엉킴이 적어집니다. 사실 포인터 참조는 너무 많은 곳에서 쓰이게 된다면 클래스가 서로서로 거미줄처럼 엉키게 됩니다. 이럴경우 나중에 새로운 함수 또는 리팩토링할때 너무 고쳐야할 부분이 많아지게 됩니다. 우선 아래의 그림을 보면 무슨 내용인가 쉽게 이해 될것이라고 생각합니다.

첫번째 그림은 만약 우리가 메세지 시스템을 사용하지 않는다는 것을 가정했을때의 모습을 그림으로 그려보았습니다. 메세지 시스템을 사용하지 않는다면, 서로 다른 메소드를 가져올때 하는 수 없이 포인터 참조를 할 수 밖에 없어집니다. 그러면 첫번째 그림과 같이 시스템들은 서로 서로 거미줄처럼 엮어질 것입니다. 물론, 디자인 패턴중 퍼사드 패턴을 이용하게 된다면, 클래스 간의 의존성을 상당히 줄일 수 있긴 합니다. 두번째 그림에서는 우리가 만약 메세지 시스템을 사용했을때의 모습입니다. 만약 입력시스템(InputSystem)에서 어떤 키가 입력 되었다고 가정합시다. 입력시스템에서는 키가 입력되었다는 사실을 우선 메세지 시스템에 보내게 됩니다. 메세지 시스템에서는 그 메세지를 큐에 저장을 해놨다가 한 프레임 지나고 메세지 큐에 있는 메세지를 딜레이 시간을 비교하여 메세지를 알맞은 발신자에게로 보내게 됩니다. 첫번째 그림과 두번째 그림을 비교해보면, 여러분들은 메세지 시스템을 사용한 부분이 훨씬 상호 의존성이 적다고 알 수 있을것입니다. 이와 같은 이점이 있는 메세지 시스템을 우리가 만들 게임엔진에 구현할 것입니다.

지금까지 우리가 만들 게임엔진에 대해 핵심적인 내용을 다루어보았는데요. 다음 시간에는 본격적으로 게임 엔진을 만들기 시작해 볼 겁니다. 물론 여기에서 다루었던 내용은 게임엔진에 중요한 부분만 다루었고 이제 본 강의에서는 하나하나 세세히 다루면서 나갈 생각입니다. Precompile Header를 어떻게 만들고 실행시키는가 하는 부분도 진도를 나가면서 다루어 나갈 생각입니다. 오늘 했던 내용이 혹시 이해 안되는 부분이 있더라도 걱정하세요. '아, 이런걸 할거구나.' 정도만 아시면 충분합니다. 다음시간부터는 절대 이런 생각을 가지면 안되고 철저히 해야하구요.

혹시 오늘 강의에서 궁금했던 점은 댓글에 남겨주시면 답해 드릴게요. 이 긴 강의를 읽어 주셨던 분들 수고하셨습니다. 비록 허접한 강의가 여러분들에게 도움이 되었기를 바랍니다.

반응형
,
Posted by 알 수 없는 사용자

이글은 "게임 오브젝트 설계.. 나도 잘하고 싶다! #3" 에서 이어지는 글입니다.


안녕하세요! 두달만에 돌아온 끼로입니다!! (자랑이냐 퍽퍼퍼퍼퍽)

오늘 올릴 내용은 컴포넌트! 외부파일과의 결합! 입니다...


일단 저번시간까지 했던 것들을 이용하여 게임 오브젝트를 구성하려고 해봅시다.

pEntity->InsertComponent<ModelComponent>( "모델파일" );

pEntity->InsertComponent<AnimationComponent>( ... );

pEntity->InsertComponent<CombatComponent>( ... );

필요한 컴포넌트들을 게임 오브젝트에 추가하면 이제 원하는 게임 오브젝트가 구성이 될 것입니다!


하지만!!! 그럼 필요한 모든 조합을 코드에 추가해야 하는걸까요..?

플레이어! 몬스터! 프랍!

이렇게 간단하게 나누어진다면 상관없겠지만..

같은 플레이어여도 같은 몬스터여도 하는 일이나 속성에 따라

조합되는 컴포넌트가 달라져야 합니다!!

그렇지 않다면 컴포넌트 기반 설계의 힘을 별로 발휘하지 못할 것 같습니다.


작업하시는 분이 퐆풍 하드코딩을 통해 모든 조합을 다 만들어내는 

그런 Create 함수들을 만든다면 모르겠지만요..




<?xml version="1.0" encoding="utf-8" ?>
<entity version="1.0">
    <components>
        <script_component version="1.0" script="Player_Man.lua" />
        <animation_event_component version="1.0">
            ...
        </animation_event_component>
        <model_component version="1.0 " render_group="1" model="pc_M_big.nif" scale="1.000000">
            <animation_set ID="1" name="pc_M_big_AAA.kfm">
            ...
            </animation_set>
             <animation_set ID="2" name="pc_M_big_BBB.kfm">
            ...
            </animation_set>
             <animation_set ID="4" name="pc_M_big_CCC.kfm">
            ...
            </animation_set>
             <animation_set ID="5" name="pc_M_big_DDD.kfm">
            ...
            </animation_set>
        </model_component>
    </components>
</entity>

이렇게 xml을 통해 하나의 게임 오브젝트를 구성하는데 필요한 컴포넌트들의 목록을 읽어와서

필요한 컴포넌트들이 조합된 게임 오브젝트를 생성하는것이 오늘 쓸 포스팅의 목적입니다!!


이것을 구현하기 위해서 제가 선택한 방법은 다음과 같습니다.


간단하게 컴포넌트들이 생성 관련된 함수들을 매니저에 등록해두고 이 등록된 함수들을 사용하여 게임 오브젝트를 생성하는 것입니다.


그렇다면 등록은 어떤걸?? 어떻게???!


class ModelComponent : public ComponentBase
{
DeclareRTTI( ModelComponent );

public:
static ComponentBase* CreateComponent( const TiXmlElement* pNode, const Encoding& encoding );
static bool WriteComponent( const ComponentBase* pComponent, TiXmlElement* pNode, const Encoding& encoding );

public:
ModelComponent();
virtual ~ModelComponent();

...

bool LoadFromXml( const TiXmlElement* pNode, const Encoding& encoding );
bool WriteToXml( TiXmlElement* pNode, const Encoding& encoding ) const;

...
};


매니저에 등록해야 하니 static으로 생성함수와 저장함수를 만들고.. 생성과 저장에서 사용할 LoadFromXml이랑 WriteToXml 같은 함수를 컴포넌트에 넣고...


ComponentBase* ModelComponent::CreateComponent( const TiXmlElement* pNode, const Encoding& encoding )
{
ModelComponent* pModelComponent = new ModelComponent();
if( !pModelComponent->LoadFromXml( pNode, encoding ) )
{
return NULL;
}

return pModelComponent;
}

bool ModelComponent::WriteComponent( const ComponentBase* pComponent, TiXmlElement* pNode, const Encoding& encoding )
{
const ModelComponent* pModelComponent = RTTI::DynamicCast<const ModelComponent>( pComponent );
if( pModelComponent == NULL )
{
return false;
}

return pModelComponent->WriteToXml( pNode, encoding );
}


구현부도 이렇게 만들고..


등록은?! 매니저에서?! 어디에서 하지?! 어플리케이션 이니셜라이즈에서?! 컴포넌트 만들때마다 등록하는 부분을 찾아서 거기도 코드를 추가?! 때릴까?!




이,일단 등록부터 어떻게 해결을 해봅시다..


class ModelComponent : public ComponentBase
{
...

private:
class ScriptRegister
{
public:
ScriptRegister();
};
static ScriptRegister s_scriptRegister;
...


이렇게 내부 클래스를 하나 만들고 그 객체를 static으로 선언합니다.


ModelComponent::ScriptRegister::ScriptRegister()
{
매니저에 CreateComponent, WriteComponent 함수들 등록
}

ModelComponent::ScriptRegister ModelComponent::s_scriptRegister;


이렇게 하면 프로세스가 실행될 때 static 객체가 생성되고 여기에서 매니저에 등록을 해주면 될 것 같습니다..

후.. 이렇게 하면 등록도 할 수 있고 그럼 생성도 할 수 있고 그럼 이제 끝!!!


...



컴포넌트를 만들때마다 이짓을 하라고?!


그래서 저는 매..매크로를.. 이용합니다.......


#define DeclareComponentScript \
private: \
class ScriptHelper \
{ \
public: \
ScriptHelper(); \
static ComponentBase* CreateComponent( const TiXmlElement* pXmlNode, const Encoding& encoding ); \
static bool WriteComponent( const ComponentBase* pComponent, TiXmlElement* pXmlNode, const Encoding& encoding ); \
}; \
static ScriptHelper s_ScriptHelper


#define ImplementComponentScript( ClassName, ScriptName ) \
ClassName::ScriptHelper::ScriptHelper() \
{ \
매니저.RegisterComponent( ScriptName, RTTI::GetTypeID<ClassName>(), CreateComponent, WriteComponent ); \
} \
ComponentBase* ClassName::ScriptHelper::CreateComponent( const TiXmlElement* pXmlNode, const Encoding& encoding ) \
{ \
ClassName* pComponent = new ClassName(); \
if( !pComponent->LoadFromXml( pXmlNode, encoding ) )  return NULL; \
return pComponent; \
} \
bool ClassName::ScriptHelper::WriteComponent( const ComponentBase* pComponent, TiXmlElement* pXmlNode, const Encoding& encoding ) \
{ \
const ClassName* pCastComponent = RTTI::DynamicCast<const ClassName>( pComponent ); \
if( pCastComponent == NULL ) return false; \
return pCastComponent->WriteToXml( pXmlNode, encoding ); \
} \
ClassName::ScriptHelper ClassName::s_ScriptHelper


그리고 이 매크로를 사용하여 외부 파일에서의 조작이 필요한 컴포넌트에만 매크로를 넣어줍니다.


class ModelComponent : public ComponentBase

{

DeclareRTTI( ModelComponent );

DeclareComponentScript;

...


ImplementRTTI( ModelComponent );

ImplementComponentScript( ModelComponent, "model_component" );


ModelComponent::ModelComponent()

...


저는 이런식으로 매크로를 사용하여 xml에서 게임 오브젝트의 컴포넌트를 조합하고 그 xml파일로 게임 오브젝트를 생성하고 있습니다.




아.. 마무리를 어떻게 해야 하는거였더라.. 글을 오랜만에 쓰니 글쓰는 법도 까먹었...


그럼 진짜 끝!!


다음엔 컴포넌트와 스크립트의 연동이나 메시지통신이나 등등 그때 상황과 기분에 따라서..

반응형
,

Graphics vs Aesthetics

기타 2012. 5. 12. 05:00
Posted by 알 수 없는 사용자

개인적으로 그래픽 프로그래밍에 관심이 많아서, 이것 저것 하면서 배우고 있는데, 그러다가 보니 "게임의 그래픽 퀄리티"에 관련해서 많은 고민도 있고 관심이 무척 많습니다. 그래서, 이것 저것 다른 자료들을 뒤져보면서 공부를 하던 중에, Aesthetics 라는 용어를 만나서 그 이야기를 한번 해볼까 합니다.

우리는 "마인크래프트"와 같은 이런 게임이 성공하면, 이렇게 많이들 이야기 합니다.

"역시 게임은 그래픽 따위 중요하지 않아! 재밌으면 그만이야!!!" (특히, 프로그래머들이 주로~)

이 말이 참 공감이 갈 때도 있지만, 한편으로 그래픽 프로그래밍을 공부하고 있는 입자에서 참 듣기 싫은 말 중에 하나입니다. 

그런데, 가만 생각해보면 그래픽 프로그래밍에서 사용하는 그래픽이란 단어와 게임의 그래픽이라는 단어는 조금 다릅니다. 뭐, 다들 어떤 느낌인지는 아시겠죠?! ㅎㅎ

엄밀히 말하면, 전자의 경우를 "Graphics" 라고 말하고, 후자의 경우를 "Aesthetics"라고 말한다고 하는군요. Aesthetics는 사전적 의미로 "미학"(http://terms.naver.com/entry.nhn?docId=390090) 이라고 하더군요. 제가 더 이상 설명할 수 있는 수준인 아니구요. 다음의 발표자료를 보도록 하지요.

이 발표자료는 우리가 자주 헷갈리는 "Graphics"와 "Aesthetics"에 대한 중요한 차이에 대해서 이야기 하고 있습니다.
(http://penny-arcade.com/patv/episode/graphics-vs.-aesthetics)

영어발표자료이긴한데, 수능 이후 영어공부를 한번도 하지 않은 제가 뜨문 뜨문 그림과 단어 조합으로 대강 뭔 소리 하는지를 어설프게 알아들일 수 있을 정도이니 다들 아시겠네요. ㅎㅎ


사실 이런 것들을 구분해봐야 뭐하겠습니까만은 중요한 것은 게임과 아트, 사운드, 이야기, 기술, 이 모든 것이 하나가 되어야 한다는 것이겠지요! 

저는 그 중에 기술적인 부분에 관심이 많은 것일 뿐이고, 그것도 따라가기 바쁜데, 다른 부분은 제 능력 밖입니다. 그래서, 앞으로 기획자, 사운드 디자이너, 아티스티 님들과 친하게 지내려고요.. ㅎㅎ


단순하게 좋은 엔진과 고급 기술들을 사용해야만 그래픽이 좋은 것이 아니라는 것을 다시 한번 생각하게 해주네요. 하지만, 좋은 엔진과 고급 기술을 잘 사용하면, 표현의 범위가 더 많아지는 것은 사실이니까... 
(그러니, 프로그래머 너무 괴롭히지 마시고, 엔진 탓은 말아주세요.. 엉엉~) 

결국, "내가 만들고자 하는 게임에서 무엇을 보여주고, 어떤 재미와 경험을 제공하겠다!" 라는 확실한 방향성이 있어야 한다! 라는 것이 될 듯 하지 않을까 합니다. 너무도 당연한가요? 하지만, 개발하다가 보면 잘 안되는 경우가 참 많더라구요~ 특히, 요즘 같이 팀의 규모가 커진 상태에서는... 

이야기 하고 보니, NDC에서 들었던 "마비노기 영웅전 포스트모템"과 "화이티데이 포스트모템"이 생각이 나네요. 시간 되시면 보시면 좋을 듯 합니다. ㅎㅎ


자... 그럼 내가 만들고 있는 게임은 어떤지 한번 진단해보고, 팀원들과 잠깐 이야기 해보는건 어떤가요?

Aesthetics가 좋은 게임을 만들고 계신가요? Graphics에만 너무 치중하는건 아닌가요??



반응형
,
Posted by 알 수 없는 사용자

안녕하세요~
오랜만에 다시 찾아뵙는 엘키입니다. 개인적으로 몸이 너무 좋지 않아 몸조리를 하고 다시 복귀했습니다. 이어지는 포스팅은 늦지 않도록 노력하겠습니다. (__) 꾸벅~

지난 포스팅에서도 이야기 했다시피 일반적으로 버그에 대한 보고는 자신이 겪은 증상에 대한 보고 입니다.

아주 행복한 시나리오는 보고 받은 대로 시도하면 100% 재현 되는 버그입니다.
이런 종류의 버그는 너무나 해결하기 쉬워 “훗~ 어떻게 고쳐줄까?” 라고 고민하는 것만 집중하면 되지요.

하지만 대다수의 잡기 힘들었던 버그는

1. “그냥 뜬금 없이 프로그램이 종료”
2. “같은 방법으로 재현하려 해도 매번 다른 동작을 보여주는 버그”
3. “보고 받은 대로 해도 재현 되지 않는 버그”

위와 같은 버그들입니다.

이번 포스팅에서는 이런 버그들을 잡는 법에 대해 알아보겠습니다.


기본적으로 제가 생각하는 디버깅은 추리입니다.


이를테면 이 녀석이랑 우리는 비슷한 일은 한다는 거죠~



버그를 잡는 과정은 다음과 같습니다.

1. A라는 오류가 생겼다.
2. 그로 인한 원인은 B이다. (로그 혹은 재현)
3. 해결책은 C다.


여기서 가장 중요한 것은 B를 증명하는 과정입니다.

B를 증명하지 못한다면 그저 우연에 맡기는 프로그래밍(실용주의 프로그래머에서 인용)으로 서비스를 하는 것일테죠.


명확히 로그로 A라는 오류가 생겼다는 로그가 발견됐다면? 별거 아닐겁니다. 아마 그럴겁니다.


물론 실수는 누구나 할 수 있기에 단순 실수였다니 다행이군~ 하고 넘어갑시다.

하지만 대부분의 버그는 부주의에서 나오고, 그런 부주의의 원인이 무엇인지를 알려주는 로그 따위는 기대하지 맙시다.

대부분의 디버깅 상황은 명확한 증상과 원인을 알려주는 로그 따위는 없는 경우가 많습니다. 주로 한정된 로그와 로직의 흐름을 통해서 이루어지는 것이 일반적입니다.

그래서 제한된 정보를 바탕으로 재앙의 원인을 찾아내는 과정을 거치게 되는데 이 과정을 추리라고 보시면 됩니다.

명탐정 코난 보셨죠? 아주 작은 실마리 하나를 놓치지 않고 범인을 찾아내는 명석한 두뇌!!
아, 물론 우리는 그 정도로 똑똑할 필요는 없습니다.

하지만 종종 그에 못지않은 추리와 상상력이 필요할 때가 있습니다.

대부분의 경우 힌트는 조각나있습니다. 조각난 힌트들을 퍼즐 맞추듯 하나 하나 끼워 맞추는 과정이 우리에겐 필요합니다.



예를 들어 볼까요?

게임하다 갑자기 클라이언트가 종료됐다는 보고가 왔습니다. 그런데 전체 유저도 아니고 소수 유저라고 하는군요. (10명 내외)

아! 덤프 서버!! 덤프 분석 페이지를 가 봅시다. 어라? 덤프가 안남았다니...최악이군요.

여기서 보통 2가지 반응을 보입니다.
1. “어? 뭐야? 보고가 잘못된거 아냐??”
2. “헐....뭐지 덤프 안남고 죽는 상황들에는 뭐가 있더라...뭐 여하튼 힌트가 될만한건?”

물론, 우리는 2번 반응을 보여야 합니다. 유저의 보고가 잘못됐다는 것 마저도 우리는 증명해야 합니다. 우리는 엔지니어니까요. ^-^


기술자(技術者, technician)는 어떤 분야에 공학적인 일에 숙련된 사람을 말한다. 반면 공학자(工學者, engineer)는 공학의 일에 자연과학적인 지식과 기술적인 지식을 가지고 과학자와 기술자 사이에 매개체가 되는 사람을 가리킨다. 공학자는 기술, 수학, 과학 지식을 사용하여 실용적인 문제를 해결한다. 공학자로 일하는 사람들은 보통 공학 분야에서 학위를 가지고 있다. 공학자는 자연과학적 지식에 기초하고 있기 때문에 기술자와 구분된다. 15-16세기에는 엔지니어란 군사 분야의 기술을 맡거나, 건축가, 수력학자, 조각가, 화가로서 재능을 빌려주는 사람이었다. [1]


출처 : http://ko.wikipedia.org/wiki/%EA%B8%B0%EC%88%A0%EC%9E%90



자~ 그럼 뭐라도 힌트를 얻어볼까요? 에러 로그 먼저 살펴보죠.


에러 로그를 봐도 별다른게 없습니다. 쳇...쉽지 않네요.

그렇다면...추가적인 보고되는 상황도 뭔가 조금씩 다릅니다. 대부분 유저의 보고는 감정적으로 이루어지는 경우가 많아, 고의적이 아니더라도 현상을 다르게 보고하는 경향이 있습니다. (상대적으로 이성적으로 판단해주시는 QA 분들 마저 현상을 착각하는 경우도 종종 있습니다.)

이런 경우 공통점을 찾기 시작해야 합니다. 대부분 로그에서 결정적인 힌트는 없더라도 비슷한 점이 발견되는 경우가 종종 있습니다.

문제가 발생한 모든 유저의 로그에서, 유저가 크래시 발생전에 했던 행동중 우편함을 열어보았다는 로그를 발견했습니다. (저의 경우에는 Disconnect 시점에서 최근 10개 패킷 번호를 로그로 남기는 구현을 했던 적이 있는데, 이 로그가 디버깅에 아주 큰 도움이 되곤했습니다.)

우편함을 뒤져볼까요?


아...뭔가 조금 이상하군요. C++ 구조체에 정의된 최대 크기는 64바이트, DB에서 기록 가능한 최대 메시지가 128바이트였군요.

DB에서 실제 사용된 메시지 크기를 기준으로 검색해봅시다.

아...53명이군요. 53명중 10명만 오류에 대한 보고를 한 것이고요.

아마 버퍼 오버플로우가 났었겠지요?

어라? 어쨰서 유저들의 보고에서는 우편함을 클릭했었다 라는 보고가 없었던 걸까요?

그리고 어째서 서버는 크래시 되지 않았던걸까요?



서버 코드는 다음과 같습니다.

typedef DWORD MAIL_IDX;

typedef DWORD USER_IDX;

class Mail : public ISyncroized
{
public:
   Mail (const MAIL_IDX Idx, const char* szMessage, const USER_IDX UserIdx)
   {
       m_Idx = Idx;
       m_szMessage = szMessage;
       m_UserIdx = UserIdx;
   }

   virtual bool Encode()
   {
       // 인코딩 작업 구현
   }

private:

   MAIL_IDX m_Idx;

   USER_IDX m_UserIdx;

   std::string m_strMessage;
};

Mail mail(Idx, szMessage, UserIdx);
pSession->Send(mail); // Send 함수 내부에서 ISyncroized 형 참조자를 통해 Encode된 정보를 유저에게 전달함.


자 그렇다면 클라이언트 코드를 볼까요?


const int MAX_MAIL_MESSAGE = 64;

typedef DWORD MAIL_IDX;
typedef DWORD USER_IDX;

class Mail
{
public:
   Mail(const MAIL_IDX Idx, std::string strMessage, const USER_IDX UserIdx)
   {
       m_Idx = Idx;
       strcpy(m_szMessage, strMessage.c_str());
       m_UserIdx = UserIdx;
   }

private:
   MAIL_IDX m_Idx;

   USER_IDX m_UserIdx;

   char m_szMessage[MAX_MAIL_MESSAGE];

private:
   IUIForm* m_pForm;

private:
   MailBox* m_pMailBox;
};

MAIL_IDX Idx = packet.Decode();

std::string strMessage = packet.Decode();

USER_IDX UserIdx = packet.Decode();

Mail mail(Idx, strMessage, UserIdx);

변수 배치상 우편함을 열자마자 오류가 생긴 것이 아니고, 그로 인한 2차 감염으로 문제가 생겼네요.

자! 찾았습니다.
신난다~~ 신난다~~~


그렇지만 좋아만 할때가 아니죠잉?


자. 어떠한 실수가 이런 문제를 만든 걸까요?
1. DB와 C++ 코드와 텍스트 길이 오차
-> 64 <-> 128
2. non-safe 한 c 표준 함수 사용. (strcpy)
-> 버퍼 길이를 지정하는 함수로 변경. (strncpy)

직접적인 원인은 위 두가지겠지만, 실제론 더 있다고 볼 수 있습니다.
1. 패킷에서 사용하기로한 자료형은 std::string에 Decode되는 자료 구조형과 달리 NULL-Terminated 문자열 사용.
-> 패킷을 데이터로 변환하는 함수에서 std::string을 지원한다면 굳이 NULL-terminated형을 사용할 필요가 없었음.

2. UI에서 제대로 표기가능한 문자열은 실제로 128byte였음.
-> 무언가 기획서의 반영이 안된것인가 찾아보았더니, 최초 UI폼은 64byte만큼 표시가 가능했으나, 이후 UI폼에선 128byte만큼 표기 가능하게 변경됨. C++ 코드에서의 UI변화 코드 수정 누락으로 인한 문제 발생.


이렇듯, 문제가 왜 발생했는가의 경위를 알아내는 것이 매우 중요합니다.
이런 실수들은 꽤나 큰 문제를 일으키곤하는데요, 방금전 버퍼 오버 플로우 관려련 코드가 로그인 시에 매번 수행되는 코드에서 사용되었다면 유저 대부분이 떨어져 나가버려 서비스는 엉망이 될 것이고, 덤프도 안남는 상황에서 (혹은 엉터리 같은 덤프가 남는 상황에서) 그 많은 유저 사이에서의 공통점을 찾기란 서울에서 김서방 찾기만큼 힘들었을겁니다.

“찾았다~~ 문제생긴 코드만 후딱 수정해서 패치하면 되지 뭐~.”

이렇게 대처해서는 안됩니다.


생각보다 많은 팀에서 버그나 크래시가 발생했을 때 이를 너무 크게다뤄 지나치게 혼을 낸다거나, “뭐 이런일도 있는거지” 하며 쉽게 넘어가죠.

실제로 필요한 대처는 버그가 발생한 코드 조각이 아니라, 진짜 단순 실수였는가, 개발자의 무지에서 온 것은 아닌가, 시스템으로 커버 불가능한 오류였는가, 개발 과정에서 캐치 할 순 없었는가 등을 다 고민해보아야 합니다.



이런 과정은 “좋은 서비스”를 하고 있는 개발 팀일 수록 체계화 되어있으며, 개개인의 실수가 팀 차원에서 커버되는 경우가 많습니다.

여하튼 버그를 찾았다면 그런 버그가 “다시는” 나오지 않게 하기 위한 여러가지 고민을 할 차례입니다.

이런 고민에 대한 이야기를 다음 글에서 이어서 하겠습니다.

반응형
,