阶段10开发完成

This commit is contained in:
tianmo committed 2026-08-31 02:15:38 +08:00
1 parent cffe02c1ec
commit c239171cc4
21 files changed
+997 -36

No files matched your search

+24 -2
View File
@@ -10,7 +10,7 @@
<button class="tl-btn" @click="store.nextFrame()" title="后一帧">▶</button>
<button class="tl-btn" @click="store.toEnd()" title="移动到最后一帧">⏭</button>
<button class="tl-btn" :class="{ on: store.timeline.loop }" @click="store.toggleLoop()" title="循环">⟳</button>
<span class="tl-frame">帧 <b>{{ store.timeline.frame }}</b> / <b>{{ store.timeline.totalFrames }}</b></span>
<span class="tl-frame">帧 <b>{{ store.timeline.frame }}</b> / <b>{{ Math.max(0, store.timeline.totalFrames - 1) }}</b></span>
<span class="tl-legend"><i class="legend-emission"></i>持续发射 <i class="legend-life"></i>粒子存在 <i class="legend-spine"></i>Spine 动画</span>
<div ref="animationMenuRef" class="tl-anim tl-owner-animation">
<span>所属动画</span>
@@ -91,7 +91,7 @@
<span class="lane-name">{{ (sys.config as any).name || '系统 ' + (i + 1) }}</span>
<span class="bar-life" :style="{ width: lifeWidthPx(sys) + 'px' }"></span>
<span
v-if="sys.config.mode === 'stream'"
v-if="sys.config.mode === 'stream' && !isLoopEnd(sys)"
class="bar-emission"
:style="{ width: emissionWidthPx(sys) + 'px' }"
>
@@ -262,16 +262,37 @@ function barWidthPx(sys: SysLike) {
function emissionWidthPx(sys: SysLike) {
if ((sys.config as any).mode !== 'stream') return 0
if (isLoopEnd(sys)) return 0
if ((sys.config as any).streamBehavior === 'loop') {
const config = sys.config as any
const durationFrames = Math.max(1, Math.ceil(Number(config.loopDurationFrames) || 1))
if (config.generateLoopStartAnimation) {
const warmupCycles = Math.max(1, Math.ceil((maxParticleLife(sys) * fps.value) / durationFrames))
return (durationFrames * warmupCycles) / pxPerF()
}
return durationFrames / pxPerF()
}
const duration = draftDurations.value[sys.id] ?? Math.max(1 / fps.value, Number((sys.config as any).duration) || 1)
return (duration * fps.value) / pxPerF()
}
function lifeWidthPx(sys: SysLike) {
const config = sys.config as any
if (config.mode === 'stream' && config.streamBehavior === 'loop') {
// 启动段和纯循环段的存在时长与发射段完全重合;结束段只显示剩余生命/拖尾。
if (!config.generateLoopEndAnimation) return emissionWidthPx(sys)
return ((maxParticleLife(sys) * fps.value) + 1) / pxPerF()
}
// 红色底条表示整个系统“仍有粒子存在”的时长:持续发射窗口 + 最长粒子生命。
// 爆发模式的发射窗口为 0,因此只保留最长粒子生命。
return emissionWidthPx(sys) + (maxParticleLife(sys) * fps.value) / pxPerF()
}
function isLoopEnd(sys: SysLike) {
const config = sys.config as any
return config.mode === 'stream' && config.streamBehavior === 'loop' && config.generateLoopEndAnimation
}
function selectedSpineDuration(spine: SpineSceneObject) {
return spine.animations.find((animation) => animation.name === spine.selectedAnimation)?.duration || 0
}
@@ -337,6 +358,7 @@ function onBarDown(e: PointerEvent, sys: SysLike) {
// 拖动粒子条右缘:调整发射时长 duration
function onBarResizeDown(e: PointerEvent, sys: SysLike) {
e.stopPropagation()
if (isLoopEnd(sys)) return
const startDur = Math.max(0, Number((sys.config as any).duration) || 0)
const secPerPx = pxPerF() / fps.value
const barLeft = (e.currentTarget as HTMLElement).closest('.lane-bar')?.getBoundingClientRect().left ?? e.clientX