UE4集成C#开发:USharp插件安装配置与实战指南
1. 项目概述USharp是什么以及为什么你需要它如果你是一名长期使用C#进行游戏逻辑开发的Unity开发者或者是一位对C#的现代语法和生态情有独钟的程序员现在却因为项目需求或技术探索需要踏入Unreal Engine 4UE4的世界那么你大概率会遇到一个核心痛点语言切换的阵痛。UE4的官方脚本语言是C这对于习惯了C#的快速迭代、垃圾回收和丰富类库的开发者来说学习曲线陡峭开发效率在初期会大打折扣。USharp这个插件的出现正是为了解决这个“水土不服”的问题。它不是一个简单的语法转换器而是一个旨在将C#语言和.NET运行时深度集成到UE4引擎中的桥梁让你能够在UE4项目中使用C#来编写游戏逻辑、编辑器工具甚至是部分引擎扩展。简单来说USharp让你在UE4里“写C#得C的性能与UE的生态”。这听起来很美好但它的安装与配置过程相比起在Unity中直接开箱即用C#要复杂和“硬核”得多。这主要是因为UE4本身是一个用C构建的庞然大物要将另一个运行时.NET和语言C#无缝地嵌入进去涉及到复杂的原生互操作、内存管理和构建流程。网络上关于USharp的资料相对零散官方文档也可能因为版本迭代而滞后导致很多开发者在第一步——安装上就卡住了。本文将基于我多次在Windows环境下为不同UE4版本4.24-4.27配置USharp的实际经验为你提供一份详尽、可复现的安装与使用指南并深入剖析其中的关键环节和常见陷阱。2. 环境准备与前置条件解析在动手之前我们必须确保基础环境完全就绪。USharp的安装不是简单的“下一步、下一步”它严重依赖一系列特定版本的工具链。任何一个环节的版本不匹配都可能导致后续编译失败或运行时崩溃。2.1 核心软件版本锁定这是最重要的一步请严格按照以下清单核对你的环境Unreal Engine 4版本USharp对UE4版本有严格要求。目前社区维护的版本主要支持UE 4.24 - UE 4.27。强烈建议使用UE 4.26或4.27这两个版本的兼容性和社区测试最为充分。避免使用UE5因为USharp的核心逻辑与UE5的模块和构建系统有较大差异除非你有特定分支版本。Visual Studio版本你需要完整的Visual Studio而不仅仅是VS Code。推荐使用Visual Studio 2019版本16.11或更高或Visual Studio 2022。在安装时必须勾选以下工作负载使用C的桌面开发这是编译UE4源码和USharp原生插件所必需的。.NET桌面开发这是编译C#项目所必需的。确保安装了对应版本的.NET SDK例如.NET Framework 4.7.2 或 .NET 6/8具体取决于USharp项目配置。.NET SDK根据你下载的USharp源码要求安装对应的.NET SDK。通常需要**.NET 6.0 SDK**或更高版本。你可以在命令行输入dotnet --version来验证。Git用于克隆USharp的源代码仓库。Python 3UE4的构建系统UnrealBuildTool依赖Python。确保Python 3如3.7或3.9已安装并添加到系统环境变量PATH中。注意切勿混用版本。例如不要试图用为UE4.26编译的USharp插件用在UE4.25的项目上。源码构建是必须的。2.2 获取USharp源代码USharp的主要开发在GitHub上进行。你需要克隆主仓库及其子模块。# 打开Git Bash或命令提示符进入你准备存放引擎插件的目录例如 D:\UE4Plugins git clone --recursive https://github.com/UnrealSharp/UnrealSharp.git--recursive参数至关重要因为USharp依赖一些子模块如用于C#/C交互的核心库。如果克隆时忘记此参数可以进入目录后执行git submodule update --init --recursive。2.3 理解USharp的目录结构克隆完成后你会看到类似如下的结构以某一版本为例UnrealSharp/ ├── Engine/ # 这个目录需要被复制或链接到你的UE4引擎源码目录下 │ └── Plugins/ │ └── Marketplace/ # 或 Runtime/ │ └── UnrealSharp/ # 插件主体 ├── UnrealSharp.sln # 用于生成C#核心库的Visual Studio解决方案 ├── README.md └── ...关键点在于Engine/Plugins/这个目录。USharp设计为一个引擎插件Engine Plugin这意味着它需要被放置在UE4引擎的源码目录中而不是单个项目里。这样所有基于该引擎创建的项目都能使用它。3. 编译与安装从源码到可用的插件这是整个流程中最具挑战性的一环。我们将分步拆解。3.1 编译C#核心库UnrealSharp.RuntimeUSharp的“大脑”是一个用C#编写的运行时库Runtime它负责C#与UE4 C之间的通信序列化、函数调用、垃圾回收协调等。使用Visual Studio打开解决方案导航到源码根目录双击打开UnrealSharp.sln。还原NuGet包首次打开时Visual Studio通常会自动开始还原NuGet包。如果没有请在“解决方案资源管理器”中右键点击解决方案选择“还原NuGet包”。确保网络通畅因为需要下载一些依赖。选择正确的构建配置在顶部的工具栏确保解决方案配置为Development或Shipping解决方案平台为Any CPU或x64根据提示。通常Development|Any CPU是安全的起点。生成解决方案右键点击解决方案选择“生成解决方案”。如果一切顺利你将在UnrealSharp.Runtime/bin/Development/或类似目录下找到生成的UnrealSharp.Runtime.dll等文件。实操心得如果编译失败首先检查.NET SDK版本是否与项目文件.csproj中指定的TargetFramework一致。常见的错误是缺少某些NuGet包可以尝试在包管理器控制台中执行dotnet restore。3.2 部署插件到UE4引擎这是将插件“安装”到引擎的关键步骤。假设你的UE4源码位于D:\UE4\UnrealEngine-4.27。定位目标目录在USharp源码中找到Engine/Plugins/Marketplace/UnrealSharp或Engine/Plugins/Runtime/UnrealSharp这个文件夹。整个UnrealSharp文件夹就是我们的插件。复制插件将上述UnrealSharp文件夹完整地复制到你的UE4引擎源码的对应位置D:\UE4\UnrealEngine-4.27\Engine\Plugins\Marketplace\。如果Marketplace目录不存在可以复制到Engine/Plugins/Runtime/下。验证关键文件复制后确保在目标路径下存在以下核心文件UnrealSharp.uplugin插件的描述文件。Source/目录里面包含插件的C模块代码。Managed/目录这里应该存放你上一步编译好的C# DLL文件。通常你需要手动将UnrealSharp.Runtime.dll及其可能存在的依赖项如UnrealSharp.Runtime.Core.dll复制到Managed/下的某个子目录如Binaries/。这一步非常关键且易错很多安装失败源于DLL位置不对或缺失。请仔细查阅你所下载的USharp版本的README.md看是否有明确的部署说明。3.3 编译UE4引擎或仅编译插件由于我们添加了一个新的C引擎插件必须重新编译UE4引擎或者至少编译这个插件模块。生成项目文件在UE4引擎源码根目录D:\UE4\UnrealEngine-4.27下找到GenerateProjectFiles.batWindows并运行它。这个脚本会读取所有插件目录下的.uplugin和.Build.cs文件更新解决方案。打开UE4解决方案运行完成后用Visual Studio打开生成的UE4.sln或UnrealEngine.sln。编译方案A完整编译引擎在VS中将解决方案配置设为Development Editor平台为Win64然后右键点击解决方案选择“生成解决方案”。这需要很长时间可能数小时。方案B仅编译插件在解决方案资源管理器中找到Plugins/Marketplace/UnrealSharp下的几个项目如UnrealSharp、UnrealSharpEditor右键点击它们并选择“生成”。这通常更快。但有时插件与引擎模块有依赖如果编译失败可能仍需采用方案A。踩坑记录编译过程中最常见的错误是“无法找到UnrealSharp.Runtime.dll”。这几乎总是因为上一步中C# DLL没有正确复制到插件的Managed/目录下或者路径在*.Build.cs文件中配置错误。你需要打开插件的C#项目属性或*.Build.cs文件检查RuntimeDependencies或PrivateDependencyModuleNames中关于托管DLL的路径设置。4. 在UE4项目中使用USharp假设引擎编译成功现在你可以创建或打开一个UE4项目来使用C#了。4.1 创建或启用插件启动UE4编辑器使用你刚刚编译好的、包含USharp的引擎版本启动Unreal Editor。创建测试项目新建一个空的C项目例如USharpTest。重要必须选择C项目而非蓝图项目。因为USharp插件本身是C的它需要C项目作为宿主来加载。启用插件在编辑器内点击菜单栏的编辑(Edit) - 插件(Plugins)。在插件浏览器中左侧分类找到“脚本(Scripting)”或“所有(All)”然后在右侧搜索“UnrealSharp”。你应该能看到“UnrealSharp Plugin”。勾选其旁边的“已启用(Enabled)”复选框然后根据提示重启编辑器。4.2 创建你的第一个C#类编辑器重启后USharp应该已经激活。设置C#项目在内容浏览器中右键点击你应该能看到一个新的上下文菜单项例如“New C# Class...”或“UnrealSharp - Create C# Class”。如果没有可能需要检查插件是否加载成功。选择基类点击后会弹出一个类似创建蓝图或C类的对话框让你选择父类例如UObject、AActor、UActorComponent等。选择AActor并命名如MyCSharpActor。项目结构生成这会在你的UE4项目目录下与Content/同级创建一个Managed/文件夹或Script/里面包含一个C#类库项目文件.csproj和你刚创建的MyCSharpActor.cs文件。编写C#代码用Visual Studio或Rider打开这个.csproj文件。你会在C#类中看到熟悉的结构但使用了USharp提供的特性Attributes。using UnrealSharp; using UnrealSharp.Attributes; using UnrealSharp.Engine; [UClass] public class MyCSharpActor : AActor { [UProperty(EditAnywhere, BlueprintReadWrite)] public float Speed { get; set; } 100.0f; public override void BeginPlay() { base.BeginPlay(); UnrealSharpLog.LogInfo($MyCSharpActor BeginPlay! Speed is {Speed}); } public override void Tick(float deltaTime) { base.Tick(deltaTime); // 用C#编写你的逻辑 var currentLocation GetActorLocation(); currentLocation.X Speed * deltaTime; SetActorLocation(currentLocation); } }编译C#项目在IDE中编译这个C#项目。USharp的构建系统会监听DLL的变化并将其热重载到运行的编辑器中。在UE编辑器中使用在内容浏览器中你现在可以像使用蓝图或C类一样右键创建基于MyCSharpActor的蓝图或者直接将其拖放到场景中。在细节Details面板中你应该能看到在C#中定义的Speed属性并且可以修改它。4.3 核心交互机制浅析理解以下机制能帮你更好地驾驭USharp避免困惑垃圾回收GC协调这是最大的挑战之一。UE4使用自己的反射和垃圾回收系统针对UObject而.NET也有完整的GC。USharp的核心任务之一就是建立两者之间的“生命周期桥梁”。简单说当一个C#对象被托管GC回收时它需要通知UE4侧对应的原生UObject通常是一个“包装器”或“影子对象”也进行清理反之亦然。这通过复杂的引用计数和弱引用机制实现。序列化与反射C#类中的[UProperty]、[UFunction]等特性会在编译时或运行时生成额外的数据让UE4的反射系统能够识别这些成员从而在蓝图编辑器中暴露属性、在序列化时保存数据。性能考量每一次从C#调用UE4的C函数如GetActorLocation或反之都涉及一次“托管-原生”的互操作P/Invoke或类似的机制这会有一定的开销。对于每帧调用的Tick函数中的高频操作需要谨慎。USharp团队做了大量优化但对于极限性能场景仍需评估。5. 常见问题、故障排查与进阶技巧即使按照步骤操作你也可能会遇到各种问题。这里记录一些典型情况及解决思路。5.1 编译阶段问题问题现象可能原因排查与解决思路运行GenerateProjectFiles.bat失败或报错Python环境问题、引擎源码损坏、路径包含中文或空格。1. 确认Python 3在PATH中命令行可执行python --version。2. 确保UE4源码路径无中文和空格。3. 尝试以管理员身份运行。4. 查看错误日志通常是某个.Build.cs文件语法错误。编译UE4或USharp插件时报“无法找到UnrealSharp.Runtime.dll”C# DLL未正确部署或路径配置错误。1. 确认Managed/目录在插件文件夹内且包含必要的DLL。2. 检查插件C模块的*.Build.cs文件查看RuntimeDependencies.Add或PublicDelayLoadDLLs中的路径是否正确指向DLL。3. 手动将C#项目输出目录设置为插件Managed/下的对应路径。C#项目编译成功但UE编辑器中不识别新类或属性C# DLL热重载失败UE编辑器未正确加载插件或C#域。1. 尝试完全关闭UE编辑器并重新启动。2. 在编辑器输出日志Output Log中搜索“UnrealSharp”或“C#”查看是否有加载错误。3. 检查项目配置文件中是否启用了插件。打开项目目录下的.uproject文件用文本编辑器确保Plugins列表中包含UnrealSharp且Enabled为true。5.2 运行时与使用阶段问题问题现象可能原因排查与解决思路在C#中调用UE4函数导致编辑器崩溃内存访问违规、类型转换错误、或调用了已销毁的UObject。1. 这是最棘手的问题。首先确保所有从C#引用的UObject都有效不为null。2. 检查函数签名是否完全匹配参数类型、引用/值传递。USharp的绑定可能对某些复杂类型支持有限。3. 使用更保守的代码逐步排查是哪一行导致的崩溃。性能感觉不如纯C托管-原生互操作开销C# GC触发。1. 避免在每帧的Tick中频繁进行大量的小型互操作调用。可以考虑将数据批量处理后再传递。2. 监控托管内存分配避免在热点路径中产生大量短生命周期对象以减少GC压力。3. 对于性能极度敏感的部分仍考虑用C实现。蓝图无法继承或覆盖C#类中标记为[UFunction]的方法反射生成不完整或蓝图虚函数表vtable不匹配。1. 确认C#方法被正确标记为[UFunction(BlueprintCallable, BlueprintOverride)]对于可覆盖的函数。2. 重新编译C#项目并完全重启编辑器。3. 查阅USharp文档了解对蓝图重写支持的具体要求和限制。5.3 进阶配置与优化技巧调试C#代码你可以在Visual Studio中像调试普通.NET程序一样调试你的C#代码。确保你的C#项目编译为Debug配置并且在UE编辑器启动时附加Visual Studio调试器到UE4Editor.exe进程。你需要加载正确的符号你的C# DLL的PDB文件。USharp的热重载机制有时会使调试变得棘手可能需要禁用热重载或使用“调试-附加到进程”的方式。使用第三方.NET库你可以在你的C#项目中通过NuGet添加几乎任何.NET Standard 2.0/2.1兼容的库用于数学计算、JSON解析、网络通信等。这极大地扩展了UE4的功能边界。构建打包Build Shipping要让使用USharp的项目打包成可执行文件你需要确保在打包配置中包含了所有必要的托管DLL和运行时。这通常需要在插件的构建脚本*.Build.cs和项目的打包设置中进行额外配置将Managed/目录下的DLL复制到最终包的合适位置如项目名/Binaries/Managed/。关注内存与对象生命周期始终明确对象的“主人”。如果一个UObject纯粹由C#创建和管理要小心它在蓝图或C中的引用。反之亦然。错误的对象生命周期管理是内存泄漏和崩溃的主要根源。善用UE4编辑器的“引用查看器”和内存分析工具。安装并成功运行USharp只是第一步。真正发挥其威力在于你如何利用C#的生态和开发效率来加速UE4项目的原型验证、工具开发和非核心性能敏感的游戏逻辑实现。它是一把需要精心维护的利器用好了能极大提升团队中C#开发者的生产力用不好则会带来额外的复杂性和调试负担。建议从一个小的、独立的模块开始尝试逐步积累在混合环境下的开发经验。

