Unity WebGL实战:解决数据库连接与字体显示难题
2026/8/4 8:09:39 网站建设 项目流程

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构建出来的应用,其网络通信能力是基于浏览器的XMLHttpRequestFetch API的。这意味着:

  1. 协议限制:通常只能使用HTTP/HTTPS协议,而像SQL Server、MySQL默认使用的1433、3306端口是私有数据库协议端口,浏览器无法直接发起这类请求。
  2. 同源策略(CORS):即使你的数据库服务神奇地提供了HTTP接口,如果它部署的域名和你的WEBGL页面域名不同,浏览器会拦截这次请求,除非数据库服务端明确设置了允许你域名访问的CORS头。
  3. 安全性:让前端代码直接包含数据库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.FromJson<ScoreResponse>(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; } }

关键点与避坑指南

  1. 必须使用协程(Coroutine)UnityWebRequest.SendWebRequest()是异步操作,必须配合yield return在协程中调用,否则会阻塞主线程。
  2. 正确处理请求生命周期:使用using语句包裹UnityWebRequest对象,确保请求结束后相关资源被正确释放,避免内存泄漏。这在WEBGL中尤为重要。
  3. 错误处理要完备:不要只看request.error,还要检查request.resultrequest.responseCode。网络超时、服务器错误、数据格式错误等都需要不同的处理逻辑。
  4. 注意JSON序列化:Unity自带的JsonUtility对于简单结构很好用,但嵌套复杂或需要处理字典时可能力不从心。可以考虑引入第三方库如Newtonsoft.Json(需兼容WEBGL),或者在数据结构设计上做些妥协。
  5. CORS问题:如果你的Unity WEBGL页面运行在localhost127.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,直接包含在构建资源中。对于玩家本地进度,可以使用PlayerPrefs(WEBGL下实际存储在浏览器的IndexedDB中)或直接操作UnityEngine.Application.persistentDataPath路径下的文件(同样受浏览器沙箱限制,但可用于缓存)。

重要提示:在任何情况下,绝对不要尝试在Unity WEBGL代码中拼接SQL语句并发送给后端。后端API应接收结构化的、明确含义的参数(如itemId,actionType),由后端进行业务逻辑处理和安全的数据库操作,防止SQL注入攻击。

3. 核心问题二:WEBGL中的字体显示崩溃与修复

字体问题通常在你第一次把带有TextMeshPro或旧版UI Text的项目发布到WEBGL时爆发。症状包括:文字不显示、显示为方块(□)、字体粗细异常、或者直接导致运行时错误。

3.1 问题根源:字体资源加载机制差异

在PC或移动平台,Unity可以“看到”并直接加载系统字体或项目内的字体文件(.ttf, .otf)。但在WEBGL中:

  1. 动态字体(Dynamic Font)失效:Unity旧版UI Text使用的“Arial”等动态字体,依赖于运行平台的系统字体。而浏览器沙箱环境无法直接枚举或访问宿主操作系统的字体库,因此会回退到浏览器默认的有限字体集,常常导致找不到字体。
  2. 字体文件引用丢失:TextMeshPro(TMP)使用的字体是资产文件(.asset)。当你为TMP创建字体资产时,它关联了一个或多个字体源文件(.ttf)。如果这个关联在构建过程中没有被正确处理,或者字体源文件没有被包含在构建里,WEBGL运行时就会加载失败。
  3. 字体图集生成失败:TMP的核心原理是为字体生成纹理图集。在WEBGL构建过程中,这个生成步骤可能因为环境差异(如缺少某些依赖)而出错,导致生成的图集是空的或损坏的。

3.2 终极解决方案:使用TextMeshPro并正确配置

对于新项目,强烈建议直接使用TextMeshPro(TMP),它是Unity官方推荐的UI文本解决方案,功能强大,对WEBGL支持也更好。但需要正确配置。

步骤一:确保导入TMP Essentials资源包在Unity Editor中,通过Window -> TextMeshPro -> Import TMP Essential Resources,确保导入了核心资源。这个操作通常会在项目初始化时做一次。

