A Gentle Introduction to Steamworks

August 17, 2017

Unity, C#, Steamworks.NET, and Facepunch.Steamworks

Steam Home Page

Introduction

Like most game developers, I’m very interested in getting my game on Steam.

Like most indie game developers, I lack any sort of access to the resources/knowledge that would easily clarify for me what being on Steam” actually means.

It’s also hard to bridge this gap in knowledge because, though a lot of Steam’s functionality is well documented, knowing where to start in that massive store of documentation, as well as what ties all the disparate pieces together, can be difficult. On top of that, general discussion about the Steam platform happens on a private forum that is only open to developers that have been approved for the platform already through either Steam Direct or a Valve referral. So for someone starting out, looking for answers to basic questions can be difficult.

Because of this, I wanted to write a high-level overview for people who are just setting out, trying to get Steam working with their games. Specifically, I wanted to dive into the Steamworks SDK, the Valve-provided software library that gives you access to things like Workshop, Leaderboards, Achievements, and so on.

Steamworks is already well documented by Valve, but it’s written from the perspective of someone who is using the native C++ library and likely already knows how all its functionality intersects. If this isn’t you, even better! What follows is for that second person, someone who is making a game in a higher-level language and just wants a simple Steamworks integration (it exists, I promise!). More specifically, this post is targeted at people using C# in some for or another, but ideally using Unity as their game engine.

Steamworks

Steamworks is two things. First, it’s the developer portal for managing everything about your game’s presence on Steam, from changing your game’s banner pictures to managing sales to listing controller support, etc. Secondly, it’s an SDK Valve provides that lets you interoperate with everything else Steam provides like Workshop, Leaderboards, Servers, Achievements, etc. Keep that order in mind! The SDK is only required to upload your content to Steam”, [source]. This means you can totally forego tackling nearly all of the aforementioned stuff of the SDK and just focus on how to use it to get your game on Steam. However, the SDK does offer a lot of other great, useful functionality. So let’s get it up and running!

If you’re a C++ developer you can go ahead and add the library to your game via the instructions here.

But, if you’re a C#/Unity developer, we’ve got a little work to do. Because native C++ headers/source aren’t compatible with Unity, we need to use a wrapper library that allows us to integrate the SDKs functionality. Such a wrapper library would allow us to use high level C# functions to call low level C++ functions. In the past, the go-to library for this was Steamworks.NET, which is exactly as it sounds: Steamworks implemented with .NET. However, it is exactly as it sounds, to a fault.

Steamworks.NET provides one-to-one mapping of Steamworks functions to C# functions, but that means you need to fully understand Steamworks as a library to get anything done. For someone who is just getting started and wants to do simple tasks, this can be a lot of work. If you want to do something more complex, Steamworks.NET requires you to essentially write your own utility wrapper on top of their wrapper, which seems to defeat the purpose of a wrapper in the first place.

Steam Home Page

Facepunch.Steamworks

Due to these limitations and other reasons, Facepunch Studios (of Rust and Garry’s Mod fame) set out to build a better Steamworks library for C#/Unity with the explicit purpose of making everything easier.

It gets rid of the need for tons of code scaffolding to do basic (and complex) tasks in Steamworks, which lets you focus on just working” with Steam. It’s also currently being used in Rust, which means it is production tested on one of the highest user-volume games on Steam. Complex tasks are abstracted away into simple function calls, and the library itself is only three files so it doesn’t cause any extra bloat. I can’t emphasize enough how much more of a godsend it is for someone starting out; it’s truly wonderful. The creator of Steamworks.NET has even said that Facepunch.Steamworks is basically what I wanted the next step of Steamworks.NET to be” and that it should probably be the default choice for most people”. Steamworks.NET is still there for people who want to implement their own version of what Facepunch.Steamworks essentially is, but in my opinion, if it’s good enough for Rust it’s good enough for me. So how does it work and what’s so special about it? Let’s get started.

Getting Started

First off, It’s easy to think that you need to be an approved Steamworks developer to start working with Steamworks, but the truth is that you can start using the SDK right now without needing to have gone through the process of registering. This is because Valve provides developers with a test AppID” of 480 that you can program against.

The AppID

An AppID is how your game is uniquely identified on Steam (and Steamworks), and is one of the primary things you get when you get your game properly registered. It stakes out” your section of Steam/Steamworks and allows you to wholly own anything associated with that AppID. AppID 480 corresponds to SpaceWar”, a demonstration game Valve made and open-sourced that shows of some of the capabilities of Steamworks (you should check it out!).

While a unique AppID is nice and obviously required for your game at some point, the test AppID (480) allows you to work with Steam’s services as if your game were live. Your real AppID should be substituted in once you get one, but in the meantime, 480 will serve you just fine. That said, probably don’t create a server with the name My Trademark Pending Game Name’ Server”.

Downloading and Importing Facepunch.Steamworks

So, let’s download the Facepunch.Steamworks library (FP lib from now on), knowing we can properly test it with AppID 480. Head to the releases section of its Github page (the library is fully open source, MIT licensed) and download the latest release. Once you extract the .zip file you should see a few folders. The READMEs go into more detail, but you essentially need to copy a small set of these files over to your Unity project, depending on your platform. The Facepunch.Steamworks files are the lib itself, and the steam_api files are platform specific files that contain the actual Steamworks SDK. Your Unity directory should look something like this after your files are imported (assuming Windows x86/x64):

Unity Project Folder
|— Assets
    |— Plugins
        |— Facepunch.Steamworks 
            |— Facepunch.Steamworks.dll
            |— Facepunch.Steamworks.pdb
            |— Facepunch.Steamworks.xml            
|— steam_api.dll (Windows x86)
|— steam_api64.dll (Windows x64)
|— steam_appid.txt

The steam_appid.txt is literally a text file with only your AppID in it, so for our cases it should just be a text file with 480 in it, no quotes. The .dll, .pdb, and .xml files come from the Release folder of the downloaded .zip file, using whichever .NET version you’re targeting. For Unity, 3.5 is fine. If you’re starting out with a clean project, Facepunch has also graciously provided a small test project that does a lot of this for you that you can you to start off your project.

Unity

Once you’ve copied all the lib’s files to their proper directory locations, you’re basically setup! All we need to do now is to write some code to get everything integrated. I’m going to copy over the test file from the test project and just abbreviate it here for clarity’s sake.

using System;
using UnityEngine;
using System.Collections;
using System.Linq;
using Facepunch.Steamworks;

public class SteamTest : MonoBehaviour
{

    void Start ()
    {
        // Don't destroy this when loading new scenes
        DontDestroyOnLoad( gameObject );

        // Configure for Unity
        // This is VERY important - call this before doing anything
        Facepunch.Steamworks.Config.ForUnity( Application.platform.ToString() );

        // Create the steam client using the test AppID (or your own AppID eventually)
        new Facepunch.Steamworks.Client( 480 );

        // Make sure we started up okay
        if ( Client.Instance == null )
        {
            Debug.LogError( "Error starting Steam!" );
            return;
        }

        // Print out some basic information
        Debug.Log("My Steam ID: " + Client.Instance.SteamId);
        Debug.Log("My Steam Username: " + Client.Instance.Username );
        Debug.Log("My Friend Count: " + Client.Instance.Friends.AllFriends.Count() );
    }

    private void OnDestroy()
    {
        if ( Client.Instance != null )
        {
            // Properly get rid of the client if this object is destroyed
            Client.Instance.Dispose();
        }
    }

    void Update()
    {
        if ( Client.Instance != null )
        {
            // This needs to be called in Update for the library to properly function
            Client.Instance.Update();
        }
    }

} 

And… that’s it! If you attach that script to a GameObject in your scene and enter play mode, you should see yourself in Steam as playing Spacewar”, and see the console print out some basic Steam information about you (if not, make sure you are actually logged onto Steam).

Life with Facepunch.Steamworks

Capabilities

Once you’re set up, accessing deeper Steam functionality is pretty easy since the FP lib covers and wraps almost all parts of the standard Steamworks SDK. The question does still stand though as to what” that stuff is, so here’s a small list and some descriptions of what you can work with (in the FP lib):

  1. Servers - Create servers using a player’s client or run a headless server elsewhere. This is used for ping-sensitive games that have steep networking requirements (think Dota 2, Overwatch, etc.)
  2. Lobbies - These are essentially staging rooms” for players that act as a meeting point to exchange SteamIDs or other user info.
  3. Friends - You and your player’s friends on Steam!
  4. Workshop - Upload/Download content from Steam Workshop!
  5. Leaderboards - Create and maintain global leaderboards for your game.
  6. Achievements - Create or award achievements.
  7. Networking - Send P2P data to clients.
  8. Steam Cloud - Save data to Steam Cloud! This is very useful for Saves.
  9. Voice - Interact with the Steam voice API for in-game chat.
  10. Stats - Set Steam-side stats for a given player.

The best way to see how to use a given function is to see if there is a working example of it in the Facepunch.Steamworks test project (NOTE: this is not the Unity test project), and model the implementation in your game.

Most of these features are documented further in the the FP lib’s wiki, but only a few classes really properly filled out. If you don’t see any example, check out the library’s code and see if the function is implemented at all. If not, see how far you can get implementing it on your end, or just submit an issue to the library. The Facepunch team is generally very responsive, so they can let you know if something is being worked on or not and even guide you on how to give back to the community, if you go the route of implementing it yourself.

Subscribers and Callbacks

When working with the FP lib (or even the native API), you’ll notice it’s not always as easy to work with as simply calling something like Client.Instance.SteamId. This is because the Steamworks SDK (and FP lib by extension) makes heavy use of asynchronous functions so that your game doesn’t hang every time you want to do some non-trivial interaction with Steam. Without async calls, you would have to wait for the main Steam server to respond before any more of your code could execute, which is obviously error-prone and frustrating, as far as play experience goes. So, to use the library, you will need to get familiar with the idea of delegates and callbacks. It may be a lot for someone just getting started, but it’s easy to understand once you grasp the core idea. I’ll give an example.

If you want to get a list of all the lobbies in your game using the FP lib, you would do this:

client.LobbyList.Refresh();

Notice how there is no return or assignment happening there. If that’s the case, how do we actually get what we just requested? In Steamworks, once this function is called, the Steam backend gets everything ready for you, and then sends you some data via a callback”. Almost literally, Steam calls you back” to say Hey, your data is ready! Here!”.

To receive the call, we need to pick up the phone, or as it’s actually known, subscribe”, to the callback. This is done by defining a function that takes in the data the callback provides. Sometimes, no data is provided, and the callback is used primarily as a hook”, or a way to know that it’s OK to resume. Once you’re inside your callback handler, you can safely continue operation. Here’s an example:

void Start()
{
    // Subscribe to the proper event by defining the function you want to be called when OnLobbyJoined is called by Steam.
    client.Lobby.OnLobbyJoined += OnLobbyJoinedCallback;
    // Call the function
    Native.Lobby.Join(LobbyID);
}

void OnLobbyJoinedCallback(bool success)
{
    // If we're inside here, the callback has properly fired!
    // We can check the status of this callback by seeing if the bool "success" that Steam passed in is true or false
    if(success)
    {
        // If there were no errors, we can safely call functions in here that require a specific circumstance 
        // For example, we can now print out the id of the Lobby's Owner
        Debug.Log(client.Lobby.Owner);
    }
}

Understanding this pattern will be immensely helpful as you use the library, as well as for understanding what Steamworks is actually doing when you inevitably peek at the actual documentation. If you want a more in-depth discussion of how this works, I definitely recommend Valve’s documentation on the topic, as well as some bits on the Steamworks.NET website.

Moving Forward

From here on, you’re free to implement whatever you want! Valve has no requirements here, but if you are on the platform it is obviously in your best interest to engage with it and its community in the ways Valve has made available. Once you get registered with Steam Direct you can simply swap in your AppID and carry over any Steam functionality you implemented with the test AppID.

I really hope this is helpful to anyone looking to get started with Steam, and I obviously strongly recommend people check out Facepunch.Steamworks on Github. If you feel so inclined, try contributing in small ways like filling out the documentation, or if you want something meatier to chew on submit an issue and ask if anything needs doing. If you have any questions about this article, feel free to reach out to me @kkukshtel. I’d also love it if you followed my game on Twitter @isotacticsgame or sign up for the newsletter.


Back To Index