| xin's profile流隙PhotosBlogLists | Help |
|
|
March 20 j2me脚本引擎Snake脚本编译器已升至1.2
虚拟机没什么改动还是1.1
修正bug
1.#define宏定义问题
2.增加主引擎脚本调用方法
重点是第二项改变,做游戏的同胞一定会用得到
里面有主引擎调用脚本方法的例子
August 01 SnakeScript1.1.0SnakeScript1.1版本(截止8.1)
新增功能及特性
1.动态优先级设定
可以在程序运行的时候动态改变某个脚本的优先级 2.动态实现脚本的加载、释放、暂停、恢复、结束等控制
这些对脚本运行的控制已经可以写入不同脚本里,而不是主引擎去调用这些功能, 这意味着RPG脚本分为主脚本和很多子脚本,主脚本负责在游戏运行过程中动态的 管理所有的子脚本 3.自动堆栈平衡 如果进行对主引擎函数的调用,则调用结束后无需手动传入参数个数来平衡堆栈,脚本引擎代劳,防止人为因素的错误发生 4.无限级别函数调用
曾经用了一个函数追踪栈来记录函数之间的调用信息,因为初始化的追踪栈是有限的,所以只支持有限级别的函数调用(比如32级) 现在通过更好的方法将函数调用信息记录到堆栈上,实现了无限级别调用,可以实现完全的地归操作,(受限与堆栈大小) 5.主引擎对脚本函数的异步及同步调用
这个是实现起来最复杂功能(占用新版本开发的50%时间),可以在任意时候,任意位置来让主引擎去调用脚本里的方法,同步异步都支持 不明白同步异步的同学去看脚本例子,内含介绍 6.脚本和脚本之间函数的同步和异步调用
这也是新版本的核心功能,现在设计脚本RPG的模式就非常清晰了,比如一个npc脚本使用了一个爆炸道具,这个npc脚本就能通知附近的油桶脚本(函数调用) 让油桶脚本播放爆炸的动画,很酷!!! 7.函数参数类型化声明
在声明函数的时候无需像以前一样传入参数个数,而是传入参数名和希望的类型,这样可以让调用者明白传什么样的参数最合理 8.静态内存初始化
一个朋友希望数组初始化能像C或java一样简单,而不是一个一个赋值,我想了一下觉得建议很好,立即实现这样初始化 比如 var xiaoxin[4] = {1, 2, "xiaoxin", 3, 5} 9.支持内嵌虚拟指令集
我非常喜欢C++里的内嵌汇编模式,于是在脚本编译器上也实现了同样的功能 _svm{ PUSH 10 PUSH 20 POP _T1 POP _T0 ADD _T0, _T1 PUSH _T0 POP _RetVal } 一个简单的加法虚拟机指令代码,可以嵌入.ss脚本代码的方法里 10.struct结构支持
还是应为很喜欢C代码的缘故,加入struct数据结构,这个没什么可说的,就是方便组织数据 struct结构可以嵌套 例: struct Anima{ var data[20] } struct Player{ var x; var y; Anima ani; } 11.实现include指令
可以编写静态库,功能和C的是一样,编译的时候查找并链接,我写了一个system库,主脚本如果要实现对虚拟机的控制需要#include预编译指令链接 12.优化脚本虚拟效率再提升30%
测试机 SE w810 SnakeScript1.0 每秒运行运行指令个数 24000条 SnakeScript1.1 每秒运行运行指令个数 35000条 看看在J2SE上的运行 SnakeScript1.0 每秒运行运行指令个数 4584030条 SnakeScript1.1 每秒运行运行指令个数 6276859条 注意: 影响效率的因素:1.多脚本并发执行,上下文切换有性能损失(5%) 2.脚本对主引擎API调用(host方法调用),性能损失(10%) 3.脚本之间的函数异步调用,性能损失(10%) 下载地址 September 08 遗传算法—寻路篇 遗传,生物学中的一个伟大的学科,至今都令我着迷,尤其是那短短几微米的DNA双螺旋结构,居然能编码出N百万种蛋白质,把一个生物体完美的表现出来,而遗传算法是是利用计算机模拟对生物体进行编码,让这些模拟的DNA进行配对杂交,变异,最后模拟自然选择从中挑选优秀个个体再进行配对,杂交,变异,最终这样一代又一代的选择下去,优秀基因个体可以很好的遗传给后代,那些拙略的个体最终被淘汰,最后所有的个体几乎都趋向完美,用在算法里也就是说找到了问题的解。所以说,遗传算法(GA)一种神奇的算法,将人们最终摆脱了只有对人工智能进行硬编码的误区。
幸亏自己从小喜欢生物,以及进化论,所以对遗传算法也是情有独钟,看完游戏编成中的人工智能技术这本书,将里面用遗传算法的寻路写成了j2me的代码,可视的寻路会更有意思。
设计概要:
1.进行对问题的编码(DNA),用两个bit编码4个方向: 00:北 01:南 10:东 11:西 (单个基因)
2.初始化基因组群体(100个),每个基因组含有70个单基因(即70步,比玩家在地图上走到终点的最远距离还长一些)
3.进行世代循环
while(true){
1.检查这100个基因组,看看他们解决问题的性能怎么样(某个基因走不通,抛弃该基因,进行下一个基因)最终为这100
个基因组每个分别分配一个曼哈顿系数(适应性分数)如果为1,则证明找到出口,跳出循环。
2.用赌轮选择算法从100个基因组中挑出2个作为父母基因(基因组的适应性分数越高被选中的几率越大)进行杂交将其
优秀基因传给下一代
3.对新诞生的基因进行变异(变异率非常的小)变异操作就是0变1 1变0
4.用下一代基因组代替上一代组,即新诞生100个基因组来代替原来100个基因组
}
作为遗传算法去寻路有个问题,就是每次寻路的时间不同,有的经过不到10代就能找到,有的经过100代的子孙都还没有找到路。
改进措施:
1. 初始化基因组是完全随机,没有策略,所以有的时候初始群体会很糟糕。
2.修改适应性分数分配函数,使多次进入同一小格的基因得到惩罚,这样能更有效的找到出口
3. 对于繁殖出的新基因需要进行外部保存,这样下次就能从文明世代向前进化,不必每次都从野人世代开始
4. 遗传算法由于用途太广,太新颖,以至于对某种特定的问题进行求解反而不如针对特定问题而开发的算法效率高(比如A*)
但是遗传算法是人工智能的风水岭,以前的硬编码的算法被隔离开来,计算机能自主编码,向前进化。
J2ME 测试程序(需要用sony的模拟器 WTK2.3 会爆内存)
J2SE 测试程序(JDK 1.6)
August 28 Snake编译器0.4.1Snake脚本编译器0.4.1(升级)编译出的脚本依然支持0.4版虚拟机
增加了#define宏定义命令 支持了函数的后向引用,但必须在脚本头部有该函数的声明 使用方法 在命令行输入 java -jar SSC.jar Test.ss 就会编译成二进制的Test.sse文件(脚本引擎的需要的文件) 可以在编译时加入参数 如: java -jar SSC.jar Test.ss -A (保留汇编文件) java -jar SSC.jar Test.ss -N (不编译成二进制文件) java -jar SSC.jar Test.ss -S:1024 (设置脚本虚拟机的堆栈大小,不设置的话默认是512(2k)) 下载地址
基于jdk1.6
基于jdk1.4.2
August 26 J2ME脚本引擎 Snake做了两年手机游戏,终于晓得了脚本是个啥东西,为什么做游戏要用到脚本呢,看了无数资料总结如下:
优点:
1,脚本可以实现游戏逻辑,在不修改主游戏引擎并且花费时间去编译的前提下,改变了脚本也就改变游戏的玩法和趣味性
2,硬编码似乎编程人员都不太提倡,所以为了避免硬编码带来的危害,人们乐意将数据放到外部文件,实现数据和引擎的分离,最终发展为
带有逻辑判断的外部数据文件,即脚本。
3,游戏中用到太多的功能函数,一旦需要修改则必须重新编译,如果将功能函数放到脚本里,由游戏引擎来调用将会是一件灵活而又优雅的
做法。
4,将程序员从逻辑处理中解放出来,他们只负责搭建完美表现的游戏主引擎,而为数据逻辑及人物物品的行为编写代码的工作丢给策划
最大限度提高游戏生产力。比如halo里的NPC行为全部都用脚本控制,并且更为神奇的是脚本之间可以相互通信,使得NPC可以
有组织有计划有策略的向玩家发起进攻,真实性不言而喻。
5,安全性,如果脚本的执行出了一点BUG绝不会引起整个游戏的崩溃,大不了卸载脚本再重新加载执行,玩家在游戏中还是正常的打怪和
聊天,一点也没觉得有什么异常。
缺点:
1,运行速度比游戏主引擎慢,即使优化到极限都没有任何速度优势,基于解释型的脚本引擎做游戏压根都不太考虑。
2,设计简单的脚本,编写代码都需要有一定的编程基础,所以策划要想编写出优雅的脚本代码也是需要学习的。
基于脚本的速度问题,我考虑过并经过测试,发现用脚本来实现精灵的逻辑运算一点问题都没有,因为是脚本虚拟机是读字节码,并且全部面向过程,就连与主游戏引擎通信的关键部分,也没有用到javaRTTI机制,最大限度的优化了运行速度,所以用主游戏引擎处理运算量大的图形渲染,脚本去做逻辑运算,我个人倾向于这种完美的搭配。
Snake脚本引擎简介:
一、高级代码编写:
1,语法类似于C。
2,支持函数定义,函数调用,数组,表达式,循环,分支。
3,弱类型(lua也是),内建类型有string,int,float,boolean
如定义变量 var x;可以给x赋的值有 x="你好Snake"; x=32; x=32.23; x=true;
4,内建35个运算符,适用于所有的表达式,如位运算>> 逻辑运算&& 赋值运算 |= 等等,运算符优先级简化为4级,
由高到低为 单目运算>算数运算>关系运算>逻辑运算,如果你写代码是不确定运算符的优先级保险起见加上括号就不会有错
5,支持转义字符输入。
6,支持注释,如行注释符// 及块注释符 /* */
二、低级代码编写:
1,语法类似于8086汇编,但要比它简单的多,有33种虚拟指令可供选择,并且可以定义方法
演示一个完整的加法函数及函数调用(分号是行注释符):
myAdd()
{
param y ;函数参数y
param x ;函数参数x
Add x, y ;将y的值与x相加并赋给x
Mov _RetVal, x ;将x值赋给寄存器(只有一个寄存器即_RetVal)
Ret ; 方法返回符(可以省略)
}
_Main()
{
var x ;定义局部变量x
var y ;定义局部变量y
var z ;定义局部变量
Mov x, 2 ;将2赋给x
Mov y, 3 ;将3赋给y
Push x ;将x压栈,调用方法需要将参数压栈
Push y ;将y压栈
Call myAdd ;调用方法
Mov z, _RetVal ;将寄存器的值赋给z
}
判断和分支的低级代码我就不给出了,想了解的话可以先写高代码,再用编译器编译成低级代码(在编译器中输入参数-A)
再参照低级代码来研究
话说回来,为什么要设计低级代码呢,干脆直接写高级代码多方便,之所以这样设计是由于编译器编译跳转分支那部分都非常的
精简,运行速度非常的快,但是编译表达式就比较繁琐,虽然在3个星期前我压根不知道什么叫编译原理,但是通过写脚本的词法分析,
语法分析,汇编等程序我才发现它的难度和复杂度超出想象好几倍,当前最流行的编译器如C++编译器是用状态机进行语法分析,在表
达式的编译上做足了优化,可是我才是个初学者,为了加快研发速度我使用了半状态机半递归下降的方式进行编译,所以在效率上没有做
太多优化,所以如果您想写复杂表达式求值并需要快速运行的代码,还是建议用低级代码编写,最后可以和高级代码进行合并,用汇编器
汇编成二进制。
三、脚本虚拟机(SVM)
1,虚拟机的执行是基于字节码的,只有一个寄存器_RetVal 用来存放返回值,运行时堆栈是用来进行函数调用的
大小默认是512K,可以自己更改 比如在低级代码上头部加入关键字 SetStackSize 1024 虚拟机会根据设置来修改堆栈大小
2,和主游戏引擎通信是基于方法调用,需要自己写方法库类,这个类需要继承Lib.class, 在自己的方法库中实现方法,考虑到效率,
没有通信是没有用到对象,并且也不是基于RTTI机制,所以使用时务必要注意2点,这两点写在测试包里,可以去看一下
3,错误处理也是考虑到效率没有基于java的异常,而是只打印错印并且虚拟机会正常运行,可是一旦打印出错误,需要做的就是停
下运行的程序 检查脚本有没有,或者是虚拟机的核心执行单元有没有BUG如果不这样做,那么后面的脚本数据已经成脏数据,
会对游戏逻辑造成潜在的危险。
4,多线程:脚本引擎能加载多个脚本并发执行,并且每个线程有个优先级,优先级高的脚本能分到更多的时间片,SVM来管理这些线程
的运行,并且主游戏引擎能方便的操作脚本虚拟机加载,暂停,停止,恢复这些脚本线程。(0.8版提供多线程功能)
5,运行方式:主游戏引擎在while循环中,每次调用虚拟机的runScript(int time)方法,通过传入时间片来决定虚拟机在这个循环
周期运行多长时间,所以脚本里就可以写这样的循环while(true)也就是脚本有自己的逻辑死循环,但是它不会干扰主引擎的逻辑死
循环,因为在传入的时间片运行完时,脚本引擎会自动跳出来让主引擎继续运行下一个循环,而这些对编程人员都是透明的。
四、最后
希望对此有兴趣的人能来共同维护这个脚本引擎,说真的,要不是自己的那点热情,做这么具有挑战的东西我想都不敢想,还好东西
总算完成了,并且经行了一些测试,并将这个引擎用到了公司的游戏中,bug还有没有我不太敢保证,毕竟是最近才做出来,所以希望
大家一起来维护它的版本,并且有好的改进策略一定要告诉我,我期待你们将优秀的编程思想注入其中,让它永远充满活力。
如果在使用的时候有什么困难或bug就欢迎联系我,我们一起探讨一下。
五、感谢
CoCoMo 看了他的脚本引擎才真正了解其内涵。
《游戏脚本高级编程》这本书写的非常好,它里面思想我全部吸纳,并且亲手实现了。
<脚本控制的人工智能AI> 这篇文章让我极度的热衷于人工智能AI脚本的编写,脚本引擎只是个工具,能够编写出智能的NPC那才是本事
《深入java虚拟机》 可以了解虚拟机的一些实现策略和最底层的数据处理。
《人工智能游戏编程真言》 里面有些写对脚本的负面看法,很客观。
六、下载地址:
虚拟机及测试程序
编译器及汇编器 http://www.j2megame.cn/attachment.php?aid=1219
|
|
|