Liberi
An exergame built for kids with CP!
All Classes Functions Variables Properties Events
Sync.cs
1 using UnityEngine;
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Diagnostics;
7 using Janus;
8 
9 public delegate void ObjectDespawnImminentHandler (GameObject go);
10 public delegate void ObjectDespawnedHandler (GameObject go);
11 
12 [AddComponentMenu("Liberi/Sync")]
13 public partial class Sync : MonoBehaviour
14 {
15  public event ObjectDespawnImminentHandler DespawnImminent;
16  public event ObjectDespawnedHandler Despawned;
17 
18  public Sync RootSync
19  {
20  get { return _rootSync; }
21  }
22 
23  public bool IsRoot
24  {
25  get { return _rootSync == this; }
26  }
27 
28  public int OwnerPeerIndex
29  {
30  get { return _rootSync._ownerPeerIndex; }
31  }
32 
33  public int ObjectIndex
34  {
35  get { return _rootSync._objectIndex; }
36  }
37 
38  public SpawnInfo SpawnInfo
39  {
40  get { return _rootSync._spawnInfo; }
41  }
42 
43  public TimelineBase[] Timelines
44  {
45  get { return _rootSync._timelines.ToArray(); }
46  }
47 
48  public TimelineBase[] EssentialTimelines
49  {
50  get { return _rootSync._essentialTimelines.ToArray(); }
51  }
52 
53  public bool SyncPosition = false;
54  public bool SyncDirection = false;
55  public bool SyncColliderState = false;
56  public bool SyncColliderSize = false;
57  public bool SyncAnimator = false;
58  public DirectionMode DirectionMode = DirectionMode.None;
59  public float PositionCorrectionStrength = 25f;
60  public float DirectionCorrectionStrength = 25f;
61  public float SetTime = 0f;
62  public float GetTime = 0f;
63  public float SendRate = 10f;
64  public bool DrawDebugGizmos;
65 
66  [TimelineTags("Essential")]
67  public Timeline<Vector3> Position;
68  [TimelineTags("Essential")]
69  public Timeline<float> Angle;
70  [TimelineTags("Essential")]
71  public Timeline<bool> Flipped;
72 
73  public Timeline<GameMessage> AnimationMessages;
74 
75  Timeline<GameMessage> _teleports;
76  Timeline<bool> _colliderState;
77  Timeline<Vector3> _colliderSize;
78 
79  MonoBehaviour[] _originalBehaviours;
80  SpawnInfo _spawnInfo;
81  int _objectIndex = -1;
82  int _ownerPeerIndex = -1;
83  List<TimelineBase> _timelines = new List<TimelineBase>();
84  List<TimelineBase> _essentialTimelines = new List<TimelineBase>();
85  Sync _rootSync;
86  float _errorTime;
87  bool _isWaitingForTeleportConfirmation;
88  BoxCollider _boxCollider;
89  SphereCollider _sphereCollider;
90  CapsuleCollider _capsuleCollider;
91  Animator _animator;
92 
93  private float GetSendRate ()
94  {
95  return SendRate;
96  }
97 
98  void Awake ()
99  {
100  _rootSync = this;
101 
102  while (_rootSync.transform.parent != null && _rootSync.transform.parent.GetComponent<Sync>() != null)
103  {
104  _rootSync = _rootSync.transform.parent.GetComponent<Sync>();
105  }
106 
107  if (this.IsRoot)
108  {
109  _originalBehaviours = GetComponentsInChildren<MonoBehaviour>().Where(mb => mb.enabled).ToArray();
110 
111  foreach (var mb in _originalBehaviours)
112  {
113  if (mb == this)
114  continue;
115 
116  mb.enabled = false;
117  }
118  }
119 
120  if (SyncPosition)
121  {
122  var positionSendFilter = TimelineUtils.BuildRateFilter<Vector3>(GetSendRate);
123 
124  Position = CreateContinuousState<Vector3>(positionSendFilter);
125 
126  _teleports = Sync.CreateEvent<GameMessage>();
127  }
128 
129  if (SyncDirection)
130  {
131  if (DirectionMode == DirectionMode.Radial)
132  {
133  var angleSendFilter = TimelineUtils.BuildRateFilter<float>(GetSendRate);
134 
135  Angle = CreateContinuousState<float>(angleSendFilter);
136  Angle.Interpolate = UTimelineUtils.InterpolateAngleSlerp;
137  }
138  else if (DirectionMode == DirectionMode.Flip)
139  {
140  Flipped = CreateDiscreteState<bool>();
141  }
142  }
143 
144  if (collider == null)
145  {
146  SyncColliderState = false;
147  SyncColliderSize = false;
148  }
149 
150  if (SyncColliderState)
151  {
152  _colliderState = CreateDiscreteState<bool>();
153  }
154 
155  if (SyncColliderSize)
156  {
157  if (collider is MeshCollider)
158  {
159  SyncColliderSize = false;
160  }
161  else
162  {
163  _boxCollider = GetComponent<BoxCollider>();
164  _sphereCollider = GetComponent<SphereCollider>();
165  _capsuleCollider = GetComponent<CapsuleCollider>();
166 
167  _colliderSize = CreateContinuousState<Vector3>();
168  _colliderSize.AddSendFilter(TimelineUtils.BuildInequalityFilter<Vector3>());
169  }
170  }
171 
172  if (SyncAnimator)
173  {
174  _animator = GetComponent<Animator>();
175  AnimationMessages = CreateDiscreteState<GameMessage>();
176  }
177  }
178 
179  void OnDrawGizmos ()
180  {
181  if (!DrawDebugGizmos)
182  return;
183 
184  if (Position != null)
185  {
186  if (Position.NumEntries >= 1)
187  {
188  Gizmos.color = new Color(0f, 0f, 1f, .75f);
189  Gizmos.DrawWireSphere(Position.PreviousValue, .1f);
190  }
191 
192  if (Position.NumEntries >= 2)
193  {
194  Gizmos.color = new Color(0f, 0f, 1f, .5f);
195  Gizmos.DrawWireSphere(Position.PreviousEntry.Prev.Value, .1f);
196  }
197 
198  Gizmos.color = Color.white;
199  }
200  }
201 
202  void OnSpawn ()
203  {
204  if (SyncPosition)
205  {
206  _teleports.EntryPassed += OnTeleportEvent;
207  }
208 
209  if (_rootSync._ownerPeerIndex == _localPeerIndex)
210  {
211  PushTransform();
212  PushCollider();
213  }
214  else
215  {
216  if (rigidbody != null)
217  {
218  this.rigidbody.useGravity = false;
219 
220  if (SyncPosition)
221  this.rigidbody.constraints |= RigidbodyConstraints.FreezePosition;
222 
223  if (SyncDirection)
224  {
225  if (DirectionMode == DirectionMode.Radial)
226  this.rigidbody.constraints |= RigidbodyConstraints.FreezeRotation;
227  else if (DirectionMode == DirectionMode.Flip)
228  {
229  Flipped.EntryPassed += OnFlippedChanged;
230  transform.SetFlipDirection(Flipped.LastValue ? Vector3.left : Vector3.right);
231  }
232  }
233  }
234 
235  PullTransform();
236  PullCollider();
237 
238  if (SyncColliderState)
239  _colliderState.EntryPassed += OnColliderStateChanged;
240  }
241 
242  if (SyncAnimator)
243  AnimationMessages.EntryPassed += OnAnimationMessagePassed;
244  }
245 
246  void PreDespawn ()
247  {
248  if (DespawnImminent != null)
249  DespawnImminent(gameObject);
250  }
251 
252  void PostDespawn ()
253  {
254  if (Despawned != null)
255  Despawned(gameObject);
256  }
257 
258  public Vector3 GetDirection ()
259  {
260  if (DirectionMode == DirectionMode.Flip)
261  return transform.GetFlipDirection();
262  else return transform.GetRadialDirection();
263  }
264 
265  public void SetDirection (Vector3 direction, bool ignoreRigidbody = false)
266  {
267  if (DirectionMode == DirectionMode.Flip)
268  {
269  transform.SetFlipDirection(direction);
270  }
271  else if (DirectionMode == DirectionMode.Radial)
272  {
273  if (!ignoreRigidbody && rigidbody != null)
274  rigidbody.SetRadialDirection(direction);
275  else transform.SetRadialDirection(direction);
276  }
277  }
278 
279  void Update ()
280  {
281  if (_rootSync._ownerPeerIndex == _localPeerIndex)
282  {
283  PushTransform();
284  PushCollider();
285  }
286  else
287  {
288  PullTransform();
289  PullCollider();
290  }
291  }
292 
293  private void PushTransform ()
294  {
295  if (SyncPosition)
296  {
297  Position[SetTime] = transform.position;
298  }
299 
300  if (SyncDirection)
301  {
302  if (DirectionMode == DirectionMode.Radial)
303  {
304  Angle[SetTime] = transform.rotation.eulerAngles.z;
305  }
306  else if (DirectionMode == DirectionMode.Flip)
307  {
308  Flipped[SetTime] = transform.localScale.x < 0;
309  }
310  }
311  }
312 
313  private void PullTransform ()
314  {
315  if (_isWaitingForTeleportConfirmation)
316  return;
317 
318  if (SyncPosition && !Position.IsEmpty)
319  {
320  var position = transform.position;
321  var targetPosition = Position[GetTime];
322  var sqrDistToTarget = (targetPosition - position).sqrMagnitude;
323  bool shouldSnap = false;
324 
325  if (sqrDistToTarget > Mathf.Pow(MaxSmoothDistance, 2))
326  {
327  shouldSnap = true;
328  }
329  else
330  {
331  if (sqrDistToTarget > Mathf.Pow(ErrorDistance, 2))
332  {
333  _errorTime += Time.deltaTime;
334  shouldSnap = _errorTime >= MaxErrorTime;
335  }
336  else if (sqrDistToTarget < Mathf.Pow(MinSmoothDistance, 2))
337  {
338  shouldSnap = true;
339  }
340  }
341 
342  if (shouldSnap)
343  {
344  transform.position = Position[GetTime];
345  _errorTime = 0f;
346  }
347  else
348  {
349  transform.position = Vector3.Lerp(position,
350  targetPosition, Mathf.Min(1f, PositionCorrectionStrength * Time.deltaTime));
351  }
352  }
353 
354  if (SyncDirection)
355  {
356  if (DirectionMode == DirectionMode.Radial && !Angle.IsEmpty)
357  {
358  var rotation = transform.rotation;
359  float currentAngle = rotation.eulerAngles.z;
360  float interpAngle = Mathf.LerpAngle(
361  currentAngle, Angle[GetTime], DirectionCorrectionStrength * Time.deltaTime);
362 
363  transform.rotation = Quaternion.Euler(0f, 0f, interpAngle);
364  }
365  }
366  }
367 
368  void PushCollider ()
369  {
370  if (SyncColliderState)
371  {
372  if (_colliderState.LastValue != collider.enabled)
373  _colliderState[0] = collider.enabled;
374  }
375 
376  if (SyncColliderSize)
377  {
378  if (_sphereCollider != null)
379  {
380  _colliderSize[SetTime] = new Vector3(_sphereCollider.radius, 0f, 0f);
381  }
382  else if (_capsuleCollider != null)
383  {
384  _colliderSize[SetTime] = new Vector3(_capsuleCollider.radius, _capsuleCollider.height, 0f);
385  }
386  else if (_boxCollider != null)
387  {
388  _colliderSize[SetTime] = _boxCollider.size;
389  }
390  }
391  }
392 
393  void PullCollider ()
394  {
395  if (SyncColliderSize && !_colliderSize.IsEmpty)
396  {
397  var size = _colliderSize[GetTime];
398 
399  if (_sphereCollider != null)
400  {
401  _sphereCollider.radius = size.x;
402  }
403  else if (_capsuleCollider != null)
404  {
405  _capsuleCollider.radius = size.x;
406  _capsuleCollider.height = size.y;
407  }
408  else if (_boxCollider != null)
409  {
410  _boxCollider.size = size;
411  }
412  }
413  }
414 
415  void OnFlippedChanged (Timeline<bool> timeline, TimelineEntry<bool> entry)
416  {
417  transform.SetFlipDirection(entry.Value ? Vector3.left : Vector3.right);
418  }
419 
420  void OnColliderStateChanged (Timeline<bool> timeline, TimelineEntry<bool> entry)
421  {
422  collider.enabled = entry.Value;
423  }
424 
425  void OnAnimationMessagePassed (Timeline<GameMessage> timeline, TimelineEntry<GameMessage> entry)
426  {
427  GameMessage msg = entry.Value;
428 
429  int stateNameHash = msg.ReadInt32();
430  float transitionDuration = msg.ReadFloat();
431  int layer = msg.ReadInt32();
432 
433  //TODO: normalizedTime using Janus time and entry instertion time.
434 
435  _animator.CrossFade(stateNameHash, transitionDuration, layer);
436  }
437 
438  void OnTeleportEvent (Timeline<GameMessage> timeline, TimelineEntry<GameMessage> entry)
439  {
440  var msg = entry.Value;
441 
442  bool isConfirmation = msg.ReadBoolean();
443  Vector3 target = msg.ReadVector3();
444  msg.FinishReading();
445 
446  transform.position = target;
447  Position.RemoveRange(Position.FirstTime, true, Position.LastTime, true, true);
448  Position.Insert(0f, target);
449 
450  if (isConfirmation)
451  {
452  Sync.SendEnabledMessage(this, "OnTeleport", target);
453 
454  if (_rootSync._ownerPeerIndex != _localPeerIndex)
455  _isWaitingForTeleportConfirmation = false;
456  }
457  else
458  {
459  if (_rootSync._ownerPeerIndex == _localPeerIndex)
460  {
461  var confirmMsg = new GameMessage();
462  confirmMsg.Write(true);
463  confirmMsg.Write(target);
464 
465  _teleports[0] = confirmMsg;
466  }
467  else _isWaitingForTeleportConfirmation = true;
468  }
469  }
470 }
static float MaxSmoothDistance
This is the distance between the current and correct positions of an object beyond which smooth corre...
Definition: Sync.Static.cs:110
static void SendEnabledMessage(GameObject go, string message, params object[] args)
Invokes a method on all enabled scripts in the GameObject.
Definition: Sync.Static.cs:740
void FinishReading()
Set the read / write position to 0 so the next user won't run over.
Definition: GameMessage.cs:19
static float MaxErrorTime
This is the length of time an object must be in the error state before a positional snap happens...
Definition: Sync.Static.cs:126
static float ErrorDistance
This is the distance between the current and correct positions of an object beyond which the position...
Definition: Sync.Static.cs:122
A class for serializing game data into a stream for propagation between objects and peers...
Definition: GameMessage.cs:14
static float MinSmoothDistance
This is the distance between the current and correct positions of an object below which smooth correc...
Definition: Sync.Static.cs:115
This class server two main functions: 1) As a MonoBehaviour, it allows for network synchronization of...
Definition: Sync.cs:13
The spawn-time information of a spawned object.
Definition: SpawnInfo.cs:13