在人工礦洞內游泳的苦力怕
在昨天展示的程式碼內,有一個createExplosion的方法,該方法產生的方塊破壞效果在Minecraft世界裡稱之為爆炸。正常的爆炸來源 ([Day4] 成為爆破礦工不算正常來源)可能會有苦力怕、終界水晶、以及今天要講的 - TNT方塊。
在我們開發的這個Minecraft版本裡 (1.8),TNT方塊的爆炸範圍預設是4,而且每一次的爆炸效果是不能累加的。
以我們昨天的爆破礦工模組為例,你應該有發現我們每次摧毀一個方塊產生爆炸後,爆炸會以摧毀的那個方塊為中心向四周圍產生爆炸效果與破壞方塊。
因此若是威力設定為2,在沿著座標軸的六個方向各自最大只會產生2個方塊的破壞
(方塊本身也有爆炸抗性,例如在有鵝卵石方塊的地方可能就不會有這樣的爆炸威力)
很複雜,不是嗎?
如果今天我們要用TNT來爆破山頭、開鑿山洞、開挖一個游泳池等,你會需要計算TNT的爆炸範圍、修補不需要被破壞的方塊等等,光想就讓人覺得非常的吃力,因此今天我們就要學習如何讓這樣的爆破動作是可以被"控制"的。
當然,假如你效仿愚公移山的精神想要自己慢慢挖,接下來的篇幅你可以當作參考即可,畢竟在麥塊的世界沒有能不能 - 只有要不要做!
開始之前,我們先來思考我們要做的事情以及設計的方向:
大概有個思路後,請先打開你的專案,由於我們現在要做更有趣的功能,先將那個惱人的自嗨模組從Main檔案註解掉:
@EventHandler
public void init(FMLInitializationEvent event) {
// 透過事件巴士註冊事件
// MinecraftForge.EVENT_BUS.register(new BreakBlockEvent());
TNTExplosions
,並在主程式內註冊此Class
MinecraftForge.EVENT_BUS.register(new TNTExplosions());
explodeHead
,並且給它@SubscribeEvent
的標註
@SubscribeEvent
public void explodeHead(???Event event) {
}
Ctrl+H
快速尋找可用的Event。因為我們需要將爆炸的威力提高,而這件事情只有自行呼叫createExplosion才有用 (TNT只允許你產生4格的爆炸範圍),因此整個流程會有點像
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
);
}
}
}
前面一個例子產生的效果很壯觀,但我們的主題是:爆破的藝術
剃山頭還不能說是一個藝術阿!
那我們試想想:假如我今天站在一個山壁前,在地上放了一個可以產生爆炸的TNT方塊。啟動它後,在一片白煙消散時,映入眼簾的是一個深不見底的隧道,感覺踏進去就會被帶入另一個世界裡...
這樣,有藝術的畫面了嗎?
先將我們剛剛剃山頭方法explodeHead去除標註,避免等等的藝術品不小心被破壞
// @SubscribeEvent
public void explodeHead(EntityJoinWorldEvent event) {
...
起手式,建立我們的爆破隧道方法explodeTunnel,給它加上@SubscribeEvent
標註
在ExplosionEvent事件下有一個Detonate子事件:該方法內提到"這個事件在爆炸後有產生影響的方塊與實體時執行一次,並且可以改變影響的方塊"。這個就是我們要的 - 控制
接下來就是去操作在這個事件下有影響的方塊。這裡有個很重要的觀念要先提:座標
請在在遊戲內按下F3
後,會出現一些遊戲的資訊。找到在下圖紅框處的XYZ欄位
為什麼要說這個呢?因為等等我們要去指定要被破壞的方塊,而方塊會需要透過XYZ座標來指定你的方塊是誰。以下的程式會以放置TNT的方塊作為"隧道正中央最下方"的相對位置來去判斷。
我期望的隧道形狀會長類似下圖的樣子 (我用PPT做示意圖,畫不好請見諒)
不囉嗦,直接放上程式碼。相關的一些說明都在程式碼內,我盡可能的說明我想做到的功能,有不清楚的地方歡迎留言
@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);
}
}
}
}
儲存,進入遊戲,隨意找一個"面向南方"的山壁 (即F3
功能打開後,紅色X軸線應該在你的左手方向或是你可以看到"Facing: south"),看看我們製造出來的隧道長什麼樣吧!
今日額外課題:上面的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);
}
}
}
}
我自己是 Minecraft 的玩家,我覺得這系列很有趣XD,真的會想跟著做做看
謝謝支持喔 :)
如果有什麼有趣的想法也歡迎留言~