Unity状态机实战:复刻空洞骑士BOSS毛里克的行为逻辑与碰撞交互

发布时间:2026/11/13 20:36:51

Unity状态机实战:复刻空洞骑士BOSS毛里克的行为逻辑与碰撞交互 1. 项目概述与核心思路最近在复刻《空洞骑士》的第二个BOSS“燥郁的毛里克”这活儿挺有意思。毛里克这个BOSS在游戏里给人的第一印象就是“莽”——横冲直撞压迫感十足但它的行为模式其实很有规律是学习Unity中状态机State Machine和物理碰撞交互的绝佳案例。很多朋友在做独立游戏时BOSS战总是做得要么太简单像个木桩要么太难毫无规律毛里克的设计正好提供了一个“有规律可循的压迫感”的范本。这个Demo的目标很明确在Unity里从零开始实现毛里克的核心行为包括它的移动、攻击、受击反馈以及最重要的——与玩家角色之间那种“刀刀到肉”的碰撞感。我们不会追求和原版一模一样那是法律问题但会抓住其设计精髓利用简单的几个状态巡逻、冲锋、休息配合恰当的动画和物理参数营造出一个让玩家需要观察、记忆并做出反应的战斗体验。这不仅仅是写代码更是对游戏设计逻辑的一次实践。2. 核心设计状态机驱动BOSS行为毛里克的行为逻辑是典型的状态机应用。它不会同时做所有事情而是在特定时间处于特定状态执行特定行为。我们将其核心行为拆解为三个主要状态这比一股脑用if-else堆砌要清晰、易维护得多。2.1 状态定义与切换条件首先我们定义一个枚举Enum来明确状态public enum MourningMawState { Patrolling, // 巡逻状态在平台上左右走动发现玩家后进入准备冲锋状态。 Charging, // 冲锋状态锁定玩家方向加速冲撞。 Resting, // 休息状态冲锋结束后短暂的硬直也是玩家的攻击窗口。 Hurt, // 受击状态被玩家击中后的反馈。 // 未来可以扩展狂暴状态低血量时、死亡状态。 }状态切换是整个BOSS逻辑的“决策层”。我们不能每帧都去检查所有条件那样效率低且混乱。正确的做法是在Update或FixedUpdate中根据当前状态只检查退出当前状态、进入下一个状态的条件。以从Patrolling切换到Charging为例void UpdateStateMachine() { switch (currentState) { case MourningMawState.Patrolling: // 执行巡逻逻辑移动、播放走路动画 PatrolBehavior(); // 状态切换条件发现玩家 if (CanSeePlayer()) { // 触发准备冲锋的动画或特效 animator.SetTrigger(PrepareCharge); // 可以设置一个短暂的准备时间增加紧张感 StartCoroutine(PrepareToCharge()); // 在协程或下一帧将状态切换为 Charging } break; case MourningMawState.Charging: // 执行冲锋逻辑向目标点移动 ChargeBehavior(); // 状态切换条件到达目标点或撞墙 if (HasReachedTarget() || IsCollidingWithWall()) { currentState MourningMawState.Resting; // 播放撞墙或停下的动画 animator.SetTrigger(ChargeEnd); // 启动休息计时器 restTimer restDuration; } break; case MourningMawState.Resting: // 执行休息逻辑播放发呆动画 // 状态切换条件休息时间结束 restTimer - Time.deltaTime; if (restTimer 0) { currentState MourningMawState.Patrolling; // 播放起身或转向动画 } break; } }注意状态切换时一定要处理好“状态进入”和“状态退出”的清理工作。例如从Charging进入Resting时要立刻将BOSS的移动速度归零否则由于物理引擎的惯性它可能会滑行一段距离破坏设计好的硬直感。我通常会在切换状态的方法里写一个EnterState()和ExitState()函数来处理这些。2.2 冲锋行为的实现细节冲锋是毛里克的招牌技能实现起来有几个关键点方向锁定、加速度控制和碰撞检测。方向锁定不是在发现玩家的瞬间就固定一个方向冲到底。原版毛里克有一个短暂的“蓄力”表现头会转向玩家。我们可以这样实现IEnumerator PrepareToCharge() { // 1. 停止巡逻移动 rigidbody2D.velocity Vector2.zero; // 2. 播放转向/蓄力动画 animator.SetBool(IsPreparing, true); // 3. 计算冲锋方向并保存 Vector2 directionToPlayer (playerTransform.position - transform.position).normalized; chargeDirection new Vector2(Mathf.Sign(directionToPlayer.x), 0); // 主要水平冲锋忽略微小垂直差 // 4. 等待一个短暂的蓄力时间给玩家反应 yield return new WaitForSeconds(chargeWindUpTime); // 5. 正式进入冲锋状态 animator.SetBool(IsPreparing, false); animator.SetBool(IsCharging, true); currentState MourningMawState.Charging; }加速度控制为了让冲锋有“启动-高速-停止”的感觉最好不要直接给一个恒定的速度。可以用Physics2D的力或者更简单地用插值Lerp来模拟加速度。void ChargeBehavior() { // currentChargeSpeed 是当前速度 maxChargeSpeed 是最大速度 currentChargeSpeed Mathf.Lerp(currentChargeSpeed, maxChargeSpeed, chargeAcceleration * Time.fixedDeltaTime); // 应用速度注意这里直接设置速度忽略了物理材质可能带来的阻力。如果想更物理可以给刚体AddForce。 rigidbody2D.velocity chargeDirection * currentChargeSpeed; }碰撞检测冲锋时我们需要检测两件事1. 是否撞到墙结束冲锋2. 是否撞到玩家造成伤害。撞墙检测可以用Physics2D.Raycast或BoxCast在BOSS前方发射一条短射线。更简单可靠的方法是在BOSS的“头部”挂一个子物体上面带一个BoxCollider2D设为Trigger专门用来检测“墙”图层Wall Layer。// 挂在头部Trigger上的脚本 void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.layer LayerMask.NameToLayer(Wall)) { // 通知父物体BOSS撞墙了 parentController.OnHitWall(); } else if (other.gameObject.CompareTag(Player)) { // 对玩家造成伤害 PlayerHealth playerHealth other.GetComponentPlayerHealth(); if (playerHealth ! null) { playerHealth.TakeDamage(chargeDamage); // 可以在这里添加击退效果 Vector2 knockbackDir (other.transform.position - transform.position).normalized; playerHealth.ApplyKnockback(knockbackDir, knockbackForce); } } }实操心得碰撞检测的分层处理至关重要。务必在Unity的Edit - Project Settings - Physics 2D中仔细设置图层碰撞矩阵Layer Collision Matrix。让BOSS的伤害触发器只与玩家图层交互让墙触发器只与环境图层交互避免误判。我曾经因为没设置好导致BOSS的冲锋被自己的另一个碰撞器挡住调试了半天。3. 动画控制器Animator与状态同步动画是BOSS的灵魂。毛里克从悠闲的爬行到警觉的转向再到狂暴的冲锋最后撞晕的呆滞这一连串动画必须和我们的代码状态机完美同步。3.1 Animator Controller 布局在Animator窗口中我们同样建立一个状态机其状态和代码中的MourningMawState一一对应Patrol(Blend Tree): 包含 idle 和 walk 的混合根据速度参数混合。PrepareCharge: 蓄力、转向动画。Charge: 冲锋动画。Rest: 撞晕后的发呆动画。Hurt: 受击闪白或后退动画。状态之间的转换Transitions不要用“Exit Time”动画播放完自动转换而应该全部由代码通过Animator的参数Parameters来控制。这是保持代码逻辑主导权的关键。(注此处为文字描述实际博文可配图)我们设置以下参数Bool参数:IsPatrolling,IsPreparing,IsCharging,IsRestingTrigger参数:TakeDamage(用于受击)在代码中切换状态时我们只设置一个Bool为true其他相关的Bool必须手动设为false。void SwitchAnimationState(MourningMawState newState) { // 首先将所有主要的Bool参数重置为false animator.SetBool(IsPatrolling, false); animator.SetBool(IsPreparing, false); animator.SetBool(IsCharging, false); animator.SetBool(IsResting, false); // 然后根据新状态激活对应的参数 switch (newState) { case MourningMawState.Patrolling: animator.SetBool(IsPatrolling, true); break; case MourningMawState.Charging: animator.SetBool(IsCharging, true); break; case MourningMawState.Resting: animator.SetBool(IsResting, true); break; // PrepareCharge状态可能由Trigger触发见下面 } } // 对于受击这种瞬时动画用Trigger public void GetHurt() { currentState MourningMawState.Hurt; animator.SetTrigger(TakeDamage); // 受击动画播放完后通过动画事件Animation Event回调将状态切回Patrolling或Resting }3.2 动画事件Animation Events的应用有些逻辑必须卡在动画的某一帧执行。比如在PrepareCharge动画的最后一帧才正式将代码状态切换为Charging并开始移动。在Charge动画中某一帧是“攻击生效帧”在这一帧才启用伤害碰撞体避免动画刚开始就判伤。在Hurt动画播放完毕后自动回到Patrolling状态。这就要用到动画事件。在Animation窗口选中动画片段可以在时间线上添加事件点并指定一个脚本中的函数。// 在PrepareCharge动画最后一帧调用的函数 public void OnPrepareChargeAnimEnd() { if (currentState MourningMawState.Patrolling) // 安全校验 { currentState MourningMawState.Charging; SwitchAnimationState(MourningMawState.Charging); // 开始冲锋移动逻辑 BeginCharge(); } } // 在Charge动画的攻击帧调用的函数 public void EnableHitbox() { chargeHitboxCollider.enabled true; // 启用伤害碰撞体 } public void DisableHitbox() { chargeHitboxCollider.enabled false; // 禁用伤害碰撞体 }注意事项动画事件非常强大但也容易出错。一定要确保事件绑定的函数名在脚本中完全一致且是public的。调试时如果动画逻辑没执行首先检查动画事件是否成功添加动画片段上会有白色小三角标记以及函数是否被正确调用。我建议在事件函数里先写一句Debug.Log(“事件被调用”)来确认。4. 物理与碰撞打造扎实的手感《空洞骑士》的手感之所以出色物理和碰撞反馈功不可没。对于毛里克这样的冲撞型BOSS我们需要调整好刚体、碰撞体和物理材质。4.1 刚体与碰撞体设置Rigidbody 2DBody Type: 选择Dynamic。这样它才会受到重力、力并能与其他动态刚体如玩家发生真实的物理交互。Gravity Scale: 设为1。让它正常下落站在平台上。Constraints: 冻结Rotation的Z轴。防止它被撞得东倒西歪除非你想要那种滑稽的效果。Collision Detection: 对于高速移动的物体建议使用Continuous连续碰撞检测防止“穿墙”或“穿过玩家”。碰撞体Collider 2D通常需要两个碰撞体主碰撞体如BoxCollider2D用于与环境地面、墙壁的物理碰撞。大小贴合BOSS的“身体”。伤害触发器如另一个BoxCollider2D作为子物体放在BOSS的“头部”或“攻击部位”。这个碰撞体设为Is Trigger true专门用于检测与玩家的伤害接触。它的形状和大小可以比主碰撞体更夸张一些以匹配攻击动画的视觉范围。4.2 物理材质Physics Material 2D这是调节“手感”的魔法参数。给BOSS的主碰撞体附加一个物理材质。Friction摩擦力可以调低一些比如0.1-0.3这样它在冲锋停止后不会感觉“粘”在地上滑动更顺滑。但也不能为0否则在巡逻时可能打滑。Bounciness弹性设为0。我们不希望它撞墙后弹回来。对于玩家的碰撞体也可以单独配置一个物理材质例如将摩擦力调到一个合适值使其在BOSS的冲撞下能被“推开”但又不会滑得失去控制。4.3 受击反馈与击退当玩家攻击命中BOSS时反馈要即时、清晰。视觉反馈最简单有效的方法是改变精灵Sprite的颜色。使用一个协程Coroutine快速将颜色变白再恢复。public SpriteRenderer spriteRenderer; public Color hurtColor Color.white; public IEnumerator FlashHurt() { Color originalColor spriteRenderer.color; spriteRenderer.color hurtColor; yield return new WaitForSeconds(0.1f); // 闪白持续时间 spriteRenderer.color originalColor; }物理反馈给BOSS一个短暂的、小小的反向击退。这能增强命中的“重量感”。public void TakeDamage(Vector2 hitPoint, float knockbackForce) { // 计算击退方向从攻击点指向BOSS中心的反方向 Vector2 knockbackDir (transform.position - new Vector3(hitPoint.x, hitPoint.y, 0)).normalized; // 应用一个瞬间的力 rigidbody2D.AddForce(knockbackDir * knockbackForce, ForceMode2D.Impulse); // 启动协程播放受击动画和闪白 StartCoroutine(FlashHurt()); animator.SetTrigger(TakeDamage); // ... 扣血逻辑 }技巧击退力knockbackForce的大小需要反复调试。太小了没感觉太大了会把BOSS推下悬崖或者破坏你的战斗节奏。可以把它做成一个可调节的公开变量在Inspector里实时调整。5. 实战配置与调试记录理论说再多不如动手配一遍。下面是我在Unity中一步步配置毛里克Prefab的过程和关键参数。5.1 游戏对象结构与组件创建空对象MourningMaw作为根节点。添加子对象Sprite挂载SpriteRenderer放入毛里克的精灵图。在根节点添加组件Rigidbody 2D: Body TypeDynamic, Gravity Scale1, ConstraintsFreeze Rotation Z, Collision DetectionContinuous。BoxCollider 2D: 调整大小大致包围身体用于物理碰撞。MourningMawController(自定义脚本)核心逻辑脚本。Animator: Controller 指向我们创建好的MourningMaw_AC。创建子对象ChargeHitbox位置调整到BOSS的头部前方。组件BoxCollider 2D勾选Is Trigger。在MourningMawController脚本中引用它用于冲锋时的伤害检测。创建子对象WallCheck位置紧贴ChargeHitbox前方。组件BoxCollider 2D勾选Is Trigger。专门用于检测墙壁图层设为只和Wall层交互。5.2 关键脚本参数公开与调试在MourningMawController脚本中将需要频繁调试的变量设为public或[SerializeField]public class MourningMawController : MonoBehaviour { [Header(Movement Settings)] [SerializeField] private float patrolSpeed 2f; [SerializeField] private float maxChargeSpeed 15f; [SerializeField] private float chargeAcceleration 5f; [SerializeField] private float chargeWindUpTime 0.8f; // 蓄力时间 [Header(State Durations)] [SerializeField] private float restDuration 2f; // 休息多久 [Header(Detection)] [SerializeField] private float sightRange 8f; // 发现玩家的距离 [SerializeField] private LayerMask playerLayer; // 在Inspector中指定玩家所在图层 [Header(Attack)] [SerializeField] private int chargeDamage 1; [SerializeField] private float playerKnockbackForce 5f; [Header(Components)] [SerializeField] private Rigidbody2D rb; [SerializeField] private Animator animator; [SerializeField] private Collider2D chargeHitbox; [SerializeField] private Transform wallCheck; // ... 其他代码 }在Unity编辑器中将这些参数拖拽赋值然后就可以在Play模式下一边测试一边实时调整这些数值直到手感满意为止。5.3 调试技巧使用Gizmos可视化为了在Scene视图中清晰地看到BOSS的检测范围、冲锋方向等可以重写OnDrawGizmos或OnDrawGizmosSelected方法。void OnDrawGizmosSelected() { if (sightRange 0) { // 绘制视野范围 Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, sightRange); } if (chargeDirection ! Vector2.zero) { // 绘制当前冲锋方向 Gizmos.color Color.red; Gizmos.DrawRay(transform.position, chargeDirection * 3); } if (wallCheck ! null) { // 绘制墙检测区域 Gizmos.color Color.blue; Gizmos.DrawWireCube(wallCheck.position, wallCheck.GetComponentBoxCollider2D().bounds.size); } }6. 常见问题与排查实录在实现过程中我踩过不少坑。这里把典型问题和解决方案列出来希望能帮你节省时间。问题现象可能原因排查与解决方案BOSS冲锋时直接穿过了玩家或墙壁。1. 移动速度过快单帧位移超过了碰撞体厚度。2. 碰撞检测类型为Discrete离散。3. 碰撞图层Layer没有正确设置双方无法交互。1. 降低最大速度或使用Rigidbody2D.MovePosition配合Physics2D.SyncTransforms慎用。2. 将Rigidbody 2D的Collision Detection设为Continuous。这是最有效的办法。3. 检查Physics 2D设置中的图层碰撞矩阵确保BOSS和玩家/墙的图层是互相关联的。动画状态和代码逻辑不同步比如已经在冲锋了但还在播放巡逻动画。1. 动画状态机的转换条件设置错误如过度依赖Exit Time。2. 代码中修改了Animator参数但没等到下一帧动画状态机才更新。3. 多个脚本在修改同一个Animator参数产生竞争。1.将所有动画转换条件改为由代码参数Bool/Trigger驱动禁用Exit Time。2. 确保在改变状态后立即调用animator.Update(0)不推荐影响性能或理解动画状态机是帧更新的接受一帧延迟。3. 统一由一个脚本如MourningMawController管理Animator的所有参数。BOSS受击后击退效果太强直接飞出了战场。1. 击退力knockbackForce数值过大。2. 受击时没有临时取消BOSS自身的移动控制物理力叠加。1. 将knockbackForce调小例如从100调到10慢慢测试。2. 在受击瞬间将BOSS的rigidbody2D.velocity归零并暂停状态机逻辑一小段时间如0.3秒。冲锋伤害触发器一次攻击对玩家造成多次伤害。OnTriggerStay2D在碰撞持续每帧都会调用或者伤害触发器没有及时关闭。1. 避免在OnTriggerStay2D中造成伤害。改用OnTriggerEnter2D。2.采用“攻击生效帧”模式在动画事件中启用伤害触发器在攻击动画末尾或另一个动画事件中立即禁用。3. 添加伤害冷却例如在造成一次伤害后设置一个短暂的布尔锁期间不再造成伤害。BOSS在平台边缘巡逻时直接掉下去。巡逻AI没有进行地面检测。在巡逻移动前使用Physics2D.Raycast或OverlapCircle检测身体前方和下方是否有“地面”图层。如果没有则触发转向。我个人最深刻的体会是状态机是管理复杂游戏对象行为的利器但一定要保证状态的纯净和切换的明确。初期我图省事把一些条件判断散落在Update各处很快就导致了状态混乱比如BOSS在休息时突然又冲了起来。后来严格遵循“一个状态只做一件事状态切换集中判断”的原则逻辑立刻清晰了调试也容易得多。最后关于这个BOSS的扩展你可以尝试为它添加一个“低血量狂暴”状态冲锋速度更快休息时间更短或者增加一个“远程吐痰”的技能丰富战斗阶段。最重要的是多玩几遍《空洞骑士》里毛里克的战斗用心感受它的节奏和反馈这些感觉才是我们通过代码真正要还原的东西。
延伸阅读

更多相关文章

2026/11/12 9:07:16

蒸汽教育案例库怎么看?不同背景留学生能找到哪些参考

搜索“蒸汽教育怎么样”时,很多留学生和家长会先进入蒸汽教育案例库,寻找与自己学校、专业或目标公司相近的案例。 但真正开始比较后,学生常常发现:即使两个人都读计算机,求职问题也可能完全不同。有人简历条件不错&am…

2026/11/11 3:33:59

2025靠谱论文查重网站口碑推荐|全方位论文查重优化解决方案,Paperyy/论文查重/万方查重,论文查重网站有哪些

一、学术查重行业现状与毕业生核心痛点解析 当下高校毕业论文、硕博学位论文、期刊投稿文稿均对原创重复率、AI生成内容合规性有着严苛审核标准,知网、维普、万方等校内指定系统检测门槛高、初稿检测成本高昂,大量毕业生面临查重结果偏差大、降重生硬不通…

2026/11/11 0:16:43

蒸汽教育怎么样?留学生看求职机构,别只看案例和导师

一名准备申请北美数据岗位的留学生,连续看了十几篇求职机构文章。 有的文章介绍名企导师,有的展示学生进入了哪些公司,还有的强调项目、内推和长期陪跑。信息看起来很多,但他看完之后仍然无法回答一个最基本的问题:自己…

2026/11/13 20:32:43

初级电工作业模拟考试怎么过?上岗必备实操技巧全解析

初级电工作业模拟考试怎么过?上岗必备实操技巧全解析 实操考试心里没底怕挂科?初级电工作业模拟考试作为电工证上岗必备的门槛,很多人在考试前总是焦虑,担心自己准备不充分、操作不熟练,最终导致考试失败。别急,今天就用一个真实案例,带你了解考试的关…

2026/11/13 20:32:43

成都电工证去哪考好些 官方回应实操考试心里没底怕挂科

成都电工证去哪考好些 官方回应实操考试心里没底怕挂科 实操考试心里没底怕挂科,这是很多准备报考成都电工证的人最真实的心声。尤其是对于南阳中小施工企业的负责人来说,电工证不仅关系到日常作业的安全性,也直接影响企业能否合规开展业务。 官方回应…

2026/11/13 20:32:43

罗庄区电工证哪里考取?2026河南最新政策下怎么考最稳妥

罗庄区电工证哪里考取?2026河南最新政策下怎么考最稳妥 你现在最头疼的事是什么? 急需特种作业证才能上岗找工作 !尤其是在南阳卧龙区,房建工程行业对特种作业证的需求越来越大,特别是电工证,几乎成了入职门槛之一。2026年河南最新政策对电工…

2026/11/13 20:32:43

焦作市低压电工证考试含金量高不高?实操考试心里没底怕挂科

焦作市低压电工证考试含金量高不高?实操考试心里没底怕挂科 实操考试心里没底怕挂科,这是很多准备考取焦作市低压电工证的电工朋友最担心的问题。尤其是在2023年底,随着各地市安全生产教育培训中心对特种作业操作证的监管越来越严格,很多电工在备考时…

2026/11/13 20:32:43

低压电工证需要实践吗?通过率揭秘实操考试不再怕挂科

低压电工证需要实践吗?通过率揭秘实操考试不再怕挂科 实操考试心里没底怕挂科?你不是一个人。低压电工证不仅是进入电力行业的“敲门砖”,也是提升个人职业竞争力的关键。很多人担心,低压电工证是否真的需要动手操作?实操考试到底难不难?这篇文章就来…

2026/11/13 20:27:37

湖北初级电工证考试怎么考?郑州报考避坑指南全在这里

湖北初级电工证考试怎么考?郑州报考避坑指南全在这里 证书过期了不知道怎么复审延期?别慌,今天咱们就来聊聊【湖北初级电工证考试】的那些事,特别是 郑州报考避坑指南 ,让你少走弯路、不花冤枉钱。 为什么说湖北初级电工证考试是电工从业的第一步?…

2026/11/12 20:35:13

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/13 0:01:06

办上海高压电工证良心建议:别被中介坑了,自己看这篇就够了

办上海高压电工证良心建议:别被中介坑了,自己看这篇就够了 你是不是也像我一样,第一次听说“高压电工证”这个名词,就感觉一头雾水?其实很多人都跟我一样,不知道去哪报名、怕被中介坑,结果花了冤枉钱还考不上。今天我就从一个在商丘培训过的学员角度,…

2026/11/13 0:01:06

黎里考电工证在哪里值不值得考

黎里考电工证在哪里值不值得考 之前考的证在外省能不能转过来?这是很多在外地工作、准备回老家发展的朋友最关心的问题。特别是在黎里地区,很多从事电力相关工作的朋友,都面临这样一个难题: 外省的电工证能否在本地使用?值不值得考?…

2026/11/13 0:01:06

毕节哪里考电工证 怎么报名学历不高也能上手

毕节哪里考电工证 怎么报名学历不高也能上手 你是不是也和我一样,学历不高,想着考个电工证,但又担心自己条件不够,怕报不上名?其实不然,只要符合条件,学历不高也能考电工证。今天就来聊聊【毕节哪里考电工证】这个问题,还有怎么报名,让你一步到位不…

2026/11/12 22:10:13

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/13 12:14:39

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