GameMonkey参考手册官方资料翻译
作者:希维• 更新时间:2023-08-28 22:00:10 •阅读 0
GameMonkey 脚本参考手册使用lua已经1年多了, 至今还常常惊叹于其作者设计的简洁和提供给用户的一套机制, "工具就在那里摆着, 去发挥你的想象力吧"~~~ lua的接口最好的体现了提供机制而不是策略的思想. 在游戏编程精粹中, 看到一篇关于介绍脚本语言的文章, 其中不光介绍了lua, 也介绍了GameMonkey :) 大概做了一下了解, 发现国内好像尚无使用的先例, 资料也比较少, 本着学习借鉴, 开拓思路的态度, 决定翻译GameMonkey的官方资料, 希望能对需要的人有帮助. 其中也有我自己认为要详细说一下的, 提醒一下的, 用rotc注出来了comments 注释// 和c++一样注释到本行末/*和c / c++ 一样的注释块*/注释块会被编译器忽略掉, 它的作用是给代码做注释或者临时使一段代码失效[调试脚本时常用]变量和常量GameMonkey不像c, Pascal那样的强类型语言, 它更像是Basic语言.GM中的变量是以下类型中的一个:null -- 没有值, 这有类型int -- 32bit的有符号整数float -- 32bit的浮点数string -- null结尾的ansi字符串table -- 数组/hash容器function -- 函数user -- 用户自定义类型rotc: 如何理解null 是一种类型, 而不是一个值?对c++er 和 cer来说, null/NULL基本就是0, 或(void*)0, 它的确是一个具体的值. 这里说GM中的null是个类型, 可能会在理解上有一定的困难. 根源是静态类型和动态类型造成的. 静态类型语言中, 类型和值是分裂开来的, 对于静态类型语言中的变量i来说, 如果能够通过编译, 那么i的类型就肯定是确定的. 静态类型语言中的类型, 只是用来说明如何对&i这样一个内存地址做解释(但问题在于这个说明是在编译期就必须决定的). 也就是说c中的变量直接映射到了内存和如何解释内存. 而动态语言通过引入一个间接层, 其值的结构是 Value(type, realvalue), 也就是说, 一个变量由一个内存和附带一个指示如何解释该内存的标志(及类型)组成的. 这样的好处是显而易见的, 可以在运行时改变变量类型(也就是改变对内存的解释方式), 下面演示动态语言中如何实现变量赋值时决定类型.比如我创造了一门动态语言, 这门语言中有2个类型, 那么这样实现enum {null, man, woman}; // 两个类型加一个null类型struct Value{ Value(){type = null; pData = 0;} char type; void* pData}; // 动态语言中的变量struct Man {Man(int h, int c){housevalue = h; carvalue = c;} int housevalue; int carvalue}; // 男类型内容是房产和车产struct Woman { Woman(char* name) {strcpy(sweetname, name);} char sweetname[12]; }; // 女类型有一个可爱的名字在我的脚本中:Value pp; // 定义个一个变量, 注意, 现在这个变量不是一个Man, 也不是一个Woman, [但它有类型--null, 但是它没有值]pp = Man(5,3); //制造一个富家男, 注意pp 现在的类型由null变成man, 值是一个Man// 实现 void operator = (Value& v, Man& m) {v.type = man; // 赋类型v.pData = &m; // 赋值}pp = Woman(“X姐"); // 制造了一个X姐[芙蓉姐, 凤姐], 注意pp现在的类型由man变成women了, 值是一个Woman// 实现 void operator = (Value& v, Man& m) {v.type = woman; // 赋类型v.pData = &m; // 赋值}pp = null; // 实现 ..... v.type = null;当你掩去c++的实现时, 脚本: Value pp;pp = Man(5, 3);pp = Woman(“X姐”);pp = null;上面就展示了如何在脚本语言中实现所谓的一个变量既能存int (Man), 又能存string(Woman), 还能只有类型没有值(type==null)的奥秘, 其实就是引入了一个间接层, 把静态语言编译时就要确定某个内存地址要怎么解释, 变成了{解释方式, 内存}这种形式, 这样的话, 可以在运行期改变解释方式和值[当然他们是匹配的], [ 可以注意到, 动态分配内存是支持这种实现的不可缺少的机制, 垃圾收集的需求也伴随而来]最后总结: null表示不解释~~~:) 你懂的GM中, 变量名是大小写敏感的, __t0 和 __t1保留做内部使用.变量名 = [a..zA..Z] + [a..zA..Z_0..9]*例子:a = null; // a 没有值b = 4; // b 是int类型c = 4.4; // c 是float类型d = “hello”; // d 是string类型e = table(); // e是一个空的表f = function() {}; // f 是一个函数更多的例子:a = ‘SMID’; // a 是一个int, 值为(‘S’<<24 | ‘M’<<16 | ‘I’<<8 | ‘D’)b = .23; // b 是一个floatc = 2.4f; // c 是一个floatd = ‘c:\windows\sys’; // d是一个string语言和它的标准函数总是试图保留值, 然而不是保留类型. 具体规则是当不同类型的变量在一起运算时, 高级别的类型将被保留. 类型从低级到高级的顺序是: int, float, string.例子:print(“hello” + 4); // 输出: hello 4, 4的类型被提高print(2 + 5); // 输出: 7, int类型被保留print(2.0 + 5); // 输出: 7.0, 5的类型被提高print(sqrt(17.0)); // 输出: 4.1231, float类型被保留print(sqrt(17)); // 输出: 4, int类型被保留int类型赋值的例子:a = 179; // 十进制a = 0xB3; // 十六进制a = 0b0011001 // 二进制a = ‘BLCK’; // 字符转成4个byte分别赋予int的四个byte中float类型赋值例子:b = 45.34; // float十进制b = .345; // floatb = 289.0; // floatb = 12.34f; // c风格floatb = 2.3E-3; // 科学计数法字符串赋值例子:c = “c:\\path\\file.ext”; // 标准双引, 用\做转义字符c = ‘c:\path\file.ext’; // 和上面一样, 单引情况下, \不做转义字符用c = “Mary says \”hello\””; // 相当于'Mary says "hello"'c = 'Chris' 's bike'; // 相当于'Chris's bike', 也就是说在单引内部表示单引的方法是连续两个单引c = “My ” “house”; // 相当于"My house"基础类型可以使用标准内建库进行显示的转换, Int(), Float(), String()例子:a = 10;b = a.String(); // 这样是最好的, 显示的调用类型转化函数, 返回转化后的值b = “” + a; // 这样不好, 赋值会将a的类型提升到string, 但是效率底下b = (10).String(); // 丑陋的b = 10.String(); // 错误的, 编译不过, 因为编译器不认同这种语法引用类型变量的可引用类型有String, Function, Table, User. 当这些变量被赋值时, 并不发生full copy, 而只是让变量指向具体的obj例子:a = table(“apple”, "orange"); // a是一个指向table的引用b = a; // b 现在和a指向同一个tableb[1] = "banana"; // 设置b[1]print(a[0], a[1]); // >> banana orangeprint(b[0], b[1]); // >> banana orange当一个变量被赋新值时, 该变量原来持有的值就有可能丢失掉了.例子:Oper = function(a, b){return a + b}; // Oper现在指向一个函数Oper = “hello”; // Oper现在指向字符串, 原来的函数被丢失了函数语法: function() { };一个函数体是一个值, 而函数是一个类型 {type = GM_FUNCTION, value=function...}注意: 记住在将函数赋值给变量后面那个分号, 这是语法必须的例子// 将一个创建一个rect table的函数赋值给CreateRectCreateRect = function(posX, posY, sizeX, sizeY){rect = table(x=posX, y=posY, width=sizeX, height=sizeY);rect.Area = function() {return .width * height; };return rect;};myRect = CreateRect(0, 0, 5, 10); // 创建一个用于描述rect的tablearea = myRect.Area(); // 可以用:代替.来隐式的传递一个this指针Size = function(){return .width * .height;};s = myRect:Size(); // 调用时, myRect会当做this指针传入Size中作用域和作用域有关的一些关键字, 语法:global Local member thisthis..函数中的变量.默认情况下, 一个在函数中使用的变量就是这个函数的本地变量. 如果要声明一个全局变量, 需要使用global关键字. 访问成员变量必须通过this或者是使用member关键字声明它是一个成员变量. 在局部使用的变量可以用local关键字声明.例子:Access = function(){ // Access 是一个local变量, 它引用着一个函数apple = 3; // apple 是函数的一个local变量global apple; // 把apple声明成全局作用域local apple; // 把apple声明成局部作用域member apple; // 把apple声明成 this的member变量this.apple; // 明确的访问this.apple.apple // 隐式的访问this.apple};例子:a = 13; // a 是一个local作用域变量print(b); // b是nullglobal b = function() { // b是一个全局作用域的变量, 类型是GM_FUNCTIONglobal c = 2; // c是一个全局作用域的变量d = 3; // d是函数局部作用域变量{ if (c == 2){ local e = 3; } // e 从这一刻开始成为函数局部作用域变量, 注意没有块作用域变量}print(e); // e = 3}在查找一个变量时, 按照local 和 parameters, 然后global的顺序查找.成员变量有微妙的不同:h = function() { // h 是一个local变量global a = 3; // a 是一个全局变量member n; // n可以从this被访问和创建d = 3; // d是函数局部作用域this.b = 3; // b是member作用域.b = .x + 1; // b, x都是member作用域print(b); // b 是 null, 因为这里并没有local的bprint(n); // 就像print(this.n)一样, 因为上面显示声明过了};全局作用域中的语句.x = 7; // localglobal x = 8; // globala = function(y) {local x = 5; // function localdostring(“print(x);”); // 这里打出8, 和lua一样, dostring总是在全局环境下编译运行的, 无法访问function的变量和parameters};变量可以是虚拟机全局作用域的, 也可以是某个函数作用域的, 或者是某个obj比如table的成员作用域的. 当执行一个文件或者是执行一个字符串的时候, 和lua一样, 文件或者是字符串被编译成一个无名的函数, 所以默认情况下, 其中最外层的未加特别申明的变量是该无名函数的函数作用域的.this总是存在的. 它或者是null, 或者是一个有效的值. 你可以传递this, 或者是使用重载冒号操作符默认的this. 这一特性多用在创建诸如类似模板行为, 那些地方的obj的操作往往只有run-time时才能确认. this也用在创建线程, 例子:obj:thread(obj.DoThings) // 开始一个线程, 并把obj作为this传递给它obj:MakeFruit(apple, orange) // 调用MakeFruit, 并把obj当做this传给它语法和操作符! Not 逻辑取反~ 每一个bit位取反^ bit位使用与或 XOR| bit位使用或 OR& bit位使用与 AND>> bit位右移<< bit位左移~= bit位取反赋值^= bit位 XOR 赋值|= bit位 OR 赋值&= bit位 AND 赋值>>= bit位 右移 赋值<<= bit位 左移 赋值= 赋值' 单引 其中的字符会当做int值" 双引 字符串(处理转义字符)` 反引 字符串(不处理转义字符)[] 方阔 用index取talbe元素. 取table元素 : 给函数传递this + 数学+- 数学-* 数学*/ 数学/% 模取余+=, –=, *=, /=, %= 数学运算并赋值{} 界定语句块; 语句结束标志<, <=, >, >=, == 数学比较&&或and 逻辑AND|| 或or 逻辑ORTables 表语法: table( = , ...);table(, …);{=, …, };{, …, };table可以被同时认为是array 和 map. 因为table中可以容纳data和function, 所以table也可以被认为是class, table中也可以容纳别的table, 这时它也已被认为是Tree. 初始化table的例子:fruit = table("apple", "banana", favorite= "tomato", "cherry");fruit = {"apple", "banana", favorite="tomato", "cherry"};这时, fruit的样子就是:fruit[0] = “apple”;fruit[1] = “banana”;fruit[2] = “cherry”;fruit[“favorite”] = "tomato"; 也可以写作是 fruit.favorite = "tomato"可以注意到, fruit.favorite="tomato"并没有占据 element[2], 虽然它在逻辑上应该是element[2]的位置, 但是它不是一个index索引成员, 是一个{key, value}成员.从表中取得元素的例子.a = thing.other; // other 是table thing中的一个成员b = thing[“other”]; // 相当于b = thing.otherc = thing[2]; // c取得了thing中的第三个indexd索引成员index = 3;d = thing[index]; // 用int做下标, 就可以把table当数组访问accoc = “fav”;e = thing[accoc]; // 用string做下标, 就可以把table当map访问注意, thing["Fav"]和thing["fav"]是两个不同的东西, 因为GM是大小写敏感的. 这样做设计上的考虑是:1) 赋值可能是string, 也可能是任何类型的值.2) 要做到大小写无关, 底层需要一些额外的工作量, 这会产生一定量的效率问题.设置table中成员的值的例子.thing.other = 4;thing[3] = “hello”;嵌套表的例子:matrix = { {1, 2, 3,}, {4, 5, 6}, {7, 8, 9,}, } // print(“matrix[2][1] = ”, matrix[2][1]); // 输出"matrix[2][1] = 8"关键字if和else语法: if ( ) { }或者 if ( ) { } else { }或者 if ( ) { } else if ( ) { } else { }例子:foo = 3;bar = 5;if ((foo * 2) > bar){print(foo * 2, “is greater than”, bar);}else{print(foo * 2, “is less than”, bar);}// 输出: 6 is greater then 5if 会计算条件表达式的值, 并根据其结果的true/false来选择执行那一段语句.if 在计算条件时, 会像大多数语言那样, 并且实现了短路求值, 下面是一些例子:if (3 * 4 + 2 > 13) == if ( ( (3*4) + 2) > 13 )if (3 > 0 || 0 > 1) 3 > 0恒真, 那么永远不会去对0 > 1求值对c程序员的提示: 你不能把condition和一个单语句无语句块标示的statements写在同一行, 这是语法不允许的例: if ( a > 3) b = 4; // 错误, b = 4 必须被{}包起来关键字for语法: for (; ; ) { }例子: for (index = 0; index < 6; index = index + 2){ print(“Index = ”, index);}输出是:Index = 0Index = 2Index = 4for 语句的执行和大多数语言一样, 循序是1. 执行 statement12. 执行condition, 是false就退出for3. 执行statements4. 执行statement2, goto 2关键字foreach语法: foreach ( and in ) {