默认预览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": "藍色短線:關鍵影格",
|
||||
|
||||
@@ -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