3 using System.Collections.Generic;
10 [AddComponentMenu(
"")]
13 private const float JitterTimeFactor = 0.001f;
15 private const string serverSettingsAssetFile =
"TrueSyncGlobalConfig";
17 private enum StartState { BEHAVIOR_INITIALIZED, FIRST_UPDATE, STARTED };
19 private StartState startState;
30 if (_TrueSyncGlobalConfig == null) {
34 return _TrueSyncGlobalConfig;
42 private Dictionary<int, List<GameObject>> gameOjectsSafeMap =
new Dictionary<int, List<GameObject>>();
47 private AbstractLockstep lockstep;
49 private FP lockedTimeStep;
54 private List<TrueSyncManagedBehaviour> generalBehaviours =
new List<TrueSyncManagedBehaviour>();
59 private Dictionary<byte, List<TrueSyncManagedBehaviour>> behaviorsByPlayer;
64 private CoroutineScheduler scheduler;
69 private List<TrueSyncManagedBehaviour> queuedBehaviours =
new List<TrueSyncManagedBehaviour>();
71 private Dictionary<ITrueSyncBehaviour, TrueSyncManagedBehaviour> mapBehaviorToManagedBehavior =
new Dictionary<ITrueSyncBehaviour, TrueSyncManagedBehaviour>();
80 if (instance == null) {
84 return instance.lockedTimeStep;
93 if (instance == null || instance.lockstep == null) {
106 if (instance == null || instance.lockstep == null) {
110 return instance.lockstep.Ticks;
119 if (instance == null || instance.lockstep == null) {
123 return instance.lockstep.LastSafeTick;
132 if (instance == null) {
133 return TSVector.zero;
145 if (instance == null || instance.lockstep == null) {
149 List<TSPlayerInfo> allPlayers =
new List<TSPlayerInfo>();
150 foreach (TSPlayer tsp
in instance.lockstep.Players.Values) {
152 allPlayers.Add(tsp.playerInfo);
165 if (instance == null || instance.lockstep == null) {
169 return instance.lockstep.LocalPlayer.playerInfo;
178 if (instance == null) {
182 return instance.ActiveConfig;
190 if (TrueSyncCustomConfig != null) {
191 customConfig = TrueSyncCustomConfig;
192 TrueSyncCustomConfig = null;
195 if (customConfig != null) {
199 return TrueSyncGlobalConfig;
216 StateTracker.AddTracking(
this,
"time");
221 Application.runInBackground =
true;
224 if (!PhotonNetwork.connected || !PhotonNetwork.inRoom) {
225 Debug.LogWarning(
"You are not connected to Photon. TrueSync will start in offline mode.");
232 lockstep = AbstractLockstep.NewInstance(
233 lockedTimeStep.AsFloat(),
243 OnPlayerDisconnection,
249 if (ReplayRecord.replayMode == ReplayMode.LOAD_REPLAY) {
250 ReplayPicker.replayToLoad.Load();
252 ReplayRecord replayRecord = ReplayRecord.replayToLoad;
253 if (replayRecord == null) {
254 Debug.LogError(
"Replay Record can't be loaded");
255 gameObject.SetActive(
false);
258 lockstep.ReplayRecord = replayRecord;
263 this.gameObject.AddComponent<TrueSyncStats>().Lockstep = lockstep;
266 scheduler =
new CoroutineScheduler(lockstep);
268 if (ReplayRecord.replayMode != ReplayMode.LOAD_REPLAY) {
269 if (communicator == null) {
270 lockstep.AddPlayer(0,
"Local_Player",
true);
272 List<PhotonPlayer> players =
new List<PhotonPlayer>(PhotonNetwork.playerList);
275 for (
int index = 0, length = players.Count; index < length; index++) {
276 PhotonPlayer p = players[index];
277 lockstep.AddPlayer((byte) p.ID, p.NickName, p.IsLocal);
283 for (
int index = 0, length = behavioursArray.Length; index < length; index++) {
284 generalBehaviours.Add(NewManagedBehavior(behavioursArray[index]));
288 initGeneralBehaviors(generalBehaviours,
false);
292 startState = StartState.BEHAVIOR_INITIALIZED;
295 private TrueSyncManagedBehaviour NewManagedBehavior(ITrueSyncBehaviour trueSyncBehavior) {
296 TrueSyncManagedBehaviour result =
new TrueSyncManagedBehaviour(trueSyncBehavior);
297 mapBehaviorToManagedBehavior[trueSyncBehavior] = result;
302 private void initBehaviors() {
303 behaviorsByPlayer =
new Dictionary<byte, List<TrueSyncManagedBehaviour>>();
305 var playersEnum = lockstep.Players.GetEnumerator();
306 while (playersEnum.MoveNext()) {
307 TSPlayer p = playersEnum.Current.Value;
309 List<TrueSyncManagedBehaviour> behaviorsInstatiated =
new List<TrueSyncManagedBehaviour>();
311 for (
int index = 0, length = playerPrefabs.Length; index < length; index++) {
312 GameObject prefab = playerPrefabs[index];
314 GameObject prefabInst = Instantiate(prefab);
315 InitializeGameObject(prefabInst, prefabInst.transform.position.ToTSVector(), prefabInst.transform.rotation.ToTSQuaternion());
318 for (
int index2 = 0, length2 = behaviours.Length; index2 < length2; index2++) {
321 behaviour.
owner = p.playerInfo;
322 behaviour.
localOwner = lockstep.LocalPlayer.playerInfo;
325 TrueSyncManagedBehaviour tsmb = NewManagedBehavior(behaviour);
326 tsmb.owner = behaviour.
owner;
329 behaviorsInstatiated.Add(tsmb);
333 behaviorsByPlayer.Add(p.ID, behaviorsInstatiated);
337 private void initGeneralBehaviors(IEnumerable<TrueSyncManagedBehaviour> behaviours,
bool realOwnerId) {
338 List<TSPlayer> playersList =
new List<TSPlayer>(lockstep.Players.Values);
339 List<TrueSyncManagedBehaviour> itemsToRemove =
new List<TrueSyncManagedBehaviour>();
341 var behavioursEnum = behaviours.GetEnumerator();
342 while (behavioursEnum.MoveNext()) {
343 TrueSyncManagedBehaviour tsmb = behavioursEnum.Current;
349 TrueSyncBehaviour bh = (TrueSyncBehaviour)tsmb.trueSyncBehavior;
359 if (behaviorsByPlayer.ContainsKey((byte)bh.
ownerIndex)) {
362 behaviorsByPlayer[(byte)bh.
ownerIndex].Add(tsmb);
363 itemsToRemove.Add(tsmb);
368 bh.
localOwner = lockstep.LocalPlayer.playerInfo;
371 tsmb.owner = bh.
owner;
375 for (
int index = 0, length = itemsToRemove.Count; index < length; index++) {
376 generalBehaviours.Remove(itemsToRemove[index]);
380 private void CheckQueuedBehaviours() {
381 if (queuedBehaviours.Count > 0) {
382 generalBehaviours.AddRange(queuedBehaviours);
383 initGeneralBehaviors(queuedBehaviours,
true);
385 for (
int index = 0, length = queuedBehaviours.Count; index < length; index++) {
386 TrueSyncManagedBehaviour tsmb = queuedBehaviours[index];
388 tsmb.SetGameInfo(lockstep.LocalPlayer.playerInfo, lockstep.Players.Count);
389 tsmb.OnSyncedStart();
392 queuedBehaviours.Clear();
397 if (lockstep != null && startState != StartState.STARTED) {
398 if (startState == StartState.BEHAVIOR_INITIALIZED) {
399 startState = StartState.FIRST_UPDATE;
400 }
else if (startState == StartState.FIRST_UPDATE) {
401 lockstep.RunSimulation(
true);
402 startState = StartState.STARTED;
411 if (instance != null && instance.lockstep != null) {
412 instance.lockstep.RunSimulation(
false);
420 if (instance != null && instance.lockstep != null) {
421 instance.lockstep.PauseSimulation();
429 if (instance != null && instance.lockstep != null) {
430 instance.lockstep.EndSimulation();
438 if (instance != null && instance.lockstep != null) {
439 instance.scheduler.UpdateAllCoroutines();
449 if (instance != null && instance.lockstep != null) {
450 instance.scheduler.StartCoroutine(coroutine);
460 return SyncedInstantiate(prefab, prefab.transform.position.ToTSVector(), prefab.transform.rotation.ToTSQuaternion());
470 public static GameObject
SyncedInstantiate(GameObject prefab, TSVector position, TSQuaternion rotation) {
471 if (instance != null && instance.lockstep != null) {
472 GameObject go = GameObject.Instantiate(prefab, position.ToVector(), rotation.ToQuaternion()) as GameObject;
474 if (ReplayRecord.replayMode != ReplayMode.LOAD_REPLAY) {
475 AddGameObjectOnSafeMap(go);
478 MonoBehaviour[] monoBehaviours = go.GetComponentsInChildren<MonoBehaviour>();
479 for (
int index = 0, length = monoBehaviours.Length; index < length; index++) {
480 MonoBehaviour bh = monoBehaviours[index];
482 if (bh is ITrueSyncBehaviour) {
483 instance.queuedBehaviours.Add(instance.NewManagedBehavior((ITrueSyncBehaviour)bh));
487 InitializeGameObject(go, position, rotation);
495 private static void AddGameObjectOnSafeMap(GameObject go) {
497 Dictionary<int, List<GameObject>> safeMap = instance.gameOjectsSafeMap;
500 if (!safeMap.ContainsKey(currentTick)) {
501 safeMap.Add(currentTick,
new List<GameObject>());
504 safeMap[currentTick].Add(go);
507 private static void CheckGameObjectsSafeMap() {
508 Dictionary<int, List<GameObject>> safeMap = instance.gameOjectsSafeMap;
511 if (safeMap.ContainsKey(currentTick)) {
512 List<GameObject> gos = safeMap[currentTick];
513 for (
int i = 0, l = gos.Count; i < l; i++) {
514 GameObject go = gos[i];
516 Renderer rend = go.GetComponent<Renderer>();
518 rend.enabled =
false;
521 GameObject.Destroy(go);
531 private static void InitializeGameObject(GameObject go, TSVector position, TSQuaternion rotation) {
532 ICollider[] tsColliders = go.GetComponentsInChildren<ICollider>();
533 if (tsColliders != null) {
534 for (
int index = 0, length = tsColliders.Length; index < length; index++) {
540 if (rootTSTransform != null) {
543 rootTSTransform.
position = position;
544 rootTSTransform.
rotation = rotation;
548 if (tsTransforms != null) {
549 for (
int index = 0, length = tsTransforms.Length; index < length; index++) {
552 if (tsTransform != rootTSTransform) {
559 if (rootTSTransform2D != null) {
562 rootTSTransform2D.
position =
new TSVector2(position.x, position.y);
563 rootTSTransform2D.
rotation = rotation.ToQuaternion().eulerAngles.z;
567 if (tsTransforms2D != null) {
568 for (
int index = 0, length = tsTransforms2D.Length; index < length; index++) {
571 if (tsTransform2D != rootTSTransform2D) {
585 public static GameObject
SyncedInstantiate(GameObject prefab, TSVector2 position, TSQuaternion rotation) {
586 return SyncedInstantiate(prefab,
new TSVector(position.x, position.y, 0), rotation);
597 if (instance != null && instance.lockstep != null) {
601 if (tsColliders != null) {
602 for (
int index = 0, length = tsColliders.Length; index < length; index++) {
604 DestroyTSRigidBody(tsCollider.gameObject, tsCollider.
Body);
609 if (tsColliders2D != null) {
610 for (
int index = 0, length = tsColliders2D.Length; index < length; index++) {
612 DestroyTSRigidBody(tsCollider2D.gameObject, tsCollider2D.
Body);
622 MonoBehaviour[] monoBehaviours = gameObject.GetComponentsInChildren<MonoBehaviour>();
624 for (
int index = 0, length = monoBehaviours.Length; index < length; index++) {
625 MonoBehaviour tsb = monoBehaviours[index];
627 if (tsb is ITrueSyncBehaviour && instance.mapBehaviorToManagedBehavior.ContainsKey((ITrueSyncBehaviour)tsb)) {
628 instance.mapBehaviorToManagedBehavior[(ITrueSyncBehaviour)tsb].disabled =
true;
638 private static void DestroyTSRigidBody(GameObject tsColliderGO,
IBody body) {
639 tsColliderGO.gameObject.SetActive(
false);
640 instance.lockstep.Destroy(body);
649 if (instance != null && instance.lockstep != null) {
650 instance.queuedBehaviours.Add(instance.NewManagedBehavior(trueSyncBehaviour));
660 if (instance != null && instance.lockstep != null) {
661 instance.lockstep.GameIsReady += IsReadyChecker;
671 if (instance != null && instance.lockstep != null) {
672 List<TrueSyncManagedBehaviour> behaviorsList = instance.behaviorsByPlayer[(byte)playerId];
674 for (
int index = 0, length = behaviorsList.Count; index < length; index++) {
675 TrueSyncManagedBehaviour tsmb = behaviorsList[index];
676 tsmb.disabled =
true;
679 if (tsColliders != null) {
680 for (
int index2 = 0, length2 = tsColliders.Length; index2 < length2; index2++) {
683 if (!tsCollider.
Body.TSDisabled) {
684 DestroyTSRigidBody(tsCollider.gameObject, tsCollider.
Body);
690 if (tsCollider2Ds != null) {
691 for (
int index2 = 0, length2 = tsCollider2Ds.Length; index2 < length2; index2++) {
694 if (!tsCollider2D.
Body.TSDisabled) {
695 DestroyTSRigidBody(tsCollider2D.gameObject, tsCollider2D.
Body);
703 private FP tsDeltaTime = 0;
706 if (lockstep != null) {
709 if (tsDeltaTime >= (lockedTimeStep - JitterTimeFactor)) {
712 instance.scheduler.UpdateAllCoroutines();
718 InputDataBase ProvideInputData() {
719 return new InputData();
722 void GetLocalData(InputDataBase playerInputData) {
723 TrueSyncInput.CurrentInputData = (InputData) playerInputData;
725 if (behaviorsByPlayer.ContainsKey(playerInputData.ownerID)) {
726 List<TrueSyncManagedBehaviour> managedBehavioursByPlayer = behaviorsByPlayer[playerInputData.ownerID];
727 for (
int index = 0, length = managedBehavioursByPlayer.Count; index < length; index++) {
728 TrueSyncManagedBehaviour bh = managedBehavioursByPlayer[index];
730 if (bh != null && !bh.disabled) {
736 TrueSyncInput.CurrentInputData = null;
739 void OnStepUpdate(List<InputDataBase> allInputData) {
740 time += lockedTimeStep;
742 if (ReplayRecord.replayMode != ReplayMode.LOAD_REPLAY) {
743 CheckGameObjectsSafeMap();
746 TrueSyncInput.SetAllInputs(null);
748 for (
int index = 0, length = generalBehaviours.Count; index < length; index++) {
749 TrueSyncManagedBehaviour bh = generalBehaviours[index];
751 if (bh != null && !bh.disabled) {
752 bh.OnPreSyncedUpdate();
753 instance.scheduler.UpdateAllCoroutines();
757 for (
int index = 0, length = allInputData.Count; index < length; index++) {
758 InputDataBase playerInputData = allInputData[index];
760 if (behaviorsByPlayer.ContainsKey(playerInputData.ownerID)) {
761 List<TrueSyncManagedBehaviour> managedBehavioursByPlayer = behaviorsByPlayer[playerInputData.ownerID];
762 for (
int index2 = 0, length2 = managedBehavioursByPlayer.Count; index2 < length2; index2++) {
763 TrueSyncManagedBehaviour bh = managedBehavioursByPlayer[index2];
765 if (bh != null && !bh.disabled) {
766 bh.OnPreSyncedUpdate();
767 instance.scheduler.UpdateAllCoroutines();
773 TrueSyncInput.SetAllInputs(allInputData);
775 TrueSyncInput.CurrentSimulationData = null;
776 for (
int index = 0, length = generalBehaviours.Count; index < length; index++) {
777 TrueSyncManagedBehaviour bh = generalBehaviours[index];
779 if (bh != null && !bh.disabled) {
781 instance.scheduler.UpdateAllCoroutines();
785 for (
int index = 0, length = allInputData.Count; index < length; index++) {
786 InputDataBase playerInputData = allInputData[index];
788 if (behaviorsByPlayer.ContainsKey(playerInputData.ownerID)) {
789 TrueSyncInput.CurrentSimulationData = (InputData) playerInputData;
791 List<TrueSyncManagedBehaviour> managedBehavioursByPlayer = behaviorsByPlayer[playerInputData.ownerID];
792 for (
int index2 = 0, length2 = managedBehavioursByPlayer.Count; index2 < length2; index2++) {
793 TrueSyncManagedBehaviour bh = managedBehavioursByPlayer[index2];
795 if (bh != null && !bh.disabled) {
797 instance.scheduler.UpdateAllCoroutines();
802 TrueSyncInput.CurrentSimulationData = null;
805 CheckQueuedBehaviours();
808 private void OnRemovedRigidBody(
IBody body) {
812 List<TrueSyncBehaviour> behavioursToRemove =
new List<TrueSyncBehaviour>(go.GetComponentsInChildren<
TrueSyncBehaviour>());
813 RemoveFromTSMBList(queuedBehaviours, behavioursToRemove);
814 RemoveFromTSMBList(generalBehaviours, behavioursToRemove);
816 var behaviorsByPlayerEnum = behaviorsByPlayer.GetEnumerator();
817 while (behaviorsByPlayerEnum.MoveNext()) {
818 List<TrueSyncManagedBehaviour> listBh = behaviorsByPlayerEnum.Current.Value;
819 RemoveFromTSMBList(listBh, behavioursToRemove);
824 private void RemoveFromTSMBList(List<TrueSyncManagedBehaviour> tsmbList, List<TrueSyncBehaviour> behaviours) {
825 List<TrueSyncManagedBehaviour> toRemove =
new List<TrueSyncManagedBehaviour>();
826 for (
int index = 0, length = tsmbList.Count; index < length; index++) {
827 TrueSyncManagedBehaviour tsmb = tsmbList[index];
829 if ((tsmb.trueSyncBehavior is
TrueSyncBehaviour) && behaviours.Contains((TrueSyncBehaviour)tsmb.trueSyncBehavior)) {
834 for (
int index = 0, length = toRemove.Count; index < length; index++) {
835 TrueSyncManagedBehaviour tsmb = toRemove[index];
836 tsmbList.Remove(tsmb);
844 ResourcePool.CleanUpAll();
845 StateTracker.CleanUp();
849 void OnPlayerDisconnection(byte playerId) {
850 TrueSyncManagedBehaviour.OnPlayerDisconnection(generalBehaviours, behaviorsByPlayer, playerId);
853 void OnGameStarted() {
854 TrueSyncManagedBehaviour.OnGameStarted(generalBehaviours, behaviorsByPlayer);
855 instance.scheduler.UpdateAllCoroutines();
857 CheckQueuedBehaviours();
860 void OnGamePaused() {
861 TrueSyncManagedBehaviour.OnGamePaused(generalBehaviours, behaviorsByPlayer);
862 instance.scheduler.UpdateAllCoroutines();
865 void OnGameUnPaused() {
866 TrueSyncManagedBehaviour.OnGameUnPaused(generalBehaviours, behaviorsByPlayer);
867 instance.scheduler.UpdateAllCoroutines();
871 TrueSyncManagedBehaviour.OnGameEnded(generalBehaviours, behaviorsByPlayer);
872 instance.scheduler.UpdateAllCoroutines();
875 void OnApplicationQuit() {
int rollbackWindow
Rollback window size.
Manages creation of player prefabs and lockstep execution.
TSPlayerInfo owner
Basic info about the owner of this behaviour.
TSPlayerInfo localOwner
Basic info about the local player.
static void UpdateCoroutines()
Update all coroutines created.
static void RegisterITrueSyncBehaviour(ITrueSyncBehaviour trueSyncBehaviour)
Registers an implementation of ITrueSyncBehaviour to be included in the simulation.
static List< TSPlayerInfo > Players
Returns the list of players connected.
Provides a few utilities to be used on TrueSync exposed classes.
GameObject[] playerPrefabs
Player prefabs to be instantiated in each machine.
static FP Time
Returns the time elapsed since the beginning of the simulation.
static void RemovePlayer(int playerId)
Removes objets related to a provided player.
static int LastSafeTick
Returns the last safe simulated tick.
static void RegisterIsReadyChecker(TrueSyncIsReady IsReadyChecker)
Register a TrueSyncIsReady delegate to that returns true if the game can proceed or false otherwise...
Represents each player's behaviour simulated on every machine connected to the game.
static IPhysicsManager instance
Returns a proper implementation of IPhysicsManager.
static TrueSyncConfig Config
Returns the active TrueSyncConfig used by the TrueSyncManager.
static PlayerComparer playerComparer
Instance of a PlayerComparer.
Truesync's ICommunicator implementation based on PUN.
bool physics3DEnabled
Indicates if the 3D physics engine should be enabled.
TSVector gravity3D
Represents the simulated gravity.
static TSPlayerInfo LocalPlayer
Returns the local player.
static IPhysicsManager New(TrueSyncConfig trueSyncConfig)
Instantiates a new IPhysicsManager.
static int Ticks
Returns the number of the last simulated tick.
Abstract collider for 3D shapes.
Represents a set of configurations for TrueSync.
static TSVector Gravity
Returns the simulated gravity.
static void SyncedStartCoroutine(IEnumerator coroutine)
Starts a new coroutine.
int numberOfPlayers
Number of players connected to the game.
int panicWindow
Maximum number of ticks to wait until all other players inputs arrive.
bool showStats
When true shows a debug interface with a few information.
FP lockedTimeStep
Time between each TrueSync's frame.
TrueSync's communicator interface.
Manages physics simulation.
static GameObject SyncedInstantiate(GameObject prefab)
Instantiate a new prefab in a deterministic way.
static void PauseSimulation()
Pauses the game simulation.
int ownerIndex
Index of the owner at initial players list.
static GameObject SyncedInstantiate(GameObject prefab, TSVector position, TSQuaternion rotation)
Instantiates a new prefab in a deterministic way.
IBody3D Body
Returns the body linked to this collider.
Abstract collider for 2D shapes.
static FP DeltaTime
Returns the deltaTime between two simulation calls.
static void RunSimulation()
Run/Unpause the game simulation.
bool physics2DEnabled
Indicates if the 2D physics engine should be enabled.
static void EndSimulation()
End the game simulation.
static void CleanUp()
Clean up references to be collected by gc.
int syncWindow
Synchronization window size.
IBody2D Body
Returns RigidBody associated to this TSRigidBody.
static void SyncedDisableBehaviour(GameObject gameObject)
Disables 'OnSyncedInput' and 'OnSyncUpdate' calls to every ITrueSyncBehaviour attached.
Represents a common interface to 2D and 3D bodies.
static void SyncedDestroy(GameObject gameObject)
Destroys a GameObject in a deterministic way.
static GameObject SyncedInstantiate(GameObject prefab, TSVector2 position, TSQuaternion rotation)
Instantiates a new prefab in a deterministic way.