步骤二:为WEBGL创建/配置TMP字体资产这是最关键的一步。你不能直接使用为编辑器环境创建的字体资产。

  1. 准备字体源文件:将你需要的.ttf.otf字体文件放入项目的Resources文件夹或任意Resources子文件夹下。例如Assets/Resources/Fonts/MyFont.ttfResources文件夹下的资源会被Unity强制包含在构建中。
  2. 创建字体资产
    • 打开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里,保存字体资产本身)。
  3. 应用字体资产:在你的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版本出现字体问题时,请按此清单检查:

  1. 文字完全消失/显示方块

    • [ ] 检查TMP字体资产是否被正确赋值给Text组件。
    • [ ] 检查该字体资产引用的源字体文件(.ttf)是否在Resources文件夹内。
    • [ ] 在Project Settings -> TextMesh Pro中检查字体资产列表。
    • [ ] 打开浏览器开发者工具(F12)的“网络(Network)”选项卡,查看是否有加载字体文件(.ttf)或字体图集纹理(.png?)的请求失败(404或跨域错误)。
  2. 字体模糊或边缘有锯齿

    • [ ] 检查TMP字体资产的“Atlas Resolution”。对于WEBGL,通常需要更高的分辨率(如1024x1024或2048x2048)来保证清晰度,但这会增加包体大小和内存占用,需要权衡。
    • [ ] 检查TMP Text组件上的“Font Size”和“Auto Sizing”设置,确保渲染尺寸合适。
  3. 构建后字体变化/回退到默认字体

    • [ ] 确认你为WEBGL平台专门创建并配置了字体资产,而不是直接使用编辑器环境下可用的系统字体创建的资产。
    • [ ] 清理构建缓存(Build Settings->Build按钮下的Clear Build或删除Library文件夹中的相关缓存),然后重新构建。
  4. 使用旧版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

  1. 分辨率与呈现(Resolution and Presentation)

    • Default Canvas Width/Height:设置初始画布大小。建议与你的游戏设计分辨率一致,或设为0以使用HTML模板中的设置。
    • WebGL Template:选择一个合适的模板。Minimal模板最干净,Default包含进度条等UI。你可以自定义模板来更好地与你的网页集成。
  2. 其他设置(Other Settings)

    • Color Space:对于大多数项目,Gamma就够了,性能更好。如果需要更精确的HDR或线性光照计算,才选择Linear,但这会显著增加着色器编译时间和内存占用。
    • Auto Graphics API:取消勾选,并确保只保留了WebGL 2.0(如果支持)。移除WebGL 1.0可以减小构建大小,并确保使用更现代的图形功能。
    • Strip Engine Code务必勾选。这会移除项目未使用的Unity引擎模块代码,极大减小.wasm.js代码文件体积。
    • Enable Exceptions:设置为NoneExplicitly Thrown OnlyFull会在生成的代码中加入大量异常处理逻辑,导致代码体积暴增和性能下降。这意味着你需要更小心地处理代码中的潜在错误,避免未捕获的异常导致游戏崩溃。
    • Data Caching:勾选。这会将资源缓存到浏览器的IndexedDB中,玩家第二次访问时加载速度会快很多。
  3. 发布设置(Publishing Settings)

    • Compression Format:选择Brotli。这是目前压缩率最高、浏览器支持良好的格式,能显著减少网络传输量。确保你的Web服务器(如Nginx)配置了支持Brotli压缩。
    • Decompression Fallback:勾选。这会在不支持Brotli的旧浏览器上使用Gzip备用,提高兼容性。

4.2 资源优化与加载策略

WEBGL应用的初始加载速度至关重要,玩家没有耐心等待几十MB的资源下载。

  1. 纹理优化

    • 使用合适的压缩格式:WEBGL主要支持ASTC(需设备支持)、ETC2(需WebGL 2.0)和回退到RGBA32。在Texture Import Settings中,为WebGL平台选择ASTCETC 2.0,并设置合适的压缩质量。对于UI纹理,可以考虑使用Crunch压缩。
    • 控制纹理尺寸:非必要的纹理坚决缩小。一个2048x2048的纹理压缩后可能还有几MB,而512x512的纹理可能只有几百KB,视觉差异在很多时候并不明显。
  2. 音频优化

    • 将长背景音乐设置为Streaming(流式加载),避免一次性载入内存。
    • 将短音效(如点击、爆炸声)的加载类型设为Decompress On Load,并选择合适的压缩格式(如Vorbis),在内存和CPU解压开销间取得平衡。
  3. 代码分包与异步加载

    • 使用Addressable Assets SystemAssetBundle。将游戏按场景、功能模块拆分成多个包。启动时只加载核心包,其他包在需要时(如进入新关卡前)异步加载。这能极大缩短首屏加载时间。
    • 对于Addressables,构建时选择Build Script: Built-In Shader Bundle选项,可以将着色器单独打包,避免重复。

4.3 部署服务器配置要点

即使构建文件完美,服务器配置不当也会导致游戏无法运行或体验极差。

  1. MIME类型:确保你的Web服务器为Unity WEBGL生成的文件类型配置了正确的MIME类型。这是最常见的问题之一。
    • .wasm->application/wasm
    • .data->application/octet-streamapplication/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; }
  2. HTTP压缩:如前所述,启用BrotliGzip压缩。在Nginx中需要加载对应的模块并进行配置。
  3. 缓存策略:为.data.wasm等较大的静态资源设置较长的缓存时间(如1年),并在文件名中嵌入哈希值(Unity构建时自动完成),实现增量更新。为.html文件设置较短的缓存或不缓存。
  4. 跨域问题(CORS):如果你的游戏资源(如AssetBundle、配置文件)存放在另一个域名(CDN)下,需要在该CDN的响应头中设置Access-Control-Allow-Origin: *或你的游戏页面域名。

5. 调试与问题排查实战指南

WEBGL的调试比原生平台更麻烦,因为你面对的是浏览器的黑盒。掌握正确的调试方法能事半功倍。

5.1 浏览器开发者工具是你的主战场

  1. 控制台(Console):查看Debug.Log的输出、JavaScript错误和警告。Unity的日志会在这里打印。注意:大量频繁的Debug.Log在WEBGL中会有性能开销,发布前建议使用条件编译#if !UNITY_WEBGL || UNITY_EDITOR来移除。
  2. 网络(Network):这是排查资源加载、API请求失败问题的核心。查看所有请求的状态码(200成功,404未找到,403禁止访问,500服务器错误,CORS错误等)。重点关注.wasm.data、字体文件、以及你发起的API请求是否成功加载。
  3. 源代码(Sources):你可以看到Unity生成的JavaScript代码(.js文件),虽然可读性差,但可以设置断点,对于追踪复杂的逻辑流或崩溃点有时有奇效。查找错误堆栈中提到的文件名和行号。
  4. 性能(Performance) & 内存(Memory):录制一段时间内的运行时性能,分析帧时间、函数调用耗时,查找性能瓶颈(是否是某个复杂Shader?是否是某段密集的JavaScript计算?)。检查内存使用情况,防止内存泄漏。

5.2 Unity端调试技巧

  1. 开发构建(Development Build):在Build Settings中勾选Development BuildAutoconnect Profiler。这样构建出的版本包含调试符号,并且会自动连接回Unity Editor的Profiler和Console窗口,你可以像在编辑器中一样进行性能分析和查看日志,极其方便。
  2. 自定义日志处理:可以创建一个脚本,重定向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)); } } }
  3. 模拟延迟与弱网:在浏览器开发者工具的“网络(Network)”选项卡中,可以设置节流(Throttling)来模拟3G、4G或自定义的网络延迟和带宽,测试游戏在弱网环境下的表现和资源加载逻辑是否健壮。

5.3 典型错误与解决方案速查表

错误现象可能原因排查步骤与解决方案
游戏黑屏,控制台报TypeErrorWebAssembly相关错误.wasm文件MIME类型错误或加载失败1. 检查服务器.wasm文件的MIME类型是否为application/wasm
2. 检查网络面板,确认.wasm文件是否成功下载(状态码200)。
3. 清理浏览器缓存并硬刷新(Ctrl+F5)。
游戏卡在加载进度条,或进度条走完后白屏.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 Field),WEBGL下可能需要额外处理焦点事件,有时在移动端虚拟键盘弹出时会有异常。
游戏运行缓慢,帧率低性能瓶颈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(用于演示)。

步骤简述

  1. 后端搭建(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}`); });
  2. 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.FromJson<LeaderboardDataList>("{\"entries\":" + jsonResponse + "}"); PopulateLeaderboard(dataList.entries); } else { Debug.LogError("Leaderboard fetch failed: " + request.error); } } } void PopulateLeaderboard(List<LeaderboardData> 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.GetComponent<LeaderboardEntryUI>(); if (ui != null) { ui.SetData(i + 1, entries[i].playerName, entries[i].score); } } } [System.Serializable] private class LeaderboardDataList { public List<LeaderboardData> entries; } [System.Serializable] private class LeaderboardData { public string playerName; public int score; } }
  3. 字体处理:确保LeaderboardEntry.prefab中使用的TMP Text组件,其字体资产是按照3.2节所述方法为WEBGL专门创建并包含在构建中的。

  4. 构建与部署:按照第4章的优化建议进行构建。将构建出的Build文件夹内容上传到你的Web服务器(如Nginx、Apache)。确保Node.js后端服务器也在运行且网络可达。

通过这个案例,你将完整走通从后端数据存储、API提供,到前端Unity WEBGL请求数据、处理响应、使用TMP安全显示文字的全流程。过程中遇到的任何CORS、字体缺失、资源加载问题,都可以依据前面的章节进行排查。这不仅仅是解决两个孤立的问题,更是掌握了一套应对WEBGL平台特殊性的完整开发方法论。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询