Unity WebGL实战:解决数据库连接与字体显示难题

📅 2026/8/4 8:09:42
Unity WebGL实战:解决数据库连接与字体显示难题
1. 项目概述当Unity3D遇上WEBGL我们到底在解决什么如果你做过Unity3D的WEBGL项目大概率会和我一样在某个深夜对着浏览器控制台里一片红色的报错信息陷入沉思。Unity3D WEBGL发布听起来很美——无需安装点开即玩跨平台体验。但真把带点“后台交互”或“复杂UI”的项目丢进去各种水土不服就来了。其中最典型、最让人头疼的两个拦路虎就是数据库连接和字体显示。这俩问题本质上暴露了WEBGL运行环境的特殊性。它不是传统的桌面应用也不是服务端程序而是一个运行在浏览器沙箱里的“客人”。这个客人权限有限它不能直接访问本地文件系统不能随意发起网络请求尤其是跨域请求更不能像在编辑器或PC端那样用熟悉的System.Data.SqlClient去直连数据库。字体问题也一样你在Unity编辑器里用得好好的字体打包成WEBGL后可能直接变成“豆腐块”□或者直接消失因为浏览器压根没加载到对应的字体文件。所以这个“实战”要解决的不是简单的功能实现而是思维模式的转换。我们需要从“Unity怎么用”切换到“WEBGL环境下Unity能怎么用”。接下来我会结合我踩过的无数个坑把这两个问题的来龙去脉、解决思路和可直接复用的代码掰开揉碎了讲清楚。无论你是想做一个带数据存档的WEBGL小游戏还是做一个需要展示动态文本信息的3D看板这篇内容都能给你一套经过验证的解决方案。2. 核心问题一WEBGL环境下的数据库连接策略在PC端你可能习惯这样写new SqlConnection(connectionString)然后Open()执行命令。但在WEBGL里这条路直接被堵死。浏览器的安全策略同源策略、CORS和WEBGL本身的限制决定了Unity无法直接建立到远程数据库的TCP连接。2.1 为什么不能直连理解限制的本质首先得明白WEBGL构建出来的应用其网络通信能力是基于浏览器的XMLHttpRequest或Fetch API的。这意味着协议限制通常只能使用HTTP/HTTPS协议而像SQL Server、MySQL默认使用的1433、3306端口是私有数据库协议端口浏览器无法直接发起这类请求。同源策略CORS即使你的数据库服务神奇地提供了HTTP接口如果它部署的域名和你的WEBGL页面域名不同浏览器会拦截这次请求除非数据库服务端明确设置了允许你域名访问的CORS头。安全性让前端代码直接包含数据库IP、用户名和密码是极度危险的行为相当于把钥匙挂在门上。所以解决方案的核心思想是引入一个中间层。让Unity WEBGL客户端不再直接面对数据库而是与一个自己搭建的后端服务器API进行通信由这个后端服务器去安全地操作数据库。2.2 主流解决方案前后端分离架构这是目前最标准、最安全的做法。架构非常简单清晰Unity WEBGL客户端 (前端) --[HTTP/HTTPS, WebSocket]-- 后端服务器API (中间层) -- 数据库你的Unity代码里不再有任何数据库连接字符串只有向后端API发起请求的逻辑。后端选型这里灵活性很大。你可以用任何你熟悉的后端技术栈Node.js Express轻量快捷JavaScript/TypeScript全栈。Python Flask/Django开发效率高生态丰富。C# ASP.NET Core如果你团队主力是C#可以无缝衔接共享一些模型定义。Java Spring Boot适合大型、需要复杂业务逻辑的项目。通信方式通常使用RESTful API或GraphQL。对于实时性要求高的场景如多人游戏状态同步可以结合WebSocket。2.3 实战代码示例Unity中发起HTTP请求假设我们有一个简单的需求玩家在游戏结束时提交分数。后端已经提供了一个POST /api/score的接口。首先在Unity中我们使用UnityWebRequest类。这是Unity封装好的、兼容WEBGL的HTTP请求工具。using UnityEngine; using UnityEngine.Networking; using System.Collections; using System.Text; public class ScoreManager : MonoBehaviour { // 你的后端API地址 private string apiBaseUrl https://your-backend-server.com/api; public void SubmitScore(int score, string playerName) { StartCoroutine(SubmitScoreCoroutine(score, playerName)); } IEnumerator SubmitScoreCoroutine(int score, string playerName) { // 1. 构造请求数据通常为JSON ScoreData data new ScoreData { score score, playerName playerName, timestamp System.DateTime.UtcNow.ToString(o) }; string jsonData JsonUtility.ToJson(data); // 2. 创建POST请求 using (UnityWebRequest request new UnityWebRequest(apiBaseUrl /score, POST)) { byte[] bodyRaw Encoding.UTF8.GetBytes(jsonData); request.uploadHandler new UploadHandlerRaw(bodyRaw); request.downloadHandler new DownloadHandlerBuffer(); // 3. 设置请求头告诉服务器我们发送的是JSON request.SetRequestHeader(Content-Type, application/json); // 如果需要认证可以在这里添加Token等 // request.SetRequestHeader(Authorization, Bearer authToken); // 4. 发送请求并等待 yield return request.SendWebRequest(); // 5. 处理响应 if (request.result UnityWebRequest.Result.Success) { Debug.Log(Score submitted successfully: request.downloadHandler.text); // 解析返回的JSON例如获取排名信息 // var response JsonUtility.FromJsonScoreResponse(request.downloadHandler.text); } else { Debug.LogError(Failed to submit score: request.error \nResponse: request.downloadHandler?.text); // 根据HTTP状态码进行更细致的错误处理 if (request.responseCode 401) { // 未授权可能需要重新登录 } } } } // 定义上传的数据结构 [System.Serializable] private class ScoreData { public int score; public string playerName; public string timestamp; } }关键点与避坑指南必须使用协程CoroutineUnityWebRequest.SendWebRequest()是异步操作必须配合yield return在协程中调用否则会阻塞主线程。正确处理请求生命周期使用using语句包裹UnityWebRequest对象确保请求结束后相关资源被正确释放避免内存泄漏。这在WEBGL中尤为重要。错误处理要完备不要只看request.error还要检查request.result和request.responseCode。网络超时、服务器错误、数据格式错误等都需要不同的处理逻辑。注意JSON序列化Unity自带的JsonUtility对于简单结构很好用但嵌套复杂或需要处理字典时可能力不从心。可以考虑引入第三方库如Newtonsoft.Json需兼容WEBGL或者在数据结构设计上做些妥协。CORS问题如果你的Unity WEBGL页面运行在localhost或127.0.0.1而后端API在另一个端口如localhost:5000依然会遇到CORS错误。务必在后端服务器配置中正确设置CORS响应头。例如在ASP.NET Core中需要在Program.cs中添加策略。2.4 备选方案与高级话题WebSocket实时通信对于聊天、实时对战等场景UnityWebRequest就不够用了。可以使用第三方WebSocket库如NativeWebSocket它们通常提供了对WEBGL的良好支持。核心是建立长连接进行双向通信。GraphQL如果数据交互复杂需要灵活查询可以考虑让后端提供GraphQL端点。Unity端可以使用类似UnityGraphQL的客户端库来构建和发送查询请求能有效减少请求次数精确获取所需数据。静态数据与本地存储对于一些只读的、非敏感的基础数据如游戏配置、物品属性可以打包成JSON或Scriptable Object直接包含在构建资源中。对于玩家本地进度可以使用PlayerPrefsWEBGL下实际存储在浏览器的IndexedDB中或直接操作UnityEngine.Application.persistentDataPath路径下的文件同样受浏览器沙箱限制但可用于缓存。重要提示在任何情况下绝对不要尝试在Unity WEBGL代码中拼接SQL语句并发送给后端。后端API应接收结构化的、明确含义的参数如itemId,actionType由后端进行业务逻辑处理和安全的数据库操作防止SQL注入攻击。3. 核心问题二WEBGL中的字体显示崩溃与修复字体问题通常在你第一次把带有TextMeshPro或旧版UI Text的项目发布到WEBGL时爆发。症状包括文字不显示、显示为方块□、字体粗细异常、或者直接导致运行时错误。3.1 问题根源字体资源加载机制差异在PC或移动平台Unity可以“看到”并直接加载系统字体或项目内的字体文件.ttf, .otf。但在WEBGL中动态字体Dynamic Font失效Unity旧版UI Text使用的“Arial”等动态字体依赖于运行平台的系统字体。而浏览器沙箱环境无法直接枚举或访问宿主操作系统的字体库因此会回退到浏览器默认的有限字体集常常导致找不到字体。字体文件引用丢失TextMeshProTMP使用的字体是资产文件.asset。当你为TMP创建字体资产时它关联了一个或多个字体源文件.ttf。如果这个关联在构建过程中没有被正确处理或者字体源文件没有被包含在构建里WEBGL运行时就会加载失败。字体图集生成失败TMP的核心原理是为字体生成纹理图集。在WEBGL构建过程中这个生成步骤可能因为环境差异如缺少某些依赖而出错导致生成的图集是空的或损坏的。3.2 终极解决方案使用TextMeshPro并正确配置对于新项目强烈建议直接使用TextMeshProTMP它是Unity官方推荐的UI文本解决方案功能强大对WEBGL支持也更好。但需要正确配置。步骤一确保导入TMP Essentials资源包在Unity Editor中通过Window - TextMeshPro - Import TMP Essential Resources确保导入了核心资源。这个操作通常会在项目初始化时做一次。步骤二为WEBGL创建/配置TMP字体资产这是最关键的一步。你不能直接使用为编辑器环境创建的字体资产。准备字体源文件将你需要的.ttf或.otf字体文件放入项目的Resources文件夹或任意Resources子文件夹下。例如Assets/Resources/Fonts/MyFont.ttf。Resources文件夹下的资源会被Unity强制包含在构建中。创建字体资产打开Window - TextMeshPro - Font Asset Creator。在Source Font File中选择你放在Resources下的字体文件。调整字符集Character Set。对于WEBGL为了减小包体不要使用“Dynamic”。建议选择“Custom Characters”或“ASCII”。如果你需要显示中文选择“Unicode Range (Hex)”并输入常用汉字范围如0x4E00-0x9FFF或者更精确地输入你游戏中会用到的所有字符。调整其他参数如图集尺寸、字体大小点击“Generate Font Atlas”。预览无误后点击“Save”或“Save as…”将字体资产保存到项目目录不要保存在Resources里保存字体资产本身。应用字体资产在你的TMP Text组件上将新创建的字体资产拖拽到“Font Asset”字段。步骤三检查并修改TMP设置重要打开Edit - Project Settings - TextMesh Pro。确保“Default Font Asset”是你为WEBGL创建的那个字体资产。查看“Font Assets”列表确认你需要的字体资产在其中。3.3 实战代码示例运行时动态加载字体备用方案有些场景下你可能需要从服务器动态加载自定义字体比如用户自定义字体。这可以通过UnityWebRequest加载字体文件然后动态创建TMP字体资产来实现。注意此操作消耗较大需谨慎使用。using TMPro; using UnityEngine; using UnityEngine.Networking; using System.Collections; public class DynamicFontLoader : MonoBehaviour { public TMP_Text targetText; // 需要应用字体的TMP文本组件 public string fontUrl https://your-cdn.com/fonts/MyCustomFont.ttf; // 字体文件URL IEnumerator Start() { // 1. 下载字体文件 using (UnityWebRequest request UnityWebRequest.Get(fontUrl)) { request.downloadHandler new DownloadHandlerBuffer(); yield return request.SendWebRequest(); if (request.result ! UnityWebRequest.Result.Success) { Debug.LogError(Failed to download font: request.error); yield break; } // 2. 获取字体数据 byte[] fontData request.downloadHandler.data; // 3. 动态创建字体资产这是一个简化示例实际过程更复杂 // 注意在WEBGL中直接使用byte[]创建Font可能受限。 // 更可靠的做法是将字体数据保存为临时文件在Application.persistentDataPath // 然后使用TMP的FontAssetCreator API或AssetBundle来加载。 // 以下代码仅为思路演示可能需要结合AssetBundle或插件实现。 Debug.Log(Font downloaded, size: fontData.Length bytes); // ... 后续复杂的字体处理与创建TMP Font Asset的代码 ... // 由于涉及TMP内部API和平台差异此处省略具体实现。 // 一个可行的思路是将字体数据发送到后端后端生成对应的TMP字体AssetBundle再下载加载。 } } }动态字体加载的注意事项性能与兼容性运行时创建字体资产非常消耗CPU和内存尤其在移动端或性能受限的WEBGL环境可能导致卡顿甚至崩溃。应尽量避免或仅在绝对必要时使用。备用字体始终为TMP Text组件设置一个在构建时包含的、可靠的备用字体资产。这样即使动态加载失败文字也不会消失。字体授权确保你拥有动态分发该字体的合法授权。3.4 常见字体问题排查清单当你的WEBGL版本出现字体问题时请按此清单检查文字完全消失/显示方块[ ] 检查TMP字体资产是否被正确赋值给Text组件。[ ] 检查该字体资产引用的源字体文件.ttf是否在Resources文件夹内。[ ] 在Project Settings - TextMesh Pro中检查字体资产列表。[ ] 打开浏览器开发者工具F12的“网络(Network)”选项卡查看是否有加载字体文件.ttf或字体图集纹理.png?的请求失败404或跨域错误。字体模糊或边缘有锯齿[ ] 检查TMP字体资产的“Atlas Resolution”。对于WEBGL通常需要更高的分辨率如1024x1024或2048x2048来保证清晰度但这会增加包体大小和内存占用需要权衡。[ ] 检查TMP Text组件上的“Font Size”和“Auto Sizing”设置确保渲染尺寸合适。构建后字体变化/回退到默认字体[ ] 确认你为WEBGL平台专门创建并配置了字体资产而不是直接使用编辑器环境下可用的系统字体创建的资产。[ ] 清理构建缓存Build Settings-Build按钮下的Clear Build或删除Library文件夹中的相关缓存然后重新构建。使用旧版UI Text的字体问题[ ]终极建议升级到TextMeshPro。如果暂时无法升级在UI Text组件上将“Font”设置为一个已导入项目的.ttf字体文件同样需放在Resources下并将“Font Style”设置为“Normal”避免使用“Bold”或“Italic”这些样式在动态字体失效时可能无法正确合成。4. WEBGL项目构建与部署的专项优化解决了数据库和字体两大难题并不意味着项目就能顺畅运行。WEBGL平台有其独特的性能特点和限制需要在构建和部署环节做针对性优化。4.1 构建设置Player Settings关键项打开File - Build Settings选择WebGL平台点击Player Settings分辨率与呈现Resolution and PresentationDefault Canvas Width/Height设置初始画布大小。建议与你的游戏设计分辨率一致或设为0以使用HTML模板中的设置。WebGL Template选择一个合适的模板。Minimal模板最干净Default包含进度条等UI。你可以自定义模板来更好地与你的网页集成。其他设置Other SettingsColor Space对于大多数项目Gamma就够了性能更好。如果需要更精确的HDR或线性光照计算才选择Linear但这会显著增加着色器编译时间和内存占用。Auto Graphics API取消勾选并确保只保留了WebGL 2.0如果支持。移除WebGL 1.0可以减小构建大小并确保使用更现代的图形功能。Strip Engine Code务必勾选。这会移除项目未使用的Unity引擎模块代码极大减小.wasm和.js代码文件体积。Enable Exceptions设置为None或Explicitly Thrown Only。Full会在生成的代码中加入大量异常处理逻辑导致代码体积暴增和性能下降。这意味着你需要更小心地处理代码中的潜在错误避免未捕获的异常导致游戏崩溃。Data Caching勾选。这会将资源缓存到浏览器的IndexedDB中玩家第二次访问时加载速度会快很多。发布设置Publishing SettingsCompression Format选择Brotli。这是目前压缩率最高、浏览器支持良好的格式能显著减少网络传输量。确保你的Web服务器如Nginx配置了支持Brotli压缩。Decompression Fallback勾选。这会在不支持Brotli的旧浏览器上使用Gzip备用提高兼容性。4.2 资源优化与加载策略WEBGL应用的初始加载速度至关重要玩家没有耐心等待几十MB的资源下载。纹理优化使用合适的压缩格式WEBGL主要支持ASTC需设备支持、ETC2需WebGL 2.0和回退到RGBA32。在Texture Import Settings中为WebGL平台选择ASTC或ETC 2.0并设置合适的压缩质量。对于UI纹理可以考虑使用Crunch压缩。控制纹理尺寸非必要的纹理坚决缩小。一个2048x2048的纹理压缩后可能还有几MB而512x512的纹理可能只有几百KB视觉差异在很多时候并不明显。音频优化将长背景音乐设置为Streaming流式加载避免一次性载入内存。将短音效如点击、爆炸声的加载类型设为Decompress On Load并选择合适的压缩格式如Vorbis在内存和CPU解压开销间取得平衡。代码分包与异步加载使用Addressable Assets System或AssetBundle。将游戏按场景、功能模块拆分成多个包。启动时只加载核心包其他包在需要时如进入新关卡前异步加载。这能极大缩短首屏加载时间。对于Addressables构建时选择Build Script: Built-In Shader Bundle选项可以将着色器单独打包避免重复。4.3 部署服务器配置要点即使构建文件完美服务器配置不当也会导致游戏无法运行或体验极差。MIME类型确保你的Web服务器为Unity WEBGL生成的文件类型配置了正确的MIME类型。这是最常见的问题之一。.wasm-application/wasm.data-application/octet-stream或application/x-unitydata.js-application/javascript.symbols.json-application/json以Nginx为例在配置文件中添加location ~ .wasm$ { add_header Content-Type application/wasm; } location ~ .data$ { add_header Content-Type application/octet-stream; }HTTP压缩如前所述启用Brotli和Gzip压缩。在Nginx中需要加载对应的模块并进行配置。缓存策略为.data、.wasm等较大的静态资源设置较长的缓存时间如1年并在文件名中嵌入哈希值Unity构建时自动完成实现增量更新。为.html文件设置较短的缓存或不缓存。跨域问题CORS如果你的游戏资源如AssetBundle、配置文件存放在另一个域名CDN下需要在该CDN的响应头中设置Access-Control-Allow-Origin: *或你的游戏页面域名。5. 调试与问题排查实战指南WEBGL的调试比原生平台更麻烦因为你面对的是浏览器的黑盒。掌握正确的调试方法能事半功倍。5.1 浏览器开发者工具是你的主战场控制台Console查看Debug.Log的输出、JavaScript错误和警告。Unity的日志会在这里打印。注意大量频繁的Debug.Log在WEBGL中会有性能开销发布前建议使用条件编译#if !UNITY_WEBGL || UNITY_EDITOR来移除。网络Network这是排查资源加载、API请求失败问题的核心。查看所有请求的状态码200成功404未找到403禁止访问500服务器错误CORS错误等。重点关注.wasm、.data、字体文件、以及你发起的API请求是否成功加载。源代码Sources你可以看到Unity生成的JavaScript代码.js文件虽然可读性差但可以设置断点对于追踪复杂的逻辑流或崩溃点有时有奇效。查找错误堆栈中提到的文件名和行号。性能Performance 内存Memory录制一段时间内的运行时性能分析帧时间、函数调用耗时查找性能瓶颈是否是某个复杂Shader是否是某段密集的JavaScript计算。检查内存使用情况防止内存泄漏。5.2 Unity端调试技巧开发构建Development Build在Build Settings中勾选Development Build和Autoconnect Profiler。这样构建出的版本包含调试符号并且会自动连接回Unity Editor的Profiler和Console窗口你可以像在编辑器中一样进行性能分析和查看日志极其方便。自定义日志处理可以创建一个脚本重定向Application.logMessageReceived事件将日志不仅输出到控制台也通过HTTP发送到你自己的日志服务器便于收集线上用户的错误信息。public class WebGLLogger : MonoBehaviour { void OnEnable() { Application.logMessageReceived HandleLog; } void OnDisable() { Application.logMessageReceived - HandleLog; } void HandleLog(string logString, string stackTrace, LogType type) { if (type LogType.Exception || type LogType.Error) { // 将 logString 和 stackTrace 通过 UnityWebRequest 发送到你的错误收集API // StartCoroutine(SendLogToServer(logString, stackTrace)); } } }模拟延迟与弱网在浏览器开发者工具的“网络(Network)”选项卡中可以设置节流Throttling来模拟3G、4G或自定义的网络延迟和带宽测试游戏在弱网环境下的表现和资源加载逻辑是否健壮。5.3 典型错误与解决方案速查表错误现象可能原因排查步骤与解决方案游戏黑屏控制台报TypeError或WebAssembly相关错误.wasm文件MIME类型错误或加载失败1. 检查服务器.wasm文件的MIME类型是否为application/wasm。2. 检查网络面板确认.wasm文件是否成功下载状态码200。3. 清理浏览器缓存并硬刷新CtrlF5。游戏卡在加载进度条或进度条走完后白屏.data文件或其他资源文件加载失败、缺失或跨域1. 检查网络面板看是否有.data、.js或其他资源请求报错404, 403, CORS。2. 确认所有构建生成的文件都已完整上传到服务器。3. 检查服务器CORS配置。字体显示为方块□TMP字体资产未正确包含或加载1. 确认字体源文件(.ttf)在Resources文件夹内。2. 检查TMP Text组件引用的字体资产是否正确。3. 重新为WEBGL平台生成字体资产。点击按钮无反应或部分UI功能失效浏览器事件系统与Unity交互问题1. 检查UI元素的Raycast Target是否被意外禁用。2. 确认没有其他全屏透明的UI面板挡住了点击。3. 对于输入框TMP Input FieldWEBGL下可能需要额外处理焦点事件有时在移动端虚拟键盘弹出时会有异常。游戏运行缓慢帧率低性能瓶颈1. 使用开发构建连接Profiler分析CPU/GPU耗时。2. 检查Draw Call数量合并静态UI和场景物体。3. 检查是否有每帧执行的昂贵操作如不必要的FindObject、Instantiate/Destroy。4. 降低纹理分辨率、简化Shader。UnityWebRequest网络请求失败CORS问题、服务器错误、网络超时1. 查看网络面板中该请求的详细状态码和响应头。2. 确认后端API服务器已正确配置CORS。3. 在Unity代码中增加超时处理和更详细的错误日志。6. 从理论到实践一个简单的积分榜案例让我们把上面所有的知识点串联起来实现一个完整的WEBGL小功能从服务器获取并显示玩家积分榜。架构前端Unity WEBGL应用使用TMP显示文字使用UnityWebRequest与后端通信。后端一个简单的Node.js Express服务器提供GET /api/leaderboardAPI从数据库如SQLite或MongoDB查询数据。数据库SQLite用于演示。步骤简述后端搭建Node.js示例// server.js const express require(express); const sqlite3 require(sqlite3).verbose(); const cors require(cors); const app express(); const port 3000; app.use(cors()); // 启用CORS允许所有域名访问生产环境应指定具体域名 app.use(express.json()); let db new sqlite3.Database(./scores.db); // 创建表如果不存在 db.run(CREATE TABLE IF NOT EXISTS leaderboard ( id INTEGER PRIMARY KEY AUTOINCREMENT, playerName TEXT NOT NULL, score INTEGER NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )); // 获取积分榜API app.get(/api/leaderboard, (req, res) { const limit parseInt(req.query.limit) || 10; db.all(SELECT playerName, score FROM leaderboard ORDER BY score DESC LIMIT ?, [limit], (err, rows) { if (err) { res.status(500).json({ error: err.message }); return; } res.json(rows); // 返回JSON数组如 [{playerName:Alice,score:1000}, ...] }); }); // 提交分数API供其他部分调用 app.post(/api/score, (req, res) { const { playerName, score } req.body; db.run(INSERT INTO leaderboard (playerName, score) VALUES (?, ?), [playerName, score], function(err) { if (err) { res.status(500).json({ error: err.message }); return; } res.json({ success: true, id: this.lastID }); }); }); app.listen(port, () { console.log(Server running at http://localhost:${port}); });Unity前端实现创建一个UI Canvas包含一个ScrollView里面用Content存放积分榜条目。创建一个LeaderboardEntry.prefab预制体包含两个TMP Text组件排名、玩家名、分数。编写LeaderboardManager.cs脚本负责调用API、解析数据、动态生成条目。// LeaderboardManager.cs (简化版) using UnityEngine; using UnityEngine.Networking; using TMPro; using System.Collections.Generic; public class LeaderboardManager : MonoBehaviour { public string apiUrl http://localhost:3000/api/leaderboard; public GameObject entryPrefab; public Transform contentParent; void Start() { StartCoroutine(FetchLeaderboard()); } IEnumerator FetchLeaderboard() { using (UnityWebRequest request UnityWebRequest.Get(apiUrl)) { yield return request.SendWebRequest(); if (request.result UnityWebRequest.Result.Success) { string jsonResponse request.downloadHandler.text; // 使用简单的JSON解析例如JsonUtility或第三方库 // 假设返回格式 [{playerName:Tom,score:1500}, ...] LeaderboardDataList dataList JsonUtility.FromJsonLeaderboardDataList({\entries\: jsonResponse }); PopulateLeaderboard(dataList.entries); } else { Debug.LogError(Leaderboard fetch failed: request.error); } } } void PopulateLeaderboard(ListLeaderboardData entries) { // 清空现有条目 foreach (Transform child in contentParent) { Destroy(child.gameObject); } for (int i 0; i entries.Count; i) { GameObject entryObj Instantiate(entryPrefab, contentParent); LeaderboardEntryUI ui entryObj.GetComponentLeaderboardEntryUI(); if (ui ! null) { ui.SetData(i 1, entries[i].playerName, entries[i].score); } } } [System.Serializable] private class LeaderboardDataList { public ListLeaderboardData entries; } [System.Serializable] private class LeaderboardData { public string playerName; public int score; } }字体处理确保LeaderboardEntry.prefab中使用的TMP Text组件其字体资产是按照3.2节所述方法为WEBGL专门创建并包含在构建中的。构建与部署按照第4章的优化建议进行构建。将构建出的Build文件夹内容上传到你的Web服务器如Nginx、Apache。确保Node.js后端服务器也在运行且网络可达。通过这个案例你将完整走通从后端数据存储、API提供到前端Unity WEBGL请求数据、处理响应、使用TMP安全显示文字的全流程。过程中遇到的任何CORS、字体缺失、资源加载问题都可以依据前面的章节进行排查。这不仅仅是解决两个孤立的问题更是掌握了一套应对WEBGL平台特殊性的完整开发方法论。