iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
3
Software Development

[Minecraft - 當個創世神] 從玩遊戲到設計遊戲系列 第 5

[Day5] 爆破也可以很有藝術

在人工礦洞內游泳的苦力怕
https://ithelp.ithome.com.tw/upload/images/20190918/201158234muWD3OhAV.png

在昨天展示的程式碼內,有一個createExplosion的方法,該方法產生的方塊破壞效果在Minecraft世界裡稱之為爆炸。正常的爆炸來源 ([Day4] 成為爆破礦工不算正常來源)可能會有苦力怕終界水晶、以及今天要講的 - TNT方塊

在我們開發的這個Minecraft版本裡 (1.8),TNT方塊的爆炸範圍預設是4,而且每一次的爆炸效果是不能累加的。
以我們昨天的爆破礦工模組為例,你應該有發現我們每次摧毀一個方塊產生爆炸後,爆炸會以摧毀的那個方塊為中心向四周圍產生爆炸效果與破壞方塊。
因此若是威力設定為2,在沿著座標軸的六個方向各自最大只會產生2個方塊的破壞
(方塊本身也有爆炸抗性,例如在有鵝卵石方塊的地方可能就不會有這樣的爆炸威力)
https://ithelp.ithome.com.tw/upload/images/20190917/20115823xnquVXFtDR.png

很複雜,不是嗎?

如果今天我們要用TNT來爆破山頭、開鑿山洞、開挖一個游泳池等,你會需要計算TNT的爆炸範圍、修補不需要被破壞的方塊等等,光想就讓人覺得非常的吃力,因此今天我們就要學習如何讓這樣的爆破動作是可以被"控制"的。

當然,假如你效仿愚公移山的精神想要自己慢慢挖,接下來的篇幅你可以當作參考即可,畢竟在麥塊的世界沒有能不能 - 只有要不要做!

來剃山頭

開始之前,我們先來思考我們要做的事情以及設計的方向:

  • 做的事情
    1. 剃山頭,我們會需要找一個類似這種 Λ 形狀的地方,來完成我們的大業
    2. TNT方塊要放在山頭上
    3. 爆炸後要能夠將整個山頭移平
  • 設計方向
    1. TNT爆炸的威力要足夠才能剷平山頭 (例如20)

大概有個思路後,請先打開你的專案,由於我們現在要做更有趣的功能,先將那個惱人的自嗨模組從Main檔案註解掉:

    @EventHandler
    public void init(FMLInitializationEvent event) {
        // 透過事件巴士註冊事件
        // MinecraftForge.EVENT_BUS.register(new BreakBlockEvent());
  1. 接著建立新的檔案到com.ithome.mymod,命名為TNTExplosions,並在主程式內註冊此Class
    MinecraftForge.EVENT_BUS.register(new TNTExplosions());
    
  2. 建立新的方法explodeHead,並且給它@SubscribeEvent的標註
    @SubscribeEvent
    public void explodeHead(???Event event) {
    }
    
  3. 問題來了,我們這邊要處理什麼事件呢?
    還記得在Day3提的小功能嗎?可以用Ctrl+H快速尋找可用的Event。因為我們需要將爆炸的威力提高,而這件事情只有自行呼叫createExplosion才有用 (TNT只允許你產生4格的爆炸範圍),因此整個流程會有點像
    • 置放TNT方塊 -> 啟動TNT -> 當TNT啟動後,產生一個實體的物品(EntityItem) -> 實體的物品如果是我們的TNT方塊,則呼叫createExplosion來產生更大的爆炸
  4. 看起來很單純,直接放上程式碼,相關的說明在註解內。記得,如果程式碼有產生紅色底線的錯誤可能是你沒有import需要的dependencies,請透過Alt+Enter並選擇Import Class來匯入喔!
    public class TNTExplosions {
    
     // 威力使用20格
     private float power = 20;
    
     @SubscribeEvent
     public void explodeHead(EntityJoinWorldEvent event) {
         // 只處理加入到世界的實體是TNT : 這裡即是指TNT方塊被啟動後閃爍的TNT"實體"
         if (event.entity instanceof EntityTNTPrimed) {
             Entity entity = event.entity;
             // 建立我們想要的爆炸威力
             event.entity.worldObj.createExplosion(
                     entity,
                     entity.posX,
                     entity.posY,
                     entity.posZ,
                     power,
                     true
             );
         }
     }
    } 
    
  5. 剷平山頭的比較圖。似乎有點理的太光了 /images/emoticon/emoticon10.gif

https://ithelp.ithome.com.tw/upload/images/20190918/20115823ZNIH56pbuh.pnghttps://ithelp.ithome.com.tw/upload/images/20190918/20115823o9gCtJlPzu.png

爆破山洞

前面一個例子產生的效果很壯觀,但我們的主題是:爆破的藝術

剃山頭還不能說是一個藝術阿!/images/emoticon/emoticon40.gif

那我們試想想:假如我今天站在一個山壁前,在地上放了一個可以產生爆炸的TNT方塊。啟動它後,在一片白煙消散時,映入眼簾的是一個深不見底的隧道,感覺踏進去就會被帶入另一個世界裡...

這樣,有藝術的畫面了嗎?

  1. 先將我們剛剛剃山頭方法explodeHead去除標註,避免等等的藝術品不小心被破壞

    // @SubscribeEvent
    public void explodeHead(EntityJoinWorldEvent event) {
    ...
    
  2. 起手式,建立我們的爆破隧道方法explodeTunnel,給它加上@SubscribeEvent標註

  3. 在ExplosionEvent事件下有一個Detonate子事件:該方法內提到"這個事件在爆炸後有產生影響的方塊與實體時執行一次,並且可以改變影響的方塊"。這個就是我們要的 - 控制
    https://ithelp.ithome.com.tw/upload/images/20190918/20115823yhmdBwNhBn.png

  4. 接下來就是去操作在這個事件下有影響的方塊。這裡有個很重要的觀念要先提:座標
    請在在遊戲內按下F3後,會出現一些遊戲的資訊。找到在下圖紅框處的XYZ欄位
    https://ithelp.ithome.com.tw/upload/images/20190918/20115823FrNFUmRGCW.png

    • 你可以對應在中央鵝卵石有一個由"紅綠藍"線條組合的座標來看
      • 紅色線條代表的是X軸、東西向、東為(+)、西為(-)
      • 綠色線條代表的是Y軸、高低向、高為(+)、低為(-)
      • 藍色線條代表的是Z軸、南北向、南為(+)、北為(-)
    • 以目前這張截圖來看,我的位置是在從整個地圖原點(0,0,0)相對的座標往東約121個方塊處、從遊戲最底部往上69個方塊處、從原點往北約304個方塊處,面向西的方向 (實際上是西北)

    為什麼要說這個呢?因為等等我們要去指定要被破壞的方塊,而方塊會需要透過XYZ座標來指定你的方塊是誰。以下的程式會以放置TNT的方塊作為"隧道正中央最下方"的相對位置來去判斷。

  5. 我期望的隧道形狀會長類似下圖的樣子 (我用PPT做示意圖,畫不好請見諒)
    https://ithelp.ithome.com.tw/upload/images/20190918/20115823AFxJQN3T7x.png

  6. 不囉嗦,直接放上程式碼。相關的一些說明都在程式碼內,我盡可能的說明我想做到的功能,有不清楚的地方歡迎留言

    @SubscribeEvent
    public void explodeTunnel(ExplosionEvent.Detonate event) {
        // 同樣只讓這個事件操作一次
        if (event.world.isRemote) {
            return;
        }
    
        // 取得產生爆炸的方塊位置
        Vec3 eventPos = event.explosion.getPosition();
        // 將要被破壞的方塊全部清空,我們自行定義
        event.getAffectedBlocks().clear();
        // 自定義一個toTunnel方法,動態加入我們想要影響的方塊
        toTunnel(event.getAffectedBlocks(), eventPos);
    }
    
    private void toTunnel(List<BlockPos> affectBlocks, Vec3 originPos) {
        // 建立一個用來存我們隧道形狀的Map
        // 這邊第一個Integer參數,指的是X軸相對TNT方塊的偏移量
        // 這邊第二個List<Integer>參數,指的是Y軸相對TNT方塊的偏移量
        Map<Integer, List<Integer>> destroyedBlockPosXY = new HashMap<Integer, List<Integer>>();
    
        // Z軸,也就是南北方向,偏移量都是從0到10個方塊 (也就是深度為10)
        // 這裡是正數,表示是一個"面向南方"的隧道
        final List<Integer> z_list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
        // 製造一個X軸左右各4個方塊長、高最多4個方塊長的隧道 (倒U形狀)
        // 這邊每一個都對應下面提到的(x, y_list)
        destroyedBlockPosXY.put(-4, Arrays.asList(0));
        destroyedBlockPosXY.put(-3, Arrays.asList(0, 1));
        destroyedBlockPosXY.put(-2, Arrays.asList(0, 1, 2));
        destroyedBlockPosXY.put(-1, Arrays.asList(0, 1, 2, 3));
        destroyedBlockPosXY.put(0, Arrays.asList(0, 1, 2, 3, 4));
        destroyedBlockPosXY.put(1, Arrays.asList(0, 1, 2, 3));
        destroyedBlockPosXY.put(2, Arrays.asList(0, 1, 2));
        destroyedBlockPosXY.put(3, Arrays.asList(0, 1));
        destroyedBlockPosXY.put(4, Arrays.asList(0));
    
        // 透過lambda的forEach方法去輪巡我們XYZ軸的偏移量列表
        // 先取每一個上面寫的(x, y_list)的值,這邊是為了取得X偏移值
        for(Map.Entry<Integer, List<Integer>> entry : destroyedBlockPosXY.entrySet()) {
            int x = entry.getKey();
            List<Integer> y_list = entry.getValue();
            // 輪巡y_list的每一個Y偏移值
            for(int y : y_list) {
                // 輪巡z_list的每一個Z偏移值
                for(int z : z_list) {
                    // 程式碼到此處,(X,Y,Z)的點產生出來的形狀就會像是深度10、寬跟高各4的隧道
                    // 透過偏移量與TNT方塊的座標(originPos)來取得我們想要破壞的方塊位置
                    BlockPos blockPos = new BlockPos(originPos.xCoord + x, originPos.yCoord + y, originPos.zCoord + z);
                    // 加入該被破壞的方塊到受影響的方塊列表內
                    affectBlocks.add(blockPos);
                }
            }
        }
    }
    
  7. 儲存,進入遊戲,隨意找一個"面向南方"的山壁 (即F3功能打開後,紅色X軸線應該在你的左手方向或是你可以看到"Facing: south"),看看我們製造出來的隧道長什麼樣吧!
    https://ithelp.ithome.com.tw/upload/images/20190918/20115823RWYXQQUkJ5.png
    https://ithelp.ithome.com.tw/upload/images/20190918/20115823dWF9YKgP0b.png


今日額外課題:上面的toTunnel方法沒辦法讓我們客製化隧道的高度與深度,請試著改用下面的方法來製作"高為6、深度為20"的隧道吧!

private void toTunnel_v2(List<BlockPos> affectBlocks, Vec3 originPos, int height, int depth) {
    for (int x = -height; x <= height; x++) {
        for (int y = -height; (y <= x && x <= 0) || (Math.abs(y) >= x && x > 0); y++) {
            int trueY = height - Math.abs(y);

            for (int z = 0; z <= depth; z++) {
                BlockPos b = new BlockPos(originPos.xCoord + x, originPos.yCoord + trueY, originPos.zCoord + z);
                affectBlocks.add(b);
            }
        }
    }
}

上一篇
[Day4] 成為爆破礦工
下一篇
[Day6] Minecraft版的俄羅斯娃娃(上)
系列文
[Minecraft - 當個創世神] 從玩遊戲到設計遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0

我自己是 Minecraft 的玩家,我覺得這系列很有趣XD,真的會想跟著做做看

謝謝支持喔 :)
如果有什麼有趣的想法也歡迎留言~

我要留言

立即登入留言