jump start guide - BeardedManStudios/ForgeNetworkingRemastered GitHub Wiki

This example will go through all of the major uses of Forge Networking Remastered. You will learn the basic building blocks required to build any online multiplayer game using Forge Networking Remastered (FNR).

Hence, it is heavily recommended to read or re-create the previous examples, or at least, see the official series of tutorials, in order to understand fully this project/example. ForgeYoutubeImageLink

Introduction

The game we will be creating for this example is simple. We will have two or more players that can see each other as capsules and we will have a cube (trigger)
When players walk into the cube, the cube is triggered and will spawn a sphere to the center of the map that will have a rigidbody and a random X and Z force. Note that the trigger will need to be destroyed when the ball is spawned in order to prevent it from spawning more than one sphere. When the sphere is collided with a player, it is destroyed and we add a point to the player who touched the sphere.

Very First Step

The first step before adding any networking to your game is to identify where the critical points to add networking will be. Below is a list of information we know that will need to be sent across the network in order for the game to play as expected on all connections.

  • Spawn player
  • Sync player positions
  • Sync player names
  • Destroy trigger after touch
  • Spawn sphere
  • Sync sphere position
  • Change sphere velocity on spawn
  • Add points for colliding with sphere
  • Change sphere position after collision

Next we can go through each of our required network fields and determine which ones should be done with an RPC, network instantiate, network destroy, and which ones should be done by synchronizing a variable.

  • Instantiate Spawn player
  • Variable Sync player positions
  • RPC Sync player names
  • Destroy Remove trigger after touch
  • Instantiate Spawn sphere
  • Variable Sync sphere position
  • RPC Change sphere velocity on spawn
  • RPC Show the last person who got the ball
  • RPC Change sphere position and velocity after collision

By doing this we are easily able to see what we should do next when we move onto the Network Contract Wizard (NCW).

Setup the Game

Next, before we setup our networking logic, we will want to setup our base game and all of it's objects and prefabs (No scripting yet).

First we will create the basic world for our players to run around in.

  1. Create folder: Scenes
  2. Create folder: Scripts
  3. Create folder: Prefabs
  4. Create folder: Materials
  5. Create a new scene
  6. Delete the Main Camera
  7. Save your scene as a new scene into Scenes folder
  8. Create a material in Materials folder
    1. Name: Trigger
    2. Color: 0, 255, 0, 128
    3. Set Rendering Mode to Transparent
  9. Create a material in Materials folder
    1. Name: GameBall
    2. Color: 255, 0, 0, 255
  10. Create a material in Materials folder
    1. Name: Player
    2. Color: 0, 0, 255, 255

Do the following in the Scene we just created.

  1. Create a cube:
    1. Position: 0, 0, 0
    2. Rotation: 0, 0, 0
    3. Scale: 25, 0.1, 25
    4. Name: Floor
  2. Create a cube
    1. Position: -12.5, 5, 0
    2. Rotation: 0, 0, 0
    3. Scale: 0.1, 10, 25
    4. Name: Left Wall
  3. Create a cube
    1. Position: 12.5, 5, 0
    2. Rotation: 0, 0, 0
    3. Scale: 0.1, 10, 25
    4. Name: Right Wall
  4. Create a cube
    1. Position: 0, 5, -12.5
    2. Rotation: 0, 0, 0
    3. Scale: 25, 10, 0.1
    4. Name: Front Wall
  5. Create a cube
    1. Position: 0, 5, 12.5
    2. Rotation: 0, 0, 0
    3. Scale: 25, 10, 0.1
    4. Name: Back Wall
  6. Create a cube
    1. Position: 7.5, 5, 7.5
    2. Rotation: 0, 0, 0
    3. Scale: 10, 10, 10
    4. Name: Start Trigger
    5. Set Material to the Trigger material we created
    6. Check the Is Trigger box in the box collider component on this object
  7. Create a sphere
    1. Position: 0, 10, 0
    2. Rotation: 0, 0, 0
    3. Scale: 1, 1, 1
    4. Name: GameBall
    5. (Optional) Set the material of SphereCollider, to Bouncy, if you have the Unity Standard Assets
    6. Set Material to the GameBall material we previously created
    7. Add a Rigidbody component
    8. Save as a Prefab in the Prefabs folder
    9. Delete from scene
  8. Create an empty Game Object
    1. Position: 0, 0, 0
    2. Rotation: 0, 0, 0
    3. Scale: 1, 1, 1
    4. Name: Game Logic
  9. Select the menu item Game Object>UI>Text
    1. The Text that is created in the Hierarchy
    2. Rename it to Last Scored
    3. Change the Text field to be empty
    4. Set the Anchor Preset to Top Left (this is the one with the top left being highlighted in the anchor image) e. Set the Pivot X to 0
    5. Set the Pivot Y to 1
    6. Set Pos X to 0
    7. Set Pos Y to 0
    8. Set Pos Z to 0
    9. Set Width to 500
  10. (Optional) Create a Light>Directional Light
    1. Set its position and rotation at 0,0,0.
    2. Set Rotation X at 90.
  11. Save the scene

Now we have completed the setup for our game, let's setup our Build Settings

  1. Open Build Settings from File>Build Settings (Ctrl + Shift + B on windows)
  2. Add MultiplayerMenu scene as the 0th scene index
  3. Add your newly saved scene as the 1st index
  4. Click Player Settings...
  5. Turn on Run In Background

Now all that is left to do is setup our Player prefab and then we will be ready to jump into setting up our network.

  1. Go to Window>General>Asset Store
  2. Open Asset Store tab, search "Standard Assets", alternatively follow the steps in the Unity manual.
  3. Click Import, then make sure the following are checked: Characters, CrossPlatformInput, Utility
  4. Open the Standard Assets>Characters>FirstPersonCharacter>Prefabs directory
  5. Drag the FPSController prefab into the scene
  6. Rename the prefab to Player
  7. Add a Mesh Filter component
  8. Add a Mesh Renderer component
  9. Select the Capsule as the Mesh in the MeshFilter component
  10. Open the Materials drop down in the Mesh Renderer
  11. Select Player material from Materials folder in MeshRenderer's Element 0
  12. Add a Capsule Collider (keep its default settings) CapsuleCollider
  13. Drag the Player GameObject from the Hierarchy into the Prefabs folder that we made in the beginning
  14. Delete the Player Game Object from the scene
  15. Save the project

Now you are ready to start setting up your network contract using the Network Contract Wizard.

Network Contract Wizard

The Network Contract Wizard (NCW) is responsible for creating the blueprint of the network communication. We will use this UI to setup the basic classes, fields, and remote procedure calls needed for our network communication. To get started, open the Network Contract Wizard UI by going to Window>Forge Networking>Network Contract Wizard within Unity. In this window you will be presented with all the current network classes.

If you included the Bearded Man Studios Inc examples folder then you should see a couple pre-made options initially.

Now let's get started on making the network contract for our simple game:

  1. Click the Create button
  2. Type in Player into the name box
  3. Click the Add Field button
    1. Type position into the text box
    2. Select the drop down
    3. Select VECTOR3 from the options
    4. Click the Interpolate button and leave the value at 0.15
  4. Click the Add RPC button
    1. Type UpdateName into the text field
    2. Open the Arguments cascade
      1. Click the add-button button
      2. Type newName into the text field
      3. Click the drop down
      4. Select STRING from the drop down
  5. Click Save & Compile

Player

What this will do is create and modify some classes in the Generated folder of your project. Before we continue to add the other network contracts, let's setup our Player class.

  1. Open the Scripts folder
  2. Create a new C# script named Player
  3. Open the Prefabs folder and select the Player prefab
  4. Click Add Component
  5. Add the Player script to the prefab
  6. Select the Scripts folder
  7. Open the Player.cs script you just created
  8. Insert the following code

Player.cs

// We use this namespace as it is where our PlayerBehavior was generated
using BeardedManStudios.Forge.Networking;
using BeardedManStudios.Forge.Networking.Generated;
using UnityEngine;
using UnityStandardAssets.Characters.FirstPerson;

