🎮

サーバークライアント間のメッセージング / Minecraft 1.20.1 Forge Modding

2025/03/12に公開

MODを作成すると .jar ファイルが生成されますが、これは初期設定でクライアントとサーバーの両方で実行できるMODとして動作します。ただし、それはMOD自体がクライアント/サーバーで同一の処理をする場合のみです。(また、目的に応じて、クライアントサイドのみのMODであったりも制作できます)

例えばアイテムの登録であったりブロックの追加は共通の処理を記述できます。ここまでなら特にクライアントサイド/サーバーサイドを意識することはありませんが、欲が出てサーバー側でこういう処理をしたいとかクライアント側だけの機能を持たせたいみたいなことが起きます。

また、両サイドで動作するとなるとクライアント側MODの動作とサーバー側MODで共通の動作をするMODでない場合にはクライアント側MODとサーバー側MODの間でなんらかのデータをやり取りしたくなります。そういった場合に今回の方法が利用できます。

クライアントとサーバー間で通信する方法

端的に説明するとネットワークハンドラー、パケットというたった2つのクラスでこれを実現できます。

  1. ネットワークハンドラーはチャネルという、簡単にいうなら通信のトンネルを定義してクライアント/サーバーに登録します。
  2. チャネルに対しメッセージを送信します。このとき、メッセージはパケットです。
  3. パケットを送信するときはネットワークハンドラーが持っているチャネルに対し、クライアント/サーバーからどっち方向にこういうメッセージ(パケット)を送る、みたいな指示をすれば通信できます。
  4. 受信したあとの処理はパケットクラスの handleメソッドに記述します。

簡単ですね?

ネットワークハンドラー

public class NetworkHandler {
    private static final String PROTOCOL_VERSION = "1";

    // ここでチャネルを登録します
    public static final SimpleChannel CHANNEL = NetworkRegistry.ChannelBuilder
            .named(new ResourceLocation("mymod", "main_channel"))
            .networkProtocolVersion(() -> PROTOCOL_VERSION)
            .clientAcceptedVersions(PROTOCOL_VERSION::equals)
            .serverAcceptedVersions(PROTOCOL_VERSION::equals)
            .simpleChannel();

    public static void registerEvents() {
        int id = 0;
        // ここでチャネルが扱うパケットを登録します。
        CHANNEL.registerMessage(id++, MyMod.class, MyModPacket::encode, MyMod::decode, MyMod::handle);
    }
}

パケット

public class MyModPacket {
    // 文字列を扱うパケットです
    private final String message;

    // コンストラクタでstringを受け付けてインスタンス変数に保持する
    public MyModPacket(String message) {
        this.message = message;
    }

    // エンコード(おそらく通信で扱うためにバイナリデータに変換してます)
    public static void encode(MyModPacket packet, FriendlyByteBuf buf) {
        buf.writeUtf(packet.message);
    }

    // デコード(プログラムで扱えるようにバイナリデータをデコードして取り出します)
    public static MyModPacket decode(FriendlyByteBuf buf) {
        return new MyModPacket(buf.readUtf());
    }

    // これがパケット受信時の処理です。パケットを受信したらこれが呼ばれます。
    // NetworkHandlerでCHANNEL.registerMessageしてるからですね、多分。
    public static void handle(AnticheatPacket packet, Supplier<NetworkEvent.Context> ctx) {
        System.out.println("DEBUG: " + packet.message);

        ctx.get().enqueueWork(() -> {
            // クライアントでメッセージを表示
            Objects.requireNonNull(Minecraft.getInstance().player).sendSystemMessage(Component.literal("[MyMod] " + packet.message));
        });

        ctx.get().setPacketHandled(true);
    }
}

Done.(表示メッセージ違いますがお気になさらず)

Discussion