Skip to content

Unity quickstart

FRVR ships a Unity wrapper — a .unitypackage that bridges your C# code into the FRVR SDK for Unity games exported to WebGL. You write C#; the wrapper handles the JS boundary inside the WebGL build.

A deliberately small C# API surface under the FRVR namespace:

  • FRVRSDK.Init — bootstrap. Also: FRVRSDK.IsInitialized, FRVRSDK.IsAvailable, FRVRSDK.Version.
  • FRVRSDK.GameReady — signal that loading is done and the game is interactive. Call once, after all loading completes (see §5).
  • FRVRSDK.Ad.ShowInterstitialAd / ShowRewardAd / ShowBannerAd / HideBannerAd / IsAdReady
  • FRVRSDK.Score.SetScore — submit the player’s current score to the channel’s native leaderboard (Game Center / Play Games).
  • FRVRSDK.Analytics.LogFTUE / LogLevelStart / LogLevelEnd / LogEvent
  • FRVRSDK.Data.SetItem / GetItem / RemoveItem / RemoveItems / SetItems / GetItems / SetLocalJson / GetLocalJson (local) and SetCloudItem / GetCloudItem / RemoveCloudItem / RemoveCloudItems (cloud)
  • FRVRSDK.Social.CanInvite / Share / Invite / AddShortcut / SubscribePushMessages
  • FRVRSDK.IAP.IsReady / GetCatalog / GetProduct / GetProductPrice / Purchase / ConsumePurchase / GetUnconsumedPurchases / RestorePurchases
  • FRVRSDK.Tournament.IsSupported / IsSupportedAPI / GetCurrentTournament / PostScore / Create / CreateOrPostScore / GetMyTournaments / GetActiveTournaments / Join / Leave / InvitePlayers
  • FRVRSDK.Notifications.CanScheduleMessages / SubscribeScheduleMessages / ScheduleMessage
  • Top-level helpers: FRVRSDK.GetChannelID / GetEntrypointData / GetAllowExternalLinks / MuteVolume / UnmuteVolume / SetVolume / GetVolume / IsMuted

It is not a 1:1 of the JavaScript SDK — it’s the subset that makes sense in a Unity game, with a C#-native shape (callback-based rather than Promise-based).

Download the latest frvr_sdk<version>.unitypackage from the Downloads page and import it via Assets → Import Package → Custom Package….

These settings are required, not suggestions:

  • Unity 6.1 or newer. The wrapper relies on recent Unity Web optimisations.
  • “Run in background” must be OFF (Project Settings → Player → Resolution and Presentation).
  • Default Unity WebGL template (Project Settings → Player → WebGL → Resolution and Presentation → WebGL Template: Default).
  • Brotli compression on the build, with fallback compression enabled (Project Settings → Player → Publishing Settings → Compression Format: Brotli, and keep the fallback setting enabled).

For additional build‑size and performance guidance see Unity web optimization tips.

3. Audio (required for iOS mute behaviour)

Section titled “3. Audio (required for iOS mute behaviour)”

Facebook on iOS enforces the system mute button aggressively. If your audio isn’t imported with the right settings, mute will silently break:

  • Load Type: Decompress on Load
  • Compression Format: AAC (for iOS‑mute compatibility) — PCM if AAC isn’t viable

Apply these to every AudioClip used in game; missing the setting on even one clip is enough to fail mute compliance.

Drop a bootstrap MonoBehaviour into your first scene:

using UnityEngine;
using FRVR;

public class FrvrBootstrap : MonoBehaviour
{
    void Start()
    {
        FRVRSDK.Init(() =>
        {
            Debug.Log("FRVRSDK initialized");

            // Safe to call any FRVRSDK.* method from here on.
            FRVRSDK.Analytics.LogLevelStart("Level 1 start");
        });
    }
}

FRVRSDK.Init only bootstraps the SDK — it does not tell the host channel the game is playable. Once all loading is finished and the player can actually interact (your main menu or first playable frame is on screen), call:

FRVRSDK.GameReady();

Call it exactly once, at the very first moment the game is interactive — never while a loading bar, splash, or transition is still on screen. This is the Unity equivalent of the Web SDK’s FRVR.bootstrapper.complete(): the host keeps showing its own loading UI until you call it, then hands control to the player.

Each call takes three callbacks — start, error, finish. The error callback receives an SdkError struct (error.error is the message string).

FRVRSDK.Ad.ShowInterstitialAd(
    () => Debug.Log("Interstitial started"),
    (SdkError error) => Debug.Log("Interstitial error: " + error.error),
    () => Debug.Log("Interstitial finished"));

FRVRSDK.Ad.ShowRewardAd(
    () => Debug.Log("Reward started"),
    (SdkError error) => Debug.Log("Reward error: " + error.error),
    () => GiveReward());   // called when the user completed the ad

// Optional preflight gate — useful for greying out buttons:
if (FRVRSDK.Ad.IsAdReady("reward")) ShowDoubleRewardButton();
FRVRSDK.Analytics.LogFTUE(1, "learn_to_jump");
FRVRSDK.Analytics.LogLevelStart("level_1");
FRVRSDK.Analytics.LogLevelEnd("level_1");
FRVRSDK.Analytics.LogEvent("shop_open", "{}");

Local (per device):

FRVRSDK.Data.SetItem("coins", "100", result => Debug.Log($"set: {result}"));
FRVRSDK.Data.GetItem("coins", value => Debug.Log($"get: {value}"));
FRVRSDK.Data.RemoveItem("coins", result => Debug.Log($"removed: {result}"));

string[] keys = { "coins", "level", "skin" };
FRVRSDK.Data.RemoveItems(keys, result => Debug.Log($"batch removed: {result}"));

Cloud (follows the player across devices):

FRVRSDK.Data.SetCloudItem("save", json, result => Debug.Log($"saved: {result}"));
FRVRSDK.Data.GetCloudItem("save", value => LoadFromJson(value));
FRVRSDK.Data.RemoveCloudItem("save", result => { });
if (FRVRSDK.IAP.IsReady())
{
    // Catalog & product lookup return JSON strings
    string catalogJson    = FRVRSDK.IAP.GetCatalog();
    string productJson    = FRVRSDK.IAP.GetProduct("bundlesmall");
    string localisedPrice = FRVRSDK.IAP.GetProductPrice("bundlesmall");  // e.g. "Din 110"

    FRVRSDK.IAP.Purchase("bundlesmall", resultJson =>
    {
        // resultJson is "{}" on failure/cancel, or a purchase object on success:
        // { purchaseId, productId, channelId, purchaseTime, transactionId, transactionReceipt }
        if (resultJson == "{}") return;
        GrantProduct(resultJson);

        FRVRSDK.IAP.ConsumePurchase(resultJson, _ => { });  // for consumables only
    });
}

// Recover unfinished consumables at startup
FRVRSDK.IAP.GetUnconsumedPurchases(purchasesJson =>
{
    // purchasesJson is a JSON array of purchase objects ("[]" if none)
});

Check the surface first — not every channel supports every action:

FRVRSDK.Social.CanInvite(canInvite =>
{
    if (canInvite) ShowInviteButton();
});

Share a screenshot as a data URL (the wrapper expects base64):

string imageBase64 = "data:image/png;base64,"
    + System.Convert.ToBase64String(shareTexture.EncodeToPNG());

FRVRSDK.Social.Share(
    "Beat my 42k in Fruit Sort FRVR!",
    imageBase64,
    headline: "Share",
    cta:      "Play",
    result => Debug.Log("share result: " + result));

An invite dialog with structured entrypoint data:

FRVRSDK.Social.Invite(
    "Come play with me!",
    imageBase64,
    headline: "Join",
    cta:      "Play",
    entryPointData: "{\"room\":\"abc\"}",
    result => Debug.Log("invite result: " + result));
if (!FRVRSDK.Tournament.IsSupported()) return;

// Post a score to the current tournament
FRVRSDK.Tournament.PostScore(tournamentId, 1500,
    () => Debug.Log("posted"),
    err => Debug.LogError(err));

// Smart combined helper: creates one if the player isn't in a tournament,
// otherwise posts their new score.
FRVRSDK.Tournament.CreateOrPostScore(
    payloadJson: "{}",
    title:       "Weekly Challenge",
    sortOrder:   "HIGHER_IS_BETTER",
    endTime:     0L,               // ms since epoch; 0 = default 2-week end time
    score:       1500,
    resultJson => Debug.Log(resultJson),
    err => Debug.LogError(err));

Submit the player’s current score to the channel’s native leaderboard (Apple Game Center, Google Play Games). This is separate from FRVR tournaments — use it in addition, on channels that expose a native score surface (e.g. YouTube Playables).

FRVRSDK.Score.SetScore(1234);

The submitted value should match the player’s best score in cloud save. On channels without a native score surface the call is a harmless no‑op, so you don’t need to guard it. See the Web SDK score reference.

FRVRSDK.Notifications.CanScheduleMessages(canSchedule =>
{
    if (!canSchedule) return;

    FRVRSDK.Notifications.SubscribeScheduleMessages(() =>
    {
        var config = new ScheduleMessageConfig
        {
            playerId       = playerId,
            delayInSeconds = 86400,
            imageUrl       = "https://cdn.mygame.com/push.png",
            message        = "Your daily reward is ready!",
            buttons        = new[] { new ScheduleMessageButton { type = "game_play", title = "Play!", payload = "" } }
        };
        FRVRSDK.Notifications.ScheduleMessage(config, () => { }, err => Debug.LogError(err));
    }, err => Debug.LogError(err));
});
  • Editor runs in simulation mode. Playmode in the Editor uses fixtures (ads play a 3-second placeholder, IAP returns a synthetic purchase, storage writes to Library/FRVRSim/). It does not talk to the real channel — final verification still requires a WebGL build run from a frvr.com / dev-frvr.com / localhost host.
  • Consistent game naming. Every place your game name appears (logo, menus, promo images, ad copy) must use the same string. Mixing “Basketball FRVR” and “Basketball” will fail channel review.
  • Audio imports are the #1 break. Getting even one AudioClip wrong on iOS will trip Facebook’s mute compliance check. Audit every clip.

Do a normal Unity WebGL build with the settings above. Hand the build to FRVR’s release pipeline (or your FRVR contact). FRVR handles:

  • Replacing dev CDN references with local paths in index.html.
  • Bundling the correct per-platform channel adapter (Facebook Instant, iOS WebView, Android TWA, Samsung, Xiaomi, …).
  • Packaging everything into the zip / package delivered to players.

You don’t self‑host and don’t pick a production channel bundle.