Unity角色多人同步

news2024/11/6 3:00:37

1.位置同步和状态同步:需要同步的节点上挂载脚本:

        gameObject.AddComponent<SyncTransform>(); ; //同步
        gameObject.GetComponent<SyncTransform>().syncId = SyncUtilFunc.GetRoleSyncId(PlayerData.Instance.PlayerId); //同步
        gameObject.GetComponent<SyncTransform>().ownerState = OwnerState.Mine;
        gameObject.GetComponent<SyncTransform>().isRole = true;
        gameObject.AddComponent<SyncState>(); //同步
        gameObject.GetComponent<SyncState>().syncId = SyncUtilFunc.GetRoleSyncId(PlayerData.Instance.PlayerId); //同步
        gameObject.GetComponent<SyncState>().ownerState = OwnerState.Mine;

(1)用于多人同步的基类:SyncBase.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum OwnerState {
    Null,//无人拥有
    Mine,//拥有者是我
    Ohter,//拥有者是其他玩家
}

public class SyncBase : MonoBehaviour
{
    /// <summary>
    /// 只有拥有者才允许发数据
    /// </summary>
    public OwnerState ownerState;


    public string syncId;
    public SyncWrapData dataBase = new SyncWrapData();
    protected string dataName;


    public void SendNetWork<T>(T data, string stName) {

        
        if (dataName != stName) {
            Debug.LogError("error:传输数据出错请检查" + dataName + " :" + stName);
            return;
        }

        dataBase.syncId = syncId;
        dataBase.name = dataName;
        dataBase.obj = JsonUtility.ToJson(data);
        dataBase.playerId = PlayerData.Instance.PlayerId.ToString();
        NetManager.Instance.SendSyncData(dataBase);
    }

    

    public void AddNetWorkListner<T>(Action<T> callback,string stName) {

        if (NetManager.Instance.isConnected() == false) {
            return;
        }

        dataName = stName;
        NetManager.Instance.AddSyncHandler(nameof(T), (SyncWrapData syncData) => {

            if (syncData.syncId == syncId) { //只关心自己同步ID的事情
                if (stName == syncData.name) {//类型结构需要和传递进来的结构一致
                    var dstData = JsonUtility.FromJson<T>(syncData.obj); //ID一样自然可以转
                    callback(dstData);
                }
            }
        });
    }


}

引入库:LitJson

引用类:

public class SyncWrapData {
    public string syncId;
    public string playerId; //拥有者的ID
    public bool isLocation;
    public string name;
    public string obj;
}

模型层:SyncModel.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class SyncUtilFunc {
    public static string GetRoleSyncId(long id) {
        return $"{ConstTag.Player}{id}";
    }

}

public class SyncWrapData {
    public string syncId;
    public string playerId; //拥有者的ID
    public bool isLocation;
    public string name;
    public string obj;
}

/// <summary>
/// 数据要乘以10转成分米
/// </summary>
public class SyncTransfomData {
    public int[] pos = new int[3]; 
    public int[] roate = new int[3];
    public int[] scale = new int[3];  
}

public static class SyncTransfomUtil
{
    public static SyncTransfomData FromTransform(Transform t,ref SyncTransfomData data) {
        data.pos[0] = (int)(t.position.x * 10f);
        data.pos[1] = (int)(t.position.y * 10f);
        data.pos[2] = (int)(t.position.z * 10f);

        data.roate[0] = (int)(t.eulerAngles.x * 10f);
        data.roate[1] = (int)(t.eulerAngles.y * 10f);
        data.roate[2] = (int)(t.eulerAngles.z * 10f);

        data.scale[0] = (int)(t.localScale.x * 10f);
        data.scale[1] = (int)(t.localScale.y * 10f);
        data.scale[2] = (int)(t.localScale.z * 10f);

        return data;
    }

    public static SyncTransfomData FromTankLocation(TankLocation location, ref SyncTransfomData data) {
        data.pos[0] = (int)(location.x*10f);
        data.pos[1] = (int)(location.y*10f);
        data.pos[2] = (int)(location.z*10f);

        data.roate[0] = (int)(location.dx * 10f);
        data.roate[1] = (int)(location.dy * 10f);
        data.roate[2] = (int)(location.dz * 10f);

        return data;
    }

    public static void ToTransform(Transform t, SyncTransfomData data) {
        t.position = new Vector3((float)data.pos[0]/10f, (float)data.pos[1] / 10f, (float)data.pos[2] / 10f);
        t.eulerAngles = new Vector3((float)data.roate[0]/10f, (float)data.roate[1] / 10f, (float)data.roate[2] / 10f);
    }

    public static TankLocation ToTankLocation(ref TankLocation location, SyncTransfomData data) {
        location.x = ((float)data.pos[0]) / 10f;
        location.y = ((float)data.pos[1]) / 10f;
        location.z = ((float)data.pos[2]) / 10f;

        location.dx = ((float)data.roate[0]) / 10f;
        location.dx = ((float)data.roate[1]) / 10f;
        location.dx = ((float)data.roate[2]) / 10f;

        return location;
    }

    /// <summary>
    /// 分米内相同
    /// </summary>
    /// <param name="v1"></param>
    /// <param name="v2"></param>
    /// <returns></returns>
    public static bool V3Equal(Vector3 v1 ,Vector3 v2) {
       return (Math.Abs(v1.x - v2.x) < 0.1f) && (Math.Abs(v1.y - v2.y) < 0.1f) && (Math.Abs(v1.z - v2.z) < 0.1f);
    }


}


public class SyncStateData {
    public string stateName;
}

public class SyncOperationData {
    public string operation;
}

public class SyncShareData{
    public string ownerId;
    public float[] pos = new float[3];
    public float[] roate = new float[3];
    public float[] scale = new float[3];
}

public class SyncWorldData {
    public Dictionary<string, SyncShareData> dir_shareDatas = new Dictionary<string, SyncShareData>();
    public Dictionary<string, SyncStateData> dir_stateDatas = new Dictionary<string, SyncStateData>();
    public Dictionary<string, SyncTransfomData> dir_transformDatas = new Dictionary<string, SyncTransfomData>();
}

public class SyncWorldModel:SingletonBase<SyncWorldModel> {

    SyncWorldData m_worldData = new();

    public void SetWorldData(SyncWorldData data) {
        m_worldData = data;
    }

    public SyncShareData GetShareData(string syncId) {
        if (m_worldData.dir_shareDatas.ContainsKey(syncId)) {
            return m_worldData.dir_shareDatas[syncId];
        }

        return null;
    }

    public SyncStateData GetStateData(string syncId) {
        if (m_worldData.dir_stateDatas.ContainsKey(syncId)) {
            return m_worldData.dir_stateDatas[syncId];
        }

        return null;
    }

    public SyncTransfomData GetTransfomData(string syncId) {
        if (m_worldData.dir_transformDatas.ContainsKey(syncId)) {
            return m_worldData.dir_transformDatas[syncId];
        }

        return null;
    }

    public void SetShareData(string syncId, SyncShareData data) {
        if (m_worldData.dir_shareDatas.ContainsKey(syncId)) {
             m_worldData.dir_shareDatas[syncId] = data;
        }
        else {
            m_worldData.dir_shareDatas.Add(syncId,data);
        }


    }

