• About
    • Resume
A Game Developer also plays some guitar

Category Archives: Game Programming

3ds-max-exporter-plugin

June 15, 2011 9:50 pm / 1 Comment / Benny Chen

把两年前的那个3ds max导出插件项目上传到了google code,代码基本上已经能嗅到铁锈的味道了。现在再上传,一来作备份,二来open source是一件光荣的事情,或许会有人需要它。

地址:

http://code.google.com/p/3ds-max-exporter-plugin/

介绍:

This project was developed with 3DS Max 9 SDK in C++, Visual Studio 2005. This exporter plug-in is aimed to export 3D mesh data from 3DS Max into a predefined file format. These mesh data includes vertices, texture, normals, skeletal animation, etc.

The project was kinda old and becoming a legacy. I developed it in 2009, but never have any update since that. Recently, I am more and more realizing the importance of being open source. So this is why after such a long time I decided to upload it onto Google Code.

I uploaded all the related docs and source code here, for anyone in need. And I would feel extremely flattered if anyone thinks it helpful.

Articles from my blogs about this plugin:
1st part: My Footsteps of Programming 3DS Max Exporter Plug-in (1)
http://www.bennychen.cn/2009/11/my-footsteps-of-programming-3ds-max-exporter-plug-in/
2nd part: My Footsteps of Programming 3DS Max Exporter Plug-in (2)
http://www.bennychen.cn/2010/05/my-footsteps-of-programming-3ds-max-exporter-plug-in-continued/

Introduction to Source Directory:

– CSMExporter is the 3dmax exporter project;
– RenderCSM is a MFC project used to render a CSM file exported from CSMExporter;
– CrowdSimulationModelExporter.dle is the generated 3ds max plugin file outputted from CSMExporter project;
– Doxygen_Docs is the html documentation generated by Doxygen for both projects ‘CSMExporter’ and ‘RenderCSM’;

Posted in: 3D, Computer Graphics, Game Programming, My Projects / Tagged: 3dsmax, exporter, plugin

iOS开发与OpenGL ES相关问题整理(2)- 绘制图片上下颠倒

