Kurs Minecraft – Wprowadzenie do tworzenia dodatków: Blok sera od zera

Kurs Minecraft – Wprowadzenie do tworzenia dodatków: Blok sera od zera

Co byś powiedział na napisanie własnego moda do Minecraft’a? W tym artykule implementuję modyfikację dodającą do gry blok sera. To stosunkowo prosty tutorial, który doskonale obrazuje od czego można zacząć tworzenie bardziej skomplikowanego moda.

1. Jak zabrać się za pisanie moda do Minecraft

W związku z tym, że Minecraft jest grą napisaną w języku JAVA, to również dodatki najprościej będzie Ci pisać właśnie w tym języku. Warto, abyś w tym celu, dobrał sobie wygodne IDE, najlepiej aby wspierało importowanie projektów gradle (o tym dlaczego, opowiem za chwilę).

Obecnie do pisania dodatków do Minecraft warto wykorzystać Minecraft Forge, czyli specjalnie przygotowane API do implementacji mod’ów. Masz wówczas możliwość skorzystania z dostarczonego Mdk (Mod Development Kit), na którego składa się projekt gradle zawierający taski do konfiguracji środowiska programistycznego. Mdk zawiera również przykład implementacji prostego moda, który możesz zbudować i uruchomić wspomnianymi taskami gradle.

2. Przygotuj się

Już za chwilę przejdziemy do omówienia struktury plików i zasobów naszego moda. Jeśli masz zamiar wykonać ten tutorial na własnym środowisku, to właśnie jest moment, w którym wypadałoby je przygotować.

2.1. Skompletuj narzędzia

Do samego developmentu będziesz potrzebować dowolnego IDE Java. Proponuję IntelliJ IDEA, ponieważ jest to obecnie najpopularniejsze i moim zdaniem najwygodniejsze IDE do Java. Następnie pobieramy Minecraft Forge Mdk w wersji 1.13.2. Pamiętajcie aby uważać przy pobieraniu i nie klikać w podejrzane reklamy czy popupy. Niestety strona pobierania jest tragiczna.

2.2. Przejrzyj dokumentację

Zanim rozpoczniemy implementację, proponuję abyś przejrzał dokumentację Minecraft Forge. Nie jest ona zbyt obszerna, więc taki pobieżny przegląd zajmie Ci nie więcej niż kilka minut, a dzięki temu będziesz w stanie bardzo szybko skojarzyć powiązanie między właśnie implementowanym kodem, a koncepcją jaka za tym stoi. Pozwoli Ci to następnie trafniej zadawać pytania o bardziej zaawansowane kwestie.

O ile wspomnianą wcześniej dokumentacje można traktować jako taki Big Picture pisania mod’ów do Minecraft o tyle zapoznanie z wpisem Modele Bloków znacznie ułatwi zrozumienie dalszej części tego artykułu.

3. Implementacja

Posiadając już solidne podstawy teoretyczne pora napisać naszego pierwszego moda. Jak każda implementacja – wymaga aby wcześniej zdefiniować wymagania, następnie zaprojektować rozwiązanie i zaplanować go tak, aby mod był wykonany “zgodnie ze sztuką”. Finalny kod tego tutoriala znajdziesz tu.

3.1. Wymagania, zakres oraz plan

W dalszej części skupimy się na napisaniu moda do Minecraft o nazwie CheeseMod. Dodatek dostarczy do gry blok sera w trzech wariantach, z którego będziemy mogli wznosić budowle podobnie jak z drewna czy skał.

Implementację rozpoczniemy od przygotowania głównej klasy CheeseMod oznaczając ją adnotacją @Mod. Dodatkowo przygotujemy klasę Reference odpowiedzialną za przechowywanie różnego rodzaju współdzielonych stałych używanych w innych klasach.

Kolejnym etapem będzie przygotowanie klas odpowiedzialnych za zarejestrowanie nowych elementów w grze takich jak Block, Item czy Entity. Umieścimy je w katalogu w pakiecie init. Kroki wykonane do tej pory są powtarzalne w przypadku niemal każdego mod’a.

Najwyższa pora przejść do omówienia, w jaki sposób przebiega implementacja bloku w Minecraft. Zacznijmy od tego, że w grze wyróżniamy m. in. Block i Item. Block, to element, z którego zbudowany jest świat – np. skała, drewno, piasek czy woda. Item, to element w inwentarzu lub w dłoni(skała, drewno, pochodnia, kilof, miecz). W naszym przypadku chcemy zdefiniować oba elementy – zarówno dodać Block jak i Item bazujący na tym bloku.

Implementację bloku rozpoczniemy od przygotowania klasy CheeseBlock. Następnie przygotujemy trzy warianty naszego bloku, które zdefiniujemy w pliku assets/<MODID>/blockstates/<BLOCK_NAME>.json. Warianty będą wskazywać na modele zdefiniowane w katalogu assets/<MODID>/models/blocks/<MODEL_NAME_N>.json. Modele z kolei będą miały odwołania do tekstur, które przygotujemy i umieścimy w folderze assets/<MODID>/textures/.

W skrócie, to byłoby na tyle jeśli chodzi o zależności między plikami w przypadku definiowania bloków. Pozostaje jeszcze kwestia opisu Item’ów. Ponieważ Item nie posiada wariantów, to jego opis umieszczamy bezpośrednio w pliku assets/<MODID>/models/ items/<ITEM_NAME>.json, pomijając tym samym warstwę blockstates.

Jeśli jeszcze nie zapoznałeś się z wpisem Modele Bloków, lub czujesz, że nie wszystko zrozumiałeś, to przeczytaj go koniecznie.

3.2. Kodowanie

Pora rozpocząć właściwy etap implementacji. W tym celu rozpakuj zawartość Mdk do folderu o nazwie CheeseMod. Uruchom InteliJ Idea i wczytaj plik build.gradle z tego folderu wybierając opcję “Otwórz jako projekt”. W oknie ustawień projektu wybierz JDK8. Pobranie wszystkich zależności oraz konfiguracja projektu może zająć nawet kilka minut, tak że musisz uzbroić się w cierpliwość.

Po pomyślnej konfiguracji możesz uruchomić taks o nazwie runClient, aby wystartować grę z przykładowym dodatkiem.

3.1. Przygotowanie klas CheeseMod oraz Reference

Na początek przygotujmy klasę Reference, zawierającą stałą MODID, reprezentujacą unikalne id tworzonego modułu.

package com.bettercoding.minecraft.cheesemod;

public final class Reference {
    public static final String MODID = "cheesemod";
}

Przekształć klasę ExampleMode do poniższej formy, lub utwórz nową klasę CheeseMod z poniższą zawartością:

package com.bettercoding.minecraft.cheesemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

// The value here should match an entry in the META-INF/mods.toml file
@Mod(Reference.MODID)
public class ExampleMod {
    // Directly reference a log4j logger.
    private static final Logger LOGGER = LogManager.getLogger();

    public ExampleMod() {
        // Register the setup method for modloading
        FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);

        // Register ourselves for server and other game events we are interested in
        MinecraftForge.EVENT_BUS.register(this);
    }

    private void setup(final FMLCommonSetupEvent event) {
        LOGGER.info("HELLO FROM {}", Reference.MODID.toUpperCase());
    }
}

Ostatnim niezbędnym elementem jest edycja pliku META-INF/mods.toml i zmiana właściwości modId=”cheesemod”. Po uruchomieniu taska runClient nasz dodatek powinien zostać poprawnie załadowany.

3.2. Tworzenia bloku

Jak już wspomniałem wcześniejszej, tworzenie bloku składa się z kilku etapów, które za chwilę przejdziemy.

3.2.1. Zdefiniowanie klasy JAVA dla bloku

W pakiecie block utwórz klasę jak poniżej. Myślę, że zrozumienie jej zawartości nie będzie problemem. Jedyna rzecz, na którą chwiałbym abyś zwrócił uwagę , to wywołanie metody setRegistryName z identyfikatorem tworzonego bloku. To on stanie się połączeniem między klasą Java a definicjami w plikach *.json, do których zaraz przejdziemy.

package com.bettercoding.minecraft.cheesemod.block;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;

public class CheeseBlock extends Block {

    public static final String BLOCK_NAME = "cheese_block";

    public CheeseBlock() {
        super(createProperties());
        setRegistryName(Reference.MODID, BLOCK_NAME);
    }

    private static Properties createProperties() {
        return Properties.create(Material.ROCK).hardnessAndResistance(3.0f, 3.0f);
    }
}

3.2.1. Przygotowanie zasobów oraz zdefiniowanie wariantów i modeli

Na początek przygotujmy trzy tekstury o wymiarach 16×16 , które odpowiadają trzem wariantom bloku sera. Należy je przygotować formacie *.png i umieścić w folderze assets/<MODID>/textures/block. Przyjmijmy nastepujące nazwy plików: cheese_block_var_1.png, cheese_block_var_2.png, cheese_block_var_3.png.

Mając przygotowane tekstury możemy na ich odstawie przygotować trzy modele bloków. W tym celu w folderze assets/<MODID>/models/block tworzymy trzy pliki (yellow_cheese.json, green_cheese.json , red_cheese.json ) z zawartością jak poniżej. Jak tworzymy trzy modele różniące się użytą teksturą.

yellow_cheese.json:

{
  "parent": "block/cube_all",
  "textures": {
    "all": "cheesemod:block/cheese_block_var_1"
  }
}

green_cheese.json:

{
  "parent": "block/cube_all",
  "textures": {
    "all": "cheesemod:block/cheese_block_var_2"
  }
}

red_cheese.json:

{
  "parent": "block/cube_all",
  "textures": {
    "all": "cheesemod:block/cheese_block_var_3"
  }
}

Następnie w pliku assets/<MODID>/blockstates/<BLOCK_NAME>.json definiujemy warianty naszego bloku.

{
  "variants": {
    "": [
      { "model": "cheesemod:block/yellow_cheese" },
      { "model": "cheesemod:block/green_cheese" },
      { "model": "cheesemod:block/red_cheese" }
    ]
  }
}

Ostatnim elementem w tym etapie jest zdefiniowanie modelu dla Item’a. Jak już wspominałem Item’u nie posiadają wariantów więc pomijamy tę warstwę tworząc w tym przypadku model Item’a na bazie modelu bloku. W tym celu utwórz plik
assets/<MODID>/models/item/cheese_block_item.json z poniższą zawartością:

{
  "parent": "cheesemod:block/red_cheese"
}

Powyższy kod wykorzystuje dziedziczenie i sprawia, że Item ma te same właściwości, co blok na którym bazuje. Nic nie stoi natomiast na przeszkodzie aby Item miał kompletnie inaczej zdefiniowany model.

{
  "parent": "block/cube_all",
  "textures": {
    "all": "cheesemod:block/cheese_block_var_3"
  }
}

3.2.2. Rejestrowanie bloków przedmiotów

Rejestrowanie elementów gry takich jak Block, Item czy Entity odbywa się poprzez utworzenie odpowiedniego listenera. W tym celu należy utworzyć klasę i adnotować ją poprzez @Mod.EventBusSubscriber, dodatkowo sam listener również należny adnotować @SubscribeEvent. Nic nie stoi na przeszkodzie aby zarówno bloki jak i przedmioty rejestrować przy użyciu jednej klasy adnotowanej @Mod.EventBusSubscriber, jednak dla przejrzystości zdecydowałem się je podzielić. Poniższe klasy umieszczamy w pakiecie init.

package com.bettercoding.minecraft.cheesemod.init;


import com.bettercoding.minecraft.cheesemod.Reference;
import com.bettercoding.minecraft.cheesemod.block.CheeseBlock;
import net.minecraft.block.Block;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


@Mod.EventBusSubscriber(modid = Reference.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModBlocks {
    private static final Logger LOGGER = LogManager.getLogger();
    public static Block cheese_block;

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        LOGGER.info("registerBlocks");
        cheese_block = new CheeseBlock();
        event.getRegistry().register(cheese_block);
    }
}
package com.bettercoding.minecraft.cheesemod.init;

import com.bettercoding.minecraft.cheesemod.Reference;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemGroup;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Mod.EventBusSubscriber(modid = Reference.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModItems {
    private static final Logger LOGGER = LogManager.getLogger();

    @SubscribeEvent
    public static void registerItems(RegistryEvent.Register<Item> event) {
        LOGGER.info("registerItems");
        Item cheeseBlockItem = createItemBlockForBlock(ModBlocks.cheese_block, new Item.Properties().group(ItemGroup.BUILDING_BLOCKS).maxStackSize(64));
        event.getRegistry().register(cheeseBlockItem);
    }

    private static ItemBlock createItemBlockForBlock(Block block, Item.Properties properties) {
        return (ItemBlock) new ItemBlock(block, properties).setRegistryName(block.getRegistryName()+"_item");
    }
}

Warto zwrócić uwagę, że w metodzie rejestrującej Item doklejamy sufiks “_item”. Nie musimy tego robić, ale chciałem przez to dokładnie wskazać powiązanie między setRegistryName a plikiem cheese_block_item.json, który odpowiada temu modelowi.

Teraz wystarczy już tylko uruchomić task runClient by zobaczyć finalny efekt.

Jeśli chciałbyś mnie wesprzeć w tym co robię:
  • możesz udostępnić ten wpis np. na swoim twitterze lub facebooku
  • możesz również dołączyć do listy darczyńców udzielając drobnego wsparcia, które przeznaczę na dalszy rozwój bloga

Leave a Reply

avatar
  Subscribe  
Notify of
Close Menu