    public void SetStateData(string syncId, SyncStateData data) {
        if (m_worldData.dir_stateDatas.ContainsKey(syncId)) {
            m_worldData.dir_stateDatas[syncId] = data;
        }
        else {
            m_worldData.dir_stateDatas.Add(syncId, data);
        }
    }

    public void SetTransfomData(string syncId, SyncTransfomData data) {
        if (m_worldData.dir_transformDatas.ContainsKey(syncId)) {
            m_worldData.dir_transformDatas[syncId] = data;
        }
        else {
            m_worldData.dir_transformDatas.Add(syncId, data);
        }

    }


    public void SyncWorldData() {

        NetManager.Instance.SendSyncWorldData(m_worldData);
    }

}




网络层脚本1:NetManager.cs


using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using LitJson;
using UnityEngine.Networking;
using UTNET;
using Unity.Burst.Intrinsics;
using System.Runtime.InteropServices;

public class NetMessage<T> {
    public uint mergeid;
    delegate void Message(object sender, T msg);

}

public class NetManager : MonoBehaviour {

    private NetManager() { }

    public static NetManager Instance;


    public string token, version;
    public Client client;
    public string HaiMetaSpaceUrl = "https://test-haimeta-1314523153.cos-website.ap-shanghai.myqcloud.com/space";

    //192.144.138.221  /game.icewhale.net
    Dictionary<string, List<Action<ExternalMessage>>> actions = new Dictionary<string, List<Action<ExternalMessage>>>();
    private Coroutine rec;


    [DllImport("__Internal")]
    private static extern string GetUA();
    private void Awake() {
        Instance = this;

#if !UNITY_EDITOR && UNITY_WEBGL
                  string a = GetUA();
                  if (a == "1")
                    {
                        //PC端
                        Debug.Log("当前运行环境在PC端");
                        PlayerData.Instance.isRunningPC = true;
                    }
                    if (a == "2")
                    {
                        //移动端
                        Debug.Log("当前运行环境在移动端");
                        PlayerData.Instance.isRunningPC = false;
                    }
#endif

        EventManager.Instance.AddListener(EventName.OnSceneLoaded,OnSceneLoaded);

    }

    private void OnSceneLoaded(object sender, EventArgs e) {
        var arg = e as SceneLoadedArg;
        if (arg.state == AppConst.ViewMode) {
            CreateConeccetionBtn();
        }
    }

    async void Start() {
        DontDestroyOnLoad(this);
        Screen.sleepTimeout = SleepTimeout.NeverSleep;
        pushBtn();
    }

    


    public void webOpenHaiMetaUrl(string param = null) {

        //string url = this.HaiMetaSpaceUrl+param;
        //UnityEngine.Application.ExternalEval("window.location.href = \"" + url + "\";");
    }

    public void webOpenNewSpace(string spaceUrl,string spaceId)
    {

        string url = spaceUrl + "?param={\"spaceId\":"+ spaceId + ",\"userName\":"+PlayerData.Instance.Name+",\"templateId\":"+ PlayerData.Instance.TemplateId + "}" + "&token=" + PlayerData.Instance.ltk + "&type=4"+ "&state=1";
        UnityEngine.Application.ExternalEval("window.location.href = \"" + url + "\";");


    }

    private void Update() {
        //if (client == null && newClient != null) {

        //    client = getNewClient();
        //}
    }

    public async void CreateConeccetionBtn()
    {
#if !UNITY_EDITOR
        await HttpHelper.Instance.GetServerConfig();
#endif
        //string host = string.Format("wss://game.icewhale.net/login");
        Debug.Log("CreateConeccetionBtn" +Host.gameServer);
        client = new Client(Host.gameServer);
        client.OnDisconnect = OnDisconnect;
        client.OnReconected = OnReconected;
        client.OnError = OnError;
        client.OnConnected = OnConnected;

        bool isConeccet = await client.ConnectAsync();
        if (isConeccet)
        {
            Debug.Log("网络链接成功");
        }
       
    }

    //public void resetClient() {
    //    setNewClient(client);
    //}

    private void OnConnected() {
        Debug.Log("OnConnected");
        if (rec != null)
            StopCoroutine(rec);
        this.TriggerEvent(EventName.SocketOpen, null);
       
    }





    private void OnError(string msg) {
        Debug.LogError(string.Format("err msg:{0}", msg));
    }

    private void OnReconected() {
        Debug.Log("OnReconect");
    }

    private void OnDisconnect() {
        Debug.Log("OnDisconnect");
        //client = null;
        WarningPanel.ShowTimeTips("重连中....",10,null,null);
        rec = StartCoroutine(client.ReconectScoektAsync()); //断线重连逻辑
    }

    public void pushBtn() {
        LoadNetCofig();
    }

    void LoadNetCofig() {
        var configPath = Application.dataPath;

#if UNITY_EDITOR
        var filepath = Path.Combine(Application.dataPath.Replace("Assets", ""), "config.txt");
#else
        var filepath = Path.Combine(Application.dataPath, "config.txt");
#endif
        Debug.Log("configPath" + filepath);
        filepath = filepath.Replace("\\", "/");
       LoadFileSetNetwork(filepath);
    }



    async void LoadFileSetNetwork(string filepath) {
        UnityWebRequest www = UnityWebRequest.Get(filepath);


        await www.SendWebRequest();

        if (www.result == UnityWebRequest.Result.ConnectionError || www.result == UnityWebRequest.Result.ProtocolError) {
            Debug.LogError(www.error);
        }
        else {
            string json = www.downloadHandler.text;
            var data = LitJson.JsonMapper.ToObject(json);

            
#if UNITY_EDITOR
            var serverMode = AppConst.serverMode;
#else
            var serverMode  = (ServerMode)int.Parse(HttpHelper.GetUrlParam("severAPI"));
#endif
            AppConst.serverMode = serverMode;
            switch (serverMode) {
                case ServerMode.preview:
                    Host.ApiHost = (string)data["preview"];
                    break;
                case ServerMode.dev:
                    Host.ApiHost = (string)data["dev"];
                    break;
                case ServerMode.overseas:
                    Host.ApiHost = (string)data["overseas"];
                    break;
            }



            switch (serverMode)
            {
                case ServerMode.overseas:
                    Host.AssetBundleIP = (string)data["OverseasAssetBundleIP"];
                    break;
                case ServerMode.preview:
                    Host.AssetBundleIP = (string)data["PreviewAssetBundleIP"];
                    break;
                case ServerMode.dev:
                    if ((string)data["AssetBundleIP"] != string.Empty)
                    {
                        Host.AssetBundleIP = (string)data["AssetBundleIP"];
                    }
                    break;
            }

                


#if !UNITY_EDITOR
            var paramUrl = HttpHelper.GetUrlParam("param");
            JsonData paramdata = JsonMapper.ToObject(UtilsFunc.UnicodeToString(paramUrl));
            try {
                string spaceId = paramdata["spaceId"].ToJson();
                PlayerData.Instance.SpaceId = spaceId;
                PlayerData.Instance.ltk = HttpHelper.GetUrlParam("token");
              
              
            }
            catch (Exception e) {
                Debug.LogError(e.Message + '\n' + e.StackTrace);
            }
#endif
            Debug.Log("LoadFileSetNetwork SpaceId" + PlayerData.Instance.SpaceId);

            //如果在编辑器下面,并且不填sapceId,那么则进入发布模式

            if (string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == false) {
                await HttpHelper.Instance.GetSceneConfig();
            }


#if !UNITY_EDITOR
            if (paramdata.ContainsKey("templateId")) {

                if (paramdata["templateId"] != null) {
                    string tempStr = (string)paramdata["templateId"];
                    bool isExist = int.TryParse(tempStr,out var tempId);
                    if (isExist) PlayerData.Instance.TemplateId = tempId;
                }
                    
            }

            string stateUrl = HttpHelper.GetUrlParam("state");
#else
            string stateUrl = string.IsNullOrEmpty(PlayerData.Instance.SpaceId) == true ? AppConst.PublicMode : AppConst.ViewMode;
#endif
            var arg = new SceneLoadActionArgs();
            arg.state = stateUrl;







            if (stateUrl == AppConst.PublicMode) {
                PlayerData.Instance.IsEditorMode = true;
                PlayerData.Instance.IsPublicMode = true;
                EventManager.Instance.TriggerEvent(EventName.OnPublishEnter, arg);
                HttpHelper.Instance.GetDefaultSpaceImg();
                return;
            }
            
#if UNITY_EDITOR
            Host.gameServer = (string)data["local"];
#endif
            EventManager.Instance.TriggerEvent(EventName.LoadSceneAction, arg);




        }
    }

    private async UniTaskVoid CreateConeccetion() {
        Debug.Log("开始...");

        bool isConeccet  = await client.ConnectAsync();

        if (isConeccet) {
            Debug.Log("网络链接成功");
            // this.Register<RegisterInfo>("test", (int code) =>
            // {
            //     Debug.Log("网络事件" +code);
            // });
            if (client != null) client.isConnect = true;

            //this.SendAPI();

        }
        else
            Debug.Log("多次链接仍让未成功");
    }

    public void AddSyncHandler(string name, Action<SyncWrapData> call) {
        NetManager.Instance.Register<GameFrame>("gameFrame", (ExternalMessage pack) => {
            GameFrame info = PackageProtocol.DecodeInfo<GameFrame>(pack.data);
            //if (info.location.playerId == PlayerData.Instance.PlayerId) {
            //    return; //自己不用给自己同步
            //}

            var opc = info.opc;
            SyncWrapData dataBase = null;
            try {

                dataBase = JsonUtility.FromJson<SyncWrapData>(opc);

                if (dataBase.playerId != PlayerData.Instance.PlayerId.ToString()) { //只接受不同playerId的数据
                    call.Invoke(dataBase);
                }

            }
            catch (Exception e) {
                //Debug.LogError(e);
            }
        });
    }

    public async void SendSyncData(SyncWrapData syncBase) {
        string jsonData = JsonUtility.ToJson(syncBase);

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }

        var gameFrame = default (GameFrame);
        gameFrame.opc = jsonData;
        await NetManager.Instance.client.RequestAsync<GameFrame, GameFrame>("gameFrame", gameFrame);
    }

    public async void SendSyncWorldData(SyncWorldData worldData) {
        string jsonData = LitJson.JsonMapper.ToJson(worldData);

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }
        //Debug.Log("SendSyncWorldData worldData:" + jsonData);
        var gameFrame = default(GameFrame);
        gameFrame.world = jsonData;
        await NetManager.Instance.client.RequestAsync<GameFrame, GameFrame>("gameFrame", gameFrame);
    }

    public async void SendFrameLocationData(SyncTransfomData syncPos,SyncWrapData wrap) {

        if (NetManager.Instance.client == null) {
            Debug.LogError("NetManager.Instance.client == null");
            return;
        }

        var gameFrame = new GameFrame();

        gameFrame.opc = JsonUtility.ToJson(wrap);

        gameFrame.location = new TankLocation();
        SyncTransfomUtil.ToTankLocation(ref gameFrame.location, syncPos);
        gameFrame.location.playerId = PlayerData.Instance.PlayerId;

        await NetManager.Instance.client.RequestAsync<GameFrame, GameFrame>("gameFrame", gameFrame);
    }




    public void Register<T>(string name, Action<ExternalMessage> ac) {
        //#if UNITY_WEBGL && !UNITY_EDITOR  //只在WebGl下生效
        if (PlayerData.Instance.IsEditorMode) return;
        //#endif
        //加入事件列表
        Proto pb = ProtoMaps.Instance.Name2Pb(name);

        List<Action<ExternalMessage>> list = null;
        if (actions.ContainsKey(name)) {
            list = actions[name];
        }
        else {
            list = new List<Action<ExternalMessage>>();
            actions.Add(name, list);
        }
        list.Add(ac);

        client.OnNetEx(name, (ExternalMessage pack) => {
            T info = PackageProtocol.DecodeInfo<T>(pack.data);
            //Debug.Log("网络事件" + JsonUtility.ToJson(info));
            //遍历事件列表依次回调
            foreach (Action<ExternalMessage> cb in list) {
                cb?.Invoke(pack);
            }

        });

    }


    public async void SendAPI() {

        //请求注册
        //RegisterInfo register = new RegisterInfo();
        //register.account = "abc";
        //register.phoneNum = "abc";
        //register.pwd = "abc";

        //RegisterInfo a = await client.RequestAsync<RegisterInfo, RegisterInfo>("test", register);

        //Debug.Log(a.phoneNum);


        client.OnNetEx("login", (ExternalMessage pack) => {

            Debug.LogError("回调返回");
            //T info = PackageProtocol.DecodeInfo<T>(pack.data);
            Debug.Log("网络事件" + JsonUtility.ToJson(info));
            遍历事件列表依次回调
            //foreach (Action<ExternalMessage> cb in list)
            //{
            //    cb?.Invoke(pack);
            //}

        });



        LoginVerify loginVerify = new LoginVerify();
        loginVerify.account = "18060974935";
        loginVerify.pwd = "123456ab";
        loginVerify.loginBizCode = 1;

        UserInfo ret = await NetManager.Instance.client.RequestAsync<LoginVerify, UserInfo>("login", loginVerify);
        Debug.Log(ret.nickName);


        TankEnterRoom myEnterRoom = new TankEnterRoom();

        myEnterRoom.roomId = 1002;
        myEnterRoom.team = 1;

        TankEnterRoom ret2 = await NetManager.Instance.client.RequestAsync<TankEnterRoom, TankEnterRoom>("enterRoom", myEnterRoom);
        Debug.Log(ret2.time);




        //int code = await client.RequestAsync<String, int>("gameObjUsed", "ok");


    }

    public bool isConnected() {
        if (client == null) {
            return false;
        }

       return client.isConnect;
    }

    private void OnDestroy() {
        if (client != null) {
            client.Cancel();
        }
    }
}

网络层脚本2:Client.cs

using System;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;
using System.Collections;
using UnityWebSocket;

namespace UTNET
{
    public enum NetWorkState
    {
        CONNECTING,
        CONNECTED,
        DISCONNECTED,
        TIMEOUT,
        ERROR,
        KICK
    }

    public class Client
    {
        private static int RqID = 0;

        //public NetWorkState state;

        public Action OnReconected, OnDisconnect, OnConnected;
        public Action<string> OnError;
        public uint retry;
        public bool isConnect = false;
        Protocol protocol;
        WebSocket socket;
        UniTaskCompletionSource<bool> utcs;
        private bool isForce;
        private bool isHeardbeat = false;
        private string host;

        public Client(string host)
        {
            Debug.Log("Client" + host);
            this.host = host;
            this.createSocket();
        }

        private void createSocket()
        {
            if (socket != null) {

            }
            Debug.Log("createSocket host=" + this.host);
            utcs = new UniTaskCompletionSource<bool>();
            if (socket != null) {
                socket.CloseAsync();
                socket.OnClose += ReCreateSocket;
            }
            else {
                socket = new WebSocket(this.host);
                socket.OnOpen += OnOpen;
                socket.OnMessage += OnMessage;
                socket.OnClose += OnClose;
                socket.OnError += OnErr;
            }
            

           
        }

        private void ReCreateSocket(object sender, CloseEventArgs e) {
            socket = new WebSocket(this.host);
            socket.OnOpen += OnOpen;
            socket.OnMessage += OnMessage;
            socket.OnClose += OnClose;
            socket.OnError += OnErr;
        }

        //断线重连逻辑
        public IEnumerator ReconectScoektAsync()
        {
            while (true)
            {
                yield return new WaitForSeconds(2);
                Debug.Log(" client : reconectScoekt");
                if (PlayerData.Instance.isAnotherLogin) break;
                if (socket.ReadyState == WebSocketState.Connecting || socket.ReadyState == WebSocketState.Open) break;
                socket.ConnectAsync();
            }
        }


        //开始心跳逻辑
        public IEnumerator StartHeardBeat()
        {
            isHeardbeat = true;
            while (true)
            {
                Debug.Log("开始发送心跳");
                if (isHeardbeat == false) continue;
                yield return new WaitForSeconds(2);
                //发送心跳
                this.sendHeardbeat();
            }
        }

        private async void sendHeardbeat()
        {
            int u1 = await this.RequestAsync<int, int>("heartbeat", 21);

        }


        public UniTask<bool> ConnectAsync()
        {
            socket.ConnectAsync();
            return utcs.Task;
        }

        private void OnOpen(object sender, OpenEventArgs e)
        {
            if (protocol == null)
                protocol = new Protocol();
            protocol.SetSocket(socket);
            protocol.OnReconected = OnReconected;
            protocol.OnError = OnError;
            //bool isOK = await protocol.HandsharkAsync(this.token);
            Debug.Log("open:" + e);
            isConnect = true;
            utcs.TrySetResult(true);
            OnConnected?.Invoke();
        }


        private async void OnClose(object sender, CloseEventArgs e)
        {
            if (socket.ReadyState == UnityWebSocket.WebSocketState.Connecting || socket.ReadyState == UnityWebSocket.WebSocketState.Open) return;
            await UniTask.SwitchToMainThread();
            isConnect = false;
            Cancel();
            if (!isForce)
            {
                await UniTask.Delay(1000);
                //socket.Open();
            }
            OnDisconnect?.Invoke();
        }

        public void OnErr(object sender, ErrorEventArgs e)
        {
            utcs.TrySetResult(false);
            isConnect = false;
            //Debug.LogError(e.Exception.Message);
        }

        private void OnMessage(object sender, MessageEventArgs e)
        {
            protocol.OnReceive(e.RawData);
            isConnect = true;
        }


        public void OnNet(uint mid, uint sid, Action<ExternalMessage> cb)
        {
            uint merge = PackageProtocol.getMergeCmd(mid, sid);
            protocol.SetOnNet(merge, cb);
        }

        public void OnNetEx(string name, Action<ExternalMessage> cb)
        {
            Proto pb = ProtoMaps.Instance.protos[name] as Proto;
            uint merge = PackageProtocol.getMergeCmd(pb.mid, pb.sid);
            protocol.SetOnNet(merge, cb);
        }

        public void Notify<T>(string route, T info = default)
        {
            //uint rqID = (uint)Interlocked.Increment(ref RqID);
            try
            {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[Notify] -->> [{0}] {1}", route, JsonUtility.ToJson(info)));
#endif
                protocol.Notify<T>(route, info);
            }
            catch (Exception e)
            {
                Debug.Log(string.Format("[Notify Exception]{0}", e.Message));
                throw e;
            }
        }



        public async UniTask<S> RequestAsyncEx<T, S>(uint mid, uint sid, T info = default, string modelName = null)
        {

            uint cmdMerge = PackageProtocol.getMergeCmd(mid, sid);

            uint rqID = (uint)Interlocked.Increment(ref RqID);
            try
            {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endif

                ExternalMessage pack = await protocol.RequestAsync<T>(rqID, cmdMerge, info, modelName);

                S msg = PackageProtocol.DecodeInfo<S>(pack.data);


#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endif
                return msg;
            }
            catch (Exception e)
            {
                //Debug.Log(string.Format("[{0}][RequestAsync Exception]{1}", rqID, e.Message));
                throw e;
            }
        }


        public async UniTask<S> RequestAsync<T, S>(string name, T info = default, string modelName = null)
        {
            
            // #if UNITY_WEBGL && !UNITY_EDITOR  //只在WebGl下生效
            if (PlayerData.Instance.IsEditorMode) {
                S msg = PackageProtocol.DecodeInfo<S>(null);
                return msg;
            }
            //#endif

            //Debug.Log($"sendMsg {name}");

            Proto pb = ProtoMaps.Instance.Name2Pb(name);

                uint cmdMerge = PackageProtocol.getMergeCmd(pb.mid, pb.sid);

                uint rqID = (uint)Interlocked.Increment(ref RqID);
                //Debug.LogError("调试消息id" + rqID);

                try
                {
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] -->> [{1}] {2}", rqID, route, JsonUtility.ToJson(info)));
#endif

                    ExternalMessage pack = await protocol.RequestAsync<T>(rqID, cmdMerge, info, modelName);


                    S msg = PackageProtocol.DecodeInfo<S>(pack.data);
#if SOCKET_DEBUG
                Debug.Log(string.Format("[{0}][Request] <<-- [{1}] {2} {3}", rqID, route, JsonUtility.ToJson(msg), JsonUtility.ToJson(msg.info)));
#endif
                    return msg;
                }
                catch (Exception e)
                {
                    //Debug.Log(string.Format("[{0}][RequestAsync Exception]{1}", rqID, e.Message));
                    //Debug.Log("房间网络连接断开.......");
                    S msg = PackageProtocol.DecodeInfo<S>(null);
                    return msg;
                    throw e;
                }

            

            
        }

        public void Cancel(bool isForce = false)
        {
            this.isForce = isForce;
            utcs.TrySetCanceled();
            if (socket.ReadyState != UnityWebSocket.WebSocketState.Closed)
            {
                socket.CloseAsync();
            }
            if (protocol != null)
            {
                protocol.StopHeartbeat();
                protocol.CanceledAllUTcs();
            }
        }


        public UnityWebSocket.WebSocketState getSocketState() {

            if (socket != null) {

                return socket.ReadyState;
            }
            return 0;
        }


        public void Close() {
            Debug.Log("UnityWebSocket......" + socket);
            Debug.Log("UnityWebSocket状态......"+ socket.ReadyState);
            if (socket!=null && socket.ReadyState == UnityWebSocket.WebSocketState.Open) {
                socket.CloseAsync();
                Debug.Log("强制玩家关闭连接......");
            }
        }


        void OnDestroy()
        {
            socket.CloseAsync(); 
        }
    }
}

网络层脚步3:PackageProtocol.cs

using ProtoBuf;
using System;
using System.IO;
using System.Text;
using UnityEngine.XR;

namespace UTNET
{
    public enum PackageType
    {
        HEARTBEAT = 1,
        REQUEST = 2,
        PUSH = 3,
        KICK = 4,
        RESPONSE = 5,
        HANDSHAKE = 6,
        ERROR = 7,
        NOTIFY = 8
    }

    public class PackageProtocol
    {
        //获取cmd
        public static uint getCmd(uint merge)
        {
            return merge >> 16;
        }
        //获取subCmd
        public static uint getSubCmd(uint merge)
        {
            return merge & 0xFFFF;
        }
        //获取mergeCmd
        public static uint getMergeCmd(uint cmd, uint subCmd)
        {
            return (cmd << 16) + subCmd;
        }
        public static byte[] Encode(PackageType type)
        {
            Package sr = new Package()
            {
                packageType = (uint)type
            };
            return Serialize(sr);
        }

        public static byte[] Encode<T>(PackageType type, uint packID, string route, T info, string modelName = null)
        {
            Package sr = new Package()
            {
                packageType = (uint)type,
                packID = packID,
                route = route,
                buff = Serialize<T>(info),
                modelName = modelName
            };
            return Serialize(sr);
        }


        public static byte[] EncodeEx<T>(uint packID, uint cmdMerge, T info)
        {
            //Package sr = new Package()
            //{
            //    packageType = (uint)type,
            //    packID = packID,
            //    route = route,
            //    buff = Encoding.Default.GetBytes(SerializeEx<T>(info)),
            //};
            //return SerializeEx(sr);

            ExternalMessage sr = new ExternalMessage()
            {
                cmdCode = 100,
                protocolSwitch = 1,
                cmdMerge = cmdMerge,
                responseStatus = 0,
                validMsg = "",
                data = Encoding.UTF8.GetBytes(SerializeEx<T>(info)),
            };
            return Serialize(sr);

        }



        public static byte[] Encode<T>(uint packID, uint cmdMerge, T info)
        {
            ExternalMessage sr = new ExternalMessage()
            {
                cmdCode = 100,
                protocolSwitch = 1,
                cmdMerge = cmdMerge,
                responseStatus = 0,
                validMsg = "",
                data = Serialize<T>(info),
                msgId = (int)packID,
            };
            return Serialize(sr);
        }

        public static string SerializeEx<T>(T t)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                Serializer.Serialize<T>(ms, t);
                return Encoding.UTF8.GetString(ms.ToArray());
            }
        }


        public static byte[] Serialize<T>(T info)
        {
            if (typeof(T) == typeof(int))
            {
                int value = (int)Convert.ChangeType(info, typeof(int));
                value = (value << 1);
                info = (T)Convert.ChangeType(info, typeof(T));
            }

            MemoryStream ms = new MemoryStream();
            Serializer.Serialize(ms, info);
            byte[] buff = ms.ToArray();
            ms.Close();
            return buff;

            //try
            //{
            //    //涉及格式转换,需要用到流,将二进制序列化到流中
            //    using (MemoryStream ms = new MemoryStream())
            //    {
            //        //使用ProtoBuf工具的序列化方法
            //        Serializer.Serialize<T>(ms, info);
            //        //定义二级制数组,保存序列化后的结果
            //        byte[] result = new byte[ms.Length];
            //        //将流的位置设为0,起始点
            //        ms.Position = 0;
            //        //将流中的内容读取到二进制数组中
            //        ms.Read(result, 0, result.Length);
            //        return result;
            //    }
            //}
            //catch (Exception ex)
            //{
            //    return null;
            //}



        }

        public static ExternalMessage Decode(byte[] buff)
        {
            //protobuf反序列化
            MemoryStream mem = new MemoryStream(buff);
            ExternalMessage rs = Serializer.Deserialize<ExternalMessage>(mem);
            mem.Close();
            return rs;
        }
        public static T DecodeInfo<T>(byte[] buff)
        {
            if (buff == null) return default;
            T rs;

            if (typeof(T) == typeof(int))
            {
                int value;
                using (var stream = new MemoryStream(buff))
                {
                    value = Serializer.Deserialize<int>(stream);
                }//转zig zag
                value = (value >> 1) ^ -(value & 1);

                rs = (T)Convert.ChangeType(value, typeof(T));
            }
            else
            {
                MemoryStream mem = new MemoryStream(buff);
                rs = Serializer.Deserialize<T>(mem);
                mem.Close();
            }

            return rs;
        }



        //public static T MyMethod<T>(byte[] buff) where T : Object
        //{
        //    return null;
        //}



    }
}

网络层脚本4:ProtoMaps.cs





using System;
using System.Collections.Generic;


namespace UTNET
{


    public interface IProto
    {

    }



    //R ��������, S ��������
    //public class Proto<R, S> : IProto
    public class Proto : IProto
    {
        public string name; //������
        public uint mid { get; set; } //��id
        public uint sid { get; set; } //��id

        public uint mergeid;
        public int typ; //������
        //public R r;
        //public S s;

        public Proto(string name, uint mid, uint sid, int typ)
        {
            this.name = name;
            this.mid = mid;
            this.sid = sid;
            this.typ = typ;
        }
    }

    public class ProtoMaps
    {

        private static readonly ProtoMaps instance = new ProtoMaps();


        public static ProtoMaps Instance
        {
            get { return instance; }
        }


        //public Dictionary<string, IProto> protos = new Dictionary<string, IProto>();

        public Dictionary<string, Proto> protos = new Dictionary<string, Proto>();

        public Dictionary<uint, Proto> id2proto = new Dictionary<uint, Proto>();


        public void Add(string name, Proto pb)
        {
            protos.Add(name, pb);
        }

        public void SortById()
        {
            foreach (var item in protos)
            {
                var pb = item.Value;
                uint mergeid = PackageProtocol.getMergeCmd(pb.mid, pb.sid);
                pb.mergeid = mergeid;
                id2proto.Add(mergeid, pb);
            }
        }


        public ProtoMaps()
        {
            this.Add("login", new Proto("login", 1, 2, 1));
            this.Add("registerInfo", new Proto("registerInfo", 1, 1, 1));
            this.Add("enterRoom", new Proto("enterRoom", 1, 13, 3));
            this.Add("roleMove", new Proto("roleMove", 4, 3, 3));
            //this.Add("onNewRoleEnter", new Proto("onNewRoleEnter", 7, 1, 2));
            //服务器主动广播的角色退出
            this.Add("roleExitRoom", new Proto("roleExitRoom", 7, 2, 2));
            this.Add("talkMrg", new Proto("talkMrg", 1, 9, 3));
           
            //玩家点击的角色退出
            this.Add("playerExitRoom", new Proto("playerExitRoom", 1, 5, 3));


            this.Add("gameFrame", new Proto("gameFrame", 4, 2, 1));
            this.Add("gameObjUsed", new Proto("gameObjUsed", 4, 4, 3));

            this.Add("getOtherInfo", new Proto("getOtherInfo", 1, 23, 1));

            this.Add("heatBeat", new Proto("heatBeat", 1, 120, 1));



            SortById();
        }

        public Proto Name2Pb(string name)
        {
            //Proto pb = ProtoMaps.Instance.protos[name];
            if (protos.ContainsKey(name))
            {
                return protos[name];
            }
            return null;
        }

        internal Proto GetByMergeId(uint cmdMerge)
        {
            if (id2proto.ContainsKey(cmdMerge))
            {
                return id2proto[cmdMerge];
            }
            return null;
        }
    }





}

网络层脚步5:ExternalMessage.cs

using ProtoBuf;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UTNET
{

    [ProtoContract]
    public class ExternalMessage
    {
        [ProtoMember(1)]
        // 请求命令类型: 0 心跳,1 业务
        public int cmdCode;
        // 协议开关,用于一些协议级别的开关控制,比如 安全加密校验等。 : 0 不校验
        [ProtoMember(2)]
        public int protocolSwitch;
        // 业务路由(高16为主, 低16为子)
        [ProtoMember(3)]
        public uint cmdMerge;
        // 响应码: 0:成功, 其他为有错误
        [ProtoMember(4,DataFormat = DataFormat.ZigZag)]
        public int responseStatus;
        // 验证信息(错误消息、异常消息),通常情况下 responseStatus == -1001 时, 会有值
        [ProtoMember(5)]
        public string validMsg;
        // 业务请求数据
        [ProtoMember(6)]
        public byte[] data;
        // 消息标记号;由前端请求时设置,服务器响应时会携带上;
        [ProtoMember(7, DataFormat = DataFormat.ZigZag)]
        public int msgId;
    }


    [ProtoContract]
    public class Package
    {
        [ProtoMember(1)]
        public uint packageType;

        [ProtoMember(2)]
        public string route = null;

        [ProtoMember(3)]
        public uint packID = 0;

        [ProtoMember(4)]
        public byte[] buff = null;

        [ProtoMember(5)]
        public string modelName = null;
    }

    //[ProtoContract]
    //public class Message<T>
    //{
    //    [ProtoMember(1)]
    //    public uint err;
    //    [ProtoMember(2)]
    //    public string errMsg = default;
    //    [ProtoMember(3)]
    //    public T data = default;
    //    public Message() { }
    //    public Message(uint err, string errMsg, T info)
    //    {
    //        this.err = err;
    //        this.errMsg = errMsg;
    //        this.data = info;
    //    }
    //}

    [ProtoContract]
    public class HandShake
    {
        [ProtoMember(1)]
        public string token;
    }

    [ProtoContract]
    public class Heartbeat
    {
        [ProtoMember(1)]
        public uint heartbeat;
    }
}

网络层脚本6:TestProto.cs


using ProtoBuf;
using ProtoBuf.Meta;
using System.Collections.Generic;

[ProtoContract]
public class RegisterInfo
{
    [ProtoMember(1)]
    public string phoneNum;
    [ProtoMember(2)]
    public string account;
    [ProtoMember(3)]
    public string pwd;
}


[ProtoContract]
public class LoginVerify 
{
   
    [ProtoMember(1)]
    public string account;
    [ProtoMember(2)]
    public string pwd;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public int loginBizCode;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public int spaceId;
}

[ProtoContract]
public class LobbyTalkMsg
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public long usrId;
    [ProtoMember(3)]
    public string nickName;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public int to_usrId;
    [ProtoMember(5)]
    public string msg;
    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public int msg_type;
}

[ProtoContract]
public class RequestSuccess
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public int success;

}

[ProtoContract]
public struct GameFrame {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long frameId;
    [ProtoMember(2)]
    public string opc;
    [ProtoMember(3)]
    public string world;
    [ProtoMember(4)]
    public TankLocation location;

}


[ProtoContract]
public class UserInfo {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2)]
    public  string nickName;
    [ProtoMember(3)]
    public string account;
    [ProtoMember(4)]
    public string gender;
    
    [ProtoMember(5)]
    public string headImgUrl;

    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public long avatarId;

    [ProtoMember(7)]
    public string avatar;
    [ProtoMember(8)]
    public string hold;

    [ProtoMember(9)]
    public string mtk;
    
    [ProtoMember(10)]
    public string ltk;

    [ProtoMember(11, DataFormat = DataFormat.ZigZag)]
    public long roomId;


    [ProtoMember(12, DataFormat = DataFormat.ZigZag)]
    public long team;


    [ProtoMember(13)]
    public TankLocation tankLocation;

    [ProtoMember(14, DataFormat = DataFormat.ZigZag)]
    public int speed;
}

[ProtoContract]
public class TankLocation {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public  float x;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public float y;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public float z;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public float dx;
    [ProtoMember(5, DataFormat = DataFormat.ZigZag)]
    public float dy;
    [ProtoMember(6, DataFormat = DataFormat.ZigZag)]
    public float dz;
    [ProtoMember(7, DataFormat = DataFormat.ZigZag)]
    public long playerId;
    [ProtoMember(8, DataFormat = DataFormat.ZigZag)]
    public long time;

}


[ProtoContract]
public class TankPlayer {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long id;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public int speed;
    [ProtoMember(3)]
    public TankLocation tankLocation;
    [ProtoMember(4)]
    public string nickname;
    [ProtoMember(5)]
    public string hold;
    // 其他属性: key: 子弹 id 1 玩具弹, 2 雪弹; value : 数量
    [ProtoMember(6)]
    public Dictionary<int, int> tankBulletMap;
}


[ProtoContract]
public class TankEnterRoom {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long roomId;
    [ProtoMember(2, DataFormat = DataFormat.ZigZag)]
    public long time;
    [ProtoMember(3, DataFormat = DataFormat.ZigZag)]
    public long team;
    [ProtoMember(4, DataFormat = DataFormat.ZigZag)]
    public long playerId;

    [ProtoMember(5)]
    public string sharetoken;

    [ProtoMember(6)]
    public string usertoken;

    [ProtoMember(7)]
    public string world;

    [ProtoMember(8)]
    public List<UserInfo> tankPlayerList;

}

[ProtoContract]
public class UserIds {
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public long[] userIds;
}

[ProtoContract]
public class UserInfos
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public UserInfo[] users;
}

[ProtoContract]
public class HeartBeat
{
    [ProtoMember(1, DataFormat = DataFormat.ZigZag)]
    public int beat;
}




(2)用于多人同步的具体实现类:SyncTransform.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UTNET;

[DisallowMultipleComponent]
public class SyncTransform : SyncBase
{
    public  SyncTransfomData  data = new SyncTransfomData();
    public bool isRole;//判断是否是角色的位置同步

