(fixed typo) |
|||
384行目: | 384行目: | ||
TileEntity自体の説明や, ブロックの向きに関する説明はここでは省略する. これらの解説はカスタムパケットとは関係がない. | TileEntity自体の説明や, ブロックの向きに関する説明はここでは省略する. これらの解説はカスタムパケットとは関係がない. | ||
</p> | </p> | ||
+ | |||
+ | ===TileEntitySampleCore=== | ||
+ | <source lang = "java"> | ||
+ | @NetworkMod | ||
+ | ( | ||
+ | clientSideRequired = true, | ||
+ | serverSideRequired = false, | ||
+ | channels = "TileEntity", | ||
+ | packetHandler = PacketHandler.class | ||
+ | ) | ||
+ | </source> | ||
+ | <blockquote> | ||
+ | channelsはパケットで使うチャンネル名, packetHandlerは後述する'''IPacketHandler'''を実装したクラス. この指定がないとカスタムパケットは使われない. | ||
+ | </blockquote> | ||
===TileEntityNoop=== | ===TileEntityNoop=== |
2012年11月20日 (火) 15:21時点における版
この記事は"Minecraft Forge4.3x"を前提MODとしています。 |
目次
TileEntityのNBT同期
1.3以降, TileEntityのNBTはサーバーとクライアントで同期しなければならなくなった. これは1.2.5までのSMPの挙動そのものであり, これを解決するにはサーバーからクライアントにNBTの情報をパケットとして送信し, 同期を取る必要がある.
なお, チェストのようにGUIとコンテナ(Container)を使い, スロットを経由するもの(すなわちアイテム)はコンテナのスーパークラスで同期処理が行われている. よって今回のサンプルではブロックの向きをNBTに保存し, 同期取りを行う.
ソースコード
- TileEntitySampleCore.java
package mods.tileentitysample; import net.minecraft.src.*; import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.network.NetworkMod; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.registry.GameRegistry; import cpw.mods.fml.common.registry.LanguageRegistry; @Mod ( modid = "TileEntitySampleCore", name = "TileEntity Sample Core", version = "1.0.0" ) @NetworkMod ( clientSideRequired = true, serverSideRequired = false, channels = "TileEntity", packetHandler = PacketHandler.class ) public class TileEntitySampleCore { @SidedProxy(clientSide = "mods.tileentitysample.client.ClientProxy", serverSide = "mods.tileentitysample.CommonProxy") public static CommonProxy proxy; public static Block blockTileEntityNoop; @Mod.Init public void init(FMLInitializationEvent event) { blockTileEntityNoop = (new BlockTileEntityNoop(3999, 0)).setBlockName("tileentitysample"); GameRegistry.registerBlock(blockTileEntityNoop); GameRegistry.registerTileEntity(TileEntityNoop.class, "TileEntityNoop"); LanguageRegistry.addName(blockTileEntityNoop, "TileEntity Noop Block"); } }
- BlockTileEntityNoop.java
package mods.tileentitysample; import net.minecraft.src.*; public class BlockTileEntityNoop extends BlockContainer { public BlockTileEntityNoop(int blockId, int terrainId) { super(blockId, Material.rock); this.blockIndexInTexture = terrainId; this.setCreativeTab(CreativeTabs.tabDecorations); } @Override public int getBlockTexture(IBlockAccess world, int x, int y, int z, int side) { TileEntityNoop tileEntityNoop = (TileEntityNoop)world.getBlockTileEntity(x, y, z); if (side == 1) { return 62; } if (side == 0) { return 62; } int facing = tileEntityNoop.getFacing(); if (side != facing) { return 45; } else { return 44; } } @Override public int getBlockTextureFromSide(int side) { if (side == 1) { return 62; } else if (side == 0) { return 62; } else if (side == 4) { return 44; } else { return 45; } } @Override public boolean isOpaqueCube() { return false; } @Override public boolean renderAsNormalBlock() { return false; } @Override public TileEntity createNewTileEntity(World world) { return new TileEntityNoop(); } @Override public void onBlockAdded(World world, int x, int y, int z) { super.onBlockAdded(world, x, y, z); this.setDefaultDirection(world, x, y, z); } private void setDefaultDirection(World world, int x, int y, int z) { TileEntityNoop tileEntityNoop = (TileEntityNoop)world.getBlockTileEntity(x, y, z); if (!world.isRemote) { int var5 = world.getBlockId(x, y, z - 1); int var6 = world.getBlockId(x, y, z + 1); int var7 = world.getBlockId(x - 1, y, z); int var8 = world.getBlockId(x + 1, y, z); byte var9 = 3; if (Block.opaqueCubeLookup[var5] && !Block.opaqueCubeLookup[var6]) { var9 = 3; } if (Block.opaqueCubeLookup[var6] && !Block.opaqueCubeLookup[var5]) { var9 = 2; } if (Block.opaqueCubeLookup[var7] && !Block.opaqueCubeLookup[var8]) { var9 = 5; } if (Block.opaqueCubeLookup[var8] && !Block.opaqueCubeLookup[var7]) { var9 = 4; } tileEntityNoop.setFacing(var9); } } @Override public void onBlockPlacedBy(World world, int x, int y, int z, EntityLiving entityliving) { int playerFacing = MathHelper.floor_double((double)((entityliving.rotationYaw * 4F) / 360F) + 0.5D) & 3; byte facing = 0; if (playerFacing == 0) { facing = 2; } if (playerFacing == 1) { facing = 5; } if (playerFacing == 2) { facing = 3; } if (playerFacing == 3) { facing = 4; } TileEntity tileEntity = world.getBlockTileEntity(x, y, z); if (tileEntity != null && tileEntity instanceof TileEntityNoop) { ((TileEntityNoop)tileEntity).setFacing(facing); world.markBlockNeedsUpdate(x, y, z); } } }
- TileEntityNoop.java
package mods.tileentitysample; import net.minecraft.src.*; public class TileEntityNoop extends TileEntity { private byte facing; public byte getFacing() { return this.facing; } public void setFacing(byte facing) { this.facing = facing; } @Override public void readFromNBT(NBTTagCompound nbttagcompound) { super.readFromNBT(nbttagcompound); facing = nbttagcompound.getByte("facing"); } @Override public void writeToNBT(NBTTagCompound nbttagcompound) { super.writeToNBT(nbttagcompound); nbttagcompound.setByte("facing", facing); } @Override public Packet getDescriptionPacket() { return PacketHandler.getPacket(this); } }
- CommonProxy.java
package mods.tileentitysample; import net.minecraft.src.*; public class CommonProxy { public World getClientWorld() { return null; } }
- PacketHandler.java
package mods.tileentitysample; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; import net.minecraft.src.*; import cpw.mods.fml.common.network.Player; import cpw.mods.fml.common.network.IPacketHandler; public class PacketHandler implements IPacketHandler { @Override public void onPacketData(NetworkManager network, Packet250CustomPayload packet, Player player) { if (packet.channel.equals("TileEntity")) { ByteArrayDataInput data = ByteStreams.newDataInput(packet.data); int x, y, z; byte facing; try { x = data.readInt(); y = data.readInt(); z = data.readInt(); facing = data.readByte(); World world = TileEntitySampleCore.proxy.getClientWorld(); TileEntity tileEntity = world.getBlockTileEntity(x, y, z); if (tileEntity instanceof TileEntityNoop) { TileEntityNoop tileEntityNoop = (TileEntityNoop)tileEntity; tileEntityNoop.setFacing(facing); } } catch (Exception e) { e.printStackTrace(); } } } public static Packet getPacket(TileEntityNoop tileEntityNoop) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); int x = tileEntityNoop.xCoord; int y = tileEntityNoop.yCoord; int z = tileEntityNoop.zCoord; byte facing = tileEntityNoop.getFacing(); try { dos.writeInt(x); dos.writeInt(y); dos.writeInt(z); dos.writeByte(facing); } catch (Exception e) { e.printStackTrace(); } Packet250CustomPayload packet = new Packet250CustomPayload(); packet.channel = "TileEntity"; packet.data = bos.toByteArray(); packet.length = bos.size(); packet.isChunkDataPacket = true; return packet; } }
- ClientProxy.java
package mods.tileentitysample.client; import net.minecraft.src.*; import cpw.mods.fml.client.FMLClientHandler; import cpw.mods.fml.common.Side; import cpw.mods.fml.common.asm.SideOnly; import mods.tileentitysample.CommonProxy; @SideOnly(Side.CLIENT) public class ClientProxy extends CommonProxy { @Override public World getClientWorld() { return FMLClientHandler.instance().getClient().theWorld; } }
解説
TileEntity自体の説明や, ブロックの向きに関する説明はここでは省略する. これらの解説はカスタムパケットとは関係がない.
TileEntitySampleCore
@NetworkMod ( clientSideRequired = true, serverSideRequired = false, channels = "TileEntity", packetHandler = PacketHandler.class )
channelsはパケットで使うチャンネル名, packetHandlerは後述するIPacketHandlerを実装したクラス. この指定がないとカスタムパケットは使われない.
TileEntityNoop
@Override public Packet getDescriptionPacket() { return PacketHandler.getPacket(this); }
適切なタイミングで呼ばれ, パケットを返すメソッド. このとき返すパケットはサーバーからクライアントへのパケットである. 今回は後述するPacketHandlerクラスでカスタムパケットの生成を行っている. なお適切なタイミングとはワールド保存時, ワールドログイン時, プレイヤーが右クリックしたときなどである.
PacketHandler
カスタムパケットの読み込み
public class PacketHandler implements IPacketHandler
カスタムパケットを読み込むためのハンドラを作るために, IPacketHandlerインタフェースを実装する.
@Override public void onPacketData(NetworkManager network, Packet250CustomPayload packet, Player player) { if (packet.channel.equals("TileEntity")) { ByteArrayDataInput data = ByteStreams.newDataInput(packet.data); int x, y, z; byte facing; try { x = data.readInt(); y = data.readInt(); z = data.readInt(); facing = data.readByte(); World world = TileEntitySampleCore.proxy.getClientWorld(); TileEntity tileEntity = world.getBlockTileEntity(x, y, z); if (tileEntity instanceof TileEntityNoop) { TileEntityNoop tileEntityNoop = (TileEntityNoop)tileEntity; tileEntityNoop.setFacing(facing); } } catch (Exception e) { e.printStackTrace(); } } }
IPacketHandlerで実装するメソッドの実装例. 引数のPacket250CustomPayload packetが送られてくるパケット. 詳細解説は後述.
if (packet.channel.equals("TileEntity"))
@NetworkModで指定したチャンネルのチェック. 複数のパケットを使う場合, このチャンネルを増やしてパケットを判別してもよいし, パケット自体にパケットインデックスなどをつけてもよい.
ByteArrayDataInput data = ByteStreams.newDataInput(packet.data);
Packet.dataはbyte配列なので, そのままではint型などは扱いづらい. そこでDataInputStreamかByteArrayDataInputを使ってバイトストリームとして扱う.
x = data.readInt(); y = data.readInt(); z = data.readInt(); facing = data.readByte();
x, y, zはworldからTileEntityを取得するために必要な情報. 今回同期するデータは一番下のfacing.
World world = TileEntitySampleCore.proxy.getClientWorld(); TileEntity tileEntity = world.getBlockTileEntity(x, y, z); if (tileEntity instanceof TileEntityNoop) { TileEntityNoop tileEntityNoop = (TileEntityNoop)tileEntity; tileEntityNoop.setFacing(facing); }
プロキシを利用してクライアントのワールドを取得し, 該当座標にあるTileEntityを取得する. 取得したTileEntityのセッタに同期したいデータを渡してデータの同期を行っている. なおここではnullチェックを省いている.
カスタムパケットの生成
public static Packet getPacket(TileEntityNoop tileEntityNoop) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); int x = tileEntityNoop.xCoord; int y = tileEntityNoop.yCoord; int z = tileEntityNoop.zCoord; byte facing = tileEntityNoop.getFacing(); try { dos.writeInt(x); dos.writeInt(y); dos.writeInt(z); dos.writeByte(facing); } catch (Exception e) { e.printStackTrace(); } Packet250CustomPayload packet = new Packet250CustomPayload(); packet.channel = "TileEntity"; packet.data = bos.toByteArray(); packet.length = bos.size(); packet.isChunkDataPacket = true; return packet; }
引数のTileEntityで同期したいデータのパケットを生成するメソッド. 使うパケットはPacket250CustomPayload.
ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos);
packet.dataに書き込むためのバイト配列と, そのストリームを生成.
dos.writeInt(x); dos.writeInt(y); dos.writeInt(z); dos.writeByte(facing);
バイト配列にデータを書き込む. x, y, zは先述した通りTileEntityの生成に必要なデータ. 同期したいデータ自体はfacing.
Packet250CustomPayload packet = new Packet250CustomPayload(); packet.channel = "TileEntity"; packet.data = bos.toByteArray(); packet.length = bos.size(); packet.isChunkDataPacket = true; return packet;
新しいパケットを生成し, パケットにチャンネルを設定する. このチャンネル名は先述した@NetworkModで指定したもの. dataに先ほど作成したバイト配列を, lengthにそのサイズを設定している.
実際の挙動
かまどと同じテクスチャの無機能ブロックを追加し, そのブロックの向きを同期しているサンプルである. バニラのかまどはメタデータを利用して向きを保存している. この場合カスタムパケットは必要ないことに注意しよう. カスタムパケットはあくまでバニラで同期されないデータを同期するための処理でる.