佳礼资讯网

 找回密码
 注册

ADVERTISEMENT

楼主: xtricky

完整dato地图编辑(点击目录第一页)期待升精

[复制链接]
 楼主| 发表于 8-1-2007 07:08 PM | 显示全部楼层
中斷For迴圈:
如果For迴圈裡面有Skip Remaining Actions,由於迴圈還是在原來的Action下面,因此會跳出整個Action:  

中斷Pick迴圈:
如果Pick裡面有Skip Remaining Actions,由於Pick所執行的是另外一個獨立的函式,因此它只跳出Pick-Action函式:  

For迴圈+延遲
由於迴圈索引Integer A(bj_forLoopAIndex;另外還有一個bj_forLoopAIndexEnd) 、Integer B是全域變數,很容易彼此相互干擾,造成錯誤。相信很多人寫過類似以下例子的東西:
Loop Delay
 Events
  Player - Player 1 (Red) types a chat message containing -test as An exact match
 Conditions
 Actions
  For each (Integer A) from 1 to 10, do (Actions)
   Loop - Actions
    Wait 1.00 seconds
    Game - Display to (All players) the text: (String((Integer A)))
回复

使用道具 举报


ADVERTISEMENT

 楼主| 发表于 8-1-2007 07:16 PM | 显示全部楼层
如果Player1在2秒和5.1秒時分別輸入一次-test,大部分的人會認為電腦這樣印:
 
時間(S)23.x4.x5.x6.x6.y7.x7.y8.x8.y9.x9.y10.x10.y11.x11.y12.x12.y...
文字 123415263748596107...
 
很可惜的,結果不是這樣,實際的執行狀況如下:
 
時間(S)23.x4.x5.x6.x6.y7.x7.y8.x8.y9.x9.y10.x10.y11.x
文字 1231234567891011

詳細執行過程如下:
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:18 PM | 显示全部楼层
詳細執行過程如下: 
時間(S)
第一次輸入
第二次輸入
變數
0
 
 
bj_forLoopAIndex = 0
bj_forLoopAIndexEnd = 0
2
輸入-test
設定bj_forLoopAIndex為1
設定bj_forLoopAIndexEnd為10
進入迴圈
等待一秒
 
bj_forLoopAIndex = 1
bj_forLoopAIndexEnd = 0
3.x
顯示出”1”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 2
bj_forLoopAIndexEnd = 0
4.x
顯示出”2”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 3
bj_forLoopAIndexEnd = 0
5.x
顯示出”3”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 4
bj_forLoopAIndexEnd = 10
5.1
 
輸入-test
設定bj_forLoopAIndex為1
設定bj_forLoopAIndexEnd為10
進入迴圈
等待一秒
bj_forLoopAIndex = 1
bj_forLoopAIndexEnd = 10
6.x
顯示出”1”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 2
bj_forLoopAIndexEnd = 10
6..y
 
顯示出”2”
設定bj_forLoopAIndex加1
等待一秒
bj_forLoopAIndex = 3
bj_forLoopAIndexEnd = 10
7.x
顯示出”3”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 4
bj_forLoopAIndexEnd = 10
7.y
 
顯示出”4”
定bj_forLoopAIndex加1
等待一秒
bj_forLoopAIndex = 5
bj_forLoopAIndexEnd = 10
8.x
顯示出”5”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 6
bj_forLoopAIndexEnd = 10
8.y
 
顯示出”6”
設定bj_forLoopAIndex加1
等待一秒
bj_forLoopAIndex = 7
bj_forLoopAIndexEnd = 10
9.x
顯示出”7”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 8
bj_forLoopAIndexEnd = 10
9.y
 
顯示出”8”
設定bj_forLoopAIndex加1
等待一秒
bj_forLoopAIndex = 9
bj_forLoopAIndexEnd = 10
10.x
顯示出”9”
設定bj_forLoopAIndex加1
等待一秒
 
bj_forLoopAIndex = 10
bj_forLoopAIndexEnd = 10
10.y
 
顯示出”10”
設定bj_forLoopAIndex加1
由於bj_forLoopAIndex比bj_forLoopAIndexEnd大,跳出迴圈
結束事件
 
bj_forLoopAIndex = 11
bj_forLoopAIndexEnd = 10
11.x
顯示出”11”
設定bj_forLoopAIndex加1
由於bj_forLoopAIndex比bj_forLoopAIndexEnd大,跳出迴圈
結束事件
 
 
bj_forLoopAIndex = 12
bj_forLoopAIndexEnd = 10
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:18 PM | 显示全部楼层
可以發現在第二個迴圈就開始發生共用變數的錯亂情形了。 那麼有沒有解決方法呢?唯一的解決方法就是使用區域變數做為迴圈的索引,這部分請參見JASS教學Pick迴圈+延遲
Pick迴圈下的Wait都沒有作用,如下例:( 反白檢視執行順序)
Pick Delay Test
 Events
  Time - Elapsed game time is 5.00 seconds ----(1)
 Conditions
 Actions
  Unit Group - Pick every unit in (Units in (Playable map area)) and do (Actions) ----(2)
   Loop - Actions (假設選到3個部隊)
    Unit - Set life of (Picked unit) to 100.00% ----(3) ----(5) ----(7)
    Wait 10.00 game-time seconds ----(4) ----(6) ----(8)
    Unit - Set mana of (Picked unit) to 0.00

大家一定認為選取的部隊會先生命值全滿,然後10秒鐘之後法力歸零對吧?不過實際結果是:只有生命值全滿,後面的動作全部停掉了。

如果真的想在Pick之下順利執行等待指令,其中最簡便的解決辦法就是放到另外一個子觸發,如下:( 反白檢視執行順序)
Pick Delay Test
 Events
  Time - Elapsed game time is 5.00 seconds ----(1)
 Conditions
 Actions
  Unit Group - Pick every unit in (Units in (Playable map area)) and do (Actions) ----(2)
   Loop - Actions (假設選到2個部隊)
    Trigger - Run Pick Delay Sub <gen> (ignoring conditions) ----(3) ----(6)

Pick Delay Sub
 Events
 Conditions
 Actions
  Unit - Set life of (Picked unit) to 100.00% ----(4) ----(7)
  Wait 10.00 game-time seconds ----(5) ----(8)
  Unit - Set mana of (Picked unit) to 0.00 ----(9) ----(10)

-->結果會在執行後所有部隊的生命值全滿,過10.x秒鐘後均法力歸零(不是一個10.x秒,一個20.x秒喔)。
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:20 PM | 显示全部楼层
五、範例/演算法:
  「演算法」是一個程式用語,意思是「使用特定語言達成特定目的的一種思維方式」。不可否認,WE的觸發的確和程式語言的概念很相似,所以我們必須學習這種思維方式,才能更加容易了解如何把一個想法化成具體的程式語言(觸發)。以下會採用問題&解答的方式來介紹,大家在看到題目時,可以先在腦中構思作法,然後再看參考解答以及其思維方式。當然這裡所提供的解法並非惟一,讀者可以去思考是否有別的更好的方法。
  當一個問題有多種可能的寫法時,什麼樣的寫法是好的?基本上我們寫觸發的優化目標有二:一是讓電腦執行有效率,二是讓人容易讀得懂。有時為了效率會犧牲易讀性;有時為了易讀性會犠牲效率(例如全部用JASS寫最有效率,但是為了易讀,連B社的地圖也並非全用JASS)。一個有技巧的人必須能在兩者之中取得平衡點。
  下面提供幾個最簡單的範例,一些比較進階的常見問題可以在FAQ找到。
  • 前提與效果
    面對一個要用觸發解決的問題,首先應該先思考:前提是什麼?效果是如何?
    前提可以看成是要達成效果的前置因素。大致上可以分成幾個層次:
    • 可用一個事件解決。如「部隊被攻擊」、「遊戲時間30秒時」、「每5秒一次」、……。
    • 可用事件與條件解決。如「一個英雄被攻擊」、「玩家失去了所有建築」、「部隊施展了黑死爪」、……。
    • 必須用多個觸發組合去取出實際或接近的前提。如「一個部隊被投射性技能擊中」、「一個部隊受到傷害」、「一個有重生技能的部隊重生」、……。
    如果以上幾個做出來的效果不好或者沒辦法做,最後可以考慮用「每X秒執行一次」的觸發去取接近的效果。
  • 每5秒在地圖中央產生二名玩者一的步兵
    顯然這個問題中,事件是「每5秒執行一次」,動作是「在地圖中央產生二名玩者一的步兵」,而條件則不需要,所以我們可以這樣寫:
    Create Footman
     Events
      Time - Every 5.00 seconds of game time
     Conditions
     Actions
      Unit - Create 1 Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:21 PM | 显示全部楼层
(地圖上的)阿薩斯死亡時,殺人者得100黃金,而被殺者則損失100黃金
這個問題的事件很簡單,就是「阿薩斯死亡」,而動作是增加100黃金與減少100黃金,可是要加給誰呢?玩者一殺就給玩者一,玩者二殺就給玩者二;玩者一死就扣玩者一、玩者二死就扣玩者二……這樣寫多累啊?
要解決這個問題,就得用到事件回應的觀念,當事件是「部隊死亡」時,可以用函數「死亡的部隊(Dying Unit)」、「殺人的部隊(Killing Unit)」、「觸發事件的部隊(即死亡的部隊)(Triggering Unit)」來取得相關的資料。慢著,可是我要加錢給玩者,而不是部隊耶!所以我們可以再用一個函數「Ower of <Unit>」來取出殺人者/被殺者的所屬玩家。
等等!動作的指令中,只有「增加玩者金錢」和「設定玩者金錢」而已,找不到「減少玩者金錢」呀!
拜託,要動腦,改成負數不就是減少了嗎?
Arthas Death
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  Player - Add 100 to (Owner of (Killing unit)) Current gold
  Player - Add -100 to (Owner of (Dying unit)) Current gold

如果問題簡化成「(地圖上的)阿薩斯死亡時,被殺者則損失100黃金」,可能就會有人這樣寫:
Arthas Death2(X)
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  Player - Add -100 to (Triggering player) Current gold
這樣寫就錯囉,為什麼?因為觸發事件的是部隊,而不是玩者。
就好像今天小明被打了,你說:「罰罵小明的人站10分鐘」,這不是莫名奇妙,牛頭不對馬嘴嗎?
同樣的,今天事件是「阿薩斯死亡」,那麼「觸發事件的玩者」自然沒有任何意義。
(地圖上的)阿薩斯死亡時,殺人者得100黃金,而被殺者則損失100黃金。但被殺者的金錢不到100時,則把所有被殺者的金錢給殺人者。
這個問題是前一個問題的加強版,顯然我們在面對像前面那個問題時,應該要考慮到此種情況的處理方式。
喂!別急著看解答,不妨先想想看怎麼做再看範例:
Arthas Death adv1
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  Player - Add (Min(((Owner of (Triggering unit)) Current gold), 100)) to (Owner of (Killing unit)) Current gold
  Player - Add ((Min(((Owner of (Triggering unit)) Current gold), 100)) x -1) to (Owner of (Triggering unit)) Current gold
 
Arthas Death adv2
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  Player - Add ((Min(((Owner of (Triggering unit)) Current gold), 100)) x -1) to (Owner of (Triggering unit)) Current gold
  Player - Add (Min(((Owner of (Triggering unit)) Current gold), 100)) to (Owner of (Killing unit)) Current gold
回复

使用道具 举报

Follow Us
 楼主| 发表于 8-1-2007 07:21 PM | 显示全部楼层
上面兩個寫法看起來有點高級,是不是,何者比較正確呢?我們可以試著套數字去算:
adv1:
若阿薩斯的擁有者有105黃金(>100),則(Min(((Owner of (Triggering unit)) Current gold), 100))是100,所以殺死阿薩斯的玩者得到100黃金。
接著,(Min(((Owner of (Triggering unit)) Current gold), 100))是100,然後阿薩斯的擁有者損失100黃金。
若阿薩斯的擁有者有90黃金(<100),則(Min(((Owner of (Triggering unit)) Current gold), 100))是90,所以殺死阿薩斯的玩者得到90黃金,
接著,(Min(((Owner of (Triggering unit)) Current gold), 100))是90,然後阿薩斯的擁有者損失90黃金。

adv2:
若阿薩斯的擁有者有105黃金(>100),則(Min(((Owner of (Triggering unit)) Current gold), 100))是100,所以阿薩斯的擁有者損失100黃金。
接著,(Min(((Owner of (Triggering unit)) Current gold), 100))是5,然後殺死阿薩斯的玩者得到5黃金。
若阿薩斯的擁有者有105黃金(>100),則(Min(((Owner of (Triggering unit)) Current gold), 100))是90,所以阿薩斯的擁有者損失90黃金。
接著,(Min(((Owner of (Triggering unit)) Current gold), 100))是0,然後殺死阿薩斯的玩者得到0黃金。

誰對誰錯很明顯了吧?所以,即使只是一個小小的順序差異都可能導致嚴重問題,要多加小心。

此外還有幾種寫法,邏輯上都沒有錯,大家可以選一個比較習慣的使用(不過筆者個人比較推薦第3種):
 
Arthas Death adv3    (建立一個整數變數N)
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  Set N = (Min(((Owner of (Triggering unit)) Current gold), 100))
  Player - Add (N x -1) to (Owner of (Triggering unit)) Current gold
  Player - Add N to (Owner of (Killing unit)) Current gold
 
Arthas Death adv4
 Events
  Unit - Arthas (Evil) 0000 <gen> Dies
 Conditions
 Actions
  If (All Conditions are True) then do (Then Actions) else do (Else Actions)
   If - Conditions
    ((Owner of (Triggering unit)) Current gold) Greater than or equal to 100
   Then - Actions
    Player - Add 100 to (Owner of (Killing unit)) Current gold
    Player - Add -100 to (Owner of (Triggering unit)) Current gold
   Else - Actions
    Player - Add ((Owner of (Triggering unit)) Current gold) to (Owner of (Killing unit)) Current gold
    Player - Add (((Owner of (Triggering unit)) Current gold) x -1) to (Owner of (Triggering unit)) Current gold
遊戲進行到40秒時,在玩者一的所有兵營旁產生4名步兵與2名火槍兵
顯然這個問題中,事件是「遊戲40秒時執行」,動作是「在玩者一的所有兵營旁產生4名步兵與2名火槍兵」,而條件則不需要。
事件方面很簡單,可是動作就有一點複雜了,因為即使你翻遍手冊,也一定找不到「在某玩者的所有兵營旁生部隊」這種指令,所以腦筋就要拐點彎了。首先我們先選取玩者一的所有兵營,然後再對所有選到的部隊(兵營)執行「產生部隊」的指令,至於產生在哪裡呢?「兵營旁」可以Position of <兵營>來取得,因此我們可以這樣寫:
Create Footman2
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units owned by Player 1 (Red) of type Barracks) and do (Actions)
   Loop - Actions
    Unit - Create 4 Footman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
    Unit - Create 2 Rifleman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees

這樣OK了嗎?嗯,大致上是可以了,不過……Pick Unit會把死亡的部隊也選出來喔,換句話說,如果玩者一有爆掉的兵營,電腦可能會笨笨地在爆掉的兵營旁邊產生部隊,所以完整的寫法可以這樣寫:
Create Footman3
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units owned by Player 1 (Red) of type Barracks) and do (Actions)
   Loop - Actions
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
     If - Conditions
      ((Picked unit) is alive) Equal to True
     Then - Actions
      Unit - Create 4 Footman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
      Unit - Create 3 Rifleman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
     Else - Actions

不過也有別的判斷方式,如:
Create Footman4
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units owned by Player 1 (Red) matching (((Unit-type of (Matching unit)) Equal to Barracks) and (((Matching unit) is alive) Equal to True))) and do (Actions)
   Loop - Actions
    Unit - Create 4 Footman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
    Unit - Create 3 Rifleman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees

也不失為一個漂亮方法。
此外要注意一點,前者的If在Pick下面,所以要用Picked Unit做判斷;後者是在matching下做判斷,所以用Matching Unit。

此外也有像這樣的寫法:
Create Footman5
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units in (Playable map area) matching (((Owner of (Matching unit)) Equal to Player 1 (Red)) and (((Unit-type of (Matching unit)) Equal to Barracks) and (((Matching unit) is alive) Equal to True)))) and do (Actions)
   Loop - Actions
    Unit - Create 4 Footman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
    Unit - Create 3 Rifleman for Player 1 (Red) at (Position of (Picked unit)) facing Default building facing degrees
遊戲進行到40秒時,在所有玩者的所有兵營旁產生4名步兵與2名火槍兵
乍看之下這個問題和上面的很類似,只不過我們可以換成Unit Group - Pick every unit in (Units of type Barracks) and do (Actions),可是當你寫到動作時,問題就來了:要為哪個玩者創造部隊?
這個前面學過,用「選到的玩家的擁有者」就好囉。以下為二種寫法:
Create Footman6
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units in (Playable map area) matching (((Unit-type of (Matching unit)) Equal to Barracks) and (((Matching unit) is alive) Equal to True))) and do (Actions)
   Loop - Actions
    Unit - Create 4 Footman for (Owner of (Picked unit)) at (Position of (Picked unit)) facing Default building facing degrees
    Unit - Create 3 Rifleman for (Owner of (Picked unit)) at (Position of (Picked unit)) facing Default building facing degrees
 
Create Footman7
 Events
  Time - Elapsed game time is 40.00 seconds
 Conditions
 Actions
  Unit Group - Pick every unit in (Units of type Barracks) and do (Actions)
   Loop - Actions
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
     If - Conditions
      ((Picked unit) is alive) Equal to True
     Then - Actions
      Unit - Create 4 Footman for (Owner of (Picked unit)) at (Position of (Picked unit)) facing Default building facing degrees
      Unit - Create 3 Rifleman for (Owner of (Picked unit)) at (Position of (Picked unit)) facing Default building facing degrees
     Else - Actions
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:22 PM | 显示全部楼层
0、附件
<附件1>:JASS語法<附件2>:資料類型
<附件3>:資料轉換<附件4>:資料運算
<附件5>:GUI和JASS資料類型<附件6>:全域變數VS區域變數
<附件7>:函數處處有<附件8>:迴圈
<附件9>:觸發器與JASS<附件10>:簡化JASS碼
<附件11>:定義自己的函數<附件12>:對部分玩者播放音效(本機玩者)
<附件13>:多執行緒的概念<附件14>:傭兵護主
<附件15>:傷害加深<附件16>:物件的身分證字號
<附件17>:回傳錯誤的應用<附件18>:JASS綜合應用

一、JASS簡介
回复

使用道具 举报


ADVERTISEMENT

 楼主| 发表于 8-1-2007 07:23 PM | 显示全部楼层
  • JASS是什麼?
    JASS是魔獸3的程式語言,用於控制遊戲和地圖的進行,也是魔獸遊戲和地圖的基礎。 地圖編輯器中擺放的部隊(Unit),區域(Region) ,觸發(Trigger)……等,最終都會被翻譯成JASS語言存在地圖檔裡,在遊戲時被使用。JASS在語法結構上比較接近Basic,同時也引用了許多C的東西。如果讀者有接觸過這二種程式語言, 應該能更快上手!

    順帶一提,和JASS相對,在觸發編輯器中,一般那種剪剪貼貼、拼拼湊湊的觸發寫法通常稱為GUI Trigger。(GUI = Graphical User Interface 圖形使用者界面)
  • 為什麼要學習JASS?
    • 某些功能只靠GUI Trigger無法完成, 必須用JASS來實現。例如對指定玩家播放音效,或者替部隊加上永不消失的被動物品技能等。
    • JASS可以定義區域變數及自訂的函數,增加設計的便利性,也提供更簡單可行的演算法。
    • GUI雖然能完成幾乎所有的功能,但是對於記憶體釋放的能力太差,容易增加電腦不必要的負擔。
    • 用JASS可以寫出比GUI效率更高的程式碼,對執行速度有不小的幫助。
  • 一定要學JASS嗎?
    當然不一定。一般來說,單純使用GUI Trigger,就可以達到大多數的功能。但是筆者還是建議對Trigger有相當了解的人學些基本的JASS寫法,可以省下不少力氣,且能讓你的地圖更不lag!!
  • 如何使用JASS?
    • 觸發編輯器中的 Edit => Convert To Custom Text 將觸發轉成文字型態。
    • 在觸發編輯器下面選 Actions => Custom Script 可以插入單列JASS敘述。
    • 此外,如果要定義所有觸發都能呼叫的函數,可以寫在這裡:
二、基本知識
  • JASS語言的基本函數和常數都是直接調用遊戲的函數,他們被存放在war3patch.mpq內的Scripts\common.j中,另外還有一些擴充函數放在war3patch.mpq內的Scripts\blizzard.j中。war3patch.mpq內的Scripts\common.ai則包含了用於設計AI的內部函數和擴展函數,雖然AI也是用JASS碼編成,但本文不探討關於AI的設計,有興趣者請自行研究。
  • 地圖中的觸發以及物件的擺設情形等,都會被編譯成JASS並儲存在war3map.j檔案中。讀者可以到WE中的 File => Export Script 將它匯出。
  • JASS語言以列為基本單位。每一列的文字必須有完整的意義,不能把一列的內容分成二列寫;也不可把二列的敘述寫成同一列。
  • JASS語言區分大小寫,該大寫就要大寫;該小寫就要小寫。
  • 寫在//後面,直到該列結束的文字都是註解內容,這也是JASS唯一的注釋語法。後面的例子會多處用到這個注釋符號,這個符號和後面的注釋只是用於解釋程式碼的功能,並不會被執行到 。
  • 在JASS中,空格的使用限制很寬鬆,除了某些必要的地方一定要有至少一個的空格以外,其它的地方都是可空可不空。此外,要空幾格都無所謂,電腦不會因為空了很多格就報錯。因此,使用者應多多利用空格作縮排,以使程式更易讀。
  • 和數學一樣,( )內的程式碼優先被執行。不過請注意,JASS中只有小括號( )有此用途;中括號[ ]用於表示陣列變數的索引值,;大括號{ }完全沒有用。


JASS錯誤處理
    • 語法錯誤:少寫一個字母,少空空白,或者把大寫寫成小寫等,都是寫JASS常犯的錯誤。一般來說,如果語法有問題,在存檔時電腦會顯示編譯錯誤的訊息, 它會指出哪一列有問題,依它的指示修正即可。不過當電腦指出某列有誤時,也可能是前面的幾列出了問題(範圍大概約1~5列),所以如果你怎麼檢查都看不出某列到底錯在哪裡的時候, 請檢查前面的程式碼。此外,某些錯誤會導致存檔時WE當掉,導致先前的辛苦付諸流水,所以請隨時存檔並且盡量小心。
    • 執行錯誤:一般會發生這種問題是指定的變數沒有資料,當電腦找不到資料時,由於無法繼續執行,因此會無條件跳出目前的函數,如果該函數是要傳回值的函數,它將不會傳回值(也是無資料),因而可能導致呼叫它的函數也跳出。此外如除數為0也會造成類似的結果。
    • 無窮迴圈:一般無窮迴圈的形成都是由於人為疏失(例如忘了寫exitwhen,或是觸發的動作引發同一個觸發,而造成無限循環等)。 一般來說,無限循環的迴圈會讓魔獸突然跳出,而且不顯示任何錯誤訊息;不過如果在其中加入等待函數,還是能有一些實際的用途,它能夠每隔一段時間進行一些特定的動作,一直持續到遊戲結束。
  • 範例請參見<附件1>
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:24 PM | 显示全部楼层
三、資料
  • 資料類型
    • boolean:布林,為一種邏輯資料,只有 true (真) 和 false (假) 二個值。
    • integer:整數,取值範圍為 -2147483648到2147483647 ( -2^31 ~ 2^31 -1 ),寫法有很多種:
      • 十進位:全部為數字,第一個數位不為0(0例外)。最常用且最基本的表示方法。 例如:12、-199930、0、999999
      • 八進位:0開頭,後面接數字,這是8進制表示方法。例如:07 ==7 、08語法錯誤、012 == 10。
      • 十六進位:0x開頭(x大小寫不限),後面全部為數位。16進制的數位包含A,B,C,D,E,F,分別表示10~15,大小寫不限。例如:0x2==2、0xA==10、0x10==16、0xFF==255
      • 單字元:用單引號括起一個ASCII字元,表示該字元的ASCII碼,範圍為0~255。 例如:'A'==65、'<'==60
      • 四字元:用單引號括起4個字元。這種表示方法大多用於表示部隊、技能、物品等物件的ID,例如:'Aloc' 、 'B000'。它的實際數值是256進制,各字元取ASCII碼,然而我們通常不會去計較它實際代表的數值,也不用此種表示方法做運算。總之只要二個ID中有任一字元不同,就是二個不同的數字 。
    • real:實數,取值範圍為 1.5 x 10^(-45)到3.4 x 10^38。實數的寫法很寬鬆,小數點幾乎可以隨便寫。但是請注意, 對於一個沒有小數位的數,如果不寫小數點,電腦會視情況將它當作整數或實數,因此為了避免不必要的誤會,即使是無小數的實數也請養成加小數點的習慣。
    • string:字串,用雙引號括起的若干個字母,例如"Are you OK?";空字串表示為""。可用null表示空值。此外還有一些特殊字元如下:
       
      輸入輸出
      |n換列
      |||
      |cffffcc00Danny是天才|rDanny是天才
      \''
      \""
      \\\
       
    • code:程式碼,其值的形式為function <函數名>,用於表示一個函數位址,通常用於傳值時傳遞一個函數。 可用null表示空值。
    • handle: 控制碼,它指向記憶體的一個位址,用於表示一個較大的結構,是很多資料類型的基類型。可用null表示空值。
      還有一些資料是繼承handle而來,它在Common.j中被定義,其定義形式如下:

      type <子類型> extends <父類型>

      常用的延伸類型表列如下:
       
      handleplayer 
      force 
      widgetunit
      destructible
      item
      group 
      location 
      region 
      rect 
      boolexprconditionfunc
      filterfunc
      trigger 
      event 
      eventid 
      triggercondition 
      triggeraction 
      timer 
      timerdialog 
      effect 
      lightning 
      weathereffect 
      terraindeformation 
      fogmodifier 
      texttag 
      sound 
      gamecache 
      leaderboard 
      multiboard 
      multiboarditem 
      dialog 
      button 
      quest 
      questitem 
      defeatcondition 

      範例請參見<附件2>
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:24 PM | 显示全部楼层
資料轉換
不同的資料可互相轉換,其轉換方式如下:
  • I2R(<Integer>):將整數轉成實數。
  • I2S(<Integer>):將整數轉成字串,一律以10進制方式呈現。
  • R2I(<Real>):將實數轉成整數,小數無條件捨去。
  • R2S(<Real>):將實數轉成字串,轉換後至少顯示3位小數。
  • R2SW(<Real>, <最低長度>, <小數位數>):將實數轉化為格式化的字串,將多於<小數位數>的小數部分四捨五入;如果轉換後長度小於<最低長度>,則在左邊補上空白。好處是可以對齊 。
  • S2I(<String>):將字串轉成整數。轉換方法是從第一個字元開始往後找,直到遇上不合法的字元即停止轉換。
  • S2R(<String>):將字串轉成實數。轉換方法是從第一個字元開始往後找,直到遇上不合法的字元即停止轉換。
範例請參考<附件3>
資料運算
  • 字串運算:
    • +:連接二個字串,如"abc" + "def" ==> "abcdef"
    • ==:判斷二個字串是否完全相同
    • !=:判斷二個字串是否不同
  • 整數/實數運算:
    • +:前數加後數
    • -:前數減後數
    • *:前數乘以後數
    • /:前數除以後數。對整數而言,小數點無條件捨去。
    • ==:判斷前者是否等於後者
    • !=:判斷前者是否不等於後者
    • >:判斷前者是否大於後者
    • <=:判斷前者是否大於或等於後者
    • <:判斷前者是否小於後者
    • <=:判斷前者是否小於等於後者
  • 邏輯運算:
    • ==:判斷二者是否同真或同假
    • !=:判斷二者是否不同
    • and:二者中如果皆為真,則結果為真,否則為假
    • or:二者中如果皆為假,則結果為假,否則為真
    • not:將真改為假或假改為真
運算優先順序如下(順序都是按照常理訂的。各位可自己想想,如果順序不是這樣會發生什麼事):
()先執行
* /
+ -
> >= <= <
not
== !=
or
and

※如果"+", "-"號後面有接數字,而前面沒有接數字,則優先運算(視為數字的正負號,和數字是一體的)
範例請參考<附件4>
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:25 PM | 显示全部楼层
四、變數(variable)
  • 什麼是變數
    照字面上的意思,"變數"即是:會變的"數",這個數即是"數據"、"資料"的意思。
    變數代表一個指向電腦記憶體位置的預留空間。在這個空間中,你可以儲存一些在遊戲執行過程中會變動的資訊。
    變數必須有名稱 (用來引用該變數所包含的值) 和資料型態 (決定變數可以儲存的資料種類)。
  • 變數的名稱
    • 變數名稱必須以英文字母開頭。變數名中可以包括:大小寫英文字母A~Z, a~z、數字0~9、底線_,其他字元會被認為非法(請不要用中文命名變數)。
    • 變數不得以關鍵字或已存在的名稱來命名。JASS的關鍵字有26個,和英文字母一樣多:
       
      and array call constant else elseif endfunction endglobals endif endloop exitwhen extends
      function globals if local loop native not or return  returns set takes type then
  • 變數的定義
    在使用一個變數前,首先要告訴war3:「我要存放資料啦,畫一塊記憶體給我!」,這就是對變數的定義。
    寫過程式的人應該都知道變數分為全域變數和區域變數。全域變數就像家裡的儲藏室一樣,每一個人(函數)都能自由地存取它;而區域變數則像家中成員私人的抽屜一樣,只有定義它的函數才能夠存取它。
    • 定義全域變數
      在we中對全域變數的定義,是在edit(編輯)功能表中點擊Variables(變數),或者直接點快顯功能表按鈕中的“X”按鈕,新增、刪除、定義資料類型。而實際在map檔中,這部分變數會放在JASS檔開頭的部分聲明,形式如下:
       
      globals
         <變數類型> <變數名稱> = 初始值
         ...
      endglobals
      = 初始值可以不寫

      此外,觸發中的Integer A、Integer B、以及所有Last Created開頭的函數(如Last Created Unit、Last Replaced Unit、Last Created Special Effect...),其實都是B社定的全域變數。
    • 定義區域變數
      區域變數的定義,必須放在一個函數的最前段,用local關鍵字開頭來聲明,全部區域變數的定義結束後,才是函數的實體部分。定義語法如下:
       
      local <變數類型> <變數名稱> = 初值
      = 初值可以不寫
  • 設定變數的值
    定義變數只是叫電腦劃空間給你而已,有了空間,再來就是把資料放進去。語法是:
     
    set <變數名稱> = <值>

    此外,大家應該有看到,在定義的時候也可以順便設定初值。
  • 取用變數的值
    變數的值設定好以後,就可以隨意取用啦。把它當作一個數或一個物件來用即可。
  • 字首
    B社為了讓程式運作順暢,而且不讓製作者自製的變數名稱和War3內部的變數名稱混淆。因此WE會自動為玩家自訂的變數名稱加上字首。例如:
    blizzard.j中定義的變數           ==>   bj_
    觸發編輯器定義的變數             ==>   udg_
    觸發器                           ==>   gg_trg_
    WE繪出的區域                     ==>   gg_rct_
    預置部隊                         ==>   gg_unit_
    聲音編輯器建立的聲音             ==>   gg_snd_
                    .
                    .
                    .
    等等
    最常見的情況是使用WE定義的變數,在寫JASS時,不要忘了加上udg_喔!
  • 常數
    顧名思義,就是用一串字來表示一個固定的值,如PI(π=3.141592653589793)。由於WE中無法手動新增常數,所以這裏只做一個簡單的介紹。
    常數是唯讀的,可以把它想成是一個無法改變數值的變數。它的定義也是在JASS檔的開頭,例如blizzard.j中:
     
    globals
        constant real    bj_PI                    = 3.14159
        constant real    bj_E                     = 2.71828
        constant integer bj_MAX_INVENTORY         = 6
        constant integer bj_PLAYER_NEUTRAL_VICTIM = 13
        constant integer bj_PLAYER_NEUTRAL_EXTRA  = 14
    endglobals

    其它的使用方法和變數大同小異。
  • 命名方式
    雖然變數的名稱可以自由命名,不過建立一個好的命名習慣,可以讓你迅速了解該變數的用途。
    一般習慣上,常數全部大寫,並且使用底線區隔單字,例如:PLAYER_COLOR_RED、ATTACK_TYPE_NORMAL、UNIT_TYPE_HERO、……
    變數則有全部小寫、小寫和底線、大小寫交錯等寫法,例如:bj_questdiscoveredsound、bj_quest_discovered_sound、bj_questDiscoveredSound、……
    如果你喜歡用a b c dhfla tyy等等無意義的字母命名,也不會出現任何問題。但是筆者不建議這種命名方式,這樣在有錯誤的時候較不便於偵錯。
    當然,如果覺得自己英文不大靈光的話,中文拼音也完全OK,總之,自己方便、日後易讀的方法,就是好方法。
  • 相關內容請參見<附件1>、<附件5>、<附件6>
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:25 PM | 显示全部楼层
五、函數(function)
  • 什麼是函數
    函數是一段用來完成一個獨立功能的程式碼,它的運作原理就像數學的f(x), g(x,y)那樣,只要輸入一些數值,它就會幫你做相應的事。函數是JASS中極為重要的部分,想學好JASS,就要學會設計函數。
  • 源函數
    API(Application Programming Interface,應用程式介面)是指包含在common.j中的函數,該部分函數實際上是一些已經由系統封裝好的函數,用戶只要知道function名 稱、功能、參數,就可以藉由呼叫這些函數完成自己想要做的事情,通常API是實現用戶無法通過自定義函數來實現的功能,在common.j中,API的定義形式如下:
     
    native <函數名> takes <參數類型1> <參數名1>, <參數類型2> <參數名2>, ... returns <傳回類型>

    函數可以不取參數(填nothing),也可以不傳回值(一樣填nothing)。

    這種函數是變函數,它根據參數和函數本體內的運算,傳回不同的值。另外有一種所謂的常函數(constant function),它傳回一個固定的值。
    常函數的定義和變函數差不多,只是在native前面多加一個constant而已。
  • 自定函數
    自定函數是指用戶在使用過程中,自己定義的函數
    一段函數,由定義和函數主體兩部分構成:
     
    function <函數名> takes <參數類型1> <參數名1>, <參數類型2> <參數名2>, ... returns <傳回類型> //定義
        //*****************************
        //***        函數主體       ***
        //*****************************
        return <運算式>
    endfunction //函數結尾

    函數可以不取參數(填nothing),也可以不傳回值(一樣填nothing)。對於不傳回值的函數,最後面可以不寫return那行。

    接著介紹函數基本語法return, 它必須寫在函數裏,格式為:
     
    return <運算式>

    當函數傳回類型為nothing時,後面不需要運算式,否則必須寫運算式。

    它可以出現在函數的任何地方,也可以出現不止一次,但是一旦碰到這一行,函數立即傳回值並跳出。
    注意,return值的類型必須和函數的傳回類型相同。除了有定義傳回類型函數的最後面要寫以外,return也可以作為提早結束函數之用。
  • 呼叫函數
    有了函數,我們當然要使用它,這個動作稱為「呼叫」。呼叫函數可以在很多地方進行,只要類型合適即可。
    • 語法
      <函數名稱>(<引數1>, <引數1>, ...)
      如果這個函數自成一行,前面必須加上call
    • 只有在後面的程式碼可以呼叫前面的函數;函數可以呼叫自己,但不能呼叫寫在後面的函數。
    • 函數的執行流程
      當我們在一段程式碼A中調用函數B的時候,A會首先傳遞一些相關的資訊給B,這些由A傳遞的值稱為引數(argument)。
      然後控制權移轉到B,直到B把所有的東西都處理完之後,再把控制權交還A,繼續執行後面的動作。
      B在處理時可能會用到一些參數(parameter),而B的參數要和A傳給它的引數類型相同,否則就會出錯。
      也就是說,一條敘述調用一個函數,就意味著只有當被調用函數執行完畢之後,才會繼續執行呼叫敍述後面的程式碼。
  • 範例請參見<附件7>
六、判斷(if)
  • 判斷語句的用途
    日常生活中,我們常常面臨選擇。「如果xxxxxx,我就oooooo」,這種事在程式中也常會碰到,我們可以用If判斷語句來讓函數根據不同的情況,做不同的事。
  • 語法
     
    if <條件> then
        //*****************************
        //***       一段程式碼      ***
        //*****************************
    elseif <條件> then
        //*****************************
        //***       一段程式碼      ***
        //*****************************
    elseif <條件> then
        //*****************************
        //***       一段程式碼      ***
        //*****************************
                    .
                    .
                    .
    else
        //*****************************
        //***       一段程式碼      ***
        //*****************************
    endif
     
  • elseif和else那段可寫可不寫
  • 範例請見<附件1>
回复

使用道具 举报

 楼主| 发表于 8-1-2007 07:26 PM | 显示全部楼层
七、迴圈(loop)
  • 迴圈是什麼
    當你要讓電腦計算1*2*3*...*50的時候,你是不是覺得寫那麼一長串程式碼很煩?事實上,類似的動作寫好幾遍的程式碼,任何人都會覺得很煩很枯燥。因此聰明的程式師就發明了迴圈,用來讓電腦執行相同或相似的動作多次,而不佔用太多程式碼。
  • 語法
     
    loop
        //*****************************
        //***       一段程式碼      ***
        //*****************************
        exitwhen <條件>
        //*****************************
        //***       一段程式碼      ***
        //*****************************
    endloop
     
  • exitwhen那段可寫可不寫。不過如果沒寫就變成無窮迴圈跑到死,除非裡面有Wait,否則War3基本上會跳出。
  • 範例請見<附件8>
八、陣列(array)
  • 什麼是陣列
    有些時候,你會不會覺得一些重複的操作很煩呢?比如把一個部隊身上的物品丟在一個地方,過一段時間之後再放回身上,例如:

        local unit u = GetTriggerUnit()
        local item myitem1 = UnitItemInSlotBJ(u, 1)
        local item myitem2 = UnitItemInSlotBJ(u, 2)
        local item myitem3 = UnitItemInSlotBJ(u, 3)
        local item myitem4 = UnitItemInSlotBJ(u, 4)
        local item myitem5 = UnitItemInSlotBJ(u, 5)
        local item myitem6 = UnitItemInSlotBJ(u, 6)
        ...

    這種時候,我們可以用一個array(陣列)來記錄一組物件。
    陣列(Array)就是一個由名稱和索引值構成的變數組,可以代表很多個同類型的變數。
  • 陣列的定義
    陣列也是變數,所以也有全域和區域之分。全域陣列只要在WE設定變數時,把array勾上就可以了。War3中,陣列的大小為8192([0]~[8191])。而在JASS中,則是在變數的定義過程,在變數類型和變數名稱之間加入 關鍵字array:
     
    globals
       <變數類型> <變數名稱> array
       ...
    endglobals

    另外,在定義陣列時,不能設定初值。
  • 陣列的引用
    引用陣列和引用變數差不多,只要加上[<索引值>]就好了。
九、進入JASS的世界
  • 我們必須承認,JASS的功能比GUI強得多,然而GUI也並非一無事處,因為它好讀,撰寫和修改往往方便。
    進入JASS之前,首先應該先從熟悉JASS語法開始,最簡單的方法就是用GUI寫幾句觸發,並且轉成JASS對照,當然,別人寫的觸發,手冊提供的所有範例,你都可以把它轉成JASS觀看,只要想學,教材到處都有。
    雖然轉成JASS之後無法還原成GUI,不過如果你還沒存檔,應該可以在看完後使用還原(Ctrl+Z)把變成JASS的那個觸發器還原。

    學到了這裡,讀者應該要知道,觸發和JASS首重靈活應用,別人提供的任何範例只是表達一個思路,以及達成的技巧。很多地方都應該學習配合自己的習慣和需要去做調整。
    JASS的靈活度和自由度遠比GUI大得多,筆者不可能把所有可能的變化都寫一遍,手冊提供的所有範例,請在了解其精神內涵後,自行靈活運用,當「抄人」是絕對學不好JASS的。
     
  • 那麼我們開始囉:
    • 首先要先了解觸發轉成JASS會變成什麼德性,請參考<附件9>。
    • 接下來要建立良好的JASS習慣,建議先從練習精簡JASS碼開始,把不必要的垃圾清掉,這個過程也能讓你更加熟悉和掌握JASS語法。請參考<附件10>。
    • 接下來請多練習模組化,也就是把地圖中常用的一些特定功能寫成獨立的函數,如此一來,只要呼叫之前寫過的函數,就能較輕鬆地達到效果,而不必重複撰寫長長的程式碼。請見<附件11>。
    • 接下來可以再看後面的<附件12>~<附件18>,各自有不同的專題,讀者可以從中學到更多JASS的知識,並且從中習得撰寫的小技巧。
      此外,也可以看看JASS函數研究一文,了解更多特殊的JASS函數。
    • 最後,有空可以多爬外國網站,你會問為什麼資料都在國外,誰叫JASS是老外發明的= ="
      以下提供幾份參考資料:
<附註>
  • 函數(function)也稱為函式、程序、副程式等
  • 變數(variable)或譯作變量
  • 全域變數(local variable)也稱作全局變量
  • 區域變數(local variable)也稱作局部變量
  • 嚴格說起來,直排稱為行或欄,橫排稱為列。因為眼淚是直的,所以我們說「兩行眼淚」而不是「兩列眼淚」。
    不過也有很多人會直接稱一排為一行,請讀者根據上下文自行判斷它究竟是指直排或橫排。
回复

使用道具 举报

 楼主| 发表于 9-1-2007 10:38 PM | 显示全部楼层
記憶體漏失(Memory Leak)
By Danny
一、什麼是記憶體漏失
很多國外的WEer常常把談Memory Leak(記憶體漏失)掛在嘴邊,這是指有些創造出來的物件,已經不會再被用到了,卻沒有被刪掉,因而佔用記憶體空間的情形。
例如:Unit - Create 1 Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees

其中(Center of (Playable map area))就是一個點。可以想成,我要電腦在操場中間產生一個步兵,但是電腦不懂什麼叫做 「操場中間」。所以我到操場中間插一個旗子(也就是一個點),之後我就命令電腦在那個旗子的位置產生一個步兵。好了,現在步兵產生了,那麼旗子呢?因為我沒有刪掉它,所以它會一直留在記憶體中。一兩個不打緊,但是如果這一類的動作很多,記憶體裡堆了數十萬個沒有用的東西,跑起來效率自然大打折扣。

同樣的情形也發生在其它的物件中。對了,什麼是「物件」?籠統地說,只要是遊戲中一個具體的東東就是物件,除了布林(boolean)、整數(integer)、字串(string)、實數(real)、程式碼(code)不是以外,其它類型的變數皆屬之 。有些物件像部隊、物品是可以看得到的;而有些像計時器、點、部隊群組,卻是看不見的。有些物件通常並不會大量產生,而且很多都是可重複利用的,因此並不需要太注意它們的刪除。
當你執行了一些函數,就會產生物件,例如部隊群組的函數,只要呼叫一次就創造一個點(部隊群組)。
最常見堆積記憶體的動作如:
  • Unit - Create 1 Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
    建立了一個點
  • Unit Group - Pick every unit in (Units owned by Player 7 (Green) of type Barracks) and do (Unit - Create 1 Footman for Player 7 (Green) at (Position of (Picked unit)) facing Default building facing degrees)
    建立了一個部隊群組及一個點
  • Unit Group - Pick every unit in (Last created unit group) and do (Unit - Order (Picked unit) to Attack-Move To (Center of Region 000 <gen>))
    建立了一個部隊群組及一個點
  • Special Effect - Create a special effect at (Target point of issued order) using Abilities\Spells\Human\ThunderClap\ThunderClapCaster.mdl
    建立了一個點和一個特效