    bool isFirst = true; //第一次同步不用插值
    private void Awake() {
        SyncTransfomUtil.FromTransform(transform,ref data);
        EventManager.Instance.AddListener(EventName.OnEnterRoom, OnEnterRoom);
    }

    private void OnEnterRoom(object sender, EventArgs e) {
        AddNetWorkListner<SyncTransfomData>(OnSyncNet, nameof(SyncTransfomData));
        OnSyncWorldData();
    }



    // Start is called before the first frame update
    void Start()
    {
        if (syncId == string.Empty) {
            Debug.LogError("syncId null");
            return;
        }

        AddNetWorkListner<SyncTransfomData>(OnSyncNet, nameof(SyncTransfomData));
        //OnSyncWorldData();
    }

    void OnSyncWorldData() {
        var syncdata = SyncWorldModel.Instance.GetTransfomData(syncId);
        if (syncdata != null) {
            OnSyncNet(syncdata);
            SyncTransfomUtil.ToTransform(transform, syncdata);
        }

    }

    public void InitLocation(SyncTransfomData syncdata) {
        data = syncdata;
        SyncTransfomUtil.ToTransform(transform, data);
    }


    public void SetPosAndSync(SyncTransfomData syncdata) {
        data = syncdata;
        SyncTransfomUtil.ToTransform(transform, data);
        SendNetWork<SyncTransfomData>(data, nameof(SyncTransfomData));
        SyncWorldModel.Instance.SetTransfomData(syncId, data);
        SyncWorldModel.Instance.SyncWorldData();
    }

    private void OnSyncNet(SyncTransfomData syData) {
        data = syData;
    }

    //private void OnMouseDrag() {
    //    var dstRay = Camera.main.ScreenPointToRay(Input.mousePosition);
    //    transform.forward = -Camera.main.transform.forward;
    //    transform.position = Camera.main.transform.position + Vector3.Normalize(dstRay.direction) * 5f;
    //    transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
    //}

      void SendPlayerLocation(SyncTransfomData data, string stName) {

        if (dataName != stName) {
            Debug.LogError("error:传输数据出错请检查");
            return;
        }

        dataBase.syncId = syncId;
        dataBase.name = dataName;
        dataBase.obj = JsonUtility.ToJson(data);
        dataBase.isLocation = true;
        dataBase.playerId = PlayerData.Instance.PlayerId.ToString();
        NetManager.Instance.SendFrameLocationData(data, dataBase);
    }

    //bool TransformEqual(int[] arryInt) {
    //    transform.position
    //}

    // Update is called once per frame
    void Update()
    {
        var posV3 = new Vector3((float)data.pos[0] / 10f, (float)data.pos[1] / 10f, (float)data.pos[2] / 10f);
        var rotateV3 = new Vector3((float)data.roate[0] / 10f, (float)data.roate[1] / 10f, (float)data.roate[2] / 10f);
        if(ownerState == OwnerState.Ohter) {

            if (SyncTransfomUtil.V3Equal(transform.position, posV3) == false)
            {
                if (isFirst)
                {
                   
                    transform.position = posV3;

                }
                else if (Vector3.Distance(transform.position, posV3) < 0.5f)
                {
                    transform.position = posV3;
                }
                else
                {
                    transform.position = Vector3.Lerp(transform.position, posV3, 5f * Time.deltaTime);
                }
                isFirst = false;
            }
            

            if (SyncTransfomUtil.V3Equal(transform.eulerAngles, rotateV3) == false) {
                transform.eulerAngles = rotateV3;
            }
        }
        else if (ownerState == OwnerState.Mine){
            var scaleV3 = new Vector3((float)data.scale[0] / 10f, (float)data.scale[1] / 10f, (float)data.scale[2] / 10f);
            if (SyncTransfomUtil.V3Equal(posV3, transform.position) &&
                SyncTransfomUtil.V3Equal(rotateV3, transform.eulerAngles) &&
                SyncTransfomUtil.V3Equal(scaleV3, transform.localScale)) {
                return;
            }
            SyncTransfomUtil.FromTransform(transform, ref data);
           if (isRole == false) { //如果不是角色就正常同步
                base.SendNetWork<SyncTransfomData>(data, nameof(SyncTransfomData));
                SyncWorldModel.Instance.SetTransfomData(syncId, data);
                SyncWorldModel.Instance.SyncWorldData();
            }
           else {
                SendPlayerLocation(data, nameof(SyncTransfomData));
            }
        }

    }


}

(3)用于状态同步的具体实现类:SyncState.cs

using System;
using UnityEngine;
using UnityEngine.Events;
[DisallowMultipleComponent]
public class SyncState : SyncBase {
    public SyncStateData data = new SyncStateData();
    public UnityEvent<SyncStateData> onNetSyncEven = new UnityEvent<SyncStateData>();
    public UnityEvent<SyncStateData> onLoginSyncEven = new UnityEvent<SyncStateData>();

    bool isInitOk;
    private void Awake() {
        EventManager.Instance.AddListener(EventName.OnEnterRoom,OnEnterRoom);
    }

    

    // Start is called before the first frame update
    void Start() {
        if (syncId == string.Empty) {
            Debug.LogError("syncId null");
            return;
        }
        AddNetWorkListner<SyncStateData>(OnSyncNet, nameof(SyncStateData));
    }

    private void OnEnterRoom(object sender, EventArgs e) {
        AddNetWorkListner<SyncStateData>(OnSyncNet, nameof(SyncStateData));
        OnSyncWorldData();//如果是创建出来的物体,不是在场景中的,那么就需要在start的时候去处理
    }
    void OnSyncWorldData() {

        if (isInitOk == true) {
            return;
        }

        var syncdata = SyncWorldModel.Instance.GetStateData(syncId);
        if (syncdata != null) {
            Debug.Log($"OnSyncWorldData {syncId} {syncdata.stateName}");
            data = syncdata;
            onLoginSyncEven?.Invoke(syncdata);
            isInitOk = true;
        }

    }

    private void OnSyncNet(SyncStateData syData) {
        onNetSyncEven?.Invoke(syData);
    }

    public void SendState(string stateName) {

        if (ownerState ==  OwnerState.Ohter) {
            return;
        }

        data.stateName = stateName;
        SendNetWork<SyncStateData>(data, nameof(SyncStateData));
        SyncWorldModel.Instance.SetStateData(syncId,data);
        SyncWorldModel.Instance.SyncWorldData();
    }

    //float timeadd = 0f;
    //private void Update() {
    //    timeadd += Time.deltaTime;
    //    if (timeadd >3) {
    //        timeadd = 0f;
    //        //data.stateName = "Jump";
    //        //onNetSyncEven?.Invoke(data);
    //        GetComponentInChildren<Animator>().SetTrigger("Jump");
    //    }
        
    //}

}

(4)状态同步的做法:本人

(5)其他人:

private void OnNetSync(SyncStateData data)
{
    var splits = data.stateName.Split(';');
    var protoName = splits[0];
    var userId = long.Parse(splits[1]);
    if (protoName == "openVioce")
    {

        if (userId == this.roleId)
        {
            
            this.isShowRoleUIVoice(true);
        }
    }
    if (protoName == "closeVioce")
    {

        if (userId == this.roleId)
        {

            this.isShowRoleUIVoice(false);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyTorch----torch.nn.init.kaiming_normal_

神经网络为什么要进行权重初始化&#xff1f; 神经网络进行权重初始化的目的在于促进网络的有效训练和收敛。正确的权重初始化可以帮助缓解梯度消失或梯度爆炸等问题&#xff0c;并且有助于加速训练过程。以下是权重初始化的几个重要原因&#xff1a; 1. **避免梯度消失或梯度…

锂离子电池SOC预测 | python代码实现基于Basisformer时间序列锂离子电池SOC预测研究

概述 基于Basisformer时间序列的锂离子电池SOC(State of Charge,即电池的荷电状态)预测研究,是一项结合深度学习和时间序列分析的前沿技术。该研究的目的是利用Basisformer模型对锂离子电池的SOC进行准确预测,从而提高电池使用效率,延长电池寿命,并优化能源管理系统。 …

unity学习(73)——服务器异常--无法处理 123类型的数据包

服务器发送回的数据包&#xff0c;客户端根本读不出来&#xff0c;type都读不出来&#xff0c;拖了三天&#xff0c;把客户端翻了个底朝天&#xff0c;发现客户端一点问题都没有&#xff01; 所有的问题不是unity的模型问题&#xff0c;就是socket网络通信中断&#xff01; 1…

大文件压缩多个小文件

压缩 压缩后 压缩后&#xff0c;符合上传大小规范

矢量(向量)数据库

矢量(向量)数据库 什么是矢量数据库&#xff1f; 在人工智能领域&#xff0c;大量的数据需要有效的分析和处理。随着我们深入研究更高级的人工智能应用&#xff0c;如图像识别、语音搜索或推荐引擎&#xff0c;数据的性质变得更加复杂。这就是矢量数据库发挥作用的地方。与存…

Kafka详细教程(一)

总体目录 1、什么是消息队列 消息队列&#xff0c;英文名&#xff1a;Message Queue&#xff0c;经常缩写为MQ。从字面上来理解&#xff0c;消息队列是一种用来存储消息的队列 。来看一下下面的代码 // 1.创建一个保存字符串的队列Queue<String> queue new LinkedList&…

ssm小区车库停车系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm小区车库停车系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

开源博客项目Blog .NET Core源码学习(12:App.Application项目结构分析)

开源博客项目Blog的App.Application项目主要定义网站页面使用的数据类&#xff0c;同时定义各类数据的增删改查操作接口和实现类。App.Application项目未安装Nuget包&#xff0c;主要引用App.Core项目的类型。   App.Application项目的顶层文件夹如下图所示&#xff0c;下面逐…

k8s入门到实战(七)—— 回顾:使用yaml文件配置pv、pvc、configmap部署mysql服务

实战&#xff1a;部署 mysql 服务 回顾加深 pv、pvc、configmap 删除所有 deployment、pv、pvc、configmap、StorageClass创建一个 nsf 挂载目录给 mysql mkdir -p /nfs/data/mysql创建 yaml 文件mysql-server.yaml # 创建pv apiVersion: v1 kind: PersistentVolume metadat…

Day22 LeedCode:235.二叉搜索树的最近公共祖先 701.二叉搜索树的插入操作 450.删除二叉搜索树的结点

235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&…

基于Arduino IDE 野火ESP8266模块 一键配网 的开发

一、配网介绍 ESP8266 一键配网&#xff08;也称为 SmartConfig 或 FastConfig&#xff09;是一种允许用户通过智能手机上的应用程序快速配置 ESP8266 Wi-Fi 模块的方法&#xff0c;而无需手动输入 SSID 和密码。为了实现这一功能&#xff0c;则需要一个支持 SmartConfig 的智能…

[flask]执行上下文的四个全局变量

flask上下文全局变量&#xff0c;程序上下文、请求上下文、上下文钩子 -- - 夏晓旭 - 博客园 (cnblogs.com) 执行上下文 执行上下文&#xff1a;即语境&#xff0c;语意&#xff0c;在程序中可以理解为在代码执行到某一行时&#xff0c;根据之前代码所做的操作以及下文即将要…

校园app开发流程-uniapp开发-支持APP小程序H5-源码交付-跑腿-二手市场-交友论坛等功能,学校自由选择!

随着科技的不断发展&#xff0c;智慧校园系统和跑腿外卖小程序已经成为当今社会的热门话题。作为未来的重要趋势之一&#xff0c;科技在教育领域中的应用越来越广泛。本文将探讨智慧校园系统和跑腿外卖小程序的开发过程&#xff0c;并阐述如何利用科技“育”见未来 一、智慧校…

基于云计算的前端资源管理系统的设计与实现

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 随着互联网的快速发展&#xff0c;前端资源管理成为了一个重要的课题。本文旨在设计并实…

elementui日期时间选择框自定义组件

1.需求场景 业务中需要&#xff0c;日期选择框方便客户对日期的选择&#xff08;比如近5天&#xff0c;本周&#xff0c;本月&#xff0c;本年等等&#xff09;&#xff0c;并按小时展示。 2.组件代码MyDateTimeChange.vue <template><el-date-pickerv-model"…

【ESP32S3 Sense接入语音识别+MiniMax模型对话】

1. 前言 围绕ESP32S3 Sense接入语音识别MiniMax模型对话展开&#xff0c;首先串口输入“1”字符&#xff0c;随后麦克风采集2s声音数据&#xff0c;对接百度在线语音识别&#xff0c;将返回文本结果丢入MiniMax模型&#xff0c;进而返回第二次结果文本&#xff0c;实现语言对话…

Linux 系统 docker搭建LNMP环境

1、安装nginx docker pull nginx (默认安装的是最新版本) 2、运行nginx docker run --name nginx -p 80:80 -d nginx:latest 备注&#xff1a;--name nginx 表示容器名为 nginx -d 表示后台运行 -p 80:80 表示把本地80端口绑定到Nginx服务端的 80端口 nginx:lates…

【问题分析】InputDispatcher无焦点窗口ANR问题【Android 14】

1 问题描述 Monkey跑出的无焦点窗口的ANR问题。 特点&#xff1a; 1&#xff09;、上层WMS有焦点窗口&#xff0c;为Launcher。 2&#xff09;、native层InputDispacher无焦点窗口&#xff0c;上层为”recents_animation_input_consumer“请求了焦点&#xff0c;但是”rece…

国赛大纲解读

1. 第一部分,是针对5G基础知识的掌握,第二部分是人工智能基本算法的掌握,就是人工智能的应用,用5G+人工智能(AI算法)进行网络优化的问题,要有网络优化的基础知识,比如说:某个区域的覆盖问题,覆盖特别差,但有数据,覆盖电频,srp值这些数据给你,根据数据来判断是…

OCP NVME SSD规范解读-15.DSSD set feature功能要求-1

4.15.2 DSSD Set Feature Requirements章节重点描述了针对数据中心NVMe SSD&#xff08;DSSD&#xff09;特有的设置特性要求。在NVMe SSD规范中&#xff0c;通过Set Feature命令可以对SSD进行各种高级功能的设置和配置&#xff0c;DSSD特有的Set Feature命令集中了一些特定于D…