// We extend PlayerBehavior which extends NetworkBehavior which extends MonoBehaviour
public class Player : PlayerBehavior
{
	// These strings are to be used to construct a player's name
	// by randomly combining 2 strings
	private string[] nameParts = new string[] { 
				"crazy", "cat", "dog", "homie", "bobble", "mr", 
				"ms", "mrs", "castle", "flip", "flop" };

	public string Name { get; private set; }


	
	// NetworkStart() is **automatically** called, when a networkObject 
	// has been fully setup on the network and ready/finalized on the network!
	// In simpler words, think of it like Unity's Start() but for the network ;)
	protected override void NetworkStart()
	{
		base.NetworkStart();

		// If this networkObject is actually the **enemy** Player
		// hence not the one we will control and own
		if (!networkObject.IsOwner)
		{
			// Don't render through a camera that is not ours
			// Don't listen to audio through a listener that is not ours
			transform.GetChild(0).gameObject.SetActive(false);

			// Don't accept inputs from objects that are not ours
			GetComponent<FirstPersonController>().enabled = false;

			// There is no reason to try and simulate physics since 
			// the position is being sent across the network anyway
			Destroy(GetComponent<Rigidbody>());
		}

		// Assign the name when this object is setup on the network
		ChangeName();
	}

	public void ChangeName()
	{
		// Only the owning client of this object can assign the name
		if (!networkObject.IsOwner)
			return;

		// Get a random index for the first name
		int first = Random.Range(0, nameParts.Length - 1);
		// Get a random index for the last name
		int last = Random.Range(0, nameParts.Length - 1);

		// Assign the name to the random selection
		Name = nameParts[first] + " " + nameParts[last];

		// Send an RPC to let everyone know what the name is for this player
		// We use "AllBuffered" so that if people come late they will get the
		// latest name for this object
		// We pass in "Name" for the args because we have 1 argument that 
		// is to be a string as it is set in the NCW
		networkObject.SendRpc(RPC_UPDATE_NAME, Receivers.AllBuffered, Name);
	}

	// Default Unity update method
	private void Update()
	{
		// Check to see if we are NOT the owner of this player
		if (!networkObject.IsOwner)
		{
			// Set this object's transform.position
 			// to the position that is syndicated across the network
			// In simpler words, its position is updated via the network.
			transform.position = networkObject.position;
			return;
		}

		// When our position changes, the networkObject.position 
		// will detect the change based on this assignment automatically
		// and this data will then be syndicated across the network
                // on the next update pass for this networkObject.
                // In simpler words, our local position (transform.position) is 
                // registered and shared onto the network -> see above if
		networkObject.position = transform.position;
	}

	// Override the abstract RPC method that we made in the NCW
	public override void UpdateName(RpcArgs args)
	{
		// Since there is only 1 argument and it is a string we can safely
		// cast the first argument to a string, knowing that it is going to
		// be the name for this player
		Name = args.GetNext<string>();
	}
}

For more information on NetworkStart() used above, read Editing Unity Integration Network Start

This is all the code we need to allow for all of the connections to see the movement of the players. The next thing we need is to be able to actually instantiate our Player prefab since it will not be in the scene at the start of the game. To do this let's open the NCW window again.

  1. Click the Create button
  2. Type in GameLogic in the name box
  3. Click the Add RPC button
    1. Type PlayerScored into the text field
    2. Open the Arguments cascade
      1. Click the add-button button
      2. Type playerName into the text field
      3. Click the drop down
      4. Select STRING from the drop down
  4. Click Save & Compile

GameLogic

What this will do is create and modify some classes in the Generated folder of your project. Before we continue to add the other network contracts, let's setup our GameLogic class.

  1. Open the Scripts folder
  2. Create a new C# script named GameLogic
  3. Select the Game Logic Game Object from the Hierarchy of our previously saved scene
  4. Click Add Component
  5. Add the GameLogic script to the Game Object
  6. Select the Scripts folder
  7. Open the GameLogic.cs script you just created
  8. Insert the following code

GameLogic.cs

// We use this namespace as it is where our GameLogicBehavior was generated
using BeardedManStudios.Forge.Networking;
using BeardedManStudios.Forge.Networking.Generated;
using BeardedManStudios.Forge.Networking.Unity;
using UnityEngine;
using UnityEngine.UI;

