西西軟件園多重安全檢測下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)其它知識 → GameMonkey參考手冊官方資料翻譯

GameMonkey參考手冊官方資料翻譯

相關(guān)軟件相關(guān)文章發(fā)表評論 來源:本站整理時間:2010/12/13 9:01:50字體大。A-A+

作者:佚名點擊:122次評論:0次標(biāo)簽: GameMonkey luna rotc

  • 類型:定時工具大。846KB語言:多國語言[中文] 評分:6.6
  • 標(biāo)簽:
立即下載
 GameMonkey 腳本參考手冊
使用lua已經(jīng)1年多了, 至今還常常驚嘆于其作者設(shè)計的簡潔和提供給用戶的一套機制, "工具就在那里擺著, 去發(fā)揮你的想象力吧"~~~ lua的接口最好的體現(xiàn)了提供機制而不是策略的思想. 在游戲編程精粹中, 看到一篇關(guān)于介紹腳本語言的文章, 其中不光介紹了lua, 也介紹了GameMonkey :) 大概做了一下了解, 發(fā)現(xiàn)國內(nèi)好像尚無使用的先例, 資料也比較少, 本著學(xué)習(xí)借鑒, 開拓思路的態(tài)度, 決定翻譯GameMonkey的官方資料, 希望能對需要的人有幫助. 其中也有我自己認(rèn)為要詳細(xì)說一下的, 提醒一下的, 用rotc注出來了

comments 注釋
// 和c++一樣注釋到本行末

/*

和c / c++ 一樣的注釋塊

*/

注釋塊會被編譯器忽略掉, 它的作用是給代碼做注釋或者臨時使一段代碼失效[調(diào)試腳本時常用]


變量和常量
GameMonkey不像c, Pascal那樣的強類型語言, 它更像是Basic語言.

GM中的變量是以下類型中的一個:

null -- 沒有值, 這有類型

int -- 32bit的有符號整數(shù)

float -- 32bit的浮點數(shù)

string -- null結(jié)尾的ansi字符串

table -- 數(shù)組/hash容器

function -- 函數(shù)

user -- 用戶自定義類型

rotc: 如何理解null 是一種類型, 而不是一個值?

對c++er 和 cer來說, null/NULL基本就是0, 或(void*)0, 它的確是一個具體的值. 這里說GM中的null是個類型, 可能會在理解上有一定的困難. 根源是靜態(tài)類型和動態(tài)類型造成的. 靜態(tài)類型語言中, 類型和值是分裂開來的, 對于靜態(tài)類型語言中的變量i來說, 如果能夠通過編譯, 那么i的類型就肯定是確定的. 靜態(tài)類型語言中的類型, 只是用來說明如何對&i這樣一個內(nèi)存地址做解釋(但問題在于這個說明是在編譯期就必須決定的). 也就是說c中的變量直接映射到了內(nèi)存和如何解釋內(nèi)存. 而動態(tài)語言通過引入一個間接層, 其值的結(jié)構(gòu)是 Value(type, realvalue), 也就是說, 一個變量由一個內(nèi)存和附帶一個指示如何解釋該內(nèi)存的標(biāo)志(及類型)組成的. 這樣的好處是顯而易見的, 可以在運行時改變變量類型(也就是改變對內(nèi)存的解釋方式), 下面演示動態(tài)語言中如何實現(xiàn)變量賦值時決定類型.

比如我創(chuàng)造了一門動態(tài)語言, 這門語言中有2個類型, 那么這樣實現(xiàn)


enum {null, man, woman}; // 兩個類型加一個null類型

struct Value{ Value(){type = null; pData = 0;} char type; void* pData}; // 動態(tài)語言中的變量

struct Man {Man(int h, int c){housevalue = h; carvalue = c;} int housevalue; int carvalue}; // 男類型內(nèi)容是房產(chǎn)和車產(chǎn)

struct Woman { Woman(char* name) {strcpy(sweetname, name);} char sweetname[12]; }; // 女類型有一個可愛的名字

在我的腳本中:

Value pp; // 定義個一個變量, 注意, 現(xiàn)在這個變量不是一個Man, 也不是一個Woman, [但它有類型--null, 但是它沒有值]

pp = Man(5,3); //制造一個富家男, 注意pp 現(xiàn)在的類型由null變成man, 值是一個Man

// 實現(xiàn) void operator = (Value& v, Man& m) {

v.type = man; // 賦類型

v.pData = &m; // 賦值

}

pp = Woman(“X姐"); // 制造了一個X姐[芙蓉姐, 鳳姐], 注意pp現(xiàn)在的類型由man變成women了, 值是一個Woman

// 實現(xiàn) void operator = (Value& v, Man& m) {

v.type = woman; // 賦類型

v.pData = &m; // 賦值

}

pp = null;

// 實現(xiàn) ..... v.type = null;
當(dāng)你掩去c++的實現(xiàn)時, 腳本:




Value pp;

pp = Man(5, 3);

pp = Woman(“X姐”);

pp = null;
上面就展示了如何在腳本語言中實現(xiàn)所謂的一個變量既能存int (Man), 又能存string(Woman), 還能只有類型沒有值(type==null)的奧秘, 其實就是引入了一個間接層, 把靜態(tài)語言編譯時就要確定某個內(nèi)存地址要怎么解釋, 變成了{(lán)解釋方式, 內(nèi)存}這種形式, 這樣的話, 可以在運行期改變解釋方式和值[當(dāng)然他們是匹配的], [ 可以注意到, 動態(tài)分配內(nèi)存是支持這種實現(xiàn)的不可缺少的機制, 垃圾收集的需求也伴隨而來]

最后總結(jié): null表示不解釋~~~:) 你懂的


GM中, 變量名是大小寫敏感的, __t0 和 __t1保留做內(nèi)部使用.

變量名 = [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 是一個函數(shù)
更多的例子:

a = ‘SMID’; // a 是一個int, 值為(‘S’<<24 | ‘M’<<16 | ‘I’<<8 | ‘D’)

b = .23; // b 是一個float

c = 2.4f; // c 是一個float

d = ‘c:\windows\sys’; // d是一個string


語言和它的標(biāo)準(zhǔn)函數(shù)總是試圖保留值, 然而不是保留類型. 具體規(guī)則是當(dāng)不同類型的變量在一起運算時, 高級別的類型將被保留. 類型從低級到高級的順序是: 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; // 十進(jìn)制

a = 0xB3; // 十六進(jìn)制

a = 0b0011001 // 二進(jìn)制

a = ‘BLCK’; // 字符轉(zhuǎn)成4個byte分別賦予int的四個byte中


float類型賦值例子:

b = 45.34; // float十進(jìn)制

b = .345; // float

b = 289.0; // float

b = 12.34f; // c風(fēng)格float

b = 2.3E-3; // 科學(xué)計數(shù)法


字符串賦值例子:

c = “c:\\path\\file.ext”; // 標(biāo)準(zhǔn)雙引, 用\做轉(zhuǎn)義字符

c = ‘c:\path\file.ext’; // 和上面一樣, 單引情況下, \不做轉(zhuǎn)義字符用

c = “Mary says \”hello\””; // 相當(dāng)于'Mary says "hello"'

c = 'Chris' 's bike'; // 相當(dāng)于'Chris's bike', 也就是說在單引內(nèi)部表示單引的方法是連續(xù)兩個單引

c = “My ” “house”; // 相當(dāng)于"My house"


基礎(chǔ)類型可以使用標(biāo)準(zhǔn)內(nèi)建庫進(jìn)行顯示的轉(zhuǎn)換, Int(), Float(), String()

例子:

a = 10;

b = a.String(); // 這樣是最好的, 顯示的調(diào)用類型轉(zhuǎn)化函數(shù), 返回轉(zhuǎn)化后的值

b = “” + a; // 這樣不好, 賦值會將a的類型提升到string, 但是效率底下

b = (10).String(); // 丑陋的

b = 10.String(); // 錯誤的, 編譯不過, 因為編譯器不認(rèn)同這種語法


引用類型變量的可引用類型有String, Function, Table, User. 當(dāng)這些變量被賦值時, 并不發(fā)生full copy, 而只是讓變量指向具體的obj

例子:

a = table(“apple”, "orange"); // a是一個指向table的引用

b = a; // b 現(xiàn)在和a指向同一個table

b[1] = "banana"; // 設(shè)置b[1]

print(a[0], a[1]); // >> banana orange

print(b[0], b[1]); // >> banana orange


當(dāng)一個變量被賦新值時, 該變量原來持有的值就有可能丟失掉了.

例子:

Oper = function(a, b){

return a + b

}; // Oper現(xiàn)在指向一個函數(shù)

Oper = “hello”; // Oper現(xiàn)在指向字符串, 原來的函數(shù)被丟失了


函數(shù)
語法: function(<params>) { <statements> };

一個函數(shù)體是一個值, 而函數(shù)是一個類型 {type = GM_FUNCTION, value=function...}

注意: 記住在將函數(shù)賦值給變量后面那個分號, 這是語法必須的

例子

// 將一個創(chuàng)建一個rect table的函數(shù)賦值給CreateRect

CreateRect = 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); // 創(chuàng)建一個用于描述rect的table

area = myRect.Area();

// 可以用:代替.來隱式的傳遞一個this指針

Size = function(){

return .width * .height;

};

s = myRect:Size(); // 調(diào)用時, myRect會當(dāng)做this指針傳入Size中


作用域
和作用域有關(guān)的一些關(guān)鍵字, 語法:

global <variable>

Local <variable>

member <variable>

this

this.<variable>

.<variable>

函數(shù)中的變量.



默認(rèn)情況下, 一個在函數(shù)中使用的變量就是這個函數(shù)的本地變量. 如果要聲明一個全局變量, 需要使用global關(guān)鍵字. 訪問成員變量必須通過this或者是使用member關(guān)鍵字聲明它是一個成員變量. 在局部使用的變量可以用local關(guān)鍵字聲明.

例子:

Access = function(){ // Access 是一個local變量, 它引用著一個函數(shù)

apple = 3; // apple 是函數(shù)的一個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是null

global b = function() { // b是一個全局作用域的變量, 類型是GM_FUNCTION

global c = 2; // c是一個全局作用域的變量

d = 3; // d是函數(shù)局部作用域變量

{ if (c == 2)

{ local e = 3; } // e 從這一刻開始成為函數(shù)局部作用域變量, 注意沒有塊作用域變量

}

print(e); // e = 3

}
在查找一個變量時, 按照local 和 parameters, 然后global的順序查找.



成員變量有微妙的不同:

h = function() { // h 是一個local變量

global a = 3; // a 是一個全局變量

member n; // n可以從this被訪問和創(chuàng)建

d = 3; // d是函數(shù)局部作用域

this.b = 3; // b是member作用域

.b = .x + 1; // b, x都是member作用域

print(b); // b 是 null, 因為這里并沒有l(wèi)ocal的b

print(n); // 就像print(this.n)一樣, 因為上面顯示聲明過了

};
全局作用域中的語句.

x = 7; // local

global x = 8; // global

a = function(y) {

local x = 5; // function local

dostring(“print(x);”); // 這里打出8, 和lua一樣, dostring總是在全局環(huán)境下編譯運行的, 無法訪問function的變量和parameters
};

變量可以是虛擬機全局作用域的, 也可以是某個函數(shù)作用域的, 或者是某個obj比如table的成員作用域的. 當(dāng)執(zhí)行一個文件或者是執(zhí)行一個字符串的時候, 和lua一樣, 文件或者是字符串被編譯成一個無名的函數(shù), 所以默認(rèn)情況下, 其中最外層的未加特別申明的變量是該無名函數(shù)的函數(shù)作用域的.

this總是存在的. 它或者是null, 或者是一個有效的值. 你可以傳遞this, 或者是使用重載冒號操作符默認(rèn)的this. 這一特性多用在創(chuàng)建諸如類似模板行為, 那些地方的obj的操作往往只有run-time時才能確認(rèn). this也用在創(chuàng)建線程, 例子:

obj:thread(obj.DoThings) // 開始一個線程, 并把obj作為this傳遞給它

obj:MakeFruit(apple, orange) // 調(diào)用MakeFruit, 并把obj當(dāng)做this傳給它



語法和操作符


! Not 邏輯取反

~ 每一個bit位取反

^ bit位使用與或 XOR

| bit位使用或 OR

& bit位使用與 AND

>> bit位右移

<< bit位左移

~= bit位取反賦值

^= bit位 XOR 賦值

|= bit位 OR 賦值

&= bit位 AND 賦值

>>= bit位 右移 賦值

<<= bit位 左移 賦值

= 賦值

' 單引 其中的字符會當(dāng)做int值

" 雙引 字符串(處理轉(zhuǎn)義字符)

` 反引 字符串(不處理轉(zhuǎn)義字符)

[] 方闊 用index取talbe元素

. 取table元素

: 給函數(shù)傳遞this

+ 數(shù)學(xué)+

- 數(shù)學(xué)-

* 數(shù)學(xué)*

/ 數(shù)學(xué)/

% 模取余

+=, –=, *=, /=, %= 數(shù)學(xué)運算并賦值

{} 界定語句塊

; 語句結(jié)束標(biāo)志

<, <=, >, >=, == 數(shù)學(xué)比較

&&或and 邏輯AND

|| 或or 邏輯OR



Tables 表
語法: table(<key> = <value>, ...);

table(<value>, …);

{<key>=<value>, …, };

{<value>, …, };

table可以被同時認(rèn)為是array 和 map. 因為table中可以容納data和function, 所以table也可以被認(rèn)為是class, table中也可以容納別的table, 這時它也已被認(rèn)為是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"并沒有占據(jù) element[2], 雖然它在邏輯上應(yīng)該是element[2]的位置, 但是它不是一個index索引成員, 是一個{key, value}成員.

從表中取得元素的例子.

a = thing.other; // other 是table thing中的一個成員

b = thing[“other”]; // 相當(dāng)于b = thing.other

c = thing[2]; // c取得了thing中的第三個indexd索引成員

index = 3;

d = thing[index]; // 用int做下標(biāo), 就可以把table當(dāng)數(shù)組訪問

accoc = “fav”;

e = thing[accoc]; // 用string做下標(biāo), 就可以把table當(dāng)map訪問
注意, thing["Fav"]和thing["fav"]是兩個不同的東西, 因為GM是大小寫敏感的. 這樣做設(shè)計上的考慮是:

1) 賦值可能是string, 也可能是任何類型的值.

2) 要做到大小寫無關(guān), 底層需要一些額外的工作量, 這會產(chǎn)生一定量的效率問題.

設(shè)置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"


關(guān)鍵字if和else
語法: if ( <condition> ) { <statements> }

或者 if ( <condition> ) { <statements> } else { <statements> }

或者 if ( <condition> ) { <statements> } else if ( <condition> ) { <statements> } else { <statements> }



例子:

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 5

if 會計算條件表達(dá)式的值, 并根據(jù)其結(jié)果的true/false來選擇執(zhí)行那一段語句.

if 在計算條件時, 會像大多數(shù)語言那樣, 并且實現(xiàn)了短路求值, 下面是一些例子:

if (3 * 4 + 2 > 13) == if ( ( (3*4) + 2) > 13 )

if (3 > 0 || 0 > 1) 3 > 0恒真, 那么永遠(yuǎn)不會去對0 > 1求值

對c程序員的提示: 你不能把condition和一個單語句無語句塊標(biāo)示的statements寫在同一行, 這是語法不允許的

例: if ( a > 3) b = 4; // 錯誤, b = 4 必須被{}包起來



關(guān)鍵字for
語法: for (<statement1>; <condition>; <statement2>) { <statements> }

例子:



for (index = 0; index < 6; index = index + 2){


print(“Index = ”, index);

}
輸出是:

Index = 0

Index = 2

Index = 4



for 語句的執(zhí)行和大多數(shù)語言一樣, 循序是

1. 執(zhí)行 statement1

2. 執(zhí)行condition, 是false就退出for

3. 執(zhí)行statements

4. 執(zhí)行statement2, goto 2



關(guān)鍵字foreach
語法: foreach (<key> and <value> in <table>) { <statements> }

foreach (<value> in <table>) { <statements> }

例子:

fruitnveg = table("apple", "orange", favorite = "pear", yucky="turnip", "pinapple");

foreach(keyVar and valVar in fruitnveg){

print(keyVar, “=", valVar);

}
輸出是:

2 = pinapple

0 = apple

favorite = pear

1 = orange

yucky = turnip

注意到遍歷tale的時候, 它并沒有按料想的順序來輸出. 事實上, 這種順序會在table中的元素填入和刪除時發(fā)生變化.

在foreach的每次迭代過程中, key和value將會作為循環(huán)體的local作用域變量. 在迭代過程中, 對table執(zhí)行刪除元素操作是安全的, 但是向table中新增元素和從table刪除元素是[原文是: Although the foreach iteration is ‘delete safe’, the behaviour of adding and removing items from a table while iterating is undefined. 我理解不了delete safe 和 removing items from a table] 請大家告訴我好的理解, 我好改正



關(guān)鍵字 while
語法: while( <condition> ) { <statements> }

例子:

index = 0;

while ( index < 3 ) {

print("index = ", index);

index = index + 1;

}
輸出:

index = 0

index = 1

index = 2

while結(jié)構(gòu)先檢查條件, 如果條件為真就執(zhí)行循環(huán)體并反復(fù)執(zhí)行這一過程直到第一次檢查到條件為假. 如果一開始條件就為假, 那么循環(huán)體中的代碼一次也不會執(zhí)行.



關(guān)鍵字 dowhile
語法: dowhile (<condition>) { <statements> }

例子:

index = 0;

dowhile (index > 0) {

print("index = ", index);

}
輸出:

index = 0

dowhile和while不同, 它先執(zhí)行循環(huán)體, 然后檢測條件已決定是否要再次執(zhí)行循環(huán)體, 循環(huán)體中的代碼至少執(zhí)行一次.



關(guān)鍵字 break, continue, return
break的例子:

for (index = 0; index < 4; index = index + 1) {

if (index == 2) {

break;

}

print(“index =”, index);

}
輸出:

index = 0

index = 1



continue的例子:

for (index = 0; index < 4; index = index + 1) {

if (index == 2) {

coutinue;

}

print(“index = ”, index);

}
輸出:

index = 0

index = 1

index = 3



return 的例子:

Add = function(a, b) {

return a + b;

}

print(“Add res = ”, Add(4, 5));
輸出:

Add res = 9



Early = function(a) {

if (a < = 0) {

print(“Dont want zero here.”);

return ;

}

print(“Above zero we handle.”);

}

Early(-2);
輸出:

Dont want zero here.

break和continue用來退出或者是忽略 for, while, dowhile, foreach 循環(huán). break會使執(zhí)行流跳出循環(huán)語句塊, continue導(dǎo)致終止本輪迭代, 并立即進(jìn)行下一輪迭代, return不光是能跳出循環(huán), 它是直接跳出當(dāng)前函數(shù).



關(guān)鍵字true, false, null
在GM中, true和false分別表示0和1. 除此之外沒有別的意義. 注意, 和其他語言不太一樣的地方

例子:

a = 3;

if (a == true) {

print(a, “==", true);

}else{

print(a, “!=", true);

}
輸出:

3 != 1

null是一種類型, 而不是一個值, 它通常用來表示錯誤. 當(dāng)它和其他類型混用時, 它的類型會被提升, 值為0. 當(dāng)一個變量被聲明但沒有賦值時, 這個變量就是一個null. 對于table中的元素, 如果被賦值為null, 就表示這個元素被從table中刪掉了.

例子:

local var;

if (var) { // var聲明了但沒賦值, 所以是null, 這里類型提升了, 值為0 == false

print(“var was initialised or non zero : ”, var);

} else {

print(“var is zero or null : ”, var);

}
輸出:

var is zero or null : null



--------------------------------------------------- 高潮分割線 ---------------------------------------------------------

上面那些我覺得正常人1~2個小時應(yīng)該能掌握了, 我翻譯的昏昏欲睡了, 下面是一些GM內(nèi)建的機制, 能夠體現(xiàn)出一些特色, 這個正是我想要的:), 其實可以把下面的這些東西看做是GM的庫, 也可以看成是GM內(nèi)置的功能.



Thread
rotc: 這里有幾點要說的

1. GM里的thread不是通常的線程, 其實就是lua里的協(xié)程.

2. GameMonkey開發(fā)的初衷有彌補當(dāng)時的LuaV4沒有協(xié)程的遺憾, 現(xiàn)在luaV5已經(jīng)有了.

3. 從接口來看, GM中的協(xié)程接口更加豐富易用.



1. int thread(function a_function, …)

創(chuàng)建一個線程.

a_function 是線程的執(zhí)行體

... 是傳給a_function的參數(shù)

該函數(shù)返回一個thread id, 控制和查詢線程都必須通過這個id來進(jìn)行.



2. void yield()

調(diào)用該函數(shù)導(dǎo)致當(dāng)前執(zhí)行體讓出對GM虛擬機的控制權(quán).



3. void exit()

調(diào)用本函數(shù)導(dǎo)致當(dāng)前執(zhí)行體立即退出.



4. void threadKill(int a_threadId)

kill掉指定的線程, 被kill掉的線程不能再次運行.

a_threadId是要kill的線程的id, 由thread()函數(shù)返回.

如果調(diào)用threadKill()將導(dǎo)致當(dāng)前線程被kill掉.



5. void threadKillAll(bool a_killCurrentThread = false)

kill掉所有的線程, 參數(shù)為false的話kill掉出自己外的所有線程, 否者連自己也kill掉.



6. void sleep(float a_time)

停止當(dāng)前執(zhí)行體指定的秒數(shù).



7. int threadTime()

返回當(dāng)前線程執(zhí)行的總時間, 單位是毫秒.



8. int threadId()

返回當(dāng)前線程的id



9. table threadAllIds()

用一個table返回所有的線程id.



10. void signal(var a_event)

引發(fā)一個事件. a_event是任意類型的, 表示一個事件.



11. void block(var a_event, ...)

讓當(dāng)前線程阻塞在一個或多個事件上. 只到事件發(fā)生, 該線程才能被轉(zhuǎn)化為可執(zhí)行的.



States
在游戲編程中, 常使用狀態(tài)的概念來描述一個游戲?qū)嶓w的行為, 就是常常說到的有限狀態(tài)機. 在GM中, states允許一個線程結(jié)束后馬上丟棄這個線程的棧并跳到另一個執(zhí)行點開始執(zhí)行.



1. void stateSet(function a_function, …)

設(shè)置當(dāng)前執(zhí)行線程的新的狀態(tài)函數(shù).

a_function 表示要執(zhí)行的狀態(tài)函數(shù).

... 表示要傳給狀態(tài)函數(shù)的參數(shù).



2. function stateGet()

獲取當(dāng)前執(zhí)行的狀態(tài)函數(shù). 如果之前沒有調(diào)用過stateSet, 那么將返回null.



3. function stateGetLast()

獲取當(dāng)前狀態(tài)的前一個狀態(tài), 可以用來得知變遷信息.



4. void stateSetExitFunction(function a_function)

設(shè)置一個函數(shù), 該函數(shù)將在狀態(tài)變遷時調(diào)用. 可以用來在進(jìn)入下一個狀態(tài)前本次狀態(tài)函數(shù)本身的一些清理工作, 如果沒有下一個狀態(tài), 那么這個函數(shù)不會被執(zhí)行.



rotc: 其實這個state的實現(xiàn)要求虛擬機實現(xiàn)了尾遞歸, 否者在狀態(tài)過多的時候會導(dǎo)致滿棧, 實現(xiàn)了尾遞歸和協(xié)程的語言都可以做出states類似的功能, 但是GM中顯示的給出的支持, 也是很方便的.



System
1. void debug()

使調(diào)試器在這里產(chǎn)生一個斷點.



2. void assert(int a_condition)

檢查a_condition, 它必須是非0值, 否者產(chǎn)生一個異常然后退出線程.



3. int sysTime()

返回虛擬機執(zhí)行的實現(xiàn), 單位是毫秒.



4. int doString(string a_script, int a_executeNow = true)

編譯并執(zhí)行a_script中的腳本

a_executeNow == true的話, script將馬上被執(zhí)行, 然后doString函數(shù)才返回, 否者返回新建的thread id.

實質(zhì)的步驟是:

1. 把 a_script 編譯成一個函數(shù)func

2. 調(diào)用 id = thread(func)

3. if a_executeNow == true

yield()

else

return id



5. int typeId(variable a_var)

返回a_var的類型值



6. string typeName(variable a_var)

返回a_var的類型名字



7. int typeRegisterOperator(int a_typeid, string a_opName, function a_func)

給指定的類型注冊一個操作

a_typeid 目標(biāo)類型

a_opName 操作名

a_func 現(xiàn)實的操作函數(shù)

返回1成功, 0失敗.

a_opName的取值: getdot, setdot, getind, setind, add, sub, mul, dov, mod, inc, dec, bitor, botxor, shiftleft, shiftright, bitinv, lt, gt, lte, gte, eq, neq, neg, pos, not



8. int typeRegisterVariable(int a_typeid, string a_varName, variable a_var)

給指定的類型注冊一個變量, 使用(type).varname的方式就可以獲得這個變量

a_typeid 目標(biāo)類型

a_varName 要加的變量名

a_var 要加的變量

返回1成功, 0失敗.



9. int sysCollectGarbage(int a_forceFullCollect = false)

如果當(dāng)前內(nèi)存使用量超過了指定的內(nèi)存使用量, 那么執(zhí)行垃圾回收.

a_forceFullCollect 如果垃圾回收可用的話, a_forceFullCollect=true將馬上開始執(zhí)行

返回1表示垃圾回收執(zhí)行了, 其他情況返回0.



10. int sysGetMemoryUsage()

返回當(dāng)前的內(nèi)存使用量, 單位byte.



11. void sysSetDesiredMemoryUsageHard(int a_desired)

設(shè)置內(nèi)存使用量硬限制. 在垃圾回收時會根據(jù)這個值來決定要不要執(zhí)行一次完整的回收.

a_desired 內(nèi)存使用硬限制, 單位是byte.



12. void sysSetDesiredMemoryUsageSoft(int a_desired)

設(shè)置內(nèi)存使用量軟限制. 在垃圾回收時會根據(jù)這個值來決定是否開始增量回收. soft值必須小于上面的hard值, 謝謝

a_desired 內(nèi)存使用軟限制, 單位是byte.



13. void sysSetDesiredMemoryUsageAuto(int a_enable)

開啟或者關(guān)閉在接下來的垃圾收集中是否能自動調(diào)整內(nèi)存限制.

a_enable 1 開啟 0 關(guān)閉



14. int sysGetDesiredMemoryUsageHard()

獲取內(nèi)存使用量硬限制, 單位是byte. 注意, 這個值是用在開始一次完整的垃圾回收前的檢測.



15. int sysGetDesiredMemoryUsageSoft()

獲取內(nèi)存使用量軟限制, 單位是byte. 注意, 這個值是用在開始增量垃圾回收前的檢測.



16. int sysGetStatsGCNumFullCollects()

返回虛擬機執(zhí)行完整垃圾回收的次數(shù).



17. int sysGetStatsGCNumIncCollects()

返回虛擬機執(zhí)行增量垃圾回收的次數(shù). 注意在restart的那一次回收中, 這個值會+2.



18. int sysGetStatsGCNumIncWarnings()

返回GC 或者VM因為配置的問題[soft和hard限制]而導(dǎo)致的警告的數(shù)量. 如果這個數(shù)龐大而且急速增加, 那么gc的軟硬內(nèi)存限制應(yīng)該重新配置以獲得更好的性能表現(xiàn). 這些警告的出現(xiàn)一般意味著gc次數(shù)過于平凡或不足. 如果這個值很小, 或者是增長很慢, 那么不用去擔(dān)心它. 可以閱讀介紹GM的gc的文檔[翻譯完這個, 我就翻譯GM gc的文檔]來獲取關(guān)于gc話題的很多信息. 我們將在以后的版本中改進(jìn)這個函數(shù), 以便讓它的意義很明確易懂.



表操作
1. int tableCount(table a_table)

計算table中元素的個數(shù).



2. table tableDuplicate(table a_table)

返回傳入table的一個副本.

我測過了, copy的深度就是a_table的元素這一層, 比如

t1={a=9}; t2={uu=t1, b=45};

t3 = tableDuplicate(t2);

t3.b = 78;

t3.uu.a = 80;

print(“t2.b = ”, t2.b); // t2.b = 45

print(“t3.b = ”, t3.b); // t3.b = 78

print(“t2.uu.a = ”, t2.uu.a); // t2.uu.a = 80

print(“t3.uu.a = ”, t3.uu.a); // t3.uu.a = 80
啥內(nèi)涵大家一看就明白了





------------------------------------------華麗的風(fēng)格線-------------------------------------------



綁定C函數(shù)到GM腳本中
C函數(shù)可以綁定到類型上, 也可以綁定到一個全局變量.

一個可以綁定到GM中的C函數(shù)的原型是:

int GM_CDECL gmVersion(gmThread* a_thread)

a_thread->GetNumParams() 可以獲得參數(shù)的個數(shù)

a_thread->Param*() 獲取各個參數(shù)

a_thread->GetThis() 訪問this

a_thread->Push*() 向腳本中返回值

還有一些有用的宏和簡寫的功能函數(shù).



C函數(shù)的返回值描述如下:

GM_OK 函數(shù)執(zhí)行成功

GM_EXCEPTION 函數(shù)執(zhí)行失敗, 在函數(shù)運行的thread中產(chǎn)生一個運行時異常

當(dāng)然函數(shù)也可能返回一些控制thread行為的值, 比如GM_SYS_SLEEP, GM_SYS_YIELD, GM_SYS_KILL, 這些值可以讓腳本的高級用戶實現(xiàn)和修改虛擬機的行為. 用戶擁有強大的控制權(quán), 可以更高效的實現(xiàn)參數(shù)變量, 重載函數(shù)(通過支持不同類型的參數(shù)), 檢查錯誤或無效的輸入.



一個GM操作符綁定函數(shù)的原型是:

void GM_CDECL dunc(gmThread* a_thread, gmVariable* a_operands)

a_operands[0] 是左參數(shù)

a_operands[1] 是右參數(shù)

a_operands[0] 同時也是返回值

如果操作符函數(shù)不能執(zhí)行該操作(比如錯誤的參數(shù)類型等), 就把a_operands[0]置為null

對于二元操作符來說, 比如O_MUL, 調(diào)用操作符函數(shù)時將選擇兩個參數(shù)類型較高的參數(shù)的綁定函數(shù). NOT是一個一元操作符(這時將使用a_operands[0].m_type的綁定函數(shù)). 這一點和c++是不一樣的, 在c++中, 如果你創(chuàng)建了一個類 Vec3, 那么Vec3 * float 的運算就需要重載一個*操作符, 而float * Vec3需要重載一個全局的友元函數(shù). GM這樣做是為了降低原生類型的處理代價和易于用戶定義類型的擴展. 所以原生的int 和 float 類型不需要在意那些比他們高級的類型, 但是用戶自定義類型例如Vec3可以很有彈性的和低級類型一起工作, 它的綁定函數(shù)將被調(diào)用.

可能發(fā)生沖突的地方就是當(dāng)用戶自定義類型之間發(fā)生運算時, 如果用戶知道注冊的順序的話, 他們可以依據(jù)這個來編碼, 否者可能要實現(xiàn)同樣的操作符函數(shù)來保證不會發(fā)生因為注冊順序而導(dǎo)致的問題. 兩個用戶類型可以給一個操作符綁定同樣的操作符函數(shù), 這樣可以避免不必要的重復(fù).

rotc: 上面這兩段話我翻譯的不好, 先放著, 等對這部分知識有了更深的理解再來修改





例子1, 實現(xiàn)一個可以注冊到GM中的C函數(shù), 比較簡單, 不寫注釋了

// int GetHealth(GObj* a_obj, int a_limit)

int _cdecl GetHealth(gmThread* a_thread) {

GM_CHECK_NUM_PARAMS(2);

GM_CHECK_USER_PARAM(GObj::s_scrUserType, userObj, 0);

GM_CHECK_INT_PARAM(limit, 1);

Gobj* gob = (Gobj* )userObj->m_user;

if (gob->m_health > a_limit) {

gob->m_health = a_limit;

}

a_thread->PushInt(gob->m_health);

return GM_OK;

}




例子, 向GM中導(dǎo)入一個函數(shù), 使得在GM中可以使用sqrt(56)或者sqrt(67.8), 過程比較簡單就不寫注釋了

int __cdecl gmfSqrt(gmThread* a_thread) {

GM_CHECK_NUM_PARAMS(1);

if (a_thread->ParamType(0) == GM_INT) {

int intVal = a_thread->Param(0).m_value.m_int;

a_thread->PushInt((int)sqrt(intVal));

return GM_OK;

} else if (a_thread->ParamType(0) == GM_FLOAT) {

float floatVal = a_thread->Param(0).m_value.m_float;

a_thread->PushFloat(sqrtf(floatVal));

return GM_OK;

}

return GM_EXCEPTION;

}

static gmFunctionEntry s_mathLib[] = {

{"sqrt", gmfSqrt}, };

machine->RegisterLibrary(s_mathLib, sizeof(s_mathLib) / sizeof(s_mathLib[0]));




例子, 為String類型加上一個Compare操作的演示, 使得可以在GM中使用 "hihi".Compare("hihi") , 因為比較重要, 給出完整代碼.

#include "gmThread.h"

int GM_CDECL gmfStringCompare(gmThread* a_thread)
{
GM_CHECK_NUM_PARAMS(1);

// Compare的參數(shù)必須是string, 因為這個函數(shù)預(yù)期將進(jìn)行字符串的比較

if (a_thread->ParamType(0) == GM_STRING)
{

// 獲取調(diào)用Compare的變量
const gmVariable* var = a_thread->GetThis();

// 這個變量一定也是一個string
GM_ASSERT(var->m_type == GM_STRING);

// gm str ----> c str
gmStringObject* obj = (gmStringObject* )GM_OBJECT(var->m_value.m_ref);
const char* thisStr = (const char* )*obj;
const char* otherStr = a_thread->ParamString(0);

// 具體的操作
a_thread->PushInt(strcmp(thisStr, otherStr) ? 0 : 1);
return GM_OK;
}
return GM_EXCEPTION;
}

static gmFunctionEntry s_stringlib[] = {
{"Compare", gmfStringCompare},
};

int main(int argc, char* argv[])
{

// 先創(chuàng)建虛擬機
gmMachine machine;

// 注冊到虛擬機
machine.RegisterTypeLibrary(GM_STRING, s_stringlib, 1);

// 好了可以用了:)
machine.ExecuteString("print("res = ", \"hihi\".Compare(\"hihi\"));");
getchar(); // Keypress before exit
return 0;
}
程序執(zhí)行結(jié)果是輸出 res = 1



從C中調(diào)用GM腳本
從C中調(diào)用GM腳本時使用gmCall輔助類會讓整個事情變得很簡單, 下面就是一個例子:

#include “gmCall.h” // 要使用gmCall就必須包含這個頭文件

gmMachine machine; // 初始化一個GM虛擬機

// 我們要調(diào)用的函數(shù)是: global Add = function(a, b) { return a + b; };

gmCall call;

int resultInt = 0;

if (call.BeginGlobalFunction(&machine, “Add”)) {

call.AddParamInt(3);

call.AddParamInt(5);

call.End();

call.GetReturnedInt(resultInt); // 取結(jié)果

}
警告: 如果你從函數(shù)中返回一個string, 那么你就馬上使用它, 或者是把它copy出來, 不要長期的持有這個指針. 因為這個字符串不會一直有效, 說不定在下一輪的垃圾收集中就把它回收了, 這樣的話, 你再次使用它的指針時就很危險了.



游戲?qū)ο髷U展
我怎樣才能擴展GM, 向它中添加一個我自己定義的類型, 就像table那樣子.

怎樣在GM中表達(dá)一個game obj?

下面的代碼就是完整的將GameObject類型導(dǎo)入到GM中, 包含創(chuàng)建, 訪問, 內(nèi)存管理的各個方面

struct GameObject {

gmTableObject* m_table; // 容納表功能

gmUserObject* m_userObject;

static gmType s_typeId; // 存儲自己定義的類型

};



gmType GameObject::s_typeId = GM_NULL;

#if GM_USE_INCGC

static bool GM_CDECL GCTrace(gmMachine* a_machine, gmUserObject* a_object, gmGarbagCollector* a_gc, const int a_workLeftToDo, int& a_workDone) {

GM_ASSERT(a_object->m_userType == GameObject::s_typeId);

GameObject* object = (GameObject* ) a_object->m_user;

if (object->m_table)

a_gc->GetNextObject(object->m_table);

a_workDone += 2;

return true;

}

static void GM_CDECL GCDestruct(gmMachine* a_machine, gmUserObject* a_object) {

GM_ASSERT(a_object->m_userType == GameObject::s_typeId);

GameObject* object = (GameObject* )a_object->m_user;

object->m_table = NULL;

}

#else

// 垃圾回收的標(biāo)記函數(shù)

void GM_CDECL GameObjectMark(gmMachine* a_machine, gmUserObject* a_object, gmuint32 a_mark) {

GM_ASSERT(a_object->m_userType == GameObject::s_typeId);

GameObject* obecjt = (GameObject* )a_object->m_user;

object->m_table->Mark(a_machine, a_mark);

}

// 垃圾回收的回收函數(shù)

void GM_CDECL GameObjectGC(gmMachine* a_machine, gmUserObject* a_object, gmuint32 a_mark) {

GM_ASSERT(a_object->m_userType == GameObject::s_typeId);

GameObject* object = (GameObject* )a_object->m_user;

object->m_table.Destruct(a_machine);

delete object;

}

#endif



// 設(shè)置一個用來描述類型的字符串以便在調(diào)用"AdString"得到它

static void GM_CDECL AsString(gmUserObject* a_object, char* a_buffer, int a_bufferLen) {

GM_ASSERT(a_ojbect->m_userType == GameObject::s_typeId);

GameObject* object = (GameObject* ) a_object->m_user;

char mixBuffer[128];

sprintf(mixBuffer, “GameObject Cptr = %x”, object);

int mixLength = strlen(mixBuffer);

int useLength = GM_MIN(mixLength, a_bufferLen - 1);

GM_ASSERT(useLenght > 0);

strncpy(a_buffer, mixBuffer, useLength);

a_buffer[useLengrh] = 0;

}

// get dot操作符用來訪問table

void GM_CDECL GameObjectGetDot(gmThread* a_thread, gmVariable* a_operands) {

GM_ASSERT(a_operands[0].m_type == GameObject::s_typeId);

gmUserObject* user = (gmUserObject* )GM_OBJECT(a_operands[0].m_value.m_ref);

GameObject* object = (GmaeObject* )user->m_user;

a_operands[0] = object->m_table->Get(a_operands[1]);

}

// set dot操作符用來訪問table

void GM_CDECL GameObjectSetDot(gmThread* a_thread, gmVariable* a_operands) {

GM_ASSERT(a_operands[0].m_type == GameObject::s_typeId);

gmUserObject* user = (gmUserObject* )GM_OBJECT(a_operands[0].m_value.m_ref);

GameObject* object = (GameObject* )user->m_user;

object->m_table->Set(a_thread->GetMachine(), a_operands[2], a_operands[1]);

}

// 從腳本中創(chuàng)建GameObject

// 注意: 游戲中像這樣直接創(chuàng)建對象實體并不常見, 還有, 可能并不像保存對象的c指針, 取而代之的是保持一個32bit的UID來代表對象, 在使用的時候通過UID來查詢和驗證對象

int GM_CDECL CreateGameObject(gmThread* a_thread) {

GameObject* object = new GameObject();

object->m_table = a_thread->GetMachine()->AllocTableObject();

object->m_userObject = a_thread->CreateUser(object, GameObject::s_typeId);

return GM_OK;

}

// 獲取一個object, 這種情況下的obj在c和腳本中是一對一的.

int GM_CDECL GetGameObject(gmThread* a_thread) {

GameObject* foundObj = NULL;

GM_CHECK_NUM_PARAMS(1);

if (a_thread->ParamType(0) == GM_STRING) {

// todo: foundObj = FindByName(a_thread->ParamString(0));

// 如果找到的話

a_thread->PushUser(foundObj->m_userObject);

a_thread->PushNull();

return GM_OK;

}

else if (a_thread->ParamType(0) == GM_INT) {

// todo: foundObj = FindById(a_thread->ParamInt(0));

// 如果找到的話

a_thread->PushUser(foundObj->m_userObject);

a_thread->PushNull();

return GM_OK;

}

return GM_EXCEPTION;

}

// 注冊函數(shù)

gmFunctionEntry regFuncList[] = {

{"GameObject", CreateGameObject},

}

// 向虛擬機注冊類型, 假定虛擬機已經(jīng)構(gòu)造好了

// 1. 注冊新類型

GameObject::s_typeId = machine.CreateUserType(“GameObject”);

// 2. 注冊垃圾回收要用到的

#if GM_USE_INCGC

a_machine->RegisterUserCallbacks(GameObject::s_typeId, GameObjectTrace, GameObjectDestruct, AsString);

#else

a_machine->RegisterUserCallbacks(GameObject::s_typeId, GameObjectMark, GameObjectGC, AsString);

#endif

// 為類型綁定get dot操作

machine.RegisterTypeOperator(GameObject::s_typeId, O_GETDOT, NULL, GameObjectGetDot);

// 為類型綁定set dot操作

machine.RegisterTypeOperator(GameObject::s_typeId, O_SETDOT, NULL, GameObjectSetDot);

// 注冊函數(shù)

machine.RegisterLibrary(regFuncList, sizeof(regFuncList) / sizeof(regFuncList[0]));


虛擬機的回調(diào)
如果一個應(yīng)用程序擁有它自己的gmObject, 它必須讓垃圾回收器知道這個對象正在被使用. 要做到這一點, 你需要在虛擬機回調(diào)中捕獲MC_COLLECT_GARBAGE 消息, 這個回調(diào)發(fā)生在垃圾回收器開始掃描根時. 一個讓gc正確管理c++持有的gmObject的代換方案是使用gmMachine::AddCPPOwnedGMObject() 和 gmMachine::RemoveCPPOwnedGMObject(). 第三種方法是使用gmGCRoot<>指針來實現(xiàn).你可以通過查閱GM gc的文檔來獲取更多這方面的知識.

應(yīng)用程序可能希望在thread創(chuàng)建和銷毀時執(zhí)行一些動作, 比如管理這個thread專有的object等. 此外, 應(yīng)用程序可能希望將thread的異常信息導(dǎo)入到error stream中. 下面是一些列子.

// 假定你在別處已經(jīng)建立了虛擬機, 現(xiàn)在你要注冊callback函數(shù)

gmMachine::s_machineCallback = ScriptCallback_Machine;

//

bool GM_CDECL ScriptCallback_Machine(gmMachine* a_machine, gmMachineCommand a_command, const void*a_context) {

switch (a_command) {

case MC_THREAD_EXCEPTION: {

// dump 異常信息導(dǎo)到標(biāo)準(zhǔn)輸出

bool first = true;

const char* entry;

while ( (entry = a_machine->GetLog().GetEntry(first)) ) {

fprintf(stderr, “%s“, entry);

}

a_machine->GetLog().Reset();

}

break;

case MC_COLLECT_GARBAGE": {

#if GM_USE_INCGC

gmGarbageCollector* gc = a_machine->GetGC();

// 對于所有的c 擁有的obj

// gc->GetNextObject(obj);

#else

gmuint32 mark = *(gmuint32 *)a_context;

// 對于所有的c 擁有的obj

// if (object->NeedsMark(mark)) {

// object->GetTableObject()->Mark(a_machine, mark);

// }

#endif

}

break;

case MC_THREAD_CREATE: {

// 線程創(chuàng)建時的回調(diào)

}

break;

case MC_THREAD_DESTROY: {

// 線程銷毀時的回調(diào)

}

break;

}

return false;

}

翻譯后記
前前后后翻譯了一周多, 終于算是告一段落了, 通過翻譯, 增加了對GameMonkey的一些理解.

因為它是從lua發(fā)展起來的, 有很多概念有一些像, 但是經(jīng)過仔細(xì)的觀察和研究代碼, 發(fā)現(xiàn)GM除了借鑒了一些lua的概念, 從實現(xiàn)上和lua是完全不一樣的. 比如元方法的實現(xiàn)等等. GM使用起來將會感覺更加復(fù)雜, 有很多問題都需要去coding解決, 而不像lua那樣美麗:) 但是從另外一方面來講, GM的確是給了程序足夠的控制力, 的確稱的上是一門面向程序員的語言.

翻譯的比較匆忙, 有什么錯盡管指出:) 謝謝

    相關(guān)評論

    閱讀本文后您有什么感想? 已有人給出評價!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)