Steamworks and Unity – P2P Multiplayer

share_steam_logoSome time ago we talked about how to integrate Steamworks.NET with Unity game. It is a good start, but now let’s go further and talk about multiplayer in Steam. This won’t be a tutorial, but a guide on using Steamworks in your own game. We will be using Steamworks.NET library that is a wrapper to steam_api.dll.

Please note that you need a Steam App ID of your game to get it work. You can get one by getting greenlit or by having your game approved directly by Valve. Still, this is a guide, so if you only want to check if Steam is a convenient platform, just read on…

P2P Multiplayer

One of the greatest features of Steamworks is matchmaking and P2P network communication. You don’t have to worry about servers’ setup – all the things are already there.

You may not be familiar with building a multiplayer game using P2P connection, because the most popular approach involves setting up client and server as a separate entity. In this situation client is the game itself and the server is an application that contains server-side logic, connects all the players together and protects them from cheaters. Since client-server scenario may be good for bigger games, for something relatively small and less competitive consider dropping-off the server part for the favor of two clients speaking with each other. Server-side logic will be included only in a single entity, so you won’t have to worry about writing additional applications to make it work. For that reason we will put P2P Steamworks communication in good use.

Is it really that easy?

Steamworks is trying to be as simple as it can be. You don’t even have to worry about making a connection, all you need is a SteamID (SteamID is a unique steam user identifier. Usually it is a big number wrapped inside a CSteamID object. You can easily get a CSteamID of any user you’re interacting with, for instance through the lobby.) When you have it, all you need is to execute this method:

pubData is the data we want to send, cubData is the number of bytes we want to send, eP2PSendType is a method of delivery. Let’s not talk about nChannel, the default value is enough for now.

Here’s an example of how to send a “Hello!” string:

There are four of these regarding the delivery method:

  • k_EP2PSendUnreliable – Small packets, may get lost, can arrive out of order, but fast.
  • k_EP2PSendUnreliableNoDelay – As above, but won’t do any connections checks. For this purpose it can be thrown away, but it is the fastest possible method of delivery.
  • k_EP2PSendReliable – Reliable message send. For big packets, will arrive in order.
  • k_EP2PSendReliableWithBuffering – As above but buffers the data before sending. Usually when you’re sending a lot of small packages that is not so important to be delivered immediately. (will be forced to send after 200 ms.)

What about the other side?

If one peer is sending the data, the other one receives it in some way. Of course there are some security precautions. You cannot send the data to any Steamworks’ client that’s out there. Before client can receive your data, he has to accept your P2P session request.

P2P session request is something that occurs when you try to send the first chunk of data to the Steamworks’ client. The process will repeat itself if you haven’t sent any data for a while (usually a couple of minutes.) You should accept only the connections you’re expecting to be made, like from another player in the lobby you’re in.

How to accept the session request? It’s really easy! All you have to do is write a code like this one:

This way a P2P session will be accepted and you’ll be ready to…

Read the message

All the messages are stored in the Steamworks message queue. To read it you have to tell it you’re ready to get it. Usually a good place to do so is an Update() function. Your application will check for new messages as soon as possible.

That’s it!


This guide does not cover cleaning up (it’s optional because unused sessions are being cleaned up automatically) and error handling. You can read about them in official Steamworks documentation, but remember that you need to be Steam partner to to have access to it. If you’re not, I hope that after our articles about Steamworks you will take into consideration becoming one.

7 thoughts on “Steamworks and Unity – P2P Multiplayer”

  1. Hi!

    This is amazing! I would love to see even more about the topic, however this is a great starting point! I have tested most of the code here and I have a few remarks and questions.

    1: The Buffer in the last snippet did not get recognized so I fixed it by adding System.Buffer
    2: also in the end of same line length is out of context I added chars.Length (I am not really sure but this way at least this is not an exception)

    1: I tryed sending messages to myself but I could not do that is that normal or did I mess something up?
    2: Do you think it is possible to create a game with sending transform.positions via this kind of P2P messaging? I am planning to do so, however I would value your feedback.

    Thank you very much for creating this and I hope I hear from you.

    Kind Regards,

    1. One additional note, I made a mistake above, so the correct line is:

      System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);

    2. 2: here my script for sending transform.positions

      CSteamID receiver = (CSteamID)76561198xxxx ; // your steam id for test
      private Callback _p2PSessionRequestCallback;

      void Start()
      // setup the callback method

      void OnP2PSessionRequest(P2PSessionRequest_t request)
      CSteamID clientId = request.m_steamIDRemote;
      uint size;
      string comand ;
      void Update()
      if (Input.GetKey (KeyCode.A) ) {
      transform.Translate(Vector3.left *Time.deltaTime);
      Send (“A”);
      if (Input.GetKey (KeyCode.D)) {
      transform.Translate(Vector3.right *Time.deltaTime);
      Send (“D”);

      if (Input.GetKeyUp (KeyCode.A) || Input.GetKeyUp (KeyCode.D)) {
      Send (“”);

      ///read message
      if (comand == “A”) {
      transform.Translate(Vector3.left *Time.deltaTime);

      if (comand == “D”) {
      transform.Translate(Vector3.right *Time.deltaTime);
      // repeat while there’s a P2P message available
      // will write its size to size variable
      while (SteamNetworking.IsP2PPacketAvailable(out size))
      // allocate buffer and needed variables
      var buffer = new byte[size];
      uint bytesRead;
      CSteamID remoteId;

      // read the message into the buffer
      if (SteamNetworking.ReadP2PPacket(buffer, size, out bytesRead, out remoteId))
      // convert to string
      char[] chars = new char[bytesRead / sizeof(char)];
      //Buffer.BlockCopy(buffer, 0, chars, 0, chars.Length);
      System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
      string message = new string(chars, 0, chars.Length);
      //Debug.Log(“Received a message: ” + message);
      void Send(string message)
      // allocate new bytes array and copy string characters as bytes
      byte[] bytes = new byte[message.Length * sizeof(char)];
      System.Buffer.BlockCopy(message.ToCharArray(), 0, bytes, 0, bytes.Length);

      SteamNetworking.SendP2PPacket ((CSteamID)76561198xxxx , bytes, (uint)bytes.Length, EP2PSend.k_EP2PSendReliable);
      void CheckMessage(string mess)
      if (mess == “A” || mess == “D”)
      comand = mess;
      comand = “”;

      anyone have a other ways to send positions like NetworkView component in Unity ?

  2. The name “ExpectingClient” does not exits in current context.
    pls tell me how to fix it.
    here is my header of script :

    using UnityEngine;
    using System.Collections;
    using Steamworks;

    Thank !

Comments are closed.