Facepunch.Steamworks + MonoGame.Framework = the easiest SteamAPI Integration for your app!

Hello dear MonoGame community,

you know what sucks? Yeah, exactly: when simple things are ridiculous complicated to realize or at least when they are too stressful or too much work to handle right.

You know whats great? The:

Facepunch.Steamworks MonoGame-Integration

Because it will make your life much easier when you want to integrate the SteamAPI into your app.

Why? Well, unlike many other SteamAPI wrappers, the Facepunch.Steamworks library allows to code in native C#! Say bye bye to manual function calling and finally code like this again:

foreach ( var friend in SteamFriends.GetFriends() )
{
    Console.WriteLine( "{friend.Id}: {friend.Name}" );
    Console.WriteLine( "{friend.IsOnline} / {friend.SteamLevel}" );
    
    friend.SendMessage( "Hello Friend" );
}
public static async Task<Texture2D> GetUserImage(SteamId id, GraphicsDevice device)
{
    var image = await SteamFriends.GetMediumAvatarAsync(id);

    if (image.HasValue)
    {
        Texture2D avatarTexture = new Texture2D(device, (int)image.Value.Width, (int)image.Value.Height, false, SurfaceFormat.Color);
        avatarTexture.SetData(image.Value.Data, 0, image.Value.Data.Length);
        return avatarTexture;
    }
    else return null;
}

UserAvatar = GetUserImage(UserID, GraphicsDevice).Result;

And with this specific MonoGame implementation I wanted to share my experience with that library and also to push out some small tutorials in form of sample projects (included in the repo).

So far they are the same as in my previous Steamworks.Net repo [mgc]:

  • Hello Facepunch.Steamworks: Simple sample which sets up bare basics of the SteamAPI and displaying a welcome message which includes your steam user name.
  • AchievementHunter: Simple sample which shows you the correct way of implementing achievements and stats as well as storing them on the steam server. It’s based upon the Steamworks Example ‘SpaceWar’ included with the Steamworks SDK.
  • Facepunch.Steamworks MonoGame Integration: Extendend sample which shows some features of the SteamAPI like UserStats, PersonaState, LeaderboardData, NumberOfCurrentPlayers, Steam User Avatar and so on.

My future plan is to create other sample projects ontop of this base to show how to create a steam workshop or how to use the SteamInput functionallity, which makes all gamepads available in your MonoGame project, which are compatible with the SteamAPI.

For now I wish you much fun with this repo and happy coding!

Cheers,
Marcel | Sandbox Blizz



PS: The MonoGame logo is a Fist, so the Facepunch integration was nothing else then… DESTINY! :grin:

8 Likes

Why not Steamworks.NET? Is this better somehow and I just don’t understand?

Facepunch.Steamworks is written in a way which makes it easier to use it and is up to date.
Latest releases enable you to use SteamNetworkingSockets:
https://partner.steamgames.com/doc/api/ISteamNetworkingSockets

1 Like

What exactly makes it easier to use?

Steamworks.NET is shaped like the original C++ Steamworks API, whereas Facepunch.Steamworks has it’s custom C# API.

Steamworks.NET readme says:

Steamworks.NET was designed to be as close as possible to the original C++ API

Facepunch.Steamworks readme says:

C# is meant to make things easier. So lets try to wrap it up in a way that makes it all easier.

From my experience Facepunch.Steamworks is easier to use because all basic things that you need to host a P2P lobby, networking etc. are all ready-to-use, so you do not have to write them from scratch.

1 Like

Gotchya. Thanks for the followup

During development in general, you will get the one or other “Aha!-Moment”. I think everyone here knows them pretty well. Regarding Steamworks.Net vs. Facepunch.Steamworks the latter one gives them pretty continuously to you.

You want to create a new workshop item for example:

Steamworks.Net:

private void CreateItem()
{
    var steamAPICall = SteamUGC.CreateItem(SteamUtils.GetAppID(), EWorkshopFileType.k_EWorkshopFileTypeCommunity);
    var steamAPICallResult = CallResult<CreateItemResult_t>.Create();
    steamAPICallResult.Set(steamAPICall, CreateItemResult);
}

private void CreateItemResult(CreateItemResult_t param, bool bIOFailure)
{
    if (param.m_eResult == EResult.k_EResultOK)
    {
        publishedFileID = param.m_nPublishedFileId;
        UpdateItem();
    }
    else
    {
        Debug.Log("Couldn't create a new item");
    }
}

private void UpdateItem()
{
    var updateHandle = SteamUGC.StartItemUpdate(SteamUtils.GetAppID(), publishedFileID);

    SteamUGC.SetItemTitle(updateHandle, currentSteamWorkshopItem.Title);
    SteamUGC.SetItemDescription(updateHandle, currentSteamWorkshopItem.Description);
    SteamUGC.SetItemContent(updateHandle, currentSteamWorkshopItem.ContentFolderPath);
    SteamUGC.SetItemTags(updateHandle, currentSteamWorkshopItem.Tags);
    SteamUGC.SetItemPreview(updateHandle, currentSteamWorkshopItem.PreviewImagePath);
    SteamUGC.SetItemVisibility(updateHandle, ERemoteStoragePublishedFileVisibility.k_ERemoteStoragePublishedFileVisibilityPublic);

    var steamAPICall = SteamUGC.SubmitItemUpdate(updateHandle, "");
    var steamAPICallResult = CallResult<SubmitItemUpdateResult_t>.Create();
    steamAPICallResult.Set(steamAPICall, UpdateItemResult);
}

Facepunch.Steamworks:

var result = await Ugc.Editor.NewCommunityFile
                  .WithTitle("My New File")
                  .WithDescription("This is a description")
                  .WithContent("c:/folder/addon/location")
                  .WithTag("awesome")
                  .WithTag("small")
                  .SubmitAsync(iProgressBar);

You see? It’s the nature of Steamworks.Net to work extensively with callbacks and callresults, simply because this is the way of how the SteamApi works and it can become pretty messy over time.

I think 99% of the users out there simply want to do it like the Facepunch.Steamworks example. Especially beginners and those who want quick results. The Facepunch library does a good job hiding things behind the scenes for your convenience.

This and the power of C# is what makes Facepunch.Steamworks easier to use and for me it’s also more satisfying.

Very cool. I prefer to have the higher level of control over my API integration that Steamworks.NET offers, but this certainly looks slick for those that don’t! :slight_smile:

1 Like

First update: Workshop Tool!

  • steam_appid.txt is not needed anymore. Facepunch.Steamworks sets environmental vars instead (SteamAppId and SteamGameId). This makes testing possible without the need of this text file.

  • Workshop Tool: Shows the bare basics of a Workshop like Querry Items, Creating Items, Uploading Items, Subscribing Items, Unsubscribing Items, Deleting Items and getting additional information about Items and the SteamRemoteStorage. Created with WindowsForms and pretty feature complete:


As you can see, it’s easily possible now to start your own Steam Workshop! There is no excuse anymore :stuck_out_tongue:

BTW: You can initialize the Workshop Tool with any AppID you own (apps in your steam library) and investigate the workshop of the corresponding app. Try it with 431960 (it’s the AppID from Wallpaper Engine). Theoretically you could also create new Workshop Items for foreign apps with this tool.

Have Fun!

1 Like

Second Update: Steam Input!

  • Facepunch.Steamworks is now added as a submodule which aims to my own fork of the library.

  • Activate action sets from an ingame-actions-file (IGA) and receive digital and analog input data from the SteamApi. Included as a MonoGame.Framework.DesktopGL project without the Microsoft.Xna.Framework.Input namespace.

  • Added a HELP-file to make it easier for the user to start receiving input data with the SteamApi.


So, I had a small chat with Garry Newman, the maintainer of the Facepunch.Steamworks library (and creator of Garry’s Mod), because the SteamInput interface was missing in the library. After adding this missing piece we struggled to receive input data from the SteamApi, as it is a bit complex to set up.

After digging endlessly in the Steamworks developer documentation, I finally figured out how everything works and shared my results in the discussion and in a seperated README file as well as a sample project in my repo.

I successfully used my XBOX-360 controller in a MonoGame project without using the Microsoft.Xna.Framework.Input namespace. But in the end all controllers supported by the SteamApi are now usable with a MonoGame project.

To mention some of them:

  • SteamController
  • PS4
  • XBoxOne
  • XBox360
  • Switch Pro

… inclusive gyroscope sensor support and setting LED colors of supported controllers like the PS4 one.

Also all generic x-input controllers are supported.

Getting input is now as simple as:

if (_CurrentController.GetDigitalState("Fire").Pressed)
{
    _FirePressed = true;
}
else _FirePressed = false;

One thing to keep in mind is that you will work with abstract actions (like “Fire” or “Move”), action sets (like “MenuControls” or “FightingControls”) and action layers (like “SniperLayer” or “InsideVehicleLayer”) instead of button presses like “IsButtonDown(Button)”.

Don’t hesitate to ask questions if something is unclear. I’m currently also on my way discovering all features and possibilities with the SteamInput interface.

Have a nice day!
Marcel | Sandbox Blizz


1 Like