# Learn Multiplayer
## Framework

### Components:
We use the Network Manager, Network Identity, and Network Transform components to talk to the High Level API (HLAPI). You can also create your own custom components that communicate with the HLAPI.
### HLAPI
The HLAPI takes care of distributed object management, state synchronization, and makes several classes available to developers. The HLAPI is connected to the Transport Layer, which runs lower level tasks behind the scenes. The Transport Layer can be accessed, but we won't be doing that.
## Unity Networking (uNet)

**Core principle of uNet is one of the clients (players) is the host**.
- if client leaves, another client becomes the host
- see this in COD
**Local Client (white box)**
Includes client and server on same machine
- Other players connect to the hosts or other players through the host IP address
- this gives access to the sever, which allows the player making the connection to reach either the host or other players
**Repeatedly connecting to an IP address for player interaction, is problematic, however**
- Unity has created a matchmaking system instead called **Relay State**

## Terms to know
**[SyncVar]** sync from Server --> Client
**[Command]** send information from Client --> Server. It can only be used if the client owns the object and has authority.
**[ClientRPC]** are just like command's but send information from Server --> all Client's
**[TargetRPC]** is just like ClientRPC, but only sends to 1 client (not all clients)
**Combinations:**
- You can wrap a **[ClientRpc]** in a **[Command]** to ask the server to perform an action and broadcast it to all clients
*Example Script:*
This script tells the server we are setting up a new player from the local player client and broadcasts to the other clients (players) that the local player is now setup.
```
public void PlayerSetup() {
CmdBroadCastNewPlayerSetup();
}
[Command]
private void CmdBroadCastNewPlayerSetup() {
RpcSetupPlayerOnAllClients();
}
[ClientRpc]
private void RpcSetupPlayerOnAllClients() {
wasEnabled = new bool[disableOnDeath.Length];
for (int i = 0; i < wasEnabled.Length; i++) {
wasEnabled[i] = disableOnDeath[i].enabled;
}
SetDefaults();
}
```
## Syncing non-player GameObject properties in uNet
Use `AssignClientAuthority`
*Example script:*
```
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class Player_Paint : NetworkBehaviour {
private int range = 200;
[SerializeField] private Transform camTransform;
private RaycastHit hit;
[SyncVar] private Color objectColor;
[SyncVar] private GameObject objectID;
private NetworkIdentity objNetId;
void Update () {
// only do something if it is the local player doing it
// so if player 1 does something, it will only be done on player 1's computer
// but the networking scripts will make sure everyone else sees it
if (isLocalPlayer) {
CheckIfPainting ();
}
}
void CheckIfPainting(){
// yes, isLocalPlayer is redundant here, because that is already checked before this function is called
// if it's the local player and their mouse is down, then they are "painting"
if(isLocalPlayer && Input.GetMouseButtonDown(0)) {
// here is the actual "painting" code
// "paint" if the Raycast hits something in it's range
if (Physics.Raycast (camTransform.TransformPoint (0, 0, 0.5f), camTransform.forward, out hit, range)) {
objectID = GameObject.Find (hit.transform.name); // this gets the object that is hit
objectColor = new Color(Random.value, Random.value, Random.value, Random.value); // I select the color here before doing anything else
CmdPaint(objectID, objectColor); // carry out the "painting" command
}
}
}
[ClientRpc]
void RpcPaint(GameObject obj, Color col){
obj.GetComponent<Renderer>().material.color = col; // this is the line that actually makes the change in color happen
}
[Command]
void CmdPaint(GameObject obj, Color col) {
objNetId = obj.GetComponent<NetworkIdentity> (); // get the object's network ID
objNetId.AssignClientAuthority (connectionToClient); // assign authority to the player who is changing the color
RpcPaint (obj, col); // usse a Client RPC function to "paint" the object on all clients
objNetId.RemoveClientAuthority (connectionToClient); // remove the authority from the player who changed the color
}
}
```
## GameObjects
Add the following gameobjects to the scene:
### Game manager
Create empty GameObject & add components:
- Network Manager
- Network Manager HUD

### Player Object
Create 3D box GameObject (avatar stand-in) & add components:
- Network Identity
- Network Transform
- Box (or capsule) collider
- Rigidbody
- Player Script

- Alternatively, you can import unity standard assets > character assets, and drop in the first person character RigidBodyFPSController
### SpawnPoint
Create empty GameObject and add `Network Start Position` component

## Make an object follow another object
- place this script on the object you want to follow another object
- Create a private transform variable for the object you want to follow (deathOrb)
- in Start(), find the object you want to follow in the scene and set it to deathOrb
- in Update(), continually update the parent object of the script to the deathOrb position (along z axis here)
```
public class FollowOrb : MonoBehaviour {
private Transform deathOrb;
void Start() {
deathOrb = GameObject.Find("Orb(Clone)").transform;
}
void Update() {
transform.position = new Vector3(transform.position.x, transform.position.y, deathOrb.position.z);
}
}
```
## Delegates & Events
Rewatch this video (start 11:00) to capture how delegates work
https://www.youtube.com/watch?v=Oa-jR-3KAEM&list=PLPV2KyIb3jR5PhGqsO7G4PsbEC_Al-kPZ&index=20
**Delegates** are containers for a function, to be used just like a variable
- values can be assigned and changed at runtime
- delegate type and parameters restrict the type and parameters of functions that can be assigned to the delegate
- must assign a function to a delegate in order to call it, so good idea to null check
*Syntax*
```
delegate returnType Name(parameter list)
DelegateName variableName
```
*Example:*
```
public delegate void JoinRoomDelegate(MatchInfoSnapshot _match);
private JoinRoomDelegate joinRoomCallback;
```
**Events** are advanced delegates that act as a broadcast system, you can assign multiple functions to events to be called when the event occurs
- any class interested in an event can subscribe methods to it
- more secure than multicast delegates in that events only allows subscribing and unsubscribing, whereas delegate can be overriden (events prevent nefarious actors from altering them)
- **!important** must always unsubscribe an event after subscribing to prevent errors
- use event handlers `+=` & `-=` to subscribe and unsubscribe, often within `OnEnable()` & `OnDisable()` methods
*Syntax*
```
delegate returnType Name(parameter list)
event DelegateName variableName
```
*Example:*
```
public delegate void StateChange(State newState);
public event StateChange OnStateChange;
```
## Instantiating objects
You can instantiate an object at a **specific position and rotation**, using the 2nd and 3rd arguments in the example below.
You can also instantiate the object as the **child** of an existing object (including the object you are instantiating the object from). This occurs as the 4th argument in the example below.
*Syntax:*
```
public static Object Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);
```
*Example:*
```
playerUIInstance = Instantiate(playerUIPrefab, playerUILocation.transform.position, playerUILocation.transform.rotation, gameObject.transform);
```
IF you want to instantiate an object as something *other* than a gameobject (e.g., transform), then you need to cast it as such when instantiating:
```
public Transform thing;
void Start()
{
Transform t = (Transform)Instantiate(thing, Vector3.zero, Quaternion.identity);
}
```
## UI Elements for BioGage

*Purpose:* Creating a dynamic gage to indicate current level of chill, and use functionally as a indicator of biofeedback success
The UI work involves a few key steps, and can be used with multiple approaches:
1. All Rectranforms
2. Rectransforms & Images
I chose the **all rectransform** approach, as it required less additional code. Images can be used to adjust the gage by height of the parent image, but that required hard coding and adjusting any time the parent object height changed.
Two RectTransforms are required for a dynamic gage with a lagging threshold, one for the gage fill and one for the threshold bar, as shown below.
The **gage fill** can be manipulated based on input in realtime by setting the ```.localscale``` property to a new Vector3 that will change based on input (```percent```).
The **threshold bar** can be manipulated by setting the ```.anchoredPosition``` property to a new Vector2 that will change based on the total size of the gage fill using the ```sizeDelta``` property
- !**important** the ```sizeDelta``` property will represent size only if the anchors of the object are together. if the anchors are at the four corners, size delta will give you the difference in size between this object and its parent ([more on sizeDelta here](https://docs.unity3d.com/ScriptReference/RectTransform-sizeDelta.html))
```
[SerializeField]
RectTransform chillLevelFill;
[SerializeField]
RectTransform thresholdBar;
public void SetChillLevel(float biodata) {
float percent = biodata / 100f;
chillLevelFill.localScale = new Vector3(1f, percent, 1f);
}
public void AdjustThreshold(float _threshold) {
float percent = _threshold / 100f;
thresholdBar.anchoredPosition = new Vector2(0f, percent * chillLevelFill.sizeDelta.y);
}
```
## Raycasts
[Unity RayCast Link](https://hackmd.io/792Qgt0pQHmxuU0anH-r5w?both)
I used RayCasts in this game to determine if a GazePointer was intersecting with a specific spot on the floor, in order to better guide VR pointer for gaze selection of buttons.
- When the RayCast did hit a canvas with the tag "PauseFloor", the Gazepointer images were enabled to give the user feedback on button selection behavior
*Syntax:*
```
RaycastHit hit;
if(Physics.Raycast(transform.position, transform.forward, out hit)){
if(hit.collider){
//Do the thing
}
}
```
*Example:*
```
RaycastHit hit;
void Update() {
if (PauseScreen.IsPaused) {
EnableGazePointer();
}
if (PauseScreen.IsPaused == false) {
DisableGazePointer();
if (Physics.Raycast(transform.position, transform.forward, out hit)) {
if (hit.collider.tag == "PauseFloor") {
EnableGazePointer();
}
}
}
}
```
## Freezing an Object
I wanted to set up a script to freeze a GameObject when the game was paused.
- GameObject was already frozen along all coordinates except Z
- I wanted to selectively freeze and unfreeze the Z coordinate
**Combining RigidbodyConstraints & rigidbody.constraints worked** for this purpose.
- [Unity RidigbodyConstraints Documentation](https://docs.unity3d.com/ScriptReference/RigidbodyConstraints.html)
- [Unity Ridigbody.constraints Documentation](https://docs.unity3d.com/ScriptReference/Rigidbody-constraints.html)
- [This Unity forum on RigidbodyConstraints is also very helpful](https://answers.unity.com/questions/238887/can-you-unfreeze-a-rigidbodyconstraint-position-as.html)
*note: Rigidbody constraints are bitmasks, and thus rely on bitwise operators*
- use ~ mask to disable
- use | (bitwise or) to chain multiple constraint functions together
*Syntax for freezing all constraints*
```
Rigidbody.constraints = RigidbodyConstraints.FreezeAll;
```
*Syntax for disabling a single constraint:*
```
Rigidbody.constraints &= ~RigidbodyConstraints.FreezePositionY;
```
*Example (methods are RPCs as this action was performed on server object)*
```
[ClientRpc]
public void RpcFreezeOrb() {
gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
}
[ClientRpc]
public void RpcUnFreezeOrbZ() {
gameObject.GetComponent<Rigidbody>().constraints &= ~RigidbodyConstraints.FreezePositionZ;
}
```
## Inheritance for biodata
Originally I had coupled my gamemechanics to my biodata source in the class **ChillForce**
I decided to decouple these features by creating two separate scripts:
1. **Chill**
2. **ChillForce**
The **Chill class** has a few purposes
- Pulls in biodata stream and set it to ChillLevel (currently LF)
- Updates ChillLevel to be utilized by gamemechanics
- Sets thresholding mechnic to determine whether player isChillin
The **ChillForce class** becomes the template for game mechnics going forward
- **Inherits** from Chill class to use **ChillLevel** and **isChillin**
- Finds orb in scene and applies force to it on server across clients
- Plays sound feedback tied to ChillForce game mechanic
### Technical decision
- I decided to go with update functions over events, because I want the biodata source feeding ChillLevel to be determined in one place (the Chill class)
- My assumption was it would be more work and duplicative to change event subscriptions relating to ChillLevel biodata throughout all game mechanic scripts in the future
- There is a risk here that update functions are using more resources, although in my early research that does not seem to a concern, as a constantly changing event would do the same amount of processing and I haven't seen an impact on frame rate locally
Here's the event subscription based code
```
void Start(){
if (isLocalPlayer) {
User.current.dataManager.OnReceiveNewLF += HandleChillForce;
}
}
public void HandleChillForce(float _chillLevel) {
force = _chillLevel / 100;
Debug.Log("The force reading on " + gameObject.name + " is " + force);
}
```
and the setter/update version I'm currently using
```
private Chill playerChill;
public void SetPlayerChill(Chill _playerChill) {
playerChill = _playerChill;
}
void Start() {
SetPlayerChill(GetComponent<Chill>());
}
public void SetChillForce(float _chillLevel) {
force = _chillLevel / 100;
Debug.Log("The force reading on " + gameObject.name + " is " + force);
}
```
## Spawning
### Spawning gameobjects without "(Clone)"
This is [very easy to do for single player](https://answers.unity.com/questions/28114/remove-clone-from-instantiated-gameobjects-name.html), but seems super tricky to do across a network, as the network creates an object on the server first, then copies of spawned objects on each client.
For now, no advantage to the gameplay here, so not pursuing unless utility arises later.
### Spawning Players in unoccupied spawnpoints
to be investigated...
- [forum link here](https://forum.unity.com/threads/spawn-a-player-and-set-their-name.337224/)
- [unity spawn gameobject doc here](https://docs.unity3d.com/Manual/UNetSpawning.html)

### Spawning player as specator
This was a doozy. I went with the approach of spawning multiple different prefabs using a CustomNetworkManager script that inherits from Network Manager.
**Scripts used:**
1. CustomNetworkManager
2. Spectator
3. SetupSpectator
**Custom Network Manager**
Some core methods had to be overridden for this to be successful:
```
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;
public class CustomNetworkManager : NetworkManager {
// used to assign player & spectator roles
public int playerPrefabId;
// # of player prefabs we want, to be set in inspector
public GameObject[] playerPrefabs;
// we'll change this to tell the system which prefab to spawn
GameObject player;
// we'll change this to tell the system where to spawn the prefab
Transform startPos;
//subclass for sending network messages
public class NetworkMessage : MessageBase {
// this lets us pass our playerPrefabId to the server from client
public int chosenType;
Debug.Log("server add prefab with type " + selectedType);
}
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader) {
NetworkMessage message = extraMessageReader.ReadMessage<NetworkMessage>();
int selectedType = message.chosenType;
Debug.Log("server add player with type " + selectedType);
if (selectedType == 0){
// user is a player, spawn round robin at spawnable locations
startPos = GetStartPosition();
}
if (selectedType == 1){
// user is a spectator, spawn at solo spectator location
startPos = GameObject.Find("SpectatorStartPos").transform;
}
player = (GameObject)Instantiate(playerPrefabs[selectedType], startPos.position, startPos.rotation);
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
public override void OnClientConnect(NetworkConnection conn) {
NetworkMessage prefabType = new NetworkMessage();
#if UNITY_EDITOR
// assigning spectator role for unity
playerPrefabId = 1;
#else
// assigining player role for non-unity platforms
playerPrefabId = 0;
#endif
// assigniing playerPrefabId to chosenClass to pass to server
prefabType.chosenType = playerPrefabId;
// adds a player GameObject for this client, with ready connection
// AddPlayer message sent to the server
ClientScene.AddPlayer(conn, 0, prefabType);
}
public override void OnClientSceneChanged(NetworkConnection conn) {
// overriding and commenting this out so we don't set the connection twice
//base.OnClientSceneChanged(conn);
}
}
```

**Spectator**
```
[RequireComponent(typeof(SetupSpectator))]
public class Spectator : NetworkBehaviour {
void Start(){
// setup the spectator for only the spectator
if (isLocalPlayer){
GetComponent<SetupSpectator>().SetSpectatorDefaults();
}
}
}
```
**SetupSpectator**
```
public class SetupSpectator : MonoBehaviour {
[SerializeField]
GameObject specCamera;
public void SetSpectatorDefaults(){
// camera active, only for spectator (due to spectator script)
specCamera.SetActive(true);
}
}
```
**PlayerSetup Component**
Must add SpectatorCamera to Game Objects to Disable Array on PlayerPrefab, so spectator camera doesn't interfere with player veiw

```
public class PlayerSetup : NetworkBehaviour {
[SerializeField]
private GameObject[] gameObjectsToDisable;
void Start() {
//disable gameobjects that should only be active on player we control
if (!isLocalPlayer) {
DisableGameObjects();
} else {
GetComponent<Player>().SetupPlayer();
}
}
//disable gameobjects (on other players)
public void DisableGameObjects() {
for (int i = 0; i < gameObjectsToDisable.Length; i++) {
gameObjectsToDisable[i].SetActive(false);
}
}
}
```
Relevant Resources:
1. [Spawning multiple player prefabs](https://forum.unity.com/threads/using-multiple-player-prefabs-with-the-network-manager.438930/) - most helpful in getting started
2. [NetworkManager](https://docs.unity3d.com/2018.2/Documentation/ScriptReference/Networking.NetworkManager.html) - handles network systems including clients, servers, scenes, events
3. [NetworkReader](https://docs.unity3d.com/2018.1/Documentation/ScriptReference/Networking.NetworkReader.html) - allows you to send messages from the client to the server
4. [ClientScene](https://docs.unity3d.com/2018.2/Documentation/ScriptReference/Networking.ClientScene.html) - manager player objects on the client, requests server to add/remove
5. [NetworkMessage](https://docs.unity3d.com/2018.1/Documentation/ScriptReference/Networking.NetworkMessage.html) - sent from client to server to do things like add player
## Spectator driven authority
When a spectator starts the game, spectator is the host (server is run from spectator's machine). See this very useful bit about how **Authority** works in unity based on the HILAPI "server authority" model:
### Authority
> Servers and clients can both manage a GameObject’s behavior. The concept of “authority” refers to how and where a GameObject is managed. Unity’s HLAPI is based around “server authority” as the default state, where the Server (the host) has authority over all GameObjects which do not represent players. Player GameObjects are a special case and treated as having “local authority”. You may want to build your game using a different system of authority - for more details, see [Network Authority](https://docs.unity3d.com/Manual/UNetAuthority.html).
Source doc: [Unity HLAPI System Concepts](https://docs.unity3d.com/Manual/UNetConcepts.html)
This was causing a severe issue in my game, where games started from the spectator compromised a core gamemechanic of the other players being able to manipulte a central "orb" object.
**This is also why you need to use commands & RPCs from a client**
## Platform dependent compilation
Its possible to write code specific to the platform the game is being played on (this is important if building a cross-platform experience). Here is some sample code that can be used within a function to dictate different behaviors. [More here](https://docs.unity3d.com/Manual/PlatformDependentCompilation.html):
```
void HandleInput()
{
#if UNITY_EDITOR
// Handle keyboard and mouse input
#elif UNITY_IOS || UNITY_ANDROID
// Handle touch screen input
#endif
}
```
## 11. Finding gameobjects
### By name
```
GameObject.Find("Orb");
```
### By tag
```
// single GameObject
GameObject.FindWithTag("Player")
// multiple Gameobjects returns array
GameObject.FindGameObjectsWithTag("Player")
```
### By layer
```
// create an array of all GameObjects
// Add them to a list if they have the right layer
// return the list as a GameObject array
GameObject[] FindGameObjectsWithLayer (int layer) {
GameObject[] goArray = FindObjectsOfType(typeof(GameObject)) as GameObject[];
List<GameObject> goList = new List<GameObject>();
for (int i = 0; i < goArray.Length; i++) {
if (goArray[i].layer == layer) {
goList.Add(goArray[i]);
}
}
if (goList.Count == 0) {
return null;
}
return goList.ToArray();
}
```
```
// define the remoteLayerTag and show in inspector
[SerializeField]
string remoteLayerName = "RemotePlayer";
/// method to set player with layer remoteLayerTag to otherPlayer
void FindOtherPlayer(){
GameObject[] otherPlayers = FindGameObjectsWithLayer(LayerMask.NameToLayer(remoteLayerName));
if (otherPlayers == null) {
Debug.Log("Didn't find another player in the scene");
return;
}
if (otherPlayers.Length == 1) {
otherPlayer = otherPlayers[0];
Debug.Log("Found a player in the scene");
otherPlayerReady = otherPlayer.GetComponent<Player>();
Debug.Log("Found " + otherPlayer + "'s Player component");
}
if (otherPlayers.Length > 1){
Debug.Log("we've got more than two players in here");
}
}
// cache otherPlayer in Start to save performance
void Start(){
FindOtherPlayer();
}
```
## Accessing members of other scripts from script:
### i. Make member static
```
// for the variable you want to access
public class Player : Monobehavior{
public static int health;
}
public class SomeOtherClass : MonoBehavior{
// access the variable above from any other script
Player.health -= damage;
}
```
*Usage Notes:*
- Pros: easy to use
- Cons: static means only one instance, so can only use if member occurs once
### ii. Use GetComponent
*a. Script is on same object*
```
ScriptName other;
other = GetComponent("ScriptName");
other.somVariable = 5;
```
*b. Script is on another object*
```
ScriptName other;
// caching object to save performance
void Start(){
other = GameObject.Find("ObjectName").GetComponent("ScriptName");
}
void Update(){
other.someVariable = 5;
}
```
*Usage Notes:*
- Pros: works for almost all variable and functions of any type
- Cons: expensive if not cached
### iii. SendMessage()
```
// If the script is on the same object, gameObject. is unnecessary
GameObject.Find("OtherObject").SendMessage("Dosomething");
gameObject.SendMessage ("Dosomething");
// another script
void Dosomething(){
//Do SendMessage;
}
// if the function requires parameter, they are passed as such
GameObject.Find("OtherObject").SendMessage("Dosomething",10,20);
void int Dosomething(int a, int b) {
return a + b;
}
```
*Usage Notes*
- Pros: easy & straightforward
- Cons: really expensive, not to be used when action repeated
- BoradcastMessage() works the same as SendMEssage(), but send the message to the object and all children of the object
## [SyncVars]
### hooks
Call a function when the SyncVar changes
[*more info here*](https://vis2k.github.io/Mirror/Classes/SyncVarHook.html)
```
// Unity syntax
[SyncVar(hook = "OnChangeHealth")]
public int m_CurrentHealth = m_MaxHealth;
void OnChangeHealth(int health) {
healthBar.sizeDelta = new Vector2(health, healthBar.sizeDelta.y);
}
```
### bools
syntax for getting/setting a [SyncVar] bool:
```
// accessible to other scripts
[SyncVar]
private bool _isReady;
public bool isReady {
get { return _isReady; }
set { _isReady = value; }
}
// uses protected set to restrict setter
[SyncVar]
private bool _isDead = false;
public bool isDead {
get { return _isDead; }
protected set { _isDead = value; }
}
```
## Debug
### Debug.Break
Pauses editor so you can checks method execution
**Ex: OnTriggerEnter**
Click on Debug.Log statement to see which gameobject what hit in hierarchy
```
void OnTriggerEnter(Collider other) {
if (other.tag == "Orb") {
isTouchingOrb = true;
Debug.Log("hit" + other.gameObject);
Debug.Break();
}
}
```
## Getter / Setter

can use bottom option if don't need SyncVar (Unet)
protected set only allows Player class or classes that derive from Player class to call it