相关新闻

Unity资源逆向编辑实战:UABEA工具核心原理与AssetBundle解包指南

Unity资源逆向编辑实战:UABEA工具核心原理与AssetBundle解包指南

1. 项目概述:为什么我们需要UABEA?如果你在Unity开发或者游戏模组制作领域摸爬滚打过一段时间,大概率会遇到一个让人头疼的问题:面对一个打包好的.asset文件或者.bundle资源包,你明知道里面有你需要的模型、贴图、音频…

2026/10/19 10:55:23 阅读更多 →
Spring Integration与MQTT协议整合实战指南

Spring Integration与MQTT协议整合实战指南

1. Spring Integration与MQTT协议整合实战指南在企业级系统集成领域,消息驱动架构已成为解耦复杂系统的标配方案。最近我在一个智慧农业项目中,需要将分布在多个温室的传感器数据实时汇聚到中央管理系统,最终选择了Spring Integration MQTT的…

2026/10/19 12:24:46 阅读更多 →
NodeRT核心功能解析:命名空间、异步方法与事件处理全攻略

NodeRT核心功能解析:命名空间、异步方法与事件处理全攻略

NodeRT核心功能解析:命名空间、异步方法与事件处理全攻略 【免费下载链接】NodeRT Winrt APIs-node.js modules generator 项目地址: https://gitcode.com/gh_mirrors/no/NodeRT NodeRT是一款强大的Winrt APIs-node.js模块生成器,它能够为所有Win…

2026/10/17 5:55:06 阅读更多 →

最新新闻

DeepSeek-V3私有化部署实战:客服系统数据安全与SOP全流程指南

DeepSeek-V3私有化部署实战:客服系统数据安全与SOP全流程指南

最近几年,私有化部署这个词在客服系统领域的热度一直没降过。尤其是DeepSeek-V3这种开源大模型出来后,很多团队都在观望:模型效果到底行不行?数据不出场这条红线能不能守得住?一套完整的SOP到底该怎么建?正…

2026/10/19 12:24:42 阅读更多 →
Hadoop+PySpark+Prophet:构建空气质量预测系统的完整实践

Hadoop+PySpark+Prophet:构建空气质量预测系统的完整实践

先说结论:这个项目真正的难点不在“预测”,而在把 Hadoop 和 PySpark 这套分布式处理逻辑,跟 Prophet 这种以 pandas 数据框为核心的单机模型舒服地拼在一起。我前后折腾了大概两周,踩的坑比写的代码还多,今天把完整的…

2026/10/19 12:24:42 阅读更多 →
昇腾NPU上vLLM-Ascend与TRL的GRPO强化学习训练实战

昇腾NPU上vLLM-Ascend与TRL的GRPO强化学习训练实战

先交代背景:我最近在昇腾NPU上把 vLLM-Ascend 和 TRL 的 GRPO 训练链路完整跑通了一遍,从环境搭建、模型推理、训练代码适配到性能调优,前前后后踩了不少坑。这个组合的实际价值在于,GRPO 没有传统的 Critic 模型,策略…

2026/10/19 12:24:42 阅读更多 →
用书匠策AI高效完成课程论文:从选题到润色全流程指南

用书匠策AI高效完成课程论文:从选题到润色全流程指南

每学期期末,课程论文都是让人又爱又恨的存在。爱的是它往往占了不少平时分,恨的是选题、查资料、搭框架、凑字数、调格式,每一步都不省心。我自己在读书那会儿,每逢论文季就像打仗,图书馆跑断腿、文献翻到眼花&#xf…

2026/10/19 12:24:42 阅读更多 →
C# WPF拾色器实战:屏幕取色、颜色转换与调色板管理

C# WPF拾色器实战:屏幕取色、颜色转换与调色板管理

简介:一款基于C# Windows Forms开发的拾色器项目源码包,面向Winform初学者或需要屏幕取色功能的开发者,解决颜色选取、颜色模型转换与界面交互等常见需求。资源包大小约2.07MB,页面未列出具体文件数量与类型,内容以C#工…

2026/10/19 12:24:42 阅读更多 →
Visual Studio与VS Code调试技巧:从断点到多线程的实战指南

Visual Studio与VS Code调试技巧:从断点到多线程的实战指南

你要是问我,写了这么多年代码,最吃功夫、最能看出水平差的环节是哪个,我的答案不是写代码,而是调试。写代码人人都会,但把一个别人写崩的模块救回来、把一个偶现崩溃揪出来、把多线程死锁理清楚,靠的全是调…

2026/10/19 12:23:42 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →