내용으로 건너뛰기
사용자 도구
로그인
사이트 도구
도구
문서 보기
이전 판
백링크
최근 바뀜
미디어 관리
사이트맵
로그인
최근 바뀜
미디어 관리
사이트맵
기술자료
작업공간
개인공간
사이트맵
추적:
kb:luacppbinding
이 문서는 읽기 전용입니다. 원본을 볼 수는 있지만 바꿀 수는 없습니다. 문제가 있다고 생각하면 관리자에게 문의하세요.
====== Lua C++ Binding ====== [[Cpp]] 프로그램에다가 루아 가상 머신을 집어넣어 보자. 목적은 간단한 함수 레벨의 튜토리얼이다. 유저 데이터니 클래스니 들어가면 상당히 복잡해지기 때문이다. 게다가 루아가 주(主)가 되지 않는 이상, 함수 정도로도 충분하다. 루아를 C++와 연동시킬 때 루아와 C++, 둘 중에 어느 쪽을 주인으로 삼을지는 상당히 중요한 문제다. 여기서 주인이라 함은, 실제 객체가 어느 쪽에 존재하느냐다. 음. 뭔가 표현하기가 어려운데, 게임을 예로 들면 PC나 NPC 등이 어느 쪽에 존재하느냐 이 말이다. * **루아 쪽에 게임 객체가 존재하는 경우:** C++ 쪽에서 루아 테이블을 받아와 조작한 후, 루아에게 알려주는 형식이 된다. 상당 분량의 코딩이 루아 쪽에서 이루어지고, 메인 루프도 루아 쪽에 존재하게 된다. 물론 그렇지 않을 수도 있다. 짜기 나름이니깐. * **C++ 쪽에 게임 객체가 존재하는 경우:** 루아는 C++ 쪽을 바로 액세스할 수 없으므로, 이전에 프로그래머가 가상 머신에다 등록한 래퍼 함수를 통해, C++ 상의 객체를 조작한 후 이를 C++에게 알려주는 방식이 된다. 결국 스크립트 언어를 얼마나 주언어로 많이 사용할 것인가에 대한 이야기라고 할 수 있는데, 위에서도 이야기했듯이 여기에서는 C++ 쪽을 주(主)로 한다. 또한 LuaPlus 같은 애드인 라이브러리는 사용하지 않는다. 함수로만 끝낼 것이기 때문에 필요가 없다. :) ====== 루아 설치 ====== 일단 루아 라이브러리가 있어야 한다. [[http://www.lua.org | 루아 공식 사이트]]에서 배포본을 다운로드받는다. 배포본의 압축을 풀면 include 디렉토리와 src 디렉토리가 있을 것이다. 다른 디렉토리도 많다만 다 필요없다. * include - 라이브러리 헤더가 들어있다. * src - 라이브러리 소스가 들어있다. * lib - 루아 표준 라이브러리가 들어있다. * lua - 루아 인터프리터가 들어있다. * luac - 루아 컴파일러가 들어있다. 이중에 실제로 필요한 것은 include, src, src/lib 디렉토리에 들어있는 파일 뿐이다. 따로 빌드해도 되지만 영 귀찮다면, lib 디렉토리에 들어있는 소스 파일도 프로젝트에 같이 추가해서 빌드해버려도 된다. 어쨌든 이렇게 빌드하면 헤더 파일과 라이브러리 파일을 가지게 된다! ====== 구현 목표 ====== 플레이어가 NPC를 클릭하면 플레이어의 명성에 따라 NPC가 다른 말을 출력하도록 만들어보자! GUI까지 다 집어넣어서 마우스 클릭 처리까지 하는 것은 아무리 생각해도 오바~이므로 콘솔 프로그램에서 간단히 시뮬레이션하는 정도로만 하자. ====== 구현 순서 ====== ==== C++ 쪽에서 게임 객체를 구현 ==== <code cpp> class Player { public: string name; // 플레이어의 이름 int fame; // 플레이어의 명성 }; extern Player g_player; class NPC { public: string name; // NPC의 이름 string click_script_name; // 플레이어가 클릭했을 때 실행할 스크립트의 이름 NPC(const string& n = "", const string& clicked = "") : name(n), click_script_name(clicked) {} }; </code> 일단 간단히 그냥 로컬에서 돌아가는 1인용 게임이라고 간주하고, 플레이어는 전역 변수로 둔다. ==== 루아에서 쓰일 래퍼 함수 구현 ==== <code cpp> // NPC의 대사를 화면에다 출력한다. int NPC_SAY(lua_State* L) { // 스택에서 메시지를 뽑아낸다. luaL_checkstring(pLuaState, -1); string msg(lua_tostring(pLuaState, -1)); lua_pop(pLuaState, 1); cout << msg << endl; // 스택에 푸쉬한 리턴값의 갯수를 반환해야한다. // 루아 쪽으로 넘겨야할 반환값이 없으므로 0을 반환한다. return 0; } // 플레이어의 명성치를 루아 쪽으로 넘긴다. int GET_PLAYER_FAME(lua_State* L) { // 플레이어의 명성치를 루아 스택에다가 푸쉬한다. lua_pushnumber(L, g_player.fame); // 스택에 푸쉬한 리턴값의 갯수를 반환해야한다. // 플레이어의 명성을 푸쉬했으니, 1을 반환한다. return 1; } </code> NPC의 대사를 출력하는 함수와 플레이어의 명성치를 루아 쪽으로 넘기는 함수다. C++ 쪽이 메인이 된다고 했으므로, 래퍼 함수는 그냥 약간의 파라미터를 주고받으며 C++ 쪽의 함수를 호출하는 함수가 대부분이 된다. ==== 루아 함수 구현 ==== clicked.lua <code lua> player_fame = GET_PLAYER_FAME() if player_fame > 500 then NPC_SAY("Oh, I know you!") else NPC_SAY("Who are you?") end </code> 플레이어의 명성치가 500 이상일 때만 아는 체를 하는 NPC 스크립트이다. ==== 루아 호출 부분 구현 ==== <code cpp> // 플레이어가 NPC를 클릭한 경우에 호출한다. // 스크립트를 무사히 실행한 경우에는 false를 반환하고, // 무언가 에러가 생긴 경우에는 true를 반환한다. bool OnNpcClick(lua_State* pLuaState, NPC& npc) { const string& filename = npc.click_script_name; // 먼저 스크립트를 파싱해서 청크를 만든 다음, 스택에 푸쉬 if (luaL_loadfile(pLuaState, filename.c_str())) { // 파싱 실패! return true; } // 트레이스 함수를 위에서 파싱한 청크 밑에다 끼워넣는다. // 트레이스 함수는 전역 테이블에 "_TRACEBACK"이란 이름으로 들어가있다. int base = lua_gettop(pLuaState); lua_getglobal(pLuaState, "_TRACEBACK"); lua_insert(pLuaState, base); // 실행한다. return lua_pcall(pLuaState, 0, 0, base) != 0; } </code> NPC를 클릭한 경우, 그 NPC의 click_script_name 파일 이름을 읽어와 해당 파일을 실행하게 된다. 디버깅을 좀 더 용이하게 하기 위해서 _TRACEBACK 함수를 스택의 맨 아래에다 집어넣고, lua_pcall을 호출하는 부분이 약간 복잡하긴 하다. ==== 통합 ==== <code cpp> extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } #include <iostream> #include <string> #include <vector> using namespace std; ////////////////////////////////////////////////////////////////////////////// // class Player ////////////////////////////////////////////////////////////////////////////// class Player { public: string name; // 플레이어의 이름 int fame; // 플레이어의 명성 }; Player g_player; ////////////////////////////////////////////////////////////////////////////// // class NPC ////////////////////////////////////////////////////////////////////////////// class NPC { public: string name; // NPC의 이름 string click_script_name; // 플레이어가 클릭했을 때 실행할 스크립트의 이름 NPC(const string& n = "", const string& clicked = "") : name(n), click_script_name(clicked) {} }; ////////////////////////////////////////////////////////////////////////////// // Function Prototypes ////////////////////////////////////////////////////////////////////////////// bool OnNpcClick(lua_State* pLuaState, NPC& npc); int NPC_SAY(lua_State* L); int GET_PLAYER_FAME(lua_State* L); ////////////////////////////////////////////////////////////////////////////// void main() { // 루아 상태 객체를 생성한다. lua_State* pLuaState = lua_open(); // 필요한 라이브러리들을 연다. lua_baselibopen(pLuaState); lua_tablibopen(pLuaState); //lua_iolibopen(pLuaState); //lua_strlibopen(pLuaState); lua_mathlibopen(pLuaState); lua_dblibopen(pLuaState); // 래퍼 함수들을 등록한다. 두번째 인자는 루아에서 쓰일 함수의 // 이름인데, 혼란을 방지하기 위해서 실제 래퍼 함수의 이름과 똑같게 // 하는 것이 좋다. lua_register(pLuaState, "NPC_SAY", NPC_SAY); lua_register(pLuaState, "GET_PLAYER_FAME", GET_PLAYER_FAME); // NPC들을 생성한다. typedef vector<NPC> NPC_VECTOR; NPC_VECTOR npcs; npclist.push_back(NPC("NPC1", "clicked.lua")); npclist.push_back(NPC("NPC2", "clicked.lua")); npclist.push_back(NPC("NPC3", "clicked.lua")); npclist.push_back(NPC("NPC4", "clicked.lua")); // 플레이어의 상태를 입력받는다. cout << "플레이어의 명성을 입력하시오 >> "; cin >> g_player.fame; // 선택을 입력받아, 클릭 함수를 호출한다. int index = 0; cout << "클릭할 NPC의 인덱스를 입력하시오 (0~3) >> "; cin >> index; OnNpcClick(pLuaState, npcs[index]); // 다 끝났으니 루아 상태 객체를 삭제한다. lua_close(pLuaState); } ////////////////////////////////////////////////////////////////////////////// // 플레이어가 NPC를 클릭한 경우에 호출한다. // 스크립트를 무사히 실행한 경우에는 false를 반환하고, // 무언가 에러가 생긴 경우에는 true를 반환한다. ////////////////////////////////////////////////////////////////////////////// bool OnNpcClick(lua_State* L, NPC& npc) { const string& filename = npc.click_script_name; // 먼저 스크립트를 파싱해서 청크를 만든 다음, 스택에 푸쉬 if (luaL_loadfile(L, filename.c_str())) { // 파싱 실패! return true; } // 트레이스 함수를 위에서 파싱한 청크 밑에다 끼워넣는다. // 트레이스 함수는 전역 테이블에 "_TRACEBACK"이란 이름으로 들어가있다. int base = lua_gettop(L); lua_getglobal(L, "_TRACEBACK"); lua_insert(L, base); // 실행한다. return lua_pcall(L, 0, 0, base) != 0; } ////////////////////////////////////////////////////////////////////////////// // NPC의 대사를 화면에다 출력한다. ////////////////////////////////////////////////////////////////////////////// int NPC_SAY(lua_State* L) { // 스택에서 메시지를 뽑아낸다. luaL_checkstring(L, -1); string msg(lua_tostring(L, -1)); lua_pop(pLuaState, 1); cout << msg << endl; // 스택에 푸쉬한 리턴값의 갯수를 반환해야한다. // 루아 쪽으로 넘겨야할 반환값이 없으므로 0을 반환한다. return 0; } ////////////////////////////////////////////////////////////////////////////// // 플레이어의 명성치를 루아 쪽으로 넘긴다. ////////////////////////////////////////////////////////////////////////////// int GET_PLAYER_FAME(lua_State* L) { // 플레이어의 명성치를 루아 스택에다가 푸쉬한다. lua_pushnumber(L, g_player.fame); // 스택에 푸쉬한 리턴값의 갯수를 반환해야한다. // 플레이어의 명성을 푸쉬했으니, 1을 반환한다. return 1; } </code> 에러 처리는 대부분 생략했다. 컴파일은 안해봤는데, 루아 디렉토리만 include 패스에 잘 추가했다면, 아마 컴파일될 것이다. -_-;;; ====== 요약 ====== 결국 필수적으로 처리해야하는 일들을 요약해보자면... * 플레이어의 입력에 따라 실행할 스크립트를 판단할 수 있는 시스템이 필요하다. 위의 예에서는 NPC의 인덱스를 플레이어로부터 입력받아, 해당 NPC 객체의 멤버 변수로 들어가있는 스크립트 파일 이름을 이용했다. * 루아 쪽에서 C++ 쪽에 있는 객체의 상태를 쿼리할 수 있는 래퍼 함수가 필요하다. 위의 예에서는 플레이어의 명성치를 쿼리하는 GET_PLAYER_FAME 함수가 있다. * 루아 쪽에서 C++ 쪽에 있는 기능을 호출할 수 있는 래퍼 함수가 필요하다. 위의 예에서는 NPC의 대사를 화면에 출력하는 NPC_SAY 함수가 있다. ====== 주의사항 ====== 루아 관련 헤더를 C++ 프로젝트에다가 포함시킬 때에는 include 문을 아래와 같이 <nowiki>extern "C"</nowiki> 문으로 감싸줘야한다. 그렇지 않으면 링크 에러가 발생한다. <code cpp> extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } </code> ---- * see also [[Lua]], [[LuaBinaryExtension]]
kb/luacppbinding.txt
· 마지막으로 수정됨: 2014/11/06 16:47 (바깥 편집)
문서 도구
문서 보기
이전 판
백링크
맨 위로