슈퍼 마리오 3D 랜드

감상 2011/12/15 20:55

 수 개월 간 블로그를 방치해두고 있었는데, 뭐라도 포스팅을 하지 않으면 영원히 쓰지 않을 것 같아서 이렇게 감상 포스팅 하나 올림. 요 근래 젤다의 전설이나 재미있게 즐긴 게임들이 많아서 몇 개 더 쓸지도 모르겠다. 쓰다 만 글들이 몇 개 있기도 하고.



 

더보기


 

'감상' 카테고리의 다른 글

슈퍼 마리오 3D 랜드  (0) 2011/12/15
마법소녀 마도카 마기카 - 10화까지의 감상  (0) 2011/03/20
슈퍼 마리오 갤럭시 2  (0) 2011/02/12
소셜 네트워크  (0) 2010/12/07
서태지 8집을 돌이켜보며  (2) 2010/12/02
기계식 키보드 이용기  (0) 2010/11/27
Trackback 0 : Comment 0

NDC 간략한 참관기 #3

개발 2011/06/19 02:52


 4일차 참관기. 갈수록 참관기를 빙자한 자기 주장이 되어가고 있다. (...) 카메라 시스템쪽 세션도 좋았는데, 발표 자료가 올라오지 않는데다 들은지 2주가 넘어가서 기억이 가물 가물하다. orz 젭알 올려주셉습셉...



 - 온라인 게임 처음부터 끝까지 동적언어로 만들기 (이승재 선임연구원 / Nexon)

 ( 참조 링크 :
리카넷:가짜블로그, runivityNDC 강연 노트 )

 요즘은 lua를 쓰는 일은 별로 없지만, lua 정도의 추상화 수준을 제공하는 언어를 가지고 게임 로직을 통째로 짤 수 있다면 참 편리할 것이라고 생각했는데, 그 일이 실제로 일어났습니다.


 게임에서 C++을 사용하는 여러 가지가 있겠지만 제일 큰 건 아무래도 퍼포먼스가 아닐까. 그런데 요즘의 클라이언트 기준으로 볼 때 게임 로직이 실행 시간에서 차지하는 비율은 잘해야 10% 안이고, 아주 빡세게 로직을 돌리는 게임이 아닌 이상에야 다들 엇비슷한 수준의 수행 시간을 차지한다. 그렇다면 로직의 수행 시간을 희생하더라도 좀 더 생산성을 고려하는 방향으로 가면 좋을 거라는 생각을 늘 해왔는데, 데스크탑 히어로즈는 그걸 온라인 게임에서 직접 적용한 사례로써 꽤 중요한 기점이 아닐까 생각한다.

 사실 지금도 대부분 게임에서 게임 로직을 스크립트나 데이터의 형태로 빼는 정도의 작업은 다들 하고 있지만, 어쨌든 핵심적인 구조와 프레임워크는 전부 C++로 구현하는 형태라 조금이라도 핵심적인 로직을 건드리려고 하면 컴파일이 뒤따라온다. 만약 그런 로직들까지 모조리 데이터/스크립트로 빼려면 구조가 복잡해지고, 또 설계하고 스크립트에 바인딩하는데 들어가는 개발 비용도 만만찮다. 특히나 lua를 C++에 쌩으로 바인딩하는건 거의 어셈을 직접 다루는 기분까지 들 정도라 -_-; luabind 같은걸 쓰면 훨씬 쉽긴 하지만 컴파일 시간이 안드로메다로 떠남.


 어쨌건 수행 시간 제약이 없다면 이런 비용을 전부 무시하고 동적 언어만 가지고도 게임을 만들 수 있다! 동적 언어로 짜면 좋은 점은 당연히 생산성이 급격하게 올라간다는 점. C++에서는 제공되지 않는 고레벨 언어 기능들을 사용하면 작성하는 코드나 구조부터가 크게 달라진다. 물론 이 쪽이 훨씬 간결하고 높은 표현력을 가지고 있을 것이다. 데스크탑 히어로즈의 로직은 약 22000 라인이었다고 하는데 C++로 짰으면 적어도 5~6만 라인은 나오지 않았을까 하는 생각이...
 
 이를테면 코루틴을 쓰면 기존 언어들에서는 State pattern이나 IoC 등을 동원해서 삽질하던 코드들이 yield 하나로 대체되어 훨씬 간결해지고 응집도도 높아진다. 그리고 클로저를 사용하면 코드 흐름의 응집성이 크게 개선되는 것과 더불어 제공되는 고차 함수의 개념으로 인해 언어 차원에서 제공되지 않는 임의의 제어 구조를 적극 작성, 활용하게 되어 불필요한 코드가 크게 줄어든다. lua에서는 테이블의 형태로 제공되는 덕타이핑[각주:1]을 쓰면 기존의 OOP 언어들에서는 별별 삽질을 해서 구현하던 컴퍼넌트 시스템의 유연성을 날로 먹을 수 있다. 동적 언어와는 연관이 덜 하지만 멀티메써드는 싱글 디스패치의 한계로 인한 한계를 넘어서서 프로그래머로 하여금 훨씬 간결한 설계를 할 수 있도록 도와준다.


 그렇다고 동적 언어에 강점만 있느냐 하면 또 그렇지만은 않다. 타입 안전성 체크가 컴파일 시점에서 런타임으로 넘어감으로 인해 실수를 찾아내는 것은 훨씬 어려워진다. 세션에서 언급된 문제점 중 가장 강조하시던 부분은 바로 오타. lua를 쓰던 사람들이라면 다들 치를 떨 것이다. - -; [각주:2] 동적 언어 커뮤니티에서는 단위 테스트를 통해 이런 실수를 잡을 수 있다고 주장하지만, 개인적으로 언어 차원에서 잡아줘야 할 무결성 문제를 사용자로 하여금 직접 잡도록 하는 것은 그 자체로 심각한 결함이라고 생각한다. 단위 테스트는 어디까지나 명세의 검증을 위해 사용되어야 하는거고, 코드의 무결성 검증은 컴파일러가 해야 할 일이 아닐까?

 개발 환경의 한계도 지적되었다. 언어 개발 커뮤니티들을 돌아보다 보면 언어나 플랫폼의 성공에는 언어 자체의 우수함 보다는 제공되는 개발 환경이 더 많은 영향을 끼친다는 의견도 많은데, 이는 어느 정도 현실을 반영하고 있다. 이클립스 없는 자바나 VS 없는 Win32 플랫폼이 이 정도로 성공할 수 있었을까? 이런 면에서 볼 때 풀 타임 작업자가 부족할 수 밖에 없는 오픈된 언어들에는 한계가 있다고들 한다. 그래도 lua 같이 커뮤니티가 큰 언어들은 기본적인 도구들은 마련되어 있으니 좀 나은 듯.


 추가로, 예전부터 해오던 개인적인 생각이 하나 있는데... 좀 더 규모가 크고 복잡한 프로젝트에서 고레벨 언어를 메인으로 쓰기 위해서는 이종 Heterogeneous 언어 간의 상이한 동시성 모델도 생각해봐야 하지 않나 싶다.

 예를 들어 lua 같은 경우는 ANSI C의 한계로 인해 멀티 쓰레드라는 개념이 거의 배제되어 있다. 고로 저레벨 코드의 지원 없이 lua를 쓰레드 안전하게 쓸 수 있는 방법은 공유되는 데이터 자체를 없애고 VM을 나누어 메시지 패싱으로 가는 것 뿐이다. [각주:3] 공유되는 데이터가 아주 적고 논리적으로 잘 분할이 된다면 VM을 다수 올리면 되겠지만, 그게 가능하면 멀티 쓰레딩보단 마영전 서버처럼 프로세스를 여러개 올리는게 합리적이다. 이렇게 되면 통신 수단을 구현하는 저레벨 코드의 한계로 인해 기존 동적 언어의 강점 중 상당수가 무력화될 수 밖에 없다.
 
 예를 들어, 내가 아는 한도에서는 C 코드에서 lua의 함수나 테이블, 코루틴 등의 객체 내부 내용들에 직접 접근할 수 있는 방법은 없다. 다시 말해 lua VM 간의 쓰레드 세이프한 메시지 패싱를 구현한다 쳐도 lua 단에서 이를 투명하게 사용하는데 제약이 있거나, 가능하더라도 그 비용이 (수행/개발 모두) 적지 않을 거라는 이야기다. 비프로그래머들도 접근 가능하다는 강점을 살리기 위해서는 이런 부분에 대한 투명성이 중요할텐데... 이브 온라인에서는 Stackless python을 메인 로직에 적극 쓰고 있다고 하는데, 이 부분에 대해서는 어떤 해법을 적용했는지 궁금하다. [각주:4] 의외로 현실적인 수준의 해법을 쓰고 있을지도 모르겠지만...


 고레벨 언어를 상용 프로젝트에서 이 정도로 적극적으로 사용하는 사례는 이브 온라인 정도 밖에 들어보지 못했는데, 이렇게 한국에서 적용한 사례를 직접 보게 되니 여러 모로 자극도 되고, 바쁘다는 핑계로 손 놓고 있던 사이드 프로젝트에도 다시 눈을 돌리게 되는 계기가 되어 좋았다. 언어를 만들어야지 -> 그럼 파서가 필요하잖아? -> 근데 파서 제네레이터가 만드는 코드들은 예쁘지가 않아 -> 그럼 내가 만들어야지 -> LALR는 쓰기가 짜증나니 GLR 파서를 만들자 -> 근데 논문이 이해가 안 간다... -> 낮잠이나 자자 -> ... 의 Yak shaving 루프를 또 한번 타기 시작 (...)



 - 슈퍼클래스 : 단순함을 유지하여 개발생산성 향상시키기 (김성익 대표 / 소프트네트)

 ( 참고 자료 : NDC 공식 블로그, NDC 강연 노트 )

 에 뭐랄까... 이 세션에 대해서는 상당히 비판적/공격적인 시각으로 접근할 것 같다. 발표자 분께 악감정이 있는 것은 아니고, 근본적으로 좁힐 수 없는 의견차 때문에 그런 것이니 양해해주시길 - -;


 일단 발표의 기저에는 점점 복잡해져 가는 소프트웨어 구조를 간단하게 만들어 설계 과정과 이를 구현하는 데에서 치르는 개발 비용을 최소화하자는 아이디어가 있다. 그 와중에 모든 기능을 슈퍼 클래스에 몰아 넣자는 역발상이 나온 것으로 보인다. 실제로 적용해본 결과 요구 사항을 구현하는데 들어가는 비용이 줄었으며, 훨씬 더 단순하고 직관적인 코드를 만들 수 있었다고 한다. 그 과정에서 단점으로는 "코드가 조잡해지는 느낌, 하드 코딩하는 느낌"이 있었지만, 이는 인식의 전환을 하면 된다는 주장이다.

 
 소프트웨어 공학이 발전하면서 경험적으로 얻은 통찰 중 하나로는 유지보수하기 쉬운, 다시 말해 이해하고 변경하기 쉬운 프로그램들은 대개 응집성 Cohesion이 높고 결합성 Coupling이 낮다는 사실이 있는다. 이는 어느 언어를 사용해서 소프트웨어를 개발하건 (심지어는 어셈블리라도!) 적용되는 원칙으로, 좋은 코드일수록 구조가 뚜렷하게 드러나는 것은 이런 원칙을 충실하게 지켰기 때문이다. 클래스/인터페이스/다형성/정보 은닉 등의 도구들의 본질은 이러한 원칙을 코드에 명시적으로 드러내고, 더 나아가 차후에 추가되는 코드에도 이 원칙들을 강제하는 것이라고 생각한다.

 어떤 문제에 대해 어떻게 설계하여 어떻게 구현하건 좋은 코드를 짜다 보면 그 나름의 구조가 있을 것이다. 이 세션에서 주장하는 바는 복잡하게 언어적인 도구를 사용하여 이런 구조를 강제하는 대신 코드 차원에서 알아서 잘 정리하면 된다는 이야기로 수렴된다는 느낌을 받았다. 다시 말해, 설계 과정을 통해 문제가 가지고 있는 구조적인 복잡도를 별도로 분리해내는 대신, 이를 전부 구현부로 떠넘기겠다는 이야기다.


 이렇게 하면 확실히 코딩하긴 편할 것이고, 구현도 빠를 것이다. 구조에 대한 고민이 없어도 동작하게는 만들 수 있기 때문에... 문제는 유지보수다. 코드를 원래 작성했던 사람이라면 코드 전반의 의도나 흐름, 구조를 이해하고 있기 때문에 이러한 기조를 일관되게 유지할 수 있을 것이다. 그런데 그 사람이 떠나면 어떻게 될까? 차후 유지보수되는 코드들에도 원래의 의도가 제대로 반영될 수 있을지 의문이다. 언어적/구조적으로 아무런 제약도 없는 상황에 놓인 코드가 어느 정도의 속도로 스파게티화 하는지는 대차게 말아 먹은 많은 수의 소프트웨어 프로젝트들이 보여주고 있다.

 사실 복잡한 구조는 가급적 지양하는게 맞지만, 그렇다고 장점이 없는 것은 아니다. 어떤 문제든 본질적으로 내재하고 있는 복잡도의 정도가 있을 것이고, 이는 구조/구현부 등에 나누어질 것이다. 불필요한 일반화를 하지 않았다고 가정할 때, 구조를 잘 설계할수록 개개 모듈들의 구현이 간단해지는 경향을 띈다. 다시 말해 이해하기 쉽다. 일반적으로 구체적인 내부 구현에 비해 전반적인 구조, 모듈 간의 관계, 명세 등을 기억하는게 훨씬 쉽다. 그렇다면 구조보다는 구현이 간단하고 이해하기 쉬운게 유지보수에 유리하다는 이야기.
 

 슈퍼 클래스에 로직을 몰아 넣는 방법으로 얻는 유연성이라고 하는 것 역시 내가 보기엔 크게 효용이 있을 것 같지는 않다. 높은 응집성과 낮은 결합성이라는 원칙을 충실하게 따르려고 하면 어떤 접근법을 사용하든 코드에는 그에 따른 구조가 생길 것이고, 이렇게 잡힌 구조가 바뀌어야 한다면 문제의 본질 자체가 변한 상황이라고 할 수 있다. 이런 상황에서 슈퍼 클래스를 이용한 접근법을 적용하니 변경책의 복잡도가 확연하게 줄어드는 상황이 얼마나 될까? 내 생각엔 응집성과 결합성의 원칙을 포기하는 이외의 경우는 없을 것 같다. goto 쓰면 편할 상황이 많지만 가급적 안 쓰는 데에는 다 이유가 있는 것과 비슷한 이치다.

 이를테면 pt 28페이지에서 좀 극단적인 시나리오를 제시하고 있는데, 내 생각엔 저 경우는 애초에 해결해야 하는 문제의 본질 자체가 게임 시스템 전반에 걸쳐 있는 케이스다. 어떤 방법론을 쓰건 간에 이 문제를 제대로 해결하려면 이동, 이벤트, 조건, 이펙트, 버프, 스탯 등의 게임 컨셉들을 엄밀하게 정의한 뒤, 이들 간의 상호 작용을 통해 해결하는게 이상적이다. 정도의 차이는 있겠지만, 이런 방식으로 해결하면 유사한 요구 사항은 앞으로 데이터 레벨에서 처리할 수 있을 것이다. 하지만 이를 위해서는 슈퍼 클래스에 몰아 넣건 뭘 하건 모델링/설계 자체의 비용을 피할 수는 없지 않을까? 만약 이 비용을 피할 수 없다면 슈퍼 클래스를 사용하는 의미가 어디에 있는지 의문이 든다.
 
 pt에서 들고 있는 사례들을 보면[각주:5] 이런 원칙들을 포기하는 방향을 제시하는 것으로 보이는데, 이런 예외가 하나 둘 쌓이기 시작하면 유지 보수가 불가능해지는 것은 시간 문제라고 본다. 특히나 해당 코드를 직접 작성한 사람이 아니라면 그 의도를 곡해할 가능성도 높을테고...


 물론 구조를 표현하기 위해서 추가적으로 들어가는 노이즈를 줄이는 것은 틀림 없이 의미 있는 일이고, 생산성에 도움이 된다는 점은 부인할 수 없다. 또한 다루는 문제의 본질에 비해 복잡한 구조는 최대한 지양해야 한다는 것 역시 동의한다. 하지만 이런 문제들을 해결하기 위해 슈퍼 클래스에 모든 로직을 몰아 넣는 구조가 가지는 근본적인 문제점 - 높은 결합성, 낮은 응집성의 코드로 수렴하게 될 위험성 - 을 생각해보자면 결과적으로 손해 보는 트레이드 오프가 아닐까 생각한다. 순간적인 생산성 향상을 댓가로 차후의 유지보수성을 지불하는 것이니 말이다.

 다만 프로토타이핑용 코드에 적용하는 경우라면 좋은 접근법이 될 수 있다. 이 경우는 한번 쓰고 버릴 코드이고, 빠른 구현 자체가 요구 사항이기 때문이다. 하지만 pt의 문맥을 볼 때 이건 발표자 분의 의도와는 거리가 있는 듯 하다. 대충 짠 프로토타이핑 코드와 좋은 구조의 코드가 공존하는 게 불가능한 것이 아니기도 하고 말이다.


 아마 발표자 분과 내가 가진 입장 차이는 아마 리팩토링에 대한 시각차에서 오지 않을까 하는 생각이 든다. 나 같은 경우는 꼭 필요하다면 리팩토링은 최대한 공격적으로 진행되어야 한다는 입장이다. 하지만 발표 문맥을 볼 때 발표자 분 같은 경우는 구조 변경에 대한 부담감을 가지고 있는 듯 하다. 이런 점에서 이런 발상을 하고 적용한게 아닌가 싶은데...
 
 하지만 이게 좋은 해법인가를 묻는다면 좀 회의적이다. 사실 많은 사람들이 구현 비용과 유지 보수성 사이에서 비슷한 고민을 했고, 그 결과 일단은 빠르게 구현한 뒤 이를 좋은 구조로 쉽게 리팩토링할 수 있도록 환경을 구축하는 것이 적당한 선이라는 결론을 내렸다. 이런 점을 생각해보면 단위 테스트의 커버리지를 높이고 적극적인 커뮤니케이션을 통해 systematic한 기획을 하도록 유도하여 리팩토링의 비용을 낮추는 정공법으로 가는 게 구조에 대한 고민/설계를 피하는 것보다는 더 근본적인 해법이 아닐까... 물론 현실적으로 쉽지 않은 선택이지만.


 번외지만 컴파일러 마법 부분도 문제가 있다. 자주 호출되는 루틴에 대해서는 컴파일러에 의하여 Constant propagation -> Dead code elimination 최적화를 사용하면 된다는 이야기인데, 문제는 예시로 든 코드에서 비교 대상이 되는 멤버 변수는 상수가 아니라는 점에 있다. JIT가 적용 된 것도 아니고 저런 최적화를 할 수 있을리가 없는데... - -; 이건 명백하게 잘못된 내용이 아닌가 싶은데 실제 게임 코드 레벨에서 확인은 하고 pt에 넣으신건지 모르겠다. (개인적으로는 이 부분에서 이 세션에 대한 신뢰를 잃었다.)

  1. 정적 타입 언어에서는 go의 interface나 Haskell의 Type class 등의 모습으로 구현된다. 이 쪽은 덜 유연한 편이지만. [본문으로]
  2. 발표자분께서는 이 문제의 근본적인 해결을 위해 아예 정적 타입 루아인 Dal이라는 언어를 만들고 계시다. [본문으로]
  3. "프로그래밍 루아" 책의 뒷 부분에서는 마치 IPC를 하듯 루아 VM 간의 통신을 도와주는 모듈을 C 코드 차원에서 구현하는 예제가 나온다. [본문으로]
  4. Lua와 마찬가지로 Stackless python의 설계에는 선점형 멀티태스킹에 대한 고려가 이루어지지 않은 것으로 알고 있다. [본문으로]
  5. 메시 모델의 손 위치를 사운드 출력부에서 참조한다거나, 카메라 객체에서 플레이어 머리 본의 TM을 참조하는 등 [본문으로]

'개발' 카테고리의 다른 글

NDC 간략한 참관기 #3  (4) 2011/06/19
NDC 간략한 참관기 #2  (0) 2011/06/12
NDC 간략한 참관기 #1  (0) 2011/06/04
기본 타입 간 (무분별한) 변환은 자제합시다  (3) 2011/05/14
Visual Studio는 C/C++을 싫어해 (?)  (3) 2011/02/27
Bitwise Switch  (2) 2011/02/19
tags : ndc, 개발, 참관기
Trackback 0 : Comments 4
◀ PREV : [1] : [2] : [3] : [4] : [5] : ... [14] : NEXT ▶