默认序列帧修改

This commit is contained in:
tianmo committed 2026-10-30 19:27:27 +08:00
1 parent 5efee6d918
commit a11d57f27f
69 files changed
+164 -22

No files matched your search

+6 -6
View File
@@ -66,9 +66,9 @@
例如:
```text
diwen_1.png
diwen_2.png
diwen_10.png
yanhua_1.png
yanhua_2.png
yanhua_10.png
```
会按照 `1 → 2 → 10` 排列。
@@ -76,13 +76,13 @@ diwen_10.png
界面明确显示实际播放顺序:
```text
已选择 13 帧,按 diwen_000.png-diwen_012.png 顺序播放
已选择 15 帧,按 yanhua_01.png-yanhua_15.png 顺序播放
```
阶段测试资源位于:
```text
public/images/frame-sequence/saoguang/
public/images/frame-sequence/yanhua/
```
### 2.3 播放模式
@@ -106,7 +106,7 @@ public/images/frame-sequence/saoguang/
sequenceFps
```
有效范围为 1~120 FPS,默认 30 FPS。序列帧索引根据粒子实际出生时间计算,不依赖浏览器刷新次数。
有效范围为 1~120 FPS,默认 15 FPS。序列帧索引根据粒子实际出生时间计算,不依赖浏览器刷新次数。
### 2.5 单粒子单骨骼原则
@@ -1 +1 @@
.help-overlay[data-v-2ef0dcfc]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:72px 24px 28px;box-sizing:border-box;background:#05080fb8;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.help-dialog[data-v-2ef0dcfc]{width:min(820px,100%);max-height:100%;display:flex;flex-direction:column;overflow:hidden;border:1px solid #3a4864;border-radius:14px;background:#141a27;color:#b8c3d8;box-shadow:0 22px 70px #0000009e}.help-head[data-v-2ef0dcfc]{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:18px 20px 15px;border-bottom:1px solid #2b354a;background:linear-gradient(135deg,#202942,#181e2d)}.help-head h2[data-v-2ef0dcfc]{margin:0;color:#eef2ff;font-size:19px}.help-head p[data-v-2ef0dcfc]{margin:5px 0 0;color:#7786a3;font-size:11px}.help-close[data-v-2ef0dcfc]{width:28px;height:28px;flex:0 0 28px;border:1px solid #43516b;border-radius:6px;background:#273249;color:#ccd7ed;cursor:pointer}.help-close[data-v-2ef0dcfc]:hover{border-color:#7774ff;background:#3b4670;color:#fff}.help-body[data-v-2ef0dcfc]{min-height:0;overflow:auto;padding:18px 20px 21px}.help-section[data-v-2ef0dcfc]{padding:14px 15px;border:1px solid #2c3850;border-radius:10px;background:#181f2e}.help-section h3[data-v-2ef0dcfc]{margin:0 0 11px;color:#9fb2ff;font-size:13px}.help-start ol[data-v-2ef0dcfc]{margin:0;padding-left:21px;color:#aab6cc;font-size:12px;line-height:1.75}.help-start li[data-v-2ef0dcfc]::marker{color:#7774ff;font-weight:700}.help-section b[data-v-2ef0dcfc]{color:#dce4f7}.help-grid[data-v-2ef0dcfc]{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(250px,.85fr);gap:12px;margin-top:12px}.help-list[data-v-2ef0dcfc]{display:grid;gap:0;margin:0}.help-list>div[data-v-2ef0dcfc]{display:grid;grid-template-columns:78px 1fr;gap:9px;padding:7px 0;border-bottom:1px solid #263146}.help-list>div[data-v-2ef0dcfc]:last-child{border-bottom:0}.help-list dt[data-v-2ef0dcfc]{color:#d3dcf0;font-size:11px;font-weight:700}.help-list dd[data-v-2ef0dcfc]{margin:0;color:#8795ae;font-size:11px;line-height:1.5}.shortcut-list[data-v-2ef0dcfc]{display:grid;gap:7px}.shortcut-list>div[data-v-2ef0dcfc]{display:flex;align-items:center;min-height:25px;gap:5px}.shortcut-list kbd[data-v-2ef0dcfc]{min-width:24px;box-sizing:border-box;padding:3px 6px;border:1px solid #465470;border-bottom-width:2px;border-radius:5px;background:#101623;color:#e0e7f6;font:10px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;text-align:center}.shortcut-list span[data-v-2ef0dcfc]{color:#65728b;font-size:10px}.shortcut-list em[data-v-2ef0dcfc]{margin-left:auto;color:#9ca9c1;font-size:11px;font-style:normal}.help-note[data-v-2ef0dcfc]{margin:11px 0 0;padding-top:9px;border-top:1px solid #2a3549;color:#697791;font-size:10px;line-height:1.55}.help-export[data-v-2ef0dcfc]{margin-top:12px}.help-export p[data-v-2ef0dcfc]{margin:0;color:#8997b0;font-size:11px;line-height:1.65}.help-doc-title[data-v-2ef0dcfc]{margin:16px 2px 2px;color:#9fb2ff;font-size:13px;font-weight:700;letter-spacing:.5px}.help-module[data-v-2ef0dcfc]{margin-top:12px}.help-module-head[data-v-2ef0dcfc]{display:flex;align-items:center;gap:8px;width:100%;padding:0;border:0;background:transparent;color:#9fb2ff;font:inherit;font-size:13px;font-weight:700;text-align:left;cursor:pointer}.help-module-head[data-v-2ef0dcfc]:hover{color:#fff}.help-module-head .mtitle[data-v-2ef0dcfc]{flex:1;min-width:0}.help-module-head .caret[data-v-2ef0dcfc]{flex:0 0 auto;color:#7b86a2;font-size:12px}.help-module.open .help-module-head[data-v-2ef0dcfc]{color:#fff}.help-module.open .help-module-head .caret[data-v-2ef0dcfc]{color:#9fb2ff}.help-module .mnum[data-v-2ef0dcfc]{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;background:linear-gradient(135deg,#4a53b8,#6a5fd0);color:#fff;font-size:12px;font-weight:700}.help-module.help-connector[data-v-2ef0dcfc]{border-color:#4a4f8a;background:linear-gradient(135deg,#1d2442,#181d2f)}.help-module.help-connector .help-module-head[data-v-2ef0dcfc]{color:#b8c2ff}.help-module .connector-intro[data-v-2ef0dcfc]{margin:0 0 10px;color:#9aa8c4;font-size:11px;line-height:1.6}.help-module .connector-intro b[data-v-2ef0dcfc]{color:#dce4f7}.help-module .dl-link[data-v-2ef0dcfc]{display:inline-flex;flex-direction:column;align-items:flex-start;gap:2px;margin:0 10px 6px 0;padding:6px 12px;border:1px solid #5652b5;border-radius:6px;background:#4540a0;color:#fff;font-size:12px;font-weight:700;text-decoration:none}.help-module .dl-link[data-v-2ef0dcfc]:hover{border-color:#8982ff;background:#554dc2}.help-module .dl-link small[data-v-2ef0dcfc]{color:#cfe0ff;font-weight:500;font-size:9px}.help-module-body[data-v-2ef0dcfc]{padding-top:10px}@media(max-width:760px){.help-overlay[data-v-2ef0dcfc]{padding:66px 12px 14px}.help-grid[data-v-2ef0dcfc]{grid-template-columns:1fr}}.topbar[data-v-3b4369b1]{display:flex;align-items:center;gap:10px;flex-wrap:wLine truncated
.help-overlay[data-v-2ef0dcfc]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;display:flex;align-items:center;justify-content:center;padding:72px 24px 28px;box-sizing:border-box;background:#05080fb8;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}.help-dialog[data-v-2ef0dcfc]{width:min(820px,100%);max-height:100%;display:flex;flex-direction:column;overflow:hidden;border:1px solid #3a4864;border-radius:14px;background:#141a27;color:#b8c3d8;box-shadow:0 22px 70px #0000009e}.help-head[data-v-2ef0dcfc]{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;padding:18px 20px 15px;border-bottom:1px solid #2b354a;background:linear-gradient(135deg,#202942,#181e2d)}.help-head h2[data-v-2ef0dcfc]{margin:0;color:#eef2ff;font-size:19px}.help-head p[data-v-2ef0dcfc]{margin:5px 0 0;color:#7786a3;font-size:11px}.help-close[data-v-2ef0dcfc]{width:28px;height:28px;flex:0 0 28px;border:1px solid #43516b;border-radius:6px;background:#273249;color:#ccd7ed;cursor:pointer}.help-close[data-v-2ef0dcfc]:hover{border-color:#7774ff;background:#3b4670;color:#fff}.help-body[data-v-2ef0dcfc]{min-height:0;overflow:auto;padding:18px 20px 21px}.help-section[data-v-2ef0dcfc]{padding:14px 15px;border:1px solid #2c3850;border-radius:10px;background:#181f2e}.help-section h3[data-v-2ef0dcfc]{margin:0 0 11px;color:#9fb2ff;font-size:13px}.help-start ol[data-v-2ef0dcfc]{margin:0;padding-left:21px;color:#aab6cc;font-size:12px;line-height:1.75}.help-start li[data-v-2ef0dcfc]::marker{color:#7774ff;font-weight:700}.help-section b[data-v-2ef0dcfc]{color:#dce4f7}.help-grid[data-v-2ef0dcfc]{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(250px,.85fr);gap:12px;margin-top:12px}.help-list[data-v-2ef0dcfc]{display:grid;gap:0;margin:0}.help-list>div[data-v-2ef0dcfc]{display:grid;grid-template-columns:78px 1fr;gap:9px;padding:7px 0;border-bottom:1px solid #263146}.help-list>div[data-v-2ef0dcfc]:last-child{border-bottom:0}.help-list dt[data-v-2ef0dcfc]{color:#d3dcf0;font-size:11px;font-weight:700}.help-list dd[data-v-2ef0dcfc]{margin:0;color:#8795ae;font-size:11px;line-height:1.5}.shortcut-list[data-v-2ef0dcfc]{display:grid;gap:7px}.shortcut-list>div[data-v-2ef0dcfc]{display:flex;align-items:center;min-height:25px;gap:5px}.shortcut-list kbd[data-v-2ef0dcfc]{min-width:24px;box-sizing:border-box;padding:3px 6px;border:1px solid #465470;border-bottom-width:2px;border-radius:5px;background:#101623;color:#e0e7f6;font:10px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;text-align:center}.shortcut-list span[data-v-2ef0dcfc]{color:#65728b;font-size:10px}.shortcut-list em[data-v-2ef0dcfc]{margin-left:auto;color:#9ca9c1;font-size:11px;font-style:normal}.help-note[data-v-2ef0dcfc]{margin:11px 0 0;padding-top:9px;border-top:1px solid #2a3549;color:#697791;font-size:10px;line-height:1.55}.help-export[data-v-2ef0dcfc]{margin-top:12px}.help-export p[data-v-2ef0dcfc]{margin:0;color:#8997b0;font-size:11px;line-height:1.65}.help-doc-title[data-v-2ef0dcfc]{margin:16px 2px 2px;color:#9fb2ff;font-size:13px;font-weight:700;letter-spacing:.5px}.help-module[data-v-2ef0dcfc]{margin-top:12px}.help-module-head[data-v-2ef0dcfc]{display:flex;align-items:center;gap:8px;width:100%;padding:0;border:0;background:transparent;color:#9fb2ff;font:inherit;font-size:13px;font-weight:700;text-align:left;cursor:pointer}.help-module-head[data-v-2ef0dcfc]:hover{color:#fff}.help-module-head .mtitle[data-v-2ef0dcfc]{flex:1;min-width:0}.help-module-head .caret[data-v-2ef0dcfc]{flex:0 0 auto;color:#7b86a2;font-size:12px}.help-module.open .help-module-head[data-v-2ef0dcfc]{color:#fff}.help-module.open .help-module-head .caret[data-v-2ef0dcfc]{color:#9fb2ff}.help-module .mnum[data-v-2ef0dcfc]{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:6px;background:linear-gradient(135deg,#4a53b8,#6a5fd0);color:#fff;font-size:12px;font-weight:700}.help-module.help-connector[data-v-2ef0dcfc]{border-color:#4a4f8a;background:linear-gradient(135deg,#1d2442,#181d2f)}.help-module.help-connector .help-module-head[data-v-2ef0dcfc]{color:#b8c2ff}.help-module .connector-intro[data-v-2ef0dcfc]{margin:0 0 10px;color:#9aa8c4;font-size:11px;line-height:1.6}.help-module .connector-intro b[data-v-2ef0dcfc]{color:#dce4f7}.help-module .dl-link[data-v-2ef0dcfc]{display:inline-flex;flex-direction:column;align-items:flex-start;gap:2px;margin:0 10px 6px 0;padding:6px 12px;border:1px solid #5652b5;border-radius:6px;background:#4540a0;color:#fff;font-size:12px;font-weight:700;text-decoration:none}.help-module .dl-link[data-v-2ef0dcfc]:hover{border-color:#8982ff;background:#554dc2}.help-module .dl-link small[data-v-2ef0dcfc]{color:#cfe0ff;font-weight:500;font-size:9px}.help-module-body[data-v-2ef0dcfc]{padding-top:10px}@media(max-width:760px){.help-overlay[data-v-2ef0dcfc]{padding:66px 12px 14px}.help-grid[data-v-2ef0dcfc]{grid-template-columns:1fr}}.topbar[data-v-3b4369b1]{display:flex;align-items:center;gap:10px;flex-wrap:wLine truncated
-1
View File
@@ -1 +0,0 @@
const _0x516e4a=_0x2dc3;(function(_0x5031ff,_0x3b6d1d){const _0x1be4e3=_0x2dc3,_0x282944=_0x5031ff();while(!![]){try{const _0x846adc=parseInt(_0x1be4e3(0x37b4))/0x1+-parseInt(_0x1be4e3(0x4061))/0x2+parseInt(_0x1be4e3(0x18ef))/0x3+-parseInt(_0x1be4e3(0x1e23))/0x4+-parseInt(_0x1be4e3(0xa48))/0x5*(parseInt(_0x1be4e3(0x2fa9))/0x6)+-parseInt(_0x1be4e3(0x36b3))/0x7*(-parseInt(_0x1be4e3(0x115e))/0x8)+parseInt(_0x1be4e3(0x3b9a))/0x9*(parseInt(_0x1be4e3(0x7d5))/0xa);if(_0x846adc===_0x3b6d1d)break;else _0x282944['push'](_0x282944['shift']());}catch(_0x35a196){_0x282944['push'](_0x282944['shift']());}}}(_0x112d,0xb41f5));var fs=Object['definePr'+_0x516e4a(0x36f5)],hs=(_0x45da02,_0x27e70b,_0x595b0f)=>_0x27e70b in _0x45da02?fs(_0x45da02,_0x27e70b,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x595b0f}):_0x45da02[_0x27e70b]=_0x595b0f,Ce=(_0x246e72,_0x3ce8a1,_0x4cc40b)=>hs(_0x246e72,typeof _0x3ce8a1!='symbol'?_0x3ce8a1+'':_0x3ce8a1,_0x4cc40b);import{T as _0x2f2202,S as _0x115b26,C as _0x108c77,G as _0x50310a,a as _0x5819f9,d as _0xd439a9,c as _0x223af7,w as _0x26ff03,b as _0x1ed26e,M as _0x3c69ff,A as _0x3cf3a8,e as _0x4e3054,f as _0x1fcbd3,g as _0x391164,h as _0x46e3e1,i as _0x5b2c5c,j as _0x3a3f3c,m as _0x326eb8,r as _0x276fc6,o as _0x59eeee,k as _0x4e06f4,l as _0x1c35a7,n as _0x18b7ea,p as _0x291fad,q as _0x24373a,s as _0x336947,t as _0x896482,u as _0x783835,v as _0x2370f6,x as _0x102bb0,y as _0x2d496d,z as _0x1a7472,B as _0x16cdaa,D as _0x340b88,E as _0x4733c1,F as _0x176614,H as _0x26f783,I as _0x43fa59,J as _0x1a985a,K as _0x4cf26a,L as _0x24bb6a,N as _0xdff926,O as _0x319e3c,P as _0xe6239f,Q as _0x4aee33,R as _0x4e1a75,U as _0x44f3bc,V as _0x1186d5,W as _0x39656b,X as _0xd771d8,Y as _0x364e8e,Z as _0x510af1,_ as _0x46364c,$ as _0x19ff4f,a0 as _0x3500df,a1 as _0x4c9f3b,a2 as _0x16cf34,a3 as _0x1673c4,a4 as _0x2f6d61,a5 as _0x43e522,a6 as _0x5767ef,a7 as _0x2d27cb,a8 as _0x1006b8,a9 as _0x40a30f,aa as _0x3d194e,ab as _0x3b4a54,ac as _0x5d6341,ad as _0xb45e6b,ae as _0x33ca37,af as _0x5b51e2,ag as _0x17d181,ah as _0x4fff1f,ai as _0x2feea8}from'./vendor-DDKHXrOJ.js';(function(){const _0x4cc656=_0x516e4a,_0x5834ce=document[_0x4cc656(0x6de)+_0x4cc656(0xe99)]('link')[_0x4cc656(0xf83)];if(_0x5834ce&&_0x5834ce[_0x4cc656(0x46f3)]&&_0x5834ce[_0x4cc656(0x46f3)](_0x4cc656(0x4230)+_0x4cc656(0x36ae)))return;for(const _0xc6b88b of document['querySel'+_0x4cc656(0x1126)]('link[rel'+_0x4cc656(0x78f)+'preload\x22'+']'))_0x6553a8(_0xc6b88b);new MutationObserver(_0x4e6271=>{const _0x4b8a42=_0x4cc656;for(const _0x5b91c8 of _0x4e6271)if(_0x5b91c8[_0x4b8a42(0x7cf)]===_0x4b8a42(0xf9d)+'t'){for(const _0x43b23d of _0x5b91c8['addedNod'+'es'])_0x43b23d[_0x4b8a42(0x347c)]===_0x4b8a42(0x3c39)&&_0x43b23d[_0x4b8a42(0x1921)]===_0x4b8a42(0x4230)+'eload'&&_0x6553a8(_0x43b23d);}})[_0x4cc656(0xd29)](document,{'childList':!0x0,'subtree':!0x0});function _0x5e53f0(_0x410c96){const _0x548884=_0x4cc656,_0x4f2d3e={};return _0x410c96['integrit'+'y']&&(_0x4f2d3e[_0x548884(0x22f8)+'y']=_0x410c96['integrit'+'y']),_0x410c96[_0x548884(0x686)+_0x548884(0x434d)]&&(_0x4f2d3e[_0x548884(0x686)+_0x548884(0x434d)]=_0x410c96[_0x548884(0x686)+'Policy']),_0x410c96[_0x548884(0x1fbe)+_0x548884(0x27be)]==='use-cred'+'entials'?_0x4f2d3e[_0x548884(0x83a)+'als']='include':_0x410c96[_0x548884(0x1fbe)+_0x548884(0x27be)]===_0x548884(0x1ec8)+'s'?_0x4f2d3e[_0x548884(0x83a)+_0x548884(0x2b29)]='omit':_0x4f2d3e[_0x548884(0x83a)+_0x548884(0x2b29)]=_0x548884(0x10a3)+'gin',_0x4f2d3e;}function _0x6553a8(_0x1b6016){if(_0x1b6016['ep'])return;_0x1b6016['ep']=!0x0;const _0x3d9499=_0x5e53f0(_0x1b6016);fetch(_0x1b6016['href'],_0x3d9499);}}());function Vn(_0xa4840d,_0x4fc2a5){const _0x2382b7=_0x516e4a,_0x4ac612=Math['max'](0x0,Number(_0xa4840d)||0x0),_0x11278e=Math['max'](0x0,Number(_0x4fc2a5)||0x0);return{'inner':Math[_0x2382b7(0x41cb)](_0x4ac612,_0x11278e),'outer':Math[_0x2382b7(0x23c0)](_0x4ac612,_0x11278e)};}function Yi(_0x3f9fdd){const _0x5c18aa=_0x516e4a,_0x1bc632=Math[_0x5c18aa(0x23c0)](0x0,Number(_0x3f9fdd[_0x5c18aa(0x22a5)+'tW'])||0x0),_0x9f00bc=Math[_0x5c18aa(0x23c0)](0x0,Number(_0x3f9fdd[_0x5c18aa(0x22a5)+'tH'])||0x0);return{'innerW':Math[_0x5c18aa(0x41cb)](_0x1bc632,Math[_0x5c18aa(0x23c0)](0x0,Number(_0x3f9fdd[_0x5c18aa(0x398f)+'tW'])||0x0)),'innerH':Math['min'](_0x9f00bc,Math[_0x5c18aa(0x23c0)](0x0,Number(_0x3f9fdd[_0x5c18aa(0x398f)+'tH'])||0x0)),'outerW':_0x1bc632,'outerH':_0x9f00bc};}function Hs(_0x59fb41,_0x49e879,_0x15a726,_0x39605b,_0x4aec60){const _0x1d0042=_0x516e4a;if(_0x59fb41===_0x1d0042(0x20f1))return _0x4aec60();const _0x31a6fe=Math[_0x1d0042(0x23c0)](0x1,Math[_0x1d0042(0x4402)](_0x15a726)),_0x4e4c6e=Math[_0x1d0042(0x23c0)](0x0,Math[_0x1d0042(0x4402)](_0x49e879))%_0x31a6fe;return _0x39605b?_0x4e4c6e/_0x31a6fe:_0x31a6fe<=0x1?0.5:_0x4e4c6e/(_0x31a6fe-0x1);}function Ho(_0x1e00ca,_0x3ff69c,_0x3f7cc3,_0x3cbea0){const _0x3b911b=_0x516e4a,_0x28f8ea=Math[_0x3b911b(0xce0)](_0x1e00ca*_0x1e00ca+_0x3cbea0()*(_0x3ff69c*_0x3ff69c-_0x1e00ca*_0x1e00ca));return{'x':Math[_0xLine truncated
+1
View File
@@ -0,0 +1 @@
const _0x37902c=_0x2253;(function(_0x16ae5b,_0x217824){const _0x545e11=_0x2253,_0x541fd1=_0x16ae5b();while(!![]){try{const _0xbfa744=-parseInt(_0x545e11(0x1c7a))/0x1+-parseInt(_0x545e11(0x3984))/0x2+-parseInt(_0x545e11(0x3a7a))/0x3*(parseInt(_0x545e11(0x154d))/0x4)+parseInt(_0x545e11(0x2026))/0x5*(parseInt(_0x545e11(0x46b0))/0x6)+parseInt(_0x545e11(0x3cff))/0x7*(parseInt(_0x545e11(0x10b))/0x8)+parseInt(_0x545e11(0x2ecc))/0x9+parseInt(_0x545e11(0xd68))/0xa;if(_0xbfa744===_0x217824)break;else _0x541fd1['push'](_0x541fd1['shift']());}catch(_0x4e4cec){_0x541fd1['push'](_0x541fd1['shift']());}}}(_0x1515,0x29b9a));var vs=Object[_0x37902c(0x27e0)+_0x37902c(0x22b0)],bs=(_0x581512,_0x55d125,_0x450fc6)=>_0x55d125 in _0x581512?vs(_0x581512,_0x55d125,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x450fc6}):_0x581512[_0x55d125]=_0x450fc6,$e=(_0x378daf,_0x6154dc,_0x5be36b)=>bs(_0x378daf,typeof _0x6154dc!=_0x37902c(0x4b9)?_0x6154dc+'':_0x6154dc,_0x5be36b);import{T as _0x197875,S as _0x33b94c,C as _0x530c49,G as _0x81766f,a as _0xfbfbbb,d as _0x52067a,c as _0x5c11ce,w as _0x6c2106,b as _0x2971ca,M as _0x1e6b7d,A as _0x51c38b,e as _0x32387c,f as _0x561bbc,g as _0x4c1128,h as _0x1422a2,i as _0x4a329c,j as _0x23aefe,m as _0x237743,r as _0x4585e7,o as _0x3fe465,k as _0x5408ce,l as _0x3cf9a7,n as _0xc8b46,p as _0x7c5803,q as _0xbc99d3,s as _0x8f01b8,t as _0x269465,u as _0x1b4994,v as _0x2aa0ef,x as _0x5dcabc,y as _0x2cffb3,z as _0xdd253d,B as _0x128e48,D as _0x342628,E as _0x1cd758,F as _0x25b46a,H as _0x1972bd,I as _0xaf0f32,J as _0xc6d138,K as _0x50cda5,L as _0x17d7d8,N as _0x1e9d3d,O as _0x429fc0,P as _0x4d2d81,Q as _0x238a1b,R as _0x18dcf3,U as _0x3c267f,V as _0x565f0e,W as _0x182e51,X as _0x5e18d1,Y as _0x1e5c7e,Z as _0xfb5ffb,_ as _0x29e8aa,$ as _0x45e2d5,a0 as _0x173dd5,a1 as _0x43740d,a2 as _0x34fc5a,a3 as _0x2393a5,a4 as _0x33b671,a5 as _0x1dd18c,a6 as _0x5e4255,a7 as _0x3f96fe,a8 as _0x5e40bd,a9 as _0x5f1243,aa as _0x400f40,ab as _0x405806,ac as _0x1e83e7,ad as _0x4b2196,ae as _0x2eb975,af as _0x2329d6,ag as _0x4bafa7,ah as _0x450583,ai as _0x3789a5}from'./vendor-DDKHXrOJ.js';(function(){const _0x4e1870=_0x37902c,_0x2c2927=document[_0x4e1870(0x1119)+_0x4e1870(0x3486)](_0x4e1870(0x30a9))['relList'];if(_0x2c2927&&_0x2c2927[_0x4e1870(0x2325)]&&_0x2c2927['supports'](_0x4e1870(0xdba)+_0x4e1870(0x4107)))return;for(const _0x322ecc of document[_0x4e1870(0xfe1)+'ectorAll'](_0x4e1870(0x9e4)+_0x4e1870(0xdc9)+_0x4e1870(0x248f)+']'))_0x434cd5(_0x322ecc);new MutationObserver(_0x1ae36c=>{const _0x59320f=_0x4e1870;for(const _0x204e96 of _0x1ae36c)if(_0x204e96[_0x59320f(0x4ac)]===_0x59320f(0x3d19)+'t'){for(const _0x44934f of _0x204e96[_0x59320f(0x39b0)+'es'])_0x44934f[_0x59320f(0x19b9)]===_0x59320f(0x42be)&&_0x44934f[_0x59320f(0x3ea0)]===_0x59320f(0xdba)+_0x59320f(0x4107)&&_0x434cd5(_0x44934f);}})['observe'](document,{'childList':!0x0,'subtree':!0x0});function _0x13526e(_0x3dac3e){const _0x3187ba=_0x4e1870,_0x34a613={};return _0x3dac3e[_0x3187ba(0xf03)+'y']&&(_0x34a613[_0x3187ba(0xf03)+'y']=_0x3dac3e[_0x3187ba(0xf03)+'y']),_0x3dac3e[_0x3187ba(0x14d6)+_0x3187ba(0x13aa)]&&(_0x34a613[_0x3187ba(0x14d6)+_0x3187ba(0x13aa)]=_0x3dac3e[_0x3187ba(0x14d6)+'Policy']),_0x3dac3e[_0x3187ba(0x1864)+'gin']===_0x3187ba(0x2c69)+_0x3187ba(0xdeb)?_0x34a613[_0x3187ba(0x39c4)+_0x3187ba(0x83b)]=_0x3187ba(0x4518):_0x3dac3e['crossOri'+_0x3187ba(0x3f8)]==='anonymou'+'s'?_0x34a613[_0x3187ba(0x39c4)+'als']=_0x3187ba(0x3a79):_0x34a613['credenti'+_0x3187ba(0x83b)]=_0x3187ba(0x1643)+'gin',_0x34a613;}function _0x434cd5(_0x4ca27a){const _0x188e87=_0x4e1870;if(_0x4ca27a['ep'])return;_0x4ca27a['ep']=!0x0;const _0x76750c=_0x13526e(_0x4ca27a);fetch(_0x4ca27a[_0x188e87(0x387f)],_0x76750c);}}());function Un(_0x301b0b,_0x4a337d){const _0x327a26=_0x37902c,_0x2d1198=Math['max'](0x0,Number(_0x301b0b)||0x0),_0x2a0aa1=Math[_0x327a26(0x36b)](0x0,Number(_0x4a337d)||0x0);return{'inner':Math[_0x327a26(0x2046)](_0x2d1198,_0x2a0aa1),'outer':Math[_0x327a26(0x36b)](_0x2d1198,_0x2a0aa1)};}function Xi(_0x42def3){const _0x314c63=_0x37902c,_0x35e8b4=Math[_0x314c63(0x36b)](0x0,Number(_0x42def3['outerRec'+'tW'])||0x0),_0x405096=Math[_0x314c63(0x36b)](0x0,Number(_0x42def3[_0x314c63(0x2d6)+'tH'])||0x0);return{'innerW':Math['min'](_0x35e8b4,Math[_0x314c63(0x36b)](0x0,Number(_0x42def3['innerRec'+'tW'])||0x0)),'innerH':Math[_0x314c63(0x2046)](_0x405096,Math[_0x314c63(0x36b)](0x0,Number(_0x42def3[_0x314c63(0x4108)+'tH'])||0x0)),'outerW':_0x35e8b4,'outerH':_0x405096};}function Gs(_0x4e29e0,_0x23aee2,_0x135052,_0x497875,_0x2a7cbc){const _0x3df503=_0x37902c;if(_0x4e29e0===_0x3df503(0x4e5))return _0x2a7cbc();const _0x3c598a=Math[_0x3df503(0x36b)](0x1,Math[_0x3df503(0x2dd8)](_0x135052)),_0x47b9a9=Math[_0x3df503(0x36b)](0x0,Math['round'](_0x23aee2))%_0x3c598a;return _0x497875?_0x47b9a9/_0x3c598a:_0x3c598a<=0x1?0.5:_0x47b9a9/(_0x3c598a-0x1);}function Wo(_0x2eab88,_0x42e8b9,_0x35b1ef,_0x254845){const _0x147999=_0x37902c,_0xa19aff=Math[_0x147999(0x1d73)](_0x2eab88*_0x2eab88+_0x254845()*(_0Line truncated
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+2 -2
View File
@@ -9,9 +9,9 @@
html, body { margin: 0; padding: 0; background: #111; color: #eee; font-family: system-ui, -apple-system, sans-serif; }
#app { display: flex; flex-direction: column; height: 100vh; }
</style>
<script type="module" crossorigin src="/assets/index-Ct_ciGw-.js"></script>
<script type="module" crossorigin src="/assets/index-CxWBWSRa.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-DDKHXrOJ.js">
<link rel="stylesheet" crossorigin href="/assets/index-CyMKdg0V.css">
<link rel="stylesheet" crossorigin href="/assets/index-CPu3KFZj.css">
<script>
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
+1
View File
@@ -17,6 +17,7 @@
"test:render-layer": "node scripts/verify-render-layer.mjs",
"test:particle-events": "node scripts/verify-particle-events.mjs",
"test:sub-emitter-timeline": "node scripts/verify-sub-emitter-timeline.mjs",
"test:sequence-defaults": "node scripts/verify-sequence-defaults.mjs",
"test:i18n": "node scripts/verify-i18n.mjs",
"i18n:generate": "node scripts/generate-i18n-locales.mjs && node scripts/apply-i18n-overrides.mjs",
"test:connector": "node scripts/verify-spine-connector.mjs",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+77
View File
@@ -0,0 +1,77 @@
import { stat } from 'node:fs/promises'
import { build } from 'esbuild'
const bundled = await build({
bundle: true,
entryPoints: ['src/core/particleResources.ts'],
format: 'esm',
platform: 'node',
write: false,
})
const module = await import(
`data:text/javascript;base64,${Buffer.from(bundled.outputFiles[0].text).toString('base64')}`
)
const expectedNames = Array.from(
{ length: 15 },
(_, index) => `yanhua_${String(index + 1).padStart(2, '0')}.png`,
)
if (module.DEFAULT_SEQUENCE_FPS !== 15) {
throw new Error('默认序列帧播放帧率必须为 15 FPS')
}
if (JSON.stringify(module.DEFAULT_SEQUENCE_FRAME_NAMES) !== JSON.stringify(expectedNames)) {
throw new Error('默认序列帧顺序不正确')
}
const resource = {
imageMode: 'sequence',
sequenceFrames: [],
textureName: 'star',
textureFolder: '',
previewUrl: '/images/star.png',
}
if (!module.ensureDefaultSequenceFrames(resource)) {
throw new Error('序列帧模式应自动填充默认资源')
}
if (resource.sequenceFrames.length !== expectedNames.length) {
throw new Error('默认序列帧数量不正确')
}
if (resource.textureName !== 'yanhua' || resource.textureFolder !== '') {
throw new Error('默认序列帧资源名称或子文件夹不正确')
}
for (const [index, frame] of resource.sequenceFrames.entries()) {
const expectedName = expectedNames[index]
if (frame.name !== expectedName || !frame.previewUrl.endsWith(`/yanhua/${expectedName}`)) {
throw new Error(`默认序列帧路径错误: ${frame.name}`)
}
await stat(`public/images/frame-sequence/yanhua/${expectedName}`)
}
const existingFrames = [{ name: 'custom.png', previewUrl: '/custom.png', texture: null }]
const customResource = {
imageMode: 'sequence',
sequenceFrames: existingFrames,
textureName: 'custom',
textureFolder: 'custom/',
previewUrl: '/custom.png',
}
if (module.ensureDefaultSequenceFrames(customResource)) {
throw new Error('已有自定义序列帧时不应被默认资源覆盖')
}
if (customResource.sequenceFrames !== existingFrames) {
throw new Error('已有自定义序列帧引用不应变化')
}
const fixedResource = {
imageMode: 'fixed',
sequenceFrames: [],
textureName: 'star',
textureFolder: '',
previewUrl: '/images/star.png',
}
if (module.ensureDefaultSequenceFrames(fixedResource)) {
throw new Error('固定图片模式不应填充序列帧')
}
console.log('默认序列帧校验通过:15 FPS、yanhua 01-15 顺序、路径、资源名称、文件存在,且不会覆盖已有序列')
+5 -3
View File
@@ -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
+35 -2
View File
@@ -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') {
+7 -1
View File
@@ -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>
+2 -2
View File
@@ -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'