// SAQA Games SDK — Unity үшін C# оралымы
//
// Орналасуы: Assets/Saqa/SaqaSDK.cs
// Қосымша қажет: Assets/Plugins/WebGL/SaqaSDK.jslib
//
// Қолданылуы:
// SaqaSDK.Init(ready => {
// Debug.Log(SaqaSDK.IsAuthorized ? SaqaSDK.PlayerName : "қонақ");
// SaqaSDK.GameReady();
// });
//
// Редакторда да, WebGL-ден тыс платформаларда да қателік бермейді —
// SDK қолжетімсіз болса, шақырулар бірден сәтсіз жауап қайтарады.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Saqa
{
/// Кез келген SDK шақыруының нәтижесі.
public class SaqaResult
{
/// Сәтті аяқталды ма.
public bool Ok;
/// Жауаптың JSON көрінісі (қате болса — null).
public string Json;
/// Қате мәтіні.
public string Error;
/// Қате коды: AUTH_REQUIRED, REQUEST_FAILED, SDK_UNAVAILABLE…
public string Code;
/// Аккаунтқа кіру қажет пе.
public bool NeedsAuth => Code == "AUTH_REQUIRED";
/// JSON-ды өз класыңызға айналдырады.
public T As() => string.IsNullOrEmpty(Json) ? default : JsonUtility.FromJson(Json);
}
[Serializable]
public class SaqaPlayerEntry
{
public string id;
public string name;
public string avatarUrl;
}
[Serializable]
public class SaqaLeaderboardEntry
{
public int rank;
public int score;
public string extraData;
public SaqaPlayerEntry player;
}
[Serializable]
public class SaqaLeaderboard
{
public SaqaLeaderboardEntry[] entries;
public SaqaLeaderboardEntry userEntry;
}
[Serializable]
public class SaqaAdResult
{
public bool wasShown;
public bool rewarded;
}
public class SaqaSDK : MonoBehaviour
{
// ── Күй ──────────────────────────────────────────────────────────────
public static bool IsReady { get; private set; }
public static bool IsAvailable { get; private set; }
public static bool IsAuthorized { get; private set; }
public static string PlayerId { get; private set; } = "";
public static string PlayerName { get; private set; } = "";
public static string AvatarUrl { get; private set; } = "";
public static string Language { get; private set; } = "kk";
public static string DeviceType { get; private set; } = "desktop";
private const string GameObjectName = "SaqaSDK";
private static SaqaSDK _instance;
private static Action _onReady;
private static int _nextId = 1;
private static readonly Dictionary> Pending =
new Dictionary>();
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")] private static extern void SaqaJsInit(string goName);
[DllImport("__Internal")] private static extern void SaqaJsCall(int id, string method, string argsJson);
#else
private static void SaqaJsInit(string goName) { }
private static void SaqaJsCall(int id, string method, string argsJson) { }
#endif
///
/// SDK-ні іске қосады. Ойын басталғанда бір рет шақырыңыз.
/// Callback SDK дайын болғанда (немесе қолжетімсіз екені анықталғанда) шақырылады.
///
public static void Init(Action onReady = null)
{
if (_instance != null)
{
onReady?.Invoke(IsAvailable);
return;
}
_onReady = onReady;
var go = new GameObject(GameObjectName);
_instance = go.AddComponent();
DontDestroyOnLoad(go);
#if UNITY_WEBGL && !UNITY_EDITOR
SaqaJsInit(GameObjectName);
#else
// Редакторда SDK жоқ — ойын қонақ режимінде жұмыс істейді
IsReady = true;
IsAvailable = false;
onReady?.Invoke(false);
#endif
}
// ── Ойыншы деректері ─────────────────────────────────────────────────
/// Сақталған деректерді оқиды. Нәтиже — JSON нысаны.
public static void GetData(Action callback, params string[] keys)
=> Call("getData", "{\"keys\":" + JsonArray(keys) + "}", callback);
///
/// Деректерді сақтайды. — JSON нысаны,
/// мысалы: {"level":7,"coins":250}
///
public static void SetData(string json, Action callback = null, bool merge = true)
=> Call("setData", "{\"data\":" + Obj(json) + ",\"merge\":" + Bool(merge) + "}", callback);
public static void GetStats(Action callback, params string[] keys)
=> Call("getStats", "{\"keys\":" + JsonArray(keys) + "}", callback);
public static void SetStats(string json, Action callback = null)
=> Call("setStats", "{\"stats\":" + Obj(json) + "}", callback);
/// Көрсеткіштерді арттырады, мысалы: {"wins":1,"deaths":2}
public static void IncrementStats(string json, Action callback = null)
=> Call("incrementStats", "{\"increments\":" + Obj(json) + "}", callback);
// ── Лидербордтар ─────────────────────────────────────────────────────
/// Нәтижені лидербордқа жібереді. Сервер тек жақсарған нәтижені жазады.
public static void SetScore(string leaderboard, long score,
string extraData = null, Action callback = null)
{
var extra = string.IsNullOrEmpty(extraData) ? "null" : Str(extraData);
Call("setScore",
"{\"name\":" + Str(leaderboard) + ",\"score\":" + score + ",\"extraData\":" + extra + "}",
callback);
}
/// Лидербордтың үздіктерін алады. Нәтижені SaqaLeaderboard-қа айналдырыңыз.
public static void GetEntries(string leaderboard, Action callback,
int quantityTop = 20, bool includeUser = true)
=> Call("getEntries",
"{\"name\":" + Str(leaderboard) + ",\"quantityTop\":" + quantityTop +
",\"includeUser\":" + Bool(includeUser) + "}",
callback);
// ── Жарнама ──────────────────────────────────────────────────────────
/// Толықэкранды жарнама. Ойынды алдын ала тоқтатыңыз.
public static void ShowFullscreenAd(Action callback = null)
=> Call("fullscreenAd", "{}", callback);
/// Сыйақылы жарнама. rewarded=true болса ғана сыйлық беріңіз.
public static void ShowRewardedAd(Action callback = null)
=> Call("rewardedAd", "{}", callback);
// ── Авторизация және экран ───────────────────────────────────────────
public static void OpenAuthDialog(Action callback = null)
=> Call("openAuthDialog", "{}", callback);
public static void RequestFullscreen(Action callback = null)
=> Call("requestFullscreen", "{}", callback);
public static void ExitFullscreen(Action callback = null)
=> Call("exitFullscreen", "{}", callback);
// ── Өмірлік цикл ─────────────────────────────────────────────────────
/// Ойын жүктеліп, ойнауға дайын болғанда шақырыңыз.
public static void GameReady() => Call("gameReady", "{}", null);
/// Нақты ойын процесі басталғанда.
public static void GameplayStart() => Call("gameplayStart", "{}", null);
/// Ойын процесі тоқтағанда (пауза, мәзір, ойын соңы).
public static void GameplayStop() => Call("gameplayStop", "{}", null);
// ── Ішкі механика ────────────────────────────────────────────────────
private static void Call(string method, string argsJson, Action callback)
{
if (_instance == null)
{
callback?.Invoke(new SaqaResult
{
Ok = false, Error = "SaqaSDK.Init() шақырылмаған", Code = "NOT_INITIALIZED"
});
return;
}
if (!IsAvailable)
{
callback?.Invoke(new SaqaResult
{
Ok = false, Error = "SDK қолжетімсіз", Code = "SDK_UNAVAILABLE"
});
return;
}
var id = _nextId++;
if (callback != null) Pending[id] = callback;
SaqaJsCall(id, method, argsJson);
}
[Serializable]
private class ReadyMessage
{
public bool available;
public bool authorized;
public string playerId;
public string playerName;
public string avatarUrl;
public string lang;
public string device;
}
[Serializable]
private class ResponseMessage
{
public int id;
public bool ok;
public string payload;
public string error;
public string code;
}
// JavaScript жағынан SendMessage арқылы шақырылады
private void HandleReady(string json)
{
var msg = JsonUtility.FromJson(json);
IsReady = true;
IsAvailable = msg.available;
IsAuthorized = msg.authorized;
PlayerId = msg.playerId ?? "";
PlayerName = msg.playerName ?? "";
AvatarUrl = msg.avatarUrl ?? "";
if (!string.IsNullOrEmpty(msg.lang)) Language = msg.lang;
if (!string.IsNullOrEmpty(msg.device)) DeviceType = msg.device;
var cb = _onReady;
_onReady = null;
cb?.Invoke(IsAvailable);
}
private void HandleResponse(string json)
{
var msg = JsonUtility.FromJson(json);
if (!Pending.TryGetValue(msg.id, out var callback)) return;
Pending.Remove(msg.id);
callback?.Invoke(new SaqaResult
{
Ok = msg.ok,
Json = msg.payload,
Error = msg.error,
Code = msg.code
});
}
// ── Кішкентай JSON көмекшілері ───────────────────────────────────────
// Unity-дің JsonUtility нысан құрастыруға ыңғайсыз, сондықтан
// қарапайым жолмен жинаймыз.
private static string Bool(bool v) => v ? "true" : "false";
private static string Obj(string json) =>
string.IsNullOrWhiteSpace(json) ? "{}" : json;
private static string Str(string value)
{
if (value == null) return "null";
var sb = new System.Text.StringBuilder("\"");
foreach (var c in value)
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default:
if (c < 32) sb.Append("\\u").Append(((int)c).ToString("x4"));
else sb.Append(c);
break;
}
}
return sb.Append('"').ToString();
}
private static string JsonArray(string[] items)
{
if (items == null || items.Length == 0) return "[]";
var parts = new string[items.Length];
for (var i = 0; i < items.Length; i++) parts[i] = Str(items[i]);
return "[" + string.Join(",", parts) + "]";
}
}
}