Skip to content

Instantly share code, notes, and snippets.

@michaelarts
Last active October 27, 2022 10:18
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save michaelarts/2ada67de3ca4295b1ea57df00fd230ca to your computer and use it in GitHub Desktop.
Save michaelarts/2ada67de3ca4295b1ea57df00fd230ca to your computer and use it in GitHub Desktop.
A static class that implements all basic functionality needed for a UWP Xbox One game. If you're releasing through ID@Xbox you'll be required to do some of these things. Just include this class in your project and start calling the functions! Enjoy!
using System;
using System.Collections.Generic;
using Windows.System;
using Windows.Storage.Streams;
using Windows.Services.Store;
using Windows.Gaming.XboxLive.Storage;
using Microsoft.Xbox.Services;
using Microsoft.Xbox.Services.System;
using Microsoft.Xbox.Services.Presence;
using Microsoft.Xbox.Services.Statistics.Manager;
using System.Threading.Tasks;
//IMPORTANT! FOLLOW THE STEPS BELOW TO ENSURE THIS CODE WILL WORK:
//1. Add references to XSAPI.UWP through NuGet.
//IF YOUR GAME ALLOWS MULTIPLE USERS TO PLAY (more than one gamertag will sign in to interact with your game), YOU MUST ALSO DO THIS:
//2. Define your game as a multi-user app: right click your Package.appxmanifest and click View Code.
//3. Under the <Properties></Properties> section add this: <uap:SupportedUsers>multiple</uap:SupportedUsers>
//4. Set the const bool below called "isMultiUser" to true.
namespace XboxOneUWP
{
public static class XboxOneUWP
{
//Properties:
const bool isMultiUser = false;
//Sign In and Services:
public static XboxLiveUser xboxUser;
private static User windowsUser;
private static XboxLiveContext xboxLiveContext;
private static bool currentlyAttemptingSignIn;
//Stats Manager:
private static StatisticManager StatsManager = null;
//Storage:
private static GameSaveProvider saveProvider;
//Trial mode:
private static StoreContext context = null;
private static StoreAppLicense appLicense = null;
public static bool SigningIn { get { return currentlyAttemptingSignIn; } }
/// <summary>
/// Brings up an account picker and lets the user pick a profile to sign in with.
/// This function can also be called if the user wants to Switch Users during the game.
/// After signing in, all services and storage will be initialized for the selected user.
/// Note: This must be called from the main UI thread.
/// </summary>
public static async void SignIn()
{
SignIn(Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher);
}
/// <summary>
/// Brings up an account picker and lets the user pick a profile to sign in with.
/// This function can also be called if the user wants to Switch Users during the game.
/// After signing in, all services and storage will be initialized for the selected user.
/// Pass in Windows.ApplicationModel.Core.CoreApplication.GetCurrentView().CoreWindow.Dispatcher
/// from the main UI thread.
/// </summary>
public static async void SignIn(object coreDispatcher)
{
//If we're already trying to sign in, return:
if (currentlyAttemptingSignIn)
return;
currentlyAttemptingSignIn = true;
if (isMultiUser == true)
{
//If someone else is signed in, sign them out:
SignOutUser();
//Bring up an account picker so the user can pick what profile he/she wants to play as:
UserPicker picker = new UserPicker();
picker.AllowGuestAccounts = false;
try
{
windowsUser = await picker.PickSingleUserAsync();
}
catch
{
windowsUser = null;
currentlyAttemptingSignIn = false;
return;
}
//Check to see if no user was selected or the player exited out of the account picker:
if (windowsUser == null)
{
currentlyAttemptingSignIn = false;
return;
}
//Make a new XboxLiveUser with the Windows User:
xboxUser = new XboxLiveUser(windowsUser);
}
else
xboxUser = new XboxLiveUser();
if (!xboxUser.IsSignedIn)
{
//Silently sign in:
try
{
await xboxUser.SignInSilentlyAsync(coreDispatcher);
}
catch (Exception ex)
{
string text = ex.Message;
}
}
//If we're unable to sign in silently, sign in normally:
if (!xboxUser.IsSignedIn)
{
try
{
await xboxUser.SignInAsync(coreDispatcher);
}
catch (Exception ex)
{
string text = ex.Message;
}
}
//If the user is signed in, create the variables we need to use services:
if (xboxUser.IsSignedIn)
{
try
{
//Create our new XboxLiveContext and StatsManager:
xboxLiveContext = new XboxLiveContext(xboxUser);
StatsManager = StatisticManager.SingletonInstance;
StatsManager.AddLocalUser(xboxUser);
StatsManager.DoWork();
//Initialize our storage for saving and loading:
await InitializeStorage();
}
catch
{
}
//Hook up the function that gets called when the user signs out:
XboxLiveUser.SignOutCompleted += SignOutCompleted;
//Load the license information associated with this user:
InitializeLicense();
}
currentlyAttemptingSignIn = false;
}
private static void SignOutCompleted(object sender, SignOutCompletedEventArgs e)
{
SignOutUser();
}
private static void SignOutUser()
{
if (StatsManager != null)
{
StatsManager.RequestFlushToService(xboxUser);
StatsManager.DoWork();
StatsManager.RemoveLocalUser(xboxUser);
StatsManager.DoWork();
StatsManager = null;
}
xboxUser = null;
xboxLiveContext = null;
saveProvider = null;
}
private static async Task<GameSaveErrorStatus> InitializeStorage()
{
if (xboxLiveContext == null)
return GameSaveErrorStatus.UserHasNoXboxLiveInfo;
// Getting a GameSaveProvider requires the Windows user object. It will automatically get the correct
// provider for the current Xbox Live user.
var users = await User.FindAllAsync();
if (users.Count > 0)
{
GameSaveProviderGetResult result = await GameSaveProvider.GetForUserAsync(
users[0], xboxLiveContext.AppConfig.ServiceConfigurationId);
if (result.Status == GameSaveErrorStatus.Ok)
{
saveProvider = result.Value;
}
return result.Status;
}
else
{
throw new Exception("No Windows users found when creating save provider.");
}
}
/// <summary>
/// Call this function to save data (like a user profile) via the ConnectedStorage API.
/// You must successfully call SignIn before this function can be used.
/// </summary>
public static async Task<GameSaveErrorStatus> SaveData(string containerName, string blobName, byte[] data)
{
if (saveProvider == null)
throw new InvalidOperationException("The save system is not initialized.");
else if (containerName.Contains(" "))
throw new InvalidOperationException("Container name cannot have spaces.");
GameSaveContainer container = saveProvider.CreateContainer(containerName);
// To store a value in the container, it needs to be written into a buffer, then stored with
// a blob name in a Dictionary.
DataWriter writer = new DataWriter();
writer.WriteBytes(data);
IBuffer dataBuffer = writer.DetachBuffer();
var updates = new Dictionary<string, IBuffer>();
updates.Add(blobName, dataBuffer);
GameSaveOperationResult result = await container.SubmitUpdatesAsync(updates, null, containerName);
return result.Status;
}
/// <summary>
/// Call this function to load data (like a user profile) via the ConnectedStorage API.
/// You must successfully call SignIn before this function can be used.
/// </summary>
public static async Task<LoadDataResult> LoadData(string containerName, string blobName)
{
if (saveProvider == null)
throw new InvalidOperationException("The save system is not initialized.");
else if (containerName.Contains(" "))
throw new InvalidOperationException("Container name cannot have spaces.");
GameSaveContainer container = saveProvider.CreateContainer(containerName);
string[] blobsToRead = new string[] { blobName };
// GetAsync allocates a new Dictionary to hold the retrieved data. You can also use ReadAsync
// to provide your own preallocated Dictionary.
GameSaveBlobGetResult result = await container.GetAsync(blobsToRead);
byte[] loadedData = null;
if (result.Status == GameSaveErrorStatus.Ok)
{
IBuffer loadedBuffer;
result.Value.TryGetValue(blobName, out loadedBuffer);
if (loadedBuffer == null)
{
throw new Exception(String.Format("Didn't find expected blob \"{0}\" in the loaded data.", blobName));
}
DataReader reader = DataReader.FromBuffer(loadedBuffer);
loadedData = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(loadedData);
}
return new LoadDataResult(result.Status, loadedData);
}
public static async Task<GameSaveContainerInfoGetResult> GetContainerInfo()
{
if (saveProvider == null)
{
throw new InvalidOperationException("The save system is not initialized.");
}
GameSaveContainerInfoQuery query = saveProvider.CreateContainerInfoQuery();
return await query.GetContainerInfoAsync();
}
public static async Task<GameSaveErrorStatus> DeleteContainer(string containerName)
{
if (saveProvider == null)
{
throw new InvalidOperationException("The save system is not initialized.");
}
GameSaveOperationResult result = await saveProvider.DeleteContainerAsync(containerName);
return result.Status;
}
/// <summary>
/// Call this function to unlock an achievement. The achievementName must match
/// what you named the achievement in the Dev Center.
/// </summary>
public static void UnlockAchievement(string achievementName, uint percentComplete)
{
if (xboxUser == null || xboxUser.IsSignedIn == false)
return;
try
{
xboxLiveContext.AchievementService.UpdateAchievementAsync(xboxUser.XboxUserId, achievementName, percentComplete);
}
catch (Exception ex)
{
string exception = ex.Message;
}
}
/// <summary>
/// Call this function to set the value of a Featured Stat. The statName must match
/// what you named the Featured Stat in the Dev Center.
/// </summary>
public static void WriteStatInt(string statName, int value)
{
if (StatsManager == null || xboxUser == null || xboxUser.IsSignedIn == false)
return;
try
{
StatsManager.SetStatisticIntegerData(xboxUser, statName, value);
StatsManager.DoWork();
}
catch (Exception ex)
{
string exception = ex.Message;
}
}
/// <summary>
/// Call this function to set the value of a Featured Stat. The statName must match
/// what you named the Featured Stat in the Dev Center.
/// </summary>
public static void WriteStatString(string statName, string value)
{
if (StatsManager == null || xboxUser == null || xboxUser.IsSignedIn == false)
return;
try
{
StatsManager.SetStatisticStringData(xboxUser, statName, value);
StatsManager.DoWork();
}
catch (Exception ex)
{
string exception = ex.Message;
}
}
/// <summary>
/// Call this function to set the rich presence of the signed in user. The presenceName
/// must match what you named the Presence in the Dev Center.
/// </summary>
public static void SetPresence(string presenceName)
{
if (xboxUser == null || xboxUser.IsSignedIn == false)
return;
try
{
PresenceData data = new PresenceData(xboxLiveContext.AppConfig.ServiceConfigurationId, presenceName);
xboxLiveContext.PresenceService.SetPresenceAsync(true, data);
}
catch (Exception ex)
{
}
}
/// <summary>
/// This function gets license for the user... are we in trial mode or full mode?
/// </summary>
private static async void InitializeLicense()
{
if (context == null && isMultiUser == true)
context = StoreContext.GetForUser(windowsUser);
else if (context == null)
context = StoreContext.GetDefault();
appLicense = await context.GetAppLicenseAsync();
// Register for the licenced changed event.
context.OfflineLicensesChanged += context_OfflineLicensesChanged;
}
private async static void context_OfflineLicensesChanged(StoreContext sender, object args)
{
//Reload the license:
appLicense = await context.GetAppLicenseAsync();
}
/// <summary>
/// Returns true if the signed in user hasn't purchased the full version of the game
/// </summary>
public static bool IsTrialMode()
{
if (appLicense != null && appLicense.IsActive)
return appLicense.IsTrial;
return false;
}
/// <summary>
/// Brings up the store page where the user can purchase your game.
/// IMPORTANT: This must be called from the UI thread.
/// </summary>
public static async void ShowMarketplace()
{
//Get the store product for this game:
StoreProductResult product = await context.GetStoreProductForCurrentAppAsync();
if (product == null || product.Product == null)
return; //No product on the store yet.
StorePurchaseResult result = await context.RequestPurchaseAsync(product.Product.StoreId);
}
/// <summary>
/// Call this function when your program exits.
/// </summary>
public static void ExitCleanUp()
{
if (xboxUser.IsSignedIn)
{
StatsManager.RequestFlushToService(xboxUser);
StatsManager.DoWork();
StatsManager.RemoveLocalUser(xboxUser);
StatsManager.DoWork();
}
xboxLiveContext = null;
saveProvider = null;
xboxUser = null;
}
}
public struct LoadDataResult // Used to return the results of a LoadData call.
{
public LoadDataResult(GameSaveErrorStatus statusValue, byte[] dataValue)
{
Status = statusValue;
Data = dataValue;
}
public GameSaveErrorStatus Status;
public byte[] Data;
}
}
@vikingfabian
Copy link

Two things got me confused:
-"string achievementName" should be "string achievementId"
-"context" would be better named "storeContext"

@vikingfabian
Copy link

"UpdateAchievementAsync" needs to be put in a task to not freeze the game, despite the name

@tayannastudios
Copy link

How do I actually use this? Say I want to unlock achievement 1, what would I call, and where do I place these scripts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment