Sostieni AppuntiFacili con una piccola donazione su PayPal
Dona con PayPalQuando giochi a Minecraft esistono due “mondi” separati che comunicano tra loro:
| Lato | Responsabilità |
|---|---|
| Client | Rendering grafico, interfaccia utente, suoni, animazioni |
| Server | Logica di gioco, generazione mondo, fisica, danni |
Anche in singleplayer, Minecraft avvia internamente un server locale.
Quando si fa modding bisogna sempre chiedersi: questo codice gira sul client, sul server o su entrambi?
// Controllare su quale lato si sta eseguendo:
if (!level.isClientSide()) {
// codice lato SERVER
}
if (level.isClientSide()) {
// codice lato CLIENT
}
Errori comuni derivano dall’eseguire codice client-only sul server (crash) o viceversa.
Le API di modding sono librerie che fanno da ponte tra il tuo codice e il codice interno di Minecraft.
Senza un’API dovresti modificare direttamente il codice di Minecraft (rischioso, fragile, illegale).
Le API più usate per Java Edition:
| API | Note |
|---|---|
| Minecraft Forge | La più diffusa, stabile, ampia community |
| Fabric | Più leggero e moderno, aggiornamenti rapidi |
| NeoForge | Fork di Forge, la direzione futura |
In questa guida usiamo Minecraft Forge (versione 1.20.1).
Forge è un framework di modding che:
Requisiti:
Passi:
C:\MinecraftMods\mia-mod)Una volta che Gradle ha finito:
Tasks → forgegradle runs → genIntellijRunsDovresti vedere Minecraft avviarsi con la dicitura “Forge” nel menu principale.
Dopo aver estratto il MDK, la struttura è:
mia-mod/
├── build.gradle ← configurazione build (nome mod, versione, dipendenze)
├── gradle.properties ← variabili: mod_id, versione, minecraft_version
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/miamod/
│ │ └── MiaMod.java ← classe principale della mod
│ └── resources/
│ ├── META-INF/
│ │ └── mods.toml ← metadati della mod (nome, autore, descrizione)
│ └── assets/
│ └── miamod/ ← texture, modelli, traduzioni
gradle.properties# Identificatore unico della mod (solo lettere minuscole e _)
mod_id=miamod
mod_name=La Mia Mod
mod_version=1.0.0
minecraft_version=1.20.1
forge_version=47.2.0
META-INF/mods.toml[[mods]]
modId = "miamod"
version = "1.0.0"
displayName = "La Mia Mod"
description = '''
La mia prima mod per Minecraft!
'''
MiaMod.java — classe principalepackage com.example.miamod;
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;
@Mod("miamod") // deve corrispondere al modId in mods.toml
public class MiaMod {
private static final Logger LOGGER = LogManager.getLogger();
public MiaMod() {
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
LOGGER.info("MiaMod caricata!");
}
private void setup(final FMLCommonSetupEvent event) {
LOGGER.info("Setup completato.");
}
}
In Forge, ogni oggetto/blocco/entità deve essere registrato con un DeferredRegister.
Creiamo un file ModItems.java:
package com.example.miamod;
import net.minecraft.world.item.Item;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModItems {
// DeferredRegister: registro rimandato (si attiva al caricamento)
public static final DeferredRegister<Item> ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, "miamod");
// Registrazione dell'item "ruby"
public static final RegistryObject<Item> RUBY =
ITEMS.register("ruby", () -> new Item(new Item.Properties()));
}
Poi nella classe principale registra il DeferredRegister sull’event bus:
public MiaMod() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
ModItems.ITEMS.register(modEventBus); // ← aggiungere questa riga
}
Con la registrazione sopra, l’item esiste ma mancano ancora texture e traduzione.
Crea il file src/main/resources/assets/miamod/textures/item/ruby.png
(immagine 16x16 pixel)
Crea src/main/resources/assets/miamod/models/item/ruby.json:
{
"parent": "item/generated",
"textures": {
"layer0": "miamod:item/ruby"
}
}
Crea src/main/resources/assets/miamod/lang/it_it.json:
{
"item.miamod.ruby": "Rubino"
}
Avvia il gioco in modalità creativa: cerca “Rubino” e dovresti trovare il tuo item!
Creiamo ModBlocks.java:
package com.example.miamod;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
public class ModBlocks {
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, "miamod");
public static final RegistryObject<Block> RUBY_BLOCK =
BLOCKS.register("ruby_block", () ->
new Block(BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_RED)
.strength(5.0f, 6.0f) // durezza e resistenza alle esplosioni
.requiresCorrectToolForDrops()
)
);
}
Registra anche ModBlocks.BLOCKS sull’event bus nella classe principale.
Ogni blocco necessita anche di un BlockItem (l’item che rappresenta il blocco nell’inventario):
// In ModItems.java, aggiungi:
public static final RegistryObject<Item> RUBY_BLOCK_ITEM =
ITEMS.register("ruby_block", () ->
new BlockItem(ModBlocks.RUBY_BLOCK.get(), new Item.Properties())
);
Crea src/main/resources/assets/miamod/textures/block/ruby_block.png (16x16)
Modello del blocco assets/miamod/models/block/ruby_block.json:
{
"parent": "block/cube_all",
"textures": {
"all": "miamod:block/ruby_block"
}
}
Stato del blocco assets/miamod/blockstates/ruby_block.json:
{
"variants": {
"": { "model": "miamod:block/ruby_block" }
}
}
Modello item del blocco assets/miamod/models/item/ruby_block.json:
{
"parent": "miamod:block/ruby_block"
}
Crea src/main/resources/data/miamod/recipes/ruby_block.json:
{
"type": "minecraft:crafting_shaped",
"pattern": [
"RRR",
"RRR",
"RRR"
],
"key": {
"R": { "item": "miamod:ruby" }
},
"result": {
"item": "miamod:ruby_block",
"count": 1
}
}
Ricetta shapeless (ingredienti in qualsiasi ordine):
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{ "item": "miamod:ruby_block" }
],
"result": {
"item": "miamod:ruby",
"count": 9
}
}
Per rendere gli item visibili nella tab creativa, crea ModCreativeTabs.java:
package com.example.miamod;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
public class ModCreativeTabs {
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS =
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, "miamod");
public static final RegistryObject<CreativeModeTab> MIAMOD_TAB =
CREATIVE_MODE_TABS.register("miamod_tab", () ->
CreativeModeTab.builder()
.title(Component.translatable("creativetab.miamod.miamod_tab"))
.icon(() -> ModItems.RUBY.get().getDefaultInstance())
.displayItems((params, output) -> {
output.accept(ModItems.RUBY.get());
output.accept(ModBlocks.RUBY_BLOCK_ITEM.get());
})
.build()
);
}
In Minecraft Forge, gli eventi sono il modo in cui intercetti le azioni del gioco.
Idea: Minecraft esegue migliaia di azioni al secondo. Con gli eventi puoi “agganciarti” a quelle che ti interessano.
Giocatore salta
↓
Minecraft lancia l'evento LivingJumpEvent
↓
Forge chiama TUTTI i metodi registrati per quell'evento
↓
Il tuo metodo riceve l'evento e fa qualcosa
Gli eventi funzionano con le annotazioni: @SubscribeEvent.
Crea ModEvents.java:
package com.example.miamod;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = "miamod")
public class ModEvents {
// tutti i metodi con @SubscribeEvent vengono registrati automaticamente
}
Ogni volta che il giocatore salta, applichiamo l’effetto velocità:
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = "miamod")
public class ModEvents {
@SubscribeEvent
public static void onPlayerJump(LivingEvent.LivingJumpEvent event) {
// controlliamo che sia un player (non un mob)
if (event.getEntity() instanceof Player player) {
// applichiamo effetto Velocità per 3 secondi (60 tick), livello 1
player.addEffect(new MobEffectInstance(
MobEffects.MOVEMENT_SPEED,
60, // durata in tick (20 tick = 1 secondo)
0 // livello (0 = livello I)
));
System.out.println(player.getName().getString() + " ha saltato!");
}
}
}
Intercettiamo il tasto destro del mouse quando il giocatore tiene in mano il nostro rubino:
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = "miamod")
public class ModEvents {
@SubscribeEvent
public static void onRightClick(PlayerInteractEvent.RightClickItem event) {
Player player = event.getEntity();
ItemStack item = player.getItemInHand(InteractionHand.MAIN_HAND);
// verifica che il giocatore tenga il rubino in mano
if (item.is(ModItems.RUBY.get())) {
// eseguiamo solo lato server
if (!player.level().isClientSide()) {
player.addEffect(new MobEffectInstance(
MobEffects.NIGHT_VISION,
200, // 10 secondi
0
));
player.sendSystemMessage(
Component.literal("✨ Visione notturna attivata!")
);
}
}
}
}
| Evento | Si attiva quando… |
|---|---|
PlayerEvent.PlayerLoggedInEvent | Un giocatore entra nel mondo |
LivingDeathEvent | Un’entità muore |
BlockEvent.BreakEvent | Un blocco viene rotto |
BlockEvent.EntityPlaceEvent | Un blocco viene piazzato |
EntityItemPickupEvent | Il giocatore raccoglie un item |
PlayerInteractEvent.RightClickBlock | Click destro su un blocco |
LivingHurtEvent | Un’entità riceve danno |
TickEvent.PlayerTickEvent | Ogni tick (20 volte al secondo) |
| Argomento | Concetti chiave |
|---|---|
| Architettura | Client vs Server, API di modding, Forge |
| Setup | MDK, Gradle, configurazione IntelliJ |
| Struttura mod | Classe principale, @Mod, event bus, DeferredRegister |
| Item custom | Registrazione, texture, modello, traduzione |
| Blocco custom | Proprietà, BlockItem, texture, blockstate |
| Ricette | Crafting shaped/shapeless in JSON |
| Creative Tab | Raggruppare i propri item |
| Eventi | @SubscribeEvent, LivingJumpEvent, RightClickItem |
Risorse utili:
Qual è la differenza principale tra Client e Server in Minecraft?
Cos'è un DeferredRegister?
Quale annotazione si usa per registrare un event handler?
Dove si definisce una crafting recipe in Forge?
Come si controlla se il codice gira lato server?
Registra un nuovo item chiamato magic_emerald. Aggiungi texture (puoi riusare quella di un item vanilla) e aggiungilo alla creative tab.
Crea un event handler che intercetta LivingFallEvent: quando il giocatore cade, stampa nella chat la distanza di caduta.
@SubscribeEvent
public static void onFall(LivingFallEvent event) {
if (event.getEntity() instanceof Player player) {
if (!player.level().isClientSide()) {
player.sendSystemMessage(
Component.literal("Caduto da: " + event.getDistance() + " blocchi!")
);
}
}
}Crea un blocco che emette luce. Usa .lightLevel(state -> 15) nelle BlockBehaviour.Properties.
public static final RegistryObject<Block> GLOWING_RUBY_BLOCK =
BLOCKS.register("glowing_ruby_block", () ->
new Block(BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_RED)
.strength(3.0f)
.lightLevel(state -> 15) // luce massima = 15
)
);