iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 20
2

Minecraft模組系列寫到這裡,對我自己來說也是一個挑戰 (我玩Minecraft的資歷非常淺);有很多功能都是一邊玩,一邊寫;如果發現了有趣的功能,就自己嘗試動手做看看。

但我們今天先停下來,重新審視一下現有程式碼,當功能越來越多的時候,把現有程式精簡化也是很重要的一環。

這裡的重新開始不會叫你把現有程式都砍光光的...別緊張。

我們來回顧一下到目前為止做了哪些功能:

  1. 建立打破方塊能夠產生訊息
  2. 建立打破方塊可以產生爆炸效果
  3. 讓爆破事件可以有自定義的範圍
  4. 客製化實體(豬)生成時產生的行為
  5. 變更實體跳躍的行為
  6. 客製化指令
  7. 用指令產生客製化建築
  8. 建立新的方塊
  9. 建立新的物品
  10. 自定義合成、燒煉與釀造

功能很多,我們在接下來開發新的功能之前,要先回過頭來看一下我們的目標:設計遊戲

觀察問題

那麼,我們現在的程式碼有什麼樣的問題呢?

  1. 模組內的功能散落各處,難以區別功能是屬於哪一種。
    上面提到的那10項功能大部分我們目前都與主程式放在同一層,需要想一個方式做一些區別,避免都混在一起。
    https://ithelp.ithome.com.tw/upload/images/20191005/201158237xFlZgVhvD.png
  2. 客戶端與伺服器端的程式碼難以區別。
    我們在某些地方會使用if (event.entity.worldObj.isRemote)之類的方式來限制執行事件的只能是客戶端或伺服器端,但這有點不直覺且麻煩。在Forge模組開發有一個叫做Proxy的功能可以幫助我們把這些繁雜的工作完成。
  3. 大致上看上面兩個問題比較明顯,如果未來有發現會再補上。

思考做法

接下來,我們就動工吧?

別緊張,讓我們先了解什麼是Proxy...之前,不知道會不會有人跟我一樣有一個疑問:到底Minecraft Forge在啟動遊戲的時候是怎麼知道要從哪裡開始執行,以及是如何執行的?

Forge啟動流程

  1. 我們在用IDE開發遊戲,並使用Forge提供的Minecraft Client啟動遊戲後,應該會在下方Run執行的訊息視窗看到類似下面的訊息:
    ...
    [Client thread/INFO] [FML]: Found 0 mods from the command line. Injecting into mod discoverer
    [Client thread/INFO] [FML]: Searching D:\minecraft_forge\myproject\run\mods for mods
    [Client thread/INFO] [myFancyMods]: Mod myFancyMods is missing the required element 'name'. Substituting myFancyMods
    ...
    
    這裡Forge是透過java的一種程式碼映射(Reflection)的功能,這個功能可以在不知道名稱的情況下,透過指定的條件(例如我們的標註@Mod)來去尋找所有滿足條件的介面、類別、方法等。
  2. 當透過@Mod找到主程式後,Forge接下來會去找尋是否有Proxy(下面會說)的定義存在;如果有,會透過Proxy去初始化並設定必要的變數。
  3. 都設定完開始啟動時,Forge接下來會再透過映射去尋找有@EventHandler標註的方法來建立所有在啟動前需要完成的事情,這個標註會有三個階段分別完成不同的事:
    • 前置初始化(PreInitialization):這個階段可以想成是準備Minecraft的所有物體。舉凡讀取設定檔、建立方塊、物品,註冊物體到Minecraft等。
    • 初始化(Initialization):這個階段是模組設置。例如註冊事件處理、註冊資源、建立配方(合成、燒煉與釀造)。
    • 後期初始化(PostInitialization):這個階段主要是與其他模組互動。如果有任何其他需要處理的的功能,或是要建立與其他模組的相依關係。
  4. 如果到這邊都沒問題,接下來就會直接進入遊戲了~

Proxy

那讓我們來了解一下到底什麼是Proxy:
在Minecraft設計裡,可以同時有多名玩家一起遊玩。所以很明顯的,玩家端會有畫面的產生、控制滑鼠與鍵盤的動作等等,就是客戶端(Client);而伺服器端(Server)就是負責世界的產生、處理不同事件等等。

我們在開發時雖然只有一台電腦,但他其實同時扮演著Client與Server的角色。
Client與Server都有自己專屬的程式碼,但他們可以執行相同的方法(這也是為什麼我們在前面幾天會需要透過IF判斷的方式來處理不同端的請求)。

