🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
Questa pagina si applica alla versione:
1.21.4
Il networking in Minecraft si usa affinché client e server possano comunicare tra loro. Il networking è un argomento ampio, per cui questa pagina è suddivisa in diverse categorie.
I pacchetti sono il concetto fondamentale del networking in Minecraft. I pacchetti sono costituiti da dati arbitrari che possono essere inviati dal server al client o dal client al server. Dai un'occhiata al diagramma qui sotto, che fornisce una rappresentazione visiva dell'architettura di rete in Fabric:
Nota come i pacchetti fungano da ponte tra il server e il client; questo perché quasi tutto ciò che fai nel gioco coinvolge il networking in qualche modo, che tu lo sappia o meno. Ad esempio, quando invii un messaggio in chat, un pacchetto viene inviato al server con il contenuto. Il server quindi invia un altro pacchetto a tutti gli altri client con il tuo messaggio.
Una cosa importante da tenere a mente è che c'è sempre un server in esecuzione, anche in singleplayer o LAN. I pacchetti vengono ancora utilizzati per la comunicazione tra client e server, anche quando nessun altro sta giocando con te. Quando si parla di lati nel networking, si utilizzano i termini "client logico" e "server logico". Il server integrato per singleplayer/LAN e il server dedicato sono entrambi server logici, ma solo il server dedicato può essere considerato un server fisico.
Quando lo stato non è sincronizzato tra client e server, possono verificarsi problemi in cui il server o altri client non concordano su ciò che un altro client sta facendo. Questo fenomeno è spesso noto come "desync". Quando scrivi la tua mod, potresti dover inviare un pacchetto di dati per mantenere sincronizzato lo stato del server e di tutti i client.
INFO
Un payload sono i dati che vengono inviati in un pacchetto.
Questo può essere fatto creando un Record
Java con un parametro BlockPos
che implementi CustomPayload
.
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;
}
}
Allo stesso tempo, abbiamo definito:
Identifier
utilizzato per identificare il payload del nostro pacchetto. Per questo esempio, il nostro identificatore sarà fabric-docs-reference:summon_lightning
.public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
CustomPayload.Id
per identificare in modo univoco questo payload personalizzato. Faremo riferimento a questo ID sia nel codice comune che in quello client.public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
PacketCodec
affinché il gioco sappia come serializzare/deserializzare i contenuti del pacchetto.public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
Abbiamo anche fatto override di getId
per restituire l'ID del nostro payload.
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
Prima di inviare un pacchetto con il nostro payload personalizzato, dobbiamo registrarlo.
INFO
S2C
e C2S
sono due suffissi comunemente usati, e che significano rispettivamente Server-to-Client e Client-to-Server.
Questo si può fare nel nostro initializer comune usando PayloadTypeRegistry.playS2C().register
che accetta un CustomPayload.Id
e un PacketCodec
.
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);
Esiste un metodo simile per registrare i payload da client a server: PayloadTypeRegistry.playC2S().register
.
Per inviare un pacchetto con il nostro payload personalizzato, possiamo usare ServerPlayNetworking.send
che accetta un ServerPlayerEntity
e un CustomPayload
.
Iniziamo creando il nostro oggetto Lightning Tater. Puoi fare override di use
per attivare un'azione quando l'oggetto viene utilizzato. In questo caso, inviamo pacchetti ai giocatori nel mondo del server.
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;
}
}
Esaminiamo il codice sopra.
Inviamo pacchetti solo quando l'azione viene avviata sul server, uscendo anticipatamente con un controllo isClient
:
if (world.isClient()) {
return ActionResult.PASS;
}
Creiamo un'istanza del payload con la posizione dell'utente:
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
Infine, otteniamo i giocatori nel mondo del server tramite PlayerLookup
e inviamo un pacchetto a ciascun giocatore.
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
INFO
L'API di Fabric fornisce PlayerLookup
, una serie di funzioni ausiliarie che andranno a cercare i giocatori in un server.
Un termine usato di frequente per descrivere la funzionalità di questi metodi è "tracking" (tracciamento). Ciò significa che un'entità o un chunk sul server sono noti al client di un giocatore (all'interno della sua distanza di visualizzazione), e che l'entità o l'entità-blocco dovrebbe notificare i client che la stanno tracciando di eventuali cambiamenti.
Il tracciamento è un concetto importante per un networking efficiente, così che solo i giocatori necessari vengano notificati dei cambiamenti tramite l'invio di pacchetti.
Per ricevere un pacchetto inviato da un server sul client, devi specificare come gestirai il pacchetto in arrivo.
Questo si può fare nell'initializer del client, chiamando ClientPlayNetworking.registerGlobalReceiver
e passando un CustomPayload.Id
e un PlayPayloadHandler
, che è un'interfaccia funzionale.
In questo caso, definiremo l'azione da attivare all'interno dell'implementazione di PlayPayloadHandler
(come espressione lambda).
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);
}
});
Esaminiamo il codice sopra.
Possiamo accedere ai dati dal nostro payload chiamando i metodi getter del Record. In questo caso payload.pos()
, che può poi essere usato per ottenere le coordinate x
, y
e z
.
BlockPos lightningPos = payload.pos();
Infine, creiamo una LightningEntity
e aggiungiamola al mondo.
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
Ora, aggiungendo questa mod a un server, e quando un giocatore usa il nostro oggetto Lightning Tater, ogni giocatore vedrà un fulmine colpire la posizione dell'utente.
Come per l'invio di un pacchetto al client, iniziamo creando un payload personalizzato. Questa volta, quando un giocatore usa una Patata Velenosa su un'entità vivente, chiediamo al server di applicare l'effetto Luminescenza su di essa.
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;
}
}
Passiamo il codec appropriato insieme a un riferimento al metodo per ottenere il valore dal Record per costruire questo codec.
Poi registriamo il nostro payload nel nostro initializer comune. Ma, questa volta, come payload Client-to-Server, utilizzando PayloadTypeRegistry.playC2S().register
.
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);
Per inviare un pacchetto, aggiungiamo un'azione per quando il giocatore usa una Patata Velenosa. Utilizzeremo l'evento UseEntityCallback
per mantenere il codice conciso.
Registriamo l'evento nel nostro initializer del client, e usiamo isClient()
per assicurarci che l'azione venga attivata solo sul client logico.
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;
});
Creiamo un'istanza del nostro GiveGlowingEffectC2SPayload
con gli argomenti necessari. In questo caso, l'ID di rete dell'entità bersaglio.
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
Infine, inviamo un pacchetto al server chiamando ClientPlayNetworking.send
con l'istanza del nostro GiveGlowingEffectC2SPayload
.
ClientPlayNetworking.send(payload);
Questo può essere fatto nell'initializer comune, chiamando ServerPlayNetworking.registerGlobalReceiver
e passando un CustomPayload.Id
e un PlayPayloadHandler
.
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
È importante che convalidare il contenuto del pacchetto lato server.
In questo caso, verifichiamo se l'entità esiste in base al suo ID di rete.
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
Inoltre, l'entità-bersaglio deve essere un'entità vivente, e limitiamo la distanza tra il giocatore e l'entità-bersaglio a 5.
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
Ora, quando un giocatore cerca di usare una Patata Velenosa su un'entità vivente, l'effetto Luminescenza verrà applicato.