news 2026/9/19 4:41:33

Serial Studio 0066 规格全解:用 19 个可独立验证的任务落地 OPC UA 客户端驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Serial Studio 0066 规格全解:用 19 个可独立验证的任务落地 OPC UA 客户端驱动

Serial Studio 0066 规格全解:用 19 个可独立验证的任务落地 OPC UA 客户端驱动

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

本文以 Serial Studio 仓库中 spec 0066(OPC UA Client Driver)的 tasks.md 为骨架,完整拆解这个驱动从线协议词汇表、原生解码模板、GUI 驱动核心、API 命令到 Python 仿真器与 CI 的 19 个有序任务(T1–T19):每个任务改哪些文件、做什么、如何验证、依赖谁。读完你可以掌握该项目 spec 驱动开发流程中"任务分解 + 逐项验证"的实操范式,并对照当前仓库源码理解 OPC UA 驱动的线格式、锁存解码、订阅/轮询回退与项目生成的真实实现。

![Serial Studio 连接 OPC UA PLC 仿真器的运行截图](https://raw.gitcode.com/GitHub_Trending/se/Serial-Studio/raw/1bb0933dcb784b9642addc61a425f1070ad1418e/examples/OPC UA PLC Simulator/doc/screenshot.png?utm_source=gitcode_repo_files)

一、tasks.md 在 spec 流程中的位置:Phase 3 的有序清单

spec 0066 由三份文档构成四阶段流程中的前三阶段(spec.md 定义"做什么",plan.md 定义"怎么做",tasks.md 定义"按什么顺序做"):

  • spec.md(Phase 1):需求 R1–R18 与验收标准 AC1–AC11,定义 Pro 用户连接security policy None的 OPC UA 服务器、浏览地址空间、勾选标签、生成完整工程、订阅/轮询取值、源时间戳、Python 仿真器与集成测试。
  • plan.md(Phase 2):技术设计——一个商业IO::Drivers::OpcUaHAL 驱动封装QOpcUaClient,每个发布间隔把"脏"标签序列化成紧凑二进制 delta 帧([u16 tag][u8 type][payload]),经普通publishReceivedData()路径发出,由新的opcua原生锁存模板解码。
  • tasks.md(Phase 3,本文主体):把 plan 拆成"小、有序、可独立验证"的单位——每一个任务都是一位评审者可以孤立阅读的一致 diff;/ss-implement自上而下执行这张清单并保持状态框最新。

门禁(Gate)规则同样在文档头部写死:在人类把 tasks.md 标记为approved之前,不得开始/ss-implement。本规格最终于 2026-08-25 关闭(spec.md 头部status: done)。

二、任务约定(Conventions):每个任务如何被约束

tasks.md 开头给出五条全局约定,它们决定了整张清单的写法:

  1. 一个任务 = 一个聚焦、可评审的变更。若一个任务触碰超过 3 个文件,或需要用一整段话才能描述,就必须拆分;
  2. Verify 是本单位的确认方式——通常是python scripts/code-verify.py --check <files>(code-verify.py 是该仓库的静态规则检查器),外加一个测试或一次回读(read-back);
  3. Deps 列出必须先落地的任务 ID
  4. 排序保证(尽量)每完成一个任务树都能概念上编译
  5. API 命令 ID 沿用现有io.modbus.*的形状,即io.opcua.*构建、启动、--dump-api-schema、截图、.ts重译等维护者专属步骤标注 [maintainer],任务保持打开状态直到维护者回报。

这套约定的实际效果在清单末尾的 Definition of Done 中得到印证:19 个任务全部[x] done,且明确记录"diff 就是所要求的内容,且仅此而已"——超车道(out-of-lane)的编辑(T5 的 vault scope、command_safety.json、重新生成的CommandStrings.cpptst_enum_labels.cpp的一行)全部在聊天中点名。

三、T1–T5:地基——线词汇表、原生模板、单元测试、枚举、凭据库

T1:线词汇表头OpcUaWire.h

tasks.md 要求的头文件是"驱动编码器与模板解码器共享的 header-only 词汇表",包含kWireVersionkMaxTags = 2048kSoftTagLimit = 512kMaxStringBytes = 256、类型标签枚举(Bool, I8, U8, I16, U16, I32, U32, I64, U64, F32, F64, Str)、每类型 payload 宽度、appendEntry(QByteArray&, index, type, QVariant)readEntry(view, pos, out);要求边界检查、SS_ASSERT密度 ≥2、readEntry零分配、商业 SPDX 头。

当前仓库中该文件已迁移到 core/Protocols/OpcUa/OpcUaWire.h,实现与任务描述完全一致并可进一步读出细节:

inline constexpr std::uint8_t kWireVersion = 1; inline constexpr int kMaxTags = 2048; inline constexpr int kSoftTagLimit = 512; inline constexpr int kMaxStringBytes = 256; inline constexpr int kMaxFrameBytes = 65536; inline constexpr int kHeaderBytes = 1; inline constexpr int kEntryHeaderBytes = 3;

帧布局在头文件注释中写得很清楚:[version u8]开头,之后是若干 entry[index u16 LE][type u8][payload],字符串前置一个 u16 LE 字节长度;帧里只包含自上一 tick 以来变化的标签,解码器对其余标签做锁存(latch)。类型枚举Type的数值即线上字节(Bool=0 … Str=11,Invalid=255)。appendEntry对字符串按 UTF-8 截断到kMaxStringBytes(且回退避免切断多字节字符的续字节);readEntry在截断的 entry 或未知类型时返回 false 且不推进pos,从而终止遍历——这正是 T2 模板"版本字节不匹配与截断 entry 停止遍历"的实现基础。

任务验证命令:python scripts/code-verify.py --check app/src/IO/Drivers/OpcUaWire.h(路径为当时布局,现见core/Protocols/)。Deps:无。

T2:opcua原生锁存模板

任务要求在BinaryTemplates.cppNativeTemplateRegistry.cpp中新增OpcUaParser : NativeLatchParser(按schemaJSON 参数[{"i":0,"t":"f64"}, ...]解码 T1 的 delta 帧),并在modbus旁注册OpcUaTemplate描述符(idopcua,参数schemaJson);不变式:每帧解码器、对kMaxBytesPerFrame的固定循环上界、遍历上的SS_ASSERT_HOTPATH、除storeAt外无分配。

当前实现位于 core/Pipeline/DataModel/Scripting/NativeTemplates/WireLatchTemplates.cpp,OpcUaTemplatemakeParser展示了任务要求的校验逻辑:schema 必须列 1 到kMaxTags个标签;每个{"i": index, "t": type}条目的 index 合法、类型已知且不重复,否则返回nullptr并给出错误文本(如 "The tag schema has an invalid, duplicate or missing index."),最终构造TypedWireParser。模板说明文本也明确"由 OPC UA 工程生成器机器管理"。Deps:T1。

T3:wire + 模板的 ctest 单元测试

任务要求在 app/tests/tst_opcua_wire.cpp(及CMakeLists.txtss_add_unit_test,置于商业构建守卫内)覆盖:每类型往返、字符串 256 字节截断、标签上界、版本拒绝,以及经OpcUaParser的锁存语义(脏子集、未知索引、截断 entry)。当前测试类的用例名与任务一一对应:

typeCodesRoundTrip / scalarsRoundTrip(+_data) / stringsTruncateAtCap / truncatedEntryStopsWalk / badVersionRejected / templateLatchesDeltas / templateIgnoresUnknownIndexAndTypeMismatch / templateRejectsBadSchema

验证分两段:code-verify.py --check可由作者完成;构建后ctest -R tst_opcua_wire标 [maintainer]。任务状态如实记录为 "code written; awaiting maintainer build + ctest"。Deps:T2。该测试后来还扩展出模糊测试 app/tests/fuzz/fuzz_opcua_wire.cpp。

T4:BusType::OpcUa枚举 + 标签 + 图标

任务要求把OpcUa追加在Mqtt之后(商业块内,索引 10,永不重排——枚举顺序同时索引Hardware.qml的 StackLayout 与集成测试api_client.pybus_map),slugopcua/ 标签OPC UAbusTypeIcon()分支,三档图标(16/24/32)注册进rcc.qrc。验证除code-verify.py --check外还有一条回读规则:grep -rn "BusType::Mqtt" app/src列出的每个 switch,现在都要有OpcUa兄弟分支,或被后续任务(T6、T8、T9、T13)覆盖。Deps:无。当前仓库中枚举与标签分别落在 core/Core/SerialStudio.h 与 core/Core/EnumLabels.cpp(由 app/tests/tst_enum_labels.cpp 覆盖 slug/label)。

T5:CredentialVaultscope

任务要求在CredentialVault构造函数增加可选scope字符串,前缀进settingsKey()(默认空保持 MQTT 键逐字节不变),避免 OPC UA 凭据与 MQTT 键命名空间冲突;文档明确这是"计划中点名的超车道编辑",MQTT/目录下其他内容不动。验证:code-verify.py --check+ 回读默认 scope 产生旧键串。这体现了任务清单的一个纪律:任何超出本任务主线的改动都必须在任务文本中显式命名,而不是悄悄做掉。

四、T6–T8:驱动核心——拨号裁决、订阅/轮询回退、缓存与项目生成

T6:驱动核心(属性、发现、拨号)

任务要求IO::Drivers::OpcUa : HAL_Driver提供属性集(endpointUrl、endpointIndex、endpointList、endpointSelectable、authMode、username、经 vault scopeopcua的 password、publishingInterval、tagCount、statusText、pollMode),requestEndpoints→ 列表;open()创建客户端(open62541)、应用认证、connectToEndpoint,以m_connecting返回 true。核心不变式是**"每次尝试恰好一次裁决"**:成功与失败都经唯一failDial()漏斗 +reportOpenFinished上报,外加 15 秒最后手段定时器;每个失败同时到达ConnectionManager::disconnectDevice(this);任何地方都不许模态框(logDriverError);建立后掉线 → 排队disconnectDevice。验证包含一条 grep 不变式:"每个reportOpenFinished(false调用点也调用failDial/disconnectDevice"。Deps:T1、T4、T5。

当前 core/Devices/IO/Drivers/OpcUa.h 可以逐条对照:Q_PROPERTY 列表中endpointUrl/endpointIndex/endpointList/endpointSelectable/authMode/publishingInterval/tagCount/statusText/pollMode全部存在;私有槽onSessionConnectedonConnectFailedonDialTimeout与私有方法failDial(const QString& reason)即任务点名的裁决漏斗;write()恒返回 -1(纯读驱动,isWritable()为 false)。

T7:订阅、轮询回退、值缓存、发布 tick

任务要求:连接后每标签一个QOpcUaNodeenableMonitoring(Value, params);全部被拒或BadServiceUnsupported/BadTooManySubscriptionsm_pollMode+m_pollTimerreadNodeAttributes。值缓存TagSlot(value、status、sourceTs、dirty);坏状态保留最后好值并计数;缺失/偏移 >5 s 的源时间戳回退到接收时间并计数;m_frameTimer按发布间隔把脏槽经 T1 编码进一次性 reserve 的缓冲,调用publishReceivedData(std::move(frame), earliestSourceTs);空则不发帧。不变式:源拥有时间(steady-clock 偏移每次连接只算一次,永不重盖章);每值无信号/分配;诊断是 1 Hz 拉取的普通计数器。验证:函数长度 ≤100 行;回读确认没有每通知的Q_EMIT。Deps:T6。

T8:标签模型 + 工程生成

任务要求OpcUaTagModel : QAbstractItemModel(惰性fetchMorebrowseChildrenreadAttributes取 DataType/AccessLevel/DisplayName/Description + EngineeringUnits,folder 三态勾选,不支持的类型不可选,软/硬标签上限),以及generateProject()/buildProject():按父文件夹分组、每标签一个 dataset(数组展开)、带单位、plt/led/字符串路由、frameParserTemplate = "opcua"frameParserParams = {schema},连接设置来自driverProperties()。验证回读:生成的 JSON 能经ProjectModel::loadFromJsonDocumentKeys::名称(而非字面量)往返。Deps:T2、T7。当前代码里这两块分别落在 core/Devices/IO/Drivers/OpcUaTagModel.cpp 与 core/Devices/IO/Drivers/OpcUa/OpcUaProjectBuilder.cpp(工程生成另有单测 app/tests/tst_opcua_project_builder.cpp 守护)。

五、T9–T13:接线、API、QML 与 CLI

T9:ConnectionManager + ModuleManager + CMake 接线

任务要求m_opcUaUi+opcUa()访问器、三处BusTypeswitch(activeUiDriveruiDriverForBusTypecreateDriver)、wireUiDriver、总线名列表、teardown 重置;Cpp_IO_OpcUa上下文属性 + 保留名;OpcUa进商业QT_MODULES/QT_LIBS与源码列表。验证是grep -n "OpcUa" .../ConnectionManager.cpp显示全部三个 switch。Deps:T6。当前接线在 core/Devices/IO/ConnectionManager.cpp 与 core/Misc/ModuleManager.cpp。值得注意的是当前 app/CMakeLists.txt 第 90 行注释写明"OPC UA does NOT use Qt6::OpcUa (spec 0067). The driver talks to the open62541 static library"——即规格完成后,后续 spec 0067 把底层换成了直接链 open62541 静态库,这是 tasks 文档本身不包含的后续演化。

T10:API handler

任务要求按 plan §API 实现io.opcua.*命令(configuration、discoverEndpoints5 秒有界、getEndpointsbrowse、tags CRUD、带注册级策略预检的generateProjectgetStatus),注册在BUILD_COMMERCIAL下;构建后由维护者跑SerialStudio --dump-api-schema,生成物只经生成器更新。当前 core/Api/API/Handlers/OpcUaHandler.cpp 已落地,app/rcc/api/sdk-symbols.json 中可见io.opcua命令面:addTagbrowseclearTagsdiscoverEndpointsexportCertificategenerateProjectgetCertificategetConfiggetStatus等(含后续 spec 加入的证书命令)。任务还记录了一个超车道编辑:命令在 app/rcc/ai/command_safety.json 中标记。Deps:T8、T9。

T11 / T12:设置面板与标签浏览器 QML

  • T11要求OpcUa.qml(endpoint URL + Discover、带禁用安全行与 tooltip 的 endpoint combo、认证模式、用户名/密码、警告横幅、间隔、标签数、Browse/Generate 按钮、状态行),Hardware.qml索引 10 的Loader,以及同Modbus.qml的 ComboBox 恢复竞争保护;
  • T12要求OpcUaTagBrowser.qmlOpcUaTagModel上的TreeView、复选框 delegate、folder 三态、type/access 列、搜索过滤、软限横幅、Select All Readable、OK/Cancel,外观镜像ModbusGroupsDialog.qml),打开对话框对仿真器的验证标 [maintainer]。

两个 QML 当前分别位于 app/qml/MainWindow/Panes/SetupPanes/Drivers/OpcUa.qml 与 app/qml/MainWindow/Panes/SetupPanes/Drivers/OpcUaTagBrowser.qml。Deps:T9(T12 另依赖 T8、T11)。

T13:命令注册表、编辑表单、流程图、CLI

任务要求driver.opcua开关命令 + 绑定(scripts/registry-verify.py校验)、总线 combobox 条目、流程图名称数组加"opcua"、CLI--opcua <url>--opcua-user--opcua-pass--opcua-interval--opcua-tag(可重复)经setupOpcUaConnection()。当前 CLI 实现见 app/src/Misc/CLI/CliIndustrialConfig.cpp:读取opts.opcuaOpt后取cm.opcUa()setEndpointUrl(url),设置--opcua-usersetAuthMode(1)。验证:python scripts/registry-verify.py+scripts/generate-command-strings.py --check+code-verify.py --check。Deps:T4、T9。

六、T14–T16:仿真器、集成测试与帮助页

T14:Python 仿真器(asyncua 服务器)

任务要求在examples/OPC UA PLC Simulator/下提供:监听opc.tcp://127.0.0.1:4840/serialstudio/asyncua服务器(Plant/Line1/… 文件夹、布尔/整数/浮点/双精度/字符串、一个浮点数组、一个周期性 Bad 状态标签、时变物理量),--port--user/--pass--secure-only(只通告 Basic256Sha256 端点)、--no-subscriptions标志;由驱动生成器产出的工程文件;examples.jsonrequiresPro: true、分类 "Industrial Automation";asyncua>=1.1进 requirements。验证:脚本启动 +nc -z 127.0.0.1 4840,截图标 [maintainer]。任务状态记录"已用 asyncua 客户端活体验证"。

当前 examples/OPC UA PLC Simulator/opcua_plc_simulator.py 及其 README 已按此落地并进一步扩展(README 记录了--rate--drop-after--security--cert-expired--cert-wrong-host等后续安全相关标志),工程文件为 examples/OPC UA PLC Simulator/OPC UA PLC Simulator.ssproj。

T15:集成测试 + fixtures

任务要求在 tests/integration/test_opcua_driver.py 实现 plan §Test 的测试清单(discover、匿名/用户名/坏凭据/关闭端口的连接、browse、不支持类型、generate、订阅速率、轮询回退、CSV 时间戳、坏状态、重连、工程往返、API 面),opcua_simulatorfixture(4840 不通则 skip;重启 helper 服务重连测试),markersopcuarequires_opcua_simbus_map["opcua"] = 10。验证:pytest ... --collect-only可先行,全量运行标 [maintainer]。任务记录 "collects 16 tests; full run pending maintainer app"。Deps:T10、T14。

T16:帮助手册页

任务要求以 Modbus 页的"口吻"写 doc/help/Drivers-OPC-UA.md(概述、厂商兼容列表、凭据警告、端点发现、标签浏览器、工程生成、订阅 vs 轮询、时间戳、CLI、仿真器演练、API 命令),注册在drivers-modbus之后,并在 doc/help/Auto-Generating-Projects.md 加交叉链接段。验证:python scripts/documentation-verify.py doc/help/Drivers-OPC-UA.md。当前帮助页正文确认与任务描述一致(概述厂商兼容性:Rockwell via FactoryTalk Linx Gateway、KEPServerEX/Ignition、Siemens S7-1200/1500 与 WinCC、Beckhoff TwinCAT、CODESYS、B&R、WAGO、Schneider),并且随着后续安全通道规格,其"None-only 限制"段落已更新为支持六种安全策略。

七、T17–T19:CI、AI 文档与静态门禁

T17:CI workflow

任务要求在.github/workflows/ci.yml的集成矩阵加opcua_sim: true/false;Linux/macOS 腿在 Mosquitto 旁启动 T14 脚本(后台 + 4840 端口等待);Windows 腿marker_exprand not requires_opcua_sim;不改 Qt 安装。验证:python -c "import yaml; yaml.safe_load(...)"+ diff 限于集成 job。当前 .github/workflows/ci.yml 中可见矩阵项opcua_sim: true/false与 Windows 腿的marker_expr: "not destructive and not dos and not audio and not requires_broker and not requires_opcua_sim",与任务描述逐字吻合。

T18:AI-facing 文档

任务要求更新 doc/claude/architecture/io.md(拨号教条:异步、一次裁决、delta 帧 tick、轮询回退、vault scope)、doc/claude/directory-map.md 新文件行、CLAUDE.md数据源列表与"11 drivers"计数。验证是ss-ai-audit抽查。

T19:静态门禁 + 评审

最后一步对所有变更跑code-verify.py --checkregistry-verify.py、对驱动/模板/handler diff 跑qt-cpp-reviewsanitize-commit.py(不提交)。任务文档还有一段值得注意的"反事实检查":最可能失守的规则是"一次裁决"拨号锁存,证据就是failDial漏斗 grep +test_closed_port_settles测试——即先声明风险规则,再指出守护它的证据。任务状态记录了真实收尾:sanitize-commit停在 singleton-census 门(+7 处,全部是"兄弟驱动静态实例"形状),由维护者决定--accept

八、Definition of Done:清单如何收口

tasks.md 的完成定义(全部勾选)展示了这类清单的验收口径,值得作为工程参照:

  1. spec.md 的每一条验收标准满足并勾选(AC3/AC4/AC6/AC7/AC10 为维护者观察项);
  2. python scripts/code-verify.py --check对所有变更文件干净;
  3. qt-cpp-review跑过 C++ diff:14 个确认发现已修,6 个调查项记入聊天
  4. 热路径未触碰;维护者在 Pro 构建上跑过一次--benchmark-hotpath(AC7);
  5. pytest tests/integration/test_opcua_driver.pyctest -R tst_opcua_wire在仿真器在线时绿;
  6. sanitize-commit.py已跑;工作树无 lint 债;
  7. diff 是"所要求的,且只有那"——超车道编辑全部点名
  8. spec.md状态置为done

九、从清单到当前仓库:任务完成后的代码演化

tasks.md 是"任务时刻"的快照,而当前仓库已经历后续重构,从源码结构看可以确认这些演化(作为理解 T1–T19 落点的补充证据):

  • 驱动类注释明确"Certificate store, browse session, subscriptions and project builder are sub-objects (spec 0070)"(见 core/Devices/IO/Drivers/OpcUa.h):T6–T8 的单体职责已被拆为OpcUaSessionOpcUaBrowserOpcUaSubscriptionsOpcUaProjectBuilderOpcUaFrameAssemblerOpcUaCertificateStoreOpcUaEndpointSelection等子对象(core/Devices/IO/Drivers/OpcUa/ 目录),但failDial裁决漏斗、m_dialTimerPendingDial等 T6 命名的一词未变;
  • 线词汇表从当时的app/src/IO/Drivers/提升到 core/Protocols/OpcUa/OpcUaWire.h,常量与帧格式与 T1 描述完全一致;
  • 测试面从 T3 的tst_opcua_wire扩展到tst_opcua_frame_assemblertst_opcua_subscriptionstst_opcua_endpoint_selectiontst_opcua_marshaltst_opcua_project_buildertst_opcua_security与 fuzz 目标(app/tests/ 目录);
  • 安全策略(T 系列中的 "None-only" 限制来自 plan 的 2026-08-22 维护者决定)在后续规格中补齐,OpcUa.h现暴露securityPolicy/securityMode/证书属性,帮助页与仿真器的--security/--cert-*标志也印证了这条时间线。

十、要点回顾

  • tasks.md 的价值不在文件清单本身,而在约定:一个任务一个可评审 diff、每个任务自带 Verify 与 Deps、维护者步骤显式标记、超车道编辑显式点名、Definition of Done 逐条勾销;
  • OPC UA 驱动的技术主线在 T1/T2/T6/T7 就已定死:header-only 线词汇表(版本字节 + u16 索引 + u8 类型 + LE payload,2048 标签上限、256 字节字符串上限、锁存解码)→ 原生模板解码 → 一次裁决拨号 + 脏槽 delta 帧 tick + 订阅/轮询自动回退;
  • 每个任务的验证命令(code-verify.py --checkctest -R tst_opcua_wirepytest ... --collect-onlyregistry-verify.pydocumentation-verify.py、yaml 语法检查)都是可直接在仓库中复现的,读者可据此对任意一个任务做独立的证据核验。

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 2:37:43

浏览器端视频抽帧与色度键抠图:绿幕视频转Sprite Sheet实战指南

我做前端动画需求时最烦的一件事&#xff0c;就是手头只有一段视频素材&#xff0c;却要把它变成游戏或网页里能用的序列帧动画。以前的第一反应是打开 Python 写 OpenCV&#xff0c;或者直接甩给 FFmpeg 一段命令行脚本。后来我发现&#xff0c;像“视频转序列帧”这种活&…

作者头像 李华
网站建设 2026/9/18 2:37:28

Unity IL2CPP 分析:frida-il2cpp-bridge 实战

1. 先搞清楚这套工具到底在解决什么问题1.1 Unity 的 IL2CPP 到底做了什么我刚开始接触这类工具的时候&#xff0c;最大的困惑不是"怎么装"&#xff0c;而是"为什么装完之后能看到的东西这么少"。后来才明白&#xff0c;问题出在 Unity 的打包方式上。Unit…

作者头像 李华
网站建设 2026/9/18 2:37:19

Blender与Godot协同构建真实3D地形全链路指南

1. 这不是“建模”&#xff0c;而是把真实地球搬进你的3D世界你有没有试过在Blender里拉一个平面&#xff0c;加个噪波修改器&#xff0c;再调几个参数——然后告诉自己&#xff1a;“这差不多就是山了”&#xff1f;我干过&#xff0c;而且干了整整三年。直到某天客户甩来一张…

作者头像 李华
网站建设 2026/9/18 2:34:16

OpenHarmony Flutter插件开发:基于Plugin Platform Interface的鸿蒙适配实践

1. 为什么 OpenHarmony 插件开发不能照搬 Android 那套写法接触过 Flutter 插件开发的同行应该都有印象&#xff0c;以前写一个插件&#xff0c;主要工作就是写个 MethodChannel 的封装&#xff0c;把 Dart 侧的方法调用映射到 Android 的 Kotlin 或者 iOS 的 Swift 上。这样做…

作者头像 李华