默认序列帧修改
This commit is contained in:
1 parent
5efee6d918
commit
a11d57f27f
69 files changed
+164
-22
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { Texture } from 'pixi.js'
|
||||
import type { EmitMode, EmitShape, Blend, AttributeMode, RotationMode, ScaleAxisMode, CurvePoint, ColorGradientStop, ColliderShape, CollisionResponse, SceneCollider, ParticleImageResource, ParticleSequenceFrame, TrailImageResource, EmitterConfig, ParticleState, TrailBoneState, TrailState } from './particleTypes'
|
||||
import { BLEND_MAP } from './particleConstants'
|
||||
import { DEFAULT_SEQUENCE_FPS, ensureDefaultSequenceFrames } from './particleResources'
|
||||
|
||||
export function defaultConfig(): EmitterConfig {
|
||||
return {
|
||||
@@ -13,7 +14,7 @@ export function defaultConfig(): EmitterConfig {
|
||||
previewUrl: '/images/star.png',
|
||||
imageMode: 'fixed',
|
||||
sequencePlayback: 'loop-forward',
|
||||
sequenceFps: 30,
|
||||
sequenceFps: DEFAULT_SEQUENCE_FPS,
|
||||
sequenceFrames: [],
|
||||
weight: 100,
|
||||
locked: false,
|
||||
@@ -380,14 +381,15 @@ export function ensureEmitterConfig(config: EmitterConfig): EmitterConfig {
|
||||
else if (!['once-hide', 'once-hold', 'loop-forward', 'loop-reverse'].includes(resource.sequencePlayback)) {
|
||||
resource.sequencePlayback = 'loop-forward'
|
||||
}
|
||||
if (!Number.isFinite(resource.sequenceFps)) resource.sequenceFps = 30
|
||||
resource.sequenceFps = Math.min(120, Math.max(1, Number(resource.sequenceFps) || 30))
|
||||
if (!Number.isFinite(resource.sequenceFps)) resource.sequenceFps = DEFAULT_SEQUENCE_FPS
|
||||
resource.sequenceFps = Math.min(120, Math.max(1, Number(resource.sequenceFps) || DEFAULT_SEQUENCE_FPS))
|
||||
if (!Array.isArray(resource.sequenceFrames)) resource.sequenceFrames = []
|
||||
for (const frame of resource.sequenceFrames) {
|
||||
if (typeof frame.name !== 'string') frame.name = ''
|
||||
if (typeof frame.previewUrl !== 'string') frame.previewUrl = ''
|
||||
if (frame.texture === undefined) frame.texture = null
|
||||
}
|
||||
ensureDefaultSequenceFrames(resource)
|
||||
if (!Number.isFinite(resource.weight)) resource.weight = 100
|
||||
if (typeof resource.locked !== 'boolean') resource.locked = false
|
||||
if (!Number.isFinite(resource.scale)) resource.scale = 1
|
||||
|
||||
@@ -1,11 +1,44 @@
|
||||
import type { Texture } from 'pixi.js'
|
||||
import type { EmitterConfig, ParticleImageResource, TrailImageResource } from './particleTypes'
|
||||
|
||||
export const DEFAULT_SEQUENCE_FRAME_DIRECTORY = '/images/frame-sequence/yanhua'
|
||||
export const DEFAULT_SEQUENCE_FPS = 15
|
||||
export const DEFAULT_SEQUENCE_FRAME_NAMES = Array.from(
|
||||
{ length: 15 },
|
||||
(_, index) => `yanhua_${String(index + 1).padStart(2, '0')}.png`,
|
||||
)
|
||||
|
||||
type SequenceResourceTarget = {
|
||||
imageMode: ParticleImageResource['imageMode']
|
||||
sequenceFrames: Array<Pick<ParticleImageResource['sequenceFrames'][number], 'name' | 'previewUrl'>>
|
||||
textureName: string
|
||||
textureFolder: string
|
||||
previewUrl: string
|
||||
}
|
||||
|
||||
export function applyDefaultSequenceFrames(resource: SequenceResourceTarget) {
|
||||
resource.sequenceFrames = DEFAULT_SEQUENCE_FRAME_NAMES.map((name) => ({
|
||||
name,
|
||||
previewUrl: `${DEFAULT_SEQUENCE_FRAME_DIRECTORY}/${name}`,
|
||||
texture: null,
|
||||
}))
|
||||
resource.textureName = 'yanhua'
|
||||
resource.textureFolder = ''
|
||||
resource.previewUrl = resource.sequenceFrames[0].previewUrl
|
||||
}
|
||||
|
||||
/** 序列帧模式未指定图片时使用随项目发布的默认序列。 */
|
||||
export function ensureDefaultSequenceFrames(resource: SequenceResourceTarget) {
|
||||
if (resource.imageMode !== 'sequence' || resource.sequenceFrames.length) return false
|
||||
applyDefaultSequenceFrames(resource)
|
||||
return true
|
||||
}
|
||||
|
||||
/** 粒子图片资源的出厂默认值(无可用资源时的兜底)。 */
|
||||
export function defaultImageResource(texture: Texture<any> | null): ParticleImageResource {
|
||||
return {
|
||||
id: 1, texture, textureName: 'star', textureFolder: '',
|
||||
previewUrl: '/images/star.png', imageMode: 'fixed', sequencePlayback: 'loop-forward', sequenceFps: 30, sequenceFrames: [],
|
||||
previewUrl: '/images/star.png', imageMode: 'fixed', sequencePlayback: 'loop-forward', sequenceFps: DEFAULT_SEQUENCE_FPS, sequenceFrames: [],
|
||||
weight: 100, locked: false, scale: 1, anchorX: 0.5, anchorY: 0.5,
|
||||
colorMode: 'fixed', colorStart: '#ffffff', colorEnd: '#ff4d4d', blend: 'normal',
|
||||
colorGradient: [{ position: 0, color: '#ffffff' }, { position: 1, color: '#ff4d4d' }],
|
||||
@@ -64,7 +97,7 @@ export function pickTrailResource(cfg: EmitterConfig, sample: number): TrailImag
|
||||
/** 序列帧在粒子年龄处的帧号与可见性。 */
|
||||
export function imageFrameState(resource: ParticleImageResource, age: number) {
|
||||
if (resource.imageMode !== 'sequence' || resource.sequenceFrames.length <= 1) return { index: 0, visible: true }
|
||||
const fps = Math.min(120, Math.max(1, Number(resource.sequenceFps) || 30))
|
||||
const fps = Math.min(120, Math.max(1, Number(resource.sequenceFps) || DEFAULT_SEQUENCE_FPS))
|
||||
const rawIndex = Math.max(0, Math.floor(Math.max(0, age) * fps))
|
||||
const lastIndex = resource.sequenceFrames.length - 1
|
||||
if (resource.sequencePlayback === 'once-hide') {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="thumb-note">{{ displayFileName }}</div>
|
||||
<label v-if="supportsSequence" class="row"><span>{{ $t('image.imageMode') }}</span>
|
||||
<select v-model="resource.imageMode" class="inp">
|
||||
<select :value="resource.imageMode || 'fixed'" class="inp" @change="changeImageMode">
|
||||
<option value="fixed">{{ $t('image.fixed') }}</option>
|
||||
<option value="sequence">{{ $t('image.sequence') }}</option>
|
||||
</select>
|
||||
@@ -151,6 +151,7 @@ const props = withDefaults(defineProps<{
|
||||
const emit = defineEmits<{
|
||||
pick: [id: number]
|
||||
pickSequence: [id: number]
|
||||
modeChange: [id: number, mode: 'fixed' | 'sequence']
|
||||
reset: [id: number]
|
||||
remove: [id: number]
|
||||
updateWeight: [id: number, value: number]
|
||||
@@ -258,6 +259,11 @@ function pickImage() {
|
||||
if (props.supportsSequence && props.resource.imageMode === 'sequence') emit('pickSequence', props.resource.id)
|
||||
else emit('pick', props.resource.id)
|
||||
}
|
||||
|
||||
function changeImageMode(event: Event) {
|
||||
const mode = (event.target as HTMLSelectElement).value as 'fixed' | 'sequence'
|
||||
emit('modeChange', props.resource.id, mode)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
default-file="star.png"
|
||||
@pick="openTexturePicker"
|
||||
@pick-sequence="openSequencePicker"
|
||||
@mode-change="setImageMode"
|
||||
@reset="resetTexture"
|
||||
@remove="removeImageResource"
|
||||
@update-weight="setResourceWeight"
|
||||
@@ -158,7 +159,7 @@ import ColorGradientEditor from '../ColorGradientEditor.vue'
|
||||
import ImageResourceCard from '../ImageResourceCard.vue'
|
||||
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
||||
import { useParticlePanelContext } from './particlePanelContext'
|
||||
const { resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, collapsed, toggle, sectionOpen, resetModule, select, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, ALPHA_CURVE, RESOURCE_ALPHA_CURVE } = useParticlePanelContext()
|
||||
const { resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, collapsed, toggle, sectionOpen, resetModule, select, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, ALPHA_CURVE, RESOURCE_ALPHA_CURVE } = useParticlePanelContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -57,7 +57,7 @@ const activeIdx = computed(() => {
|
||||
return i >= 0 ? i : 0
|
||||
})
|
||||
const sys = computed(() => store.systems[activeIdx.value] || null)
|
||||
const { textureFileInput, sequenceFileInput, pendingResourceId, trailTextureFileInput, pendingTrailResourceId, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, rebalanceTrailWeights, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, readFileAsDataUrl, resetTexture, normalizeHex, setResourceFixedColor, commitResourceFixedColor, removeImageResource, roundWeight, distributeImageWeights, rebalanceImageWeights, setResourceWeight, toggleResourceLock } = useParticleResources(sys)
|
||||
const { textureFileInput, sequenceFileInput, pendingResourceId, trailTextureFileInput, pendingTrailResourceId, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, rebalanceTrailWeights, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, readFileAsDataUrl, resetTexture, normalizeHex, setResourceFixedColor, commitResourceFixedColor, removeImageResource, roundWeight, distributeImageWeights, rebalanceImageWeights, setResourceWeight, toggleResourceLock } = useParticleResources(sys)
|
||||
const automaticLoopDuration = computed(() => sys.value
|
||||
? automaticLoopTransitionFrames(sys.value.config, store.timeline.fps)
|
||||
: 30)
|
||||
@@ -657,5 +657,5 @@ function onSpawnPathChange() {
|
||||
// 重置/删除图片 → 回到默认 star.png
|
||||
|
||||
|
||||
return { store, exportSettings, exportBusy, exportTarget, exportMessage, exportError, connectorPairUrl, connectorStatusText, connectorStatusClass, connectorReady, connectorAuthorizationUrl, connectorDisabledReason, connectorButtonTitle, refreshConnectorHealth, exportSpineJson, exportSpineProject, exportSpineImages, configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError, localProjects, selectedLocalProjectName, selectLocalProject, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy, projectFilesInput, saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject, downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, loadAllProjectFiles, onConfigFileChange, refreshPresets, loadSelectedPreset, resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, trailTextureFileInput, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, automaticLoopDuration, loopDurationFrames, activeObjectType, activeSection, canUseSection, activateSection, sectionVisible, sectionOpen, activeCollider, activePath, activeSpine, canCopyObject, selectedSpineBone, boundSpineBone, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerSlots, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, collisionPickerOpen, selectedCollisionBodies, availableCollisionBodies, addCollisionBody, removeCollisionBody, collapsed, toggle, setEmitMode, setLoopDurationFrames, setStreamBehavior, setLoopSection, setLoopSectionDuration, setLoopCustomDuration, onEmitterFollowModeChange, bindSelectedBone, resetModule, normalizeExportFps, select, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, setSpeedOverLifeMode, prepareScaleOverLifeCurve, setScaleOverLifeMode, onShapeChange, onSpawnPathChange, LINEAR_CURVE, FLAT_CURVE, ALPHA_CURVE, RESOURCE_ALPHA_CURVE, TRAIL_ALPHA_CURVE, TRAIL_SHAPE_CURVE }
|
||||
return { store, exportSettings, exportBusy, exportTarget, exportMessage, exportError, connectorPairUrl, connectorStatusText, connectorStatusClass, connectorReady, connectorAuthorizationUrl, connectorDisabledReason, connectorButtonTitle, refreshConnectorHealth, exportSpineJson, exportSpineProject, exportSpineImages, configFileInput, selectedPreset, presetOptions, presetLoading, configMessage, configError, localProjects, selectedLocalProjectName, selectLocalProject, projectSaveOpen, projectNameDraft, projectSaveError, projectStorageBusy, projectFilesInput, saveConfig, closeProjectSaveDialog, confirmSaveConfig, loadLocalProject, downloadLocalProject, downloadCurrentProject, removeLocalProject, downloadAllLocalProjects, loadAllProjectFiles, onConfigFileChange, refreshPresets, loadSelectedPreset, resolvedCurveEditorMode, sys, textureFileInput, sequenceFileInput, trailTextureFileInput, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, resetTexture, setResourceFixedColor, commitResourceFixedColor, removeImageResource, setResourceWeight, toggleResourceLock, automaticLoopDuration, loopDurationFrames, activeObjectType, activeSection, canUseSection, activateSection, sectionVisible, sectionOpen, activeCollider, activePath, activeSpine, canCopyObject, selectedSpineBone, boundSpineBone, renderLayerSlotPickerOpen, renderLayerSlotSearch, renderLayerReadySpines, renderLayerSpineReady, renderLayerSlots, renderLayerFilteredSlots, renderLayerSlotLabel, renderLayerResolvedLabel, renderLayerWarning, setRenderLayerSlotMode, selectRenderLayerSpine, pickRenderLayerSlot, clearRenderLayerSlot, collisionPickerOpen, selectedCollisionBodies, availableCollisionBodies, addCollisionBody, removeCollisionBody, collapsed, toggle, setEmitMode, setLoopDurationFrames, setStreamBehavior, setLoopSection, setLoopSectionDuration, setLoopCustomDuration, onEmitterFollowModeChange, bindSelectedBone, resetModule, normalizeExportFps, select, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, setSpeedOverLifeMode, prepareScaleOverLifeCurve, setScaleOverLifeMode, onShapeChange, onSpawnPathChange, LINEAR_CURVE, FLAT_CURVE, ALPHA_CURVE, RESOURCE_ALPHA_CURVE, TRAIL_ALPHA_CURVE, TRAIL_SHAPE_CURVE }
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Texture } from 'pixi.js'
|
||||
import { useParticleStore } from '../../store/particleStore'
|
||||
import type { ParticleSystem } from '../../store/particleStore'
|
||||
import { defaultConfig, ensureEmitterConfig } from '../../core/particleEmitter'
|
||||
import { applyDefaultSequenceFrames, DEFAULT_SEQUENCE_FPS } from '../../core/particleResources'
|
||||
import NumSlider from '../NumSlider.vue'
|
||||
import CurveEditor from '../CurveEditor.vue'
|
||||
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
||||
@@ -227,7 +228,7 @@ function addImageResource() {
|
||||
previewUrl: '/images/star.png',
|
||||
imageMode: 'fixed',
|
||||
sequencePlayback: 'loop-forward',
|
||||
sequenceFps: 30,
|
||||
sequenceFps: DEFAULT_SEQUENCE_FPS,
|
||||
sequenceFrames: [],
|
||||
weight: 100,
|
||||
locked: false,
|
||||
@@ -292,6 +293,27 @@ async function onSequenceFiles(event: Event) {
|
||||
pendingResourceId.value = null
|
||||
}
|
||||
|
||||
async function setImageMode(resourceId: number, mode: 'fixed' | 'sequence') {
|
||||
const resource = sys.value?.config.imageResources.find((item) => item.id === resourceId)
|
||||
if (!resource) return
|
||||
if (mode === 'fixed') {
|
||||
resource.imageMode = mode
|
||||
return
|
||||
}
|
||||
if (!resource.sequenceFrames.length) applyDefaultSequenceFrames(resource)
|
||||
try {
|
||||
await Promise.all(resource.sequenceFrames.map(async (frame) => {
|
||||
if (!frame.texture) frame.texture = markRaw(await Texture.fromURL(frame.previewUrl)) as any
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('[sequence-default]', error)
|
||||
return
|
||||
}
|
||||
resource.texture = resource.sequenceFrames[0]?.texture || resource.texture
|
||||
resource.previewUrl = resource.sequenceFrames[0]?.previewUrl || resource.previewUrl
|
||||
resource.imageMode = mode
|
||||
}
|
||||
|
||||
function readFileAsDataUrl(file: File) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
@@ -311,7 +333,7 @@ function resetTexture(resourceId: number) {
|
||||
resource.previewUrl = '/images/star.png'
|
||||
resource.imageMode = 'fixed'
|
||||
resource.sequencePlayback = 'loop-forward'
|
||||
resource.sequenceFps = 30
|
||||
resource.sequenceFps = DEFAULT_SEQUENCE_FPS
|
||||
resource.sequenceFrames = []
|
||||
resource.scale = 1
|
||||
resource.anchorX = 0.5
|
||||
@@ -428,6 +450,6 @@ function toggleResourceLock(resourceId: number) {
|
||||
}
|
||||
|
||||
|
||||
return { textureFileInput, sequenceFileInput, pendingResourceId, trailTextureFileInput, pendingTrailResourceId, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, rebalanceTrailWeights, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, readFileAsDataUrl, resetTexture, normalizeHex, setResourceFixedColor, commitResourceFixedColor, removeImageResource, roundWeight, distributeImageWeights, rebalanceImageWeights, setResourceWeight, toggleResourceLock }
|
||||
return { textureFileInput, sequenceFileInput, pendingResourceId, trailTextureFileInput, pendingTrailResourceId, addTrailResource, openTrailTexturePicker, onTrailRigChange, onTrailResourceBoneCountChange, onTrailResourceRotationChange, onTrailTextureFile, resetTrailTexture, removeTrailResource, rebalanceTrailWeights, setTrailResourceWeight, toggleTrailResourceLock, setTrailColor, commitTrailColor, addImageResource, openTexturePicker, openSequencePicker, onTextureFile, onSequenceFiles, setImageMode, readFileAsDataUrl, resetTexture, normalizeHex, setResourceFixedColor, commitResourceFixedColor, removeImageResource, roundWeight, distributeImageWeights, rebalanceImageWeights, setResourceWeight, toggleResourceLock }
|
||||
}
|
||||
import { LINEAR_CURVE, FLAT_CURVE, ALPHA_CURVE, RESOURCE_ALPHA_CURVE, TRAIL_ALPHA_CURVE, TRAIL_SHAPE_CURVE } from './particlePanelCurves'
|
||||
Reference in new issue
Block a user