やりたいこと
サードパーティのライブラリを使っていろいろやろうとすると、Objective-C, Java+NDKが必要になってくるから、やっぱりC++ベースの方がフットワーク軽くいろいろできる。
けど、スクリプトも使いたい!
と思い調べていたら、ComponentLuaなるものを発見!
http://discuss.cocos2d-x.org/t/cocos2d-x-v3-9-released/24948
これよさそう。
やってみる
Luaベースでプロジェクトを作る
cocos new なんたらかんたら -l lua
(自分はCocosStudioベースのなので、そっちから作りました)
AppDelegate.cppの初期化処理がLua向けなので、C++向けに書きなおす
AppDelegate.cpp
bool AppDelegate::applicationDidFinishLaunching()
{
// C++向けの初期化処理を追加
// set default FPS
// initialize director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
glview = GLViewImpl::createWithRect("appname", Rect(0, 0, 960, 640));
director->setOpenGLView(glview);
}
director->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
FileUtils::getInstance()->addSearchPath("res");
// Luaエンジンの初期化は多分必要
// register lua module
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);
lua_State* L = engine->getLuaStack()->getLuaState();
lua_module_register(L);
register_all_packages();
LuaStack* stack = engine->getLuaStack();
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
//register custom function
//LuaStack* stack = engine->getLuaStack();
//register_custom_function(stack->getLuaState());
# if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
// NOTE:Please don't remove this call if you want to debug with Cocos Code IDE
auto runtimeEngine = RuntimeEngine::getInstance();
runtimeEngine->addRuntime(RuntimeLuaImpl::create(), kRuntimeEngineLua);
runtimeEngine->start();
# else
/* ここをコメントアウト
if (engine->executeScriptFile("src/main.lua"))
{
return false;
}
*/
# endif
// シーンを開始
Director::getInstance()->runWithScene(TitleScene::createScene());
return true;
}
TitleScene.cpp
# include "CCComponentLua.h"
# include "TitleScene.h"
USING_NS_CC;
Scene* TitleScene::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = TitleScene::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool TitleScene::init()
{
// 1. super init first
if (!Layer::init()) {
return false;
}
auto node = CSLoader::getInstance()->createNode("res/TitleScene.csb");
auto luaComponent = ComponentLua::create("src/TitleScene.lua");
node->addComponent(luaComponent);
addChild(node);
return true;
}
src/TitleScene.lua
local TitleScene = {}
TitleScene.onEnter = function(self)
-- self:getOwner()でaddComponent()したNodeが取得できる
print( self:getOwner():getName() )
end
TitleScene.onExit = function(self)
-- print("onExit")
end
TitleScene.update = function(self)
-- print(self)
end
-- it is needed to return player to let c++ nodes know it
return TitleScene
できた。
おまけ
ComponentLua.cppを書き換えて、C++ <--> Luaでイベントメッセージのやりとりをできるようにしてみる。
ComponentLua.cpp
/****************************************************************************
Copyright (c) 2015 Chukong Technologies Inc.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
# include "CCComponentLua.h"
# include <string>
# include "base/CCScriptSupport.h"
# include "CCLuaEngine.h"
# include "LuaBasicConversions.h"
NS_CC_BEGIN
const std::string ComponentLua::ON_ENTER = "onEnter";
const std::string ComponentLua::ON_EXIT = "onExit";
const std::string ComponentLua::UPDATE = "update";
const std::string ComponentLua::ON_MESSAGE = "onMessage"; // 追加
# define KEY_COMPONENT "component"
int ComponentLua::_index = 0;
ComponentLua* ComponentLua::create(const std::string& scriptFileName)
{
CC_ASSERT(!scriptFileName.empty());
initClass();
auto componentLua = new(std::nothrow) ComponentLua(scriptFileName);
if (componentLua)
{
componentLua->autorelease();
}
return componentLua;
}
ComponentLua::ComponentLua(const std::string& scriptFileName)
: _scriptFileName(scriptFileName)
, _table(nullptr)
, _strIndex("")
{
_succeedLoadingScript = loadAndExecuteScript();
}
ComponentLua::~ComponentLua()
{
removeLuaTable();
}
void ComponentLua::getScriptObjectInternal() const
{
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
lua_pushstring(l, KEY_COMPONENT); // stack: "component"
lua_rawget(l, LUA_REGISTRYINDEX); // stack: LUA_REGISTRYINDEX["component"]
lua_pushstring(l, _strIndex.c_str()); // stack: LUA_REGISTRYINDEX["component"] strIndex
lua_rawget(l, -2); // stack: LUA_REGISTRYINDEX["component"]
}
void* ComponentLua::getScriptObject() const
{
getScriptObjectInternal();
return nullptr;
}
void ComponentLua::update(float delta)
{
if (_succeedLoadingScript && getLuaFunction(ComponentLua::UPDATE))
{
getUserData();
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
lua_pushnumber(l, delta);
LuaEngine::getInstance()->getLuaStack()->executeFunction(2);
}
}
void ComponentLua::onEnter()
{
if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_ENTER))
{
getUserData();
LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
}
}
void ComponentLua::onExit()
{
if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_EXIT))
{
getUserData();
LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
}
}
// 追加
void ComponentLua::sendMessage(const ComponentLua::Message& message)
{
if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_MESSAGE))
{
getUserData();
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
std_map_string_string_to_luaval(l, message);
LuaEngine::getInstance()->getLuaStack()->executeFunction(2);
}
}
bool ComponentLua::getLuaFunction(const std::string& functionName)
{
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
lua_pushstring(l, KEY_COMPONENT); // stack: "component"
lua_rawget(l, LUA_REGISTRYINDEX); // stack: table_of_component
lua_pushstring(l, _strIndex.c_str()); // stack: table_of_component strIndex
lua_rawget(l, -2); // stack: table_of_component table_of_this
lua_pushstring(l, functionName.c_str()); // stack: table_of_component table_of_this "update"
lua_rawget(l, -2); // stack: table_of_component table_of_this table_of_this["update"]
lua_remove(l, -2); // stack: table_of_component table_of_this["update"]
lua_remove(l, -2); // stack: table_of_this["update"]
int type = lua_type(l, -1);
// if (type != LUA_TFUNCTION)
// {
// CCLOG("can not get %s function from %s", functionName.c_str(), _scriptFileName.c_str());
// }
return type == LUA_TFUNCTION;
}
bool ComponentLua::loadAndExecuteScript()
{
// register native functions
auto engine = LuaEngine::getInstance();
lua_State *l = engine->getLuaStack()->getLuaState();
// load script
auto fileUtils = FileUtils::getInstance();
std::string fullPathOfScript = fileUtils->fullPathForFilename(_scriptFileName);
Data data = fileUtils->getDataFromFile(fullPathOfScript);
int error = LUA_ERRFILE;
if(data.getSize() > 0)
error = engine->getLuaStack()->luaLoadBuffer(l, (const char*)data.getBytes(), (int)data.getSize(), fullPathOfScript.c_str());
if (error)
{
CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
lua_pop(l, 1);
return false;
}
// execute script
error = lua_pcall(l, 0, 1, 0);
if (error)
{
CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
lua_pop(l, 1);
return false;
}
// check the return value from lua script is a table
int type = lua_type(l, -1);
if (type != LUA_TTABLE)
{
CCLOG("%s should return a table, or the script component can not work currectly", _scriptFileName.c_str());
return false;
}
storeLuaTable();
return true;
}
void ComponentLua::initClass()
{
static bool run = true;
if (run)
{
// LUA_REGISTRYINDEX["component"] = new table
LuaEngine* engine = LuaEngine::getInstance();
lua_State* l = engine->getLuaStack()->getLuaState();
lua_pushstring(l, KEY_COMPONENT); // stack: "component"
lua_newtable(l); // stack: "component" table
lua_rawset(l, LUA_REGISTRYINDEX); // stack: -
run = false;
}
}
void ComponentLua::storeLuaTable()
{
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
_index++;
_strIndex.append(StringUtils::toString(_index));
// LUA_REGISTRYINDEX["component"][strIndex] = table return from lua
lua_pushstring(l, KEY_COMPONENT); // stack: table_return_from_lua "component"
lua_rawget(l, LUA_REGISTRYINDEX); // stack: table_return_from_lua table_of_component
lua_pushstring(l, _strIndex.c_str()); // stack: table_return_from_lua table_of_component strIndex
lua_pushvalue(l, -3); // stack: table_return_from_lua table_of_component strIndex table_return_from_lua
lua_rawset(l, -3); // stack: table_return_from_lua table_of_component
lua_pop(l, 1); // stack: table_return_from_lua
// add table's elements to userdata's metatable
object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua", this); // stack: table_return_from_lua userdata
lua_getmetatable(l, -1); // stack: table_return_from_lua userdata mt
lua_remove(l, -2); // stack: table_return_from_lua mt
lua_pushnil(l); // stack: table_return_from_lua mt nil
while (lua_next(l, -3)) // stack: table_return_from_lua mt key value
{
lua_pushvalue(l, -2); // stack: table_return_from_lua mt key value key
lua_insert(l, -2); // stack: table_return_from_lua mt key key value
lua_rawset(l, -4); // stack: table_return_from_lua mt key
}
lua_pop(l, 2);
}
void ComponentLua::removeLuaTable()
{
if (_succeedLoadingScript)
{
// LUA_REGISTRYINDEX["component"][strIndex] = nil
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
lua_pushstring(l, KEY_COMPONENT); // stack: "component"
lua_rawget(l, LUA_REGISTRYINDEX); // stack: LUA_REGISTRYINDEX["component"]
lua_pushstring(l, _strIndex.c_str()); // stack: LUA_REGISTRYINDEX["component"] strIndex
lua_pushnil(l); // stack: LUA_REGISTRYINDEX["component"] strIndex nil
lua_rawset(l, -3); // stack: LUA_REGISTRYINDEX["component"]
}
}
void ComponentLua::getUserData()
{
lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua",this);
}
// 追加
int ComponentLua::tolua_cocos2d_ComponentLua_onMessage(lua_State* tolua_S)
{
if (NULL == tolua_S)
return 0;
int argc = 0;
ComponentLua* self = nullptr;
# if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
if (!tolua_isusertype(tolua_S,1,"cc.ComponentLua",0,&tolua_err)) goto tolua_lerror;
# endif
self = static_cast<cocos2d::ComponentLua*>(tolua_tousertype(tolua_S,1,0));
# if COCOS2D_DEBUG >= 1
if (nullptr == self) {
tolua_error(tolua_S,"invalid 'self' in function 'tolua_cocos2d_ComponentLua_onMessage'\n", NULL);
return 0;
}
# endif
argc = lua_gettop(tolua_S) - 1;
if (1 == argc)
{
ComponentLua::Message arg;
luaval_to_std_map_string_string(tolua_S, 2, &arg, "tolua_cocos2d_ComponentLua_onMessage");
if( self->_callbackMessage ){
self->_callbackMessage(arg);
}
return 0;
}
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d\n", "cc.Node:unscheduleUpdate", argc, 0);
return 0;
# if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'tolua_cocos2d_ComponentLua_onMessage'.",&tolua_err);
return 0;
# endif
}
NS_CC_END
// 追加
int register_componentlua_moudle(lua_State* L)
{
lua_getglobal(L, "_G");
if (lua_istable(L,-1))//stack:...,_G,
{
lua_pushstring(L, "cc.ComponentLua");
lua_rawget(L, LUA_REGISTRYINDEX);
if (lua_istable(L,-1))
{
tolua_function(L, "sendMessage", ComponentLua::tolua_cocos2d_ComponentLua_onMessage);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
return 1;
}
ComponentLua.h
/****************************************************************************
Copyright (c) 2015 Chukong Technologies Inc.
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
# pragma once
# include <string>
# include <unordered_map>
# include "2d/CCComponent.h"
extern int register_componentlua_moudle(lua_State* L);
NS_CC_BEGIN
class ComponentLua : public Component
{
public:
// 追加
typedef std::map<std::string,std::string> Message;
static ComponentLua* create(const std::string& scriptFileName);
ComponentLua(const std::string& scriptFileName);
/**
* This function is used to be invoked from lua side to get the corresponding script object of this component.
*/
void* getScriptObject() const;
virtual void update(float dt);
virtual void onEnter();
virtual void onExit();
// 追加
void sendMessage(const Message& message);
// 追加
void addMessageCallback(const std::function<void(const Message&)> callback){
_callbackMessage = callback;
}
static int tolua_cocos2d_ComponentLua_onMessage(lua_State* tolua_S);
private:
~ComponentLua();
void getScriptObjectInternal() const;
bool loadAndExecuteScript();
void getUserData();
void storeLuaTable();
bool getLuaFunction(const std::string& functionName);
void removeLuaTable();
static void initClass();
private:
// Script file path
std::string _scriptFileName;
bool _succeedLoadingScript;
// a table returned from lua
const void* _table;
// string value of index
std::string _strIndex;
// the index used to get lua table, it is unique for every component
static int _index;
static const std::string ON_ENTER;
static const std::string ON_EXIT;
static const std::string UPDATE;
static const std::string ON_MESSAGE; // 追加
// 追加
std::function<void(const Message&)> _callbackMessage;
};
NS_CC_END
lua_module_register.cpp
# include "lua_module_register.h"
# include "cocosdenshion/lua_cocos2dx_cocosdenshion_manual.h"
# include "network/lua_cocos2dx_network_manual.h"
# include "cocosbuilder/lua_cocos2dx_cocosbuilder_manual.h"
# include "cocostudio/lua_cocos2dx_coco_studio_manual.hpp"
# include "extension/lua_cocos2dx_extension_manual.h"
# include "ui/lua_cocos2dx_ui_manual.hpp"
# include "spine/lua_cocos2dx_spine_manual.hpp"
# include "3d/lua_cocos2dx_3d_manual.h"
# include "audioengine/lua_cocos2dx_audioengine_manual.h"
# include "physics3d/lua_cocos2dx_physics3d_manual.h"
# include "navmesh/lua_cocos2dx_navmesh_manual.h"
# include "CCComponentLua.h" // 追加
int lua_module_register(lua_State* L)
{
//Dont' change the module register order unless you know what your are doing
register_cocosdenshion_module(L);
register_network_module(L);
register_cocosbuilder_module(L);
register_cocostudio_module(L);
register_ui_moudle(L);
register_extension_module(L);
register_spine_module(L);
register_cocos3d_module(L);
register_audioengine_module(L);
register_componentlua_moudle(L); // 追加
# if CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION
register_physics3d_module(L);
# endif
# if CC_USE_NAVMESH
register_navmesh_module(L);
# endif
return 1;
}
TitleScene.cpp
// Luaにメッセージを送る
ComponentLua::Message message;
for( int i = 0; i < 3; i++ ){
message[std::to_string(i)] = 'a'+i;
}
luaComponent->sendMessage(message);
// Luaからのメッセージを受け取る
luaComponent->addMessageCallback([](const ComponentLua::Message& message){
for( auto& m : message ){
CCLOG("%s:%s", m.first.c_str(), m.second.c_str());
}
});
TitleScene.lua
local TitleScene = {}
TitleScene.onEnter = function(self)
print( self:getOwner():getName() )
-- Cにメッセージを送る
local message = {}
message["a"] = "b"
message["c"] = "d"
message["e"] = "f"
self:sendMessage(message)
end
TitleScene.onExit = function(self)
-- print("onExit")
end
TitleScene.update = function(self)
-- print(self)
end
-- Cからメッセージを受け取る
TitleScene.onMessage = function(self, message)
for key, value in pairs(message) do
print(key, value)
end
end
-- it is needed to return player to let c++ nodes know it
return TitleScene