脚本在游戏中占据的位置是不可质疑的。不仅仅是由于其解释语言的特性(修改不需要对源代码进行编译);而且游戏的逻辑采用脚本语言更加容易描述。对于底层脚本和高层脚本,至今尚未有一个明确的定义。本系列文章试图从技术的角度,在概念上对底层和高层脚本进行一个划分,并对微线程在游戏引擎的引入的必要性进行分析。 以下面网上对于脚本与游戏主循环的同步作为一个引子,展开本文研究的话题。 “基于命令的脚本语言可以用来控制游戏中的non-player character,可以让他们"自主"的转悠、动作,使游戏更生动。当然,这些NPCs的动作也可以使用Hard-code的方式跟游戏引擎写在同一处,显而易见的是,这种方式将游戏的底层(Gameengine)和游戏的高层(Logic)混在一起,对于游戏的扩展、游戏的调试。。。。没有可取之处。 下面的这些脚本片断用来控制一个NPC,每个命令的含义很直观。 // RPG NPC Script // A Command-Based Language Demo // ---- Walking in a square pattern ShowTextBox "THAT WAS SIMPLE ENOUGH." Pause 2400 ShowTextBox "NOW LET'S WALK IN A SQUARE PATTERN." Pause 2400 HideTextBox Pause 800 SetCharDir "Right" MoveChar 40 0 MoveChar 8 8 SetCharDir "Down" MoveChar 0 80 MoveChar -8 8 要让这个脚本控制一个NPC,我们必须编写一个"脚本解释器",从脚本文件内读入每行命令、解释、并执行。需要注意的是,如何与游戏的主循环同步呢?也就是说,如果我们顺序执行这个脚本序列(在一个循环中),则整个程序的其他部分得不到响应,那就会出现这样的情况:在NPC运动过程中,游戏主角将不会响应用户的控制。如何解决这个问题呢?多线程(Multi-Thread)?这是个自找麻烦的做法。 实际上,为了解决面临的同步问题,我们可以把脚本的作用限制在"改变程序的状态"这个范围内,例如:NPC要从A处移动到B处,我们不会在解释这个命令脚本的时候做这样的事情:把代表NPC的图片从A处以一个微小的增量移动,直到它到到B处,这样就导致了"不同步",我们要做的是,设置这个NPC的状态为正在移动,并记录目标点的坐标,然后,在游戏的主循环中,我们检测到这个NPC的状态,如果它没有到目标点,我们就继续移动它。 同时需要注意的是,如果这个命令没有执行完,那么我们应该不允许下一个命令的开始,也就是说,我们在游戏的主循环中,依次解释每一条命令,并且设置相应的状态值,但是遇到类似"移动"这样的命令,如果没有执行完这条命令,程序将不会解释执行下一条命令,也就是说,移动到某处不是一蹴而就的,是需要过程的。” 本文将针对六个方面的问题展开讨论:高层脚本和底层脚本;微线程;微线程与主循环同步;脚本组织与对象脚本;脚本与任务系统;网游客户端脚本与游戏的交互性。” 本文内容仅代表星河工作室针对此类问题的观点。相关的技术在星河平台2.1版本全部得到支持(SRPV2.1),可访问相关网站:http://www.srplab.com。 一:高层脚本和底层脚本 高层脚本更适于描述逻辑,上述的例子是一个高层脚本,下面的一段描述也是高层脚本: 走到(死水沼泽,56,99) 等待(500)毫秒 走到(死水沼泽,56,42) 找到(首饰店掌柜)(死水沼泽【7】,52,31) 与首饰店掌柜对话 选择【出售首饰】 自动卖掉【手镯】类物品 自动卖掉【戒指】类物品 自动卖掉【项链】类物品 选择【返回】 结束对话 找到【服装店掌柜】(死水沼泽【7】,49,37) 与【服装店掌柜】对话 选择【出售衣服】 自动卖掉【衣服】类别物品 自动卖掉【头盔】类别物品 选择【返回】 结束对话 对于底层脚本,更加类似于函数,如下一段代码(摘自:Game Programming With Python,lua and Ruby)是底层脚本: function render_frame(screen, background) -- When called renders a new frame. -- First clears the screen SDL.SDL_FillRect(screen, NULL, background); -- re-draws each actor in gamestate.actors for i = 1, getn(gamestate.actors) do gamestate.actors:render(screen) end -- updates SDL.SDL_UpdateRect(screen, 0, 0, 0, 0) end function engine_init(argv) local width, height; local video_bpp; local videoflags; videoflags = SDL.bit_or(SDL.SDL_HWSURFACE, SDL.SDL_ANYFORMAT) width = 800 height = 600 video_bpp = 16 -- Set video mode gamestate.screen = SDL.SDL_SetVideoMode(width, height, video_bpp, videoflags); gamestate.background = SDL.SDL_MapRGB(gamestate.screen.format, 0, 0, 0); SDL.SDL_ShowCursor(0) -- initialize the timer/ticks gamestate.begin_time = SDL.SDL_GetTicks(); gamestate.last_update_ticks = gamestate.begin_time; end 那么什么是高层脚本,什么是底层脚本,两者之间的区别是什么? 如果脚本不能够顺序执行完毕,则该脚本是一个高层脚本。高层脚本更加适合于描述游戏逻辑。如果脚本能够顺序执行完毕,则该脚本是一个底层脚本,底层脚本使游戏在更新局部功能的时候不需要重新编译。 对于高层脚本描述游戏逻辑,有时一条脚本命令需要一个执行过程,比如:走到(死水沼泽,56,99),该脚本命令执行后,会触发一个移动的动作,需要几帧甚至更多的游戏循环,才能够执行完毕。类似命令“MoveChar 0 80”同样。因此在命令执行过程中,需要让出CPU,给游戏其它部分运行,直到达到目标状态。 对于底层脚本,不存在类似的问题,执行过程都是顺序的,中间不中断。 高层脚本执行如何进行,采用类似前面,通过引入状态的方法进行控制,该方法有哪些缺陷?有没有更好的方案,答案是有,哪就是采用微线程。 我们在第二部分讨论微线程,并与一个通过引入状态进行控制的代码进行比较。 abc
|