简介ParticleEditor 是面向 Cocos2d-x 游戏开发者的粒子效果编辑工具适合需要制作火焰、烟雾、雨滴等动态特效的初中级开发者与独立团队使用。它提供可视化界面可新建或加载 .plist 配置调整发射速率、粒子寿命、角度与速度范围、重力、旋转、颜色渐变和混合模式并支持圆形、环形、扇形等发射形状及实时预览导出 .plist 与 .png 后可直接接入 Cocos2d-x 项目。压缩包为 zip 格式约 10.02MB文件总数显示为 0类型明细暂未提供。已有 176 人学习内容围绕粒子参数调试、纹理与发射器设置、预览导出及性能优化展开可帮助读者理解粒子系统构成掌握从编辑到项目整合的完整流程并为复杂特效制作与资源开销控制提供参考。1. ParticleEditor 粒子编辑器把调参、预览、导出串成一条产线粒子效果最费时间的环节往往不是写代码而是等编译。改一个发射速率、刷新一次、进游戏看一眼、不对、再改——一轮下来五分钟没了一天能验证的想法不超过二十个。ParticleEditor 粒子编辑器就是针对这条链路做的资源包它把发射器、模块参数、生命周期曲线、颜色渐变、图集与混合模式全部放进一个可视化面板里实时预览调完直接导出成运行时可读的配置文件不再依赖引擎编辑器反复重启。它适合三类人做特效的手工派、需要批量产出粒子配置的独立开发者以及被“编辑器里好看、跑起来不一样”折腾过的客户端同学。下面按数据结构、渲染接入、导出一致性、踩坑四条线依次说清。2. 粒子数据模型发射器加模块栈为什么参数要分层存2.1 从发射器到模块栈参数分层的选型理由粒子系统的参数数量很容易失控。一个普通的烟雾喷发涉及发射速率、爆发数量、发射形状、初始速度区间、生命周期、尺寸随生命变化、颜色随生命变化、旋转、重力、阻尼、图集、混合模式、排序方式——十几组参数每组还可能是常量、随机区间、曲线三种形态之一。如果按扁平结构存字段名会长到没法读而且每加一种能力就要改 schema、改导出、改运行时加载三处不同步就开始出玄学问题。模块栈的做法是把能力拆成独立的可选块发射模块负责“什么时候产生多少”形状模块负责“从哪儿、朝哪个方向”初始速度与生命周期模块负责“出生时的初值”后续的 size/color/rotation over life 负责“随时间怎么变”渲染模块负责“怎么画”。每个模块自带类型标签运行时按类型注册对应的更新函数遇到不认识的模块直接跳过而不是崩掉——这一点对版本迭代特别重要编辑器加新模块时可以不动旧资源。分层还有一个实际收益模块可以复用到别的发射器上。我一般会把“烟雾基础参数”抽成一份模块集合用它作为模板派生出小、中、大三档只改容量和尺寸曲线其余继承。扁平结构做不到这种继承只能复制粘贴改一处漏一处。代价是加载时要多做一次模块合并但这一步只在加载期发生一次对运行时零开销。2.2 配置文件结构一份人和机器都能读的 JSON导出格式选了 JSON 而不是自定义二进制理由很直接粒子配置的体积在几十 KB 量级解析开销可以忽略换来的是可 diff、可 review、可手改。二进制适合顶点数据不适合这种需要人反复微调的参数表。下面是一份实际在用的发射器配置字段名做了简化。{ schema: particle.emitter, name: smoke_burst, capacity: 512, duration: 2.0, loop: false, randomSeed: 20240517, modules: [ { type: emission, rate: 0, bursts: [{ time: 0.0, count: 64 }] }, { type: shape, shape: cone, angle: 25.0, radius: 0.15, direction: [0, 1, 0] }, { type: init_velocity, speed: { mode: range, min: 1.2, max: 2.0 } }, { type: life, life: { mode: range, min: 0.8, max: 1.4 } }, { type: size_over_life, curve: { keys: [ { t: 0.0, v: 0.20, tanIn: 0.0, tanOut: 0.35 }, { t: 0.35, v: 0.55, tanIn: 0.35, tanOut: 0.10 }, { t: 1.0, v: 0.90, tanIn: 0.10, tanOut: 0.0 } ] } }, { type: color_over_life, gradient: [ { t: 0.0, rgba: [0.85, 0.85, 0.88, 0.0] }, { t: 0.12, rgba: [0.85, 0.85, 0.88, 0.6] }, { t: 1.0, rgba: [0.55, 0.55, 0.60, 0.0] } ] }, { type: render, blend: alpha, atlas: smoke_soft, sort: emitter, facing: camera } ] }几个字段值得单独说。capacity是粒子池的硬上限不是建议值运行时会按它一次性预分配实例缓冲写超了就丢新粒子而不是扩容——扩容意味着重分配显存卡顿比少几个粒子难看得多。duration与loop决定发射器的生命周期配合bursts.time做一次性爆发randomSeed是后面做确定性回放的关键同一份配置加同一个种子两次运行必须逐帧对得上。capacity与bursts.count的比值建议留 4 倍以上余量因为粒子存活期间还要叠加生命周期内的波动。渲染模块里的sort: emitter表示只在发射器级别排序、粒子内部不排序这是性能和正确性之间的务实取舍后文会展开。2.3 曲线与关键帧为什么导出时要烘成查找表曲线在编辑器里是三次 Hermite 关键帧每个关键帧带t、v和进出切线tanIn/tanOut。保留切线的好处是手感连续拖动一个点不会让整段变折线。但运行时每个粒子每帧都算一遍 Hermite 是多此一举曲线本身是归一化时间域上的函数跟粒子的具体状态无关完全可以提前算好。我一般会把它烘成 32 帧的等距查找表运行时只做一次向下取整加线性插值。32 帧对尺寸、透明度这类缓变量足够如果是转速或颜色带明显突变就用 64 帧。烘焙脚本大致如下。def hermite(k0, k1, t): dt k1[t] - k0[t] s (t - k0[t]) / dt s2, s3 s * s, s * s * s h00 2 * s3 - 3 * s2 1 h10 s3 - 2 * s2 s h01 -2 * s3 3 * s2 h11 s3 - s2 return (h00 * k0[v] h10 * dt * k0[tanOut] h01 * k1[v] h11 * dt * k1[tanIn]) def bake_curve(keys, frames32): 把关键帧烘成定长表返回 float 列表长度 frames1 keys sorted(keys, keylambda k: k[t]) out, seg [], 0 for i in range(frames 1): t i / frames # 段指针只前进不后退帧数远大于关键帧数单趟扫描即可 while seg len(keys) - 2 and t keys[seg 1][t]: seg 1 k0, k1 keys[seg], keys[seg 1] out.append(hermite(k0, k1, min(max(t, k0[t]), k1[t]))) return outframes是采样密度参数说明里写死为 32 与 64 两档不让使用者随手填 1000——查找表最终要写进实例数据侧的一维纹理或 uniform 数组密度过高中收益为零还会挤占常量缓冲。seg指针单趟推进是刻意写的避免每帧从头搜关键帧虽然烘焙只在加载期跑一次但这个写法顺手也能用在运行时的多段渐变采样上。烘焙完记得把结果缓存到配置文件的baked字段里下次加载直接读表跳过计算。3. 接进渲染管线实例化绘制与每实例 8 个浮点的打包3.1 为什么用实例化而不是逐粒子一次绘制一个中等规模的特效场景屏幕上同时存在的粒子数在 2000 到 8000 之间。逐粒子提交绘制意味着每帧几千次 draw callCPU 侧光是准备命令就吃满了GPU 反而空转。实例化绘制把这批粒子压成一到两次提交每实例只多传一小段属性数据这是这类场景唯一合理的选择。真正的成本在数据打包上。每实例传多少字节直接决定几千个粒子每帧的上传带宽。我的做法是压到 8 个 float也就是 32 字节xyz世界坐标加w存尺寸一个rgba存颜色已预乘剩下两个 float 存图集索引和旋转弧度。生命周期比例这类只在更新期用的数据不传给 GPU留在 CPU 侧。8000 个粒子按这个布局是 256 KB 每帧双缓冲交替写带宽完全可控。透明混合的排序是另一半问题。正确做法是按深度对每个粒子排序但几千个粒子的排序开销和状态切换开销都不划算而且粒子本身是软边贴图前后顺序错了视觉上往往察觉不到。工程上的常见做法是按发射器整体排序发射器内部按出生顺序输出配合加色或预乘混合把顺序错误的观感压到最低。这不是完美方案但它是可交付的方案。3.2 顶点属性布局与图集索引顶点侧只需要一份四边形角点所有粒子共用。每实例数据用独立缓冲按属性步长读取。属性分量类型含义a_corner2float四边形角点取值 -0.5 到 0.5a_posSize4floatxyz 世界坐标w 为世界尺寸a_color4floatrgba已预乘 alphaa_uvRot2floatx 为图集索引y 为旋转弧度a_uvRot.x用浮点存图集索引有点奢侈但省掉了一次整数到浮点的转换和额外的属性槽实际用下来更省事。着色器里把索引拆成行列再叠加角点偏移得到 UV。attribute vec2 a_corner; attribute vec4 a_posSize; attribute vec4 a_color; attribute vec2 a_uvRot; uniform mat4 u_viewProj; uniform vec2 u_atlasGrid; // 图集行列数例如 (4.0, 4.0) uniform vec3 u_cameraRight; uniform vec3 u_cameraUp; varying vec2 v_uv; varying vec4 v_color; void main() { float size a_posSize.w; float ang a_uvRot.y; float c cos(ang), s sin(ang); // 先绕视线轴旋转角点再按相机基向量展开成面向相机的四边形 vec2 corner vec2(c * a_corner.x - s * a_corner.y, s * a_corner.x c * a_corner.y); vec3 world a_posSize.xyz (u_cameraRight * corner.x u_cameraUp * corner.y) * size; // 图集索引 - 行列0.5/格数 是半像素内缩防止相邻图块渗色 float idx a_uvRot.x; float col mod(idx, u_atlasGrid.x); float row floor(idx / u_atlasGrid.x); vec2 cell 1.0 / u_atlasGrid; vec2 uv (vec2(col, row) a_corner 0.5) * cell; v_uv clamp(uv, cell * 0.5, 1.0 - cell * 0.5); v_color a_color; gl_Position u_viewProj * vec4(world, 1.0); }u_atlasGrid是图集行列数必须和贴图实际切分一致改图集时忘了改这个值表现为所有粒子贴图错位是最常见的翻车点之一。clamp那一行做半像素内缩不加的话软边贴图在缩放时会采到隔壁图块的像素边缘出现一条杂色带。旋转在 CPU 侧预算好弧度传进来不要每帧在着色器里算atan2那是几千次重复计算。3.3 一个最小可用的更新循环更新循环有两件事必须做对固定步长、容量硬上限。可变步长会让物理积分随帧率漂移同一个特效在 30 帧和 144 帧机器上表现不同这种问题排查起来极其难受。const STEP 1 / 60; // 固定模拟步长 const MAX_CATCHUP 5; // 单帧最多追 5 步防止卡顿后雪崩 const MAX_DT 0.25; // 单帧输入上限窗口拖动时 dt 会异常大 function update(world, dt) { world.acc Math.min(dt, MAX_DT); let steps 0; while (world.acc STEP steps MAX_CATCHUP) { step(world, STEP); world.acc - STEP; steps; } if (steps MAX_CATCHUP) world.acc 0; // 追不上就丢弃欠账宁可跳帧 uploadInstances(world); // 每帧只上传一次不按模拟步上传 } function step(world, dt) { for (const em of world.emitters) { em.time dt; const want em.rate * dt em.pendingBurst; // 本步应生成数量 em.spawnBudget want; let n Math.floor(em.spawnBudget); // 小数部分留到下一步避免低速率丢失 em.spawnBudget - n; while (n-- 0) spawnOne(em, world); // spawnOne 内部检查 writeIndex 不越过 capacity } for (const p of world.particles) { // 环形池只遍历存活区间 p.age dt; if (p.age p.life) { recycle(world, p); continue; } integrate(p, dt); } }STEP固定为 1/60与显示刷新率解耦MAX_CATCHUP是给低端机的保险帧率掉到 20 时如果老实补 3 步以上会越补越卡形成死亡螺旋所以追不上就清零欠账。spawnBudget的小数累计是为了低速发射器比如每秒 3 个粒子rate * STEP只有 0.05直接取整会永远生成 0 个。uploadInstances放在模拟循环外模拟跑两步也只上传一次这是显存带宽和模拟精度之间的分界线。4. 编辑器与运行时对齐导出烘焙、固定步长与回放校验4.1 导出链路编辑器状态到运行时格式的三步变换导出不是简单的JSON.stringify。中间有三步变换漏掉任何一步都会出现“编辑器里好看、跑起来不一样”。第一步是曲线烘焙把 Hermite 关键帧换成查找表同时把渐变按同样密度采样成 RGBA 序列。第二步是单位与坐标系归一编辑器里尺寸用相对屏幕高度的比例表示导出时按目标分辨率换算成世界单位方向向量统一归一化。第三步是路径规范化图集名只保留逻辑名实际贴图路径由运行时的资源映射表决定这样同一份配置能跑在不同分辨率的资源集上。这三步写在一个导出管道里管道本身不接受“跳过烘焙”的选项。曾经为了调试方便加过一个raw开关结果有人导了未烘焙的配置进包运行时按查找表去读关键帧数组读出来全是垃圾值粒子尺寸随生命变成一条乱跳的曲线——这就是典型的不给自己留后悔药。4.2 确定性模拟种子、索引哈希与浮点误差要验证编辑器与运行时一致前提是模拟本身可复现。可复现需要两个条件固定步长以及随机数不依赖调用顺序。全局Math.random()是最大的敌人。它按调用顺序取值粒子回收顺序一变同一个粒子拿到的随机数就变了两次运行的结果自然对不上。常见做法是每个发射器持有一个种子粒子生成时用hash(seed, particleId, fieldIndex)生成一个 0 到 1 的值粒子 ID 单调递增且回收不复用这样每个粒子的随机初值与它出生的顺序无关。// 整数哈希同一 (seed, id, field) 永远得到同一个 [0,1) 值 function rand01(seed, id, field) { let x (seed ^ (id * 0x9E3779B1) ^ (field * 0x85EBCA6B)) 0; x ^ x 16; x Math.imul(x, 0x7FEB352D) 0; x ^ x 15; x Math.imul(x, 0x846CA68B) 0; x ^ x 16; return x / 4294967296; }field是参数槽位编号同一次生成里速度用field0、生命周期用field1互不干扰。这样即使改了生成顺序只要 ID 分配策略不变每个粒子的初值就不变。注意imul是必需的普通乘法在 JS 里会溢出成浮点丢失低位。浮点一致性是另一半。不同平台、不同编译优化会带来末位差异因此校验时不能拿原始浮点做全等比较要量化后再比。4.3 回放校验脚本把每帧状态压成一个哈希校验的写法很朴素跑固定帧数每帧把存活粒子的关键状态量化后拼成一个哈希和基线逐帧比对第一处不一致的帧号就是问题所在。import hashlib, struct Q 1000.0 # 量化精度 1e-3吸收平台间的浮点末位差异 def frame_hash(particles): h hashlib.blake2b(digest_size8) for p in sorted(particles, keylambda p: p[id]): h.update(struct.pack(4i, int(round(p[x] * Q)), int(round(p[y] * Q)), int(round(p[life_left] * Q)), int(round(p[size] * Q)))) return h.hexdigest() def replay(config, frames180): world build_world(config) trace [] for f in range(frames): step(world, 1.0 / 60.0) trace.append(frame_hash(world.particles)) return trace base load_trace(baseline/smoke_burst.trace) cur replay(load_config(smoke_burst.json)) for i, (a, b) in enumerate(zip(base, cur)): if a ! b: print(f首次分叉在第 {i} 帧{a} ! {b}) break else: print(全帧一致)Q取 1000是因为粒子位置在 1e-3 以下的差异肉眼不可见但会污染哈希量化太粗又会漏掉真实漂移1e-3 是个实用值。排序键必须是稳定的 ID 而不是数组下标否则环形池复用顺序一变哈希立刻全乱。基线文件要跟着配置一起提交改配置就必须重新生成基线并在 code review 里说明差异否则这个机制形同虚设。5. 避坑与排查五条翻车记录和它们的定位方法5.1 编辑器里颜色正常运行时发灰现象编辑器预览是干净的浅灰烟雾导进运行时整体偏暗、边缘发脏。原因混合模式与色彩空间没对齐。编辑器面板在线性空间合成导出时颜色按 sRGB 直传运行时又在 sRGB 空间做 alpha 混合中间少了一次转换同时颜色没做预乘半透明区域被重复压暗。解决在 render 模块里把blend与premultiply显式写出来导出管道统一把颜色转换到线性空间并预乘着色器里固定用ONE, ONE_MINUS_SRC_ALPHA。改完记得把校验基线一起重出否则回放校验会全线飘红。5.2 粒子数一过两千帧率断崖现象800 个粒子跑满 60 帧加到 2500 个直接掉到 20 帧且 CPU 占用飙升。原因每帧重建实例缓冲并重新分配加上逐粒子一次提交。缓冲区反复创建销毁会触发显存同步是典型的隐藏开销。解决按capacity一次性预分配写入用环形游标每帧只更新脏区间绘制走实例化一个发射器一次提交。改完之后 8000 个粒子的提交次数是个位数。5.3 同一个特效两次运行结果不同现象视觉上差不多但回放校验天天报分叉帧号还随机。原因一是用了全局随机数二是 dt 可变。两者叠加任何一次加载顺序或帧率波动都会改变结果。解决换成hash(seed, id, field)的随机方案模拟固定 1/60 步长渲染插值单独做。判定标准很硬同一配置连续跑十次十次哈希完全一致才算过。5.4 曲线在编辑器里平滑运行时一抖一抖现象尺寸曲线在面板上是一条顺滑的弧线运行时粒子长大过程中有明显的台阶感。原因烘焙采样太稀加上关键帧切线没参与计算退化成了线性插值还有一种情况是查找表索引用了四舍五入而不是向下取整加插值导致相邻两帧跳变。解决把frames提到 64确认烘焙函数走的是 Hermite 而不是线性查表时固定用floor加小数部分插值。这类问题肉眼难判断最省事的验证是导出一条尺寸随时间的数值序列画出来对比。5.5 子发射器一开粒子数指数爆炸现象主发射器每秒 20 个挂上子发射器后总量瞬间上万直接打满容量上限主特效反而不见了。原因子发射器继承了父发射器的发射速率而父发射器每生成一个粒子又触发一次子发射递归乘算深度没有限制时还可能形成环。解决子发射器使用独立的速率配置默认从 0 起配而不是继承同时给递归深度加上限一般 2 层足够并在全局维护一个每帧生成预算超预算就丢弃低优先级发射器的生成请求。容量上限保护的是显存生成预算保护的是 CPU两者都要有。6. 进阶用法把粒子参数纳入回归与性能预算调完一个特效只是开始真正难的是三个月后有人改了公共模块你还能不能第一时间知道哪个特效坏了。我的做法是给每个发射器配一份预算表把主观的“看起来还行”变成几个可断言的数字。指标采集位置建议阈值超标后的第一动作峰值存活粒子数模拟循环内每帧统计不超过 capacity 的 70%查子发射器递归与爆发重叠单发射器提交次数渲染提交前1 次检查是否退化成逐粒子绘制模拟耗时占比帧计时分段低于整帧预算的 15%查每帧重建缓冲与排序每帧上传字节上传函数入口小于 512 KB压缩每实例属性到 8 个 float回放哈希分叉帧校验脚本180 帧内无分叉按分叉帧回查随机源与步长预算表的价值在于它能自动跑。把回放校验、峰值统计、上传字节统计挂到构建流水线里每次改动粒子配置或公共模块都跑一遍超阈值直接失败。这样一来“这个特效好像变卡了”就变成了具体的帧号和字节数。还有一个我常用的技巧参数快照对比。每份配置导出时附带一份烘好的查找表和随机种子改动前后的两份快照同时跑 180 帧把每帧的粒子数与哈希并排打印。多数时候差异集中在某几帧那一帧往前数两三步就能定位到具体是哪个模块改的。比对着编辑器面板逐项开关快得多也不用靠记忆猜上次改了什么。血泪经验是不要相信“我只改了一个数值”。粒子系统里参数是耦合的改发射速率会连带影响峰值粒子数、上传字节和排序开销改尺寸曲线会连带影响填充率。所以从那以后我每次改粒子配置都强制走一遍回放校验加预算表哪怕只是把透明度从 0.6 调到 0.55。这套东西前期的确要多花半小时搭但它省下的是凌晨两点对着屏幕找“为什么只有这台机器上烟雾是黑的”的时间。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Samba 4 运维脚本实战:sysvol 备份、域信息巡检与避坑指南 简介:这份资源面向在 Debian Jessie 与 Stretch 上部署和维护 Samba 4 AD-DC 的运维人员与系统管理员,汇集了作者日常使用的实用脚本,用于解决域控备份、SePrivileges 权限核查、sysvol ACL 设置与域信息查看等常见运维问题,适合具… · 2026/11/2 21:31:19
WPF TreeView拖拽落点判定与跨层级重排实战 简介:面向WPF桌面开发者的TreeView拖放功能示例,聚焦节点顺序调整与数据移动、复制等常见交互需求,适合已具备一定WPF基础、希望深入理解拖放事件与数据绑定的开发者参考。压缩包共12个文件、约11KB,主要由6个cs源码、2个xaml界面… · 2026/11/2 21:31:19
监控 AI 搜索引用的 5 个最佳 API(2026):GEO 数据层怎么搭 GEO(生成式引擎优化)是 2026 年最火的概念之一,但我聊下来的团队,十个有九个卡在同一个地方:SaaS 工具订阅买了,报表很好看,数据层是黑的——你不知道 AI 概览引了哪些站,就不知道该… · 2026/11/2 21:27:17
模拟CMOS集成电路第二版答案高效使用与验证指南 模拟CMOS集成电路设计这门课,学过的都懂那种感觉:书上的推导一行行看下来好像都明白,合上书做课后题立刻卡壳。尤其是第二版教材里那些偏置电路、频率响应、噪声分析的习题,光靠课堂听讲很难真正吃透。我当年学这门课时࿰… · 2026/11/2 23:08:20
游戏化学习计算机原理:用交互机制构建硬件直觉 1. 项目概述:当游戏机制成为计算机原理的“活体教具”“学霸带你游戏化从游戏中学习计算机原理”——这个标题一出来,我就在好几个技术教育社群里看到有人截图转发,配文是“终于等到不让人犯困的计原课了”。说实话,我带过三年高校… · 2026/11/2 23:08:20
机器学习核心概念与实战流程详解 1. 机器学习入门概述机器学习作为人工智能的核心分支,正在深刻改变我们解决问题的方式。想象一下,你正在教一个孩子识别动物:传统编程就像给孩子一本详细的动物识别手册,而机器学习则是给孩子看大量动物图片,让他自己总… · 2026/11/2 23:08:20
CES 2026:消费电子展的娱乐化转型与技术革新 1. 项目概述:CES 2026的"整活"盛宴CES国际消费电子展向来是科技界的风向标,但2026年的展会似乎正在经历一场有趣的转型。从"吃着棒棒糖听歌"到"看扫地机爬楼",这些看似荒诞的展示背后,反映的是消费… · 2026/11/2 23:08:20
回形针的物理课与传奇史:从办公桌到互联网营销经典 我不知道你们办公桌的抽屉里有没有这么一盒东西——混在长尾夹、订书钉和几支写不出水的笔中间,偶尔掉出一两枚锈迹斑斑的回形针。这东西小到几乎没人会专门为它下单,但真让我列一份“办公桌上不可或缺的十件杂物”,回形针一定排前三。今天这… · 2026/11/2 23:08:20
SSH安全防护:从暴力破解到密钥登录实战指南 1. 事件背景与问题定位去年夏天,我们团队的一台测试服务器突然出现异常流量暴增,最终导致整个测试环境瘫痪。经过排查发现,这台CentOS 7.9系统的测试机由于使用默认SSH端口22和弱密码"Test123",在开放公网3天后就遭到暴… · 2026/11/2 23:07:20
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
雷达信号调制技术解析:从LFM到伪线性调频原理与应用 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
基于MAX14431与TM4C123的工业SPI隔离电路设计全攻略 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
PLFM_RADAR:相位线性调频雷达信号处理框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 1:01:15
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18