默认预览setup pose、修复播放动画时的画布缩放问题
This commit is contained in:
1 parent
a4e86b37fc
commit
52bdbc9f3c
19 files changed
+166
-40
No files matched your search
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Ordner neu laden",
|
||||
"animationList": "Animationsliste",
|
||||
"animations": "Animationen",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Vorschau nicht möglich",
|
||||
"durationPlaceholder": "Dauer --",
|
||||
"keyframesLegend": "Blaue Marken: Keyframes",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "Reload folder",
|
||||
"animationList": "Animation list",
|
||||
"animations": "Animations",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Cannot preview",
|
||||
"durationPlaceholder": "Duration --",
|
||||
"keyframesLegend": "Blue ticks: keyframes",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Recargar carpeta",
|
||||
"animationList": "Lista de animaciones",
|
||||
"animations": "Animaciones",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "No se puede previsualizar",
|
||||
"durationPlaceholder": "Duración --",
|
||||
"keyframesLegend": "Marcas azules: fotogramas clave",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "フォルダーを再読み込み",
|
||||
"animationList": "アニメーション一覧",
|
||||
"animations": "アニメーション",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "プレビューできません",
|
||||
"durationPlaceholder": "長さ --",
|
||||
"keyframesLegend": "青い線:キーフレーム",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "폴더 다시 불러오기",
|
||||
"animationList": "애니메이션 목록",
|
||||
"animations": "애니메이션",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "미리볼 수 없음",
|
||||
"durationPlaceholder": "길이 --",
|
||||
"keyframesLegend": "파란 선: 키프레임",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Перезагрузить папку",
|
||||
"animationList": "Список анимаций",
|
||||
"animations": "Анимации",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Просмотр невозможен",
|
||||
"durationPlaceholder": "Длительность --",
|
||||
"keyframesLegend": "Синие метки: ключевые кадры",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "重新加载目录",
|
||||
"animationList": "动画列表",
|
||||
"animations": "动画",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "无法预览",
|
||||
"durationPlaceholder": "时长 --",
|
||||
"keyframesLegend": "蓝色短线:关键帧",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "重新載入目錄",
|
||||
"animationList": "動畫清單",
|
||||
"animations": "動畫",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "無法預覽",
|
||||
"durationPlaceholder": "時長 --",
|
||||
"keyframesLegend": "藍色短線:關鍵影格",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Ordner neu laden",
|
||||
"animationList": "Animationsliste",
|
||||
"animations": "Animationen",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Vorschau nicht möglich",
|
||||
"durationPlaceholder": "Dauer --",
|
||||
"keyframesLegend": "Blaue Marken: Keyframes",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "Reload folder",
|
||||
"animationList": "Animation list",
|
||||
"animations": "Animations",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Cannot preview",
|
||||
"durationPlaceholder": "Duration --",
|
||||
"keyframesLegend": "Blue ticks: keyframes",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Recargar carpeta",
|
||||
"animationList": "Lista de animaciones",
|
||||
"animations": "Animaciones",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "No se puede previsualizar",
|
||||
"durationPlaceholder": "Duración --",
|
||||
"keyframesLegend": "Marcas azules: fotogramas clave",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "フォルダーを再読み込み",
|
||||
"animationList": "アニメーション一覧",
|
||||
"animations": "アニメーション",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "プレビューできません",
|
||||
"durationPlaceholder": "長さ --",
|
||||
"keyframesLegend": "青い線:キーフレーム",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "폴더 다시 불러오기",
|
||||
"animationList": "애니메이션 목록",
|
||||
"animations": "애니메이션",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "미리볼 수 없음",
|
||||
"durationPlaceholder": "길이 --",
|
||||
"keyframesLegend": "파란 선: 키프레임",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"reloadTitle": "Перезагрузить папку",
|
||||
"animationList": "Список анимаций",
|
||||
"animations": "Анимации",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "Просмотр невозможен",
|
||||
"durationPlaceholder": "Длительность --",
|
||||
"keyframesLegend": "Синие метки: ключевые кадры",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "重新加载目录",
|
||||
"animationList": "动画列表",
|
||||
"animations": "动画",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "无法预览",
|
||||
"durationPlaceholder": "时长 --",
|
||||
"keyframesLegend": "蓝色短线:关键帧",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"reloadTitle": "重新載入目錄",
|
||||
"animationList": "動畫清單",
|
||||
"animations": "動畫",
|
||||
"setupPose": "Setup Pose",
|
||||
"errorTitle": "無法預覽",
|
||||
"durationPlaceholder": "時長 --",
|
||||
"keyframesLegend": "藍色短線:關鍵影格",
|
||||
|
||||
@@ -250,8 +250,9 @@
|
||||
let renderer = null;
|
||||
let activeRuntimeDir = null;
|
||||
let activeVersion = null;
|
||||
let currentAnim = 0, currentSkin = null;
|
||||
let playing = true, loop = true, speed = 1;
|
||||
const SETUP_POSE_INDEX = -1;
|
||||
let currentAnim = SETUP_POSE_INDEX, currentSkin = null;
|
||||
let playing = false, loop = true, speed = 1;
|
||||
let premultipliedAlpha = false;
|
||||
let previewSettings = null;
|
||||
let timeKeeper = null, lastTime = 0, running = false;
|
||||
@@ -265,6 +266,7 @@
|
||||
// 画布平移 / 缩放状态(左键拖拽平移,滚轮缩放,右键回中心)
|
||||
let panX = 0, panY = 0, zoom = 1;
|
||||
let baseFitScale = 1, baseFitCx = 0, baseFitCy = 0;
|
||||
let stableFitBounds = null;
|
||||
let dragging = false; // 是否正在拖拽
|
||||
let dragStartX = 0, dragStartY = 0, dragStartPanX = 0, dragStartPanY = 0;
|
||||
|
||||
@@ -460,8 +462,17 @@
|
||||
// baseFitScale 为自动适配用的基准缩放;panX/panY/zoom 为用户平移/缩放叠加。
|
||||
// fitScale = baseFitScale * zoom;fitCx/fitCy 是骨骼包围盒中心(世界坐标)。
|
||||
let fitScale = 1, fitCx = 0, fitCy = 0;
|
||||
function fitToCanvas() {
|
||||
function captureStableFitBounds() {
|
||||
const bounds = getBounds(skeleton);
|
||||
stableFitBounds = bounds && bounds.width > 0 && bounds.height > 0
|
||||
? { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height }
|
||||
: null;
|
||||
}
|
||||
|
||||
function fitToCanvas() {
|
||||
// 自动适配始终使用加载完成时的 Setup Pose 包围盒。动画播放会改变当前帧
|
||||
// 的包围盒,但不应改变用户滚轮缩放所依赖的基准比例。
|
||||
const bounds = stableFitBounds;
|
||||
const cw = canvas.clientWidth, ch = canvas.clientHeight;
|
||||
if (!bounds || bounds.width <= 0 || bounds.height <= 0) {
|
||||
baseFitScale = 1; baseFitCx = 0; baseFitCy = 0; fitScale = 1; fitCx = 0; fitCy = 0; return;
|
||||
@@ -981,16 +992,25 @@
|
||||
function fillAnimations() {
|
||||
animationList.innerHTML = '';
|
||||
const animations = skeletonData.animations || [];
|
||||
animationPanel.style.display = animations.length ? 'flex' : 'none';
|
||||
animationPanel.style.display = 'flex';
|
||||
const setupItem = document.createElement('button');
|
||||
setupItem.type = 'button';
|
||||
setupItem.className = 'animation-item active';
|
||||
setupItem.dataset.index = String(SETUP_POSE_INDEX);
|
||||
setupItem.textContent = t('viewer.setupPose');
|
||||
setupItem.title = t('viewer.setupPose');
|
||||
setupItem.setAttribute('role', 'option');
|
||||
setupItem.setAttribute('aria-selected', 'true');
|
||||
animationList.appendChild(setupItem);
|
||||
animations.forEach((a, i) => {
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = 'animation-item' + (i === 0 ? ' active' : '');
|
||||
item.className = 'animation-item';
|
||||
item.dataset.index = String(i);
|
||||
item.textContent = a.name;
|
||||
item.title = a.name;
|
||||
item.setAttribute('role', 'option');
|
||||
item.setAttribute('aria-selected', i === 0 ? 'true' : 'false');
|
||||
item.setAttribute('aria-selected', 'false');
|
||||
animationList.appendChild(item);
|
||||
});
|
||||
}
|
||||
@@ -1026,6 +1046,7 @@
|
||||
const resolvedIndex = Number.isInteger(index) ? index : (skeletonData.animations || []).indexOf(anim);
|
||||
if (resolvedIndex >= 0) selectAnimationItem(resolvedIndex);
|
||||
state.setAnimation(0, name, loop);
|
||||
playing = true;
|
||||
updateTimelineDuration(anim);
|
||||
state.update(0);
|
||||
state.apply(skeleton);
|
||||
@@ -1033,6 +1054,23 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
function showSetupPose() {
|
||||
if (!state || !skeleton) return;
|
||||
selectAnimationItem(SETUP_POSE_INDEX);
|
||||
playing = false;
|
||||
if (typeof state.clearTracks === 'function') state.clearTracks();
|
||||
else if (typeof state.clearTrack === 'function') state.clearTrack(0);
|
||||
if (typeof skeleton.setToSetupPose === 'function') skeleton.setToSetupPose();
|
||||
else {
|
||||
if (typeof skeleton.setBonesToSetupPose === 'function') skeleton.setBonesToSetupPose();
|
||||
if (typeof skeleton.setSlotsToSetupPose === 'function') skeleton.setSlotsToSetupPose();
|
||||
}
|
||||
updateWorld(skeleton);
|
||||
updateTimelineDuration(null, true);
|
||||
setStatus(t('viewer.setupPose'));
|
||||
draw();
|
||||
}
|
||||
|
||||
// 跨版本地通过名字查找动画(Spine 3.x / 4.x 都有 animations 数组)
|
||||
function findAnimByName(name) {
|
||||
const arr = (skeletonData && skeletonData.animations) || [];
|
||||
@@ -1043,9 +1081,9 @@
|
||||
}
|
||||
|
||||
// 更新时间轴范围(max = 动画时长),并把滑块归零,同时渲染关键帧标记
|
||||
function updateTimelineDuration(anim) {
|
||||
function updateTimelineDuration(anim, forceStatic = false) {
|
||||
duration = animationDuration(anim);
|
||||
isStaticAnimation = !!anim && duration <= STATIC_EPSILON;
|
||||
isStaticAnimation = forceStatic || (!!anim && duration <= STATIC_EPSILON);
|
||||
timeline.max = String(isStaticAnimation ? 1 : Math.max(duration, 0.001));
|
||||
timeline.step = String(isStaticAnimation ? 1 : Math.min(1 / FPS, Math.max(duration / 1000, 0.001)));
|
||||
timeline.value = '0';
|
||||
@@ -1287,18 +1325,9 @@
|
||||
fillAnimations();
|
||||
fillSkins(currentSkin);
|
||||
|
||||
// 默认使用导出顺序中的第一个动画;duration=0 的动画按静帧处理。
|
||||
if (skeletonData.animations.length > 0) {
|
||||
const defaultAnim = skeletonData.animations[0];
|
||||
state.setAnimation(0, defaultAnim.name, loop);
|
||||
const idx = skeletonData.animations.indexOf(defaultAnim);
|
||||
if (idx >= 0) selectAnimationItem(idx);
|
||||
updateTimelineDuration(defaultAnim);
|
||||
} else {
|
||||
updateTimelineDuration(null);
|
||||
}
|
||||
|
||||
updateWorld(skeleton);
|
||||
// 默认显示 Setup Pose 静帧,由用户从列表中主动选择动画。
|
||||
showSetupPose();
|
||||
captureStableFitBounds();
|
||||
fitToCanvas();
|
||||
draw();
|
||||
|
||||
@@ -1330,6 +1359,10 @@
|
||||
const item = e.target.closest('.animation-item');
|
||||
if (!item) return;
|
||||
const idx = parseInt(item.dataset.index, 10);
|
||||
if (idx === SETUP_POSE_INDEX) {
|
||||
showSetupPose();
|
||||
return;
|
||||
}
|
||||
const anims = skeletonData.animations;
|
||||
if (anims && anims[idx]) setAnimationByName(anims[idx].name, idx);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
|
||||
const roots = ['.', 'release/UNIVERSAL_SpineViewer'];
|
||||
|
||||
function functionBody(source, name, nextName) {
|
||||
const start = source.indexOf(`\tfunction ${name}(`);
|
||||
const end = source.indexOf(`\n\tfunction ${nextName}(`, start + 1);
|
||||
assert.ok(start >= 0 && end > start, `missing function ${name}`);
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
const viewerPath = path.join(root, 'viewer/spine.html');
|
||||
const html = fs.readFileSync(viewerPath, 'utf8');
|
||||
const script = html.match(/<script>([\s\S]*?)<\/script>/)[1];
|
||||
new vm.Script(script);
|
||||
|
||||
assert.match(script, /const SETUP_POSE_INDEX = -1;/);
|
||||
assert.match(script, /let currentAnim = SETUP_POSE_INDEX[\s\S]*let playing = false/);
|
||||
assert.match(script, /setupItem\.dataset\.index = String\(SETUP_POSE_INDEX\)/);
|
||||
assert.match(script, /showSetupPose\(\);\s*captureStableFitBounds\(\);\s*fitToCanvas\(\);/);
|
||||
assert.match(script, /state\.clearTracks[\s\S]*skeleton\.setToSetupPose/);
|
||||
assert.match(script, /if \(idx === SETUP_POSE_INDEX\)[\s\S]*showSetupPose\(\)/);
|
||||
|
||||
const fit = functionBody(script, 'fitToCanvas', 'draw');
|
||||
assert.match(fit, /const bounds = stableFitBounds;/);
|
||||
assert.doesNotMatch(fit, /getBounds\(/, 'wheel/resize fitting must not sample an animated frame');
|
||||
|
||||
const capture = functionBody(script, 'captureStableFitBounds', 'fitToCanvas');
|
||||
assert.match(capture, /getBounds\(skeleton\)/);
|
||||
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'));
|
||||
for (const language of manifest.languages) {
|
||||
const locale = JSON.parse(fs.readFileSync(path.join(root, '_locales', language + '.json'), 'utf8'));
|
||||
assert.equal(locale.viewer.setupPose, 'Setup Pose', `${root}/${language} must keep the Spine term untranslated`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('PASS: Setup Pose is the default still frame and zoom uses stable bounds');
|
||||
+53
-20
@@ -250,8 +250,9 @@
|
||||
let renderer = null;
|
||||
let activeRuntimeDir = null;
|
||||
let activeVersion = null;
|
||||
let currentAnim = 0, currentSkin = null;
|
||||
let playing = true, loop = true, speed = 1;
|
||||
const SETUP_POSE_INDEX = -1;
|
||||
let currentAnim = SETUP_POSE_INDEX, currentSkin = null;
|
||||
let playing = false, loop = true, speed = 1;
|
||||
let premultipliedAlpha = false;
|
||||
let previewSettings = null;
|
||||
let timeKeeper = null, lastTime = 0, running = false;
|
||||
@@ -265,6 +266,7 @@
|
||||
// 画布平移 / 缩放状态(左键拖拽平移,滚轮缩放,右键回中心)
|
||||
let panX = 0, panY = 0, zoom = 1;
|
||||
let baseFitScale = 1, baseFitCx = 0, baseFitCy = 0;
|
||||
let stableFitBounds = null;
|
||||
let dragging = false; // 是否正在拖拽
|
||||
let dragStartX = 0, dragStartY = 0, dragStartPanX = 0, dragStartPanY = 0;
|
||||
|
||||
@@ -460,8 +462,17 @@
|
||||
// baseFitScale 为自动适配用的基准缩放;panX/panY/zoom 为用户平移/缩放叠加。
|
||||
// fitScale = baseFitScale * zoom;fitCx/fitCy 是骨骼包围盒中心(世界坐标)。
|
||||
let fitScale = 1, fitCx = 0, fitCy = 0;
|
||||
function fitToCanvas() {
|
||||
function captureStableFitBounds() {
|
||||
const bounds = getBounds(skeleton);
|
||||
stableFitBounds = bounds && bounds.width > 0 && bounds.height > 0
|
||||
? { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height }
|
||||
: null;
|
||||
}
|
||||
|
||||
function fitToCanvas() {
|
||||
// 自动适配始终使用加载完成时的 Setup Pose 包围盒。动画播放会改变当前帧
|
||||
// 的包围盒,但不应改变用户滚轮缩放所依赖的基准比例。
|
||||
const bounds = stableFitBounds;
|
||||
const cw = canvas.clientWidth, ch = canvas.clientHeight;
|
||||
if (!bounds || bounds.width <= 0 || bounds.height <= 0) {
|
||||
baseFitScale = 1; baseFitCx = 0; baseFitCy = 0; fitScale = 1; fitCx = 0; fitCy = 0; return;
|
||||
@@ -981,16 +992,25 @@
|
||||
function fillAnimations() {
|
||||
animationList.innerHTML = '';
|
||||
const animations = skeletonData.animations || [];
|
||||
animationPanel.style.display = animations.length ? 'flex' : 'none';
|
||||
animationPanel.style.display = 'flex';
|
||||
const setupItem = document.createElement('button');
|
||||
setupItem.type = 'button';
|
||||
setupItem.className = 'animation-item active';
|
||||
setupItem.dataset.index = String(SETUP_POSE_INDEX);
|
||||
setupItem.textContent = t('viewer.setupPose');
|
||||
setupItem.title = t('viewer.setupPose');
|
||||
setupItem.setAttribute('role', 'option');
|
||||
setupItem.setAttribute('aria-selected', 'true');
|
||||
animationList.appendChild(setupItem);
|
||||
animations.forEach((a, i) => {
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = 'animation-item' + (i === 0 ? ' active' : '');
|
||||
item.className = 'animation-item';
|
||||
item.dataset.index = String(i);
|
||||
item.textContent = a.name;
|
||||
item.title = a.name;
|
||||
item.setAttribute('role', 'option');
|
||||
item.setAttribute('aria-selected', i === 0 ? 'true' : 'false');
|
||||
item.setAttribute('aria-selected', 'false');
|
||||
animationList.appendChild(item);
|
||||
});
|
||||
}
|
||||
@@ -1026,6 +1046,7 @@
|
||||
const resolvedIndex = Number.isInteger(index) ? index : (skeletonData.animations || []).indexOf(anim);
|
||||
if (resolvedIndex >= 0) selectAnimationItem(resolvedIndex);
|
||||
state.setAnimation(0, name, loop);
|
||||
playing = true;
|
||||
updateTimelineDuration(anim);
|
||||
state.update(0);
|
||||
state.apply(skeleton);
|
||||
@@ -1033,6 +1054,23 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
function showSetupPose() {
|
||||
if (!state || !skeleton) return;
|
||||
selectAnimationItem(SETUP_POSE_INDEX);
|
||||
playing = false;
|
||||
if (typeof state.clearTracks === 'function') state.clearTracks();
|
||||
else if (typeof state.clearTrack === 'function') state.clearTrack(0);
|
||||
if (typeof skeleton.setToSetupPose === 'function') skeleton.setToSetupPose();
|
||||
else {
|
||||
if (typeof skeleton.setBonesToSetupPose === 'function') skeleton.setBonesToSetupPose();
|
||||
if (typeof skeleton.setSlotsToSetupPose === 'function') skeleton.setSlotsToSetupPose();
|
||||
}
|
||||
updateWorld(skeleton);
|
||||
updateTimelineDuration(null, true);
|
||||
setStatus(t('viewer.setupPose'));
|
||||
draw();
|
||||
}
|
||||
|
||||
// 跨版本地通过名字查找动画(Spine 3.x / 4.x 都有 animations 数组)
|
||||
function findAnimByName(name) {
|
||||
const arr = (skeletonData && skeletonData.animations) || [];
|
||||
@@ -1043,9 +1081,9 @@
|
||||
}
|
||||
|
||||
// 更新时间轴范围(max = 动画时长),并把滑块归零,同时渲染关键帧标记
|
||||
function updateTimelineDuration(anim) {
|
||||
function updateTimelineDuration(anim, forceStatic = false) {
|
||||
duration = animationDuration(anim);
|
||||
isStaticAnimation = !!anim && duration <= STATIC_EPSILON;
|
||||
isStaticAnimation = forceStatic || (!!anim && duration <= STATIC_EPSILON);
|
||||
timeline.max = String(isStaticAnimation ? 1 : Math.max(duration, 0.001));
|
||||
timeline.step = String(isStaticAnimation ? 1 : Math.min(1 / FPS, Math.max(duration / 1000, 0.001)));
|
||||
timeline.value = '0';
|
||||
@@ -1287,18 +1325,9 @@
|
||||
fillAnimations();
|
||||
fillSkins(currentSkin);
|
||||
|
||||
// 默认使用导出顺序中的第一个动画;duration=0 的动画按静帧处理。
|
||||
if (skeletonData.animations.length > 0) {
|
||||
const defaultAnim = skeletonData.animations[0];
|
||||
state.setAnimation(0, defaultAnim.name, loop);
|
||||
const idx = skeletonData.animations.indexOf(defaultAnim);
|
||||
if (idx >= 0) selectAnimationItem(idx);
|
||||
updateTimelineDuration(defaultAnim);
|
||||
} else {
|
||||
updateTimelineDuration(null);
|
||||
}
|
||||
|
||||
updateWorld(skeleton);
|
||||
// 默认显示 Setup Pose 静帧,由用户从列表中主动选择动画。
|
||||
showSetupPose();
|
||||
captureStableFitBounds();
|
||||
fitToCanvas();
|
||||
draw();
|
||||
|
||||
@@ -1330,6 +1359,10 @@
|
||||
const item = e.target.closest('.animation-item');
|
||||
if (!item) return;
|
||||
const idx = parseInt(item.dataset.index, 10);
|
||||
if (idx === SETUP_POSE_INDEX) {
|
||||
showSetupPose();
|
||||
return;
|
||||
}
|
||||
const anims = skeletonData.animations;
|
||||
if (anims && anims[idx]) setAnimationByName(anims[idx].name, idx);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user