🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
Diese Seite ist für folgende Version geschrieben:
1.21.4
In Minecraft wird Networking verwendet, damit Client und Server miteinander kommunizieren können. Networking ist ein weites Feld, daher ist diese Seite in mehrere Kategorien unterteilt.
Pakete sind das Kernkonzept von Minecraft Networking. Pakete bestehen aus beliebigen Daten, die entweder vom Server zum Client oder vom Client zum Server gesendet werden können. Das folgende Diagramm zeigt eine visuelle Darstellung der Networkingarchitektur in Fabric:
Beachte, dass die Pakete die Brücke zwischen dem Server und dem Client bilden. Das liegt daran, dass fast alles, was du im Spiel tust, in irgendeiner Weise mit Networking zu tun hat, ob du es weißt oder nicht. Wenn du zum Beispiel eine Chat-Nachricht sendest, wird ein Paket mit dem Inhalt an den Server geschickt. Der Server sendet dann ein weiteres Paket mit deiner Nachricht an alle anderen Clients.
Wichtig ist, dass immer ein Server läuft, auch im Einzelspielermodus und im LAN. Pakete werden immer noch zur Kommunikation zwischen dem Client und dem Server verwendet, auch wenn niemand anderes mit dir spielt. Wenn von Seiten bei Networking die Rede ist, werden die Begriffe "logischer Client" und "logischer Server" verwendet. Der integrierte Singleplayer/LAN-Server und der dedizierte Server sind beide logische Server, aber nur der dedizierte Server kann als physischer Server betrachtet werden.
Wenn der Status zwischen dem Client und dem Server nicht synchronisiert wird, kann es zu Problemen kommen, wenn der Server oder andere Clients nicht mit dem übereinstimmen, was ein anderer macht. Dies wird oft als "Desync" bezeichnet. Wenn du deinen eigenen Mod schreibst, musst du eventuell ein Datenpaket senden, um den Status des Servers und aller Clients synchron zu halten.
INFO
Eine Payload sind die Daten, die innerhalb eines Pakets gesendet werden.
Dies kann durch das Erstellen eines Java Record
mit einem BlockPos
-Parameter, der CustomPayload
implementiert, gelöst werden.
public record SummonLightningS2CPayload(BlockPos pos) implements CustomPayload {
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
Zugleich haben wir folgendes definiert:
Identifier
, der zur Identifizierung des Payload unseres Pakets verwendet wird. In diesem Beispiel lautet der Bezeichner fabric-docs-reference:summon_lightning
.public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
CustomPayload.Id
zur eindeutigen Identifizierung dieses benutzdefinierten Payloads. Wir werden auf diese ID sowohl in unserem allgemeinen als auch in unserem Client-Code verweisen.public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
PacketCodec
, damit das Spiel weiß, wie es den Inhalt des Pakets serialisieren/deserialisieren kann.public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
Wir haben auch getId
überschrieben, um unsere Payload-ID zurückzugeben.
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
Bevor wir ein Paket mit unserem benutzerdefinierten Payload senden, müssen wir ihn registrieren.
INFO
S2C
und C2S
sind zwei gängige Suffixe, die Server-to-Client und Client-to-Server bedeuten.
Dies kann in unserem gemeinsamen Initialisierers mit Hilfe von PayloadTypeRegistry.playS2C().register
erfolgen, das eine CustomPayload.Id
und einen PacketCodec
entgegennimmt.
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);
Eine ähnliche Methode existiert, um Client-to-Server Payloads zu registrieren: PayloadTypeRegistry.playC2S().register
.
Um ein Paket mit unserem benutzerdefinierten Payload zu senden, können wir ServerPlayNetworking.send
verwenden, das eine ServerPlayerEntity
und einen CustomPayload
entgegennimmt.
Beginnen wir mit der Erstellung unseres Blitz-Kartoffel-Item. Du kannst use
überschreiben, um eine Aktion auszulösen, wenn das Item verwendet wird. In diesem Fall, lasst uns Pakete an die Spieler in der Serverwelt senden.
public class LightningTaterItem extends Item {
public LightningTaterItem(Settings settings) {
super(settings);
}
@Override
public ActionResult use(World world, PlayerEntity user, Hand hand) {
if (world.isClient()) {
return ActionResult.PASS;
}
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
return ActionResult.SUCCESS;
}
}
Schauen wir uns den obigen Code an.
Wir senden nur Pakete, wenn die Aktion auf dem Server initiiert wird, indem wir frühzeitig mit einer isClient
-Prüfung zurückkehren:
if (world.isClient()) {
return ActionResult.PASS;
}
Wir erstellen eine Instanz des Payloads mit der Position des Users:
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
Schließlich rufen wir die Spieler in der Serverwelt durch PlayerLookup
ab und senden ein Paket an jeden Spieler.
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
INFO
Fabric API bietet PlayerLookup
, eine Sammlung von Hilfsfunktionen, die Spieler auf einem Server suchen.
Ein häufig verwendeter Begriff zur Beschreibung der Funktionalität dieser Methoden ist "Tracking". Es bedeutet, dass eine Entität oder ein Chunk auf dem Server dem Client eines Spielers bekannt ist (innerhalb seiner Sichtweite) und die Entität oder die Block Entität sollte die Clients über Änderungen informieren.
Tracking ist ein wichtiges Konzept für effizientes Networking, damit nur die notwendigen Spieler durch das Senden von Paketen über Änderungen informiert werden.
Um ein von einem Server gesendetes Paket auf dem Client zu empfangen, musst du angeben, wie du das eingehende Paket behandeln willst.
Dies kann im Client-Initialisierer erfolgen, indem ClientPlayNetworking.registerGlobalReceiver
aufgerufen und eine CustomPayload.Id
und ein PlayPayloadHandler
übergeben wird, der eine funktionale Schnittstelle ist.
In diesem Fall definieren wir die auszulösende Aktion innerhalb der Implementierung der PlayPayloadHandler
-Implementierung (als Lambda-Ausdruck).
ClientPlayNetworking.registerGlobalReceiver(SummonLightningS2CPayload.ID, (payload, context) -> {
ClientWorld world = context.client().world;
if (world == null) {
return;
}
BlockPos lightningPos = payload.pos();
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
});
Schauen wir uns den obigen Code an.
Wir können auf die Daten aus unserem Payload zugreifen, indem wir die Getter-Methoden des Records aufrufen. In diesem Fall payload.pos()
. Welche genutzt werden können, um die x
,- y
- und z
-Positionen abzurufen.
BlockPos lightningPos = payload.pos();
Schließlich erstellen wir eine LightningEntity
und fügen sie der Welt hinzu.
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
Wenn du nun diesen Mod auf einem Server installierst und ein Spieler dein Item "Lightning Tater" benutzt, sehen alle Spieler Blitze, die an der Position des Users einschlagen.
Genau wie beim Senden eines Pakets an den Client erstellen wir zunächst einen benutzerdefinierten Payload. Dieses Mal, wenn ein Spieler eine giftige Kartoffel auf einer lebenden Entität verwendet, fordern wir den Server auf, den Leuchten-Effekt auf diese anzuwenden.
public record GiveGlowingEffectC2SPayload(int entityId) implements CustomPayload {
public static final Identifier GIVE_GLOWING_EFFECT_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "give_glowing_effect");
public static final CustomPayload.Id<GiveGlowingEffectC2SPayload> ID = new CustomPayload.Id<>(GIVE_GLOWING_EFFECT_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, GiveGlowingEffectC2SPayload> CODEC = PacketCodec.tuple(PacketCodecs.INTEGER, GiveGlowingEffectC2SPayload::entityId, GiveGlowingEffectC2SPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
Wir übergeben den entsprechenden Codec zusammen mit einem Methodenverweis, um den Wert aus dem Record zu erhalten und diesen Codec zu bauen.
Dann registrieren wir unseren Payload in unserem gemeinsamen Initialisierer. Diesmal jedoch als Client-to-Server Payload unter Verwendung von PayloadTypeRegistry.playC2S().register
.
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);
Um ein Paket zu senden, fügen wir eine Aktion hinzu, wenn der Spieler eine giftige Kartoffel benutzt. Wir werden das Event UseEntityCallback
verwenden, um die Dinge übersichtlich zu halten.
Wir registrieren das Event in unserem Client-Initialisierer, und wir verwenden isClient()
, um sicherzustellen, dass die Aktion nur auf dem logischen Client ausgelöst wird.
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (!world.isClient()) {
return ActionResult.PASS;
}
ItemStack usedItemStack = player.getStackInHand(hand);
if (entity instanceof LivingEntity && usedItemStack.isOf(Items.POISONOUS_POTATO) && hand == Hand.MAIN_HAND) {
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
ClientPlayNetworking.send(payload);
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
});
Wir erstellen eine Instanz unseres GiveGlowingEffectC2SPayload
mit den notwendigen Argumenten. In diesem Fall, der Netzwerk ID der Zielentität.
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
Schließlich senden wir ein Paket an den Server, indem wir ClientPlayNetworking.send
mit der Instanz unseres GiveGlowingEffectC2SPayload
aufrufen.
ClientPlayNetworking.send(payload);
Dies kann im gemeinsamen Initialisierer geschehen, indem man ServerPlayNetworking.registerGlobalReceiver
aufruft und eine CustomPayload.Id
und einen PlayPayloadHandler
übergibt.
ServerPlayNetworking.registerGlobalReceiver(GiveGlowingEffectC2SPayload.ID, (payload, context) -> {
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
livingEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.GLOWING, 100));
}
});
INFO
Es ist wichtig, dass du den Inhalt des Pakets auf der Serverseite validierst.
In diesem Fall überprüfen wir anhand der Netzwerk-ID, ob die Entität existiert.
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
Außerdem muss es sich bei der Zielentität um eine lebende Entität handeln, und wir beschränken die Reichweite der Zielentität vom Spieler auf 5.
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
Wenn ein Spieler nun versucht, eine giftige Kartoffel auf einer lebenden Entität zu verwenden, wird der Leuchten-Effekt auf diese angewendet.