2010年4月23日

[转]组件工厂 -----3D游戏开发

狭义的游戏对象是指游戏世界中所能看到及可交互的对象,如玩家、怪物、物品等,我们这里也主要讨论这类对象在服务器上的组织及实现。
  

  在大部分的MMOG中,游戏对象的类型都大同小异,主要有物品、生物、玩家等。比如在wow中,通过服务器发下来的GUID我们可以了解到,游戏中有9大类对象,包括物品(Item)、背包(Container)、生物(Unit)、玩家(Player)、游戏对象(GameObject)、动态对象(DynamicObject)、尸体(Corpse)等。

  在mangos的实现中,对象使用类继承的方式,由Object基类定义游戏对象的公有接口及属性,包括GUID的生成及管理、构造及更新UpdateData数据的虚接口、设置及获取对象属性集的方法等。然后分出了两类派生对象,一是Item,另一是WorldObject。Item即物品对象,WorldObject顾名思义,为世界对象,即可添加到游戏世界场景中的对象,该对象类型定义了纯虚接口,也就是不可被实例化,主要是在Object对象的基础上又添加了坐标设置或获取的相关接口。

  Item类型又派兵出了一类Bag对象,这是一种特殊的物品对象,其本身具有物品的所有属性及方法,但又可作为新的容器类型,并具有自己特有的属性和方法,所以实现上采用了派生。mangos在实现时对Bag的类型定义做了点小技巧,Item的类型为2,Bag的类型为6,这样在通过位的方式来表示类型时,Bag类型也就同时属于Item类型了。虽然只是很小的一个技巧,但在很多地方却带来了极大的便利。

  从WorldObject派生出的类型就有好几种了,Unit、GameObject、DynamicObject和Corpse。Unit为所有生物类型的基类,同WorldObject一样,也不可被实例化。它定义了生物类型的公有属性,如种族、职业、性别、生命、魔法等,另外还提供了相关的一些操作接口。游戏中实际的生物对象类型为Creature,从Unit派生,另外还有一类派生对象Player为玩家对象。Player与Creature在实现上最大的区别是玩家的操作由客户端发来的消息驱动,而Creature的控制是由自己定义的AI对象来驱动,另外Player内部还包括了很多的逻辑系统实现。

  另外还有两类特殊的Creature,Pet和Totem,其对象类型仍然还是生物类,只是实现上与会有些特殊的东西需要处理,所以在mangos中将其作为独立的派生类,只是实现上的一点处理。另外在GameObject中也实现有派生对象,最终的继承关系图比较简单,就不麻烦地去画图了。

  从我所了解的早期游戏实现来看,大部分的游戏对象结构都是采用的类似这种方式。可能与早期对面向对象的理解有关,当面向对象的概念刚出来时,大家认为继承就是面向对象的全部,所以处处皆对象,处处皆继承。

  类实现的是一种封装,虽然从云风那里出来的弃C++而转投C的声音可能会影响一部分人,但是,使用什么语言本身就是个人喜好及团队整体情况决定的。我们所要的也是最终的实现结果,至于中间的步骤,完全看个人。还是用云风的话说,这只是一种信仰问题,我依然采用我所熟悉的C++,下面的描述也是如此。

  随着面向对象技术的深入,以及泛型等概念的相继提出,软件程序结构方面的趋势也有了很大改变。C++大师们常说的话中有一句是这样说的,尽是采用组合而不是继承。游戏对象的实现也有类似的转变,趋向于以组合的方式来实现游戏对象类型,也就是实现一个通用的entity类型,然后以脚本定义的方式组合出不同的实际游戏对象类型。

  描述的有些抽象,具体实现下一篇来仔细探讨下。


在游戏编程精粹四有三篇文章讲到了实体以及实体管理的实现方案,其中一篇文章说到了实体管理系统的四大要素:定义实体怎样沟通的实体消息,实现一实体类代码和数据的实体代码,维护已经注册在案的实体类列表,和用来创建、管理、发送消息的实体管理器。

  关于实体消息的内容之前讨论事件机制的时候做过一点说明,其实这也就是按接口调用和按消息驱动的区别,现在mangos的做法是完全的接口调用,所以引擎内部就没有任何的实体消息。实体代码实现和实体管理器是我们重点要讨论的内容。

  另有一篇文章也提到了使用类继续的方式实现游戏对象的两大问题,一是它要求系统中的所有对象都必须从一个起点衍生而成,也就是说所有对象类在编译的时候已经确定,这可能是一个不受欢迎的限制,如果开发者决定添加新的对象类,则必须要对基类有所了解,方能支持新类。另一个问题在于所有的对象类都必须实现同样的一些底层函数。

  对于第二个问题,可以通过接口继承的方式来避免基类的方法太多。在mangos的实现中就采用了类似的方法,从Object虚基类派生的Unit和WorldObject仍然还是不可实例化的类,这两种对象定义了不同的属性和方法,分来实现不同类型的对象。在游戏内部可以根据对象的实际类型来Object指针向下转型为Unit或WorldObject,以调用需要的接口。方法虽然不够OO,也还能解决问题。但是第一个问题是始终无法避免的。

  所以我们便有了通用实体这么一个概念,其主要方法是将原来基类的接口进行分类,分到一个个不同的子类中,然后以对象组合的方式来生成我们所需要的实际游戏对象类型。这个组合的过程可以通过脚本定义的方式,这样便可以在运行时生成为同的对象类型,也就解决了上面提到的第一个问题。

  通用实体的实现方法在目前的游戏引擎及开源代码中也可以看到影子。一个是BigWorld,从提供的资料来看,其引擎只提供了一个entity游戏对象,然后由游戏内容实现者通过xml和python脚本来自由定义不同类型的entity类型,每种类型可有不同的property和不同的方法。这样原来由基类定义的接口完全转移到脚本定义,具有非常强的灵活性。

  另外还有一个是CEL中的entity实现。按照CEL的描述,entity可以是游戏中的任意对象,包括玩家可交互的对象,如钥匙、武器等,也可以包括不能直接交互的对象,如游戏世界,甚至任务链中的一部分等。entity本身并没有任何特性,具体的功能实现需要靠附加property来完成。简单来说,property才定义了entity可以做什么,至于该怎么做,那又是依靠behavior来定义。所以,最终在CEL中一个游戏对象其实是由entity组合了多个property及多个behavior而生成的。

  但是CEL中的property与BigWorld中的property意义不大一样,在CEL中可定义的property其实是引擎内部要先提供的,比如其示例中所举的pcobject.mesh、pcmove.linear、pctools.inventory等,而BigWorld中的property是完全的自由定制。从这个角度来讲,其实可以把CEL中的property看作是游戏的逻辑系统,也就是我们上面所描述的,接口分类后所定义的子类。

  由引擎内部提供可选择的property与BigWorld所采用的完全自由定制property其实本质上是相同的。在用BigWorld实现的游戏中,也不可能为每种游戏对象类型都完全从头定义property,基于代码利用的原则,也会先定义一些小类,然后在entity类型定义时以自定义property的方式来包含这些小类。当然,我没有使用过BigWorld,上面的描述也只是基于游戏实现的大原则所做出来的。

  描述的依然有些抽象,我们可以用wow及mangos代码来说明一下。mangos中为object定义了一个属性集合,根据对象类型的不同,这个属性集的大小及保存数据也会有差异,另外游戏对象内部含有处理不同游戏逻辑的系统,如RestSystem、FloodFilterSystem、VariousSystem等等,在player.h中以接口组的方式进行定义。

  如果要将这种结构改为我们描述的通用entity系统,可以让object只提供property注册和删除的接口,这里的property定义与CEL中的相同,放在mangos中也就是上面说的RestSystem、FloodFilterSystem、VariousSystem这些。然后也通过xml文件的方式定义我们所需要的游戏对象类型,如player,creature,item等,不同的对象类型可以选择加载不同的property,加载的原则是需要哪些功能就加载哪些property。

  对象的定义按上面的方法完成后,对象的实现也需要做一些修改。以前客户端的消息是直接交由player来处理,AI也是直接调用creature的接口来完成一些功能,现在通用的entity内部已经没有任何可用的方法,所有的实现都转到了property中,所以需要由各个property实现自己来注册感兴趣的事件及消息,entity实现一个消息的转发,转给对此感兴趣的property来处理。其余的实现就没有什么不同了。

  当然,我们再做一点扩展,让property不光由引擎来提供,用脚本本身也能定义property,并且可以通过xml来注册这些property,这样便实现了与BigWorld一样的完全自由特性。这其实也就是将很多用C++实现的功能转移到了python中,这种做法可作为参考,但不一定对所有人合适,至少在我看来,这样的实现也基本只能由程序员来做,所以让程序员选择自己最擅长的语言可能会更易于开发和调试。

有关游戏对象实现的描述,前面两篇文章中说的不甚清楚,主要是一直都要引用网上能够找到的资料来进行描述,以避免与公司引起不必要的麻烦。所以语言有些拼凑的感觉,举的例子也很不恰当,今天正好看到了游戏编程精粹五和六上的两篇文章,内容都差不多,<<基于组件的对象管理>>和<<基于组件的游戏对象系统>>,说的也是我上两篇文章想要描述的内容,所以再补一篇,引用其中的部分文字进行明确的说明。

 

  传统的游戏对象管理系统采用继承的方式来实现,例如,所有的子类都从CObject派生。大多数情况下,直接派生的也是抽象类,其中带一些功能而另一些子类则不带这些功能,比如可控制/不可控制,可动画/不可动画等。mangos的实现中基本就是这种情况,从Object直接派生的Unit和WorldObject都是不可直接实例化的类。

  传统方法的问题在于无法应对需求的变化,如要求武器也有动画效果时就无法处理了。如果硬要是这样做,那随着需求的啬,很多的方法会被放到基类中,最终的结果是继承树变得越来越头重脚轻,这样的类会丧失它的内聚性,因为它们试图为所有对象完成所有的事。

  就是说到最后,基类会有一个很长的接口列表,而很多的游戏对象类型根本不需要实现其中的一些甚至大部分接口,但是按照这种结构却又必须去实现。以至于于实现一个非常庞大的对象,而且想要修改一点功能会导致系统的大调整。

  我们希望的系统是可以将现有的功能组合到新的对象中,并且在将新的功能添加到现有的对象中时不需要重构大量的代码和调整继承树的结构。

  实现的方法就是从组件来创建一个对象。组件是一个包含所有相关数据成员和方法的类,它完成某个特定的任务。把几个组件组合在一起就可以创建一个新的对象。如把Entity组件、Render组件和Collectable组件组合在一起生成了一个Spoon对象。Entity组件让我们可以把对象放到游戏世界中,Render组件让我们可以为对象指定一个模型进行渲染,而Collectable组件让我们可以拾取这个对象。

  关于组件的实现,所有的组件都从一个基础组件接口派生,可称其为IComponent。每个组件也有自己的接口定义,并且这个接口也需要从IComponent派生,类似于这样:IComponent -- ICmpRender -- CCmpRender

  这里的每个组件也就是我在上一篇中所说的由引擎提供的属性,或者说在BigWorld中自己实现然后定义的属性,或者使用mangos中的定义,就是一个个的System,虽然mangos并没有将其完全做成组件,但是通过其代码注释可以看到,接口也是按功能组进行了分类,如果要拆分成组件也是比较方便的。

  组件之间的通信有两种方法,一是用组件ID查询到组件接口指针,然后调用接口方法;二是使用消息的方式,向对象中所有组件发消息。在初始化的时候,每一个组件类型都会告诉对象管理器应该接收什么样的消息。

  查询接口的方法也就是直接的方法调用,只不过接口不是全部在基类中,所以必须先查询到指定的组件然后才能调用其接口。消息的使用前面已经说过多次,其实现方案也有过说明。

  最后是关于游戏对象功能的扩展和游戏对象的定义。需要扩展功能也就是需要实现一个新的组件,或者修改现在组件。在大多数情况下,扩展都不会引起结构的很大调整,受影响的最多只是使用到该组件的部分代码。

  游戏对象定义可采用完全数据驱动的方式,使用xml或者脚本语言来定义对象类型,以及每个类型需要加载的组件。对象类型注册到对象管理器后,由管理器提供创建指定类型的对象的方法。数据驱动的方式能够让策划自由定义游戏对象类型,并且随时可自由创建新的对象类型。

posted @ 2010-04-23 16:06 avatar 阅读(508) | 评论 (0)编辑 收藏

2010年4月21日

转【游戏开发】MMORPG服务器架构

一个比较抽象的MMORPG服务器架构,简单描述。
登录分配服务器:游戏区唯一入口点,根据登录服务器负载和排队情况,分配登录玩家到指定登录服务器中进行登录;
登录服务器:处理玩家验证身份合法性,及游戏选游戏世界操作,控制服务器人数;
数据服务器:处理玩家数据读取,保存和缓存的地方;
世界服务器:整个游戏时间的中心管理数据的服务器;
游戏服务器:处理玩家主逻辑的服务器;
道具服务器:处理所有直接道具相关逻辑的服务器,管理道具的产生消亡,交易,合成,升级,等操作;
网关服务器:提供给玩家与游戏服务器的中转服务器,玩家一旦登录游戏,始终处于一个网关服务器;
聊天服务器:提供更丰富的聊天形式,消息优化,转发。
中心验证服务器:与登录服务器通信,主要进行玩家身份验证,及反馈用户信息。
中心计费服务器:充值,扣费,交易操作的处理。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/olncy/archive/2008/12/15/3520572.aspx

一个高性能MMORPG网络游戏的架构实例

一、服务器组模型的选型
       考虑到近年来计算机硬件技术的飞速发展,物理服务器的性价比得到了很大的提高,结合项目需要通过服务器组给数万玩家提供高质量服务的商业要求,经过研究对比数种服务器模型后,决定采取了上图所示的服务器组模型。

 


二、MMORPG服务器系统架构

       MMORPG大型网游服务器是使用高性能网络I/O模型配合消息队列连接各服务线程的一个非常稳定的高性能网游系统。其中消息队列系基于共享内存自行开发完成。在单服务器标准工作环境下进行测试,一台双 XEON 服务器可以非常轻松地达到为4,500用户每秒处理5,000请求,每秒处理请求数可超过225,000。

三、MMORPG的实现

       首先,在基础建设方面,与规划现实中的城市一样,得先搭建起一系列的房屋、道路及出口、管线和诸多NPC人物等构成的基本要素和活动空间,通过在服务器端(Server side)取得预先设计好的综合地理、NPC人物、技能等一系列的初始化数字数据(具体文档片段请见附件A.地图数据文件示例和附件B.司机 NPC 数据文件示例),然后依靠程序将数字数据和游戏逻辑有机地协调起来,最终形成一套完整的虚拟游戏基础空间。

 

       在确定了地图数据生成规则后,就可以使用编辑器任意编辑游戏场景。依赖于这样良好的基础设施,才能在其他游戏逻辑的配合下实现完整的故事情节。同时服务器端负责将属于用户各自的游戏逻辑数据通过验证后发送到合法的用户客户端机器里,完成客户端游戏逻辑的建立和数据同步。担负服务器与客户端通讯的是自定义格式的数据通讯封包,它就像数字神经般贯穿着整个游戏的始终。数据封包与如下4部分消息有关,它们分别为场景消息, 同步消息,主角消息和界面消息。

   

       A.主角消息包括客户端所控制的角色的所有动作,包括走路,聊天、交易、战斗等。
   

       B.场景消息包括昼夜兴替、气候变化,一定的时间在场景里出现某些东西等,这类消息具有的特点是所有消息的发起者都是服务器,广播对象则是场景里的所有玩家。
   

       C.同步消息是针对发起对象是某个玩家,经过服务器广播给所有看得见他的玩家,该消息也包括所有的动作,该种消息是服务器广播给客户端的,主角消息则一般是客户端主动发给服务器端。
   

       D.界面消息是服务器发给客户端的聊天消息和各种属性及状态变化的信息。

 

       值得一谈的还有处于网络游戏中比较重要的服务器同客户端消息广播和同步问题。其中一种方法是采取在国际上被称为 Mutual synchronization(相互同步),是一种对未来网络的前景的良好预测出来的解决方案来解决确保每个玩家在各自客户端上看到的东西大体是一样的同步问题。

 

       首先客户端需要在登录游戏的时候建立很多张广播列表,这些列表在客户端后台和服务器端要保持不定时同步。其中要建立多张列表,是因为要广播的类型包括全局信息、本地信息和远程信息等等,这些列表都是在客户端登陆的时候根据服务器发过来的消息建立好的。在建立列表的同时,还需要获得每个列表中广播对象的传输时间,并且要维护一张完整的用户状态列表在后台,也是进行不定时的和服务器进行同步,根据本地的用户状态表,可以使一部分决策由客户端来决定,当客户端发送这部分决策的时候,则直接将最终决策发送到各个广播列表里面的客户端,并对其时间进行校对,以保证每个客户端在收到的消息的时间是和本地时间进行校对过的,再采用预测补偿计算提前量的方法,计算出提前量,根据计算量确定实际行走速度,将会使同步变得非常的平滑。

 

       其中,广播的重点就在于如何计算出广播的对象,首先在服务器端的连接结构里面增加一个广播对象的队列,该队列在客户端登陆服务器的时候由服务器传输给合法的客户端,然后由客户端自己来维护这个队列,当有人走出客户端视野的时候,由客户端主动要求服务器给那个对象发送消失的消息。

 

       当有人走进视野的情况,则先需要客户端在每次给服务器发送更新位置的消息的时候,服务器都给该连接算出一个视野范围,然后在需要广播的时候,循环整张地图上的玩家,找到坐标在其视野范围内的玩家从而完成广播的全过程。

 

       其次是虚拟对象系统。其中主要会涉及到NPC的概念,尤其是广泛应用的A Star算法等在提供NPC的人工智能决策方面有着重要的作用。NPC智能使用一种是被动触发事件和是主动触发事件的方式由计算机来实现对NPC做何种决策。A Star算法就是典型的启发式搜索的应用,其普通原理是先设计一个Rule() 函数,来获这一个点的代价,然后每次搜索的时候把下一步可能到达的所有点都经过Rule() 函数评价一下,获取两到三个代价比较小的点,继续搜索,直至得到代价最小的一个点。最明显的应用是NPC在实现自动选择攻击目标和逃跑时的实现。实现自动选择攻击目标时,首先获得地图上距离该NPC附近的敌人列表,设计相应Rule() 函数,根据敌人的强弱、远近,判断出几个评估数据,然后选择代价最小的敌人进行主动攻击。逃跑则是在主动事件里面检查自己的HP,如果HP低于某个值,而敌人正近战攻击的时候,则触发逃跑函数,在逃跑函数里面也是对周围的所有的敌人组织成列表,然后设计Rule() 函数,先分析选择出对你构成威胁最大的敌人,该函数还需要判断敌人的运动速度,战斗力强弱,最后得出一个主要敌人,然后针对该主要敌人进行路径的Rule() 的函数的设计,搜索的范围只可能是和主要敌人相反的方向,然后再根据该几个方向上的敌人的强弱来计算代价,做出最后的选择,如果幸运的话,可以有80%的机率逃往没有 NPC 阻挡的邻近地图中去。

 

       最后,由于脚本是RPG游戏的灵魂,自然脚本编译器就扮演了十分重要的地位。在基于编译的服务器端程序中,是无法在程序的运行过程中构建一些东西的,所以必须通过脚本编译器提供一些简单的语法和文法解析的功能,进行一系列的逻辑判断和循环,以提高服务器端的灵活程度。可使用类似汇编语言的那种结构来实现脚本编译器,设置一些条件跳转和循环来实现逻辑判断和循环。提供一些通用指令,如判断、循环、四则运算、寻址等等,针对不同的脚本采用不同的解析方法,对NPC就用NPC固定的脚本,对Item就用Item固定的脚本,解析完以后就把结果生成底层该对象的结构便于使用。

 

       经过以上的建设步骤,一个完整的MMORPG网络游戏系统就被逐步建立起来了。

