NeoForge 26.1 Docs
  • 문서
  • 노트
NeoForge 26.1
NeoForge 26.1
v26.1
학습 진도
0 / 77 챕터 완료방문 0
BlockEntity 기초 — CounterBlockEntityBlockEntityRenderer 구현Entity 등록과 AttributeSupplierEntityRenderer와 EntityModelAI Goal 시스템CustomPacketPayload — 클라이언트/서버 패킷 통신AttachmentTypes — 엔티티·블록에 데이터 부착WorldGen Feature 기초 — 광물 생성
고급 시스템

CustomPacketPayload — 클라이언트/서버 패킷 통신

NeoForge 26의 CustomPacketPayload record와 StreamCodec으로 클라이언트↔서버 패킷을 정의하고, RegisterPayloadHandlersEvent와 PacketDistributor로 전송하는 전체 과정을 학습합니다.

CustomPacketPayload — 클라이언트/서버 패킷 통신

NeoForge 26은 구식 SimpleChannel 대신 CustomPacketPayload 인터페이스를 사용합니다. Java record로 패킷 구조를 선언하고, StreamCodec으로 직렬화 방식을 지정하며, RegisterPayloadHandlersEvent로 핸들러를 등록합니다. 이 챕터에서는 서버→클라이언트 인사 메시지 패킷(GreetingPayload)을 예제로 전체 흐름을 익힙니다.


1. CustomPacketPayload record 정의

패킷 하나는 CustomPacketPayload를 구현하는 record 하나에 대응합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/network/GreetingPayload.java
public record GreetingPayload(String message) implements CustomPacketPayload {
 
    /** 패킷 식별자 — 네임스페이스:경로 형태의 고유 ID */
    public static final CustomPacketPayload.Type<GreetingPayload> TYPE =
        new CustomPacketPayload.Type<>(
            Identifier.fromNamespaceAndPath("examplemod", "greeting")
        );
 
    /**
     * StreamCodec — 네트워크 직렬화/역직렬화 정의.
     * composite()은 필드별 코덱과 생성자를 묶어 줍니다.
     */
    public static final StreamCodec<FriendlyByteBuf, GreetingPayload> STREAM_CODEC =
        StreamCodec.composite(
            ByteBufCodecs.STRING_UTF8, GreetingPayload::message,
            GreetingPayload::new
        );
 
    @Override
    public Type<? extends CustomPacketPayload> type() {
        return TYPE;
    }
}
멤버역할
TYPE패킷 고유 식별자. Identifier는 모드ID:이름 형태여야 합니다
STREAM_CODEC직렬화(쓰기)·역직렬화(읽기) 로직. 필드 순서와 생성자 인수 순서가 일치해야 합니다
type()CustomPacketPayload 인터페이스 필수 구현 — 자신의 TYPE 반환

2. StreamCodec 상세

StreamCodec은 바이트 버퍼와 Java 타입 사이의 변환을 정의합니다.

2-1. 기본 제공 코덱

코덱Java 타입
ByteBufCodecs.INTint
ByteBufCodecs.STRING_UTF8String
ByteBufCodecs.BOOLboolean
ByteBufCodecs.LONGlong
ByteBufCodecs.FLOATfloat

2-2. 복합 코덱 (composite)

// 필드 2개짜리 예제: String + int
public record ItemSyncPayload(String name, int count) implements CustomPacketPayload {
    // ...
    public static final StreamCodec<FriendlyByteBuf, ItemSyncPayload> STREAM_CODEC =
        StreamCodec.composite(
            ByteBufCodecs.STRING_UTF8, ItemSyncPayload::name,
            ByteBufCodecs.INT,        ItemSyncPayload::count,
            ItemSyncPayload::new       // (name, count) → new ItemSyncPayload(name, count)
        );
}

2-3. 컬렉션 코덱

// List<String> 전송
StreamCodec<FriendlyByteBuf, List<String>> listCodec =
    ByteBufCodecs.STRING_UTF8.apply(ByteBufCodecs.list());

3. RegisterPayloadHandlersEvent — 핸들러 등록

패킷 핸들러는 Mod Bus에서 발행되는 RegisterPayloadHandlersEvent에서 등록합니다.

// examplemod-template-26.1.2/src/main/java/com/example/examplemod/network/NetworkHandlers.java
@EventBusSubscriber(modid = ExampleMod.MODID)
public class NetworkHandlers {
 
    @SubscribeEvent
    public static void onRegisterPayloadHandlers(RegisterPayloadHandlersEvent event) {
        PayloadRegistrar registrar = event.registrar("1");  // 프로토콜 버전
 
        // 서버 → 클라이언트
        registrar.playToClient(
            GreetingPayload.TYPE,
            GreetingPayload.STREAM_CODEC,
            (payload, context) -> {
                // 핸들러는 네트워크 스레드에서 실행됩니다.
                // 메인 스레드 작업이 필요하면 context.enqueueWork(...)로 전환하세요.
                context.player().sendSystemMessage(
                    Component.literal(payload.message())
                );
            }
        );
 
        // 클라이언트 → 서버
        // registrar.playToServer(MyRequestPayload.TYPE, MyRequestPayload.STREAM_CODEC,
        //     (payload, context) -> { /* 서버에서 실행됨 */ });
    }
}

그리고 ExampleMod 생성자에서 Mod Bus에 등록합니다.

// ExampleMod 생성자
modEventBus.addListener(NetworkHandlers::onRegisterPayloadHandlers);
// 또는 NetworkHandlers 클래스에 @EventBusSubscriber(modid = MODID) 어노테이션 사용

4. PacketDistributor — 패킷 전송

핸들러 등록 후, 코드 어디서든 PacketDistributor로 패킷을 전송합니다.

// 서버 → 특정 플레이어
PacketDistributor.sendToPlayer(serverPlayer, new GreetingPayload("Hello!"));
 
// 서버 → 청크를 추적 중인 모든 플레이어 (예: 블록 변경 알림)
PacketDistributor.sendToPlayersTrackingChunk(serverLevel, chunkPos, payload);
 
// 서버 → 전체 플레이어
PacketDistributor.sendToAllPlayers(payload);
 
// 클라이언트 → 서버 (클라이언트 코드에서만 호출 가능)
PacketDistributor.sendToServer(payload);
메서드방향대상
sendToPlayer서버 → 클라특정 ServerPlayer
sendToPlayersTrackingChunk서버 → 클라해당 청크 추적자 전원
sendToAllPlayers서버 → 클라접속 중인 전체 플레이어
sendToServer클라 → 서버서버 (클라이언트 코드만)

5. 안티패턴

5-1. 잘못된 방향 등록

⚠️ playToClient / playToServer 방향 불일치

// ❌ playToClient로 등록했는데 sendToServer로 전송 → 핸들러 호출 안 됨
registrar.playToClient(MyPayload.TYPE, codec, handler);
// ...나중에...
PacketDistributor.sendToServer(new MyPayload(...)); // ❌ 등록 방향 불일치
 
// ✅ 흐름 맞추기
// playToClient = sendToPlayer / sendToAllPlayers / sendToPlayersTrackingChunk
// playToServer = sendToServer (클라이언트 코드에서만)

playToClient로 등록된 패킷은 서버가 클라이언트로 보낼 때만 핸들러가 호출됩니다. 반대 방향으로 전송하면 핸들러가 실행되지 않고 연결이 끊어질 수 있습니다.

5-2. Forge Bus vs Mod Bus 혼동

⚠️ RegisterPayloadHandlersEvent는 반드시 Mod Bus

// ❌ Forge Bus에 등록 → 이벤트 수신 안 됨
NeoForge.EVENT_BUS.addListener(NetworkHandlers::onRegisterPayloadHandlers);
 
// ✅ Mod Bus에 등록
modEventBus.addListener(NetworkHandlers::onRegisterPayloadHandlers);

RegisterPayloadHandlersEvent는 Forge Bus(NeoForge.EVENT_BUS)가 아닌 Mod Bus에서 발행됩니다. Forge Bus에 등록하면 이벤트를 받지 못해 모든 패킷 핸들러가 누락됩니다.

5-3. 무한 패킷 루프

⚠️ 핸들러 안에서 같은 패킷 재전송 금지

// ❌ 핸들러가 같은 패킷을 다시 보내면 무한 루프
registrar.playToClient(PingPayload.TYPE, codec, (payload, context) -> {
    PacketDistributor.sendToServer(new PingPayload()); // ❌ 무한 순환
});
 
// ✅ 핸들러는 패킷을 수신한 쪽에서만 로직 실행
registrar.playToServer(PingPayload.TYPE, codec, (payload, context) -> {
    // 서버에서 처리만 하고 같은 패킷을 다시 클라이언트로 보내지 않음
    LOGGER.info("Ping received from client");
});

6. 전체 흐름 요약

[서버 이벤트 발생]
    ↓
PacketDistributor.sendToPlayer(player, new GreetingPayload("Hello!"))
    ↓  [네트워크 직렬화: STREAM_CODEC.encode]
    ↓  [패킷 전송]
    ↓  [역직렬화: STREAM_CODEC.decode]
[클라이언트 핸들러 호출]
    ↓
(payload, context) -> context.player().sendSystemMessage(...)

요약

단계클래스핵심 내용
1GreetingPayloadrecord + CustomPacketPayload · TYPE · STREAM_CODEC
2NetworkHandlers@SubscribeEvent RegisterPayloadHandlersEvent → registrar.playToClient/Server
3ExampleMod 생성자modEventBus.addListener(NetworkHandlers::...)
4전송 지점PacketDistributor.sendToPlayer / sendToServer

다음 챕터에서는 Capability 시스템으로 엔티티·블록·아이템에 커스텀 데이터를 부착하는 방법을 학습합니다.

AI Goal 시스템

registerGoals 오버라이드로 커스텀 Mob에 FloatGoal, MeleeAttackGoal, WaterAvoidingRandomStrollGoal 등 Vanilla Goal을 추가하고, goalSelector와 targetSelector의 우선순위 체계를 이해합니다.

AttachmentTypes — 엔티티·블록에 데이터 부착

NeoForge AttachmentTypes(구 Capabilities)로 플레이어·엔티티·블록 엔티티에 임의 데이터를 Codec 기반으로 저장·복원하는 방법을 학습합니다.

On this page

CustomPacketPayload — 클라이언트/서버 패킷 통신1. CustomPacketPayload record 정의2. StreamCodec 상세2-1. 기본 제공 코덱2-2. 복합 코덱 (composite)2-3. 컬렉션 코덱3. RegisterPayloadHandlersEvent — 핸들러 등록4. PacketDistributor — 패킷 전송5. 안티패턴5-1. 잘못된 방향 등록5-2. Forge Bus vs Mod Bus 혼동5-3. 무한 패킷 루프6. 전체 흐름 요약요약
NeoForge 26.1 Docs

NeoForge 26.1 모딩 개발 문서 사이트

GitHubDiscord

문서

  • 문서
  • 노트

GitHub

  • GitHub
  • Discord

© 2026 NeoForge 26.1 Docs. 콘텐츠는 MIT 라이선스로 제공됩니다.

Built with Next.js · Tailwind CSS · shadcn/ui