Popcorning Guinea Pig
Search Results for

    Steam Integration

    The Popcorning Guinea Pig engine provides built-in Steam integration through Steamworks.NET, enabling your games to access Steam platform features like user profiles, achievements, friends, and social features.

    Overview

    The Steam integration consists of two main components:

    • Player System: Handles Steam user data and avatars
    • Steamworks Services: Provides access to Steam platform APIs

    Prerequisites

    Steamworks SDK Setup

    1. Register Your Game: Create an app on Steamworks Partner Portal
    2. Get App ID: Note your Steam App ID for configuration
    3. Download SDK: The engine includes Steamworks.NET 2025.164.1 (Steamworks SDK 1.65). Games must ship matching steam_api* natives from that SDK — the wrapper does not.

    Steam Client Requirements

    • Steam client must be running on the user's machine
    • Game must be launched through Steam (for production)
    • For development: Steam client running + steam_appid.txt file

    Player System

    SteamPlayer Class

    The SteamPlayer class extends the base Player class with Steam-specific functionality:

    using PopcorningGuineaPig.Engine.Gameplay;
    using Steamworks;
    
    public class MyGame : Game
    {
        private SteamPlayer localPlayer;
        
        public override void Load()
        {
            // Initialize Steam (must be done before creating SteamPlayer)
            if (SteamAPI.Init())
            {
                // Create Steam player with current user's data
                localPlayer = new SteamPlayer();
                
                Console.WriteLine($"Welcome, {localPlayer.Name}!");
                Console.WriteLine($"Steam ID: {localPlayer.Id}");
            }
            else
            {
                Console.WriteLine("Failed to initialize Steam API");
                // Fall back to regular Player
                localPlayer = new Player() { Name = "Guest" };
            }
        }
        
        protected override void CleanupResources()
        {
            SteamAPI.Shutdown();
        }
    }
    

    Player Properties

    The SteamPlayer provides these properties:

    public class SteamPlayer : Player
    {
        public CSteamID Id { get; set; }           // Steam ID of the player
        public string Name { get; set; }           // Steam persona name
        public byte[] AvatarImageData { get; }     // Avatar image as RGBA bytes
        public int AvatarWidth { get; }            // Avatar image width
        public int AvatarHeight { get; }           // Avatar image height
    }
    

    Avatar Integration

    Steam avatars are automatically retrieved and can be used in your UI:

    public class ProfileScene : Scene
    {
        private SteamPlayer player;
        private Texture2D avatarTexture;
        
        public override void Load()
        {
            player = new SteamPlayer();
            
            // Convert avatar data to Raylib texture
            if (player.AvatarImageData != null)
            {
                // Create Raylib Image from raw RGBA data
                var image = new Image
                {
                    data = player.AvatarImageData,
                    width = player.AvatarWidth,
                    height = player.AvatarHeight,
                    format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
                    mipmaps = 1
                };
                
                // Convert to texture for rendering
                avatarTexture = Raylib.LoadTextureFromImage(image);
                Raylib.UnloadImage(image);
            }
        }
        
        public override void Render(int width, int height)
        {
            // Draw player name
            Raylib.DrawText(player.Name, 50, 50, 24, Color.WHITE);
            
            // Draw avatar if available
            if (avatarTexture.id != 0)
            {
                Raylib.DrawTexture(avatarTexture, 50, 80, Color.WHITE);
            }
        }
        
        public override void Unload()
        {
            if (avatarTexture.id != 0)
            {
                Raylib.UnloadTexture(avatarTexture);
            }
        }
    }
    

    Steam API Integration

    Friends System

    Access Steam friends list and their information:

    public class FriendsManager
    {
        public List<SteamPlayer> GetOnlineFriends()
        {
            var friends = new List<SteamPlayer>();
            
            int friendCount = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate);
            
            for (int i = 0; i < friendCount; i++)
            {
                CSteamID friendId = SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagImmediate);
                
                // Check if friend is online
                if (SteamFriends.GetFriendPersonaState(friendId) != EPersonaState.k_EPersonaStateOffline)
                {
                    var friend = new SteamPlayer(friendId);
                    friends.Add(friend);
                }
            }
            
            return friends;
        }
        
        public void InviteFriendToGame(CSteamID friendId)
        {
            // Send game invite to friend
            SteamFriends.InviteUserToGame(friendId, "+connect_lobby");
        }
    }
    

    Rich Presence

    Update your game's status on Steam:

    public class GamePresence
    {
        public void SetMainMenuPresence()
        {
            SteamFriends.SetRichPresence("status", "In Main Menu");
            SteamFriends.SetRichPresence("steam_display", "#Status");
        }
        
        public void SetInGamePresence(string levelName, int playerCount)
        {
            SteamFriends.SetRichPresence("status", $"Playing {levelName}");
            SteamFriends.SetRichPresence("players", playerCount.ToString());
            SteamFriends.SetRichPresence("steam_display", "#Status_WithPlayers");
        }
        
        public void ClearPresence()
        {
            SteamFriends.ClearRichPresence();
        }
    }
    

    Steam Achievements

    Implement Steam achievements in your game:

    public class AchievementManager
    {
        private Dictionary<string, bool> achievementStatus = new();
        
        public void Initialize()
        {
            // Load current achievement status
            LoadAchievements();
        }
        
        public void UnlockAchievement(string achievementName)
        {
            if (!achievementStatus.ContainsKey(achievementName) || !achievementStatus[achievementName])
            {
                // Unlock on Steam
                SteamUserStats.SetAchievement(achievementName);
                SteamUserStats.StoreStats();
                
                achievementStatus[achievementName] = true;
                
                Console.WriteLine($"Achievement unlocked: {achievementName}");
            }
        }
        
        public bool IsAchievementUnlocked(string achievementName)
        {
            return achievementStatus.ContainsKey(achievementName) && achievementStatus[achievementName];
        }
        
        private void LoadAchievements()
        {
            // SDK 1.61+: stats are ready after SteamAPI.Init; RequestCurrentStats is gone.
        }
        
        // Call this in your game's update loop
        public void HandleCallbacks()
        {
            // Process Steam callbacks
            SteamAPI.RunCallbacks();
        }
    }
    

    Steam Statistics

    Track and submit game statistics:

    public class GameStats
    {
        public void RecordGamePlayed()
        {
            // Increment games played stat
            int currentGames = 0;
            SteamUserStats.GetStat("games_played", out currentGames);
            SteamUserStats.SetStat("games_played", currentGames + 1);
            SteamUserStats.StoreStats();
        }
        
        public void RecordScore(int score)
        {
            // Track high score
            int highScore = 0;
            SteamUserStats.GetStat("high_score", out highScore);
            
            if (score > highScore)
            {
                SteamUserStats.SetStat("high_score", score);
                SteamUserStats.StoreStats();
            }
        }
        
        public void RecordPlayTime(float seconds)
        {
            // Add to total play time
            float totalTime = 0;
            SteamUserStats.GetStat("total_playtime", out totalTime);
            SteamUserStats.SetStat("total_playtime", totalTime + seconds);
            SteamUserStats.StoreStats();
        }
    }
    

    Steam Cloud Save

    Save and load game data to Steam Cloud:

    public class CloudSaveManager
    {
        public bool SaveToCloud(string filename, byte[] data)
        {
            try
            {
                // Write data to Steam Cloud
                bool success = SteamRemoteStorage.FileWrite(filename, data, data.Length);
                
                if (success)
                {
                    Console.WriteLine($"Saved {filename} to Steam Cloud ({data.Length} bytes)");
                }
                
                return success;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to save to Steam Cloud: {ex.Message}");
                return false;
            }
        }
        
        public byte[] LoadFromCloud(string filename)
        {
            try
            {
                if (SteamRemoteStorage.FileExists(filename))
                {
                    int fileSize = SteamRemoteStorage.GetFileSize(filename);
                    byte[] data = new byte[fileSize];
                    
                    int bytesRead = SteamRemoteStorage.FileRead(filename, data, fileSize);
                    
                    if (bytesRead == fileSize)
                    {
                        Console.WriteLine($"Loaded {filename} from Steam Cloud ({bytesRead} bytes)");
                        return data;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to load from Steam Cloud: {ex.Message}");
            }
            
            return null;
        }
        
        public void SyncCloudData()
        {
            // Force synchronization with Steam Cloud
            // This is usually automatic, but can be forced if needed
            Console.WriteLine("Syncing with Steam Cloud...");
        }
    }
    

    Development Setup

    Creating steam_appid.txt

    For development, create a steam_appid.txt file in your game's directory:

    480
    

    Replace 480 with your actual Steam App ID. This file should be in the same directory as your executable.

    Project Configuration

    Steamworks.NET arrives with PopcorningGuineaPig.Engine (2025.164.1, SDK 1.65). Do not add a separate PackageReference — nuget.org still only has 2024.8.0 (SDK 1.60), which will not Init against 1.65 natives. Ship Valve's redistributable (steam_api64.dll / libsteam_api.dylib / libsteam_api.so) next to the exe.

    The unix wrapper is IL-only but PE-flagged AMD64. The nupkg's runtimes/osx-arm64 copy is retargeted to ARM64 at pack time so net10 on Apple Silicon will load it. Do not patch Steamworks.NET.dll in the game.

    Initialization Pattern

    Use this pattern for Steam initialization in your game:

    public class SteamEnabledGame : Game
    {
        private bool steamInitialized = false;
        private SteamPlayer localPlayer;
        private AchievementManager achievements;
        
        public override void Load()
        {
            // Initialize Steam API
            steamInitialized = SteamAPI.Init();
            
            if (steamInitialized)
            {
                Console.WriteLine("Steam API initialized successfully");
                
                // Initialize Steam-dependent features
                localPlayer = new SteamPlayer();
                achievements = new AchievementManager();
                achievements.Initialize();
                
                // Set initial rich presence
                SteamFriends.SetRichPresence("status", "Starting Game");
            }
            else
            {
                Console.WriteLine("Steam API failed to initialize - running without Steam features");
                localPlayer = new Player { Name = "Player" };
            }
            
            // Continue with normal game initialization
            InitializeGame();
        }
        
        public override void Update(double deltaTime)
        {
            // Process Steam callbacks
            if (steamInitialized)
            {
                SteamAPI.RunCallbacks();
                achievements.HandleCallbacks();
            }
            
            // Normal game update
            base.Update(deltaTime);
        }
        
        protected override void CleanupResources()
        {
            if (steamInitialized)
            {
                SteamFriends.ClearRichPresence();
                SteamAPI.Shutdown();
            }
            
            base.CleanupResources();
        }
    }
    

    Platform Integration UI

    Create Steam-aware UI components:

    public class SteamProfilePanel : Panel
    {
        private SteamPlayer player;
        private Label nameLabel;
        private Button friendsButton;
        
        public SteamProfilePanel(SteamPlayer player, Vector2 position, Vector2 size) : base(position, size)
        {
            this.player = player;
            Setup();
        }
        
        private void Setup()
        {
            // Player name label
            nameLabel = new Label(
                text: player.Name,
                position: new Vector2(GetPosition().X + 10, GetPosition().Y + 10),
                fontSize: 18,
                color: Color.WHITE
            );
            
            // Friends button
            friendsButton = new Button(
                position: new Vector2(GetPosition().X + 10, GetPosition().Y + 40),
                size: new Vector2(100, 30),
                text: "View Friends"
            );
            
            friendsButton.SetOnClick(() => {
                // Open Steam overlay to friends list
                SteamFriends.ActivateGameOverlay("friends");
            });
            
            // Add to panel
            AddChild(nameLabel);
            AddChild(friendsButton);
        }
        
        public override void Update()
        {
            base.Update();
            friendsButton.Update();
        }
        
        public override void Draw()
        {
            base.Draw();
            nameLabel.Draw();
            friendsButton.Draw();
        }
    }
    

    Error Handling

    Proper error handling for Steam integration:

    public class SteamErrorHandler
    {
        public static bool SafeInitialize()
        {
            try
            {
                if (!SteamAPI.Init())
                {
                    Console.WriteLine("Steam API initialization failed:");
                    Console.WriteLine("- Steam client may not be running");
                    Console.WriteLine("- Invalid App ID in steam_appid.txt");
                    Console.WriteLine("- Steamworks SDK not properly configured");
                    return false;
                }
                
                // Verify user is logged in
                if (!SteamUser.BLoggedOn())
                {
                    Console.WriteLine("User is not logged into Steam");
                    return false;
                }
                
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Steam initialization exception: {ex.Message}");
                return false;
            }
        }
        
        public static void HandleSteamCallback(CallbackMsg_t callback)
        {
            // Handle Steam callback errors
            switch (callback.m_iCallback)
            {
                case SteamAPICallCompleted_t.k_iCallback:
                    // Handle API call completion
                    break;
                case UserStatsReceived_t.k_iCallback:
                    // Handle user stats received
                    break;
                default:
                    Console.WriteLine($"Unhandled Steam callback: {callback.m_iCallback}");
                    break;
            }
        }
    }
    

    Best Practices

    Performance Considerations

    1. Callback Processing: Always call SteamAPI.RunCallbacks() regularly
    2. Avatar Caching: Cache avatar textures to avoid repeated conversions
    3. Async Operations: Don't block the main thread for Steam operations

    Security

    1. Never Trust Client Data: Always validate Steam data server-side for multiplayer
    2. Handle Offline Mode: Gracefully degrade when Steam is unavailable
    3. Privacy Awareness: Respect user privacy settings

    User Experience

    1. Fallback Gracefully: Provide functionality even when Steam is unavailable
    2. Clear Feedback: Inform users about Steam-specific features
    3. Respect Steam UI: Use Steam overlays for Steam-specific interactions

    Testing

    Development Testing

    # Set your App ID
    echo "480" > steam_appid.txt
    
    # Run with Steam client open
    dotnet run
    

    Integration Testing Checklist

    • [ ] Steam API initializes correctly
    • [ ] Player data loads properly
    • [ ] Avatar images display correctly
    • [ ] Rich presence updates work
    • [ ] Achievements unlock and save
    • [ ] Statistics track correctly
    • [ ] Cloud saves work bidirectionally
    • [ ] Friends list integration functions
    • [ ] Graceful degradation when Steam unavailable

    Common Issues

    "Steam API failed to initialize"

    Causes:

    • Steam client not running
    • Invalid App ID in steam_appid.txt
    • Missing Steamworks.NET package

    Solutions:

    1. Ensure Steam client is running
    2. Verify App ID is correct
    3. Check steam_appid.txt is in the correct location

    "Avatar not loading"

    Causes:

    • User has private profile
    • Avatar hasn't downloaded yet
    • Network connectivity issues

    Solutions:

    1. Provide default avatar fallback
    2. Retry avatar fetch after delay
    3. Check Steam privacy settings

    Advanced Features

    Steam Workshop Integration

    public class WorkshopManager
    {
        public void SubscribeToItem(PublishedFileId_t itemId)
        {
            SteamUGC.SubscribeItem(itemId);
        }
        
        public void GetSubscribedItems()
        {
            uint numSubscribed = SteamUGC.GetNumSubscribedItems();
            PublishedFileId_t[] subscribedItems = new PublishedFileId_t[numSubscribed];
            SteamUGC.GetSubscribedItems(subscribedItems, numSubscribed);
            
            foreach (var item in subscribedItems)
            {
                // Process subscribed workshop items
                ProcessWorkshopItem(item);
            }
        }
        
        private void ProcessWorkshopItem(PublishedFileId_t itemId)
        {
            // Handle workshop item download and installation
            ulong sizeOnDisk;
            string folder;
            uint timestamp;
            
            bool installed = SteamUGC.GetItemInstallInfo(itemId, out sizeOnDisk, out folder, 1024, out timestamp);
            
            if (installed)
            {
                Console.WriteLine($"Workshop item installed at: {folder}");
                // Load workshop content from folder
            }
        }
    }
    

    Further Reading

    • API Reference - Complete API documentation
    • Game Architecture - Player architecture overview
    • Settings Management - Steam settings management
    • Multiplayer Guide - Steam networking features

    Steam integration provides powerful social and platform features that enhance your game's appeal and user engagement. Use these features to create a more connected and social gaming experience.

    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.