从马里奥银币项目学习2D平台游戏开发:核心机制与Godot实践
1. 项目概述从“马里奥的银币1”说起最近在复古游戏和独立游戏开发的圈子里一个名为“马里奥的银币1”mario1的项目引起了我的注意。乍一看标题你可能会联想到任天堂经典的《超级马里奥》系列尤其是那个标志性的收集品——银币。没错这个项目的核心灵感正是来源于此但它绝不是一个简单的模仿或复刻。mario1本质上是一个使用现代游戏开发工具如Godot、Unity或类似引擎重新诠释经典2D平台跳跃游戏核心机制的独立开发实践项目。它探讨的是如何在保留“跳跃、顶砖块、吃金币/银币、躲避敌人”这些灵魂玩法的同时融入新的设计思路、视觉风格或技术实现从而创造出既熟悉又新鲜的体验。这个项目非常适合几类朋友一是刚入门游戏开发想通过一个结构清晰、目标明确的小项目来练手的新手二是对经典游戏设计原理着迷希望拆解并重建其核心逻辑的爱好者三是独立开发者在寻找一个轻量级的原型进行快速迭代和创意实验。通过完成mario1你不仅能掌握2D物理、碰撞检测、动画状态机、关卡编辑等游戏开发基本功更能深刻理解那些让《超级马里奥》历经数十年仍充满魅力的设计哲学——比如精妙的关卡节奏控制、正负反馈的即时传递以及那种“易于上手难于精通”的微妙平衡。2. 核心玩法与设计思路拆解2.1 经典元素的现代化解构“银币”在原始游戏中通常是作为收集品用于增加分数有时关联着隐藏要素或奖励关卡。在mario1项目中我们可以赋予银币更丰富的设计层次。首先它是最基础的正反馈单元。玩家操控角色触碰到银币时需要立刻获得清晰、愉悦的反馈一个清脆的音效、一个微微放大的闪烁动画、以及UI上计数的即时更新。这种即时奖励是驱动玩家不断探索的核心动力之一。其次银币可以成为关卡引导与节奏调节器。有经验的关卡设计师不会随意摆放银币。它们往往被放置在跳跃路径的弧线顶端暗示着最优的起跳时机和落点或者被排成一条线引导玩家走向隐藏区域或避开陷阱。在mario1中我们可以刻意设计几种银币布局模式弧线形用于指引标准跳跃阶梯形暗示连续跳跃分散形鼓励探索而密集排列形则能制造短期的收集爽感调节关卡节奏。最后我们可以引入简单的银币收集连锁机制。例如在限定时间内连续收集银币可以获得额外分数加成或者当收集到一定数量如50枚时角色临时获得一段时间的加速或无敌状态。这种小规模的系统设计能让基础的收集行为产生策略深度鼓励玩家更积极地规划路线。2.2 角色控制与物理手感打磨马里奥式操作手感的精髓在于“重量感”与“灵活性”的平衡。在mario1中实现这一点需要精细调整物理参数。角色的移动不应是简单的速度赋值而应包含加速度、最大速度、地面摩擦力和空中控制力这几个关键变量。加速度决定了角色从静止到跑起来需要的时间这影响了操作的响应感。值太小会感觉迟钝太大则像在冰面上打滑。最大速度限制了角色的移动上限防止游戏过快失控。地面摩擦力让角色在停止输入指令后能自然减速停下而不是瞬间静止这增加了实感。空中控制力则尤为关键。在经典设计中角色起跳后玩家仍能通过方向键微调其在空中的水平位置但这种控制力通常弱于地面。这既保证了跳跃轨迹的可预测性基础又提供了一定的容错和调整空间是实现那些精妙跳跃的核心。实操心得调整手感是个“玄学”过程没有绝对的最优值。我的方法是先设定一组“感觉还行”的初始参数然后录制一段简单的跑跳操作。反复观看并问自己起步是否跟手急停是否自然跳跃弧线是否符合预期最好的测试是闭着眼睛操作几分钟纯粹凭肌肉记忆和感觉来判断是否舒适。通常需要数十次甚至上百次的微调。2.3 关卡结构从线性到箱庭的思考初代《超级马里奥》的关卡是经典的线性从左到右结构但其中充满了垂直探索和秘密区域。在mario1项目中我们可以从这种结构入手但思考如何加入一些轻度的“箱庭”元素。所谓箱庭可以理解为在一个相对紧凑、精心布置的空间内设计多条路径和多种互动可能性。例如一个基础的mario1关卡可以这样设计主要目标是从A点到达B点的旗杆。但在主路径上我们设计一个高台上面有一排显眼的银币。直接跳跃无法到达。玩家需要注意到高台下方有一个隐藏的砖块顶开后可能获得一个“超级蘑菇”变大然后才能顶碎上方的普通砖块开辟第二条通往高台的路径。或者玩家也可以选择继续前进在后面区域找到一条管道进入地下区域从另一端绕回到这个高台的后方。这种设计鼓励观察、记忆和回溯用较小的关卡尺寸承载了更多的探索内容。对于独立开发者来说这种设计思维比单纯制作一个很长的线性关卡更有训练价值。3. 核心技术实现与工具选型3.1 游戏引擎的选择Godot vs Unity vs 其他对于mario1这类2D像素风或精致2D风格的项目引擎的选择至关重要。Godot近年来在独立2D游戏开发领域势头迅猛。它的场景树Scene Tree节点架构非常直观与2D游戏的对象化思维天然契合。内置的2D物理引擎、轻量级的动画播放器AnimationPlayer和可视化着色器编辑器对于实现马里奥式的游戏功能绰绰有余。更重要的是Godot完全开源免费没有版权费用或收入分成对个人和小团队极其友好。其GDScript语言语法类似Python上手快速。Unity依然是行业巨擘资源生态Asset Store无比丰富社区庞大遇到任何问题几乎都能找到解决方案。对于2D游戏Unity的成熟度毋庸置疑其新的2D渲染管线和Tilemap系统也非常强大。但Unity的学习曲线相对陡峭组件Component体系需要时间理解且对于超小型项目可能略显“重型”。其他选项像GameMaker Studio和Construct这类专门针对2D的引擎以“低代码”或可视化编程为特色能极大提升原型开发速度。如果你更关注快速实现玩法而非深入技术细节它们是绝佳选择。我的选择与理由对于纯粹的、以学习和实践经典2D机制为目标的mario1项目我强烈推荐Godot。它的设计哲学清晰从零开始构建一个角色控制器、碰撞系统、关卡管理器所涉及的代码和节点结构能让你更透彻地理解游戏对象、物理、渲染之间的关系而不是被引擎的复杂性和大量的插件所淹没。这份“亲手搭建”的理解是新手最宝贵的财富。3.2 核心系统实现要点3.2.1 角色控制器Character Controller这是游戏手感的心脏。不建议在项目初期直接使用引擎内置的“平台角色控制器”组件如果有的话而是建议自己基于碰撞体和刚体物理或自定义运动逻辑来实现。一个简化的帧更新逻辑循环如下输入处理每帧获取玩家的水平方向输入如A/D或左右箭头。速度计算根据输入、加速度、最大速度、当前是否在地面等状态计算水平方向的目标速度。跳跃判定检测“跳跃”按键是否在本帧被按下并且角色是否处于“在地面”状态。只有同时满足才施加一个垂直向上的瞬时冲量Impulse或设定一个向上的初速度。应用速度将计算好的速度赋给角色的物理身体或直接更新其位置。状态检测应用移动后立即通过射线检测RayCast或碰撞体检测判断角色底部是否与“地面”层接触更新“在地面”状态。这个状态是下一帧能否跳跃的依据。# 基于Godot GDScript的极简代码思路 extends CharacterBody2D export var max_speed 300.0 export var acceleration 1500.0 export var friction 1200.0 export var jump_force -400.0 # 向上为负 func _physics_process(delta): var direction Input.get_axis(move_left, move_right) if direction ! 0: velocity.x move_toward(velocity.x, direction * max_speed, acceleration * delta) else: velocity.x move_toward(velocity.x, 0, friction * delta) if is_on_floor() and Input.is_action_just_pressed(jump): velocity.y jump_force # 应用重力 velocity.y gravity * delta move_and_slide()3.2.2 碰撞与交互系统游戏中的一切互动都基于碰撞。你需要清晰地定义物理层Physics Layers。例如第1层玩家第2层敌人第3层地面/平台第4层可收集物银币第5层可交互物砖块、水管在引擎的碰撞矩阵中设置哪些层之间需要检测碰撞如玩家与地面、玩家与敌人哪些只需要检测重叠Area2D如玩家与银币。对于银币通常使用Area2D节点。当玩家的碰撞体进入该区域时触发body_entered信号执行收集逻辑播放动画音效、增加计数、销毁银币节点。对于砖块尤其是“”砖块需要区分顶部碰撞和其他面碰撞。通常使用RayCast从玩家顶部向上发射检测是否撞到了砖块底部并且玩家当前是向上移动的velocity.y 0。满足条件时触发砖块的“被顶”事件。3.2.3 动画状态机Animation State Machine马里奥的奔跑、跳跃、滑行、变小/变大等状态切换是游戏表现力的关键。使用动画状态机能优雅地管理这些状态。状态包括Idle待机、Running奔跑、Jumping起跳、Falling下落、Sliding滑行等。状态转换的条件通常是物理状态是否在地面、水平速度大小和输入。在Godot中可以使用AnimationTree和AnimationNodeStateMachine。在Unity中则是著名的Animator Controller。设置的关键在于清晰定义状态和转换条件避免条件冲突导致状态抖动。例如“Jumping”到“Falling”的转换条件可以是velocity.y 0即上升速度转为下降。3.3 美术与音效资源风格定位mario1项目的美术不必追求复古的8位像素风除非你个人特别喜欢。现代2D游戏美术风格多样精致像素风在保持像素颗粒感的同时使用更多颜色、更细腻的动画如跑步时的披风飘动。矢量插画风线条清晰色块干净适合表现卡通、明快的世界。手绘风格更具艺术感和独特性但制作成本较高。对于原型和最小可行产品MVP完全可以使用简单的几何形状方块、圆形和纯色来搭建关卡这被称为“程序美术”Programmer Art。核心是让玩法跑通。音效同理可以从免费音效库如Freesound寻找临时的跳跃声、收集声、碰撞声。一个清脆的“叮”声对于银币收集反馈的提升是立竿见影的。4. 开发流程与项目管理实践4.1 从零开始的迭代开发步骤第0步引擎与项目设置创建新项目设置好目标分辨率例如基于16:9定为1920x1080但游戏内相机视口可能锁定一个较小的像素分辨率如320x180再进行缩放。配置好物理层和碰撞矩阵。第1步移动的方块创建一个简单的矩形碰撞体作为玩家实现最基础的左右移动和重力下落。确保它能站在另一个作为地面的矩形上。第2步实现跳跃加入跳跃逻辑并精细调整重力大小、跳跃力度直到手感“舒适”。这是最需要耐心的一步。第3步创建银币实例化一个Area2D节点添加精灵一个黄色圆形和碰撞形状。编写脚本当玩家进入区域时打印“Coin Collected!”然后销毁自身。第4步构建关卡使用引擎的TileMap瓦片地图系统或直接摆放静态物体搭建第一个简单的测试关卡。包含起跑点、一些平台、一些悬空的银币和一个终点区域。第5步添加敌人与互动创建最简单的敌人如一个来回移动的栗子怪。实现玩家与敌人的碰撞伤害变小或死亡。实现顶砖块功能。第6步集成UI与状态创建UI场景显示银币数量、生命值、时间等。将游戏状态如玩家重生、关卡重置管理起来。第7步打磨与扩展加入更多关卡元素移动平台、弹簧、水管传送门、完善动画和音效、设计多个小关卡。4.2 版本控制与项目管理即使是一个人开发也务必使用Git进行版本控制。在项目根目录创建.gitignore文件忽略引擎生成的临时文件、库文件等。为每个相对独立的功能如“实现基础移动”、“添加跳跃物理”、“完成第一个关卡布局”创建一个分支开发测试完成后再合并到主分支。这能保持主分支的稳定性并清晰记录开发历程。使用一个简单的项目管理工具如Trello、Notion或GitHub Projects来管理你的待办事项Todo List。将功能点拆解成一个个小任务例如“调整跳跃手感 - 增加空中微调参数”、“设计关卡1-2的银币引导路径”、“修复角色在斜坡上滑落的Bug”。每完成一个就勾掉能带来持续的成就感并防止项目失控。5. 常见问题、调试技巧与性能优化5.1 开发中高频问题排查角色抖动或卡进地面/墙体原因最常见于碰撞形状CollisionShape与精灵Sprite尺寸不匹配或者物理引擎的“边界”处理问题。排查在引擎调试模式中开启“可见碰撞形状”和“物理调试”。确保角色的碰撞体略小于其精灵视觉大小特别是在底部。检查移动函数如move_and_slide的参数如floor_max_angle最大地面角度是否合适。解决在Godot中move_and_slide()后使用get_last_motion()和is_on_floor()等函数精确判断状态。确保每一帧的速度和位置更新是基于上一帧的稳定状态。碰撞检测不触发或错误触发原因图层Layer和掩码Mask设置错误。节点没有添加到正确的场景树或者信号Signal没有正确连接。排查双击检查Area2D或CollisionShape2D的层和掩码属性。在代码中打印调试信息确认_ready()函数是否执行信号回调函数是否被调用。解决画一张简单的图层交互图。例如玩家层Layer 1需要检测地面层Mask 3开启并与银币层Mask 4开启进行区域重叠检测。确保双方掩码匹配。动画状态机逻辑混乱原因状态转换条件过于复杂或存在互斥。排查在状态机运行时打印当前状态名和关键条件变量如is_on_floor,velocity.x。解决简化逻辑。优先使用物理状态作为主导条件。例如先根据is_on_floor判断是在“地面状态组”还是“空中状态组”再在组内根据速度细分。5.2 性能优化要点对于2D游戏mario1的性能压力通常不大但好习惯要早养成绘制调用Draw Calls这是2D性能的关键。大量独立的精灵节点会导致绘制调用激增。使用TileMap来绘制静态关卡背景和平台它能将大量相同图块合并批次极大减少绘制调用。对于频繁动态创建/销毁的对象如子弹、特效使用对象池Object Pooling技术而非实时实例化/销毁。物理更新确保只有需要物理模拟的物体才具有物理体RigidBody2D或CharacterBody2D。静态的装饰物使用StaticBody2D或无物理体。合理设置物理引擎的更新频率有时低于帧率的物理更新如30Hz对2D平台游戏来说已经足够平滑。纹理与图集将多个小精灵图片打包成一个大的纹理图集Texture Atlas这能减少GPU纹理切换次数。大多数现代游戏引擎的精灵导入设置或专门的工具如TexturePacker可以自动完成此工作。5.3 测试与反馈不要只靠自己测试。将早期可玩的版本哪怕只有一个跳跃平台和几个银币分享给朋友观察他们如何操作。他们会在你意想不到的地方卡住或者以你从未想过的方式去尝试跳跃。这种外部反馈对于调整关卡设计和操作手感至关重要。建立简单的日志系统记录玩家的死亡位置、收集银币的路径。这些数据能直观地告诉你关卡的哪个部分可能过难哪个区域的银币被所有人忽略。开发mario1这样的项目最大的收获往往不是最终的那个可执行文件而是在解决上述一个个具体问题、调整一个个细微参数的过程中所积累的对游戏开发系统性认知和动手能力。它像是一把钥匙帮你打开了理解经典游戏设计、掌握现代开发工具的大门。当你看到自己操控的角色在一个由你亲手搭建的世界里流畅地奔跑、跳跃、顶出银币并发出清脆声响时那种成就感是无可替代的。这只是一个起点从这里出发你可以去创造任何你想象中的世界。

相关新闻

AI技术写作实战指南:从代码生成到文档撰写的效率提升

AI技术写作实战指南:从代码生成到文档撰写的效率提升

在技术社区里,AI写作工具已经从“黑科技”变成了日常开发的“瑞士军刀”。无论是生成代码注释、撰写技术文档,还是辅助构思博客大纲,AI的介入已经无处不在。然而,在实际使用中,开发者们常常面临一个尴尬的局面&#xf…

2026/10/10 4:14:36 阅读更多 →
花了小半个月吃遍周边,2026年找到食材盲点不踩雷的火锅

花了小半个月吃遍周边,2026年找到食材盲点不踩雷的火锅

花了小半个月吃遍周边,2026年找到食材盲点不口味稳定的火锅一、火锅消费的品质选择核心是什么?2026年,食客挑选火锅时更关注锅底工艺透明度、食材溯源清晰度和菜品风味稳定性,食材盲点不口味稳定的火锅,核心特征是工艺…

2026/9/24 23:31:09 阅读更多 →
GeoServer发布TIFF/IMG影像去黑边全攻略:GDAL处理与SLD样式优化

GeoServer发布TIFF/IMG影像去黑边全攻略:GDAL处理与SLD样式优化

1. 项目概述:为什么我们需要处理影像的黑边问题?在地理信息系统(GIS)和数据可视化领域,将海量的遥感影像、航拍图或专题地图发布为网络地图服务(WMS/WMTS)是一项基础且高频的需求。GeoServer作为…

2026/10/1 20:25:47 阅读更多 →

最新新闻

Obsidian离线插件实战指南:断网不崩、本地全控的生产力底座

Obsidian离线插件实战指南:断网不崩、本地全控的生产力底座

简介:本资源是面向Obsidian深度用户与离线环境工作者的「离线插件大全」,专为无法稳定访问官方插件社区的场景设计,解决第三方插件安装受阻、网络受限时的功能扩展难题。压缩包共967个文件,涵盖686个可直接部署的插件ZIP包&#x…

2026/10/10 4:13:39 阅读更多 →
知识蒸馏到Agent蒸馏:OPD在线策略蒸馏的原理与工程实践

知识蒸馏到Agent蒸馏:OPD在线策略蒸馏的原理与工程实践

知识蒸馏这几年在模型压缩和训练加速上都快被讲烂了,但一旦把问题从“教一个网络模仿另一个网络”换成“教一个 Agent 模仿另一个 Agent”,事情立刻就不一样了。标题里那串词——知识蒸馏、OPD、策略梯度、Agent——看着像四个独立概念,实际是…

2026/10/10 4:13:39 阅读更多 →
新电脑硬盘分区实战指南:从原理到三种落地方法

新电脑硬盘分区实战指南:从原理到三种落地方法

1. 为什么新电脑硬盘分区这件事,比你想象中更重要?“新电脑到手第一件事该干啥?”——很多人会说装软件、调壁纸、连WiFi。但真正决定这台机器未来三年用得顺不顺、卡不卡、重装系统麻不麻烦的,其实是开机后那几分钟里&#xff0c…

2026/10/10 4:13:39 阅读更多 →
医院超声影像系统落地:DICOM接入、存储调阅与DICOMweb实践

医院超声影像系统落地:DICOM接入、存储调阅与DICOMweb实践

简介:医院超声影像系统是一套面向医疗机构的信息化解决方案,围绕护士端与医生端协同工作,覆盖患者登记、排队叫号、超声影像查看与诊断分析等环节,适合医疗软件开发人员、医院信息科人员及医学信息化方向的学习者参考。资源包共23…

2026/10/10 4:13:39 阅读更多 →
PCA9422与STM32L073RZ低功耗电源管理方案设计与I2C配置实战

PCA9422与STM32L073RZ低功耗电源管理方案设计与I2C配置实战

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

2026/10/10 4:13:39 阅读更多 →
多Agent集群24小时稳定运行实战:架构设计、调度机制与运维踩坑记录

多Agent集群24小时稳定运行实战:架构设计、调度机制与运维踩坑记录

这段时间我折腾出来一套 24 小时不停工作的多 Agent 集群,不是那种跑几分钟就结束的 demo,而是真的把一堆 AI Agent 挂在后台,日复一日地处理任务。这个项目从构思到稳定运行,前前后后踩了不少坑,也把很多藏在文档背后…

2026/10/10 4:12:39 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →