Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
MPAI-MMM
mmm-tec-unity
Commits
f6ed3c6f
Commit
f6ed3c6f
authored
Feb 21, 2025
by
cann-alberto
Browse files
first project commit
parent
4487b14f
Changes
1000
Hide whitespace changes
Inline
Side-by-side
Too many changes to show.
To preserve performance only
20 of 1000+
files are displayed.
Plain diff
Email patch
Assets/Mirror/Components/InterestManagement/Match/NetworkMatch.cs
0 → 100644
View file @
f6ed3c6f
// 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
{
Guid
_matchId
;
#pragma warning disable IDE0052 // Suppress warning for unused field...this is for debugging purposes
[
SerializeField
,
ReadOnly
]
[
Tooltip
(
"Match ID is shown here on server for debugging purposes."
)]
string
MatchID
=
string
.
Empty
;
#pragma warning restore IDE0052
///<summary>Set this to the same value on all networked objects that belong to a given match</summary>
public
Guid
matchId
{
get
=>
_matchId
;
set
{
if
(!
NetworkServer
.
active
)
throw
new
InvalidOperationException
(
"matchId can only be set at runtime on active server"
);
if
(
_matchId
==
value
)
return
;
Guid
oldMatch
=
_matchId
;
_matchId
=
value
;
MatchID
=
value
.
ToString
();
// Only inform the AOI if this netIdentity has been spawned (isServer) and only if using a MatchInterestManagement
if
(
isServer
&&
NetworkServer
.
aoi
is
MatchInterestManagement
matchInterestManagement
)
matchInterestManagement
.
OnMatchChanged
(
this
,
oldMatch
);
}
}
}
}
Assets/Mirror/Components/InterestManagement/Match/NetworkMatch.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 5d17e718851449a6879986e45c458fb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/Scene.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 7655d309a46a4bd4860edf964228b3f6
timeCreated: 1622649517
\ No newline at end of file
Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs
0 → 100644
View file @
f6ed3c6f
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
>();
[
ServerCallback
]
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
);
}
[
ServerCallback
]
public
override
void
OnDestroyed
(
NetworkIdentity
identity
)
{
// Don't RebuildSceneObservers here - that will happen in Update.
// Multiple objects could be destroyed in same frame and we don't
// want to rebuild for each one...let Update do it once.
// We must add the current scene to dirtyScenes for Update to rebuild it.
if
(
lastObjectScene
.
TryGetValue
(
identity
,
out
Scene
currentScene
))
{
lastObjectScene
.
Remove
(
identity
);
if
(
sceneObjects
.
TryGetValue
(
currentScene
,
out
HashSet
<
NetworkIdentity
>
objects
)
&&
objects
.
Remove
(
identity
))
dirtyScenes
.
Add
(
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
)
{
if
(!
lastObjectScene
.
TryGetValue
(
identity
,
out
Scene
currentScene
))
continue
;
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
);
}
}
}
Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: b979f26c95d34324ba005bfacfa9c4fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/SpatialHashing.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: cfa12b73503344d49b398b01bcb07967
timeCreated: 1613110634
\ No newline at end of file
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs
0 → 100644
View file @
f6ed3c6f
// 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
();
}
}
}
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid2D.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 7c5232a4d2854116a35d52b80ec07752
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid3D.cs
0 → 100644
View file @
f6ed3c6f
// Grid3D based on Grid2D
// -> 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
Grid3D
<
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
<
Vector3Int
,
HashSet
<
T
>>
grid
;
// cache a 9 x 3 neighbor grid of vector3 offsets so we can use them more easily
readonly
Vector3Int
[]
neighbourOffsets
;
public
Grid3D
(
int
initialCapacity
)
{
grid
=
new
Dictionary
<
Vector3Int
,
HashSet
<
T
>>(
initialCapacity
);
neighbourOffsets
=
new
Vector3Int
[
9
*
3
];
int
i
=
0
;
for
(
int
x
=
-
1
;
x
<=
1
;
x
++)
{
for
(
int
y
=
-
1
;
y
<=
1
;
y
++)
{
for
(
int
z
=
-
1
;
z
<=
1
;
z
++)
{
neighbourOffsets
[
i
]
=
new
Vector3Int
(
x
,
y
,
z
);
i
+=
1
;
}
}
}
}
// helper function so we can add an entry without worrying
public
void
Add
(
Vector3Int
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
(
Vector3Int
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
(
Vector3Int
position
,
HashSet
<
T
>
result
)
{
// clear result first
result
.
Clear
();
// add neighbours
foreach
(
Vector3Int
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
();
}
}
}
Assets/Mirror/Components/InterestManagement/SpatialHashing/Grid3D.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: b157c08313c64752b0856469b1b70771
timeCreated: 1713533175
\ No newline at end of file
Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashing3DInterestManagement.cs
0 → 100644
View file @
f6ed3c6f
// extremely fast spatial hashing interest management based on uMMORPG GridChecker.
// => 30x faster in initial tests
// => scales way higher
// checks on three dimensions (XYZ) which includes the vertical axes.
// this is slower than XY checking for regular spatial hashing.
using
System.Collections.Generic
;
using
UnityEngine
;
namespace
Mirror
{
[
AddComponentMenu
(
"Network/ Interest Management/ Spatial Hash/Spatial Hashing Interest Management"
)]
public
class
SpatialHashing3DInterestManagement
:
InterestManagement
{
[
Tooltip
(
"The maximum range that objects will be visible at."
)]
public
int
visRange
=
30
;
// we use a 9 neighbour grid.
// so we always see in a distance of 2 grids.
// for example, our own grid and then one on top / below / left / right.
//
// this means that grid resolution needs to be distance / 2.
// so for example, for distance = 30 we see 2 cells = 15 * 2 distance.
//
// on first sight, it seems we need distance / 3 (we see left/us/right).
// but that's not the case.
// resolution would be 10, and we only see 1 cell far, so 10+10=20.
public
int
resolution
=>
visRange
/
2
;
// same as XY because if XY is rotated 90 degree for 3D, it's still the same distance
[
Tooltip
(
"Rebuild all every 'rebuildInterval' seconds."
)]
public
float
rebuildInterval
=
1
;
double
lastRebuildTime
;
[
Header
(
"Debug Settings"
)]
public
bool
showSlider
;
// the grid
// begin with a large capacity to avoid resizing & allocations.
Grid3D
<
NetworkConnectionToClient
>
grid
=
new
Grid3D
<
NetworkConnectionToClient
>(
1024
);
// project 3d world position to grid position
Vector3Int
ProjectToGrid
(
Vector3
position
)
=>
Vector3Int
.
RoundToInt
(
position
/
resolution
);
public
override
bool
OnCheckObserver
(
NetworkIdentity
identity
,
NetworkConnectionToClient
newObserver
)
{
// calculate projected positions
Vector3Int
projected
=
ProjectToGrid
(
identity
.
transform
.
position
);
Vector3Int
observerProjected
=
ProjectToGrid
(
newObserver
.
identity
.
transform
.
position
);
// distance needs to be at max one of the 8 neighbors, which is
// 1 for the direct neighbors
// 1.41 for the diagonal neighbors (= sqrt(2))
// => use sqrMagnitude and '2' to avoid computations. same result.
return
(
projected
-
observerProjected
).
sqrMagnitude
<=
2
;
// same as XY because if XY is rotated 90 degree for 3D, it's still the same distance
}
public
override
void
OnRebuildObservers
(
NetworkIdentity
identity
,
HashSet
<
NetworkConnectionToClient
>
newObservers
)
{
// add everyone in 9 neighbour grid
// -> pass observers to GetWithNeighbours directly to avoid allocations
// and expensive .UnionWith computations.
Vector3Int
current
=
ProjectToGrid
(
identity
.
transform
.
position
);
grid
.
GetWithNeighbours
(
current
,
newObservers
);
}
[
ServerCallback
]
public
override
void
ResetState
()
{
lastRebuildTime
=
0D
;
}
// update everyone's position in the grid
// (internal so we can update from tests)
[
ServerCallback
]
internal
void
Update
()
{
// NOTE: unlike Scene/MatchInterestManagement, this rebuilds ALL
// entities every INTERVAL. consider the other approach later.
// IMPORTANT: refresh grid every update!
// => newly spawned entities get observers assigned via
// OnCheckObservers. this can happen any time and we don't want
// them broadcast to old (moved or destroyed) connections.
// => players do move all the time. we want them to always be in the
// correct grid position.
// => note that the actual 'rebuildall' doesn't need to happen all
// the time.
// NOTE: consider refreshing grid only every 'interval' too. but not
// for now. stability & correctness matter.
// clear old grid results before we update everyone's position.
// (this way we get rid of destroyed connections automatically)
//
// NOTE: keeps allocated HashSets internally.
// clearing & populating every frame works without allocations
grid
.
ClearNonAlloc
();
// put every connection into the grid at it's main player's position
// NOTE: player sees in a radius around him. NOT around his pet too.
foreach
(
NetworkConnectionToClient
connection
in
NetworkServer
.
connections
.
Values
)
{
// authenticated and joined world with a player?
if
(
connection
.
isAuthenticated
&&
connection
.
identity
!=
null
)
{
// calculate current grid position
Vector3Int
position
=
ProjectToGrid
(
connection
.
identity
.
transform
.
position
);
// put into grid
grid
.
Add
(
position
,
connection
);
}
}
// rebuild all spawned entities' observers every 'interval'
// this will call OnRebuildObservers which then returns the
// observers at grid[position] for each entity.
if
(
NetworkTime
.
localTime
>=
lastRebuildTime
+
rebuildInterval
)
{
RebuildAll
();
lastRebuildTime
=
NetworkTime
.
localTime
;
}
}
// OnGUI allocates even if it does nothing. avoid in release.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// slider from dotsnet. it's nice to play around with in the benchmark
// demo.
void
OnGUI
()
{
if
(!
showSlider
)
return
;
// only show while server is running. not on client, etc.
if
(!
NetworkServer
.
active
)
return
;
int
height
=
30
;
int
width
=
250
;
GUILayout
.
BeginArea
(
new
Rect
(
Screen
.
width
/
2
-
width
/
2
,
Screen
.
height
-
height
,
width
,
height
));
GUILayout
.
BeginHorizontal
(
"Box"
);
GUILayout
.
Label
(
"Radius:"
);
visRange
=
Mathf
.
RoundToInt
(
GUILayout
.
HorizontalSlider
(
visRange
,
0
,
200
,
GUILayout
.
Width
(
150
)));
GUILayout
.
Label
(
visRange
.
ToString
());
GUILayout
.
EndHorizontal
();
GUILayout
.
EndArea
();
}
#endif
}
}
Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashing3DInterestManagement.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 120b4d6121d94e0280cd2ec536b0ea8f
timeCreated: 1713534045
\ No newline at end of file
Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs
0 → 100644
View file @
f6ed3c6f
// extremely fast spatial hashing interest management based on uMMORPG GridChecker.
// => 30x faster in initial tests
// => scales way higher
// checks on two dimensions only(!), for example: XZ for 3D games or XY for 2D games.
// this is faster than XYZ checking but doesn't check vertical distance.
using
System.Collections.Generic
;
using
UnityEngine
;
namespace
Mirror
{
[
AddComponentMenu
(
"Network/ Interest Management/ Spatial Hash/Spatial Hashing Interest Management"
)]
public
class
SpatialHashingInterestManagement
:
InterestManagement
{
[
Tooltip
(
"The maximum range that objects will be visible at."
)]
public
int
visRange
=
30
;
// we use a 9 neighbour grid.
// so we always see in a distance of 2 grids.
// for example, our own grid and then one on top / below / left / right.
//
// this means that grid resolution needs to be distance / 2.
// so for example, for distance = 30 we see 2 cells = 15 * 2 distance.
//
// on first sight, it seems we need distance / 3 (we see left/us/right).
// but that's not the case.
// resolution would be 10, and we only see 1 cell far, so 10+10=20.
public
int
resolution
=>
visRange
/
2
;
[
Tooltip
(
"Rebuild all every 'rebuildInterval' seconds."
)]
public
float
rebuildInterval
=
1
;
double
lastRebuildTime
;
public
enum
CheckMethod
{
XZ_FOR_3D
,
XY_FOR_2D
}
[
Tooltip
(
"Spatial Hashing supports 3D (XZ) and 2D (XY) games."
)]
public
CheckMethod
checkMethod
=
CheckMethod
.
XZ_FOR_3D
;
[
Header
(
"Debug Settings"
)]
public
bool
showSlider
;
// the grid
// begin with a large capacity to avoid resizing & allocations.
Grid2D
<
NetworkConnectionToClient
>
grid
=
new
Grid2D
<
NetworkConnectionToClient
>(
1024
);
// project 3d world position to grid position
Vector2Int
ProjectToGrid
(
Vector3
position
)
=>
checkMethod
==
CheckMethod
.
XZ_FOR_3D
?
Vector2Int
.
RoundToInt
(
new
Vector2
(
position
.
x
,
position
.
z
)
/
resolution
)
:
Vector2Int
.
RoundToInt
(
new
Vector2
(
position
.
x
,
position
.
y
)
/
resolution
);
public
override
bool
OnCheckObserver
(
NetworkIdentity
identity
,
NetworkConnectionToClient
newObserver
)
{
// calculate projected positions
Vector2Int
projected
=
ProjectToGrid
(
identity
.
transform
.
position
);
Vector2Int
observerProjected
=
ProjectToGrid
(
newObserver
.
identity
.
transform
.
position
);
// distance needs to be at max one of the 8 neighbors, which is
// 1 for the direct neighbors
// 1.41 for the diagonal neighbors (= sqrt(2))
// => use sqrMagnitude and '2' to avoid computations. same result.
return
(
projected
-
observerProjected
).
sqrMagnitude
<=
2
;
}
public
override
void
OnRebuildObservers
(
NetworkIdentity
identity
,
HashSet
<
NetworkConnectionToClient
>
newObservers
)
{
// add everyone in 9 neighbour grid
// -> pass observers to GetWithNeighbours directly to avoid allocations
// and expensive .UnionWith computations.
Vector2Int
current
=
ProjectToGrid
(
identity
.
transform
.
position
);
grid
.
GetWithNeighbours
(
current
,
newObservers
);
}
[
ServerCallback
]
public
override
void
ResetState
()
{
lastRebuildTime
=
0D
;
}
// update everyone's position in the grid
// (internal so we can update from tests)
[
ServerCallback
]
internal
void
Update
()
{
// NOTE: unlike Scene/MatchInterestManagement, this rebuilds ALL
// entities every INTERVAL. consider the other approach later.
// IMPORTANT: refresh grid every update!
// => newly spawned entities get observers assigned via
// OnCheckObservers. this can happen any time and we don't want
// them broadcast to old (moved or destroyed) connections.
// => players do move all the time. we want them to always be in the
// correct grid position.
// => note that the actual 'rebuildall' doesn't need to happen all
// the time.
// NOTE: consider refreshing grid only every 'interval' too. but not
// for now. stability & correctness matter.
// clear old grid results before we update everyone's position.
// (this way we get rid of destroyed connections automatically)
//
// NOTE: keeps allocated HashSets internally.
// clearing & populating every frame works without allocations
grid
.
ClearNonAlloc
();
// put every connection into the grid at it's main player's position
// NOTE: player sees in a radius around him. NOT around his pet too.
foreach
(
NetworkConnectionToClient
connection
in
NetworkServer
.
connections
.
Values
)
{
// authenticated and joined world with a player?
if
(
connection
.
isAuthenticated
&&
connection
.
identity
!=
null
)
{
// calculate current grid position
Vector2Int
position
=
ProjectToGrid
(
connection
.
identity
.
transform
.
position
);
// put into grid
grid
.
Add
(
position
,
connection
);
}
}
// rebuild all spawned entities' observers every 'interval'
// this will call OnRebuildObservers which then returns the
// observers at grid[position] for each entity.
if
(
NetworkTime
.
localTime
>=
lastRebuildTime
+
rebuildInterval
)
{
RebuildAll
();
lastRebuildTime
=
NetworkTime
.
localTime
;
}
}
// OnGUI allocates even if it does nothing. avoid in release.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// slider from dotsnet. it's nice to play around with in the benchmark
// demo.
void
OnGUI
()
{
if
(!
showSlider
)
return
;
// only show while server is running. not on client, etc.
if
(!
NetworkServer
.
active
)
return
;
int
height
=
30
;
int
width
=
250
;
GUILayout
.
BeginArea
(
new
Rect
(
Screen
.
width
/
2
-
width
/
2
,
Screen
.
height
-
height
,
width
,
height
));
GUILayout
.
BeginHorizontal
(
"Box"
);
GUILayout
.
Label
(
"Radius:"
);
visRange
=
Mathf
.
RoundToInt
(
GUILayout
.
HorizontalSlider
(
visRange
,
0
,
200
,
GUILayout
.
Width
(
150
)));
GUILayout
.
Label
(
visRange
.
ToString
());
GUILayout
.
EndHorizontal
();
GUILayout
.
EndArea
();
}
#endif
}
}
Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 39adc6e09d5544ed955a50ce8600355a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/Team.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 2d418e60072433b4bbebbf5f3a7de1bb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs
0 → 100644
View file @
f6ed3c6f
// simple component that holds team information
using
System
;
using
UnityEngine
;
namespace
Mirror
{
[
DisallowMultipleComponent
]
[
AddComponentMenu
(
"Network/ Interest Management/ Team/Network Team"
)]
[
HelpURL
(
"https://mirror-networking.gitbook.io/docs/guides/interest-management"
)]
public
class
NetworkTeam
:
NetworkBehaviour
{
[
SerializeField
]
[
Tooltip
(
"Set teamId on Server at runtime to the same value on all networked objects that belong to a given team"
)]
string
_teamId
;
public
string
teamId
{
get
=>
_teamId
;
set
{
if
(
Application
.
IsPlaying
(
gameObject
)
&&
!
NetworkServer
.
active
)
throw
new
InvalidOperationException
(
"teamId can only be set at runtime on active server"
);
if
(
_teamId
==
value
)
return
;
string
oldTeam
=
_teamId
;
_teamId
=
value
;
//Only inform the AOI if this netIdentity has been spawned(isServer) and only if using a TeamInterestManagement
if
(
isServer
&&
NetworkServer
.
aoi
is
TeamInterestManagement
teamInterestManagement
)
teamInterestManagement
.
OnTeamChanged
(
this
,
oldTeam
);
}
}
[
Tooltip
(
"When enabled this object is visible to all clients. Typically this would be true for player objects"
)]
public
bool
forceShown
;
}
}
Assets/Mirror/Components/InterestManagement/Team/NetworkTeam.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 2576730625b1632468cbcbfe5e721f88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs
0 → 100644
View file @
f6ed3c6f
using
System.Collections.Generic
;
using
UnityEngine
;
namespace
Mirror
{
[
AddComponentMenu
(
"Network/ Interest Management/ Team/Team Interest Management"
)]
public
class
TeamInterestManagement
:
InterestManagement
{
readonly
Dictionary
<
string
,
HashSet
<
NetworkTeam
>>
teamObjects
=
new
Dictionary
<
string
,
HashSet
<
NetworkTeam
>>();
readonly
HashSet
<
string
>
dirtyTeams
=
new
HashSet
<
string
>();
// LateUpdate so that all spawns/despawns/changes are done
[
ServerCallback
]
void
LateUpdate
()
{
// Rebuild all dirty teams
// dirtyTeams will be empty if no teams changed members
// by spawning or destroying or changing teamId in this frame.
foreach
(
string
dirtyTeam
in
dirtyTeams
)
{
// rebuild always, even if teamObjects[dirtyTeam] is empty.
// Players might have left the team, but they may still be spawned.
RebuildTeamObservers
(
dirtyTeam
);
// clean up empty entries in the dict
if
(
teamObjects
[
dirtyTeam
].
Count
==
0
)
teamObjects
.
Remove
(
dirtyTeam
);
}
dirtyTeams
.
Clear
();
}
[
ServerCallback
]
void
RebuildTeamObservers
(
string
teamId
)
{
foreach
(
NetworkTeam
networkTeam
in
teamObjects
[
teamId
])
if
(
networkTeam
.
netIdentity
!=
null
)
NetworkServer
.
RebuildObservers
(
networkTeam
.
netIdentity
,
false
);
}
// called by NetworkTeam.teamId setter
[
ServerCallback
]
internal
void
OnTeamChanged
(
NetworkTeam
networkTeam
,
string
oldTeam
)
{
// This object is in a new team so observers in the prior team
// and the new team need to rebuild their respective observers lists.
// Remove this object from the hashset of the team it just left
// Null / Empty string is never a valid teamId
if
(!
string
.
IsNullOrWhiteSpace
(
oldTeam
))
{
dirtyTeams
.
Add
(
oldTeam
);
teamObjects
[
oldTeam
].
Remove
(
networkTeam
);
}
// Null / Empty string is never a valid teamId
if
(
string
.
IsNullOrWhiteSpace
(
networkTeam
.
teamId
))
return
;
dirtyTeams
.
Add
(
networkTeam
.
teamId
);
// Make sure this new team is in the dictionary
if
(!
teamObjects
.
ContainsKey
(
networkTeam
.
teamId
))
teamObjects
[
networkTeam
.
teamId
]
=
new
HashSet
<
NetworkTeam
>();
// Add this object to the hashset of the new team
teamObjects
[
networkTeam
.
teamId
].
Add
(
networkTeam
);
}
[
ServerCallback
]
public
override
void
OnSpawned
(
NetworkIdentity
identity
)
{
if
(!
identity
.
TryGetComponent
(
out
NetworkTeam
networkTeam
))
return
;
string
networkTeamId
=
networkTeam
.
teamId
;
// Null / Empty string is never a valid teamId...do not add to teamObjects collection
if
(
string
.
IsNullOrWhiteSpace
(
networkTeamId
))
return
;
// Debug.Log($"TeamInterestManagement.OnSpawned({identity.name}) currentTeam: {currentTeam}");
if
(!
teamObjects
.
TryGetValue
(
networkTeamId
,
out
HashSet
<
NetworkTeam
>
objects
))
{
objects
=
new
HashSet
<
NetworkTeam
>();
teamObjects
.
Add
(
networkTeamId
,
objects
);
}
objects
.
Add
(
networkTeam
);
// Team ID could have been set in NetworkBehaviour::OnStartServer on this object.
// Since that's after OnCheckObserver is called it would be missed, so force Rebuild here.
// Add the current team to dirtyTeames for LateUpdate to rebuild it.
dirtyTeams
.
Add
(
networkTeamId
);
}
[
ServerCallback
]
public
override
void
OnDestroyed
(
NetworkIdentity
identity
)
{
// Don't RebuildSceneObservers here - that will happen in LateUpdate.
// Multiple objects could be destroyed in same frame and we don't
// want to rebuild for each one...let LateUpdate do it once.
// We must add the current team to dirtyTeames for LateUpdate to rebuild it.
if
(
identity
.
TryGetComponent
(
out
NetworkTeam
currentTeam
))
{
if
(!
string
.
IsNullOrWhiteSpace
(
currentTeam
.
teamId
)
&&
teamObjects
.
TryGetValue
(
currentTeam
.
teamId
,
out
HashSet
<
NetworkTeam
>
objects
)
&&
objects
.
Remove
(
currentTeam
))
dirtyTeams
.
Add
(
currentTeam
.
teamId
);
}
}
public
override
bool
OnCheckObserver
(
NetworkIdentity
identity
,
NetworkConnectionToClient
newObserver
)
{
// Always observed if no NetworkTeam component
if
(!
identity
.
TryGetComponent
(
out
NetworkTeam
identityNetworkTeam
))
return
true
;
if
(
identityNetworkTeam
.
forceShown
)
return
true
;
// Null / Empty string is never a valid teamId
if
(
string
.
IsNullOrWhiteSpace
(
identityNetworkTeam
.
teamId
))
return
false
;
// Always observed if no NetworkTeam component
if
(!
newObserver
.
identity
.
TryGetComponent
(
out
NetworkTeam
newObserverNetworkTeam
))
return
true
;
// Null / Empty string is never a valid teamId
if
(
string
.
IsNullOrWhiteSpace
(
newObserverNetworkTeam
.
teamId
))
return
false
;
//Debug.Log($"TeamInterestManagement.OnCheckObserver {identity.name} {identityNetworkTeam.teamId} | {newObserver.identity.name} {newObserverNetworkTeam.teamId}");
// Observed only if teamId's team
return
identityNetworkTeam
.
teamId
==
newObserverNetworkTeam
.
teamId
;
}
public
override
void
OnRebuildObservers
(
NetworkIdentity
identity
,
HashSet
<
NetworkConnectionToClient
>
newObservers
)
{
// If this object doesn't have a NetworkTeam then it's visible to all clients
if
(!
identity
.
TryGetComponent
(
out
NetworkTeam
networkTeam
))
{
AddAllConnections
(
newObservers
);
return
;
}
// If this object has NetworkTeam and forceShown == true then it's visible to all clients
if
(
networkTeam
.
forceShown
)
{
AddAllConnections
(
newObservers
);
return
;
}
// Null / Empty string is never a valid teamId
if
(
string
.
IsNullOrWhiteSpace
(
networkTeam
.
teamId
))
return
;
// Abort if this team hasn't been created yet by OnSpawned or OnTeamChanged
if
(!
teamObjects
.
TryGetValue
(
networkTeam
.
teamId
,
out
HashSet
<
NetworkTeam
>
objects
))
return
;
// Add everything in the hashset for this object's current team
foreach
(
NetworkTeam
netTeam
in
objects
)
if
(
netTeam
.
netIdentity
!=
null
&&
netTeam
.
netIdentity
.
connectionToClient
!=
null
)
newObservers
.
Add
(
netTeam
.
netIdentity
.
connectionToClient
);
}
void
AddAllConnections
(
HashSet
<
NetworkConnectionToClient
>
newObservers
)
{
foreach
(
NetworkConnectionToClient
conn
in
NetworkServer
.
connections
.
Values
)
{
// authenticated and joined world with a player?
if
(
conn
!=
null
&&
conn
.
isAuthenticated
&&
conn
.
identity
!=
null
)
newObservers
.
Add
(
conn
);
}
}
}
}
Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: dceb9a7085758fd4590419ff5b14b636
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:
Assets/Mirror/Components/LagCompensation.meta
0 → 100644
View file @
f6ed3c6f
fileFormatVersion: 2
guid: 00ac1d0527f234939aba22b4d7cbf280
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Prev
1
2
3
4
5
6
7
8
…
50
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment