模块 4:Unity → 抖音小游戏
完整发布管线

Day 3 ⏱ 预计阅读 6~8 小时

⚠️ 版本说明

本模块基于 2025~2026 年的抖音小游戏平台和 TTSDK (原 StarkSDK) 编写。抖音平台更新频繁,发布前请务必对照官方最新文档确认细节。

技术架构概览

Unity → 抖音小游戏 技术栈 游戏业务逻辑 (C#) 第三方纯 C# 插件 TTSDK C# SDK Unity 引擎 BGDT 构建工具链 WebGL 构建 → WASM + Brotli 压缩 → JS 胶水层 → 资源打包 抖音小游戏运行时 (StarkMini) 基础能力:文件/网络/渲染 | 开放能力:登录/分享/支付/广告 宿主 App (抖音) → 操作系统 Android: V8 + 内部渲染引擎, JIT WASM iOS: WKWebView 运行时, JIT WASM 编译路径: C# → DLL → IL2CPP → C++ → Emscripten → WASM

4.1 环境准备与前置条件

必须安装的工具

工具版本要求说明
Unity EditorUnity 6 LTS(推荐 6000.0.40f1+)国际版,非团结引擎
WebGL Build Support与 Unity 同版本Unity Hub → Installs → Add Module
Android Build Support与 Unity 同版本真机调试需要(IL2CPP)
Node.js16+抖音开发者工具依赖
抖音开发者工具最新版从抖音开放平台下载
TTSDK (原 StarkSDK) Unity Plugin最新版从抖音开发者控制台下载(搜索 BGDT 或 TTSDK)

步骤 1:安装 Unity WebGL 模块

  1. 打开 Unity Hub → Installs
  2. 找到你的 Unity 6 版本 → 点击齿轮图标 → Add Modules
  3. 勾选 WebGL Build Support
  4. 点击 Install,等待下载完成

步骤 2:注册抖音开放平台账号

  1. 访问 developer.open-douyin.com
  2. 注册/登录开发者账号
  3. 进入控制台 → 创建小游戏应用
  4. 获取 AppID(后续 TTSDK (原 StarkSDK) 配置需要)

步骤 3:下载抖音开发者工具

  1. 访问抖音开放平台 → 开发 → 开发工具
  2. 下载 抖音开发者工具(Windows/Mac)
  3. 安装并登录你的开发者账号

4.2 TTSDK/BGDT 集成与配置

下载 TTSDK (原 StarkSDK)

  1. 登录抖音开放平台控制台
  2. 进入你的小游戏应用 → 开发 → 资源下载
  3. 下载 TTSDK (原 StarkSDK) Unity Plugin(通常是一个 .unitypackage 文件)

导入到 Unity 工程

  1. 在 Unity 中,Assets → Import Package → Custom Package
  2. 选择下载的 TTSDK (原 StarkSDK) .unitypackage 文件
  3. 勾选所有内容 → Import
  4. 导入后在菜单栏会出现 TTSDK (原 StarkSDK) 菜单

配置 TTSDK (原 StarkSDK)

  1. TTSDK (原 StarkSDK) → Settings(或 Window → TTSDK (原 StarkSDK) → Settings)
  2. 填写 AppID(从抖音开放平台获取)
  3. 选择目标平台(抖音/TikTok)
  4. 配置其他选项(按默认即可开始)
📋 TTSDK (原 StarkSDK) 提供的能力
能力C# API
登录TTSDK (原 StarkSDK).Login()
分享StarkShare.ShareAppMessage()
支付StarkPay.RequestPayment()
广告(激励视频)StarkAd.ShowRewardedVideoAd()
录屏StarkRecorder.Start/Stop()
存储StarkStorage.SetStorageSync()
关注StarkFollow.FollowOfficialAccount()
客服StarkService.OpenCustomerService()

4.3 WebGL 构建设置详解

Player Settings 配置

  1. Edit → Project Settings → Player
  2. 选择 WebGL 标签页
  3. 按以下配置:
设置项推荐值说明
Color SpaceLinearURP 用 Linear;Built-in 可用 Gamma
Auto Graphics APIWebGL 2.0取消勾选 Auto,手动只保留 WebGL 2.0
Compression FormatBrotli压缩率最高,抖音运行时支持
Code OptimizationDisk Size减小产物体积
IL2CPP Code GenerationSize (LTO)优化产物大小
Strip Engine Code✅ 勾选裁剪未使用的引擎代码
Strip Managed AssembliesStrip Assemblies裁剪未使用的 .NET 程序集

使用 TTSDK (原 StarkSDK) 构建

  1. TTSDK (原 StarkSDK) → Build(或 Window → TTSDK (原 StarkSDK) → Build)
  2. 选择输出目录(建议 Builds/DouyinMiniGame
  3. 点击 Build,TTSDK (原 StarkSDK) Unity Tools 会自动:
    • 执行 WebGL 构建
    • 生成 WASM 代码和 Brotli 压缩包
    • 注入 JS 胶水层代码
    • 打包资源
    • 生成符合抖音小游戏格式的产物目录

产物目录结构

Builds/DouyinMiniGame/
├── game.js              # 入口 JS 文件
├── game.json            # 小游戏配置文件
├── project.config.json  # 项目配置
├── index.html           # WebGL 入口页(调试用)
├── Build/
│   ├── *.wasm.code.unityweb    # WASM 代码(Brotli 压缩)
│   ├── *.wasm.framework.unityweb  # 框架代码
│   └── *.data.unityweb         # 资源数据包
├── TemplateData/        # 启动封面等资源
└── StreamingAssets/     # Addressables/AB 包

4.4 抖音开发者工具使用

导入项目

  1. 打开抖音开发者工具
  2. 点击「导入项目」或「+」按钮
  3. 选择构建产物目录(Builds/DouyinMiniGame
  4. 填写 AppID(与 TTSDK (原 StarkSDK) 配置的一致)
  5. 点击「导入」

模拟器预览

  1. 导入后自动在模拟器中运行
  2. 左侧面板选择设备型号(iPhone / Android)
  3. 模拟器支持触摸、摇一摇、横竖屏切换
  4. 底部 Console 查看 JS 日志和错误

真机预览

  1. 点击工具栏「预览」按钮
  2. 生成二维码
  3. 用抖音 App 扫描二维码
  4. 在真机上运行小游戏
⚠️ 真机预览需要条件

预览需要你的抖音账号已添加为该小游戏的开发者/体验成员。在抖音开放平台控制台 → 成员管理 中添加。

上传与审核

  1. 点击工具栏「上传」按钮
  2. 填写版本号(如 1.0.0)
  3. 上传完成后,在抖音开放平台控制台 → 版本管理 中提交审核
  4. 审核通过后即可发布上线

4.5 分包方案完整做法

抖音小游戏体积限制

类型限制说明
首包体积< 100 MB超过无法上传;推荐 WebGL 方案控制在 10~20 MB 以内
推荐目标< 60 MBNative 32+64 位合计;WebGL 实际体验标杆约 10 MB
总包体积分包后无硬性上限但单次下载建议 < 50 MB,超出部分走 CDN 按需加载
引擎版本TTSDK ≥ 6.2.0开发者工具 ≥ 4.2.2
🚨 首包体积控制是关键

虽然首包上限已放宽到 100 MB,但用户首次加载时间才是真正的瓶颈。WebGL 方案构建产物(WASM + 框架 + 数据)通常 10~30 MB,Brotli 压缩后仍有数 MB。建议通过 Wasm 分包 + 资源按需加载将首包控制在 10~20 MB 以内,确保弱网环境下 5 秒内可交互。

分包策略

抖音小游戏分包加载结构 首包 (≤100MB) game.js WASM 核心 最小框架代码 Wasm 分包1 按需加载 WASM 模块片段 Wasm 分包2 按需加载 Wasm 分包N 远程资源包 Addressables 远程 CDN 托管 远程 AB 包 模型/贴图/音效 首次下载 运行时下载 Wasm 分包工具: 拆分大 WASM 文件 → 每个分包 ≤ 4MB 资源: 用 Addressables/AssetBundle 部署到 CDN,运行时按需下载

Wasm 分包工具

TTSDK (原 StarkSDK) 提供了 Wasm 分包工具,将大的 .wasm 文件拆分为多个小文件:

  1. TTSDK (原 StarkSDK) → Build 面板中找到 Wasm 分包选项
  2. 设置每个分包的最大大小(建议 3~4 MB)
  3. 构建后产物中 WASM 文件被拆分为多个子包
  4. 首个子包随首包下载,其余在需要时加载

资源按需加载(Addressables)

游戏资源(模型、贴图、音频)不应该打进首包,而是通过 Addressables 部署到 CDN:

// 配置 Addressables 远程加载路径
// Window → Asset Management → Addressables → Profiles
// RemoteLoadPath 设置为你的 CDN 地址
// 例如: https://cdn.yourgame.com/assets/StandaloneWindows64/

// 在游戏中按需加载远程资源
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class ResourceLoader : MonoBehaviour
{
    // 加载远程 Prefab
    public void LoadCropModel(string cropKey)
    {
        Addressables.LoadAssetAsync<GameObject>(cropKey).Completed += handle =>
        {
            if (handle.Status == AsyncOperationStatus.Succeeded)
            {
                Instantiate(handle.Result, transform.position, Quaternion.identity);
            }
            else
            {
                Debug.LogError($"加载失败: {cropKey}");
            }
        };
    }

    // 带进度的加载
    public void LoadWithProgress(string key)
    {
        var handle = Addressables.DownloadDependenciesAsync(key);
        StartCoroutine(MonitorProgress(handle));
    }

    private System.Collections.IEnumerator MonitorProgress(
        AsyncOperationHandle handle)
    {
        while (!handle.IsDone)
        {
            Debug.Log($"下载进度: {handle.PercentComplete:P0}");
            yield return null;
        }
        Debug.Log("下载完成");
    }
}

AssetBundle 方案(替代 Addressables)

如果不使用 Addressables,也可以直接用 AssetBundle:

// AssetBundle 加载(更底层,需要手动管理)
public class ABLoader : MonoBehaviour
{
    private AssetBundle _bundle;

    public IEnumerator LoadBundle(string url)
    {
        using UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url);
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            _bundle = DownloadHandlerAssetBundle.GetContent(request);
            GameObject prefab = _bundle.LoadAsset<GameObject>("CropWheat");
            Instantiate(prefab);
        }
    }

    void OnDestroy()
    {
        _bundle?.Unload(false);
    }
}

4.6 内存优化手段

⚠️ 小游戏内存限制

抖音小游戏的内存建议上限:iOS 约 1GB,Android 约 1~2GB(因设备而异)。Unity WebGL 默认会占用较多内存,必须优化。

纹理优化

策略操作节省
纹理压缩使用 ASTC 4x4(移动端推荐格式)4~8x
减小 Max SizeUI 贴图用 512,场景贴图用 10242~4x
Mipmap3D 物体开启,UI 贴图关闭33% 内存
Read/Write取消勾选 Read/Write Enabled2x 内存
图集小图合并为 Sprite Atlas减少 Draw Call

Mesh 优化

代码优化

// 1. 避免 Update 中每帧分配(GC 压力)
// ❌ 错误
void Update() {
    var list = new List<CropData>(allCrops.Where(c => c.IsReady)); // 每帧 new List!
}

// ✅ 正确:缓存引用
private List<CropData> _readyCrops = new List<CropData>();
void Update() {
    _readyCrops.Clear();
    foreach (var c in allCrops)
        if (c.IsReady) _readyCrops.Add(c);
}

// 2. 避免字符串拼接(使用 StringBuilder 或缓存)
// ❌ 每帧 GC
Debug.Log("Gold: " + gold + " Day: " + day);

// ✅ 使用插值(编译器优化)或关闭日志
Debug.Log($"Gold: {gold} Day: {day}");

// 3. 对象池替代 Instantiate/Destroy
// 见模块 3 的 SimplePool 实现

// 4. 裁剪未使用的代码
// Project Settings → Player → Strip Engine Code ✅
// 添加 link.xml 防止反射代码被裁剪

link.xml 防裁剪

<!-- 文件: Assets/link.xml -->
<!-- 防止 IL2CPP 裁剪掉反射或序列化使用的类型 -->
<linker>
    <assembly fullname="UnityEngine" preserve="all"/>
    <assembly fullname="TTSDK (原 StarkSDK)" preserve="all"/>
    <assembly fullname="Assembly-CSharp">
        <type fullname="YourNamespace.SaveData" preserve="all"/>
    </assembly>
</linker>

4.7 不支持/受限功能清单

功能状态替代方案
System.Threading❌ 完全不支持用 Coroutine 或 async/await
多线程❌ 不支持所有逻辑在主线程
System.IO.File❌ 不支持用 PlayerPrefs 或 JS 存储 API
Socket(原生)❌ 不支持用 WebSocket
Sqlite❌ 不支持JSON 文件 + PlayerPrefs
VideoPlayer⚠️ 受限用平台原生视频 API
WebCamTexture❌ 不支持
MovieTexture❌ 不支持
动态加载 DLL❌ 不支持所有代码编译时确定
反射(部分)⚠️ 受限link.xml 保留必要类型
MonoBehaviour.OnGUI⚠️ 低效用 UGUI 或新 UI Toolkit
MeshCollider(动态)⚠️ 性能差用基本 Collider 组合
HDRP 渲染管线❌ 不支持用 URP 或 Built-in
实时阴影(多灯)⚠️ 性能差只用 1 个主光阴影
后处理特效⚠️ 受限最少化使用,Bloom 可用
⚠️ 线程替代方案

Unity WebGL 不支持任何多线程。如果你的农场工程中使用了 Task.RunThreadConcurrentQueue 等,必须改为单线程方案(协程/异步)。第三方库如果内部用了线程也会报错。

4.8 平台 API 桥接(登录/支付/分享/录屏)

登录

using StarkSDKSpace; // TTSDK 保持向后兼容命名空间

public class DouyinLogin : MonoBehaviour
{
    public void Login()
    {
        // 调用抖音登录
        StarkSDK.API.GetAccountManager().Login(
            success: (string code, string anonymousCode, string encryptedData) =>
            {
                Debug.Log($"登录成功! code={code}");
                // 把 code 发送到你的后端服务器换取用户信息
                StartCoroutine(SendCodeToServer(code));
            },
            fail: (string errMsg) =>
            {
                Debug.LogError($"登录失败: {errMsg}");
            }
        );
    }

    private System.Collections.IEnumerator SendCodeToServer(string code)
    {
        string url = "https://your-api.com/auth/douyin";
        string json = JsonUtility.ToJson(new LoginRequest { code = code });
        using UnityWebRequest req = new UnityWebRequest(url, "POST");
        req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(json));
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("Content-Type", "application/json");
        yield return req.SendWebRequest();
        Debug.Log(req.downloadHandler.text);
    }
}

[System.Serializable]
class LoginRequest { public string code; }

分享

public class DouyinShare : MonoBehaviour
{
    public void ShareToFriend()
    {
        StarkSDK.API.GetShareManager().ShareAppMessage(
            title: "来我的农场看看!",
            imageUrl: "https://cdn.yourgame.com/share.jpg",
            query: "from=share&farmId=123", // 自定义参数
            success: () => Debug.Log("分享成功"),
            fail: (string err) => Debug.Log($"分享失败: {err}")
        );
    }
}

激励视频广告

public class DouyinAd : MonoBehaviour
{
    private string _adUnitId = "your_ad_unit_id";

    public void ShowRewardedAd()
    {
        var ad = StarkSDK.API.GetAdManager().CreateRewardedVideoAd(_adUnitId);

        ad.OnClose += (bool isReward) =>
        {
            if (isReward)
            {
                Debug.Log("用户看完广告,发放奖励!");
                GameManager.Instance.AddGold(50);
            }
            else
            {
                Debug.Log("用户中途关闭");
            }
        };

        ad.OnError += (int code, string msg) =>
        {
            Debug.LogError($"广告错误: {code} - {msg}");
        };

        ad.Show();
    }
}

录屏

public class DouyinRecorder : MonoBehaviour
{
    private bool _isRecording = false;

    public void ToggleRecording()
    {
        if (!_isRecording)
        {
            StarkSDK.API.GetGameRecorder().Start();
            _isRecording = true;
        }
        else
        {
            StarkSDK.API.GetGameRecorder().Stop();
            _isRecording = false;
        }
    }

    public void ShareRecording()
    {
        // 录屏结束后分享
        StarkSDK.API.GetGameRecorder().Share((success) =>
        {
            if (success) Debug.Log("录屏分享成功");
        });
    }
}

本地存储

// 替代 PlayerPrefs 的跨平台存储
public class StorageHelper
{
    public static void SetString(string key, string value)
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        StarkSDK.API.GetStorageManager().SetStorageSync(key, value);
#else
        PlayerPrefs.SetString(key, value);
#endif
    }

    public static string GetString(string key, string defaultValue = "")
    {
#if UNITY_WEBGL && !UNITY_EDITOR
        return StarkSDK.API.GetStorageManager().GetStorageSync(key) ?? defaultValue;
#else
        return PlayerPrefs.GetString(key, defaultValue);
#endif
    }

    public static void SetInt(string key, int value)
    {
        SetString(key, value.ToString());
    }

    public static int GetInt(string key, int defaultValue = 0)
    {
        string val = GetString(key);
        return string.IsNullOrEmpty(val) ? defaultValue : int.Parse(val);
    }
}