// We extend GameLogicBehavior which extends NetworkBehavior which extends MonoBehaviour
public class GameLogic : GameLogicBehavior
{
	public Text scoreLabel;
	private void Start()
	{
		// This will be called on every client so each client
		// will essentially instantiate their own player on the network. 
		// We also pass in the position we want them to spawn at
		NetworkManager.Instance.InstantiatePlayer(position: new Vector3(0, 5, 0));
	}

	// Override the abstract RPC method that we made in the NCW
	public override void PlayerScored(RpcArgs args)
	{
		// Since there is only 1 argument and it is a string we can safely
		// cast the first argument to a string knowing that it is going to
		// be the name for the scoring player
		string playerName = args.GetNext<string>();

		// Update the UI to show the last player that scored
		scoreLabel.text = "Last player to score was: " + playerName;
	}
}

Before we proceed, when we call NetworkManager.Instance.InstantiatePlayer on Start(), Forge doesn't know what it is supposed to instantiate. So, for Forge to instantiate the Player prefab on the above call, do the following:

  1. On the Project tab, Open Bearded Man Studios Inc > Prefabs
  2. Select the prefab named NetworkManager
  3. In the Player Network Object array field, put the Player prefab from the Prefabs folder as the Element 0 (0th index)

Now we are able to not only spawn the player, but we are also able to print out the last player that scored to the screen. Talking about scoring, we possibly want to get the ball rolling (if you would excuse the expression) and instantiate the ball and serialize it's position to all the clients. However just before that, lets fill out our scoreLabel object on our Game Logic Game Object.

  1. Select the Game Logic from the hierarchy
  2. Drag the Last Scored UI Game Object from the hierarchy to the Score Label field on the GameLogic script

Okay, now we are actually ready to make the ball now! So let's start by opening up the NCW window once again.

  1. Click the Create button
  2. Type in GameBall into the name box
  3. Click the Add Field button
    1. Type position into the text box
    2. Select the drop down
    3. Select VECTOR3 from the options
    4. Click the Interpolate button and leave the value at 0.15
  4. Click Save & Compile

Imgur

What this will do is create and modify some classes in the Generated folder of your project. Before we continue to add the other network contracts, let's setup our GameBall class.

  1. Open the Scripts folder
  2. Create a new C# script named GameBall
  3. Open the Prefabs folder and select the GameBall prefab
  4. Click Add Component
  5. Add the GameBall script to the prefab
  6. Select the Scripts folder
  7. Open the GameBall.cs script you just created
  8. Insert the following code

GameBall.cs

// We use this namespace as it is where our BallBehavior was generated
using BeardedManStudios.Forge.Networking;
using BeardedManStudios.Forge.Networking.Generated;
using BeardedManStudios.Forge.Networking.Unity;
using UnityEngine;

// We extend GameBallBehavior which extends NetworkBehavior which extends MonoBehaviour
public class GameBall : GameBallBehavior
{
	private Rigidbody rigidbodyRef;
	private GameLogic gameLogic;

	private void Awake()
	{
		rigidbodyRef = GetComponent<Rigidbody>();
		gameLogic = FindObjectOfType<GameLogic>();
	}

	// Default Unity update method
	private void Update()
	{
		// Check to see if we are the owner of this ball
		if (!networkObject.IsOwner)
		{
			// If we are not the owner then we set the position to the
			// position that is syndicated across the network
 			// for this ball
			transform.position = networkObject.position;
			return;
		}

		// Registers and syndicates transform.position  
                // across the network, on the next update pass
		networkObject.position = transform.position;
	}

	private void OnCollisionEnter(Collision triggeringCollision)
	{
		// We are making this authoritative by only
		// allowing the server to call it
		if (!networkObject.IsServer)
			return;

		// Only continue, if a player touches the ball
		// otherwise normal collision/bounciness happens.
		if (triggeringCollision.gameObject.GetComponent<Player>() == null)
			return;

		// **Call an RPC from gameLogic** to print the player's name 
		// as the last player to touch the ball
		gameLogic.networkObject.SendRpc(
                	GameLogicBehavior.RPC_PLAYER_SCORED,
			Receivers.All,
			triggeringCollision.gameObject.GetComponent<Player>().Name
		);

		// Reset the ball
		Reset();
	}

        /* Minor note on this function:
        /// Check out the invokes and references of this function.
        /// This function is called always from a server.
        /// 
        /// The velocity or force dont matter for other clients
        /// since the server relays the **position** across the network.
        /// And we only really care about the position.
        */
	public void Reset()
	{
		// Move the ball to 0, 10, 0
		transform.position = Vector3.up * 10;

		// Reset the velocity for this object to zero
		rigidbodyRef.velocity = Vector3.zero;

		// Create a random force to apply to this object 
		// between 300 to 500 or -300 to -500
		Vector3 force = new Vector3(0, 0, 0);
		force.x = Random.Range(300, 500);
		force.z = Random.Range(300, 500);

		// 50% chance to make the force.x inverted/negative.
		if (Random.value < 0.5f)
			force.x *= -1;

		// 50% chance to make the force.z inverted/negative.
		if (Random.value < 0.5f)
			force.z *= -1;

		// Add the random force to the ball
		rigidbodyRef.AddForce(force);
	}
}

With the ball complete, we are finally ready to move onto the last script that we will need to create, the behavior for the trigger that spawns the above GameBall!

  1. Open the Scripts folder
  2. Create a new C# script named StartTrigger
  3. Select the Start Trigger Game Object from the Hierarchy of our previously saved scene
  4. Click Add Component
  5. Add the StartTrigger script to the Game Object named "StartTrigger"
  6. Select the Scripts folder
  7. Open the StartTrigger.cs script you just created
  8. Insert the following code

StartTrigger.cs

using BeardedManStudios.Forge.Networking.Unity;
using UnityEngine;

// Do note that this script is just a Monobehaviour
// but we have access to NetworkManager.Instance which is very helpful!
public class StartTrigger : MonoBehaviour
{

	private void Update()
	{
		// If the game started we will remove this trigger from the scene
		if (FindObjectOfType<GameBall>() != null)
			Destroy(gameObject);
	}

	private void OnTriggerEnter(Collider triggeringCollider)
	{

		// Only allow the server player to start the game 
		// so the server is the owner of the ball
		// because if a client is the owner of the ball
		// and that client disconnects, the ball will be destroyed.
		if (!NetworkManager.Instance.IsServer)
			return;

		// We detect if the colliding gameobject has
		// the Component/Script Player.cs and if it doesn't have it
		// we simply don't do anything by using return;
		if (triggeringCollider.GetComponent<Player>() == null)
                	return;


		// We need to create the ball on the network
		GameBall ball = NetworkManager.Instance.InstantiateGameBall() as GameBall;

		// Reset the ball position and give it a random velocity
		ball.Reset();

		// We destroy this trigger gameobject since we dont need it anymore.
		// This gameobject is destroyed only for the server.
		// However, it is destroyed for the client via Update() because
		// the ball is spawned.
		Destroy(gameObject);
	}
}

Like GameLogic.cs, calling NetworkManager.Instance.InstantiateGameBall, Forge doesn't know it should spawn the prefab GameBall. So, for Forge to instantiate the GameBall prefab on the above call, do the following:

  1. On the Project tab, Open Bearded Man Studios Inc > Prefabs
  2. Select the prefab named NetworkManager
  3. In the GameBall Network Object array field, put the GameBall prefab from the Prefabs folder as the Element 0 (0th index)

Congratulations

You have completed the steps for this tutorial. All that is left is to build, run and test it out. To do this just build the project as you normally would do within the Unity Editor.

  1. Build the project
  2. Run 2 instances of the project
  3. Select Host in one instance
  4. Click Connect in the second instance
  5. You may be prompted to allow access to the application on the firewall, which you will need to accept

Troubleshooting

Getting a null reference exception?

The most common user errors with this part of the documentation are:

  • Forgot to turn on Run in Background*
  • Tried pressing the play button in the scene and not loading the Multiplayer Menu scene first
  • Not setting up the multiplayer menu scene as index 0 and the demo scene as index 1
⚠️ **GitHub.com Fallback** ⚠️