May 22, 2011 5:07 pm / 1 Comment / Benny Chen
  • 使用CGContextDrawImage绘制图片上下颠倒

  • 首先要说的是,在iOS的不同framework中使用着不同的坐标系:

    • UIKit - y轴向下
    • Core Graphics(Quartz) - y轴向上
    • OpenGL ES - y轴向上

    UIKit是iPhone SDK的Cocoa Touch层的核心framework,是iPhone应用程序图形界面和事件驱动的基础,它和传统的windows桌面一样,坐标系是y轴向下的; Core Graphics(Quartz)一个基于2D的图形绘制引擎,它的坐标系则是y轴向上的;而OpenGL ES是iPhone SDK的2D和3D绘制引擎,它使用左手坐标系,它的坐标系也是y轴向上的,如果不考虑z轴,在二维下它的坐标系和Quartz是一样的。

    现在回到问题,当通过CGContextDrawImage绘制图片到一个context中时,如果传入的是UIImage的CGImageRef,因为UIKit和CG坐标系y轴相反,所以图片绘制将会上下颠倒。解决方法有以下几种,

    解决方法一:在绘制到context前通过矩阵垂直翻转坐标系

    // uiImage是将要绘制的UIImage图片,width和height是它的宽高
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), uiImage.CGImage);
    

    解决方法二:使用UIImage的drawInRect函数,该函数内部能自动处理图片的正确方向

    // uiImage是将要绘制的UIImage图片,width和height是它的宽高
    UIGraphicsPushContext( context );
    [uiImage drawInRect:CGRectMake(0, 0, width, height)];
    UIGraphicsPopContext();
    

    解决方法三:垂直翻转投影矩阵
    这种方法通过设置上下颠倒的投影矩阵,使得原本y轴向上的GL坐标系看起来变成了y轴向下,并且坐标原点从屏幕左下角移到了屏幕左上角。如果你习惯使用y轴向下的坐标系进行二维操作,可以使用这种方法,同时原本颠倒的图片经过再次颠倒后回到了正确的方向:

    // uiImage是将要绘制的UIImage图片,width和height是它的宽高
    
    // 图片被颠倒的绘制到context
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), uiImage.CGImage);
    
    // 设置上下颠倒的投影矩阵(则原来颠倒的图片回到了正确的方向)
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof( 0, framebufferWidth, framebufferHeight, 0, -1, 1 );
    
    Posted in: Game Programming, iOS / Tagged: CGContextDrawImage, CoreGraphics, ios, opengles, UIKit, 上下颠倒, 坐标系, 方向

    iOS开发与OpenGL ES相关问题整理(1)

    May 22, 2011 2:21 pm / 1 Comment / Benny Chen

    在极其有限的工作日的晚上和周末进行着iOS上game programming的研究,进展非常缓慢,不过还是有必要将过程中的一些问题随时记录下来。

  • OpenGL ES崩溃在函数’glMatrixMode’

    这很有可能是因为在OpenGL ES2的context中使用OpenGL ES1的函数,ES1是固定函数渲染管线 (fixed function pipeline),而ES2是可编程的渲染管线 (programmable pipeline),ES2不再支持这些ES1的固定渲染管线的函数, 比如’glMatrixMode’。所以当ES2遇到这些不支持的ES1函数时,你的程序会收到一个’EXC_BAD_ACCESS’消息并且崩溃。通过gdb查看callstack,显示最后一个函数是gliUnimplemented:

    #0  0x0b05c0e5 in gliUnimplemented ()
    #1  0x00a1c6c3 in glMatrixMode ()
    

    如果一定要使用ES1函数, 你只能以ES1来初始化你的GL context:

    EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    
  • 函数’pathForResource’返回nil

    需要将资源文件添加到项目的’Groups & Files’中

  • OpenGL ES崩溃在绘制函数’glDrawArrays’

    这可能是因为你启动了GL的某个渲染状态(比如vertex,color,texture coordinate等等,这里有所有可能状态的清单),但是却没有在绘制函数(比如’glDrawArray’)进行之前,设置该状态所对应的数据指针。

    比如说:

    glEnableClientState( GL_COLOR_ARRAY );
    glColorPointer( 4, GL_UNSIGNED_BYTE, 0, youColorArray );
    

    对于上面这两句代码,如果你设置了使用color数组,但是却没有设置color数组指针,在真正的绘制时你的程序就会因为找不到对应的指针而崩溃。同样的, 如果你调用了‘glEnableClientState(GL_TEXTURE_COORD_ARRAY), 则你也需要通过glTexCoordPointer()设置纹理坐标的数组指针,如此类推。

  • 怎么样通过OpenGL ES以像素为单位来绘制,而不是屏幕比例?

    在iPhone上通过OpenGL ES进行2D绘制时,默认的原点位置在屏幕中央,并且屏幕的坐标范围依次是从-1到1,不管是横轴还是纵轴。如下图所示,GL中的默认的单位1分别代表着屏幕长度和宽度的一半。

    图片来自book: ‘Learning iOS Game Programming_A Hands-On Guide to Building Your First iPhone Game’
    如果希望将原点移动到屏幕左下角,并且绘制时希望以像素为单位,只需要通过glOrthof函数将投影矩阵设置成一个垂直投影矩阵即可,代码如下:

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof( 0, framebufferWidth, 0, framebufferHeight, -1, 1 );
    
  • Posted in: Game Programming, iOS / Tagged: glDrawArray, glMatrixMode, ios, opengles, pathForResource, 像素, 崩溃

    Game Framework的两种实现方式

    April 24, 2011 2:11 pm / Leave a Comment / Benny Chen

    一年多前,曾经写过一篇关于Game Engine Framework的文章,当时基本上是为了巩固并加深对framework的理解。最近又做了一些关于framework的工作,对于framework的实现方式又有了些新的认识。虽然我现在做的已经完全不是game了,不过方式对于game也同样适用。

    这篇文章主要希望通过一些示例性的C++代码介绍game framework的两种实现方式。首先,我还是搬出一年多前的那篇文章里的game流程图,以下的一些代码也主要基于这张图实现。对于图的细节在这里不再赘述,可以再去翻看之前的那篇文章。

    FoC of Game

    1.通过继承

    这是一种最传统的方式了,之前我一直使用这种方式。基本上是提供一个基类,基类封装并决定了整个程序控制流,同时基于该控制流,基类提供了一系列的接口(在C++里是虚函数),以供继承类override,并实现定制化的行为。就像下面的这个Game类,run函数决定了整个程序的执行逻辑,同时initialize,update,render等非纯虚函数则提供了接口以供用户定制并实现具体Game想要的行为。

    // 通过继承来实现的framework
    class Game
    {
    public:
    	void run()
    	{
    		m_isQuit = false;
    		initialize();
    		while ( !m_isQuit ) 
    		{
    			handleInput();
    			update();
    			render();
    		}
    		destroy();
    	}
    	
    protected:
    	virtual void initialize() {}
    	virtual void update() {}
    	virtual void render() {}
    	virtual void destroy() {}
    	virtual void onMouse( Mouse mouse ) {}
    	virtual void onKeyboard( Keyboard keyboard ) {}
    	
    private:
    	void handleInput()
    	{
    		Event e;
    		while ( popEvent( &e )  )
    		{
    			switch ( e.type ) 
    			{
    			case EVENT_QUIT:
    				m_isQuit = true;
    				break;
    			case EVENT_MOUSE;
    				onMouse( e.mouse );
    				break;
    			case EVENT_KEYBOARD:
    				onKeyboard( e.keyboard );
    				break;
    			default:
    				break;
    			}
    		}
    	}
    	
    private:
    	bool m_isQuit;
    };
    

    在上面的代码中,我把实现全部写在了类的定义头文件中,在这里只是为了省事,在现实中你最好还是分开在.h和.cpp文件中。而且在这里,Game类还可以做的更多,我这里只是为了说明实现方式,所以依照前面的流程图而尽量让它简单。另外,不要纠结这段代码中的Event,Mouse,Keyboard等几个类和popEvent方法,它们只是我为了将故事说得更圆满一点而假象出来的几个类,我想你应该能猜到它们是用来干嘛的。

    基于此framework,一个具体的游戏只需要重写这些虚函数就可以了,像下面的这些代码。

    class ConcreteGame : public Game
    {
    private:
    	void initialize()
    	{
    		// do some real initialization
    	}
    	
    	void update()
    	{
    		// do some real update
    	}
    	
    	void render()
    	{
    		// do some real rendering
    	}
    
    	void destroy()
    	{
    		// do some real destroy
    	}
    	
    	void onMouse( Mouse mouse )
    	{
    		// handle mouse event
    	}
    	
    	void onKeyboard( Keyboard keyboard )
    	{
    		// handle keyboard event
    	}
    };
    
    int main()
    {
    	ConcreteGame mygame;
    	mygame.run();
    	return 0;
    }
    

    2.通过Delegation模式

    前一种方法有一个缺点,就是将程序的控制流和具体行为紧耦合在了一起,而且还必须使用继承,不易于扩展。现代软件设计的一些方法告诉我们,要尽量使用接口,且尽量使用组合而非继承。Delegation模式就可以帮我们达到这一目的。

    何为Delegation模式,wiki上的解释一语中的:

    Delegation is the simple yet powerful concept of handing a task over to another part of the program.

    Delegation将一些task委托给程序的另外一部分来处理,以达到了行为使用者和具体行为的松耦合。

    以下是通过Delegation模式重新实现的framework。

    // 通过Delegation模式来实现的framework
    class GameDelegation
    {
    public:
    	virtual void initialize() {}
    	virtual void update() {}
    	virtual void render() {}
    	virtual void destroy() {}
    	virtual void onMouse( Mouse mouse ) {}
    	virtual void onKeyboard( Keyboard keyboard ) {}
    };
    
    class Game
    {
    public:
    	Game( GameDelegation *gameDelegation )
    	{
    		m_gameDelegation = gameDelegation;
    	}
    	
    	void run()
    	{
    		m_isQuit = false;
    		if ( m_gameDelegation == NULL )
    		{
    			return;
    		}
    		
    		m_gameDelegation->initialize();
    		while ( !m_isQuit ) 
    		{
    			handleInput();
    			m_gameDelegation->update();
    			m_gameDelegation->render();
    		}
    	}
    	
    private:
    	void handleInput()
    	{
    		Event e;
    		while ( popEvent( &e ) )
    		{
    			switch ( e.type ) 
    			{
    			case EVENT_QUIT:
    				m_isQuit = true;
    				break;
    			case EVENT_MOUSE;
    				m_gameDelegation->onMouse( e.mouse );
    				break;
    			case EVENT_KEYBOARD:
    				m_gameDelegation->onKeyboard( e.keyboard );
    				break;
    			default:
    				break;
    			}
    		}
    	}
    	
    private:
    	bool m_isQuit;
    	GameDelegation *m_gameDelegation;
    };
    

    基于此framework,当需要具体实现一个游戏的时候,只需要实现GameDelegation接口即可,然后将Game类的GameDelegation设置为你所实现的具体的ConcreteGameDelegation类,代码如下。

    class ConcreteGameDelegation : public GameDelegation
    {
    public:
    	void initialize()
    	{
    		// do some real initialization
    	}
    	
    	void update()
    	{
    		// do some real update
    	}
    	
    	void render()
    	{
    		// do some real rendering
    	}
    
    	void destroy()
    	{
    		// do some real destroy
    	}
    	
    	void onMouse( Mouse mouse )
    	{
    		// handle mouse event
    	}
    	
    	void onKeyboard( Keyboard keyboard )
    	{
    		// handle keyboard event
    	}
    };
    
    int main()
    {
    	ConcreteGameDelegation myGameDelegation;
    	Game mygame( &myGameDelegation );
    	mygame.run();
    	return 0;
    }
    

    最近我正好做了一些iOS上开发的研究,发现Delegation在iOS框架中被普遍使用。比如,iOS中的每个应用程序对应的是一个UIApplication类,为每一个UIApplication,开发人员必须要实现一个特定的UIApplicationDelegate,并将它指定给当前的应用程序(在main函数中通过UIApplicationMain函数指定,或者是在nib文件中绑定)。在这个UIApplicationDelegate类中,开发人员就需要重写诸如didFinishLaunchingWithOptions,applicationWillTerminate这样的方法,就类似与上面game framework中的initialize,destroy等方法。当然iOS框架要复杂很多,它还用到其它一系列的设计模式,有空研究些这样设计是非常有趣的。

    图片来自于这篇苹果官方关于iOS中delegation的介绍:http://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html

    Posted in: C++, Game Programming, iOS / Tagged: C++, delegation, framework, game, 实现, 继承

    Runtime Game Engine Architecture

    March 20, 2011 8:10 pm / 2 Comments / Benny Chen
    runtime game engine architecture

    上上周末闲得无聊的时候画的:游戏引擎的大概架构图,图片源于Jason Gregory的《Game Engine Architecture》的第29页,这本书也基本上是围绕着这张图对每一个模块深入展开。这张图献给自己和其他游戏引擎架构研究的爱好者,应该打印一张贴到家里的剪贴板上。这段时间对我来说是一段困难的时期,不清楚未来是否还会在game的道路上走下去,但愿这不是一个终结。

    runtime game engine architecture

    runtime game engine architecture, from ‘Game Engine Architecture‘ P.29

    Posted in: Game Programming / Tagged: runtime game engine architecture, 架构图, 游戏引擎

    关于游戏引擎多线程的一些整理和思考

    January 5, 2011 9:05 pm / 2 Comments / Benny Chen

    在最近的项目中,由于时间紧任务重,我果断放弃了引擎原来有点畸形的多线程设计,退回到单线程。最后事实证明,这是一个明智的决定,不但效率没有降低,同时那些永无止境的多线程bug也一并被消除,系统稳定了很多,最终项目得以在deadline之前有一个安全的交付。

    1.是否多线程

    去掉多线程的主要原因是,原来的多线程设计本身就是一个未经过深思熟虑的设计,它几乎是在项目开发的中后期突然插入的。整个引擎的最初设计没有考虑多线程,现在突然引入了多线程,而又没有经过精心的设计,那结果就是各种多线程bug扑面而来,让开发人员焦头烂额。事实是,如果需要多线程,那最好应该从项目设计架构的第一天开始就仔细考虑。

    直观上感觉,好像使用多线程就会带来性能的提升。其实不然,如果没有经过精心的设计,随便引入多线程,只会适得其反。不但性能没有得到提升,而且引入多线程后带来的庞大开发代价和潜在的多线程bug,会让你和团队崩溃到吐血,典型的吃力不讨好的事情。相比于单线程,引入多线程的设计需要带来2到3倍的开发和测试代价,在这么一个时间紧急的项目上,在项目的中后期突然引入多线程真是个大错特错的决策,应该果断拿掉。在这篇博客文章“一种3D引擎的多线程设计方案”里,作者就提到,“多线程的引入使引擎变得更加复杂,不良的设计带来的性能提升非常有限,甚至在单核环境下还会出现明显的性能下降”。

    从这篇Unreal引擎工程师Tim Sweeney的访谈文章中,我找到了在我的情况中应该拒绝多线程的另一个重要原因。

    Tim提到:

    For multithreading optimizations, we’re focusing on physics, animation updates, the renderer’s scene traversal loop, sound updates, and content streaming. We are not attempting to multithread systems that are highly sequential and object-oriented, such as the gameplay.

    This is also why it’s especially important to focus multithreading efforts on the self-contained and performance-critical subsystems in an engine that offer the most potential performance gain. You definitely don’t want to execute your 150,000 lines of object-oriented gameplay logic across multiple threads – the combinatorical complexity of all of the interactions is beyond what a team can economically manage. But if you’re looking at handing off physics calculations or animation updates to threads, that becomes a more tractable problem.

    Tim的主要思想是,游戏引擎多线程应该关注在“独立自主且性能关键的子系统”上,比如物理系统或者是动画系统,这会带来最大的性能提升。而在我的项目中,多线程是放在了脚本(scripting)层,这正中了上面Tim所提到的多线程的雷区——gameplay。脚本是处理gameplay层次的逻辑的,它是高度顺序且面向对象的,并且与其他各个子系统有着千丝万缕的关联。正如Tim所说的,将脚本逻辑放到多线程来执行所带来的交互的复杂度已经超过一个团队的承受范围了。

    从这篇文章中我们也可以获知,像Unreal这样知名的商业引擎,也是到最新的第3版(这种称为下一代的游戏引擎)中才开始支持了多线程,这从一个侧面反映了多线程技术在游戏引擎中是一项高级而复杂的技术。主要原因这受制于硬件的发展,多核CPU的PC是到近些年才开始在市场中变为主流,在单核CPU中使用多线程会带来额外的开销,所以在传统的游戏开发中,往往都是单线程的。(这里是一个很好的关于用户软硬件使用情况统计的网站,不过遗憾的是统计数据基本都是来源于国外,不带中国人玩的,对于国内没什么参考价值)

    另外对于游戏引擎引入多线程的考虑,还有很重要的一点是,我们需要认真的考虑当前引擎的性能具体是受限在哪里,是CPU还是GPU,我们该在哪里进行优化。因为GPU渲染在游戏中是如此的重要,所以在大多数情况下,很可能GPU才是主要的性能瓶颈。只有真正的当物理,AI等这些模块变得越来越重要,并且约束系统性能时,我们才需要仔细考虑是否需要为游戏引擎带来多线程的可能。

    总之,多线程就是一把双刃剑,在决定使用它之前,一定要经过明智而审慎的思考和设计。

    2.如何多线程

    对于游戏引擎的多线程设计,最近读到了一些相当不错的文章,在这里作个整理。

    Gamasutra上的Multithreaded Game Engine Architectures是一篇非常基础且有意思的文章。

    该文章首先指出,程序并行有两种主流的方式,第一种称为函数并行(functional parallelism),另一种称为数据并行(data parallelism)。所谓functional parallelism,即将程序中不同的任务分配给不同的线程并行执行,而data parallelism则是将数据划分到不同的线程上并行执行,但每一个线程执行的是相同的任务。这样的分类比较利于我们更好的理解程序并行的行为,我们可以在很多地方看到有这样的分类,比如在Intel的这篇关于并行引擎架构的文章中,提到了类似的functional decomposition和data decomposition,而John Owens在Siggraph 2008的talk “Parallel Programming Models Overview”中,也提到了这样的分类,他称之为task parallelism和data parallelism.

    基于这些并行方式,该文章提出了游戏引擎多线程的3种架构。

    前两种架构都是基于functional parallelism的方式,第一种架构称为同步的函数并行模型(synchronous function parallel model),是最常见的一种游戏引擎多线程方式。该方式保留传统的game loop,将完全独立的子系统的任务分离到辅线程,并与主线程并行执行,每帧作同步。Intel的Multi-threaded Rendering and Physics Simulation这篇文章就是这种方式的典型。

    第二种架构称为异步的函数并行模型 (asynchronous function parallel model),是一种非常新颖而前瞻的架构,它脱离了传统的game loop,各个子系统跑在各自的线程上,并按自己的节奏拥有独立的game loop,该架构拥有非常好的可扩展性,但难点就是不同子系统之间的数据同步问题。对于这种架构方式,Intel的这篇Designing the Framework of a Parallel Game Engine是一篇非常棒的文章,极力推荐,该文章提出了Free Step Mode和Lock Step Mode两种并行执行的模式,不仅仅是并行架构,从这篇文章中还能学到其他很多关于游戏架构的知识。

    第三种架构是基于data parallelism的方式,文章中称之为数据并行模型(data parallel model)。这种方式我们并不陌生,比如我们经常听到的GPGPU就是这种方式在GPU上的体现,另外我们所熟知的SIMD(Single Instruction Multiple Data),SPMD(Single Program Multiple Data)技术都是这种方式的典型。该方式可以获得最大的并行性,但缺点是采用数据并行的系统往往非常难于设计,通常需要修改原来的面向对象的设计。可以看看Intel的这篇TickerTape的文章,利用SIMD技术提升性能,但却需要破坏原有的类的设计,将结构数组(Array of Structures)改为数组结构(Structure of Arrays)。

    关于更多的游戏引擎和多线程的资源,可以参见这里。

    Posted in: Game Programming / Tagged: architecture, data decomposition, function decomposition, game engine, multithread, 函数并行, 多线程, 数据并行, 架构, 游戏引擎

    Lua相关问题整理(4) – 让Lua的eval函数支持赋值语句

    November 28, 2010 12:22 pm / Leave a Comment / Benny Chen

    上一篇文章提到了在Lua中实现类似于JavaScript中的eval函数,遗憾是该eval函数不支持赋值语句,原因是Lua的赋值运算符是不支持返回值。所以如果要让该eval函数也支持赋值语句,就需要一个额外的工作,让它鉴别一个语句是不是赋值语句,如果是,则return的是被赋值后变量的值。为此,我写了一个isAssignmentExpression函数,比较粗糙,不过够用了,基本思想是检测语句中第一个出现的”=“操作符,且该”=“不能在一对引号当中。

    -- Lua code
    function isAssignmentExpression( str )
    	local i = 1;
    	local curChar;
    	local quotesType = "none" -- none, single or double
    	local isEscaping = false
    
    	curChar = string.sub( str, 1, 1 )
    	while ( curChar ~= "" ) do
    		if ( curChar == "'" and
    			 isEscaping == false and
    			 quotesType ~= "double" )
    		then
    			if ( quotesType == "single" )
    			then quotesType = "none"
    			elseif ( quotesType == "none" )
    			then quotesType = "single"
    			end
    		end
    
    		if ( curChar == """ and
    			 isEscaping == false and
    			 quotesType ~= "single" )
    		then
    			if ( quotesType == "double" )
    			then quotesType = "none"
    			elseif ( quotesType == "none" )
    			then quotesType = "double"
    			end
    		end
    
    		if ( curChar == "\" and isEscaping == false )
    		then isEscaping = true
    		else isEscaping = false
    		end
    
    		if ( curChar == "=" and quotesType == "none" )
    		then
    			if ( string.sub( str, i+1, i+1 ) ~= "=" )
    			then
    				return true, string.sub( str, 1, i - 1 )
    			else
    				return false
    			end
    		end
    
    		i = i + 1
    		curChar = string.sub( str, i, i )
    	end
    
    	return false
    end
    
    function eval( str )
    	local bAssign
    	local var
    	bAssign, var = isAssignmentExpression( str )
    	if ( bAssign )
    	then
    		print( "Assignment, var=" .. var )
    		loadstring( str )()
    		return loadstring( "return " .. var )()
    	else
    		return loadstring( "return " .. str )()
    	end
    end
    
    -- 以下是一组测试
    print( eval( "3+4" ) )
    -- 7
    function Multiply( a, b )
    	return a*b
    end
    print( eval( "Multiply( 3, 4 )" ) ) 
    -- 12
    print( eval( "i" ) ) 
    -- nil
    print( eval( "i = 1" ) ) 
    -- Assignment, var=i    
    -- 1
    print( eval( "i = i + 1" ) ) 
    -- Assignment, var=i    
    -- 2
    print( eval( "i" ) ) 
    -- 2
    print( eval( "i+1" ) ) 
    -- 3
    print( eval( "i, j = 4, 5" ) ) 
    -- Assignment, var=i, j    
    -- 4	   5
    print( eval( "i = {}" ) ) 
    -- Assignment, var=i    
    -- table: 003CD818
    print( eval( "i[ "0" ] = 0" ) ) 
    -- Assignment, var=i[ "0" ]     
    -- 0
    print( eval( "i[ "\"0=" ] = 1" ) ) 
    -- Assignment, var=i[ ""0=" ]     
    -- 1
    print( eval( "i.name="hello"" ) ) 
    -- Assignment, var=i.name     
    -- hello
    print( eval( "i[0], i.name = 4" ) ) 
    -- Assignment, var=i[0], i.name     
    -- 4	  nil
    print( eval( "i == 10" ) ) 
    -- false
    
    Posted in: Game Programming, Lua / Tagged: eval, lua, 赋值语句

    Lua相关问题整理(3)

    November 27, 2010 5:04 pm / Leave a Comment / Benny Chen
    1. 在注册给Lua的C函数中为Lua提供默认参数

      使用luaL_optstring, luaL_optnumber, luaL_optinteger等Lua API,如下示例,函数有一个默认字符串参数,默认值为””,这样在Lua中调用whatever的时候,whatever()或者whatever( “whatever”)均可。(Oh…whatever…随便…都行…)

      // C code
      int Whatever( lua_State *L )
      {
      	string str = luaL_optstring( L, 1, "" );
      	//...omitted code...
      }
      lua_register( L, "whatever", Whatever );
      
    2. 建立Lua字符串到C enum的映射

    3. 使用luaL_checkoption这个Lua API,它可以把从Lua传来的string转换为相应的C string array中的index,从而可以建立Lua字符串和C enum的映射。以下是个简单的示例:

      // C code
      enum PlayerType
      {
      	PLAYER_TYPE_UNDEFINED = -1,
      	PLAYER_TYPE_KING = 0, // 主公
      	PLAYER_TYPE_INSURGENT, // 反贼
      	PLAYER_TYPE_LOYAL, // 忠臣
      	PLAYER_TYPE_TREACHEROUS, //内奸-_^
      	NUM_PLAYER_TYPE // just a sentinel
      };
      
      const char * const PlayerTypeList[NUM_PLAYER_TYPE + 1] =
      {
      	"KING",
      	"INSURGENT",
      	"LOYAL",
      	"TREACHEROUS",
      	NULL
      };
      
      static int testPlayerType( lua_State *L )
      {
      	PlayerType type = static_cast< PlayerType >( 
      						luaL_checkoption( L, 1, 
      						"INSURGENT", PlayerTypeList ) );
      	std::cout << "Type index is " << type 
      		      << " - " << PlayerTypeList[type] 
      		      << std::endl;
      	return 0;
      }
      
      lua_register( L, "setPlayerType", setPlayerType )
      

      首先enum PlayerType定义了一组角色类型,来自人人都爱的三国杀:-)。

      接着PlayerTypeList定义了一个字符串数组,给Lua使用。注意需要保证enum和字符串数组的对应,比如PlayerTypeList[PLAYER_TYPE_KING]是“KING”,同时,PlayerTypeList必须以NULL结尾。

      在定义给Lua的函数testPlayerType中,就可以用luaL_checkoption将Lua传来的字符串参数转换为相应enum的值。luaL_checkoption还支持默认参数,比如在上面例子中,将第三个参数设为“INSURGENT”,如果Lua中没有提供任何参数,则PlayerType就为与“INSURGENT”相对应的PLAYER_TYPE_INSURGENT。

      以下是一组测试及结果:

      --Lua code
      testPlayerType( "KING" ) -- Type index is 0 - KING
      testPlayerType() -- Type index is 1 - INSURGENT
      testPlayerType( "whatever" ) -- bad argument #1 to 'testPlayerType' (invalid option 'whatever')
      
    4. 在Lua中实现eval函数

      众所周知,JavaScript中有一个著名的eval函数,它用于把一个字符串当作一段JS代码去执行,在Lua中没有提供类似的函数,但稍微包装下Lua的库函数loadstring即可实现,以下是代码。

      --Lua code
      function eval( str )
      	local func = loadstring( "return " ..str );
      	return func()
      end
      

      这样已经可以了,不过相比于JS的eval函数,功能稍微差一些,因为它不支持赋值语句,这是Lua语言天然的原因,因为Lua的赋值运算符没有返回值,在其他语言中常见传递赋值的“i=j=1”(先赋值j=1,然后将(j=1)的返回值j赋值给i),在Lua中是不允许的。所以当eval执行的是赋值运算(比如i=1)的时候,return i=1就会出错。

      下面是一些测试例子:

      --Lua code
      print( eval( "3+4" ) ) -- OK, 打印7
      
      function Multiply( a, b )
      	return a*b
      end
      
      print( eval( "Multiply( 3, 4 )" ) ) -- OK,打印12
      
      print( eval( "i = 1" ) ) -- 错误, attempt to call a nil value
      i = 1
      print( eval( "i" ) ) -- OK,打印1
      print( eval( "i = i + 1" ) ) -- 错误, attempt to call a nil value
      
    5. 实现luaL_checkbool

      不知道为什么Lua的API没有提供luaL_checkbook函数,不过很容易实现:

      // C code
      BOOL luaL_checkbool( lua_State *luaVM, int numArg )
      {
      	BOOL b = FALSE;
      	if ( lua_type( L, numArg ) == LUA_TBOOLEAN )
      	{
      		b = lua_toboolean( L, numArg );
      	}
      	else
      	{
      		luaL_typerror( L, numArg, lua_typename( L, LUA_TBOOLEAN ) )
      	}
      	return b;
      }
      
    Posted in: C++, Game Programming, Lua / Tagged: enum, eval, lua, luaL_checkbool, 默认参数

    Lua相关问题整理(2)- 如何在C中为Lua提供同步调用接口

    November 7, 2010 10:25 pm / Leave a Comment / Benny Chen

    这个问题的具体描述是——C注册给Lua一个函数,但Lua调用该C函数并不能立即获得结果(比如需要访问远程服务器获取值),如何能让Lua停止并等待,直到获取到结果后,才继续执行接下来的脚本。

    举个例子,比如说有一个C函数login,我们试图通过调用该函数以执行用户的登录操作并获取验证结果。首先看下面这段C代码:

    // C code
    int login( lua_State *L )
    {
    	string user = luaL_checkstring( L, 1 );
    	string password = luaL_checkstring( L, 2 );
    
    	// 该函数将user和password发送到服务器后立即返回,
    	// 绝不要在此处阻塞,这将严重影响效率
    	sendAuthenticationInfo( user, password );
    
    	return 0;
    }
    // 注册给Lua
    lua_register( L, "login", login );
    

    可以看到,因为该函数需要访问远程的登录服务器,在系统中一般都采取异步操作(让系统阻塞等待结果是不可接受的)。但是这样Lua开发人员调用login函数时,也只能异步等待结果,下面是Lua中处理登录操作的代码。

    -- Lua code
    -- 当用户点击“登陆”按钮后,执行该函数
    function onClickLoginBtn()
    	local user = ...
    	local password = ...
    	login( user, password ) -- 只是发送login命令
    end
    
    --当获取login结果后的回调函数
    function onGetLoginResult( bPass )
    	if bPass == true then
    		print( "Authentication succeeded." )
    		...omitted code...
    	else
    		print( "Authentication failed." )
    		...omitted code...
    	end
    end
    

    这段Lua代码很好理解,首先onClickLoginBtn是一个按钮事件处理函数,当用户点击了”登录“按钮后,会触发该函数。该函数首先从界面上获取用户输入的user和password,然后调用了上面C中所定义的login函数。正如前面的C代码所写的,login函数不会马上得到认证结果,所以执行后马上退出。另一个函数是onGetLoginResult,这个Lua函数需要当C系统中获取到认证结果后被回调执行,以真正执行login的后续操作。所以,我们还需要在C中添加回调的代码。

    // C code
    // 当服务器端返回认证结果后
    BOOL loginResult = ...
    lua_getglobal( L, "onGetLoginResult" );
    lua_pushboolean( L, loginResult );
    lua_call( L, 1, 0 );
    

    在这里,我们在C中hardcode了回调onGetLoginResult的代码,这很丑陋,不过可以避免,比如可以给前面注册给Lua的login函数增加一个参数callbackFunctionName,以让Lua显式的告诉系统当获取登录结果后的回调函数名称。

    再次回到本文一开始所提出的问题,尽管在系统中的异步调用不可避免,但我们希望在Lua中能够有同步机制,即Lua脚本在得到验证结果后才允许被继续执行,如何才能做到这一点呢。

    Lua有一个非常棒的coroutine机制,在Lua代码中可以通过协同程序来进行多线程,可以使用coroutine.yield和coroutine.resume来对协同程序进行挂起和恢复。需要达到上面所提出的目标,只需在系统中使用与coroutine.yield和coroutine.resume相对应的Lua C API——lua_yield和lua_resume即可。如下所示,login函数有些小改变:

    // C code
    int login( lua_State *L )
    {
    	string user = luaL_checkstring( L, 1 );
    	string password = luaL_checkstring( L, 2 );
    	sendAuthenticationInfo( user, password );
    	return lua_yield( L, 0 );
    }
    // 注册给Lua
    lua_register( L, "login", login );
    

    可以看到,与前面的区别只有login函数的最后一句,调用了lua_yield后再返回,而不是return 0,这样就可以达到阻塞Lua脚本的目的。事实是,lua_yield是一个比较特殊的函数,它只能作为注册的C函数的返回值使用,否则调用失败。

    当系统获得服务器端的登陆验证结果后,通过lua_resume即可恢复之前被阻塞的Lua。

    // C code
    // 当服务器端返回认证结果后
    BOOL loginResult = ...
    lua_pushboolean( L, loginResult );
    lua_resume( L, 1 );
    

    上面的代码是当你的系统中只有一个Lua虚拟机的情形,如果使用了多个Lua虚拟机,事情稍微有一点点复杂,login函数还需要将当前调用的lua_State存储下来,以便lua_resume的时候,可以知道恢复的是哪一个被阻塞的虚拟机。

    OK,当C中有这样的实现后,Lua程序人员将会为此而高兴,因为Lua代码变得如此简洁。

    -- Lua code
    -- 当用户点击“登陆”按钮后,执行该函数
    function onClickLoginBtn()
    	local user = ...
    	local password = ...
    	if login( user, password ) == true then
    		print( "Authentication succeeded." )
    		...omitted code...
    	else
    		print( "Authentication failed." )
    		...omitted code...
    	end
    end
    

    系统or平台开发人员应当尽可能的为用户考虑,正如上面第二种解决方法所追求的那样。

    Posted in: C++, Game Programming, Lua / Tagged: C++, coroutine, login, lua, resume, yield, 同步调用

    Lua相关问题整理(1)

    November 7, 2010 10:17 pm / Leave a Comment / Benny Chen
    1. (Under Linux)cannot find ‘dlsym’ ‘dlopen’ ‘dlerror’ ‘dlclose’

      需要同时链接”dl“库

    2. (Under Linux)编译lua报错luaconf.h:275:31: error: readline/readline.h: No such file or directory

      需要下载并安装GNU Readline Library

    3. PANIC: unprotected error in call to Lua API (unable to get ModuleFileName)

      1: 不推荐的解决方式:将Project Properties->Configuration Properties->General下的Character Set从unicode改成multi-set;
      2: 彻底的解决方式,参考此链接:http://lua-users.org/lists/lua-l/2006-06/msg00427.html

    4. 如何将Lua文本文件转化为Lua块文件(chunk file)

      调用LuaAPI – lua_dump

      关于lua_dump: about lua_dump: Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces a binary chunk that, if loaded again, results in a function equivalent to the one dumped. As it produces parts of the chunk, lua_dump calls function writer (see lua_Writer) with the given data to write them.

    5. 如何在C中调用Lua脚本层的库函数

      void BeginLuaLibCall( lua_State *L, const string &libName, const string &functionName )
      {
           lua_pushstring( L, libName.c_str() );
           lua_gettable( L, LUA_GLOBALSINDEX );
           lua_pushstring( L, functionName.c_str() );
           lua_gettable( L, -2 );
      }
      
      void EndLuaLibCall( lua_State *L )
      {
           lua_pop( L, -1 );
      }
      

      示例:调用table.getn(该函数用来获取一个table的size)

      // 利用上面的函数
      BeginLuaLibCall( L, "table", "getn" );
      // 假设你的脚本中有一个table变量myTable,获取它到栈顶
      lua_getglobal( L, "myTable" ); 
      // 执行table.getn( t )
      lua_call( L, 1, 1 ); 
      // 打印结果
      std::cout << lua_tonumber( L, -1 ) ) << std::endl;
      // 将结果弹出栈
      lua_pop( L, -1 );
      // 将名叫“table”的table弹出栈
      EndLuaLibCall( L);
      
    Posted in: Game Programming, Lua / Tagged: chunk, dl, function, library, lua, readline

    Post Navigation

    ← Older Posts
    Newer Posts →

    LinkedIn

    Milan Petrovic

    Categories

    • In My Life (25)
      • A Day in the Life (8)
      • English Learning (2)
      • Learn a Word (7)
      • Something In The Way (8)
    • Music Heaven (8)
      • Guitar (1)
      • In Concert (1)
      • Lyrics (3)
    • OK Computer (54)
      • 3D (3)
      • C++ (10)
      • Computer Graphics (15)
      • Game Programming (23)
      • iOS (6)
      • Linux (1)
      • Lua (9)
      • My Projects (3)
      • Some Experiences (9)
      • Talking in Code (2)
      • Unity (2)
    • Quotations (2)
    • Uncategorized (1)
    • Visca Barça (24)
      • FCB BJ (5)

    Recent Posts

    • [译]优化你的手机游戏(没有延迟的,才是健康的)- 一篇给游戏美术设计师读的文章
    • 新浪微博API for MOAI
    • 稍后继续
    • Unity Developer ++
    • Another Thread @ Moai Forum
    • 1st Day of Golden Week
    • 为SyntaxHighlighter添加新语言
    • 基于Lua的State Pattern
    • Class Diagram of Pacman
    • 基于Moai的Pacman

    Recent Comments

    • 约修亚_RK on 为SyntaxHighlighter添加新语言
    • 爱装的小男孩 on 小心DLL链接静态库时的内存错误
    • happyfire on Game Loop的几种实现方式
    • William on 新浪微博API for MOAI
    • Benny Chen on 新浪微博API for MOAI
    • your man on 新浪微博API for MOAI
    • 你家男人 on 稍后继续
    • 逍遥 on 关于对std::vector的遍历
    • papa on Unity Developer ++
    • T客网 ︱ Techpot » Blog Archive » iOS开发与OpenGL ES相关问题整理(1) on iOS开发与OpenGL ES相关问题整理(1)

    Tags

    2d 3D 3dsmax 3ds max air Apply architecture Asia tour barca Beijing bilbao binary search blocked bob boost bruce springsteen C++ capo CGContextDrawImage Champions League Change DLL DX10 eval exporter flash framework frustum culling game game engine iniesta ios linux lua Moai opengles pacman plug-in plugin 北京 导出插件 崩溃 巴萨 游戏引擎 踢球
    © Copyright 2026 - A Game Developer
    Infinity Theme by DesignCoral / WordPress