4.9 避坑大全

🔥 坑 #1:首包体积超限

症状:构建产物 > 4MB,上传时报错。

解决

  • 启用 Wasm 分包工具
  • 所有游戏资源用 Addressables/AB 包走 CDN,不放 Resources
  • 裁剪:Strip Engine Code + Strip Assemblies
  • 纹理压缩用 ASTC,减小 Max Size
  • 移除不用的 Plugin 和脚本
🔥 坑 #2:System.Threading 报错

症状PlatformNotSupportedException: Operation is not supported on this platform.

解决

  • 搜索工程中所有 new ThreadTask.RunThreadPool,改为 Coroutine
  • 检查第三方库是否使用了线程(有些库有 WebGL 兼容模式)
  • #if !UNITY_WEBGL || UNITY_EDITOR 包裹线程代码
🔥 坑 #3:内存溢出崩溃

症状:真机运行一段时间后闪退。

解决

  • 关闭纹理的 Read/Write Enabled
  • 使用对象池替代频繁 Instantiate/Destroy
  • 不用的 Addressables 资源及时 Release
  • 减少同时存在的粒子系统数量
  • Profiler 检查 GC Alloc,消除每帧分配
🔥 坑 #4:启动黑屏/白屏

症状:导入抖音开发者工具后显示黑屏。

解决

  • 检查 game.js 入口文件是否正确
  • 检查 WASM 文件路径是否正确
  • Console 查看具体 JS 错误
  • 确认 Brotli 解压正常(某些版本有兼容问题,尝试 Gzip)
🔥 坑 #5:中文路径/资源名报错

症状:构建或加载资源时莫名其妙报错。

解决:所有文件路径、资源名、场景名只用英文和数字和下划线

🔥 坑 #6:Addressables 远程加载 404

症状:Editor 中正常,小游戏里加载资源失败。

解决

  • 确认 CDN 地址可访问(小游戏有域名白名单,需要在 game.json 中配置)
  • 确认 Build Target 为 WebGL(不是 Standalone)
  • 确认已执行 Addressables Build(Groups 窗口 → Build)
  • 确认 catalog.json 文件在 CDN 上
🔥 坑 #7:Shader 在 WebGL 中不工作

症状:物体显示为粉红色/紫色。

解决

  • 确保 Shader 兼容 WebGL 2.0(不能用 Compute Shader、几何着色器等)
  • URP Shader 基本兼容;Built-in 自定义 Shader 需要检查
  • 搜索 #pragma target 4.0 或更高,改为 #pragma target 3.0
🔥 坑 #8:加载时间过长

症状:小游戏启动要等 10 秒以上。

解决

  • 定制启动封面(TTSDK (原 StarkSDK) 设置中配置)
  • 减少首包内容
  • 使用 Brotli 最高压缩
  • Wasm 分包:先加载核心,其余延迟加载
  • CDN 资源预下载:在启动 loading 界面就开始下载后续资源
🔥 坑 #9:Input System 不兼容

症状:新 Input System 包在 WebGL 中触屏不工作。

解决

  • 使用旧版 Input API(Input.GetMouseButtonInput.GetKey
  • 或确保 Input System 包版本 ≥ 1.4(有 WebGL 触屏支持)
  • 触屏在 WebGL 中映射为鼠标事件
🔥 坑 #10:第三方插件不兼容

症状:构建时或运行时报各种奇怪错误。

解决

  • 检查插件是否有 WebGL 版本(很多插件有 WebGL Build Target 的条件编译)
  • 纯 C# 插件一般兼容
  • 使用原生 DLL(.dll/.so/.dylib)的插件完全不兼容
  • #if UNITY_WEBGL && !UNITY_EDITOR 条件编译隔离不兼容代码

4.10 上手当天必须真机跑通的最小流程

✅ Day 3 当天 Checklist

按以下清单逐项打勾,确保当天真机可跑:

#步骤验证标准
1安装 Unity 6 + WebGL 模块Unity Hub 显示 WebGL Build Support 已安装
2注册抖音开放平台,创建小游戏应用获得 AppID
3下载并导入 TTSDK (原 StarkSDK)Unity 菜单栏出现 TTSDK (原 StarkSDK)
4配置 TTSDK (原 StarkSDK)(填 AppID)Settings 面板无报错
5创建最小测试场景(一个 Cube + 旋转脚本)Unity Editor 中 Play 正常
6TTSDK (原 StarkSDK) → Build产物目录生成,无报错
7安装并打开抖音开发者工具工具启动正常
8导入构建产物到开发者工具模拟器中看到旋转的 Cube
9真机预览(扫码)手机上看到旋转的 Cube
10检查产物体积首包体积达标(推荐 ≤ 20MB)(或已配置分包)
11导入实际农场工程,重复 6~9真机上能看到游戏画面
12如有功能异常,查 Console 报错逐一修复核心玩法可运行
💡 优先级建议

第 1 天的目标不是把所有功能都跑通,而是把最小可运行的构建传到真机上。先确认工具链跑通,再逐步处理兼容性问题。具体优先级:

  1. 先跑通最小 Cube 场景(确认工具链正确)
  2. 导入农场工程,跑通 WebGL 构建(不管功能对不对,先出画面)
  3. 解决黑屏/白屏/报错
  4. 逐个功能测试:点击、种植、收获、UI
  5. 优化体积和性能
  6. 接入平台 API(登录/分享/支付)
🎉 恭喜完成全部教程!

你已经掌握了从 Cocos → Unity 迁移、Unity 编辑器和 C# 编程、3D 游戏开发基础、工程二开方法、以及完整的抖音小游戏发布管线。

接下来就是动手实践了。按照 Day 3 Checklist 一步步操作,遇到问题回到对应章节查找解决方案。

祝你发布顺利!🚀