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.
Qual È l'Importanza del Networking?
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.
Introduzione al Networking
Definire un Payload
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.
java
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;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Allo stesso tempo, abbiamo definito:
- Un
Identifierutilizzato per identificare il payload del nostro pacchetto. Per questo esempio, il nostro identificatore saràfabric-docs-reference:summon_lightning.
java
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");1
- Un'istanza pubblica statica di
CustomPayload.Idper identificare in modo univoco questo payload personalizzato. Faremo riferimento a questo ID sia nel codice comune che in quello client.
java
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);1
- Un'istanza pubblica statica di
PacketCodecaffinché il gioco sappia come serializzare/deserializzare i contenuti del pacchetto.
java
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);1
Abbiamo anche fatto override di getId per restituire l'ID del nostro payload.
java
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}1
2
3
4
2
3
4
Registrare un Payload
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.
java
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);1
Esiste un metodo simile per registrare i payload da client a server: PayloadTypeRegistry.playC2S().register.
Inviare un Pacchetto al Client
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.
java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Esaminiamo il codice sopra.
Inviamo pacchetti solo quando l'azione viene avviata sul server, uscendo anticipatamente con un controllo isClient:
java
if (world.isClient()) {
return ActionResult.PASS;
}1
2
3
2
3
Creiamo un'istanza del payload con la posizione dell'utente:
java
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());1
Infine, otteniamo i giocatori nel mondo del server tramite PlayerLookup e inviamo un pacchetto a ciascun giocatore.
java
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}1
2
3
2
3
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.
Ricevere un Pacchetto sul Client
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).
java
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);
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.
java
BlockPos lightningPos = payload.pos();1
Infine, creiamo una LightningEntity e aggiungiamola al mondo.
java
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}1
2
3
4
5
6
2
3
4
5
6
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.
Inviare un Pacchetto al Server
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.
java
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;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
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.
java
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);1
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.
java
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;
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Creiamo un'istanza del nostro GiveGlowingEffectC2SPayload con gli argomenti necessari. In questo caso, l'ID di rete dell'entità bersaglio.
java
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());1
Infine, inviamo un pacchetto al server chiamando ClientPlayNetworking.send con l'istanza del nostro GiveGlowingEffectC2SPayload.
java
ClientPlayNetworking.send(payload);1
Ricevere un Pacchetto sul Server
Questo può essere fatto nell'initializer comune, chiamando ServerPlayNetworking.registerGlobalReceiver e passando un CustomPayload.Id e un PlayPayloadHandler.
java
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));
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
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.
java
Entity entity = context.player().getWorld().getEntityById(payload.entityId());1
Inoltre, l'entità-bersaglio deve essere un'entità vivente, e limitiamo la distanza tra il giocatore e l'entità-bersaglio a 5.
java
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {1
Ora, quando un giocatore cerca di usare una Patata Velenosa su un'entità vivente, l'effetto Luminescenza verrà applicato.
















