sewmina7@gmail.com há 1 ano atrás
commit
da3b6cf083
100 ficheiros alterados com 6627 adições e 0 exclusões
  1. 72 0
      .gitignore
  2. 55 0
      .vscode/settings.json
  3. 78 0
      Assets/Client.mat
  4. 8 0
      Assets/Client.mat.meta
  5. 8 0
      Assets/Ignorance.meta
  6. 8 0
      Assets/Ignorance/Demo.meta
  7. 8 0
      Assets/Ignorance/Demo/Basic.meta
  8. 732 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity
  9. 7 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnorance.unity.meta
  10. 63 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting
  11. 8 0
      Assets/Ignorance/Demo/Basic/BasicWithIgnoranceSettings.lighting.meta
  12. 8 0
      Assets/Ignorance/Demo/PongChamp.meta
  13. 188 0
      Assets/Ignorance/Demo/PongChamp/AtariBall.prefab
  14. 8 0
      Assets/Ignorance/Demo/PongChamp/AtariBall.prefab.meta
  15. 190 0
      Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab
  16. 8 0
      Assets/Ignorance/Demo/PongChamp/AtariRacket.prefab.meta
  17. 11 0
      Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D
  18. 8 0
      Assets/Ignorance/Demo/PongChamp/BallMaterial 1.physicsMaterial2D.meta
  19. 1164 0
      Assets/Ignorance/Demo/PongChamp/Demo.unity
  20. 7 0
      Assets/Ignorance/Demo/PongChamp/Demo.unity.meta
  21. 8 0
      Assets/Ignorance/Demo/PongChamp/Scripts.meta
  22. 67 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs
  23. 12 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongBall.cs.meta
  24. 25 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs
  25. 12 0
      Assets/Ignorance/Demo/PongChamp/Scripts/AtariPongRacket.cs.meta
  26. 49 0
      Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs
  27. 11 0
      Assets/Ignorance/Demo/PongChamp/Scripts/OnlineTimer.cs.meta
  28. 180 0
      Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab
  29. 7 0
      Assets/Ignorance/Demo/PongChamp/TenryuuBall.prefab.meta
  30. 8 0
      Assets/Ignorance/Demo/PongChamp/Textures.meta
  31. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png
  32. 128 0
      Assets/Ignorance/Demo/PongChamp/Textures/PoutRyuu.png.meta
  33. 8 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites.meta
  34. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png
  35. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Ball.png.meta
  36. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png
  37. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/DottedLine.png.meta
  38. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png
  39. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/Racket.png.meta
  40. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png
  41. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallHorizontal.png.meta
  42. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png
  43. 88 0
      Assets/Ignorance/Demo/PongChamp/Textures/Sprites/WallVertical.png.meta
  44. BIN
      Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png
  45. 121 0
      Assets/Ignorance/Demo/PongChamp/Textures/pogchamp.png.meta
  46. 8 0
      Assets/Mirror.meta
  47. 8 0
      Assets/Mirror/Authenticators.meta
  48. 192 0
      Assets/Mirror/Authenticators/BasicAuthenticator.cs
  49. 11 0
      Assets/Mirror/Authenticators/BasicAuthenticator.cs.meta
  50. 129 0
      Assets/Mirror/Authenticators/DeviceAuthenticator.cs
  51. 11 0
      Assets/Mirror/Authenticators/DeviceAuthenticator.cs.meta
  52. 14 0
      Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef
  53. 7 0
      Assets/Mirror/Authenticators/Mirror.Authenticators.asmdef.meta
  54. 70 0
      Assets/Mirror/Authenticators/TimeoutAuthenticator.cs
  55. 11 0
      Assets/Mirror/Authenticators/TimeoutAuthenticator.cs.meta
  56. 8 0
      Assets/Mirror/CompilerSymbols.meta
  57. 14 0
      Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef
  58. 7 0
      Assets/Mirror/CompilerSymbols/Mirror.CompilerSymbols.asmdef.meta
  59. 60 0
      Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs
  60. 11 0
      Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs.meta
  61. 8 0
      Assets/Mirror/Components.meta
  62. 12 0
      Assets/Mirror/Components/AssemblyInfo.cs
  63. 11 0
      Assets/Mirror/Components/AssemblyInfo.cs.meta
  64. 8 0
      Assets/Mirror/Components/Discovery.meta
  65. 111 0
      Assets/Mirror/Components/Discovery/NetworkDiscovery.cs
  66. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscovery.cs.meta
  67. 449 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs
  68. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs.meta
  69. 132 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs
  70. 11 0
      Assets/Mirror/Components/Discovery/NetworkDiscoveryHUD.cs.meta
  71. 4 0
      Assets/Mirror/Components/Discovery/ServerRequest.cs
  72. 11 0
      Assets/Mirror/Components/Discovery/ServerRequest.cs.meta
  73. 18 0
      Assets/Mirror/Components/Discovery/ServerResponse.cs
  74. 11 0
      Assets/Mirror/Components/Discovery/ServerResponse.cs.meta
  75. 8 0
      Assets/Mirror/Components/Experimental.meta
  76. 93 0
      Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs
  77. 11 0
      Assets/Mirror/Components/Experimental/NetworkLerpRigidbody.cs.meta
  78. 361 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody.cs
  79. 11 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody.cs.meta
  80. 360 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs
  81. 11 0
      Assets/Mirror/Components/Experimental/NetworkRigidbody2D.cs.meta
  82. 115 0
      Assets/Mirror/Components/GUIConsole.cs
  83. 11 0
      Assets/Mirror/Components/GUIConsole.cs.meta
  84. 8 0
      Assets/Mirror/Components/InterestManagement.meta
  85. 3 0
      Assets/Mirror/Components/InterestManagement/Distance.meta
  86. 74 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs
  87. 11 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs.meta
  88. 15 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs
  89. 11 0
      Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagementCustomRange.cs.meta
  90. 3 0
      Assets/Mirror/Components/InterestManagement/Match.meta
  91. 160 0
      Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs
  92. 11 0
      Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs.meta
  93. 15 0
      Assets/Mirror/Components/InterestManagement/Match/NetworkMatch.cs
  94. 11 0
      Assets/Mirror/Components/InterestManagement/Match/NetworkMatch.cs.meta
  95. 3 0
      Assets/Mirror/Components/InterestManagement/Scene.meta
  96. 108 0
      Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs
  97. 11 0
      Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs.meta
  98. 3 0
      Assets/Mirror/Components/InterestManagement/SpatialHashing.meta
  99. 104 0
      Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs
  100. 11 0
      Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs.meta