其中紅色和粗字的部分就是建立而沒有被刪除的物件。
二、記憶體漏失的解決方法(實用篇)
記憶體漏失不是太複雜的一件事,但是概念並不是很容易理解。而且此問題從小到大都有,如果企圖要解決所有的記憶體漏失,真的會寸步難行,一行簡單的觸發都必須改用複雜百倍的方式編寫 。因此這裡提供最實用的幾個技巧,基本上能掌握住這個部分,你的地圖已經沒什麼大問題了。
  • 重複利用既有的物件
    我們每天都要喝水,喝水需要茶杯。試問你是每天買一個茶杯來用呢?還是買一個茶杯保存起來,每天用它喝水?

    假設我們要製作一張類似DotA或三國無雙的地圖,像這一類的攻擊觸發應該是免不了的。如果我們這麼寫:
    Unit Attack 1
     Events
      Unit - A unit enters HumanBase1 <gen>
     Conditions
     Actions
      Unit - Order (Entering unit) to Attack-Move To (Center of DemonBase1 <gen>)
    那麼,每當有一個部隊進入了HumanBase1,就會產生一個點(Center of DemonBase1 <gen>),而且產生的點都不會被刪除?

    這些點既然都位於同樣的位置,為什麼不重複使用呢?所以為了環保起見,我們可以改寫成這樣:
    Map Init
     Events
      Map initialization
     Conditions
     Actions
      Set DemonBase[1] = (Center of DemonBase1 <gen>)
      ……
    Unit Attack 1
     Events
      Unit - A unit enters HumanBase1 <gen>
     Conditions
     Actions
      Unit - Order (Entering unit) to Attack-Move To DemonBase[1]

    也就是預先把可以多次利用的物件用變數記錄下來,然後重複使用這個物件,減少新物件的產生。
  • 刪除使用過且不再需要的新建物件
    • 首先介紹刪除的方法,有一些要用到JASS:
      • trigger (觸發器)--Custom script: call DestroyTrigger(xxx)
      • location (點)--Custom script: call RemoveLocation(xxx)
      • group (部隊群組)--Custom script: call DestroyGroup(xxx) ※注意Clear Group是清除部隊群組中的部隊資料,而不是把整個部隊群組刪除
      • force (玩者群組)--Custom script: call DestroyForce(xxx) ※注意ForceClear是清除玩者群組中的玩者資料,而不是把整個玩者群組刪除
      • 部隊--死亡的部隊於屍體消失之際,系統會自動移除。所以不用特別去管它。像隱藏施法部隊這種用後不理的,要刪的話可以用GUI的Unit - Remove、Unit - Kill、Unit - Add Expiration Timer,甚至把生命回復設成負值讓它自動死亡都可以
      • 物品--被用掉的物品,系統會自動移除。所以不用特別去管它。真的要刪的話用GUI的Item - Remove即可
      • lightning (閃電特效)--用GUI的Lightning - Destroy Lightning Effect
      • effect (特效)--用GUI的Special Effect - Destroy
      • texttag (浮動文字)--用GUI的Floating Text - Destroy、Floating Text - Change Lifespan皆可。前者是直接刪除;後者是設定它的壽命,時間一到會自動被刪除
      • 影像、貼圖、任務、……--同樣用GUI就能刪掉,廢話不多話了
    • 刪除特效--
      創造特效後要移除,只要你沒刪掉,即使它只出來一下就會消失,看起來好像沒了,但是實際上它還是存在,會佔用記憶體的空間。
      而建立在部隊身上的特效,在部隊死亡屍體腐爛後,特效就看不到了,但是和前面一樣,只要你沒用觸發刪掉它,它還是會佔用記憶體。
      刪除的方法就是用Special Effect - Destroy (Last created special effect)。
      創造出特效後立刻移除它,特效還是會播放至少一次,所以對於只播一次的特效,順手刪掉它吧。
      例如:
       
      Special Effect - Create a special effect attached to the overhead of (Triggering unit) using Abilities\Spells\Other\TalkToMe\TalkToMe.mdl
      Special Effect - Destroy (Last created special effect)

      如果你要讓它持續播放一段時間,可以先利用變數存起來,時間到再刪掉。
    • 刪除觸發--把執行一次就用不到的觸發刪除(關掉(Turn Off)可讓它不再執行,但是仍會佔用空間,不如刪除一勞永逸)。
      方法是在動作的最前面加上Custom script: call DestroyTrigger(GetTriggeringTrigger())。例如:
       
      Melee Initialization
       Events
        Map initialization
       Conditions
       Actions
        Custom script:  call DestroyTrigger(GetTriggeringTrigger())
        Melee Game - Use melee time of day (for all players)
        Player - Set Player 1 (Red) Current gold to 100000000
        Player - Set Player 1 (Red) Current lumber to 100000000
    • 刪除點--
      點是最容易產生的垃圾物件,所以特別要注意怎麼刪除臨時建立的點。除了如之前所述,可以先用變數記錄常用的點以外,還有一個簡單的方法可以清除這些垃圾。之前的範例我們也可以改用這個方法:
       
      Unit Attack 1
       Events
        Unit - A unit enters HumanBase1 <gen>
       Conditions
       Actions
        Set P1 = (Center of DemonBase1 <gen>)
        Unit - Order (Entering unit) to Attack-Move To P1
        Custom script:  call RemoveLocation(udg_P1)

      簡單的說,就是先用變數記錄打算要用到的點,對它進行操作,然後刪掉它。
      特別注意第三行,由於b社會把所有觸發編輯器裡定義的變數加上字首udg_,所以這裡要填入udg_P1,而不是P1。

      另外像這樣的寫法常常有人會疏忽:
       
      Unit - Move (Triggering unit) instantly to ((Position of (Triggering unit)) offset by 500.00 towards (Facing of (Triggering unit)) degrees)

      實際上這一行接連產生了兩個點,而不是一個。
      首先Position of (Triggering unit)就像是在部隊的位置插一支旗子,回傳之。
      後一步的((Position of (Triggering unit)) offset by 500.00 towards (Facing of (Triggering unit)) degrees)就是在那個旗子位移後的位置再插一支旗子,回傳之。
      所以想把點刪乾淨就得像這樣寫:
       
      Set P1 = (Position of (Triggering unit))
      Set P2 = (P1 offset by 500.00 towards (Facing of (Triggering unit)) degrees)
      Unit - Move (Triggering unit) instantly to P2
      Custom script: call RemoveLocation(udg_P1)
      Custom script: call RemoveLocation(udg_P2)

      使用這個方法,一個地圖裡只需要一、兩個像P1、P2這樣的臨時變數就夠了,不必動用大量的變數及陣列。
    • 刪除部隊群組--
      對於部隊群組的移除,最偷懶的方法是用Custom script: set bj_wantDestroyGroup = true
      bj_wantDestroyGroup是Blizzard.j裏的一個全域變數,預設為false。在執行Blizzard.j中和Unit Group有關 的函數時,會先檢查bj_wantDestroyGroup,決定是否移除傳入的Unit Group,然後自動把bj_wantDestroyGroup 還原為 false。例如:
      Custom script: set bj_wantDestroyGroup = true
      Unit Group - Pick every unit in (Units owned by Player 1 (Red)) and do (Unit - Kill (Picked unit))

      執行到Pick Every Unit In group...動作時,電腦會檢查bj_wantDestroyGroup這個變數是否為真,如果為真,就會在進行完動作後,把傳入的group(此例中就是(Units owned by Player 1 (Red)))刪除掉,並且自動把變數bj_wantDestroyGroup設為假。

      除了Pick Every Unit In group...以外,Blizzard.j中還有這些函數會檢查bj_wantDestroyGroup並決定是否刪除當作引數傳入的group:
      Pick every unit in Unit Group and do Action(ForGroupBJ)
      All units of Unit Group are dead(IsUnitGroupDeadBJ)
      Unit Group is empty(IsUnitGroupEmptyBJ)
      Number of units in Unit Group(CountUnitsInGroup)
      Random unit from Unit Group(GroupPickRandomUnit)
      Unit Group - Add Unit Group(GroupAddGroup)
      Unit Group - Remove Unit Group(GroupRemoveGroup)
      同理可以如下運用:
      Custom script: set bj_wantDestroyGroup = true
      If ((Number of units in (Units in (Playable map area))) Greater than 50) then do (Skip remaining actions) else do (Do nothing)
      (執行到Number of units in Unit Group時,檢查bj_wantDestroyGroup為真,即刪除(Units in (Playable map area))
回复

使用道具 举报

 楼主| 发表于 9-1-2007 10:39 PM | 显示全部楼层
Custom script: set bj_wantDestroyGroup = true
If ((All units of (Units owned by Player 1 (Red)) are dead) Equal to True) then do ------------ else do (Do nothing)
(執行到All units of Unit Group are dead時,檢查bj_wantDestroyGroup為真,即刪除(Units owned by Player 1 (Red))

如果希望更了解這個方法的運作原理,可以去看Blizzard.j中的函數(搜尋bj_wantDestroyGroup即可以找到許多相關資料)。

使用自訂的Group變數時,有兩大類常見用法:
  • 新增-清空法(重複利用):
    • 初始化時建立Group變數及Group
    • 在此變數中加入部隊(例:Unit Group - Add (Triggering unit) to ToTGroup)
    • 使用此變數
    • 清空此變數
    • 重複或不重複2~4
    此做法屬重複利用的方式,不要把Group刪除
  • 設定-刪除法(非 重複利用):
    • 初始化時建立Group變數及Group
    • 設定此變數的值為某個部隊群組(例:Set ToTGroup = (Last created unit group))
    • 使用此變數
    • 清空此變數
    • 重複或不重複2~4
    此做法非重複利用的方式,可以把Group刪除

此外由於B社的疏失,Units Of Type(GetUnitsOfTypeIdAll)使用此用法會有bug, 不但無法正規選到所要的部隊,也會造成其它地方的bj_wantDestroyGroup判斷出錯。請不要對它這樣用。
舉例來說,這樣的一段程式碼會出問題:
Custom script: set bj_wantDestroyGroup = true
Unit Group - Pick every unit in (Units of type Footman) and do (Actions)
 Loop - Actions
  ……

這裡筆者不想多著墨,因為解釋這個錯誤的詳細結果沒什麼意義,如有興趣請自行查詢Blizzard.j中的GetUnitsOfTypeIdAll函數,並且自己去做實驗。

如果你無法安心地使用這個指令,也可以參照刪除點的作法。同樣要記得加上udg_:
Set TempGroup = (Units owned by Player 1 (Red))
Unit Group - Pick every unit in TempGroup and do (Unit - Kill (Picked unit))
Custom script:  call DestroyGroup(udg_TempGroup)
不要產生物件
要建立部隊於一特定地點,在觸發中是Create Units Facing Angle、Create Units Facing Point,對應的JASS函數則是CreateNUnitsAtLoc、CreateNUnitsAtLocFacingLocBJ。但是在JASS中還有像這樣的函數可用:
native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit
也就是說,你可以給座標,而不用創造點。
例如:Unit - Create 1 Footman for Player 1 (Red) at (Position Of Triggering Unit) facing Default building facing degrees
用JASS可以改寫成:call CreateUnit( Player(0), 'hfoo', GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit), bj_UNIT_FACING )
如果你要寫一整段的JASS,可以考慮用座標來傳送,就可以避免創造不必要的點。 三、記憶體漏失的解決方法(進階篇)
首先先聲明,以下是寫給功力高深,動輒落JASS、耍Custom Script、玩區域變數、搞return bug、……的屌人看的。如果你都沒用到,甚至連前面寫的那幾個名詞都不懂,那麼這不是你該來的地方,乖乖回去玩你的GUI Trigger吧!
  • 明辨是非篇
    有些強者看了這篇教學以後,回去就開始厲行清掃工作,見point砍point、見group殺group……。像這種:
    Player Group - Pick every player in (All allies of Player 1 (Red)) and do (Player - Add 1000 to (Picked player) Current gold)
    他們自然知道要優化成:
    Set TempForce = (All allies of Player 1 (Red))
    Player Group - Pick every player in TempForce and do (Player - Add 1000 to (Picked player) Current gold)
    Custom script: call DestroyForce(udg_TempForce)

    啥?你不知道?那你顯然是只懂第二篇的GUI Trigger player,早說過你不該來這裡了,趕快回去玩GUI吧-o-"
    結果你還是看了……好吧,既然要看就把它看完,不准臨陣脫逃,嘿嘿!
    依此類推:
    Set TempForce = (All players)
    Player Group - Pick every player in TempForce and do (Player - Add 500 to (Picked player) Current gold)
    Custom script: call DestroyForce(udg_TempForce)

    寫完以後就會發現--一切都變得不對勁了!!
    為什麼勒?拜託,見鬼殺鬼、見妖斬妖、見魔屠魔,可不要見神也砍神、見佛照滅佛啊XD

    傳回物件的函數,大致上可分成兩種。一種是先建立一個物件再傳回;另一種是傳回已知物件的記憶體位址。
    像All allies of Player 1 (Red)函數是GetPlayersAllies,我們可在blizzard.j中找到它:
    function GetPlayersAllies takes player whichPlayer returns force
     local force f = CreateForce()
     call ForceEnumAllies(f, whichPlayer, null)
     return f
    endfunction

    由此可知它先建立了一個force,把傳入的玩者的同盟加入,再傳回。所以這個函數實際上產生了一個玩者群組,所以該不該殺? 當然該殺!

    而All players呢?它的函數是GetPlayersAll,我們也可以在blizzard.j中找到它:
    function GetPlayersAll takes nothing returns force
     return bj_FORCE_ALL_PLAYERS
    endfunction

    嘎?怎麼是變數?我們再繼續找,費盡千辛萬苦,終於在InitBlizzardGlobals下找到這兩行:
    function InitBlizzardGlobals takes nothing returns nothing
     set bj_FORCE_ALL_PLAYERS = CreateForce()
     call ForceEnumPlayers(bj_FORCE_ALL_PLAYERS, null)
    endfunction

    這樣了解了嗎?其實B社在地圖初始化之時,就先創造了一個叫bj_FORCE_ALL_PLAYERS的玩者群組,並且把所有的玩者加入。所以我們在地圖中無論呼叫GetPlayersAll幾遍,它都只是傳回同樣一個玩者群組,而不是先創一個玩者群組再傳回。所以當然不能亂殺,不然以後就抓不到了。
    啥?你問我要怎麼寫?啊就不殺啊,事實上這樣寫就沒錯了,很輕鬆愉快,是不是?
    Player Group - Pick every player in (All players) and do (Player - Add 500 to (Picked player) Current gold)

    同樣的,像Playable Map Area、Entire Map也是在初始化就建好等著被人隨便亂叫的變數函數,以後看到可別亂砍。
    而像Triggering Unit、Sold Item、……這種一看就知道是傳回一個已存在的部隊(物品),而不是先創一個再傳回。該怎麼處理不用多說了吧?

    所以以後殺人前要先睜大眼睛,親朋好友不殺、朝廷命官不殺、皇帝寵妃不殺、……咳,扯遠了,總之,不確定的話就先查一下blizzard.j和common.j吧。

    本手冊的觸發器中英對照表中已將會造成記憶體問題的函數以紅底標示,以區域為例,Initial Camera Bounds沒標示,表示它只是傳回一個已存在的物件;
    Region With Offset有標紅底,表示它會先建立一個區域再傳回,這種最好在用完後就把它刪掉。祝屠殺愉快!
  • 要殺乾淨篇
    大家長大後大概多少會寫到像這樣的東西,以下是一個技能觸發的片段:
    Code1:
    function Ampify_Damage_child takes nothing returns nothing
        call SetUnitLifeBJ( GetTriggerUnit(), RMaxBJ(( GetUnitStateSwap(UNIT_STATE_LIFE, GetTriggerUnit()) - GetEventDamage() ), 0.50) )
    endfunction

    function Trig_Ampify_Damage_Actions takes nothing returns nothing
        local trigger trg = CreateTrigger()
        call TriggerRegisterUnitEvent( trg, GetSpellTargetUnit(), EVENT_UNIT_DAMAGED )
        call TriggerAddAction(trg, function Ampify_Damage_child)
        call PolledWait(45.0)
        call DestroyTrigger(trg)
    endfunction

    好啦,我承認範例很爛,JASS的範例很難找咩,不要強人所難了XD

    回歸正題,以上的程式碼是否有記憶體漏失的問題?
    嗯,乍看之下沒有,實際上是有……。基本上它還漏了一個triggeraction。這樣寫才不會有這個問題:

    Code2:
    function Ampify_Damage_child takes nothing returns nothing
        call SetUnitLifeBJ( GetTriggerUnit(), RMaxBJ(( GetUnitStateSwap(UNIT_STATE_LIFE, GetTriggerUnit()) - GetEventDamage() ), 0.50) )
    endfunction

    function Trig_Ampify_Damage_Actions takes nothing returns nothing
        local trigger T = CreateTrigger()
        local triggeraction A =  TriggerAddAction(trg, function Ampify_Damage_child)
        call TriggerRegisterUnitEvent( T, GetSpellTargetUnit(), EVENT_UNIT_DAMAGED )
        call PolledWait(45.0)
        call TriggerRemoveAction(T,A)
        call DestroyTrigger(T)
    endfunction

    所以如果你常常寫那種創臨時觸發來用的JASS,要記得連Action也一起刪除喔。
    common.j中還有一個TriggerClearActions函數,它是把觸發中的動作清空,但是不會真的把動作刪掉。
    我們可以想成,CreateTrigger建立一個觸發傳回;TriggerAddAction建立一個觸發動作,把它連結到該觸發,再傳回觸發動作。
    TriggerRemoveAction把觸發動作和它與觸發的連結關係刪除;TriggerClearActions是把觸發中與所有觸發動作的連結切斷,但是沒有把那些觸發動作刪除。

    等等,……action會造成記憶體漏失,那麼類似的event和condition呢?
    每一個event都會佔不小的空間並造成leak。不過它就像是附著在trigger中的一部分,所以只要刪trigger,event就會一併消失。
    然而,如果一個觸發擺了上百個event,也是很佔資源的。trigger佔用記憶體的量大約為event的1.5倍,換句話說,兩個event就比一個trigger大了,這也是為什麼筆者不推薦全地圖部隊受到傷害事件的原因。
    順帶一提,在觸發中登錄事件是蠻耗資源的事,跑這一類的函數比跑大多數其它的函數慢得多。
    而condition嘛……一般我們在創臨時觸發的時候都不寫condition,把條件直接加在action裡,所以這個問題幾乎不用考慮。

    事實上和triggercondition相較之下,boolexpr反而比較有可能造成問題。我們通常在建立condition時都是這種格式:
回复

使用道具 举报


ADVERTISEMENT

 楼主| 发表于 9-1-2007 10:40 PM | 显示全部楼层
  • Code3:
  • function Trig_Test_Conditions takes nothing returns boolean
        if ( not ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == true ) ) then
            return false
        endif
        return true
    endfunction

    function Trig_Test_Actions takes nothing returns nothing
    endfunction

    //===========================================================================
    function InitTrig_Test takes nothing returns nothing
        set gg_trg_Test = CreateTrigger( )
        call TriggerAddCondition( gg_trg_Test, Condition( function Trig_Test_Conditions ) )
        call TriggerAddAction( gg_trg_Test, function Trig_Test_Actions )
    endfunction

  • Condition( function Trig_Test_Conditions )本身就先製造出一個conditionfunc,而TriggerAddCondition又製造一個triggercondition。那麼真要刪的話,一定刪到頭昏眼花。
    所幸筆者測試conditionfunc和triggercondition的記憶體漏失情形,結果是無法觀測(可能沒有,可能有但是太小),所以這部分可以放心。
  • 變數清空篇
    我們再來問:上面連triggeraction都刪的龜毛函數(code2)還有沒有記憶體問題。答案是:有。
    我勒!@#$()*&@$%&^!@%#!%#@!&^#@,到底要怎麼改才對?要這樣:
     
  • function Ampify_Damage_child takes nothing returns nothing
        call SetUnitLifeBJ( GetTriggerUnit(), RMaxBJ(( GetUnitStateSwap(UNIT_STATE_LIFE, GetTriggerUnit()) - GetEventDamage() ), 0.50) )
    endfunction

    function Trig_Ampify_Damage_Actions takes nothing returns nothing
        local trigger T = CreateTrigger()
        local triggeraction A =  TriggerAddAction(T, function Ampify_Damage_child)
        call TriggerRegisterUnitEvent( T, GetSpellTargetUnit(), EVENT_UNIT_DAMAGED )
        call PolledWait(45.0)
        call TriggerRemoveAction(T,A)
        call DestroyTrigger(T)
        set T = null
        set A = null

    endfunction

  • 這個步驟稱為變數清空(nullifying)。之所以連這個都要做,是由於B社的程式師偷懶,留下區域變數的bug。
    詳細原因後面會說明。只有區域變數會造成這個問題,全域變數不會。例如:

    [區域變數]
  • function MyFunc takes nothing returns nothing
        local trigger T = CreateTrigger()
        call DestroyTrigger(T)
        set T = null
    endfunction

  • [全域變數]
  • function MyFunc takes nothing returns nothing
        set udg_T = CreateTrigger()
        call DestroyTrigger(udg_T)
    endfunction

  • 兩者同樣都不會造成記憶體問題。

    此外,這個問題只發生在物件變數,也就是像整數、實數、字串等都不會有問題。譬如以下這個無聊函數,你給電腦跑幾十萬遍也不會有記憶體問題:
  • function MyFunc takes nothing returns nothing
        local string s = "實在太感謝Danny了!"
        local integer i = 520
        local real r = 5438.49
        local boolean IShouldStudyHarder = true
    endfunction

  • 事實上和物件沒刪相比,物件區域變數沒清空所造成的影響小非常多(筆者測試大約是1/12左右)。更何況使用區域變數的人並沒有那麼多,這個問題幾乎是小到可以忽略,事實上,B社在blizzard.j撰寫的所有函數,也沒有做到這個動作。關於是否有必要清空,可以看個人需要,有些人就是很龜毛,不能容忍一絲一毫的浪費;而有些人則嫌那幾行清空指令太礙眼,目前兩派的人都有,讀者可自己決定要怎麼做。

    此外有人發現,在極少數的情況下,做變數清空以後會影響物件的編碼,造成使用return bug傳回的值發生一些問題,這個現象最常發生在Timer的清空上。解決的辦法當然就是--不要做變數清空。


    以下提供一個簡單的模型解釋記憶體的運作原理與和物件的關係。這個模型只是為了方便說明,未必100%正確(想知道正確理論的請去找B社的程式師);
    而裡面提供的數據僅作為舉例用,未必是確切的數值。電腦方面並非筆者的專業,如果有哪位大大對這方面有更進一步的了解,敬請不吝指教。
  • 變數
編號表
名稱類型內容(物件編號)編號變數連結物件位址
udg_Archmageunit10488021048801021~25
udg_Paladinunit1048807104880221~20
udg_MyHerounit10488021048803131~40
udg_GameTimertimer10488031048804026~30
(local) targetunit104880910488051 
udg_P1location104880810488060 
udg_P2location10488051048807141~60
udg_FootmanGuardunit104880510488081121~125
   1048809181~100
   10488100101~120
回复

使用道具 举报

 楼主| 发表于 9-1-2007 10:42 PM | 显示全部楼层
這是記憶體內部大致的配置情形,它大約是這樣運作的:
  • 刪除物件:假設我們要刪除一個點: call RemoveLocation(udg_P1)。電腦會去讀udg_P1對到的編號表1048808號,把對應的物件121~125區域清空,並且更新編號表。這是刪除後的情形:
    變數編號表
    名稱類型內容(物件編號)編號變數連結物件位址
    udg_Archmageunit10488021048801021~25
    udg_Paladinunit1048807104880221~20
    udg_MyHerounit10488021048803131~40
    udg_GameTimertimer10488031048804026~30
    (local) targetunit104880910488051 
    udg_P1location104880810488060 
    udg_P2location10488051048807141~60
    udg_FootmanGuardunit104880510488081 
       1048809181~100
       10488100101~120
  • 建立物件:假設我們要建立一個山王。電腦會從記憶體中找一大塊可用的位址畫給它用,假設是61~80號。然後搜尋變數連結=0且物件位址為空的空間建立物件。這是建立後的情形:
    變數編號表
    名稱類型內容(物件編號)編號變數連結物件位址
    udg_Archmageunit10488021048801021~25
    udg_Paladinunit1048807104880221~20
    udg_MyHerounit10488021048803131~40
    udg_GameTimertimer10488031048804026~30
    (local) targetunit104880910488051 
    udg_P1location10488081048806061~80
    udg_P2location10488051048807141~60
    udg_FootmanGuardunit104880510488081121~125
       1048809181~100
       10488100101~120
    那個61~80八成就是記錄山王的生命啦、法力啦、技能啦、經驗值啦、……等等有的沒有的資料。
    或許有人會問了:1048805不是沒有物件嗎?為什麼不建立在這裡? 當然這是為了防止bug,想想為什麼1048805有被變數連結卻沒有值?也許它之前是連到一個步兵,後來那個步兵在一場戰鬥中壯烈犧牲了,系統就很聰明地把它從記憶體中挪走。然後變成一開始那樣。但是它仍舊被一個變數udg_FootmanGuard連結,假設我們把山王創造在這個地方,那麼我們會發現,udg_FootmanGuard本來一直是指一個步兵,後來步兵死了,有一天udg_FootmanGuard突然變成一個山王……這當然不合理,步兵死後udg_FootmanGuard理所當然要一直指向空的物件才行。所以只要有被變數連結,那個位置就不能被使用,即使它是空的。
  • 修改變數:假設我們修改變數: set udg_MyHero = udg_Paladin。電腦會把udg_MyHero重新連到1048007號,並且修改編號表中的變數連結個數:
