TOP PAGE

Unity用自作のTCP通信用とシリアライズ用モジュールの使い方を示す簡単な例

ここで、紹介する作品は、ドラックによるカメラの回転を、全てのクライアントで行えて、その画面を共有する作品です。
この作品では、以下の2つの自作モジュール群の利用例として作ったものです。
このページの例は、サーバーとクライアント機能をまとめた1つの作品例ですが、 サーバー用の作品とクライアント用作品が別々になっている2つの作品例は、別途こちらのページで紹介しています
各モジュール詳細は、下記表のリンクをが参照ください。

TCP通信用モジュール TcpCommunication.cs
TcpAppServer.cs
TCPのバイナリや文字列通信を容易にするモージュールです。
シリアライズ用モジュール BinaryPack.cs 汎用的なバイナリのシリアライズ・デシリアライズ モジュールで上記の通信用データを対象に使っています。


この作品を次のイメージのようにパッケージにしたファイル(NetDragRotate.unitypackage)は、
  →このリンクからダウンロードできます。
(Unity 2021.2以降でないと使えませんのご注意ください)
次の3つの作品が各フォルダに入っています。それぞれのフォルダ内のSceneを開いて実行できます。
ネットワークの部分は、Scriptフォルダ内TcpCommunication.csTcpAppServer.csを利用して動作します。
シリアライスの部分は、Scriptフォルダ内のBinaryPack.csを利用して動作します。
  1. EX_DragRotateフォルダがネットワーク無しの作品
     (ネットワークを無視したドラックでカメラを回転させるだけの作品)
  2. EX_DragRotateNotNetフォルダがネットワーク無しの作品2
     (ネットワークにすることを考慮して、上記を変更したネットワーク無しの作品)
  3. EX_DragRotateNetフォルダがネットワークの作品
     (サーバー兼クライアント作品で、ドラック操作した視点を共有するネットワーク作品)

ScriptSerializeフォルダの中に、各種シリアライズ・クラスが入っています。
上記の2と3のプロジェクトでSerializeStrVector3クラスを利用しています。
また、2のプロジェクトでSerializeVector3Arrayクラスを利用しています。
また、3のプロジェクトでSerializeCamera.csクラスを利用しています。



ネットワークを無視したドラックでカメラを回転させるだけの作品

ネットワーク無しの作品を作って、次にそれをネットワーク化を考えて行きます。
それは、ドラック操作を行うと、目標のGameObjectを中心にカメラを回転させる簡単な作品です。
その動作イメージを下記に示します。
(この動作ムービーを紹介したXへのリンク)
Hierarcyの中に作った空のGameObjectの名前をMainと、Capsuleを配置し、 このMainに下記ソース(DragRotate.cs)アタッチするだけで、実現している作品です。
なお、Cubeはスクリプトで生成しています。マウスを押し始めた位置と、移動後の位置の差から、回転方向を得てターゲットを中心を基準にカメラを回転させています。
using UnityEngine;

public class DragRotate : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 prevMousePosition;// 以前のマウス位置
    bool prevMouseButton0 = false;// 以前のマウスボタン状態

    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);//カメラ位置初期位置
    }

    void Update()
    {
        bool nowMouseButtonOn0 = Input.GetMouseButton(0);// 現在のマウス位置を取得
        if (this.prevMouseButton0 == false && nowMouseButtonOn0)//マウスボタンを押し込んだ瞬間?
        {
            this.prevMousePosition = Input.mousePosition;
        }
        else if (nowMouseButtonOn0)//マウスボタンを押し続けている間?
        {
            Vector3 diff = Input.mousePosition - prevMousePosition;
            diff *= 0.2f;// 回転速度を付加
            // カメラを、Center_positionを中心に回転
            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            this.prevMousePosition = Input.mousePosition;
        }
        this.prevMouseButton0 = nowMouseButtonOn0;
    }
}
なお、上記では現在のマウス位置を取得のInput.GetMouseButton(0);だけを利用して、ドラック操作を判定している。
対して、下記のように記述すると正しく動作できない場合がある。
右のUpdateの方が、始めに作ったコードで、簡潔で正しく動作しそうに見える。
しかし、Unity 2022.3.1f1の時点で、Unityエディタでの実行で
Input.GetMouseButtonDown(0)のtrue判定を見失う場合がある実験結果が得られた。
(ビルドした作品では正しく動作するので、不具合原因を見つけるのに苦労した。)
以上の不具合より、このコードは使わないこにして、前述のフラグ操作のコードに変更した。
しかし、この考え方は後述のネットワーク化した時の受信側で採用しています。
using UnityEngine;

public class DragRotate : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 prevMousePosition;// 以前のマウス位置
    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);//カメラ位置初期位置
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))//マウスボタンを押し込んだ瞬間?
        {
            this.prevMousePosition = Input.mousePosition;
        }
        else if (Input.GetMouseButton(0))//マウスボタンを押し続けている間?
        {
            Vector3 diff = Input.mousePosition - prevMousePosition;
            diff *= 0.2f;// 回転速度を付加
            // カメラを、Center_positionを中心に回転
            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            this.prevMousePosition = Input.mousePosition;
        }
    }
}

ネットワークにすることを考慮して、上記を変更したネットワーク無しの作品

「ネットワークにすることの考慮」の一つは、「マウス操作のイベントで、カメラを移動させる」というこれまの流れが次のように変わることである。
  1. クライアント側は「マウス操作のイベントで、情報をイベント送信用変数にセット」
  2. クライアント側は「イベント送信用変数で送信する。」
  3. サーバ側は「イベントの受信データを受信用変数にセットする。」
  4. サーバ側は「イベント受信用変数を使って、カメラを回転」
  5. サーバ側は「カメラの情報を、カメラ用送信変数にセット」
  6. サーバ側は「カメラ用送信変数で送信」
  7. クライアント側は「受信データをカメラ用受信変数にセット」
  8. クライアント側は「カメラ用受信変でカメラを移動」
かなり、ややこしい感じられるが、要は変数に一旦記憶してから処理する発想をすればよい。
この考え方で、前述をコードを変更すると、次のようになります。
using System.IO;
using UnityEngine;

public class DragRotateNotNet : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 prevMousePosition;// 以前のマウス位置
    bool prevMouseButton0 = false;// 以前のマウスボタン状態

    // オペレータ操作によるイベント情報記憶用
    string eventStr = ""; // イベント識別文字列 "MouseButtonDown" or "MouseButton"
    Vector3 mousePosition;// 情報伝達に使うマウスの位置

    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);//カメラ位置初期位置
    }

    void FixedUpdate()
    {
        // オペレータ操作によるイベント情報は、一旦、情報伝達変数に記憶
        bool nowMouseButtonOn0 = Input.GetMouseButton(0);// 現在のマウス位置を取得
        if (this.prevMouseButton0 == false && nowMouseButtonOn0)//マウスボタンを押し込んだ瞬間?
        {
           this.eventStr = "MouseButtonDown";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        else if (nowMouseButtonOn0)//マウスボタンを押し続けている間?
        {
            this.eventStr = "MouseButton";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        this.prevMouseButton0 = nowMouseButtonOn0;


        // 伝達変数のイベント情報で、動作を進行させる(後で、サーバ側イベント受信部の処理になる)
        if (this.eventStr == "MouseButtonDown")//マウスボタンを押し込んだ瞬間?
        {
            this.prevMousePosition = this.mousePosition;
        }
        else if (this.eventStr == "MouseButton")//マウスボタンを押し続けている間?
        {
            Vector3 diff = mousePosition - prevMousePosition;
            diff *= 0.2f;// 回転速度を付加

            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            prevMousePosition = mousePosition;
            eventStr = "";
        }
    }
}
  の箇所が大きく変更・追加した部分です。
void Update()をvoid FixedUpdate()に変更していますが」、これは異議を唱える方もいらっしゃるでしょう。
個人的には、固定サイクルで行った方が問題が起きにくいと感じます。デバックもしやすいでしょう。

データのシリアライズ

もう一つのネットワーク化する前にしておきたい内容は、データのシリアライズです。
タダでさえ、ネットワークのコードがややこしいものです。 ネットワーク化する前にシリアライズ関数やクラス群は、しっかりと正しく動作することをチェック検証すべきです。

この作品におけるシリアライズ対象データは、クライアントから送る「"MouseButtonDown" or "MouseButton" とマウス座標」と、 サーバーから送る「カメラ情報(位置と角度)」です。
「"MouseButtonDown" or "MouseButton" とマウス座標」は、 以前に作って検証済みのBinaryPack.csを利用した「SerializeStrVector3.cs」を使うことにします。

もう一つのシリアライズ対象となる「カメラ情報(位置と角度)」のため次のSerializeVector3Arrayを作ってみました。
このSerializeVector3Arrayは、汎用的に使えることを目的に、Vector3のオブジェクトの配列をシリアライズ対象としたもので、 カメラ情報の位置のVwector3を先頭要素、角度のVwector3を次の要素に記憶して、これでバイナリにして送受信させることにします。
以下がSerializeVector3Arrayのソースコードです。
using UnityEngine;
using System;
using System.Buffers.Binary;

public class SerializeVector3Array//Vector3のオブジェクトの配列をシリアライズ
{
    const int ObjectOfVector3Array = 30; //識別ID (重複しないように注意して番号を指定)
    public Vector3 [] v3array;// Serialize対象データ

    public static bool Initialize()//デリゲート配列に登録関数
    {   //MyDataのオブジェクト識別IDの復元関数の登録
        if (BinaryPack.dencoders[ObjectOfVector3Array] != null) throw new Exception("BinaryPackの識別IDが重複しています");
        BinaryPack.dencoders[ObjectOfVector3Array] = getObjectOfMyData;
        return true;
    }

    public SerializeVector3Array() { }// コンストラクタ
    public SerializeVector3Array(Vector3 [] v3array)// コンストラクタ
    {
        this.v3array = v3array;//シリアライズ対象
    }

    public static BinaryPack.Data getBytesByMyData(SerializeVector3Array pram)//シリアライズ
    {
        Vector3[] v3array = pram.v3array;
        BinaryPack.Data binData = new BinaryPack.Data(ObjectOfVector3Array);
        binData.bytes = new byte[2 + 4 * 3 * v3array.Length];
        byte [] byV3Count = new byte[2];
        BinaryPrimitives.WriteUInt16BigEndian(byV3Count, (ushort)v3array.Length);
        int idx = 0;
        Buffer.BlockCopy(byV3Count, 0, binData.bytes, idx, byV3Count.Length);
        idx += byV3Count.Length;
        for (int n=0; n < v3array.Length; n++)
        {
            BinaryPack.Data v3 = BinaryPack.getBytesByFloat(v3array[n].x);
            Buffer.BlockCopy(v3.bytes, 0, binData.bytes, idx, v3.bytes.Length);
            idx += v3.bytes.Length;

            v3 = BinaryPack.getBytesByFloat(v3array[n].y);
            Buffer.BlockCopy(v3.bytes, 0, binData.bytes, idx, v3.bytes.Length);
            idx += v3.bytes.Length;

            v3 = BinaryPack.getBytesByFloat(v3array[n].z);
            Buffer.BlockCopy(v3.bytes, 0, binData.bytes, idx, v3.bytes.Length);
            idx += v3.bytes.Length;
        }
        return binData;
    }
    public static SerializeVector3Array getObjectOfMyData(byte[] buffer, ref int idx)//デシリアライズ
    {   // 配列のidx 位置からのバイナリをSerializeStrVector3に変換
        SerializeVector3Array obj = new SerializeVector3Array();
        byte[] byV3Count = new byte[2];
        Buffer.BlockCopy(buffer, idx, byV3Count, 0, sizeof(ushort));
        UInt16 v3count = BinaryPrimitives.ReadUInt16BigEndian(byV3Count);//後ろに並ぶbyte数を取得
        obj.v3array = new Vector3[v3count];
        idx += 2;
        for (int n = 0; n < v3count; n++) {
            Vector3 v3 = new Vector3();
            v3.x = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
            v3.y = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
            v3.z = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
            obj.v3array[n] = v3;
        }
        return obj;
    }

    public static void test()// 単体テスト用 シリアライズ後に復元して、正しくデシリアライズできたか確かめる
    {
        //bool regSerializeVector3Array = SerializeVector3Array.Initialize(); // オブジェクト識別IDの登録

        //シリアライズ
        SerializeVector3Array v3array = new SerializeVector3Array();
        v3array.v3array = new Vector3[2]; // v3arrayに送信情報を設定
        v3array.v3array[0] = new Vector3(0.1f, 0.2f, 0.3f);
        v3array.v3array[1] = new Vector3(0.5f, 0.6f, 0.7f);
        byte[] buffer = BinaryPack.getBynary(SerializeVector3Array.getBytesByMyData(v3array));

        // デシリアライズ(復元)して、検証
        SerializeVector3Array v3array2 = (SerializeVector3Array)BinaryPack.getObjectOfOne(buffer, 0);
        Debug.Log($"v3array2.v3array[0] :{v3array2.v3array[0]}");
        Debug.Log($"v3array2.v3array[0] :{v3array2.v3array[0]}");
    }
}
上記は、SerializeVector3Array.test()で検証用メソッドが実行できる。
このクラスのシリアライズを利用して、カメラの位置と回転角度を実行の終了時にバイナリでファイルへ記憶する。
また、起動時にこのファイルを読み取り、カメラの位置と回転角度を実行終了時の状態に戻します。
これにより、前述したDragRotateNotNet.csの内容は、次のようになります。
  の箇所が大きく変更・追加した部分です。)
using System.IO;
using UnityEngine;

public class DragRotateNotNet : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 prevMousePosition;// 以前のマウス位置
    bool prevMouseButton0 = false;// 以前のマウスボタン状態

    // オペレータ操作によるイベント情報記憶用
    string eventStr = ""; // イベント識別文字列 "MouseButtonDown" or "MouseButton"
    Vector3 mousePosition;// 情報伝達に使うマウスの位置

    // シリアライズ用変数の初期化
    static bool regSerializeVector3Array = SerializeVector3Array.Initialize(); // オブジェクト識別IDの登録
    const string filePath = "Serialize.bin";

    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);//カメラ位置初期位置

       // SerializeVector3Array.test(); return; // シリアライス単体のテストデバック
        BinPacket.topSize = 2; //一回の送信で使うBinPacketの先頭のパケットサイズの記憶バイトを4から2に変更

        // filePathのファイルが存在すれば、そのファイル内容で初期表示
        FileInfo fileInfo = new FileInfo(filePath);
        if (true && fileInfo.Exists)
        {
            byte[] buffer = new byte[fileInfo.Length];
            using (FileStream stream = File.Open(filePath, FileMode.Open))
            {
                int idx = 0;
                while (idx < buffer.Length)
                {
                    idx += stream.Read(buffer, idx, buffer.Length - idx);// ファイルから読み込む
                }
            }
            SerializeVector3Array v3array = (SerializeVector3Array)BinaryPack.getObjectOfOne(buffer, 0);
            mainCamera.transform.position = v3array.v3array[0];
            mainCamera.transform.rotation = Quaternion.Euler(v3array.v3array[1]);//オイラー角からQuaternionへ
        }
    }

    void FixedUpdate()
    {
        // オペレータ操作によるイベント情報は、一旦、情報伝達変数に記憶
        bool nowMouseButtonOn0 = Input.GetMouseButton(0);// 現在のマウス位置を取得
        if (this.prevMouseButton0 == false && nowMouseButtonOn0)//マウスボタンを押し込んだ瞬間?
        {
           this.eventStr = "MouseButtonDown";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        else if (nowMouseButtonOn0)//マウスボタンを押し続けている間?
        {
            this.eventStr = "MouseButton";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        this.prevMouseButton0 = nowMouseButtonOn0;


        // 伝達変数のイベント情報で、動作を進行させる(後で、サーバ側イベント受信部の処理になる)
        if (this.eventStr == "MouseButtonDown")//マウスボタンを押し込んだ瞬間?
        {
            this.prevMousePosition = this.mousePosition;
        }
        else if (this.eventStr == "MouseButton")//マウスボタンを押し続けている間?
        {
            Vector3 diff = mousePosition - prevMousePosition;
            diff *= 0.2f;// 回転速度を付加

            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            prevMousePosition = mousePosition;
            eventStr = "";
        }
    }

    public void OnApplicationQuit()// プログラム終了時に、SerializeStrVector3オブジェクトをファイル化
    {
        SerializeVector3Array v3array = new SerializeVector3Array();
        v3array.v3array = new Vector3[2]; // v3arrayに送信情報を設定
        v3array.v3array[0] = mainCamera.transform.position;
        v3array.v3array[1] = mainCamera.transform.rotation.eulerAngles;//Quaternionからオイラー角へ
        byte[] buffer = BinaryPack.getBynary(SerializeVector3Array.getBytesByMyData(v3array));

        using (FileStream stream = File.Open(filePath, FileMode.Create))
        {
            stream.Write(buffer, 0, buffer.Length);// ファイルに書き込み
        }
    }
}

最終案のシリアライズ

上記のSerializeVector3Arrayを使った場合、カメラの回転角をQuaternionからオイラー角のVector3へに変換してをれをシリアライズしています。
復元する場合も、デシリアライズしてから元のQuaternionに変換が必要です。
その分だけ手間が掛かるので、「カメラ情報(位置と角度)」は、 専用のシリアライズクラスのSerializeCamera.csを次のように作って、利用する方法が最終案です。
using UnityEngine;
using System;

public class SerializeCamera
{
    const int ObjectOfSerializeCamera = 50; //識別ID (重複しないように注意して番号を指定)
    public Vector3 position;// Serialize対象データ
    public Quaternion rotation;// Serialize対象データ

    public static bool Initialize()//デリゲート配列に登録関数
    {   //MyDataのオブジェクト識別IDの復元関数の登録
        if (BinaryPack.dencoders[ObjectOfSerializeCamera] != null) throw new Exception("BinaryPackの識別IDが重複しています");
        BinaryPack.dencoders[ObjectOfSerializeCamera] = getObjectOfMyData;
        return true;
    }

    public SerializeCamera() { }// コンストラクタ
    public SerializeCamera(Camera camera)// コンストラクタ
    {
        this.position = camera.transform.position;//シリアライズ対象
        this.rotation = camera.transform.rotation;//シリアライズ対象
    }

    public static BinaryPack.Data getBytesByMyData(Camera camera)//シリアライズ
    {  
        BinaryPack.Data positionX = BinaryPack.getBytesByFloat(camera.transform.position.x);
        BinaryPack.Data potitionY = BinaryPack.getBytesByFloat(camera.transform.position.y);
        BinaryPack.Data potitionZ = BinaryPack.getBytesByFloat(camera.transform.position.z);
        BinaryPack.Data rotationX = BinaryPack.getBytesByFloat(camera.transform.rotation.x);
        BinaryPack.Data rotationY = BinaryPack.getBytesByFloat(camera.transform.rotation.y);
        BinaryPack.Data rotationZ = BinaryPack.getBytesByFloat(camera.transform.rotation.z);
        BinaryPack.Data rotationW = BinaryPack.getBytesByFloat(camera.transform.rotation.w);

        BinaryPack.Data binData = new BinaryPack.Data(ObjectOfSerializeCamera);
        binData.bytes = new byte[sizeof(float) * 3 + sizeof(float) * 4];
        int idx = 0;// 以下で、7つのバイト列を結合したbyte配列を作る
        Buffer.BlockCopy(positionX.bytes, 0, binData.bytes, idx, positionX.bytes.Length);
        idx += positionX.bytes.Length;
        Buffer.BlockCopy(potitionY.bytes, 0, binData.bytes, idx, potitionY.bytes.Length);
        idx += potitionY.bytes.Length;
        Buffer.BlockCopy(potitionZ.bytes, 0, binData.bytes, idx, potitionZ.bytes.Length);
        idx += potitionZ.bytes.Length;

        Buffer.BlockCopy(rotationX.bytes, 0, binData.bytes, idx, rotationX.bytes.Length);
        idx += rotationX.bytes.Length;
        Buffer.BlockCopy(rotationY.bytes, 0, binData.bytes, idx, rotationY.bytes.Length);
        idx += rotationY.bytes.Length;
        Buffer.BlockCopy(rotationZ.bytes, 0, binData.bytes, idx, rotationZ.bytes.Length);
        idx += rotationZ.bytes.Length;
        Buffer.BlockCopy(rotationW.bytes, 0, binData.bytes, idx, rotationW.bytes.Length);
        idx += rotationW.bytes.Length;
        return binData;
    }

    public static SerializeCamera getObjectOfMyData(byte[] buffer, ref int idx)//デシリアライズ
    {   // 配列のidx 位置からのバイナリをSerializeStrVector3に変換
        SerializeCamera obj = new SerializeCamera();
        obj.position.x = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
        obj.position.y = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
        obj.position.z = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);

        obj.rotation.x = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
        obj.rotation.y = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
        obj.rotation.z = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);
        obj.rotation.w = (float)BinaryPack.getObjectOfFloat(buffer, ref idx);

        return obj;
    }

    public static void test()
    {
        // bool regSerializeCamera = SerializeCamera.Initialize(); // オブジェクト識別IDの登録

        // カメラにテストデータを設定、シリアライズ後に復元して、正しくデシリアライズできたか確かめる
        Camera mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 1, -10);    //テストデータ
        Vector3 rotationEuler = new Vector3(0,0,0);             //テストデータ
        mainCamera.transform.rotation = Quaternion.Euler(rotationEuler);//オイラー角からQuaternionへ
        Debug.Log($"{mainCamera.transform.position},{mainCamera.transform.rotation}");
        // (0.00, 1.00, -10.00),(0.00000, 0.00000, 0.00000, 1.00000)

        BinaryPack.Data data = SerializeCamera.getBytesByMyData(mainCamera);//シリアライズ
        byte[] buffer = BinaryPack.getBynary(data);//シリアライズのオブジェクト識別IDをセット

        // デシリアライズ(復元)の確認
        SerializeCamera serializeCamera = (SerializeCamera)BinaryPack.getObjectOfOne(buffer, 0);
        Debug.Log($"{serializeCamera.position},{serializeCamera.rotation}");
        //  (0.00, 1.00, -10.00),(0.00000, 0.00000, 0.00000, 1.00000)

        rotationEuler = serializeCamera.rotation.eulerAngles;//Quaternionからオイラー角へ
        Debug.Log($"オイラー角:{rotationEuler}");
        // オイラー角: (0.00, 0.00, 0.00) この実行結果で、この例では正しいいと検証した。
    }
}
SerializeVector3Arrayの代わりに、上記のSerializeCameraクラスを利用するように変更した DragRotateNotNet.csを以下に示します。
以前のDragRotateNotNet,csに対する変更点を、(  赤字  )で示しています。
using System.IO;
using UnityEngine;

public class DragRotateNotNet : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 prevMousePosition;// 以前のマウス位置
    bool prevMouseButton0 = false;// 以前のマウスボタン状態

    // オペレータ操作によるイベント情報記憶用
    string eventStr = ""; // イベント識別文字列 "MouseButtonDown" or "MouseButton"
    Vector3 mousePosition;// 情報伝達に使うマウスの位置

    // シリアライズ用変数の初期化
    static bool regSerializeCamera = SerializeCamera.Initialize(); // オブジェクト識別IDの登録
    const string filePath = "Serialize.bin";

    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);//カメラ位置初期位置

        // SerializeCamera.test(); return; // シリアライス単体のテストデバック
        BinPacket.topSize = 2; //一回の送信で使うBinPacketの先頭のパケットサイズの記憶バイトを4から2に変更

       // filePathのファイルが存在すれば、そのファイル内容で初期表示
        FileInfo fileInfo = new FileInfo(filePath);
        if (true && fileInfo.Exists)
        {
            byte[] buffer = new byte[fileInfo.Length];
            using (FileStream stream = File.Open(filePath, FileMode.Open))
            {
                int idx = 0;
                while (idx < buffer.Length)
                {
                    idx += stream.Read(buffer, idx, buffer.Length - idx);// ファイルから読み込む
                }
            }
            SerializeCamera serializeCamera = (SerializeCamera)BinaryPack.getObjectOfOne(buffer, 0);
            mainCamera.transform.position = serializeCamera.position;
            mainCamera.transform.rotation = serializeCamera.rotation;
        }
    }

    void FixedUpdate()
    {
        // オペレータ操作によるイベント情報は、一旦、情報伝達変数に記憶
        bool nowMouseButtonOn0 = Input.GetMouseButton(0);// 現在のマウス位置を取得
        if (this.prevMouseButton0 == false && nowMouseButtonOn0)//マウスボタンを押し込んだ瞬間?
        {
           this.eventStr = "MouseButtonDown";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        else if (nowMouseButtonOn0)//マウスボタンを押し続けている間?
        {
            this.eventStr = "MouseButton";// 後で、クライアント側イベント情報の送信部になる
            this.mousePosition = Input.mousePosition;
        }
        this.prevMouseButton0 = nowMouseButtonOn0;


        // 伝達変数のイベント情報で、動作を進行させる(後で、サーバ側イベント受信部の処理になる)
        if (this.eventStr == "MouseButtonDown")//マウスボタンを押し込んだ瞬間?
        {
            this.prevMousePosition = this.mousePosition;
        }
        else if (this.eventStr == "MouseButton")//マウスボタンを押し続けている間?
        {
            Vector3 diff = mousePosition - prevMousePosition;
            diff *= 0.2f;// 回転速度を付加

            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            prevMousePosition = mousePosition;
            eventStr = "";
        }
    }

    public void OnApplicationQuit()// プログラム終了時に、SerializeStrVector3オブジェクトをファイル化
    {
        BinaryPack.Data data = SerializeCamera.getBytesByMyData(mainCamera);//シリアライズ
        byte[] buffer = BinaryPack.getBynary(data);//シリアライズのオブジェクト識別IDをセット

        using (FileStream stream = File.Open(filePath, FileMode.Create))
        {
            stream.Write(buffer, 0, buffer.Length);// ファイルに書き込み
        }
    }
}

サーバー兼クライアントの機能を持つネットワーク作品(ドラックでカメラ位置変更画面を共有する)

以下は、 サーバーまたはクライアントを起動時に選択して始める、サーバーとクライアント機能をまとめた1つの作品例で、 ドラックすると、生成キューブの中心を視点にして、カメラがその周りを回転する作品です。
下記イメージで、左で動作しているのウインドウがサーバです。 そして右のウインドウとUnityエディターの実行はクライアントの画面で、3つの画面が共有しています。

(この動作ムービーを紹介したXへのリンク)
上記表示の文字列はデバック用です。サーバと接続するまでの情報表示は、TcpAppServer .onGUI_messageの内容です。
接続後の通信に関する情報表示は、TcpCommunication .onGUI_messageの内容です。
これらのデバック的な情報表示の変数は設定は、「public static」なので、自身のプログラムから自由に設定可能です。
また、これらの表示は、TcpAppServer .onGUI_flagなどfalse設定で表示を抑止できます。

Hierarcyの中に作った空のGameObjectの名前をMainと、CapsuleとPnaelを配置しています。
このMainに下記ソース(DragRotate.cs)アタッチするだけで、セットワークの接続処理を実現しています

マウスを押し始めた位置と、移動後の位置の差から、 回転方向を得て、原点を中心を基準にカメラを回転させています。

右のイメージが起動時の画面例で、サーバーとして実行させる場合は、 下に並んでいるIPアドレスを選んでボタンをクリックします
ポート番号を変更する場合は、このIPアドレスのボタンクリック前に、 キー入力で変更します。
この操作で、サーバーが起動し、同時に自身のクライアント機能で、 自身のサーバーに接続して動作が始まります。
この時点で、ドラック操作による回転が可能になります。
それは、自身のクライアント機能で、ドラック情報をサーバーに送信し、 それを受信したサーバ側で、カメラ回転動作を行っているからです。
これより接続しているクライアントにカメラ情報を一定時間ごとに送信する サーバー動作が始まっています。
(ですが、この配信は自身のクライアントには送らない制御をしています。)

次に別途の同じアプリを起動して、右のイメージ画面の 「サーバーに接続する」ボタンのクリックで、クライアントして起動します
その前に、サーバで起動したIPアドレスと、ポート番号を合わせる必要があります。(キー入力で編集)

クライアント側では、ドラック情報をサーバーに送信します。
そして、受信したカメラ情報で、自身のカメラを移動する処理です。

なお、この作品では「クライアント名」の文字列を使っていません。
(サーバに情報を送る時、この「クライアント名」を送ることで、サーバの挙動を変更する場合に使えます。 これを色情報として使う作品例は、そちらのページで紹介しています。

サーバー兼クライアント作品例のソース

前述の「ネットワーク化を考慮したネットワーク無しの作品」の最終案のソースを変更・追加しています。
なお、このカメラ情報をファイルに残す部分を無くして、代わりにサーバーで送信をしており、
ファイルからカメラ情報を復元コードを無くして、代わりにクライアントで受信した情報で復元してカメラを移動しています。
(ネットワークに関する追加変更箇所が   の部分です。)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DragRotateNet : MonoBehaviour
{
    Vector3 Center_position;//ここを中心に回転
    Camera mainCamera;
    Vector3 lastMousePosition;// (サーバ起動側だけが使う変数に取り扱いが変わる)
    bool lastMouseButton0 = false;// マウスボタン0が押されていればtrue

    // オペレータ操作によるイベント情報記憶用
    string eventStr = ""; // イベント識別文字列 "MouseButtonDown" or "MouseButton"
    Vector3 mousePosition;

    // 以下がネットワーク用に追加した変数---------------
    TcpCommunication tcpCommunication = new TcpCommunication(); // クライアント用
    static bool serializeStrVector3 = SerializeStrVector3.Initialize();
    static bool serializeCamera = SerializeCamera.Initialize();
    bool flagServer = false;// サーバであればtrue
    float serverSendNextTiming; //サーバー側で、Time.timeが、この時間以上になったら情報を発信する。 

    void Start()
    {
        Center_position = new Vector3(0, 0, 0);//ここを中心に回転
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);// 動的生成
        Material mat = cube.GetComponent<Renderer>().material;
        mat.color = new Color(0.5f, 0.5f, 0.5f);//色設定
        mainCamera = Camera.main;
        mainCamera.transform.position = new Vector3(0, 0, -3);

        GameObject canvas = GameObject.Find("Canvas");
        GameObject panel = canvas.gameObject.transform.Find("Panel").gameObject;
        if (panel != null) panel.SetActive(true);// 無地のパネルを表示


        BinPacket.topSize = 2; //一回の送信で使うBinPacketの先頭のパケットサイズの記憶バイトを4から2に変更
        // ネットワーク サーバ用初期設定-------------------------
        Time.fixedDeltaTime = 0.02f;//Application.targetFrameRate = 50;に相当する固定フレーム間隔指定
        TcpAppServer.onGUI_width = 400;//OnGUI()で使う表示幅変更
        TcpAppServer.onGUI_stepY = 25;
        TcpAppServer.onGUI_height = 35;
        TcpAppServer.onGUI_TextFieldFontSize = 26;//OnGUI()で使う表示文字サイス変更
        TcpAppServer.onGUI_LabelFontSize = 14;
        TcpAppServer.onGUI_ButtonFontSize = 16;
        TcpAppServer.onTcpServerStart += (TcpAppServer server) =>// サーバ起動イベントの処理
        {   // サーバが起動したら実行
            tcpCommunication.ConnectTo();// static メンバtarget_IP, tcp_portが示すサーバーに接続(自身のクライアント機能起動)
            TcpAppServer.onGUI_flagServer = false;//onGUI用操作画面のサーバ起動部表示のみ消す
            flagServer = true;// サーバとして、起動したことを記憶!
        };

        TcpAppServer.onReceive += (TcpCommunication com, BinPacket messaeg) =>// サーバの受信処理
        {   //受信したmessaegのパケットから、SerializeStrVector3を復元して変数にセット
            SerializeStrVector3 ssv3 = (SerializeStrVector3)BinaryPack.getObjectOfOne(messaeg.packet, BinPacket.topSize);
            eventStr = ssv3.str;// クライアントからのイベント情報で、操作("MouseButtonDown"か"MouseButton")を記憶
            mousePosition = ssv3.v3;// クライアントからのイベント情報で、位置を記憶
            TcpAppServer.onGUI_message = $"{com.messageObject}回目サーバ受信:{ssv3.str} 残りキューバッファ数{com.queueBuffer.number} ";
            if(com.messageObject == null) com.messageObject = 1;// 以下の2行は、この作品に必要ない処理で、
            else com.messageObject = (int)com.messageObject + 1;// 各クライアントからの送信数を数える処理の例
        };

        // ネットワーククライアント用初期設定-------------------------
        TcpCommunication.onGUI_LabelFontSize = 14;
        TcpCommunication.tcpClientButton += () =>// ユーザの接続ボタンの処理
        {
            tcpCommunication.ConnectTo();//TCPサーバに接続
        };
       
        tcpCommunication.onConnect += () => {// サーバの接続許可で実行するクライアント側のイベント処理
            panel.SetActive(false);// パネルを消す
            //TcpAppServer.onGUI_flag = false;//onGUI用操作画面を消す
            TcpAppServer.onGUI_flagClient = false;//onGUI用操作画面のクライアントの接続部表示のみ消す
            TcpAppServer.onGUI_flagServer = false;//onGUI用操作画面のサーバ起動部表示のみ消す
        };
    }

    public void FixedUpdate()
    {
        // サーバ側の処理 :ドラックイベント受信情報で、対象を回転動作を進行させる
        if (eventStr == "MouseButtonDown")//マウスボタンを押し込んだ瞬間?
        {
            lastMousePosition = mousePosition;
        }
        else if (eventStr == "MouseButton")//マウスボタンを押し続けている間?
        {
            Vector3 diff = mousePosition - lastMousePosition;
            diff *= 0.2f;// 回転速度を付加

            mainCamera.transform.RotateAround(Center_position, Vector3.up, diff.x);
            mainCamera.transform.RotateAround(Center_position, mainCamera.transform.right, -diff.y);
            lastMousePosition = mousePosition;
            eventStr = "";
        }

        // サーバ側の処理で、でカメラ位置と角度を、全てのクライアントに送信する処理
        if (this.flagServer)
        {
            bool sendTiming = Time.time >= this.serverSendNextTiming;
            this.serverSendNextTiming = Time.time + 0.01f; // 10ミリ秒ごとに送信(送信し過ぎの制御)

            byte[] buffer = BinaryPack.getBynary(SerializeCamera.getBytesByMyData(mainCamera));
            for (int i = 0; i < TcpAppServer.instance.clientlist.Count; i++)
            {
                TcpCommunication com = TcpAppServer.instance.clientlist[i];
                if (com == null) continue;
                if (com.netRemoteString == this.tcpCommunication.netLocalString) continue;// 自身には送らない
                com.SendMessage(buffer); // クライアントに送信
            }
        }

        //-------以下がクライアント側の処理------------------
        if (this.tcpCommunication.isRecRunning == false)
        {
            return;//クライアントの受信スレッドが起動していないならreturn
        }

        // クライアントのオペレータ操作によるイベント情報の送信
        bool mouseBtn0 = Input.GetMouseButton(0);
        Vector2 mousePos = Input.mousePosition;// マウス位置  
        if(this.lastMouseButton0 == false && mouseBtn0)//マウスボタンを押し込んだ瞬間?
        {           
            SerializeStrVector3 obj = new SerializeStrVector3("MouseButtonDown", mousePos);
            byte[] buffer = BinaryPack.getBynary(SerializeStrVector3.getBytesByMyData(obj));// シリアライズ byte列取得      
            this.tcpCommunication.SendMessage(buffer);// パケットに入れて送信
        }
        else if (mouseBtn0)//マウスボタンを押し続けている間?
        {
            SerializeStrVector3 obj = new SerializeStrVector3("MouseButton", mousePos);
            byte[] buffer = BinaryPack.getBynary(SerializeStrVector3.getBytesByMyData(obj));
            this.tcpCommunication.SendMessage(buffer);// パケットに入れて送信
        }
        this.lastMouseButton0 = mouseBtn0;

        // クライアント側の受信処理(受信バッファに存在すれば、最大3個まで取り出して処理)
        for (int n = 0; n < 3; n++)
        {
            BinPacket binPacket = QueueBuffer.GetBuff(tcpCommunication.queueBuffer); // 受信情報の取り出し
            if (binPacket == null) break;// 受信無しならリターン
            Debug.Log($"binPacket.packet:{binPacket.packet.Length} byteの受信");
            SerializeCamera serializeCamera = (SerializeCamera)BinaryPack.getObjectOfOne(binPacket.packet, BinPacket.topSize);
            mainCamera.transform.position = serializeCamera.position;//カメラを受信情報で反映させる。
            mainCamera.transform.rotation = serializeCamera.rotation;
        }
    }

    private void OnGUI()// (この情報表示は、無くても動作します)
    {
        GUI.backgroundColor = Color.cyan;// new Color(0.9f,0.9f,0.9f);
        GUIStyle lableStyle = GUI.skin.GetStyle("label");   // ---Labelスタイル設定
        lableStyle.fontSize = 26;
        GUI.color = Color.black;//文字色設定
        GUI.Label(new Rect(10, 400, 680, 500), TcpCommunication.onGUI_message);
    }
}
なおこのソース以外に、BinaryPack.cs、 シリアライスクラズのSerializeStrVector3SerializeCamera.csが必要です。
そして、TcpCommunication.csTcpAppServer.csの汎用モジュールが必要で、それによりこれだけ簡単にネットワーク作品を実現しています。