Disclaimer: This is not the correct way to implement the Steamworks API. It's just something that I found to work. Since there was no other documentation regarding this, I thought it might be useful to share.
Steam is a video game digital distribution service and storefront. The Steamworks API allows your game to access the various features that Steam provides.
The API officially supports C++. It has some support for other languages. Regarding C:
steam_api_flat.h declares a set of "flat" functions that mirror the interface functions in the SDK. This is not pure C code, but it does use plain C linkage and calling conventions, so it is easy to interop with other languages. These functions are exported by steam_api[64][.dll/.so/dylib].
Up until now I had only used pure C from zig, so I tried the same set of things.
// in build.zig
exe.addCSourceFile("dependencies/steam/steam_api_impl.c", &[_][]const u8{"-std=c99"});
// where steam_api_impl.c has
#define STEAM_FLAG_1
#define STEAM_FLAG_2
#include "steam_api_flat.h"
or directly in my c.zig
pub usingnamespace @cImport({
// other includes...
@cInclude("steam_api_flat.h");
});
Neither of these worked, which was because steam_api_flat.h
has an #include steam_api.h
which #include
s a bunch of other header files, one of which eventually has some class
or template
or something else which causes the compile to fail.
Basically, I was not able to import C++ code into zig.
I needed just the following functionality
- Connect to Steam Client
- Trigger Steam Achievements
What I did instead
Convince Zig that the API that I need to use exists.
// core
pub extern fn SteamAPI_Init() bool;
pub extern fn SteamAPI_Shutdown() void;
pub extern fn SteamAPI_RestartAppIfNecessary(app_id: u32) bool;
// achievements
// the steam_api_flat.h is not publicly available code, so I won't share the API openly here. The C++ API is openly available, and you should be able to refer that and understand most of the details.
// basically, to translate, most of the class pointers can be cast to *anyopaque.
// for achievements, I had to translate the following commands.
pub extern fn get_steam_user(..);
pub extern fn get_steam_id(..);
pub extern fn get_steam_user_stats(..);
pub extern fn steam_request_user_stats(..);
pub extern fn steam_set_achievement(..);
pub extern fn steam_store_stats(..);
I don't like that so much of it is anyopaque
. There may be better ways to do this, but I'm not sure how to.
Convince the linker that these extern
APIs exist.
// in build.zig
// for the linker
exe.addObjectFile("dependencies/steamworks_sdk_155/win64/steam_api64.lib");
// this just adds the dll file to zig-out/bin dir
b.installBinFile("dependencies/steamworks_sdk_155/win64/steam_api64.dll", "steam_api64.dll");
Use the code in game
In main.zig, at startup
var steam_user_stats: *anyopaque = undefined;
if (c.SteamAPI_RestartAppIfNecessary(constants.STEAM_APP_ID)) {
return; // steam will relaunch the game from the steam client.
}
if (c.SteamAPI_Init()) {
var user = c.get_steam_user();
const steam_id = c.get_steam_id(user);
steam_user_stats = c.get_steam_user_stats();
_ = c.steam_request_user_stats(steam_user_stats, steam_id);
if (constants.BUILDER_MODE) helpers.debug_print("steam init done: user stats {d}\n", .{steam_id});
} else {
if (constants.BUILDER_MODE) helpers.debug_print("steam init failed\n", .{});
}
defer c.SteamAPI_Shutdown();
// send steam_user_stats to the game.
In game,
// Achievement is an enum
fn trigger_achievement(self: *Self, achievement: Achievement) void {
// mark achievement as completed
const triggered = c.steam_set_achievement(self.steam_user_stats, &@tagName(achievement)[0]);
if (triggered) {
// update the steam client, so that the overlay can popup.
_ = c.steam_store_stats(self.steam_user_stats);
}
}
And we're done.
Other notes
- Some APIs have callbacks. I don't know how this method would work in that case.
- There is a json file,
steam_api.json
that describes (almost all of) the interfaces, types, and functions in the SDK. It should be possible to autogenerate theextern
APIs with this.
As mentioned, this is not the ideal way. I am sure there are better ways to accomplish the same. But this was just something that worked for me.
This was all discovered when working on my game, Konkan Coast Pirate Solutions.
It is a puzzle game about helping pirate ships do pirate things. Set up the simulations. Watch how the ships behave. Explore how the systems interact.
Top comments (0)