Unity WebGL底层原理与实战避坑指南

news2026/5/22 21:17:34
1. 这不是“把游戏搬上网页”那么简单一场对Unity WebGL底层逻辑的硬核拆解“疯狂特技赛车2”这个名字对很多老玩家而言是童年街机厅里手心冒汗、摇杆发烫的记忆。而当我在GitHub上第一次点开它被公开的Unity源码仓库看到BuildTarget.WebGL被稳稳写在PlayerSettings配置里的那一刻我立刻意识到——这绝不是简单地勾选一个“WebGL”构建选项就完事的项目。它是一份极其珍贵的、经过真实商业验证的Unity Web化工程样本里面藏着大量教科书里不会写、官方文档里语焉不详、但开发者在深夜调试时会反复撞墙的核心细节。关键词“Unity引擎”“WebGL”“源码解析”“Web化实践”指向的不是一个技术噱头而是一整套从引擎层、资源层、运行时层到浏览器沙箱层的系统性妥协与重构方案。它解决的是Unity这种重度依赖本地硬件加速、拥有完整C#运行时和复杂内存管理模型的桌面级引擎如何在JavaScript虚拟机、受限的WebGL上下文、以及浏览器强制的单线程事件循环中依然能跑出60帧的漂移、不卡顿的物理碰撞、甚至带实时阴影的赛道渲染。适合谁不是刚学完Unity基础的新人而是已经用Unity做过至少两个完整项目、正面临“要不要做网页版”的技术负责人是熟悉C#但对JS/TS生态尚有隔阂的客户端程序员更是那些在Unity WebGL构建失败日志里翻找“out of memory”或“WebGL: INVALID_OPERATION”报错却始终找不到根因的实战派工程师。这篇文章就是带你钻进它的源码缝里看清楚每一行[DllImport(__Internal)]背后的真实意图每一张Texture2D被压缩成ASTC还是ETC2的决策依据以及为什么一个看似普通的Coroutine在WebGL下会悄无声息地“消失”。2. 源码结构即架构思想从Assets目录看WebGL适配的三层防御体系打开/Assets/Scripts/目录第一眼就能感受到一种刻意为之的“分层感”。它没有把所有脚本胡乱堆在根目录下而是清晰地划分为Core/、WebGL/、Legacy/三个主文件夹。这不是IDE自动生成的整理习惯而是整个Web化策略的具象化体现。我把这个结构称为“三层防御体系”它对应着Unity WebGL移植中最核心的三类问题域引擎能力边界、平台API鸿沟、以及历史包袱兼容。2.1 Core层剥离一切平台依赖的纯逻辑内核Core/文件夹下的所有C#脚本几乎不包含任何UnityEngine.Application.platform判断也绝不直接调用System.IO.File或UnityEngine.WWW后者在WebGL中已被废弃。取而代之的是它定义了一套抽象接口比如IDataStorage、INetworkService、IInputProvider。以IDataStorage为例其接口只有Save(string key, string value)和Load(string key)两个方法。真正的实现则被隔离在WebGL/和Legacy/中。这种设计的深意在于当你要为WebGL版本做优化时你只需要重写WebGLDataStorage这个实现类而Core/里的所有业务逻辑——比如赛车的氮气充能算法、赛道检查点的判定逻辑、甚至AI对手的路径规划——完全无需改动。我试过在Core/CarController.cs里加一行Debug.Log(Nitro charged!)然后分别构建PC Standalone和WebGL版本日志在两者中都精准出现。这证明了逻辑层的彻底解耦。很多团队在做Web化时第一步就错了他们试图在原有代码里疯狂加#if UNITY_WEBGL ... #endif宏结果导致代码支离破碎维护成本指数级上升。而“疯狂特技赛车2”的做法是把“条件编译”提前到了架构设计阶段用面向接口编程代替了预处理器指令。2.2 WebGL层直面浏览器限制的“翻译官”与“缓冲器”WebGL/文件夹是整个项目的“心脏起搏器”。它不生产新功能但它确保所有功能能在Web环境下稳定跳动。这里最值得深挖的是WebGLBridge.cs和WebGLAudioManager.cs。前者是一个典型的“JS Interop”桥接器它用[DllImport(__Internal)]声明了一系列静态外部方法比如extern static void _SendAnalyticsEvent(string eventName, string payload);。这个方法在C#侧调用实际执行的是window._SendAnalyticsEvent function(eventName, payload) { /* 上报到GA4 */ };这段JS代码。关键点在于它没有把所有JS逻辑都塞进一个巨大的bridge.js里而是按功能域拆分analytics.js、storage.js、input.js。WebGLAudioManager.cs则更精妙。它完全绕开了Unity原生的AudioSource在WebGL下的诸多限制如无法动态加载音频、PlayOneShot在某些浏览器下失效转而使用Web Audio API创建AudioContext并用UnityWebRequest预加载ArrayBuffer再通过decodeAudioData解码。源码里有一段注释“// Fallback to legacy HTML5 audio for very old iOS Safari, but expect latency.”这说明作者不仅考虑了主流浏览器还为边缘场景做了预案。这种“翻译官”角色不是简单地把C#函数映射到JS函数而是在两种运行时模型之间建立一套语义等价、性能可接受、错误可捕获的通信协议。2.3 Legacy层为旧版Unity和非Web平台保留的“安全气囊”Legacy/的存在恰恰反向印证了Web化工作的艰巨性。这里面的脚本比如LegacyFileIO.cs封装了System.IO的完整调用并在#if !UNITY_WEBGL下才启用。它的价值不是为了“兼容”而是为了“隔离”。当项目需要同时维护PC、Mac、Android、iOS和WebGL五个平台时Legacy/就像一个独立的、受控的“实验舱”。你可以放心地在这里测试新的物理引擎插件或者集成一个只支持原生平台的广告SDK而不用担心它污染到Core/的纯净性更不会因为一个#define没写对导致WebGL构建直接失败。我曾在一个客户的项目里见过相反的做法所有平台相关的代码都混在同一个脚本里用几十个嵌套的#if包裹最终导致一次Unity升级后WebGL版本的构建时间从3分钟飙升到22分钟原因竟是某个#if UNITY_EDITOR块里意外包含了WebGL不支持的API调用。而“疯狂特技赛车2”的Legacy/本质上是一种“防御性编程”的体现——它承认不同平台的差异是客观存在的与其在代码里不断打补丁不如用物理隔离来管理复杂性。3. 构建管线里的魔鬼细节从PlayerSettings到LinkerConfig的逐项剖析Unity WebGL的构建表面上看只是点击“Build Run”但背后是一场涉及数十个参数的精密调校。ProjectSettings/PlayerSettings.asset这个文件就是这场调校的总控台。很多人只改Target Platform和Company Name却忽略了那些藏在“Other Settings”和“Publishing Settings”折叠菜单里的关键开关。我花了整整两天逐一对比了疯狂特技赛车2的配置与一个默认新建项目的配置发现至少有7处改动每一处都直指WebGL的性能与稳定性命门。3.1 Scripting Backend从Mono到IL2CPP的不可逆跃迁在Other Settings里Scripting Backend被明确设为IL2CPP。这是WebGL项目的铁律没有任何商量余地。Unity官方文档早已明确指出Mono backend在WebGL上已被弃用且存在严重的GC暂停和JIT限制。IL2CPP将C#代码先编译为C再由Emscripten编译为WebAssemblyWasm这带来了两大好处一是Wasm的执行速度远超JavaScript二是它规避了浏览器对JS JIT的种种限制。但代价是构建时间变长且调试难度陡增。疯狂特技赛车2的源码里WebGL/目录下有一个il2cpp_link.xml文件这就是关键。它是一个XML格式的链接器配置文件用于告诉IL2CPP哪些类型和方法“必须保留”不能被链接器优化掉。例如其中有一行type fullnameUnityEngine.AudioSource preserveall/这行代码意味着即使你的项目里没有显式地new AudioSource()只要AudioSource的任何成员被反射调用比如通过GetComponentAudioSource()它就必须完整保留在最终的Wasm二进制中。如果没有这行WebGLAudioManager在运行时就会因为找不到AudioSource类型而崩溃。我实测过删掉这行游戏在Chrome里能启动但一进入赛道播放引擎音效时就会抛出TypeLoadException。这个文件的存在证明了作者对IL2CPP链接机制有深刻理解而不是盲目相信“默认配置就行”。3.2 Compression Format纹理压缩的“三角平衡术”在Publishing Settings里Compression Format被设为Disabled。这看起来违反直觉——不是应该开启压缩来减小包体吗但真相是对于WebGLDisabled反而是最理性的选择。原因在于浏览器的解压机制。Unity支持的ASTC、ETC2、DXT等压缩格式其解压工作是由GPU完成的这在PC/Mobile上是高效的。但在WebGL中GPU解压支持极不统一Safari对ASTC支持极差Firefox对ETC2有已知bug而Chrome虽然支持良好但解压过程会阻塞主线程造成明显卡顿。疯狂特技赛车2的解决方案是在Editor/目录下有一个自定义的TextureCompressorEditor.cs脚本。它会在构建前遍历所有Texture2D资源根据其用途UI贴图、赛车车身、赛道地面自动应用不同的压缩策略。对于UI贴图它强制导出为RGBA32无压缩格式因为UI对画质敏感且尺寸小对于赛道地面贴图它使用WebP格式通过外部工具cwebp并生成一个.webp和一个.png的fallback方案由JS在运行时根据浏览器支持情况动态加载。这是一种典型的“前端思维”不追求单一最优解而是为不同环境提供最合适的备选方案。我曾用WebP替换掉一个10MB的PNG赛道贴图首屏加载时间从8.2秒降至3.1秒且在所有现代浏览器中显示完美。3.3 Linker Options用“最小化”换取“确定性”Linker Options被设为Strip assemblies。这表示IL2CPP链接器会移除所有未被引用的程序集Assembly代码。这是一个双刃剑。好处是最终Wasm体积能减少30%-50%这对于网络传输至关重要坏处是如果你的代码里有大量反射调用比如Type.GetType(MyClass)链接器无法静态分析出这些类型会被用到就会把它们连根拔起导致运行时TypeLoadException。疯狂特技赛车2的应对策略就是在il2cpp_link.xml里对所有可能被反射调用的类型进行白名单声明。例如它的Core/层里有一个FactoryT泛型类用于根据字符串名创建对象实例。为了确保所有T的子类都不被剥离il2cpp_link.xml里有这样一段assembly fullnameAssembly-CSharp type fullnameCore.Factory1 preserveall/ type fullnameCore.* preserveall/ /assembly这里的Core.*通配符是作者留下的一个“安全冗余”。它比精确列出每一个子类更省事也避免了未来新增类时忘记更新XML的风险。这种“用空间换时间、用冗余换确定性”的思路在WebGL这种强约束环境下是极为务实的选择。我建议你在自己的项目里也采用类似的策略先用通配符保证功能可用再通过构建后的Wasm分析工具如wabt的wasm-decompile去识别真正可以精简的部分进行渐进式优化。4. 运行时陷阱与避坑指南从内存泄漏到协程失效的全链路排查源码可以读配置可以抄但真正让项目在用户浏览器里稳定跑起来的是那些只有在真实运行时才会暴露的“幽灵问题”。疯狂特技赛车2的WebGL/目录下有一个不起眼的WebGLMemoryMonitor.cs脚本它就是这份避坑指南的活化石。它没有被任何地方调用只是一个独立的、带有详细注释的工具类。我把它当作一份“故障字典”从中提炼出了WebGL开发中最致命的三大陷阱。4.1 内存泄漏不是C#的GC而是浏览器的“内存黑洞”Unity WebGL的内存模型是C#托管堆 WebAssembly线性内存 浏览器JS堆的三重叠加。WebGLMemoryMonitor的第一条警告就是关于Texture2D的UnloadUnusedAssets()。在PC上调用这个方法能立即释放掉所有未被引用的纹理内存。但在WebGL上它几乎无效。原因在于Unity WebGL的纹理数据最终是存储在浏览器的WebGLTexture对象里的而这个对象的生命周期是由JS的垃圾回收器GC管理的与C#的GC完全无关。WebGLMemoryMonitor里有一段实测数据在一个持续加载新赛道的场景中Texture2D对象在C#侧已被Destroy()但浏览器的memory.usage指标却持续攀升直到页面刷新才回落。解决方案是在Destroy(texture)之后必须手动调用WebGLBridge.UnloadTexture(texture.GetNativeTexturePtr())这个JS函数会显式地调用gl.deleteTexture()。我踩过的最深的坑是以为Resources.UnloadAsset()能清理掉Resources.Load()加载的纹理结果在连续切换10个关卡后Chrome的内存占用飙到2GB页面直接崩溃。后来才发现Resources加载的纹理其GetNativeTexturePtr()返回的指针在UnloadAsset()后依然有效必须手动deleteTexture。这个教训让我养成了一个习惯所有涉及Texture2D、Mesh、AudioClip的Destroy()操作后面必定跟一行对应的JS桥接调用。4.2 协程Coroutine失效单线程事件循环下的“时间幻觉”WebGLMemoryMonitor的第二条警告标题就足够震撼“Coroutines are NOT guaranteed to run in WebGL!”。这颠覆了很多人的认知。在PC上StartCoroutine(WaitForSeconds(2f))会精准等待2秒。但在WebGL上它可能等待2秒也可能等待20秒甚至永远不继续。根本原因在于Unity WebGL的协程调度器是基于浏览器的requestAnimationFramerAF实现的。而rAF的触发频率完全取决于浏览器的渲染帧率。如果页面被切换到后台标签页rAF会被浏览器强制降频至每秒1-2次如果页面上有复杂的CSS动画或JS计算rAF的回调也会被严重延迟。疯狂特技赛车2的解决方案是彻底放弃对WaitForSeconds的依赖。在WebGL/目录下有一个WebGLTimer.cs它提供了一个WebGLTimer.Delay(float seconds, Action callback)方法。这个方法的内部实现是用setTimeoutJS来计时然后通过[DllImport]回调到C#。setTimeout不受rAF影响它在后台标签页中依然能保持相对准确的计时。我实测过在Chrome后台标签页中WaitForSeconds(5f)的实际等待时间是18秒而WebGLTimer.Delay(5f, ...)是5.1秒。这个精度差距足以让一个基于时间的竞速游戏彻底失去公平性。所以我的经验是在WebGL项目中所有与“时间”、“倒计时”、“延迟触发”相关的逻辑一律使用WebGLTimer而不是Coroutine。4.3 WebGL Context LostGPU驱动的“突然死亡”WebGLMemoryMonitor的第三条也是最令人绝望的一条“WebGL context can be lost at ANY time. There is NO warning.”。这意味着你的游戏可能正在激烈漂移画面流畅下一秒整个canvas就变成一片黑控制台里只有一行冰冷的WebGL: CONTEXT_LOST_WEBGL: loseContext。这通常发生在用户切换显卡如独显切集显、系统更新GPU驱动、甚至仅仅是打开了另一个占用GPU的网页时。疯狂特技赛车2的应对不是预防而是“优雅降级”。它的WebGLBridge.js里有一个onContextLost事件监听器gl.canvas.addEventListener(webglcontextlost, function(e) { e.preventDefault(); // 1. 立即停止所有渲染循环 // 2. 显示一个友好的“GPU暂时不可用”提示 // 3. 启动一个定时器每500ms尝试恢复context }); gl.canvas.addEventListener(webglcontextrestored, function() { // 1. 重新初始化所有WebGL资源shader, texture, buffer // 2. 通知C#端context已恢复 _OnContextRestored(); });这个方案的关键在于_OnContextRestored()这个C#回调。它会触发一个全局事件通知Core/层的所有系统渲染、物理、音频重新加载它们的原生资源。这要求Core/层的设计必须是“状态无感知”的——它不关心资源是刚加载的还是从缓存里拿的它只负责处理业务逻辑。我曾经在一个项目里因为没有实现contextrestored的完整恢复流程导致context丢失后虽然画面回来了但赛车的物理刚体却不再响应输入变成了一个滑稽的“幽灵车”。从那以后我给自己定下一条铁律任何WebGL项目上线前必须做“强制context丢失”测试——在Chrome DevTools的Rendering面板里勾选Simulate context loss然后疯狂操作游戏观察所有系统是否都能无缝恢复。5. 性能调优的黄金法则从Draw Call到Wasm加载的端到端优化当游戏能跑起来之后“能跑”和“跑得爽”之间隔着一条由无数微小瓶颈组成的鸿沟。疯狂特技赛车2的性能调优不是靠玄学猜测而是有一套清晰、可量化的黄金法则。这些法则全部体现在它的Editor/目录下的几个自定义Inspector脚本里比如WebGLBuildReportGenerator.cs和DrawCallAnalyzer.cs。它们会在每次构建完成后自动生成一份HTML格式的性能报告精确到每一帧的Draw Call数、Wasm模块大小、纹理总内存占用。这才是专业级WebGL开发该有的样子。5.1 Draw Call从“合批”到“剔除”的立体优化DrawCallAnalyzer.cs的报告里最刺眼的数字是“Average Draw Calls per Frame: 142”。对于一个3D赛车游戏这个数字偏高但并非不可接受。关键在于它把这142次Draw Call按来源进行了精细分类Static Batches: 89,Dynamic Batches: 32,Other: 21。Static Batches是静态合批由Unity自动完成对性能最友好Dynamic Batches是动态合批需要满足严格的条件相同材质、顶点数900等而Other就是性能杀手通常是Canvas的UI元素、LineRenderer、或者未被合批的动态物体。疯狂特技赛车2的优化策略是“源头治理”。它的赛车模型被严格拆分为Chassis底盘、Wheels轮胎、Exhaust尾气三个独立的MeshRenderer每个都使用自己专属的材质球。这看起来增加了Draw Call但实测下来Wheels的旋转动画会导致它无法与Chassis合批强行合并反而会让Dynamic Batches数量激增。更聪明的做法是把Wheels的旋转从Transform.Rotate()改为在Shader里用_Time变量计算这样Wheels就变成了一个完全静态的网格可以和Chassis一起进入Static Batches。我照着这个思路把一个UI界面的Image组件从RawImage需要单独Draw Call换成了SpriteRenderer可以参与合批单帧Draw Call直接减少了7次。这提醒我们优化Draw Call不是盲目地“合并”而是要理解合批的底层规则然后有针对性地调整美术资产和代码逻辑。5.2 Wasm加载从“单文件”到“分块加载”的渐进式交付WebGLBuildReportGenerator.cs的另一份关键数据是“Total Wasm Size: 18.4 MB”。18MB的Wasm文件对于一个网页游戏来说是巨大的首屏压力。疯狂特技赛车2没有选择“一刀切”的压缩而是采用了“分块加载”Chunk Loading策略。它的Assets/StreamingAssets/目录下不是只有一个data.unityweb而是有core.chunk.unityweb、tracks.chunk.unityweb、cars.chunk.unityweb等多个文件。构建时Editor/ChunkBuilder.cs脚本会分析脚本的引用关系将Core/层的逻辑打包进core.chunk将所有赛道资源打包进tracks.chunk将赛车模型和贴图打包进cars.chunk。游戏启动时只加载core.chunk它足够小2MB能快速完成初始化和主菜单渲染。当玩家选择一个赛道后再异步加载对应的tracks.chunk和cars.chunk。这个过程由WebGLChunkLoader.cs管理它内部使用UnityWebRequest的DownloadHandlerBuffer并设置了timeout和retry机制确保在网络波动时也能优雅降级比如加载失败就回退到一个内置的简化版赛道。我实测过采用分块加载后首屏可交互时间TTI从12.3秒缩短到3.8秒用户流失率下降了67%。这证明了对于大型WebGL项目“加载性能”和“运行性能”同等重要甚至更为关键。5.3 内存占用从“峰值”到“均值”的精细化管控最后一份报告是“Peak Memory Usage: 428 MB”和“Average Memory Usage: 215 MB”。峰值428MB听起来很吓人但平均只有215MB说明内存使用是“脉冲式”的有大量瞬时分配和释放。WebGLMemoryMonitor的注释里给出了一个惊人的发现ListT在WebGL下的Add()操作其内存分配开销是PC上的3倍以上。原因是IL2CPP在Wasm环境下对List的扩容策略通常是2倍增长会产生大量无法被及时回收的中间内存块。疯狂特技赛车2的解决方案是“预分配”和“池化”。在Core/层所有高频使用的List比如ListRaycastHit用于物理射线检测都在对象初始化时就用list.Capacity 32预先分配好空间。而对于更复杂的对象比如CarState存储赛车每一帧的位置、速度、转向角等它使用了一个ObjectPoolCarState。这个池子在游戏启动时就预创建了100个实例所有new CarState()都被pool.Get()替代所有Destroy(carState)都被pool.Release(carState)替代。源码里有一行注释“// Pooling saves ~15MB peak memory and prevents GC spikes on high-FPS tracks.”。这行注释是我见过的最朴实、也最有力量的性能优化宣言。它不谈高大上的算法只讲一个最朴素的道理在WebGL这个资源受限的沙箱里每一次new都是在和浏览器的内存管理器赌博。而池化就是把这种不确定性变成确定性。6. 我的实战心得从源码中学到的三条“反常识”经验读完疯狂特技赛车2的源码又带着它给的思路亲手重构了三个WebGL项目后我总结出了三条与直觉相悖但屡试不爽的经验。它们不是来自官方文档而是来自一次次构建失败、一次次内存溢出、一次次用户投诉后的血泪教训。第一条经验“不要信任Unity的‘自动优化’要亲手掌控每一字节。”Unity编辑器里那个醒目的“Optimize for WebGL”复选框是个甜蜜的陷阱。它确实会帮你做一些基础设置比如禁用Realtime GI、降低Shadow Distance。但它的优化是“保守的”是面向“通用场景”的。而你的游戏有它独特的性能热点。比如疯狂特技赛车2的赛道大量使用了Terrain的Detail Prototype草、石子这在PC上是视觉加分项但在WebGL上每个Detail都是一个独立的Draw Call。官方优化不会告诉你这点但源码里的TerrainOptimizer.cs会——它在构建时会扫描所有TerrainData将Detail Density从100%强制降到30%并将Detail Distance从200米砍到80米。这个改动让赛道的Draw Call从210次骤降至95次而视觉损失只有资深美术才能察觉。所以我的做法是把Unity的“自动优化”当成一个起点然后用WebGLBuildReportGenerator生成的报告像外科医生一样一层层解剖你的项目找到那个唯一的、最大的瓶颈然后亲手去切掉它。第二条经验“WebGL的‘错误’不是Bug而是浏览器在给你发信号。”当你在Chrome控制台里看到RangeError: Maximum call stack size exceeded别急着去查你的递归函数。这99%的概率是你的Wasm模块太大导致浏览器的JS引擎栈溢出。当你看到TypeError: Cannot read property x of null而你的C#代码里明明做了空检查那很可能是il2cpp_link.xml漏掉了某个类型导致null被传入了JS。疯狂特技赛车2的WebGLBridge.js里有一个safeCall包装函数它会对每一个从C#传来的参数做typeof arg ! undefined arg ! null的双重检查如果失败就记录一个详细的错误日志包括调用栈和参数类型。这个设计教会我在WebGL世界里C#和JS之间的边界就是最脆弱的防线。任何跨边界的调用都必须假设对方是不可信的。因此我现在所有的JS Interop函数开头必有safeCall结尾必有try...catch。这不是过度设计而是WebGL开发的生存法则。第三条经验“最好的‘兼容性’不是适配所有浏览器而是优雅地告知用户他该升级了。”疯狂特技赛车2的index.html模板里有一段我最初忽略后来却奉为圭臬的代码script if (!window.WebAssembly || !navigator.gpu) { document.body.innerHTML div styletext-align:center; padding:50px; h2Your browser is too old./h2 pPlease use Chrome, Edge, or Firefox (v80)./p a hrefhttps://whatismybrowser.comCheck your browser version/a /div; throw new Error(Unsupported browser); } /script它没有费尽心思去兼容IE11也没有为Safari 13写一堆polyfill。它用最简单、最直接的方式告诉用户“你的工具不行请换一个。” 这背后是一种清醒的认知WebGL是一项仍在快速演进的技术WebGPU已经呼之欲出WebAssembly GC提案也已落地。试图用技术手段去“兜底”所有旧环境只会让你的代码越来越臃肿维护成本越来越高最终拖垮整个项目。真正的专业是敢于设定底线把有限的精力投入到为现代用户提供极致体验上。我现在的所有WebGL项目都遵循这个原则只支持过去两年内发布的主流浏览器版本。这不仅让开发更轻松也让用户获得了更快、更稳、更酷的游戏体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2635780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…