提供: Minecraft Modding Wiki
この編集を取り消せます。
下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。
最新版 | 編集中の文章 | ||
1行目: | 1行目: | ||
− | {{前提MOD|reqmod="Minecraft Forge Universal 10.12. | + | {{前提MOD|reqmod="Minecraft Forge Universal 10.12.0.xxx~"}} |
+ | |||
==パケットについて== | ==パケットについて== | ||
<p>1.7.2でパケット関連のシステムが大幅に変更され、nettyと呼ばれるオープンプロジェクトのシステムを利用したHandshake方式になりました。<br> | <p>1.7.2でパケット関連のシステムが大幅に変更され、nettyと呼ばれるオープンプロジェクトのシステムを利用したHandshake方式になりました。<br> | ||
− | + | [http://www.minecraftforge.net/wiki/Netty_Packet_Handling Sirgingalot氏によるチュートリアル]が良いものなので、それの和訳ベースで幾つか注釈をつけて解説したいと思います。</p> | |
+ | |||
+ | ==はじめに== | ||
+ | これは、FMLで用意されているSimpleChannelHandlerの代替手段です。(注:というのも、SimpleChannelHandler は現状上手く動作しない)<br> | ||
+ | この手法では、自動でパケットの識別子が生成され、クライアント、サーバーの両方の処理を1つのパケットクラス内で処理することが出来ます。 | ||
+ | |||
+ | ==パケット構造の例== | ||
+ | 今回は、以下に記すAbstract Classをパケットをすべてのパケットで継承し、その上で、必要な拡張を行う形を取ります。 <br> | ||
+ | サイド別のパケット受け取り処理はそれぞれ'handle'メソッドで行うことが出来ます。<br> | ||
+ | 注意:このクラスを継承した子クラスには必ず、空のコンストラクタを設定して下さい。<br> | ||
+ | (注:つまり、引数を持つコンストラクタを作ったらな、必ず、引数なしのものも作る必要が有るということです。)<br> | ||
+ | (注:あとで、パケットクラスの例を載せますが、引数ありのコンストラクタを用意するのが色々便利なので、空のコンストラクタを付けることを忘れないようにして下さい。)<br> | ||
+ | ===AbstractPacket Class=== | ||
+ | AbstractPacket.java | ||
+ | <source lang = "java"> | ||
+ | package あなたのMODのパッケージ; | ||
+ | |||
+ | import io.netty.buffer.ByteBuf; | ||
+ | import io.netty.channel.ChannelHandlerContext; | ||
+ | import net.minecraft.entity.player.EntityPlayer; | ||
+ | |||
+ | |||
+ | /** | ||
+ | * AbstractPacket クラス PacketPipelineで扱うパケットはすべてこのクラスを継承する. | ||
+ | * @author sirgingalot | ||
+ | */ | ||
+ | public abstract class AbstractPacket { | ||
+ | |||
+ | /** | ||
+ | * パケットのデータをByteBufに変換する. 複雑なデータ(NBTとか)はハンドラーが用意されている ( @link{cpw.mods.fml.common.network.ByteBuffUtils}を見てくれ) | ||
+ | * | ||
+ | * @param ctx channel context(和訳注:殆ど使わない) | ||
+ | * @param buffer 変換するByteBufの変数。これにwriteしていく。 | ||
+ | */ | ||
+ | public abstract void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer); | ||
+ | |||
+ | /** | ||
+ | * ByteBufからデータを取り出す. 複雑なデータはハンドラーが用意されている ( @link{cpw.mods.fml.common.network.ByteBuffUtils}を見てくれ) | ||
+ | * | ||
+ | * @param ctx channel context(普段は使わない) | ||
+ | * @param buffer データを取り出すByteBuf。これからreadしていく。 | ||
+ | */ | ||
+ | public abstract void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer); | ||
+ | |||
+ | /** | ||
+ | * クライアント側でパケットを受け取った後処理するメソッド。decodeIntoでByteBufが読み出されたあとに実行される。 | ||
+ | * | ||
+ | * @param player the player reference | ||
+ | */ | ||
+ | public abstract void handleClientSide(EntityPlayer player); | ||
+ | |||
+ | /** | ||
+ | * サーバー側でパケットを受け取った後処理するメソッド。decodeIntoでByteBufが読み出されたあとに実行される。 | ||
+ | * | ||
+ | * @param player the player reference | ||
+ | */ | ||
+ | public abstract void handleServerSide(EntityPlayer player); | ||
+ | } | ||
+ | </source> | ||
+ | ==パケットハンドラー== | ||
+ | パケットを処理するメインのクラスです。 | ||
+ | インラインエンコード/デコード処理を可能とする、パケットの識別子は自動的に生成されます。 | ||
+ | また、再度別処理をパケットクラスで行うことも可能となります。 | ||
+ | 注:チャンネル名称を必ず変更して下さい。(現在"チャンネル名称"になっている) | ||
− | == | + | ===PacketPipeline Class=== |
− | |||
<source lang = "java"> | <source lang = "java"> | ||
+ | package あなたのMODのパッケージ; | ||
− | + | import io.netty.buffer.ByteBuf; | |
+ | import io.netty.buffer.Unpooled; | ||
+ | import io.netty.channel.ChannelHandler; | ||
+ | import io.netty.channel.ChannelHandlerContext; | ||
+ | import io.netty.handler.codec.MessageToMessageCodec; | ||
+ | import java.util.Collections; | ||
+ | import java.util.Comparator; | ||
+ | import java.util.EnumMap; | ||
+ | import java.util.LinkedList; | ||
+ | import java.util.List; | ||
+ | |||
+ | import net.minecraft.client.Minecraft; | ||
+ | import net.minecraft.entity.player.EntityPlayer; | ||
+ | import net.minecraft.entity.player.EntityPlayerMP; | ||
+ | import net.minecraft.network.INetHandler; | ||
+ | import net.minecraft.network.NetHandlerPlayServer; | ||
+ | import cpw.mods.fml.common.FMLCommonHandler; | ||
+ | import cpw.mods.fml.common.network.FMLEmbeddedChannel; | ||
+ | import cpw.mods.fml.common.network.FMLOutboundHandler; | ||
import cpw.mods.fml.common.network.NetworkRegistry; | import cpw.mods.fml.common.network.NetworkRegistry; | ||
− | import cpw.mods.fml.common.network. | + | import cpw.mods.fml.common.network.internal.FMLProxyPacket; |
import cpw.mods.fml.relauncher.Side; | import cpw.mods.fml.relauncher.Side; | ||
+ | import cpw.mods.fml.relauncher.SideOnly; | ||
+ | |||
+ | /** | ||
+ | * Packet pipeline クラス。パケットの登録と、パケットのSendToを行う。 | ||
+ | * @author sirgingalot | ||
+ | * some code from: cpw | ||
+ | */ | ||
+ | @ChannelHandler.Sharable | ||
+ | public class PacketPipeline extends MessageToMessageCodec<FMLProxyPacket, AbstractPacket> { | ||
+ | |||
+ | //こちらで使わないので、知る必要はないが、登録するChannelの変数。基本的に1MOD(1 packetPipelineインスタンス)に1Channel | ||
+ | private EnumMap<Side, FMLEmbeddedChannel> channels; | ||
+ | //こちらも使わない。Packetクラスの保存先。1Channelに付き256種類のPacketが登録できる。 | ||
+ | private LinkedList<Class<? extends AbstractPacket>> packets = new LinkedList<Class<? extends AbstractPacket>>(); | ||
+ | //使わない。PostInitされたかどうか。 | ||
+ | private boolean isPostInitialised = false; | ||
+ | |||
+ | /** | ||
+ | * pipelineにpacketを登録するメソッド。識別子は自動でセットされる。256個までしか登録できない。 | ||
+ | * | ||
+ | * @param clazz the class to register | ||
+ | * | ||
+ | * @return 登録が成功したかどうか。256個以上の登録、同クラス登録、postInitialise内で登録するとfalseになる。 | ||
+ | */ | ||
+ | public boolean registerPacket(Class<? extends AbstractPacket> clazz) { | ||
+ | if (this.packets.size() > 256) { | ||
+ | //256個以上登録しようとした。 | ||
+ | // You should log here!!(logを出力すべき。) | ||
+ | return false; | ||
+ | } | ||
+ | if (this.packets.contains(clazz)) { | ||
+ | //同じクラスを登録しようとした。 | ||
+ | // You should log here!! | ||
+ | return false; | ||
+ | } | ||
− | + | if (this.isPostInitialised) { | |
+ | //postinit処理で登録しようとした。 | ||
+ | // You should log here!! | ||
+ | return false; | ||
+ | } | ||
− | + | this.packets.add(clazz); | |
− | + | return true; | |
− | + | } | |
+ | // packetのエンコード処理。ここで、識別子が設定されている。 | ||
+ | @Override | ||
+ | protected void encode(ChannelHandlerContext ctx, AbstractPacket msg, List<Object> out) throws Exception { | ||
+ | ByteBuf buffer = Unpooled.buffer(); | ||
+ | Class<? extends AbstractPacket> clazz = msg.getClass(); | ||
+ | if (!this.packets.contains(msg.getClass())) { | ||
+ | throw new NullPointerException("No Packet Registered for: " + msg.getClass().getCanonicalName()); | ||
+ | } | ||
− | + | byte discriminator = (byte) this.packets.indexOf(clazz); | |
+ | buffer.writeByte(discriminator); | ||
+ | msg.encodeInto(ctx, buffer); | ||
+ | FMLProxyPacket proxyPacket = new FMLProxyPacket(buffer.copy(), ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get()); | ||
+ | out.add(proxyPacket); | ||
+ | } | ||
− | + | // packetのデコード処理。 | |
− | + | @Override | |
− | + | protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception { | |
− | + | ByteBuf payload = msg.payload(); | |
− | + | byte discriminator = payload.readByte(); | |
− | } | + | Class<? extends AbstractPacket> clazz = this.packets.get(discriminator); |
+ | if (clazz == null) { | ||
+ | throw new NullPointerException("No packet registered for discriminator: " + discriminator); | ||
+ | } | ||
− | = | + | AbstractPacket pkt = clazz.newInstance(); |
− | + | pkt.decodeInto(ctx, payload.slice()); | |
− | |||
− | + | EntityPlayer player; | |
− | + | switch (FMLCommonHandler.instance().getEffectiveSide()) { | |
+ | case CLIENT: | ||
+ | player = this.getClientPlayer(); | ||
+ | pkt.handleClientSide(player); | ||
+ | break; | ||
− | + | case SERVER: | |
+ | INetHandler netHandler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get(); | ||
+ | player = ((NetHandlerPlayServer) netHandler).playerEntity; | ||
+ | pkt.handleServerSide(player); | ||
+ | break; | ||
− | + | default: | |
+ | } | ||
− | + | out.add(pkt); | |
+ | } | ||
− | + | // 初期化メソッド。FMLInitializationEventで呼び出す。 | |
− | + | public void initialise() { | |
− | + | this.channels = NetworkRegistry.INSTANCE.newChannel("チャンネル名称", this); | |
+ | } | ||
− | + | // post初期化メソッド。FMLPostInitializationEventで呼び出す。 | |
− | + | // packetの識別子がクライントとサーバーで同一なものか確認している。 | |
− | + | public void postInitialise() { | |
− | + | if (this.isPostInitialised) { | |
+ | return; | ||
+ | } | ||
− | + | this.isPostInitialised = true; | |
− | + | Collections.sort(this.packets, new Comparator<Class<? extends AbstractPacket>>() { | |
− | + | ||
− | + | @Override | |
− | } | + | public int compare(Class<? extends AbstractPacket> clazz1, Class<? extends AbstractPacket> clazz2) { |
− | + | int com = String.CASE_INSENSITIVE_ORDER.compare(clazz1.getCanonicalName(), clazz2.getCanonicalName()); | |
+ | if (com == 0) { | ||
+ | com = clazz1.getCanonicalName().compareTo(clazz2.getCanonicalName()); | ||
+ | } | ||
+ | |||
+ | return com; | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | @SideOnly(Side.CLIENT) | ||
+ | private EntityPlayer getClientPlayer() { | ||
+ | return Minecraft.getMinecraft().thePlayer; | ||
+ | } | ||
+ | /** | ||
+ | * プレイヤー全員にpacketを送る。 | ||
+ | * <p/> | ||
+ | * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper | ||
+ | * | ||
+ | * @param message The message to send | ||
+ | */ | ||
+ | public void sendToAll(AbstractPacket message) { | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALL); | ||
+ | this.channels.get(Side.SERVER).writeAndFlush(message); | ||
+ | } | ||
− | + | /** | |
− | < | + | * あるプレイヤーにpacketを送る。 |
− | + | * <p/> | |
+ | * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper | ||
+ | * | ||
+ | * @param message The message to send | ||
+ | * @param player The player to send it to | ||
+ | */ | ||
+ | public void sendTo(AbstractPacket message, EntityPlayerMP player) { | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.PLAYER); | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(player); | ||
+ | this.channels.get(Side.SERVER).writeAndFlush(message); | ||
+ | } | ||
− | + | /** | |
− | + | * ある一定範囲内にいるプレイヤーにpacketを送る。 | |
− | + | * <p/> | |
− | + | * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper | |
+ | * | ||
+ | * @param message The message to send | ||
+ | * @param point The {@link cpw.mods.fml.common.network.NetworkRegistry.TargetPoint} around which to send | ||
+ | */ | ||
+ | public void sendToAllAround(AbstractPacket message, NetworkRegistry.TargetPoint point) { | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.ALLAROUNDPOINT); | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(point); | ||
+ | this.channels.get(Side.SERVER).writeAndFlush(message); | ||
+ | } | ||
− | public | + | /** |
+ | * 指定ディメンションにいるプレイヤー全員にpacketを送る。 | ||
+ | * <p/> | ||
+ | * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper | ||
+ | * | ||
+ | * @param message The message to send | ||
+ | * @param dimensionId The dimension id to target | ||
+ | */ | ||
+ | public void sendToDimension(AbstractPacket message, int dimensionId) { | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.DIMENSION); | ||
+ | this.channels.get(Side.SERVER).attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).set(dimensionId); | ||
+ | this.channels.get(Side.SERVER).writeAndFlush(message); | ||
+ | } | ||
− | + | /** | |
− | + | * サーバーにpacketを送る。 | |
− | + | * <p/> | |
− | + | * Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper | |
− | + | * | |
− | + | * @param message The message to send | |
− | + | */ | |
− | + | public void sendToServer(AbstractPacket message) { | |
− | + | this.channels.get(Side.CLIENT).attr(FMLOutboundHandler.FML_MESSAGETARGET).set(FMLOutboundHandler.OutboundTarget.TOSERVER); | |
+ | this.channels.get(Side.CLIENT).writeAndFlush(message); | ||
+ | } | ||
} | } | ||
</source> | </source> | ||
+ | ==Pipelineの登録== | ||
+ | 上のクラスを以下の方法でFMLに登録します。 | ||
+ | ===@Modクラス内での記述=== | ||
+ | <source lang = "java"> | ||
+ | public static final PacketPipeline packetPipeline = new PacketPipeline(); | ||
− | + | @EventHandler | |
− | + | public void initialise(FMLInitializationEvent evt) { | |
− | + | packetPipeline.initialise(); | |
+ | //ここでパケットクラスの登録をする。 | ||
+ | packetPipeline.registerPacket(KeyHandlingPacket.class); | ||
+ | } | ||
− | + | @EventHandler | |
− | public | + | public void postInitialise(FMLPostInitializationEvent evt) { |
− | + | packetPipeline.postInitialise(); | |
} | } | ||
</source> | </source> | ||
− | + | ==パケットの登録== | |
− | === | + | パケットの登録は、先ほどのpacketPipelineクラスのinitialise()メソッド内か、例のソースのようにinitialise()メソッド後に行って下さい。 |
+ | ==パケットクラスの例== | ||
+ | 私が動作確認したパケットクラスを載せておきます。 | ||
+ | また、Tinker's Constructの例は | ||
+ | [https://github.com/SlimeKnights/TinkersConstruct/tree/1.7/src/main/java/tconstruct/util/network/packet Tinker's Construct Packets] | ||
+ | で見ることが出来ます。 | ||
<source lang = "java"> | <source lang = "java"> | ||
− | package | + | package ak.ChainDestruction; |
− | import | + | import io.netty.buffer.ByteBuf; |
+ | import io.netty.channel.ChannelHandlerContext; | ||
import net.minecraft.entity.player.EntityPlayer; | import net.minecraft.entity.player.EntityPlayer; | ||
+ | //クライアント側でキー入力によって変化したboolean変数をサーバー側に伝達するパケット。AbstractPacketを継承 | ||
+ | public class KeyHandlingPacket extends AbstractPacket | ||
+ | { | ||
+ | //保持しておくboolean型変数 | ||
+ | boolean toggle; | ||
+ | boolean digUnderToggle; | ||
+ | //引数を持つコンストラクタを追加する場合は、空のコンストラクタを用意してくれとのこと。 | ||
+ | public KeyHandlingPacket() { | ||
+ | } | ||
+ | //パケット生成を簡略化するために、boolean型変数を引数に取るコンストラクタを追加。 | ||
+ | public KeyHandlingPacket(boolean ver1, boolean ver2) { | ||
+ | toggle = ver1; | ||
+ | digUnderToggle = ver2; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) { | ||
+ | //ByteBufに変数を代入。基本的にsetメソッドではなく、writeメソッドを使う。 | ||
+ | buffer.writeBoolean(toggle); | ||
+ | buffer.writeBoolean(digUnderToggle); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) { | ||
+ | //ByteBufから変数を取得。こちらもgetメソッドではなく、readメソッドを使う。 | ||
+ | toggle = buffer.readBoolean(); | ||
+ | digUnderToggle = buffer.readBoolean(); | ||
+ | |||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void handleClientSide(EntityPlayer player) { | ||
+ | //今回はクライアントの情報をサーバーに送るので、こちらはなにもしない。 | ||
+ | //NO OP | ||
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | @Override | |
− | + | public void handleServerSide(EntityPlayer player) { | |
− | + | //代入したいクラスの変数に代入。Worldインスタンスはplayerから取得できる。 | |
+ | ChainDestruction.interactblockhook.toggle = toggle; | ||
+ | ChainDestruction.interactblockhook.digUnderToggle = digUnderToggle; | ||
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
</source> | </source> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− |