본문으로 건너뛰기
버전: Minecraft 26.2

네트워크와 동기화

페이로드 채널​

PayloadChannel<String> greetings = mod.network().channel("greetings", PayloadCodec.string());
greetings.onServerReceive((message, sender) -> { /* 서버 스레드 */ });
greetings.onClientReceive(message -> { /* 클라이언트 스레드 */ });
greetings.sendToPlayer(player, "hello");
greetings.sendToServer("thanks"); // 클라이언트에서
  • 채널은 common 엔트리포인트에서 선언하며, 채널 ID는 <mod id>:<path> 형식입니다.
  • 받은 메시지는 해당 게임 스레드(서버 스레드 또는 클라이언트 스레드)에서 처리합니다.
  • 크기 한도: 서버 방향은 32 KiB, 클라이언트 방향은 1 MiB입니다. 한도를 넘으면 POLY-NETWORK-003 오류가 납니다.
  • 잘못된 페이로드: 버리고 POLY-NETWORK-004로 기록합니다.
  • 연결되기 전에 보내면 POLY-NETWORK-002 오류가 납니다.
신뢰 경계

클라이언트는 어떤 값이든 보낼 수 있으므로, 서버는 클라이언트가 보낸 값을 항상 검증해야 합니다.

코덱 직접 만들기​

문자열 외의 메시지는 PayloadCodec<T>의 encode와 decode를 구현해서 보냅니다.

record Ping(int id, String text) {}

PayloadCodec<Ping> PING_CODEC = new PayloadCodec<>() {
@Override
public void encode(Ping value, PayloadWriter out) {
out.writeInt(value.id()).writeString(value.text());
}

@Override
public Ping decode(PayloadReader in) {
return new Ping(in.readInt(), in.readString());
}
};

PayloadWriter/PayloadReader는 boolean, int, long, double, String, 바이트 배열을 읽고 쓸 수 있습니다. 코덱은 네트워크 스레드에서 실행되므로 게임 상태에 접근하지 마세요.

접속 준비 단계의 교환​

플레이어가 월드에 들어오기 전, 게임의 접속 준비(configuration) 단계에서 서버가 클라이언트에 질문하고 답을 받을 수 있습니다. 버전 확인, 설정 맞추기, 클라이언트가 먼저 받아야 하는 데이터를 전달할 때 사용합니다. 교환이 끝나야 플레이어가 월드에 들어오며, 서버는 이 단계에서 접속을 거절할 수 있습니다.

// common 엔트리포인트: 서버와 클라이언트 양쪽에서 선언
handshake = mod.network().configurationExchange("handshake", PayloadCodec.string())
.serverRequest((id, name) -> Optional.of("version?")) // 이 플레이어에게 보낼 내용, 비우면 건너뜀
.serverCheck((id, name, reply) -> reply.filter(v -> v.startsWith("my_mod "))
.isPresent() ? Optional.empty() // 접속 허용
: Optional.of("Install My Mod on your client to join.")); // 거절 메시지

// client 엔트리포인트
handshake.clientAnswer(request -> "my_mod 1.2.0");
  1. 서버의 serverRequest가 플레이어마다 보낼 메시지를 정합니다. Optional.empty()를 돌려주면 그 플레이어는 교환을 건너뜁니다.
  2. 클라이언트의 clientAnswer가 답합니다.
  3. 서버의 serverCheck가 그 답을 확인합니다. timeoutTicks(기본 100틱, 5초) 안에 답이 오지 않으면 빈 값을 받으며, 모드가 없는 클라이언트는 답하지 않습니다. 빈 값을 반환하면 접속을 허용하고, 메시지를 반환하면 그 메시지와 함께 접속을 끊습니다.
  • serverCheck가 없으면 답이 없는 플레이어는 거절됩니다("This server needs the mod … on your client.").
  • 서버 쪽 코드는 서버 스레드에서, 클라이언트 쪽 코드는 클라이언트 스레드에서 호출됩니다.
  • 교환은 게임이 레지스트리를 맞춘 뒤 플레이어를 월드에 넣기 전에, 선언한 순서대로 하나씩 진행합니다.
  • 모드 코드가 예외를 던지면 POLY-NETWORK-005를 기록합니다. 요청이 실패하면 교환을 건너뛰고, 답이 실패하면 답하지 않으며, 검사가 실패하면 그 플레이어를 거절합니다.

엔티티 데이터​

플레이어를 포함한 모든 엔티티에 모드 데이터를 붙일 수 있습니다.

EntityDataKey MANA = mod.content().entityData("mana", true); // true: 플레이어 자신의 클라이언트로 동기화
StoredData mana = player.data(MANA);
mana.data().putInt("value", 10);
mana.markChanged();
  • 데이터는 엔티티 저장 데이터의 poly_entity_data에 저장되며, 플레이어는 리스폰해도 유지됩니다.
  • 클라이언트에서는 읽기만 할 수 있으므로 서버에서 바꾸세요.

동기화 옵션​

EntityDataKey MOOD = mod.content().entityData("golem_mood", EntityDataOptions.DEFAULT
.syncedToTrackers() // 그 엔티티를 보는 모든 클라이언트로 전송
.withVersion(2, (data, fromVersion) -> { /* … */ })); // 옛 버전 저장 데이터는 불러올 때 변환
옵션보내는 대상보내는 시점
syncedToOwner() (entityData(path, true))그 플레이어 자신의 클라이언트값이 바뀔 때와 접속할 때
syncedToTrackers()그 엔티티를 보는 모든 클라이언트클라이언트가 엔티티를 보기 시작할 때 한 번, 그리고 값이 바뀔 때마다
(기본)보내지 않음—

클라이언트에서는 entity.data(key)로 읽습니다. 동기화하지 않은 다른 플레이어나 몹의 데이터는 클라이언트로 전송되지 않습니다.

월드 저장소 동기화​

StorageOptions options = StorageOptions.DEFAULT.syncedToClients();
world.storage(ResourceId.of("my_mod", "info"), options); // 클라이언트에서는 읽기 전용 사본

같은 저장소 ID를 다른 옵션으로 열면 IllegalArgumentException이 발생합니다. 클라이언트에서도 같은 옵션으로 여세요.

블록 엔티티 동기화​

BlockEntityDefinition.builder().syncToClients()를 켜면 markChanged()를 호출할 때 블록 엔티티 데이터가 클라이언트로 전송됩니다. 자세한 내용은 블록 엔티티와 메뉴를 참고하세요.

오류 코드​

코드뜻
POLY-NETWORK-001채널 ID 오류
POLY-NETWORK-002연결되기 전에 전송함, 또는 등록이 닫힌 뒤에 선언함
POLY-NETWORK-003크기 초과
POLY-NETWORK-004해독 실패(버림)
POLY-NETWORK-005접속 준비 교환의 모드 코드가 예외를 던짐, 또는 잘못된 대기 시간
검증 범위

싱글 플레이 통합 서버의 네트워크 왕복만 자동으로 검증했습니다. 전용 서버와 별도 클라이언트 사이의 접속은 EULA 동의가 필요해 자동으로 검증하지 않았습니다.

Buy me a coffee