iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 29
3
Software Development

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

[Day29] Trick or treat!

https://ithelp.ithome.com.tw/upload/images/20191014/20115823u7J0mlhBMW.png

不給糖就搗蛋!

這不是崇洋媚外的心態,在遊戲內純粹就是好玩的心理。為了應景一下,在我們目前的模組我想要做到以下的功能:

  • 將目前現有的其他所有模組功能Disable,我們不需要其他的功能來影響萬聖節。
  • 將預設的時間更改為半夜,這樣才有萬聖節的氣息。
  • 生物 Mobs產生發光的效果,就像拿著南瓜燈籠一樣。
  • 生物碰觸到玩家不會有任何攻擊行為,不然你就沒辦法給糖吃了。
  • 生物碰觸到玩家會丟訊息:Trick or treat

好了,讓我們一步一步地完成上面的工作吧!

將目前現有的其他所有模組功能Disable

這個功能我們在[Day24]已經有提過了,我想要達成這個目的很簡單:在進入遊戲後,直接在Mods設定中的將功能全部移除:
https://ithelp.ithome.com.tw/upload/images/20191014/20115823m47Wy9Eb34.png

將預設的時間更改為晚上

這個功能有一點麻煩,但我們先想想要怎麼做以及思考方向:

有了以上的想法後,我們可以透過在講指令時提過,任何指令類別一定要繼承CommandBase;透過Ctrl + H查找CommandBase的子類別,就會找到下面兩個與我們提到要變動時間的工作有關的指令類別:

  • net.minecraft.command.CommandTime : 指令**/time**類別
  • net.minecraft.command.CommandGameRule : 指令**/gamerule**類別

這兩個類別裡面就已經很清楚的說明如何用指令改時間了,所以我們就將對應的功能放到ClientProxy內的FMLServerStartingEvent事件:

ClientProxy.java

...(略)
@Override
public void serverStarting(FMLServerStartingEvent event) {
    // 0 means the OverWorld (主世界)
    // -1 means the Nether (地獄)
    // 1 means the Ent (終界)
    WorldServer world = event.getServer().worldServerForDimension(0);

    // 更改時間為半夜0時 (18000 = 第15分鐘 * 60秒 * 20刻)
    world.setWorldTime(18000);

    // 將gamerule中的日夜交替動作取消
    world.getGameRules().setOrCreateGameRule("doDaylightCycle", "false");
}
..(略)

這個時候存檔進入遊戲會看到的是月亮在正上方(午夜),並且時間不會變化。

將生物產生發光的效果

這個想法出現後我馬上就後悔了...因為實在是太花我的時間去思考要如何達成。
但是,最後還是有了一個我想要的效果出來了/images/emoticon/emoticon37.gif

我們這邊只以苦力怕作為範例。

Minecraft要自定義不存在的效果是非常困難的,因此首先一定是先想:是什麼現有的東西也會發光,讓我們可以去參考呢?

我想到的是:火把
當然你也有可能想到紅石火把、營火等等,這些東西都有一個共通的特性:都是方塊(Block)

我們要改的可是生物(Entity / Mobs)的亮度阿!

所以這裡我找了一下資料,大概的作法都是:

  1. 在生物在世界中更新的事件LivingUpdateEvent處理我們的邏輯。
  2. 在該生物的位置產生一個"隱形"方塊(a.k.a 空氣方塊),並且將該方塊產生"亮度"效果。
  3. 將該方塊隨著生物移動而移動

相關的作法只要找一下都可以參考,我這邊只引用我需要的部分:

  1. com.ithome.mymod.events套件目錄下新增LivingUpdate類別
  2. 在該類別實作LivingUpdateEvent事件的方法:
    public class LivingUpdate {
        @SubscribeEvent
        public void livingUpdate(LivingEvent.LivingUpdateEvent event) {
            if(event.entity instanceof HalloweenCreeper) {
                BlockPos pos = event.entity.getPosition();
                addLight(event.entity.worldObj, pos.getX(), pos.getY(), pos.getZ(), 10);
            }
        }
    
        private void addLight(World world, int posX, int posY, int posZ, int lightLevel) {
            world.setLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ), lightLevel);
            world.markBlockRangeForRenderUpdate( posX, posY, posZ, 12, 12, 12);
            world.updateBlockTick(new BlockPos( posX,  posY, posZ),
                    world.getBlockState(new BlockPos( posX,  posY, posZ)).getBlock(), 1, 0);
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos( posX, posY + 1, posZ));
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos( posX, posY - 1, posZ));
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX + 1, posY, posZ));
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX - 1, posY, posZ));
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ + 1));
            world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ - 1));
        }
    }
    
  3. MyMod.java主程式內的eventTriggerMap物件內,新增我們的事件註冊:
    MyMod.eventTriggerMap.put(new LivingUpdate(), true);
    

生物碰觸到玩家不會有任何攻擊行為

我們這邊只以苦力怕作為範例。

這個功能與我們在[Day27]提到的"孤僻貓"很像;要做的只是將所有的AI工作移除後,只保留會"追"玩家的部分就好:

  1. 新建HalloweenCreeper類別到com.ithome.mymod.entities套件下,繼承EntityCreeper並且把所有AI工作先移除:
    public class HalloweenCreeper extends EntityCreeper {
        public static String name = "HalloweenCreeper";
    
        public HalloweenCreeper(World worldIn) {
            super(worldIn);
            setupUI();
        }
        private void setupUI() {
            this.tasks.taskEntries.clear();
            this.targetTasks.taskEntries.clear();
        }
    }
    
  2. 回到MyMod.java主程式,註冊新的HalloweenCreeper實體,在preInit方法內加上:
    // 註冊HalloweenCreeper
    while (EntityList.getClassFromID(availableId) != null) {
        availableId++;
    }
    EntityRegistry.registerGlobalEntityID(HalloweenCreeper.class, HalloweenCreeper.name, availableId);
    

生物碰觸到玩家會丟訊息:Trick or treat

丟訊息的AI工作就比較簡單了:

  1. 建立EntityAIHalloween類別在com.ithome.mymod.ai下:
    EntityAIHalloween.java
    public class EntityAIHalloween extends EntityAIBase {
        private final EntityMob entityMob;
        // 判斷碰撞的時間,避免太頻繁執行
        public static long timer;
    
        public EntityAIHalloween(EntityMob entityMob) {
            this.entityMob = entityMob;
            // 7可以與大部分的AI工作同時發生
            setMutexBits(7);
        }
    
        @Override
        public boolean shouldExecute() {
            if(entityMob.getAttackTarget() != null) {
                EntityLivingBase target = entityMob.getAttackTarget();
                float distance = entityMob.getDistanceToEntity(target);
                if(distance < 2.0F) {
                    return true;
                }
            }
            return false;
        }
        @Override
        public void startExecuting() {
            EntityPlayer player = (EntityPlayer)entityMob.getAttackTarget();
            // 往碰撞的玩家送出訊息
            player.addChatComponentMessage(new ChatComponentText(EnumChatFormatting.LIGHT_PURPLE + "Trick or treat!"));
            // 設定執行時間
            timer = System.currentTimeMillis();
        }
        @Override
        public boolean continueExecuting() {
            // 若當前時間與執行時間相差在2秒內,就繼續這個AI工作來避免下一輪的觸發 (避免太頻繁)
            return System.currentTimeMillis() - timer < 2 * 1000;
        }
    }
    
  2. 更新HalloweenCreeper類別,加上必要的AI工作:
    private void setupUI() {
        this.tasks.taskEntries.clear();
        this.targetTasks.taskEntries.clear();
    
        // 送訊息給玩家的工作
        this.tasks.addTask(0, new EntityAIHalloween(this));
        // 閒晃
        this.tasks.addTask(1, new EntityAIWander(this, 0.8D));
        // 尋找玩家與靠近
        this.tasks.addTask(2, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
        this.tasks.addTask(3, new EntityAIAttackOnCollide(this, 1.0D, false));
        // Idle狀態
        this.tasks.addTask(4, new EntityAILookIdle(this));
        // 設定目標的工作
        this.targetTasks.addTask(0, new EntityAINearestAttackableTarget(this, EntityPlayer.class, true));
    }
    

後續還有很多的功能可以自行發揮,這裡就完成一個簡單版的Halloween!


上一篇
[Day28] 載入其他模組
下一篇
[Day30] 麥塊之魂與你同在
系列文
[Minecraft - 當個創世神] 從玩遊戲到設計遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
konekoya
iT邦新手 5 級 ‧ 2019-10-15 09:04:17

只剩最後一天了,加油!

/images/emoticon/emoticon01.gif

我要留言

立即登入留言