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这个库对这部分有良好的封装,据说这两个库可以同时使用,就是两方面都很方便了,不过还没有时间研究,已经搞定了的朋友请多多指教呀!