解决粒子首位帧衔接问题
This commit is contained in:
1 parent
0656684a92
commit
9c2421110c
22 files changed
+2873
-1407
No files matched your search
+65
-11
@@ -233,14 +233,38 @@
|
||||
<NumSlider v-if="sys.config.streamBehavior === 'normal'" label="发射时长" :min="1 / 30" :max="60" :step="1 / 30" v-model="sys.config.duration" />
|
||||
<NumSlider v-else label="发射时长 (f)" :min="1" :max="1800" :step="1" :model-value="loopDurationFrames" @update:model-value="setLoopDurationFrames" />
|
||||
<div v-if="sys.config.streamBehavior === 'loop'" class="stream-loop-options">
|
||||
<label class="switch-row">
|
||||
<input type="checkbox" :checked="sys.config.generateLoopStartAnimation" @change="setLoopSection('start', $event)" />
|
||||
<span class="switch-ui"></span><span>生成开始循环动画</span>
|
||||
</label>
|
||||
<label class="switch-row">
|
||||
<input type="checkbox" :checked="sys.config.generateLoopEndAnimation" @change="setLoopSection('end', $event)" />
|
||||
<span class="switch-ui"></span><span>生成结束循环动画</span>
|
||||
</label>
|
||||
<div class="loop-section-row">
|
||||
<label class="switch-row">
|
||||
<input type="checkbox" :checked="sys.config.generateLoopStartAnimation" @change="setLoopSection('start', $event)" />
|
||||
<span class="switch-ui"></span><span>生成开始循环动画</span>
|
||||
</label>
|
||||
<div v-if="sys.config.generateLoopStartAnimation" class="loop-duration-control">
|
||||
<label class="switch-row loop-duration-toggle">
|
||||
<input type="checkbox" :checked="sys.config.loopStartUseCustomDuration" @change="setLoopCustomDuration('start', $event)" />
|
||||
<span class="switch-ui"></span><span>指定时长</span>
|
||||
</label>
|
||||
<label class="loop-section-duration">
|
||||
<input v-if="sys.config.loopStartUseCustomDuration" :value="sys.config.loopStartDurationFrames" type="number" min="1" max="1800" step="1" aria-label="开始循环段时长" @change="setLoopSectionDuration('start', $event)" /><span v-if="sys.config.loopStartUseCustomDuration">f</span>
|
||||
<span v-else class="loop-auto-duration">自动 {{ automaticLoopDuration }}f</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loop-section-row">
|
||||
<label class="switch-row">
|
||||
<input type="checkbox" :checked="sys.config.generateLoopEndAnimation" @change="setLoopSection('end', $event)" />
|
||||
<span class="switch-ui"></span><span>生成结束循环动画</span>
|
||||
</label>
|
||||
<div v-if="sys.config.generateLoopEndAnimation" class="loop-duration-control">
|
||||
<label class="switch-row loop-duration-toggle">
|
||||
<input type="checkbox" :checked="sys.config.loopEndUseCustomDuration" @change="setLoopCustomDuration('end', $event)" />
|
||||
<span class="switch-ui"></span><span>指定时长</span>
|
||||
</label>
|
||||
<label class="loop-section-duration">
|
||||
<input v-if="sys.config.loopEndUseCustomDuration" :value="sys.config.loopEndDurationFrames" type="number" min="1" max="1800" step="1" aria-label="结束循环段时长" @change="setLoopSectionDuration('end', $event)" /><span v-if="sys.config.loopEndUseCustomDuration">f</span>
|
||||
<span v-else class="loop-auto-duration">自动 {{ automaticLoopDuration }}f</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sys.config.streamBehavior === 'loop'" class="modifier-hint stream-loop-hint">循环持续模式不支持骨骼跟随和路径跟随;进入或退出时会将粒子跟随设置恢复默认值。</div>
|
||||
</template>
|
||||
@@ -897,6 +921,7 @@ import { downloadEditorConfig, loadEditorConfigFile, loadEditorConfigText } from
|
||||
import { defaultSpineExportSettings, ensureSpineExportSettings } from '../export/spineExportSettings'
|
||||
import { buildSpineJson, downloadSpineJson } from '../export/spineJsonExporter'
|
||||
import { validateSpineJson } from '../export/spineJsonValidator'
|
||||
import { automaticLoopTransitionFrames } from '../core/loopSegmentTiming'
|
||||
|
||||
const store = useParticleStore()
|
||||
store.settings.exportSettings = ensureSpineExportSettings(store.settings.exportSettings)
|
||||
@@ -1022,6 +1047,9 @@ const activeIdx = computed(() => {
|
||||
return i >= 0 ? i : 0
|
||||
})
|
||||
const sys = computed(() => store.systems[activeIdx.value] || null)
|
||||
const automaticLoopDuration = computed(() => sys.value
|
||||
? automaticLoopTransitionFrames(sys.value.config, store.timeline.fps)
|
||||
: 30)
|
||||
const loopDurationFrames = computed(() => {
|
||||
const config = sys.value?.config
|
||||
if (!config) return 1
|
||||
@@ -1180,13 +1208,27 @@ function setLoopSection(section: 'start' | 'end', event: Event) {
|
||||
const checked = (event.target as HTMLInputElement).checked
|
||||
if (section === 'start') {
|
||||
config.generateLoopStartAnimation = checked
|
||||
if (checked) config.generateLoopEndAnimation = false
|
||||
} else {
|
||||
config.generateLoopEndAnimation = checked
|
||||
if (checked) config.generateLoopStartAnimation = false
|
||||
}
|
||||
}
|
||||
|
||||
function setLoopSectionDuration(section: 'start' | 'end', event: Event) {
|
||||
const config = sys.value?.config
|
||||
if (!config) return
|
||||
const frames = Math.min(1800, Math.max(1, Math.ceil(Number((event.target as HTMLInputElement).value) || 30)))
|
||||
if (section === 'start') config.loopStartDurationFrames = frames
|
||||
else config.loopEndDurationFrames = frames
|
||||
}
|
||||
|
||||
function setLoopCustomDuration(section: 'start' | 'end', event: Event) {
|
||||
const config = sys.value?.config
|
||||
if (!config) return
|
||||
const checked = (event.target as HTMLInputElement).checked
|
||||
if (section === 'start') config.loopStartUseCustomDuration = checked
|
||||
else config.loopEndUseCustomDuration = checked
|
||||
}
|
||||
|
||||
function onEmitterFollowModeChange(event: Event, config: {
|
||||
emitterFollowMode: 'none' | 'path' | 'bone'
|
||||
emitterFollowSpineId: number
|
||||
@@ -1223,7 +1265,9 @@ type ResetModule = 'emitMode' | 'shape' | 'attr' | 'look' | 'mods' | 'export'
|
||||
|
||||
const MODULE_RESET_KEYS: Record<Exclude<ResetModule, 'export'>, string[]> = {
|
||||
emitMode: [
|
||||
'mode', 'rate', 'streamBehavior', 'loopDurationFrames', 'generateLoopStartAnimation', 'generateLoopEndAnimation',
|
||||
'mode', 'rate', 'streamBehavior', 'loopDurationFrames',
|
||||
'generateLoopStartAnimation', 'loopStartUseCustomDuration', 'loopStartDurationFrames',
|
||||
'generateLoopEndAnimation', 'loopEndUseCustomDuration', 'loopEndDurationFrames',
|
||||
'burstCount', 'burstLoop', 'delay', 'duration',
|
||||
],
|
||||
shape: ['shape', 'radius', 'rectW', 'rectH', 'direction', 'spread', 'coneAngle'],
|
||||
@@ -1888,6 +1932,16 @@ function toggleResourceLock(resourceId: number) {
|
||||
.segs { display: flex; gap: 6px; margin: 6px 0; }
|
||||
.stream-behavior-segs { margin-top: 10px; }
|
||||
.stream-loop-options { display: grid; gap: 8px; margin: 8px 0 4px; padding: 9px 10px; border: 1px solid #32405a; border-radius: 6px; background: #171f2e; }
|
||||
.loop-section-row { display: flex; min-height: 27px; align-items: center; justify-content: space-between; gap: 8px; }
|
||||
.loop-duration-control { display: flex; flex: 0 0 auto; align-items: center; gap: 7px; }
|
||||
.loop-duration-toggle { gap: 5px; font-size: 10px; white-space: nowrap; }
|
||||
.loop-duration-toggle .switch-ui { width: 26px; height: 14px; }
|
||||
.loop-duration-toggle .switch-ui::after { width: 10px; height: 10px; }
|
||||
.loop-duration-toggle input:checked + .switch-ui::after { transform: translateX(12px); }
|
||||
.loop-section-duration { display: flex; flex: 0 0 auto; align-items: center; gap: 4px; color: #7f8ba4; font-size: 11px; }
|
||||
.loop-section-duration input { position: static; width: 54px; height: 25px; padding: 0 6px; border: 1px solid #3a465f; border-radius: 5px; outline: none; background: #101725; color: #e4e8f2; text-align: right; opacity: 1; pointer-events: auto; }
|
||||
.loop-section-duration input:focus { border-color: #716eff; }
|
||||
.loop-auto-duration { min-width: 54px; color: #77839a; text-align: right; white-space: nowrap; }
|
||||
.seg { flex: 1; padding: 5px; background: #1e1e2e; border: 1px solid #2e2e44; border-radius: 6px; color: #aab; font-size: 12px; cursor: pointer; }
|
||||
.seg.on { background: #3a5a8c; color: #fff; border-color: #5a7ab8; }
|
||||
.mini-btn { background: #2e3a55; border: 1px solid #3a4a6a; color: #cdf; border-radius: 5px; padding: 3px 8px; font-size: 11px; cursor: pointer; }
|
||||
|
||||
Reference in new issue
Block a user