ZZ MDNA网络游戏整体架构编程

posted @ 2010-04-21 23:17 avatar 阅读(1053) | 评论 (0)编辑 收藏

【游戏开发】LuaForWindows 环境配置

LuaForWindows 环境配置

from: http://bbs.luaer.cn/read-Lua-tid-233.html


环境:lua for windows (lfW)
主页:http://luaforwindows.luaforge.net/

lua for windows其实是一整套Lua的开发环境,它包括:
Lua Interpreter(Lua解释器)
Lua Reference Manual(Lua参考手册)
Quick Lua Tour (Lua快速入门)
Examples (Lua范例)
Libraries with documentation (一些Lua库和文档)
SciTE (一个很棒的多用途编辑器,已经对Lua做了特殊设置)

其它详细的内容请到luaforge的主页上查看。

之所以推荐这套环境是因为它整合了在windows学习和开发Lua所需要的所有东西

,对于新手来说是非常体贴的,附带的SciTE只要经过简单配置就能够很方便的编

写Lua程序,编译,运行,调试。它还是附带自动提示和代码自动补全功能的哦,

对于用惯VC + VA的开发人员来说,实在是太亲切了。

下面介绍一下整个lfW:
下载lfw,最新的版本是5.1.3.13,直接安装,注意最后一步会询问SciTE编辑器

是否使用“黑色”风格,我比较喜欢黑色底色,所以在这里打钩,之后继续。
安装完成后,学习Lua所需要的一切环境就全部安装完毕,十分简单。

下面可以测试是否安装成功
打开SciTE,新建一个文件,输入一行lua代码:

print("hello,lua")

然后保存为hello.lua,注意保存文件时要加文件名后缀.lua,否则可能不能正确

的运行。
按F5,如果SciTE的输出窗口出现
>lua -e "io.stdout:setvbuf 'no'" "hello.lua"
hello,lua
>Exit code: 0
字样则代表整个lua开发环境安装成功。

如果对SciTE默认的配色方案或者字体不满意,点击【Options】菜单中的【Open

Global Options File】,则可以看到SciTE环境的全局配置文件。里面可以修改

的包括字体,颜色,窗口布局等等,修改相应的值即可。如果找不到自己想要修

改的项目,可以再到【Options】的【Open black.properties】(如果使用的是

白色背景,这里则是white.properties)文件中查找,这里存储的是更加细致的

属性配置。修改这两个文件,基本上就能够满足大部分学习或是开发者的个人喜

好。还有一点,在Global Options File中,查找command.help.*.lua,后面对应

的是在编辑器中按下F1键弹出的chm格式的lua手册文件路径,这里需要修改一下

,把文件路径改正确就可以了(5.1.3版本似乎没这个问题了)。

整个环境还附带一个QuickLuaTour,是一个用Lua写的一个基于控制台的小教程,

很易于理解。

基本上整个环境就是这样,之后就可以开始学习Lua了。

posted @ 2010-04-21 23:15 avatar 阅读(1016) | 评论 (0)编辑 收藏

【推荐】强大的代码阅读工具Understand

【推荐】强大的代码阅读工具Understand
4 个附件
_http://www.scitools.com/products/understand/

Understand软件的功能主要定位于代码的阅读理解。界面貌似是用Qt开发的。

具备如下特性:
1、支持多语言:Ada, C, C++, C#, Java, FORTRAN, Delphi, Jovial, and PL/M ,混合语言的project也支持
2、多平台: Windows/Linux/Solaris/HP-UX/IRIX/MAC OS X
3、代码语法高亮、代码折叠、交叉跳转、书签等基本阅读功能。
4、可以对整个project的architecture、metrics进行分析并输出报表。
5、可以对代码生成多种图(butterfly graph、call graph、called by graph、control flow graph、UML class graph等),在图上点击节点可以跳转到对应的源代码位置。
6、提供Perl API便于扩展。作图全部是用Perl插件实现的,直接读取分析好的数据库作图。
7、内置的目录和文件比较器。
8、支持project的snapshot,并能和自家的TrackBack集成便于监视project的变化。

小技巧(官网的FAQ里有):

1、设置字体和颜色风格

修改默认字体:Tools -> Options -> Editor -> Default style
修改颜色: Tools -> Options -> Editor -> Styles

2、生成UML类图、调用树图

默认安装的插件不支持这两种图,需要从官网下载插件。
_http://www.scitools.com/perl_scripts/uperl/uml_class.upl
_http://www.scitools.com/perl_scripts/uperl/invocation.upl
放到sti/conf/scripts/local目录下。
然后重新运行,执行 project-> project graphical views -> xxxx可以生成这两种图。

3、更改图的字体

直接修改对应的脚本文件(\Program Files\STI\conf\scripts目录下),在do_load( )函数的对应位置加入如下的设置:

$graph->default("fontname","Consolas","node");
$graph->default("fontsize","10","node");
$graph->default("fontname","Consolas","edge");
$graph->default("fontsize","10","edge");

注意:有的脚本中的作图变量名不是 $graph 而是 $g。

另外一款代码可视化理解工具http://www.sgvsarc.com/prod_crystalrevs_screenshots.htm

posted @ 2010-04-21 23:14 avatar 阅读(5879) | 评论 (0)编辑 收藏

转【游戏开发】利用CEGUI+Lua实现灵活的游戏UI框架

利用CEGUI+Lua实现灵活的游戏UI框架

      在上一篇文章中,介绍了一种基于组件方式的游戏UI架构设计方案,在这里,笔者将介绍如何利用CEGUI和Lua来实现这种灵活的框架。

       CEGUI是一个兼容OpenGL、DirectX的优秀开源GUI库,关于她的介绍以及如何在Direct3D中使用她,可以参考http://blog.csdn.net/Lodger007/archive/2007/07/02/1675141.aspx一文。Lua是一种强大的脚本语言,她使用栈作为数据接口,能够很容易地与其它语言交互,关于她的介绍可以参考http://www.lua.org/,以及笔者以前翻译的三篇系列文章:Lua入门(http://blog.csdn.net/Lodger007/archive/2006/06/26/836466.aspx)、调用Lua函数(http://blog.csdn.net/Lodger007/archive/2006/06/26/836897.aspx)、在Lua中调用C++函数(http://blog.csdn.net/Lodger007/archive/2006/06/26/837349.aspx)。
       在实现中,作为UI组件管理器的GUISystem是一个单件,这样,你能够很方便地在任何地方使用其全局唯一的对象。下面是Singleton和GUISystem的实现代码:

posted @ 2010-04-21 23:13 avatar 阅读(830) | 评论 (0)编辑 收藏

[转]c++调用lua脚本1(平台windows)

 通过c++调用lua 脚本,
    环境VC++6.0
    lua  sdk 5.1


   在调用前 先认识几个函数。
1. 调用lua_open()将创建一个指向Lua解释器的指针。
2. luaL_openlibs()函数加载Lua库。
3. 使用luaL_dofile()加载脚本并运行脚本。
4. lua_close()来关闭Lua指向解释器的指针。
 
5. 调用lua_getglobal()将add()函数压入栈顶,add()为lua函数。
6. 第一个参数x,通过调用lua_pushnumber()入栈。
7. 再次调用lua_pushnumber()将第二个参数入栈。
8. 使用lua_call()调用Lua函数。
9. 调用lua_tonumber()从栈顶取得函数的返回值。
10. lua_pop()移除栈顶的值。


代码
add.lua
1function add ( x, y )
2    return x + y
3end
4

main.cpp
#include <stdio.h>

extern "C" {
#include 
"lua.h"
#include 
"lualib.h"
#include 
"lauxlib.h"
}


/* the Lua interpreter */
lua_State  
* L;

int luaadd ( int x, int y )
{
    
int sum;
    
    
//函数名
    lua_getglobal(L, "add");
    
    
//第一个参数压栈
    lua_pushnumber(L, x);
    
    
//第二个参数压栈
    lua_pushnumber(L, y);

    
//调用函数
    lua_call(L, 21);
    
    
//得到返回值
    sum = (int)lua_tonumber(L, -1);
    lua_pop(L, 
1);
    
    
return sum;
}


int main ( int argc, char *argv[] )
{
    
int sum;
    
    
//创建一个指向Lua解释器的指针。
    L = lua_open();
    
    
//函数加载Lua库
    luaL_openlibs(L);

    
//加载脚本
    luaL_dofile(L,"add.lua");
    
    
//调用函数
    sum = luaadd( 1011);
    
    
// print the result 
    printf( "The sum is %d\n", sum );
    
    
//关闭 释放资源    
    lua_close(L);
    
    
return 0;
}



注意问题:
1.工程头文件lua.h等,编译器能找到,可以通过工具来设置头文件路径。
2. 添加lua5.1.lib到Object/library modules列表中。

测试结果
The sum is 21

关于lua的认识
http://www.cppblog.com/expter/archive/2008/12/24/70224.html

posted @ 2010-04-21 23:00 avatar 阅读(334) | 评论 (0)编辑 收藏

转经典塔防游戏TowersTrap-[lua复刻版本,附全部lua源代码]

图片:
Click Here To EnLarge
经典塔防游戏TowersTrap

这是个很经典的塔防游戏,非常耐玩, 喜欢的朋友可以试试。

游戏介绍: 玩过魔兽的朋友应该对这个不陌生了,游戏中只有一个出口,游戏有多个难度选择,玩家必须在"怪"到达出口之前消灭它们。

操作介绍:先确定塔的类型,然后在图中任意位置放下。点击已建造的塔后塔的上下会出现数字,分别是升级和出售的价钱,升级塔也很重要哦~


安装说明:
1.下载安装游戏引擎love2d
http://www.love2d.org/download
2.下载游戏文件
http://219.136.249.30/svn_readonly/towerstrap/dist/towerstrap.love

3.游戏下载到任意目录后(注意,love2d引擎不支持中文目录),双击towerstrap.love文件就可以开始游戏了

源代码:
svn:
http://219.136.249.30/svn_readonly/towerstrap/
交流请加qq群:阿兜明@83086500

posted @ 2010-04-21 22:57 avatar 阅读(3616) | 评论 (10)编辑 收藏

Lua脚本语法说明(修订)

Lua脚本语法说明(增加lua5.1部份特性)

  Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。
  所以,我只简单的归纳一下Lua的一些语法规则,使用起来方便好查就可以了。估计看完了,就懂得怎么写Lua程序了。

  在Lua中,一切都是变量,除了关键字。

I.  首先是注释
  写一个程序,总是少不了注释的。
  在Lua中,你可以使用单行注释和多行注释。
  单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。
  多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*...*/"。在注释当中,"[["和"]]"是可以嵌套的(在lua5.1中,中括号中间是可以加若干个"="号的,如 [==[ ... ]==]),见下面的字符串表示说明。

II.  Lua编程
  经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单:
  print("Hello world")
  在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。
  Lua 有好几种程序控制语句,如:
控制语句 格式 示例
If if 条件 then ... elseif 条件 then ... else ... end
if 1+1=2 then print("true")
elseif 1+2~=3 then print("true")
else print("false"end

While while 条件 do ... end
while 1+1~=2 do print("true"end

Repeat repeat ... until 条件
repeat print("Hello"until 1+1~=2

For for 变量=初值, 终点值, 步进 do ... end
for i = 1102 do print(i) end

For for 变量1, 变量2, ... 变量n in 表或枚举函数 do ... end
for a,b in mylist do print(a, b) end


  注意一下,for的循环变量总是只作用于for的局部变量;当省略步进值时,for循环会使用1作为步进值。
  使用break可以用来中止一个循环。
  相对C语言来说,Lua有几个地方是明显不同的,所以面要特别注意一下:

  .语句块
    语句块在C中是用"{"和"}"括起来的,在Lua中,它是用do 和 end 括起来的。比如:
    do print("Hello") end
    可以在 函数 中和 语句块 中定局部变量。

  .赋值语句
    赋值语句在Lua被强化了。它可以同时给多个变量赋值。
    例如:
    a,b,c,d=1,2,3,4
    甚至是:
    a,b=b,a  -- 多么方便的交换变量功能啊。
    在默认情况下,变量总是认为是全局的。假如需要定义局部变量,则在第一次赋值的时候,需要用local说明。比如:
    local a,b,c = 1,2,3  -- a,b,c都是局部变量

  .数值运算
    和C语言一样,支持 +, -, *, /。但Lua还多了一个"^"。这表示指数乘方运算。比如2^3 结果为8, 2^4结果为16。
    连接两个字符串,可以用".."运处符。如:
    "This a " .. "string." -- 等于 "this a string"

  .比较运算
比较符号 < > <= >= == ~=
含义 小于 大于 小于或等于 大于或等于 相等 不相等

    所有这些操作符总是返回true或false。
    对于Table,Function和Userdata类型的数据,只有 == 和 ~=可以用。相等表示两个变量引用的是同一个数据。比如:
    a={1,2}
    b
=a
    
print(a==b, a~=b)  --输出 true, false
    a={1,2}
    b
={1,2}
    
print(a==b, a~=b)  --输出 false, true


  .逻辑运算
    and, or, not
    其中,and 和 or 与C语言区别特别大。
    在这里,请先记住,在Lua中,只有false和nil才计算为false,其它任何数据都计算为true,0也是true!
    and 和 or的运算结果不是true和false,而是和它的两个操作数相关。
    a and b:如果a为false,则返回a;否则返回b
    a or b:如果 a 为true,则返回a;否则返回b

    举几个例子:
     print(4 and 5--输出 5
     print(nil and 13--输出 nil
     print(false and 13--输出 false
     print(4 or 5--输出 4
     print(false or 5--输出 5

    在Lua中这是很有用的特性,也是比较令人混洧的特性。
    我们可以模拟C语言中的语句:x = a? b : c,在Lua中,可以写成:x = a and b or c。
    最有用的语句是: x = x or v,它相当于:if not x then x = v end 。

  .运算符优先级,从低到高顺序如下:
     or
     and
     <     >     <=    >=    ~=    ==
     .. (字符串连接)
     +     -
     *     /     %
     not   #(lua5.1 取长度运算)     - (一元运算)
     ^
和C语言一样,括号可以改变优先级。

III.  关键字
  关键字是不能做为变量的。Lua的关键字不多,就以下几个:
    
and break do else elseif
end false for function if
in local nil not or
repeat return then true until while

IV.  变量类型
  怎么确定一个变量是什么类型的呢?大家可以用type()函数来检查。Lua支持的类型有以下几种:
Nil 空值,所有没有使用过的变量,都是nil。nil既是值,又是类型。
Boolean 布尔值,只有两个有效值:true和false
Number 数值,在Lua里,数值相当于C语言的double
String 字符串,如果你愿意的话,字符串是可以包含"\0"字符的(这和C语言总是以"\0"结尾是不一样的)
Table 关系表类型,这个类型功能比较强大,请参考后面的内容。
Function 函数类型,不要怀疑,函数也是一种类型,也就是说,所有的函数,它本身就是一个变量。
Userdata 嗯,这个类型专门用来和Lua的宿主打交道的。宿主通常是用C和C++来编写的,在这种情况下,Userdata可以是宿主的任意数据类型,常用的有Struct和指针。
Thread 线程类型,在Lua中没有真正的线程。Lua中可以将一个函数分成几部份运行。如果感兴趣的话,可以去看看Lua的文档。
现在回过头来看看,倒觉得不是线程类型。反而象是用来做遍历的,象是Iterator函数。
如:
function range(n)
  local
= 0
  
while(i < n) do
    coroutine.yield( i )
    i = i + 1
  
end
end
可惜的是要继续运行,需要coroutine.resume函数,有点鸡肋。请指教。

V.  变量的定义
  所有的语言,都要用到变量。在Lua中,不管在什么地方使用变量,都不需要声明,并且所有的这些变量总是全局变量,除非我们在前面加上"local"。这一点要特别注意,因为我们可能想在函数里使用局部变量,却忘了用local来说明。
  至于变量名字,它是大小写相关的。也就是说,A和a是两个不同的变量。
  定义一个变量的方法就是赋值。"="操作就是用来赋值的
  我们一起来定义几种常用类型的变量吧。
  A.  Nil
    正如前面所说的,没有使用过的变量的值,都是Nil。有时候我们也需要将一个变量清除,这时候,我们可以直接给变量赋以nil值。如:
    var1=nil  -- 请注意 nil 一定要小写

  B.  Boolean
    布尔值通常是用在进行条件判断的时候。布尔值有两种:true 和 false。在Lua中,只有false和nil才被计算为false,而所有任何其它类型的值,都是true。比如0,空串等等,都是true。不要被 C语言的习惯所误导,0在Lua中的的确确是true。你也可以直接给一个变量赋以Boolean类型的值,如:
    theBoolean = true

  C.  Number
    在Lua中,是没有整数类型的,也不需要。一般情况下,只要数值不是很大(比如不超过100,000,000,000,000),是不会产生舍入误差的。在WindowsXP能跑的当今主流PC上,实数的运算并不比整数慢。
    实数的表示方法,同C语言类似,如:
    4 0.4 4.57e-3 0.3e12 5e+20

  D.  String
    字符串,总是一种非常常用的高级类型。在Lua中,我们可以非常方便的定义很长很长的字符串。
    字符串在Lua中有几种方法来表示,最通用的方法,是用双引号或单引号来括起一个字符串的,如:
    "That's go!"
    或
    'Hello world!'

    和C语言相同的,它支持一些转义字符,列表如下:
    \a  bell
    \b  back space
    \f  form feed
    \n  newline
    \r  carriage return
    \t  horizontal tab
    \v  vertical tab
    \\  backslash
    \"  double quote
    \"  single quote
    \[  left square bracket
    \]  right square bracket

    由于这种字符串只能写在一行中,因此,不可避免的要用到转义字符。加入了转义字符的串,看起来实在是不敢恭维,比如:
    "one line\nnext line\n\"in quotes\", "in quotes""
    一大堆的"\"符号让人看起来很倒胃口。如果你与我有同感,那么,我们在Lua中,可以用另一种表示方法:用"[["和"]]"将多行的字符串括起来。(lua5.1: 中括号中间可以加入若干个"="号,如 [==[ ... ]==],详见下面示例
    示例:下面的语句所表示的是完全相同的字符串:
= 'alo\n123"'
= "alo\n123\""
= '\97lo\10\04923"'
= [[alo
123"
]]
= [==[
alo
123"
]==]

    值得注意的是,在这种字符串中,如果含有单独使用的"[["或"]]"就仍然得用"\["或"\]"来避免歧义。当然,这种情况是极少会发生的。

  E.  Table
    关系表类型,这是一个很强大的类型。我们可以把这个类型看作是一个数组。只是C语言的数组,只能用正整数来作索引;在Lua中,你可以用任意类型来作数组的索引,除了nil。同样,在C语言中,数组的内容只允许一种类型;在Lua中,你也可以用任意类型的值来作数组的内容,除了nil。
    Table的定义很简单,它的主要特征是用"{"和"}"来括起一系列数据元素的。比如:
    T1 = {}  -- 定义一个空表
    T1[1]=10  -- 然后我们就可以象C语言一样来使用它了。

    T1["John"]={Age=27, Gender="Male"}
    这一句相当于:
    T1
["John"]={}  -- 必须先定义成一个表,还记得未定义的变量是nil类型吗
    T1["John"]["Age"]=27
    T1
["John"]["Gender"]="Male"
    当表的索引是字符串的时候,我们可以简写成:
    T1.John
={}
    T1.John.Age
=27
    T1.John.Gender
="Male"
    或
    T1.John{Age
=27, Gender="Male"}
这是一个很强的特性。

    在定义表的时候,我们可以把所有的数据内容一起写在"{"和"}"之间,这样子是非常方便,而且很好看。比如,前面的T1的定义,我们可以这么写:
    T1=
    {
      
10,  -- 相当于 [1] = 10
      [100] = 40,
      John
=  -- 如果你原意,你还可以写成:["John"] =
      {
        Age
=27,   -- 如果你原意,你还可以写成:["Age"] =27
        Gender=Male   -- 如果你原意,你还可以写成:["Gender"] =Male
      },
      
20  -- 相当于 [2] = 20
    }

    看起来很漂亮,不是吗?我们在写的时候,需要注意三点:
    第一,所有元素之间,总是用逗号","隔开;
    第二,所有索引值都需要用"["和"]"括起来;如果是字符串,还可以去掉引号和中括号;
    第三,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编;

    表类型的构造是如此的方便,以致于常常被人用来代替配置文件。是的,不用怀疑,它比ini文件要漂亮,并且强大的多。

  F.  Function
    函数,在Lua中,函数的定义也很简单。典型的定义如下:
    function add(a,b)  -- add 是函数名字,a和b是参数名字
     return a+b  -- return 用来返回函数的运行结果
    end

    请注意,return语言一定要写在end之前。假如我们非要在中间放上一句return,那么就应该要写成:do return end。
    还记得前面说过,函数也是变量类型吗?上面的函数定义,其实相当于:
    add = function (a,b) return a+end
当重新给add赋值时,它就不再表示这个函数了。我们甚至可以赋给add任意数据,包括nil (这样,赋值为nil,将会把该变量清除)。Function是不是很象C语言的函数指针呢?

    和C语言一样,Lua的函数可以接受可变参数个数,它同样是用"..."来定义的,比如:
    function sum (a,b,)
如果想取得...所代表的参数,可以在函数中访问arg局部变量(表类型)得到 (lua5.1: 取消arg,并直接用"..."来代表可变参数了,本质还是arg)。
    如 sum(1,2,3,4)
    则,在函数中,a = 1, b = 2, arg = {3, 4}  (lua5.1:  a = 1, b = 2, ... = {3, 4})
    更可贵的是,它可以同时返回多个结果,比如:
    function s()
      
return 1,2,3,4
    
end
    a,b,c,d 
= s()  -- 此时,a = 1, b = 2, c = 3, d = 4

    前面说过,表类型可以拥有任意类型的值,包括函数!因此,有一个很强大的特性是,拥有函数的表,哦,我想更恰当的应该说是对象吧。Lua可以使用面向对象编程了。不信?举例如下:
    t =
    {
     Age 
= 27
     
add = function(self, n) self.Age = self.Age+end
    }
    
print(t.Age)  -- 27
    t.add(t, 10)
    
print(t.Age)  -- 37

    不过,t.add(t,10) 这一句实在是有点土对吧?没关系,在Lua中,我们可以简写成:
    t:add(10)    -- 相当于 t.add(t,10)

  G.  Userdata 和 Thread
    这两个类型的话题,超出了本文的内容,就不打算细说了。

VI.  结束语
  就这么结束了吗?当然不是,接下来,我们需要用Lua解释器,来帮助理解和实践了。相信这样会更快的对Lua上手了。
  就象C语言一样,Lua提供了相当多的标准函数来增强语言的功能。使用这些标准函数,可以很方便的操作各种数据类型,并处理输入输出。有关这方面的信息,我们可以参考《Programming in Lua 》一书,也可以在网络上直接观看电子版,网址为:http://www.lua.org/pil/index.html
  
备注:本文的部份内容摘、译自lua随机文档。
相关链接:
1. Lua 官方网站: http://www.lua.org
2. Lua Wiki网站,你可以在这里找到很多相关的资料,如文档、教程、扩展,以及C/C++的包装等: http://lua-users.org/wiki/
3. Lua 打包下载(包括各种平台和编译器的工程文件如vs2003,vs2005):http://luabinaries.luaforge.net/download.html

这是我编译好的Lua5.02的解释器:http://files.cnblogs.com/ly4cn/lua.zip

posted @ 2010-04-21 22:55 avatar 阅读(253) | 评论 (0)编辑 收藏

Lua 脚本 C++ 封装库 LuaWrapper

转载: http://www.d2-life.com/LBS/blogview.asp?logID=41

  使用Lua作脚本,主要是因为它小巧玲珑(体积小,运行快),而且它的语法又比较简单明了。不过,使用LuaAPI将Lua引擎集成到程序中,确实有一些不方便——用落木随风网友的话来说,就是"就象用汇编"。当然,现在你不用再这么辛苦了,因为你可以使用LuaWrapper For C++。使用这个工具,在C++中集成Lua脚本就是轻而易举的事。你原有的C++函数和类,几乎不需要任何改变,就可以与Lua脚本共享。

作者: 沐枫 (第二人生成员)
版权所有转载请注明原出处
主页:第二人生 http://www.d2-life.com
   http://www.d2-life.com/LBS/blogview.asp?logID=41

为什么要用Lua作脚本?
  使用Lua作脚本,主要是因为它小巧玲珑(体积小,运行快),而且它的语法又比较简单明了。不过,使用LuaAPI将Lua引擎集成到程序中,确实有一些不方便——用落木随风网友的话来说,就是"就象用汇编"。当然,现在你不用再这么辛苦了,因为你可以使用LuaWrapper For C++。使用这个工具,在C++中集成Lua脚本就是轻而易举的事。你原有的C++函数和类,几乎不需要任何改变,就可以与Lua脚本共享。
  我们接下来,用实例来说明,如何用LuaWrapper来集成Lua脚本到你的程序中去。

1.  创建Lua引擎
  LuaWrap lua; 或者 LuaWrap* lua = new LuaWrap;
  创建一个LuaWrap对象,就是创建一个Lua脚本引擎。并且根据Lua的特性,你可以创建任意多个Lua引擎,甚至可以分布在不同的线程当中。

2.  装载并执行脚本程序
  你可以从缓冲区中装载Lua脚本:
  lua.LoadString(
    "print('Hello World')"
  );
  当然,你也可以从文件中装入,并执行Lua脚本:
  Lua.LoadFile("./test.lua");
  Lua的脚本,可以是源代码,也可以经过编译后的中间代码。也许你对编译后的中间代码更感兴趣——如果你不希望让源代码赤裸裸的袒露在大家的眼前。

3.  获取和设置Lua变量
  能够获取和设置脚本变量的内容,是一个最基本的功能。你可以使用GetGlobal和SetGlobal函数来做到这一点:
  (1)  获取变量:
    int a = lua.GetGlobal<int>("a");
    LuaTable table = lua.GetGlobal<LuaTable>("t");
    这里,<> 里头的类型,就是想要的变量的类型。
  (2)  设置变量:
    lua.SetGlobal("a", a);
    lua.SetGlobal("t", table);

4.  调用Lua函数
  使用Call函数,就可以很简单的从你的程序中调用Lua函数:
  lua.Call<void>("print", "Hello World");
  int sum = lua.Call<int>("add", 2, 3);
  这里,<> 里头的类型是返回值的类型。

5.  如何让Lua也能调用C++的函数
  精采的地方来了。假如有下面这样的一个函数:
  int add(int a, int b)
  {
    return a + b;
  }
  如果想让它能够让Lua使用,只需将它注册到Lua引擎当中就可以了:
  lua.RegisterFunc("add", int(int,int), add);
  这样,Lua中就可以用直接使用了:
  (Lua脚本)sum = add(1, 3)

  (*) RegisterFunc的功能,就是让你把C++的函数注册到Lua中,供Lua脚本使用。
    第一个参数,是想要在Lua中用的函数名。
    第二个参数,是C++中函数的原型; C++允许函数重载的,你可以使用函数原型,来选择需要注册到Lua引擎中的那个函数。
    第三个参数,就是C++中函数的指针了。

6.  如何能让C++的类在Lua中使用
  我们先看看下面这个C++类:
class MyArray
{
  std::vector<double> array;
public:
  void setvalue(int index, double value);
  double getvalue(int index);
  int size();
  const char* ToString();
};

  你准备要让Lua能够自由访问并操作这个类。很简单,你只需增加几个宏定义就可以了:

class MyArray
{
  std::vector<double> array;
public:
  void setvalue(int index, double value);
  double getvalue(int index);
  int size();
  const char* ToString();
  // 将一个 class 作为一个 Lua 对象是很容易的,只需要增加以下宏定义。
  DEFINE_TYPENAME("My.array");
  BEGIN_REGLUALIB("array")
      LUALIB_ITEM_CREATE("new", MyArray )  // 创建MyArray 
      LUALIB_ITEM_DESTROY("del", MyArray )  // 消除MyArray。
  END_REGLUALIB()
  BEGIN_REGLUALIB_MEMBER()
    LUALIB_ITEM_FUNC("size", int (MyArray*), &MyArray::size)
    LUALIB_ITEM_FUNC("__getindex", double(MyArray*, int), &MyArray::getvalue)  
    LUALIB_ITEM_FUNC("__newindex", void (MyArray*, int, double), &MyArray::setvalue)
    LUALIB_ITEM_FUNC("__tostring", const char* (MyArray*), &MyArray::ToString)
    LUALIB_ITEM_DESTROY("__gc", MyArray )   // 垃圾收集时消除对象用。
  END_REGLUALIB_MEMBER()
};

  只要有了这些宏定义,这个类就是可以在Lua中使用的类了,我们就可以在Lua中注册这个类了:
  lua.Register<MyArray>()

  这样注册以后,我们在Lua中就可以使用这个类了:
  a = array.new()  -- 创建对象,相当于 a = new Myarray
  a[1] = 10  -- 调用__newindex,也就是C++中的 a->setvalue(1, 10)
  a[2] = 20  -- 调用__newindex,也就是C++中的 a->setvalue(2, 20)
  print(
    a,  -- 调用 __tostring,也就是C++中的 a->ToString()
    a:size(), -- 相当于C++中的 a->size()
    a[1], -- 调用__getindex,也就是C++中的a->getvalue(1)
    a[2]) --调用__getindex,也就是C++中的a->getvalue(2)
  array.del(a)  -- 清除对象,相当于 delete a
  a = nil  -- 清空 a,很象C++中的 a = NULL

  当然,你也可以不用del这个对象,而是等待Lua帮你自动进行垃圾回收。在Lua进行垃圾回收时,它会自动调用这个对象的 __gc ,相当于 delete。

  那么,在C++中要创建MyArray对象,并且传递给Lua全局变量怎么办?就象前面讲过的一样,使用SetGlobal:
  MyArray* a = new MyArray;
  lua.SetGlobal("a", a);
  要获取该对象,同样的,应该使用GetGlobal:
  MyArray* a = lua.GetGlobal<MyArray>("a");
  
  对于传递给Lua的对象,就让Lua来管理该对象的生存周期好了。如果你非要删除它的话,你可以使用DelGlobalObject:
  lua.DelGlobalObject<MyArray>("a");
  不过这么做的话,你应当明白你在做什么,因为在Lua的脚本中,可能已经在多处引用了这个对象了。删除了其中一个,将导致其它引用对象失效,从而可能引致系统崩溃。

  (1)  DEFINE_TYPENAME("My.array");
    定义类型的名称。在Lua中,这个类型名称是唯一用来识别C++类型的,你必须为不同的对象给予不同的名称。

  (2)  BEGIN_REGLUALIB("array") ... END_REGLUALIB()
    你可以为一个对象定义一个程序库,"array"就是程序库的名字。在程序库中定义的函数是全局函数,在Lua中,使用该函数,需要在函数前加上库的名字,如:array.new()。通常,程序库会包含创建对象的方法。如:
    LUALIB_ITEM_CREATE("new", MyArray )  // 创建MyArray

    这样子,你才能在Lua中创建MyArray:
    a = array.new()
  
    你也可以选择增加一个删除对象操作:
    LUALIB_ITEM_DESTROY("del", MyArray )   // 删除MyArray
    这样,你就可以直接删除一个对象了:
    array.del(a)

  (3)  BEGIN_REGLUALIB_MEMBER() ...END_REGLUALIB_MEMBER()
    在此处,你可以定义对象的成员函数,也可以重载对象的操作符——是的,就象C++的operator重载。例如:
    LUALIB_ITEM_FUNC("__newindex", void (MyArray*, int, double), &MyArray::setvalue)
    就是重载 operator[] 操作符。Lua中可重载的操作符还有许多,如:

    __getindex:操作符[],支持读取访问,如 v = a[10]
    __newindex:操作符[],支持赋值访问,如 a[10] = 1.22
    __tostring:将变量转换成字串__add:等同于operator +
    __add:操作符 +
    __sub:操作符 -
    __mul:操作符 ×
    __div:操作符 ÷
    __pow:操作符 ^ (乘方)
    __unm:一元操作符 -
    __concat:操作符 .. (字符串连接)
    __eq:操作符 == (a ~= b等价于 not a == b)
    __lt:操作符 < (a > b 等价于 b < a)
    __le:操作符 <= (a >= b 等价于 b <= a,要注意的是,如果没有定义"__le",则Lua将会尝试将a<=b 转换成 not (b < a) )

    __gc:在垃圾回收时调用此函数,相当于C++的析构函数。强烈建议定义此操作符,以免造成内存泄漏等情况。比如:
    LUALIB_ITEM_DESTROY("__gc", MyArray )   // 垃圾收集时消除对象用。

    (注) 这里要说明一下,在lua中,访问索引操作符是__index,不是__getindex,在luaWrapper库中,为了方便使用,将其映射为__getindex,同时,对__index的定义将会被忽略。

    就这么简单。假如你已经有现成的类,而你没有修改该类的权力,如何将其加入到Lua中呢?答案就是,继承它,将把派生类加入到Lua中。

结束语
  LuaWrapper 需要用到boost库的支持:boost/type_traits.hpp, boost/function.hpp, boost/bind.hpp,它使用了C++的模板部份特化,因此,C++编译器如果不支持此特性,将无法编译。目前支持此特性的编译器已经有很多。在VisualStudo产品系列中,只有VC7.1能支持此特性,因此,您如果正在使用VisualStudio,请确认你用的是VisualStudio2003。
  如果你觉得 LuaWrapper For C++ 能够帮助你,我会感觉很荣幸。我很愿意将这个程序库分享给大家。顺便一提的是,如果你在使用过程中发现BUG,或是有好的建议,希望您能与我联系。你在使用过程中,请不要删除文件中的署名信息;如果你修改了程序库,请您在修改的文件中加入您的修改说明。当然,我会非常欢迎您能将修改后的程序回馈给我。我会继续优化并完善它。

posted @ 2010-04-21 22:46 avatar 阅读(414) | 评论 (0)编辑 收藏

【转贴】使用 Lua 编写可嵌入式脚本

使用 Lua 编写可嵌入式脚本

Lua 提供了高级抽象,却又没失去与硬件的关联

将此页作为电子邮件发送

将此页作为电子邮件发送



级别: 初级

Martin Streicher (martin.streicher@linux-mag.com), 首席编辑, Linux Magazine

2006 年 6 月 12 日

虽然编译性编程语言和脚本语言各自具有自己独特的优点,但是如果我们使用这两种类型的语言来编写大型的应用程序会是什么样子呢?Lua 是一种嵌入式脚本语言,它非常小,速度很快,功能却非常强大。在创建其他配置文件或资源格式(以及与之对应的解析器)之前,请尝试一下 Lua。

尽管诸如 Perl、Python、PHP 和 Ruby 之类的解释性编程语言日益被 Web 应用程序广泛地采纳 —— 它们已经长期用来实现自动化系统管理任务 —— 但是诸如 C、C++ 之类的编译性编程语言依然是必需的。编译性编程语言的性能是脚本语言所无法企及的(只有手工调优的汇编程序的性能才能超过它),有些软件 —— 包括操作系统和设备驱动程序 —— 只能使用编译代码来高效地实现。实际上,当软件和硬件需要进行无缝地连接操作时,程序员本能地就会选择 C 编译器:C 非常基础,距离 “原始金属材料非常近” —— 即可以操作硬件的很多特性 —— 并且 C 的表现力非常强大,可以提供高级编程结构,例如结构、循环、命名变量和作用域。

然而,脚本语言也有自己独特的优点。例如,当某种语言的解释器被成功移植到一种平台上以后,使用这种语言编写的大量脚本就可以不加任何修改在这种新平台上运行 —— 它们没有诸如系统特定的函数库之类的依赖限制。(我们可以考虑一下 Microsoft® Windows® 操作系统上的许多 DLL 文件和 UNIX® 及 Linux® 上的很多 libcs)。另外,脚本语言通常都还会提供高级编程构造和便利的操作,程序员可以使用这些功能来提高生产效率和灵活性。另外,使用解释语言来编程的程序员工作的速度更快,因为这不需要编译和链接的步骤。C 及其类似语言中的 “编码、编译、链接、运行” 周期缩减成了更为紧凑的 “编写脚本、运行”。

Lua 新特性

与其他脚本语言一样,Lua 也有自己的一些特性:

  • Lua 类型。在 Lua 中,值可以有类型,但是变量的类型都是动态决定的。nil、布尔型、数字字符串 类型的工作方式与我们期望的一样。
    • Nil 是值为 nil 的一种特殊类型,用来表示没有值。
    • 布尔型的值可以是 truefalse 常量。(Nil 也可以表示 false,任何非 nil 的值都表示 true。)
    • Lua 中所有的数字都是双精度的(不过我们可以非常简便地编写一些代码来实现其他数字类型)。
    • 字符串是定长字符数组。(因此,要在一个字符串后面附加上字符,必须对其进行拷贝。)
  • 表、函数线程 类型都是引用。每个都可以赋值给一个变量,作为参数传递,或作为返回值从函数中返回。例如,下面是一个存储函数的例子:

    -- example of an anonymous function
                                    -- returned as a value
                                    -- see http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf
                                    function add(x)
                                    return function (y) return (x + y) end
                                    end
                                    f = add(2)
                                    print(type(f), f(10))
                                    function  12
                                    

  • Lua 线程。线程是通过调用内嵌函数 coroutine.create(f) 创建的一个协同例程 (co-routine),其中 f 是一个 Lua 函数。线程不会在创建时启动;相反,它是在创建之后使用 coroutine.resume(t) 启动的,其中 t 就是一个线程。每个协同例程都必须使用 coroutine.yield() 偶尔获得其他协同例程的处理器。
  • 赋值语句。Lua 允许使用多种赋值语句,可以先对表达式进行求值,然后再进行赋值。例如,下面的语句:

    i = 3
                                    a = {1, 3, 5, 7, 9}
                                    i, a[i], a[i+1], b = i+1, a[i+1], a[i]
                                    print (i, a[3], a[4], b, I)
                                    

    会生成 4 7 5 nil nil。如果变量列表的个数大于值列表的个数,那么多出的变量都被赋值为 nil;因此,b 就是 nil。如果值的个数多于变量的个数,那么多出的值部分就会简单地丢弃。在 Lua 中,变量名是大小写敏感的,这可以解释为什么 I 的值是 nil。
  • 块(Chunk)。 可以是任何 Lua 语句序列。块可以保存到文件中,或者保存到 Lua 程序中的字符串中。每个块都是作为一个匿名函数体来执行的。因此,块可以定义局部变量和返回值。
  • 更酷的东西。Lua 具有一个标记-清理垃圾收集器。在 Lua 5.1 中,垃圾收集器是以增量方式工作的。Lua 具有完整的词法闭包(这与 Scheme 类似,而与 Python 不同)。Lua 具有可靠的尾部调用语义(同样,这也与 Scheme 类似,而与 Python 不同)。

Programming in Lua 和 Lua-users wiki (链接请参见后面的 参考资料 部分)中可以找到更多 Lua 代码的例子。

在所有的工程任务中,要在编译性语言和解释性语言之间作出选择,就意味着要在这种环境中对每种语言的优缺点、权重和折中进行评测,并接受所带来的风险。





回页首


在两个世界之间最好地进行混合

如果您希望充分利用这两个世界的优点,应该怎样办呢,是选择最好的性能还是选择高级强大的抽象?更进一步说,如果我们希望对处理器密集且依赖于系统的算法和函数以及与系统无关且很容易根据需要而进行修改的单独逻辑进行优化,那又当如何呢?

对高性能代码和高级编程的需要进行平衡是 Lua(一种可嵌入式脚本语言)要解决的问题。在需要时我们可以使用编译后的代码来实现底层的功能,然后调用 Lua 脚本来操作复杂的数据。由于 Lua 脚本是与编译代码独立的,因此我们可以单独修改这些脚本。使用 Lua,开发周期就非常类似于 “编码、编译、运行、编写脚本、编写脚本、编写脚本 ...”。

例如,Lua Web 站点 “使用” 页面(请参见 参考资料)列出了主流市场上的几个计算机游戏,包括 World of Warcraft 和(家用版的)Defender,它们集成 Lua 来实现很多东西,从用户界面到敌人的人工智能都可以。Lua 的其他应用程序包括流行的 Linux 软件更新工具 apt-rpm 的扩展机制,还有 “Crazy Ivan” Robocup 2000 冠军联赛的控制逻辑。这个页面上的很多推荐感言都对 Lua 的小巧与杰出性能赞不绝口。





回页首


开始使用 Lua

Lua 5.0.2 版本是撰写本文时的最新版本,不过最近刚刚发布了 5.1 版本。您可以从 lua.org 上下载 Lua 的源代码,在 Lua-users wiki(链接请参见 参考资料)上可以找到预先编译好的二进制文件。完整的 Lua 5.0.2 核心文件中包括了标准库和 Lua 编译器,不过只有 200KB 大小。

如果您使用的是 Debian Linux,那么可以以超级用户的身份运行下面的命令来快速安装 Lua 5.0:

# apt-get install lua50
                            

本文中给出的例子都是在 Debian Linux Sarge 上运行的,使用的是 Lua 5.0.2 和 2.4.27-2-686 版本的 Linux 内核。

在系统上安装好 Lua 之后,我们可以首先来试用一下单独的 Lua 解释器。(所有的 Lua 应用程序必须要嵌入到宿主应用程序中。解释器只是一种特殊类型的宿主,对于开发和调试工作来说非常有用。)创建一个名为 factorial.lua 的文件,然后输入下面的代码:

-- defines a factorial function
                            function fact (n)
                            if n == 0 then
                            return 1
                            else
                            return n * fact(n-1)
                            end
                            end
                            print("enter a number:")
                            a = io.read("*number")
                            print(fact(a))
                            

factorial.lua 中的代码 —— 更确切地说是任何 Lua 语句序列 —— 都称为一个,这在上面的 Lua 特性 中已经进行了介绍。要执行刚才创建的代码块,请运行命令 lua factorial.lua

$ lua factorial.lua
                            enter a number:
                            10
                            3628800
                            

或者像在其他解释性语言中一样,我们可以在代码顶部添加一行 “标识符”(#!),使这个脚本变成可执行的,然后像单独命令一样来运行这个文件:

$ (echo '#! /usr/bin/lua'; cat factorial.lua) > factorial
                            $ chmod u+x factorial
                            $ ./factorial
                            enter a number:
                            4
                            24
                            





回页首


Lua 语言

Lua 具有现代脚本语言中的很多便利:作用域,控制结构,迭代器,以及一组用来处理字符串、产生及收集数据和执行数学计算操作的标准库。在 Lua 5.0 Reference Manual 中有对 Lua 语言的完整介绍(请参见 参考资料)。

在 Lua 中,只有 具有类型,而变量的类型是动态决定的。Lua 中的基本类型(值)有 8 种: nil,布尔型,数字,字符串,函数,线程,表 以及 用户数据。前 6 种类型基本上是自描述的(例外情况请参见上面的 Lua 特性 一节);最后两个需要一点解释。

Lua 表

在 Lua 中,表是用来保存所有数据的结构。实际上,表是 Lua 中惟一的 数据结构。我们可以将表作为数组、字典(也称为散列联合数组)、树、记录,等等。

与其他编程语言不同,Lua 表的概念不需要是异构的:表可以包含任何类型的组合,也可以包含类数组元素和类字典元素的混合体。另外,任何 Lua 值 —— 包括函数或其他表 —— 都可以用作字典元素的键值。

要对表进行浏览,请启动 Lua 解释器,并输入清单 1 中的黑体显示的代码。


清单 1. 体验 Lua 表
$ lua
                            > -- create an empty table and add some elements
                            > t1 = {}
                            > t1[1] = "moustache"
                            > t1[2] = 3
                            > t1["brothers"] = true
                            > -- more commonly, create the table and define elements
                            > all at once
                            > t2 = {[1] = "groucho", [3] = "chico", [5] = "harpo"}
                            > t3 = {[t1[1]] = t2[1], accent = t2[3], horn = t2[5]}
                            > t4 = {}
                            > t4[t3] = "the marx brothers"
                            > t5 = {characters = t2, marks = t3}
                            > t6 = {["a night at the opera"] = "classic"}
                            > -- make a reference and a string
                            > i = t3
                            > s = "a night at the opera"
                            > -- indices can be any Lua value
                            > print(t1[1], t4[t3], t6[s])
                            moustache   the marx brothers classic
                            > -- the phrase table.string is the same as table["string"]
                            > print(t3.horn, t3["horn"])
                            harpo   harpo
                            > -- indices can also be "multi-dimensional"
                            > print (t5["marks"]["horn"], t5.marks.horn)
                            harpo   harpo
                            > -- i points to the same table as t3
                            > = t4[i]
                            the marx brothers
                            > -- non-existent indices return nil values
                            > print(t1[2], t2[2], t5.films)
                            nil     nil     nil
                            >  -- even a function can be a key
                            > t = {}
                            > function t.add(i,j)
                            >> return(i+j)
                            >> end
                            > print(t.add(1,2))
                            3
                            > print(t['add'](1,2))
                            3
                            >  -- and another variation of a function as a key
                            > t = {}
                            > function v(x)
                            >> print(x)
                            >> end
                            > t[v] = "The Big Store"
                            > for key,value in t do key(value) end
                            The Big Store
                            

正如我们可能期望的一样,Lua 还提供了很多迭代器函数来对表进行处理。全局变量 table 提供了这些函数(是的,Lua 包就是表)。有些函数,例如 table.foreachi(),会期望一个从 1(数字 1)开始的连续整数范围:

> table.foreachi(t1, print)
                            1 moustache
                            2 3
                            

另外一些函数,例如 table.foreach(),会对整个表进行迭代:

> table.foreach(t2,print)
                            1       groucho
                            3       chico
                            5       harpo
                            > table.foreach(t1,print)
                            1       moustache
                            2       3
                            brothers        true
                            

尽管有些迭代器对整数索引进行了优化,但是所有迭代器都只简单地处理 (key, value) 对。

现在我们可以创建一个表 t,其元素是 {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="www.lua.org"},然后运行 table.foreach(t, print) table.foreachi(t, print)

用户数据

由于 Lua 是为了嵌入到使用另外一种语言(例如 C 或 C++)编写的宿主应用程序中,并与宿主应用程序协同工作,因此数据可以在 C 环境和 Lua 之间进行共享。正如 Lua 5.0 Reference Manual 所说,userdata 类型允许我们在 Lua 变量中保存任意的 C 数据。我们可以认为 userdata 就是一个字节数组 —— 字节可以表示指针、结构或宿主应用程序中的文件。

用户数据的内容源自于 C,因此在 Lua 中不能对其进行修改。当然,由于用户数据源自于 C,因此在 Lua 中也没有对用户数据预定义操作。不过我们可以使用另外一种 Lua 机制来创建对 userdata 进行处理的操作,这种机制称为 元表(metatable)。

元表

由于表和用户数据都非常灵活,因此 Lua 允许我们重载这两种类型的数据的操作(不能重载其他 6 种类型)。元表 是一个(普通的)Lua 表,它将标准操作映射成我们提供的函数。元表的键值称为事件;值(换而言之就是函数)称为元方法

函数 setmetatable()getmetatable() 分别对对象的元表进行修改和查询。每个表和 userdada 对象都可以具有自己的元表。

例如,添加操作对应的事件是 __add。我们可以推断这段代码所做的事情么?

-- Overload the add operation
                            -- to do string concatenation
                            --
                            mt = {}
                            function String(string)
                            return setmetatable({value = string or ''}, mt)
                            end
                            -- The first operand is a String table
                            -- The second operand is a string
                            -- .. is the Lua concatenate operator
                            --
                            function mt.__add(a, b)
                            return String(a.value..b)
                            end
                            s = String('Hello')
                            print((s + ' There ' + ' World!').value )
                            

这段代码会产生下面的文本:

Hello There World!
                            

函数 String() 接收一个字符串 string,将其封装到一个表({value = s or ''})中,并将元表 mt 赋值给这个表。函数 mt.__add() 是一个元方法,它将字符串 b 添加到在 a.value 中找到的字符串后面 b 次。这行代码 print((s + ' There ' + ' World!').value ) 调用这个元方法两次。

__index 是另外一个事件。__index 的元方法每当表中不存在键值时就会被调用。下面是一个例子,它记住 (memoize) 函数的值:

-- code courtesy of Rici Lake, rici@ricilake.net
                            function Memoize(func, t)
                            return setmetatable(
                            t or {},
                            {__index =
                            function(t, k)
                            local v = func(k);
                            t[k] = v;
                            return v;
                            end
                            }
                            )
                            end
                            COLORS = {"red", "blue", "green", "yellow", "black"}
                            color = Memoize(
                            function(node)
                            return COLORS[math.random(1, table.getn(COLORS))]
                            end
                            )
                            

将这段代码放到 Lua 解释器中,然后输入 print(color[1], color[2], color[1])。您将会看到类似于 blue black blue 的内容。

这段代码接收一个键值 node,查找 node 指定的颜色。如果这种颜色不存在,代码就会给 node 赋一个新的随机选择的颜色。否则,就返回赋给 node 的颜色。在前一种情况中,__index 元方法被执行一次以分配一个颜色。后一种情况比较简单,所执行的是快速散列查找。

Lua 语言提供了很多其他功能强大的特性,所有这些特性都有很好的文档进行介绍。在碰到问题或希望与专家进行交谈时,请连接 Lua Users Chat Room IRC Channel(请参见 参考资料)获得非常热心的支持。





回页首


嵌入和扩展

除了语法简单并且具有功能强大的表结构之外,Lua 的强大功能使其可以与宿主语言混合使用。由于 Lua 与宿主语言的关系非常密切,因此 Lua 脚本可以对宿主语言的功能进行扩充。但是这种融合是双赢的:宿主语言同时也可以对 Lua 进行扩充。举例来说,C 函数可以调用 Lua 函数,反之亦然。

Lua 与宿主语言之间的这种共生关系的核心是宿主语言是一个虚拟堆栈。虚拟堆栈与实际堆栈类似,是一种后进先出(LIFO)的数据结构,可以用来临时存储函数参数和函数结果。要从 Lua 中调用宿主语言的函数(反之亦然),调用者会将一些值压入堆栈中,并调用目标函数;被调用的函数会弹出这些参数(当然要对类型和每个参数的值进行验证),对数据进行处理,然后将结果放入堆栈中。当控制返回给调用程序时,调用程序就可以从堆栈中提取出返回值。

实际上在 Lua 中使用的所有的 C 应用程序编程接口(API)都是通过堆栈来进行操作的。堆栈可以保存 Lua 的值,不过值的类型必须是调用程序和被调用者都知道的,特别是向堆栈中压入的值和从堆栈中弹出的值更是如此(例如 lua_pushnil()lua_pushnumber()

清单 2 给出了一个简单的 C 程序(节选自 参考资料Programming in Lua 一书的第 24 章),它实现了一个很小但却功能完善的 Lua 解释器。


清单 2. 一个简单的 Lua 解释器
 1 #include <stdio.h>
                            2 #include <lua.h>
                            3 #include <lauxlib.h>
                            4 #include <lualib.h>
                            5
                            6 int main (void) {
                            7   char buff[256];
                            8   int error;
                            9   lua_State *L = lua_open();   /* opens Lua */
                            10   luaopen_base(L);             /* opens the basic library */
                            11   luaopen_table(L);            /* opens the table library */
                            12   luaopen_io(L);               /* opens the I/O library */
                            13   luaopen_string(L);           /* opens the string lib. */
                            14   luaopen_math(L);             /* opens the math lib. */
                            15
                            16   while (fgets(buff, sizeof(buff), stdin) != NULL) {
                            17     error = luaL_loadbuffer(L, buff, strlen(buff), "line") ||
                            18             lua_pcall(L, 0, 0, 0);
                            19     if (error) {
                            20       fprintf(stderr, "%s", lua_tostring(L, -1));
                            21       lua_pop(L, 1);  /* pop error message from the stack */
                            22     }
                            23   }
                            24
                            25   lua_close(L);
                            26   return 0;
                            27 }
                            

第 2 行到第 4 行包括了 Lua 的标准函数,几个在所有 Lua 库中都会使用的方便函数以及用来打开库的函数。第 9 行创建了一个 Lua 状态。所有的状态最初都是空的;我们可以使用 luaopen_...() 将函数库添加到状态中,如第 10 行到第 14 行所示。

第 17 行和 luaL_loadbuffer() 会从 stdin 中以块的形式接收输入,并对其进行编译,然后将其放入虚拟堆栈中。第 18 行从堆栈中弹出数据并执行之。如果在执行时出现了错误,就向堆栈中压入一个 Lua 字符串。第 20 行访问栈顶(栈顶的索引为 -1)作为 Lua 字符串,打印消息,然后从堆栈中删除该值。

使用 C API,我们的应用程序也可以进入 Lua 状态来提取信息。下面的代码片段从 Lua 状态中提取两个全局变量:

..
                            if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))
                            error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
                            lua_getglobal(L, "width");
                            lua_getglobal(L, "height");
                            ..
                            width = (int) lua_tonumber(L, -2);
                            height = (int) lua_tonumber(L, -1);
                            ..
                            

请再次注意传输是通过堆栈进行的。从 C 中调用任何 Lua 函数与这段代码类似:使用 lua_getglobal() 来获得函数,将参数压入堆栈,调用 lua_pcall(),然后处理结果。如果 Lua 函数返回 n 个值,那么第一个值的位置在堆栈的 -n 处,最后一个值在堆栈中的位置是 -1。

反之,在 Lua 中调用 C 函数也与之类似。如果您的操作系统支持动态加载,那么 Lua 可以根据需要来动态加载并调用函数。(在必须使用静态加载的操作系统中,可以对 Lua 引擎进行扩充,此时调用 C 函数时需要重新编译 Lua。)





回页首


结束语

Lua 是一种学习起来容易得难以置信的语言,但是它简单的语法却掩饰不了其强大的功能:这种语言支持对象(这与 Perl 类似),元表使表类型具有相当程度的可伸展性,C API 允许我们在脚本和宿主语言之间进行更好的集成和扩充。Lua 可以在 C、C++、C#、Java™ 和 Python 语言中使用。

在创建另外一个配置文件或资源格式(以及相应的处理程序)之前,请尝试一下 Lua。Lua 语言及其社区非常健壮,具有创新精神,随时准备好提供帮助。





回页首


参考资料

学习

获得产品和技术
  • 下载 Lua 5.0.2Lua 5.1 源代码,从头开始编译 Lua。

  • Lua-users wiki 上,浏览很多预先构建的、可安装的 Lua 二进制文件。

  • LuaForge 上可以找到大量的代码库,包括很多语言绑定和专门的计算库。

  • 订购免费的 SEK for Linux ,这有两张 DVD,包括最新的 IBM for Linux 试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。

  • 在您的下一个开发项目中采用 IBM 试用软件,这可以从 developerWorks 直接下载。


讨论






关于作者

Martin Streicher 是 Linux Magazine 的首席编辑。他在普渡大学获得了计算机硕士学位,自 1982 年以来,就一直在使用 Pascal、C、Perl、Java 以及(最近使用的)Ruby 编程语言编写类 Unix 系统。

posted @ 2010-04-21 22:44 avatar 阅读(309) | 评论 (0)编辑 收藏

仅列出标题  下一页
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(2)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