꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 친절한티스
지루한 컴파일 시간 조금이라도 줄여보자
코딩을 하다 보면 프로젝트의 몸집이 커지게 되고, 그에 비례하여 컴파일 타임도 기하급수적으로 늘어나게 됩니다. 단지 코드 몇줄 수정하고 추가 했을 뿐인데 매번 컴파일 타임이 수분~수십분 걸리게 되면 작업의 리듬도 깨지게 되고 프로그래머의 능률은 저하되게 되죠. 그래서 빌드 타임을 최소화 하기 위한 여러 기법들이 있습니다. 인크레디 빌드 IncrediBuild 같은 서드 파티의 도움을 받을 수도 있고, 오늘 소개 해드릴 미리 컴파일 된 헤더를 사용하는 방법등이 있습니다. 

 
미리 컴파일 된 헤더(Precompiled Header)는 무엇인가요?
단어 그대로 헤더 파일들을 미리 컴파일 해두는 것입니다. 미리 컴파일 된 헤더를 만들면 pch 라는 파일이 생성 되는데 컴파일러는 이를 참조하여 프로젝트 컴파일을 수행하게 됩니다. 미리 컴파일 시킨 헤더를 참조하니 기존에 헤더 파일을 일일히 파싱하던 때에 비해서 컴파일 속도가 빠른 것 입니다.

미리 컴파일된 헤더 파일을 사용하는 메이크파일의 구조
이미지 출처 : MSDN

미리 컴파일된 헤더를 사용하는 방법은 간단합니다. 일단 미리 컴파일 된 헤더로 사용될 헤더 파일(.h)과 소스 파일(.cpp)을 프로젝트에 추가 합니다. ( 이하 Visual Studio 기준 )


그 다음 프로젝트 속성에 들어가 미리 컴파일 된 헤더를 사용하겠다는 설정 해줍니다.
 


추가한 cpp 파일의 속성 메뉴에 들어가 미리 컴파일 된 헤더 만들기를 설정 해줍니다. 이를 통해 Precompiled.h 파일에서 참조 하고 있는 헤더 파일들을 미리 컴파일 하게 됩니다.



이로서 미리 컴파일 된 헤더 사용 하기의 준비는 모두 끝났습니다.

어떤 헤더 파일을 미리 컴파일 시켜야 할까?
앞서 미리 컴파일된 헤더를 쓰면 빌드 타임이 줄어든다고 했습니다. 그렇다면 많은 헤더들을 추가 하면 좋을까요? 결론은 아닙니다. 미리 컴파일 된 헤더에 포함 시킬 헤더를 선정하는 것은 신중 해야 합니다. 예로 미리 컴파일 된 헤더에 포함된 파일이 수정이 일어나면 미리 컴파일 된 헤더도 새로 만들게 됩니다. 이렇게 되면 미리 컴파일 된 헤더를 사용하는 의미가 없어집니다. MSDN을 보면 다음 사항에 미리 컴파일된 헤더 파일 사용을 권장하고 있습니다.

  • 자주 바뀌지 않고 크기가 큰 코드 본문을 항상 사용합니다.
  • 프로그램이 여러 개의 모듈로 구성되어 있으며, 모든 모듈이 포함 파일의 표준 집합 및 동일한 컴파일 옵션을 사용합니다. 이 경우 모든 포함 파일을 미리 컴파일해서 하나의 미리 컴파일된 헤더로 만들 수 있습니다.

여기서 핵심은 자주 바뀌지 않고라 할수 있습니다. 대표적으로 STL 헤더 같은 것을 생각할 수 있습니다. 이들의 헤더 파일은 거의 바뀔일이 없죠. 그 외에 프로젝트에 사용하는 서드파티 라이브러리 헤더 파일이라든가 한번 만들어두고 수정할일 없는 헤더 파일이 있는 경우 추가해주면 좋습니다. 실제로 사용 예제 코드를 살펴 보겠습니다.

// Precompiled.h
#pragma once
#include <string>
#include <vector>
#include <list>

// Precompiled.cpp와 미리 컴파일된 헤더를 사용하는 프로젝트의 모든 cpp 파일에 추가
#include "Precompiled.h"

위와 같이 헤더 파일을 추가 해 되면 string, vector, list 헤더 파일은 미리 컴파일 되게 됩니다. 이로 인해 생기는 이점이 하나 더 있습니다. vector와 list를 사용할때 일일히 #inlcude <vector> 를 추가할 필요가 없어지는 겁니다. 간단히 이야기 해 #include <vector> 구문이 전역적으로 적용되는 것과 같은 효과가 발생하게 됩니다.

자그마한 관심으로 컴파일 시간이 줄어들수 있습니다

미리 컴파일된 헤더를 사용하는 것 외에도 작은 습관 하나로 컴파일 타임을 줄이는 법도 있습니다. 다음과 같이 헤더 파일이 있다고 가정해보겠습니다.

// Foo.h
#include "Bar.h"
class Foo
{
private:
	Bar mBar;
};

Foo라는 클래스에서 Bar 클래스를 멤버 변수로 가지고 있습니다. 이를 위해 Bar 클래스가 선언 되어있는 헤더 파일을 포함하고 있습니다. 그런데 어느 순간 Bar 클래스가 수정되었다고 가정해보겠습니다. 그러면 컴파일 시에 Bar 헤더를 포함해 Foo 헤더까지 재 컴파일을 하게 됩니다. 어찌 보면 당연한 결과지만 작은 수정을 통해 이를  방지할 수 있는 방법이 있습니다.

// Foo.h
class Bar;
class Foo
{
private:
	Bar *mpBar;
};

// Foo.cpp
#include "Bar.h"

바뀐 부분을 보시면 Bar 멤버변수가 포인터로 변했고, include 를 안하는 대신 전방 선언을 넣었습니다. 그리고 Bar 클래스를 사용하는 본문 파일에 include를 추가하였습니다. 이렇게 변환 하였을 경우 Bar 클래스의 수정이 일어나도 Foo.h 파일은 재 컴파일이 일어나지 않게됩니다. 컴파일 타임이 줄어 드는 것이죠.

참조
미리 컴파일된 헤더를 사용하자
MSDN 미리 컴파일된 헤더 파일 만들기
빌드 시간에 관한 고찰
반응형
,
Posted by 밥을먹는선비

이번부터 한번은 일리히트 한번은 유니티를 번갈아 강좌를 해보도록 하겠습니다.

아무래도 일리히트만 했더니 너무 마이너하게 간다는 생각이 들어 고민을 많이했습니다. 유니티의 탈을쓴 일리히트엔진강좌(??먼소리지?)정도라고 오해를 해주시면 감사하겠습니다.

이것도 반응 시원찬으면 언리얼스크립트로 바꾸도록하겠습니다. ㅡ.ㅡ;;




본론으로....

오늘은 첫시간이니 디버깅 객체에 대해서 좀 알아보도록하겠습니다.

웹을 하셨던 분들은 아마도 디버깅을 위해서 alert() 이나 console.log() 등의 함수들을 많이 썻을겁니다. 

유니티에서는 Debug 라는 객체를 제공합니다.

Debug.Log() 라는 함수로 콘솔창에 메씨지나 변수 값들을 출력해 보실수있습니다.

일단 새로 프로잭트를 만들고 다음과 같이 레이 아웃을 세팅합니다.



새로운 씬을 만듭니다.



카메라 객체 하나만 있는 초기 상태입니다.



프로잭트 창에 마우스 우클릭후 나온 메뉴에서 자바스크립트를 추가합니다.



이름을 hellounity하시고 오픈 버틍을 눌러줍니다.



모노디벨로퍼가 실행되고 소스창이 나옵니다.




아래와 같이 소스를 수정하고 세이브를 합니다.



 다시 유니티 창으로 돌아와서 프로잭트창에 있는 방금 편집한 hellounity.js를 카메라 오브잭트에 끌어다 줍니다.



Main Camera 인스펙터창에 스크립트가 추가 된것 확인



실행시켜보면 맨아래에 hello unity가 찍혀있음.
(글씨 왼쪽의 느낌표를 눌러주면 콘솔창이 따로 팝업됨)

  

Debug.Line() 이용해서 즉석해서 디버깅용 직선을 그릴수있습니다.

픽킹광선을 만들어 디버깅라인을 출력 해보도록 하겠습니다.

raycast.js를 만들고 아래와 같이 작성합니다.

GameObject.Find 함수는 이름으로 다른 오브잭트를 찾는 함수입니다.

ScreenPointToRay 는 이차원 마우스 좌표를 삼차원 광선으로 만들어 주는 함수입니다.


GameObject 메뉴에서 Sphere 를 추가 해주고 이름을 ball로 합니다.


빈 오브잭트를 만들어줍니다.



방금 만든 오브잭트를 선택하고 BoxCollider를 추가시켜줍니다.




BoxCollider 스케일을 조정하고 다음과 같이 스크립트를 연결시켜줍니다.




실행결과



 


반응형
,
Posted by 김포프
컴파일 경고(warning) 얼마나 신경쓰세요? 현재 작업중인 코드베이스를 컴파일하면 경고가 몇개나 나오나요? 하나라도 있다면 반드시 다 고치세요. 구차한 변명따윈 필요없습니다. 하나도 안나오게 다 고치세요. 왜인지는 얼마전에 있던 제 경험담을 통해 아주 짧게 주저리주저리 설명드립니다.

며칠 전에 발견한 컴파일 경고 하나
현재 마무리를 도와주고 있는 콘솔게임을 PS3용으로 컴파일하다(즉 g++을 사용하면) 운좋게(왜 운좋은건지는 나중에 설명) 다음과 같은 컴파일 경고를 봤습니다.

1>..\..\src\monster.cpp(2814): warning : unused variable 'temp'


보통 때 같으면 무시하겠는데(왜 무시하는지도 나중에 설명.. 원래는 절대 무시하면 안됨) 그날 따라 무슨 신내림을 받았던지...

'응? 이놈이 뭐지? 궁금한걸?'

이란 생각이 더블클릭을 해서 소스코드를 봤지요. 

bool swapped = false;
if( dist1 > dist2 )
{
  swapped = true;
  float temp = dist1;
  dist1 = dist2;
  dist2 = dist1;
}


어라... 버그가 보이는군요. 무슨 버그일까요.... 잠시 여백을 두어 생각하실 기회를 드리겠습니다... 한 눈 팔지 말고 코드 분석하세요...

