EX-GAS扩展开发指南:自定义GameplayCue和AbilityTask的高级技巧
EX-GAS扩展开发指南自定义GameplayCue和AbilityTask的高级技巧【免费下载链接】gameplay-ability-system-for-unityGameplay-Ability-System For Unity项目地址: https://gitcode.com/gh_mirrors/ga/gameplay-ability-system-for-unityEX-GASGameplay Ability System是Unity游戏开发中强大的技能系统框架为开发者提供了完整的游戏能力管理解决方案。本文将深入探讨EX-GAS中两个核心扩展点GameplayCue和AbilityTask帮助开发者掌握自定义这些组件的高级技巧提升游戏表现力和交互体验。什么是GameplayCue为什么需要自定义GameplayCue是EX-GAS中的游戏提示系统负责处理游戏效果的可视化表现如特效、音效、动画等。它遵循不干涉游戏数值的原则专注于表现层逻辑。GameplayCue的核心原则不干涉数值体系Cue不应该修改游戏属性或Buff数值不影响核心玩法在即时战斗游戏中Cue不应该影响角色位移等核心战斗逻辑专注表现层主要负责视觉效果、音效播放等表现层功能自定义GameplayCue的基本结构每个GameplayCue都由两部分组成GameplayCue类数据配置类继承自GameplayCueT负责编辑器中的参数配置GameplayCueSpec类运行时逻辑类继承自GameplayCueSpec负责实际执行逻辑创建自定义瞬时Cue示例让我们创建一个播放自定义音效的GameplayCueInstant// 自定义音效Cue数据类 [CreateAssetMenu(fileName CustomSoundCue, menuName GAS/Cue/CustomSoundCue)] public class CustomSoundCue : GameplayCueInstant { [SerializeField] private AudioClip soundClip; [SerializeField] private float volume 1.0f; [SerializeField] private Vector3 positionOffset Vector3.zero; public override GameplayCueInstantSpec CreateSpec(GameplayCueParameters parameters) { return new CustomSoundCueSpec(this, parameters); } } // 自定义音效Cue规格类 public class CustomSoundCueSpec : GameplayCueInstantSpecCustomSoundCue { public CustomSoundCueSpec(CustomSoundCue cue, GameplayCueParameters parameters) : base(cue, parameters) { } public override void Trigger() { // 获取拥有者位置 Vector3 position Owner.transform.position cue.positionOffset; // 播放音效 AudioSource.PlayClipAtPoint(cue.soundClip, position, cue.volume); // 可以添加更多逻辑如粒子效果、屏幕震动等 Debug.Log($播放音效: {cue.soundClip.name} 于 {Owner.name}); } }创建自定义持续Cue示例持续Cue适合需要长时间运行的效果如光环特效、持续音效等// 自定义光环Cue数据类 [CreateAssetMenu(fileName AuraCue, menuName GAS/Cue/AuraCue)] public class AuraCue : GameplayCueDurational { [SerializeField] private GameObject auraPrefab; [SerializeField] private float auraScale 1.0f; [SerializeField] private Color auraColor Color.white; public override GameplayCueDurationalSpec CreateSpec(GameplayCueParameters parameters) { return new AuraCueSpec(this, parameters); } } // 自定义光环Cue规格类 public class AuraCueSpec : GameplayCueDurationalSpecAuraCue { private GameObject auraInstance; private Material auraMaterial; public AuraCueSpec(AuraCue cue, GameplayCueParameters parameters) : base(cue, parameters) { } public override void OnAdd() { // 创建光环实例 auraInstance GameObject.Instantiate(cue.auraPrefab, Owner.transform); auraInstance.transform.localScale Vector3.one * cue.auraScale; // 设置光环颜色 auraMaterial auraInstance.GetComponentRenderer()?.material; if (auraMaterial ! null) { auraMaterial.color cue.auraColor; } } public override void OnRemove() { // 销毁光环实例 if (auraInstance ! null) { GameObject.Destroy(auraInstance); } } public override void OnGameplayEffectActivate() { // 激活光环 if (auraInstance ! null) { auraInstance.SetActive(true); } } public override void OnGameplayEffectDeactivate() { // 隐藏光环 if (auraInstance ! null) { auraInstance.SetActive(false); } } public override void OnTick() { // 每帧更新逻辑 if (auraInstance ! null) { // 可以添加旋转、缩放动画等 auraInstance.transform.Rotate(0, 30 * Time.deltaTime, 0); } } }GameplayCue在编辑器中的配置界面AbilityTask技能逻辑的可视化编排AbilityTask是EX-GAS中用于实现技能逻辑的可视化工具特别适合用于TimelineAbility中。它允许开发者将复杂的技能逻辑分解为可重用的任务单元。AbilityTask的类型EX-GAS提供了两种主要的AbilityTask类型InstantAbilityTask即时任务在特定时间点执行一次OngoingAbilityTask持续任务在时间范围内持续执行创建自定义即时任务示例让我们创建一个发射子弹的即时任务// 发射子弹任务数据类 [System.Serializable] public class ShootProjectileTaskData : AbilityTaskData { public GameObject projectilePrefab; public float projectileSpeed 10f; public float damage 10f; public Vector3 spawnOffset Vector3.forward; } // 发射子弹任务类 public class ShootProjectileTask : InstantAbilityTask { private ShootProjectileTaskData data; public override void Init(AbilitySpec spec) { base.Init(spec); // 从Spec中获取任务数据 data spec.GetTaskDataShootProjectileTaskData(); } public override void OnExecute() { if (data.projectilePrefab null) return; // 计算发射位置和方向 Vector3 spawnPosition Spec.Owner.transform.position Spec.Owner.transform.TransformDirection(data.spawnOffset); Quaternion spawnRotation Spec.Owner.transform.rotation; // 实例化子弹 GameObject projectile GameObject.Instantiate( data.projectilePrefab, spawnPosition, spawnRotation ); // 设置子弹属性 ProjectileController controller projectile.GetComponentProjectileController(); if (controller ! null) { controller.damage data.damage; controller.speed data.projectileSpeed; controller.owner Spec.Owner; } // 添加力或速度 Rigidbody rb projectile.GetComponentRigidbody(); if (rb ! null) { rb.velocity Spec.Owner.transform.forward * data.projectileSpeed; } } }创建自定义持续任务示例持续任务适合处理需要随时间变化的效果如持续伤害区域// 持续伤害区域任务数据类 [System.Serializable] public class DamageAreaTaskData : AbilityTaskData { public GameObject areaPrefab; public float radius 5f; public float damagePerSecond 5f; public float duration 3f; } // 持续伤害区域任务类 public class DamageAreaTask : OngoingAbilityTask { private DamageAreaTaskData data; private GameObject areaInstance; private float elapsedTime; public override void Init(AbilitySpec spec) { base.Init(spec); data spec.GetTaskDataDamageAreaTaskData(); } public override void OnStart(int startFrame) { // 创建伤害区域 areaInstance GameObject.Instantiate(data.areaPrefab, Spec.Owner.transform.position, Quaternion.identity); areaInstance.transform.localScale Vector3.one * data.radius * 2; elapsedTime 0f; } public override void OnEnd(int endFrame) { // 销毁伤害区域 if (areaInstance ! null) { GameObject.Destroy(areaInstance); } } public override void OnTick(int frameIndex, int startFrame, int endFrame) { if (areaInstance null) return; // 更新计时 elapsedTime Time.deltaTime; // 每帧检测范围内的敌人并造成伤害 Collider[] colliders Physics.OverlapSphere(areaInstance.transform.position, data.radius); foreach (var collider in colliders) { AbilitySystemComponent target collider.GetComponentAbilitySystemComponent(); if (target ! null target ! Spec.Owner) { // 应用伤害效果 ApplyDamageToTarget(target); } } // 检查持续时间 if (elapsedTime data.duration) { // 任务结束 OnEnd(endFrame); } } private void ApplyDamageToTarget(AbilitySystemComponent target) { // 创建伤害GameplayEffect GameplayEffect damageEffect CreateDamageEffect(); target.ApplyGameplayEffectToSelf(damageEffect); } }TimelineAbility编辑器中的任务轨道配置高级技巧GameplayCue与AbilityTask的协同工作在实际开发中GameplayCue和AbilityTask经常需要协同工作。下面是一个综合示例展示如何将它们结合使用示例连击技能系统// 连击技能数据类 [CreateAssetMenu(fileName ComboAbility, menuName GAS/Ability/ComboAbility)] public class ComboAbilityAsset : AbilityAsset { public int maxComboCount 3; public float comboResetTime 2f; public GameObject[] comboEffects; // 不同连击段数的特效 public AudioClip[] comboSounds; // 不同连击段数的音效 public override Type AbilityType() { return typeof(ComboAbility); } } // 连击技能类 public class ComboAbility : AbstractAbilityComboAbilityAsset { private int currentCombo 0; private float lastAttackTime 0f; public ComboAbility(ComboAbilityAsset abilityAsset) : base(abilityAsset) { } public override AbilitySpec CreateSpec(AbilitySystemComponent owner) { return new ComboAbilitySpec(this, owner); } } // 连击技能规格类 public class ComboAbilitySpec : AbilitySpec { private ComboAbilityAsset comboData; private int currentCombo 0; private float lastAttackTime 0f; public ComboAbilitySpec(ComboAbility ability, AbilitySystemComponent owner) : base(ability, owner) { comboData (ComboAbilityAsset)ability.DataReference; } public override void ActivateAbility(params object[] args) { // 检查连击重置 if (Time.time - lastAttackTime comboData.comboResetTime) { currentCombo 0; } // 增加连击计数 currentCombo (currentCombo % comboData.maxComboCount) 1; lastAttackTime Time.time; // 执行连击攻击 ExecuteComboAttack(currentCombo); // 播放连击特效和音效 PlayComboEffects(currentCombo); // 更新UI显示 UpdateComboUI(currentCombo); } private void ExecuteComboAttack(int comboLevel) { // 根据连击等级执行不同的攻击逻辑 switch (comboLevel) { case 1: // 第一段攻击 ExecuteFirstAttack(); break; case 2: // 第二段攻击 ExecuteSecondAttack(); break; case 3: // 第三段攻击 ExecuteThirdAttack(); break; } } private void PlayComboEffects(int comboLevel) { // 使用GameplayCue播放特效 if (comboLevel - 1 comboData.comboEffects.Length) { GameObject effectPrefab comboData.comboEffects[comboLevel - 1]; if (effectPrefab ! null) { // 创建自定义特效Cue ComboEffectCue cue ScriptableObject.CreateInstanceComboEffectCue(); cue.effectPrefab effectPrefab; cue.comboLevel comboLevel; // 应用Cue cue.ApplyFrom(this); } } // 播放音效 if (comboLevel - 1 comboData.comboSounds.Length) { AudioClip sound comboData.comboSounds[comboLevel - 1]; if (sound ! null) { // 使用自定义音效Cue CustomSoundCue soundCue ScriptableObject.CreateInstanceCustomSoundCue(); soundCue.soundClip sound; soundCue.volume 1.0f; soundCue.ApplyFrom(this); } } } }性能优化与最佳实践1. GameplayCue性能优化// 使用对象池优化频繁创建的Cue public class PooledVFXCue : GameplayCueDurational { [SerializeField] private string poolKey vfx_pool; [SerializeField] private int poolSize 10; public override GameplayCueDurationalSpec CreateSpec(GameplayCueParameters parameters) { return new PooledVFXCueSpec(this, parameters); } } public class PooledVFXCueSpec : GameplayCueDurationalSpecPooledVFXCue { private GameObject vfxInstance; public PooledVFXCueSpec(PooledVFXCue cue, GameplayCueParameters parameters) : base(cue, parameters) { } public override void OnAdd() { // 从对象池获取实例 vfxInstance ObjectPoolManager.Instance.GetFromPool(cue.poolKey); if (vfxInstance null) { // 池为空时创建新实例 vfxInstance GameObject.Instantiate(cue.VfxPrefab); ObjectPoolManager.Instance.AddToPool(cue.poolKey, vfxInstance); } vfxInstance.transform.position Owner.transform.position; vfxInstance.SetActive(true); } public override void OnRemove() { // 归还到对象池 if (vfxInstance ! null) { ObjectPoolManager.Instance.ReturnToPool(cue.poolKey, vfxInstance); vfxInstance null; } } }2. AbilityTask的编辑器集成为了让自定义的AbilityTask在TimelineAbility编辑器中可用需要创建对应的Inspector#if UNITY_EDITOR using UnityEditor; using GAS.Editor; // 发射子弹任务的Inspector [CustomEditor(typeof(ShootProjectileTaskData))] public class ShootProjectileTaskInspector : InstantTaskInspectorShootProjectileTask { public override void OnInspectorGUI() { base.OnInspectorGUI(); ShootProjectileTaskData data (ShootProjectileTaskData)target; EditorGUILayout.Space(); EditorGUILayout.LabelField(子弹设置, EditorStyles.boldLabel); data.projectilePrefab (GameObject)EditorGUILayout.ObjectField( 子弹预制体, data.projectilePrefab, typeof(GameObject), false ); data.projectileSpeed EditorGUILayout.FloatField(子弹速度, data.projectileSpeed); data.damage EditorGUILayout.FloatField(伤害值, data.damage); data.spawnOffset EditorGUILayout.Vector3Field(生成偏移, data.spawnOffset); if (GUI.changed) { EditorUtility.SetDirty(data); } } } #endif3. 参数验证与错误处理public class SafeGameplayCue : GameplayCueInstant { [SerializeField] private GameObject effectPrefab; [SerializeField] private bool useFallback true; [SerializeField] private GameObject fallbackPrefab; public override GameplayCueInstantSpec CreateSpec(GameplayCueParameters parameters) { // 参数验证 if (!ValidateParameters()) { Debug.LogError($SafeGameplayCue参数验证失败: {name}); return null; } return new SafeGameplayCueSpec(this, parameters); } private bool ValidateParameters() { if (effectPrefab null) { if (useFallback fallbackPrefab ! null) { effectPrefab fallbackPrefab; return true; } return false; } return true; } }调试与测试技巧1. 使用GAS运行时监视器GAS运行时监视器是调试GameplayCue和AbilityTask的强大工具可以实时查看当前激活的Ability属性值变化正在运行的GameplayEffect标签状态2. 自定义调试信息public class DebugGameplayCue : GameplayCueInstant { [SerializeField] private string debugMessage; [SerializeField] private Color debugColor Color.white; public override GameplayCueInstantSpec CreateSpec(GameplayCueParameters parameters) { return new DebugGameplayCueSpec(this, parameters); } } public class DebugGameplayCueSpec : GameplayCueInstantSpecDebugGameplayCue { public DebugGameplayCueSpec(DebugGameplayCue cue, GameplayCueParameters parameters) : base(cue, parameters) { } public override void Trigger() { // 在场景中显示调试信息 Debug.Log($color#{ColorUtility.ToHtmlStringRGB(cue.debugColor)}{cue.debugMessage}/color); // 在拥有者位置显示文字 if (Owner ! null) { ShowFloatingText(Owner.transform.position, cue.debugMessage, cue.debugColor); } } private void ShowFloatingText(Vector3 position, string text, Color color) { // 实现浮动文字显示逻辑 // 可以使用TextMeshPro、UGUI或其他方式 } }总结与建议自定义GameplayCue和AbilityTask是EX-GAS扩展开发的核心技能。通过掌握这些技巧你可以创建丰富的视觉效果通过自定义GameplayCue实现独特的游戏表现构建复杂的技能逻辑利用AbilityTask实现可重用的技能组件提高开发效率通过可视化编辑减少代码量增强系统灵活性创建可配置的游戏效果系统最佳实践总结保持Cue的纯粹性GameplayCue只负责表现不干涉游戏逻辑合理使用AbilityTask将复杂技能分解为可重用的任务单元注意性能优化使用对象池、缓存等技巧优化频繁创建的对象完善的错误处理验证参数提供备用方案充分利用编辑器创建友好的Inspector界面提高策划配置效率通过本文介绍的高级技巧你可以充分发挥EX-GAS的潜力创建出更加丰富、灵活的游戏能力系统。记住良好的扩展设计不仅能提升游戏质量还能显著提高开发效率更多详细信息和API参考请查看项目中的官方文档和Ability文档。【免费下载链接】gameplay-ability-system-for-unityGameplay-Ability-System For Unity项目地址: https://gitcode.com/gh_mirrors/ga/gameplay-ability-system-for-unity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

AI Agent的“骨架”之争:四种Harness设计哲学深度解构

AI Agent的“骨架”之争:四种Harness设计哲学深度解构

1. 引言 2025年,AI Agent 已从实验室概念走向生产级应用。然而,当开发者真正着手构建一个 Agent 系统时,最先面临的不是模型选型,而是一个更底层的问题:Agent 的“骨架”该怎么搭? 这个“骨架”就是 Agent …

2026/10/13 4:57:02 阅读更多 →
Plotly区间滑块交互设计:GPT-4驱动的直觉化可视化实战

Plotly区间滑块交互设计:GPT-4驱动的直觉化可视化实战

1. 项目概述:这不是炫技,是把交互可视化从“能用”推向“直觉化”的一次实战 你有没有过这种体验:花两小时调好一个Plotly图表,加了下拉菜单、悬停提示、图例开关,自以为很专业——结果同事点开第一眼就问:…

2026/10/11 23:20:31 阅读更多 →
AI模型功能完整度评估体系首次解密,覆盖37项原子能力指标,附开源评分工具链下载

AI模型功能完整度评估体系首次解密,覆盖37项原子能力指标,附开源评分工具链下载

更多请点击: https://intelliparadigm.com 第一章:AI模型功能完整度评估体系首次解密 传统AI模型评估长期聚焦于准确率、F1值等单一维度指标,忽视了模型在真实业务场景中必须具备的多维能力支撑。本章首次公开一套可落地、可量化、可扩展的A…

2026/9/25 18:49:54 阅读更多 →

最新新闻

实习考勤不内耗:习讯云打卡全流程避坑指南

实习考勤不内耗:习讯云打卡全流程避坑指南

实习第一个月,我差点被习讯云实习考勤打卡逼到内耗。每天到公司第一件事不是换工牌,而是掏出手机打开习讯云,看定位转圈、看按钮变灰、看页面弹“不在考勤范围”,一套流程下来,本来就不多的精气神先耗掉三分之一。后来…

2026/10/13 4:58:19 阅读更多 →
长连接与短连接:高并发系统中的TCP连接治理实战

长连接与短连接:高并发系统中的TCP连接治理实战

1. 从一次“连接超时”告警说起:为什么我们总在长连接和短连接之间反复横跳?上周五下午三点,某公司后台监控系统突然弹出一连串红色告警:“订单服务TCP连接数突增300%”“网关节点CPU持续92%”“下游支付接口响应延迟突破800ms”。…

2026/10/13 4:58:19 阅读更多 →
微电网风光柴储经济调度:Matlab建模与求解实战

微电网风光柴储经济调度:Matlab建模与求解实战

微电网经济调度,很多刚入门的同学一看“风光柴储”四个字就有点发怵,觉得光是理解拓扑就够复杂了,还要在Matlab里做优化,更是一头雾水。但实际做下来你会发现,它本质就是一个带约束的数学优化问题:把风机、…

2026/10/13 4:58:19 阅读更多 →
Llama 3 架构精读与 FP8 量化实战:长上下文部署全记录

Llama 3 架构精读与 FP8 量化实战:长上下文部署全记录

Llama 3 出来之后,圈子里聊得最多的其实就两件事:一是架构上到底改了些什么,二是它到底能不能被普通人跑起来。官方博客里的参数表人人都能搜到,但真正上手就会发现,一张表和你实际推理时的显存、吞吐、质量完全是两码…

2026/10/13 4:58:19 阅读更多 →
B端体验设计缓解视觉疲劳:色彩降噪、信息密度与深色模式实战

B端体验设计缓解视觉疲劳:色彩降噪、信息密度与深色模式实战

最近在复盘一个后台管理系统项目时,客户方的运营主管跟我说了一句特别真实的话:“连续录入三小时单据,眼睛胀得不行,下班后看什么都带重影。”这句话我一直记到现在。B端体验设计与C端最大的分野就在这里:C端用户是“看…

2026/10/13 4:58:19 阅读更多 →
UCINET数据导入实战:格式规范、Excel整理与高频排错

UCINET数据导入实战:格式规范、Excel整理与高频排错

1. 先搞清楚UCINET到底在等什么数据很多人在UCINET上栽的第一个跟头,不是不会跑算法,而是数据根本喂不进去。明明Excel里填得工工整整,一点Import却报错,或者导进去之后变成一团乱麻,节点对不上、值全错位。这些问题的…

2026/10/13 4:57:19 阅读更多 →

日新闻

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

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

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

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/12 0:16:38 阅读更多 →
影刀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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →