npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hero-doll-kit

v1.13.3

Published

雷曼式部件骨架成品包:41 个动作(含六个大招) + 四槽换装 + 可选的战斗表现层与残影层,Cocos Creator 3.8。npm 装包后跑 hero-doll-sync 把资源同步进工程 assets/resources/。

Readme

hero-doll 部件骨架包

雷曼式部件骨架的成品包:35 个动作 + 四槽换装(头盔/武器/上衣/鞋),零外部依赖—— 动作是 Cocos 原生 cc.AnimationClip,运行时只有一个 HeroDoll 组件。 由骨架仓库 tools/pack-kit.py 打出,包里的 skins.ts 是生成物,别手改。

接入(Cocos Creator 3.8)

两种拿到包的方式,落地是同一个东西:

npm 方式(推荐,可跟版本更新)——在 Cocos 工程根目录:

npm i hero-doll-kit
npx hero-doll-sync        # 把资源同步进 assets/resources/hero-doll/

以后升级:npm update hero-doll-kit && npx hero-doll-sync。包自带固定 UUID 的 .meta, 原地覆盖后场景里的引用不会断。★Creator 只导入 assets/ 里的东西、不认 node_modules, 所以 sync 这步省不掉;同步是以包为准的整目录替换,别改 assets/resources/hero-doll/ 里的文件。

手工方式——整个 hero-doll/ 文件夹拷进目标工程的 assets/resources/ 下。 ★连 .meta 一起拷。贴图的 .meta 里写死了 type: sprite-frame——少了它,Creator 会按 工程默认导入,3D 模板工程的默认是 texture,没有 spriteFrame 子资源,换装会静默失效 (Bundle resources doesn't contain hero-doll/skins/xxx/spriteFrame)。

然后(两种方式相同):

  1. 必须在 resources 里——换装贴图按路径动态加载;改了文件夹名就同步改组件的 basePath 属性;
  2. 把 hero-doll.prefab 拖进场景(或 instantiate 出来);
  3. 根节点挂上 HeroDoll 组件;
  4. 代码里:
const doll = node.getComponent(HeroDoll);
doll.play('walk');              // 35 个剪辑名见 skins.ts 顶部注释
doll.play('attack4');           // 120° 大横斩,持刀手与武器整体放大
doll.crossFade('attack', 0.1);  // 带淡入切动作
doll.equip('helmet', 2);        // 戴第 2 顶盔;0 = 穿回原件
doll.skinList('weapon');        // 给换装 UI 用:[{name, icon 路径}],第 0 项是原件
doll.bone('head');              // 取得骨骼节点,叠加注视/受击等程序控制
doll.slot('weapon');            // 取得手腕下的武器附件槽,可挂刀光/枪口特效
doll.setIK('foot-front', groundTarget, { weight: 1, maxDistance: 420 });
doll.setIK('hand-front', gripTarget, { followRotation: true });
doll.clearIK('foot-front');      // 解除单脚;不传参数则解除全部 IK

// 通用约束
doll.setTransformConstraint('hand-back', shieldGrip, {
  positionWeight: 1, rotationWeight: 0.5,
});
doll.setAimConstraint('head', enemy, {
  weight: 0.8, offsetAngle: 180, minAngle: -35, maxAngle: 35,
});
doll.setGroundConstraint('foot-front', groundNode, 0, 1);
doll.setRotationLimit('head', -40, 40);
doll.setPathConstraint('hand-front', [pathA, pathB, pathC, pathD], {
  progress: 0, speed: 0.35, loop: true, weight: 1,
  orientToPath: true, rotationOffset: 90,
});
doll.setPathProgress('hand-front', 0.5); // speed=0 时可手动驱动
doll.clearConstraints('head');

// Clipping:水面以下隐藏;也可用 ellipse 做圆形窗口
doll.setWaterline(180, true);
doll.setClipRect(-240, 100, 480, 520, 'ellipse');
doll.clearClipping();

// 帧事件统一从角色根节点发出
doll.node.on(HeroDoll.EVENT, (name: string, clip: string, payload: string) => {
  if (name === 'hit') damageEnemy();
  if (name === 'footstep') playStepSound(payload); // payload = front/back
});

动画事件

攻击命中、脚步、落地、施法和拾取等时间点已经写进 .anim。运行时组件会把 Cocos 帧事件从 内部 rig 节点统一转发为根节点的 HeroDoll.EVENT,监听参数依次是事件名、剪辑名和标签。 事件只说明动画到达关键节拍,不会自动造成伤害或播放声音,具体游戏逻辑由监听方决定。

IK(浮空部件版)

setIK 接收场景中的目标节点,在 Animation 每帧采样之后修正对应骨骼的世界位置。把目标节点留在 地面即可钉住脚;把它挂到移动武器或方向盘上即可让手跟随。weight 可在动画姿势与 IK 之间混合, maxDistance 防止部件离身体太远,followRotation 可让脚贴斜坡或让手跟随握点角度。

当前图片没有上臂/前臂和大腿/小腿,因此这是末端约束,不会计算肘膝弯曲;以后增加分段肢体时, 可以保持 setIK 的调用方式并把内部求解器升级成双骨 IK。

通用 Constraints

  • setTransformConstraint:按独立权重跟随目标位置和旋转,可附加世界坐标偏移;
  • setAimConstraint:朝向目标,可设置图片方向校正、混合权重和最小/最大角;
  • setGroundConstraint:骨骼不能低于固定世界高度或移动地面节点;
  • setPathConstraint:沿两个以上控制点组成的 Catmull-Rom 曲线移动,可自动播放或手动控制进度;
  • setPathProgress:直接设置路径归一化进度;
  • setRotationLimit:限制最终世界旋转角;
  • clearConstraints:清除单个部件或全部通用约束。

路径控制点可传 Node(实时读取世界坐标,路径会跟着节点移动)或 Vec3(固定世界坐标)。 orientToPath 会用曲线切线控制骨骼朝向,rotationOffset 用于校正图片本身的朝向。

每帧固定按“关键帧动画 → IK → Transform → Path → Ground → Aim → Rotation Limit”求解,最后的旋转限制 因此能够兜住 Aim 或 Transform 产生的过大角度。所有约束都不启用时没有额外姿势改动。

Clipping 遮罩

预制体在 hero-doll 与 rig 之间带独立 clip 容器。setClipRect 支持矩形和椭圆,坐标使用 角色局部坐标;setWaterline 是水面裁切快捷方式,可选择只显示水上或水下。运行时会移动 clip、 同时反向移动 rig,所以改变遮罩区域不会挪动角色和骨骼。clearClipping 可无损关闭遮罩。

HeroDollFighter(可选的战斗表现层)

HeroDoll 是纯播放器:你叫它播什么它播什么。HeroDollFighter 是盖在上面的一层, 管跳跃状态机、连招串接、命中顿帧/震屏/特效。两个都挂在同一个根节点上,不挂 Fighter 不影响 HeroDoll 的任何功能。

const fighter = node.getComponent(HeroDollFighter);
fighter.setWeapon(3);                    // 和 doll.equip('weapon', 3) 配套调用
fighter.attack();                        // 连点会顺着当前档位的连段往下接
fighter.jump();
fighter.setMotion(vy, grounded);         // 每帧把你的移动逻辑喂进来,它据此选 air/fall/land
if (fighter.locked) return;              // 出招硬直中,别让玩家移动
fighter.weaponTier;                      // 'light' | 'mid' | 'heavy'

连段按武器档位选,档位写在 skins.ts 的 tier 字段里(由 tools/gen-skins.py 生成):

| 档位 | 连段 | 武器 | |---|---|---| | light | punch | 空手(「无」) | | mid | attack → attack2 → attack3 | 剑/刀/戟/枪等刃类 | | heavy | attack4 → slam | 斧/锤/狼牙棒 |

这份表只是默认值,每个游戏的招式设计不一样 —— 比如某一招是你关卡设计的支点 (会真跃起、落地炸一片、带无敌帧,怪位和平台间距都照它摆),那它必须留在连段里。 两种改法,改完不影响连段状态机的其余部分:

fighter.setCombo('mid', ['attack4', 'attack4', 'slam']);  // 换掉 mid 档
fighter.setCombo('mid');                                   // 不传 = 撤销,回到内置那份
fighter.chainOf('mid');                                    // 查当前实际用的是哪串

或者在编辑器里填 comboOverride 属性,一行一档,留空则用内置的:

mid: attack4, attack4, slam
light: punch, punch

★剪辑名必须在 prefab 的 _clips 里(35 个剪辑名见 skins.ts 顶部注释)。 setCombo 会先替你查一遍,对不上的会 warn 出来 —— 没登记的剪辑 play() 不播但照锁硬直。

特效搭配:哪把刀配哪道光归你

内置的推导是「武器档位 → fx/ 目录,同档里按出现次序分配变体,slam 钉死用紫新月」。 那是这套美术的编排,不是骨架的判断 —— 你的刀和你的光不一定这么配。 一个静态钩子接管全部三条,不设则行为完全不变:

import { FX_VARIANTS } from './hero-doll/skins';

HeroDollFighter.pickFx = ({ weaponIdx, tier, clip, onGround }) => {
  if (onGround) return null;                    // 砸地那圈交回默认
  if (clip === 'slam') return 'heavy/05';       // 收招大招用自家这套
  return `${tier}/01`;
};

返回相对 fx/ 的目录名;返回 null 或不设 → 落回内置推导。变体总数从 FX_VARIANTS 读(打包时数出来的),拼字符串前对一下就不会指到不存在的目录。 是静态的:一个工程一套美术编排,不用逐个实例配。

★预热只跑「当前武器 + 空剪辑名」那一套(换武器时问一次),按剪辑分叉的套子第一次 触发会晚一拍:那一下先不出,加载完从下一次起正常。

移动锁:攻击硬直和落地恢复是两回事

fighter.locked        // 攻击中 or 落地恢复中 —— 别吃移动输入
fighter.attackLocked  // 只有攻击硬直
fighter.landing       // 只有落地恢复
fighter.landRecover   // 落地恢复时长,默认 0.30(land 剪辑本身就是 0.30s)

setMotion 只被攻击硬直拦。落地恢复那 0.30s 里人完全可能已经又离地了 (连跳、兔子跳),所以那段照样吃 setMotion,air/fall 播得出来。 拦移动输入用 locked,语义和以前一致。

朝向

刀气方向读的是世界缩放(getWorldScale().x),不是娃娃节点自己的 scale.x。 横版的常规做法是把翻转挂在更上层的角色节点上(影子、倾斜、跳跃放大都读同一个 scale), 娃娃节点自己的 scale.x 恒为正 —— 所以不必为了迁就特效层把翻转下沉到娃娃节点。

特效大小不是给倍率、是给目标屏幕宽度再反推缩放(FX_W),所以换任意画幅的图都自动对齐。 特效挂点默认取 node.parent——★别挂在角色节点下面,角色一走动已经甩出去的剑气会跟着平移。

帧事件之外:自己挑时机

上面那套是「播剪辑 → 帧事件 → 出特效」,时机写死在 .anim 里。 跟着物理弧线采样驱动姿势的接入方没有 play(),也就没有帧事件 —— 跳多高、多久落地每关都不同, 固定时长的播放对不上,落地冲击波也必须炸在真正着地那一刻,而不是剪辑的第 0.90 秒。

这三个入口是给这种情况的,走的是和 hit / land 帧完全同一段代码, 缩放、朝向、锚点、前伸那套照旧:

fighter.spawnSlash();              // 按当前武器档位甩一道
fighter.spawnSlash('slam');        // 指定剪辑名 → 走 SLASH_FX 的专属配色(下劈的紫剑气)
fighter.spawnSlash('slam', true);  // onGround:砸地那圈,冒在脚下
fighter.feelHit();                 // 顿帧 + 震屏,照 hitStop / hitShake / shakeTarget
fighter.strikeBolt();              // 砸地落雷,landBolt 关掉时自动空转

★帧事件里的 land 分支是三件一起(特效 + 落雷 + 顿帧)。自己驱动时只调 spawnSlash 会少掉后两件,想还原完整的落地冲击就三个都调:

fighter.spawnSlash('slam', true);
fighter.strikeBolt();
fighter.feelHit();

这几个入口是纯表现:不播剪辑、不锁移动、不推进连段。硬直和状态归你自己管。

HeroDollGhost(可选的残影层)

冲刺、滑铲、攻击、腾空时身后留一串正在淡出的自己。和 HeroDollFighter 一样是可选层: 不挂就零开销,挂了也不碰 HeroDoll 的任何状态。

唯一要你决定的是「此刻该不该留影」 —— 那是玩法。池子、间隔、淡出、摆位都在这层里, 因为它们跟着角色长相走:十个关卡各抄一份「怎么留影」,就是十份会一起腐烂的代码。

const gh = node.getComponent(HeroDollGhost);
gh.count = 4;        // 池子大小(同时最多几道)
gh.life = 0.22;      // 每道存活秒数
gh.gap = 0.055;      // 每隔多久留一道
gh.alpha = 130;      // 起始透明度,之后线性淡到 0
gh.tint = new Color(140, 210, 255, 255);   // 染色;useTint 关掉则照抄本色
gh.parentNode = fxLayer;   // 挂哪。不填取**娃娃节点**的父级 —— 见下面这条★
gh.behind = true;          // 排到父节点最前,画在角色下层

update(dt) { gh.step(isDashing || isAttacking || airborne, dt); }

另外两个入口:gh.emit() 不看间隔冷却,立刻补一道(起跳瞬间、命中帧这种精确时刻用); gh.clear() 立刻收掉所有在场的残影。

★**parentNode 不填时取的是娃娃节点的父级**。在标准用法里(prefab 直接拖进场景、 每帧挪的就是娃娃节点)这是对的。但如果你把娃娃当视觉子节点挂在自己的角色节点下 (碰撞体、物理、HUD 锚点都在那一层,每帧挪的是外层),那么默认值恰好就是那个会动的节点 —— 留在原地的残影会跟着人一起平移,整条拖影塌成一坨。这时必须显式指定一个静止的层:

gh.parentNode = stageLayer;   // 通常是角色节点的父级,不是娃娃节点的父级

组件自己会查这一条:连续两次留影之间,父级动了、而且基本是驮着角色一起动的, 就在控制台 warn 一次(世界层在滚——视差、地图平移——不会误报,那种情况残影相对世界是静止的)。

想自己管池子

Boss 冲刺残影、定格回放、传送残像这类,池子和时机确实是关卡自己的事,用低一层的入口:

doll.ghostInto(target, tint?);   // 把当前姿势拓一份到 target 下,返回成功与否
doll.clearGhost(target?);        // 丢掉副本缓存(target 销毁后调)

★ghostInto 只管「拓印」这一件事。副本根不带本体根节点的 transform —— 根变换由 target 承担,否则会和 target 叠两次。自己调的话把 target 摆到角色的世界位姿上。

拓印做了哪些事(自己写一份的话这些都得有)

这几条都是「不报错、只是画面不对」,列在这里省得再踩一遍:

| | | |---|---| | 克隆先拆组件再挂进场景 | instantiate 会把 HeroDoll / HeroDollFighter / Animation 一起复制走。挂进场景那一刻它们的 onLoad 就跑了 —— 每一道残影都会去加载自己的换装贴图、注册自己的帧事件、命中时甩自己的刀气。顺序必须是 instantiate → 拆 → 再 parent(未激活的节点不跑 onLoad)。这里用白名单:只留 UITransform / Sprite(含 MeshSprite)/ UIOpacity / Mask,你自己往娃娃节点上挂的脚本也一样不该在残影里跑 | | 连 UITransform 一起拷 | 换装会改部件的 contentSize 和 anchorPoint(每件皮肤挂点锚点都不同)。只拷 position/scale/angle 的话,残影会拿原件的锚点去摆新装备的图,肩膀和武器错位 | | 收全部节点,不只是带 Sprite 的 | clip / rig / spin / <部件>-fx 都是纯容器,但剪辑的整体位移、翻滚转体、挤压拉伸正写在这几层上。只收 Sprite 节点的话残影看着是「死的」 | | MeshSprite 的四个标量也要拷 | 袍摆/披风的形状由 bend / wave / bulge / phase 决定,不在 transform 里。不拷的话本体在飘、残影是直筒(walk/run/dash/fall 最明显) | | 残影挂在会动的节点外面 | 挂在每帧移动的节点下,留在原地的残影会跟着平移。注意 parentNode 默认取的是娃娃节点的父级 —— 娃娃自己就是角色时没问题,娃娃是某个会动的角色节点的视觉子节点时就得显式指定。根变换取的是世界位姿,所以翻转挂在更上层也认,娃娃节点自己再有位移(setWaterline)也算得对 |

skins.ts 里除了换装表还有什么

包里这几个导出是接入方会用到、但只看换装表发现不了的:

| | | |---|---| | tier | 武器档位,HeroDollFighter 靠它选连招和特效。自己实现战斗层的话也从这里读,别在代码里写死武器编号 | | alt | 一件装备额外覆盖的部件。目前只有「无」用到:它除了换 hand-front,还带一只 hand-back | | RIG_PER_PX | 节点坐标 ÷ 屏幕像素 = 11.5633。角色在节点坐标里 1827 单位高,美术意图是屏幕上 158px。凡是"想让某个东西在屏幕上有 N 像素"的换算都要过这个数 | | FX_VARIANTS | 每档特效有几套配色,按武器在本档里的次序分配。改 fx/ 目录数就得同步改它,否则取模会指到不存在的目录 |

自己实现换装 / 自己驱动时间轴要注意

用 doll.play() 和 doll.equip() 的话下面三条都已经在组件里处理好了,不用管。 绕过它们自己实现的接入方才需要看:

  • equip 要按"槽的部件 ∪ 该槽任何一件 skin 用 alt 覆盖到的部件"来遍历,不能只按 SLOT[slot] 那一份。因为「无」比别的武器多带一只 hand-back:从「无」换成大剑时, 只遍历 SLOT['weapon'] = ['hand-front'] 的话,那只后手会留在身上换不回去。

  • 手动 sample() 驱动时间轴、不走 play() 的,切剪辑时必须自己把 <部件>-fx 层的 scale 归位。 各剪辑写的 fx 节点不是同一批——land/hurt/crouch 写三个, 其余只写一个。没写到的那些不会被新剪辑覆盖,于是落地压扁的脚会带进待机姿势。

  • MeshSprite 的 bend/wave/bulge/phase 同理会残留。 cast 写 bend+bulge、 walk 写 wave+phase+bend、attack4 一个都不写,从前两个切到 attack4 会留着上一个的形变。

挤压拉伸 / 网格变形

两档,都不用写运行时代码——数据在剪辑里,引擎自己采样。

挤压拉伸(非等比缩放):每根骨骼下有一层 <部件>-fx 节点专收动作缩放, scl 轨的 x/y 分开发,保体积(sx*sy≈1)就是 squash & stretch。 land 着地压扁、crouch 蹬地拉长、hurt 挨打横压 都用了。 放在 fx 层是为了不覆盖换装比例。

网格变形(MeshSprite)+ 权重绑定:刚体旋转做不出来的东西——披风下摆、袍摆、翅膀弯曲。 被任何动作变形过的部件,预制体里挂的是 MeshSprite(继承 cc.Sprite,多四个属性), 把图切成 1×8 网格逐顶点位移。目前 fly 的袍摆飘动、cast 的聚气带袍用了。

四个属性由动画直接驱动,不存逐顶点关键帧——那会把 .anim 撑爆而且没法手调:

| | | |---|---| | bend | 顶边钉住、底边横移(弯曲/被风吹斜) | | wave | 沿纵向的正弦横移(下摆飘动) | | bulge | 中段横向鼓胀(呼吸/鼓包) | | phase | wave 的相位(让波往下走) |

顶点位置由 MeshSprite.deform() 现算:布料沿纵向切成 6 节骨骼,顶端焊死在挂点上, 每个顶点绑到相邻两节、权重 (1-f, f)。那四个标量摊成每节的转角,走正向动力学算关节位置—— 所以布料长度守恒,弯到 90° 是卷起来而不是被抻长。

这条公式在三处必须一模一样: MeshSprite.ts / 骨架仓库的 hero_rig.mesh_deform() / 调参页——改一处三处一起改。

不走 3D MeshRenderer 是因为它和 UI 的层级排序打架:这套骨架的前后关系全靠节点顺序读 (子节点必然画在父节点之上),换 3D 渲染器等于把立身之本拆了。所以用 2D 的自定义 assembler。

结构

| | | |---|---| | hero-doll.prefab | hero-doll → clip → rig → spin → body-bone → 头/双手/双脚 bone → 图片 attachment。clip 管遮罩,翻滚/击飞转 spin;前手骨下预留 weapon-slot | | anims/ | 35 个剪辑:转角度 + 位移(y 蹲压 / x 前伸),引擎原生采样,无自定义运行时 | | parts/ skins/ icons/ | 本体贴图 / 39 件换装 / 选择格子图标 | | skins.ts | 换装表:每件的挂点锚点 + 归一缩放,HeroDoll.equip 只查表。另外导出 tier / alt / RIG_PER_PX / FX_VARIANTS,见下 | | HeroDollFighter.ts | 可选的战斗表现层:跳跃状态机 + 按武器档位选连招 + 命中顿帧/震屏/特效。不挂它,HeroDoll 照常工作——纯播放器 | | HeroDollGhost.ts | 可选的残影层:池子 + 淡出 + 拓印。不挂就零开销 | | fx/ | 命中特效贴图,按 档位/套号/fx-01.png… 组织。由 HeroDollFighter 加载,不挂它就用不到 | | HeroDollEventRelay.ts | 帧事件转发器,HeroDoll 自己挂上去用。别删也别并进 HeroDoll.ts——Cocos 一个脚本只允许一个 Component 类,并进去会 errorID(3615) 然后 _sealed TypeError,整个场景起不来 |

.meta 是包的一部分:prefab 靠 UUID 引用贴图和剪辑,丢了 meta 引用全断。 拷贝时保留隐藏文件;同一工程里别导入两份(UUID 会撞)。

换一个角色 / 换一批装备图能用吗

能——组件和表全是生成的,美术全换也不用改代码,但要回骨架仓库重跑工具链:

新散图 → tools/cut-parts.py / cut-sheet.py     # 切件
       → tools/tone-match.py                   # 调子拉齐
       → tools/gen-skins.py                    # 量挂点/缩放 → skins.py
       → tools/gen-anims.py                    # 出 35 个 .anim
       → 工程里跑 tools/gen-hero-prefab.py     # 出 prefab(贴图 UUID 每个工程不同)
       → tools/pack-kit.py                     # 重新打包

两个前提:新角色得是同构的(6 件浮空部件、朝左、比例别差太远——动作表里的角度 是按这套身材调的,身材大变要回调参页重调);量挂点的规则里有几条是按这套图定的 (武器找白手套、头盔补 17px 下巴插深),换画风可能要在 gen-skins.py 里改常数。

发一个新版本(骨架仓库这边)

#  1. 改 tools/kit/package.json 的 version(版本号真源在 tools/kit/,kit/ 是生成物)
python3 tools/pack-kit.py          # 2. 重打包
cd kit/hero-doll && npm publish    # 3. 发布(首次要 npm login;包名 hero-doll-kit)