7. 카바티나 스토리의 루아
맵 별 스크립트
• 던전: 어떤 몬스터들을 다 잡으면 다음 몬스터들을 젠,
무슨 스위치를 누르면 문을 연다 등
• 퀘스트: 어디에서 무슨 아이템을 사용하면 퀘스트 완료 등
이벤트
• 언제부터 언제까지 경험치 두 배
• 매일 몇 시에 PVP 맵에 입장 가능
8. 반성
더 일찍부터 & 더 많이 쓸 걸 그랬다
몬스터 AI는 자체 포맷, 맵 스크립트는 루아
• 상호 통신 불가능
• 몬스터 AI도 루아로 짰으면 개발이 편했을 텐데…
사실 MMORPG라 느릴까봐 겁나서 못한 거지만
초기에 만든 거대 보스 AI는 C++…
9. 반성
더 일찍부터 & 더 많이 쓸 걸 그랬다
라이브 도중 PvP 맵 추가
• 서버 로직, 클라이언트 UI 모두 루아로 만들었음
• 서버 C++ 수정은 아주 적었다 (유저간 공격가능 플래그 켜는 정도)
10. 현실의 벽
바인딩
상호 함수 호출
객체 참조 유지
자료구조 뒤져보기
디버깅
…
(언어 사이를 넘어다니는 비용)
11. 다양한 바인딩 도구가 있지만,
바인딩 비용을 0으로 만드는 것은 불가능!
수행 시간 비용 말고 작업 비용을 말합니다.
적절한 도구를 쓰면 비용을 상당히 줄일 수 있지만
자잘한 비용이 계속 들어가게 되고
이게 누적되면서 전체 설계에 영향을 미칩니다.
12. 엔진
엔진과 로직과 스크립트
프로그래머가 만든다
게임 로직 프로그래머가 만든다
데이터,
스크립트
프로그래머가 아닌 사람이
주로 만든다
게임 로직과 데이터/스크립
트 사이는 기획적인 필요 때
문에 복잡하고, 변경이 자주
일어납니다.
엔진과 게임 로직 사이는 비
교적 간결하고, 변경이 자주
일어나지 않는 편입니다.
13. 스크립트 붙이기는 어차피 해야 한다
보다 단순하고 변경이 덜 일어나는 곳에
언어 바뀌는 경계를 긋자
엔진과 로직과 스크립트
게임 로직 프로그래머가 만든다
16. 총 개발기간 17개월
프로그래머 두 명
(한 명은 기획 및 팀장 겸임 + 학업 병행)
전작은 3D MMORPG였고 데스크탑 히어로즈는 2D 멀티플레이어 액션
게임이었습니다. 게임 로직이나 툴, 엔진 코드 등은 전혀 가져올 수 있는
것이 없었습니다.
아, UI 라이브러리 가져왔군요. 게임브리오로부터 자체 2D 엔진으로 포
팅하느라 고생 좀 했지요.
17. 코드 분량
루아 코드 분량 툴로 편집하는 파일 제외
분류 LOC 편집 주체
게임 로직 & 툴 22,000 프로그래머
스테이지 8,000 기획자
테이블 11,000 기획자
19. local function TryStageStart( stageNum, party, animation, onOK )
Request( 'StageStart', stageNum, party, function( res )
if res ~= 0 then
onOK()
animation( function()
SwitchStage( stageNum )
end )
else
MessageBox{ text = ‘스테이지를 시작할 수 없어요’ }
end
end )
end
낯선, 문법
20. 낯선, 문법
다른 언어:
다른 brain mode
뒤에서 더 설명하겠지만, C++로 코딩할 때와 루아로 코딩할 때 굉장히 다
른 코드가 나옵니다. 동작, 관용어구, 간결하게 짤 수 있는 방식이 전혀 다
르기 때문인데요. C++과 루아는 문법이 매우 다르게 생겼기 때문에, 자신
이 어떤 언어를 사용하고 있는지를 매 순간 느끼게 됩니다.
21. 낯선, 문법
프로그래머 자체 진단 기능:
if cond then
doSomething();
}
가끔 이런 코드가 튀어나오면
‘아 좀 쉬어야겠다’ 혹은 ‘정신 좀 차려야지’
22. 빌드 시간
C++로 게임 로직을 만들면…
• 기획자가 기능을 요청한다
• 될 때까지
{ 코드를 고치고 빌드하고 테스트한다 }
• 커밋한다
• 빌드머신이 빌드한다
• 기획자가 업데이트한다
빌드 시간 자체보다, 빌드로 인해 몰
입이 깨지는 게 더 문제입니다. 빌드
몇십초 넘어가면 다들 웹서핑이나
IRC 하지 않나요? ㅎㅎ
23. 빌드 시간
루아로 게임 로직을 만들면…
• 기획자가 기능을 요청한다
• 될 때까지
{ 코드를 고치고 빌드하고 테스트한다 }
• 커밋한다
• 빌드머신이 빌드한다
• 기획자가 업데이트한다
루아로 프로그래밍하면 몰입이 깨질
틈이 없이 집중된 시간을 계속 유지하
게 됩니다. 덕분에 8시간 근무하고 나
면 굉장히 피곤한데요, 구현하는 분량
은 C++로 같은 시간 근무할 때보다 훨
씬 많기 때문에 야근을 거의 안 했습니
다. 사실 할 수도 없죠. 피곤하니까요.
24. 빌드 시간
수정이 간단한 경우에는 종종 이렇게도 함
• 기획자가 기능을 요청한다
• 기획자 자리에서, 될 때까지
{ 코드를 수정하고 테스트한다 }
일종의 페어 프로그래밍
프로그래밍의 노하우… 같은 게 전달되지 않을까 기대합니다. 대화를 통해 스
펙을 분명히 하고 (그래서 미스커뮤니케이션으로 엉뚱한 기능을 만드는 경우가
없고), 기능의 사용법을 동시에 전달하는 효과도 있고요.
스크립팅 하시는 분을 의미합니다
25. 에러의 격리
서버에서 유저당 VM 하나 할당
안전한 예외처리 가능
• 유저 요청 처리하다 에러 나면,
커넥션 끊고 유저 VM을 제거해 버린다
• PHP 에러 났다고 아파치가 죽지는 않듯이
• 물론 에러 로그는 남깁니다
33. 테이블: 툴 제작
로직 영역의 데이터 편집 툴을 루아로 작성
• 테이블을 루아 문법으로 저장
• 파서 공짜!
• 툴 기능 추가에 아주 민첩하게 대응
엔진 영역의 데이터는 순수 C++ 툴로 편집
(루아 loadstring)
34. 코루틴
비선점형 멀티스레딩: 명시적인 문맥 전환
파이버(Fiber) 비슷
시간 흐름에 따라 진행되는 코드를 짜기 좋다
• 게임 프로그램에서 매우 자주 나오죠!
35. 코루틴
활용 예: 몬스터 AI
• 생성되면 먼저 ‘gen’ 시퀀스를 재생하자
• ‘gen’ 시퀀스가 끝나면 ‘stand’ 시퀀스를 재생하자
• 내 주변 영역에 적이 들어오기까지 기다리자
• 일반 AI 모드로 전환하자
이 모든 과정이 함수 한 개!
C++로 짜려면 끔찍하죠;; State pattern 같은 것을 동원해야 합니다.
36. 코루틴
활용 예: 서버에 캐시샵 구매 요청을 보내면
• 구매 패킷을 보내고, 구매 응답을 기다린다
• 구매 응답을 처리한다. 잔액 확인 패킷을 보내고, 응답을
기다린다
• 잔액 확인 응답을 처리하고 최종 응답을 클라로 보낸다
스레드가 블록되지 않습니다!
기다리기 시작할 때 yield,
응답이 오면 resume
37. 코루틴
다만 문맥 전환이 좀 까다로움. 적절한 래핑 필요
• 스레드 모델:
Sleep, 몹 그룹이 전멸할 때까지 대기 등
• 요청/응답 모델:
요청 보내기 함수를 호출하면 일시정지됨.
응답이 오면 코루틴 재개
• 몬스터 행동 모델:
한 틱 분량 작업을 처리한 후 yield할 의무가 있음
매 틱 자동 resume됨 예외: 인터럽트당한 경우. 넘어진다든지…
yield/resume
38. 클로저
코드 중간에 함수를 만들면
그 순간 보이는 지역변수들이 함수에 묶임
일종의 콜백. C++이라면 function object
C++로도 function object 만들면 다 구현 가능합니다. 하지만, 하고자 하는 일과
직접 관련이 없는 코드를 굉장히 많이 써야 하고, 이름을 지어야 하고, 코드 블록
을 다른 곳에 놓아야 합니다. 바인딩 경우처럼, 이런 사소한 비용이 모여서, 전혀
다른 코드가 나오게 됩니다(C++ 0x에서는 많이 나아졌습니다).
클로저가 없는 언어로만 프로그래밍해보면, 클로저를 쓰면 코드를 어떻게 짜게
되는지, 프로그래밍이 어떻게 편해지는지를 좀처럼 이해하기 어렵습니다. 직접
겪어보는 것이 가장 좋은 방법일 듯합니다.
사실 이 발표의 제목에서 동적 언어라는 단어를 썼지만, 저는 루아의 동적 타입
보다 클로저가 더 큰 매력이라 생각합니다.
39. 클로저
local function TryStageStart( stageNum, party, animation, onOK )
Request( 'StageStart', stageNum, party, function( res )
if res ~= 0 then
onOK();
animation( function()
SwitchStage( stageNum );
end )
else
MessageBox{ ... }
end
end )
end
40. 클로저
효능 및 효과
한 눈에 보여서 좋던데.
읽을 수 있잖아.
점프해서 쫓아다니는 게 아니라.
내 코드 뜯어고쳐 봤냐
고칠만하던? 이해할만하던?
프로그래머 L씨
(30세, 팀장)
43. 오타!
선언되지 않은 변수는 에러 없이 nil
전역변수와 지역변수는 쉽게 해결 가능
• strict.lua (기본 루아 배포에 포함)
객체 멤버가 문제!
• 결국 class를 만들었습니다…
(선언되지 않은 멤버 접근에 에러 발생)
꼭 씁시다!!!
44. 오타!
하지만 실행해 보아야만 오타를 알 수 있다
C++이면 컴파일러가 잡아주는데…
실제 배포되었던 코드!!!
• PolitelyOpenWebPaeg( url );
• ret을 써야 할 곳에 res
45. 메모리 사용량
서버에서 유저당 500KB 소모
• 유저당 VM 하나
• 모든 코드, 테이블을 각 VM에서 독립적으로 로드
메모리 사용량이 병목이 될 것 같은 상황
• 공통 데이터를 묶어서 스레드당 VM 하나로 빼냈습니다
• 300KB대로 줄였음
• 그래도 여전히 크다…
46. 열악한 개발환경
만들었던 도구들 (스크립트+로직 공용)
• 호출 바인딩 (lua_tinker 많이 참고함. 감사합니다!)
• C++에서 테이블 접근
• 에러 핸들링
• UTF-8 한글 식별자 허용 패치
• …
48. 열악한 개발환경
아직 더 필요한 것: 디버깅 기능 보강
• GUI, 브레이크포인트/스텝
• 외부 프로그램 붙여서 띄우기는 싫고
• 돌다가 죽으면 디버거 떠서 상황 파악할 수 있게
49. 클로저의 다크 사이드
dlg:SetEventHandler( ‘Buy’, ‘CLICK’, function()
Request( ‘BuyItem’, …, function( ret )
(dlg의 내용을 갱신)
end );
end );
dlg:SetEventHandler( ‘Close’, function()
dlg:Remove();
dlg = nil;
end );
1. Buy 눌러서 요청 보내 놓고
2. 닫기 버튼을 눌러버린 다음
3. 응답이 온다면
dlg가 nil이에요 T_T
51. 유지보수성
코드 양이 적은 대신 엄격한 규율이 필요
• 주석을 더 열심히 써야 한다
• 코드를 더 열심히 읽어야 한다
• 더 많이 테스트해야 한다
프로그래머가 많아져도 괜찮을까?
세대 교체가 이어져도 괜찮을까?
프로젝트 시작부터 지금까지 두 명. 교체 없었음
52. 어차피 프로젝트 열 개 중에 하나만 살아남을 거라면
장기적인 확장성, 유지보수성에 투입할 자원을 돌려서
출시 전까지 결과물에의 직접적인 기여를 늘리는 것이
옳은 선택일터다.
그래서 살아남는 하나가 되면?
그 다음은 그때 걱정하기
트위터, 2010년 9월
유지보수성
53. 클라이언트 해킹 문제
안티해킹툴의 보호를 못 받는 상황
• 데이터 패키징으로 최소한의 방어는 함
• 해킹을 염두에 두고 기획적인 안전장치 설치함