チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

20121030追記

よくよく考えてみたら構造体とか共用体ではなく、継承&メンバ拡張してベースクラス型の配列にまとめちゃうのがオブジェクト指向的には本当ですね。こんな感じに。

  • ベースクラス(パケット種別を記憶)
    • 継承クラス1(パケット種別1に対応するデータ実部を拡張する)
    • 継承クラス2(パケット種別2に対応するデータ実部を拡張する)

BinaryFormatterはベースクラスにキャストした状態でも実際に確保されている内容でシリアライズしてくれますし、共用体だとどうしても使ったり使わなかったりするフィールドが生じがちですが、多態風にしちゃえばジャストサイズに切り詰められるのがウレシイところ。確認に使った適当なサンプルを置いときます。

[System.Serializable()]
public enum PacketType { // 適当なパケット種別
	Unknown,
	CharacterGenerate,
	CharacterBroken,
	BulletGenerate,
	BulletBroken
}

[System.Serializable()]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class PayloadBase {
	public PacketType packetType; // ベースはとりあえずパケット種別だけ
}

[System.Serializable()]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class UnitPacketPayload : PayloadBase {
	public int unitID; // ユニットのID的なものを追加した継承
}

[System.Serializable()]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class TransformingUnitPacketPayload : UnitPacketPayload {
	public float position_x; // さらに位置情報を追加した継承
	public float position_y;
	public float position_z;
	public float rotation_x;
	public float rotation_y;
	public float rotation_z;
	public float rotation_w;
	public void SetTransform( Transform trans ) {
		if ( trans == null ) return;
		position_x = trans.position.x;
		position_y = trans.position.y;
		position_z = trans.position.z;
		rotation_x = trans.rotation.x;
		rotation_y = trans.rotation.y;
		rotation_z = trans.rotation.z;
		rotation_w = trans.rotation.w;
	}
}

[System.Serializable()]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class CharacterPacketPayload : TransformingUnitPacketPayload {
	public float life; // さらにさらにライフ情報を追加、という具合に。
}

[System.Serializable()]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class Packet { // PayloadBaseから派生する内容を複数並べて1パケットにする。
	public int senderID; // 送信者識別みたいなのとか
	public float sendTime; // 送信時刻とか
	public System.Collections.Generic.List<PayloadBase> payload; // 内容リスト
}

[UnityEngine.ContextMenu("DoTest")]
private void DoTest() {
	
	// 送信パケットを作って、何個か適当に埋めます。
	Packet packet = new Packet();
	packet.senderID = 0xFF;
	packet.sendTime = 100.0f;
	packet.payload = new System.Collections.Generic.List<PayloadBase>();
	
	{
		CharacterPacketPayload item = new CharacterPacketPayload();
		item.packetType = PacketType.CharacterGenerate;
		item.unitID = 1;
		item.SetTransform(transform);
		item.life = 100.0f;
		packet.payload.Add(item);
	}
	
	{
		TransformingUnitPacketPayload item = new TransformingUnitPacketPayload();
		item.packetType = PacketType.BulletGenerate;
		item.unitID = 2;
		item.SetTransform(transform);
		packet.payload.Add(item);
	}
	
	{
		UnitPacketPayload item = new UnitPacketPayload();
		item.packetType = PacketType.CharacterBroken;
		item.unitID = 1;
		packet.payload.Add(item);
	}
	
	{
		UnitPacketPayload item = new UnitPacketPayload();
		item.packetType = PacketType.BulletBroken;
		item.unitID = 2;
		packet.payload.Add(item);
	}
	
	// シリアライズしてバイト列へ変換。
	byte[] serializedBinary = null; // コイツにバイト列を放り込みます。
	System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
	using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream()) {
		bf.Serialize (memoryStream, packet);
		serializedBinary = memoryStream.ToArray();
	}
	
	// 上でシリアライズしたバイト列を、新しくPacketインスタンスにデシリアライズします。
	Packet deserializedPacket = null;
	if ( serializedBinary != null && 0 < serializedBinary.Length ) {
		using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream()) {
			memoryStream.Write( serializedBinary, 0, serializedBinary.Length );
			memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
			deserializedPacket = bf.Deserialize(memoryStream) as Packet;
		}
	}
	
	bf = null; // ここでブレーク張ってdeserializedPacketの中身を確認。
}

実際読み取る時は、enum同様にパケット種別でswitchしますが、そこからは共用体メンバではないので、ベースクラスのインスタンスから、パケット種別で示し合わせた派生クラスへのキャストをトライして、メンバにアクセスするって感じです。