Parsec

Uncategorized

Tutorial for working with the Parsec Unity plug-in featuring Strangest.io

We have a game developer SDK that enables online multiplayer for local multiplayer games. Our goal is to democratize access to online multiplayer and give the game developers the power of Parsec inside their game, so they can have online play without netcode and server maintenance as well as built-in distribution through the Parsec Arcade.

We are re-posting this amazing article from Strangest.io who shared more about integrating the Parsec Unity plug-in into their games Bizzarioware and Underworld. Go get these games on itch.io!


Illustrations by Nelson Ricardo for Strangest.io

Howdy from Austin, Texas! We are Strangest.io, an independent development studio bringing outsider art and interactive media together. We work as a collective of independent programmers, artists, animators and storytellers to bring interesting double-I experiences to the masses, mostly for free. While working on Q1 updates for our flagship titles Bizzarioware and Underworld, we came across an amazing piece of technology called Parsec. Our team was dead set on bringing online connectivity to our platform. We were searching for online multiplayer solutions and trying to vet them for which one would fit best for our projects. For us, technologies like Unet and Photon appeared to be too much for our limited development staff to implement in the time we had.

Thankfully, an acquaintance at Rock Paper Shotgun suggested that we look into Parsec. We didn’t know anything about Parsec or their technologies, but with this recommendation we decided to do some research. There were a couple of things about Parsec that really caught our attention:

  • No netcode.
  • Low-latency multiplayer connectivity.
  • Straight forward SDK with examples on Github.
  • Unity plugin available for free.
  • Supports Rewired right out of the box.
  • A large community of online players (their Discord has 43K+ members)!

Before you read more, we want you to know that we were not paid to write this! We wrote this article of our volition, and really just want to advocate for the use of this technology within the gamedev community. We want folks to know that there are great options available outside of Photon and Unet that are just as powerful and way easier to implement.

With that being said, Parsec was a dream come true in terms of what we were looking for in an online multiplayer platform. We were able to integrate our games with their platform within a month, given that all of us are full-time employees or students, and all of our tests so far have been successful. Our goal with this article is to walk you through how to get setup with Parsec in Unity so that you can bring your projects online and available to a large player-base with minimal effort. Now, lets get to the meat and potatoes…


Getting Started:

Requesting a GameID:

The first thing we would suggest doing is emailing Parsec about your project. Let them know what it is that you are working on, provide them with a 440×600 display card graphic and request a GameID. The GameID is the most crucial part of that email, without it — you will not be able to integrate your game with Parsec. In our experience, we received a reply from their team within a 24 hour period with our GameID’s for Bizzarioware and Underworld. You can continue on through the rest of the implementation without a GameID (by using the one for the test project used in the Github examples), but eventually you will need to grab one. Also, the display card graphic is what end users will see when a game is available to join on their platform. Here is an example of what Bizzarioware looks like when a session is open for folks to join:

The Bizzarioware display card graphic, visible when a public game session is open in Parsec Arcade.

Downloading the ParsecUnity plugin and reviewing the example project:

After you have requested a GameID, you will want to navigate to the ParsecUnity repository and clone or download those files. Now would also be a good time to download the example project to get a general idea of how the ParsecUnity plugin is implemented. The example project is particularly useful as it demonstrates the different steps of authenticating and starting a Parsec session. We definitely suggest spending some time reviewing how the sample project is setup, creating, authenticating and starting Parsec sessions as well reviewing the GameManager.cs script. After you spend some time reviewing how the example project works and testing it in the Parsec Arcade, we can continue on to importing the plugin and implementing it into a project.

Importing the ParsecUnity package:

When our team was working through the Parsec implementation for Bizzarioware and Underworld, one of the issues we came across was importing external DLL’s into Unity. There was a surprising lack of information on how to do this — so we thought we would write up a quick step to get Parsec into Unity properly:

  • The first thing you will want to do is import the ‘ParsecUnity’ folder into the base directory of your Unity project.
  • After the package is imported, go to Edit > Project Settings > Player > Other Settings > Configuration, and set the ‘API Compatibility Level’ field to .Net 4.6 or higher. Do the same for the ‘Scripting Runtime Version’.
  • Navigate back to the base directory of your project and find the ‘ParsecUnity’ folder. Right-click on the folder and select ‘Reimport’. This will reimport the assets in this directory.
  • Restart Unity.

After Unity has been restarted, navigate into the ‘ParsecUnity’ directory and you should see the following public scripts when highlighting the ‘ParsecUnity46.dll’ file:

You should now be able to select from ParsecStreamCamera, ParsecStreamFull and ParsecStreamGeneral.

If you can see all of the above scripts, then your DLL is more than likely imported properly. These instructions may change depending on what version of Unity your project is in. For reference, our projects are in the latest LTS version of Unity 2017 (2017.4.361f).

Illustrations by Nelson Ricardo for Strangest.io

Implementing Parsec:

Creating a ‘ParsecOnline’ GameObject:

This is where your review of the example projects ‘GameManager’ script will come into play. This GameObject is going to vary quite a bit from project to project as it will depend on what you need to implement for your circumstance. For this example, we are going to walk through the basic game manager for Bizzarioware and how it works. In our case — there are essentially three components of our ‘ParsecOnline’ GameObject:

  • ParsecStream Component
  • ParsecOnlineController Component
  • ConnectedPartner Component

ParsecStream:

The first component that you will need on your GameObject is a Parsec streamer. Parsec provides two streaming components, each of which has their own use. ParsecStreamFull will stream the end result of your game’s render loop and ParsecStreamCamera will stream the output of the camera it is attached to (Note: both of these classes inherit from ParsecStreamGeneral).

We suggest that you add this component to a camera within your scene. When the component is added to the GameObject, you will see that it requires an AudioSource, AudioListener and Camera (more on the AudioSource/Listener later). The streamer will pickup the audio for its associated listener and stream it along with the video. There are two configurable elements of this component. The first is labeled ‘GameID’ — this field is where you will provide the GameID that you requested from Parsec earlier. Alternatively, while you are testing the technology — you can use the one provided in the example project to authenticate and start streaming session. The second is an OnUserAuthenticated event — this is where you will link to your ParsecOnlineController in the next step.

ParsecOnlineController:

The second component that you will need on your GameObject is an online controller. This will equate to the GameManager script from the example and provide you with the tools required to create, authenticate and start a Parsec session. We want to reiterate that this component will vary greatly depending on how you want Parsec to interface with your project. With that being said, here are a few functions that you will need to port over from the GameManager example in one way or another:

AuthenticationPoll: This function is used to provide us with the status of our Parsec session. In addition, we can add code to show/hide GameObjects in our scene when creating a session. For example, you may not want to allow users to start a session of your game without first creating a public Parsec lobby. For this, you could hide your start button and show it when the session is approved. You can see a similar example to this in case ParsecUnity.API.SessionResultEnum.CodeApproved:

public void AuthenticationPoll(ParsecUnity.API.SessionResultDataData data, ParsecUnity.API.SessionResultEnum status)
 {
  switch (status)
  {
  case ParsecUnity.API.SessionResultEnum.PolledTooSoon:
   break;
  case ParsecUnity.API.SessionResultEnum.Pending:
   StatusField.text = "Waiting for response...";
   break;
  case ParsecUnity.API.SessionResultEnum.CodeApproved:
   StatusField.text = "Code Approved...";
   authData = data;
   ParsecControlPanel.gameObject.SetActive(true);
   break;
  case ParsecUnity.API.SessionResultEnum.CodeInvallidExpiredDenied:
   StatusField.text = "Code Expired...";
   break;
  case ParsecUnity.API.SessionResultEnum.Unknown:
   StatusField.text = "Unknown State...";
   break;
  default:
   break;
  }
 }

GetAccessCode: This function is used to call a method in ParsecStreamGeneral called RequestCodeAndPoll. RequestCodeAndPoll will return session data for our Parsec session and allow the end user to authenticate the current session. In our example, this will display the URL that the end user will need to visit and the code they will need to provide Parsec:

public void GetAccessCode(){
  SessionData sessionData = streamer.RequestCodeAndPoll ();
  if ((sessionData != null) && (sessionData.data != null)){
   VerificationURI.text = sessionData.data.verification_uri;
   UserCode.text = sessionData.data.user_code;
   StatusField.text = "Waiting on authentication from host...";
  }
 }

StartParsec and StopParsec: These functions will allow us to start and stop streaming our Parsec session. An important part of StartParsec is passing in arguments for our session. You will need to pass in how many players can connect to the session (int), whether or not the game is publicly available in the Parsec Arcade (bool), the name of your game (string), the description of your game (string) and the authenticated session id (sessionID). This function will also also provide us with the URL to provide someone so that they can join our session:

public void startParsec(){
  streamer.StartParsec (2, true, "Bizzarioware", "An online Bizzarioware session.", authData.id);
  InviteCode.text = streamer.GetInviteUrl (authData, 600, 10);
 }public void stopParsec(){
  streamer.StopParsec ();
  ParsecControlPanel.gameObject.SetActive(false);
  VerificationURI.text = "";
  UserCode.text = "";
  StatusField.text = "Multiplayer session stopped...";
 }

Streamer_GuestConnected and Streamer_GuestDisconnected: As the name of these functions suggests, they will allow us to handle GuestConnected and GuestDisconnected events. In our example, GuestConnected will create an instance of a our custom Rewired controller, assign our Parsec guest to an available player (see Rewired) and assign our custom controller to the player. In addition, we are tracking our connected Parsec guest using a custom class that we modify variables for on connect. This allows us to do things like display the connected guest’s alias:

public void Streamer_GuestConnected(object sender, ParsecGaming.Parsec.ParsecGuest guest){
  int iPlayer = getFreePlayer ();
  if (guestsJoined > 3) return;
  if (iPlayer == 0) return; // iPlayer probably can be removed
  CustomController csController = ReInput.controllers.CreateCustomController (0, "Parsec_"+ guest.id);
  ParsecInput.AssignGuestToPlayer (guest, iPlayer);
  Rewired.Player m_rewiredPlayer = Rewired.ReInput.players.GetPlayer (iPlayer);
  ParsecUnity.ParsecRewiredInput.AssignCustomControllerToUser (guest, csController);
  m_rewiredPlayer.controllers.AddController (csController, true);
  if (guestsJoined == 0){
   connectedPartner.thisGuestParsecGuest = guest;
   connectedPartner.thisGuestCustomController = csController;
   connectedPartner.isConnected = true;
   connectedPartner.guestName = guest.name;
  }
  incrementFreePlayers ();
 }public void Streamer_GuestDisconnected(object sender, ParsecGaming.Parsec.ParsecGuest guest){
  Debug.Log ("A guest has disconnected.");
  if (guestsJoined == 0) return;
  guest.state = Parsec.ParsecGuestState.GUEST_DISCONNECTED;
  guestsJoined--;
  nextGuestIndex--;
 }

ConnectedPartner:

As discussed above, we are using a class to track information about our connected Parsec guest. For Underworld — we created a more complex system to track connected players as it is up to 8 concurrent players. For this article, we will be walking through the ConnectedPartner class created for Bizzarioware as it only supports two player multiplayer. The idea is that when a guest connects, you will modify the variables associated with an instance of this class to track their connection state, guest name, time active or inactive and the controller assigned to them. Here is a very basic example:

public int thisGuestID;
 public bool isActive = false;
 public bool isConnected = false;
 public string guestName;
 public string guestState;
 public Parsec.ParsecGuest thisGuestParsecGuest;
 public CustomController thisGuestCustomController;
 public float timeInactive;
 public string internalStatusCode;void LateUpdate(){
  if (isConnected){
   guestName = thisGuestParsecGuest.name.ToString ();
  } else {
   guestName = "Not Connected";
  }
  guestState = thisGuestParsecGuest.state.ToString ();
  checkStatus ();
 }void checkStatus(){
  timeInactive = calcTimeInactive ();
  if (timeInactive < 15.0f){
   internalStatusCode = "Green";
   isActive = true;
  }
  if (timeInactive > 15.0f && timeInactive < 35.0f){
   internalStatusCode = "Yellow";
   isActive = true;
  }
  if (timeInactive > 35.0f){
   internalStatusCode = "Red";
   isActive = false;
  }
 }

Note: calcTimeInactive is a function that returns a float for an approximation of how long it has been since the user has interacted with their controller. This is not built into Parsec so we used GetTimeInactive from Rewired.

How our GameObject is setup:

Here is a quick overview of how our GameObject is setup.

The Unity hierarchy:

Our ParsecOnline GameObject in the Unity hierarchy.

The ParsecOnline GameObject:

How the ParsecOnline GameObject is laid out.

The ConnectedGuest GameObject that is a child of ParsecOnline:

How the ConnectedGuest GameObject is laid out.

Testing your implementation:

In our projects, we created a quick UI similar to what is provided in the sample project. To test, you will want to hook all of the components in your ParsecOnlineController up to buttons, input fields, toggles etc. For a quick test, we would suggest hooking a button up to the following functions: GetAccessCode, StartParsec and StopParsec. You can write your verification URL, authentication code, invite link and whatever else you need to the console. The general workflow will be:

  • Invoke GetAccessCode, visit the verification URL and provide Parsec with your authentication code.
  • After the session is authenticated, start a private session — debug the invite link and visit it from your local machine or send it to a friend.
  • Join the session. If you are using Rewired (which we highly recommend) you can check to make sure that your controllers are being assigned properly and that you are receiving input. Check for audio issues, video issues and latency. There are some settings you can recommend to your users for best performance.
  • Make adjustments as necessary.

Here is some early footage of us testing Underworld’s Parsec implementation:


Illustrations by Nelson Ricardo for Strangest.io

Final Thoughts:

You should be using Rewired:

Parsec supports Unity’s default input methods. But after switching over to Rewired sometime last year, it is very clear that Unity’s default input setup is junk (note: this may no longer be the case in u2019.1). In a similar vein to our statement about not shilling for Parsec, we are also not shilling for Rewired — but, it is so much better in every way it is hard to imagine making games without it. If you can spare the extra cash — we recommend that you get a copy and give it a shot. You will not regret it.

Our experience with the Parsec team:

Our team ran into quite a few issues while implementing Parsec. The team over at Parsec (particularly Skippy and Benjy) went way out of their way to help us in any way they could. Skippy provided us with new builds of Parsec that addressed issues we brought up and was very helpful overall. One of the reasons we decided to push forward with this piece of technology was because of how responsive and cool their team was. It would not surprise us if they gave your team the same treatment during implementation.

Is adding online connectivity fruitful:

For the level of effort when using this technology, absolutely. In our test play sessions — we had a stream of users connect to our games, stay connected and play through several matches of both Bizzarioware and Underworld. We also saw a positive response in downloads as a result of folks seeing our games in the Parsec Arcade.

Thoughts on the AudioListener and AudioSource required by ParsecStreamGeneral:

One last weird little quirk that I wanted to mention. According to Unity’s audio filter documentation: ‘ To change the ordering of these and any other components, open a context menu in the inspector and select the Move Up or Move Down commands. Enabling or disabling an effect component determines whether it will be applied or not.’

After troubleshooting audio issues with the Parsec team. It appears that if you would like full multi-source audio streamed from Unity, you will need to write a script to disable and the re-enable the audio source attached to the ParsecStreamGeneral component. Without performing this measure, Parsec will only stream the audio from the source that is attached to the GameObject itself and not the other AudioSources in the scene.

A quick example of the issue — and proposed resolution can be found here:


Plugs:

If you enjoyed this article or any of our projects that we are bringing to the masses (mostly) for free — please consider becoming a Patreon.

How it feels being a game developer:


Resources:

Share

Power your remote workplace. Try Parsec for Teams

Sign up