2007年11月12日 星期一

啥SL—【LSL程式基本架構】

  對一物件點選PieMenu的Edit,並在編輯視窗中點開more按鈕,在名為Content的標籤內點選New Script新增程式。對該程式點兩下就會出現以下視窗和LSL最基本的程式碼(有時候會挺緩慢的,請耐心等候)。

  這篇文章因為有考慮到完全沒寫過程式的初學者,所以寫得有點雜亂(其實可能沒有這種人來這裡XD~),建議已經掌握一些要點的朋友可以大約略看看有哪些型態,並注意每一段的注意事項和紅色字體即可。


  如圖,這就是LSL全部的基本架構了。切記,基本State區為default區塊,該區塊前後順序如圖,不能隨意更動位置,也就是說你自訂的State一定要在default後面,全域變數與全域函式一定要在default前面;而全域變數與全域函式的順序就沒有那麼嚴格了。

  看!!LSL沒有繼承、沒有abstract、沒有overloading當然也沒有interface!!!而且他每個Script還有16KB的限制!!(16KB的限制請參考這裡:http://lslwiki.net/lslwiki/wakka.php?wakka=Memory)

  很好,自high完後開始正式介紹一下LSL基本架購,由於自己程式語言學藝不精,要是自己有哪方面用詞說錯,敬請指正。

  在說明前有幾點列表,算是我之後沒有詳細談到的部分,希望對那些完全沒學過程式的初學者有所幫助:
  • 只要輸入"//"這兩個符號後,其後面的字皆為註解,不列入為執行的程式碼,但注意,LSL沒有像java一樣方便的駐解方式,就是/*.....*/,它只有"//"
  • LSL是是case-sensitive的,也就是大小寫區分的意思
  • 每個區塊均以"{"、"}"這兩個符號代表區格
  • 每個全域函式與event在其名稱後面都會有個"("、")"這兩個括符
  • 宣告變數、state轉移、return 型態、jump、使用@標計jump以及使用function或自訂的全域函式都得在最後加上";"這個符號
  • compiler指的就是把LSL編譯成電腦看得懂的語言,也就是當你對你的script按save時下方會出現compile....等字樣。詳細相關資料可看維基百科(http://zh.wikipedia.org/w/index.php?title=Compiler&variant=zh-tw)
  • save的快捷鍵為ctrl+S
  • 存檔會需要一點時間,請耐心等候,要是你在comipler期間關掉檔案,檔案是會回復原來狀態的
  • ctrl+z為回上一步,ctrl+y為回下一步。
  • 編輯器上方多少還是有可用的工具,在Edit中有Search/Replace這個工具方便使用者找尋或替代自己要的英文字母
  • 在LSL編輯器內無法輸入中文(新的Second Life軟體已經可以輸入)
  • LSL的程式碼是有可能會消失在SecondLife的Database的,請記得作好備份

變數型態

  先來介紹一下LSL的變數型態,他不像一般的程式語言所有型態都有,且宣告的名稱也有一點不太一樣(例如一般的程式語言宣告整數是"int a;",它是"integer a;")。LSL的變數型態只有:
  1. string(字串)(eg. string str="test";)
  2. integer(整數)(eg. integer int=2;)
  3. float(浮點數,意即含小數之數字)(eg. float fl=2.5;)
  4. vector(向量,x、y、z三方向為浮點數的向量)(eg. vector location=<1.25,4.22,3.2>;)
  5. rotation(角度,除了x、y、z外,還多了個s的四個浮點數。這四個浮點數字代表意義可參考這裡:http://www.lslwiki.net/lslwiki/wakka.php?wakka=quaternions,至於為何Liden不用直觀的x、y、z調角度我就不清楚了,希望知道的人可以告知一下,謝謝。)(eg. rotation rot=<0.1,02.,0.3,1.0>;)
  6. key(在SecondLife內,每個物件與人都有一個key,這是用來區別一個物件或人的代號,因此是一個有固定長度的特別字串)(eg. key object="595c90dc-feff-5a80-9401-0cc111e8d8bb";)
  7. list(有點像一般程式都會有的陣列,但不必宣告此陣列是屬於何型態。你只要想成是放很多不同型態的變數放在裡面的列表就可以了,不過list內不可以放list,雖然compiler會過,但程式碼正式執行會有run-time error)(eg. list test=["test",2,5.33,<1.0,2.0,4.2>,<0,0,0,1>];)
  至於宣告方法和很多一般程式語言一樣,便是:
  變數型態 變數名稱;
  例如:
  integer test;

  代表test這個變數是integer(整數)
  若要給他一個值,就加個"=",
  例如:
  integer test=2;

  意思便是test這個變數是2的意思。

  給值的部分當然可以之後的全域函式或Event內再給,但是絕對不可以在全域變數區寫:
  integer test;
  test=2;
  以上寫法只可以在全域函式或Event內寫。

  名詞解釋:
  全域變數:只要把變數宣告在default前就是全域變數。
  區域變數:只要把變數宣告在event或全域函式(在LSL的函式只有全域函式)內就是區域變數。變數名稱可以和其他區域的區域變數重複。

  注意事項:
  • 全域變數的宣告名稱不可重複,也不可以和state或函式名稱重複
  • 變數宣告不可宣告在event與state之間,不然不能compiler
  • vector和rotation內的浮點數其實可以輸入整數,只是這樣會比較慢。來源:這裡的第三段文字(LSL does do some implicit typecasting but the LSL compiler does not actualy convert types or simplify code; it just makes implicit typecasts explicit. So while <0,> is valid, it will use more bytecode and be slower then <0.0,>. For this reason please be sure to use a decimal point. 0. is enough you don't need an extra zero.
    ) 
強制轉型

  通常一個function(我習慣稱method)會要求你傳入的是字串或浮點數或整數,如果要傳入的是浮點數你直接傳整數,是行得通的,它會自動幫你轉成浮點數。但如果是要求傳入整數,你傳入浮點數,或字串是不行的,此時要在前面加個(integer)例如:
  float fl=5.2;
  string str="5";
  integer int1=(integer)fl;
  integer int2=(integer)str;
  此時int1和int2都為5。
  同理,像是llSay這個function有需要string,你只要這樣寫llSay(0,(string)5),他就會把5變成"5"了。以上範例只能適用在宣告區域變數,宣告全域變數不可這樣用,不然compiler不會過(很有可能是LSL的bug),不過基本上轉型在合理區塊都是可以用的。

布林值

  LSL沒有boolean這個變態,喔,是變數型態,但是他可以藉由integer來得知這是true還是faluse。例如:
  integer T=TRUE;
  integer F=FALSE;

  TRUE和FALSE都要大寫,代表這是LSL的Constant,也就是說你不可以宣告一個變數名稱叫TRUE。而TRUE代表1,FALSE代表0。


  Operator也就是程式語言的符號,由於資料龐大,請直接進入Operator上面連結觀看(沒錯!俺懶了!!!)。


  LSL的流程控制有以下幾個,這些都可在event或全域函式內撰寫
  1. if-else(如果if()內的條件為TRUE就執行,之後跳到下一個else if()內判斷,如再為FALSE就執行else內的程式碼。注意,這裡的else if與else可以不寫)
    (eg.
    if(TRUE)
    {
      ......
    }
    else if(test==2)
    {
      ......
    }
    else
    {
      ......
    }
    )
    流程圖可看此篇文章的下面圖片(http://squall.cs.ntou.edu.tw/cprog/Materials/IfStatement.html),基本上所有程式語言都是大同小異的。
  2. while(和do-while、for都是迴圈。只要符合while()內的條件,就會不斷重複執行{}內的程式碼,直到條件不符合為止)
    (eg.
    while(TRUE)
    {
      .........
    }
    )
    以上為無限迴圈,永遠跳不出來,除非你使用了jump。
  3. do-while(先執行do內的程式碼再判斷while內的條件,如果成立後便一直執行。)
    (eg.
    do
    {
      ......

    }while(進迴圈條件)
    )
  4. for(()內的格式如下:
    for(初始化;條件;改變)
    初始化和改變可以不寫,只有條件的話跟while是沒啥兩樣的。)
    (eg.
    integer index;
    for(index=0;index<5;index++)
    {
      ......
    }
    在index等於5前都不會跳出,也就是說這程式碼會執行五次,index++意即index=index+1一直遞增下去。)

  5. jump(嗯……就是……跳!在流程內的程式碼跳到指定的名稱那一行,從那一行之後開始執行,而這標計用@自訂名稱表示。jump 自訂名稱,@自訂名稱。)
    (eg.
    jump 自訂名稱;

    ................程式敘述
    @自訂名稱;)
  6. return(回傳所需型態的結果,有何用處會在之後介紹如何使用全域函式解釋)
    (eg. return
    所需變數型態的結果;)
  7. state(轉移state)
    (eg.state 你想要轉移的state名稱; )
  注意事項:
  • 在全域函式要寫轉移State有一個很大的bug,就是你直接在函式內寫state 你想要轉移的state名稱;,compiler是會error的,這時要脫褲子放屁一下,你要寫:
    if(TRUE)

    state 你想要轉移的state名稱;
  基本上,一個script一定要有一個default state,而一個default state一定要有一個event(沒有的話compiler過不了)。也就是說,當一個程式碼在跑的時候,他就是從default state開始跑起。

  default state如下:
  default
  {
   event名稱()
   {

   }
  }

  而自訂State方式如下:
  state 你想要的名稱
  {
   event名稱()
   {

   }
  }


  event的列表全部都有列在上面Event的連結,在這邊只會提到幾個較常使用的:

  state_entry:這是所有state進入後都會先執行的event。

  touch_start:只要玩家點選PieMenu的Touch,就會啟動目前程式跑到的state裡面的這個event。

  timer:只要啟動了llSetTimerEvent(浮點數)這個function,這個event就會啟動。那個浮點數代表每幾秒啟動一次timer內的程式碼。

  注意:
  • 同樣的Event可以被允許重複寫在同一個state上,雖然這個一點用都沒有,但是LSL是不會當例外處理的,當event被啟動時,他會啟動寫在最後面的event。因為本人曾經發生過不小心把程式碼複製貼上兩次,在debug時覺得自己沒寫錯怎會出錯,原來出錯是出在這種地方。
  • 如果該Event被啟動,但程式碼還停留在迴圈內,那這個程式碼會先處理迴圈,再來處理Event的啟動。如:
    state_entry()
    {
      while(TRUE)
      {
        llSetTimerEvent(1);
      }
    }
    timer()
    {
      llSay(0,"fuck!!!!!!");
    }
    由於這個程式很乖,所以它不會在每秒鐘喊fuck!!!!!!
    主要是因為無限迴圈永遠跑不出去,所以他永遠不會執行Event,代表只要有迴圈在,Event這裡並不能同步執行
  • 當state轉移時,event會被抽離,如果你下一個state有上一個state想要繼續啟動的event,這很有可能會造成資訊遺漏,所以要是能的話,我在這邊建議state轉換儘量少使用(但這個前題是有想要做遊戲這種需要每秒一直有event持續啟動的專題)
  • 雖然state轉換時會把event抽離,但只有timer這個event例外,他會從先前state的timer轉移執行現在state的timer。如果有必要在兩個state都用到timer的話,務必請在轉移state前把timer啟動設為0(代表不啟動timer),請重新在另一個state設定llSetTimerEvent()。以下為發生轉移state照樣執行另一個state的timer的情形:
  default
  {
  state_entry()
  {
  llSetTimerEvent(1);
  }

  timer()
  {
  state A;
  }
  }

  state A
  {
  state_entry()
  {

  }

  timer()
  {
  llSay(0,"state A timer()");
  }
  }
  上面的範例意思是,state A中並沒有llSetTimerEvent,結果他還是會跑state A中的timer程式碼證明了timer()這個event不會因為state轉移而被抽離。

全域函式

  在全域函式中,包含了要回傳和不回傳的型式。而你的自訂函式格式可以如以下四種:
  1. 變數型態 自訂函式名稱(){.......程式碼.......}
  2. 變數型態 自訂函式名稱(變數型態 自訂傳入變數名){.......程式碼.......}
  3. 自訂函式名稱(){.......程式碼.......}
  4. 自訂函式名稱(變數型態 自訂傳入變數名){.......程式碼.......}
  1和2皆為要回傳的型式,也就是說,在程式碼內你必須要使用return回傳一開始宣告的變數型態結果。

  (eg.
  string caculateYourCup(string name)
  {
    if(name=="Tina")
    {
      return "A Cup";
    }
   return "B Cup"; 
  
  }
  這涵式的意思是,當遇到名字為Tine的人就回傳"A cup",其餘為"B Cup"
  )

  注意:
  • 這裡可沒有什麼overloading這種東西。overloading就是同名字的函式,但傳入不同的變數

  簡單來說,連結內可以說是LSL的API,如前面有用到的llSay(0,"fuck!!!!")便是使用了LSL的Function。

  基本上我介紹LSL會把重心擺在這,藉由要做的物件來分享一些LSL的Function使用方法。所以基本的介紹就到此告一段落了……如果有LSL的相關問題,歡迎來詢問(跟SL有關的問題,也可以唷!SL不是SecondLife,是ShortLin!!!嗚……SecondLife把我的SL搶走了……Zzz)。

沒有留言: