Yes2SDK for Unity
A single SDK for your Unity WebGL game. Integrate once against Yes2SDK, submit through the Yes2Games Dashboard, and the Yes2Games team handles the rest.
The current SDK version is also exposed at runtime via Yes2SDK.Version (string), so you can log it for support tickets.
Requirements
- Unity 2021.3 or newer (Unity 6 supported — see Building)
- WebGL build target
com.unity.nuget.newtonsoft-json(>= 3.2.1)
Installation
Via Git URL (recommended)
- Open Window > Package Manager
- Click + > Add package from git URL...
- Enter:
https://github.com/yes2games/yes2sdk-unity.git#v2.8.0 - Click Add
Pinning the URL with
#v2.8.0keeps the package hash stable across resolves. Bump the tag when a newer release ships. Without a tag, Package Manager re-resolves againstmainon every refresh and reports phantom diffs.
Via Local Folder
- Clone or download the repository
- Open Window > Package Manager
- Click + > Add package from disk...
- Navigate to the
Unity/folder and selectpackage.json
Initial Setup
- Open Yes2SDK > Build Window in the Unity menu bar
- Click Install Template — this installs the
Yes2SDK-SuperSDKWebGL template into your project - The status indicator changes from "Setup Pending" to "Ready"
After updating the SDK package, click Reinstall Template in the Build Window to copy changes into your project.
The package's EditMode tests stay hidden from the Test Runner until the project opts in. See Automated tests for the one-line
testablesentry.
Quick Start
This is the minimum integration your game must have. The lifecycle has three distinct stages — don't chain them together:
App launch → InitializeAsync (SDK ready)
Splash + loading → SetLoadingProgress(0..100) as assets load
Game playable → StartGameAsync (scene ready, accepting input)
using System.Collections;
using UnityEngine;
using Yes2SDK;
public class GameManager : MonoBehaviour
{
void Start()
{
// Stage 1 — at app launch, initialize the SDK.
Yes2SDK.InitializeAsync(
onSuccess: () =>
{
Debug.Log("SDK ready");
StartCoroutine(LoadAndStart());
},
onError: err => Debug.LogError(err)
);
}
IEnumerator LoadAndStart()
{
// Stage 2 — load your assets and report progress (0..100).
for (int p = 0; p <= 100; p += 10)
{
Yes2SDK.SetLoadingProgress(p);
yield return new WaitForSeconds(0.05f); // replace with real loading work
}
// Stage 3 — splash done, scene loaded, game is playable.
Yes2SDK.StartGameAsync(
onSuccess: () => Debug.Log("Game started"),
onError: err => Debug.LogError(err)
);
}
}
Don't call
StartGameAsyncdirectly inside theInitializeAsyncsuccess handler. The platform's loading bar treatsStartGameAsyncas "the game is playable now" — calling it before the player has anything to interact with shows a misleading 100% loading state.
Without this flow your game won't be accepted for review.
Core API
Implement everything in this section. Together these cover what Yes2Games needs to validate and monetize your game.
Lifecycle (required)
// Call once at startup
Yes2SDK.InitializeAsync(onSuccess, onError);
// Call as your game loads (0-100)
Yes2SDK.SetLoadingProgress(progress);
// Call when loading finishes and the game is playable
Yes2SDK.StartGameAsync(onSuccess, onError);
Important: these three calls fire in three distinct stages — don't chain them.
InitializeAsyncis at app launch.SetLoadingProgressis updated as your assets load.StartGameAsyncruns only when the game is actually playable (splash gone, scene loaded, accepting input). See Quick Start for the full pattern.
await-friendly overloads
Every *Async method has a Task-returning overload that takes a CancellationToken. Pass CancellationToken.None for no timeout, or a real token for cancellation/timeout support. Errors are thrown as Yes2SDKException (whose ErrorCode matches the Error.ErrorCode of the underlying failure):
using System.Threading;
using System.Threading.Tasks;
using Yes2SDK;
// Cancel init if it hangs for more than 10 seconds
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
await Yes2SDK.InitializeAsync(cts.Token);
Debug.Log("SDK ready");
}
catch (Yes2SDKException ex) when (ex.ErrorCode == ErrorCode.NetworkError)
{
// Platform unreachable — fall back or retry with backoff
}
catch (TaskCanceledException)
{
// Init didn't complete inside the timeout
}
The callback-based form is unchanged and still preferred for short-lived game-loop call sites where allocating Tasks is overkill.
Handle pause / resume so your game reacts when the SDK pauses you (e.g. during an ad):
Yes2SDK.OnPause += () => { Time.timeScale = 0; AudioListener.pause = true; };
Yes2SDK.OnResume += () => { Time.timeScale = 1; AudioListener.pause = false; };
Ads (required)
Interstitial ads run at natural break points. Rewarded ads run only when the player opts in.
Always wrap ad calls in
Game.GameplayStop()/Game.GameplayStart(). Platforms count "active gameplay seconds" for monetization — leaving gameplay running during an ad inflates those numbers and is grounds for rejection. See Gameplay Tracking for the full rule.
Yes2SDK.Game.GameplayStop(); // before the ad
Yes2SDK.Ads.ShowInterstitial(
placement: "level-end",
description: "Between levels",
beforeAd: () => PauseGame(),
afterAd: () => { ResumeGame(); Yes2SDK.Game.GameplayStart(); },
onError: err => { ResumeGame(); Yes2SDK.Game.GameplayStart(); }
);
Yes2SDK.Game.GameplayStop(); // before the ad
Yes2SDK.Ads.ShowRewarded(
placement: "extra-life",
description: "Extra life reward",
beforeAd: () => PauseGame(),
afterAd: () => { ResumeGame(); Yes2SDK.Game.GameplayStart(); },
adDismissed: () => { /* no reward — see firing order below */ },
adViewed: () => GiveReward(),
onError: err => { ResumeGame(); Yes2SDK.Game.GameplayStart(); }
);
Rewarded ad firing order
The callbacks fire in this order. Pay attention — getting it wrong silently breaks reward logic:
beforeAd → pause game (always)
(ad shown)
adViewed → grant reward (ONLY fires if the player watched the full ad)
— or —
adDismissed → no reward (fires if the player skipped/closed early)
afterAd → resume game (always — fires whether the player watched or dismissed)
⚠️ Do NOT grant rewards in
afterAd.afterAdfires for both completion and dismissal — granting rewards there gives them away on skip. Always grant inadViewed.
afterAdis last, and it is what completes the ad: the outcome arrives first, thenafterAd. A new ad can be started fromafterAd, but not fromadViewedoradDismissed, because the previous ad is still in flight there.
Format support
Ads.IsInterstitialSupported()/Ads.IsRewardedSupported()— whether the platform serves that ad format at all. Use these to feature-gate ad UI up front instead of hard-coding which platforms have which format. Both reportfalsewhen the injected runtime is older than the checks, so a gate never sends the game into a call the platform cannot serve. In the Editor both reporttrue, because the mock serves either format.- Banner support is a separate check on its own module:
Banners.IsSupported().
// Hide the whole offer where the format is absent, rather than failing on click.
doubleCoinsButton.gameObject.SetActive(Yes2SDK.Ads.IsRewardedSupported());
Support is not readiness: these ask whether the format exists on the platform, IsRewardedAdAvailable() below asks whether an ad looks ready right now.
Concurrent ad guard + readiness
Ads.IsAdShowing()— returnstruewhile aShowInterstitialorShowRewardedis in flight (between the call andafterAd/error). CallingShow*again while one is already showing is rejected immediately —onErrorfires withErrorCode.InvalidParamsand a message starting"Another ad is already in flight (AdAlreadyShowing)…".Ads.IsRewardedAdAvailable()— best-effort check whether a rewarded ad appears available right now. Most platform SDKs don't expose explicit readiness, so this returnstrueas long as the platform's ad module is loaded; the actualShowRewardedcall can still fail withnoFill. Use it as a hint, not a guarantee.
rewardButton.interactable =
!Yes2SDK.Ads.IsAdShowing() && Yes2SDK.Ads.IsRewardedAdAvailable();
Gameplay Tracking (required)
Tells Yes2Games when an active round begins and ends. Also call GameplayStop() before any ad and GameplayStart() after.
Yes2SDK.Game.GameplayStart();
Yes2SDK.Game.GameplayStop();
Analytics.LogLevelStart/LogLevelEndcan also trigger gameplay start / stop — use either pair, but don't call both.
Data (required)
Key-value storage. Persists across sessions automatically.
Yes2SDK.Data.SetInt("highScore", 1500);
Yes2SDK.Data.SetString("playerName", "Hero");
int score = Yes2SDK.Data.GetInt("highScore", defaultValue: 0);
bool exists = Yes2SDK.Data.HasKey("highScore");
Yes2SDK.Data.DeleteKey("highScore");
Analytics (recommended)
Yes2SDK.Analytics.LogEvent("custom-event", new Dictionary<string, object> {
{ "key", "value" }
});
Yes2SDK.Analytics.LogLevelStart("level-1");
Yes2SDK.Analytics.LogLevelEnd("level-1", score: 1500, success: true);
// Time-based games (racing, time-attack) can include duration:
Yes2SDK.Analytics.LogLevelEnd("level-1", score: 1500, success: true, durationSeconds: 87.3f);
Yes2SDK.Analytics.LogScore(1500);
Session (recommended)
string locale = Yes2SDK.Session.GetLocale(); // e.g. "en", "fr"
string device = Yes2SDK.Session.GetDevice(); // "desktop" or "mobile"
Treat session info as a hint, not a guarantee. Don't branch your core game logic on it.
Optional APIs
These modules add extra player-facing features. They are not guaranteed to be available at runtime — guard with IsSupported() (available on Auth, Friends, Banners, Score, and Player), and always handle FeatureNotSupported errors gracefully. Don't make your core gameplay depend on them.
Auth
if (Yes2SDK.Auth.IsSupported())
{
Yes2SDK.Auth.GetCurrentUserAsync(
onSuccess: user => Debug.Log($"User: {user.Name}, authenticated: {user.IsAuthenticated}"),
onError: err => Debug.LogError(err)
);
Yes2SDK.Auth.SignInAsync(
onSuccess: user => Debug.Log($"Signed in as {user.Name}"),
onError: err => Debug.LogError(err)
);
}
Friends
if (Yes2SDK.Friends.IsSupported())
{
Yes2SDK.Friends.ListFriendsAsync(
page: 0, size: 10,
onSuccess: page => {
foreach (var friend in page.Friends)
Debug.Log($"{friend.Username} ({friend.Id})");
},
onError: err => Debug.LogError(err)
);
}
Banners
There are two banner surfaces and they are not interchangeable. Pick by the shape of banner the platform actually offers:
| Surface | Shape | Use when |
|---|---|---|
Ads.ShowBanner(BannerPosition) | One banner, placed top or bottom by the platform. No id, no size. | The platform offers a single sticky banner and accepts no container or size. |
Banners.ShowBanner(id, BannerSize) | Many banners, each in a named container at a size you choose. | The platform renders display ads into containers you lay out yourself. |
They are separate end to end: separate callbacks, separate platform mappings, and no shared state. That last part is why mixing them breaks rather than merely duplicating: hiding on one surface can clear banners the other surface placed, and a refresh can miss one, because each surface tracks only what it placed itself. Pick one surface per game.
Support for either surface varies by platform, and passing a container or size to a platform that has neither means those arguments are ignored. Check the per-method support tables before you commit to one: Ads, Banners.
Only the container surface has a support probe. Banners.IsSupported() answers
before you call; the position-based surface has no equivalent, so an unsupported
platform reports itself through onError after the fact.
// Container-based: many banners, explicit sizes.
if (Yes2SDK.Banners.IsSupported())
{
Yes2SDK.Banners.ShowBanner("sidebar-left", BannerSize.Medium_300x250);
Yes2SDK.Banners.HideBanner("sidebar-left");
Yes2SDK.Banners.HideAllBanners();
}
// Position-based: one sticky banner, platform picks the size.
Yes2SDK.Ads.ShowBanner(BannerPosition.Bottom,
onShown: () => Debug.Log("Banner shown"),
onError: err => Debug.LogError(err));
Yes2SDK.Ads.HideBanner();
Game Extras
HappyTime() signals to the platform that the player just hit a positive moment — level cleared, achievement unlocked, boss defeated. Some platforms use this signal to time monetization prompts and rate requests so they don't interrupt frustrating moments. Call it sparingly, only on genuine highs.
Yes2SDK.Game.HappyTime();
Yes2SDK.Game.InviteLinkAsync(
new Dictionary<string, string> { { "roomId", "abc123" } },
onSuccess: link => Debug.Log($"Invite: {link}")
);
Yes2SDK.Game.ShowInviteButton(new Dictionary<string, string> { { "roomId", "abc123" } });
Yes2SDK.Game.HideInviteButton();
GameSettings settings = Yes2SDK.Game.GetSettings();
Yes2SDK.Game.OnSettingsChanged += s => ApplySettings(s);
Yes2SDK.Game.CopyToClipboard("https://...");
Score
if (Yes2SDK.Score.IsSupported())
{
Yes2SDK.Score.AddScore(150f);
Yes2SDK.Score.SubmitScore("encrypted-score-string");
}
Player Data
if (Yes2SDK.Player.IsDataSupported())
{
Yes2SDK.Player.SetDataAsync("{\"level\":5}", onSuccess: () => {});
Yes2SDK.Player.GetDataAsync(new[] { "level" }, onSuccess: json => {});
Yes2SDK.Player.FlushDataAsync();
}
if (Yes2SDK.Player.IsConnectedPlayersSupported())
{
Yes2SDK.Player.GetConnectedPlayersAsync(onSuccess: json => {});
}
Integration Checklist
Your build is ready for review when:
-
InitializeAsyncis called at startup -
SetLoadingProgressis called as assets load -
StartGameAsyncis called when the game is playable -
OnPause/OnResumeare handled (mute audio, pause gameplay) - Interstitial ads run at natural break points
- Rewarded ads grant reward only in
adViewed -
Game.GameplayStop()is called before every ad;Game.GameplayStart()after - Gameplay resumes in
afterAdANDonError -
Datais used for persistent player data
The QA Inspector in the Yes2Games Dashboard validates all of this automatically.
Building
- Open Yes2SDK > Build Window
- Click Build WebGL (or Build and Run to launch in browser).
The output folder is what you upload to the Yes2Games Dashboard — the dashboard handles SDK injection, platform bundling, and walks you through the QA Inspector and review request.
Build Configuration
| Setting | Recommended | Notes |
|---|---|---|
| Template | Yes2SDK-SuperSDK | Required. The build-time guard fails the build with any other template. |
| Compression | Disabled | Required for dashboard upload — the CDN doesn't currently send Content-Encoding headers. |
| Code Stripping | Medium | Balances build size and AOT safety. |
| Exception Support | Explicitly Thrown Exceptions Only | See note below. |
| Memory Size | 256 MB+ | Most games need at least 256–512 MB. Smaller heaps trigger a generic "unspecified error" at boot. |
⚠️ Why not
Exception Support: None?
Noneproduces the smallest build, but Unity WebGL'sNonemode strips exception infrastructure entirely — even an exception caught by atry/catchaborts the wasm. This breaks any code path where a dependency usestry/catchas control flow (Newtonsoft.Json — which the SDK itself imports — third-party Unity asset packages, save systems, etc.).Most games should ship with Explicitly Thrown Exceptions Only: about 10% larger build but compatible with the .NET ecosystem. Use
Noneonly after auditing your full dependency graph.
Where to set these
- Yes2SDK Build Window (recommended) — Yes2SDK > Build Window > WebGL Settings (collapsible panel). Edits write to Player Settings live, no Apply step. Click Reset to recommended to apply all the values from the table above at once.
- Unity 2021–2022: Edit > Project Settings > Player > WebGL (project-wide).
- Unity 6+: File > Build Profiles — select your WebGL profile, then click Player Settings at the bottom of the profile panel. Settings apply only to that profile.
Unity 6 moved WebGL settings from project-wide Player Settings into per-profile Build Profiles. The Yes2SDK Build Window's Settings panel reads and writes the project-wide Player Settings. If you have an active Build Profile with overrides for these settings, the profile takes precedence at build time — edit those overrides via File > Build Profiles. The panel will surface a notice when an active profile is detected so you don't edit values that are then ignored.
Build Mode for diagnostics
The Build Window has a Build Mode dropdown for one-off overrides without changing your Player Settings:
- Production — use Player Settings as-is. Default for shipping builds.
- Production Safe — temporarily forces Exception Support to
Explicitly Thrownfor one build. Useful when your Player Settings isNonebut you need a build that catches third-party throws. - Diagnostic — temporarily forces
Full With Stacktrace. Use to capture real C# class/method names in browser console errors when chasing a crash.
The override saves and restores Player Settings around each build, so it never leaves your project in an unexpected state.
Editor Testing
In the Unity Editor, SDK calls run against mock implementations:
InitializeAsync/StartGameAsyncsucceed immediately- Ads show a fullscreen mock ad in Play Mode: a countdown (3s interstitial, 5s rewarded), then Close Ad (fires
afterAd), or Claim Reward (firesadViewed) / Skip (firesadDismissed) for rewarded ads. This lets you verify pause-resume wiring and both reward outcomes by clicking. The ad fills the game view and scales with its resolution, landscape or portrait. While the popup is up, input to the game behind it is blocked (uGUI clicks, legacyInputaxis/button polling), matching how a real ad overlay behaves. Direct new Input System device polling (e.g.Keyboard.current) is not suppressed. - IAP is mocked in Play Mode:
IsSupported()returns true,GetCatalogAsyncreturns a sample catalog, andPurchaseAsyncopens a Buy / Cancel dialog. Any product id is accepted, so you can test with your real ids. Purchases last for the current play session. - Failures can be simulated: the Ad result dropdown (No fill / Ad blocked / Error) makes ad calls fire
onErrorwith platform-shaped error codes, and Fail purchases makesPurchaseAsyncfail, so error handling is testable without a platform build. DatausesPlayerPrefs- Other optional APIs return
FeatureNotSupported
Both mocks can be turned off under Yes2SDK > Build Window > Play Mode Testing. With the ad popup off, ad callbacks fire instantly with no UI (pass "dismiss" as the rewarded description to trigger adDismissed). Batch-mode runs (CI) always use the instant flow.
For richer simulation (specific locales, network conditions, event log capture), use the QA Inspector in the Yes2Games Dashboard.
Automated tests
The package ships EditMode tests covering the ad callback contract: callback order, and the in-flight teardown that keeps one bad ad from blocking every later one. They run on the instant flow, so they need no rendering and work in batch mode.
To see them in a consuming project, add the package to testables in that project's Packages/manifest.json:
{
"testables": [
"com.yes2games.yes2sdk"
]
}
They then appear under Window > General > Test Runner > EditMode. Headless:
Unity -batchmode -nographics -projectPath <project> -runTests -testPlatform EditMode -testResults results.xml
testables is a project-manifest field, so a package cannot opt itself in; each consuming project adds the line.
Error Handling
All async methods accept an onError callback with an Error struct:
public struct Error
{
public string Code;
public string Message;
public string Context;
public ErrorCode ErrorCode;
}
public enum ErrorCode
{
NotInitialized, InvalidParams, FeatureNotSupported, PlatformError,
NetworkError, RateLimited, UserCancelled, Unknown
}
Use ErrorCode for control flow:
onError: err => {
if (err.ErrorCode == ErrorCode.FeatureNotSupported)
// gracefully hide the feature
else
Debug.LogError(err);
}
Error code reference
| Code | When it fires | Recommended handling |
|---|---|---|
NotInitialized | An API was called before InitializeAsync succeeded. | Wait for init to complete first; never call SDK methods from Awake() without checking Yes2SDK.IsInitialized. |
InvalidParams | A required parameter was null/empty or out of range. | Treat as a programmer error — fix the call site. |
FeatureNotSupported | The optional API isn't available on this platform (e.g. Friends on Poki). | Hide the related UI; fall back to a non-platform alternative. Always pre-check with IsSupported() for optional APIs. |
PlatformError | The underlying platform SDK rejected the call. | Log err.Message and err.Context for support; treat the call as failed. |
NetworkError | A platform call failed network-side (timeout, offline, server error). | Retry with backoff. Don't retry indefinitely. |
RateLimited | Too many calls in a short window (e.g. ad spam protection). | Back off and try again later — don't retry immediately. |
UserCancelled | The player closed/dismissed a flow (e.g. login dialog, rewarded ad). | Not an error in the usual sense — silently respect the player's choice, no toast. |
Unknown | The error didn't match any of the above. | Log everything (err.Code, err.Message, err.Context) and treat as a hard failure. |
Running alongside other SDKs
Real games often ship with multiple platform SDKs in the same build (Yes2SDK + Poki + Yandex + Playgama, etc.). A few ground rules to keep them from stepping on each other:
- Init order. Initialize Yes2SDK first. Yes2SDK figures out which actual platform is hosting the game and routes through it — initializing your own platform SDK directly first can race with Yes2SDK's detection.
- One owner for pause / resume. Pick one SDK to drive
Time.timeScaleandAudioListener.pause. If both Yes2SDK and another SDK call resume/pause, you'll get oscillation. Recommended: subscribe toYes2SDK.OnPause/OnResumeand ignore the other SDK's equivalent. - One owner for ads. Don't call ads via two SDKs in the same session — the platform almost always rejects the second call. Pick the SDK that targets the platform you're actually hosted on.
- Namespace collisions. If you have your own
Platformtype, qualify the Yes2SDK enum (Yes2SDK.Platform) at the call site or use ausingalias (using Y2 = Yes2SDK;). C# resolves namespace-vs-type ambiguity by full qualification. - Init timeout. If you depend on Yes2SDK init completing before your other SDK's flow, wrap
InitializeAsyncin aCancellationTokenwith a timeout (seeawait-friendly overloads) so your game doesn't hang on a wedged JS bridge.
License
MIT — see LICENSE.