跳转到内容

Unity 快速上手

FRVR 提供 Unity 封装包 —— 一个 .unitypackage,把你的 C# 代码桥接到 FRVR SDK,服务于导出到 WebGL 的 Unity 游戏。你写 C#;封装包在 WebGL 构建里处理 JS 边界。

FRVR 命名空间下一组刻意做小的 C# API:

  • FRVRSDK.Init —— 启动
  • FRVRSDK.GameReady —— 通知宿主渠道:加载已完成、游戏可交互。在所有加载结束后调用一次(见 第 5 节)。
  • FRVRSDK.Ad.ShowInterstitialAd / ShowRewardAd
  • FRVRSDK.Score.SetScore —— 把玩家当前分数提交到渠道的原生排行榜(Game Center / Play Games)。
  • FRVRSDK.Analytics.LogFTUE / LogLevelStart / LogLevelEnd / LogEvent
  • FRVRSDK.Data.SetItem / GetItem / RemoveItem / RemoveItems(本地)以及 SetCloudItem / GetCloudItem / RemoveCloudItem / RemoveCloudItems
  • FRVRSDK.Social.CanInvite / Share / Invite
  • FRVRSDK.IAP.Purchase / ConsumePurchase / IsReady / GetCatalog / GetProduct / GetProductPrice / GetUnconsumedPurchases
  • FRVRSDK.Tournament.IsSupported / IsSupportedAPI / GetCurrentTournament / PostScore / Create / CreateOrPostScore / GetMyTournaments / GetActiveTournaments / Join / Leave / InvitePlayers
  • FRVRSDK.Notifications.CanScheduleMessages / SubscribeScheduleMessages / ScheduleMessage

不是 JavaScript SDK 的 1:1 镜像 —— 而是在 Unity 游戏里讲得通的子集,带 C# 原生风格(基于回调,不是 Promise)。

下载页 下载最新的 frvr_sdk<version>.unitypackage,通过 Assets → Import Package → Custom Package… 导入。

下面这些是必须设置,不是建议:

  • Unity 6.1 或更新版本。 封装包依赖较新的 Unity Web 优化项。
  • “Run in background” 必须关闭(Project Settings → Player → Resolution and Presentation)。
  • 使用默认的 Unity WebGL 模板(Project Settings → Player → WebGL → Resolution and Presentation → WebGL Template: Default)。
  • 构建启用 Brotli 压缩,并打开 fallback compression(Project Settings → Player → Publishing Settings → Compression Format: Brotli,fallback 选项保持开启)。

进一步的构建体积和性能建议见 Unity Web 优化提示

iOS 上的 Facebook 对系统静音键执行得很激进。如果你的音频导入设置不对,静音会悄悄失效:

  • Load Type: Decompress on Load
  • Compression Format: AAC(为了兼容 iOS 静音)—— AAC 不可行时用 PCM

把这套设置应用到游戏中用到的每一个 AudioClip;只要漏掉一个,就足以让静音合规检查失败。

在你的第一个场景里放一个 bootstrap MonoBehaviour:

using UnityEngine;
using FRVR;

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

            // 从此往后调用任意 FRVRSDK.* 方法都是安全的。
            FRVRSDK.Analytics.LogLevelStart("Level 1 start");
        });
    }
}

FRVRSDK.Init 只是启动 SDK —— 它不会告诉宿主渠道游戏已经可玩。当所有加载结束、玩家真正可以交互时(主菜单或第一帧可玩画面已经显示),调用:

FRVRSDK.GameReady();

只调用一次,在游戏可交互的第一时间调用 —— 加载条、闪屏或过场动画还在屏幕上时绝不能调用。它等价于 Web SDK 的 FRVR.bootstrapper.complete():在你调用之前,宿主会一直显示它自己的加载界面,调用之后才把控制权交给玩家。

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

