diff --git a/Assets/Mirror/Components/NetworkMatchChecker.cs b/Assets/Mirror/Components/NetworkMatchChecker.cs
new file mode 100644
index 000000000..f45085f8f
--- /dev/null
+++ b/Assets/Mirror/Components/NetworkMatchChecker.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+ ///
+ /// Component that controls visibility of networked objects based on match id.
+ /// Any object with this component on it will only be visible to other objects in the same match.
+ /// This would be used to isolate players to their respective matches within a single game server instance.
+ ///
+ [AddComponentMenu("Network/NetworkMatchChecker")]
+ [RequireComponent(typeof(NetworkIdentity))]
+ [HelpURL("https://mirror-networking.com/docs/Components/NetworkMatchChecker.html")]
+ public class NetworkMatchChecker : NetworkVisibility
+ {
+ static readonly Dictionary> matchPlayers = new Dictionary>();
+
+ Guid currentMatch = Guid.Empty;
+
+ [Header("Diagnostics")]
+ [SyncVar]
+ public string currentMatchDebug;
+
+ ///
+ /// Set this to the same value on all networked objects that belong to a given match
+ ///
+ public Guid matchId
+ {
+ get { return currentMatch; }
+ set
+ {
+ if (currentMatch == value) return;
+
+ // cache previous match so observers in that match can be rebuilt
+ Guid previousMatch = currentMatch;
+
+ // Set this to the new match this object just entered ...
+ currentMatch = value;
+ // ... and copy the string for the inspector because Unity can't show Guid directly
+ currentMatchDebug = currentMatch.ToString();
+
+ if (previousMatch != Guid.Empty)
+ {
+ // Remove this object from the hashset of the match it just left
+ matchPlayers[previousMatch].Remove(netIdentity);
+
+ // RebuildObservers of all NetworkIdentity's in the match this object just left
+ RebuildMatchObservers(previousMatch);
+ }
+
+ if (currentMatch != Guid.Empty)
+ {
+ // Make sure this new match is in the dictionary
+ if (!matchPlayers.ContainsKey(currentMatch))
+ matchPlayers.Add(currentMatch, new HashSet());
+
+ // Add this object to the hashset of the new match
+ matchPlayers[currentMatch].Add(netIdentity);
+
+ // RebuildObservers of all NetworkIdentity's in the match this object just entered
+ RebuildMatchObservers(currentMatch);
+ }
+ else
+ {
+ // Not in any match now...RebuildObservers will clear and add self
+ netIdentity.RebuildObservers(false);
+ }
+ }
+ }
+
+ public override void OnStartServer()
+ {
+ if (currentMatch == Guid.Empty) return;
+
+ if (!matchPlayers.ContainsKey(currentMatch))
+ matchPlayers.Add(currentMatch, new HashSet());
+
+ matchPlayers[currentMatch].Add(netIdentity);
+
+ // No need to rebuild anything here.
+ // identity.RebuildObservers is called right after this from NetworkServer.SpawnObject
+ }
+
+ void RebuildMatchObservers(Guid specificMatch)
+ {
+ foreach (NetworkIdentity networkIdentity in matchPlayers[specificMatch])
+ if (networkIdentity != null)
+ networkIdentity.RebuildObservers(false);
+ }
+
+ #region Observers
+
+ ///
+ /// Callback used by the visibility system to determine if an observer (player) can see this object.
+ /// If this function returns true, the network connection will be added as an observer.
+ ///
+ /// Network connection of a player.
+ /// True if the player can see this object.
+ public override bool OnCheckObserver(NetworkConnection conn)
+ {
+ // Not Visible if not in a match
+ if (matchId == Guid.Empty)
+ return false;
+
+ NetworkMatchChecker networkMatchChecker = conn.identity.GetComponent();
+
+ if (networkMatchChecker == null)
+ return false;
+
+ return networkMatchChecker.matchId == matchId;
+ }
+
+ ///
+ /// Callback used by the visibility system to (re)construct the set of observers that can see this object.
+ /// Implementations of this callback should add network connections of players that can see this object to the observers set.
+ ///
+ /// The new set of observers for this object.
+ /// True if the set of observers is being built for the first time.
+ public override void OnRebuildObservers(HashSet observers, bool initialize)
+ {
+ if (currentMatch == Guid.Empty) return;
+
+ foreach (NetworkIdentity networkIdentity in matchPlayers[currentMatch])
+ if (networkIdentity != null && networkIdentity.connectionToClient != null)
+ observers.Add(networkIdentity.connectionToClient);
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Mirror/Components/NetworkMatchChecker.cs.meta b/Assets/Mirror/Components/NetworkMatchChecker.cs.meta
new file mode 100644
index 000000000..7c7d6cfc4
--- /dev/null
+++ b/Assets/Mirror/Components/NetworkMatchChecker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1020a74962faada4b807ac5dc053a4cf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs b/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs
new file mode 100644
index 000000000..d030c4ae3
--- /dev/null
+++ b/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace Mirror.Tests
+{
+ public class NetworkMatchCheckerTest
+ {
+ private GameObject player1;
+ private GameObject player2;
+ private GameObject player3;
+ private NetworkMatchChecker player1MatchChecker;
+ private NetworkMatchChecker player2MatchChecker;
+ private NetworkConnection player1Connection;
+ private NetworkConnection player2Connection;
+ private NetworkConnection player3Connection;
+ private GameObject transportGO;
+ static int nextConnectionId;
+ private Dictionary> matchPlayers;
+
+ [SetUp]
+ public void Setup()
+ {
+ transportGO = new GameObject("transportGO");
+ Transport.activeTransport = transportGO.AddComponent();
+
+ player1 = new GameObject("TestPlayer1", typeof(NetworkIdentity), typeof(NetworkMatchChecker));
+ player2 = new GameObject("TestPlayer2", typeof(NetworkIdentity), typeof(NetworkMatchChecker));
+ player3 = new GameObject("TestPlayer3", typeof(NetworkIdentity));
+
+ player1MatchChecker = player1.GetComponent();
+ player2MatchChecker = player2.GetComponent();
+
+
+ player1Connection = CreateNetworkConnection(player1);
+ player2Connection = CreateNetworkConnection(player2);
+ player3Connection = CreateNetworkConnection(player3);
+ Dictionary> g = GetMatchPlayersDictionary();
+ matchPlayers = g;
+ }
+
+ private static Dictionary> GetMatchPlayersDictionary()
+ {
+ Type type = typeof(NetworkMatchChecker);
+ FieldInfo fieldInfo = type.GetField("matchPlayers", BindingFlags.Static | BindingFlags.NonPublic);
+ return (Dictionary>)fieldInfo.GetValue(null);
+ }
+
+ static NetworkConnection CreateNetworkConnection(GameObject player)
+ {
+ NetworkConnectionToClient connection = new NetworkConnectionToClient(++nextConnectionId);
+ connection.identity = player.GetComponent();
+ connection.identity.connectionToClient = connection;
+ connection.identity.observers = new Dictionary();
+ connection.isReady = true;
+ return connection;
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ UnityEngine.Object.DestroyImmediate(player1);
+ UnityEngine.Object.DestroyImmediate(player2);
+ UnityEngine.Object.DestroyImmediate(player3);
+ UnityEngine.Object.DestroyImmediate(transportGO);
+
+ matchPlayers.Clear();
+ matchPlayers = null;
+ }
+
+ static void SetMatchId(NetworkMatchChecker target, Guid guid)
+ {
+ // set using reflection so bypass property
+ FieldInfo field = typeof(NetworkMatchChecker).GetField("currentMatch", BindingFlags.Instance | BindingFlags.NonPublic);
+ field.SetValue(target, guid);
+ }
+
+ [Test]
+ public void OnCheckObserverShouldBeTrueForSameMatchId()
+ {
+ string guid = Guid.NewGuid().ToString();
+
+ SetMatchId(player1MatchChecker, new Guid(guid));
+ SetMatchId(player2MatchChecker, new Guid(guid));
+
+ bool player1Visable = player1MatchChecker.OnCheckObserver(player1Connection);
+ Assert.IsTrue(player1Visable);
+
+ bool player2Visable = player1MatchChecker.OnCheckObserver(player2Connection);
+ Assert.IsTrue(player2Visable);
+ }
+
+ [Test]
+ public void OnCheckObserverShouldBeFalseForDifferentMatchId()
+ {
+ string guid1 = Guid.NewGuid().ToString();
+ string guid2 = Guid.NewGuid().ToString();
+
+ SetMatchId(player1MatchChecker, new Guid(guid1));
+ SetMatchId(player2MatchChecker, new Guid(guid2));
+
+ bool player1VisableToPlayer1 = player1MatchChecker.OnCheckObserver(player1Connection);
+ Assert.IsTrue(player1VisableToPlayer1);
+
+ bool player2VisableToPlayer1 = player1MatchChecker.OnCheckObserver(player2Connection);
+ Assert.IsFalse(player2VisableToPlayer1);
+
+
+ bool player1VisableToPlayer2 = player2MatchChecker.OnCheckObserver(player1Connection);
+ Assert.IsFalse(player1VisableToPlayer2);
+
+ bool player2VisableToPlayer2 = player2MatchChecker.OnCheckObserver(player2Connection);
+ Assert.IsTrue(player2VisableToPlayer2);
+ }
+
+ [Test]
+ public void OnCheckObserverShouldBeFalseIfObjectDoesNotHaveNetworkMatchChecker()
+ {
+ string guid = Guid.NewGuid().ToString();
+
+ SetMatchId(player1MatchChecker, new Guid(guid));
+
+ bool player3Visable = player1MatchChecker.OnCheckObserver(player3Connection);
+ Assert.IsFalse(player3Visable);
+ }
+
+ [Test]
+ public void OnCheckObserverShouldBeFalseForEmptyGuid()
+ {
+ string guid = Guid.Empty.ToString();
+
+ SetMatchId(player1MatchChecker, new Guid(guid));
+ SetMatchId(player2MatchChecker, new Guid(guid));
+
+ bool player1Visable = player1MatchChecker.OnCheckObserver(player1Connection);
+ Assert.IsFalse(player1Visable);
+
+ bool player2Visable = player1MatchChecker.OnCheckObserver(player2Connection);
+ Assert.IsFalse(player2Visable);
+ }
+
+ [Test]
+ public void SettingMatchIdShouldRebuildObservers()
+ {
+ string guidMatch1 = Guid.NewGuid().ToString();
+
+ // make players join same match
+ player1MatchChecker.matchId = new Guid(guidMatch1);
+ player2MatchChecker.matchId = new Guid(guidMatch1);
+
+ // check player1's observers contains player 2
+ Assert.IsTrue(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
+ // check player2's observers contains player 1
+ Assert.IsTrue(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
+ }
+
+ [Test]
+ public void ChangingMatchIdShouldRebuildObservers()
+ {
+ string guidMatch1 = Guid.NewGuid().ToString();
+ string guidMatch2 = Guid.NewGuid().ToString();
+
+ // make players join same match
+ player1MatchChecker.matchId = new Guid(guidMatch1);
+ player2MatchChecker.matchId = new Guid(guidMatch1);
+
+ // make player2 join different match
+ player2MatchChecker.matchId = new Guid(guidMatch2);
+
+ // check player1's observers does NOT contain player 2
+ Assert.IsFalse(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
+ // check player2's observers does NOT contain player 1
+ Assert.IsFalse(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
+ }
+
+ [Test]
+ public void ClearingMatchIdShouldRebuildObservers()
+ {
+ string guidMatch1 = Guid.NewGuid().ToString();
+
+ // make players join same match
+ player1MatchChecker.matchId = new Guid(guidMatch1);
+ player2MatchChecker.matchId = new Guid(guidMatch1);
+
+ // make player 2 leave match
+ player2MatchChecker.matchId = Guid.Empty;
+
+ // check player1's observers does NOT contain player 2
+ Assert.IsFalse(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
+ // check player2's observers does NOT contain player 1
+ Assert.IsFalse(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
+ }
+ }
+}
diff --git a/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs.meta
new file mode 100644
index 000000000..b3c51cf81
--- /dev/null
+++ b/Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2fa0a455ab9b4cf47b9eab0f2b03ce0c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/doc/Components/NetworkMatchChecker.md b/doc/Components/NetworkMatchChecker.md
new file mode 100644
index 000000000..5b145ebf6
--- /dev/null
+++ b/doc/Components/NetworkMatchChecker.md
@@ -0,0 +1,13 @@
+# Network Scene Checker
+
+The Network Match Checker component controls visibility of networked objects based on match id.
+
+![Network Scene Checker component](NetworkMatchChecker.png)
+
+Any object with this component on it will only be visible to other objects in the same match.
+
+This would be used to isolate players to their respective matches within a single game server instance.
+
+When you create a match, generate and store, in a List for example, a new match id with `System.Guid.NewGuid();` and assign the same match id to the Network Scene Checker via `GetComponent().matchId`.
+
+Mirror's built-in Observers system will isolate SyncVar's and ClientRpc's on networked objects to only send updates to clients with the same match id.
diff --git a/doc/Components/NetworkMatchChecker.png b/doc/Components/NetworkMatchChecker.png
new file mode 100644
index 000000000..c2d67ef08
Binary files /dev/null and b/doc/Components/NetworkMatchChecker.png differ
diff --git a/doc/Components/index.md b/doc/Components/index.md
index d2ff69696..a7dea9096 100644
--- a/doc/Components/index.md
+++ b/doc/Components/index.md
@@ -16,6 +16,8 @@ These core components are included in Mirror:
The Network Proximity Checker component controls the visibility of game objects for network clients, based on proximity to players.
- [Network Scene Checker](NetworkSceneChecker.md)
The Network Scene Checker component controls visibility of networked objects between scenes.
+- [Network Match Checker](NetworkMatchChecker.md)
+ The Network Match Checker component controls visibility of networked objects based on match id.
- [Network Room Manager](NetworkRoomManager.md)
The Network Room Manager is an extension component of Network Manager that provides a basic functional room.
- [Network Room Player](NetworkRoomPlayer.md)