回复

使用道具 举报

 楼主| 发表于 9-1-2007 10:42 PM | 显示全部楼层
變數編號表
名稱類型內容(物件編號)編號變數連結物件位址
udg_Archmageunit10488021048801021~25
udg_Paladinunit1048807104880211~20
udg_MyHerounit10488071048803131~40
udg_GameTimertimer10488031048804026~30
(local) targetunit104880910488051 
udg_P1location104880810488060 
udg_P2location10488051048807241~60
udg_FootmanGuardunit104880510488081121~125
   1048809181~100
   10488100101~120
清空變數:假設我們清空變數: set udg_MyHero = null。電腦會把udg_MyHero連到空號(可能是0),並且修改編號表中的變數連結個數:
變數編號表
名稱類型內容(物件編號)編號變數連結物件位址
udg_Archmageunit10488021048801021~25
udg_Paladinunit1048807104880211~20
udg_MyHerounit01048803131~40
udg_GameTimertimer10488031048804026~30
(local) targetunit104880910488051 
udg_P1location104880810488060 
udg_P2location10488051048807141~60
udg_FootmanGuardunit104880510488081121~125
   1048809181~100
   10488100101~120
刪除區域變數:最後是我們的重點,假設我們的某個函數(其中有一個區域變數target)執行到endfunction,此時target會被清除。
電腦會把target這個變數清掉, 此時1048809的變數連結理當被改成0,但是它並不會(應該是一個bug):
變數編號表
名稱類型內容(物件編號)編號變數連結物件位址
udg_Archmageunit10488021048801021~25
udg_Paladinunit1048807104880221~20
udg_MyHerounit10488021048803131~40
udg_GameTimertimer10488031048804026~30
   10488051 
udg_P1location104880810488060 
udg_P2location10488051048807141~60
udg_FootmanGuardunit104880510488081121~125
   1048809181~100
   10488100101~120

現在,假設我們執行call RemoveUnit(target)以後離開函數,結果變成:
變數編號表
名稱類型內容(物件編號)編號變數連結物件位址
udg_Archmageunit10488021048801021~25
udg_Paladinunit1048807104880221~20
udg_MyHerounit10488021048803131~40
udg_GameTimertimer10488031048804026~30
   10488051 
udg_P1location104880810488060 
udg_P2location10488051048807141~60
udg_FootmanGuardunit104880510488081121~125
   10488091 
   10488100101~120
回复

使用道具 举报

 楼主| 发表于 9-1-2007 10:42 PM | 显示全部楼层
  • 顯然這時候1048809的位置早該被清空等著其它的物件放,可是卻由於這個bug,導致這個空間不能再被使用。
    所以我們只好在區域變數被系統自動刪除前,手動把它的內容清空,使它對應的編號表的變數連結被扣掉。
    如果沒有這樣做,久而久之,就有一大堆空間被佔據住不能使用, 造成記憶體漏失。

    最後再重申一次,這個模型和裡面寫的數字純粹作為舉例用,一個location未必只佔用5個位址;物件也未必是從0開始往上編;編號表也不一定是從1048801開始。
  • 還沒清完篇
    看完上一篇大家感想如何?嗯……以後寫JASS,最後面記得加個幾行清空的指令吧(誰叫你就是愛用區域變數?)。

    不過像這種函數怎麼辦呢?
  • //這個函數是實際可用的,它比blizzard.j中的GetUnitsOfTypeIdAll漂亮且有效率
    function GetUnitsOfType takes integer id returns group
        local group g = CreateGroup()
        call GroupEnumUnitsOfType(g,UnitId2String(id),null)
        return g
    endfunction

  • 當然誰都知道要把區域變數g清空,問題是清空了怎麼回傳group?

    這時候我們可以利用那個leak的特性,以神奇的「包裝法」解決這個問題。因為沒有定義區域變數g,所以自然可以不必清空:
  • //這個函數是實際可用的,它比blizzard.j中的GetUnitsOfTypeIdAll漂亮且有效率
    function GetUnitsOfType_core takes group g, integer id returns group
        call GroupEnumUnitsOfType(g,UnitId2String(id),null)
        return g
    endfunction

    function GetUnitsOfType takes integer id returns group
        return GetUnitsOfType_core(CreateGroup(),id)
    endfunction
     

  • 當然,這個方法也可以用在前面的情形。例如前面的某段程式碼也可以改寫成這樣,不管要定義幾個物件型的全域變數,都可以套用此方法:
  • function Ampify_Damage_child takes nothing returns nothing
        call SetUnitLifeBJ( GetTriggerUnit(), RMaxBJ(( GetUnitStateSwap(UNIT_STATE_LIFE, GetTriggerUnit()) - GetEventDamage() ), 0.50) )
    endfunction

    function Trig_Ampify_Damage_Actions_Core takes trigger T, triggeraction A returns nothing
        set T = CreateTrigger()
        set A = TriggerAddAction(T, function Ampify_Damage_child)
        call TriggerRegisterUnitEvent( T, GetSpellTargetUnit(), EVENT_UNIT_DAMAGED )
        call PolledWait(45.0)
        call TriggerRemoveAction(T,A)
        call DestroyTrigger(T)
    endfunction

    function Trig_Ampify_Damage_Actions takes nothing returns nothing
        call Trig_Ampify_Damage_Actions_Core(null, null)
    endfunction
     


  • 還有另一個方法,就是祭出我們的大神--return bug。像這樣:
  • //這個函數是實際可用的,它比blizzard.j中的GetUnitsOfTypeIdAll漂亮且有效率
    function GetUnitsOfType takes integer id returns group
        local integer g = H2I(CreateGroup())
        call GroupEnumUnitsOfType(I2G(g),UnitId2String(id),null)
        return
    g
        return null
    endfunction
  • 筆者廢話篇
    看完以後有沒有覺得世界末日到了?點、部隊群組、觸發要刪不打緊,連觸發的動作還要另外刪,定區域變數還要清空,如果要回傳甚至還得扯上return bug……
    不過大家大可不必那麼擔心,還記得我們解決記憶體漏失的目的嗎--減少遊戲lag和減少跳出的延遲(後者是我自己偷加的XD)。

    不要看前面說得那麼可怕,那些高頻率的執行對電腦而言只是小事一樁,倒是魔獸各方面的運算,3D貼圖的計算等等反而更耗用資源。小問題累積很久才可能變成大問題,而我們通常在大問題還沒發生以前就結束遊戲了。以點的累積來說,大約10~30萬以上會感覺到明顯的跳出延遲,再更多才會造成遊戲中的lag。區域變數沒清空造成的記憶體漏失與此相比之下 ,更加微不足道(大約是觸發影響力的1/12左右)。而觸發動作(triggeraction)雖然嚴重程度和點差不多,可是它實在是非常非常非常非常不好刪,所以除非你太常用臨時觸發,問題嚴重,否則當做沒看到對身心較有幫助。

    以B社的官方地圖為例,它們其實也只做到刪除點、部隊群組、特效與浮動文字,大致上用到的是本文「實用篇」的方式。其它像觸發、觸發動作、變數清空等,B社根本沒有在管。B社提供的blizzard.j函數中用的區域變數(雖然用到的不多)也沒有做到變數清空。然而大家玩B社出的地圖會很不順 ,或者狂lag嗎?

    記住,我們左刪右刪的最終目的是增加遊戲的順暢度。一條觸發或函數,除非解決記憶體漏失不會太麻煩,或者它的使用頻率實在太高,漏洞很嚴重,才有必要去處理;否則都可以不理它。很多老外天天在寫JASS,常把memory leak掛在嘴邊,主要是為了嚴謹性的考量,不希望有人使用了他們的函數或系統後出現lag等症狀,所以對此方面的要求較高。 筆者建議像點、部隊群組、特效最好都刪掉;而像triggeraction和需要用到return bug來清空變數的麻煩事就免了;其它的……自行判斷。總之我們製作地圖,只要跑起來順就好了,記憶體漏失把主要的做好即可,次要的、不重要的就可以馬虎一點,不必把時間浪費在不重要的事情上。(謎:那你寫這一串廢話不是也在浪費時間嗎? 我:……………………)

    最後,總結一下本篇的重點:
  • 要搞清楚什麼可以刪,什麼不能刪。千萬別把重要的公用物件變數刪除了。
  • 除了trigger以外,event和triggeraction也會造成leak。此外,登錄event比起大多數的函數更耗資源,執行更慢。
  • 處理區域變數造成的微量記憶體漏失:
    • 做奱數清空(nullify)的動作
    • 使用「包裝法」
    • 使用return bug
    • 改用全域變數
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

 

ADVERTISEMENT



ADVERTISEMENT



ADVERTISEMENT

ADVERTISEMENT


版权所有 © 1996-2023 Cari Internet Sdn Bhd (483575-W)|IPSERVERONE 提供云主机|广告刊登|关于我们|私隐权|免控|投诉|联络|脸书|佳礼资讯网

GMT+8, 28-11-2024 05:58 AM , Processed in 0.131345 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表