CEGUI的全部界面逻辑都可以用lua脚本来写,它能轻松暴露这么大量的函数得益于它使用的是tolua++脚本系统,所以我们游戏的脚本系统也决定采用tolua++来实现。
tolua是对lua库的封装,但只支持C语言,而tolua++则是对tolua的封装,为了支持C++语言。
首先定义pkg文件,pkg文件是tolua++生成暴露接口代码的主要文件,该文件中定义了你要暴露程序中的那些类和接口,它跟你实际的程序没有任何关联,也就是说它不会去找你真的有没有pkg里所定义的函数,但是在程序编译时会报错。这个文件放在那里都是可以的。为了方便,一般都把你要暴露的类的.h文件拷出来改成pkg就可以了。
下面就是一个将Ogre的一些数据基本类型暴露给lua的pkg文件,基本和原来的.h差不多,某些不需要的函数可以删掉,tolua++可以支持所有C++机制,如继承、模版、多态、typedef、宏、enum、命名空间、内联等,可谓方便之极,最新版本还加入了支持std::string类型
//OgreBase.pkg
$#include "OgrePrerequisites.h"
$#include "ogrevector3.h"
$#include "OgreColourValue.h"
$#include "OgreQuaternion.h"
$#include "OgreMath.h"
namespace Ogre
{
typedef float Real;
class Vector3
{
public:
Real x, y, z;
inline Vector3( const Real fX, const Real fY, const Real fZ );
inline bool operator == ( const Vector3& rkVector ) const;
inline Vector3 operator + ( const Vector3& rkVector ) const;
inline Vector3 operator - ( const Vector3& rkVector ) const;
inline Vector3 operator * ( const Real fScalar ) const;
inline Vector3 operator * ( const Vector3& rhs) const;
inline Vector3 operator / ( const Real fScalar ) const;
inline Vector3 operator / ( const Vector3& rhs) const;
inline Real length () const;
inline Real squaredLength () const;
inline Real distance(const Vector3& rhs) const;
inline Real squaredDistance(const Vector3& rhs) const;
inline Real dotProduct(const Vector3& vec) const;
inline Real absDotProduct(const Vector3& vec) const;
inline Real normalise();
inline Vector3 crossProduct( const Vector3& rkVector ) const;
inline Vector3 midPoint( const Vector3& vec ) const;
inline void makeFloor( const Vector3& cmp );
inline void makeCeil( const Vector3& cmp );
inline Vector3 perpendicular(void) const;
inline bool isZeroLength(void) const;
};
class ColourValue
{
public:
float r,g,b,a;
ColourValue( float red = 1.0f,float green = 1.0f,float blue = 1.0f,float alpha = 1.0f );
void saturate(void);
ColourValue saturateCopy(void) const;
bool operator==(const ColourValue& rhs) const;
inline ColourValue operator + ( const ColourValue& rkVector ) const;
inline ColourValue operator - ( const ColourValue& rkVector ) const;
inline ColourValue operator * (const float fScalar ) const;
inline ColourValue operator * ( const ColourValue& rhs) const;
inline ColourValue operator / (const float fScalar ) const;
inline ColourValue operator / ( const ColourValue& rhs) const; };
class Quaternion
{
public:
Real w, x, y, z;
inline Quaternion (Real fW = 1.0,Real fX = 0.0, Real fY = 0.0, Real fZ = 0.0);
inline Quaternion(const Vector3& xaxis, const Vector3& yaxis, const Vector3& zaxis);
inline Quaternion(const Vector3* akAxis);
void FromAxes (const Vector3* akAxis);
void FromAxes (const Vector3& xAxis, const Vector3& yAxis, const Vector3& zAxis);
void ToAxes (Vector3* akAxis) const;
void ToAxes (Vector3& xAxis, Vector3& yAxis, Vector3& zAxis) const;
Vector3 xAxis(void) const;
Vector3 yAxis(void) const;
Vector3 zAxis(void) const;
Quaternion operator+ (const Quaternion& rkQ) const;
Quaternion operator- (const Quaternion& rkQ) const;
Quaternion operator* (const Quaternion& rkQ) const;
inline bool operator== (const Quaternion& rhs) const;
Vector3 operator* (const Vector3& rkVector) const;
Real Dot (const Quaternion& rkQ) const; // dot product
Real Norm () const; // squared-length
Real normalise(void);
Quaternion Inverse () const; // apply to non-zero quaternion
Quaternion UnitInverse () const; // apply to unit-length quaternion
Quaternion Exp () const;
Quaternion Log () const;
Radian getRoll(bool reprojectAxis = true) const;
Radian getPitch(bool reprojectAxis = true) const;
Radian getYaw(bool reprojectAxis = true) const;
bool equals(const Quaternion& rhs, const Radian& tolerance) const;
};
class Radian
{
public:
Radian ( Real r=0 );
Radian ( const Degree& d );
Real valueDegrees() const; // see bottom of this file
Real valueRadians() const;
Real valueAngleUnits() const;
Radian operator + ( const Radian& r ) const;
Radian operator - ( const Radian& r ) const;
Radian operator * ( const Radian& f ) const;
Radian operator / ( Real f ) const;
bool operator == ( const Radian& r ) const;
};
class Degree
{
public:
Degree ( Real d=0 );
Degree ( const Radian& r );
Real valueDegrees() const { return mDeg; }
Real valueRadians() const; // see bottom of this file
Real valueAngleUnits() const;
Degree operator + ( const Degree& d ) const ;
Degree operator - ( const Degree& d ) const ;
Degree operator * ( Real f ) const ;
Degree operator * ( const Degree& f ) const ;
Degree operator / ( Real f ) const;
};
}
CEGUI的pkg组织方式是非常值得学习的,他将所有的PKG文件包含在一个总的PKG文件里,生成代码时只要编译这个总的PKG就可以了。
下面就是我们游戏的主PKG文件:
/ScriptSystem.pkg
$pfile "OgreBase.pkg"
$pfile "GameUI.pkg"
$pfile "CTool.pkg"
$pfile "GameMusic.pkg"
$pfile "GameMap.pkg"
$pfile "GameRole.pkg"
$pfile "Options.pkg"
用$pfile命令可以在一个pkg文件里包含另一个PKG文件,可以无限层的包含下去
pkg还有很多其它命令,比如$rename 改名等等,都在它的文档中有介绍。
细心的朋友会发现,上面每一行下面都空出一行来,这可不是为了美观,因为不空一行某些行生成时会报错,具体的原因我也搞不清楚,反正空一行是最保险万无一失的喽! 生成报错的朋友可以试试这个方法。
到这一步,前期的准备工作都已经完成了,下面就可以用tolua++的工具生成代码了。
想通过tolua++的源码编译出tolua++.exe工具是相当繁索的,得在特殊的系统,特殊的系统环境下进行编译,具体步骤没有研究过,但是tolua++.exe这个东西可以通过很多地方获得到的,比如网上可以找到别人已经编译好的.exe,下载下来直接使用,最简单的徒径就是在CEGUI本身就可以找到这个文件,不过他改名为ceguiToLua++.exe了,但功能是一样的。
这时用命令行的方式,即可生成代码。 tolua++.exe -o lua_ScriptSystem.h ScriptSystem.pkg
lua_ScriptSystem.h你要生成出来的代码文件名。将这个文件加到你的工程中,
然后调用tolua_pkg文件名(如ScriptSystem)_open(传入lua对象指针) 函数即可将程序中的功能暴露给传入的lua对象指针。
为了方便,可以把他做成.bat的批处理文件方便以后修改后重新生成代码时使用。
ScriptSystem.bat
@echo on
D:/tolua++-1.0.92/bin/tolua++.exe -o lua_ScriptSystem.h ScriptSystem.pkg
@pause
接下来就要在你的工程中构建一个专门负责脚本系统的管理类,在这个类里可以实现toLua_xxxx_open函数的调用,lua对象的初始化,脚本的执行函数,除此之外,你还可以写一些其它的功能,比如C++向脚本里发事件的机制,或者脚本里回调C++的事件机制。
下面就是是我们游戏的脚本系统封装类,由于CEGUI已经将自己暴露给lua,并且写了独立的控件事件机制,还有脚本的出错信息等,所以想保留这些功能的话就得依赖于CEGUI,把我们自己的接口暴露给CEGUI创建的lua对象而不是我们自己创建的lua对象,并且执行脚本时必须用CEGUI::System::getSingleton().executeScriptFile(fileName.c_str()) 他的函数来执行脚本。当然事件机制的话还是得自己写的,这需要从底层操作一下lua的虚拟栈。
/ScriptSystem.h///
/*-----------------------------------------------------------------------------------------------------------------
脚本系统
Author: 苗琳
Date: 2009-4-14
Update:
----------------------------------------------------------------------------------------------------------------**/
#ifndef _DreamIsland_ScriptSystem_hpp__
#define _DreamIsland_ScriptSystem_hpp__
#pragma once
#include "Singleton.h"
#include <OgrePrerequisites.h>//向前声明
struct lua_State;class ScriptSystem:
public Singleton<ScriptSystem>
{
public:
ScriptSystem(void); //该类目前依赖CEGUI,所以必须在CEGUI系统构造后构造 ~ScriptSystem(void);
//执行脚本文件
void executeScriptFile(const std::string& fileName);
//执行脚本文件中不带参数的的函数
int executeScriptFunction(const std::string& funName);
//执行脚本文件中带一个参数的函数
int executeScriptFunction(const std::string& funName , const std::string& value);
//执行脚本文件中带两个参数的函数
int executeScriptFunction(const std::string& funName , const std::string& value , const std::string& value2);
//向指定的脚本文件发事件
void fireEvent(const std::string& scriptName , const std::string& eventName , const std::string& eventValue = "");private:
lua_State* m_pLua; //lua系统接口对象
};#endif
/ScriptSystem.cpp///
#include "pch.h"
#include "ScriptSystem.h"
#include "CEGUIlua.h"
#include "tolua++.h"
#include "LogManager.h"#include "lua_ScriptSystem.h"
template<> ScriptSystem* Singleton<ScriptSystem>::ms_Singleton = 0;
ScriptSystem::ScriptSystem(void) : m_pLua(NULL)
{
//设置脚本处理器
CEGUI::LuaScriptModule* pScript = new CEGUI::LuaScriptModule();
CEGUI::System::getSingleton().setScriptingModule(pScript);
//保存lua系统接口
m_pLua = pScript->getLuaState(); //调用暴露各类接口的函数
tolua_ScriptSystem_open(m_pLua); //
}ScriptSystem::~ScriptSystem(void)
{
}void ScriptSystem::executeScriptFile(const std::string& fileName)
{
try
{
CEGUI::System::getSingleton().executeScriptFile(fileName.c_str());
}
catch(...)
{
}
}int ScriptSystem::executeScriptFunction(const std::string& funName)
{
try
{
return CEGUI::System::getSingleton().executeScriptGlobal(funName.c_str());
}
catch (...)
{
GLogManager.logMessage(std::string("[调用脚本函数错误] 函数名:") + funName);
return -1;
}
}int ScriptSystem::executeScriptFunction(const std::string& funName , const std::string& value)
{
int top = lua_gettop(m_pLua); //获得lua的函数
lua_getglobal(m_pLua, funName.c_str());
lua_pushstring(m_pLua , value.c_str()); //判断是否是一个函数
if (!lua_isfunction(m_pLua,-2))
{
lua_settop(m_pLua,top);
throw CEGUI::ScriptException("Unable to get Lua global: '"+funName+"' as name not represent a global Lua function" );
} //调用这个函数
int error = lua_pcall(m_pLua,1,1,0); //调用报错
if (error)
{
std::string errMsg = lua_tostring(m_pLua,-1);
lua_pop(m_pLua,1);
throw CEGUI::ScriptException("Unable to evaluate Lua global: '"+funName+"/n/n"+errMsg+"/n");
} //获得返回值
if (!lua_isnumber(m_pLua,-1))
{
lua_settop(m_pLua,top);
CEGUI::ScriptException("Unable to get Lua global : '"+funName+"' return value as it's not a number" );
return -1;
} int ret = (int)lua_tonumber(m_pLua,-1);
lua_pop(m_pLua ,1); return ret;
}
int ScriptSystem::executeScriptFunction(const std::string& funName , const std::string& value , const std::string& value2)
{
int top = lua_gettop(m_pLua); //获得lua的函数
lua_getglobal(m_pLua, funName.c_str());
lua_pushstring(m_pLua , value.c_str());
lua_pushstring(m_pLua , value2.c_str()); //判断是否是函数
if (!lua_isfunction(m_pLua,-3))
{
lua_settop(m_pLua,top);
throw CEGUI::ScriptException("Unable to get Lua global: '"+funName+"' as name not represent a global Lua function" );
} //调用函数
int error = lua_pcall(m_pLua,2,1,0); //调用报错
if (error)
{
std::string errMsg = lua_tostring(m_pLua,-1);
lua_pop(m_pLua,1);
throw CEGUI::ScriptException("Unable to evaluate Lua global: '"+funName+"/n/n"+errMsg+"/n");
} //获得返回值
if (!lua_isnumber(m_pLua,-1))
{
lua_settop(m_pLua,top);
CEGUI::ScriptException("Unable to get Lua global : '"+funName+"' return value as it's not a number" );
return -1;
} int ret = (int)lua_tonumber(m_pLua,-1);
lua_pop(m_pLua ,1); return ret;
}
void ScriptSystem::fireEvent(const std::string& scriptName , const std::string& eventName , const std::string& eventValue)
{
std::string funName = "OnEvent_" + scriptName;
try
{
executeScriptFunction(funName , eventName , eventValue);
}
catch (...)
{
std::string log = "[发送脚本事件异常] scriptName : ";
log += scriptName;
log += " eventName : " + eventName;
log += " eventValue : " + eventValue;
GLogManager.logMessage(log);
}
}
最后一步,就可以写真正的lua脚本了,在脚本中可以调用我们已经暴露好的功能,下面是一张地图名为10010的lua脚本,实现了24小时环境及天空的变化效果。
//script.lua///
------------------
--设置雾效
------------------------------------
--设置环境光
------------------
local ambient = GameMap:getAmbientLight();
local fogColour = GameMap:getFogColour();--设置天空盒
local timeTab = os.date("*t" , os.time());
local time = timeTab.hour * 60 + timeTab.min;
local oldTime;if time > 1080 or time < 360 then
GameMap:setSkyDome(false , "CloudySky");
GameMap:setSkyBox(true , "night");
else
GameMap:setSkyDome(true , "CloudySky");
GameMap:setSkyBox(false , "night");
end;------------------
--更新
------------------
function update_10010()
--获得当前时间
timeTab = os.date("*t" , os.time());
time = timeTab.hour * 60 + timeTab.min;
if oldTime == time then
return 0; --时间没有变不用处理
else
oldTime = time; --记录新时间,并处理
end;
--计算时间与天空盒+雾效颜色+环境光的变化
local colour;
if time < 720 then
colour = 0.4 + 0.6 * (time/720)
else
colour = 0.4 + 0.6 * (1.0 - (time-720)/720)
end;
--防御性代码
if colour > 1.0 then
colour = 1.0
elseif colour < 0 then
colour = 0
end;
GameMap:setAmbientLight(ambient*colour);
GameMap:setFogColour(fogColour*colour);
return 1;
end;
------------------
--处理事件
------------------
function OnEvent_10010( eventName , eventValue)
if eventName == "jump" then
end; return 1;
end;
tolua++只封装了对将C++功能暴露给lua调用这部分,如果反过来将lua的变量函数发给C++使用,tolua++就不方便了,luaPlus这个库对这部分有良好的封装,据说这两个库可以同时使用,就是两方面都很方便了,不过还没有时间研究,已经搞定了的朋友请多多指教呀!