The RoLearn C++ SDK ships a single-header library suitable for any native PC game on Steam — engine-agnostic, no external dependencies beyond a JSON header. This guide walks through the Steamworks-specific integration: header-only setup, the C-ABI wrapper for engines that cannot consume C++ headers, identity mapping to Steam IDs, and the cross-platform analytics roll-up pattern when the same title ships on both Steam and another platform tracked by RoLearn.
What ships
Three pieces in the Steamworks bundle (downloads page → Steamworks tab):
rolearn_sdk.h— the public C++ header. Single-file, header-only, all SDK behavior lives here.rolearn_sdk_c.h+rolearn_sdk_c.cpp— a C-ABI wrapper for engines that cannot consume the C++ header directly (Game Maker, RPG Maker MV/MZ with C plugins, some proprietary in-house engines).json.hpp— bundled nlohmann/json. Only dependency beyond the C++ standard library. Skip if your project already uses nlohmann/json from elsewhere.
Targets C++17 or newer. Compiles cleanly under MSVC 19.30+, Clang 13+, and GCC 11+. Tested in production with Unreal Engine 5 (via the C ABI from blueprint plugins), Godot 4 (via GDExtension), and bare-metal SDL2 games.
Header-only install (C++ projects)
Drop rolearn_sdk.h and json.hpp into your project's third-party include path. No CMake target, no link step, no vcpkg dance unless you want it. If you do use vcpkg, a port is available — see the downloads page.
Initialize in your game's bootstrap (where you call SteamAPI_Init is the right neighborhood):
#include "rolearn_sdk.h"
void bootstrap_analytics() {
rolearn::Config cfg;
cfg.api_key = "rk_live_XXXX";
cfg.endpoint = "https://rolearn.dev/api/sdk/events";
cfg.game_id = "440"; // your Steam app ID, as string
cfg.platform = "steam";
cfg.persist_on_disk = true; // survives game crash / kill
cfg.flush_every_seconds = 10;
rolearn::SDK::instance().init(cfg);
}On clean shutdown, call shutdown() — it flushes the outstanding batch and joins the worker thread:
void on_game_exit() {
rolearn::SDK::instance().shutdown();
}If your game is force-killed (crash, Steam kill switch, user ends process), the on-disk persistence layer holds the outstanding batch; it re-emits on the next launch.
Sending events from C++
The header-only API uses nlohmann/json's brace-init style. The canonical example:
rolearn::SDK::instance().track("level_complete",
{
{"level", 5},
{"score", 1840},
{"duration", 92},
{"result", "win"}
},
player_steam_id_str); // optional 3rd argFor purchases (Steam micro-transactions, in-game store consumables, gift redemptions):
rolearn::SDK::instance().purchase({
.sku = "keys_5",
.price = 4.99,
.currency = "USD",
.quantity = 1,
.source = "steam_microtxn"
});For Steam-native ad-format integrations (rare but happens — Steam-Workshop sponsored content placements) or in-game ad networks:
rolearn::SDK::instance().track("ad_impression", {
{"format", "rewarded_video"},
{"network", "your_network"},
{"revenue_usd", 0.012},
{"placement", "post_match"}
});The C-ABI wrapper (non-C++ engines)
For engines that consume C ABIs only — Game Maker plugin extensions, RPG Maker plugins, custom engines without a C++17-capable toolchain — link against the C wrapper:
#include "rolearn_sdk_c.h"
void bootstrap(void) {
rolearn_init(
/* api_key */ "rk_live_XXXX",
/* endpoint */ "https://rolearn.dev/api/sdk/events",
/* game_id */ "440",
/* platform */ "steam"
);
}
void on_level_complete(int level, int score) {
/* payload is a JSON string */
rolearn_track("level_complete",
"{\"level\":5,\"score\":1840,\"result\":\"win\"}",
NULL /* player_id */);
}
void on_shutdown(void) {
rolearn_flush();
rolearn_shutdown();
}The C wrapper exposes the same surface as the C++ one — track, purchase, brand-zone events, equip, share, flush, shutdown. The payload argument is a serialized JSON string rather than a builder object; build the JSON yourself with your engine's preferred library, or with sprintf for simple cases.
Identity: mapping Steam IDs to player_id
The SDK's player_id field is intentionally platform-agnostic. For Steam, the convention is to pass the player's SteamID64 as a string:
std::string player_id = std::to_string(SteamUser()->GetSteamID().ConvertToUint64());
rolearn::SDK::instance().track("queue_entered", {{"mode", "ranked"}}, player_id);Three things to know about identity:
- SteamID64 is fine to send to the SDK. It is not PII under GDPR — it is a pseudonymous identifier the player chose to associate with your game by purchasing it through Steam. Your privacy policy must still disclose that you collect it.
- Country auto-fills from
ISteamUtils::GetIPCountry()when Steam is initialized. If you calltrack()beforeSteamAPI_Initcompletes, the country falls back to "XX" — initialize Steam first, then RoLearn. - Don't send Steam personas (display names).Personas can change at any time, and the SDK's aggregations are keyed on stable IDs. Send the SteamID64 as identity, store personas in your own backend if you need them for customer support.
Cross-platform roll-up: same game, multiple platforms
Many studios ship the same title on Steam, mobile, and (less often) Roblox UGC ports. The RoLearn workspace handles cross-platform analytics by keying on (team_id, game_id, platform). The conventions that produce a clean cross-platform dashboard:
- Pick a single
game_idper title and use it across every platform's SDK init. Steam app ID, internal product code, or a UUID — pick one and use it consistently. The SDK does NOT require the ID to match a Steam app ID; it is opaque to the server. - Pass a different
platformvalue per build:"steam","unity","roblox". The dashboard pivots on this column; the analytics views let you filter to one or sum across all. - Use the same event vocabulary across platforms. Calling
track("level_complete", …)on Steam andtrack("levelComplete", …)on Unity will produce two unrelated event types in the dashboard. Pick one name, enforce it via a shared header / code-gen / wiki page. - Identity is harder. The same human player on Steam and on mobile has two different IDs (SteamID64 vs Apple/Google ID), and there is no way for RoLearn to know they are the same person without your backend providing a cross-platform "account ID" you control. If you have one, pass it as
player_idon both platforms. If you don't, retention cohorts are calculated per platform — which is usually the right read anyway.
Persistence and crash resilience
Native PC games crash. The SDK's on-disk persistence is on by default (persist_on_disk = true) and writes buffered batches to the OS temp directory, keyed by a worker UUID. On the next launch, the worker picks up orphaned batch files and re-emits them. End-result: a game that crashes between flushes loses zero events.
For games that handle their own crash dumps (Crashpad, Breakpad, custom), call flush_sync() from your crash handler to drain the buffer before the dump completes:
void on_crash_signal(int signum) {
rolearn::SDK::instance().flush_sync(); // blocks up to 2s
// ... your existing crash-dump logic ...
std::abort();
}flush_sync() is safe to call from a signal handler in the SDK's worker design (no allocations or locks on the hot path), but most teams call it earlier — from a last-chance handler before the crash dump fires.
Verifying the Steam integration
Same verification path as the other platforms: build, launch, check SDK → Events in the RoLearn web app. Steam-specific diagnostics:
- Are events landing under the right
platform="steam"column in the dashboard filter? If not, check yourConfiginitialization —platformdefaults to"steam"but is overridable. - Is country resolving? If every event shows country = "XX", the SDK initialized before SteamAPI did. Move
init()afterSteamAPI_Init()in your bootstrap. - Persistence dir writable? If you see
[RoLearn] failed to write WALin your game's console, the SDK can't write to its temp directory. Setpersist_pathinConfigto a guaranteed-writable directory inside your game's save path.
Where to go next
- SDK integration reference — full event schema, validation rules, ingest response format.
- Custom Event Tracking Patterns — naming, payload shape, funnel design. Applies to Steam games equally.
- Building Custom Dashboards — covers the cross-platform pivot view in detail.
- GDPR Compliance with the RoLearn SDK — applies to Steam-shipped EU titles. Includes Steam ID deletion handling.