한 눈 팔지 말랬잖아요.. 이 이미지를 보시면 이미 한눈 파신 겁니다. (그래도 출처는 http://curiousanimals.net)



자, 문제점을 찾으셨나요? 찾으셨을거라 믿습니다... 아니시라면.... 당신은 프로그래머가 아니야~ 버럭!

그래도 또 설명하는 친절한 포프씨 -_-
위 코드가 하려는 일은 두 거리(dist1, dist2) 중에 dist2가 언제나 dist1보다 크도록 변수값을 교환해주는 거군요. 매우 간단하죠. 근데... 뭔가 이상하죠? temp 변수가 안쓰였다니... 프로그래머가 아니신 분들을 위해 친절하게 예를 들어 한 단계씩 보여드리죠..

1. 일단 대충 변수값을 제맘대로 대입
다음과 같이 변수값을 정해서 이게 왜 버그인제 예를 보여드리겠습니다.

dist1 = 20
dist2 = 10


2. float temp = dist1;
이 라인을 실행하면 변수값이 다음과 같이 됩니다. 별문제는 없죠 아직.

temp = 20
dist1 = 20
dist2 = 10


3. dist1 = dist2; 
이 놈 이후엔 변수값이 이리 됩니다. 역시 괜찮습니다.

temp = 20
dist1 = 10
dist2 = 10


4. dist2 = dist1;  
이게 마지막 라인이군요. 드디어 버그가 보이는군요. 이걸 실행하면 최종 변수값이 이따위가(?) 됩니다.

temp = 20
dist1 = 10
dist2 = 10

 
temp에 저장해눴던 값을 dist2에 대입하는 대신 dist1을 대입함으로 해서... 버그가 생긴거지요...  그럼, 이 버그로 인해서 생기는 문제는 뭐였을까요?

전.모.릅.니.다.

정말 몰라요....(출처: http://knowyourmeme.com)



대충 코드를 훑어봤는데... 파일 이름은 monster.cpp이고 함수이름은 updateTail() 이었습니다. 사실 정확히 어떤 버그가 생기는지는 제대로 살펴보지 않았습니다. 그냥 대충 생각해보니 몬스터들이 팔을 휘두르거나 그럴 때 자취를 보여주기 위해 띄를 하나 그려 주거든요? 가끔, 그 띄가 뒤로 역류해서 겹쳐보이는 경우가 있었습니다. 그게 바로 이 버그 때문일지도 모릅니다. 하지만... 귀찮아서 더 자세히 보진 않았습니다. 그냥

'이 버그 발견했으니 고쳐주세요.'

라고 이메일만 보냈습니다.

버그는 어느 프로그래머나 만든다.
뭐 이정도 버그야 어느 프로그래머도 만들 수 있는 버그입니다. 그 부분에 대해선 프로그래머를 탓하지 않겠습니다. 저같은 꽃미남 프로그래머도 만드는 버그입니다.

뭐, 위의 예는 약간의 비주얼적인 하자.... 별 큰 문제는 아니었죠... 근데... 이게 뭔가 더 중요한 코드였다면 엄청난 하자가 될수도 있죠. 특히나 이처럼 특정조건에서만 생기는 문제였다면 디버깅도 쉽지 않습니다.

하지만 버그를 안만들기 위해 노력은 해야할거 아닌가?
따라서 제가 탓하고 싶은 건 컴파일 경고가 떡하니

"너 뭔가 잘못하고 있을지도 몰라. temp변수가 안쓰였잖아"

라고 말해주는데 그걸 무시한 만행(?)입니다. 

컴파일러 경고를 고치는 것만으로도 상당히 많은 수의 버그를 미리 잡아낼 수 있습니다. 이 코드가 들어가있는 게임... 수백만장 팔립니다... 개발비도 수백만불 됩니다. 이런 프로젝트에서 가장 쉽게 코드품질을 보장할 수 있는 법을 무시하는 프로그래머의 자세 좋지 못하죠. -_-

보통 이따위로 깔끔하지 못하게 프로그래머하시는 분들... 전 타박해 드립니다... 하지만, 이번에는 그러지 못하는 이유가 있었습니다. 그것은....

너도 범인이다.
사실 문제는 이 코드를 작성한 프로그래머에게만 있는 건 아니었습니다. 이 회사의 모든 프로그래머 또는 제대로 된 정책을 만들지 않은 리드급 프로그래머에게도 문제가 있었습니다.

왜냐면..... 이 코드베이스를 컴파일하면 컴파일 경고가 1019개 나오기 때문입니다. -_- 1019개나 되는 경고속에 파묻힌 경고 하나를 찾으라고 하는게 어불성설이지요.....

자네...나보고 어쩌라는 겐가....?


보통 때 같으면 전 이런 경고 다 고쳐버리고,

"앞으로 경고 나오면 경고 하나당 저에게 3대씩 맞습니다."

라고 통보를 해드립니다. 하지만 그럴 수 없었던 이유가 이미 너무 늦었다는거죠.  -_-.... 게임 마무리 단계에서는 아무리 사소한 거라도 안건드립니다. (이건 전에 '게임 출시전 개발자가 갖춰야할 마음가짐'이란 글에서 말씀 드렸었습니다.) 딱히 발견된 버그가 있지 않는한 절대 안고칩니다. 코드를 바꿀 때마다 새로운 버그가 나올 위험이 있거든요. 그래서.... 안고칩니다.... 무조건 안고칩니다.

게임 출시하고 나서 고치죠. 근데 이 프로젝트 이후에 제가 이 팀을 도와줄 일이 없을거 같으니.... 

아마 평생 안고칠지도.... -_-

컴파일 경고를 방지하는 무대뽀 실용적인 방법
자, 그럼 저희처럼 너무 늦기전에 이런 일이 발생하는 것을 방지합시다. 당장 오늘부터라도 시작합시다.

컴파일러 경고를 오류로 처리한다.
가장 실용적인 방법은 컴파일러 경고가 나는 경우 이걸 아에 오류(error)로 처리해서 실행파일 조차 안만들어지게 하는겁니다. 아주 무대뽀 실용적이지요. 간단히 Visual Studio에서 컴파일 옵션만 켜주면 됩니다.
 

아주 실용적(?)인 방법


이러면 경고가 날 때마다 아예 실행파일이 안만들어지니 테스트를 못하지요! 그래서 프로그래머는 반드시 이 경고를 고쳐야만 합니다. 그래야 제대로 도는지 테스트라도 하니.... 근데 문제는.... 임시적으로 뭐 테스트하고 지우기 위해 대충 코드짜놨는데 경고가 나면...... 짜증나지요... 프로그래머들의 반란(?)을 부추기는 계기가 됩니다.... -_-

그래서 이 옵션은 release 빌드용으로만 켜주는게 좋습니다. debug 빌드에서는 프로그래머가 뭔짓을 해도 냅두는게 장수의 비결입니다. 문제는 프로그래머가 debug 빌드에서만 테스트하고 경고있는 코드를 그대로 check-in 했을 때인데요.

뭐, 이럴 때는 자동빌드머쉰(automatic build machine)이 release 빌드에 실패한뒤 그 프로그래머에

"당장 경고를 고치지 않으면 구워먹겠다!"

라는 협박 이메일을 보낼 거라고 믿습니다. 자동빌드머쉰이 없다면 만드세요. 이것도 없이 어떻게 게임 개발하지...? -_-;;;

정말 말도 안되는 경고는 꺼준다
가끔 정말 말도 안되는 경고도 있습니다. 별 의미가 없는 경고도 있고요. 이런 건 꺼주시면 됩니다. 단, 자기 맘대로 끄지 마시고 다른 프로그래머들과 상의한 뒤 동의를 얻어 꺼주세요. 자기 맘대로 끄는 것을 허용하면 경고 고치기 싫어서 그냥 꺼버리는 몰상식한 프로그래머들 꼭 나옵니다. 이런 분들 발견하시면 퇴사처리 해버리셔도 됩니다. -_-

끄는 법에는 2가지가 있습니다. 프로젝트 전체로 꺼주는 법도 있고, 각 파일별로 (또는 그보다 작은 단위로) 꺼주는 법도 있습니다. 원하는 대로 골라서 쓰시면 됩니다.

프로젝트 전체에서 경고 꺼주기
아래처럼 프로젝트 속성에서 꺼주면 됩니다. 아래 예는 프로젝트 전체에서 경고 #4507을 다 꺼주는 겁니다.


파일별로 꺼주기
그냥 아래처럼 #pragma warning()을 써주시면 그 뒤부터는 경고가 안나옵니다.

#pragma warning( disable : 4507 )


이걸 나중에 다시 켜줄려면

#pragma warning( default: 4507 )


을 해주면 됩니다.

대충 정리
뭐... 결국 할 말은.... '경고를 반드시 고쳐주세요.'였으나 역시나 너무나 주저리주저리 써서 대충 정리.
  • 컴파일 경고만 살펴봐도 정말 명백한 버그들을 고칠 수 있다.
  • 릴리즈 빌드에서는 컴파일 경고를 컴파일 오류로 처리해서 프로그래머들이 언제나 경고없는 소스코드를 유지할 수 있게 돕는다.
  • 필요없는 경고는 프로젝트 속성에서 전역적으로 꺼주거나 파일별로 #pragma warning()을 이용해서 꺼준다.

p.s. 앞으로도 대충 어이없는 경험을 바탕으로 '제발 이러지 맙시다.'라는 글을 종종 올리겠습니다. 은근 좋아하는 분들이 많으신듯..... -_-

반응형
,
Posted by 알 수 없는 사용자
0.  이번 연속으로 게임 탄압 관련 이야기를 쓰게 되네요.  빨리 본래 쓰려던 거나 써야 할텐데
최근에 몇몇 게임 관련 컨퍼런스, 게임문화재단 세미나 등등 갈 때마다 이 이야기가 나오더군요.
그리고 이번에 서강대에서 했던 미래토크에서는 이런 자리 잘 안나오시는 송재경대표님이 나오셨길래 흥미있게 지켜봤습니다.

이래 기사화 해놓은게 있는데.
http://www.thisisgame.com/board/view.php?id=1123473&category=102

사실 현장에서 직접 본 바로는 좀 다른 느낌이었습니다만, 이 기사가 그렇다고 없는 말 쓴건 아니니까. ^^

일단 송재경대표님급 정도 되시는 분이 사실 모호하게 이야기하고 넘어가도 문제없을 자리에서 저렇게 한쪽 입장을 명확히 말씀하시는거 보고 솔직히 조금 놀라기도 했고, 일면 감동스럽기도 했습니다. 한국 온라인 게임 미래가 밝다는 생각이 문득 드는 것은 넥슨의 jj, 엔씨의 tj 등 이런 분들이 명확하고 소신있게 자신만의 방향을 명확히 끌어 가시는 데다가, 이분들 그래봐야 서울대 8* 학번 수준으로 아직 젊으시거든요.  업계에서 제일 리더급에 계신 분들이 다들 물려받아서 회사 키우신 분이 아니라, 바닥에서 직접 비젼을 가지고 이렇게 회사를 만들어 오신 분들이라는 것때문에 참 다행이라는 생각이 들었습니다.

그러나 일면 또 든 걱정은, 송재경대표님의 발언이나 생각이 좀 위험할 수도 있겠다는 부분 때문이었습니다.  물론 송재경대표님이야 아직 아키에이지가 사회적으로 영향력을 내비치는 게임도 아니고, XL게임즈가 업계에 주도적인 메이져 회사는 아니니까.. (어찌나 본인이 사회성 없으시다는 말을 서로 농담처럼 돌려서 하시던지, 이거 공적인 자리에서, 송재경 대표님급의 사람이 자랑스레 이야기할 부분은 절대 아니라고 생각합니다. ) 이라고 생각하긴 하지만, 정말 소위 말하는 N사들의 마인드도 이렇다면 좀 걱정인데... 라는 생각이 들었습니다.  그래서 마지막으로 한번만 더 게임 탄압에 대한 입장에 관한 글을 쓸까 합니다.


1.  단합이나 단결?  게임사가 꼭 갖춰야할 덕목은 절대 아닙니다.

위에 기사 일부 인용하죠.

              “업계가 뭉치려고 하는 것은 70년대 제조업 마인드다. 당시에는 작업현장에 모여서 일했던 만큼 자신들의 이익을 위해 남을 배척하고 가격을 담합하는 등 이기적인 집단으로 발전했다. 하지만 게임은 다른 업체와 담합하는 것이 아니라 각자 창의적인 아이디어와 개발력으로 승부하는 만큼 모일 필요가 없다. 그만큼 함께 모이는 것이 어렵다. 좋게 말하면 포스트모던이라고 생각한다”고 말했다.

뭉쳐서 조직을 형성하고 담합해 수익을 나누는 구시대적인 발상은 기성 세대의 이데올로기일 뿐이라는 주장이다. 이어서 송재경 대표는 “그런 기성세대의 생각을 무너뜨려야만 새로운 게임을 만들 수 있고 발전할 수 있다”

십분 동의하는 바이긴 하지만, 아무래도 질문 포인트(하긴 질문 하신 분도 포인트가 좀 애매했고)에 대한 답변 방향이 좀 엇나간 답이긴 했습니다.

지금 게임 업계를 공격 하는 사람들이 하고 있는 것은 무차별적인 공격입니다.  정상적인 공격이라면, 특정 게임의 특정 부분이 문제다 라고 조목조목 비판했어야지, 이렇게 도매급으로 묶어서 게임이 다 나쁜 것 이라고 공격할 필요는 없었습니다.  상당히 정서적인 기반을 근거로 한 불편한 공격이었을 뿐입니다.  그리고 업계 전체에 대해 명확한 목적(삥뜯기)이 있었던 공격이고요.  당연히 게임 만들고, 돈버는 방향에 대해서 단합이나 단결 혹은 가격 담합따위를 할 필요야 없지만, 저런 무차별적인 공격에 대해서는 당연히 공동적으로 대응해야 할 부분인 셈입니다.  메이져 회사 오너들께서도 이런 생각이 사실 상당하실 것이라고 생각되기 때문에 지금 방식의 대응이 계속 되고 있다고 생각됩니다. (혹은 굳이 한국 정부랑 등지고 싶지 않으셔서 그냥 그러려니 하시는 거 같기도 하더군요) 게임은 재미있고, 그리고 송대표님 말씀대로 감동을 줄 수 있을 정도로 훌륭한 것, 아니면 적어도 확실하게 사람들의 interest를 잡아둬서 돈 버는 것 만들면 되고, 그게 단합이나 담합한다고 만들어지지 않습니다.  핵심은 저런 공격에 대해서는 명확하게 업계가 함께 대응해 나가야 한다는 것 입니다.   

좀 극단적인 비유를 하나 들어보죠. 한국도 평소에 내부에 갈등이나 경쟁 요소가 많습니다.  그런데 가령 외국에서 한국에 실제 전쟁을 벌이려고 쳐들어온다고 칩시다. 그럼 내부에서 할일은 일단 뭉쳐서 전쟁 쳐들어오는 놈은 막아야 하죠.  조금 더 해볼까요?  지금도 전세계 각 국가가 협력과 갈등이 반목되고 있죠. 그런데 갑자기 외계인이 나타나서 전 지구인을 말살하겠다고 나옵니다. 그럼 어째야 하죠? 일단 전세계가 힘모아서 외계인이랑 싸워야지 않겠습니까?


2. 문제는 한 목소리를 낼 구심점 부재의 문제
그냥 제가 보기에는 지금 메이져업체분들끼리 뭉쳐서 목소리 낼 구심점이 없는게 제일 큰 이유로 보입니다. TJ, JJ 두분이 서로한테 아쉬운 소리 할 이유도 당연히 없고요. 특히나 JJ쪽은 국내매출이 전체 매출의 35% 선이더군요. 솔직히 넥슨은 이제 한국도 중요하겠지만, 글로벌마켓이 더 중요한 회사라는 것입니다. N사의 나대표님도 굳이 이런데 나서서 하실 입장도 아니고, 세분이 무슨 서울대 선후배랍시고 세분 사이에서 권위잡을 상황도 아니실테고, 그건 NHN의 이의장님이나, 이박사님도 마찬가지실 것입니다. 이렇기 때문에 무슨 업계 어른이 나서서 구심점이 되야 한다식의 것이 성립이 되기 참 어려운 동네입니다. 

그렇기 때문에 솔직히 말하자면 이부분에서 결국 우리 업계가 할 부분은 큰 기조만 정해놓고 각개격파, 각자 알아서 잘 해결책을 찾자라는 것이 해결책이 될 수 밖에 없다고 생각합니다. 그러나 적어도 이분들이 모여서 할 일은 최소한의 기조는 잡아야 한다는 것입니다. 이부분의 공동 대응은 꼭 필요할 것으로 보입니다.  당연히 다들 대단하신 분들이니 각자 해결책은 각자 원하시는 방향으로 가면 되지 않겠습니까?


3. 부정적 정서에 맞서 싸우는 방식이 아닌, 긍정을 늘려나가는 방식.
그리고 서강대 세미나때 느낀 것입니다. 사실 게임업계 공격하는 쪽의 주장을 받혀주는 근거는 미약하기 짝이 없습니다. 그냥 터무니없다고 하고 넘겨도 될 공격입니다. 이런 공격에 대한 게임 업계의 반응이 더 걱정입니다. '일면 일리있는 주장이다.'라니요. 오히려 '일면 일리있는 주장'이 조금 끼어있었다 하더라도  차라리 나머지 터무니 없는 주장에 묶어서 도매급으로 무시해야 할 것인데, 이를 통해서 반성의 계기로 삼자라는 것은 걱정입니다.  우리 업계에 대한 반성은 그냥 우리 스스로 하면 됩니다. 우리 업계에 계신 분들이 그렇게 자기 스스로 반성 못할만큼 어리석고 미성숙한 사람들은 아니라고 생각합니다.  왜 말도 안되는 주장이 나온걸 계기 삼아서 반성해야 합니까?  정서적인 기반을 근거로 한 공격입니다.  정서라는 것은 그냥 말그대로 감정인데, 감정에 대해서 굳이 이성적인 대응을 할 필요는 없다고 보입니다.

그래서 계속 다시 한번 말씀드리는 바는, 저쪽 공격이 상당히 정서적인 공격의 영역이라는 점을 생각해서, 그 정서를 긍정적으로 돌려놓는 부분에 대해서 집중해야 하는 것이 맞다고 생각합니다.  가령 무조건 싫다고 하는 사람한테, 그게 싫지 않은 이성적 이유를 100가지 대봐야 뭐합니까. 이미 무조건!! 싫다고 하지 않습니까? 거기에 조건 100개를 대는게 문제라는 거죠.

정서적 공격에 대한 대응은 맞서 싸우는 게 아니라, 포용하고, 전환해주는 것입니다.  제가 말한 사회 공헌이나, 교육 인프라 사업 구축등의 방식이 사실은 그런 의미에서 말한 것입니다.  

아무튼 이 지리한 싸움이 결국은 셧다운제, 매출 1% 기부제 등으로 번져나갈 것이 명약관화 입니다.  지금 대응 수준을 보니 답이 안나오네요.
결국 업계가 아직은 미성숙한 대응을 하는 바람에 어이없는 사람들에게 삥뜯겨 나갈 것을 생각하니 종사자입장에서는 좀 답답할 뿐입니다. 
그거 가지고 사실은 게임에 더 투자하고, 업계 종사자들한테 더 혜택이 돌아가야 할진데.

ps. 고용 창출 이야기 조금 더 첨언하자면, 게임업계 총 종사자가 10만명 정도인데, 삼성전자 수원 지역쪽만 4만명입니다.  물론 우리 업계가 고용만 늘린다고 능사인 동네는 아니고, 그렇게 할 수도 없습니다.  단지 고용 창출은 우리가 근본적으로 해결 할 수 없는 문제라는 것이죠. 

반응형
,
Posted by 알 수 없는 사용자
이 글은 2012/02/17 - [프로그래밍] - 게임 오브젝트 설계.. 나도 잘하고 싶다! #2 에서 이어지는 글입니다.



오늘 올릴 내용은.. 템플릿과 함께하는 컴포넌트 설계! 입니다..
따라서 개인취향에 따라.. 이번 글은 패스하셔도 욕하지 않겠습ㄴ.. 
 
 


이전글에 나오는 컴포넌트를 사용하는 방식으로 게임 오브젝트를 만든다고 해봅시다.

GameObject* pGameObject = new GameObject();
RenderComponent* pRenderComponent = new RenderComponent();
pGameObject->InsertComponent( pRenderComponent );

...

ComponentBase* pComponent = pGameObject->GetComponent( "rander" ); 



아무 문제없이 돌아가야 할 것 같습니다. 그런데!! 화면에 게임 오브젝트가 그려지지 않습니다!
컴파일에러도 안나고 아무리 봐도 문제가 없을것 같은 코드인데도 제대로 된 동작을 하지 않는다면 버그를 찾아서 고쳐야 합니다. 모든 코드를 하나하나 다 살펴보고 테스트 해보고 디버깅해보고.. 머리는 아파오고 집에는 못가고 그렇게 야근을 하다보면 집에 늦게 가고 집에 가면서도 잡지 못한 버그 생각에 머리는 또 아프고.. 늦게 퇴근해서 다음날 늦잠을 자고 지각을 하고 이런 악순환이 계속될 것입니다.


 
 

아마 몇몇 분들은 눈치채셨을지도 모르겠습니다만.. 위 코드의 문제는 무엇이었을까요?!!
바로 오타!! 입니다.. RenderComponent는 "render"라는 이름을 가지지만 "rander"를 얻어오려 하니 당연히 얻어올 수 없죠.. Orz


참으로 어처구니 없는 실수입니다..




물론 위와 같은 경우는 꽤나 쉽게 찾을 수 있는 버그입니다. 하지만 좀 더 복잡한 상황이라면.. 또는 그렇지 않더라도 애초에 이런 실수를 할 수 없도록 설계를 한다면 더 좋을 것입니다.



그래서 저는 템플릿을 선택하였습니다. 



템플릿을 이용한 이유는 속도도 다른 이유도 아닌 이런 수준의 실수는 컴파일 타임에 잡아내기 위해서입니다.

GameObject* pGameObject = new GameObject();
pGameObject->InsertComponent<RenderComponent>();


...

RenderComponent* pRenderComponent = pGameObject->GetComponent<RenderComponent>();

 
위와 같은 코드가 제가 설명하려는 템플릿을 이용한 컴포넌트를 사용하는 코드입니다.


이걸 설명하기 전에 먼저 RTTI에 대해 설명해야 할 것 같습니다. 왜냐하면 RTTI를 이용한 방법이거든요..


그렇다면 RTTI란 무엇인가?!!!
 

감사합니다. 친절한티스님.


저는 컴포넌트 기반 설계를 할 때에 RTTI를 사용합니다. 왜냐하면 ComponentBase*에서 자식 객체로 변환하는일이 많기 때문입니다. 그래서 RTTI를 이미 쓰고 있습니다. 그리고 RTTI의 특성을 이용하여 위의 문제도 해결하고 있습니다.




바로 이녀석을 이용하는 것입니다. RTTI 구현의 핵심이기도 한 이녀석은 특성상 클래스별로 하나만 존재하는 객체입니다. 따라서 이 객체를 이용하면 타입별 아이디를 만드는것도 가능합니다.


[주의] 위 코드는 비표준 문법이 사용되어 작성된 코드입니다.
Visual Studio 이외의 컴파일러에서는 컴파일이 되지 않을겁니다..



일단 제가 사용중인 RTTI 코드를 첨부하였습니다. 이 코드는 현재 프로젝트에서 쓰는 코드와는 차이도 있고 원형은 GPG4권에 나온걸 기준으로 수정을 한 코드이니 이 코드를 공개하는것은 문제가 없을 듯 합니다.




RTTI를 사용하는 클래스의 static객체인 s_RTTI의 주소를 ID로 사용하는 방식입니다. 이것을 이용하면 클래스 이름 또는 객체로 ID를 얻어올 수 있습니다. 

이 RTTI의 GetTypeID()를 이용하여 GameObject에 컴포넌트의 추가와 컴포넌트를 얻어오는 부분을 다음과 같이 구현하는 것입니다.

Cpp2Html[-] Collapse

template<typename T>
T* InsertComponent()
{
    unsigned int componentID = RTTI::GetTypeID<T>();
    if( m_components.find( componentID ) != m_components.end() )
    {
        return NULL;
    }

    T* pComponent = new T();
    m_components.insert( std::make_pair( componentID, pComponent ) );
    pComponent->SetOwner( this );
    return pComponent;
}

Cpp2Html[-] Collapse
template<typename T>
T* GetComponent()
{
    ComponentTable::const_iterator iter = m_components.find( RTTI::GetTypeID<T>() );
    if( iter != m_components.end() )
    {
        return static_cast<T*>( iter->second.get() );
    }

    return NULL;
}



이렇게 하면 이제 가장 위에서 봤던 코드가 가능해집니다!!



그러나 아직 해결하지 않은 한가지 문제가 더 있습니다. 바로 패밀리식별자 입니다..




위와 같은 구조로 컴포넌트가 만들어져있다고 생각해봅시다. AAA1과 AAA2는 AAA를 상속받습니다. 이들의 패밀리식별자는 "AAA"정도로 만들 수 있겠네요. 게임오브젝트에 추가되어 있는 AAA1를 AAA의 이름으로 얻어오려면 어떻게 해야 할까요?


AAAComponent* pAAAComponent = pGameObject->GetComponent<AAAComponent>();



이렇게 얻어오면 되는걸까요? AAAComponent로 추가하지 않으면 현재의 GetComponent함수로는 원하는 동작을 기대할 수 없습니다.......... 




그래서 생각한 방법은 AAA를 기준으로 관리가 되게 만드는 것입니다. 추가할 때에는 AAA1로 추가를 하면서 자신 이외에 AAA로부터 상속받은 다른 컴포넌트가 이미 추가되어있는지를 확인하고, 컴포넌트를 얻어올 때에는 AAA로도 얻어올 수 있다면 문제가 해결될 것 같습니다.




그래서 제가 생각한 방법은 이런 관리를 하기 위한 특별한 컴포넌트 계층인 SingleComponent를 만드는 것입니다. SingleComponent를 상속받은 컴포넌트는 무조건 게임오브젝트에 하나만 넣을 수 있고, SingleComponent의 바로 아래 클래스를 기준으로 관리를 하는 것으로 기존의 패밀리식별자를 대처하는 것입니다.

이렇게 구현하기 위해서는 AAA1Component로 SingleComponent의 아래에 있는 AAAComponent를 알아내야 합니다. 또는 AAA밑에 다른 계층이 있어도 그 클래스로 AAAComponent를 알아내야 하죠. 그래서 고민을 한 끝에 RTTI에 이런 기능을 넣게 되었습니다.



먼저 템플릿에서 현재 클래스와 상위 클래스를 사용하기 위해 MyType과 BaseType를 만들고..
(__super 키워드는 비표준 문법입니다. __super를 안쓰려면 상위 클래스의 이름을 따로 받아야 할 것 같습니다..)


그리고 MyType과 BaseType을 사용해서 이런 간단한 유틸리티 템플릿 구조체를 만들었습니다.

one_step_direct_descendant는 Derived부터 Base까지 상위 타입이 Base와 같은지를 비교해가면서 Base의 바로 아래 타입을 알아올 수 있는 템플릿 구조체 입니다.

이것을 이용하여 다음과 같이 작성을 하면 AAAComponent의 타입을 사용할 수 있습니다.

one_step_direct_descendant<SingleComponent, AAA1Component>::type;


이제 이렇게 SingleComponent와 AAA1Component로 AAAComponent를 얻어올 수 있게 되었습니다!


그래서 저는 이 템플릿 구조체를 사용하여 InsertComponent를 할때에 SingleComponent를 상속하고 있는 컴포넌트이면 SingleComponent의 바로 아래 타입으로 저장을 하고 상속하고 있지 않는 컴포넌트이면 해당 컴포넌트의 타입으로 저장을 하는식으로 InsertComponent 함수를 작성하였습니다.

Cpp2Html [-] Collapse
template<typename T>
T* InsertComponent()
{
    unsigned int componentID = boost::mpl::if_<type_traits::is_base_of<SingleComponent, T>,
        single_component_type,
        component_type>::type::Invoke<T>();

    if( m_components.find( componentID ) != m_components.end() )
    {
        return NULL;
    }

    T* pComponent = new T();
    m_components.insert( std::make_pair( componentID, pComponent ) );
    pComponent->SetOwner( this );
    return pComponent;
}

먼저 if_와 is_base_of를 사용하여 SingleComponent 로부터 상속된 타입인지를 검사합니다. SingleComponent로부터 상속된 타입이면 single_component_type의 Invoke 함수를 실행하고 아니면 component_type의 Invoke 함수를 실행합니다.

if_ : 
http://www.boost.org/doc/libs/1_49_0/libs/mpl/doc/refmanual/if.html 참조
is_base_of :  http://www.boost.org/doc/libs/1_49_0/libs/type_traits/doc/html/boost_typetraits/reference/is_base_of.html  참조

Cpp2Html [-] Collapse
struct single_component_type
{
    template<typename T>
    unsigned int Invoke()
    {
        return RTTI::GetTypeID<typename RTTI::one_step_direct_descendant<SingleComponent, T>::type>();
    }
};

struct component_type
{
    template<typename T>
    unsigned int Invoke()
    {
        return RTTI::GetTypeID<T>();
    }
};

single_component_type의 Invoke 함수는 InsertComponent에 들어온 타입과 SingleComponent로부터 SingleComponent의 바로 아래 타입의 ID를 반환하고, component_type의 Invoke 함수는 현재 타입의 ID를 반환합니다.

GetComponent 함수도 역시 마찬가지입니다.

Cpp2Html [-] Collapse
template<typename T>
T* GetComponent()
{
    unsigned int componentID = boost::mpl::if_<type_traits::is_base_of<SingleComponent, T>,
        single_component_type,
        component_type>::type::Invoke<T>();

    ComponentTable::const_iterator iter = m_components.find( componentID );
    if( iter != m_components.end() )
    {
        return static_cast<T*>( iter->second.get() );
    }

    return NULL;
}

InsertComponent와 같은 방법으로 컴포넌트의 ID를 얻어오고 그 ID로 컴포넌트를 찾아서 반환해주는 것입니다.

이렇게 되면 컴포넌트를 만들때 하나만 관리되어야 하는 컴포넌트이면 SingleComponent를 상속받고 그렇지 않으면 ComponentBase를 상속하여 구현을 하는 것으로 관리가 될 것입니다. 


(사실 위의 코드는 문제가 조금 있습니다. 따라서 현재 저희 프로젝트에서 사용하는 코드와 조금 다릅니다. 위 코드의 문제점에 대해 알아내신 분은 kgun86@dragonflygame.com으로 연락주세요.. 저희팀에서 어떤식으로 처리를 했는지에 대해 설명해드리고 같이 토론을 해보면 좋을 것 같습니다.. ㅎㅎ)


일단 이번 연재는 이정도로 마무리를 하도록 하고.. 다음 연재에서는 매크로를 사용한 컴포넌트 설계에 대해 설명을 하게 될 것 같습니다.




ps. 저희팀에서 현재 프로그래머를 모집하고 있습니다!!

경력자만 모집하고 있구요.. 네트워크, 그래픽스, 게임플레이, 툴, DB 등등등 모든 분야를 모집하고 있습니다!!

사무라이쇼다운 온라인을 MMORPG로 개발하고 있구요. 저희팀으로 오시면 하고 싶은거 시켜드립니다! 자세한건 메일 보내주시면 설명해드리겠습니다!


 
위 초대장의 빈칸을 채워서 kgun86@dragonflygame.com 으로 보내주세요!!! 

반응형
,
Posted by 월하
이걸 써도 되는지 고민을 많이 했습니다.

실제로 제가 작성한 글도 아니고 해서요.

하지만... 이건 정말...... 기획자라면 꼭 한번은 보고 넘어갔으면 해서

조심스럽게 동영상을 첨부합니다.

메가맨X로 배우는 레벨 디자인과 튜토리얼입니다.

19분 가량의 영상이고 약간의 욕설이 있지만 이 모든걸 감수하고 볼 가치가 있습니다.

 

만약 문제가 된다면 삭제 하도록 하겠습니다.
반응형
,