Photon TRUESYNC
- Photon 官网首页(光子 专注多人网络同步游戏)
- Realtime 即时 (其实是让你去注册一个账号,然后创建应用。估计是使用它们的云服务平台。)
- PUN (看着像Photon Unity Networking的缩写, 是Unity网络组建,主要是链接到https://www.assetstore.unity3d.com/en/#!/content/1786)
- Bolt (是 Steam 用的好像,主要是链接到https://www.assetstore.unity3d.com/en/#!/content/83233)
- Thunder (Unity的UNET API的高级中继,打通和匹配服务,主要是链接到https://www.assetstore.unity3d.com/en/#!/content/83233)
- TrueSync (TrueSync奠基在PUN的所有功能之上,更提供一个能够轻松实作高精度同步的架构与特制的物理引擎。,主要是链接到 https://www.assetstore.unity3d.com/en/#!/content/73228)
- Photon Unity Networking Free, Unity网络组建 (assetstore)
- Photon Bolt $95.00, Steam使用的 (assetstore)
- Photon Thunder Unity的UNET API的高级中继,打通和匹配服务(assetstore)
- Photon TrueSync (assetstore)
- Photon Documentation Realtime
- Photon Documentation PUN
- Photon Documentation Bolt
- Photon Documentation Thunder and UNet
- Photon Documentation TrueSync
- Photon forum 论坛
- Photon产品介绍
Photon IPunCallbacks 调用PhotonNetwork.ConnectUsingSettings(gameVersion);时的回调
OnPhotonCustomRoomPropertiesChanged OnPhotonCustomRoomPropertiesChanged OnCreatedRoom OnJoinedRoom
Photon IPunCallbacks 调用CreateRoom时的回调
OnConnectedToPhoton OnConnectedToMaster
Photon IPunCallbacks 调用JoinRandomRoom时的回调
OnPhotonRandomJoinFailed
Photon IPunCallbacks 调用PhotonNetwork.LeaveRoom时的回调
OnLeftRoom OnConnectedToMaster
Photon IPunCallbacks 自己调用PhotonNetwork.LeaveRoom时的回调
OnLeftRoom OnConnectedToMaster
Photon IPunCallbacks 其他玩家调用PhotonNetwork.LeaveRoom时的回调
OnPhotonPlayerDisconnected(PhotonPlayer other)
Photon IPunCallbacks 调用PhotonNetwork.LoadLevel;时的回调
OnLeftRoom
TrueSync代码
- TrueSyncConfig
帧同步配置
管理、驱动帧同步 //同步全局配置 static TrueSyncConfig TrueSyncGlobalConfig; // 配置 static TrueSyncConfig Config; TrueSyncConfig ActiveConfig; // 锁帧 private AbstractLockstep lockstep; // 协程调度 CoroutineScheduler scheduler; // 玩家预设列表 GameObject[] playerPrefabs; // private Dictionary<int, List<GameObject>> gameOjectsSafeMap = new Dictionary<int, List<GameObject>>(); // 非哪个玩家拥有的行为,叫做普通行为 List<TrueSyncManagedBehaviour> generalBehaviours = new List<TrueSyncManagedBehaviour>(); // 对应玩家所拥有的行为 Dictionary<byte, List<TrueSyncManagedBehaviour>> behaviorsByPlayer; // 一个临时缓存池,用在后面注册行为的时候占存。在帧更新的时候OnStepUpdate调用CheckQueuedBehaviours。分配玩家拥有者和调OnSyncedStart。然后就清理该列表 List<TrueSyncManagedBehaviour> queuedBehaviours = new List<TrueSyncManagedBehaviour>(); // 保存了所有行为的字典 Dictionary<ITrueSyncBehaviour, TrueSyncManagedBehaviour> mapBehaviorToManagedBehavior = new Dictionary<ITrueSyncBehaviour, TrueSyncManagedBehaviour>(); // 时间 FP time = 0; static FP Time; // 帧时间 static FP DeltaTime; // 帧 static int Ticks; static int LastSafeTick; // 重力 static TSVector Gravity; // 玩家列表 static List<TSPlayerInfo> Players; // 本地玩家 static TSPlayerInfo LocalPlayer; // 启动状态, 在Update决定什么时候运行lockstep.RunSimulation(true); private enum StartState { BEHAVIOR_INITIALIZED, FIRST_UPDATE, STARTED }; private StartState startState; void Awake() { TrueSyncConfig currentConfig = ActiveConfig; lockedTimeStep = currentConfig.lockedTimeStep; // 初始化状态跟踪 StateTracker.Init(currentConfig.rollbackWindow); // 初始化随机数 TSRandom.Init(); // 初始化物理管理器 if (currentConfig.physics2DEnabled || currentConfig.physics3DEnabled) { PhysicsManager.New(currentConfig); PhysicsManager.instance.LockedTimeStep = lockedTimeStep; PhysicsManager.instance.Init(); } // 跟踪 时间 StateTracker.AddTracking(this, "time"); } void Start() 做了什么? 设置instance = this; 设置Application.runInBackground = true; 初始化通信PhotonTrueSyncCommunicator 创建lockstep 检测是否是录像模式, 如果是就加载录像 如果配置了显示TrueSyncStats,那就初始化 创建协程调度 scheduler = new CoroutineScheduler(lockstep); 非录像模式下 初始化帧的玩家列表 初始化场景中现有的帧同步行为 TrueSyncBehaviour 实例化玩家预设playerPrefabs和同步其行为的拥有者 initBehaviors 初始化行为拥有者,并分配给对于玩家。没有继承TrueSyncBehaviour的就继续放到普通行为列表。initGeneralBehaviors 添加物理对象移除监听 PhysicsManager.instance.OnRemoveBody(OnRemovedRigidBody); 设置启动状态 startState = StartState.BEHAVIOR_INITIALIZED; // 创建ITrueSyncBehaviour的TrueSyncManagedBehaviour private TrueSyncManagedBehaviour NewManagedBehavior(ITrueSyncBehaviour trueSyncBehavior) // 初始化玩家预设和他们的同步行为。行为设置拥有者,并其添加到对应玩家的行为字典里behaviorsByPlayer private void initBehaviors() // 对行为列表分配拥有者, 在Start(), CheckQueuedBehaviours()里调用 private void initGeneralBehaviors(IEnumerable<TrueSyncManagedBehaviour> behaviours, bool realOwnerId) // 将注册行为占存列表列queuedBehaviours的行为调initGeneralBehaviors分配拥有者。调SetGameInfo和OnSyncedStart两个方法 private void CheckQueuedBehaviours() // 只做一件事,检测启动状态,如果是第一次启动就调lockstep.RunSimulation(true); void Update() // 在帧同步调暂停后 恢复继续运行。instance.lockstep.RunSimulation(false); public static void RunSimulation() // 暂停游戏 调instance.lockstep.PauseSimulation(); public static void PauseSimulation() // 结束游戏 调instance.lockstep.EndSimulation(); public static void EndSimulation() // 更新一次协程, 主要是物理里调用了。默认的协程更新在 帧更新里OnStepUpdate public static void UpdateCoroutines() // 添加一个协程 public static void SyncedStartCoroutine(IEnumerator coroutine) // 实例化一个预设 // 先势力化一个GameObject // 非录像模式将该对象添加到帧记录里。AddGameObjectOnSafeMap(go); // 将该对象的帧行为添加到queuedBehaviours,等待帧更新的时候分配拥有者和调度初始化方法 // 调该对象上组件的初始化方法(ICollider注册到物理管理器里PhysicsManager, TSTransform, TSTransform2D)。InitializeGameObject public static GameObject SyncedInstantiate(GameObject prefab) public static GameObject SyncedInstantiate(GameObject prefab, TSVector position, TSQuaternion rotation) public static GameObject SyncedInstantiate(GameObject prefab, TSVector2 position, TSQuaternion rotation) // 将势力化的GameObject添加到当前的帧+1列表里 private static void AddGameObjectOnSafeMap(GameObject go) // 在帧更新OnStepUpdate的时候掉, 清理销毁掉当前 Ticks + 1里的GameObject。猜测估计是帧回滚的时候把预处理的对象销毁 private static void CheckGameObjectsSafeMap() // 调该对象上组件的初始化方法(ICollider注册到物理管理器里PhysicsManager, TSTransform, TSTransform2D) private static void InitializeGameObject(GameObject go, TSVector position, TSQuaternion rotation) // 销毁GameObject // 第一步调SyncedDisableBehaviour, 停止更新该对象上的ITrueSyncBehaviour // 第二步调 TSCollider和TSCollider2D 调 DestroyTSRigidBody public static void SyncedDestroy(GameObject gameObject) // 将GameObject的ITrueSyncBehaviour的disabled设置为true, 停止对他调帧更新方法 OnSyncedInput,OnSyncUpdate。 public static void SyncedDisableBehaviour(GameObject gameObject) // 设置 tsColliderGO.gameObject.SetActive(false); // 将物理对象从lockstep销毁 instance.lockstep.Destroy(body); private static void DestroyTSRigidBody(GameObject tsColliderGO, IBody body) // 注册ITrueSyncBehaviour, 将他添加到queuedBehaviours。在下次CheckQueuedBehaviours的时候,也就是在下次帧更新的时候OnStepUpdate,对他分配拥有者,调SetGameInfo和OnSyncedStart两个方法 public static void RegisterITrueSyncBehaviour(ITrueSyncBehaviour trueSyncBehaviour) // 注册游戏是否继续的委托。 调委托会返回一个bool值。 true游戏可以继续运行。lockstep里会调该方法检测是否可以继续CheckGameIsReady() public static void RegisterIsReadyChecker(TrueSyncIsReady IsReadyChecker) // 移除玩家 // 第一步将该玩家的 行为全部禁止帧更新behaviorsByPlayer[(byte)playerId],disabled = true; // 第二步将这些行为的GameObject上拥有TSCollider、TSCollider2D的物理全部掉DestroyTSRigidBody public static void RemovePlayer(int playerId) // 检测帧更新时间,时间到就调instance.scheduler.UpdateAllCoroutines();和lockstep.Update(); // lockedTimeStep,帧同步一帧的时间 // JitterTimeFactor, 为了避免浮动点数比较造成误差。if (tsDeltaTime >= (lockedTimeStep - JitterTimeFactor)) // tsDeltaTime, 用的时间还是用Unity的 tsDeltaTime += UnityEngine.Time.deltaTime; void FixedUpdate() // 里面创建一个输入数据结构 return new InputData(); 是在lockstep创建的时候传这个方法给他 InputDataBase ProvideInputData() // 这个方法会调本地玩家所有帧行为的OnSyncedInput方法 // 这这个方法生命周期内 TrueSyncInput.CurrentInputData = playerInputData // 是在lockstep创建的时候传这个方法给他 void GetLocalData(InputDataBase playerInputData) // 帧更新 // 添加当前时间 time += lockedTimeStep; // 非录像模式, 检测GameObject CheckGameObjectsSafeMap(); // 遍历generalBehaviours普通行为列表,调行为的OnPreSyncedUpdate()。还会调协程更新instance.scheduler.UpdateAllCoroutines(); // 遍历allInputData,和对应玩家的行为列表behaviorsByPlayer。 调行为的OnPreSyncedUpdate()。还会调协程更新instance.scheduler.UpdateAllCoroutines(); // 遍历generalBehaviours普通行为列表,调行为的OnSyncedUpdate()。还会调协程更新instance.scheduler.UpdateAllCoroutines(); // 遍历allInputData,和对应玩家的行为列表behaviorsByPlayer。 调行为的OnSyncedUpdate()。还会调协程更新instance.scheduler.UpdateAllCoroutines(); // 检测占存行为列表CheckQueuedBehaviours()。给他们分配拥有者和调同步开始方法 // 是在lockstep创建的时候传这个方法给他 void OnStepUpdate(List<InputDataBase> allInputData) // 玩家离线消息处理 // 调TrueSyncManagedBehaviour.OnPlayerDisconnection(generalBehaviours, behaviorsByPlayer, playerId); // 是在lockstep创建的时候传这个方法给他 void OnPlayerDisconnection(byte playerId) // 游戏开始消息处理 // 是在lockstep创建的时候传这个方法给他 void OnGameStarted() { TrueSyncManagedBehaviour.OnGameStarted(generalBehaviours, behaviorsByPlayer); instance.scheduler.UpdateAllCoroutines(); CheckQueuedBehaviours(); } // 游戏暂停消息处理 // 是在lockstep创建的时候传这个方法给他 void OnGamePaused() { TrueSyncManagedBehaviour.OnGamePaused(generalBehaviours, behaviorsByPlayer); instance.scheduler.UpdateAllCoroutines(); } // 游戏继续消息处理 void OnGameUnPaused() { TrueSyncManagedBehaviour.OnGameUnPaused(generalBehaviours, behaviorsByPlayer); instance.scheduler.UpdateAllCoroutines(); } // 游戏结束消息处理 void OnGameEnded() { TrueSyncManagedBehaviour.OnGameEnded(generalBehaviours, behaviorsByPlayer); instance.scheduler.UpdateAllCoroutines(); } // 移除物理对象事件处理 // 会移除该对象的GameObject上所有同步行为 调RemoveFromTSMBList // PhysicsManager.instance.OnRemoveBody(OnRemovedRigidBody); 在这里注册 private void OnRemovedRigidBody(IBody body) // 从tsmbList列表中,移除behaviours private void RemoveFromTSMBList(List<TrueSyncManagedBehaviour> tsmbList, List<TrueSyncBehaviour> behaviours) // 清理 // 清理对象池 ResourcePool.CleanUpAll(); // 清理状态跟踪 StateTracker.CleanUp(); // 去除实例变量引用 instance = null; public static void CleanUp() // Unity的消息。退出应用 void OnApplicationQuit() { EndSimulation(); }
接口 通信器 // 往返时间 int RoundTripTime(); // 操作时间 void OpRaiseEvent(byte eventCode, object message, bool reliable, int[] toPlayers); // 添加监听 void AddEventListener(OnEventReceived onEventReceived);
帧同步 通信器, 实现ICommunicator接口 // 往返时间 int RoundTripTime(); // 操作时间 void OpRaiseEvent(byte eventCode, object message, bool reliable, int[] toPlayers); // 添加监听 void AddEventListener(OnEventReceived onEventReceived);
代理事件, 接收消息 // byte eventCode 消息编号 // object content 消息内容 public delegate void OnEventReceived(byte eventCode, object content);
帧同步行为,继承自 MonoBehaviour, 实现接口 ITrueSyncBehaviourGamePlay, ITrueSyncBehaviourCallbacks // 该行为的拥有者玩家 public TSPlayerInfo owner; // 本地玩家,相当于快捷访问本地玩家 public TSPlayerInfo localOwner; // 快捷访问 TSTransform tsTransform TSTransform2D tsTransform2D TSRigidBody tsRigidBody TSRigidBody2D tsRigidBody2D TSCollider tsCollider TSCollider2D tsCollider2D // 基本上就是上面这些属性,实现的接口都是空的没有写业务逻辑
帧同步行为管理器,实现接口 ITrueSyncBehaviourGamePlay, ITrueSyncBehaviour, ITrueSyncBehaviourCallbacks 主要是包装了TrueSyncBehaviour/ITrueSyncBehaviour, 实现的接口方法直接掉TrueSyncBehaviour的放方法,TrueSyncBehaviour的OnSyncedStartLocalPlayer 方法该行为是本地用户时才调 // 有一个属性, true是,不会参与帧更新 [AddTracking] public bool disabled; 后面就是一些全局静态方法,用管理处理列表的事件
协程调用 在TrueSyncManager.SyncedStartCoroutine 调 StartCoroutine(IEnumerator coroutine) 启动一个协程 在TrueSyncManager驱动UpdateAllCorutines()
接口 帧同步行为 就只有一个方法 // 设置游戏信息 (本地玩家, 玩家数量) void SetGameInfo(TSPlayerInfo localOwner, int numberOfPlayers);
接口 玩家操作帧同步行为, 继承自ITrueSyncBehaviour // 同步 玩家输入操作 void OnSyncedInput(); // 同步 读取玩家操作 void OnSyncedUpdate(); void OnPreSyncedUpdate();
接口 回调同步行为, 继承自ITrueSyncBehaviour // 开始 void OnSyncedStart(); // 开始 -- 只调本地玩家的 void OnSyncedStartLocalPlayer(); // 游戏暂停 void OnGamePaused(); // 游戏继续 void OnGameUnPaused(); // 可以游戏了, 什么时候调用 我也不清楚 void OnGameEnded(); // 有玩家离线时调用 void OnPlayerDisconnection(int playerId);
随机数 http://www.codeproject.com/Articles/164087/Random-Number-Generation https://github.com/ihaiucom/learn.PhotonTrueSync/blob/master/PhotonGame/Assets/TrueSync/Engine/Math/TSRandom.cs 原理是,传一个因素进去,然后里面生成N数量的数组。每个数有一个公式计算来生成。所以传相同的因素生成的结果是一样的。 获取随机数的时候根据当前索引mti依次读取数组里面的数。当mti大于N时,内部重新生成默认因素是5489U
TrueSync/Engine/Math/TSRandom.cs
状态跟踪信息, 用来保存对象引用,和对象的成员属性信息MemberInfo object relatedObj; 保存对象 MemberInfo propInfo; 对象的成员属性信息
状态跟踪信息, 持有TrackedInfo跟踪信息 // 保存值到value变量 public void SaveValue() // 将保存的值用反射赋值给对象 public void RestoreValue()
状态跟踪 //TSRandom 用到 StateTracker.AddTracking(r, "mt"); StateTracker.AddTracking(r, "mti"); //TrueSyncManager 用到 StateTracker.AddTracking(this, "time"); //TSTransform 用到 配合 [AddTracking] Attribute 使用, StateTracker.AddTracking(object obj)通过反射获取obj的成员变量 StateTracker.AddTracking(this); // TrueSyncManagedBehaviour 用到 StateTracker.AddTracking(this); StateTracker.AddTracking(trueSyncBehavior); // 这个是核心了, 里面保存了rollbackWindow数量的列表,AddTracking的时候回把StateTracker.State添加到所有列表里 // SaveState 的时候就保存GenericBufferWindow当前列表的, 保存完后就GenericBufferWindow的索引移动下一个 // RestoreState 从GenericBufferWindow当前的列表把值恢复 StateTracker.instance.states = new GenericBufferWindow<List<StateTracker.State>>(rollbackWindow);
通用缓存窗口 // StateTracker 用到 StateTracker.instance.states = new GenericBufferWindow<List<StateTracker.State>>(rollbackWindow); // CompoundStats 用到 this.bufferStats = new GenericBufferWindow<Stats>(10); // AbstractLockstep 用到 this.bufferSyncedInfo = new GenericBufferWindow<SyncedInfo>(3); 构造方法: 会创建一个T[size] 的数组buffer,并且实例化T
ResourcePool 是一个抽象对象池,他有一个静态对象池列表。他管理所有对象池的清理CleanUpAll(); ResourcePool<T> 是ResourcePool派生类。里面有一个对象栈存储空闲的对象。 // 还回对象 GiveBack(T obj) // 获取对象, 如果T是ResourcePoolItem的派生类就会调对象的CleanUp()方法 T GetNew() // 实例化对象 T NewInstance() ResourcePoolItem 是对象池对象接口,实现该接口的对象在获取对象时会调CleanUp()方法 【使用】 internal class ResourcePoolListSyncedData : ResourcePool<List<SyncedData>> internal class ResourcePoolStateTrackerState : ResourcePool<StateTracker.State> internal class ResourcePoolSyncedData : ResourcePool<SyncedData>
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver 其实就是一个字典Dictionary, 然后实现了Unity的接口ISerializationCallbackReceiver。OnBeforeSerialize、OnAfterDeserialize 派生类 public class SerializableDictionaryByteByte : SerializableDictionary<byte, byte> public class SerializableDictionaryByteByteArray : SerializableDictionary<byte, byte[]> public class SerializableDictionaryByteInt : SerializableDictionary<byte, int> public class SerializableDictionaryBytePlayer : SerializableDictionary<byte, TSPlayer> public class SerializableDictionaryByteString : SerializableDictionary<byte, string> public class SerializableDictionaryIntSyncedData : SerializableDictionary<int, SyncedData>
继承ResourcePoolItem 对象池对象接口 抽象输入数据,主要定义了几个接口。和一个owerID拥有者属性 // 序列化 public abstract void Serialize(List<byte> bytes); // 解析 public abstract void Deserialize(byte[] data, ref int offset); // 是否想等 public abstract bool EqualsData(InputDataBase otherBase); // 清理 public abstract void CleanUp(); // 拷贝 public abstract void CopyFrom(InputDataBase fromBase);
继承InputDataBase 拥有各个基本类型字典 序列化和解析都是对这些解绑类型字典 每个值的序列化:key, valueType, value 数组的: key, valueType, length, value[] 字符串的用char[] 也就是数组 然后有各个类型的AddXX和GetXX
同步信息,保存了3个属性 // 玩家ID public byte playerId; // 帧 public int tick; // 校验码 public string checksum; 2 个方法 // 序列化 public static byte[] Encode(SyncedInfo info) // 解析 public static SyncedInfo Decode(byte[] infoBytes)
同步数据 主要就序列化下面两个方法的数据 public void GetEncodedHeader(Listbytes) { // 帧 Utils.GetBytes(this.tick, bytes); // 拥有者玩家ID bytes.Add(this.inputData.ownerID); // 从哪个玩家掉线 bytes.Add(this.dropFromPlayerId); // 是否掉线 bytes.Add(this.dropPlayer ? 1 : 0); } public void GetEncodedActions(List bytes) { this.inputData.Serialize(bytes); } </pre> TSPlayerInfo 玩家信息,保存2个属性 // 玩家ID [SerializeField] internal byte id; // 玩家名称 [SerializeField] internal string name;TSPlayer 玩家 // 玩家信息 [SerializeField] public TSPlayerInfo playerInfo; // 掉线次数 [NonSerialized] public int dropCount; //是否掉线 [NonSerialized] public bool dropped; // 开始发送同步数据 [NonSerialized] public bool sentSyncedStart; // 保存玩家整个战斗的操作同步数据, 他是一个字典SerializableDictionary<int, SyncedData> [SerializeField] internal SerializableDictionaryIntSyncedData controls; // 最后一次 同步操作数据的帧, AddData(SyncedData data) private int lastTick; internal TSPlayer(byte id, string name) { // 创建玩家信息 this.playerInfo = new TSPlayerInfo(id, name); this.dropCount = 0; this.dropped = false; // 创建玩家操作字典存储器 this.controls = new SerializableDictionaryIntSyncedData(); } // 获取某帧是否有真实同步操作数据 public bool IsDataReady(int tick) { return this.controls.ContainsKey(tick) && !this.controls[tick].fake; } // 获取某帧是否有模拟同步操作数据, 客户端先行,是客户端预测的操作, 回滚添加的。 public bool IsDataDirty(int tick) { bool flag = this.controls.ContainsKey(tick); return flag && this.controls[tick].dirty; } // 获取该帧的同步操作数据 public SyncedData GetData(int tick) { bool flag = !this.controls.ContainsKey(tick); SyncedData result; if (flag) { // 如果不存在,就查找上一帧是否存在 bool flag2 = this.controls.ContainsKey(tick - 1); SyncedData syncedData; if (flag2) { // 如果存在上一帧,就克隆上一帧的同步数据 syncedData = this.controls[tick - 1].clone(); syncedData.tick = tick; } else { // 否则就新建一个同步数据 syncedData = SyncedData.pool.GetNew(); syncedData.Init(this.ID, tick); } // 设置为伪造的 syncedData.fake = true; // 保存到帧字典 this.controls[tick] = syncedData; result = syncedData; } else { // 如果存在该,就返回该帧数据 result = this.controls[tick]; } return result; } // 添加存在帧同步数据 public void AddData(SyncedData data) { int tick = data.tick; bool flag = this.controls.ContainsKey(tick); if (flag) { // 如果已经存在,就还给对象池 SyncedData.pool.GiveBack(data); } else { // 否则 添加到存储里 this.controls[tick] = data; // 设置最后存储的帧 this.lastTick = tick; } }ReplayRecord 录像 负责记录游戏是所有玩家的SynceData 序列化: 各个玩家的所有操作AbstractLockstep.SimulationState 帧同步模拟器状态 private enum SimulationState { // 没启动 NOT_STARTED, // 等待玩家 WAITING_PLAYERS, // 运行中 RUNNING, // 暂停 PAUSED, // 结束 ENDED }AbstractLockstep 帧同步抽象基类 // 玩家初始容量 private const int INITIAL_PLAYERS_CAPACITY = 4; // 同步游戏开始编码 private const byte SYNCED_GAME_START_CODE = 196; // 模拟器编码 (0 暂停, 1 第一次启动, 3 End) private const byte SIMULATION_CODE = 197; // 验证码编码 private const byte CHECKSUM_CODE = 198; // 发送编码 private const byte SEND_CODE = 199; // 模拟器时间--暂停 private const byte SIMULATION_EVENT_PAUSE = 0; // 模拟器时间--运行 private const byte SIMULATION_EVENT_RUN = 1; // 模拟器时间--结束 private const byte SIMULATION_EVENT_END = 3; // 游戏结束前 最大等待所有玩家输入的帧 private const int MAX_PANIC_BEFORE_END_GAME = 5; // 同步信息 缓存窗口 private const int SYNCED_INFO_BUFFER_WINDOW = 3; // 添加玩家 // internal Dictionary<byte, TSPlayer> players; // internal ListactivePlayers; // this.localPlayer = tSPlayer; public void AddPlayer(byte playerId, string playerName, bool isLocal) // 更新在线的其他玩家ID列表 internal void UpdateActivePlayers() // 运行模拟器 // TrueSyncStats的Update里检测调 lockstep.RunSimulation(true); // CheckGameStart() 里调 RunSimulation(false); public void RunSimulation(bool firstRun) // 更新设置simulationState模拟器状态, 和调对应的回调(OnGameStarted, OnGameUnPaused) // RunSimulation() 里调 // private void OnEventDataReceived(byte eventCode, object content) 里调 private void Run() // 发送操作 // 调通信器 this.communicator.OpRaiseEvent(eventCode, message, reliable, toPlayers); private void RaiseEvent(byte eventCode, object message) private void RaiseEvent(byte eventCode, object message, bool reliable, int[] toPlayers) </pre> </ul> </div>