首页 / 资讯中心 / 文章详情

[Cocos Creator] 本地文件加载系列七:帧动画,代码动态创建与 TaoToken 配置骨架

[Cocos Creator] 本地文件加载系列七:帧动画,代码动态创建与 TaoToken 配置骨架 ★ FEATURED ARTICLE
1. 为什么要在 Cocos Creator 里动态创建帧动画如果你做过 Cocos Creator 的序列帧动画大概率经历过这种流程美术给一张 plist 加一张 png拖进编辑器手动建 AnimationClip一帧一帧对改一次资源就要重来一遍。项目里角色皮肤一多编辑器里堆满 clip改起来非常痛苦。这篇要解决的就是这件事不依赖编辑器预制的 AnimationClip全部用代码在运行时动态创建帧动画。核心 API 是cc.AnimationClip.createWithSpriteFrames配合cc.SpriteAtlas或手动解析 plist把一组cc.SpriteFrame直接拼成可播放的 clip挂到cc.Animation上播放。适合谁看已经会 Cocos Creator 2.4.x 基础组件用法、做过 resources 动态加载、现在想让帧动画走配置化/热更/本地文件导入链路的开发者。场景覆盖两种一种是 resources 下打包好的图集动态加载另一种是用户从本地选 plistpng 文件后即时生成动画——后者在换装、捏脸、贴纸类项目里很常见。我试过把这两种链路统一成一套_createAnim出口代码量不大但踩的坑集中在 plist 解析和 sample/speed 参数上。下面按「前置准备 → 可复制配置 → 验证 → 排错」的顺序走一遍最后附上 TaoToken 的 settings.json 配置骨架方便你把模型调用通道也统一管起来。2. TaoToken 前置统一 Key 与 API 通道动态创建帧动画本身是纯客户端逻辑跟网络无关。但实际项目里帧动画资源往往来自后端下发、AI 生成序列帧、或者构建流水线里的自动化脚本这些环节会调用模型接口。与其在每个脚本里散落 base_url 和 key不如用 TaoToken 做一层统一通道。TaoToken 是一个模型 API 聚合入口把不同模型的调用收敛到一套 Key 和兼容 OpenAI 的接口格式上。对 Cocos 项目来说好处是构建脚本、资源处理工具、编辑器扩展可以共用同一份配置不用为每个模型单独维护鉴权。你需要先拿到 Key进入控制台创建 API Key地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建后复制保存Key 只在创建时完整显示一次。接口基地址统一用 https://taotoken.net/api 注意这个地址不带任何查询参数。模型对话调试可以在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 里直接试确认模型名和返回格式后再写进项目配置。如果你打算把帧动画生成、资源描述文案、批处理脚本都接进来长期跑的话可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 适合需要持续调用、按量可控的编码场景。注意TaoToken 只是模型调用的统一入口不替代 Cocos Creator 编辑器也不参与帧动画的渲染逻辑。它管的是「资源从哪来、脚本怎么调模型」动画创建仍然在引擎内完成。3. 可复制配置settings.json 与帧动画代码骨架3.1 settings.json 配置示例在项目根目录或工具脚本目录放一份settings.json把 TaoToken 通道参数集中管理。这样构建脚本、编辑器扩展、Node 侧工具都能读同一份{ taotoken: { baseUrl: https://taotoken.net/api, apiKey: sk-你的Key粘贴在这里, defaultModel: gpt-4o-mini, timeoutMs: 30000, retry: { maxAttempts: 3, backoffMs: 800 } }, frameAnim: { defaultSample: 24, defaultSpeed: 0.5, wrapMode: Normal, atlasPath: textures/role/role_atlas, clipName: role_run } }frameAnim段是给帧动画用的默认参数sample是每秒采样帧数speed是播放速度倍率这两个值直接决定动画快慢后面会重点讲。apiKey建议不要提交到仓库用环境变量或本地覆盖文件替代。读取配置的 Node 侧代码const fs require(fs); const path require(path); function loadSettings(root) { const p path.join(root, settings.json); const raw fs.readFileSync(p, utf-8); const cfg JSON.parse(raw); if (process.env.TAOTOKEN_API_KEY) { cfg.taotoken.apiKey process.env.TAOTOKEN_API_KEY; } return cfg; } module.exports { loadSettings };3.2 从 resources 图集动态创建帧动画这是最常用的一条链路。图集已经打进 resources运行时加载cc.SpriteAtlas取出所有SpriteFrame直接创建 clipconst { ccclass, property } cc._decorator; ccclass export class FrameAnimFromAtlas extends cc.Component { property(cc.Sprite) spr: cc.Sprite null; property(cc.Animation) sprAnim: cc.Animation null; private _clipName: string role_run; start() { this._loadLocalFrameAnim(); } private _loadLocalFrameAnim() { cc.resources.load(textures/role/role_atlas, cc.SpriteAtlas, (err, atlas: cc.SpriteAtlas) { if (err) { cc.error(图集加载失败, err); return; } const frames atlas.getSpriteFrames(); this._createAnim(frames); }); } private _createAnim(frames: cc.SpriteFrame[]) { if (!frames || frames.length 0) { cc.warn(没有可用的 SpriteFrame); return; } const clip cc.AnimationClip.createWithSpriteFrames(frames, frames.length); clip.name this._clipName; clip.speed 0.5; clip.sample 24; clip.wrapMode cc.WrapMode.Normal; this.sprAnim.addClip(clip); this.sprAnim.play(this._clipName); } }关键点createWithSpriteFrames(frames, length)的第二个参数是帧数一般直接传frames.length。sample要和美术导出 plist 时的帧率一致否则动画会忽快忽慢。3.3 从本地 plist png 动态创建这条链路用于用户选文件导入。流程是选 png → 转 Texture2D选 plist → 解析成帧数据 → 拼 SpriteFrame → 创建 clip。plist 里的frame字段是{{x,y},{w,h}}这种字符串需要自己解析private _parseRect(rectStr: string): cc.Rect { const rect new cc.Rect(); const strAry rectStr.replace(/[\{\}]/g, ).split(,); rect.x ~~strAry[0]; rect.y ~~strAry[1]; rect.width ~~strAry[2]; rect.height ~~strAry[3]; return rect; } private _parseVec2(vec2Str: string): cc.Vec2 { const v2 cc.v2(); const strAry vec2Str.replace(/[\{\}]/g, ).split(,); v2.x ~~strAry[0]; v2.y ~~strAry[1]; return v2; } private _parseSize(sizeStr: string): cc.Size { const strAry sizeStr.replace(/[\{\}]/g, ).split(,); return new cc.Size(~~strAry[0], ~~strAry[1]); } private _checkSpriteFrames() { if (!this._curPlistData || !this._curTexture) return; const frames this._curPlistData[frames]; const sprFrmAry: cc.SpriteFrame[] []; for (const k in frames) { const fd frames[k]; const rect this._parseRect(fd.frame); const offset this._parseVec2(fd.offset); const originalSize this._parseSize(fd.sourceSize); const sprFrm new cc.SpriteFrame( this._curTexture, rect, fd.rotated, offset, originalSize ); sprFrmAry.push(sprFrm); } this._createAnim(sprFrmAry); }_createAnim复用 3.2 里的实现即可。Texture2D 记得按需设置setPremultiplyAlpha(true)否则半透明边缘会有黑边。3.4 参数对照表参数作用常见取值踩坑点sample每秒采样帧数24 / 30与 plist 导出帧率不一致会变速speed播放速度倍率0.5 / 1.0小于 1 变慢大于 1 变快wrapMode循环模式Normal / Loop循环动画必须设 Loopframes.length帧数实际帧数传错会导致丢帧或越界4. 验证请求与成功结果4.1 验证 TaoToken 通道先用 curl 确认 Key 和 base_url 通curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: ping}] }返回里带choices数组就说明通道正常。如果 401检查 Key 是否复制完整如果 404检查 base_url 是否误加了路径后缀。4.2 验证帧动画把 3.2 的组件挂到节点上sprAnim指向同节点的cc.Animation运行后观察控制台无报错图集加载回调进入_createAnimsprAnim.play后 Sprite 开始逐帧切换把sample从 24 改成 12动画明显变慢一半说明参数生效。成功标志动画流畅播放切换wrapMode为Loop后循环不停。如果只显示第一帧多半是frames.length传成了 1或者 clip 没 addClip 就 play。5. 本篇常见错排查报错一createWithSpriteFrames返回 null 或动画不播放。原因通常是 frames 数组为空。检查atlas.getSpriteFrames()是否真的取到帧plist 解析时frames字段名是否拼错。加一行cc.log(frames.length)最快定位。报错二动画速度不对忽快忽慢。sample和美术导出 plist 时的帧率不一致。plist 里通常有metadata或帧信息能反推帧率把sample对齐即可。speed是额外倍率别拿它当帧率用。报错三本地 plist 解析后帧位置错乱。frame字符串里的顺序是x,y,w,h但有些导出工具是x,y,w,h带空格。~~转换前先trim()否则~~ 12在某些环境会得到 0。报错四半透明边缘发黑。Texture2D 没设预乘 alpha。加载后调用texture.setPremultiplyAlpha(true)再创建 SpriteFrame。报错五TaoToken 请求超时。timeoutMs设太短或网络环境波动。把retry.maxAttempts调到 3backoffMs用 800 起步指数退避。仍失败就去 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 确认 Key 状态。6. 把通道和动画链路固定下来动态创建帧动画的代码骨架其实就三段加载资源、拼 SpriteFrame、createWithSpriteFrames出 clip。真正花时间的是 plist 解析和参数对齐这两块上面都给了可直接复制的实现。TaoToken 那层配置建议单独抽成settings.json构建脚本和编辑器扩展共用。接入细节看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。需要长期跑批处理或 Agent 编码的Coding Plan 在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。模型名和返回格式先在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 里试通再写进配置能省掉不少调试时间。最后一个小技巧_createAnim里加个clip.name去重判断addClip前先removeClip同名 clip避免热更或重复导入时动画叠加。这个坑我在换装项目里踩过一次表现是动画越播越快查了半天才发现是 clip 被加了两次。
阅读完成 · 觉得有帮助?
咨询建站