🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
This page is written for:
1.21
This page is written for:
1.21
Je komplexer deine Items werden, desto mehr benutzerdefinierte Daten musst du vielleicht für jedes Item speichern. Das Spiel erlaubt es, persistente Daten in einem ItemStack
zu speichern, und seit der Version 1.20.5 tun wir das mit Hilfe von Datenkomponenten.
Datenkomponenten ersetzen NBT-Daten aus früheren Versionen durch strukturierte Datentypen, die auf einen ItemStack
angewendet werden können, um dauerhafte Daten über diesen Stack zu speichern. Datenkomponenten sind namensgebunden, was bedeutet, dass wir unsere eigenen Datenkomponenten implementieren können, um benutzerdefinierte Daten über einen ItemStack
zu speichern und später darauf zuzugreifen. Eine vollständige Liste der Vanilla-Datenkomponenten kann auf dieser Minecraft-Wiki-Seite gefunden werden.
Neben der Registrierung von benutzerdefinierten Komponenten wird auf dieser Seite auch die allgemeine Verwendung der Komponenten-API behandelt, die auch für Vanilla-Komponenten gilt. Du kannst die Definitionen aller Vanilla-Komponenten in der Klasse DataComponentTypes
sehen und darauf zugreifen.
Wie bei allem anderen in deinem Mod musst du deine benutzerdefinierte Komponente mit einem ComponentType
registrieren. Dieser Komponententyp nimmt ein generisches Argument entgegen, das den Typ des Wertes deiner Komponente enthält. Darauf werden wir weiter unten bei der Behandlung von einfachen und fortgeschrittenen Komponenten näher eingehen.
Wähle eine sinnvolle Klasse, in der du dies unterbringen kannst. Für dieses Beispiel werden wir ein neues Paket namens component
und eine Klasse erstellen, die alle unsere Komponententypen enthält und ModComponents
heißt. Stelle sicher, dass du ModComponents.initialize()
in deinem Mod-Initialisierer aufrufst.
public class ModComponents {
protected static void initialize() {
FabricDocsReference.LOGGER.info("Registering {} components", FabricDocsReference.MOD_ID);
// Technically this method can stay empty, but some developers like to notify
// the console, that certain parts of the mod have been successfully initialized
}
}
Dies ist die grundlegende Vorlage für die Registrierung eines Component Typs:
public static final ComponentType<?> MY_COMPONENT_TYPE = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "my_component"),
ComponentType.<?>builder().codec(null).build()
);
Hier gibt es einige Dinge zu beachten. In der ersten und vierten Zeile ist ein ?
zu sehen. Dieser wird durch den Typ des Wertes deiner Komponente ersetzt. Wir werden das bald befüllen.
Zweitens musst du einen Identifier
angeben, der die beabsichtigte ID deiner Komponente enthält. Diese ist mit der Mod-ID deines Mods verknüpft.
Schließlich haben wir einen ComponentType.Builder
, der die eigentliche ComponentType
-Instanz erstellt, die registriert wird. Dies enthält ein weiteres wichtiges Detail, das wir besprechen müssen: den Codec
. deiner Komponente. Dies ist derzeit null
, aber wir werden es auch bald befüllen.
Einfache Datenkomponenten (wie minecraft:damage
) bestehen aus einzelnen Datenwerten, wie einem int
, float
, boolean
oder String
.
Als Beispiel wollen wir einen Integer
-Wert erstellen, der festhält, wie oft der Spieler mit der rechten Maustaste geklickt hat, während er unseren Gegenstand hielt. Aktualisieren wir unsere Komponentenregistrierung wie folgt:
public static final ComponentType<Integer> CLICK_COUNT_COMPONENT = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "click_count"),
ComponentType.<Integer>builder().codec(Codec.INT).build()
);
Du kannst sehen, dass wir jetzt <Integer>
als unseren generischen Typ übergeben, was anzeigt, dass diese Komponente als ein einzelner int
Wert gespeichert wird. Für unseren Codec verwenden wir den mitgelieferten Codec.INT
Codec. Für einfache Komponenten wie diese können wir mit einfachen Codecs auskommen, aber komplexere Szenarien erfordern möglicherweise einen benutzerdefinierten Codec (dies wird später kurz behandelt).
Wenn du das Spiel startest, solltest du einen Befehl wie diesen eingeben können:
Wenn du den Befehl ausführst, solltest du das Element erhalten, das die Komponente enthält. Allerdings verwenden wir unsere Komponente derzeit nicht für irgendetwas Nützliches. Beginnen wir damit, den Wert der Komponente so zu lesen, dass wir ihn sehen können.
Fügen wir ein neues Item hinzu, das den Zähler jedes Mal erhöht, wenn es mit der rechten Maustaste angeklickt wird. Du solltest die Seite Benutzerdefinierte Iteminteraktionen lesen, die die Techniken behandelt, die wir in diesem Leitfaden verwenden werden.
public class CounterItem extends Item {
public CounterItem(Settings settings) {
super(settings);
}
}
Denke wie üblich daran, das Item in deiner Klasse ModItems
zu registrieren.
public static final Item COUNTER = register(new CounterItem(
new Item.Settings()
), "counter");
Wir werden einen Tooltip-Code hinzufügen, um den aktuellen Wert der Klickzahl anzuzeigen, wenn wir mit dem Mauszeiger über unser Item im Inventar fahren. Wir können die Methode get()
auf unserem ItemStack
verwenden, um den Wert unserer Komponente wie folgt zu erhalten:
int clickCount = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
Dadurch wird der aktuelle Wert der Komponente als der Typ zurückgegeben, den wir bei der Registrierung unserer Komponente definiert haben. Diesen Wert können wir dann verwenden, um einen Tooltip-Eintrag hinzuzufügen. Füge diese Zeile der Methode appendTooltip
in der Klasse CounterItem
hinzu:
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {
int count = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
tooltip.add(Text.translatable("item.fabric-docs-reference.counter.info", count).formatted(Formatting.GOLD));
}
Vergiss nicht, deine Sprachdatei (/assets/<mod id>/lang/en_us.json
) zu aktualisieren und diese zwei Zeilen hinzuzufügen:
{
"item.fabric-docs-reference.counter": "Counter",
"item.fabric-docs-reference.counter.info": "Used %1$s times",
}
Starte das Spiel und führe diesen Befehl aus, um dir ein neues Zähler Item mit einer Anzahl von 5 zu geben.
/give @p fabric-docs-reference:counter[fabric-docs-reference:click_count=5]
Wenn du den Mauszeiger über dieses Item in deinem Inventar bewegst, solltest du die Anzahl im Tooltip sehen!
Wenn du dir jedoch ein neues Counter Item ohne die benutzerdefinierte Komponente gibst, stürzt das Spiel ab, wenn du den Mauszeiger über den Gegenstand in deinem Inventar bewegst. Im Absturzbericht sollte ein Fehler wie dieser angezeigt werden:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "net.minecraft.item.ItemStack.get(net.minecraft.component.ComponentType)" is null
at com.example.docs.item.custom.CounterItem.appendTooltip(LightningStick.java:45)
at net.minecraft.item.ItemStack.getTooltip(ItemStack.java:767)
Da der ItemStack
derzeit keine Instanz unserer benutzerdefinierten Komponente enthält, wird der Aufruf von stack.get()
mit unserem Komponententyp erwartungsgemäß null
zurückgeben.
Es gibt drei Lösungen, mit denen wir dieses Problem angehen können.
Wenn du deinen Artikel registrierst und ein Item.Settings
-Objekt an deinen Item Konstruktor übergibst, kannst du auch eine Liste von Standardkomponenten angeben, die auf alle neuen Items angewendet werden. Wenn wir zu unserer Klasse ModItems
zurückkehren, wo wir das CounterItem
, registrieren, können wir einen Standardwert für unsere benutzerdefinierte Komponente hinzufügen. Füge dies hinzu, damit bei neuen Einträgen die Anzahl 0
angezeigt wird.
public static final Item COUNTER = register(new CounterItem(
// Initialize the click count component with a default value of 0
new Item.Settings().component(ModComponents.CLICK_COUNT_COMPONENT, 0)
), "counter");
Wenn ein neues Item erstellt wird, wird automatisch unsere benutzerdefinierte Komponente mit dem angegebenen Wert angewendet.
WARNING
Mit Hilfe von Befehlen ist es möglich, eine Standardkomponente aus einem ItemStack
zu entfernen. In den nächsten beiden Abschnitten erfährst du, wie du vorgehen musst, wenn die Komponente in deinem Item nicht vorhanden ist.
Außerdem können wir beim Lesen des Komponentenwerts die Methode getOrDefault()
auf unserem Objekt ItemStack
verwenden, um einen bestimmten Standardwert zurückzugeben, wenn die Komponente nicht auf dem Stack vorhanden ist. Dadurch werden Fehler vermieden, die durch eine fehlende Komponente entstehen. Wir können unseren Tooltip-Code wie folgt anpassen:
int clickCount = stack.getOrDefault(ModComponents.CLICK_COUNT_COMPONENT, 0);
Wie du sehen kannst, benötigt diese Methode zwei Argumente: Unseren Komponententyp wie zuvor und einen Standardwert, der zurückgegeben wird, wenn die Komponente nicht vorhanden ist.
Mit der Methode contains()
kann auch geprüft werden, ob eine bestimmte Komponente auf einem ItemStack
vorhanden ist. Sie nimmt den Komponententyp als Argument und gibt true
oder false
zurück, je nachdem, ob der Stack diese Komponente enthält.
boolean exists = stack.contains(ModComponents.CLICK_COUNT_COMPONENT);
Wir fahren mit der dritten Option fort. Wir fügen also nicht nur einen Standardkomponentenwert hinzu, sondern prüfen auch, ob die Komponente auf dem Stack vorhanden ist, und zeigen den Tooltip nur an, wenn dies der Fall ist.
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {
if (stack.contains(ModComponents.CLICK_COUNT_COMPONENT)) {
int count = stack.get(ModComponents.CLICK_COUNT_COMPONENT);
tooltip.add(Text.translatable("item.fabric-docs-reference.counter.info", count).formatted(Formatting.GOLD));
}
}
Starte das Spiel erneut und fahre mit dem Mauszeiger über das Item ohne die Komponente. Du solltest sehen, dass er "Used 0 times" anzeigt und das Spiel nicht mehr abstürzt.
Versuche, dir selbst einen Counter zu geben, indem du unsere benutzerdefinierte Komponente entfernst. Du kannst diesen Befehl nutzen, um dies zu tun:
/give @p fabric-docs-reference:counter[!fabric-docs-reference:click_count]
Wenn du den Mauszeiger über dieses Element bewegst, sollte der Tooltip fehlen.
Lass uns nun versuchen, unseren Komponentenwert zu aktualisieren. Wir werden die Anzahl der Klicks jedes Mal erhöhen, wenn wir unser Counter Item verwenden. Um den Wert einer Komponente auf einem ItemStack
zu ändern, verwenden wir die Methode set()
wie folgt:
stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Hier wird unser Komponententyp und der Wert, auf den wir ihn setzen wollen, verwendet. In diesem Fall ist das unsere neue Klickzahl. Diese Methode gibt auch den alten Wert der Komponente zurück (falls vorhanden), was in manchen Situationen nützlich sein kann. Zum Beispiel:
int oldValue = stack.set(ModComponents.CLICK_COUNT_COMPONENT, newValue);
Richten wir eine neue Methode use()
ein, um die alte Klickzahl zu lesen, sie um eins zu erhöhen und dann die aktualisierte Klickzahl zu setzen.
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
ItemStack stack = user.getStackInHand(hand);
// Don't do anything on the client
if (world.isClient()) {
return TypedActionResult.success(stack);
}
// Read the current count and increase it by one
int count = stack.getOrDefault(ModComponents.CLICK_COUNT_COMPONENT, 0);
stack.set(ModComponents.CLICK_COUNT_COMPONENT, ++count);
// Return the original stack
return TypedActionResult.success(stack);
}
Starte nun das Spiel und klicke mit der rechten Maustaste auf den Counter in deiner Hand. Wenn du dein Inventar öffnest und dir das Item noch einmal ansiehst, solltest du sehen, dass die Nutzungszahl um die Anzahl der Klicks gestiegen ist, die du darauf gemacht hast.
Du kannst auch eine Komponente von deinem ItemStack
entfernen, wenn sie nicht mehr benötigt wird. Dies geschieht mit der Methode remove()
, die deinen Komponententyp entgegennimmt.
stack.remove(ModComponents.CLICK_COUNT_COMPONENT);
Diese Methode gibt auch den Wert der Komponente zurück, bevor sie entfernt wird, sodass du sie auch wie folgt verwenden kannst:
int oldCount = stack.remove(ModComponents.CLICK_COUNT_COMPONENT);
Möglicherweise musst du mehrere Attribute in einer einzigen Komponente speichern. Als Vanilla-Beispiel speichert die Komponente minecraft:food
mehrere Werte in Bezug auf Nahrung, wie nutrition
, saturation
, eat_seconds
und mehr. In diesem Leitfaden werden sie als "zusammengesetzte" Komponenten bezeichnet.
Für zusammengesetzte Komponenten musst du eine record
-Klasse erstellen, um die Daten zu speichern. Dies ist der Typ, den wir in unserem Komponententyp registrieren und den wir lesen und schreiben werden, wenn wir mit einem ItemStack
interagieren. Beginne mit der Erstellung einer neuen Record-Klasse im Paket component
, das wir zuvor erstellt haben.
public record MyCustomComponent() {
}
Beachte, dass sich nach dem Klassennamen Klammern befinden. Hier definieren wir die Liste der Eigenschaften, die unsere Komponente haben soll. Fügen wir eine Fließkommazahl und einen booleschen Wert mit den Bezeichnungen temperature
und burnt
hinzu.
public record MyCustomComponent(float temperature, boolean burnt) {
}
Da wir eine benutzerdefinierte Datenstruktur definieren, gibt es für unseren Anwendungsfall keinen bereits vorhandenen Codec
wie bei den einfachen Komponenten. Das bedeutet, dass wir unseren eigenen Codec konstruieren müssen. Definieren wir einen in unsere Record-Klasse mit einem RecordCodecBuilder
, auf den wir verweisen können, sobald wir die Komponente registrieren. Weitere Einzelheiten zur Verwendung eines RecordCodecBuilder
findest du in diesem Abschnitt der Codecs-Seite.
public static final Codec<MyCustomComponent> CODEC = RecordCodecBuilder.create(builder -> {
return builder.group(
Codec.FLOAT.fieldOf("temperature").forGetter(MyCustomComponent::temperature),
Codec.BOOL.optionalFieldOf("burnt", false).forGetter(MyCustomComponent::burnt)
).apply(builder, MyCustomComponent::new);
});
Du kannst sehen, dass wir eine Liste von benutzerdefinierten Feldern auf der Grundlage der primitiven Codec
-Typen definieren. Wir teilen dem Spiel jedoch auch mit, wie unsere Felder heißen, indem wir fieldOf()
verwenden und dann forGetter()
benutzen, um dem Spiel mitzuteilen, welches Attribut unseres Datensatzes es füllen soll.
Du kannst auch optionale Felder definieren, indem du optionalFieldOf()
verwendest und einen Standardwert als zweites Argument übergibst. Alle Felder, die nicht als optional gekennzeichnet sind, werden benötigt, wenn die Komponente mit /give
erstellt wird, also stelle sicher, dass du alle optionalen Argumente als solche kennzeichnest, wenn du deinen Codec erstellst.
Schließlich rufen wir apply()
auf und übergeben den Konstruktor unseres Datensatzes. Weitere Einzelheiten über die Erstellung von Codecs und fortgeschrittene Anwendungsfälle findest du auf der Seite Codecs.
Die Registrierung einer zusammengesetzten Komponente ist ähnlich wie zuvor. Wir übergeben einfach unsere Record-Klasse als generischen Typ und unseren benutzerdefinierten Codec
an die Methode codec()
.
public static final ComponentType<MyCustomComponent> MY_CUSTOM_COMPONENT = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(FabricDocsReference.MOD_ID, "custom"),
ComponentType.<MyCustomComponent>builder().codec(MyCustomComponent.CODEC).build()
);
Starte jetzt das Spiel. Versuche, die Komponente mit dem Befehl /give
anzuwenden. Zusammengesetzte Komponentenwerte werden als ein mit {}
umschlossenes Objekt übergeben. Wenn du leere geschweifte Klammern einfügst, wird eine Fehlermeldung angezeigt, die besagt, dass der erforderliche Schlüssel temperature
fehlt.
Füge dem Objekt einen Temperaturwert mit dem Syntax temperature:8.2
hinzu. Du kannst auch optional einen Wert für burnt
mit dem gleichen Syntax übergeben, aber entweder true
oder false
. Du solltest nun sehen, dass der Befehl gültig ist und dir ein Item mit der Komponente liefern kann.
Die Verwendung der Komponente im Code ist die gleiche wie zuvor. Die Verwendung von stack.get()
gibt eine Instanz der Klasse record
zurück, die Sie dann zum Lesen der Werte verwenden können. Da Datensätze schreibgeschützt sind, musst du eine neue Instanz deines Datensatzes erstellen, um die Werte zu aktualisieren.
// read values of component
MyCustomComponent comp = stack.get(ModComponents.MY_CUSTOM_COMPONENT);
float temp = comp.temperature();
boolean burnt = comp.burnt();
// set new component values
stack.set(ModComponents.MY_CUSTOM_COMPONENT, new MyCustomComponent(8.4f, true));
// check for component
if (stack.contains(ModComponents.MY_CUSTOM_COMPONENT)) {
// do something
}
// remove component
stack.remove(ModComponents.MY_CUSTOM_COMPONENT);
Du kannst auch einen Standardwert für eine zusammengesetzte Komponente festlegen, indem du ein Komponenten-Objekt an deine Item.Settings
übergibst. Zum Beispiel:
public static final Item COUNTER = register(new CounterItem(
new Item.Settings().component(ModComponents.MY_CUSTOM_COMPONENT, new MyCustomComponent(0.0f, false))
), "counter");
Jetzt kannst du benutzerdefinierte Daten an einem ItemStack
speichern. Nutze dies mit Bedacht!