+ 72 - 0
.gitignore

@@ -0,0 +1,72 @@
+# This .gitignore file should be placed at the root of your Unity project directory
+#
+# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
+#
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+
+# MemoryCaptures can get excessive in size.
+# They also could contain extremely sensitive data
+/[Mm]emoryCaptures/
+
+# Recordings can get excessive in size
+/[Rr]ecordings/
+
+# Uncomment this line if you wish to ignore the asset store tools plugin
+# /[Aa]ssets/AssetStoreTools*
+
+# Autogenerated Jetbrains Rider plugin
+/[Aa]ssets/Plugins/Editor/JetBrains*
+
+# Visual Studio cache directory
+.vs/
+
+# Gradle cache directory
+.gradle/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.mdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+*.mdb.meta
+
+# Unity3D generated file on crash reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.aab
+*.unitypackage
+*.app
+
+# Crashlytics generated file
+crashlytics-build.properties
+
+# Packed Addressables
+/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
+
+# Temporary auto-generated Android Assets
+/[Aa]ssets/[Ss]treamingAssets/aa.meta
+/[Aa]ssets/[Ss]treamingAssets/aa/*

+ 55 - 0
.vscode/settings.json

@@ -0,0 +1,55 @@
+{
+    "files.exclude":
+    {
+        "**/.DS_Store":true,
+        "**/.git":true,
+        "**/.gitmodules":true,
+        "**/*.booproj":true,
+        "**/*.pidb":true,
+        "**/*.suo":true,
+        "**/*.user":true,
+        "**/*.userprefs":true,
+        "**/*.unityproj":true,
+        "**/*.dll":true,
+        "**/*.exe":true,
+        "**/*.pdf":true,
+        "**/*.mid":true,
+        "**/*.midi":true,
+        "**/*.wav":true,
+        "**/*.gif":true,
+        "**/*.ico":true,
+        "**/*.jpg":true,
+        "**/*.jpeg":true,
+        "**/*.png":true,
+        "**/*.psd":true,
+        "**/*.tga":true,
+        "**/*.tif":true,
+        "**/*.tiff":true,
+        "**/*.3ds":true,
+        "**/*.3DS":true,
+        "**/*.fbx":true,
+        "**/*.FBX":true,
+        "**/*.lxo":true,
+        "**/*.LXO":true,
+        "**/*.ma":true,
+        "**/*.MA":true,
+        "**/*.obj":true,
+        "**/*.OBJ":true,
+        "**/*.asset":true,
+        "**/*.cubemap":true,
+        "**/*.flare":true,
+        "**/*.mat":true,
+        "**/*.meta":true,
+        "**/*.prefab":true,
+        "**/*.unity":true,
+        "build/":true,
+        "Build/":true,
+        "Library/":true,
+        "library/":true,
+        "obj/":true,
+        "Obj/":true,
+        "ProjectSettings/":true,
+        "temp/":true,
+        "Temp/":true
+    }
+}

+ 78 - 0
Assets/Client.mat

@@ -0,0 +1,78 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: Client
+  m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _BumpScale: 1
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 1, g: 0, b: 0, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+  m_BuildTextureStacks: []

+ 8 - 0
Assets/Client.mat.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4151b8cfb6c586f46b7c2235f8e37ffb
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 2100000
+  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: 

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

@@ -0,0 +1,732 @@
+%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: 0
+  serverBindsAll: 1
+  serverBindAddress: 
+  serverMaxPeerCapacity: 64
+  serverMaxNativeWaitTime: 1
+  serverStatusUpdateInterval: 0
+  clientMaxNativeWaitTime: 3
+  clientStatusUpdateInterval: 0
+  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: []
+--- !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: 266fac335be17a243af86e88de84766d, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  mainPanel: {fileID: 1712119861}
+  playersPanel: {fileID: 379082679}
+--- !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: 0
+  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: 

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

@@ -0,0 +1,192 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror.Authenticators
+{
+    [AddComponentMenu("Network/ Authenticators/Basic Authenticator")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-authenticators/basic-authenticator")]
+    public class BasicAuthenticator : NetworkAuthenticator
+    {
+        [Header("Server Credentials")]
+        public string serverUsername;
+        public string serverPassword;
+
+        [Header("Client Credentials")]
+        public string username;
+        public string password;
+
+        readonly HashSet<NetworkConnection> connectionsPendingDisconnect = new HashSet<NetworkConnection>();
+
+        #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 OnServerConnectInternal when a client needs to authenticate
+        /// </summary>
+        /// <param name="conn">Connection to client.</param>
+        public override void OnServerAuthenticate(NetworkConnectionToClient 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(NetworkConnectionToClient conn, AuthRequestMessage msg)
+        {
+            //Debug.Log($"Authentication Request: {msg.authUsername} {msg.authPassword}");
+
+            if (connectionsPendingDisconnect.Contains(conn)) return;
+
+            // check the credentials by calling your web server, database table, playfab api, or any method appropriate.
+            if (msg.authUsername == serverUsername && msg.authPassword == serverPassword)
+            {
+                // 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
+            {
+                connectionsPendingDisconnect.Add(conn);
+
+                // 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, 1f));
+            }
+        }
+
+        IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime)
+        {
+            yield return new WaitForSeconds(waitTime);
+
+            // Reject the unsuccessful authentication
+            ServerReject(conn);
+
+            yield return null;
+
+            // remove conn from pending connections
+            connectionsPendingDisconnect.Remove(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>(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 OnClientConnectInternal when a client needs to authenticate
+        /// </summary>
+        public override void OnClientAuthenticate()
+        {
+            AuthRequestMessage authRequestMessage = new AuthRequestMessage
+            {
+                authUsername = username,
+                authPassword = password
+            };
+
+            NetworkClient.connection.Send(authRequestMessage);
+        }
+
+        /// <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.Log($"Authentication Response: {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: 

+ 129 - 0
Assets/Mirror/Authenticators/DeviceAuthenticator.cs

@@ -0,0 +1,129 @@
+using System;
+using UnityEngine;
+
+namespace Mirror.Authenticators
+{
+    /// <summary>
+    /// An authenticator that identifies the user by their device.
+    /// <para>A GUID is used as a fallback when the platform doesn't support SystemInfo.deviceUniqueIdentifier.</para>
+    /// <para>Note: deviceUniqueIdentifier can be spoofed, so security is not guaranteed.</para>
+    /// <para>See https://docs.unity3d.com/ScriptReference/SystemInfo-deviceUniqueIdentifier.html for details.</para>
+    /// </summary>
+    [AddComponentMenu("Network/ Authenticators/Device Authenticator")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-authenticators/device-authenticator")]
+    public class DeviceAuthenticator : NetworkAuthenticator
+    {
+        #region Messages
+
+        public struct AuthRequestMessage : NetworkMessage
+        {
+            public string clientDeviceID;
+        }
+
+        public struct AuthResponseMessage : NetworkMessage { }
+
+        #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 OnServerConnectInternal when a client needs to authenticate
+        /// </summary>
+        /// <param name="conn">Connection to client.</param>
+        public override void OnServerAuthenticate(NetworkConnectionToClient conn)
+        {
+            // do nothing, wait for client to send his id
+        }
+
+        void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMessage msg)
+        {
+            Debug.Log($"connection {conn.connectionId} authenticated with id {msg.clientDeviceID}");
+
+            // Store the device id for later reference, e.g. when spawning the player
+            conn.authenticationData = msg.clientDeviceID;
+
+            // Send a response to client telling it to proceed as authenticated
+            conn.Send(new AuthResponseMessage());
+
+            // Accept the successful authentication
+            ServerAccept(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>(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 OnClientConnectInternal when a client needs to authenticate
+        /// </summary>
+        public override void OnClientAuthenticate()
+        {
+            string deviceUniqueIdentifier = SystemInfo.deviceUniqueIdentifier;
+
+            // Not all platforms support this, so we use a GUID instead
+            if (deviceUniqueIdentifier == SystemInfo.unsupportedIdentifier)
+            {
+                // Get the value from PlayerPrefs if it exists, new GUID if it doesn't
+                deviceUniqueIdentifier = PlayerPrefs.GetString("deviceUniqueIdentifier", Guid.NewGuid().ToString());
+
+                // Store the deviceUniqueIdentifier to PlayerPrefs (in case we just made a new GUID)
+                PlayerPrefs.SetString("deviceUniqueIdentifier", deviceUniqueIdentifier);
+            }
+
+            // send the deviceUniqueIdentifier to the server
+            NetworkClient.connection.Send(new AuthRequestMessage { clientDeviceID = deviceUniqueIdentifier } );
+        }
+
+        /// <summary>
+        /// Called on client when the server's AuthResponseMessage arrives
+        /// </summary>
+        /// <param name="msg">The message payload</param>
+        public void OnAuthResponseMessage(AuthResponseMessage msg)
+        {
+            Debug.Log("Authentication Success");
+            ClientAccept();
+        }
+
+        #endregion
+    }
+}

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

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 60960a6ba81a842deb2fdcdc93788242
+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/Timeout Authenticator")]
+    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(OnClientAuthenticated.Invoke);
+        }
+
+        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(NetworkConnectionToClient 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.LogError($"Authentication Timeout - Disconnecting {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: 

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

@@ -0,0 +1,60 @@
+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",
+                "MIRROR_46_0_OR_NEWER",
+                "MIRROR_47_0_OR_NEWER",
+                "MIRROR_53_0_OR_NEWER",
+                "MIRROR_55_0_OR_NEWER",
+                "MIRROR_57_0_OR_NEWER",
+                "MIRROR_58_0_OR_NEWER",
+                "MIRROR_65_0_OR_NEWER",
+                "MIRROR_66_0_OR_NEWER",
+                "MIRROR_2022_9_OR_NEWER",
+                "MIRROR_2022_10_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: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Mirror/Components.meta

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

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

@@ -0,0 +1,12 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Mirror.Tests.Common")]
+[assembly: InternalsVisibleTo("Mirror.Tests")]
+// need to use Unity.*.CodeGen assembly name to import Unity.CompilationPipeline
+// for ILPostProcessor tests.
+[assembly: InternalsVisibleTo("Unity.Mirror.Tests.CodeGen")]
+[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: 

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

@@ -0,0 +1,111 @@
+using System;
+using System.Net;
+using UnityEngine;
+using UnityEngine.Events;
+
+namespace Mirror.Discovery
+{
+    [Serializable]
+    public class ServerFoundUnityEvent : UnityEvent<ServerResponse> {};
+
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/Network Discovery")]
+    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.active;
+
+            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: 

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

@@ -0,0 +1,449 @@
+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;
+        
+        // broadcast address needs to be configurable on iOS:
+        // https://github.com/vis2k/Mirror/pull/3255
+        public string BroadcastAddress = "";
+
+        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()
+        {
+            EndpMulticastLock();
+            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()
+        {
+            BeginMulticastLock();
+            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 (NetworkReaderPooled networkReader = NetworkReaderPool.Get(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 (NetworkWriterPooled writer = NetworkWriterPool.Get())
+            {
+                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);
+
+        // Android Multicast fix: https://github.com/vis2k/Mirror/pull/2887
+#if UNITY_ANDROID
+        AndroidJavaObject multicastLock;
+        bool hasMulticastLock;
+#endif
+        void BeginMulticastLock()
+		{
+#if UNITY_ANDROID
+            if (hasMulticastLock) return;
+                
+            if (Application.platform == RuntimePlatform.Android)
+            {
+                using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
+                {
+                    using (var wifiManager = activity.Call<AndroidJavaObject>("getSystemService", "wifi"))
+                    {
+                        multicastLock = wifiManager.Call<AndroidJavaObject>("createMulticastLock", "lock");
+                        multicastLock.Call("acquire");
+                        hasMulticastLock = true;
+                    }
+                }
+			}
+#endif
+        }
+
+        void EndpMulticastLock()
+        {
+#if UNITY_ANDROID
+            if (!hasMulticastLock) return;
+            
+            multicastLock?.Call("release");
+            hasMulticastLock = false;
+#endif
+        }
+
+#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 clientUpdClient to fix: 
+            // https://github.com/vis2k/Mirror/pull/2908
+            //
+            // If, you cancel discovery the clientUdpClient is set to null.
+            // However, nothing cancels ClientListenAsync. If we change the if(true)
+            // to check if the client is null. You can properly cancel the discovery, 
+            // and kill the listen thread.
+            //
+            // Prior to this fix, if you cancel the discovery search. It crashes the 
+            // thread, and is super noisy in the output. As well as causes issues on 
+            // the quest.
+            while (clientUdpClient != null)
+            {
+                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);
+            
+            if (!string.IsNullOrWhiteSpace(BroadcastAddress))
+            {
+                try
+                {
+                    endPoint = new IPEndPoint(IPAddress.Parse(BroadcastAddress), serverBroadcastListenPort);
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogException(ex);
+                }
+            }
+
+            using (NetworkWriterPooled writer = NetworkWriterPool.Get())
+            {
+                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 (NetworkReaderPooled networkReader = NetworkReaderPool.Get(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/Network Discovery HUD")]
+    [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/Network Lerp Rigidbody")]
+    [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;
+
+        double 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 && isOwned;
+
+        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()
+        {
+            double now = NetworkTime.localTime; // Unity 2019 doesn't have Time.timeAsDouble yet
+            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
+            target.position += 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/Network Rigidbody")]
+    [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 && isOwned;
+
+        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 (!isOwned)
+            {
+                Debug.LogWarning("SendToServer called without authority");
+                return;
+            }
+
+            SendVelocity();
+            SendRigidBodySettings();
+        }
+
+        [Client]
+        void SendVelocity()
+        {
+            double now = NetworkTime.localTime; // Unity 2019 doesn't have Time.timeAsDouble yet
+            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 double 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/Network Rigidbody 2D")]
+    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 && isOwned;
+
+        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 (!isOwned)
+            {
+                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: 

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

@@ -0,0 +1,115 @@
+// 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?
+            // => always show exceptions & errors
+            // => usually a good idea to show warnings too, otherwise it's too
+            //    easy to miss OnDeserialize warnings etc. in builds
+            bool isImportant = type == LogType.Error || type == LogType.Exception || type == LogType.Warning;
+
+            // 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

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

@@ -0,0 +1,74 @@
+// straight forward Vector3.Distance based interest management.
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+    [AddComponentMenu("Network/ Interest Management/ Distance/Distance Interest Management")]
+    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)
+        {
+            return identity.TryGetComponent(out DistanceInterestManagementCustomRange custom) ? custom.visRange : visRange;
+        }
+
+        [ServerCallback]
+        public override void Reset()
+        {
+            lastRebuildTime = 0D;
+        }
+
+        public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver)
+        {
+            int range = GetVisRange(identity);
+            return Vector3.Distance(identity.transform.position, newObserver.identity.transform.position) < range;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnectionToClient> newObservers)
+        {
+            // 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);
+                    }
+                }
+            }
+        }
+
+        // internal so we can update from tests
+        [ServerCallback]
+        internal void Update()
+        {
+            // 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: 

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

@@ -0,0 +1,15 @@
+// add this to NetworkIdentities for custom range if needed.
+// only works with DistanceInterestManagement.
+using UnityEngine;
+
+namespace Mirror
+{
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/ Interest Management/ Distance/Distance Custom Range")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")]
+    public class DistanceInterestManagementCustomRange : NetworkBehaviour
+    {
+        [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

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

@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+    [AddComponentMenu("Network/ Interest Management/ Match/Match Interest Management")]
+    public class MatchInterestManagement : InterestManagement
+    {
+        readonly Dictionary<Guid, HashSet<NetworkIdentity>> matchObjects =
+            new Dictionary<Guid, HashSet<NetworkIdentity>>();
+
+        readonly Dictionary<NetworkIdentity, Guid> lastObjectMatch =
+            new Dictionary<NetworkIdentity, Guid>();
+
+        readonly HashSet<Guid> dirtyMatches = new HashSet<Guid>();
+
+        public override void OnSpawned(NetworkIdentity identity)
+        {
+            if (!identity.TryGetComponent<NetworkMatch>(out NetworkMatch networkMatch))
+                return;
+
+            Guid currentMatch = 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)
+        {
+            lastObjectMatch.TryGetValue(identity, out Guid currentMatch);
+            lastObjectMatch.Remove(identity);
+            if (currentMatch != Guid.Empty && matchObjects.TryGetValue(currentMatch, out HashSet<NetworkIdentity> objects) && objects.Remove(identity))
+                RebuildMatchObservers(currentMatch);
+        }
+
+        // internal so we can update from tests
+        [ServerCallback]
+        internal void Update()
+        {
+            // for each spawned:
+            //   if match changed:
+            //     add previous to dirty
+            //     add new to dirty
+            foreach (NetworkIdentity netIdentity in NetworkServer.spawned.Values)
+            {
+                // Ignore objects that don't have a NetworkMatch component
+                if (!netIdentity.TryGetComponent<NetworkMatch>(out NetworkMatch networkMatch))
+                    continue;
+
+                Guid newMatch = networkMatch.matchId;
+                lastObjectMatch.TryGetValue(netIdentity, out Guid currentMatch);
+
+                // Guid.Empty is never a valid matchId
+                // Nothing to do if matchId hasn't changed
+                if (newMatch == Guid.Empty || newMatch == currentMatch)
+                    continue;
+
+                // Mark new/old matches as dirty so they get rebuilt
+                UpdateDirtyMatches(newMatch, currentMatch);
+
+                // This object is in a new match so observers in the prior match
+                // and the new match need to rebuild their respective observers lists.
+                UpdateMatchObjects(netIdentity, newMatch, currentMatch);
+            }
+
+            // rebuild all dirty matches
+            foreach (Guid dirtyMatch in dirtyMatches)
+                RebuildMatchObservers(dirtyMatch);
+
+            dirtyMatches.Clear();
+        }
+
+        void UpdateDirtyMatches(Guid newMatch, Guid currentMatch)
+        {
+            // Guid.Empty is never a valid matchId
+            if (currentMatch != Guid.Empty)
+                dirtyMatches.Add(currentMatch);
+
+            dirtyMatches.Add(newMatch);
+        }
+
+        void UpdateMatchObjects(NetworkIdentity netIdentity, Guid newMatch, Guid currentMatch)
+        {
+            // 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;
+
+            // 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);
+        }
+
+        void RebuildMatchObservers(Guid matchId)
+        {
+            foreach (NetworkIdentity netIdentity in matchObjects[matchId])
+                if (netIdentity != null)
+                    NetworkServer.RebuildObservers(netIdentity, false);
+        }
+
+        public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver)
+        {
+            // Never observed if no NetworkMatch component
+            if (!identity.TryGetComponent<NetworkMatch>(out NetworkMatch identityNetworkMatch))
+                return false;
+
+            // Guid.Empty is never a valid matchId
+            if (identityNetworkMatch.matchId == Guid.Empty)
+                return false;
+
+            // Never observed if no NetworkMatch component
+            if (!newObserver.identity.TryGetComponent<NetworkMatch>(out NetworkMatch newObserverNetworkMatch))
+                return false;
+
+            // Guid.Empty is never a valid matchId
+            if (newObserverNetworkMatch.matchId == Guid.Empty)
+                return false;
+
+            return identityNetworkMatch.matchId == newObserverNetworkMatch.matchId;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnectionToClient> newObservers)
+        {
+            if (!identity.TryGetComponent<NetworkMatch>(out NetworkMatch networkMatch))
+                return;
+
+            Guid matchId = 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: 

+ 15 - 0
Assets/Mirror/Components/InterestManagement/Match/NetworkMatch.cs

@@ -0,0 +1,15 @@
+// simple component that holds match information
+using System;
+using UnityEngine;
+
+namespace Mirror
+{
+    [DisallowMultipleComponent]
+    [AddComponentMenu("Network/ Interest Management/ Match/Network Match")]
+    [HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")]
+    public class NetworkMatch : NetworkBehaviour
+    {
+        ///<summary>Set this to the same value on all networked objects that belong to a given match</summary>
+        public Guid matchId;
+    }
+}

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

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5d17e718851449a6879986e45c458fb7
+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

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

@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Mirror
+{
+    [AddComponentMenu("Network/ Interest Management/ Scene/Scene Interest Management")]
+    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);
+        }
+
+        // internal so we can update from tests
+        [ServerCallback]
+        internal void Update()
+        {
+            // for each spawned:
+            //   if scene changed:
+            //     add previous to dirty
+            //     add new to dirty
+            foreach (NetworkIdentity identity in NetworkServer.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, NetworkConnectionToClient newObserver)
+        {
+            return identity.gameObject.scene == newObserver.identity.gameObject.scene;
+        }
+
+        public override void OnRebuildObservers(NetworkIdentity identity, HashSet<NetworkConnectionToClient> newObservers)
+        {
+            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: 

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

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cfa12b73503344d49b398b01bcb07967
+timeCreated: 1613110634

+ 104 - 0
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs

@@ -0,0 +1,104 @@
+// Grid2D from uMMORPG: get/set values of type T at any point
+// -> not named 'Grid' because Unity already has a Grid type. causes warnings.
+// -> struct to avoid memory indirection. it's accessed a lot.
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+    // struct to avoid memory indirection. it's accessed a lot.
+    public struct Grid2D<T>
+    {
+        // the grid
+        // note that we never remove old keys.
+        // => over time, HashSet<T>s will be allocated for every possible
+        //    grid position in the world
+        // => Clear() doesn't clear them so we don't constantly reallocate the
+        //    entries when populating the grid in every Update() call
+        // => makes the code a lot easier too
+        // => this is FINE because in the worst case, every grid position in the
+        //    game world is filled with a player anyway!
+        readonly Dictionary<Vector2Int, HashSet<T>> grid;
+
+        // cache a 9 neighbor grid of vector2 offsets so we can use them more easily
+        readonly Vector2Int[] neighbourOffsets;
+
+        public Grid2D(int initialCapacity)
+        {
+            grid = new Dictionary<Vector2Int, HashSet<T>>(initialCapacity);
+
+            neighbourOffsets = new[] {
+                Vector2Int.up,
+                Vector2Int.up + Vector2Int.left,
+                Vector2Int.up + Vector2Int.right,
+                Vector2Int.left,
+                Vector2Int.zero,
+                Vector2Int.right,
+                Vector2Int.down,
+                Vector2Int.down + Vector2Int.left,
+                Vector2Int.down + Vector2Int.right
+            };
+        }
+
+        // helper function so we can add an entry without worrying
+        public void Add(Vector2Int position, T value)
+        {
+            // initialize set in grid if it's not in there yet
+            if (!grid.TryGetValue(position, out HashSet<T> hashSet))
+            {
+                // each grid entry may hold hundreds of entities.
+                // let's create the HashSet with a large initial capacity
+                // in order to avoid resizing & allocations.
+#if !UNITY_2021_3_OR_NEWER
+                // Unity 2019 doesn't have "new HashSet(capacity)" yet
+                hashSet = new HashSet<T>();
+#else
+                hashSet = new HashSet<T>(128);
+#endif
+                grid[position] = hashSet;
+            }
+
+            // add to it
+            hashSet.Add(value);
+        }
+
+        // helper function to get set at position without worrying
+        // -> result is passed as parameter to avoid allocations
+        // -> result is not cleared before. this allows us to pass the HashSet from
+        //    GetWithNeighbours and avoid .UnionWith which is very expensive.
+        void GetAt(Vector2Int position, HashSet<T> result)
+        {
+            // return the set at position
+            if (grid.TryGetValue(position, out HashSet<T> hashSet))
+            {
+                foreach (T entry in hashSet)
+                    result.Add(entry);
+            }
+        }
+
+        // helper function to get at position and it's 8 neighbors without worrying
+        // -> result is passed as parameter to avoid allocations
+        public void GetWithNeighbours(Vector2Int position, HashSet<T> result)
+        {
+            // clear result first
+            result.Clear();
+
+            // add neighbours
+            foreach (Vector2Int offset in neighbourOffsets)
+                GetAt(position + offset, result);
+        }
+
+        // clear: clears the whole grid
+        // IMPORTANT: we already allocated HashSet<T>s and don't want to do
+        //            reallocate every single update when we rebuild the grid.
+        //            => so simply remove each position's entries, but keep
+        //               every position in there
+        //            => see 'grid' comments above!
+        //            => named ClearNonAlloc to make it more obvious!
+        public void ClearNonAlloc()
+        {
+            foreach (HashSet<T> hashSet in grid.Values)
+                hashSet.Clear();
+        }
+    }
+}

+ 11 - 0
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs.meta

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

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff