Architecture - Shared Authority
Unity Project
All game scripts are located in the Scripts folder under the Asteroids.SharedSimple namespace.
| Folder | Content |
|---|---|
/Scripts |
GameManager and PlayerColorPalette |
/Scripts/Asteroid |
AsteroidBehaviour and AsteroidSpawner |
/Scripts/Player |
SpaceshipController, PlayerWeapon, LocalInputPoller, SpaceshipSpawner, SpaceshipVisuals and SpaceshipSounds |
/Scripts/UI |
StartMenu, GameUI and PlayerUI |
/Scripts/Utilities |
NetworkObjectPool, ScreenBoundary, ScreenUtils and SelfDestructingVFX |
/Prefabs |
NetworkRunner prefab, Asteroids with BigAsteroid and SmallAsteroid, Player with Spaceship and Bullet, UI with GameUI and VFX |
/Scenes |
Menu and Game scenes |
/TextMesh Pro |
TextMesh Pro resources with the Exo 2 font, see Overview |
/Photon |
Fusion SDK |
The project has two scenes:
Menu: The start menu with theStartMenuscript, the first scene of a build.Game: The play area with theSpawnerobject holding theAsteroidSpawnerand theSpaceshipSpawner, theGameStateControllerobject holding theGameManager, theGameUIand four spawn points.
The master client loads the Game scene with NetworkRunner.LoadScene once the session runs, see Scene Loading.
High Level Breakdown
The game boots in the Menu scene, StartMenu instantiates the NetworkRunner prefab and starts the session with GameMode.Shared, see Session and Menu.
In the Shared Authority topology every client has state authority over the objects it spawns, so each client spawns and controls its own spaceship and no input travels over the network, see Network Topologies. The scene objects and the asteroids belong to the Shared Mode Master Client. A client that wants to change an object it does not control asks the state authority with an RPC. The same game exists as a Client Host project, see Architecture - Client Host.
Game Manager runs the game state and the round timer on the master client and hands out spawn points.
Spaceship reads the local input, moves the ship, wraps it around the screen and detects asteroid collisions.
Weapon fires projectiles stored as networked data and requests asteroid hits from the master client.
Asteroids spawns big asteroids, confirms hits, breaks big asteroids into small ones and pools them.
Session and Menu
StartMenu.StartGameAsync instantiates the NetworkRunner prefab and starts the session:
C#
var startGameArgs = new StartGameArgs()
{
GameMode = mode,
SessionName = roomName,
ObjectProvider = _runnerInstance.GetComponent<NetworkObjectPool>(),
};
START passes GameMode.Shared, the room name becomes the session name, see Network Runner.
The session uses the PlayerCount of 4 from the NetworkProjectConfig, GameManager.MaxPlayers sizes the spawn points to match.
When the local client is the master client (IsSharedModeMasterClient), StartMenu loads the Game scene.
The nickname of the local player is kept in the static StartMenu.ChosenLocalPlayerNickname.
Game Manager
GameManager sits on the GameStateController scene object, the master client has state authority over it.
The game state and the round timer are networked properties, the timer is a TickTimer:
C#
[Networked] private TickTimer _timer { get; set; }
[Networked] private EGameState _gameState { get; set; }
StartGame closes the session and starts the timer with the _gameSessionLength of 180 seconds.
It sets the ships alive with RPC_SetShipAlive because each ship belongs to its own player.
CheckEndGame switches the state to Ending when the timer expired or no ship has lives left.
CheckGameStateUpdated runs in Render, so every peer reacts to a state change.
It raises GameStateChanged for the GameUI, shuts the runner down _endDelay seconds after the round ended and, on the master client, starts or stops the asteroid spawner.
NetworkEvents.OnShutdown then loads the Menu scene.
IPlayerJoined and IPlayerLeft are only called on the object state authority in Shared Authority, so GameManager compares Runner.ActivePlayers in Render on every peer and raises OnPlayerJoined and OnPlayerLeft for the player panels of the GameUI.
GameUI implements IStateAuthorityChanged and enables the Start game button on the new master client when the master client changes.
SpaceshipSpawner hands out the spawn points.
The master client spawns its own ship, every other client sends RPC_RequestSpawnPoint to the master client, which answers with the targeted RPC_SpawnAt, see RPCs.
The client then spawns its own ship with Runner.Spawn, so it has state authority over it, see Spawning.
The spawn point owners are a networked NetworkArray<PlayerRef>, so a new master client keeps handing them out, see Network Collections.
A client repeats its request after _spawnRequestRetryDelay seconds when the master client left before answering.
Spaceship
SpaceshipController adds a LocalInputPoller to the ship of the local player, the state authority of the ship.
LocalInputPoller reads the Move and Fire actions of the InputSystem_Actions asset in Update, the ship takes the input with LocalInputPoller.GetInput in FixedUpdateNetwork and the input is not sent over the network, see Shared Mode Player Input.
A fire press stays set until the next tick takes it, so a press between two ticks is not lost.
The ship also sets its Nickname and Lives directly in Spawned.
The x axis rotates the ship and the y axis accelerates it, the velocity is the networked _velocity and a drag slows the ship down without input.
ScreenBoundary on the camera sets the play area from the orthographic size and a fixed 16:9 aspect.
A ship that leaves the play area moves to the opposite edge with NetworkTransform.Teleport, see Network Transform.
Once the spawn shield ShieldTimer expired, HasHitAsteroid runs an OverlapSphere of the physics scene against the asteroid layer.
On a hit the ship calls AsteroidBehaviour.TryRequestHit and takes the damage locally without waiting for the master client to confirm the asteroid hit.
A hit sets IsAlive to false, removes a life and starts the respawn timer, the respawned ship gets a new shield.
Weapon
PlayerWeapon stores up to 16 projectiles as ProjectileData structs in a networked array, the bullets are local visuals, see Projectiles Essentials:
C#
[Networked, Capacity(16)] private NetworkArray<ProjectileData> _projectileData { get; }
A fire press detected with WasPressed against the networked previous buttons writes a new entry with the fire tick, position and velocity.
The networked _shootCooldown limits the fire rate to one shot per 0.2 seconds.
In FixedUpdateNetwork every active projectile casts a ray of the physics scene from its previous to its current position.
A hit on an asteroid calls AsteroidBehaviour.TryRequestHit with the shooting player.
The master client validates the hit and sends RPC_AddScore to the ship, which adds the asteroid Points to its networked Score.
Render instantiates the bullet visuals for new shots by comparing the networked FireCount with a local counter and moves them along the calculated path.
Asteroids
The asteroids are master client objects, the master client spawns them and has state authority over them, see Is Master Client Object.
AsteroidSpawner spawns a BigAsteroid at a random screen edge every 5 to 10 seconds while the round runs and pushes it towards the center with a force on its Rigidbody, a NetworkTransform replicates its position.
Asteroids that leave the play area are despawned.
The spawn timer and the list of asteroids are networked, the list is a NetworkLinkedList<NetworkId> with a capacity of 64, so a new master client carries on spawning when the previous one leaves.
TryRequestHit sends RPC_RequestHit to the master client, which applies the hit and rewards the shooter:
C#
// Hits are detected by the ship owner, but only the master can change the asteroid.
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
private void RPC_RequestHit(PlayerRef shooter)
{
if (TryHitAsteroid(shooter) == false)
return;
if (shooter != PlayerRef.None &&
Runner.TryGetPlayerObject(shooter, out var playerObject) &&
playerObject.TryGetComponent<SpaceshipController>(out var ship))
{
ship.RPC_AddScore(Points);
}
}
Until the confirmation arrives, a client that is not the master client predicts the hit.
It disables the asteroid collider and plays the destruction effect, when the master client does not confirm the hit within _hitConfirmTimeoutInSeconds the asteroid grows back.
The master client sets the networked _wasHit and starts a despawn timer, so the destruction effect plays before the asteroid is despawned.
A big asteroid calls AsteroidSpawner.BreakUpBigAsteroid, which spawns three to five SmallAsteroid objects around it, limited by the free capacity of the asteroid list.
AsteroidBehaviour stores the Rigidbody velocities and the AsteroidSpawner reference in networked properties.
When the master client changes, IStateAuthorityChanged.StateAuthorityChanged restores the velocities on the new master client so the asteroids keep moving.
The NetworkObjectPool on the NetworkRunner prefab derives from NetworkObjectProviderDefault and recycles the asteroid objects instead of instantiating and destroying them, see NetworkObjectProvider.
AsteroidBehaviour.Spawned resets the local state of a reused asteroid, such as the collider, the hit prediction and the scale of its model.
Last updated on
Back to top