Créer un IForgeRegistry
-
Sommaire
Introduction
Forge possède un système qui permet d’enregistrer des éléments du jeu, ce processus d’enregistrement permet de faire savoir au jeu que ces éléments existent. Vous utilisez sûrement déjà ce système pour enregistrer vos blocs, vos objets, vos entités, vos sons, vos potions, etc … Un registre est similaire à une map qui assigne une clé à une valeur, de plus il assigne automatiquement un ID aux éléments. En prenant pour exemple les blocs, le bloc Block#STONE est enregistré avec la clé “minecraft:stone” et l’ID est affecté automatiquement. On utilise la classe ResourceLocation comme clé, et pour la valeur, ce doit être une classe qui implémente IForgeRegistryEntry. Ce système d’enregistrement permet aussi d’avoir le même nom pour deux valeurs différentes tant qu’elles sont de deux types différents, là encore en prenant pour exemple le bloc “minecraft:stone”, on a aussi l’objet “minecraft:stone”, ils ont le même nom mais l’un de de type Block et l’autre de type Item.
Pré-requis
Code
Créer son propre registre
Nous allons voir comment créer notre propre registre, mais avant tout il faut que l’on crée un classe qui représentera ce que nous voulons enregistrer. Nous allons donc créer la classe qui nous enregistrerons dans notre registre :
public class Buzz { }
Voilà tout simplement, vous pouvez mettre ce que vous voulez à l’intérieur, c’est un classe totalement normale. Comme je l’ai indiqué dans l’introduction notre classe va devoir implémenter IForgeRegistryEntry, cependant pour nous faciliter la tâche Forge a déjà créé une implémentention de cette interface. Il nous donc seulement faire hériter Buzz de IForgeRegistryEntry$Impl :
public class Buzz extends IForgeRegistryEntry.Impl <buzz>{ }
Et voilà, c’est des instances de la classe Buzz que nous ajouterons à notre registre. Cependant nous n’avons toujours pas créer le registre lui-même, nous allons le faire tout de suite. Premièrement il faut créer une classe d’événement, cette classe doit être enregistrée sur l’event bus forge avant la phase de pre-initialisation. C’est plutôt logique, il faut créer les registres avant d’y ajouter un élément. C’est pourquoi il faut soit annoter la classe avec @EventBusSubscriber ou appeller MinecraftForge.EVENT_BUS#register dans le constructeur de votre classe principale (celle annotée du @Mod). Pour ma part j’utilise l’annotation :
@EventBusSubscriber(modid = ModTuto.MODID) public class ModTutoRegistries { }
Je vais ajouter l’événement RegistryEvent.NewRegistry, c’est seulement à ce moment là que l’on crée nos registres :
@EventBusSubscriber(modid = ModTuto.MODID) public class ModTutoRegistries { @SubscribeEvent public static void onRegistryCreation(RegistryEvent.NewRegistry event) { } }
On va enfin créer le registre, pour cela nous allons utiliser la classe RegistryBuilder. Voici ce qu’il faut indiquer pour pouvoir créer un registre :
- Son nom : c’est un ResourceLocation, il permet d’identifier les registres.
- Son type : c’est une Class, elle correspond à la classe que nous allons mettre dans le registre.
- La range des IDs : comme je l’ai dit les IDs sont affectés automatiquement mais il faut définir un ID minimum et un ID maximum que l’on peut donner.
- Des Callback : il y en a de 4 types :
- Callback d’ajout : appelé lorsqu’un élément est ajouté au registre, il implémente l’interface AddCallback.
- Callback de nettoyage : appelé lorsque le registre est vidé, il implémente l’interface ClearCallback.
- Callback de création : appelé lorsqu’une instance du registre est créée, il implémente l’interface CreateCallback.
- Callback de subsitution : appelé lorsqu’un élément est remplacé par un autre, il implémente l’interface SubstitutionCallback.[/list]
Note : à l’écriture de ce tutoriel les callbacks de nettoyage de de création ne semble pas opérationnels.
Une fois votre registre créé vous pouvez le stocker dans une variable pour pouvoir y accéder plus facilement et de façon plus optimisée, mais nous verrons que l’on peut récupérer ce dernier de n’importe où en connaissant seulement le type d’élément qu’il contient. Créons notre registre :
@EventBusSubscriber(modid = ModTuto.MODID) public class ModTutoRegistries { public static IForgeRegistry <buzz>buzzRegistry = null; @SubscribeEvent public static void onRegistryCreation(RegistryEvent.NewRegistry event) { RegistryBuilder <buzz>builder = new RegistryBuilder<buzz>(); // Création du builder builder.setName(new ResourceLocation(ModTuto.MODID, "buzz")); // On définit son nom builder.setType(Buzz.class); // La classe de ce que nous allons mettre dedans builder.setIDRange(0, 5000); // La range des IDs, ici j'ai mis 5000 IDs max, vous pouvez mettre + ou - buzzRegistry = builder.create(); // On crée le registre } }
Le RegistryBuilder permet d’appeller les fonctions en chaine, c’est à dire que j’aurais pu mettre :
buzzRegistry = new RegistryBuilder<buzz>().setName(new ResourceLocation(ModTuto.MODID, "buzz")).setType(Buzz.class).setIDRange(0, 5000).create();
Nous venons de créer notre registre. C’est tout pour cette partie, nous allons à présent voir comment ajouter des éléments aux registres de la bonne manière.
Ajout d’éléments aux registres
Nous allons voir comment rajouter un élément à un registre, je rappelle qu’un élément est une classe qui implémente IForgeRegistryEntry. Pour ajouter un élément à un registre nous allons encore passer par les événements, je vais donc créer une classe qui va gérer l’événement RegistryEvent.Register, on va ici enregistrer nos éléments Buzz :
@EventBusSubscriber(modid = ModTuto.MODID) public class BuzzRegistering { @SubscribeEvent public static void registerBuzz(RegistryEvent.Register <buzz>event) { } }
Je vais créer 5 éléments Buzz et les ajouter à mon registre :
@EventBusSubscriber(modid = ModTuto.MODID) public class BuzzRegistering { public static Buzz buzzVert; public static Buzz buzzRouge; public static Buzz buzzBleu; public static Buzz buzzOrange; public static Buzz buzzViolet; @SubscribeEvent public static void registerBuzz(RegistryEvent.Register <buzz>event) { buzzVert = new Buzz().setRegistryName(ModTuto.MODID, "vert"); buzzRouge = new Buzz().setRegistryName(ModTuto.MODID, "rouge"); buzzBleu = new Buzz().setRegistryName(ModTuto.MODID, "bleu"); buzzOrange = new Buzz().setRegistryName(ModTuto.MODID, "orange"); buzzViolet = new Buzz().setRegistryName(ModTuto.MODID, "violet"); event.getRegistry().registerAll(buzzVert, buzzRouge, buzzBleu, buzzOrange, buzzViolet); } }
Et c’est tout, j’ai ajouté mes éléments au registre créé précédemment. Attention, il faut bien définir le nom d’enregistrement de chaque élément, il doit être différent pour chaque élément d’un même registre. Pour le définir il faut utiliser IForgeRegistryEntry#setRegistryName, puis on enregistre nos élément dans l’event en récupérant le registre et en appelant IForgeRegistry#registerAll.
Cette méthode est valable pour tous les registres, ainsi pour enregistrer vos blocs, vos objets, etc … vous devriez avoir la chose suivante :
@EventBusSubscriber(modid = ModTuto.MODID) public class GlobalRegistering { public static Block myBlock1; public static Block myBlock2; public static Item myItem1; public static Item myItem2; public static Item myItem3; public static Item myItem4; public static VillagerProfession prof1; public static VillagerProfession prof2; public static VillagerProfession prof3; @SubscribeEvent public static void registerBlocks(RegistryEvent.Register <block>event) { myBlock1 = new Block(Material.SAND).setRegistryName(ModTuto.MODID, "block1"); myBlock2 = new Block(Material.ROCK).setRegistryName(ModTuto.MODID, "block2"); event.getRegistry().registerAll(myBlock1, myBlock2); } @SubscribeEvent public static void registerItems(RegistryEvent.Register event) { myItem1 = new Item().setRegistryName(ModTuto.MODID, "item1"); myItem2 = new Item().setRegistryName(ModTuto.MODID, "item2"); myItem3 = new Item().setRegistryName(ModTuto.MODID, "item3"); myItem4 = new Item().setRegistryName(ModTuto.MODID, "item4"); event.getRegistry().registerAll(myItem1, myItem2, myItem3, myItem4); } @SubscribeEvent public static void registerVillagerProfessions(RegistryEvent.Register <villagerprofession>event) { prof1 = new VillagerProfession("modtuto:mineur", "modtuto:path", "modtuto:path").setRegistryName(ModTuto.MODID, "profession1"); prof2 = new VillagerProfession("modtuto:bucheron", "modtuto:path", "modtuto:path").setRegistryName(ModTuto.MODID, "profession2"); prof3 = new VillagerProfession("modtuto:agent_immobilier", "modtuto:path", "modtuto:path").setRegistryName(ModTuto.MODID, "profession3"); event.getRegistry().registerAll(prof1, prof2, prof3); } }
Une dernière chose avant de vous quitter, comme promis je vais vous dire comment obtenir l’instance d’un registre de n’importe où en connaissant le type d’élément qu’il contient, pour cela il faut utiliser GameRegistry#findRegistry(IForgeRegistryEntry), qui vous retourne une instance de IForgeRegistry. Les registres existant nativement sont :
- Block.class
- Item.class
- Potion.class
- Biome.class
- SoundEvent.class
- PotionType.class
- Enchantment.class
- VillagerProfession.class
- EntityEntry.class
L’annotation @ObjectHolder
Vous pouvez injecter les valeurs d’un registre dans un champ d’un classe en l’annotant de @ObjectHolder. Ce champ doit être public static et final, le type du champ indiquera dans quel registre chercher l’élément, il faut ensuite indiquer son nom. Si je veux récupéré mon Buzz vert, je vais faire la chose suivante :
public class Fizz { @ObjectHolder("modtuto:vert") public static final Buzz buzzVert = null; }
La valeur est alors injecté dans mon champ, vous pouvez aussi annoter la classe pour définir le domaine :
@ObjectHolder("modtuto") public class Fizz { @ObjectHolder("vert") public static final Buzz buzzVert = null; }
Dès lors que la classe est annotée, Forge va tenté d’injecter un valeur dans tous les champs public static et final de la classe. Si un champ n’est pas annoté, alors son nom sera utilisé comme chemin.
@ObjectHolder("modtuto") public class Fizz { public static final Buzz vert = null; }
Les 3 exemples que je viens de donner ont EXACTEMENT le même résultat. Cependant si vous voulez par exemple injecter le bloc de stone dans la même classe il suffit de l’annoter comme dans le premier exemple :
@ObjectHolder("modtuto") public class Fizz { public static final Buzz vert = null; @ObjectHolder("minecraft:stone") public static final Block blockOfStone = null; }
Si un élément n’est pas trouvé alors il n’est pas injecté et un message de debug est envoyé dans les logs.
Résultat
J’ai ajouté une commande m’affichant le contenu du registre contenant les Buzz et voici ce que cela m’a affiché :
modtuto:vert modtuto:rouge modtuto:bleu modtuto:orange modtuto:violet
Rédaction : BrokenSwing
Correction : Pas encore corrigé
Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International -
Salut,
Sympa ! Mais j’ai encore un peu de mal à comprendre ce qui nous pousserait à utiliser ce système plutôt qu’une simple HashMap ? J’ai bien compris qu’il y a des assignations automatiques d’ID pour chaque valeur, mais en quoi c’est intéressant ? Merci d’avance ! -
Bien sûr que tu peux très simplement remplacer ce système par un HashMap, et c’est finalement ce que c’est plus ou moins. Mais pourquoi refaire un système qui existe déjà ? Ici tu as 1 héritage (ou une implémentation si l’héritage n’est pas possible) à faire et une création de registre (1 ligne), c’est plutôt rapide, donc premier point positif. Ensuite les mods qui veulent ajouter des éléments à un registre que tu aurais créé le font pas la voie classique (GameRegistry#register si c’est mal fait, pas les event si c’est bien fait). Et dernier point, je dirais que ce système supporte l’annotation @ObjectHolder qui peut être super utile dans certains cas. Les IDs automatiques c’est juste une spécification, ils ne doivent pas être utilisés, mais cela permet néanmoins de limiter les nombre d’élément grâce à la range.
-
Cela permet aussi une utilisation plus facile par les autres moddeurs qui pourrait vouloir/devoir ajouter des élément à ton mods. Vu que la procédure est dés lors la même que pour minecraft et ne passe pas par un enchevêtrement de classes obscures ou peu commentées.
-
Par contre, avec ce système, comment faire pour enregistrer un bloc mais pas l’item associé ?
-
Depuis la 1.10 ou la 1.11, je ne sais plus trop, quand tu enregistre un bloc l’itembloc n’est plus créé automatiquement, donc si tu veux ajouter un bloc sans ajouter un itembloc qui lui est associé tu fais exactement ce que j’ai montré c’est à dire :
@SubscribeEvent public static void registerBlocks(RegistryEvent.Register<Block> event) { event.getRegistry().registerAll(tonBloc); }
-
Je viens de découvrir les LegacyNamespacedRegistry (qui implémentent IRegistry et IObjectIntIterable). C’est la registry qui sert pour les TileEntity car elles n’ont pas besoin d’ID. Ça s’utilise comme une map et il y a des fonctions en plus, comme getRandomObject et getNameForObject. De plus il a un système de legacy names qui permet de donner plusieurs noms à un même objet (exemple : si on veut changer le nom d’un objet tout en restant compatible avec les anciennes maps avec l’ancien nom).