ARPG战斗框架实战:基于虚幻GAS的技能与属性系统设计
1. 为什么ARPG战斗框架需要GAS做ARPG的人迟早会撞上一堵墙战斗逻辑越写越乱。最开始你可能只是想让角色挥一刀扣个血播个特效。写着写着暴击、闪避、霸体、护盾、持续伤害、属性克制、技能连携、冷却缩减、buff叠加规则全涌进来代码从几百行膨胀到几千行最后连自己都不敢改。这不是能力问题是架构问题——用普通的状态机加事件回调去承载一套完整的战斗系统本质上是在用螺丝刀盖楼。GAS全称Gameplay Ability System是虚幻引擎内置的一套技能与属性框架。它把战斗中最容易失控的几个部分——技能、属性、效果、标签——抽象成了独立的模块让它们各司其职又互相联动。我最初接触GAS的时候也觉得它重、概念多、上手曲线陡但真正在一个ARPG项目里跑通之后回头再看那些手写的战斗逻辑感觉就像从手搓轮子换成了流水线。这篇内容适合两类人看一类是正在做ARPG或者动作类项目、战斗逻辑已经开始失控的开发者另一类是听说过GAS但一直没敢下手的同学。我会从整体设计思路讲到核心模块拆解再到实操落地和踩坑排查尽量把每个为什么这么设计讲清楚让你看完能直接在自己的项目里搭出一个可用的战斗框架。2. 战斗框架的整体设计与模块拆解2.1 为什么选GAS而不是自己造轮子先说一个很多人关心的问题GAS到底值不值得用。我的判断标准很简单——如果你的项目满足以下任意两条就应该认真考虑GAS技能数量超过20个且技能之间有交互比如A技能触发B技能的额外效果属性系统复杂存在buff/debuff的叠加、刷新、驱散需求需要网络同步且对战斗一致性有要求团队多人协作需要一套统一的战斗逻辑规范自己造轮子的最大问题不是写不出来而是写到后期维护成本指数级上升。我见过一个项目光伤害计算这一个环节就散落在十几个文件里改一个暴击公式要翻遍整个工程。GAS把这些东西收敛到了AttributeSet和GameplayEffect里改公式只改一个地方。当然GAS也不是银弹。它的学习成本确实高概念多Ability、Effect、Tag、Cue、Task……初期搭建慢。但这是一次性投入后面每个技能的开发速度会越来越快。我的经验是前两周痛苦第三周开始真香。2.2 核心模块的职责划分GAS的核心模块可以类比成一家餐厅的运作模块类比职责AbilitySystemComponent餐厅经理统一调度管理所有技能和效果的注册与执行GameplayAbility厨师具体执行某个技能的逻辑AttributeSet食材库存存储血量、攻击力等数值属性GameplayEffect菜谱定义属性如何被修改加血、扣血、加buffGameplayTag菜单分类标签标记状态眩晕、霸体、沉默等GameplayCue摆盘和音效负责表现层特效、音效、镜头震动这个划分的关键价值在于逻辑与表现分离。技能逻辑只管数值和状态变化特效音效通过Cue去触发。这样改特效不用动逻辑改逻辑不用管特效两个人可以并行开发。2.3 数据流的设计思路一次完整的技能释放数据是这样流动的玩家输入触发 → AbilitySystemComponent收到激活请求检查Tag条件是否被沉默、是否在冷却→ 通过则激活AbilityAbility执行逻辑 → 通过GameplayEffect修改AttributeSetAttributeSet数值变化 → 触发回调 → 派发GameplayCue播放表现数值变化同步到网络 → 客户端表现一致理解这条链路非常重要因为后面排查问题时你需要知道问题出在哪一环。是技能没激活第2步还是数值没变第3步还是特效没播第4步定位清楚了解决起来就快。提示建议在项目初期就把这条数据流画成图贴在工位上团队每个人都要理解。我踩过的坑就是美术同学不知道Cue是怎么触发的特效没播就来找程序结果发现是Tag条件没配。3. 核心模块的深度解析与实操要点3.1 AttributeSet属性系统的地基AttributeSet是GAS里最基础也最容易踩坑的模块。它本质上是一个存放属性的容器但它的初始化时机、网络同步方式、数值钳制规则都有讲究。先看一个典型的AttributeSet定义CUCLASS() class UMyAttributeSet : public UAttributeSet { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, ReplicatedUsing OnRep_Health) FGameplayAttributeData Health; ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health) UPROPERTY(BlueprintReadOnly, ReplicatedUsing OnRep_MaxHealth) FGameplayAttributeData MaxHealth; ATTRIBUTE_ACCESSORS(UMyAttributeSet, MaxHealth) virtual void PreAttributeChange(const FGameplayAttribute Attribute, float NewValue) override; virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData Data) override; };这里有几个关键点为什么用FGameplayAttributeData而不是直接用float因为它内置了BaseValue和CurrentValue两个值。BaseValue是基础值来自等级、装备CurrentValue是当前值经过buff修正后。这样当你移除一个加攻buff时系统知道要回到哪个基础值不会算错。PreAttributeChange和PostGameplayEffectExecute的区别前者在属性即将改变时调用适合做数值钳制比如血量不能超过最大值后者在Effect执行完毕后调用适合做衍生逻辑比如血量归零触发死亡。我一开始把死亡逻辑写在PreAttributeChange里结果出现了血量还没真正归零就触发死亡的问题后来挪到Post里才正常。属性初始化的坑AttributeSet的构造函数里不要做任何依赖其他系统的初始化。我见过有人在构造函数里读配置表结果网络同步时客户端和服务器读到的值不一致。正确的做法是在Possess之后通过一个InitEffect来初始化属性。3.2 GameplayAbility技能逻辑的载体GameplayAbility是每个技能的大脑。它的生命周期分为几个阶段激活 → 执行 → 结束。每个阶段都有对应的回调可以重写。一个近战攻击技能的典型实现void UGA_MeleeAttack::ActivateAbility(...) { // 检查是否满足激活条件 if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) { EndAbility(Handle, ActorInfo, ActivationInfo, true, true); return; } // 播放攻击蒙太奇 UAnimInstance* AnimInstance GetAvatarActor()-GetMesh()-GetAnimInstance(); AnimInstance-Montage_Play(AttackMontage); // 绑定动画通知在特定帧触发伤害判定 FOnMontageEnded EndDelegate; EndDelegate.BindUObject(this, UGA_MeleeAttack::OnMontageEnded); AnimInstance-Montage_SetEndDelegate(EndDelegate, AttackMontage); }CommitAbility的作用它会检查并扣除技能消耗蓝量、耐力和冷却。很多人忘了调用它导致技能可以无限放。记住任何有消耗或冷却的技能ActivateAbility里第一件事就是CommitAbility。动画通知触发伤害不要在ActivateAbility里直接算伤害而是通过动画通知AnimNotify在攻击动画的特定帧触发。这样伤害判定和视觉表现是对齐的玩家看到刀砍到敌人身上才掉血手感才对。技能中断的处理如果角色在攻击过程中被眩晕技能需要正确中断。这就要用到AbilityTag和CancelAbility。给攻击技能打上Ability.Attack标签眩晕效果激活时取消所有带这个标签的技能。3.3 GameplayEffect数值修改的统一入口GameplayEffect是GAS里最强大的模块也是最容易用错的。它有三种类型Instant立即修改属性不留下任何痕迹比如直接扣血Duration持续一段时间后自动移除比如持续5秒的加速buffInfinite永久存在直到被手动移除比如装备提供的属性加成Modifier的运算方式也很关键运算方式说明适用场景Add加法固定数值加成Multiply乘法百分比加成Divide除法减伤计算AddBase修改基础值等级成长这里有个经典问题多个百分比加成怎么算比如两个50%攻击的buff是1.5×1.52.25还是10.50.52.0GAS默认是加法聚合2.0如果你想要乘法叠加需要自定义Aggregator。这个选择要在项目初期就定好后期改会牵一发动全身。Duration Effect的刷新规则当一个已经在生效的buff再次被施加时是刷新时间还是叠加层数这由Stacking规则决定。我建议在项目里统一规范增益buff用刷新时间减益buff用叠加层数避免混乱。3.4 GameplayTag状态管理的利器GameplayTag是GAS里我最喜欢的部分。它用层级化的标签来标记状态比如State.Stunned // 眩晕 State.Invincible // 无敌 State.Attacking // 攻击中 Ability.Attack.Melee // 近战攻击技能 Cooldown.Skill.Fire // 火系技能冷却Tag的层级继承特性如果你查询State所有以State.开头的标签都会匹配。这在做状态判断时非常方便比如是否处于任何控制状态只需要查State.Control。用Tag做技能阻断给眩晕效果配置GrantedTags: State.Stunned然后在攻击技能的ActivationBlockedTags里加上State.Stunned。这样眩晕期间攻击技能自动无法激活不需要写任何if判断。这就是GAS的优雅之处——用配置代替代码。注意Tag的命名规范一定要在项目初期定好并且写进团队文档。我见过Tag命名混乱的项目最后没人知道哪个Tag是干什么的比没有Tag还糟糕。4. 完整战斗流程的实操搭建4.1 从零搭建一个可用的战斗框架假设我们要做一个最基础的ARPG战斗角色有血量、攻击力、防御力三个属性有一个普通攻击技能和一个火球技能。搭建步骤如下第一步创建AttributeSet定义Health、MaxHealth、AttackPower、Defense四个属性。在PreAttributeChange里做钳制Health不能小于0不能大于MaxHealth。第二步创建InitEffect做一个Instant类型的GameplayEffect用SetByCaller的方式设置初始属性值。在角色Possess时应用这个Effect完成属性初始化。第三步创建技能基类做一个UGA_BaseAbility处理通用的CommitAbility、EndAbility逻辑所有具体技能继承它。第四步实现普通攻击创建UGA_MeleeAttack配置ActivationBlockedTags眩晕、死亡时不可用绑定动画蒙太奇通过AnimNotify触发伤害判定。第五步实现火球技能创建UGA_Fireball配置消耗蓝量和冷却。激活时生成火球投射物投射物命中时应用一个伤害GameplayEffect。第六步配置伤害计算创建一个DamageExecution自定义Execution Calculation在里面读取攻击方的AttackPower和防御方的Defense计算最终伤害通过SetByCaller写入。4.2 伤害计算的核心实现伤害计算是战斗框架的心脏。GAS提供了Execution Calculation机制让你可以完全自定义计算逻辑。void UDamageExecution::Execute_Implementation( const FGameplayEffectCustomExecutionParameters ExecutionParams, FGameplayEffectCustomExecutionOutput OutExecutionOutput) const { // 获取来源和目标的AttributeSet const UMyAttributeSet* SourceSet ExecutionParams.GetSourceAbilitySystemComponent()-GetSetUMyAttributeSet(); const UMyAttributeSet* TargetSet ExecutionParams.GetTargetAbilitySystemComponent()-GetSetUMyAttributeSet(); // 读取攻击力和防御力 float AttackPower SourceSet-GetAttackPower(); float Defense TargetSet-GetDefense(); // 伤害公式攻击力 * (1 - 防御减伤率) float DamageReduction Defense / (Defense 100.0f); // 防御减伤曲线 float FinalDamage AttackPower * (1.0f - DamageReduction); // 输出伤害 OutExecutionOutput.AddOutputModifier( FGameplayModifierEvaluatedData( UMyAttributeSet::GetHealthAttribute(), EGameplayModOp::Additive, -FinalDamage ) ); }防御减伤公式的选择我用的这个公式Defense/(Defense100)是很多游戏常用的特点是防御越高收益越低不会出现堆防御到无敌的情况。你也可以用其他曲线但一定要保证是递减收益否则数值会崩。为什么要用Execution而不是普通Modifier因为伤害计算需要同时读取攻击方和目标方的属性普通Modifier只能读取一方的值。Execution是唯一能拿到双方数据的机制。4.3 表现层的接入方式逻辑跑通之后表现层通过GameplayCue接入。在GameplayEffect里配置Cue的Tag比如GameplayCue.Hit.Fire然后在Cue的Notify里播放特效和音效。void AMyCueNotify::HandleGameplayCue(AActor* MyTarget, EGameplayCueEvent::Type EventType, const FGameplayCueParameters Parameters) { if (EventType EGameplayCueEvent::Executed) { // 播放受击特效 UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), HitEffect, MyTarget-GetActorLocation()); // 播放受击音效 UGameplayStatics::PlaySoundAtLocation(GetWorld(), HitSound, MyTarget-GetActorLocation()); } }Cue的两种模式一种是静态的Static特效直接挂在角色身上一种是Actor类型的会生成一个独立的Actor。受击特效一般用静态的持续性的buff特效比如燃烧用Actor类型的方便跟随角色移动。表现层的性能优化Cue的触发频率可能很高比如持续伤害每秒触发一次特效不要每次都生成新的可以用对象池复用。我在项目里做过统计用对象池之后特效相关的GC压力下降了60%以上。5. 常见问题与排查技巧实录5.1 技能不激活的排查思路这是最高频的问题。排查顺序如下检查ActivationBlockedTags是不是被某个Tag挡住了在运行时打印当前拥有的所有Tag看看有没有意外的阻断Tag。检查CommitAbility是否失败蓝量不够、冷却中都会导致Commit失败。在CommitAbility的返回值处打日志。检查Ability是否被正确授予GiveAbility有没有调用AbilitySpec的InputID对不对检查网络权限如果是联机确认是服务器还是客户端在激活。有些技能只能在服务器激活。我遇到过一个很隐蔽的case技能在编辑器里测试正常打包后就不行。最后发现是Tag的配置在打包时被裁剪了需要在DefaultGameplayTags.ini里显式声明。5.2 属性不同步的典型原因联机项目里属性不同步是家常便饭。常见原因现象可能原因解决方案客户端血量不更新AttributeSet没有标记Replicated加上UPROPERTY(Replicated)服务器和客户端数值不一致在客户端做了属性修改所有属性修改走GameplayEffect由服务器执行属性初始化时机不对在构造函数里初始化改用InitEffect在Possess后初始化回调没触发没有实现OnRep函数补上OnRep_XXX回调核心原则属性修改永远由服务器发起客户端只做预测和表现。这个原则不能破破了就会出现各种诡异的不同步。5.3 性能问题的定位与优化GAS在技能数量多、buff频繁触发时会有性能压力。我实测下来主要的性能热点在三个地方AttributeSet的遍历每次属性变化都会遍历所有AttributeSet如果AttributeSet很多会有开销。优化方法是把不常用的属性合并到一个Set里。GameplayEffect的堆叠检查大量同类型Effect同时存在时堆叠检查会变慢。优化方法是合理设置Stacking规则避免无限堆叠。Cue的频繁触发持续伤害类Effect每秒触发Cue如果特效很重会卡。优化方法是降低Cue的触发频率或者用轻量级的表现替代。提示用Unreal Insights抓一下GameplayAbility的耗时如果单个技能激活超过1ms就要注意了。正常应该在0.1ms以内。5.4 独家避坑经验最后分享几个文档里不会写、但实际项目中一定会遇到的坑坑一Tag的拼写错误。GAS的Tag是字符串匹配拼错了不会报错只会静默失效。建议用FGameplayTag的静态变量而不是硬编码字符串编译期就能发现问题。坑二Effect的Duration和Period混淆。Duration是效果持续多久Period是每隔多久执行一次。做持续伤害时两个都要设只设Duration会导致效果结束时才结算一次伤害。坑三Ability的实例化策略。默认是InstancedPerActor意味着每个角色一份实例。如果你的技能需要保存状态比如蓄力要用InstancedPerExecution。选错了会出现技能状态串味的问题。坑四Cue的NetExecutionPolicy。表现层的Cue默认是服务器和客户端都执行如果特效只在服务器播客户端看不到。检查Cue的配置确保NetExecutionPolicy是NetMulticast或LocalOnly。坑五属性钳制的时机。PreAttributeChange里改NewValue只是改了本次的值不会影响BaseValue。如果你想让钳制永久生效要在PostGameplayEffectExecute里改。这套框架我在两个项目里跑过从最初的磕磕绊绊到后来的得心应手大概花了三个月。现在回头看GAS最大的价值不是它提供了多少功能而是它强制你用一套规范的思路去组织战斗逻辑。当你习惯了Attribute、Effect、Tag、Ability这套语言之后你会发现不只是战斗很多游戏系统都可以用类似的思路去拆解。后续如果要做技能编辑器或者战斗回放GAS的数据结构也能直接复用这是自己造轮子很难做到的。

相关新闻

Aspose.Words版本合集怎么选?19.5等三版引用与激活避坑指南

Aspose.Words版本合集怎么选?19.5等三版引用与激活避坑指南

简介:该合集面向Java开发人员,提供aspose-words-for-java 19.5、18.10等三个版本的jar包,可在JDK 6环境下直接导入Eclipse使用,解决Word文档转PDF、生成与处理文档等场景需求。经测试的破解版本无水印、无文件大小限制&#xff0c…

2026/10/13 15:50:27 阅读更多 →
51单片机+8×8点阵写贪吃蛇:从硬件接线到C语言实现全攻略

51单片机+8×8点阵写贪吃蛇:从硬件接线到C语言实现全攻略

如果你学过C语言,又刚好有点单片机基础,那“51单片机 8*8点阵 贪吃蛇”这个组合,绝对是最值得上手的一个练手项目。它不复杂,但GPIO控制、动态扫描、按键输入、状态机设计全都能练到。我前前后后做过三版,也帮同学排…

2026/10/12 14:39:03 阅读更多 →
open-code-review实践:把代码审查从形式化变成团队高效的机制

open-code-review实践:把代码审查从形式化变成团队高效的机制

open-code-review 实践手记:把形同虚设的代码审查,变成团队真正受益的环节做开源软件这些年,我越来越确定一件事:把 code review 做成 open 的,不是给工具名加个 open source 前缀就算完事——真正要做的,是…

2026/10/12 14:39:03 阅读更多 →

最新新闻

基于Matlab GUI的轮轨接触几何计算程序设计与实现

基于Matlab GUI的轮轨接触几何计算程序设计与实现

做车辆动力学的人,应该都有过被轮轨接触几何支配的恐惧。不管你是在算临界速度、评估脱轨安全性,还是在调悬挂参数,第一关都得把轮轨接触几何算清楚。左右轮滚动圆半径差、接触角、等效锥度,这些量直接决定你后面的动力学仿真靠不…

2026/10/13 16:51:39 阅读更多 →
Winform轻量级可交互FlowChart流程图实现方案

Winform轻量级可交互FlowChart流程图实现方案

简介:这是一份基于WinForm平台实现的轻量级流程图绘制工具源码,面向C#初学者与小型项目开发者,解决快速构建可视化业务流程图的需求。资源以FlowChart.Net开源库为基础深度定制,功能简洁实用,适合嵌入教学演示、内部管…

2026/10/13 16:51:39 阅读更多 →
SIP软电话源码实战:从编译到NAT穿透与信令定制

SIP软电话源码实战:从编译到NAT穿透与信令定制

简介:本资源是一份基于SIP协议的软电话完整源代码工程,面向通信协议开发初学者、VoIP系统学习者及嵌入式/网络应用开发者,旨在帮助理解SIP信令交互、媒体协商与实时语音通信的核心实现逻辑。压缩包共302个文件,含45个C源文件&…

2026/10/13 16:51:39 阅读更多 →
测试工程师成长路径:从功能测试到测试架构师的能力进阶指南

测试工程师成长路径:从功能测试到测试架构师的能力进阶指南

前阵子我面试一位五年经验的测试工程师,简历上写着“熟悉自动化测试”,结果我随口问了一句“接口测试的断言除了状态码还会验什么”,他支支吾吾说了半天,最后落点在“看返回结果对不对”。这个场景不是个例,聊测试工程…

2026/10/13 16:51:39 阅读更多 →
MySQL课件精讲:从E-R图到候选码的数据库设计核心

MySQL课件精讲:从E-R图到候选码的数据库设计核心

简介:这套《MySql数据库原理及应用》课件汇总共1个文件(pptx格式),压缩包大小13.29MB,覆盖整本教材的教学内容。资源面向数据库初学人员、高校师生及自学MySQL的读者,系统讲解数据库系统基础知识与MySQL应用…

2026/10/13 16:51:39 阅读更多 →
AI工程化能力三支柱:数据智能、模型架构与系统交付

AI工程化能力三支柱:数据智能、模型架构与系统交付

1. 这不是“学AI”,而是构建一套可迁移、能落地的工程化能力体系很多人点开“AI培训学什么”这类标题,第一反应是找速成班、抄代码、背模型名字——结果学了三个月,连数据怎么清洗都卡在pandas报错上;或者花两万块报了“大模型实战…

2026/10/13 16:50:38 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →