Browse Source

Basic Networking done

warlock 3 years ago
parent
commit
891318680d
100 changed files with 6655 additions and 3 deletions
  1. 24 0
      Assembly-CSharp-Editor.csproj
  2. 1 3
      Assembly-CSharp.csproj
  3. 8 0
      Assets/Fonts.meta
  4. BIN
      Assets/Fonts/ARCADECLASSIC.TTF
  5. 22 0
      Assets/Fonts/ARCADECLASSIC.TTF.meta
  6. 8 0
      Assets/Ignorance.meta
  7. 8 0
      Assets/Ignorance/Demo.meta
  8. 8 0
      Assets/Ignorance/Demo/Basic.meta
  9. 737 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity
  10. 7 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity.meta
  11. 63 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting
  12. 8 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting.meta
  13. 8 0
      Assets/Ignorance/Demo/PongChamp.meta
  14. 188 0
      Assets/Ignorance/Demo/PongChamp/AtariBall.prefab
  15. 8 0
      Assets/Ignorance/Demo/PongChamp/AtariBall.prefab.meta
  16. 190 0
      Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab
  17. 8 0
      Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab.meta
  18. 11 0
      Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D
  19. 8 0
      Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D.meta
  20. 1164 0
      Assets/Ignorance/Demo/PongChamp/Demo.unity
  21. 7 0
      Assets/Ignorance/Demo/PongChamp/Demo.unity.meta
  22. 8 0
      Assets/Ignorance/Demo/PongChamp/Scripts.meta
  23. 67 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs
  24. 12 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs.meta
  25. 25 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs
  26. 12 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs.meta
  27. 49 0
      Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs
  28. 11 0
      Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs.meta
  29. 180 0
      Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab
  30. 7 0
      Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab.meta
  31. 8 0
      Assets/Ignorance/Demo/PongChamp/Textures.meta
  32. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png
  33. 128 0
      Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png.meta
  34. 8 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites.meta
  35. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png
  36. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png.meta
  37. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png
  38. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png.meta
  39. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png
  40. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png.meta
  41. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png
  42. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png.meta
  43. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png
  44. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png.meta
  45. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png
  46. 121 0
      Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png.meta
  47. 8 0
      Assets/Mirror.meta
  48. 8 0
      Assets/Mirror/Authenticators.meta
  49. 182 0
      Assets/Mirror/Authenticators/BasicAuthenticator.cs
  50. 11 0
      Assets/Mirror/Authenticators/BasicAuthenticator.cs.meta
  51. 14 0
      Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef
  52. 7 0
      Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef.meta
  53. 70 0
      Assets/Mirror/Authenticators/TimeoutAuthenticator.cs
  54. 11 0
      Assets/Mirror/Authenticators/TimeoutAuthenticator.cs.meta
  55. 8 0
      Assets/Mirror/CompilerSymbols.meta
  56. 14 0
      Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef
  57. 7 0
      Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef.meta
  58. 50 0
      Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs
  59. 11 0
      Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs.meta
  60. 8 0
      Assets/Mirror/Components.meta
  61. 9 0
      Assets/Mirror/Components/AssemblyInfo.cs
  62. 11 0
      Assets/Mirror/Components/AssemblyInfo.cs.meta
  63. 8 0
      Assets/Mirror/Components/Discovery.meta
  64. 114 0
      Assets/Mirror/Components/Discovery/NetworkDiscovery.cs
  65. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta
  66. 385 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs
  67. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta
  68. 132 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs
  69. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta
  70. 4 0
      Assets/Mirror/Components/Discovery/ServerRequest.cs
  71. 11 0
      Assets/Mirror/Components/Discovery/ServerRequest.cs.meta
  72. 18 0
      Assets/Mirror/Components/Discovery/ServerResponse.cs
  73. 11 0
      Assets/Mirror/Components/Discovery/ServerResponse.cs.meta
  74. 8 0
      Assets/Mirror/Components/Experimental.meta
  75. 93 0
      Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs
  76. 11 0
      Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs.meta
  77. 361 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody.cs
  78. 11 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody.cs.meta
  79. 360 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs
  80. 11 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs.meta
  81. 12 0
      Assets/Mirror/Components/Experimental/NetworkTransform.cs
  82. 11 0
      Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta
  83. 529 0
      Assets/Mirror/Components/Experimental/NetworkTransformBase.cs
  84. 11 0
      Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta
  85. 18 0
      Assets/Mirror/Components/Experimental/NetworkTransformChild.cs
  86. 11 0
      Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta
  87. 112 0
      Assets/Mirror/Components/GUIConsole.cs
  88. 11 0
      Assets/Mirror/Components/GUIConsole.cs.meta
  89. 8 0
      Assets/Mirror/Components/InterestManagement.meta
  90. 3 0
      Assets/Mirror/Components/InterestManagement/Distance.meta
  91. 69 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs
  92. 11 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs.meta
  93. 14 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs
  94. 11 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs.meta
  95. 3 0
      Assets/Mirror/Components/InterestManagement/Match.meta
  96. 125 0
      Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs
  97. 11 0
      Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs.meta
  98. 3 0
      Assets/Mirror/Components/InterestManagement/Scene.meta
  99. 109 0
      Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs
  100. 11 0
      Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs.meta

File diff suppressed because it is too large
+ 24 - 0
Assembly-CSharp-Editor.csproj


File diff suppressed because it is too large
+ 1 - 3
Assembly-CSharp.csproj


+ 8 - 0
Assets/Fonts.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 862a763e465d3529fa57c09973c89133
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Fonts/ARCADECLASSIC.TTF


+ 22 - 0
Assets/Fonts/ARCADECLASSIC.TTF.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: aa5beb46a7b830442a612d72f0278ded
+TrueTypeFontImporter:
+  externalObjects: {}
+  serializedVersion: 4
+  fontSize: 16
+  forceTextureCase: -2
+  characterSpacing: 0
+  characterPadding: 1
+  includeFontData: 1
+  fontName: ArcadeClassic
+  fontNames:
+  - ArcadeClassic
+  fallbackFontReferences: []
+  customCharacters: 
+  fontRenderingMode: 0
+  ascentCalculationMode: 1
+  useLegacyBoundsCalculation: 0
+  shouldRoundAdvanceValue: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5a7f75edb8db8494a856b560d2bce067
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0f3dc386b0074be4caf7b858e3189529
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo/Basic.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cd20cf8481e733f42a140665486354bd
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 737 - 0
Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity

@@ -0,0 +1,737 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 2
+  m_OcclusionBakeSettings:
+    smallestOccluder: 5
+    smallestHole: 0.25
+    backfaceThreshold: 100
+  m_SceneGUID: 00000000000000000000000000000000
+  m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 9
+  m_Fog: 0
+  m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+  m_FogMode: 3
+  m_FogDensity: 0.01
+  m_LinearFogStart: 0
+  m_LinearFogEnd: 300
+  m_AmbientSkyColor: {r: 0, g: 0, b: 0, a: 1}
+  m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+  m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+  m_AmbientIntensity: 1
+  m_AmbientMode: 3
+  m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+  m_SkyboxMaterial: {fileID: 0}
+  m_HaloStrength: 0.5
+  m_FlareStrength: 1
+  m_FlareFadeSpeed: 3
+  m_HaloTexture: {fileID: 0}
+  m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+  m_DefaultReflectionMode: 1
+  m_DefaultReflectionResolution: 128
+  m_ReflectionBounces: 1
+  m_ReflectionIntensity: 1
+  m_CustomReflection: {fileID: 0}
+  m_Sun: {fileID: 0}
+  m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
+  m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 12
+  m_GIWorkflowMode: 0
+  m_GISettings:
+    serializedVersion: 2
+    m_BounceScale: 1
+    m_IndirectOutputScale: 1
+    m_AlbedoBoost: 1
+    m_EnvironmentLightingMode: 0
+    m_EnableBakedLightmaps: 0
+    m_EnableRealtimeLightmaps: 0
+  m_LightmapEditorSettings:
+    serializedVersion: 12
+    m_Resolution: 2
+    m_BakeResolution: 40
+    m_AtlasSize: 1024
+    m_AO: 0
+    m_AOMaxDistance: 1
+    m_CompAOExponent: 1
+    m_CompAOExponentDirect: 0
+    m_ExtractAmbientOcclusion: 0
+    m_Padding: 2
+    m_LightmapParameters: {fileID: 0}
+    m_LightmapsBakeMode: 1
+    m_TextureCompression: 1
+    m_FinalGather: 0
+    m_FinalGatherFiltering: 1
+    m_FinalGatherRayCount: 256
+    m_ReflectionCompression: 2
+    m_MixedBakeMode: 2
+    m_BakeBackend: 1
+    m_PVRSampling: 1
+    m_PVRDirectSampleCount: 32
+    m_PVRSampleCount: 500
+    m_PVRBounces: 2
+    m_PVREnvironmentSampleCount: 500
+    m_PVREnvironmentReferencePointCount: 2048
+    m_PVRFilteringMode: 2
+    m_PVRDenoiserTypeDirect: 0
+    m_PVRDenoiserTypeIndirect: 0
+    m_PVRDenoiserTypeAO: 0
+    m_PVRFilterTypeDirect: 0
+    m_PVRFilterTypeIndirect: 0
+    m_PVRFilterTypeAO: 0
+    m_PVREnvironmentMIS: 0
+    m_PVRCulling: 1
+    m_PVRFilteringGaussRadiusDirect: 1
+    m_PVRFilteringGaussRadiusIndirect: 5
+    m_PVRFilteringGaussRadiusAO: 2
+    m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+    m_PVRFilteringAtrousPositionSigmaIndirect: 2
+    m_PVRFilteringAtrousPositionSigmaAO: 1
+    m_ExportTrainingData: 0
+    m_TrainingDataDestination: TrainingData
+    m_LightProbeSampleCountMultiplier: 4
+  m_LightingDataAsset: {fileID: 0}
+  m_LightingSettings: {fileID: 4890085278179872738, guid: f34de581ee6ab3743844c33f257a03c2,
+    type: 2}
+--- !u!196 &4
+NavMeshSettings:
+  serializedVersion: 2
+  m_ObjectHideFlags: 0
+  m_BuildSettings:
+    serializedVersion: 2
+    agentTypeID: 0
+    agentRadius: 0.5
+    agentHeight: 2
+    agentSlope: 45
+    agentClimb: 0.4
+    ledgeDropHeight: 0
+    maxJumpAcrossDistance: 0
+    minRegionArea: 2
+    manualCellSize: 0
+    cellSize: 0.16666667
+    manualTileSize: 0
+    tileSize: 256
+    accuratePlacement: 0
+    maxJobWorkers: 0
+    preserveTilesOutsideBounds: 0
+    debug:
+      m_Flags: 0
+  m_NavMeshData: {fileID: 0}
+--- !u!1 &249891953
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 249891957}
+  - component: {fileID: 249891954}
+  - component: {fileID: 249891956}
+  - component: {fileID: 249891955}
+  m_Layer: 0
+  m_Name: NetworkManager
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &249891954
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 249891953}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  offsetX: 0
+  offsetY: 0
+--- !u!114 &249891955
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 249891953}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 872fa23ef6e77334ca452ce16f6cd091, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  port: 7777
+  LogType: 1
+  DebugDisplay: 1
+  serverBindsAll: 1
+  serverBindAddress: 
+  serverMaxPeerCapacity: 64
+  serverMaxNativeWaitTime: 1
+  serverStatusUpdateInterval: 3
+  clientMaxNativeWaitTime: 1
+  clientStatusUpdateInterval: 3
+  Channels: 0100000002000000
+  ClientDataBufferSize: 1000
+  ClientConnEventBufferSize: 10
+  ServerDataBufferSize: 5000
+  ServerConnEventBufferSize: 100
+  PacketBufferCapacity: 4096
+  MaxAllowedPacketSize: 33554432
+--- !u!114 &249891956
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 249891953}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 20460c43f0320ed4baf8c1dcf953eafa, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  dontDestroyOnLoad: 0
+  runInBackground: 1
+  autoStartServerBuild: 1
+  serverTickRate: 30
+  offlineScene: 
+  onlineScene: 
+  transport: {fileID: 249891955}
+  networkAddress: localhost
+  maxConnections: 100
+  authenticator: {fileID: 0}
+  playerPrefab: {fileID: 897184729387425976, guid: dc2c4328591bef748abb8df795c17202,
+    type: 3}
+  autoCreatePlayer: 1
+  playerSpawnMethod: 1
+  spawnPrefabs: []
+  mainPanel: {fileID: 1712119861}
+  playersPanel: {fileID: 379082679}
+--- !u!4 &249891957
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 249891953}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: -10, y: 4, z: 5}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &288173824
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 288173827}
+  - component: {fileID: 288173826}
+  m_Layer: 0
+  m_Name: Main Camera
+  m_TagString: MainCamera
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!20 &288173826
+Camera:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 288173824}
+  m_Enabled: 1
+  serializedVersion: 2
+  m_ClearFlags: 2
+  m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0}
+  m_projectionMatrixMode: 1
+  m_GateFitMode: 2
+  m_FOVAxisMode: 0
+  m_SensorSize: {x: 36, y: 24}
+  m_LensShift: {x: 0, y: 0}
+  m_FocalLength: 50
+  m_NormalizedViewPortRect:
+    serializedVersion: 2
+    x: 0
+    y: 0
+    width: 1
+    height: 1
+  near clip plane: 0.3
+  far clip plane: 1000
+  field of view: 60
+  orthographic: 1
+  orthographic size: 5
+  m_Depth: -1
+  m_CullingMask:
+    serializedVersion: 2
+    m_Bits: 4294967295
+  m_RenderingPath: -1
+  m_TargetTexture: {fileID: 0}
+  m_TargetDisplay: 0
+  m_TargetEye: 3
+  m_HDR: 1
+  m_AllowMSAA: 1
+  m_AllowDynamicResolution: 0
+  m_ForceIntoRT: 0
+  m_OcclusionCulling: 1
+  m_StereoConvergence: 10
+  m_StereoSeparation: 0.022
+--- !u!4 &288173827
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 288173824}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 1, z: -1}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &379082678
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 379082679}
+  - component: {fileID: 379082681}
+  - component: {fileID: 379082682}
+  - component: {fileID: 379082680}
+  - component: {fileID: 379082683}
+  m_Layer: 5
+  m_Name: PlayersPanel
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &379082679
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 379082678}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 864730913}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 1, y: 1}
+  m_AnchoredPosition: {x: 0, y: 2.5}
+  m_SizeDelta: {x: -10, y: -15}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &379082680
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 379082678}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 8a8695521f0d02e499659fee002a26c2, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Padding:
+    m_Left: 5
+    m_Right: 5
+    m_Top: 5
+    m_Bottom: 5
+  m_ChildAlignment: 0
+  m_StartCorner: 0
+  m_StartAxis: 0
+  m_CellSize: {x: 120, y: 65}
+  m_Spacing: {x: 20, y: 20}
+  m_Constraint: 0
+  m_ConstraintCount: 2
+--- !u!222 &379082681
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 379082678}
+  m_CullTransparentMesh: 0
+--- !u!114 &379082682
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 379082678}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 0, g: 0, b: 0, a: 0.039215688}
+  m_RaycastTarget: 0
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 0}
+  m_Type: 0
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!114 &379082683
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 379082678}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_ShowMaskGraphic: 0
+--- !u!1 &533055200
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 533055204}
+  - component: {fileID: 533055203}
+  - component: {fileID: 533055202}
+  - component: {fileID: 533055201}
+  m_Layer: 5
+  m_Name: Canvas
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &533055201
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 533055200}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_IgnoreReversedGraphics: 1
+  m_BlockingObjects: 0
+  m_BlockingMask:
+    serializedVersion: 2
+    m_Bits: 4294967295
+--- !u!114 &533055202
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 533055200}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_UiScaleMode: 0
+  m_ReferencePixelsPerUnit: 100
+  m_ScaleFactor: 1
+  m_ReferenceResolution: {x: 800, y: 600}
+  m_ScreenMatchMode: 0
+  m_MatchWidthOrHeight: 0
+  m_PhysicalUnit: 3
+  m_FallbackScreenDPI: 96
+  m_DefaultSpriteDPI: 96
+  m_DynamicPixelsPerUnit: 1
+  m_PresetInfoIsWorld: 0
+--- !u!223 &533055203
+Canvas:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 533055200}
+  m_Enabled: 1
+  serializedVersion: 3
+  m_RenderMode: 0
+  m_Camera: {fileID: 0}
+  m_PlaneDistance: 100
+  m_PixelPerfect: 0
+  m_ReceivesEvents: 1
+  m_OverrideSorting: 0
+  m_OverridePixelPerfect: 0
+  m_SortingBucketNormalizedSize: 0
+  m_AdditionalShaderChannelsFlag: 0
+  m_SortingLayerID: 0
+  m_SortingOrder: 0
+  m_TargetDisplay: 0
+--- !u!224 &533055204
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 533055200}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 0, y: 0, z: 0}
+  m_Children:
+  - {fileID: 1712119861}
+  m_Father: {fileID: 0}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 0, y: 0}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0, y: 0}
+--- !u!1 &864730912
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 864730913}
+  - component: {fileID: 864730916}
+  - component: {fileID: 864730914}
+  m_Layer: 5
+  m_Name: BorderPanel
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!224 &864730913
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 864730912}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 379082679}
+  m_Father: {fileID: 1712119861}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 1, y: 1}
+  m_AnchoredPosition: {x: 0, y: -55}
+  m_SizeDelta: {x: -40, y: -150}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &864730914
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 864730912}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_RaycastTarget: 0
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0}
+  m_Type: 1
+  m_PreserveAspect: 0
+  m_FillCenter: 0
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!222 &864730916
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 864730912}
+  m_CullTransparentMesh: 0
+--- !u!1 &1356257340
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1356257343}
+  - component: {fileID: 1356257342}
+  - component: {fileID: 1356257341}
+  m_Layer: 0
+  m_Name: EventSystem
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &1356257341
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1356257340}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_HorizontalAxis: Horizontal
+  m_VerticalAxis: Vertical
+  m_SubmitButton: Submit
+  m_CancelButton: Cancel
+  m_InputActionsPerSecond: 10
+  m_RepeatDelay: 0.5
+  m_ForceModuleActive: 0
+--- !u!114 &1356257342
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1356257340}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_FirstSelected: {fileID: 0}
+  m_sendNavigationEvents: 1
+  m_DragThreshold: 10
+--- !u!4 &1356257343
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1356257340}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1712119860
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1712119861}
+  - component: {fileID: 1712119863}
+  - component: {fileID: 1712119862}
+  m_Layer: 5
+  m_Name: MainPanel
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 0
+--- !u!224 &1712119861
+RectTransform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1712119860}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 864730913}
+  m_Father: {fileID: 533055204}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+  m_AnchorMin: {x: 0, y: 0}
+  m_AnchorMax: {x: 1, y: 1}
+  m_AnchoredPosition: {x: 0, y: 0}
+  m_SizeDelta: {x: 0, y: 0}
+  m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1712119862
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1712119860}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_Material: {fileID: 0}
+  m_Color: {r: 0, g: 0, b: 0, a: 0}
+  m_RaycastTarget: 0
+  m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+  m_Maskable: 1
+  m_OnCullStateChanged:
+    m_PersistentCalls:
+      m_Calls: []
+  m_Sprite: {fileID: 0}
+  m_Type: 0
+  m_PreserveAspect: 0
+  m_FillCenter: 1
+  m_FillMethod: 4
+  m_FillAmount: 1
+  m_FillClockwise: 1
+  m_FillOrigin: 0
+  m_UseSpriteMesh: 0
+  m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1712119863
+CanvasRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1712119860}
+  m_CullTransparentMesh: 0

+ 7 - 0
Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 2db17f37f98c4cf4a88890358a4fa966
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 63 - 0
Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting

@@ -0,0 +1,63 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!850595691 &4890085278179872738
+LightingSettings:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: BasicWithIgnoranceSettings
+  serializedVersion: 3
+  m_GIWorkflowMode: 1
+  m_EnableBakedLightmaps: 0
+  m_EnableRealtimeLightmaps: 0
+  m_RealtimeEnvironmentLighting: 1
+  m_BounceScale: 1
+  m_AlbedoBoost: 1
+  m_IndirectOutputScale: 1
+  m_UsingShadowmask: 1
+  m_BakeBackend: 1
+  m_LightmapMaxSize: 1024
+  m_BakeResolution: 40
+  m_Padding: 2
+  m_TextureCompression: 1
+  m_AO: 0
+  m_AOMaxDistance: 1
+  m_CompAOExponent: 1
+  m_CompAOExponentDirect: 0
+  m_ExtractAO: 0
+  m_MixedBakeMode: 2
+  m_LightmapsBakeMode: 1
+  m_FilterMode: 1
+  m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0}
+  m_ExportTrainingData: 0
+  m_TrainingDataDestination: TrainingData
+  m_RealtimeResolution: 2
+  m_ForceWhiteAlbedo: 0
+  m_ForceUpdates: 0
+  m_FinalGather: 0
+  m_FinalGatherRayCount: 256
+  m_FinalGatherFiltering: 1
+  m_PVRCulling: 1
+  m_PVRSampling: 1
+  m_PVRDirectSampleCount: 32
+  m_PVRSampleCount: 500
+  m_PVREnvironmentSampleCount: 500
+  m_PVREnvironmentReferencePointCount: 2048
+  m_LightProbeSampleCountMultiplier: 4
+  m_PVRBounces: 2
+  m_PVRMinBounces: 2
+  m_PVREnvironmentMIS: 0
+  m_PVRFilteringMode: 2
+  m_PVRDenoiserTypeDirect: 0
+  m_PVRDenoiserTypeIndirect: 0
+  m_PVRDenoiserTypeAO: 0
+  m_PVRFilterTypeDirect: 0
+  m_PVRFilterTypeIndirect: 0
+  m_PVRFilterTypeAO: 0
+  m_PVRFilteringGaussRadiusDirect: 1
+  m_PVRFilteringGaussRadiusIndirect: 5
+  m_PVRFilteringGaussRadiusAO: 2
+  m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+  m_PVRFilteringAtrousPositionSigmaIndirect: 2
+  m_PVRFilteringAtrousPositionSigmaAO: 1

+ 8 - 0
Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f34de581ee6ab3743844c33f257a03c2
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 4890085278179872738
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo/PongChamp.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 634f781b33ac69147bff9edd5829cfd6
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 188 - 0
Assets/Ignorance/Demo/PongChamp/AtariBall.prefab

@@ -0,0 +1,188 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1080679924113744
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4700925592147096}
+  - component: {fileID: 212107498293566416}
+  - component: {fileID: 61279514624852186}
+  - component: {fileID: 50354248948880112}
+  - component: {fileID: 114290021321007948}
+  - component: {fileID: 2590138469697868697}
+  - component: {fileID: 114121325390084138}
+  m_Layer: 0
+  m_Name: AtariBall
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4700925592147096
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: -3, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!212 &212107498293566416
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: 4b66f21097323d44ab40669b2fb9c53d, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!61 &61279514624852186
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 6200000, guid: 97a3e4cddb8635c4eba1265f44d106bf, type: 2}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 1, y: 1}
+    newSize: {x: 1, y: 1}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 1, y: 1}
+  m_EdgeRadius: 0
+--- !u!50 &50354248948880112
+Rigidbody2D:
+  serializedVersion: 4
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_BodyType: 0
+  m_Simulated: 0
+  m_UseFullKinematicContacts: 0
+  m_UseAutoMass: 0
+  m_Mass: 0.0001
+  m_LinearDrag: 0
+  m_AngularDrag: 0.05
+  m_GravityScale: 0
+  m_Material: {fileID: 0}
+  m_Interpolate: 0
+  m_SleepingMode: 1
+  m_CollisionDetection: 0
+  m_Constraints: 4
+--- !u!114 &114290021321007948
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  sceneId: 0
+  serverOnly: 0
+  visible: 0
+  m_AssetId: 
+  hasSpawned: 0
+--- !u!114 &2590138469697868697
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 2a08d5ab1f59230458264367f00c54d8, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0.1
+  speed: 100
+--- !u!114 &114121325390084138
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1080679924113744}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0
+  clientAuthority: 0
+  localPositionSensitivity: 0.01
+  localRotationSensitivity: 0.01
+  localScaleSensitivity: 0.01
+  compressRotation: 1
+  interpolateScale: 0
+  syncScale: 0

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/AtariBall.prefab.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: da367a4c269be6d4a855ee1a9a68256b
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 100100000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 190 - 0
Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab

@@ -0,0 +1,190 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1240244544407914
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4118252415362944}
+  - component: {fileID: 212641192162007874}
+  - component: {fileID: 61279767645666242}
+  - component: {fileID: 50389918509199184}
+  - component: {fileID: 114104497298166850}
+  - component: {fileID: -4874889082967523790}
+  - component: {fileID: 114398896143473162}
+  m_Layer: 0
+  m_Name: AtariRacket
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4118252415362944
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!212 &212641192162007874
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: e11aad367beb44f4b8a3def9b8fc57ec, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 2, y: 4}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!61 &61279767645666242
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 2, y: 4}
+    newSize: {x: 2, y: 4}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 2, y: 4}
+  m_EdgeRadius: 0
+--- !u!50 &50389918509199184
+Rigidbody2D:
+  serializedVersion: 4
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_BodyType: 0
+  m_Simulated: 1
+  m_UseFullKinematicContacts: 0
+  m_UseAutoMass: 0
+  m_Mass: 1
+  m_LinearDrag: 0
+  m_AngularDrag: 0.05
+  m_GravityScale: 0
+  m_Material: {fileID: 0}
+  m_Interpolate: 1
+  m_SleepingMode: 1
+  m_CollisionDetection: 1
+  m_Constraints: 4
+--- !u!114 &114104497298166850
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  sceneId: 0
+  serverOnly: 0
+  visible: 0
+  m_AssetId: a85b24263182aae4bbc1829bb2b73d7a
+  hasSpawned: 0
+--- !u!114 &-4874889082967523790
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: b68eb08343b29a54dbd5ed7830fb9211, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0.1
+  speed: 1500
+--- !u!114 &114398896143473162
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1240244544407914}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0
+  clientAuthority: 1
+  localPositionSensitivity: 0.01
+  localRotationSensitivity: 0.01
+  localScaleSensitivity: 0.01
+  compressRotation: 1
+  interpolatePosition: 1
+  interpolateRotation: 1
+  interpolateScale: 0
+  syncScale: 0

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a85b24263182aae4bbc1829bb2b73d7a
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 100100000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 11 - 0
Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D

@@ -0,0 +1,11 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!62 &6200000
+PhysicsMaterial2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: BallMaterial 1
+  friction: 0
+  bounciness: 1

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 315510c0721e82f4cad07c2669b863fa
+timeCreated: 1426602119
+licenseType: Store
+NativeFormatImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1164 - 0
Assets/Ignorance/Demo/PongChamp/Demo.unity

@@ -0,0 +1,1164 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 2
+  m_OcclusionBakeSettings:
+    smallestOccluder: 5
+    smallestHole: 0.25
+    backfaceThreshold: 100
+  m_SceneGUID: 00000000000000000000000000000000
+  m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 9
+  m_Fog: 0
+  m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+  m_FogMode: 3
+  m_FogDensity: 0.01
+  m_LinearFogStart: 0
+  m_LinearFogEnd: 300
+  m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+  m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+  m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+  m_AmbientIntensity: 1
+  m_AmbientMode: 3
+  m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+  m_SkyboxMaterial: {fileID: 0}
+  m_HaloStrength: 0.5
+  m_FlareStrength: 1
+  m_FlareFadeSpeed: 3
+  m_HaloTexture: {fileID: 0}
+  m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+  m_DefaultReflectionMode: 0
+  m_DefaultReflectionResolution: 128
+  m_ReflectionBounces: 1
+  m_ReflectionIntensity: 1
+  m_CustomReflection: {fileID: 0}
+  m_Sun: {fileID: 0}
+  m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
+  m_UseRadianceAmbientProbe: 0
+--- !u!157 &4
+LightmapSettings:
+  m_ObjectHideFlags: 0
+  serializedVersion: 11
+  m_GIWorkflowMode: 0
+  m_GISettings:
+    serializedVersion: 2
+    m_BounceScale: 1
+    m_IndirectOutputScale: 1
+    m_AlbedoBoost: 1
+    m_EnvironmentLightingMode: 0
+    m_EnableBakedLightmaps: 0
+    m_EnableRealtimeLightmaps: 0
+  m_LightmapEditorSettings:
+    serializedVersion: 12
+    m_Resolution: 2
+    m_BakeResolution: 40
+    m_AtlasSize: 1024
+    m_AO: 0
+    m_AOMaxDistance: 1
+    m_CompAOExponent: 0
+    m_CompAOExponentDirect: 0
+    m_ExtractAmbientOcclusion: 0
+    m_Padding: 2
+    m_LightmapParameters: {fileID: 0}
+    m_LightmapsBakeMode: 1
+    m_TextureCompression: 1
+    m_FinalGather: 0
+    m_FinalGatherFiltering: 1
+    m_FinalGatherRayCount: 1024
+    m_ReflectionCompression: 2
+    m_MixedBakeMode: 1
+    m_BakeBackend: 0
+    m_PVRSampling: 1
+    m_PVRDirectSampleCount: 32
+    m_PVRSampleCount: 500
+    m_PVRBounces: 2
+    m_PVREnvironmentSampleCount: 500
+    m_PVREnvironmentReferencePointCount: 2048
+    m_PVRFilteringMode: 0
+    m_PVRDenoiserTypeDirect: 0
+    m_PVRDenoiserTypeIndirect: 0
+    m_PVRDenoiserTypeAO: 0
+    m_PVRFilterTypeDirect: 0
+    m_PVRFilterTypeIndirect: 0
+    m_PVRFilterTypeAO: 0
+    m_PVREnvironmentMIS: 0
+    m_PVRCulling: 1
+    m_PVRFilteringGaussRadiusDirect: 1
+    m_PVRFilteringGaussRadiusIndirect: 5
+    m_PVRFilteringGaussRadiusAO: 2
+    m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+    m_PVRFilteringAtrousPositionSigmaIndirect: 2
+    m_PVRFilteringAtrousPositionSigmaAO: 1
+    m_ExportTrainingData: 0
+    m_TrainingDataDestination: TrainingData
+    m_LightProbeSampleCountMultiplier: 4
+  m_LightingDataAsset: {fileID: 0}
+  m_UseShadowmask: 0
+--- !u!196 &5
+NavMeshSettings:
+  serializedVersion: 2
+  m_ObjectHideFlags: 0
+  m_BuildSettings:
+    serializedVersion: 2
+    agentTypeID: 0
+    agentRadius: 0.5
+    agentHeight: 2
+    agentSlope: 45
+    agentClimb: 0.4
+    ledgeDropHeight: 0
+    maxJumpAcrossDistance: 0
+    minRegionArea: 2
+    manualCellSize: 0
+    cellSize: 0.16666667
+    manualTileSize: 0
+    tileSize: 256
+    accuratePlacement: 0
+    debug:
+      m_Flags: 0
+  m_NavMeshData: {fileID: 0}
+--- !u!1 &289876230
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 289876232}
+  - component: {fileID: 289876231}
+  m_Layer: 0
+  m_Name: DottedLine
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!212 &289876231
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 289876230}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 4294967295
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: 75254f20e37ff3640beccde38f796fed, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!4 &289876232
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 289876230}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1607538195}
+  m_RootOrder: 4
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &473997959
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 473997961}
+  - component: {fileID: 473997960}
+  m_Layer: 0
+  m_Name: RacketSpawnLeft
+  m_TagString: Untagged
+  m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &473997960
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 473997959}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+--- !u!4 &473997961
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 473997959}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: -20, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 5
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &753891880
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 753891882}
+  - component: {fileID: 753891881}
+  - component: {fileID: 753891883}
+  m_Layer: 0
+  m_Name: WallBottom
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!212 &753891881
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 753891880}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 4294967295
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: 51f6a08479c829a49b6a15df6849e727, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!4 &753891882
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 753891880}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: -16, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1607538195}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!61 &753891883
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 753891880}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 50, y: 1}
+    newSize: {x: 1, y: 1}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 50, y: 1}
+  m_EdgeRadius: 0
+--- !u!1 &1212086918
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1212086921}
+  - component: {fileID: 1212086920}
+  - component: {fileID: 1212086919}
+  m_Layer: 0
+  m_Name: EventSystem
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &1212086919
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1212086918}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_HorizontalAxis: Horizontal
+  m_VerticalAxis: Vertical
+  m_SubmitButton: Submit
+  m_CancelButton: Cancel
+  m_InputActionsPerSecond: 10
+  m_RepeatDelay: 0.5
+  m_ForceModuleActive: 0
+--- !u!114 &1212086920
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1212086918}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  m_FirstSelected: {fileID: 0}
+  m_sendNavigationEvents: 1
+  m_DragThreshold: 10
+--- !u!4 &1212086921
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1212086918}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 7
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1278075347
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1278075350}
+  - component: {fileID: 1278075348}
+  - component: {fileID: 1278075349}
+  m_Layer: 0
+  m_Name: Timer
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &1278075348
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278075347}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: d0996ec573094c24890a4d4233ee871e, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0.1
+--- !u!114 &1278075349
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278075347}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  sceneId: 3073769009
+  serverOnly: 0
+  visible: 0
+  m_AssetId: 
+  hasSpawned: 0
+--- !u!4 &1278075350
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1278075347}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1346799726
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1346799731}
+  - component: {fileID: 1346799730}
+  - component: {fileID: 1346799728}
+  - component: {fileID: 1346799727}
+  m_Layer: 0
+  m_Name: Main Camera
+  m_TagString: MainCamera
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!81 &1346799727
+AudioListener:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1346799726}
+  m_Enabled: 1
+--- !u!124 &1346799728
+Behaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1346799726}
+  m_Enabled: 1
+--- !u!20 &1346799730
+Camera:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1346799726}
+  m_Enabled: 1
+  serializedVersion: 2
+  m_ClearFlags: 1
+  m_BackGroundColor: {r: 0, g: 0, b: 0, a: 0.019607844}
+  m_projectionMatrixMode: 1
+  m_GateFitMode: 2
+  m_FOVAxisMode: 0
+  m_SensorSize: {x: 36, y: 24}
+  m_LensShift: {x: 0, y: 0}
+  m_FocalLength: 50
+  m_NormalizedViewPortRect:
+    serializedVersion: 2
+    x: 0
+    y: 0
+    width: 1
+    height: 1
+  near clip plane: 0.3
+  far clip plane: 1000
+  field of view: 60
+  orthographic: 1
+  orthographic size: 40
+  m_Depth: -1
+  m_CullingMask:
+    serializedVersion: 2
+    m_Bits: 4294967295
+  m_RenderingPath: -1
+  m_TargetTexture: {fileID: 0}
+  m_TargetDisplay: 0
+  m_TargetEye: 3
+  m_HDR: 0
+  m_AllowMSAA: 1
+  m_AllowDynamicResolution: 0
+  m_ForceIntoRT: 0
+  m_OcclusionCulling: 1
+  m_StereoConvergence: 10
+  m_StereoSeparation: 0.022
+--- !u!4 &1346799731
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1346799726}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: -10}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1352350029
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1352350031}
+  - component: {fileID: 1352350030}
+  - component: {fileID: 1352350032}
+  m_Layer: 0
+  m_Name: WallLeft
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!212 &1352350030
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1352350029}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 4294967295
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: b41679787bea72d419d10a6df7dc137f, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!4 &1352350031
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1352350029}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -24.5, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1607538195}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!61 &1352350032
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1352350029}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 1, y: 32}
+    newSize: {x: 1, y: 1}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 1, y: 32}
+  m_EdgeRadius: 0
+--- !u!1 &1368547944
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1368547946}
+  - component: {fileID: 1368547945}
+  - component: {fileID: 1368547947}
+  m_Layer: 0
+  m_Name: WallTop
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!212 &1368547945
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1368547944}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 4294967295
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: 51f6a08479c829a49b6a15df6849e727, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!4 &1368547946
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1368547944}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 16, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1607538195}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!61 &1368547947
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1368547944}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 50, y: 1}
+    newSize: {x: 1, y: 1}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 50, y: 1}
+  m_EdgeRadius: 0
+--- !u!1 &1397990094
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1397990096}
+  - component: {fileID: 1397990095}
+  m_Layer: 0
+  m_Name: RacketSpawnRight
+  m_TagString: Untagged
+  m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!114 &1397990095
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1397990094}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+--- !u!4 &1397990096
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1397990094}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 20, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 6
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1575697329
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1575697331}
+  - component: {fileID: 1575697330}
+  - component: {fileID: 1575697332}
+  m_Layer: 0
+  m_Name: WallRight
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!212 &1575697330
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1575697329}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 4294967295
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: b41679787bea72d419d10a6df7dc137f, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!4 &1575697331
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1575697329}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 24.5, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1607538195}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!61 &1575697332
+BoxCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1575697329}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  m_SpriteTilingProperty:
+    border: {x: 0, y: 0, z: 0, w: 0}
+    pivot: {x: 0.5, y: 0.5}
+    oldSize: {x: 1, y: 32}
+    newSize: {x: 1, y: 1}
+    adaptiveTilingThreshold: 0.5
+    drawMode: 0
+    adaptiveTiling: 0
+  m_AutoTiling: 0
+  serializedVersion: 2
+  m_Size: {x: 1, y: 32}
+  m_EdgeRadius: 0
+--- !u!1 &1607538194
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1607538195}
+  m_Layer: 0
+  m_Name: Table
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &1607538195
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1607538194}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 1368547946}
+  - {fileID: 753891882}
+  - {fileID: 1575697331}
+  - {fileID: 1352350031}
+  - {fileID: 289876232}
+  m_Father: {fileID: 0}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1886246549
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1886246550}
+  - component: {fileID: 1886246552}
+  - component: {fileID: 1886246551}
+  - component: {fileID: 1886246553}
+  m_Layer: 0
+  m_Name: NetworkManager
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &1886246550
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1886246549}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1886246551
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1886246549}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 6442dc8070ceb41f094e44de0bf87274, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  showGUI: 1
+  offsetX: 0
+  offsetY: 0
+--- !u!114 &1886246552
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1886246549}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 8aab4c8111b7c411b9b92cf3dbc5bd4e, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  dontDestroyOnLoad: 1
+  PersistNetworkManagerToOfflineScene: 0
+  runInBackground: 1
+  autoStartServerBuild: 1
+  serverTickRate: 60
+  offlineScene: 
+  onlineScene: 
+  transport: {fileID: 1886246553}
+  networkAddress: localhost
+  maxConnections: 4
+  disconnectInactiveConnections: 0
+  disconnectInactiveTimeout: 60
+  authenticator: {fileID: 0}
+  playerPrefab: {fileID: 1240244544407914, guid: a85b24263182aae4bbc1829bb2b73d7a,
+    type: 3}
+  autoCreatePlayer: 1
+  playerSpawnMethod: 1
+  spawnPrefabs: []
+--- !u!114 &1886246553
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1886246549}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 872fa23ef6e77334ca452ce16f6cd091, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  port: 7777
+  LogType: 1
+  DebugDisplay: 1
+  serverBindsAll: 1
+  serverBindAddress: 
+  serverMaxPeerCapacity: 50
+  serverMaxNativeWaitTime: 1
+  clientMaxNativeWaitTime: 3
+  clientStatusUpdateInterval: 3
+  Channels: 0100000002000000
+  PacketBufferCapacity: 4096
+  MaxAllowedPacketSize: 33554432
+--- !u!1001 &543632697568170294
+PrefabInstance:
+  m_ObjectHideFlags: 0
+  serializedVersion: 2
+  m_Modification:
+    m_TransformParent: {fileID: 0}
+    m_Modifications:
+    - target: {fileID: 440601958556739609, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: sceneId
+      value: 1882393603
+      objectReference: {fileID: 0}
+    - target: {fileID: 440601958556739609, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_AssetId
+      value: 5339554c46006dd4e91cae9ff34c095d
+      objectReference: {fileID: 0}
+    - target: {fileID: 542835289192032773, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_Name
+      value: Ball
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_RootOrder
+      value: 4
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalPosition.x
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalPosition.y
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalPosition.z
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalRotation.w
+      value: 1
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalRotation.x
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalRotation.y
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalRotation.z
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalEulerAnglesHint.x
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalEulerAnglesHint.y
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 548291784780898253, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: m_LocalEulerAnglesHint.z
+      value: 0
+      objectReference: {fileID: 0}
+    - target: {fileID: 5995301680282788937, guid: 5339554c46006dd4e91cae9ff34c095d,
+        type: 3}
+      propertyPath: speed
+      value: 60
+      objectReference: {fileID: 0}
+    m_RemovedComponents: []
+  m_SourcePrefab: {fileID: 100100000, guid: 5339554c46006dd4e91cae9ff34c095d, type: 3}

+ 7 - 0
Assets/Ignorance/Demo/PongChamp/Demo.unity.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c63e1eea85874ae43a7fdd723a38741e
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/Scripts.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e2898db03f0b9654498df19a98dc9503
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 67 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs

@@ -0,0 +1,67 @@
+using UnityEngine;
+using Mirror;
+
+namespace Ignorance.Examples.PongChamp
+{
+    public class AtariPongBall : NetworkBehaviour
+    {
+        public float speed = 100;
+        private Rigidbody2D rigidbody2d;
+
+        private void Awake()
+        {
+            rigidbody2d = GetComponent<Rigidbody2D>();
+        }
+
+        public override void OnStartServer()
+        {
+            base.OnStartServer();
+
+            // only simulate ball physics on server
+            rigidbody2d.simulated = true;
+
+            // Serve the ball from left player
+            rigidbody2d.velocity = Vector2.right * speed;
+        }
+
+        float HitFactor(Vector2 ballPos, Vector2 racketPos, float racketHeight)
+        {
+            // ascii art:
+            // ||  1 <- at the top of the racket
+            // ||
+            // ||  0 <- at the middle of the racket
+            // ||
+            // || -1 <- at the bottom of the racket
+            return (ballPos.y - racketPos.y) / racketHeight;
+        }
+
+        // only call this on server
+        [ServerCallback]
+        void OnCollisionEnter2D(Collision2D col)
+        {
+            // Note: 'col' holds the collision information. If the
+            // Ball collided with a racket, then:
+            //   col.gameObject is the racket
+            //   col.transform.position is the racket's position
+            //   col.collider is the racket's collider
+
+            // did we hit a racket? then we need to calculate the hit factor
+            if (col.transform.GetComponent<AtariPongBall>())
+            {
+                // Calculate y direction via hit Factor
+                float y = HitFactor(transform.position,
+                                    col.transform.position,
+                                    col.collider.bounds.size.y);
+
+                // Calculate x direction via opposite collision
+                float x = col.relativeVelocity.x > 0 ? 1 : -1;
+
+                // Calculate direction, make length=1 via .normalized
+                Vector2 dir = new Vector2(x, y).normalized;
+
+                // Set Velocity with dir * speed
+                rigidbody2d.velocity = dir * speed;
+            }
+        }
+    }
+}

+ 12 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2a08d5ab1f59230458264367f00c54d8
+timeCreated: 1426602353
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 25 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs

@@ -0,0 +1,25 @@
+using UnityEngine;
+using Mirror;
+
+namespace Ignorance.Examples.PongChamp
+{
+    public class AtariPongRacket : NetworkBehaviour
+    {
+        public float speed = 1500;
+        private Rigidbody2D rigidbody2d;
+
+        private void Awake()
+        {
+            rigidbody2d = GetComponent<Rigidbody2D>();
+        }
+
+        // need to use FixedUpdate for rigidbody
+        void FixedUpdate()
+        {
+            // only let the local player control the racket.
+            // don't control other player's rackets
+            if (isLocalPlayer)
+                rigidbody2d.velocity = new Vector2(0, Input.GetAxisRaw("Vertical")) * speed * Time.fixedDeltaTime;
+        }
+    }
+}

+ 12 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: b68eb08343b29a54dbd5ed7830fb9211
+timeCreated: 1426597826
+licenseType: Store
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs

@@ -0,0 +1,49 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Mirror;
+using System.Diagnostics;
+using Debug = UnityEngine.Debug;
+
+public class OnlineTimer : NetworkBehaviour
+{
+    private Stopwatch stopwatch;
+
+    // Start is called before the first frame update
+    private void Awake()
+    {
+        stopwatch = new Stopwatch();
+    }
+
+    public override void OnStartClient()
+    {
+        stopwatch.Reset();
+        stopwatch.Start();
+
+        Debug.Log("Stopwatch started!");
+
+       base.OnStartClient();
+    }
+
+    public void OnDisable()
+    {
+        if(stopwatch.IsRunning)
+        {
+            System.TimeSpan ts = stopwatch.Elapsed;
+            stopwatch.Stop();
+
+            Debug.Log("Stopwatch stopped: duration " + string.Format("{0:00}:{1:00}:{2:00}.{3:00}",
+            ts.Hours, ts.Minutes, ts.Seconds,
+            ts.Milliseconds / 10));
+        }
+    }
+
+    private void OnGUI()
+    {
+        if (!stopwatch.IsRunning) return;
+
+        GUI.Box(new Rect(new Vector2(2, Screen.height - 36), new Vector2(320, 32)), "ONLINE TIME: " + string.Format("{0:00}:{1:00}:{2:00}.{3:00}",
+            stopwatch.Elapsed.Hours, stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds,
+            stopwatch.Elapsed.Milliseconds / 10));
+    }
+}

+ 11 - 0
Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d0996ec573094c24890a4d4233ee871e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 180 - 0
Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab

@@ -0,0 +1,180 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &542835289192032773
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 548291784780898253}
+  - component: {fileID: 394858453892616325}
+  - component: {fileID: 520653813702449573}
+  - component: {fileID: 440601958556739609}
+  - component: {fileID: 440842259441939327}
+  - component: {fileID: 8881572407762724401}
+  - component: {fileID: 5995301680282788937}
+  m_Layer: 0
+  m_Name: TenryuuBall
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &548291784780898253
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: -3, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!212 &394858453892616325
+SpriteRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_Enabled: 1
+  m_CastShadows: 0
+  m_ReceiveShadows: 0
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 0
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_Sprite: {fileID: 21300000, guid: 824ee62c0fc357b4081855e43253a948, type: 3}
+  m_Color: {r: 1, g: 1, b: 1, a: 1}
+  m_FlipX: 0
+  m_FlipY: 0
+  m_DrawMode: 0
+  m_Size: {x: 1, y: 1}
+  m_AdaptiveModeThreshold: 0.5
+  m_SpriteTileMode: 0
+  m_WasSpriteAssigned: 1
+  m_MaskInteraction: 0
+  m_SpriteSortPoint: 0
+--- !u!50 &520653813702449573
+Rigidbody2D:
+  serializedVersion: 4
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_BodyType: 0
+  m_Simulated: 1
+  m_UseFullKinematicContacts: 0
+  m_UseAutoMass: 0
+  m_Mass: 0.0001
+  m_LinearDrag: 0
+  m_AngularDrag: 0
+  m_GravityScale: 0
+  m_Material: {fileID: 6200000, guid: 315510c0721e82f4cad07c2669b863fa, type: 2}
+  m_Interpolate: 2
+  m_SleepingMode: 1
+  m_CollisionDetection: 1
+  m_Constraints: 4
+--- !u!114 &440601958556739609
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  sceneId: 0
+  serverOnly: 0
+  visible: 0
+  m_AssetId: 5339554c46006dd4e91cae9ff34c095d
+  hasSpawned: 0
+--- !u!114 &440842259441939327
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0.1
+  clientAuthority: 0
+  localPositionSensitivity: 0.01
+  localRotationSensitivity: 0.01
+  localScaleSensitivity: 0.01
+  compressRotation: 0
+  interpolatePosition: 1
+  interpolateRotation: 1
+  interpolateScale: 1
+  syncScale: 1
+--- !u!58 &8881572407762724401
+CircleCollider2D:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_Enabled: 1
+  m_Density: 1
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_UsedByEffector: 0
+  m_UsedByComposite: 0
+  m_Offset: {x: 0, y: 0}
+  serializedVersion: 2
+  m_Radius: 1.28
+--- !u!114 &5995301680282788937
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 542835289192032773}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 2a08d5ab1f59230458264367f00c54d8, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  syncMode: 0
+  syncInterval: 0.1
+  speed: 70

+ 7 - 0
Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5339554c46006dd4e91cae9ff34c095d
+PrefabImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/Textures.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 23b5913b9b8b2e1419a41acde1b49883
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png


+ 128 - 0
Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png.meta

@@ -0,0 +1,128 @@
+fileFormatVersion: 2
+guid: 824ee62c0fc357b4081855e43253a948
+TextureImporter:
+  internalIDToNameTable: []
+  externalObjects: {}
+  serializedVersion: 11
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: 1
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: -1
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: -1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 100
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  applyGammaDecoding: 0
+  platformSettings:
+  - serializedVersion: 3
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+    forceMaximumCompressionQuality_BC6H_BC7: 0
+  - serializedVersion: 3
+    buildTarget: Standalone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+    forceMaximumCompressionQuality_BC6H_BC7: 0
+  - serializedVersion: 3
+    buildTarget: iPhone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+    forceMaximumCompressionQuality_BC6H_BC7: 0
+  - serializedVersion: 3
+    buildTarget: Android
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+    forceMaximumCompressionQuality_BC6H_BC7: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 5e97eb03825dee720800000000000000
+    internalID: 0
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+    secondaryTextures: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0dbfc247338e79a4dbe7474f69b46503
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png


+ 88 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png.meta

@@ -0,0 +1,88 @@
+fileFormatVersion: 2
+guid: 03167da12fa50334e8c8641a23df41b0
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: 16
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 1
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: c5a291323e0d5f34883a55625f66ca70
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png


+ 88 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png.meta

@@ -0,0 +1,88 @@
+fileFormatVersion: 2
+guid: 75254f20e37ff3640beccde38f796fed
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: 16
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 1
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 98b4e2aa86aa3d843821adfe71dbbac0
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png


+ 88 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png.meta

@@ -0,0 +1,88 @@
+fileFormatVersion: 2
+guid: e11aad367beb44f4b8a3def9b8fc57ec
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: 16
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 1
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 09819c66a21defd49b2cfc87fea685d2
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png


+ 88 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png.meta

@@ -0,0 +1,88 @@
+fileFormatVersion: 2
+guid: 51f6a08479c829a49b6a15df6849e727
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: 16
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 1
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 74c5541eed52f67428025c83260d8bec
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png


+ 88 - 0
Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png.meta

@@ -0,0 +1,88 @@
+fileFormatVersion: 2
+guid: b41679787bea72d419d10a6df7dc137f
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: 0
+    aniso: 16
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 1
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 0
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 3a92f998f14389948aa928ac64e8e426
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png


+ 121 - 0
Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png.meta

@@ -0,0 +1,121 @@
+fileFormatVersion: 2
+guid: 160d53f9f2b149c418d6d07e95438e53
+TextureImporter:
+  fileIDToRecycleName: {}
+  externalObjects: {}
+  serializedVersion: 9
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    sRGBTexture: 1
+    linearTexture: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapsPreserveCoverage: 0
+    alphaTestReferenceValue: 0.5
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: 0.25
+    normalMapFilter: 0
+  isReadable: 0
+  streamingMipmaps: 0
+  streamingMipmapsPriority: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 6
+  cubemapConvolution: 0
+  seamlessCubemap: 0
+  textureFormat: 1
+  maxTextureSize: 2048
+  textureSettings:
+    serializedVersion: 2
+    filterMode: -1
+    aniso: -1
+    mipBias: -100
+    wrapU: 1
+    wrapV: 1
+    wrapW: -1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 1
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: 0.5, y: 0.5}
+  spritePixelsToUnits: 100
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spriteGenerateFallbackPhysicsShape: 1
+  alphaUsage: 1
+  alphaIsTransparency: 1
+  spriteTessellationDetail: -1
+  textureType: 8
+  textureShape: 1
+  singleChannelComponent: 0
+  maxTextureSizeSet: 0
+  compressionQualitySet: 0
+  textureFormatSet: 0
+  platformSettings:
+  - serializedVersion: 2
+    buildTarget: DefaultTexturePlatform
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  - serializedVersion: 2
+    buildTarget: Standalone
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  - serializedVersion: 2
+    buildTarget: Android
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  - serializedVersion: 2
+    buildTarget: WebGL
+    maxTextureSize: 2048
+    resizeAlgorithm: 0
+    textureFormat: -1
+    textureCompression: 1
+    compressionQuality: 50
+    crunchedCompression: 0
+    allowsAlphaSplitting: 0
+    overridden: 0
+    androidETC2FallbackOverride: 0
+  spriteSheet:
+    serializedVersion: 2
+    sprites: []
+    outline: []
+    physicsShape: []
+    bones: []
+    spriteID: 1a75587224e4a5f468c03dbd1af78d0b
+    vertices: []
+    indices: 
+    edges: []
+    weights: []
+  spritePackingTag: 
+  pSDRemoveMatte: 0
+  pSDShowRemoveMatteOption: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5cf8eb36be0834b3da408c694a41cb88
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Authenticators.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1b2f9d254154cd942ba40b06b869b8f3
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 182 - 0
Assets/Mirror/Authenticators/BasicAuthenticator.cs

@@ -0,0 +1,182 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+namespace Mirror.Authenticators
+{
+    [AddComponentMenu("Network/Authenticators/BasicAuthenticator")]
+    public class BasicAuthenticator : NetworkAuthenticator
+    {
+        [Header("Custom Properties")]
+
+        // set these in the inspector
+        public string username;
+        public string password;
+
+        #region Messages
+
+        public struct AuthRequestMessage : NetworkMessage
+        {
+            // use whatever credentials make sense for your game
+            // for example, you might want to pass the accessToken if using oauth
+            public string authUsername;
+            public string authPassword;
+        }
+
+        public struct AuthResponseMessage : NetworkMessage
+        {
+            public byte code;
+            public string message;
+        }
+
+        #endregion
+
+        #region Server
+
+        /// <summary>
+        /// Called on server from StartServer to initialize the Authenticator
+        /// <para>Server message handlers should be registered in this method.</para>
+        /// </summary>
+        public override void OnStartServer()
+        {
+            // register a handler for the authentication request we expect from client
+            NetworkServer.RegisterHandler<AuthRequestMessage>(OnAuthRequestMessage, false);
+        }
+
+        /// <summary>
+        /// Called on server from StopServer to reset the Authenticator
+        /// <para>Server message handlers should be registered in this method.</para>
+        /// </summary>
+        public override void OnStopServer()
+        {
+            // unregister the handler for the authentication request
+            NetworkServer.UnregisterHandler<AuthRequestMessage>();
+        }
+
+        /// <summary>
+        /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate
+        /// </summary>
+        /// <param name="conn">Connection to client.</param>
+        public override void OnServerAuthenticate(NetworkConnection conn)
+        {
+            // do nothing...wait for AuthRequestMessage from client
+        }
+
+        /// <summary>
+        /// Called on server when the client's AuthRequestMessage arrives
+        /// </summary>
+        /// <param name="conn">Connection to client.</param>
+        /// <param name="msg">The message payload</param>
+        public void OnAuthRequestMessage(NetworkConnection conn, AuthRequestMessage msg)
+        {
+            // Debug.LogFormat(LogType.Log, "Authentication Request: {0} {1}", msg.authUsername, msg.authPassword);
+
+            // check the credentials by calling your web server, database table, playfab api, or any method appropriate.
+            if (msg.authUsername == username && msg.authPassword == password)
+            {
+                // create and send msg to client so it knows to proceed
+                AuthResponseMessage authResponseMessage = new AuthResponseMessage
+                {
+                    code = 100,
+                    message = "Success"
+                };
+
+                conn.Send(authResponseMessage);
+
+                // Accept the successful authentication
+                ServerAccept(conn);
+            }
+            else
+            {
+                // create and send msg to client so it knows to disconnect
+                AuthResponseMessage authResponseMessage = new AuthResponseMessage
+                {
+                    code = 200,
+                    message = "Invalid Credentials"
+                };
+
+                conn.Send(authResponseMessage);
+
+                // must set NetworkConnection isAuthenticated = false
+                conn.isAuthenticated = false;
+
+                // disconnect the client after 1 second so that response message gets delivered
+                StartCoroutine(DelayedDisconnect(conn, 1));
+            }
+        }
+
+        IEnumerator DelayedDisconnect(NetworkConnection conn, float waitTime)
+        {
+            yield return new WaitForSeconds(waitTime);
+
+            // Reject the unsuccessful authentication
+            ServerReject(conn);
+        }
+
+        #endregion
+
+        #region Client
+
+        /// <summary>
+        /// Called on client from StartClient to initialize the Authenticator
+        /// <para>Client message handlers should be registered in this method.</para>
+        /// </summary>
+        public override void OnStartClient()
+        {
+            // register a handler for the authentication response we expect from server
+            NetworkClient.RegisterHandler<AuthResponseMessage>((Action<AuthResponseMessage>)OnAuthResponseMessage, false);
+        }
+
+        /// <summary>
+        /// Called on client from StopClient to reset the Authenticator
+        /// <para>Client message handlers should be unregistered in this method.</para>
+        /// </summary>
+        public override void OnStopClient()
+        {
+            // unregister the handler for the authentication response
+            NetworkClient.UnregisterHandler<AuthResponseMessage>();
+        }
+
+        /// <summary>
+        /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate
+        /// </summary>
+        public override void OnClientAuthenticate()
+        {
+            AuthRequestMessage authRequestMessage = new AuthRequestMessage
+            {
+                authUsername = username,
+                authPassword = password
+            };
+
+            NetworkClient.connection.Send(authRequestMessage);
+        }
+
+        // Deprecated 2021-04-29
+        [Obsolete("Call OnAuthResponseMessage without the NetworkConnection parameter. It always points to NetworkClient.connection anyway.")]
+        public void OnAuthResponseMessage(NetworkConnection conn, AuthResponseMessage msg) => OnAuthResponseMessage(msg);
+
+        /// <summary>
+        /// Called on client when the server's AuthResponseMessage arrives
+        /// </summary>
+        /// <param name="msg">The message payload</param>
+        public void OnAuthResponseMessage(AuthResponseMessage msg)
+        {
+            if (msg.code == 100)
+            {
+                // Debug.LogFormat(LogType.Log, "Authentication Response: {0}", msg.message);
+
+                // Authentication has been accepted
+                ClientAccept();
+            }
+            else
+            {
+                Debug.LogError($"Authentication Response: {msg.message}");
+
+                // Authentication has been rejected
+                ClientReject();
+            }
+        }
+
+        #endregion
+    }
+}

+ 11 - 0
Assets/Mirror/Authenticators/BasicAuthenticator.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 28496b776660156428f00cf78289c1ec
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 14 - 0
Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef

@@ -0,0 +1,14 @@
+{
+    "name": "Mirror.Authenticators",
+    "references": [
+        "Mirror"
+    ],
+    "optionalUnityReferences": [],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": []
+}

+ 7 - 0
Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e720aa64e3f58fb4880566a322584340
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 70 - 0
Assets/Mirror/Authenticators/TimeoutAuthenticator.cs

@@ -0,0 +1,70 @@
+using System.Collections;
+using UnityEngine;
+
+namespace Mirror.Authenticators
+{
+    /// <summary>
+    /// An authenticator that disconnects connections if they don't
+    /// authenticate within a specified time limit.
+    /// </summary>
+    [AddComponentMenu("Network/Authenticators/TimeoutAuthenticator")]
+    public class TimeoutAuthenticator : NetworkAuthenticator
+    {
+        public NetworkAuthenticator authenticator;
+
+        [Range(0, 600), Tooltip("Timeout to auto-disconnect in seconds. Set to 0 for no timeout.")]
+        public float timeout = 60;
+
+        public void Awake()
+        {
+            authenticator.OnServerAuthenticated.AddListener(connection => OnServerAuthenticated.Invoke(connection));
+            authenticator.OnClientAuthenticated.AddListener(connection => OnClientAuthenticated.Invoke(connection));
+        }
+
+        public override void OnStartServer()
+        {
+            authenticator.OnStartServer();
+        }
+
+        public override void OnStopServer()
+        {
+            authenticator.OnStopServer();
+        }
+
+        public override void OnStartClient()
+        {
+            authenticator.OnStartClient();
+        }
+
+        public override void OnStopClient()
+        {
+            authenticator.OnStopClient();
+        }
+
+        public override void OnServerAuthenticate(NetworkConnection conn)
+        {
+            authenticator.OnServerAuthenticate(conn);
+            if (timeout > 0)
+                StartCoroutine(BeginAuthentication(conn));
+        }
+
+        public override void OnClientAuthenticate()
+        {
+            authenticator.OnClientAuthenticate();
+            if (timeout > 0)
+                StartCoroutine(BeginAuthentication(NetworkClient.connection));
+        }
+
+        IEnumerator BeginAuthentication(NetworkConnection conn)
+        {
+            // Debug.Log($"Authentication countdown started {conn} {timeout}");
+            yield return new WaitForSecondsRealtime(timeout);
+
+            if (!conn.isAuthenticated)
+            {
+                // Debug.Log($"Authentication Timeout {conn}");
+                conn.Disconnect();
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Authenticators/TimeoutAuthenticator.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 24d8269a07b8e4edfa374753a91c946e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/CompilerSymbols.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1f8b918bcd89f5c488b06f5574f34760
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 14 - 0
Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef

@@ -0,0 +1,14 @@
+{
+    "name": "Mirror.CompilerSymbols",
+    "references": [],
+    "optionalUnityReferences": [],
+    "includePlatforms": [
+        "Editor"
+    ],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": []
+}

+ 7 - 0
Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 325984b52e4128546bc7558552f8b1d2
+AssemblyDefinitionImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 50 - 0
Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs

@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace Mirror
+{
+    static class PreprocessorDefine
+    {
+        /// <summary>
+        /// Add define symbols as soon as Unity gets done compiling.
+        /// </summary>
+        [InitializeOnLoadMethod]
+        public static void AddDefineSymbols()
+        {
+            string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
+            HashSet<string> defines = new HashSet<string>(currentDefines.Split(';'))
+            {
+                "MIRROR",
+                "MIRROR_17_0_OR_NEWER",
+                "MIRROR_18_0_OR_NEWER",
+                "MIRROR_24_0_OR_NEWER",
+                "MIRROR_26_0_OR_NEWER",
+                "MIRROR_27_0_OR_NEWER",
+                "MIRROR_28_0_OR_NEWER",
+                "MIRROR_29_0_OR_NEWER",
+                "MIRROR_30_0_OR_NEWER",
+                "MIRROR_30_5_2_OR_NEWER",
+                "MIRROR_32_1_2_OR_NEWER",
+                "MIRROR_32_1_4_OR_NEWER",
+                "MIRROR_35_0_OR_NEWER",
+                "MIRROR_35_1_OR_NEWER",
+                "MIRROR_37_0_OR_NEWER",
+                "MIRROR_38_0_OR_NEWER",
+                "MIRROR_39_0_OR_NEWER",
+                "MIRROR_40_0_OR_NEWER",
+                "MIRROR_41_0_OR_NEWER",
+                "MIRROR_42_0_OR_NEWER",
+                "MIRROR_43_0_OR_NEWER",
+                "MIRROR_44_0_OR_NEWER"
+            };
+
+            // only touch PlayerSettings if we actually modified it.
+            // otherwise it shows up as changed in git each time.
+            string newDefines = string.Join(";", defines);
+            if (newDefines != currentDefines)
+            {
+                PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, newDefines);
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f1d66fe74ec6f42dd974cba37d25d453
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Components.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Assets/Mirror/Components/AssemblyInfo.cs

@@ -0,0 +1,9 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Mirror.Tests.Common")]
+[assembly: InternalsVisibleTo("Mirror.Tests")]
+[assembly: InternalsVisibleTo("Mirror.Tests.Generated")]
+[assembly: InternalsVisibleTo("Mirror.Tests.Runtime")]
+[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Editor")]
+[assembly: InternalsVisibleTo("Mirror.Tests.Performance.Runtime")]
+[assembly: InternalsVisibleTo("Mirror.Editor")]

+ 11 - 0
Assets/Mirror/Components/AssemblyInfo.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a65b9283f7a724e70b8e17cb277f4c1e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Components/Discovery.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b5dcf9618f5e14a4eb60bff5480284a6
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 114 - 0
Assets/Mirror/Components/Discovery/NetworkDiscovery.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Net;
+using UnityEngine;
+using UnityEngine.Events;
+
+namespace Mirror.Discovery
+{
+    [Serializable]
+    public class ServerFoundUnityEvent : UnityEvent<ServerResponse> {};
+
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/NetworkDiscovery")]
+    public class NetworkDiscovery : NetworkDiscoveryBase<ServerRequest, ServerResponse>
+    {
+        #region Server
+
+        public long ServerId { get; private set; }
+
+        [Tooltip("Transport to be advertised during discovery")]
+        public Transport transport;
+
+        [Tooltip("Invoked when a server is found")]
+        public ServerFoundUnityEvent OnServerFound;
+
+        public override void Start()
+        {
+            ServerId = RandomLong();
+
+            // active transport gets initialized in awake
+            // so make sure we set it here in Start()  (after awakes)
+            // Or just let the user assign it in the inspector
+            if (transport == null)
+                transport = Transport.activeTransport;
+
+            base.Start();
+        }
+
+        /// <summary>
+        /// Process the request from a client
+        /// </summary>
+        /// <remarks>
+        /// Override if you wish to provide more information to the clients
+        /// such as the name of the host player
+        /// </remarks>
+        /// <param name="request">Request coming from client</param>
+        /// <param name="endpoint">Address of the client that sent the request</param>
+        /// <returns>The message to be sent back to the client or null</returns>
+        protected override ServerResponse ProcessRequest(ServerRequest request, IPEndPoint endpoint)
+        {
+            // In this case we don't do anything with the request
+            // but other discovery implementations might want to use the data
+            // in there,  This way the client can ask for
+            // specific game mode or something
+
+            try
+            {
+                // this is an example reply message,  return your own
+                // to include whatever is relevant for your game
+                return new ServerResponse
+                {
+                    serverId = ServerId,
+                    uri = transport.ServerUri()
+                };
+            }
+            catch (NotImplementedException)
+            {
+                Debug.LogError($"Transport {transport} does not support network discovery");
+                throw;
+            }
+        }
+
+        #endregion
+
+        #region Client
+
+        /// <summary>
+        /// Create a message that will be broadcasted on the network to discover servers
+        /// </summary>
+        /// <remarks>
+        /// Override if you wish to include additional data in the discovery message
+        /// such as desired game mode, language, difficulty, etc... </remarks>
+        /// <returns>An instance of ServerRequest with data to be broadcasted</returns>
+        protected override ServerRequest GetRequest() => new ServerRequest();
+
+        /// <summary>
+        /// Process the answer from a server
+        /// </summary>
+        /// <remarks>
+        /// A client receives a reply from a server, this method processes the
+        /// reply and raises an event
+        /// </remarks>
+        /// <param name="response">Response that came from the server</param>
+        /// <param name="endpoint">Address of the server that replied</param>
+        protected override void ProcessResponse(ServerResponse response, IPEndPoint endpoint)
+        {
+            // we received a message from the remote endpoint
+            response.EndPoint = endpoint;
+
+            // although we got a supposedly valid url, we may not be able to resolve
+            // the provided host
+            // However we know the real ip address of the server because we just
+            // received a packet from it,  so use that as host.
+            UriBuilder realUri = new UriBuilder(response.uri)
+            {
+                Host = response.EndPoint.Address.ToString()
+            };
+            response.uri = realUri.Uri;
+
+            OnServerFound.Invoke(response);
+        }
+
+        #endregion
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c761308e733c51245b2e8bb4201f46dc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 385 - 0
Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs

@@ -0,0 +1,385 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using UnityEngine;
+
+// Based on https://github.com/EnlightenedOne/MirrorNetworkDiscovery
+// forked from https://github.com/in0finite/MirrorNetworkDiscovery
+// Both are MIT Licensed
+
+namespace Mirror.Discovery
+{
+    /// <summary>
+    /// Base implementation for Network Discovery.  Extend this component
+    /// to provide custom discovery with game specific data
+    /// <see cref="NetworkDiscovery">NetworkDiscovery</see> for a sample implementation
+    /// </summary>
+    [DisallowMultipleComponent]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-discovery")]
+    public abstract class NetworkDiscoveryBase<Request, Response> : MonoBehaviour
+        where Request : NetworkMessage
+        where Response : NetworkMessage
+    {
+        public static bool SupportedOnThisPlatform { get { return Application.platform != RuntimePlatform.WebGLPlayer; } }
+
+        // each game should have a random unique handshake,  this way you can tell if this is the same game or not
+        [HideInInspector]
+        public long secretHandshake;
+
+        [SerializeField]
+        [Tooltip("The UDP port the server will listen for multi-cast messages")]
+        protected int serverBroadcastListenPort = 47777;
+
+        [SerializeField]
+        [Tooltip("If true, broadcasts a discovery request every ActiveDiscoveryInterval seconds")]
+        public bool enableActiveDiscovery = true;
+
+        [SerializeField]
+        [Tooltip("Time in seconds between multi-cast messages")]
+        [Range(1, 60)]
+        float ActiveDiscoveryInterval = 3;
+
+        protected UdpClient serverUdpClient;
+        protected UdpClient clientUdpClient;
+
+#if UNITY_EDITOR
+        void OnValidate()
+        {
+            if (secretHandshake == 0)
+            {
+                secretHandshake = RandomLong();
+                UnityEditor.Undo.RecordObject(this, "Set secret handshake");
+            }
+        }
+#endif
+
+        public static long RandomLong()
+        {
+            int value1 = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
+            int value2 = UnityEngine.Random.Range(int.MinValue, int.MaxValue);
+            return value1 + ((long)value2 << 32);
+        }
+
+        /// <summary>
+        /// virtual so that inheriting classes' Start() can call base.Start() too
+        /// </summary>
+        public virtual void Start()
+        {
+            // Server mode? then start advertising
+#if UNITY_SERVER
+            AdvertiseServer();
+#endif
+        }
+
+        // Ensure the ports are cleared no matter when Game/Unity UI exits
+        void OnApplicationQuit()
+        {
+            //Debug.Log("NetworkDiscoveryBase OnApplicationQuit");
+            Shutdown();
+        }
+
+        void OnDisable()
+        {
+            //Debug.Log("NetworkDiscoveryBase OnDisable");
+            Shutdown();
+        }
+
+        void OnDestroy()
+        {
+            //Debug.Log("NetworkDiscoveryBase OnDestroy");
+            Shutdown();
+        }
+
+        void Shutdown()
+        {
+            if (serverUdpClient != null)
+            {
+                try
+                {
+                    serverUdpClient.Close();
+                }
+                catch (Exception)
+                {
+                    // it is just close, swallow the error
+                }
+
+                serverUdpClient = null;
+            }
+
+            if (clientUdpClient != null)
+            {
+                try
+                {
+                    clientUdpClient.Close();
+                }
+                catch (Exception)
+                {
+                    // it is just close, swallow the error
+                }
+
+                clientUdpClient = null;
+            }
+
+            CancelInvoke();
+        }
+
+        #region Server
+
+        /// <summary>
+        /// Advertise this server in the local network
+        /// </summary>
+        public void AdvertiseServer()
+        {
+            if (!SupportedOnThisPlatform)
+                throw new PlatformNotSupportedException("Network discovery not supported in this platform");
+
+            StopDiscovery();
+
+            // Setup port -- may throw exception
+            serverUdpClient = new UdpClient(serverBroadcastListenPort)
+            {
+                EnableBroadcast = true,
+                MulticastLoopback = false
+            };
+
+            // listen for client pings
+            _ = ServerListenAsync();
+        }
+
+        public async Task ServerListenAsync()
+        {
+            while (true)
+            {
+                try
+                {
+                    await ReceiveRequestAsync(serverUdpClient);
+                }
+                catch (ObjectDisposedException)
+                {
+                    // socket has been closed
+                    break;
+                }
+                catch (Exception)
+                {
+                }
+            }
+        }
+
+        async Task ReceiveRequestAsync(UdpClient udpClient)
+        {
+            // only proceed if there is available data in network buffer, or otherwise Receive() will block
+            // average time for UdpClient.Available : 10 us
+
+            UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
+
+            using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
+            {
+                long handshake = networkReader.ReadLong();
+                if (handshake != secretHandshake)
+                {
+                    // message is not for us
+                    throw new ProtocolViolationException("Invalid handshake");
+                }
+
+                Request request = networkReader.Read<Request>();
+
+                ProcessClientRequest(request, udpReceiveResult.RemoteEndPoint);
+            }
+        }
+
+        /// <summary>
+        /// Reply to the client to inform it of this server
+        /// </summary>
+        /// <remarks>
+        /// Override if you wish to ignore server requests based on
+        /// custom criteria such as language, full server game mode or difficulty
+        /// </remarks>
+        /// <param name="request">Request coming from client</param>
+        /// <param name="endpoint">Address of the client that sent the request</param>
+        protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint)
+        {
+            Response info = ProcessRequest(request, endpoint);
+
+            if (info == null)
+                return;
+
+            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
+            {
+                try
+                {
+                    writer.WriteLong(secretHandshake);
+
+                    writer.Write(info);
+
+                    ArraySegment<byte> data = writer.ToArraySegment();
+                    // signature matches
+                    // send response
+                    serverUdpClient.Send(data.Array, data.Count, endpoint);
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogException(ex, this);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Process the request from a client
+        /// </summary>
+        /// <remarks>
+        /// Override if you wish to provide more information to the clients
+        /// such as the name of the host player
+        /// </remarks>
+        /// <param name="request">Request coming from client</param>
+        /// <param name="endpoint">Address of the client that sent the request</param>
+        /// <returns>The message to be sent back to the client or null</returns>
+        protected abstract Response ProcessRequest(Request request, IPEndPoint endpoint);
+
+        #endregion
+
+        #region Client
+
+        /// <summary>
+        /// Start Active Discovery
+        /// </summary>
+        public void StartDiscovery()
+        {
+            if (!SupportedOnThisPlatform)
+                throw new PlatformNotSupportedException("Network discovery not supported in this platform");
+
+            StopDiscovery();
+
+            try
+            {
+                // Setup port
+                clientUdpClient = new UdpClient(0)
+                {
+                    EnableBroadcast = true,
+                    MulticastLoopback = false
+                };
+            }
+            catch (Exception)
+            {
+                // Free the port if we took it
+                //Debug.LogError("NetworkDiscoveryBase StartDiscovery Exception");
+                Shutdown();
+                throw;
+            }
+
+            _ = ClientListenAsync();
+
+            if (enableActiveDiscovery) InvokeRepeating(nameof(BroadcastDiscoveryRequest), 0, ActiveDiscoveryInterval);
+        }
+
+        /// <summary>
+        /// Stop Active Discovery
+        /// </summary>
+        public void StopDiscovery()
+        {
+            //Debug.Log("NetworkDiscoveryBase StopDiscovery");
+            Shutdown();
+        }
+
+        /// <summary>
+        /// Awaits for server response
+        /// </summary>
+        /// <returns>ClientListenAsync Task</returns>
+        public async Task ClientListenAsync()
+        {
+            while (true)
+            {
+                try
+                {
+                    await ReceiveGameBroadcastAsync(clientUdpClient);
+                }
+                catch (ObjectDisposedException)
+                {
+                    // socket was closed, no problem
+                    return;
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogException(ex);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Sends discovery request from client
+        /// </summary>
+        public void BroadcastDiscoveryRequest()
+        {
+            if (clientUdpClient == null)
+                return;
+
+            if (NetworkClient.isConnected)
+            {
+                StopDiscovery();
+                return;
+            }
+
+            IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort);
+
+            using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter())
+            {
+                writer.WriteLong(secretHandshake);
+
+                try
+                {
+                    Request request = GetRequest();
+
+                    writer.Write(request);
+
+                    ArraySegment<byte> data = writer.ToArraySegment();
+
+                    clientUdpClient.SendAsync(data.Array, data.Count, endPoint);
+                }
+                catch (Exception)
+                {
+                    // It is ok if we can't broadcast to one of the addresses
+                }
+            }
+        }
+
+        /// <summary>
+        /// Create a message that will be broadcasted on the network to discover servers
+        /// </summary>
+        /// <remarks>
+        /// Override if you wish to include additional data in the discovery message
+        /// such as desired game mode, language, difficulty, etc... </remarks>
+        /// <returns>An instance of ServerRequest with data to be broadcasted</returns>
+        protected virtual Request GetRequest() => default;
+
+        async Task ReceiveGameBroadcastAsync(UdpClient udpClient)
+        {
+            // only proceed if there is available data in network buffer, or otherwise Receive() will block
+            // average time for UdpClient.Available : 10 us
+
+            UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync();
+
+            using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer))
+            {
+                if (networkReader.ReadLong() != secretHandshake)
+                    return;
+
+                Response response = networkReader.Read<Response>();
+
+                ProcessResponse(response, udpReceiveResult.RemoteEndPoint);
+            }
+        }
+
+        /// <summary>
+        /// Process the answer from a server
+        /// </summary>
+        /// <remarks>
+        /// A client receives a reply from a server, this method processes the
+        /// reply and raises an event
+        /// </remarks>
+        /// <param name="response">Response that came from the server</param>
+        /// <param name="endpoint">Address of the server that replied</param>
+        protected abstract void ProcessResponse(Response response, IPEndPoint endpoint);
+
+        #endregion
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b9971d60ce61f4e39b07cd9e7e0c68fa
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 132 - 0
Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs

@@ -0,0 +1,132 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror.Discovery
+{
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/NetworkDiscoveryHUD")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-discovery")]
+    [RequireComponent(typeof(NetworkDiscovery))]
+    public class NetworkDiscoveryHUD : MonoBehaviour
+    {
+        readonly Dictionary<long, ServerResponse> discoveredServers = new Dictionary<long, ServerResponse>();
+        Vector2 scrollViewPos = Vector2.zero;
+
+        public NetworkDiscovery networkDiscovery;
+
+#if UNITY_EDITOR
+        void OnValidate()
+        {
+            if (networkDiscovery == null)
+            {
+                networkDiscovery = GetComponent<NetworkDiscovery>();
+                UnityEditor.Events.UnityEventTools.AddPersistentListener(networkDiscovery.OnServerFound, OnDiscoveredServer);
+                UnityEditor.Undo.RecordObjects(new Object[] { this, networkDiscovery }, "Set NetworkDiscovery");
+            }
+        }
+#endif
+
+        void OnGUI()
+        {
+            if (NetworkManager.singleton == null)
+                return;
+
+            if (!NetworkClient.isConnected && !NetworkServer.active && !NetworkClient.active)
+                DrawGUI();
+
+            if (NetworkServer.active || NetworkClient.active)
+                StopButtons();
+        }
+
+        void DrawGUI()
+        {
+            GUILayout.BeginArea(new Rect(10, 10, 300, 500));
+            GUILayout.BeginHorizontal();
+
+            if (GUILayout.Button("Find Servers"))
+            {
+                discoveredServers.Clear();
+                networkDiscovery.StartDiscovery();
+            }
+
+            // LAN Host
+            if (GUILayout.Button("Start Host"))
+            {
+                discoveredServers.Clear();
+                NetworkManager.singleton.StartHost();
+                networkDiscovery.AdvertiseServer();
+            }
+
+            // Dedicated server
+            if (GUILayout.Button("Start Server"))
+            {
+                discoveredServers.Clear();
+                NetworkManager.singleton.StartServer();
+                networkDiscovery.AdvertiseServer();
+            }
+
+            GUILayout.EndHorizontal();
+
+            // show list of found server
+
+            GUILayout.Label($"Discovered Servers [{discoveredServers.Count}]:");
+
+            // servers
+            scrollViewPos = GUILayout.BeginScrollView(scrollViewPos);
+
+            foreach (ServerResponse info in discoveredServers.Values)
+                if (GUILayout.Button(info.EndPoint.Address.ToString()))
+                    Connect(info);
+
+            GUILayout.EndScrollView();
+            GUILayout.EndArea();
+        }
+
+        void StopButtons()
+        {
+            GUILayout.BeginArea(new Rect(10, 40, 100, 25));
+
+            // stop host if host mode
+            if (NetworkServer.active && NetworkClient.isConnected)
+            {
+                if (GUILayout.Button("Stop Host"))
+                {
+                    NetworkManager.singleton.StopHost();
+                    networkDiscovery.StopDiscovery();
+                }
+            }
+            // stop client if client-only
+            else if (NetworkClient.isConnected)
+            {
+                if (GUILayout.Button("Stop Client"))
+                {
+                    NetworkManager.singleton.StopClient();
+                    networkDiscovery.StopDiscovery();
+                }
+            }
+            // stop server if server-only
+            else if (NetworkServer.active)
+            {
+                if (GUILayout.Button("Stop Server"))
+                {
+                    NetworkManager.singleton.StopServer();
+                    networkDiscovery.StopDiscovery();
+                }
+            }
+
+            GUILayout.EndArea();
+        }
+
+        void Connect(ServerResponse info)
+        {
+            networkDiscovery.StopDiscovery();
+            NetworkManager.singleton.StartClient(info.uri);
+        }
+
+        public void OnDiscoveredServer(ServerResponse info)
+        {
+            // Note that you can check the versioning to decide if you can connect to the server or not using this method
+            discoveredServers[info.serverId] = info;
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 88c37d3deca7a834d80cfd8d3cfcc510
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 4 - 0
Assets/Mirror/Components/Discovery/ServerRequest.cs

@@ -0,0 +1,4 @@
+namespace Mirror.Discovery
+{
+    public struct ServerRequest : NetworkMessage {}
+}

+ 11 - 0
Assets/Mirror/Components/Discovery/ServerRequest.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ea7254bf7b9454da4adad881d94cd141
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 18 - 0
Assets/Mirror/Components/Discovery/ServerResponse.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Net;
+
+namespace Mirror.Discovery
+{
+    public struct ServerResponse : NetworkMessage
+    {
+        // The server that sent this
+        // this is a property so that it is not serialized,  but the
+        // client fills this up after we receive it
+        public IPEndPoint EndPoint { get; set; }
+
+        public Uri uri;
+
+        // Prevent duplicate server appearance when a connection can be made via LAN on multiple NICs
+        public long serverId;
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Discovery/ServerResponse.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 36f97227fdf2d7a4e902db5bfc43039c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Components/Experimental.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bfbf2a1f2b300c5489dcab219ef2846e
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 93 - 0
Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs

@@ -0,0 +1,93 @@
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    [AddComponentMenu("Network/Experimental/NetworkLerpRigidbody")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-lerp-rigidbody")]
+    public class NetworkLerpRigidbody : NetworkBehaviour
+    {
+        [Header("Settings")]
+        [SerializeField] internal Rigidbody target = null;
+        [Tooltip("How quickly current velocity approaches target velocity")]
+        [SerializeField] float lerpVelocityAmount = 0.5f;
+        [Tooltip("How quickly current position approaches target position")]
+        [SerializeField] float lerpPositionAmount = 0.5f;
+
+        [Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
+        [SerializeField] bool clientAuthority = false;
+
+        float nextSyncTime;
+
+
+        [SyncVar()]
+        Vector3 targetVelocity;
+
+        [SyncVar()]
+        Vector3 targetPosition;
+
+        /// <summary>
+        /// Ignore value if is host or client with Authority
+        /// </summary>
+        /// <returns></returns>
+        bool IgnoreSync => isServer || ClientWithAuthority;
+
+        bool ClientWithAuthority => clientAuthority && hasAuthority;
+
+        void OnValidate()
+        {
+            if (target == null)
+            {
+                target = GetComponent<Rigidbody>();
+            }
+        }
+
+        void Update()
+        {
+            if (isServer)
+            {
+                SyncToClients();
+            }
+            else if (ClientWithAuthority)
+            {
+                SendToServer();
+            }
+        }
+
+        void SyncToClients()
+        {
+            targetVelocity = target.velocity;
+            targetPosition = target.position;
+        }
+
+        void SendToServer()
+        {
+            float now = Time.time;
+            if (now > nextSyncTime)
+            {
+                nextSyncTime = now + syncInterval;
+                CmdSendState(target.velocity, target.position);
+            }
+        }
+
+        [Command]
+        void CmdSendState(Vector3 velocity, Vector3 position)
+        {
+            target.velocity = velocity;
+            target.position = position;
+            targetVelocity = velocity;
+            targetPosition = position;
+        }
+
+        void FixedUpdate()
+        {
+            if (IgnoreSync) { return; }
+
+            target.velocity = Vector3.Lerp(target.velocity, targetVelocity, lerpVelocityAmount);
+            target.position = Vector3.Lerp(target.position, targetPosition, lerpPositionAmount);
+            // add velocity to position as position would have moved on server at that velocity
+            targetPosition += target.velocity * Time.fixedDeltaTime;
+
+            // TODO does this also need to sync acceleration so and update velocity?
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7f032128052c95a46afb0ddd97d994cc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 361 - 0
Assets/Mirror/Components/Experimental/NetworkRigidbody.cs

@@ -0,0 +1,361 @@
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    [AddComponentMenu("Network/Experimental/NetworkRigidbody")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-rigidbody")]
+    public class NetworkRigidbody : NetworkBehaviour
+    {
+        [Header("Settings")]
+        [SerializeField] internal Rigidbody target = null;
+
+        [Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
+        public bool clientAuthority = false;
+
+        [Header("Velocity")]
+
+        [Tooltip("Syncs Velocity every SyncInterval")]
+        [SerializeField] bool syncVelocity = true;
+
+        [Tooltip("Set velocity to 0 each frame (only works if syncVelocity is false")]
+        [SerializeField] bool clearVelocity = false;
+
+        [Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
+        [SerializeField] float velocitySensitivity = 0.1f;
+
+
+        [Header("Angular Velocity")]
+
+        [Tooltip("Syncs AngularVelocity every SyncInterval")]
+        [SerializeField] bool syncAngularVelocity = true;
+
+        [Tooltip("Set angularVelocity to 0 each frame (only works if syncAngularVelocity is false")]
+        [SerializeField] bool clearAngularVelocity = false;
+
+        [Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
+        [SerializeField] float angularVelocitySensitivity = 0.1f;
+
+        /// <summary>
+        /// Values sent on client with authority after they are sent to the server
+        /// </summary>
+        readonly ClientSyncState previousValue = new ClientSyncState();
+
+        void OnValidate()
+        {
+            if (target == null)
+            {
+                target = GetComponent<Rigidbody>();
+            }
+        }
+
+
+        #region Sync vars
+        [SyncVar(hook = nameof(OnVelocityChanged))]
+        Vector3 velocity;
+
+        [SyncVar(hook = nameof(OnAngularVelocityChanged))]
+        Vector3 angularVelocity;
+
+        [SyncVar(hook = nameof(OnIsKinematicChanged))]
+        bool isKinematic;
+
+        [SyncVar(hook = nameof(OnUseGravityChanged))]
+        bool useGravity;
+
+        [SyncVar(hook = nameof(OnuDragChanged))]
+        float drag;
+
+        [SyncVar(hook = nameof(OnAngularDragChanged))]
+        float angularDrag;
+
+        /// <summary>
+        /// Ignore value if is host or client with Authority
+        /// </summary>
+        /// <returns></returns>
+        bool IgnoreSync => isServer || ClientWithAuthority;
+
+        bool ClientWithAuthority => clientAuthority && hasAuthority;
+
+        void OnVelocityChanged(Vector3 _, Vector3 newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.velocity = newValue;
+        }
+
+
+        void OnAngularVelocityChanged(Vector3 _, Vector3 newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.angularVelocity = newValue;
+        }
+
+        void OnIsKinematicChanged(bool _, bool newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.isKinematic = newValue;
+        }
+
+        void OnUseGravityChanged(bool _, bool newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.useGravity = newValue;
+        }
+
+        void OnuDragChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.drag = newValue;
+        }
+
+        void OnAngularDragChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.angularDrag = newValue;
+        }
+        #endregion
+
+
+        internal void Update()
+        {
+            if (isServer)
+            {
+                SyncToClients();
+            }
+            else if (ClientWithAuthority)
+            {
+                SendToServer();
+            }
+        }
+
+        internal void FixedUpdate()
+        {
+            if (clearAngularVelocity && !syncAngularVelocity)
+            {
+                target.angularVelocity = Vector3.zero;
+            }
+
+            if (clearVelocity && !syncVelocity)
+            {
+                target.velocity = Vector3.zero;
+            }
+        }
+
+        /// <summary>
+        /// Updates sync var values on server so that they sync to the client
+        /// </summary>
+        [Server]
+        void SyncToClients()
+        {
+            // only update if they have changed more than Sensitivity
+
+            Vector3 currentVelocity = syncVelocity ? target.velocity : default;
+            Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
+
+            bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
+            bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
+
+            if (velocityChanged)
+            {
+                velocity = currentVelocity;
+                previousValue.velocity = currentVelocity;
+            }
+
+            if (angularVelocityChanged)
+            {
+                angularVelocity = currentAngularVelocity;
+                previousValue.angularVelocity = currentAngularVelocity;
+            }
+
+            // other rigidbody settings
+            isKinematic = target.isKinematic;
+            useGravity = target.useGravity;
+            drag = target.drag;
+            angularDrag = target.angularDrag;
+        }
+
+        /// <summary>
+        /// Uses Command to send values to server
+        /// </summary>
+        [Client]
+        void SendToServer()
+        {
+            if (!hasAuthority)
+            {
+                Debug.LogWarning("SendToServer called without authority");
+                return;
+            }
+
+            SendVelocity();
+            SendRigidBodySettings();
+        }
+
+        [Client]
+        void SendVelocity()
+        {
+            float now = Time.time;
+            if (now < previousValue.nextSyncTime)
+                return;
+
+            Vector3 currentVelocity = syncVelocity ? target.velocity : default;
+            Vector3 currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
+
+            bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
+            bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
+
+            // if angularVelocity has changed it is likely that velocity has also changed so just sync both values
+            // however if only velocity has changed just send velocity
+            if (angularVelocityChanged)
+            {
+                CmdSendVelocityAndAngular(currentVelocity, currentAngularVelocity);
+                previousValue.velocity = currentVelocity;
+                previousValue.angularVelocity = currentAngularVelocity;
+            }
+            else if (velocityChanged)
+            {
+                CmdSendVelocity(currentVelocity);
+                previousValue.velocity = currentVelocity;
+            }
+
+
+            // only update syncTime if either has changed
+            if (angularVelocityChanged || velocityChanged)
+            {
+                previousValue.nextSyncTime = now + syncInterval;
+            }
+        }
+
+        [Client]
+        void SendRigidBodySettings()
+        {
+            // These shouldn't change often so it is ok to send in their own Command
+            if (previousValue.isKinematic != target.isKinematic)
+            {
+                CmdSendIsKinematic(target.isKinematic);
+                previousValue.isKinematic = target.isKinematic;
+            }
+            if (previousValue.useGravity != target.useGravity)
+            {
+                CmdSendUseGravity(target.useGravity);
+                previousValue.useGravity = target.useGravity;
+            }
+            if (previousValue.drag != target.drag)
+            {
+                CmdSendDrag(target.drag);
+                previousValue.drag = target.drag;
+            }
+            if (previousValue.angularDrag != target.angularDrag)
+            {
+                CmdSendAngularDrag(target.angularDrag);
+                previousValue.angularDrag = target.angularDrag;
+            }
+        }
+
+        /// <summary>
+        /// Called when only Velocity has changed on the client
+        /// </summary>
+        [Command]
+        void CmdSendVelocity(Vector3 velocity)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.velocity = velocity;
+            target.velocity = velocity;
+        }
+
+        /// <summary>
+        /// Called when angularVelocity has changed on the client
+        /// </summary>
+        [Command]
+        void CmdSendVelocityAndAngular(Vector3 velocity, Vector3 angularVelocity)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            if (syncVelocity)
+            {
+                this.velocity = velocity;
+
+                target.velocity = velocity;
+
+            }
+            this.angularVelocity = angularVelocity;
+            target.angularVelocity = angularVelocity;
+        }
+
+        [Command]
+        void CmdSendIsKinematic(bool isKinematic)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.isKinematic = isKinematic;
+            target.isKinematic = isKinematic;
+        }
+
+        [Command]
+        void CmdSendUseGravity(bool useGravity)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.useGravity = useGravity;
+            target.useGravity = useGravity;
+        }
+
+        [Command]
+        void CmdSendDrag(float drag)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.drag = drag;
+            target.drag = drag;
+        }
+
+        [Command]
+        void CmdSendAngularDrag(float angularDrag)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.angularDrag = angularDrag;
+            target.angularDrag = angularDrag;
+        }
+
+        /// <summary>
+        /// holds previously synced values
+        /// </summary>
+        public class ClientSyncState
+        {
+            /// <summary>
+            /// Next sync time that velocity will be synced, based on syncInterval.
+            /// </summary>
+            public float nextSyncTime;
+            public Vector3 velocity;
+            public Vector3 angularVelocity;
+            public bool isKinematic;
+            public bool useGravity;
+            public float drag;
+            public float angularDrag;
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkRigidbody.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 83392ae5c1b731446909f252fd494ae4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 360 - 0
Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs

@@ -0,0 +1,360 @@
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    [AddComponentMenu("Network/Experimental/NetworkRigidbody2D")]
+    public class NetworkRigidbody2D : NetworkBehaviour
+    {
+        [Header("Settings")]
+        [SerializeField] internal Rigidbody2D target = null;
+
+        [Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
+        public  bool clientAuthority = false;
+
+        [Header("Velocity")]
+
+        [Tooltip("Syncs Velocity every SyncInterval")]
+        [SerializeField] bool syncVelocity = true;
+
+        [Tooltip("Set velocity to 0 each frame (only works if syncVelocity is false")]
+        [SerializeField] bool clearVelocity = false;
+
+        [Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
+        [SerializeField] float velocitySensitivity = 0.1f;
+
+
+        [Header("Angular Velocity")]
+
+        [Tooltip("Syncs AngularVelocity every SyncInterval")]
+        [SerializeField] bool syncAngularVelocity = true;
+
+        [Tooltip("Set angularVelocity to 0 each frame (only works if syncAngularVelocity is false")]
+        [SerializeField] bool clearAngularVelocity = false;
+
+        [Tooltip("Only Syncs Value if distance between previous and current is great than sensitivity")]
+        [SerializeField] float angularVelocitySensitivity = 0.1f;
+
+        /// <summary>
+        /// Values sent on client with authority after they are sent to the server
+        /// </summary>
+        readonly ClientSyncState previousValue = new ClientSyncState();
+
+        void OnValidate()
+        {
+            if (target == null)
+            {
+                target = GetComponent<Rigidbody2D>();
+            }
+        }
+
+
+        #region Sync vars
+        [SyncVar(hook = nameof(OnVelocityChanged))]
+        Vector2 velocity;
+
+        [SyncVar(hook = nameof(OnAngularVelocityChanged))]
+        float angularVelocity;
+
+        [SyncVar(hook = nameof(OnIsKinematicChanged))]
+        bool isKinematic;
+
+        [SyncVar(hook = nameof(OnGravityScaleChanged))]
+        float gravityScale;
+
+        [SyncVar(hook = nameof(OnuDragChanged))]
+        float drag;
+
+        [SyncVar(hook = nameof(OnAngularDragChanged))]
+        float angularDrag;
+
+        /// <summary>
+        /// Ignore value if is host or client with Authority
+        /// </summary>
+        /// <returns></returns>
+        bool IgnoreSync => isServer || ClientWithAuthority;
+
+        bool ClientWithAuthority => clientAuthority && hasAuthority;
+
+        void OnVelocityChanged(Vector2 _, Vector2 newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.velocity = newValue;
+        }
+
+
+        void OnAngularVelocityChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.angularVelocity = newValue;
+        }
+
+        void OnIsKinematicChanged(bool _, bool newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.isKinematic = newValue;
+        }
+
+        void OnGravityScaleChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.gravityScale = newValue;
+        }
+
+        void OnuDragChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.drag = newValue;
+        }
+
+        void OnAngularDragChanged(float _, float newValue)
+        {
+            if (IgnoreSync)
+                return;
+
+            target.angularDrag = newValue;
+        }
+        #endregion
+
+
+        internal void Update()
+        {
+            if (isServer)
+            {
+                SyncToClients();
+            }
+            else if (ClientWithAuthority)
+            {
+                SendToServer();
+            }
+        }
+
+        internal void FixedUpdate()
+        {
+            if (clearAngularVelocity && !syncAngularVelocity)
+            {
+                target.angularVelocity = 0f;
+            }
+
+            if (clearVelocity && !syncVelocity)
+            {
+                target.velocity = Vector2.zero;
+            }
+        }
+
+        /// <summary>
+        /// Updates sync var values on server so that they sync to the client
+        /// </summary>
+        [Server]
+        void SyncToClients()
+        {
+            // only update if they have changed more than Sensitivity
+
+            Vector2 currentVelocity = syncVelocity ? target.velocity : default;
+            float currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
+
+            bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
+            bool angularVelocityChanged = syncAngularVelocity && ((previousValue.angularVelocity - currentAngularVelocity) > angularVelocitySensitivity);
+
+            if (velocityChanged)
+            {
+                velocity = currentVelocity;
+                previousValue.velocity = currentVelocity;
+            }
+
+            if (angularVelocityChanged)
+            {
+                angularVelocity = currentAngularVelocity;
+                previousValue.angularVelocity = currentAngularVelocity;
+            }
+
+            // other rigidbody settings
+            isKinematic = target.isKinematic;
+            gravityScale = target.gravityScale;
+            drag = target.drag;
+            angularDrag = target.angularDrag;
+        }
+
+        /// <summary>
+        /// Uses Command to send values to server
+        /// </summary>
+        [Client]
+        void SendToServer()
+        {
+            if (!hasAuthority)
+            {
+                Debug.LogWarning("SendToServer called without authority");
+                return;
+            }
+
+            SendVelocity();
+            SendRigidBodySettings();
+        }
+
+        [Client]
+        void SendVelocity()
+        {
+            float now = Time.time;
+            if (now < previousValue.nextSyncTime)
+                return;
+
+            Vector2 currentVelocity = syncVelocity ? target.velocity : default;
+            float currentAngularVelocity = syncAngularVelocity ? target.angularVelocity : default;
+
+            bool velocityChanged = syncVelocity && ((previousValue.velocity - currentVelocity).sqrMagnitude > velocitySensitivity * velocitySensitivity);
+            bool angularVelocityChanged = syncAngularVelocity && previousValue.angularVelocity != currentAngularVelocity;//((previousValue.angularVelocity - currentAngularVelocity).sqrMagnitude > angularVelocitySensitivity * angularVelocitySensitivity);
+
+            // if angularVelocity has changed it is likely that velocity has also changed so just sync both values
+            // however if only velocity has changed just send velocity
+            if (angularVelocityChanged)
+            {
+                CmdSendVelocityAndAngular(currentVelocity, currentAngularVelocity);
+                previousValue.velocity = currentVelocity;
+                previousValue.angularVelocity = currentAngularVelocity;
+            }
+            else if (velocityChanged)
+            {
+                CmdSendVelocity(currentVelocity);
+                previousValue.velocity = currentVelocity;
+            }
+
+
+            // only update syncTime if either has changed
+            if (angularVelocityChanged || velocityChanged)
+            {
+                previousValue.nextSyncTime = now + syncInterval;
+            }
+        }
+
+        [Client]
+        void SendRigidBodySettings()
+        {
+            // These shouldn't change often so it is ok to send in their own Command
+            if (previousValue.isKinematic != target.isKinematic)
+            {
+                CmdSendIsKinematic(target.isKinematic);
+                previousValue.isKinematic = target.isKinematic;
+            }
+            if (previousValue.gravityScale != target.gravityScale)
+            {
+                CmdChangeGravityScale(target.gravityScale);
+                previousValue.gravityScale = target.gravityScale;
+            }
+            if (previousValue.drag != target.drag)
+            {
+                CmdSendDrag(target.drag);
+                previousValue.drag = target.drag;
+            }
+            if (previousValue.angularDrag != target.angularDrag)
+            {
+                CmdSendAngularDrag(target.angularDrag);
+                previousValue.angularDrag = target.angularDrag;
+            }
+        }
+
+        /// <summary>
+        /// Called when only Velocity has changed on the client
+        /// </summary>
+        [Command]
+        void CmdSendVelocity(Vector2 velocity)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.velocity = velocity;
+            target.velocity = velocity;
+        }
+
+        /// <summary>
+        /// Called when angularVelocity has changed on the client
+        /// </summary>
+        [Command]
+        void CmdSendVelocityAndAngular(Vector2 velocity, float angularVelocity)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            if (syncVelocity)
+            {
+                this.velocity = velocity;
+
+                target.velocity = velocity;
+
+            }
+            this.angularVelocity = angularVelocity;
+            target.angularVelocity = angularVelocity;
+        }
+
+        [Command]
+        void CmdSendIsKinematic(bool isKinematic)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.isKinematic = isKinematic;
+            target.isKinematic = isKinematic;
+        }
+
+        [Command]
+        void CmdChangeGravityScale(float gravityScale)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.gravityScale = gravityScale;
+            target.gravityScale = gravityScale;
+        }
+
+        [Command]
+        void CmdSendDrag(float drag)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.drag = drag;
+            target.drag = drag;
+        }
+
+        [Command]
+        void CmdSendAngularDrag(float angularDrag)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            this.angularDrag = angularDrag;
+            target.angularDrag = angularDrag;
+        }
+
+        /// <summary>
+        /// holds previously synced values
+        /// </summary>
+        public class ClientSyncState
+        {
+            /// <summary>
+            /// Next sync time that velocity will be synced, based on syncInterval.
+            /// </summary>
+            public float nextSyncTime;
+            public Vector2 velocity;
+            public float angularVelocity;
+            public bool isKinematic;
+            public float gravityScale;
+            public float drag;
+            public float angularDrag;
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ab2cbc52526ea384ba280d13cd1a57b9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 12 - 0
Assets/Mirror/Components/Experimental/NetworkTransform.cs

@@ -0,0 +1,12 @@
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/Experimental/NetworkTransformExperimental")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-transform")]
+    public class NetworkTransform : NetworkTransformBase
+    {
+        protected override Transform targetTransform => transform;
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 741bbe11f5357b44593b15c0d11b16bd
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 529 - 0
Assets/Mirror/Components/Experimental/NetworkTransformBase.cs

@@ -0,0 +1,529 @@
+// vis2k:
+// base class for NetworkTransform and NetworkTransformChild.
+// New method is simple and stupid. No more 1500 lines of code.
+//
+// Server sends current data.
+// Client saves it and interpolates last and latest data points.
+//   Update handles transform movement / rotation
+//   FixedUpdate handles rigidbody movement / rotation
+//
+// Notes:
+// * Built-in Teleport detection in case of lags / teleport / obstacles
+// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp
+// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code.
+// * Initial delay might happen if server sends packet immediately after moving
+//   just 1cm, hence we move 1cm and then wait 100ms for next packet
+// * Only way for smooth movement is to use a fixed movement speed during
+//   interpolation. interpolation over time is never that good.
+//
+using System;
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    public abstract class NetworkTransformBase : NetworkBehaviour
+    {
+        // target transform to sync. can be on a child.
+        protected abstract Transform targetTransform { get; }
+
+        [Header("Authority")]
+
+        [Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")]
+        [SyncVar]
+        public bool clientAuthority;
+
+        [Tooltip("Set to true if updates from server should be ignored by owner")]
+        [SyncVar]
+        public bool excludeOwnerUpdate = true;
+
+        [Header("Synchronization")]
+
+        [Tooltip("Set to true if position should be synchronized")]
+        [SyncVar]
+        public bool syncPosition = true;
+
+        [Tooltip("Set to true if rotation should be synchronized")]
+        [SyncVar]
+        public bool syncRotation = true;
+
+        [Tooltip("Set to true if scale should be synchronized")]
+        [SyncVar]
+        public bool syncScale = true;
+
+        [Header("Interpolation")]
+
+        [Tooltip("Set to true if position should be interpolated")]
+        [SyncVar]
+        public bool interpolatePosition = true;
+
+        [Tooltip("Set to true if rotation should be interpolated")]
+        [SyncVar]
+        public bool interpolateRotation = true;
+
+        [Tooltip("Set to true if scale should be interpolated")]
+        [SyncVar]
+        public bool interpolateScale = true;
+
+        // Sensitivity is added for VR where human players tend to have micro movements so this can quiet down
+        // the network traffic.  Additionally, rigidbody drift should send less traffic, e.g very slow sliding / rolling.
+        [Header("Sensitivity")]
+
+        [Tooltip("Changes to the transform must exceed these values to be transmitted on the network.")]
+        [SyncVar]
+        public float localPositionSensitivity = .01f;
+
+        [Tooltip("If rotation exceeds this angle, it will be transmitted on the network")]
+        [SyncVar]
+        public float localRotationSensitivity = .01f;
+
+        [Tooltip("Changes to the transform must exceed these values to be transmitted on the network.")]
+        [SyncVar]
+        public float localScaleSensitivity = .01f;
+
+        [Header("Diagnostics")]
+
+        // server
+        public Vector3 lastPosition;
+        public Quaternion lastRotation;
+        public Vector3 lastScale;
+
+        // client
+        // use local position/rotation for VR support
+        [Serializable]
+        public struct DataPoint
+        {
+            public float timeStamp;
+            public Vector3 localPosition;
+            public Quaternion localRotation;
+            public Vector3 localScale;
+            public float movementSpeed;
+
+            public bool isValid => timeStamp != 0;
+        }
+
+        // Is this a client with authority over this transform?
+        // This component could be on the player object or any object that has been assigned authority to this client.
+        bool IsOwnerWithClientAuthority => hasAuthority && clientAuthority;
+
+        // interpolation start and goal
+        public DataPoint start = new DataPoint();
+        public DataPoint goal = new DataPoint();
+
+        // We need to store this locally on the server so clients can't request Authority when ever they like
+        bool clientAuthorityBeforeTeleport;
+
+        void FixedUpdate()
+        {
+            // if server then always sync to others.
+            // let the clients know that this has moved
+            if (isServer && HasEitherMovedRotatedScaled())
+            {
+                ServerUpdate();
+            }
+
+            if (isClient)
+            {
+                // send to server if we have local authority (and aren't the server)
+                // -> only if connectionToServer has been initialized yet too
+                if (IsOwnerWithClientAuthority)
+                {
+                    ClientAuthorityUpdate();
+                }
+                else if (goal.isValid)
+                {
+                    ClientRemoteUpdate();
+                }
+            }
+        }
+
+        void ServerUpdate()
+        {
+            RpcMove(targetTransform.localPosition, Compression.CompressQuaternion(targetTransform.localRotation), targetTransform.localScale);
+        }
+
+        void ClientAuthorityUpdate()
+        {
+            if (!isServer && HasEitherMovedRotatedScaled())
+            {
+                // serialize
+                // local position/rotation for VR support
+                // send to server
+                CmdClientToServerSync(targetTransform.localPosition, Compression.CompressQuaternion(targetTransform.localRotation), targetTransform.localScale);
+            }
+        }
+
+        void ClientRemoteUpdate()
+        {
+            // teleport or interpolate
+            if (NeedsTeleport())
+            {
+                // local position/rotation for VR support
+                ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
+
+                // reset data points so we don't keep interpolating
+                start = new DataPoint();
+                goal = new DataPoint();
+            }
+            else
+            {
+                // local position/rotation for VR support
+                ApplyPositionRotationScale(InterpolatePosition(start, goal, targetTransform.localPosition),
+                                           InterpolateRotation(start, goal, targetTransform.localRotation),
+                                           InterpolateScale(start, goal, targetTransform.localScale));
+            }
+        }
+
+        // moved or rotated or scaled since last time we checked it?
+        bool HasEitherMovedRotatedScaled()
+        {
+            // Save last for next frame to compare only if change was detected, otherwise
+            // slow moving objects might never sync because of C#'s float comparison tolerance.
+            // See also: https://github.com/vis2k/Mirror/pull/428)
+            bool changed = HasMoved || HasRotated || HasScaled;
+            if (changed)
+            {
+                // local position/rotation for VR support
+                if (syncPosition) lastPosition = targetTransform.localPosition;
+                if (syncRotation) lastRotation = targetTransform.localRotation;
+                if (syncScale) lastScale = targetTransform.localScale;
+            }
+            return changed;
+        }
+
+        // local position/rotation for VR support
+        // SqrMagnitude is faster than Distance per Unity docs
+        // https://docs.unity3d.com/ScriptReference/Vector3-sqrMagnitude.html
+
+        bool HasMoved => syncPosition && Vector3.SqrMagnitude(lastPosition - targetTransform.localPosition) > localPositionSensitivity * localPositionSensitivity;
+        bool HasRotated => syncRotation && Quaternion.Angle(lastRotation, targetTransform.localRotation) > localRotationSensitivity;
+        bool HasScaled => syncScale && Vector3.SqrMagnitude(lastScale - targetTransform.localScale) > localScaleSensitivity * localScaleSensitivity;
+
+        // teleport / lag / stuck detection
+        // - checking distance is not enough since there could be just a tiny fence between us and the goal
+        // - checking time always works, this way we just teleport if we still didn't reach the goal after too much time has elapsed
+        bool NeedsTeleport()
+        {
+            // calculate time between the two data points
+            float startTime = start.isValid ? start.timeStamp : Time.time - Time.fixedDeltaTime;
+            float goalTime = goal.isValid ? goal.timeStamp : Time.time;
+            float difference = goalTime - startTime;
+            float timeSinceGoalReceived = Time.time - goalTime;
+            return timeSinceGoalReceived > difference * 5;
+        }
+
+        // local authority client sends sync message to server for broadcasting
+        [Command(channel = Channels.Unreliable)]
+        void CmdClientToServerSync(Vector3 position, uint packedRotation, Vector3 scale)
+        {
+            // Ignore messages from client if not in client authority mode
+            if (!clientAuthority)
+                return;
+
+            // deserialize payload
+            SetGoal(position, Compression.DecompressQuaternion(packedRotation), scale);
+
+            // server-only mode does no interpolation to save computations, but let's set the position directly
+            if (isServer && !isClient)
+                ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
+
+            RpcMove(position, packedRotation, scale);
+        }
+
+        [ClientRpc(channel = Channels.Unreliable)]
+        void RpcMove(Vector3 position, uint packedRotation, Vector3 scale)
+        {
+            if (hasAuthority && excludeOwnerUpdate) return;
+
+            if (!isServer)
+                SetGoal(position, Compression.DecompressQuaternion(packedRotation), scale);
+        }
+
+        // serialization is needed by OnSerialize and by manual sending from authority
+        void SetGoal(Vector3 position, Quaternion rotation, Vector3 scale)
+        {
+            // put it into a data point immediately
+            DataPoint temp = new DataPoint
+            {
+                // deserialize position
+                localPosition = position,
+                localRotation = rotation,
+                localScale = scale,
+                timeStamp = Time.time
+            };
+
+            // movement speed: based on how far it moved since last time has to be calculated before 'start' is overwritten
+            temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetTransform, Time.fixedDeltaTime);
+
+            // reassign start wisely
+            // first ever data point? then make something up for previous one so that we can start interpolation without waiting for next.
+            if (start.timeStamp == 0)
+            {
+                start = new DataPoint
+                {
+                    timeStamp = Time.time - Time.fixedDeltaTime,
+                    // local position/rotation for VR support
+                    localPosition = targetTransform.localPosition,
+                    localRotation = targetTransform.localRotation,
+                    localScale = targetTransform.localScale,
+                    movementSpeed = temp.movementSpeed
+                };
+            }
+            // second or nth data point? then update previous
+            // but: we start at where ever we are right now, so that it's perfectly smooth and we don't jump anywhere
+            //
+            //    example if we are at 'x':
+            //
+            //        A--x->B
+            //
+            //    and then receive a new point C:
+            //
+            //        A--x--B
+            //              |
+            //              |
+            //              C
+            //
+            //    then we don't want to just jump to B and start interpolation:
+            //
+            //              x
+            //              |
+            //              |
+            //              C
+            //
+            //    we stay at 'x' and interpolate from there to C:
+            //
+            //           x..B
+            //            \ .
+            //             \.
+            //              C
+            //
+            else
+            {
+                float oldDistance = Vector3.Distance(start.localPosition, goal.localPosition);
+                float newDistance = Vector3.Distance(goal.localPosition, temp.localPosition);
+
+                start = goal;
+
+                // local position/rotation for VR support
+                // teleport / lag / obstacle detection: only continue at current position if we aren't too far away
+                // XC  < AB + BC (see comments above)
+                if (Vector3.Distance(targetTransform.localPosition, start.localPosition) < oldDistance + newDistance)
+                {
+                    start.localPosition = targetTransform.localPosition;
+                    start.localRotation = targetTransform.localRotation;
+                    start.localScale = targetTransform.localScale;
+                }
+            }
+
+            // set new destination in any case. new data is best data.
+            goal = temp;
+        }
+
+        // try to estimate movement speed for a data point based on how far it moved since the previous one
+        // - if this is the first time ever then we use our best guess:
+        //     - delta based on transform.localPosition
+        //     - elapsed based on send interval hoping that it roughly matches
+        static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval)
+        {
+            Vector3 delta = to.localPosition - (from.localPosition != transform.localPosition ? from.localPosition : transform.localPosition);
+            float elapsed = from.isValid ? to.timeStamp - from.timeStamp : sendInterval;
+
+            // avoid NaN
+            return elapsed > 0 ? delta.magnitude / elapsed : 0;
+        }
+
+        // set position carefully depending on the target component
+        void ApplyPositionRotationScale(Vector3 position, Quaternion rotation, Vector3 scale)
+        {
+            // local position/rotation for VR support
+            if (syncPosition) targetTransform.localPosition = position;
+            if (syncRotation) targetTransform.localRotation = rotation;
+            if (syncScale) targetTransform.localScale = scale;
+        }
+
+        // where are we in the timeline between start and goal? [0,1]
+        Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition)
+        {
+            if (!interpolatePosition)
+                return currentPosition;
+
+            if (start.movementSpeed != 0)
+            {
+                // Option 1: simply interpolate based on time, but stutter will happen, it's not that smooth.
+                // This is especially noticeable if the camera automatically follows the player
+                // -         Tell SonarCloud this isn't really commented code but actual comments and to stfu about it
+                // -         float t = CurrentInterpolationFactor();
+                // -         return Vector3.Lerp(start.position, goal.position, t);
+
+                // Option 2: always += speed
+                // speed is 0 if we just started after idle, so always use max for best results
+                float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed);
+                return Vector3.MoveTowards(currentPosition, goal.localPosition, speed * Time.deltaTime);
+            }
+
+            return currentPosition;
+        }
+
+        Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation)
+        {
+            if (!interpolateRotation)
+                return defaultRotation;
+
+            if (start.localRotation != goal.localRotation)
+            {
+                float t = CurrentInterpolationFactor(start, goal);
+                return Quaternion.Slerp(start.localRotation, goal.localRotation, t);
+            }
+
+            return defaultRotation;
+        }
+
+        Vector3 InterpolateScale(DataPoint start, DataPoint goal, Vector3 currentScale)
+        {
+            if (!interpolateScale)
+                return currentScale;
+
+            if (start.localScale != goal.localScale)
+            {
+                float t = CurrentInterpolationFactor(start, goal);
+                return Vector3.Lerp(start.localScale, goal.localScale, t);
+            }
+
+            return currentScale;
+        }
+
+        static float CurrentInterpolationFactor(DataPoint start, DataPoint goal)
+        {
+            if (start.isValid)
+            {
+                float difference = goal.timeStamp - start.timeStamp;
+
+                // the moment we get 'goal', 'start' is supposed to start, so elapsed time is based on:
+                float elapsed = Time.time - goal.timeStamp;
+
+                // avoid NaN
+                return difference > 0 ? elapsed / difference : 1;
+            }
+            return 1;
+        }
+
+        #region Server Teleport (force move player)
+
+        /// <summary>
+        /// This method will override this GameObject's current Transform.localPosition to the specified Vector3  and update all clients.
+        /// <para>NOTE: position must be in LOCAL space if the transform has a parent</para>
+        /// </summary>
+        /// <param name="localPosition">Where to teleport this GameObject</param>
+        [Server]
+        public void ServerTeleport(Vector3 localPosition)
+        {
+            Quaternion localRotation = targetTransform.localRotation;
+            ServerTeleport(localPosition, localRotation);
+        }
+
+        /// <summary>
+        /// This method will override this GameObject's current Transform.localPosition and Transform.localRotation
+        /// to the specified Vector3 and Quaternion and update all clients.
+        /// <para>NOTE: localPosition must be in LOCAL space if the transform has a parent</para>
+        /// <para>NOTE: localRotation must be in LOCAL space if the transform has a parent</para>
+        /// </summary>
+        /// <param name="localPosition">Where to teleport this GameObject</param>
+        /// <param name="localRotation">Which rotation to set this GameObject</param>
+        [Server]
+        public void ServerTeleport(Vector3 localPosition, Quaternion localRotation)
+        {
+            // To prevent applying the position updates received from client (if they have ClientAuth) while being teleported.
+            // clientAuthorityBeforeTeleport defaults to false when not teleporting, if it is true then it means that teleport
+            // was previously called but not finished therefore we should keep it as true so that 2nd teleport call doesn't clear authority
+            clientAuthorityBeforeTeleport = clientAuthority || clientAuthorityBeforeTeleport;
+            clientAuthority = false;
+
+            DoTeleport(localPosition, localRotation);
+
+            // tell all clients about new values
+            RpcTeleport(localPosition, Compression.CompressQuaternion(localRotation), clientAuthorityBeforeTeleport);
+        }
+
+        void DoTeleport(Vector3 newLocalPosition, Quaternion newLocalRotation)
+        {
+            targetTransform.localPosition = newLocalPosition;
+            targetTransform.localRotation = newLocalRotation;
+
+            // Since we are overriding the position we don't need a goal and start.
+            // Reset them to null for fresh start
+            goal = new DataPoint();
+            start = new DataPoint();
+            lastPosition = newLocalPosition;
+            lastRotation = newLocalRotation;
+        }
+
+        [ClientRpc(channel = Channels.Unreliable)]
+        void RpcTeleport(Vector3 newPosition, uint newPackedRotation, bool isClientAuthority)
+        {
+            DoTeleport(newPosition, Compression.DecompressQuaternion(newPackedRotation));
+
+            // only send finished if is owner and is ClientAuthority on server 
+            if (hasAuthority && isClientAuthority)
+                CmdTeleportFinished();
+        }
+
+        /// <summary>
+        /// This RPC will be invoked on server after client finishes overriding the position.
+        /// </summary>
+        /// <param name="initialAuthority"></param>
+        [Command(channel = Channels.Unreliable)]
+        void CmdTeleportFinished()
+        {
+            if (clientAuthorityBeforeTeleport)
+            {
+                clientAuthority = true;
+
+                // reset value so doesn't effect future calls, see note in ServerTeleport
+                clientAuthorityBeforeTeleport = false;
+            }
+            else
+            {
+                Debug.LogWarning("Client called TeleportFinished when clientAuthority was false on server", this);
+            }
+        }
+
+        #endregion
+
+        #region Debug Gizmos
+
+        // draw the data points for easier debugging
+        void OnDrawGizmos()
+        {
+            // draw start and goal points and a line between them
+            if (start.localPosition != goal.localPosition)
+            {
+                DrawDataPointGizmo(start, Color.yellow);
+                DrawDataPointGizmo(goal, Color.green);
+                DrawLineBetweenDataPoints(start, goal, Color.cyan);
+            }
+        }
+
+        static void DrawDataPointGizmo(DataPoint data, Color color)
+        {
+            // use a little offset because transform.localPosition might be in the ground in many cases
+            Vector3 offset = Vector3.up * 0.01f;
+
+            // draw position
+            Gizmos.color = color;
+            Gizmos.DrawSphere(data.localPosition + offset, 0.5f);
+
+            // draw forward and up like unity move tool
+            Gizmos.color = Color.blue;
+            Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.forward);
+            Gizmos.color = Color.green;
+            Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.up);
+        }
+
+        static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color)
+        {
+            Gizmos.color = color;
+            Gizmos.DrawLine(data1.localPosition, data2.localPosition);
+        }
+
+        #endregion
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ea7c690c4fbf8c4439726f4c62eda6d3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 18 - 0
Assets/Mirror/Components/Experimental/NetworkTransformChild.cs

@@ -0,0 +1,18 @@
+using UnityEngine;
+
+namespace Mirror.Experimental
+{
+    /// <summary>
+    /// A component to synchronize the position of child transforms of networked objects.
+    /// <para>There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the received values.</para>
+    /// </summary>
+    [AddComponentMenu("Network/Experimental/NetworkTransformChildExperimentalExperimental")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-transform-child")]
+    public class NetworkTransformChild : NetworkTransformBase
+    {
+        [Header("Target")]
+        public Transform target;
+
+        protected override Transform targetTransform => target;
+    }
+}

+ 11 - 0
Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f65214da13a861f4a8ae309d3daea1c6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 112 - 0
Assets/Mirror/Components/GUIConsole.cs

@@ -0,0 +1,112 @@
+// People should be able to see and report errors to the developer very easily.
+//
+// Unity's Developer Console only works in development builds and it only shows
+// errors. This class provides a console that works in all builds and also shows
+// log and warnings in development builds.
+//
+// Note: we don't include the stack trace, because that can also be grabbed from
+// the log files if needed.
+//
+// Note: there is no 'hide' button because we DO want people to see those errors
+// and report them back to us.
+//
+// Note: normal Debug.Log messages can be shown by building in Debug/Development
+//       mode.
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Mirror
+{
+    struct LogEntry
+    {
+        public string message;
+        public LogType type;
+
+        public LogEntry(string message, LogType type)
+        {
+            this.message = message;
+            this.type = type;
+        }
+    }
+
+    public class GUIConsole : MonoBehaviour
+    {
+        public int height = 150;
+
+        // only keep the recent 'n' entries. otherwise memory would grow forever
+        // and drawing would get slower and slower.
+        public int maxLogCount = 50;
+
+        // log as queue so we can remove the first entry easily
+        Queue<LogEntry> log = new Queue<LogEntry>();
+
+        // hotkey to show/hide at runtime for easier debugging
+        // (sometimes we need to temporarily hide/show it)
+        // => F12 makes sense. nobody can find ^ in other games.
+        public KeyCode hotKey = KeyCode.F12;
+
+        // GUI
+        bool visible;
+        Vector2 scroll = Vector2.zero;
+
+        void Awake()
+        {
+            Application.logMessageReceived += OnLog;
+        }
+
+        // OnLog logs everything, even Debug.Log messages in release builds
+        // => this makes a lot of things easier. e.g. addon initialization logs.
+        // => it's really better to have than not to have those
+        void OnLog(string message, string stackTrace, LogType type)
+        {
+            // is this important?
+            bool isImportant = type == LogType.Error || type == LogType.Exception;
+
+            // use stack trace only if important
+            // (otherwise users would have to find and search the log file.
+            //  seeing it in the console directly is way easier to deal with.)
+            // => only add \n if stack trace is available (only in debug builds)
+            if (isImportant && !string.IsNullOrWhiteSpace(stackTrace))
+                message += "\n" + stackTrace;
+
+            // add to queue
+            log.Enqueue(new LogEntry(message, type));
+
+            // respect max entries
+            if (log.Count > maxLogCount)
+                log.Dequeue();
+
+            // become visible if it was important
+            // (no need to become visible for regular log. let the user decide.)
+            if (isImportant)
+                visible = true;
+
+            // auto scroll
+            scroll.y = float.MaxValue;
+        }
+
+        void Update()
+        {
+            if (Input.GetKeyDown(hotKey))
+                visible = !visible;
+        }
+
+        void OnGUI()
+        {
+            if (!visible) return;
+
+            scroll = GUILayout.BeginScrollView(scroll, "Box", GUILayout.Width(Screen.width), GUILayout.Height(height));
+            foreach (LogEntry entry in log)
+            {
+                if (entry.type == LogType.Error || entry.type == LogType.Exception)
+                    GUI.color = Color.red;
+                else if (entry.type == LogType.Warning)
+                    GUI.color = Color.yellow;
+
+                GUILayout.Label(entry.message);
+                GUI.color = Color.white;
+            }
+            GUILayout.EndScrollView();
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/GUIConsole.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9021b6cc314944290986ab6feb48db79
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Components/InterestManagement.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c66f27e006ab94253b39a55a3b213651
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 3 - 0
Assets/Mirror/Components/InterestManagement/Distance.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fa4cbc6b9c584db4971985cb9f369077
+timeCreated: 1613110605

+ 69 - 0
Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs

@@ -0,0 +1,69 @@
+// straight forward Vector3.Distance based interest management.
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+    public class DistanceInterestManagement : InterestManagement
+    {
+        [Tooltip("The maximum range that objects will be visible at. Add DistanceInterestManagementCustomRange onto NetworkIdentities for custom ranges.")]
+        public int visRange = 10;
+
+        [Tooltip("Rebuild all every 'rebuildInterval' seconds.")]
+        public float rebuildInterval = 1;
+        double lastRebuildTime;
+
+        // helper function to get vis range for a given object, or default.
+        int GetVisRange(NetworkIdentity identity)
+        {
+            DistanceInterestManagementCustomRange custom = identity.GetComponent<DistanceInterestManagementCustomRange>();
+            return custom != null ? custom.visRange : visRange;
+        }
+
+        public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection newObserver)
+        {
+            int range = GetVisRange(identity);
+            return Vector3.Distance(identity.transform.position, newObserver.identity.transform.position) < range;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnection> newObservers, bool initialize)
+        {
+            // cache range and .transform because both call GetComponent.
+            int range = GetVisRange(identity);
+            Vector3 position = identity.transform.position;
+
+            // brute force distance check
+            // -> only player connections can be observers, so it's enough if we
+            //    go through all connections instead of all spawned identities.
+            // -> compared to UNET's sphere cast checking, this one is orders of
+            //    magnitude faster. if we have 10k monsters and run a sphere
+            //    cast 10k times, we will see a noticeable lag even with physics
+            //    layers. but checking to every connection is fast.
+            foreach (NetworkConnectionToClient conn in NetworkServer.connections.Values)
+            {
+                // authenticated and joined world with a player?
+                if (conn != null && conn.isAuthenticated && conn.identity != null)
+                {
+                    // check distance
+                    if (Vector3.Distance(conn.identity.transform.position, position) < range)
+                    {
+                        newObservers.Add(conn);
+                    }
+                }
+            }
+        }
+
+        void Update()
+        {
+            // only on server
+            if (!NetworkServer.active) return;
+
+            // rebuild all spawned NetworkIdentity's observers every interval
+            if (NetworkTime.localTime >= lastRebuildTime + rebuildInterval)
+            {
+                RebuildAll();
+                lastRebuildTime = NetworkTime.localTime;
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8f60becab051427fbdd3c8ac9ab4712b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 14 - 0
Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs

@@ -0,0 +1,14 @@
+// add this to NetworkIdentities for custom range if needed.
+// only works with DistanceInterestManagement.
+using UnityEngine;
+
+namespace Mirror
+{
+    [RequireComponent(typeof(NetworkIdentity))]
+    [DisallowMultipleComponent]
+    public class DistanceInterestManagementCustomRange : MonoBehaviour
+    {
+        [Tooltip("The maximum range that objects will be visible at.")]
+        public int visRange = 20;
+    }
+}

+ 11 - 0
Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b2e242ee38a14076a39934172a19079b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 3 - 0
Assets/Mirror/Components/InterestManagement/Match.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5eca5245ae6bb460e9a92f7e14d5493a
+timeCreated: 1622649517

+ 125 - 0
Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs

@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+
+namespace Mirror
+{
+    public class MatchInterestManagement : InterestManagement
+    {
+        readonly Dictionary<Guid, HashSet<NetworkIdentity>> matchObjects =
+            new Dictionary<Guid, HashSet<NetworkIdentity>>();
+
+        readonly Dictionary<NetworkIdentity, Guid> lastObjectMatch =
+            new Dictionary<NetworkIdentity, Guid>();
+
+        HashSet<Guid> dirtyMatches = new HashSet<Guid>();
+
+        public override void OnSpawned(NetworkIdentity identity)
+        {
+            Guid currentMatch = identity.GetComponent<NetworkMatch>().matchId;
+            lastObjectMatch[identity] = currentMatch;
+            
+            // Guid.Empty is never a valid matchId...do not add to matchObjects collection
+            if (currentMatch == Guid.Empty) return;
+            
+            // Debug.Log($"MatchInterestManagement.OnSpawned({identity.name}) currentMatch: {currentMatch}");
+            if (!matchObjects.TryGetValue(currentMatch, out HashSet<NetworkIdentity> objects))
+            {
+                objects = new HashSet<NetworkIdentity>();
+                matchObjects.Add(currentMatch, objects);
+            }
+
+            objects.Add(identity);
+        }
+
+        public override void OnDestroyed(NetworkIdentity identity)
+        {
+            Guid currentMatch = lastObjectMatch[identity];
+            lastObjectMatch.Remove(identity);
+            if (currentMatch != Guid.Empty && matchObjects.TryGetValue(currentMatch, out HashSet<NetworkIdentity> objects) && objects.Remove(identity))
+                RebuildMatchObservers(currentMatch);
+        }
+
+        void Update()
+        {
+            // only on server
+            if (!NetworkServer.active) return;
+
+            // for each spawned:
+            //   if match changed:
+            //     add previous to dirty
+            //     add new to dirty
+            foreach (NetworkIdentity netIdentity in NetworkIdentity.spawned.Values)
+            {
+                Guid currentMatch = lastObjectMatch[netIdentity];
+                Guid newMatch = netIdentity.GetComponent<NetworkMatch>().matchId;
+                if (newMatch == currentMatch) continue;
+
+                // Mark new/old scenes as dirty so they get rebuilt
+                // Guid.Empty is never a valid matchId
+                if (currentMatch != Guid.Empty)
+                    dirtyMatches.Add(currentMatch);
+                dirtyMatches.Add(newMatch);
+
+                // This object is in a new match so observers in the prior match
+                // and the new scene need to rebuild their respective observers lists.
+
+                // Remove this object from the hashset of the match it just left
+                // Guid.Empty is never a valid matchId
+                if (currentMatch != Guid.Empty)
+                    matchObjects[currentMatch].Remove(netIdentity);
+
+                // Set this to the new match this object just entered
+                lastObjectMatch[netIdentity] = newMatch;
+                
+                // Guid.Empty is never a valid matchId...do not add to matchObjects collection
+                if (newMatch == Guid.Empty) continue;
+                
+
+                // Make sure this new match is in the dictionary
+                if (!matchObjects.ContainsKey(newMatch))
+                    matchObjects.Add(newMatch, new HashSet<NetworkIdentity>());
+
+                // Add this object to the hashset of the new match
+                matchObjects[newMatch].Add(netIdentity);
+            }
+
+            // rebuild all dirty matchs
+            foreach (Guid dirtyMatch in dirtyMatches)
+            {
+                RebuildMatchObservers(dirtyMatch);
+            }
+
+            dirtyMatches.Clear();
+        }
+
+        void RebuildMatchObservers(Guid matchId)
+        {
+            foreach (NetworkIdentity netIdentity in matchObjects[matchId])
+                if (netIdentity != null)
+                    NetworkServer.RebuildObservers(netIdentity, false);
+        }
+
+        public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection newObserver)
+        {
+            return identity.GetComponent<NetworkMatch>().matchId ==
+                   newObserver.identity.GetComponent<NetworkMatch>().matchId;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnection> newObservers,
+            bool initialize)
+        {
+            Guid matchId = identity.GetComponent<NetworkMatch>().matchId;
+            
+            // Guid.Empty is never a valid matchId
+            if (matchId == Guid.Empty) return;
+            
+            if (!matchObjects.TryGetValue(matchId, out HashSet<NetworkIdentity> objects))
+                return;
+
+            // Add everything in the hashset for this object's current match
+            foreach (NetworkIdentity networkIdentity in objects)
+                if (networkIdentity != null && networkIdentity.connectionToClient != null)
+                    newObservers.Add(networkIdentity.connectionToClient);
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d09f5c8bf2f4747b7a9284ef5d9ce2a7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 3 - 0
Assets/Mirror/Components/InterestManagement/Scene.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7655d309a46a4bd4860edf964228b3f6
+timeCreated: 1622649517

+ 109 - 0
Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs

@@ -0,0 +1,109 @@
+using System.Collections.Generic;
+using UnityEngine.SceneManagement;
+
+namespace Mirror
+{
+    public class SceneInterestManagement : InterestManagement
+    {
+        // Use Scene instead of string scene.name because when additively
+        // loading multiples of a subscene the name won't be unique
+        readonly Dictionary<Scene, HashSet<NetworkIdentity>> sceneObjects =
+            new Dictionary<Scene, HashSet<NetworkIdentity>>();
+
+        readonly Dictionary<NetworkIdentity, Scene> lastObjectScene =
+            new Dictionary<NetworkIdentity, Scene>();
+
+        HashSet<Scene> dirtyScenes = new HashSet<Scene>();
+
+        public override void OnSpawned(NetworkIdentity identity)
+        {
+            Scene currentScene = identity.gameObject.scene;
+            lastObjectScene[identity] = currentScene;
+            // Debug.Log($"SceneInterestManagement.OnSpawned({identity.name}) currentScene: {currentScene}");
+            if (!sceneObjects.TryGetValue(currentScene, out HashSet<NetworkIdentity> objects))
+            {
+                objects = new HashSet<NetworkIdentity>();
+                sceneObjects.Add(currentScene, objects);
+            }
+
+            objects.Add(identity);
+        }
+
+        public override void OnDestroyed(NetworkIdentity identity)
+        {
+            Scene currentScene = lastObjectScene[identity];
+            lastObjectScene.Remove(identity);
+            if (sceneObjects.TryGetValue(currentScene, out HashSet<NetworkIdentity> objects) && objects.Remove(identity))
+                RebuildSceneObservers(currentScene);
+        }
+
+        void Update()
+        {
+            // only on server
+            if (!NetworkServer.active) return;
+
+            // for each spawned:
+            //   if scene changed:
+            //     add previous to dirty
+            //     add new to dirty
+            foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
+            {
+                Scene currentScene = lastObjectScene[identity];
+                Scene newScene = identity.gameObject.scene;
+                if (newScene == currentScene) continue;
+
+                // Mark new/old scenes as dirty so they get rebuilt
+                dirtyScenes.Add(currentScene);
+                dirtyScenes.Add(newScene);
+
+                // This object is in a new scene so observers in the prior scene
+                // and the new scene need to rebuild their respective observers lists.
+
+                // Remove this object from the hashset of the scene it just left
+                sceneObjects[currentScene].Remove(identity);
+
+                // Set this to the new scene this object just entered
+                lastObjectScene[identity] = newScene;
+
+                // Make sure this new scene is in the dictionary
+                if (!sceneObjects.ContainsKey(newScene))
+                    sceneObjects.Add(newScene, new HashSet<NetworkIdentity>());
+
+                // Add this object to the hashset of the new scene
+                sceneObjects[newScene].Add(identity);
+            }
+
+            // rebuild all dirty scenes
+            foreach (Scene dirtyScene in dirtyScenes)
+            {
+                RebuildSceneObservers(dirtyScene);
+            }
+
+            dirtyScenes.Clear();
+        }
+
+        void RebuildSceneObservers(Scene scene)
+        {
+            foreach (NetworkIdentity netIdentity in sceneObjects[scene])
+                if (netIdentity != null)
+                    NetworkServer.RebuildObservers(netIdentity, false);
+        }
+
+        public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection newObserver)
+        {
+            return identity.gameObject.scene == newObserver.identity.gameObject.scene;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnection> newObservers,
+            bool initialize)
+        {
+            if (!sceneObjects.TryGetValue(identity.gameObject.scene, out HashSet<NetworkIdentity> objects))
+                return;
+
+            // Add everything in the hashset for this object's current scene
+            foreach (NetworkIdentity networkIdentity in objects)
+                if (networkIdentity != null && networkIdentity.connectionToClient != null)
+                    newObservers.Add(networkIdentity.connectionToClient);
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b979f26c95d34324ba005bfacfa9c4fc
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Some files were not shown because too many files changed in this diff