這個時候,Forge聽到開發者的心聲,Proxy就是用來幫忙開發者方便分辨誰要處理Client與Server。

而使用的方法,我想你已經猜到了,就是使用標註:@SideProxy

開始動工

注意:以下我們要做的事情是重構程式碼,假如你是直接從這一篇開始看的人,請自行參考對應的[DayXXX]

  1. [Day3]我們建立了一個主程式Main.java檔案,在Forge的模組開發中,一般都會習慣將主程式命名與套件名稱相同。因此請在com.ithome.mymod這個套件下建立一個新的Class名為MyMod,並將Main這個類別內的@Mod標註拿掉,這樣之後啟動就不會找到這個模組了:

    https://ithelp.ithome.com.tw/upload/images/20191005/20115823DD6YFebhcc.png

    原本的Main.java檔案內容,把@Mod加上註解。

    // 透過註釋@Mod告訴Forge這個是模組的主要檔案
    //@Mod(modid = Main.MODID, version = Main.VERSION)
    public class Main {
    ...(以下略)
    
  2. 打開MyMod檔案,跟[Day3]一樣,定義Modidnameversion

    package com.ithome.mymod;
    
    import com.ithome.mymod.configs.Constants;
    import net.minecraftforge.fml.common.Mod;
    
    @Mod(modid = Constants.MOD_ID, name = Constants.MOD_NAME, version = Constants.VERSION)
    public class MyMod {
    }
    

    建立一個新的Constaints檔案在com.ithome.mymod.configs套件下,這個檔案是用來定義所有會用到的字串(包含我們前一個步驟的modid, name, version)。
    https://ithelp.ithome.com.tw/upload/images/20191005/20115823WiVjHx1T9m.png

    package com.ithome.mymod.configs;
    
    public class Constants {
    
        // Mod info
        public static final String MOD_ID = "mymod";
        public static final String MOD_NAME = "My Mod";
        public static final String VERSION = "1.0";
    }
    

    注意這裡我們使用小寫來定義模組ID,不像Main這個檔案有用大小寫。理由我在[Day14]的註冊方塊一節有說明過了。

  3. 接著把Proxy功能加上。記得我們在前面說過,要將不同功能的類別分類,比較好的方式就是建立獨立的目錄。我們先在MyMod檔案內,加上@SideProxy標註與proxy變數:

    package com.ithome.mymod;
    
    import com.ithome.mymod.configs.Constants;
    import com.ithome.mymod.proxy.IProxy;
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.common.SidedProxy;
    
    @Mod(modid = Constants.MOD_ID, name = Constants.MOD_NAME, version = Constants.VERSION)
    public class MyMod {
    
        @SidedProxy(serverSide = Constants.SERVER_PROXY, clientSide = Constants.CLIENT_PROXY)
        public static IProxy proxy;
    }
    
  4. 然後把Client與Server的類別名稱完整字串放入到Constants類別中:

    package com.ithome.mymod.configs;
    
    public class Constants {
    
        // Mod info
        public static final String MOD_ID = "mymod";
        public static final String MOD_NAME = "My Mod";
        public static final String VERSION = "1.0";
    
        // Proxy
        public static final String CLIENT_PROXY = "com.ithome.mymod.proxy.ClientProxy";
        public static final String SERVER_PROXY = "com.ithome.mymod.proxy.ServerProxy";
    }
    
  5. 最後再建立三個檔案:

    • IProxy : Proxy介面,用來定義不管是Client或是Server都需要的方法,以及必須要時做的方法都寫在這裡。
    • ClientProxy : Client端的Proxy
    • ServerProxy : Server端的Proxy
      https://ithelp.ithome.com.tw/upload/images/20191005/20115823gMt4n3TqON.png

    IProxy.java

    package com.ithome.mymod.proxy;
    
    public interface IProxy {
    }
    

    ClientProxy.java

    package com.ithome.mymod.proxy;
    
    public class ClientProxy implements IProxy {
    }
    

    ServerProxy.java

    package com.ithome.mymod.proxy;
    
    public class ServerProxy implements IProxy {
    }
    
  6. 存檔進入遊戲,如同[Day3]一樣,現在你應該要看到我們新的模組mymod出現了!

https://ithelp.ithome.com.tw/upload/images/20191005/20115823KNfcTKss4J.png


上一篇
[Day19] 燒煉與釀造
下一篇
[Day21] 整理現有程式碼(上)
系列文
[Minecraft - 當個創世神] 從玩遊戲到設計遊戲30

尚未有邦友留言

立即登入留言