🇺🇦 Українська (Ukrainian - Ukraine)
🇺🇦 Українська (Ukrainian - Ukraine)
Зовнішній вигляд
🇺🇦 Українська (Ukrainian - Ukraine)
🇺🇦 Українська (Ukrainian - Ukraine)
Зовнішній вигляд
Ця сторінка написана для версії:
1.21.4
Мережа в Minecraft використовується для того, щоб клієнт і сервер могли спілкуватися один з одним. Мережа– це широка тема, тому ця сторінка поділена на кілька категорій.
Пакети - це основна концепція мережі в Minecraft. Пакети складаються з довільних даних, які можуть надсилатися від сервера до клієнта і навпаки. Ознайомтеся з наведеною нижче схемою, яка наочно демонструє мережеву архітектуру в Fabric:
Зверніть увагу, що пакети є мостом між сервером і клієнтом; це тому, що майже все, що ви робите в грі, певним чином включає мережу, знаєте ви це чи ні. Наприклад, коли ви надсилаєте повідомлення чату, на сервер надсилається пакет із вмістом. Потім сервер надсилає ще один пакет усім іншим клієнтам із вашим повідомленням.
Пам’ятайте про одну важливу річ: сервер завжди працює, навіть у грі наодинці та LAN. Пакети все ще використовуються для обміну даними клієнту і сервера, навіть якщо ніхто інший не грає з вами. Коли говорять про сторони в мережі, використовуються терміни «логічний клієнт» і «логічний сервер». Інтегрований сервер для гри наодинці/LAN та виділений сервер є логічними серверами, але лише виділений сервер можна вважати фізичним сервером.
Коли стан між клієнтом і сервером не синхронізовано, ви можете зіткнутися з проблемами, через які сервер або інші клієнти не погоджуються з тим, що робить інший клієнт. Це часто називають «десинхронізацією». Під час написання власного моду вам може знадобитися надіслати пакет даних, щоб синхронізувати стан сервера та всіх клієнтів.
INFO
Корисне навантаження — це дані, які надсилаються в пакеті.
Це можна зробити, створивши Java Record
з параметром BlockPos
, який реалізує 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;
}
}
Водночас ми визначили:
Ідентифікатор
, який використовується для ідентифікації корисного навантаження нашого пакета. Для цього прикладу нашим ідентифікатором буде fabric-docs-reference:summon_lightning
.public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
CustomPayload.Id
для унікальної ідентифікації цього спеціального корисного навантаження. Ми будемо посилатися на це ID як у нашому загальному, так і в клієнтському коді.public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
PacketCodec
, щоб гра знала, як серіалізувати/десеріалізувати вміст пакету.public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
Ми також замінили getId
, щоб повернути наш ідентифікатор корисного навантаження.
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
Перш ніж надіслати пакет із нашим власним корисним навантаженням, нам потрібно його зареєструвати.
INFO
S2C
і C2S
— два загальні суфікси, які означають Server-to-Client і Client-to-Server відповідно.
Це можна зробити в нашому загальному ініціалізаторі за допомогою PayloadTypeRegistry.playS2C().register
, який приймає CustomPayload.Id
і PacketCodec
.
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);
Подібний метод існує для реєстрації корисних даних клієнт-сервер: PayloadTypeRegistry.playC2S().register
.
Щоб надіслати пакет із нашим власним корисним навантаженням, ми можемо використати ServerPlayNetworking.send
, який приймає ServerPlayerEntity
і CustomPayload
.
Почнемо зі створення нашого предмета, блискавичного татера. Ви можете замінити use
, щоб активувати дію під час використання елемента. У цьому випадку надішлімо пакети гравцям у світі серверів.
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;
}
}
Розгляньмо код вище.
Ми надсилаємо пакети лише тоді, коли дію розпочато на сервері, повертаючись раніше з перевіркою isClient
:
if (world.isClient()) {
return ActionResult.PASS;
}
Ми створюємо екземпляр корисного навантаження з позицією користувача:
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
Нарешті, ми отримуємо гравців у світі сервера через PlayerLookup
і надсилаємо пакет кожному гравцеві.
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
INFO
API Fabric надає PlayerLookup
, набір допоміжних функцій, які шукатимуть гравців на сервері.
Для опису функціональності цих методів часто використовується термін «відстеження». Це означає, що сутність або блок на сервері відомі клієнту гравця (в межах їх видимості), і сутність або блокова сутність повинні повідомити відстеження змін клієнтів.
Відстеження є важливою концепцією для ефективної роботи в мережі, щоб лише необхідні гравці отримували повідомлення про зміни шляхом надсилання пакетів.
Щоб отримати пакет, надісланий із сервера на клієнті, вам потрібно вказати, як ви будете обробляти вхідний пакет.
Це можна зробити в ініціалізаторі клієнта, викликавши ClientPlayNetworking.registerGlobalReceiver
і передавши CustomPayload.Id
і PlayPayloadHandler
, який є функціональним інтерфейсом.
У цьому випадку ми визначимо дію, яка буде запускатися в реалізації реалізації PlayPayloadHandler
(як лямбда-вираз).
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);
}
});
Розгляньмо код вище.
Ми можемо отримати доступ до даних із нашого корисного навантаження, викликавши методи отримання запису. У цьому випадку payload.pos()
. Який потім можна використовувати для отримання позицій "x", "y" і "z".
BlockPos lightningPos = payload.pos();
Нарешті ми створюємо LightningEntity
і додаємо його до світу.
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
Тепер, якщо ви додасте цей мод на сервер і коли гравець використовує наш блискавичний татер, кожен гравець побачить блискавку вражаючи позицію користувача.
Подібно до надсилання пакета клієнту, ми починаємо зі створення спеціального корисного навантаження. Цього разу, коли гравець використовує отруйну картоплю на живій істоті, ми просимо сервер застосувати до неї ефект сяяння.
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;
}
}
Ми передаємо відповідний кодек разом із посиланням на метод, щоб отримати значення із запису для створення цього кодека.
Потім ми реєструємо наше корисне навантаження в нашому загальному ініціалізаторі. Однак цього разу як корисне навантаження клієнт-сервер за допомогою PayloadTypeRegistry.playC2S().register
.
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);
Щоб надіслати пакет, додаймо дію, коли гравець використовує отруйну картоплю. Ми будемо використовувати UseEntityCallback
подія, щоб зберегти речі стисло.
Ми реєструємо подію в нашому ініціалізаторі клієнта та використаємо isClient()
, щоб гарантувати, що дія лише запускається на логічному клієнті.
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;
});
Ми створимо екземпляр нашого GiveGlowingEffectC2SPayload
з необхідними аргументами. У цьому випадку ідентифікатор мережі з цільової сутності.
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
Нарешті, ми надсилаємо пакет на сервер, викликаючи ClientPlayNetworking.send
з примірником нашого GiveGlowingEffectC2SPayload
.
ClientPlayNetworking.send(payload);
Це можна зробити в загальному ініціалізаторі, викликавши ServerPlayNetworking.registerGlobalReceiver
і передавши CustomPayload.Id
і 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
Важливо, щоб ви перевірили вміст пакета на стороні сервера.
У цьому випадку ми перевіряємо, чи існує сутність, на основі її ідентифікатора мережі.
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
Крім того, цільова сутність має бути живою істотою, і ми обмежуємо діапазон цільової сутності від гравця до 5.
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
Тепер, коли будь-який гравець намагатиметься використати отруйну картоплю на живій істоті, до нього буде застосовано ефект світіння.