1. 程式人生 > >RPG遊戲製作-08-為lua註冊c/c++函式

RPG遊戲製作-08-為lua註冊c/c++函式

接下來,就可以註冊c函式給lua使用了。本遊戲中把不同種類的函式集合成各種各樣的模組,就類似於lua中的math模組io模組。目前的模組如下:

1.base模組,註冊了一些常用的基礎函式。

2.movement模組,移動函式,場景切換函式等。

3.screen模組,場景相關函式,如淡入淡出函式,調出揹包函式等。

4.timer模組,延遲函式。

void register_funcs(lua_State* pL)
{
	static const luaL_Reg cpplibs[] = {
		{"base", open_base},
		{"movement", open_movement},
		{"screen", open_screen},
		{"timer", open_timer},
		{NULL, NULL}
	};
	const luaL_Reg* lib = nullptr;
	for (lib = cpplibs;lib->func;lib++)
	{
		luaL_requiref(pL, lib->name, lib->func, 1);
		lua_pop(pL, 1);
	}
}

這個函式類似於lua中的luaL_openlibs(),添加了全部的模組。詳細的請看帖子:

https://blog.csdn.net/bull521/article/details/79938211

然後就是一些模組內函式的編寫,由於目前函式較少,故全部放在了ScriptFunc.h ScriptFunc.cpp檔案中。

//------------------------------base------------------------------------
//一些基礎的函式及宣告
extern int open_base(lua_State* pL);
//遊戲狀態
extern int getGameState(lua_State* pL);
extern int setGameState(lua_State* pL);

每個模組都必有一個open_*()函式,來註冊相關的函式給lua。

int open_base(lua_State* pL)
{
	const luaL_Reg baselib[] = {
		{"getGameState", getGameState},
		{"setGameState", setGameState},
		{NULL, NULL}
	};
	luaL_newlib(pL, baselib);
	return 1;
}
int getGameState(lua_State* pL)
{
	auto state = GameScene::getInstance()->getGameState();
	lua_Integer nState = static_cast<lua_Integer>(state);

	lua_pushinteger(pL, nState);

	return 1;
}

int setGameState(lua_State* pL)
{
	lua_Integer nState = luaL_checkinteger(pL, 1);
	GameState state = static_cast<GameState>(nState);

	GameScene::getInstance()->setGameState(state);

	return 0;
}

遊戲狀態主要是了以後在進行指令碼狀態下,需要遮蔽各種事件的響應。

在lua中使用c/c++中的列舉體有兩種方法:

1.在c/c++中建立一個table,之後放入對應的鍵值對,如果有多個,重複上述操作。

2.在lua中根據列舉體的值直接建立。

我這裡使用的是第二種,第一種雖然能實現當列舉體的順序改變時,不需要修改列舉體註冊函式,但是當列舉體太多時,會有很多重複性程式碼。

//------------------------------movement------------------------------------
int open_movement(lua_State* pL)
{
	const luaL_Reg movementlib[] = {
		{"changeMap", changeMap},
		{NULL, NULL}
	};
	luaL_newlib(pL, movementlib);

	return 1;
}

int changeMap(lua_State* pL)
{
	const char* mapName = luaL_checkstring(pL, 1);
	float x = (float)luaL_checknumber(pL, 2);
	float y = (float)luaL_checknumber(pL, 3);

	GameScene::getInstance()->changeMap(mapName, Point(x, y));

	return 0;
}

movement模組中目前僅有一個切換地圖函式。

//------------------------------screen------------------------------------
int open_screen(lua_State* pL)
{
	const luaL_Reg screenlib[] = {
		{"fadeInScreen", fadeInScreen},
		{"fadeOutScreen", fadeOutScreen},
		{NULL, NULL}
	};
	luaL_newlib(pL, screenlib);

	return 1;
}

int fadeInScreen(lua_State* pL)
{
	float duration = (float)luaL_checknumber(pL, 1);

	auto gameScene = GameScene::getInstance();
	auto scriptLayer = gameScene->getScriptLayer();
	//設定等待時間
	scriptLayer->setWaitType(WaitType::Time);
	scriptLayer->setWaitTime(duration);

	gameScene->getEffectLayer()->fadeInScreen(duration);
	gameScene->yield(0);

	return 0;
}

int fadeOutScreen(lua_State* pL)
{
	float duration = (float)luaL_checknumber(pL, 1);

	auto gameScene = GameScene::getInstance();
	auto scriptLayer = gameScene->getScriptLayer();
	//設定等待時間
	scriptLayer->setWaitType(WaitType::Time);
	scriptLayer->setWaitTime(duration);

	gameScene->getEffectLayer()->fadeOutScreen(duration);
	gameScene->yield(0);

	return 0;
}

淡入淡出場景函式會阻塞對應指令碼的執行,即協程,這裡注意下yield的位置是在函式的末尾的,因為lua_yield()後的語句不會執行的,這個在lua wiki中有說明。

//------------------------------screen------------------------------------
int open_timer(lua_State* pL)
{
	const luaL_Reg timerlib[] = {
		{"delay", delay},
		{NULL, NULL}
	};
	luaL_newlib(pL, timerlib);

	return 1;
}

int delay(lua_State* pL)
{
	float duration = (float)luaL_checknumber(pL, 1);

	auto gameScene = GameScene::getInstance();
	auto scriptLayer = gameScene->getScriptLayer();
	//設定等待時間
	scriptLayer->setWaitType(WaitType::Time);
	scriptLayer->setWaitTime(duration);

	gameScene->yield(0);

	return 0;
}

程式碼同上,相比於淡入淡出場景只是少了一個動畫而已。

int GameScene::yield(int nresult)
{
	return lua_yield(m_pLuaState, nresult);
}

簡單封裝了lua_yield()。其實可以直接在上面的函式中lua_yield(pL)的,因為pL就是GameScene中的m_pLuaState,這裡是為了保持一致性,故全部呼叫的GameScene中封裝的函式。

另外一個問題就是注意一下NPC的execute函式的修改:

bool NonPlayerCharacter::execute(int playerID)
{
	auto gameScene = GameScene::getInstance();

	int result = LUA_ERRRUN;
	bool ret = false;
	//獲得函式
	gameScene->getLuaGlobal(m_name.c_str());
	gameScene->getLuaField(-1, "execute");
	//放入引數
	gameScene->getLuaGlobal(m_name.c_str());
	gameScene->pushInt(playerID);
	//執行函式
	result = gameScene->resumeFunction(2);

	if (result == LUA_YIELD)
	{
	}
	else if (result == LUA_OK)
	{
		//獲取返回值
		ret = gameScene->toLuaBoolean(-1);
		gameScene->pop(2);
	}

	int n = lua_gettop(gameScene->getLuaState());

	return ret;
}

當lua_resume()返回的是LUA_YIELD時,此時的棧會被清空,也就是說不需要主動清空棧即可,而返回LUA_OK時,則需要主動清空棧的內容。

然後就是在lua檔案中新增內容了。

先建立一個rpg_core.lua,內部聲明瞭一些table來對映c/c++中的列舉體。

--包含著一些列舉體和一些函式
--遊戲狀態列舉體
GameState = {};
GameState.Normal = 0;
GameState.Fighting = 1;
GameState.Script = 2;

然後就是 "門"table:

--傳送門 傳送到1.tmx
Map01_02_Portal01 = NonPlayerCharacter:new();

function Map01_02_Portal01:execute(playerID)
	base.setGameState(GameState.Script);
	--淡入場景
	screen.fadeInScreen(0.8);
	--切換場景
	movement.changeMap("map/1.tmx",26,23);
        --淡出場景
	screen.fadeOutScreen(0.8);

	base.setGameState(GameState.Normal);
	return true;
end