模块 4:Unity → 抖音小游戏
完整发布管线
Day 3 ⏱ 预计阅读 6~8 小时
⚠️ 版本说明
本模块基于 2025~2026 年的抖音小游戏平台和 TTSDK (原 StarkSDK) 编写。抖音平台更新频繁,发布前请务必对照官方最新文档确认细节。
技术架构概览
4.1 环境准备与前置条件
必须安装的工具
步骤 1:安装 Unity WebGL 模块
- 打开 Unity Hub → Installs
- 找到你的 Unity 6 版本 → 点击齿轮图标 → Add Modules
- 勾选 WebGL Build Support
- 点击 Install,等待下载完成
步骤 2:注册抖音开放平台账号
- 访问 developer.open-douyin.com
- 注册/登录开发者账号
- 进入控制台 → 创建小游戏应用
- 获取 AppID(后续 TTSDK (原 StarkSDK) 配置需要)
步骤 3:下载抖音开发者工具
- 访问抖音开放平台 → 开发 → 开发工具
- 下载 抖音开发者工具(Windows/Mac)
- 安装并登录你的开发者账号
4.2 TTSDK/BGDT 集成与配置
下载 TTSDK (原 StarkSDK)
- 登录抖音开放平台控制台
- 进入你的小游戏应用 → 开发 → 资源下载
- 下载 TTSDK (原 StarkSDK) Unity Plugin(通常是一个 .unitypackage 文件)
导入到 Unity 工程
- 在 Unity 中,Assets → Import Package → Custom Package
- 选择下载的 TTSDK (原 StarkSDK) .unitypackage 文件
- 勾选所有内容 → Import
- 导入后在菜单栏会出现 TTSDK (原 StarkSDK) 菜单
配置 TTSDK (原 StarkSDK)
- TTSDK (原 StarkSDK) → Settings(或 Window → TTSDK (原 StarkSDK) → Settings)
- 填写 AppID(从抖音开放平台获取)
- 选择目标平台(抖音/TikTok)
- 配置其他选项(按默认即可开始)
📋 TTSDK (原 StarkSDK) 提供的能力
4.3 WebGL 构建设置详解
Player Settings 配置
- Edit → Project Settings → Player
- 选择 WebGL 标签页
- 按以下配置:
使用 TTSDK (原 StarkSDK) 构建
- TTSDK (原 StarkSDK) → Build(或 Window → TTSDK (原 StarkSDK) → Build)
- 选择输出目录(建议
Builds/DouyinMiniGame)
- 点击 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 抖音开发者工具使用
导入项目
- 打开抖音开发者工具
- 点击「导入项目」或「+」按钮
- 选择构建产物目录(
Builds/DouyinMiniGame)
- 填写 AppID(与 TTSDK (原 StarkSDK) 配置的一致)
- 点击「导入」
模拟器预览
- 导入后自动在模拟器中运行
- 左侧面板选择设备型号(iPhone / Android)
- 模拟器支持触摸、摇一摇、横竖屏切换
- 底部 Console 查看 JS 日志和错误
真机预览
- 点击工具栏「预览」按钮
- 生成二维码
- 用抖音 App 扫描二维码
- 在真机上运行小游戏
⚠️ 真机预览需要条件
预览需要你的抖音账号已添加为该小游戏的开发者/体验成员。在抖音开放平台控制台 → 成员管理 中添加。
上传与审核
- 点击工具栏「上传」按钮
- 填写版本号(如 1.0.0)
- 上传完成后,在抖音开放平台控制台 → 版本管理 中提交审核
- 审核通过后即可发布上线
4.5 分包方案完整做法
抖音小游戏体积限制
🚨 首包体积控制是关键
虽然首包上限已放宽到 100 MB,但用户首次加载时间才是真正的瓶颈。WebGL 方案构建产物(WASM + 框架 + 数据)通常 10~30 MB,Brotli 压缩后仍有数 MB。建议通过 Wasm 分包 + 资源按需加载将首包控制在 10~20 MB 以内,确保弱网环境下 5 秒内可交互。
分包策略
Wasm 分包工具
TTSDK (原 StarkSDK) 提供了 Wasm 分包工具,将大的 .wasm 文件拆分为多个小文件:
- TTSDK (原 StarkSDK) → Build 面板中找到 Wasm 分包选项
- 设置每个分包的最大大小(建议 3~4 MB)
- 构建后产物中 WASM 文件被拆分为多个子包
- 首个子包随首包下载,其余在需要时加载
资源按需加载(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 默认会占用较多内存,必须优化。
纹理优化
Mesh 优化
- LOD(Level of Detail):远处物体用低面数模型
- Mesh 压缩:Import Settings → 勾选 Mesh Compression
- 减少顶点数:农场场景中复杂建筑考虑烘焙为低模
- Instancing:相同材质的重复物体用 GPU Instancing
代码优化
// 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 不支持/受限功能清单
⚠️ 线程替代方案
Unity WebGL 不支持任何多线程。如果你的农场工程中使用了 Task.Run、Thread、ConcurrentQueue 等,必须改为单线程方案(协程/异步)。第三方库如果内部用了线程也会报错。
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 Thread、Task.Run、ThreadPool,改为 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.GetMouseButton、Input.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 天的目标不是把所有功能都跑通,而是把最小可运行的构建传到真机上。先确认工具链跑通,再逐步处理兼容性问题。具体优先级:
- 先跑通最小 Cube 场景(确认工具链正确)
- 导入农场工程,跑通 WebGL 构建(不管功能对不对,先出画面)
- 解决黑屏/白屏/报错
- 逐个功能测试:点击、种植、收获、UI
- 优化体积和性能
- 接入平台 API(登录/分享/支付)
🎉 恭喜完成全部教程!
你已经掌握了从 Cocos → Unity 迁移、Unity 编辑器和 C# 编程、3D 游戏开发基础、工程二开方法、以及完整的抖音小游戏发布管线。
接下来就是动手实践了。按照 Day 3 Checklist 一步步操作,遇到问题回到对应章节查找解决方案。
祝你发布顺利!🚀