FRVRSDK.Ad.ShowRewardAd(
    onStart:  () => Debug.Log("Reward started"),
    onError:  error => Debug.Log("Reward error: " + error),
    onFinish: () => GiveReward());   // 仅当用户完整看完广告时调用
FRVRSDK.Analytics.LogFTUE(1, "learn_to_jump");
FRVRSDK.Analytics.LogLevelStart("level_1");
FRVRSDK.Analytics.LogLevelEnd("level_1");
FRVRSDK.Analytics.LogEvent("shop_open", "{}");

本地存储(每台设备各自一份):

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}"));

云端存储(跨设备跟随玩家):

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())
{
    // 目录与商品查询返回 JSON 字符串
    string catalogJson    = FRVRSDK.IAP.GetCatalog();
    string productJson    = FRVRSDK.IAP.GetProduct("bundlesmall");
    string localisedPrice = FRVRSDK.IAP.GetProductPrice("bundlesmall");  // 例如 "Din 110"

    FRVRSDK.IAP.Purchase("bundlesmall", resultJson =>
    {
        // 失败/取消时 resultJson 为 "{}",成功时是一个 purchase 对象:
        // { purchaseId, productId, channelId, purchaseTime, transactionId, transactionReceipt }
        if (resultJson == "{}") return;
        GrantProduct(resultJson);

        FRVRSDK.IAP.ConsumePurchase(resultJson, _ => { });  // 仅消耗品调用
    });
}

// 启动时恢复未消耗的消耗品
FRVRSDK.IAP.GetUnconsumedPurchases();  // 返回 purchase 对象的 JSON 数组

先检测能力 —— 不是每个渠道都支持每种动作:

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

把截图作为 data URL 分享(封装期望 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));

带结构化入口数据的邀请弹窗:

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;

// 给当前锦标赛上传一个分数
FRVRSDK.Tournament.PostScore(tournamentId, 1500,
    () => Debug.Log("posted"),
    err => Debug.LogError(err));

// 智能合并辅助方法:玩家不在锦标赛里就创建一个,
// 否则就直接上传新分数。
FRVRSDK.Tournament.CreateOrPostScore(
    payload:   "{}",
    title:     "Weekly Challenge",
    sortOrder: "HIGHER_IS_BETTER",
    score:     1500,
    endTime:   "0",                // "0" = 默认 2 周结束时间
    resultJson => Debug.Log(resultJson),
    err => Debug.LogError(err));

把玩家当前分数提交到渠道的原生排行榜(Apple Game Center、Google Play Games)。这与 FRVR 锦标赛是两套东西 —— 在提供原生分数接口的渠道(例如 YouTube Playables)上额外使用它。

FRVRSDK.Score.SetScore(1234);

提交的分值应与云存档里玩家的最高分一致。在没有原生分数接口的渠道上,这个调用是无害的空操作,所以无需额外判空保护。见 Web SDK 分数参考

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 里没法测。 封装包只在 WebGL 构建里工作;Editor 的 playmode 触达不到 SDK。请构建到 Web、运行产物,通过浏览器控制台排查。
  • 游戏名要前后一致。 你的游戏名出现的所有地方(logo、菜单、宣传图、广告文案)必须用同一个字符串。“Basketball FRVR” 和 “Basketball” 混用会过不了渠道审核。
  • 音频导入是头号坑。 只要有一个 AudioClip 在 iOS 上设置错,就会触发 Facebook 的静音合规检查。把每一个 clip 都过一遍。

按上述设置做一个普通的 Unity WebGL 构建。把构建产物交给 FRVR 发布流水线(或你的 FRVR 对接人)。FRVR 负责:

  • index.html 里把 dev CDN 引用替换成本地路径。
  • 打入对应平台的渠道适配器(Facebook Instant、iOS WebView、Android TWA、三星、小米…)。
  • 把所有内容打成发给玩家的 zip / 包。

你不自行托管,也不挑选生产渠道包。