提供: Minecraft Modding Wiki
移動先: 案内検索

警告: ログインしていません。編集を行うと、あなたの IP アドレスが公開されます。ログインまたはアカウントを作成すれば、あなたの編集はその利用者名とともに表示されるほか、その他の利点もあります。

この編集を取り消せます。 下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。
最新版 編集中の文章
1行目: 1行目:
{{前提MOD|reqmod="Minecraft Forge Universal 10.12.1.1090~"}}
+
{{前提MOD|reqmod="Minecraft Forge Universal 10.12.0.xxx~"}}
 +
 
 
==パケットについて==
 
==パケットについて==
 
<p>1.7.2でパケット関連のシステムが大幅に変更され、nettyと呼ばれるオープンプロジェクトのシステムを利用したHandshake方式になりました。<br>
 
<p>1.7.2でパケット関連のシステムが大幅に変更され、nettyと呼ばれるオープンプロジェクトのシステムを利用したHandshake方式になりました。<br>
以前ここで解説したパケットシステムはメモリリークを誘発するので、こちらの方法が推奨されています。</p>
+
[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===
===PacketHander===
 
 
<source lang = "java">
 
<source lang = "java">
 +
package あなたのMODのパッケージ;
  
package mods.samplepacketmod;
+
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.simpleimpl.SimpleNetworkWrapper;
+
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;
 +
}
  
public class PacketHandler {
+
if (this.isPostInitialised) {
 +
  //postinit処理で登録しようとした。
 +
// You should log here!!
 +
return false;
 +
}
  
    //このMOD用のSimpleNetworkWrapperを生成。チャンネルの文字列は固有であれば何でも良い。MODIDの利用を推奨。
+
this.packets.add(clazz);
    //チャンネル名は20文字以内の文字数制限があるので注意。
+
return true;
    public static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel("SamplePacketMod");
+
}
  
 +
// 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());
 +
}
  
    public static void init() {
+
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);
 +
}
  
        /*IMesssageHandlerクラスとMessageクラスの登録。
+
// packetのデコード処理。
        *第三引数:MessageクラスのMOD内での登録ID。256個登録できる
+
@Override
        *第四引数:送り先指定。クライアントかサーバーか、Side.CLIENT Side.SERVER*/
+
protected void decode(ChannelHandlerContext ctx, FMLProxyPacket msg, List<Object> out) throws Exception {
        INSTANCE.registerMessage(MessageSampleHandler.class, MessageSample.class, 0, Side.SERVER);
+
ByteBuf payload = msg.payload();
    }
+
byte discriminator = payload.readByte();
}</source>
+
Class<? extends AbstractPacket> clazz = this.packets.get(discriminator);
 +
if (clazz == null) {
 +
throw new NullPointerException("No packet registered for discriminator: " + discriminator);
 +
}
  
===MessageSample===
+
AbstractPacket pkt = clazz.newInstance();
<source lang = "java">
+
pkt.decodeInto(ctx, payload.slice());
package mods.samplepacketmod;
 
  
import cpw.mods.fml.common.network.simpleimpl.IMessage;
+
EntityPlayer player;
import io.netty.buffer.ByteBuf;
+
switch (FMLCommonHandler.instance().getEffectiveSide()) {
 +
case CLIENT:
 +
player = this.getClientPlayer();
 +
pkt.handleClientSide(player);
 +
break;
  
public class MessageSample implements IMessage {
+
case SERVER:
 +
INetHandler netHandler = ctx.channel().attr(NetworkRegistry.NET_HANDLER).get();
 +
player = ((NetHandlerPlayServer) netHandler).playerEntity;
 +
pkt.handleServerSide(player);
 +
break;
  
    public byte data;
+
default:
 +
}
  
    public MessageSample(){}
+
out.add(pkt);
 +
}
  
    public MessageSample(byte par1) {
+
// 初期化メソッド。FMLInitializationEventで呼び出す。
        this.data= par1;
+
public void initialise() {
    }
+
this.channels = NetworkRegistry.INSTANCE.newChannel("チャンネル名称", this);
 +
}
  
    @Override//IMessageのメソッド。ByteBufからデータを読み取る。
+
// post初期化メソッド。FMLPostInitializationEventで呼び出す。
    public void fromBytes(ByteBuf buf) {
+
// packetの識別子がクライントとサーバーで同一なものか確認している。
        this.data= buf.readByte();
+
public void postInitialise() {
    }
+
if (this.isPostInitialised) {
 +
return;
 +
}
  
    @Override//IMessageのメソッド。ByteBufにデータを書き込む。
+
this.isPostInitialised = true;
    public void toBytes(ByteBuf buf) {
+
Collections.sort(this.packets, new Comparator<Class<? extends AbstractPacket>>() {
        buf.writeByte(this.data);
+
 
    }
+
@Override
}
+
public int compare(Class<? extends AbstractPacket> clazz1, Class<? extends AbstractPacket> clazz2) {
</source>
+
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);
 +
}
  
===MessageSampleHandler===
+
/**
<source lang = "java">
+
* あるプレイヤーにpacketを送る。
package mods.samplepacketmod;
+
* <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);
 +
}
  
import cpw.mods.fml.common.network.simpleimpl.IMessage;
+
/**
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
+
* ある一定範囲内にいるプレイヤーにpacketを送る。
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
+
* <p/>
import io.netty.buffer.ByteBuf;
+
* 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 class MessageSampleHandler implements IMessageHandler<MessageSample, IMessage> {
+
/**
 +
* 指定ディメンションにいるプレイヤー全員に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);
 +
}
  
    @Override//IMessageHandlerのメソッド
+
/**
    public IMessage onMessage(MessageSample message, MessageContext ctx) {
+
* サーバーにpacketを送る。
        //クライアントへ送った際に、EntityPlayerインスタンスはこのように取れる。
+
* <p/>
        //EntityPlayer player = SamplePacketMod.proxy.getEntityPlayerInstance();
+
* Adapted from CPW's code in cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper
        //サーバーへ送った際に、EntityPlayerインスタンス(EntityPlayerMPインスタンス)はこのように取れる。
+
*
        //EntityPlayer entityPlayer = ctx.getServerHandler().playerEntity;
+
* @param message The message to send
        //Do something.
+
*/
        return null;//本来は返答用IMessageインスタンスを返すのだが、旧来のパケットの使い方をするなら必要ない。
+
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();
  
===CommonProxy(クライアントにMessageを飛ばした際にEntityPlayerを必要とする場合)===
+
@EventHandler
<source lang = "java">
+
public void initialise(FMLInitializationEvent evt) {
package mods.samplepacketmod;
+
packetPipeline.initialise();
 +
//ここでパケットクラスの登録をする。
 +
packetPipeline.registerPacket(KeyHandlingPacket.class);
 +
}
  
import net.minecraft.entity.player.EntityPlayer;
+
@EventHandler
public class CommonProxy {
+
public void postInitialise(FMLPostInitializationEvent evt) {
    public EntityPlayer getEntityPlayerInstance() {return null;}
+
packetPipeline.postInitialise();
 
}
 
}
 
</source>
 
</source>
 
+
==パケットの登録==
===ClientProxy(クライアントにMessageを飛ばした際にEntityPlayerを必要とする場合)===
+
パケットの登録は、先ほどの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 mods.samplepacketmod;
+
package ak.ChainDestruction;
  
import net.minecraft.client.Minecraft;
+
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
  
public class ClientProxy extends CommonProxy {
+
  }
    @Override
 
    public EntityPlayer getEntityPlayerInstance() {
 
        return Minecraft.getMinecraft().thePlayer;
 
    }
 
}
 
</source>
 
  
===@Modクラス内の記述===
+
  @Override
<source lang = "java">
+
  public void handleServerSide(EntityPlayer player) {
package mods.samplepacketmod;
+
  //代入したいクラスの変数に代入。Worldインスタンスはplayerから取得できる。
 +
  ChainDestruction.interactblockhook.toggle = toggle;
 +
  ChainDestruction.interactblockhook.digUnderToggle = digUnderToggle;
  
import cpw.mods.fml.common.Mod;
+
  }
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
 
  
@Mod(modid="SamplePacketMod", name="SamplePacketMod", version="1.0.0",dependencies="required-after:Forge@[10.12.1.1090,)")
 
public class SamplePacketMod {
 
@Mod.Instance("SamplePacketMod")
 
public static SamplePacketMod instance;
 
    @SidedProxy(clientSide = "mods.samplepacketmod.ClientProxy", serverSide = "mods.samplepacketmod.CommonProxy")
 
    public static CommonProxy proxy;
 
@Mod.EventHandler
 
public void preInit(FMLPreInitializationEvent event)
 
{
 
        PacketHandler.init();
 
}
 
 
}
 
}
 
</source>
 
</source>
 
===パケットを送る際の記述===
 
<source lang = "java">
 
PacketHandler.INSTANCE.sendToServer(new MessageSample(data));
 
</source>
 
==解説==
 
Nettyを利用していることには変わりないので、パケット(Message)の書き方が1.6とは大分違ったものとなっている。<br>
 
ソース内に必要なコメントは載せているので、パケットの流れを解説する。<br>
 
 
PacketHandler.INSTANCE.sendToServer<br>
 
↓<br>
 
IMessageのtoBytesメソッド<br>
 
↓<br>
 
サーバーorクライアントへ<br>
 
↓<br>
 
IMessageのfromBytesメソッド<br>
 
↓<br>
 
IMessageHandlerのonMessageメソッド<br>
 
IMessageクラスに引数を指定したコンストラクタを用意する必要はないが、例のように、用意したほうが利便性は高い。
 

Minecraft Modding Wikiへの投稿はすべて、他の投稿者によって編集、変更、除去される場合があります。 自分が書いたものが他の人に容赦なく編集されるのを望まない場合は、ここに投稿しないでください。
また、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細はMinecraft Modding Wiki:著作権を参照)。 著作権保護されている作品は、許諾なしに投稿しないでください!

このページを編集するには、下記の確認用の質問に回答してください (詳細):

取り消し 編集の仕方 (新しいウィンドウで開きます)

このページで使用されているテンプレート: