티스토리 툴바


3ds Max를 사용해서 게임용 캐릭터를 셋업하는 방법에 대해서 다루는 책
3D 캐릭터를 셋업하는 과정이 이 책의 핵심이며 이를 위해 오랜 실무를 경험해 온 저자의 고급 노하우들이 공개
위 내용은 GameDevForever의 저자 핑속(이상원)님의 책을 홍보하기 위하여 운영진 자체적으로 올린 광고이며 일체의 수익이 없습니다.(핑속님도 몰...이젠알아요)
Posted by 엘키
이번 시간에는 예고한 대로 버그 재발 방지에 대해서 알아보겠습니다.


저를 포함한 대부분의 개발자들은 버그를 만듭니다.
인재들이 모였다는 MS의 IE 베타버전이 얼마나 엉망인지 아시죠? QA와 오류 보고 시스템 등을 통한 검수가 정식 버전을 안정성있게 만드는 것이지 MS가 만든 제품도 버그가 많습니다.
그 들이 극찬하는 IOS나 OSX요? 네 그렇게 신중한 애플도 버그를 만듭니다.
특히나 많은 버그가 개발 과정에서 더더욱 많이 튀어나오는데, 이들 대부분이 개발자의 습관에서 비롯됩니다.

흔 히 많이 하는 실수가 초기화 오류(변수값의 초기화 값이 없다거나, 변수가 초기화 되는 시점이 잘못된 경우)라던가, 경계값 오류(임계치에 가까운 값을 잘못 설정한 오류), 연속 시도시 잘못된 결과가 도출되는 오류 (예를들면 결제 버튼 여러번 누르면 돈은 여러번 감소하고 아이템은 지급안된다던가하는 오류), 버그 수정시 다른 버그를 만들어 내는 문제 등이 있습니다.

생각보다 같은 종류의 실수를 자주 하는 사람이 많은데, 모 QA팀에서는 버그 스타일에 따라 담당자를 배정하는 사례도 있었다고 할 정도죠.
물론 어쩌다가 한번 나오는 버그는 개발자의 실수로 모두가 좋게 넘어가지만 같은 종류의 버그가 여러번 나오고, 제대로 된 수정이 이루어지지 않는다면 여러가지 측면에서 문제가 생깁니다.

QA팀에 없는 경우에는 개발팀 테스트 후 바로 서비스가 될 테니 더 말 할것 없고, QA팀이 있는 경우라해도 개발 과정의 버그도 줄여야 합니다.
반드시 그렇게 해야 하는 근거는 다음과 같습니다.

1. QA팀에 신뢰를 심어주기 위해
- 물론 QA팀은 몇번 같은 종류의 버그가 생겼다면 꼼꼼히 챙겨봐주신다. 하지만, 버그가 급격히 줄어든 걸 보면 뿌듯하고 개발팀에 신뢰도가 향상하게 되어있다.

2. 우리의 버그를 검증해주는 QA팀 마저도 놓칠 수 있기에
- 특히나 대규모 패치일 경우 더더욱 그런데, 체크 리스트 목록을 많이 작성하다보면 평소보다 테스트하는 디테일이 떨어질 수 있다.

3. 안좋은 습관은 긴급한 코드 작성시 극도로 심해지기 때문에

- 이건 주로 내가 그런데, 급하게 코드를 작성하다보면 안좋은 습관 (여러가지 측면을 다 고려해야 하는데, 현재 증상만 보고 버그를 수정한다거나 하는 경우)이 나온다.

4. 멍청하단 소리를 듣지 않기 위해서
- 같은 실수 여러번하면 멍청하다는 소리를 듣게 되있다. 듣기 싫다면 고쳐라.


이런 문제는 정상적인 개발자라면 대부분 2~3년차 때는 고치게 되는 편입니다. (안타깝게 비정상적인 개발자가 꽤 많죠...ㅠ_ㅠ 누구라고는 얘기 안하겠습니다.)


여하튼 개인의 습관에서 나오는 문제를 고쳐야 되는 이유에 대해 이야기 해봤습니다.
자 이제, 팀 차원에서의 버그 재발을 막기 위한 노력에 대해서 예를 들어 이야기 해보겠습니다.

개발자가 어느정도 숙련자 위주로 이루어져있는 팀이 있습니다. S프로젝트라고 지칭해보죠.

S프로젝트는 3년의 개발을 거쳐 OBT 직전에 와있는 사내에서도 주목받는 팀입니다. 그런데 이 팀에 인원이 부족하네요.
자 우리 회사는 어느정도 건실하고, 주목받는 팀에서 본보기가 되기 위해 신입도 뽑아보자고 마음을 먹었습니다.
10여명 면접을 보고 그 중 가장 성실하고 센스 있어 보이는 C를 뽑았습니다.

OBT가 시작됐습니다. 예상보다 반응이 좋습니다. 너무 반응이 좋다보니 유저가 폭발적이네요. 로그인이 잘 안된다고 합니다.
하지만 우리가 누군가요? 베타랑들 답게 능숙하게 트러블을 10분만에 빠르게 해결하고 정상화 시켰습니다.
게시판에서도 호평 일색입니다. 안정적인 서비스, 재미, 그래픽 모두가 만족스럽다고 합니다.
딱히 문제도 없다보니 OBT중이지만 이 행복한 팀은 OBT기간임에도 칼퇴근도 합니다.

반응이 좋지만 조금씩 불안해집니다. 미리 만들어놓은 컨텐츠가 1달치뿐이니까요. 슬슬 컨텐츠를 더 만들어야 할 때 입니다.
베테랑 개발자 A가 이 시기에 집안일로 어쩔 수 없이 자리를 비우게 됩니다.
해야 될 일은 많은데 한사람이 비어 어쩔 수 없이 C에게 컨텐츠 개발을 시켜봅니다.

그런데 똘똘해보이고, 센스 있어보이던 C가 실수 연발입니다.
개발과정이 A보다 몇배 오래 걸림은 물론이고, QA에서 발생한 버그를 한번에 수정하지 못하고 계속 여러번 오가게 만들어 QA도 늦게 끝나 야근하는 날이 늘어납니다.
다른 개발자들은 이 신입에게 불만을 토로합니다. 좀 모자란거 아니냐, 잘못 뽑은거 같다, 나 신입땐 안그랬다 등등...
팀 분위기는 조금씩 안좋아지고, 야근이 잦아지다보니 서비스 퀄리티는 조금씩 떨어집니다.
아 뭔가 잘못 되어가는거 같습니다. 우울해집니다.


노파심에서 말씀드리자면 이 이야기는 절대 신입을 뽑지 말자는 의미가 아닙니다.
상황이 이렇게 된데에는 잘 흘러가는 것처럼 보이던 S프로젝트 팀에 잠재적 문제가 있었습니다.


과연 이 팀의 문제가 무엇이었을까요?

1. 개개인의 차이에 맞지 않는 일정 수립
- 베테랑 개발자 A와 신입 개발자 C의 개발 속도 차이를 고려하지 않고 일정이 수립.
- 여기에 + @로 신입 개발자 C는 코드 분석 시간과 업무 방식에 대한 적응 기간도 필요한데 이마저 고려되지 않았음.

2. 개발 검수 과정이 없었음.
- 너무나 숙련된 개발자들로 이루어진 팀이었다보니, 개발 과정에 대해 누구도 관심을 갖지 않음. 각자의 할일에만 포커스가 맞추어져 있었음.
- 개발 검수가 없었다는 것은 코드 검수도 이루어 지지 않았다는 것이고, 신입 개발자 C가 작성한 코드가 지금껏 암묵적으로 지켜져오던 룰을 깼을 가능성도 있음.
- 분명 개발 검수 과정이 제대로 이루어졌다라고 한다면 적절한 로그를 남기도록 작업했을 터인데, 이마저도 이루어지지 않음.

3. 디버깅이란 경험이 많은 역할을 하는데, 이에 대한 리딩이 없었음.
- 디버깅은 정상 동작과 비정상 동작의 차이와 증상을 힌트로 원인을 찾아내는 과정이다.
- 신입 개발자인 C가 디버깅에 익숙할리가 없는데 이에 대한 방치로 인해 업무가 지연되었고, 이는 C가 아닌 개발팀이 만든 문제다.


실제로 이 팀에 내재된 문제는 더 있을 겁니다.
이런 상황을 만들었다는 것 자체가, 개발자 개개인의 역량에 지나치게 의존한다는 증거라고 볼 수 있기 때문입니다.

게다가 업무에 대한 교류와 검토가 이루어지지 않다보니, 베테랑 개발자 A라 할지라도 순간 정줄을 놓는 순간 버그를 양산할 여지가 다분합니다.
문제는 버그가 양산된다해도 우리의 희망 QA팀에서 대부분 잡아주시겠지만 개발 규약이라던지, 로그가 적절하게 취합되고 있는지, 예외 상황은 얼마나 잘 처리되었는지 등에 대한 검토마저 개개인의 역량에 맡기고 있다는 사실입니다.

요 새 PC가 워낙에 빠르다지만 클라이언트는 유저에게 배포되기 때문에, 서버는 많은 유저를 수용하고 그들의 요청을 처리해야 되기 때문에 속도도 중요한데, 검수가 없다는 것은 이 코드가 정상 상황에서 잘 돌아가기만 하면 된다고 여기고 있다는 얘기가 되는 겁니다.

이렇듯 꽤나많은 문제를 갖고 있는 이 팀이 문제들을 해결하기 위해선 몇가지 노력이 필요합니다.

1. 오류 보고 시스템
- M2 프로젝트에서 개발과정 도입되었다고 알려져 있는 이 시스템은, 나는 5명이상의 프로그래머가 함께 일하는 모든 개발팀에서 도입되어야 한다고 생각한다. 오류 보고 시스템을 적극 사용하는 과정중에 코드 검수가 이루어지며, 런타임 오류가 발생한 위치와 작성자, 원인 등을 명확히 파악하기에 유용하기 때문이다.

2. 개발 규약
- 암묵적이 아닌 개발 규약은 어느정도 존재할 필요가 있다. 뭐 띄어쓰기는 어떻게 하고, 네이밍은 어떻게하고 이런거보다는, 예외 처리는 어떻게 해야 하는지, 로그 기록에 대한 기준, 외부 라이브러리 도입 기준, 코드 관리 규약 (만들어져 있는 기능들에 대한 명세 및 관리) 등에 대한 이야기를 정리해놓고 손쉽게 관리하도록 노력하는 것이좋다.

3. 코드 리뷰
- 코드 리뷰를 어렵게 생각하지 마라. 개발 전에 작업 방향에 대해 이야기를 나눈 이후, 개발 테스트 이전이나 개발 테스트중 코드 검수를 요청하면 된다. 작업 방향에 대한 이야기가 선행되어 있기 때문에 전면적 리팩토링을 하게 될일은 없을 터이고, 개발 테스트 도중 발견됐을 버그나, 부분적인 코드 완성도를 끌어올리고 개발 규약에 맞는지, 유저에게 오류 발생시 어떻게 보여지고 어떻게 처리될지에 대한 고려를 중점적으로 보면 된다.
- 다만 지나치게 잦은 코드 리뷰와, 코드의 큰 방향성을 뒤흔드는 코드리뷰는 옳지 않다. 이는 개발 미팅 후 개발자들 간의 조율 과정에서 끝나야 한다.

4. 자동화 된 테스트
- 유닛테스트 + 모듈 테스트로 분리되어 얘기되기도 하는데, 분리 가능한 코드 조각을 테스트 하는 것이 유닛 테스트, 외부 프로그램 내지는 포함된 코드가 실제 동작중인 프로그램을 대상으로 반복된 테스트를 통해 새로 작성된 코드가 이미 작성된 기능을 망가뜨리진 않았는지, 또 새로 작성된 코드에 대한 테스트를 작성함으로써 해당 기능에 대한 이해도를 높이는 순작용도 가지고 있다.
-  또한 자동화된 테스트를 통해 크래시가 얼마나 자주, 또 매번인지 때때로 발생하는 지 등도 알아 낼 수 있다.

5. 자동화된 측정 시스템
- 자동화된 테스트에 함께 붙이면 되는 경우가 많으며, 성능 측정과 잔존물 측정이 함께 이루어져야 한다.
성능 측정은 perfmon과 같은 외부 프로그램을 통한 측정과, 코드 수행속도 측정, 부가 데이터 측정 (로그나 DB 데이터 등...)등으로 분류된다.
측정 대상은 상황마다 다르지만, 보통 퇴근시간에 측정 시스템과 테스트 시스템을 켜놓은 후 다음날 결과를 바탕으로 회의와 업무를 진행하는 식으로 이루어지는 것이 좋다.

이렇게 갖춰놓는다면 아무리 사고뭉치라해도 개발 과정에서 대다수를 캐치 할 수 있습니다.


자 이렇게 좋은 개발팀이 버그를 많이 만들지 않도록 구축해야 할 것들에 대해 이야기해보았습니다.
어느정도 디버깅에 대한 이야기를 마무리 한거 같네요. 아쉬운 부분이 좀 있지만, 디버깅에 대해 좀 더 진지하게 다시 할 얘기가 생겼을 때 연재하도록 하겠습니다.

다음 시간에는 디버깅 주제가 아닌 게임 개발 이야기로 찾아올게요~ 감사합니다 ^^

저작자 표시 비영리 변경 금지

댓글을 달아 주세요

Posted by ozlael

지난 번 글에서 이어지는 글입니다.

이전 글 : PC에서 3D 입체 영상 게임 개발하기 #1



들어가며

입체영상은 인간이 입체를 느끼는 요소를 인위적으로 만들어 내는 것이지요. 그러므로 우선 우리가 입체를 느끼는 원리를 이해해야 입체 영상을 다룰 수 있습니다. 이번 글에서는 이러한 입체 영상의 인식 및 구성 요소에 대하여 다루어보도록 하겠습니다.



입체시(stereo vision)

우리가 사물을 볼 때는 사물을 입체로 인식합니다. 가까운 사물은 가까이 느껴지고 멀리 있는 사물은 멀리 느껴지지요 (응? 당연한건가?) 이러한 인간의 입체 공간 인지 능력을 입체시(stereo vision)이라고 합니다. 이 입체시 능력은 인간의 눈이 두개인 것에서 출발을 합니다. 손바닥을 양 눈 사이에 세워놓고 오른쪽눈을 감고 왼쪽 눈으로 손바닥을 볼 때랑 왼쪽 눈을 감고 오른쪽  눈으로 손바닥을 볼 때랑 서로 다른 모양과 다른 면들 보게 되지요. 이처럼 각각의 눈에 다른 영상이 들어오는 것을 양안시차 혹은 양안부등(binocualr disparity)이라고 부릅니다. 이 양안시차를 뇌가 합성하여 입체 정보로 만들어 내고 이를 통해 입체시가 발현되는 것이지요. 이 양안시차는 두 가지 요소에 의해 만들어집니다.



세퍼레이션(separation)

먼저, 인간의 눈은 두 눈이 한 곳에 몰려있지 않고 어느정도 간격을 두고 떨어져있지요. 평균적인 성인 기준으로 두 눈은 6.5cm 정도 떨어져있는데요, 이를 안간(interocular)이라고 표현합니다. 두 눈이 떨어져서 다른 곳에 위치하고 있기 때문에 각각의 눈으로 다른 영상을 보는 것이지요. 입체 촬영 역시 이 안간을 흉내내어 카메라 렌즈 두 개를 어느정도 떨어트려 배치합니다. 이 카메라 혹은 렌즈 사이의 거리를 축간거리(interaxial distance)이라고 합니다.

이미지 출처 : http://www.dashwood3d.com/blog/beginners-guide-to-shooting-stereoscopic-3d/

이런 카메라 거리를 나타내는 또 다른 척도로 세퍼레이션이 있습니다. 세퍼레이션은 인터렉셜을 스크린 크기로 나눈 값을 의미합니다. 하지만 일반적으로 스크린크기는 상수이므로 보통은 카메라 사이 간격 조절을 세퍼레이션과 인터렉셜을 같은 의미로 용어가 혼용하여 사용되기도 합니다.

separation = interaxial / screen width

 이 세퍼레이션은 사람이 입체 공간의 깊이를 인지하는데 영향을 미칩니다. 세퍼레이션의 갚이 작으면 즉 좌우 카메라 간격이 적으면 입체 3D 공간의 전체적인 깊이가 얕게 느껴지고, 세퍼레이션의 값이 크면 즉 좌우 카메라 간격이 많으면 입체 3D 공간의 깊이가 깊에 느껴지게 됩니다. 

이미지 출처 : http://www.3dtv.fr/NAB09_3D-Tutorial_BernardMendiburu.pdf



컨버젼스(convergence)

또한, 두 눈의 주시선은 무한히 평행하게 뻗어나가는 것이 아니라 어느 한 점에서 교차되게 되어있습니다. 그 교차점이 우리가 초점을 맞추는 대상이고 이를 폭주 또는 수렴(convergence)이라고 합니다. 

입체 촬영 시 역시 이 컨버젼으를 흉내를 냅니다. 사람눈과 마찬가지로 두 카메라의 주시 방향을 평행하게 두는 것이 아니라 어느 한 지점에서 교차되게 배치를 합니다. 이 카메라의 시선이 교차되는 지점이 컨버젼스 포인트가 되는 것입니다. 즉 이 컨버젼스 포인트는 시차가 0이 되는 것이고 시청자가 볼 시 스크린 또는 모니터의 위치가 됩니다. 이 컨버젼스 포인트보다 앞에 있는 사물은 모니터 밖에 튀어나와 있게 느껴지는 것이고, 컨버젼스 포인트보다 뒤에 있는 사물은 모니터 안에 있게 느껴지는 것이지요.

이미지 출처 : http://www.3dtv.fr/NAB09_3D-Tutorial_BernardMendiburu.pdf



패럴랙스(parallax)

이 세퍼레이션과 컨버젼스는 최종적으로 패럴렉스(parallax)라는 요소로 모니터에 투영이 됩니다. 이 패럴렉스는 모니터에 투영 된 좌안과 우안 영상의 수평 위치 차이를 의미합니다. 모니터보다 앞에 튀어나와 있도록 영상이 분리되어 있으면 음시차 (negative parallax)라 불리고 모니터 뒤에 있도록 영상이 분리되어 있으면 양시차(positive parallax)라 불립니다. 좌우 영상이 모니터상에 분리 없이 투영되어 있으면 딱 모니터 깊이에 위치가는 것이고 이는 무시차 (zero parallax) 상태가 됩니다.

이미지 출처 : http://www.3dtv.fr/NAB09_3D-Tutorial_BernardMendiburu.pdf

다음 이미지는 컨버젼스와 세퍼레이션과 패럴렉스의 상관 관계를 한눈에 나타내줍니다. 빨간선은 좌안과 우안의 주시선을 타타냅니다. 주시선이 모이는 나무가 컨버젼스 포인트고 스크린 위치가 되는 것이지요. 컨버젼스에 수렴할수록 같이 세퍼레이션 값이 작아지는 것을 볼 수 있습니다. 

이미지 출처 : http://forum.iz3d.com/viewtopic.php?t=3267



마치며

입체 영상을 다루기 위한 구성 요소는 여기까지 설명을 드리는 것으로 마치기로 하구요, 다음 시간에는 본격적으로 PC 게임에 입체 영상을 적용하는 방법에 대해 다루기로 하겠습니다. 지루한 글 끝까지 읽어주셔서 감사합니다. 굽신굽신

댓글을 달아 주세요

  1. 크로스 2012/05/21 10:05  댓글주소  수정/삭제  댓글쓰기

    분명 좋은 강좌를 읽었는데 어쩐지 레이더즈를 해보고싶은 느낌이 드는 신기한 글이네요. 하핫

Posted by 친절한티스

요즘 디아블로 3를 즐기고 있는데 로그인 에러 때문에 빡치더군요. 돈 주고 게임을 샀는데 할 수가 없어요. 그러다 로그인 과정에 일정한 패턴도 있겠다. 한번 자동으로 만들어보자 해서 만들어보게 되었습니다. ( 이렇게 프로그래밍 스킬을 써먹는 것 이지요. )


열어줘!! 열어달라고!!!


일단 크게 3 부분이 필요하다는 것을 정리했습니다.


1. 전역 키 후킹

매크로 프로그램 같은 경우 게임의 배경에서 돌아가야 합니다. 포커스가 디아블로에 있다 하더라도 자동 로그인 프로그램이 on/off 될수 있도록 전역적인 키 후킹이 필요합니다. ( 예. F1로 자동 로그인 켜기, F2로 자동 로그인 끄기 )


2. 패턴화된 작업을 자동화

보통 로그인이 과정이 아이디는 입력해놓으면 고정되고, 패스워드 입력 -> 접속 버튼 클릭 -> (에러난 경우 ) 에러창 출력 -> 에러창 닫기 -> 패스워드 입력 -> ... 대략 이렇습니다. 이 과정만 자동화 해놓으면 되죠.


3. 화면 해상도

클릭 해야 될 부분이 해상도 마다 다릅니다. 모든 사람이 1920 * 1080 해상도를 쓰는 것이 아니므로, 클릭 좌표를 비율로 관리해야 했습니다. 일단 제가 사용하는 1680 * 1050 해상도를 기준으로 좌표를 클릭해야 할 좌표들을 얻은 다음 비율을 통해 대부분의 해상도에서 ( 와이드 기준 ) 작동 하도록 하였습니다.


일단 작업 해야될 부분을 정리 했고, 이제 실제로 만들어 보도록 하죠.


먼저 전역 키 후킹 클래스를 만들어봅니다. 구글링을 해보니 이미 CodeProject에 좋은 샘플이 있더군요. 이것을 기반으로 작업해보겠습니다. ( 출처 : http://www.codeproject.com/Articles/19004/A-Simple-C-Global-Low-Level-Keyboard-Hook )


// 후킹에 필요한 Win32API를 임포트
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);

// 후킹 프로시저 함수와 후킹 데이타 구조체 정의
delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
keyboardHookProc mkeyboardHookProc;
struct keyboardHookStruct
{
	public int vkCode;
	public int scanCode;
	public int flags;
	public int time;
	public int dwExtraInfo;
}

// 후킹 후 불려질 콜백 함수 정의
public delegate bool CustomKeyEventHandler(Key k);
public event CustomKeyEventHandler CustomKeyDown;
public event CustomKeyEventHandler CustomKeyUp;

// 후킹 시작
const int WH_KEYBOARD_LL = 13;
public void hook() 
{
	IntPtr hInstance = LoadLibrary("User32");
 	mkeyboardHookProc = hookProc;
 	hhook = SetWindowsHookEx(WH_KEYBOARD_LL, mkeyboardHookProc, hInstance, 0);
}

// 후킹 끝
public void unhook() 
{
	UnhookWindowsHookEx(hhook);
}

// 후킹 프로시저
public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
{
	if (code >= 0) 
    {
		Key key = KeyInterop.KeyFromVirtualKey(lParam.vkCode);      
        
        // 등록된 키가 눌러졌으면 
		if (HookedKeys.Contains(key)) 
        {
            bool bHandled = false;
			if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && CustomKeyDown != null)
            {
                bHandled = CustomKeyDown(key);
			}
            else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && CustomKeyUp != null)
            {
                bHandled = CustomKeyUp(key);
			}
            if (bHandled)
				return 1;
		}
	}
	return CallNextHookEx(hhook, code, wParam, ref lParam);
}


코드는 상당히 간단합니다. HookedKeys에 후킹 하고 싶은 키과 불려질 함수를 등록 해두고, 해당 키가 입력 되었을때 등록된 함수가 호출 되는 방식입니다. (KeyDown, KeyUp을 구분 해둠)


실제 사용하는 부분입니다.


// F1과 F2키를 후킹 하도록 한다.
// 위의 키가 눌리면 CustomKeyUpEvent가 호출
mGlobalHooker = new GlobalHooker();
mGlobalHooker.HookedKeys.Add(Key.F1);
mGlobalHooker.HookedKeys.Add(Key.F2);
mGlobalHooker.CustomKeyUp += new CustomKeyEventHandler(CustomKeyUpEvent);


이제 키 후킹은 되었으니 자동 로그인 부분을 만들어 보죠. 제가 생각하는 구조는 필요한 암호를 입력 받아놓고, 패스워드 필드에 패스워드 입력 후 ( 붙여넣기 ) 접속 버튼을 클릭. 그 다음 에러 창이 뜨고, 에러창 닫기를 클릭. 다시 패스워드 필드를 클릭 하고 ( 가끔 포커스가 아이디 필드로 날아가니 ) 다시 패스워드 입력 후... 이후 반복 작업입니다.




대략 4가지 일을 해야 합니다. 이 모든 일이 한번에 일어나면 이벤트 동작이 제대로 안됩니다. 이전 이벤트가 씹히기도 하죠. 그래서 각 이벤트를 일정 딜레이를 주고, 발생하도록 합니다. 일단 그전에 클릭 해야될 좌표를 구해야 합니다. 제가 1680 * 1050 해상도를 사용하는데, 이 해상도를 기준으로 클릭 좌표를 화면 해상도 비율로 구해놓았습니다. 그렇기 때문에 해상도가 변해도 클릭해야 될 곳을 정확히 클릭하도록 했습니다.

// 마우스와 키 입력 이벤트를 생성하기 위한 Win32Api 불러오기
[DllImport("user32.dll")]
static extern void keybd_event(byte vk, byte scan, int flags, int extrainfo);
[DllImport("user32.dll")]
static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);
[DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
static extern void SetCursorPos(uint X, uint Y);

// 현재 화면 해상도(게임 해상도)를 비율로 구함
// 이 작업은 자동 로그인이 시작 될때 실행 됨
mScreenWidthRatio = 1.0 / 1680 * SystemParameters.PrimaryScreenWidth;
mScreenHeightRatio = 1.0 / 1050 * SystemParameters.PrimaryScreenHeight;

// 작업 이벤트를 순차적으로 등록
mEvents = new ArrayList();
mEvents.Add(new LoginEventHandler(LoginEvent1));
mEvents.Add(new LoginEventHandler(LoginEvent2));
mEvents.Add(new LoginEventHandler(LoginEvent3));
mEvents.Add(new LoginEventHandler(LoginEvent4));

// 이벤트 1 - 패스워드 필드 클릭
void LoginEvent1()
{
    double CursorX = mScreenWidthRatio * 835;
    double CursorY = mScreenHeightRatio * 686;

    SetCursorPos((uint)CursorX, (uint)CursorY);
    mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0);
}

// 이벤트 2 - 패스워드 붙여넣기
void LoginEvent2()
{
    keybd_event(CTRLKEY, 0, 0, 0);
    keybd_event(VKEY, 0, 0, 0);
    keybd_event(CTRLKEY, 0, KEYEVENT_UP, 0);
}

// 이벤트 3 - 접속 버튼 클릭
void LoginEvent3()
{
    double CursorX = mScreenWidthRatio * 835;
    double CursorY = mScreenHeightRatio * 835;

    SetCursorPos((uint)CursorX, (uint)CursorY);
    mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0);
}

// 이벤트 4 - 에러37 확인 버튼 누르기
void LoginEvent4()
{
    double CursorX = mScreenWidthRatio * 835;
    double CursorY = mScreenHeightRatio * 615;

    SetCursorPos((uint)CursorX, (uint)CursorY);
    mouse_event(LBUTTONDOWN | LBUTTONUP, (uint)CursorX, (uint)CursorY, 0, 0);
}


각 이벤트들을 배열리스트에 담아두고 지정된 딜레이 시간마다 순차적으로 호출 되게 합니다. 여기서는 F1키가 입력되면 작업을 실행도록 합니다.

// 타이머를 생성
TimerClock = new DispatcherTimer();
TimerClock.Tick += new EventHandler(Timer_Tick);

// 등록된 키가 눌리면 불려지는 함수
bool CustomKeyUpEvent(Key k)
{
    switch (k)
    {
        case Key.F1:
            {
                if (!TimerClock.IsEnabled)
                {
                    // 해상도 비율로 사용 가능하게 비율을 구함
                    mScreenWidthRatio = 1.0 / 1680 * SystemParameters.PrimaryScreenWidth;
                    mScreenHeightRatio = 1.0 / 1050 * SystemParameters.PrimaryScreenHeight;

                    // 실행될 프로시저 인덱스
                    mCurrEventIDX = 0;
                            
                    // 입력된 딜레이 값을 지정
                    // 입력된 패스워드를 클립보드에 복사
                    miDelayTime = Convert.ToInt32(textBox1.Text);
                    Clipboard.SetData(DataFormats.Text, textBox2.Text);

                    // 타이머 실행
                    TimerClock.Interval = new TimeSpan(0, 0, 0, 0, miDelayTime);
                    TimerClock.Start();
                }
                else
                {
                    TimerClock.Stop();
                }
            }
            return true;

        // F2키는 종료. 종료 되기전에 클립보드를 비운다.
        case Key.F2:
            {
                Clipboard.SetData(DataFormats.Text, "");
                Application.Current.Shutdown();
            }
            return true;
        default: return false;
    }
}

// 타이머에 의해서 호출된다
// 호출될때마다 순차적으로 등록된 이벤트 함수를 호출한다
void Timer_Tick(object sender, EventArgs e)
{
    if (0 == mEvents.Count)
        return;

    if (mCurrEventIDX >= mEvents.Count)
        mCurrEventIDX = 0;

    (mEvents[mCurrEventIDX] as LoginEventHandler)();
    ++mCurrEventIDX;
}

대략 이걸로 기본적인 동작은 하게 됩니다. 이렇게 만들어진 결과물~ 쨔란~ 알고 보면 별거 아님.


Diablo3AutoLogin.zip

저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. 완전 2012/05/20 17:07  댓글주소  수정/삭제  댓글쓰기

    오류나는데용?

  2. 크로스 2012/05/20 22:53  댓글주소  수정/삭제  댓글쓰기

    소..소프트웨어적으로 일종의 오토를 만드셨어..ㄷㄷㄷㄷ
    랄까, 요즘 대부분의 온라인게임에선 소프트웨어 오토를 막고있기때문에
    아마 다른건 더 안되겠죠??

    • 친절한티스 2012/05/21 00:38  댓글주소  수정/삭제

      머.. 오토라고 할거까지 있나요.

      그냥 일일히 비번/복붙 하는거 자동화 한거뿐 ㅋㅋ

      뭐.. 따지고 보면 오토는 오토네욤.

  3. 로엔그람 2012/05/20 23:02  댓글주소  수정/삭제  댓글쓰기

    저는 잘 됩니다~ 완전 멋지십니다^^

  4. Lyn 2012/05/21 09:41  댓글주소  수정/삭제  댓글쓰기

    블리자드는 게임에 보안 안하기로 유명하니 ㅋㅋ

  5. denoil 2012/05/21 10:05  댓글주소  수정/삭제  댓글쓰기

    우왕 유용한 프로그램 감사합니다!! 정말 필요했어요 ㅠㅠ

  6. 인자인자유인자 2012/05/21 17:38  댓글주소  수정/삭제  댓글쓰기

    아~ 필요하던 프로그램이군요..일일이 비밀번호 넣기 귀찮았는데...
    그런데 OPT설정해 놓으면 이 프로그램 사용 못하나요??

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 캐릭터만 들어가면 되니까요.)





저작자 표시 비영리 변경 금지
TAG 포프

댓글을 달아 주세요

  1. 디퍼드H 2012/05/20 10:34  댓글주소  수정/삭제  댓글쓰기

    오호! 흥미롭네요~ 이젠 이렇게 써야겠군.

  2. 크로스 2012/05/20 13:34  댓글주소  수정/삭제  댓글쓰기

    std::string을 알게된 뒤론 char*로 문자열을 안쓰게된지 좀 되었지요 ~_~ㅎ
    그런데 위 코드는 내부적으로 memset을 부른다니 흥미롭네요.

  3. denoil 2012/05/21 10:16  댓글주소  수정/삭제  댓글쓰기

    내가 줄곧 사용하던 방법인데 memset이 맞아여?
    char Temp[3] = { 0, }; 이렇게하면 3개의 배열 요소가 0으로 초기화 되긴하는데
    char Tmep[3] = { 1, }; 해버리면 1, 0, 0 이런식으로 초기화 되거든요.
    memset으로는 1, 0, 0이 나올수 없으니..0이 아닐때는 뭐 물같은걸 끼얹나?
    제가 아는 C언어 지식으로는 { .. } 이 안에 쓴 숫자만 그 숫자로 초기화 된 후에
    나머진 0으로 초기화 하는걸로 알고 있습니다.

  4. denoil 2012/05/21 10:32  댓글주소  수정/삭제  댓글쓰기

    역시나 테스트 해보니 결론은
    char Temp[3] = {}; <- 이렇게 {} 하면 모두 0으로 초기화 되고
    {..} <- 이 안에 들어가는 숫자는 0으로 초기화 후에 해당 숫자로 셋팅 하는가 보네요.
    포프형한테 추가 작성해달라니깐 그냥 댓글달으라네요-_-;;

  5. swkwon04 2012/05/21 11:12  댓글주소  수정/삭제  댓글쓰기

    굳이 순서를 따지자면..

    char temp[10] = {1, 2, }; 를 MSVS2010 32bit 에서 테스트해본 결과..

    temp[0] = 1;
    temp[1] = 2;
    가 먼저 이루어 지고, temp[2] ~ temp[9] 까지는 4Byte씩 0으로 초기화 합니다.

    char temp[10] = {0, }; 이 컴파일 후 memset을 호출 하는 이유는 컴파일러가 컴파일 하면서
    코드를 바꾸는 것 아닌가 싶어요.

  6. 책읽는잉여 2012/05/21 14:32  댓글주소  수정/삭제  댓글쓰기

    제가 알고 있기론..
    기본생성자를 호출해주는 것으로 알고요..
    builtin type 의 경우에는 제로초기화가 적용되는 것으로 알아요..

    int AAA[3] = {4,};
    이건 AAA[3] = {4, int(), int() };
    이렇게..

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) 현상이 왜 일어나는 겁니까?
여러분도 다 알고 있는 거예요. 먼지나 수분과 같은 입자가 공기중에 있고, 그 입자들이 빛을 사방으로 튕겨내기 때문 아닙니까? 다 알고 있으시잖아요.

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

 

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

 

네가 넣었구나.

 

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

 

 

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

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

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

 

  1. Area 라이트 같은거 말씀하시면 곤란... 포토매트릭 라이팅 말고 진짜 기본 라이팅 말하는 거니까요. [본문으로]
  2. 알아듣는 분도 물론 계십니다만, 일반적으로는 낮설어 하는 용어입니다 [본문으로]
  3. 전부 얘기하면 그것만 도데체 양이 얼마나 될지 알 수 없으므로... [본문으로]
저작자 표시 비영리 동일 조건 변경 허락

댓글을 달아 주세요

  1. 끼로 2012/05/16 09:21  댓글주소  수정/삭제  댓글쓰기

    헙! 벌써 끝나다니! 더 써주세요!

  2. 친절한티스 2012/05/17 12:18  댓글주소  수정/삭제  댓글쓰기

    왜케 짧아요!!

  3. 디퍼드H 2012/05/17 13:56  댓글주소  수정/삭제  댓글쓰기

    흠, 흥미로운 내용이네요. Point Light의 그림자 구현할때에는 또 omni-directional shadow mapping
    이라고 하더군요. omni light가 point light라는게 이해가 가긴해요~!^^ㅋ

  4. ozlael 2012/05/17 14:14  댓글주소  수정/삭제  댓글쓰기

    왜케 짧아요!!(2)

  5. 대마왕J 2012/05/18 03:41  댓글주소  수정/삭제  댓글쓰기

    열화와 같은 성원에 약간 양을 늘렸습니다. 아 새벽은 좋아 (펑)

  6. 끼로 2012/05/18 09:57  댓글주소  수정/삭제  댓글쓰기

    우오오오오 늘었다!!!!!

  7. bighey 2012/05/18 11:32  댓글주소  수정/삭제  댓글쓰기

    짧아지니까 적응 안되긴해요..

  8. Lyn 2012/05/18 13:11  댓글주소  수정/삭제  댓글쓰기

    음... 그런데 빛이 약해지는건 광자가 퍼지면서 영향을 받는 면적이 거리에 비례해 넓어지기 때문이 아닌가요?

    • 대마왕J 2012/05/18 16:10  댓글주소  수정/삭제

      어쨌거나 퍼지면서 약해지는건 사실이니까요. 그 퍼지는 이유는 공기중에 들어있는 각종 요소들이겠고...

  9. 칵어두들두 2012/05/20 02:11  댓글주소  수정/삭제  댓글쓰기

    전혀 몰랐네요... 많이 알아가요

Posted by 디퍼드H

안녕하세요. 이번에 새로 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를 어떻게 만들고 실행시키는가 하는 부분도 진도를 나가면서 다루어 나갈 생각입니다. 오늘 했던 내용이 혹시 이해 안되는 부분이 있더라도 걱정하세요. '아, 이런걸 할거구나.' 정도만 아시면 충분합니다. 다음시간부터는 절대 이런 생각을 가지면 안되고 철저히 해야하구요.

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

댓글을 달아 주세요

  1. 김포프 2012/05/18 00:06  댓글주소  수정/삭제  댓글쓰기

    어라? 이정도 내공의 글이 올라오면... 컴포넌트 글 쓰시던 끼로님이 설자리가 없어지는데..... 끼로님 어쩌죠? 레드불 더 마시면서 야근 두배로 더 하셔야겠어요(그럼 하루 36시간 일하나?) ㅋㅋ

  2. 끼로 2012/05/18 00:34  댓글주소  수정/삭제  댓글쓰기

    그나저나 그러면 기다리다보면 디퍼드H님이 컴포넌트 시스템에서의 메시지통신에 대해서 설명을 자세하게 해주시려나.. 사실 그거 이제 곧 써야 할 차례인데 귀찮아서 안쓰고 있..

    • 디퍼드H 2012/05/18 04:33  댓글주소  수정/삭제

      저같은 인디게임 밖에 안만들어봤던 초짜 프로그래머와 끼로 님의 메세지 통신 방법을 둘다 보여주는 것도 나쁘지 않도고 생각합니다. 서로 아마 방법이 약간 다를테니깐요. 끼로님꺼 메세지 기대할게요~^^ 저도 끼로님 메세지 통신 궁금합니다.

  3. 크로스 2012/05/18 00:54  댓글주소  수정/삭제  댓글쓰기

    이제 여기 자료만 잘 봐도 게임한둘쯤 뚝딱 나오겠군요...ㄷㄷㄷ

  4. 친절한티스 2012/05/18 01:46  댓글주소  수정/삭제  댓글쓰기

    메시지 통신에 대해 자세히 알고 싶어요~

    • 디퍼드H 2012/05/18 04:35  댓글주소  수정/삭제

      우선 끼로님 메세지 통신이 먼저 올라오지 않을까요? 끼로님 메세지 통신이 더 좋을지도... 제가 만든건 우체국 시스템을 따서 만든거라~ 좋을지 않좋을지 우선 최대한 빨리 올릴게요~^^

    • 끼로 2012/05/18 09:54  댓글주소  수정/삭제

      음? 저는 메시지통신 이야기 안할 생각인데.. 참고로 저는 컴포넌트 기반 게임오브젝트를 쓰고 있긴 하지만 메시지통신은 안써요 ㅎㅎ 그래서 잘 몰름 그래서 고민중이었는데 디퍼드H님이 써주실꺼라 생각하고.. 걍 다른거 쓰려고 함 ㅋㅋ

    • 디퍼드H 2012/05/20 10:29  댓글주소  수정/삭제

      아... 저의 부담감이 배가 되겠군요..ㅜ

  5. flyingcode 2012/05/18 03:04  댓글주소  수정/삭제  댓글쓰기

    ㅎ 제가 지금 공부하려는 내용이 나온것 같네요.

    정주행하면서 따라가겠습니다. ㅋ 기대하고 있겠습니다.

  6. JazzEz2dj 2012/05/18 10:12  댓글주소  수정/삭제  댓글쓰기

    만쉐~!

  7. 바하무트 2012/05/18 17:41  댓글주소  수정/삭제  댓글쓰기

    오오... ^^ 이제 홈메이드 엔진 전성시대가~

  8. 김포프 2012/05/20 08:02  댓글주소  수정/삭제  댓글쓰기

    생각해보니 전 transform을 언제나 entity 컴포넌트에 넣곤 했었는데... transform이란 이름이 따로 있으니 좋네요....

    • 디퍼드H 2012/05/20 10:27  댓글주소  수정/삭제

      프로젝트를 하면서, transform을 컴포넌트로 하기보다 spatial컴포넌트를 만들어서 transform 클래스를 컴포지션하는게 scene graph 짜는데 도움 될 것 같다는 생각은 드는데 우선 그냥 transform 컴포넌트를 따로 두었어요~^^