Перейти к содержимому

SDK для Unity (C#)

Единый файл-синглтон с асинхронными методами (async/Task) и обработкой ошибок. Авторизация происходит автоматически по сессии BlueStudz Launcher — игроку не нужно логиниться повторно внутри игры.

Установка

  1. Скопируйте скрипт ниже в Assets/Scripts/BlueStudz/BlueStudzSDK.cs.
  2. Создайте пустой GameObject на стартовой сцене и добавьте компонент BlueStudzSDK, заполните поле Secret Token из кабинета разработчика.
  3. Вызовите await BlueStudzSDK.Instance.Init() при старте игры.

Справочник методов C# API

МетодПараметрыВозвращаетОписание
Init(appId, secretToken)string appId = null, string secretToken = nullTask<bool>Инициализация SDK. Считывает BLUESTUDZ_LAUNCH_TOKEN и обменивает на сессионный токен.
Achievements.Unlock(id)string achievementIdTask<bool>Разблокировка достижения. Подтверждается сервером с помощью SecretToken.
CloudSaves.Sync(...)string sha256, long size, string key, int? baseVerTask<CloudSaveResult>Регистрация загруженного сохранения в S3 и детекция конфликтов (baseVersion).
Presence.SetStatus(active)bool activeTask<bool>Обновление Rich Presence игрока (виден друзьям в лаунчере как «В игре»).

Исходный код интеграции

BlueStudzSDK.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// BlueStudz SDK for Unity - drop this single file into Assets/Scripts/BlueStudz/.
/// Reads BLUESTUDZ_API / BLUESTUDZ_GAME_ID / BLUESTUDZ_LAUNCH_TOKEN from the
/// environment (set automatically by the BlueStudz Launcher when it starts
/// your game) so players never have to log in a second time inside your game.
/// </summary>
public class BlueStudzSDK : MonoBehaviour
{
    public static BlueStudzSDK Instance { get; private set; }

    [Tooltip("Secret token from your BlueStudz developer dashboard (SDK tab). Never ship this in a public repo.")]
    public string SecretToken;

    private string apiBase;
    private string gameId;
    private string userToken;
    private string userId;
    public bool IsAuthenticated { get; private set; }

    private void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    /// <summary>
    /// Call once at startup. appId/secretToken override the environment
    /// variables the Launcher sets - pass null to use the Launcher's values.
    /// </summary>
    public async Task<bool> Init(string appId = null, string secretToken = null)
    {
        apiBase = Environment.GetEnvironmentVariable("BLUESTUDZ_API") ?? "https://api.bluestudz.ru";
        gameId = appId ?? Environment.GetEnvironmentVariable("BLUESTUDZ_GAME_ID");
        SecretToken = secretToken ?? SecretToken;
        var launchToken = Environment.GetEnvironmentVariable("BLUESTUDZ_LAUNCH_TOKEN");

        if (string.IsNullOrEmpty(gameId) || string.IsNullOrEmpty(launchToken))
        {
            Debug.LogWarning("[BlueStudzSDK] Not launched via BlueStudz Launcher - running unauthenticated.");
            return false;
        }

        try
        {
            var verifyResult = await PostJson<GameVerifyResponse>(
                "/auth/game/verify",
                "{\"launchToken\":\"" + launchToken + "\"}",
                withAuth: false);
            userToken = verifyResult.gameToken;
            userId = verifyResult.user.id;

            var authBody = $"{{\"gameId\":\"{gameId}\",\"secretToken\":\"{SecretToken}\",\"userId\":\"{userId}\",\"userToken\":\"{userToken}\"}}";
            var authResult = await PostJson<SuccessResponse>("/sdk/auth/verify", authBody, withAuth: false);
            IsAuthenticated = authResult.success;
            return IsAuthenticated;
        }
        catch (Exception e)
        {
            Debug.LogError($"[BlueStudzSDK] Init failed: {e.Message}");
            return false;
        }
    }

    public static class Achievements
    {
        public static async Task<bool> Unlock(string achievementId)
        {
            var body = "{\"achievementId\":\"" + achievementId + "\"}";
            var result = await Instance.PostJson<SuccessResponse>("/sdk/achievements/unlock", body, withAuth: true, withSecret: true);
            return result.success;
        }
    }

    public static class CloudSaves
    {
        /// <summary>
        /// sha256/sizeBytes/storageKey come from your own upload of the save
        /// archive to the presigned URL from POST /storage/presign; this call
        /// only registers the resulting file with BlueStudz's conflict detection.
        /// </summary>
        public static async Task<CloudSaveResult> Sync(string sha256, long sizeBytes, string storageKey, int? baseVersion)
        {
            var baseVersionJson = baseVersion.HasValue ? baseVersion.Value.ToString() : "null";
            var body = $"{{\"sha256\":\"{sha256}\",\"sizeBytes\":{sizeBytes},\"storageKey\":\"{storageKey}\",\"baseVersion\":{baseVersionJson}}}";
            return await Instance.PostJson<CloudSaveResult>("/sdk/cloudsaves/sync", body, withAuth: true);
        }
    }

    public static class Presence
    {
        /// <summary>Marks the player as active in this game (or clears it).</summary>
        public static async Task<bool> SetStatus(bool active)
        {
            var body = "{\"active\":" + (active ? "true" : "false") + "}";
            var result = await Instance.PostJson<SuccessResponse>("/sdk/presence/status", body, withAuth: true);
            return result.success;
        }
    }

    private async Task<T> PostJson<T>(string path, string jsonBody, bool withAuth, bool withSecret = false)
    {
        using var req = new UnityWebRequest($"{apiBase}{path}", "POST");
        var bytes = Encoding.UTF8.GetBytes(jsonBody);
        req.uploadHandler = new UploadHandlerRaw(bytes);
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("Content-Type", "application/json");
        if (withAuth) req.SetRequestHeader("Authorization", $"Bearer {userToken}");
        if (withAuth) req.SetRequestHeader("x-bs-gameid", gameId);
        if (withSecret) req.SetRequestHeader("x-bs-secrettoken", SecretToken);

        var op = req.SendWebRequest();
        while (!op.isDone) await Task.Yield();

        if (req.result != UnityWebRequest.Result.Success)
            throw new Exception($"{path} failed: {req.error} ({req.downloadHandler.text})");

        return JsonUtility.FromJson<T>(req.downloadHandler.text);
    }

    [Serializable] private class SuccessResponse { public bool success; }
    [Serializable] private class GameVerifyUser { public string id; }
    [Serializable] private class GameVerifyResponse { public string gameToken; public GameVerifyUser user; }
    [Serializable] public class CloudSaveResult { public bool success; public bool conflict; public string status; }
}

Пример использования

csharp
await BlueStudzSDK.Instance.Init();
if (BlueStudzSDK.Instance.IsAuthenticated)
{
    await BlueStudzSDK.Achievements.Unlock("first_blood");
    await BlueStudzSDK.Presence.SetStatus(true);
}