Godot 4 GDExtension 开发实战:从官方模板到高性能 C++ 扩展

news2026/5/3 3:55:06
1. 项目概述与核心价值如果你正在用 Godot 4 开发游戏并且觉得 GDScript 在某些性能密集型或需要复用现有 C 库的场景下有些力不从心那么 GDExtension 就是你必须要掌握的技术。而godotengine/godot-cpp-template这个仓库就是官方为你铺好的、通往 C 高性能扩展开发世界的一条“高速公路”。它不是另一个需要你从零开始配置的复杂项目而是一个开箱即用、经过最佳实践验证的脚手架。简单来说这个模板帮你解决了 GDExtension 开发中最繁琐、最容易出错的前期配置工作。它预置了与 Godot 4 引擎通信所必需的godot-cpp绑定库作为子模块一个结构清晰的 C 源码目录一个用于测试的空白 Godot 项目甚至还包括了用于自动化构建和发布的 GitHub Actions 工作流。这意味着你从克隆这个模板仓库到编译出第一个能运行的.gdextension库文件可能只需要几分钟。对于开发者而言时间就是最宝贵的资源这个模板直接将你从“环境搭建者”的角色解放出来让你能立刻专注于“功能创造者”的核心工作——用 C 为你的 Godot 游戏编写高性能模块。2. 模板结构深度解析与设计思路刚拿到一个模板最忌讳的就是盲目操作。理解其目录结构和每个文件的设计意图能让你在后续定制和排错时事半功倍。这个模板的目录树看似简单但每一处都蕴含着对 GDExtension 工作流的深刻理解。2.1 核心目录与文件职责godot-cpp/(子模块)这是整个模板的基石。它并不是模板作者编写的代码而是 Godot 官方维护的 C 绑定库。你可以把它理解为一套庞大的“翻译词典”和“通信协议”。Godot 引擎本身是用 C 写的它暴露给脚本层的是一套基于Variant、Object、ClassDB的 C API。直接使用这套原始 API 编写扩展极其繁琐且容易出错。godot-cpp库的作用就是用现代 C支持 C17的语法如类、继承、智能指针将这些底层 C API 包装起来让你能用写普通 C 类的方式去创建 Godot 能识别的节点、资源或工具类。模板将其作为子模块引入确保了绑定库版本的可控性和一致性。src/目录这是你施展拳脚的地方存放你自定义的 C 扩展代码。模板初始只提供了两个文件register_types.cpp这是你扩展的“总入口”和“类注册表”。所有你希望暴露给 Godot 的 C 类都需要在这里通过initialize_*_module和deinitialize_*_module函数进行注册和注销。godot-cpp提供的宏如GDREGISTER_CLASS让这个过程变得非常简洁。example.h/.cpp这是一个示例类展示了如何创建一个继承自godot::Node的简单 C 类并将其方法、属性暴露给 GDScript。它是你学习绑定语法的最佳起点。project/目录这是一个完整的、但内容为空的 Godot 4 项目。它的存在至关重要体现了“开发即测试”的理念。project/bin/目录下存放着关键的example.gdextension配置文件以及编译生成的动态链接库如libexample.*.so或example.*.dll。当你用 Godot 编辑器打开这个project文件夹时引擎会自动读取gdextension文件并加载你的 C 库这样你就能在编辑器中实时测试你的 C 类就像使用内置节点一样方便。这种将测试项目与源码放在一起的结构极大简化了开发-测试的循环。SConstruct文件这是项目的构建脚本使用 SCons 构建工具。它定义了如何编译你的 C 代码以及godot-cpp绑定库并生成针对不同平台Windows, Linux, macOS的二进制文件。模板中的SConstruct已经配置好了绝大多数编译参数如包含路径、库依赖、编译器标志等。你通常只需要修改一个地方libname变量它决定了最终输出库文件的名称。.github/workflows/目录这里存放着 GitHub Actions 的自动化脚本是模板提供的“生产力倍增器”。builds.yml通常用于在每次代码推送时进行持续集成测试确保你的修改不会破坏编译。而make_build.yml则更强大它可以手动触发为所有支持的平台如 Windows MSVC/MinGW, Linux, macOS一次性编译出所有版本的库文件并打包成一个 zip 供你发布。这对于需要分发跨平台插件的开发者来说简直是神器免去了在不同机器上配置编译环境的痛苦。2.2 设计哲学约定优于配置这个模板的成功之处在于它采用了“约定优于配置”的理念。它预设了一套经过验证的、合理的项目结构、构建流程和开发工作流。作为使用者你不需要思考“我的源码该放哪里”、“gdextension文件该怎么写”、“如何跨平台编译”这些基础问题。你只需要遵循模板的约定修改几个关键标识如库名、入口函数名就能得到一个完全可用的开发环境。这种设计极大地降低了入门门槛让开发者能快速进入创造性的编码阶段而不是在构建系统的泥潭中挣扎。3. 从模板到专属项目的完整实操指南理解了结构接下来我们一步步将其转化为你自己的 GDExtension 项目。这个过程就像给一个精装修的房子贴上自己的门牌、搬入自己的家具。3.1 项目初始化与仓库克隆首先你需要在 GitHub 上创建自己的项目副本。不要直接git clone原仓库那样你会失去创建独立 Git 历史的能力。正确做法是在浏览器中打开godotengine/godot-cpp-template仓库页面。点击绿色的“Use this template”按钮。在新页面中为你自己的新仓库命名例如my-awesome-gdextension选择公开或私有然后点击创建。这样你就得到了一个全新的仓库其初始内容就是这个模板但拥有独立的 Git 历史。接下来将你的新仓库克隆到本地git clone https://github.com/你的用户名/my-awesome-gdextension.git cd my-awesome-gdextension克隆完成后第一件关键事是初始化子模块。godot-cpp绑定库是以子模块形式链接的你需要将其内容拉取到本地git submodule update --init --recursive这个命令会下载godot-cpp库的代码到godot-cpp/目录。没有这一步后续编译将无法进行。3.2 关键标识符的修改与意义模板中使用了“example”作为占位符。现在你需要将其全部替换为你自己扩展的标识。这涉及四个文件的联动修改顺序很重要第一步确定核心库名 (libname)打开根目录的SConstruct文件找到类似下面的行libname example将其中的example改为你想要的库名例如my_game_utils。这个名字将用于生成最终的动态库文件名如libmy_game_utils.linux.x86_64.so或my_game_utils.windows.x86_64.dll。建议使用简短、清晰、不含空格和特殊字符的英文名。第二步更新 GDExtension 配置文件进入project/bin/目录你会找到example.gdextension文件。这个文件是 Godot 引擎加载你扩展的“说明书”。修改文件内容用文本编辑器打开它。你需要修改两处library路径找到library开头的行将其后的路径中的EXTENSION-NAME替换为你上一步设置的libname。例如在 Linux 部分可能从library linux.x86_64/libEXTENSION-NAME.linux.x86_64.so改为library linux.x86_64/libmy_game_utils.linux.x86_64.so。注意模板通常为不同平台预置了配置你需要逐一修改。entry_symbol名称找到entry_symbol行将其值从example_library_init改为一个与之对应的新名字例如my_game_utils_library_init。这个符号名是引擎在加载库时寻找的初始化函数名必须与 C 代码中的函数名严格一致。重命名文件将example.gdextension文件本身重命名为与你的库名一致例如my_game_utils.gdextension。这样更清晰避免混淆。第三步同步修改 C 入口函数打开src/register_types.cpp文件。找到名为example_library_init的函数以及对应的deinit函数将函数名改为你在gdextension文件中定义的entry_symbol即my_game_utils_library_init。// 修改前 extern C { GDExtensionBool GDE_EXPORT example_library_init(...) { ... } } // 修改后 extern C { GDExtensionBool GDE_EXPORT my_game_utils_library_init(...) { ... } }这个extern C块至关重要它确保了 C 函数名不会被编译器进行名称修饰Name Mangling从而让 Godot 引擎能够通过纯 C 的链接方式找到它。注意以上三步的修改必须保持同步。libname决定文件名entry_symbol和 C 函数名必须完全相同。任何不一致都会导致 Godot 无法加载你的扩展通常会在编辑器输出中看到“无法找到入口点”之类的错误。3.3 首次编译与验证配置完成后就可以进行首次编译了。确保你在项目根目录有SConstruct文件的目录下执行sconsSCons 会自动检测你的平台编译godot-cpp绑定库如果尚未编译然后编译你的src/下的 C 代码最终将生成的库文件输出到project/bin/下对应的平台子目录中。编译成功后用 Godot 4.0 或更高版本的编辑器打开project/文件夹。如果一切配置正确Godot 启动时会在编辑器底部“输出”面板打印出加载日志。打开模板中可能存在的测试场景或自己创建一个将一个节点添加到场景中你应该能在右侧的“节点”面板中看到你从 C 注册的类例如Example。如果能成功添加该节点并运行场景看到预期的输出比如模板示例中的打印Type: 24那么恭喜你你的第一个 GDExtension 项目已经成功运行3.4 集成开发环境 (IDE) 配置虽然可以用文本编辑器加命令行开发但一个配置好的 IDE 能极大提升效率尤其是代码补全、跳转和静态检查功能。模板支持生成compile_commands.json文件这是许多现代 IDE如 CLion, VS Code with clangd, Qt Creator用于理解项目编译命令的标准。在项目根目录执行scons compiledbyes这个命令会在编译的同时在根目录生成一个compile_commands.json文件。或者如果你只想生成数据库而不编译scons compiledbyes compile_commands.json生成此文件后在 VS Code 中安装clangd扩展它通常能自动识别并利用这个文件来提供精准的代码智能感知。在 CLion 中你可以通过File | Reload CMake Project如果它被识别为 CMake 项目或直接打开包含该文件的目录IDE 会自动配置索引。有了代码补全你就能方便地查看godot-cpp中提供的所有 Godot 引擎类和方法避免手动查阅 API 文档的麻烦。4. 编写你的第一个 C GDExtension 类模板提供的Example类很简单。现在让我们从头开始创建一个更有实用价值的类一个HealthComponent生命值组件它展示了属性绑定、信号发射和 GDScript 交互。4.1 创建头文件与类声明在src/目录下创建新文件health_component.h。// health_component.h #ifndef HEALTH_COMPONENT_H #define HEALTH_COMPONENT_H #include godot_cpp/classes/node.hpp #include godot_cpp/core/binder_common.hpp namespace godot { class HealthComponent : public Node { GDCLASS(HealthComponent, Node) // 关键宏用于启用Godot的反射系统 private: double max_health; double current_health; protected: // 必须声明这个静态函数用于绑定方法和属性 static void _bind_methods(); public: HealthComponent(); ~HealthComponent(); // 属性声明将通过 _bind_methods 暴露 void set_max_health(double p_max_health); double get_max_health() const; void set_current_health(double p_health); double get_current_health() const; // 方法声明 void take_damage(double damage); void heal(double amount); bool is_alive() const; // 信号声明 void health_changed(double new_health); void died(); }; } #endif // HEALTH_COMPONENT_H关键点解析GDCLASS(HealthComponent, Node)这是godot-cpp中最核心的宏。它自动生成一系列运行时所需的类型信息使你的 C 类能够被 Godot 的脚本系统识别和操作。第一个参数是你的类名第二个是父类名。_bind_methods()这是一个静态的、受保护的虚函数。你需要在.cpp文件中实现它在那里使用ClassDB的绑定方法将你的属性、方法和信号“告诉”Godot。信号声明在头文件中像普通函数一样声明信号参数即信号传递的参数。在.cpp文件中会用特殊的宏进行绑定。4.2 实现源文件与 Godot 绑定创建对应的health_component.cpp文件。// health_component.cpp #include health_component.h namespace godot { void HealthComponent::_bind_methods() { // 1. 绑定属性 ClassDB::bind_method(D_METHOD(get_max_health), HealthComponent::get_max_health); ClassDB::bind_method(D_METHOD(set_max_health, p_max_health), HealthComponent::set_max_health); ClassDB::add_property(HealthComponent, PropertyInfo(Variant::FLOAT, max_health), set_max_health, get_max_health); ClassDB::bind_method(D_METHOD(get_current_health), HealthComponent::get_current_health); ClassDB::bind_method(D_METHOD(set_current_health, p_health), HealthComponent::set_current_health); ClassDB::add_property(HealthComponent, PropertyInfo(Variant::FLOAT, current_health), set_current_health, get_current_health); // 2. 绑定方法 ClassDB::bind_method(D_METHOD(take_damage, damage), HealthComponent::take_damage); ClassDB::bind_method(D_METHOD(heal, amount), HealthComponent::heal); ClassDB::bind_method(D_METHOD(is_alive), HealthComponent::is_alive); // 3. 绑定信号 ADD_SIGNAL(MethodInfo(health_changed, PropertyInfo(Variant::FLOAT, new_health))); ADD_SIGNAL(MethodInfo(died)); } HealthComponent::HealthComponent() { max_health 100.0; current_health max_health; } HealthComponent::~HealthComponent() { // 清理资源如果有的话 } // 属性 setter/getter 实现 void HealthComponent::set_max_health(double p_max_health) { max_health p_max_health; if (current_health max_health) { current_health max_health; } } double HealthComponent::get_max_health() const { return max_health; } void HealthComponent::set_current_health(double p_health) { double old_health current_health; current_health CLAMP(p_health, 0, max_health); if (current_health ! old_health) { emit_signal(health_changed, current_health); if (current_health 0) { emit_signal(died); } } } double HealthComponent::get_current_health() const { return current_health; } // 方法实现 void HealthComponent::take_damage(double damage) { if (damage 0) { set_current_health(current_health - damage); } } void HealthComponent::heal(double amount) { if (amount 0) { set_current_health(current_health amount); } } bool HealthComponent::is_alive() const { return current_health 0; } }关键点解析_bind_methods()实现这是连接 C 和 Godot 的桥梁。ClassDB::bind_method将 C 方法绑定到 Godot 的方法名上。D_METHOD宏用于生成方法描述字符串。ClassDB::add_property注册属性使其能在编辑器的检查器中显示和编辑。PropertyInfo定义了属性的类型和名称。ADD_SIGNAL注册信号。MethodInfo描述了信号的名称和参数列表。emit_signal在 C 代码中发射信号与 GDScript 中的emit_signal作用相同。任何连接到该信号的 GDScript 函数都会被调用。CLAMP一个 Godot 提供的工具宏用于将值限制在指定范围内确保生命值不会超出 0 到max_health。4.3 注册新类到模块最后需要在src/register_types.cpp中注册这个新类使其对 Godot 可见。// 在 register_types.cpp 的 initialize_example_module 函数中添加 #include health_component.h // 包含新类的头文件 void initialize_example_module(ModuleInitializationLevel p_level) { if (p_level ! MODULE_INITIALIZATION_LEVEL_SCENE) { return; } ClassDB::register_classHealthComponent(); // 注册HealthComponent类 // ... 其他已存在的注册 }同样在deinitialize_example_module函数中不需要做特别操作因为类注册是全局的引擎关闭时会自动清理。4.4 在 Godot 中测试重新运行scons编译项目。编译成功后在 Godot 编辑器中打开project/。创建一个新场景添加一个任意节点如Node3D。选中该节点点击“添加子节点”在搜索框中输入HealthComponent你应该能看到它。添加它。在右侧检查器中你应该能看到max_health和current_health两个属性并且可以修改它们。附上一个 GDScript 脚本到父节点测试功能extends Node3D onready var health_component $HealthComponent func _ready(): # 连接信号 health_component.health_changed.connect(_on_health_changed) health_component.died.connect(_on_died) # 测试方法 print(初始生命: , health_component.current_health) health_component.take_damage(30) print(受伤后生命: , health_component.current_health) print(是否存活: , health_component.is_alive()) health_component.heal(10) health_component.take_damage(90) # 这会触发死亡 func _on_health_changed(new_health): print(生命值改变: , new_health) func _on_died(): print(角色死亡)运行场景观察输出面板的日志验证你的 C 组件是否正常工作信号是否正确发射。5. 高级配置、构建优化与自动化发布当你的扩展功能越来越复杂或者需要分发给其他开发者使用时模板提供的进阶功能就派上用场了。5.1 自定义 SConstruct添加第三方库假设你的扩展需要链接一个第三方数学库比如GLMOpenGL Mathematics。你需要修改SConstruct文件。指定头文件路径在SConstruct中找到定义cpp_path的地方通常是一个列表添加你的第三方库头文件路径。# 假设你把 GLM 放在项目根目录的 thirdparty/glm/ 下 cpp_path [ ... Dir(thirdparty/glm).abspath, ]指定链接库找到定义链接参数的部分可能是env.Append(LINKFLAGS或libs列表添加库名和可能的库路径。# 对于动态链接库 env.Append(LINKFLAGS[-lglm]) # 或者指定具体路径 # env.Append(LIBPATH[path/to/glm/lib]) # env.Append(LIBS[glm])不同平台Windows, Linux, macOS的链接语法可能不同SCons 通常能通过env对象处理平台差异但有时需要写条件判断。5.2 编译配置与优化SCons 支持通过命令行参数或修改SConstruct来调整编译。目标平台模板通常默认编译当前主机平台。你可以通过target参数指定但更常见的做法是使用模板自带的 GitHub Actions 进行跨平台编译。构建类型默认可能是debug或release。debug包含调试符号便于调试release进行优化体积小、速度快。你可以在SConstruct中查找target相关的配置或通过命令行传递参数如果脚本支持例如scons targettemplate_release具体参数名需查看 SConstruct 逻辑。自定义编译器标志你可以在SConstruct中找到env.Append(CCFLAGS的部分添加你需要的警告级别、语言标准等例如env.Append(CCFLAGS[-Wall, -Wextra, -Werror, -stdc17])。5.3 利用 GitHub Actions 自动化构建与发布这是模板最强大的功能之一。.github/workflows/make_build.yml工作流可以一键为所有主流平台构建你的扩展。手动触发构建在你的 GitHub 仓库页面点击“Actions”标签。在左侧边栏找到“Make Build”工作流名称可能因模板更新而异。点击“Run workflow”按钮选择分支通常是main或master然后运行。工作流会在云端启动多个虚拟机Runner分别编译 Windows、Linux、macOS 等平台的版本。获取构建产物工作流运行完成后通常需要几分钟点击该次运行记录。在页面底部“Artifacts”区域你可以下载一个包含所有平台二进制文件的压缩包如godot-cpp-template.zip。解压后里面应该包含类似linux.x86_64/、windows.x86_64/、macos.arm64/的目录每个目录里都有对应平台的.gdextension文件和动态库。创建发布版本在 GitHub 仓库的“Releases”页面点击“Draft a new release”。填写版本号如v1.0.0和发布说明。将上一步下载的构建产物压缩包直接拖到文件附件区域。发布后其他用户就可以直接从 Releases 页面下载预编译好的多平台插件无需自己编译。自定义工作流你可以编辑make_build.yml来满足特定需求例如只编译特定平台。在构建前运行单元测试。自动将构建产物上传到其他存储服务。根据 Git 标签自动创建发布。6. 实战中常见问题与深度排查指南即使有了完善的模板在实际开发中你依然会遇到各种问题。下面是一些我踩过坑后总结的常见问题及其解决方案。6.1 编译失败问题排查表问题现象可能原因解决方案scons命令报错提示找不到godot-cpp头文件godot-cpp子模块未初始化或更新。运行git submodule update --init --recursive。链接错误提示undefined reference to ‘godot::…’1.godot-cpp库未编译。2. 你的 C 文件没有正确链接到godot-cpp库。1. 确保先成功运行过scons它会自动编译godot-cpp。2. 检查SConstruct中LIBS列表是否包含了godot-cpp的库名如godot-cpp。编译成功但 Godot 编辑器启动时报错无法加载扩展1.entry_symbol名称不匹配。2..gdextension文件中的库路径或文件名错误。3. 依赖的 Godot 引擎版本不匹配。1. 仔细核对gdextension文件的entry_symbol和register_types.cpp中的函数名是否完全一致包括大小写。2. 检查project/bin/下对应平台的子目录中动态库文件是否存在且文件名是否与gdextension中library路径指定的完全一致。3. 确认你使用的 Godot 版本与godot-cpp子模块的版本兼容。模板通常追踪 Godot 稳定版使用过新或过旧的 Godot 编辑器可能导致 ABI 不兼容。编辑器能加载但场景中找不到自定义的节点类1. C 类未在register_types.cpp中注册。2. 类的GDCLASS宏使用有误。3. 编译后未重启 Godot 编辑器有时需要。1. 确认在initialize_*_module函数中调用了ClassDB::register_classYourClass()。2. 检查头文件中的GDCLASS(YourClass, ParentClass)拼写是否正确。3. 尝试完全关闭并重新打开 Godot 编辑器。属性在编辑器中可见但不可编辑属性绑定时只绑定了 getter没有绑定 setter或者 setter 方法签名错误。在_bind_methods中确保ClassDB::add_property使用的 setter 和 getter 方法名与ClassDB::bind_method绑定的方法名一致且 setter 方法确实接受一个参数。信号可以连接但发射时 GDScript 收不到1. 信号未在_bind_methods中用ADD_SIGNAL注册。2. C 中发射信号的名称与注册的名称不一致。3. GDScript 中连接信号的对象生命周期已结束。1. 检查_bind_methods中是否有对应的ADD_SIGNAL。2. 确保emit_signal(“signal_name”, …)中的字符串与注册时MethodInfo里的第一个参数完全一致。3. 在 GDScript 的_ready中连接信号并确保持有该信号的对象在发射时仍然存在。6.2 调试技巧与性能考量调试 C 扩展使用print_linegodot-cpp提供了godot::UtilityFunctions::print或直接使用printf输出会显示在 Godot 编辑器的“输出”面板。这是最简单的日志调试法。使用 IDE 调试器以 CLion 或 VS Code 为例。首先用scons targettemplate_debug如果支持编译一个调试版本。然后在 IDE 中配置调试器附加到 Godot 编辑器进程。你需要找到 Godot 可执行文件的路径并设置好符号文件和源文件映射。这样就能在 C 代码中设置断点进行单步调试。处理 Godot 崩溃如果 Godot 因你的扩展而崩溃错误信息可能很模糊。在 Linux/macOS 下可以通过命令行启动 Godot 查看更详细的输出。在 Windows 下可以尝试使用调试器捕获崩溃时的调用栈。性能与内存管理避免每帧频繁的 C - GDScript 调用跨语言调用是有开销的。如果需要在每帧更新中处理大量数据尽量在 C 侧完成计算或者将数据批量传递。理解godot-cpp的引用计数godot-cpp对 Godot 的核心对象继承自godot::Object使用了引用计数。大部分情况下你不需要手动管理内存。但是如果你在 C 中创建了 Godot 对象如godot::Array,godot::Dictionary并长期持有需要注意其生命周期。通常使用局部变量即可它们会在离开作用域时自动释放。使用PackedArray等高效类型当需要向 GDScript 传递大量数据时使用PackedByteArray、PackedFloat64Array等类型比普通的Array更高效。性能剖析Godot 编辑器自带性能剖析器。如果你的扩展导致游戏卡顿用剖析器查看是哪个函数耗时最多然后针对性优化 C 代码。6.3 版本管理与协作建议锁定godot-cpp版本godot-cpp子模块默认指向某个特定提交。这是一个好习惯因为它确保了所有协作者使用相同版本的绑定库避免了因绑定库更新导致的意外编译错误。当你确定要升级到新版本 Godot 时再主动更新子模块到对应的提交。.gitignore模板通常已经配置了合理的.gitignore忽略编译产物如bin/,godot-cpp/bin/和 IDE 配置文件。不要将编译生成的二进制文件提交到仓库。清晰的文档在仓库根目录添加一个README.md说明你的扩展是做什么的如何编译以及简单的使用示例。这对于其他想使用你扩展的开发者至关重要。从官方模板出发你获得的不只是一个项目起点更是一套符合 Godot 生态最佳实践的开发范式。它隐藏了底层的复杂性让你能专注于用 C 的力量去增强你的游戏。当你熟悉了这套流程后甚至可以以此为基础创建属于你自己的、领域特定的 GDExtension 模板进一步提效。记住模板是工具而你的创意和代码才是游戏真正的灵魂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2577069.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;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…