优化碰撞识别,加入暖场帧数
This commit is contained in:
1 parent
b8652acce0
commit
fed1015849
21 files changed
+195
-23
No files matched your search
Vendored
-1
@@ -1 +0,0 @@
|
||||
const _0x510257=_0x5a3e;(function(_0x498efe,_0x1d7a42){const _0x63d9de=_0x5a3e,_0x26ef44=_0x498efe();while(!![]){try{const _0x11c620=parseInt(_0x63d9de(0x4640))/0x1*(parseInt(_0x63d9de(0x3c94))/0x2)+parseInt(_0x63d9de(0x146c))/0x3+-parseInt(_0x63d9de(0x2c3a))/0x4+parseInt(_0x63d9de(0x35d5))/0x5*(parseInt(_0x63d9de(0x144b))/0x6)+parseInt(_0x63d9de(0x2dc))/0x7+-parseInt(_0x63d9de(0x4378))/0x8*(-parseInt(_0x63d9de(0x3dd5))/0x9)+parseInt(_0x63d9de(0x398b))/0xa*(-parseInt(_0x63d9de(0x5d0))/0xb);if(_0x11c620===_0x1d7a42)break;else _0x26ef44['push'](_0x26ef44['shift']());}catch(_0x3dae48){_0x26ef44['push'](_0x26ef44['shift']());}}}(_0x3de8,0x7c3e4));var ps=Object[_0x510257(0x321c)+_0x510257(0x3ea7)],ms=(_0x889725,_0x45e6df,_0x274b1e)=>_0x45e6df in _0x889725?ps(_0x889725,_0x45e6df,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x274b1e}):_0x889725[_0x45e6df]=_0x274b1e,Ce=(_0x27fee3,_0x14d304,_0x1bde99)=>ms(_0x27fee3,typeof _0x14d304!='symbol'?_0x14d304+'':_0x14d304,_0x1bde99);import{T as _0x16bf37,S as _0x5466b7,C as _0x597aa3,G as _0x87a75,a as _0x18e97a,d as _0x468616,c as _0xa4f2b8,w as _0x4250bd,b as _0xeb3cfb,M as _0x56c512,A as _0x4c757b,e as _0x283823,f as _0x3d7d19,g as _0x46c58f,h as _0x2d9479,i as _0x487d55,j as _0x448fc9,m as _0x1f88cd,r as _0x131a89,o as _0x26792c,k as _0x4a9200,l as _0x1448b7,n as _0x5d9244,p as _0x1008ce,q as _0x24f5c8,s as _0x55d31d,t as _0x5c1a82,u as _0x360c0b,v as _0x1ecd59,x as _0x11ea01,y as _0x125b24,z as _0x4b3dd4,B as _0x27fc01,D as _0x55ad4e,E as _0x406b6f,F as _0x692e1b,H as _0x3e495b,I as _0x5b4e0a,J as _0x112b5d,K as _0x46cf07,L as _0x1ab032,N as _0x26f8fd,O as _0x241d0b,P as _0x3098fd,Q as _0x355d0b,R as _0x1c88c6,U as _0x4d8814,V as _0x3bddd4,W as _0xdac53a,X as _0x466bea,Y as _0xbb6d35,Z as _0x331c43,_ as _0x547ae2,$ as _0x1f4672,a0 as _0xe95442,a1 as _0x3f63dd,a2 as _0x56bf25,a3 as _0x1fd2f7,a4 as _0x247b6b,a5 as _0x7483f1,a6 as _0x301caa,a7 as _0x4ddd92,a8 as _0xa634b2,a9 as _0x1f489b,aa as _0x5f0f6e,ab as _0x448aa7,ac as _0x13c12e,ad as _0x5b6bfb,ae as _0x1c3971,af as _0x2a78aa,ag as _0x120aa7,ah as _0x2a2dcf,ai as _0x178a37}from'./vendor-Cb_6CYmx.js';(function(){const _0x30c23a=_0x510257,_0x1803ed=document[_0x30c23a(0x3bf5)+_0x30c23a(0x395e)](_0x30c23a(0x2aa6))[_0x30c23a(0x1e57)];if(_0x1803ed&&_0x1803ed[_0x30c23a(0x34d8)]&&_0x1803ed['supports']('modulepr'+_0x30c23a(0x1f6f)))return;for(const _0x55c277 of document[_0x30c23a(0xdcc)+_0x30c23a(0x24ef)](_0x30c23a(0xc3e)+_0x30c23a(0x1b3f)+_0x30c23a(0x2d7)+']'))_0xc53449(_0x55c277);new MutationObserver(_0x194db0=>{const _0x218e97=_0x30c23a;for(const _0x97cf73 of _0x194db0)if(_0x97cf73[_0x218e97(0x3ab8)]==='childLis'+'t'){for(const _0x66cff0 of _0x97cf73[_0x218e97(0xc5a)+'es'])_0x66cff0[_0x218e97(0x3c1d)]===_0x218e97(0x34bb)&&_0x66cff0[_0x218e97(0x2dce)]==='modulepr'+_0x218e97(0x1f6f)&&_0xc53449(_0x66cff0);}})[_0x30c23a(0x287)](document,{'childList':!0x0,'subtree':!0x0});function _0x477890(_0x58993d){const _0x1e8e43=_0x30c23a,_0x536833={};return _0x58993d[_0x1e8e43(0x131f)+'y']&&(_0x536833[_0x1e8e43(0x131f)+'y']=_0x58993d[_0x1e8e43(0x131f)+'y']),_0x58993d[_0x1e8e43(0x8c8)+_0x1e8e43(0x24fb)]&&(_0x536833[_0x1e8e43(0x8c8)+'Policy']=_0x58993d['referrer'+'Policy']),_0x58993d[_0x1e8e43(0x3c1f)+_0x1e8e43(0x2a74)]==='use-cred'+_0x1e8e43(0x187e)?_0x536833['credenti'+_0x1e8e43(0x4697)]=_0x1e8e43(0x4130):_0x58993d[_0x1e8e43(0x3c1f)+_0x1e8e43(0x2a74)]===_0x1e8e43(0x3588)+'s'?_0x536833['credenti'+_0x1e8e43(0x4697)]=_0x1e8e43(0x40ce):_0x536833[_0x1e8e43(0x447b)+'als']='same-ori'+_0x1e8e43(0x2a74),_0x536833;}function _0xc53449(_0x577589){const _0x596e9c=_0x30c23a;if(_0x577589['ep'])return;_0x577589['ep']=!0x0;const _0x2b010c=_0x477890(_0x577589);fetch(_0x577589[_0x596e9c(0x3d3a)],_0x2b010c);}}());function zn(_0x36e4ec,_0x553ff0){const _0x327341=_0x510257,_0x2a0cc8=Math[_0x327341(0x2224)](0x0,Number(_0x36e4ec)||0x0),_0x356b2d=Math['max'](0x0,Number(_0x553ff0)||0x0);return{'inner':Math['min'](_0x2a0cc8,_0x356b2d),'outer':Math[_0x327341(0x2224)](_0x2a0cc8,_0x356b2d)};}function ji(_0x461f3a){const _0x430fa4=_0x510257,_0x18f9f0=Math[_0x430fa4(0x2224)](0x0,Number(_0x461f3a['outerRec'+'tW'])||0x0),_0x45c6e5=Math['max'](0x0,Number(_0x461f3a[_0x430fa4(0x172d)+'tH'])||0x0);return{'innerW':Math[_0x430fa4(0x332b)](_0x18f9f0,Math[_0x430fa4(0x2224)](0x0,Number(_0x461f3a['innerRec'+'tW'])||0x0)),'innerH':Math[_0x430fa4(0x332b)](_0x45c6e5,Math[_0x430fa4(0x2224)](0x0,Number(_0x461f3a[_0x430fa4(0x1584)+'tH'])||0x0)),'outerW':_0x18f9f0,'outerH':_0x45c6e5};}function Us(_0x4f1c4c,_0x4ed467,_0x335dd7,_0x1fd153,_0x34f4f9){const _0x4a3bcd=_0x510257;if(_0x4f1c4c===_0x4a3bcd(0x3b74))return _0x34f4f9();const _0x2cdb18=Math[_0x4a3bcd(0x2224)](0x1,Math['round'](_0x335dd7)),_0x5c8f8e=Math[_0x4a3bcd(0x2224)](0x0,Math[_0x4a3bcd(0x423)](_0x4ed467))%_0x2cdb18;return _0x1fd153?_0x5c8f8e/_0x2cdb18:_0x2cdb18<=0x1?0.5:_0x5c8f8e/(_0x2cdb18-0x1);}function Uo(_0x4a8400,_0x39dc83,_0x12dcd6,_0x5b4c87){const _0x3e031b=_0x510257,_0x4402ab=Math['sqrt'](_0x4a8400*_0xLine truncated
|
||||
Vendored
+1
@@ -0,0 +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-de1baa8a]{display:flex;align-items:center;gap:10px;flex-wrap:wLine truncated
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
.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-de1baa8a]{display:flex;align-items:center;gap:10px;flex-wrap:wLine truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
const _0x202e38=_0x15bb;(function(_0x409791,_0x3ac34d){const _0x2a9db8=_0x15bb,_0x4476ba=_0x409791();while(!![]){try{const _0x1c64c1=-parseInt(_0x2a9db8(0x256f))/0x1*(parseInt(_0x2a9db8(0x42f0))/0x2)+-parseInt(_0x2a9db8(0x1d38))/0x3*(-parseInt(_0x2a9db8(0xf43))/0x4)+-parseInt(_0x2a9db8(0x3609))/0x5*(parseInt(_0x2a9db8(0x927))/0x6)+parseInt(_0x2a9db8(0x1cc6))/0x7*(parseInt(_0x2a9db8(0x45a2))/0x8)+parseInt(_0x2a9db8(0x43ba))/0x9*(parseInt(_0x2a9db8(0x1e06))/0xa)+-parseInt(_0x2a9db8(0x809))/0xb+parseInt(_0x2a9db8(0x1154))/0xc*(-parseInt(_0x2a9db8(0x31dd))/0xd);if(_0x1c64c1===_0x3ac34d)break;else _0x4476ba['push'](_0x4476ba['shift']());}catch(_0x4eb889){_0x4476ba['push'](_0x4476ba['shift']());}}}(_0x384f,0xb4407));var ps=Object[_0x202e38(0xc00)+_0x202e38(0x2770)],ms=(_0x5b1db1,_0x6ed7f5,_0x14260c)=>_0x6ed7f5 in _0x5b1db1?ps(_0x5b1db1,_0x6ed7f5,{'enumerable':!0x0,'configurable':!0x0,'writable':!0x0,'value':_0x14260c}):_0x5b1db1[_0x6ed7f5]=_0x14260c,ke=(_0x4c3a70,_0x2df07e,_0x209f42)=>ms(_0x4c3a70,typeof _0x2df07e!=_0x202e38(0x3de1)?_0x2df07e+'':_0x2df07e,_0x209f42);import{T as _0x2f45dd,S as _0x3b930a,C as _0x4fb826,G as _0x3e219d,a as _0x5d9c86,d as _0x329760,c as _0x28a8c4,w as _0x4e8ea3,b as _0x518d73,M as _0x38b11d,A as _0x4bbf18,e as _0x4e0b77,f as _0x1c12d8,g as _0x118ee6,h as _0x518087,i as _0x343be0,j as _0x3aea42,m as _0x4245ee,r as _0x345b8b,o as _0x5a1993,k as _0x55586f,l as _0x27fe2d,n as _0x233271,p as _0x3499a7,q as _0x4bfd2f,s as _0x2489db,t as _0xd56584,u as _0x418cd7,v as _0x49b4e4,x as _0x1dfcde,y as _0x6931fc,z as _0x433d94,B as _0x5e51e6,D as _0x2c42df,E as _0x2a7120,F as _0x56caf9,H as _0x92f02b,I as _0x531c0d,J as _0xea11ef,K as _0x45b8dd,L as _0x55e444,N as _0xf76111,O as _0x27fd42,P as _0x536523,Q as _0x1b2419,R as _0x178f0b,U as _0x2c79c0,V as _0xeabcda,W as _0x546e3f,X as _0x10c5d5,Y as _0x48ae11,Z as _0x50af4a,_ as _0x117110,$ as _0x3e4f2e,a0 as _0x497587,a1 as _0xb85fd5,a2 as _0x992e5a,a3 as _0x4ad220,a4 as _0x1cef85,a5 as _0x4f2607,a6 as _0x422693,a7 as _0x5c5ee4,a8 as _0x58d34a,a9 as _0x28f39d,aa as _0xce11ba,ab as _0x1c58f3,ac as _0x2fe175,ad as _0x403499,ae as _0x5607c6,af as _0x3cac7a,ag as _0x489ef5,ah as _0x22b07f,ai as _0x553394}from'./vendor-Cb_6CYmx.js';(function(){const _0x359d0a=_0x202e38,_0x2cdb2f=document[_0x359d0a(0x3230)+_0x359d0a(0xe72)](_0x359d0a(0x31cd))[_0x359d0a(0x1b7f)];if(_0x2cdb2f&&_0x2cdb2f['supports']&&_0x2cdb2f[_0x359d0a(0x3111)](_0x359d0a(0x747)+_0x359d0a(0x17f5)))return;for(const _0x11d7ef of document[_0x359d0a(0x5cf)+_0x359d0a(0x2685)](_0x359d0a(0xe2a)+_0x359d0a(0xa9b)+_0x359d0a(0x36d9)+']'))_0x155df4(_0x11d7ef);new MutationObserver(_0x443b71=>{const _0x514ef2=_0x359d0a;for(const _0x1d0590 of _0x443b71)if(_0x1d0590[_0x514ef2(0xab6)]===_0x514ef2(0x1971)+'t'){for(const _0x2a7962 of _0x1d0590[_0x514ef2(0x1300)+'es'])_0x2a7962[_0x514ef2(0x10c8)]===_0x514ef2(0x1161)&&_0x2a7962[_0x514ef2(0x254a)]===_0x514ef2(0x747)+_0x514ef2(0x17f5)&&_0x155df4(_0x2a7962);}})[_0x359d0a(0x1acc)](document,{'childList':!0x0,'subtree':!0x0});function _0x5ced3d(_0x5f3b8d){const _0x4e3b5b=_0x359d0a,_0x5d8c3d={};return _0x5f3b8d[_0x4e3b5b(0x2598)+'y']&&(_0x5d8c3d[_0x4e3b5b(0x2598)+'y']=_0x5f3b8d[_0x4e3b5b(0x2598)+'y']),_0x5f3b8d['referrer'+_0x4e3b5b(0x3607)]&&(_0x5d8c3d['referrer'+_0x4e3b5b(0x3607)]=_0x5f3b8d['referrer'+_0x4e3b5b(0x3607)]),_0x5f3b8d['crossOri'+_0x4e3b5b(0x2ac6)]==='use-cred'+_0x4e3b5b(0x2c35)?_0x5d8c3d[_0x4e3b5b(0x1ea5)+_0x4e3b5b(0x12ed)]=_0x4e3b5b(0x3e9c):_0x5f3b8d['crossOri'+_0x4e3b5b(0x2ac6)]===_0x4e3b5b(0x2b7e)+'s'?_0x5d8c3d[_0x4e3b5b(0x1ea5)+_0x4e3b5b(0x12ed)]=_0x4e3b5b(0x28f2):_0x5d8c3d['credenti'+_0x4e3b5b(0x12ed)]=_0x4e3b5b(0x9a6)+_0x4e3b5b(0x2ac6),_0x5d8c3d;}function _0x155df4(_0x10a173){const _0x2c4918=_0x359d0a;if(_0x10a173['ep'])return;_0x10a173['ep']=!0x0;const _0x2646b1=_0x5ced3d(_0x10a173);fetch(_0x10a173[_0x2c4918(0x345a)],_0x2646b1);}}());function zn(_0x649915,_0x4ff56f){const _0x3ed132=_0x202e38,_0x247a97=Math[_0x3ed132(0x2746)](0x0,Number(_0x649915)||0x0),_0x58cf91=Math['max'](0x0,Number(_0x4ff56f)||0x0);return{'inner':Math['min'](_0x247a97,_0x58cf91),'outer':Math[_0x3ed132(0x2746)](_0x247a97,_0x58cf91)};}function Vi(_0x328171){const _0xf6b96a=_0x202e38,_0xfa47b6=Math['max'](0x0,Number(_0x328171[_0xf6b96a(0x67a)+'tW'])||0x0),_0x22b8a6=Math[_0xf6b96a(0x2746)](0x0,Number(_0x328171[_0xf6b96a(0x67a)+'tH'])||0x0);return{'innerW':Math[_0xf6b96a(0x18b8)](_0xfa47b6,Math[_0xf6b96a(0x2746)](0x0,Number(_0x328171[_0xf6b96a(0x2b48)+'tW'])||0x0)),'innerH':Math[_0xf6b96a(0x18b8)](_0x22b8a6,Math[_0xf6b96a(0x2746)](0x0,Number(_0x328171[_0xf6b96a(0x2b48)+'tH'])||0x0)),'outerW':_0xfa47b6,'outerH':_0x22b8a6};}function Us(_0x5b615f,_0x4ef316,_0x7caf7f,_0x1d4f9d,_0x405420){const _0x1ec7ea=_0x202e38;if(_0x5b615f===_0x1ec7ea(0x12b9))return _0x405420();const _0x524f66=Math[_0x1ec7ea(0x2746)](0x1,Math['round'](_0x7caf7f)),_0x20d2aa=Math['max'](0x0,Math['round'](_0x4ef316))%_0x524f66;return _0x1d4f9d?_0x20d2aa/_0x524f66:_0x524f66<=0x1?0.5:_0x20d2aa/(_0x524f66-0x1);}function qLine truncated
|
||||
Vendored
+2
-2
@@ -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-5gcswTi2.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-D9evYJNa.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-Cb_6CYmx.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C1A9e4gY.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BiWG32s_.css">
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
|
||||
@@ -13,6 +13,13 @@ const { ParticleEmitter, defaultConfig } = await import(
|
||||
)
|
||||
const { Texture } = await import('./pixi-stub.mjs')
|
||||
|
||||
if (defaultConfig().warmupFrames !== 0) throw new Error('暖场帧数默认值必须为 0')
|
||||
const normalizedWarmup = makeConfig({ warmupFrames: -2.7 })
|
||||
const normalizedWarmupEmitter = new ParticleEmitter(normalizedWarmup)
|
||||
if (normalizedWarmup.warmupFrames !== 0 || normalizedWarmupEmitter.activeCount !== 0) {
|
||||
throw new Error('暖场帧数必须归一化为不小于 0 的整数')
|
||||
}
|
||||
|
||||
const subBundled = await build({
|
||||
bundle: true,
|
||||
entryPoints: ['src/core/subEmitterIds.ts'],
|
||||
@@ -171,4 +178,50 @@ if (![...collisionCountsBySpawn.values()].some((counts) => counts[0] === 1 && co
|
||||
throw new Error('同一粒子的碰撞次数未正确累计')
|
||||
}
|
||||
|
||||
console.log('粒子事件校验通过:出生 / 死亡 / 碰撞 / 子发射器生成与禁用自动发射 / 属性继承 / 深度上限')
|
||||
const warmupEmitter = new ParticleEmitter(makeConfig({
|
||||
mode: 'stream',
|
||||
streamBehavior: 'normal',
|
||||
rate: 30,
|
||||
duration: 10,
|
||||
lifeMin: 10,
|
||||
lifeMax: 10,
|
||||
speedMin: 0,
|
||||
speedMax: 0,
|
||||
}))
|
||||
warmupEmitter.reset()
|
||||
warmupEmitter.warmup(3, step)
|
||||
if (warmupEmitter.activeCount !== 3) {
|
||||
throw new Error(`暖场 3 帧后应有 3 颗粒子,实际 ${warmupEmitter.activeCount} 颗`)
|
||||
}
|
||||
|
||||
// 非等比缩放下,应按世界距离选择最近面,不能让宽扁矩形的顶面碰撞跳到侧面。
|
||||
const scaledRectEmitter = new ParticleEmitter(makeConfig({
|
||||
collision: true,
|
||||
lifeMin: 1,
|
||||
lifeMax: 1,
|
||||
speedMin: 0,
|
||||
speedMax: 0,
|
||||
useGravity: false,
|
||||
}))
|
||||
scaledRectEmitter.setColliders([{
|
||||
...collider,
|
||||
shape: 'rect',
|
||||
x: 0,
|
||||
y: -60,
|
||||
scaleX: 4,
|
||||
scaleY: 0.5,
|
||||
width: 120,
|
||||
height: 80,
|
||||
}])
|
||||
scaledRectEmitter.setAutoEmission(false)
|
||||
scaledRectEmitter.spawnEventParticle(-125.02, -56.58, 1)
|
||||
const scaledRectEvents = []
|
||||
scaledRectEmitter.onParticleEvent = (event) => scaledRectEvents.push(event)
|
||||
scaledRectEmitter.update(step, 'force')
|
||||
const scaledRectHit = scaledRectEvents.find((event) => event.kind === 'collision')
|
||||
if (!scaledRectHit) throw new Error('非等比矩形未产生碰撞事件')
|
||||
if (Math.abs(scaledRectHit.worldX + 125.02) > 0.1 || Math.abs(scaledRectHit.worldY + 40) > 0.1) {
|
||||
throw new Error(`非等比矩形吸附面错误:实际命中 (${scaledRectHit.worldX}, ${scaledRectHit.worldY})`)
|
||||
}
|
||||
|
||||
console.log('粒子事件校验通过:暖场 / 出生 / 死亡 / 碰撞 / 非等比矩形碰撞面 / 子发射器生成与禁用自动发射 / 属性继承 / 深度上限')
|
||||
@@ -125,6 +125,7 @@ export function defaultConfig(): EmitterConfig {
|
||||
gravityCurve: [{ x: 0, y: 1 }, { x: 1, y: 1 }],
|
||||
damping: 0,
|
||||
seed: 0,
|
||||
warmupFrames: 0,
|
||||
colorStart: '#ffffff',
|
||||
colorEnd: '#ff4d4d',
|
||||
colorOverLife: false,
|
||||
@@ -358,6 +359,7 @@ export function ensureEmitterConfig(config: EmitterConfig): EmitterConfig {
|
||||
if (config.streamBehavior !== 'normal' && config.streamBehavior !== 'loop') config.streamBehavior = 'normal'
|
||||
if (needsLoopDurationMigration || !Number.isFinite(config.loopDurationFrames)) config.loopDurationFrames = Math.ceil(Math.max(1 / 30, Number(config.duration) || 1) * 30)
|
||||
config.loopDurationFrames = Math.min(1800, Math.max(1, Math.ceil(config.loopDurationFrames)))
|
||||
config.warmupFrames = Math.max(0, Math.floor(Number(config.warmupFrames) || 0))
|
||||
if (typeof config.generateLoopStartAnimation !== 'boolean') config.generateLoopStartAnimation = false
|
||||
if (typeof config.generateLoopEndAnimation !== 'boolean') config.generateLoopEndAnimation = false
|
||||
if (typeof config.loopStartUseCustomDuration !== 'boolean') config.loopStartUseCustomDuration = false
|
||||
|
||||
@@ -862,4 +862,10 @@ export class ParticleEmitter extends Container {
|
||||
this._lastSeed = this.cfg.seed
|
||||
this.rng = mulberry32(this.cfg.seed >>> 0)
|
||||
}
|
||||
|
||||
/** 从当前重置状态向前模拟,用于时间轴第 0 帧的暖场。 */
|
||||
warmup(frames: number, frameDuration: number) {
|
||||
const count = Math.max(0, Math.floor(Number(frames) || 0))
|
||||
for (let frame = 0; frame < count; frame++) this.update(frameDuration)
|
||||
}
|
||||
}
|
||||
@@ -119,8 +119,8 @@ export function resolveColliderPoint(collider: SceneCollider, worldX: number, wo
|
||||
const halfWidth = Math.max(0.001, collider.width * 0.5)
|
||||
const halfHeight = Math.max(0.001, collider.height * 0.5)
|
||||
if (Math.abs(localX) >= halfWidth || Math.abs(localY) >= halfHeight) return null
|
||||
const penetrationX = halfWidth - Math.abs(localX)
|
||||
const penetrationY = halfHeight - Math.abs(localY)
|
||||
const penetrationX = (halfWidth - Math.abs(localX)) * scaleX
|
||||
const penetrationY = (halfHeight - Math.abs(localY)) * scaleY
|
||||
if (penetrationX < penetrationY) {
|
||||
const nx = localX >= 0 ? 1 : -1
|
||||
return toWorld(nx * halfWidth, localY, nx, 0)
|
||||
|
||||
@@ -265,6 +265,8 @@ export interface EmitterConfig {
|
||||
damping: number
|
||||
/** 全局随机种子;包括 0 在内的任意整数都可确定性复现 */
|
||||
seed: number
|
||||
/** 时间轴开始前预先模拟的帧数。 */
|
||||
warmupFrames: number
|
||||
// 颜色
|
||||
colorStart: string
|
||||
colorEnd: string
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "Drehung",
|
||||
"scaleX": "X-Skalierung",
|
||||
"scaleY": "Y-Skalierung",
|
||||
"warmupFrames": "Vorlauf in Frames",
|
||||
"seed": "Zufallsseed",
|
||||
"seedTitle": "Steuern Sie alle Zufallsattribute, die Ressourcenauswahl und die Zufallsstichprobe von Modifikatoren im Partikelsystem",
|
||||
"randomizeSeed": "Generieren Sie einen neuen globalen Zufallsstartwert",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "„Konfiguration speichern“ speichert alle editierbaren Objekte und Parameter; „Export Spine Json“ exportiert nur das angezeigte Partikelsystem und backt die Kollisions-, Folge-, Nachlauf- und Sub-Emitter-Ergebnisse in unabhängige Animationsdaten."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "Nach dem Aufrufen des kontinuierlichen Schleifenmodus werden die Partikelverfolgungseinstellungen ungültig und auf den Standardwert zurückgesetzt. Weitermachen?",
|
||||
"confirmLoopFollowEnter": "Nach dem Aufrufen des kontinuierlichen Schleifenmodus werden die Partikelverfolgungseinstellungen ungültig, auf den Standardwert zurückgesetzt und der Vorlauf in Frames ignoriert. Weitermachen?",
|
||||
"confirmLoopFollowExit": "Nach dem Verlassen des Schleifenpersistenzmodus werden die Partikelverfolgungseinstellungen ungültig und auf ihre Standardwerte zurückgesetzt. Weitermachen?",
|
||||
"invalidJson": "Die Konfigurationsdatei ist ungültig JSON",
|
||||
"invalidConfigFormat": "Nicht das Editorprofil SpineParticle",
|
||||
|
||||
@@ -499,6 +499,7 @@
|
||||
"rotation": "Rotation",
|
||||
"scaleX": "Scale X",
|
||||
"scaleY": "Scale Y",
|
||||
"warmupFrames": "Warm-up frames",
|
||||
"seed": "Random seed",
|
||||
"seedTitle": "Controls random attributes, resource selection, and modifier sampling",
|
||||
"randomizeSeed": "Generate a new global random seed",
|
||||
@@ -834,7 +835,7 @@
|
||||
"differenceDesc": "Save Config preserves editable objects and parameters. Export Spine JSON includes only visible particle systems and bakes collisions, following, trails, and sub-emitters into editable animation data."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "Enabling loop stream disables particle following and resets its settings. Continue?",
|
||||
"confirmLoopFollowEnter": "Enabling loop stream disables particle following, resets its settings, and disables warm-up frames. Continue?",
|
||||
"confirmLoopFollowExit": "Leaving loop stream disables particle following and resets its settings. Continue?",
|
||||
"invalidJson": "The configuration file is not valid JSON",
|
||||
"invalidConfigFormat": "This is not a SpineParticle editor configuration",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "Rotación",
|
||||
"scaleX": "Escala X",
|
||||
"scaleY": "Escala Y",
|
||||
"warmupFrames": "Fotogramas de calentamiento",
|
||||
"seed": "Semilla aleatoria",
|
||||
"seedTitle": "Controle todos los atributos aleatorios, la selección de recursos y el muestreo aleatorio de modificadores en el sistema de partículas.",
|
||||
"randomizeSeed": "Generar una nueva semilla aleatoria global",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "\"Guardar configuración\" guarda todos los objetos y parámetros que se pueden editar; \"Exportar Spine Json\" solo exporta el sistema de partículas mostrado y convierte los resultados de colisión, seguimiento, seguimiento y subemisor en datos de animación independientes."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "Después de ingresar al modo de bucle continuo, la siguiente configuración de partículas no será válida y se restaurará al valor predeterminado. ¿Continuar?",
|
||||
"confirmLoopFollowEnter": "Al activar el flujo en bucle, el seguimiento de partículas se desactivará y se restablecerá, y los fotogramas de calentamiento se ignorarán. ¿Continuar?",
|
||||
"confirmLoopFollowExit": "Después de salir del modo de persistencia del bucle, la siguiente configuración de partículas no será válida y se restaurará a sus valores predeterminados. ¿Continuar?",
|
||||
"invalidJson": "El archivo de configuración no es válido JSON",
|
||||
"invalidConfigFormat": "No es el perfil del editor SpineParticle",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "Rotation",
|
||||
"scaleX": "Échelle X",
|
||||
"scaleY": "Échelle Y",
|
||||
"warmupFrames": "Images de préchauffage",
|
||||
"seed": "Graine aléatoire",
|
||||
"seedTitle": "Contrôlez tous les attributs aléatoires, la sélection des ressources et l'échantillonnage aléatoire du modificateur dans le système de particules",
|
||||
"randomizeSeed": "Générer une nouvelle graine aléatoire globale",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "« Enregistrer la configuration » enregistre tous les objets et paramètres pouvant être modifiés ; \"Exporter Spine Json\" exporte uniquement le système de particules affiché et intègre les résultats de collision, de suivi, de fuite et de sous-émetteur dans des données d'animation indépendantes."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "Après être entré en mode continu de boucle, les paramètres de suivi des particules seront invalides et restaurés à leur valeur par défaut. Continuer?",
|
||||
"confirmLoopFollowEnter": "Après être entré en mode continu de boucle, les paramètres de suivi des particules seront invalides et restaurés à leur valeur par défaut, et les images de préchauffage seront ignorées. Continuer?",
|
||||
"confirmLoopFollowExit": "Après avoir quitté le mode de persistance de boucle, les paramètres de suivi des particules seront invalides et restaurés à leurs valeurs par défaut. Continuer?",
|
||||
"invalidJson": "Le fichier de configuration n'est pas valide JSON",
|
||||
"invalidConfigFormat": "Pas le profil de l'éditeur SpineParticle",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "回転",
|
||||
"scaleX": "X スケール",
|
||||
"scaleY": "Y スケール",
|
||||
"warmupFrames": "ウォームアップフレーム",
|
||||
"seed": "ランダムシード",
|
||||
"seedTitle": "パーティクル システム内のすべてのランダム アトリビュート、リソースの選択、モディファイアのランダム サンプリングを制御します。",
|
||||
"randomizeSeed": "新しいグローバルランダムシードを生成する",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "「設定の保存」では、編集可能なすべてのオブジェクトとパラメータが保存されます。 「Spine Json のエクスポート」は、表示されているパーティクル システムのみをエクスポートし、コリジョン、フォロー、トレーリング、サブエミッターの結果を独立したアニメーション データにベイクします。"
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "ループ連続モードに入ると、パーティクル追従設定は無効になり、デフォルト値に戻ります。続く?",
|
||||
"confirmLoopFollowEnter": "ループ連続モードに入ると、パーティクル追従設定は無効になり、デフォルト値に戻り、ウォームアップフレームも無効になります。続く?",
|
||||
"confirmLoopFollowExit": "ループ永続モードを終了すると、パーティクル追従設定は無効になり、デフォルト値に戻ります。続く?",
|
||||
"invalidJson": "構成ファイルが有効ではありません JSON",
|
||||
"invalidConfigFormat": "SpineParticle 編集者プロフィールではありません",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "회전",
|
||||
"scaleX": "X 크기",
|
||||
"scaleY": "Y 크기",
|
||||
"warmupFrames": "웜업 프레임",
|
||||
"seed": "랜덤 시드",
|
||||
"seedTitle": "입자 시스템의 모든 무작위 속성, 리소스 선택 및 수정자 무작위 샘플링을 제어합니다.",
|
||||
"randomizeSeed": "새로운 전역 무작위 시드 생성",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "\"구성 저장\"은 편집할 수 있는 모든 개체와 매개변수를 저장합니다. \"Spine Json 내보내기\"는 표시된 입자 시스템만 내보내고 충돌, 추적, 추적 및 하위 방사체 결과를 독립적인 애니메이션 데이터로 굽습니다."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "루프 연속 모드에 들어가면 입자 추적 설정이 무효화되고 기본값으로 복원됩니다. 계속하다?",
|
||||
"confirmLoopFollowEnter": "루프 연속 모드에 들어가면 입자 추적 설정이 무효화되어 기본값으로 복원되고 웜업 프레임도 적용되지 않습니다. 계속할까요?",
|
||||
"confirmLoopFollowExit": "루프 지속 모드를 종료한 후 입자 추적 설정이 무효화되고 기본값으로 복원됩니다. 계속하다?",
|
||||
"invalidJson": "구성 파일이 유효하지 않습니다. JSON",
|
||||
"invalidConfigFormat": "SpineParticle 편집자 프로필이 아닙니다.",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "Rotação",
|
||||
"scaleX": "Escala X",
|
||||
"scaleY": "Escala Y",
|
||||
"warmupFrames": "Quadros de aquecimento",
|
||||
"seed": "Semente aleatória",
|
||||
"seedTitle": "Controle todos os atributos aleatórios, seleção de recursos e modificador de amostragem aleatória no sistema de partículas",
|
||||
"randomizeSeed": "Gere uma nova semente aleatória global",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "“Salvar configuração” salva todos os objetos e parâmetros que podem ser editados; \"Exportar Spine Json\" exporta apenas o sistema de partículas exibido e transforma os resultados de colisão, seguimento, rastreamento e subemissor em dados de animação independentes."
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "Depois de entrar no modo de loop contínuo, as configurações seguintes de partículas serão inválidas e restauradas para o valor padrão. Continuar?",
|
||||
"confirmLoopFollowEnter": "Depois de entrar no modo de loop contínuo, o rastreamento de partículas será desativado, suas configurações serão restauradas e os quadros de aquecimento serão ignorados. Continuar?",
|
||||
"confirmLoopFollowExit": "Depois de sair do modo de persistência de loop, as configurações a seguir serão inválidas e restauradas para seus valores padrão. Continuar?",
|
||||
"invalidJson": "O arquivo de configuração não é válido JSON",
|
||||
"invalidConfigFormat": "Não é o perfil do editor SpineParticle",
|
||||
|
||||
@@ -499,6 +499,7 @@
|
||||
"rotation": "旋转",
|
||||
"scaleX": "缩放X",
|
||||
"scaleY": "缩放Y",
|
||||
"warmupFrames": "暖场帧数",
|
||||
"seed": "随机种子",
|
||||
"seedTitle": "控制粒子系统内全部随机属性、资源选择与修改器随机采样",
|
||||
"randomizeSeed": "生成新的全局随机种子",
|
||||
@@ -834,7 +835,7 @@
|
||||
"differenceDesc": "“保存配置”保存全部可继续编辑的对象和参数;“导出 Spine Json”只导出显示中的粒子系统,并把碰撞、跟随、拖尾和子发射器结果烘焙为独立动画数据。"
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "进入循环持续模式后,粒子跟随设置会失效并恢复默认值。是否继续?",
|
||||
"confirmLoopFollowEnter": "进入循环持续模式后,粒子跟随设置会失效并恢复默认值,且暖场帧数失效。是否继续?",
|
||||
"confirmLoopFollowExit": "退出循环持续模式后,粒子跟随设置会失效并恢复默认值。是否继续?",
|
||||
"invalidJson": "配置文件不是有效的 JSON",
|
||||
"invalidConfigFormat": "不是 SpineParticle 编辑器配置文件",
|
||||
|
||||
@@ -487,6 +487,7 @@
|
||||
"rotation": "旋轉",
|
||||
"scaleX": "X 縮放",
|
||||
"scaleY": "Y 縮放",
|
||||
"warmupFrames": "暖場幀數",
|
||||
"seed": "隨機種子",
|
||||
"seedTitle": "控製粒子系統內全部隨機屬性、資源選擇與修改器隨機取樣",
|
||||
"randomizeSeed": "產生新的全域隨機種子",
|
||||
@@ -811,7 +812,7 @@
|
||||
"differenceDesc": "「儲存配置」儲存全部可繼續編輯的物件和參數;「匯出 Spine Json」只匯出顯示中的粒子系統,並把碰撞、跟隨、拖尾和子發射器結果烘焙為獨立動畫資料。"
|
||||
},
|
||||
"errors": {
|
||||
"confirmLoopFollowEnter": "進入循環持續模式後,粒子跟隨設定會失效並恢復預設值。是否繼續?",
|
||||
"confirmLoopFollowEnter": "進入循環持續模式後,粒子跟隨設定會失效並恢復預設值,且暖場幀數失效。是否繼續?",
|
||||
"confirmLoopFollowExit": "退出循環持續模式後,粒子跟隨設定會失效並恢復預設值。是否繼續?",
|
||||
"invalidJson": "設定檔不是有效的 JSON",
|
||||
"invalidConfigFormat": "不是 SpineParticle 編輯器設定檔",
|
||||
|
||||
+63
-6
@@ -204,12 +204,47 @@ function syncEmitters() {
|
||||
emitterSync.sync(store, world, dotTex, trailTex, spineRuntime)
|
||||
}
|
||||
|
||||
/** 事件互相连接的粒子系统共享最长暖场帧数,保证子发射器也能从父事件产生。 */
|
||||
function warmupFramesBySystem() {
|
||||
const byId = new Map(store.systems.map((system) => [system.id, system]))
|
||||
const subEmitterIds = collectSubEmitterIds(store.systems)
|
||||
const neighbors = new Map(store.systems.map((system) => [system.id, new Set<number>()]))
|
||||
for (const system of store.systems) {
|
||||
if (system.config.eventEnabled === false) continue
|
||||
for (const rule of system.config.events || []) {
|
||||
if (!byId.has(rule.targetSystemId) || rule.targetSystemId === system.id) continue
|
||||
neighbors.get(system.id)!.add(rule.targetSystemId)
|
||||
neighbors.get(rule.targetSystemId)!.add(system.id)
|
||||
}
|
||||
}
|
||||
const result = new Map<number, number>()
|
||||
for (const system of store.systems) {
|
||||
if (result.has(system.id)) continue
|
||||
const component: number[] = []
|
||||
const pending = [system.id]
|
||||
while (pending.length) {
|
||||
const id = pending.pop()!
|
||||
if (component.includes(id)) continue
|
||||
component.push(id)
|
||||
pending.push(...neighbors.get(id)!)
|
||||
}
|
||||
const frames = Math.max(0, ...component.map((id) => {
|
||||
if (subEmitterIds.has(id)) return 0
|
||||
const config = byId.get(id)?.config
|
||||
if (config?.mode === 'stream' && config.streamBehavior === 'loop') return 0
|
||||
return Math.floor(Number(config?.warmupFrames) || 0)
|
||||
}))
|
||||
for (const id of component) result.set(id, frames)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步烘焙一个系统的整段帧缓存。
|
||||
* 与逐帧录制结果一致(同样的固定步长、同样的随机序列),因此不会改变预览内容,
|
||||
* 只是把"参数变动时就该生成好的烘焙结果"补齐。
|
||||
*/
|
||||
function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps: number, totalFrames: number) {
|
||||
function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps: number, totalFrames: number, warmupFrames: number) {
|
||||
const frameCount = Math.max(2, Math.floor(totalFrames) || 2)
|
||||
// 循环持续本来就是一次性烘焙,直接复用同一套逻辑。
|
||||
if (system.config.mode === 'stream' && system.config.streamBehavior === 'loop') {
|
||||
@@ -218,10 +253,12 @@ function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps:
|
||||
}
|
||||
emitter.reset()
|
||||
const frameDuration = 1 / Math.max(1, fps)
|
||||
emitter.warmup(warmupFrames, frameDuration)
|
||||
const frames: ParticleState[][] = []
|
||||
for (let frame = 0; frame < frameCount; frame++) {
|
||||
// 与逐帧录制保持一致:第 0 帧保持空白,避免正式播放前提前发射。
|
||||
if (frame === 0) { emitter.apply([]); frames.push([]) } else frames.push(emitter.update(frameDuration))
|
||||
// 第 0 帧使用暖场后的状态;未设置暖场时仍保持从空白开始。
|
||||
if (frame === 0) frames.push(warmupFrames > 0 ? emitter.capture() : [])
|
||||
else frames.push(emitter.update(frameDuration))
|
||||
}
|
||||
system.frames = frames
|
||||
}
|
||||
@@ -233,6 +270,7 @@ function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps:
|
||||
function ensureFramesBaked(fps: number) {
|
||||
const totalFrames = Math.max(2, Math.floor(store.timeline.totalFrames) || 2)
|
||||
const subEmitterIds = collectSubEmitterIds(store.systems)
|
||||
const warmupFrames = warmupFramesBySystem()
|
||||
// 烘焙预热会大量出生/死亡粒子,期间暂停跨系统事件,避免注入预热粒子。
|
||||
emitterSync.suspendEvents(true)
|
||||
try {
|
||||
@@ -242,7 +280,7 @@ function ensureFramesBaked(fps: number) {
|
||||
if ((system.frames?.length || 0) >= totalFrames) continue
|
||||
const emitter = emitterSync.emitters.get(system.id)
|
||||
if (!emitter) continue
|
||||
bakeSystemFrames(system as unknown as ParticleSystem, emitter, fps, totalFrames)
|
||||
bakeSystemFrames(system as unknown as ParticleSystem, emitter, fps, totalFrames, warmupFrames.get(system.id) || 0)
|
||||
}
|
||||
} finally {
|
||||
emitterSync.suspendEvents(false)
|
||||
@@ -398,6 +436,7 @@ function loop() {
|
||||
displayFrame = tl.frame
|
||||
collected.length = 0
|
||||
total = 0
|
||||
const warmedSystems = new Set<number>()
|
||||
if (!tl.recorded) {
|
||||
// 录制阶段:固定步长模拟并记录当前帧。
|
||||
emitterSync.suspendEvents(true)
|
||||
@@ -421,14 +460,32 @@ function loop() {
|
||||
} finally {
|
||||
emitterSync.suspendEvents(false)
|
||||
}
|
||||
if (tl.frame === 0) {
|
||||
const warmupFrames = warmupFramesBySystem()
|
||||
const targets = store.systems.flatMap((system) => {
|
||||
const emitter = emitterSync.emitters.get(system.id)
|
||||
const isLoopStream = system.config.mode === 'stream' && system.config.streamBehavior === 'loop'
|
||||
const frames = warmupFrames.get(system.id) || 0
|
||||
return emitter && !isLoopStream && frames > 0 ? [{ system, emitter, frames }] : []
|
||||
})
|
||||
const lastFrame = Math.max(0, ...targets.map(({ frames }) => frames))
|
||||
for (let warmupFrame = 0; warmupFrame < lastFrame; warmupFrame++) {
|
||||
for (const target of targets) {
|
||||
if (warmupFrame < target.frames) target.emitter.update(frameDuration)
|
||||
}
|
||||
}
|
||||
for (const { system } of targets) warmedSystems.add(system.id)
|
||||
}
|
||||
for (const [systemId, em] of emitterSync.emitters) {
|
||||
const system = store.systems.find((item) => item.id === systemId)
|
||||
const isLoopStream = system?.config.mode === 'stream' && system.config.streamBehavior === 'loop'
|
||||
const loopFrame = isLoopStream
|
||||
? system.frames?.[tl.frame]
|
||||
: undefined
|
||||
const forceEmptyInitialFrame = tl.frame === 0 && !isLoopStream
|
||||
const states = forceEmptyInitialFrame ? [] : (loopFrame || em.update(frameDuration))
|
||||
const forceEmptyInitialFrame = tl.frame === 0 && !isLoopStream && !warmedSystems.has(systemId)
|
||||
const states = forceEmptyInitialFrame
|
||||
? []
|
||||
: loopFrame || (warmedSystems.has(systemId) ? em.capture() : em.update(frameDuration))
|
||||
if (forceEmptyInitialFrame) em.apply([])
|
||||
else if (loopFrame) em.apply(loopFrame)
|
||||
if (system?.visible !== false) {
|
||||
|
||||
@@ -86,6 +86,24 @@
|
||||
<NumSlider :label="$t('scene.rotation')" :min="-360" :max="360" :step="1" v-model="sys.config.rootRotation" />
|
||||
<NumSlider :label="$t('scene.scaleX')" :min="0.05" :max="10" :step="0.05" v-model="sys.config.rootScaleX" />
|
||||
<NumSlider :label="$t('scene.scaleY')" :min="0.05" :max="10" :step="0.05" v-model="sys.config.rootScaleY" />
|
||||
<div class="seed-row scene-warmup">
|
||||
<label for="system-warmup">{{ $t('scene.warmupFrames') }}</label>
|
||||
<div class="scene-number-unit" :class="{ disabled: warmupDisabled }">
|
||||
<input
|
||||
id="system-warmup"
|
||||
class="seed-input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
:disabled="warmupDisabled"
|
||||
:value="warmupFramesText"
|
||||
@input="warmupFramesText = ($event.target as HTMLInputElement).value"
|
||||
@change="commitWarmupFrames"
|
||||
@blur="commitWarmupFrames"
|
||||
/>
|
||||
<span>f</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="seed-row scene-seed">
|
||||
<label for="system-seed">{{ $t('scene.seed') }}</label>
|
||||
<input id="system-seed" class="seed-input" type="number" min="0" step="1" v-model.number="sys.config.seed" :title="$t('scene.seedTitle')" />
|
||||
@@ -184,18 +202,37 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import NumSlider from '../NumSlider.vue'
|
||||
import CurveEditor from '../CurveEditor.vue'
|
||||
import ParticleAttributeControl from '../ParticleAttributeControl.vue'
|
||||
import ColorGradientEditor from '../ColorGradientEditor.vue'
|
||||
import ImageResourceCard from '../ImageResourceCard.vue'
|
||||
import SpineObjectPanel from '../../spine/SpineObjectPanel.vue'
|
||||
import { collectSubEmitterIds } from '../../core/subEmitterIds'
|
||||
import { useParticlePanelContext } from './particlePanelContext'
|
||||
const { store, resolvedCurveEditorMode, sys, activeObjectType, activeCollider, activePath, activeSpine, canCopyObject, collapsed, toggle, sectionOpen, addSys, addCollider, addPath, addSpine, copySelectedObject, applyPathPreset, clearPathAnchors, randomizeSeed, LINEAR_CURVE } = useParticlePanelContext()
|
||||
|
||||
type DisplaySystem = { system: (typeof store.systems)[number]; depth: number }
|
||||
|
||||
const warmupDisabled = computed(() => !!sys.value && (
|
||||
collectSubEmitterIds(store.systems).has(sys.value.id)
|
||||
|| (sys.value.config.mode === 'stream' && sys.value.config.streamBehavior === 'loop')
|
||||
))
|
||||
const warmupFramesText = ref('0')
|
||||
watch(
|
||||
() => [sys.value?.id, sys.value?.config.warmupFrames] as const,
|
||||
() => { warmupFramesText.value = String(sys.value?.config.warmupFrames ?? 0) },
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function commitWarmupFrames() {
|
||||
if (!sys.value || warmupDisabled.value) return
|
||||
const frames = Math.max(0, Math.floor(Number(warmupFramesText.value) || 0))
|
||||
warmupFramesText.value = String(frames)
|
||||
sys.value.config.warmupFrames = frames
|
||||
}
|
||||
|
||||
/** 子发射器嵌在父系统下方显示,方便直接选中;移除引用后自动回到普通行。 */
|
||||
const displaySystems = computed<DisplaySystem[]>(() => {
|
||||
const byId = new Map(store.systems.map((system) => [system.id, system]))
|
||||
@@ -314,5 +351,11 @@ const displaySystems = computed<DisplaySystem[]>(() => {
|
||||
.seed-random { width: 34px; height: 30px; border: 1px solid #3a4860; border-radius: 6px; background: #2c3950; color: #d7def0; cursor: pointer; }
|
||||
.seed-random:hover { background: #394964; }
|
||||
.scene-seed { margin-top: 10px; }
|
||||
.scene-warmup { margin-top: 10px; }
|
||||
.scene-number-unit { min-width: 0; flex: 1; display: flex; align-items: center; border: 1px solid #344158; border-radius: 6px; background: #0f1625; }
|
||||
.scene-number-unit:focus-within { border-color: #625ff1; }
|
||||
.scene-number-unit .seed-input { border: 0; background: transparent; }
|
||||
.scene-number-unit > span { padding-right: 8px; color: #7e8aa3; font-size: 12px; }
|
||||
.scene-number-unit.disabled { opacity: .5; }
|
||||
.seed-hint { margin: 5px 0 1px 72px; color: #657188; font-size: 9px; line-height: 1.45; }
|
||||
</style>
|
||||
Reference in new issue
Block a user