mirror of
https://github.com/HappyLifeOk/cc-3-8-x-mcp.git
synced 2026-06-10 17:56:47 +00:00
379 lines
15 KiB
JavaScript
379 lines
15 KiB
JavaScript
|
|
// ============================================================
|
|||
|
|
// CC3 AnimationClip (.anim) 对象构建原语(纯 CJS,零三方依赖)
|
|||
|
|
//
|
|||
|
|
// .anim 文件和 .prefab 一样是 JSON 数组 + `__id__` 交叉引用,
|
|||
|
|
// 复用 parsePrefab / writePrefab 解析写入。
|
|||
|
|
//
|
|||
|
|
// 但 .anim 内部对象类型(AnimationClip / Track / Curve / Channel)
|
|||
|
|
// 有自己的 schema 规范,最容易踩的坑:
|
|||
|
|
//
|
|||
|
|
// ✅ cc.animation.RealTrack → 单通道 → `_channel: {__id__: X}` (单数)
|
|||
|
|
// ✅ cc.animation.ObjectTrack → 单通道 → `_channel: {__id__: X}` (单数)
|
|||
|
|
// ✅ cc.animation.VectorTrack → 多通道 → `_channels: [x, y, z]` (复数数组)
|
|||
|
|
// ✅ cc.animation.ColorTrack → 多通道 → `_channels: [r, g, b, a]` (复数数组)
|
|||
|
|
//
|
|||
|
|
// 写错字段名(比如给 RealTrack 写 `_channels: [...]`)会导致 CC3 编辑器
|
|||
|
|
// 按 schema 验证时直接忽略整条轨道,表现为「anim 文件里有数据但编辑器
|
|||
|
|
// 不显示关键帧、运行时也不播」——历史踩过的坑,见 anim-schema.md。
|
|||
|
|
//
|
|||
|
|
// 用本文件里的 make* 工厂函数,保证字段名一定写对。
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const { ref } = require('./primitives.js');
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// 插值模式常量
|
|||
|
|
// 对应 cc.RealCurve.interpolationMode 枚举
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
const InterpolationMode = Object.freeze({
|
|||
|
|
LINEAR: 0, // 线性插值,两关键帧之间平滑过渡
|
|||
|
|
CONSTANT: 1, // 常量插值,保持当前值直到下一帧瞬变
|
|||
|
|
CUBIC: 2, // 三次插值,带缓动曲线
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// Extrapolation 模式常量
|
|||
|
|
// 对应 cc.RealCurve.preExtrapolation / postExtrapolation
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
const Extrapolation = Object.freeze({
|
|||
|
|
LINEAR: 0,
|
|||
|
|
CLAMP: 1, // 最常用:首帧之前保持首帧值,末帧之后保持末帧值
|
|||
|
|
REPEAT: 2,
|
|||
|
|
PINGPONG: 3,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// RealKeyframeValue:单个浮点关键帧
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {number} opts.value
|
|||
|
|
* @param {number} [opts.interpolationMode=CONSTANT] 0=LINEAR 1=CONSTANT 2=CUBIC
|
|||
|
|
* @param {number} [opts.easingMethod=0] 0=Linear,其余见 cc.EasingMethod
|
|||
|
|
*/
|
|||
|
|
function makeRealKeyframe(opts) {
|
|||
|
|
const { value, interpolationMode = InterpolationMode.CONSTANT, easingMethod = 0 } = opts;
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.RealKeyframeValue',
|
|||
|
|
interpolationMode,
|
|||
|
|
tangentWeightMode: 0,
|
|||
|
|
value,
|
|||
|
|
rightTangent: 0,
|
|||
|
|
rightTangentWeight: 1,
|
|||
|
|
leftTangent: 0,
|
|||
|
|
leftTangentWeight: 1,
|
|||
|
|
easingMethod,
|
|||
|
|
__editorExtras__: null,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.RealCurve:浮点曲线
|
|||
|
|
// times/values 一一对应,长度必须相同
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {number[]} opts.times - 递增时间点(秒)
|
|||
|
|
* @param {object[]} opts.values - RealKeyframeValue 对象数组,与 times 同长
|
|||
|
|
* @param {number} [opts.preExtrapolation=1] CLAMP
|
|||
|
|
* @param {number} [opts.postExtrapolation=1] CLAMP
|
|||
|
|
*/
|
|||
|
|
function makeRealCurve(opts) {
|
|||
|
|
const { times, values, preExtrapolation = Extrapolation.CLAMP, postExtrapolation = Extrapolation.CLAMP } = opts;
|
|||
|
|
if (times.length !== values.length) {
|
|||
|
|
throw new Error(`makeRealCurve: times.length (${times.length}) !== values.length (${values.length})`);
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.RealCurve',
|
|||
|
|
_times: times,
|
|||
|
|
_values: values,
|
|||
|
|
preExtrapolation,
|
|||
|
|
postExtrapolation,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.ObjectCurve:对象引用曲线(用于 spriteFrame 等资产序列)
|
|||
|
|
// values 是资产引用对象数组(`{ __uuid__, __expectedType__ }`)
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {number[]} opts.times
|
|||
|
|
* @param {object[]} opts.values - 资产引用对象
|
|||
|
|
*/
|
|||
|
|
function makeObjectCurve(opts) {
|
|||
|
|
const { times, values, preExtrapolation = Extrapolation.CLAMP, postExtrapolation = Extrapolation.CLAMP } = opts;
|
|||
|
|
if (times.length !== values.length) {
|
|||
|
|
throw new Error(`makeObjectCurve: times.length (${times.length}) !== values.length (${values.length})`);
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.ObjectCurve',
|
|||
|
|
_times: times,
|
|||
|
|
_values: values,
|
|||
|
|
preExtrapolation,
|
|||
|
|
postExtrapolation,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.Channel:Track → Curve 的中间层
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {number} curveIdx - RealCurve/ObjectCurve 在 objects 数组里的下标
|
|||
|
|
*/
|
|||
|
|
function makeChannel(curveIdx) {
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.Channel',
|
|||
|
|
_curve: ref(curveIdx),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.HierarchyPath / ComponentPath / TrackPath
|
|||
|
|
// 用于定位轨道的目标节点与属性
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {string} path - 节点层级路径,如 "n4" 或 "content/titleBar";根节点自身写 ""
|
|||
|
|
*/
|
|||
|
|
function makeHierarchyPath(path) {
|
|||
|
|
return { __type__: 'cc.animation.HierarchyPath', path };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param {string} component - 组件类型,如 "cc.UIOpacity" / "cc.Sprite"
|
|||
|
|
*/
|
|||
|
|
function makeComponentPath(component) {
|
|||
|
|
return { __type__: 'cc.animation.ComponentPath', component };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param {unknown[]} parts - 混合 HierarchyPath/ComponentPath 引用与属性字符串
|
|||
|
|
* 例:[ref(hierIdx), ref(compIdx), "opacity"]
|
|||
|
|
* 或根节点属性:[ref(compIdx), "position"](无 hierarchy path)
|
|||
|
|
*/
|
|||
|
|
function makeTrackPath(parts) {
|
|||
|
|
return { __type__: 'cc.animation.TrackPath', _paths: parts };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.RealTrack(单通道浮点轨道)
|
|||
|
|
// ⚠️ 字段必须是 `_channel`(单数),不是 `_channels`
|
|||
|
|
// ⚠️ 写错会被 CC3 编辑器 schema 验证忽略,轨道形同虚设
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {number} trackPathIdx - TrackPath 在 objects 数组里的下标
|
|||
|
|
* @param {number} channelIdx - Channel 在 objects 数组里的下标
|
|||
|
|
*/
|
|||
|
|
function makeRealTrack(trackPathIdx, channelIdx) {
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.RealTrack',
|
|||
|
|
_binding: {
|
|||
|
|
__type__: 'cc.animation.TrackBinding',
|
|||
|
|
path: ref(trackPathIdx),
|
|||
|
|
proxy: null,
|
|||
|
|
},
|
|||
|
|
_channel: ref(channelIdx),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.ObjectTrack(单通道对象轨道,常用于 spriteFrame 序列帧)
|
|||
|
|
// ⚠️ 字段必须是 `_channel`(单数)
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {number} trackPathIdx
|
|||
|
|
* @param {number} channelIdx
|
|||
|
|
*/
|
|||
|
|
function makeObjectTrack(trackPathIdx, channelIdx) {
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.ObjectTrack',
|
|||
|
|
_binding: {
|
|||
|
|
__type__: 'cc.animation.TrackBinding',
|
|||
|
|
path: ref(trackPathIdx),
|
|||
|
|
proxy: null,
|
|||
|
|
},
|
|||
|
|
_channel: ref(channelIdx),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.VectorTrack(多通道,x/y/z 或 x/y/z/w)
|
|||
|
|
// ⚠️ 字段必须是 `_channels`(复数,数组),且必须提供 `_nComponents`
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {number} trackPathIdx
|
|||
|
|
* @param {number[]} channelIndices - 各轴 Channel 下标数组(长度 = nComponents)
|
|||
|
|
* @param {number} nComponents - 2(Vec2) / 3(Vec3) / 4(Vec4/Quat)
|
|||
|
|
*/
|
|||
|
|
function makeVectorTrack(trackPathIdx, channelIndices, nComponents) {
|
|||
|
|
if (channelIndices.length !== nComponents) {
|
|||
|
|
throw new Error(`makeVectorTrack: channelIndices.length (${channelIndices.length}) !== nComponents (${nComponents})`);
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.VectorTrack',
|
|||
|
|
_binding: {
|
|||
|
|
__type__: 'cc.animation.TrackBinding',
|
|||
|
|
path: ref(trackPathIdx),
|
|||
|
|
proxy: null,
|
|||
|
|
},
|
|||
|
|
_channels: channelIndices.map(ref),
|
|||
|
|
_nComponents: nComponents,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.ColorTrack(4 通道 r/g/b/a)
|
|||
|
|
// ⚠️ 字段必须是 `_channels`(复数,数组),无 `_nComponents`
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {number} trackPathIdx
|
|||
|
|
* @param {number[]} channelIndices - [rIdx, gIdx, bIdx, aIdx]
|
|||
|
|
*/
|
|||
|
|
function makeColorTrack(trackPathIdx, channelIndices) {
|
|||
|
|
if (channelIndices.length !== 4) {
|
|||
|
|
throw new Error(`makeColorTrack: expected 4 channel indices, got ${channelIndices.length}`);
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.ColorTrack',
|
|||
|
|
_binding: {
|
|||
|
|
__type__: 'cc.animation.TrackBinding',
|
|||
|
|
path: ref(trackPathIdx),
|
|||
|
|
proxy: null,
|
|||
|
|
},
|
|||
|
|
_channels: channelIndices.map(ref),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.AnimationClipAdditiveSettings(每个 clip 尾巴一份)
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
function makeAdditiveSettings() {
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.AnimationClipAdditiveSettings',
|
|||
|
|
enabled: false,
|
|||
|
|
refClip: null,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.AnimationClip 文件头(objects[0])
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {string} opts.name
|
|||
|
|
* @param {number} opts.sample - 采样帧率
|
|||
|
|
* @param {number} opts.duration - 秒
|
|||
|
|
* @param {number} [opts.speed=1]
|
|||
|
|
* @param {number} [opts.wrapMode=1] 1=Normal, 2=Loop, 22=PingPong, 36=PingPongLoop
|
|||
|
|
* @param {number} opts.hash - 哈希值(通常 hashString(name))
|
|||
|
|
* @param {number[]} opts.trackIndices - 所有 Track 在 objects 数组里的下标
|
|||
|
|
* @param {number} opts.additiveIdx - AdditiveSettings 下标
|
|||
|
|
* @param {number[]} [opts.embeddedPlayerIndices=[]] - EmbeddedPlayer 下标
|
|||
|
|
*/
|
|||
|
|
function makeAnimationClip(opts) {
|
|||
|
|
const {
|
|||
|
|
name,
|
|||
|
|
sample,
|
|||
|
|
duration,
|
|||
|
|
speed = 1,
|
|||
|
|
wrapMode = 1,
|
|||
|
|
hash,
|
|||
|
|
trackIndices,
|
|||
|
|
additiveIdx,
|
|||
|
|
embeddedPlayerIndices = [],
|
|||
|
|
} = opts;
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.AnimationClip',
|
|||
|
|
_name: name,
|
|||
|
|
_objFlags: 0,
|
|||
|
|
__editorExtras__: { embeddedPlayerGroups: [] },
|
|||
|
|
_native: '',
|
|||
|
|
sample,
|
|||
|
|
speed,
|
|||
|
|
wrapMode,
|
|||
|
|
enableTrsBlending: false,
|
|||
|
|
_duration: duration,
|
|||
|
|
_hash: hash,
|
|||
|
|
_tracks: trackIndices.map(ref),
|
|||
|
|
_exoticAnimation: null,
|
|||
|
|
_events: [],
|
|||
|
|
_embeddedPlayers: embeddedPlayerIndices.map(ref),
|
|||
|
|
_additiveSettings: ref(additiveIdx),
|
|||
|
|
_auxiliaryCurveEntries: [],
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// cc.animation.EmbeddedPlayer / EmbeddedAnimationClipPlayable
|
|||
|
|
// 用于 movieclip 这类「主 clip 触发节点自己 Animation 播子 clip」的结构
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {number} opts.begin - 主 clip 时间线上子 clip 开始秒
|
|||
|
|
* @param {number} opts.end - 主 clip 时间线上子 clip 结束秒
|
|||
|
|
* @param {number} opts.playableIdx - EmbeddedAnimationClipPlayable 下标
|
|||
|
|
*/
|
|||
|
|
function makeEmbeddedPlayer(opts) {
|
|||
|
|
const { begin, end, playableIdx } = opts;
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.EmbeddedPlayer',
|
|||
|
|
begin,
|
|||
|
|
end,
|
|||
|
|
reconciledSpeed: false,
|
|||
|
|
playable: ref(playableIdx),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @param {object} opts
|
|||
|
|
* @param {string} opts.path - 目标节点路径(相对 clip 所在节点)
|
|||
|
|
* @param {string} opts.clipUuid - 子 clip 资产 UUID
|
|||
|
|
*/
|
|||
|
|
function makeEmbeddedAnimationClipPlayable(opts) {
|
|||
|
|
const { path, clipUuid } = opts;
|
|||
|
|
return {
|
|||
|
|
__type__: 'cc.animation.EmbeddedAnimationClipPlayable',
|
|||
|
|
path,
|
|||
|
|
clip: {
|
|||
|
|
__uuid__: clipUuid,
|
|||
|
|
__expectedType__: 'cc.AnimationClip',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
// 哈希函数:生成 AnimationClip._hash
|
|||
|
|
// 用简单字符串哈希,只需保证同名 clip 得到同一 hash 即可
|
|||
|
|
// ─────────────────────────────────────────────
|
|||
|
|
function hashString(s) {
|
|||
|
|
let hash = 0;
|
|||
|
|
for (let i = 0; i < s.length; i++) {
|
|||
|
|
hash = ((hash << 5) - hash) + s.charCodeAt(i);
|
|||
|
|
hash |= 0;
|
|||
|
|
}
|
|||
|
|
return Math.abs(hash);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
InterpolationMode,
|
|||
|
|
Extrapolation,
|
|||
|
|
makeRealKeyframe,
|
|||
|
|
makeRealCurve,
|
|||
|
|
makeObjectCurve,
|
|||
|
|
makeChannel,
|
|||
|
|
makeHierarchyPath,
|
|||
|
|
makeComponentPath,
|
|||
|
|
makeTrackPath,
|
|||
|
|
makeRealTrack,
|
|||
|
|
makeObjectTrack,
|
|||
|
|
makeVectorTrack,
|
|||
|
|
makeColorTrack,
|
|||
|
|
makeAdditiveSettings,
|
|||
|
|
makeAnimationClip,
|
|||
|
|
makeEmbeddedPlayer,
|
|||
|
|
makeEmbeddedAnimationClipPlayable,
|
|||
|
|
hashString,
|
|||
|
|
};
|