diff --git a/assets/GameplayIntro/compressed.meta b/assets/GameplayIntro/compressed.meta deleted file mode 100644 index 5bdc151..0000000 --- a/assets/GameplayIntro/compressed.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "34338d87-8ed5-4847-bdb5-cc0e0085901b", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/Script/CloudRiseView.ts b/assets/Script/CloudRiseView.ts index 5276879..6b2fb71 100644 --- a/assets/Script/CloudRiseView.ts +++ b/assets/Script/CloudRiseView.ts @@ -137,7 +137,6 @@ export default class CloudRiseView extends cc.Component { this.entry.active = !this.map && CloudRiseService.homeEntryVisible(ActivityScheduleService.active("cloudRise"), ActivityScheduleService.hasSchedule(), ActivityScheduleService.now()); this.updateEntryCountdown(); - this.layoutEntry(); if (!this.map && this.displayedCoin !== cc.fx.GameConfig.GM_INFO.coin) { this.displayedCoin = cc.fx.GameConfig.GM_INFO.coin; const home: any = this.host.getComponent("JiaZai"); @@ -317,14 +316,6 @@ export default class CloudRiseView extends cc.Component { this.entryCountdown.string = [Math.floor(seconds / 3600), Math.floor(seconds / 60) % 60, seconds % 60] .map(pad).join(":"); } - private layoutEntry(): void { - if (this.map || !this.entry.active || !this.entry.parent) return; - const top = this.entry.parent, first = top.getChildByName("shop"); - if (!first) return; - const visible = ["shop", "yicon", "redeemBtn", "passBtn", "jungle", "xinshou"] - .map(name => top.getChildByName(name)).filter(node => node && node.active); - this.entry.setPosition(first.x, visible.length ? Math.min(...visible.map(node => node.y)) - 220 : first.y); - } onDestroy(): void { cc.game.off(cc.game.EVENT_SHOW, this.onShow, this); this.unschedule(this.tick); diff --git a/assets/Script/HomeActivityEntryLayout.ts b/assets/Script/HomeActivityEntryLayout.ts new file mode 100644 index 0000000..8f3f99e --- /dev/null +++ b/assets/Script/HomeActivityEntryLayout.ts @@ -0,0 +1,52 @@ +/** One owner for home activity positions; visibility is still decided by each activity. */ +export default class HomeActivityEntryLayout { + private static signatures = new WeakMap(); + private static leftNames = ["shop", "yicon", "redeemBtn", "passBtn", "jungle", "xinshou", "chengxu", "cloudRise"]; + private static rightNames = ["rank", "gacha", "day", "hammer"]; + + static refresh(top: cc.Node): void { + if (!top || !top.parent) return; + const first = top.getChildByName("shop"), right = top.getChildByName("rank"); + const footer = top.parent.getChildByName("shezhiBtn"); + if (!first || !right || !footer) return; + const left = this.leftNames.map(name => top.getChildByName(name)).filter(node => !!node); + const fixedRight = this.rightNames.map(name => top.getChildByName(name)).filter(node => !!node); + // Adaptation sets the same steady scale on Load and the side buttons. Ignore temporary button press zoom. + const scale = Math.abs(top.parent.scaleY) || 1; + const signature = JSON.stringify([first.x, first.y, right.x, top.y, scale, footer.y, footer.height, + ...left.map(n => [n.name, n.active, n.width, n.height]), + ...fixedRight.map(n => [n.name, n.active, n.x, n.y, n.width, n.height])]); + if (this.signatures.get(top) === signature) return; + this.signatures.set(top, signature); + + const footerTop = footer.y + footer.height * (1 - footer.anchorY) * scale - top.y + 16; + let leftY = first.y + 220, leftBottom = Infinity; + const occupiedRight = fixedRight.filter(n => n.active); + let rightY = occupiedRight.length ? Math.min(...occupiedRight.map(n => n.y)) : first.y + 220; + let rightBottom = occupiedRight.length + ? Math.min(...occupiedRight.map(n => n.y + this.extent(n).bottom * scale)) : Infinity; + for (const node of left.filter(n => n.active)) { + const extent = this.extent(node), above = extent.top * scale, below = extent.bottom * scale; + const y = Math.min(leftY - 220, leftBottom - 16 - above); + if (y + below >= footerTop) { + node.setPosition(first.x, y); leftY = y; leftBottom = y + below; + } else { + // Use the free lower right slots instead of covering the bottom-left Settings button. + const nextY = Math.min(rightY - 220, rightBottom - 16 - above); + node.setPosition(right.x, nextY); rightY = nextY; rightBottom = nextY + below; + } + } + } + + /** Include clocks and labels below icons so the next entry cannot cover them. */ + private static extent(node: cc.Node): { top: number; bottom: number } { + let top = node.height * (1 - node.anchorY), bottom = -node.height * node.anchorY; + for (const child of node.children) { + if (!child.active) continue; + const bounds = this.extent(child), scale = Math.abs(child.scaleY); + top = Math.max(top, child.y + bounds.top * scale); + bottom = Math.min(bottom, child.y + bounds.bottom * scale); + } + return { top, bottom }; + } +} diff --git a/assets/Script/HomeActivityEntryLayout.ts.meta b/assets/Script/HomeActivityEntryLayout.ts.meta new file mode 100644 index 0000000..2af4fcd --- /dev/null +++ b/assets/Script/HomeActivityEntryLayout.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "79848e7c-3bbb-4cff-b60b-ec1748b22be4", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/assets/Script/HomeActivitySchedule.ts b/assets/Script/HomeActivitySchedule.ts index 51fef9b..1eb5ae3 100644 --- a/assets/Script/HomeActivitySchedule.ts +++ b/assets/Script/HomeActivitySchedule.ts @@ -1,4 +1,5 @@ import ActivityScheduleService from "./module/Config/ActivityScheduleService"; +import HomeActivityEntryLayout from "./HomeActivityEntryLayout"; const { ccclass } = cc._decorator; @@ -6,6 +7,10 @@ const { ccclass } = cc._decorator; @ccclass export default class HomeActivitySchedule extends cc.Component { private visibility = 0; + lateUpdate(): void { + const load = this.node.getChildByName("Load"); + HomeActivityEntryLayout.refresh(load && load.getChildByName("Top")); + } onEnable(): void { cc.game.on(cc.game.EVENT_SHOW, this.onShow, this); cc.game.on(cc.game.EVENT_HIDE, this.onHide, this); diff --git a/assets/Script/JiaZai.ts b/assets/Script/JiaZai.ts index e866c29..d97d891 100644 --- a/assets/Script/JiaZai.ts +++ b/assets/Script/JiaZai.ts @@ -86,10 +86,8 @@ export default class JiaZai extends cc.Component { private jungleSettlementRequestId: number = 0; private jungleSettlementRequestTimeout: any = null; private pendingJungleSettlement: any = null; - private starterPackDefaultPosition: cc.Vec2 = null; private starterPackOpening = false; private starterPackChecking = false; - private benefitsEntryOffset: cc.Vec2 = null; // 缓存 reward 预制体 private static cachedRewardPrefab: cc.Prefab | null = null; @@ -1397,6 +1395,9 @@ export default class JiaZai extends cc.Component { "shop", "yicon", "passBtn", + "chengxu", + "cloudRise", + "redeemBtn", "xinshou", "jungle", "avatar", @@ -3144,37 +3145,8 @@ export default class JiaZai extends cc.Component { return top && top.getChildByName("xinshou"); } - /** 新手礼包补位时,福利入口保持场景中配置的相对间距。 */ + /** Visibility is owned here; HomeActivitySchedule arranges all entries before rendering. */ private refreshHomeActivityEntryLayout(): void { - let starterPackButton = this.getStarterPackHomeButton(); - let jungleButton = this.getJungleTreasureHomeButton(); - if (!starterPackButton || !jungleButton) { - return; - } - - if (!this.starterPackDefaultPosition) { - this.starterPackDefaultPosition = cc.v2(starterPackButton.x, starterPackButton.y); - } - - const benefitsButton = starterPackButton.parent.getChildByName("chengxu"); - if (benefitsButton && !this.benefitsEntryOffset) { - this.benefitsEntryOffset = cc.v2( - benefitsButton.x - this.starterPackDefaultPosition.x, - benefitsButton.y - this.starterPackDefaultPosition.y, - ); - } - - let targetPosition = starterPackButton.active && !jungleButton.active - ? cc.v2(jungleButton.x, jungleButton.y) - : this.starterPackDefaultPosition; - starterPackButton.setPosition(targetPosition); - if (benefitsButton && this.benefitsEntryOffset) { - // Hidden activities do not reserve slots above the benefits entry. - const benefitsPosition = starterPackButton.active - ? cc.v2(targetPosition.x + this.benefitsEntryOffset.x, targetPosition.y + this.benefitsEntryOffset.y) - : jungleButton.active ? this.starterPackDefaultPosition : cc.v2(jungleButton.x, jungleButton.y); - benefitsButton.setPosition(benefitsPosition); - } MiniProgramBenefitsBridge.instance.refreshEntry(); } diff --git a/assets/UI/UI/start/startUI2.plist b/assets/UI/UI/start/startUI2.plist index 635fb58..925c3c2 100644 --- a/assets/UI/UI/start/startUI2.plist +++ b/assets/UI/UI/start/startUI2.plist @@ -24,7 +24,7 @@ offset {-1,-7} rotated - + sourceColorRect {{3,19},{196,180}} sourceSize @@ -63,7 +63,7 @@ offset {-1,-7} rotated - + sourceColorRect {{5,19},{192,180}} sourceSize @@ -103,7 +103,6 @@ {0,-3} rotated - sourceColorRect {{11,11},{182,188}} sourceSize @@ -116,7 +115,7 @@ offset {0,-7} rotated - + sourceColorRect {{4,19},{196,180}} sourceSize @@ -142,7 +141,7 @@ offset {0,0} rotated - + sourceColorRect {{0,0},{188,190}} sourceSize @@ -156,7 +155,6 @@ {0,0} rotated - sourceColorRect {{0,5},{204,194}} sourceSize @@ -182,7 +180,7 @@ offset {-5,-8} rotated - + sourceColorRect {{2,21},{190,178}} sourceSize @@ -196,7 +194,6 @@ {0,0} rotated - sourceColorRect {{0,0},{146,160}} sourceSize @@ -209,7 +206,7 @@ offset {0,-8} rotated - + sourceColorRect {{11,21},{182,178}} sourceSize diff --git a/assets/UI/UI/start/startUI2.plist.meta b/assets/UI/UI/start/startUI2.plist.meta index 1e0cabe..7b669a3 100644 --- a/assets/UI/UI/start/startUI2.plist.meta +++ b/assets/UI/UI/start/startUI2.plist.meta @@ -39,7 +39,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": false, + "rotated": true, "offsetX": -1, "offsetY": -7, "trimX": 198, @@ -108,7 +108,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": false, + "rotated": true, "offsetX": -1, "offsetY": -7, "trimX": 380, @@ -178,7 +178,6 @@ "trimType": "auto", "trimThreshold": 1, "rotated": false, - "rotated": false, "offsetX": 0, "offsetY": -3, "trimX": 390, @@ -201,7 +200,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": true, + "rotated": false, "offsetX": 0, "offsetY": -7, "trimX": 192, @@ -247,7 +246,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": true, + "rotated": false, "offsetX": 0, "offsetY": 0, "trimX": 2, @@ -271,7 +270,6 @@ "trimType": "auto", "trimThreshold": 1, "rotated": true, - "rotated": true, "offsetX": 0, "offsetY": 0, "trimX": 2, @@ -317,7 +315,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": false, + "rotated": true, "offsetX": -5, "offsetY": -8, "trimX": 562, @@ -341,7 +339,6 @@ "trimType": "auto", "trimThreshold": 1, "rotated": false, - "rotated": false, "offsetX": 0, "offsetY": 0, "trimX": 342, @@ -364,7 +361,7 @@ "rawTextureUuid": "b45f4eaf-4250-449a-81ea-a7ca1e534a02", "trimType": "auto", "trimThreshold": 1, - "rotated": false, + "rotated": true, "offsetX": 0, "offsetY": -8, "trimX": 825, diff --git a/assets/UI/UI/start/startUI2.png b/assets/UI/UI/start/startUI2.png index 7d7ce02..02b3428 100644 Binary files a/assets/UI/UI/start/startUI2.png and b/assets/UI/UI/start/startUI2.png differ diff --git a/assets/gacha_bundle/compressed.meta b/assets/gacha_bundle/compressed.meta deleted file mode 100644 index 265dafb..0000000 --- a/assets/gacha_bundle/compressed.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "4970b759-7a17-4559-84a3-9bbe73489173", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/gacha_bundle/img/cat.meta b/assets/gacha_bundle/img/cat.meta deleted file mode 100644 index 1d401d6..0000000 --- a/assets/gacha_bundle/img/cat.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "e159bf60-8e51-4dca-85dd-8bec167c9036", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/libs/dn-sdk-minigame.meta b/assets/libs/dn-sdk-minigame.meta deleted file mode 100644 index 2f00906..0000000 --- a/assets/libs/dn-sdk-minigame.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "dbde4bfd-0b20-45aa-8c9c-139722aabc64", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/passCheck/script.meta b/assets/passCheck/script.meta deleted file mode 100644 index 0ec56e9..0000000 --- a/assets/passCheck/script.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "7a2e5485-be73-4ed1-af4a-6a00ab845cce", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/propWindow/compressed.meta b/assets/propWindow/compressed.meta deleted file mode 100644 index 0f9bc47..0000000 --- a/assets/propWindow/compressed.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "a78cb4f0-a528-4a3e-9470-01100e4c24d1", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/res/shop.meta b/assets/res/shop.meta deleted file mode 100644 index dda2120..0000000 --- a/assets/res/shop.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "d0d45606-9a70-4a8b-a213-540cc0100e9d", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/assets/win/compressed.meta b/assets/win/compressed.meta deleted file mode 100644 index 1b51139..0000000 --- a/assets/win/compressed.meta +++ /dev/null @@ -1,13 +0,0 @@ -{ - "ver": "1.1.3", - "uuid": "4572f582-ceb9-419d-b453-44d48b3ac622", - "importer": "folder", - "isBundle": false, - "bundleName": "", - "priority": 1, - "compressionType": {}, - "optimizeHotUpdate": {}, - "inlineSpriteFrames": {}, - "isRemoteBundle": {}, - "subMetas": {} -} \ No newline at end of file diff --git a/settings/project.json b/settings/project.json index 839d3b7..5b07db2 100644 --- a/settings/project.json +++ b/settings/project.json @@ -57,4 +57,4 @@ "cloud-function" ], "assets-sort-type": "name" -} \ No newline at end of file +} diff --git a/tools/test-activity-schedule.cjs b/tools/test-activity-schedule.cjs index 7ee8e42..dd8de9a 100644 --- a/tools/test-activity-schedule.cjs +++ b/tools/test-activity-schedule.cjs @@ -42,7 +42,7 @@ function fixture(storage = new Map()) { return {service, cc, storage, requests, events, setNow: n => now = n, setEnv: e => environment = e, failStorage: () => storageFails = true, loadHome: () => load('assets/Script/HomeActivitySchedule.ts', cc, - {'./module/Config/ActivityScheduleService': {default: service}}, () => now), + {'./module/Config/ActivityScheduleService': {default: service}, './HomeActivityEntryLayout': {default: {refresh(){}}}}, () => now), emit: name => {const event = events.get(name); event.fn.call(event.target);}}; } const flush = async () => {for (let i = 0; i < 12; i++) await Promise.resolve();}; diff --git a/tools/test-home-activity-entry-layout.cjs b/tools/test-home-activity-entry-layout.cjs new file mode 100644 index 0000000..f627be7 --- /dev/null +++ b/tools/test-home-activity-entry-layout.cjs @@ -0,0 +1,65 @@ +const test=require('node:test'),assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm'),ts=require('typescript'); +const left=['shop','yicon','redeemBtn','passBtn','jungle','xinshou','chengxu','cloudRise']; +const right=['rank','gacha','day','hammer']; +function fixture(){ + const exports={}; + vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/HomeActivityEntryLayout.ts','utf8'),{ + compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017}}).outputText,{exports}); + const a=JSON.parse(fs.readFileSync('assets/Scene/HomeScene.fire')),nodes=new Map();let writes=0; + for(let i=0;in.name===name);},setPosition(x,y){this.x=x;this.y=y;writes++;}}); + } + for(const [i,n]of nodes){n.children=a[i]._children.map(r=>nodes.get(r.__id__));n.parent=nodes.get(a[i]._parent?.__id__);} + const top=[...nodes.values()].find(n=>n.name==='Top'&&n.parent.name==='Load'); + const gift=top.getChildByName('xinshou'); + const frame=JSON.parse(fs.readFileSync('assets/UI/UI/start/startUI2.plist.meta')).subMetas['cloudRise.png']; + const cloud={...gift,name:'cloudRise',width:frame.rawWidth,height:frame.rawHeight,children:gift.children.filter(n=>['New Sprite','time'].includes(n.name))}; + top.children.push(cloud); + const layout=exports.default; + function box(n){const b=layout.extent(n),scale=top.parent.scaleY;return {top:n.y+b.top*scale,bottom:n.y+b.bottom*scale};} + function check(){ + const all=[...left,...right].map(n=>top.getChildByName(n)).filter(n=>n.active); + for(let i=0;i300)continue; + const a=box(p),b=box(q);assert(a.bottom>=b.top+15.99||b.bottom>=a.top+15.99,`${p.name} overlaps ${q.name}`); + } + const footer=top.parent.getChildByName('shezhiBtn'),bound=footer.y+footer.height*(1-footer.anchorY)*top.parent.scaleY-top.y; + for(const n of all)assert(box(n).bottom>=bound+15.99,`${n.name} covers footer: ${box(n).bottom} < ${bound+16}`); + } + return {layout,top,check,get writes(){return writes;}}; +} +test('every visibility combination fits without overlap, including both columns and bottom controls',()=>{ + const f=fixture(); + for(const scale of [1,0.85]){ + f.top.parent.scaleY=scale; + for(let mask=0;mask<4096;mask++){ + [...left,...right].forEach((name,i)=>f.top.getChildByName(name).active=!!(mask&(1<{ + const f=fixture();[...left,...right].forEach(name=>f.top.getChildByName(name).active=name!=='redeemBtn'); + f.layout.refresh(f.top);f.check(); + const benefit=f.top.getChildByName('chengxu'),cloud=f.top.getChildByName('cloudRise'); + assert.notDeepEqual([benefit.x,benefit.y],[cloud.x,cloud.y]); + assert.equal(cloud.x,f.top.getChildByName('rank').x,'overflow uses the free right slot'); + const old={x:benefit.x,y:benefit.y};benefit.active=false;f.layout.refresh(f.top);f.check(); + // The countdown makes CloudRise taller than benefits; only reclaim a slot if the whole entry fits. + f.top.getChildByName('xinshou').active=false;f.layout.refresh(f.top);f.check(); + assert.equal(cloud.x,old.x);assert(cloud.y>old.y); + const writes=f.writes;for(let i=0;i<20;i++)f.layout.refresh(f.top);assert.equal(f.writes,writes,'unchanged frames do not reposition buttons'); + benefit.active=true;f.layout.refresh(f.top);f.check(); +}); + +test('home lays out late in the frame after asynchronous entry visibility changes',()=>{ + const source=fs.readFileSync('assets/Script/HomeActivitySchedule.ts','utf8');let arranged; + const exports={},cc={Component:class{},_decorator:{ccclass:c=>c}}; + vm.runInNewContext(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2017,experimentalDecorators:true}}).outputText, + {exports,cc,require:name=>({default:name.endsWith('HomeActivityEntryLayout')?{refresh:top=>arranged=top}:{}})}); + const f=fixture(),home=new exports.default();home.node={getChildByName:()=>f.top.parent};home.lateUpdate();assert.equal(arranged,f.top); + assert(!fs.readFileSync('assets/Script/CloudRiseView.ts','utf8').includes('private layoutEntry()'),'CloudRise no longer writes competing positions'); + assert(!fs.readFileSync('assets/Script/JiaZai.ts','utf8').includes('benefitsEntryOffset'),'benefits no longer follows a separate layout'); +});