优化0827-1

main
chenanghui 2 months ago
parent 3939284a0f
commit 4a76039ec0
  1. 564
      assets/bundles/first/config/Chapter.json
  2. 14
      assets/bundles/first/config/Const_weight.json
  3. 560
      assets/bundles/first/config/Monster2.json
  4. 9
      assets/bundles/first/config/const.json
  5. 4
      assets/bundles/tetriCards/prefab/Tetris_heal_1.prefab
  6. 9
      assets/mx/module/res/ResourcesManager.ts
  7. 48
      assets/mx/module/ui/UIManager.ts
  8. 1
      assets/script/GameLaunch.ts
  9. 10
      assets/script/diagnostics/PfDiagnostics.ts
  10. 47
      assets/script/game/BattleResLoader.ts
  11. 5
      assets/script/game/GameController.ts
  12. 6
      assets/script/game/HomeBattlePreload.ts
  13. 51
      assets/script/game/Physics2DGate.ts
  14. 9
      assets/script/game/Physics2DGate.ts.meta
  15. 1
      assets/script/game/SmartPreload.ts
  16. 40
      assets/script/interface/AutoTable.ts
  17. 3
      assets/script/jiujiuwoMap/jiujiuwoMap.ts
  18. 202
      assets/script/sdk/SDKManager.ts
  19. 2
      assets/script/sdk/config/XiaoXiaoFangkuaiZhen.ts
  20. 5
      assets/script/sdk/functions/InterstitialAd.ts
  21. 59
      assets/script/sdk/functions/RewardedVideoAd.ts
  22. 31
      assets/script/sdk/platform/MiniGamePlatform.ts
  23. 15
      assets/script/tetriCard/tetriAbility.ts
  24. 254
      assets/script/tetriCard/tetriCardItem.ts
  25. 64
      assets/script/tetriCard/tetriFloorNode.ts
  26. 47
      assets/script/tetriCard/tetriNode.ts
  27. 26
      assets/script/tetriCard/tetriWeaponCarrier.ts
  28. 89
      assets/script/tetriMap/tetriMap.ts
  29. 66
      assets/script/ui/BattleGame/BattleCore.ts
  30. 36
      assets/script/ui/jiujiuwoyaGame/UIjiujiuwoyaGame.ts
  31. 209
      assets/script/ui/tetriGame/UItetriGame.ts
  32. 1
      bin/tt_xxfkz2.1.4.bin
  33. 1
      bin/tt_xxfkz2.1.5.bin
  34. 4
      settings/v2/packages/information.json

File diff suppressed because it is too large Load Diff

@ -117,12 +117,12 @@
"id": 1102,
"desc": "主动刷新——刷新品质权重",
"constValue": 0,
"constString": "7,21,17,5"
"constString": "7,17,21,5"
},
{
"id": 1103,
"desc": "主动刷新——2个紫色技能保底阈值",
"constValue": 5,
"constValue": 4,
"constString": ""
},
{
@ -135,7 +135,7 @@
"id": 1105,
"desc": "主动刷新——紫色脸黑控制,X次开始加权,每次加权值y,保底值z",
"constValue": 0,
"constString": "4,8,8"
"constString": "3,8,8"
},
{
"id": 1106,
@ -153,7 +153,7 @@
"id": 1108,
"desc": "主动刷新——橙色脸黑控制,X次开始加权,每次加权值y,保底值z",
"constValue": 0,
"constString": "12,1,25"
"constString": "9,1,25"
},
{
"id": 1109,
@ -224,19 +224,19 @@
{
"id": 1208,
"desc": "波次结算银币",
"constValue": 70,
"constValue": 100,
"constString": ""
},
{
"id": 1209,
"desc": "波次预警提前时间20s(第1关)",
"constValue": 15,
"constValue": 20,
"constString": ""
},
{
"id": 1210,
"desc": "波次预警提前时间15s(大于1关)",
"constValue": 15,
"constValue": 20,
"constString": ""
},
{

@ -29,31 +29,33 @@
"attr25": 18,
"attr26": 15,
"attr27": 20,
"attr28": 84
"attr28": 84,
"attr29": 11,
"attr30": 11
},
{
"id": 2,
"attr0": 38,
"attr1": 32,
"attr2": 9,
"attr3": 21,
"attr4": 21,
"attr5": 21,
"attr6": 21,
"attr7": 21,
"attr8": 21,
"attr9": 21,
"attr10": 21,
"attr11": 21,
"attr12": 21,
"attr13": 21,
"attr14": 21,
"attr15": 21,
"attr16": 21,
"attr17": 21,
"attr18": 21,
"attr19": 21,
"attr20": 21,
"attr1": 44,
"attr2": 11,
"attr3": 26,
"attr4": 26,
"attr5": 27,
"attr6": 27,
"attr7": 27,
"attr8": 28,
"attr9": 28,
"attr10": 28,
"attr11": 29,
"attr12": 29,
"attr13": 29,
"attr14": 30,
"attr15": 30,
"attr16": 30,
"attr17": 31,
"attr18": 31,
"attr19": 32,
"attr20": 32,
"attr21": 37,
"attr22": 37,
"attr23": 37,
@ -61,31 +63,33 @@
"attr25": 37,
"attr26": 35,
"attr27": 40,
"attr28": 161
"attr28": 161,
"attr29": 21,
"attr30": 21
},
{
"id": 3,
"attr0": 94,
"attr1": 66,
"attr2": 26,
"attr3": 53,
"attr4": 53,
"attr5": 53,
"attr6": 53,
"attr7": 53,
"attr8": 53,
"attr9": 53,
"attr10": 53,
"attr11": 53,
"attr12": 53,
"attr13": 53,
"attr14": 53,
"attr15": 53,
"attr16": 53,
"attr17": 53,
"attr18": 53,
"attr19": 53,
"attr20": 53,
"attr1": 88,
"attr2": 35,
"attr3": 61,
"attr4": 62,
"attr5": 63,
"attr6": 64,
"attr7": 65,
"attr8": 66,
"attr9": 67,
"attr10": 68,
"attr11": 69,
"attr12": 70,
"attr13": 71,
"attr14": 72,
"attr15": 73,
"attr16": 74,
"attr17": 75,
"attr18": 77,
"attr19": 78,
"attr20": 79,
"attr21": 63,
"attr22": 63,
"attr23": 63,
@ -93,31 +97,33 @@
"attr25": 63,
"attr26": 71,
"attr27": 98,
"attr28": 179
"attr28": 179,
"attr29": 53,
"attr30": 53
},
{
"id": 4,
"attr0": 113,
"attr1": 76,
"attr2": 61,
"attr3": 76,
"attr4": 76,
"attr5": 76,
"attr6": 76,
"attr7": 76,
"attr8": 76,
"attr9": 76,
"attr10": 76,
"attr11": 76,
"attr12": 76,
"attr13": 76,
"attr14": 76,
"attr15": 76,
"attr16": 76,
"attr17": 76,
"attr18": 76,
"attr19": 76,
"attr20": 76,
"attr1": 58,
"attr2": 56,
"attr3": 47,
"attr4": 49,
"attr5": 50,
"attr6": 52,
"attr7": 54,
"attr8": 56,
"attr9": 58,
"attr10": 60,
"attr11": 62,
"attr12": 64,
"attr13": 67,
"attr14": 69,
"attr15": 71,
"attr16": 74,
"attr17": 77,
"attr18": 79,
"attr19": 82,
"attr20": 85,
"attr21": 74,
"attr22": 74,
"attr23": 74,
@ -125,31 +131,33 @@
"attr25": 74,
"attr26": 143,
"attr27": 178,
"attr28": 186
"attr28": 186,
"attr29": 76,
"attr30": 76
},
{
"id": 5,
"attr0": 225,
"attr1": 51,
"attr2": 53,
"attr3": 62,
"attr4": 62,
"attr5": 62,
"attr6": 62,
"attr7": 62,
"attr8": 62,
"attr9": 62,
"attr10": 62,
"attr11": 62,
"attr12": 71,
"attr13": 82,
"attr14": 94,
"attr15": 108,
"attr16": 124,
"attr17": 143,
"attr18": 164,
"attr19": 189,
"attr20": 217,
"attr1": 140,
"attr2": 65,
"attr3": 168,
"attr4": 170,
"attr5": 172,
"attr6": 175,
"attr7": 177,
"attr8": 179,
"attr9": 182,
"attr10": 184,
"attr11": 187,
"attr12": 189,
"attr13": 192,
"attr14": 194,
"attr15": 197,
"attr16": 199,
"attr17": 202,
"attr18": 205,
"attr19": 207,
"attr20": 210,
"attr21": 53,
"attr22": 53,
"attr23": 53,
@ -157,31 +165,33 @@
"attr25": 53,
"attr26": 240,
"attr27": 306,
"attr28": 400
"attr28": 400,
"attr29": 62,
"attr30": 82
},
{
"id": 6,
"attr0": 281,
"attr1": 168,
"attr2": 168,
"attr3": 174,
"attr4": 174,
"attr5": 174,
"attr6": 174,
"attr7": 174,
"attr8": 174,
"attr9": 174,
"attr10": 174,
"attr11": 174,
"attr12": 200,
"attr13": 230,
"attr14": 265,
"attr15": 305,
"attr16": 351,
"attr17": 404,
"attr18": 465,
"attr19": 535,
"attr20": 615,
"attr1": 198,
"attr2": 119,
"attr3": 245,
"attr4": 250,
"attr5": 255,
"attr6": 259,
"attr7": 264,
"attr8": 270,
"attr9": 275,
"attr10": 280,
"attr11": 285,
"attr12": 291,
"attr13": 297,
"attr14": 302,
"attr15": 308,
"attr16": 314,
"attr17": 320,
"attr18": 326,
"attr19": 333,
"attr20": 340,
"attr21": 130,
"attr22": 130,
"attr23": 130,
@ -189,31 +199,33 @@
"attr25": 130,
"attr26": 368,
"attr27": 1042,
"attr28": 502
"attr28": 502,
"attr29": 174,
"attr30": 230
},
{
"id": 7,
"attr0": 375,
"attr1": 331,
"attr2": 266,
"attr3": 302,
"attr4": 302,
"attr5": 302,
"attr6": 302,
"attr7": 302,
"attr8": 302,
"attr9": 302,
"attr10": 302,
"attr11": 302,
"attr12": 347,
"attr13": 399,
"attr14": 459,
"attr15": 528,
"attr16": 607,
"attr17": 698,
"attr18": 803,
"attr19": 923,
"attr20": 1061,
"attr1": 236,
"attr2": 180,
"attr3": 344,
"attr4": 352,
"attr5": 360,
"attr6": 368,
"attr7": 377,
"attr8": 385,
"attr9": 394,
"attr10": 403,
"attr11": 412,
"attr12": 422,
"attr13": 431,
"attr14": 441,
"attr15": 451,
"attr16": 461,
"attr17": 472,
"attr18": 483,
"attr19": 494,
"attr20": 505,
"attr21": 154,
"attr22": 154,
"attr23": 154,
@ -221,31 +233,33 @@
"attr25": 154,
"attr26": 547,
"attr27": 1544,
"attr28": 500
"attr28": 500,
"attr29": 302,
"attr30": 399
},
{
"id": 8,
"attr0": 450,
"attr1": 417,
"attr2": 428,
"attr3": 428,
"attr4": 428,
"attr5": 428,
"attr6": 428,
"attr7": 428,
"attr8": 428,
"attr9": 428,
"attr10": 492,
"attr11": 566,
"attr12": 651,
"attr13": 749,
"attr14": 861,
"attr15": 990,
"attr16": 1139,
"attr17": 1310,
"attr18": 1507,
"attr19": 1733,
"attr20": 1993,
"attr1": 236,
"attr2": 329,
"attr3": 244,
"attr4": 252,
"attr5": 261,
"attr6": 270,
"attr7": 279,
"attr8": 288,
"attr9": 298,
"attr10": 308,
"attr11": 319,
"attr12": 330,
"attr13": 341,
"attr14": 353,
"attr15": 365,
"attr16": 377,
"attr17": 390,
"attr18": 403,
"attr19": 417,
"attr20": 431,
"attr21": 165,
"attr22": 165,
"attr23": 165,
@ -253,31 +267,33 @@
"attr25": 165,
"attr26": 741,
"attr27": 2772,
"attr28": 780
"attr28": 780,
"attr29": 566,
"attr30": 749
},
{
"id": 9,
"attr0": 656,
"attr1": 495,
"attr2": 499,
"attr3": 499,
"attr4": 499,
"attr5": 499,
"attr6": 499,
"attr7": 499,
"attr8": 574,
"attr9": 660,
"attr10": 759,
"attr11": 873,
"attr12": 1004,
"attr13": 1155,
"attr14": 1328,
"attr15": 1527,
"attr16": 1756,
"attr17": 2019,
"attr18": 2322,
"attr19": 2670,
"attr20": 3071,
"attr1": 236,
"attr2": 515,
"attr3": 449,
"attr4": 477,
"attr5": 506,
"attr6": 537,
"attr7": 570,
"attr8": 605,
"attr9": 642,
"attr10": 682,
"attr11": 723,
"attr12": 768,
"attr13": 815,
"attr14": 865,
"attr15": 918,
"attr16": 975,
"attr17": 1034,
"attr18": 1098,
"attr19": 1165,
"attr20": 1237,
"attr21": 189,
"attr22": 189,
"attr23": 189,
@ -285,31 +301,33 @@
"attr25": 189,
"attr26": 849,
"attr27": 4450,
"attr28": 1186
"attr28": 1186,
"attr29": 873,
"attr30": 1155
},
{
"id": 10,
"attr0": 938,
"attr1": 404,
"attr2": 431,
"attr3": 308,
"attr4": 354,
"attr5": 407,
"attr6": 468,
"attr7": 538,
"attr8": 619,
"attr9": 712,
"attr10": 819,
"attr11": 942,
"attr12": 1083,
"attr13": 1245,
"attr14": 1432,
"attr15": 1647,
"attr16": 1894,
"attr17": 2178,
"attr18": 2505,
"attr19": 2881,
"attr20": 3313,
"attr1": 236,
"attr2": 755,
"attr3": 665,
"attr4": 723,
"attr5": 785,
"attr6": 853,
"attr7": 927,
"attr8": 1007,
"attr9": 1095,
"attr10": 1190,
"attr11": 1293,
"attr12": 1405,
"attr13": 1526,
"attr14": 1658,
"attr15": 1802,
"attr16": 1958,
"attr17": 2128,
"attr18": 2312,
"attr19": 2512,
"attr20": 2730,
"attr21": 118,
"attr22": 118,
"attr23": 118,
@ -317,31 +335,33 @@
"attr25": 125,
"attr26": 1160,
"attr27": 5281,
"attr28": 1193
"attr28": 1193,
"attr29": 942,
"attr30": 1245
},
{
"id": 11,
"attr0": 1296,
"attr1": 404,
"attr2": 643,
"attr3": 621,
"attr4": 714,
"attr5": 821,
"attr6": 944,
"attr7": 1086,
"attr8": 1249,
"attr9": 1436,
"attr10": 1651,
"attr11": 1899,
"attr12": 2184,
"attr13": 2512,
"attr14": 2889,
"attr15": 3322,
"attr16": 3820,
"attr17": 4393,
"attr18": 5052,
"attr19": 5810,
"attr20": 6682,
"attr1": 236,
"attr2": 755,
"attr3": 891,
"attr4": 1007,
"attr5": 1138,
"attr6": 1286,
"attr7": 1453,
"attr8": 1642,
"attr9": 1855,
"attr10": 2097,
"attr11": 2369,
"attr12": 2677,
"attr13": 3026,
"attr14": 3419,
"attr15": 3864,
"attr16": 4366,
"attr17": 4934,
"attr18": 5576,
"attr19": 6301,
"attr20": 7120,
"attr21": 175,
"attr22": 184,
"attr23": 184,
@ -349,31 +369,33 @@
"attr25": 184,
"attr26": 1268,
"attr27": 10214,
"attr28": 1821
"attr28": 1821,
"attr29": 1899,
"attr30": 2512
},
{
"id": 12,
"attr0": 1620,
"attr1": 404,
"attr2": 815,
"attr3": 728,
"attr4": 837,
"attr5": 963,
"attr6": 1107,
"attr7": 1273,
"attr8": 1464,
"attr9": 1684,
"attr10": 1937,
"attr11": 2228,
"attr12": 2562,
"attr13": 2946,
"attr14": 3388,
"attr15": 3896,
"attr16": 4480,
"attr17": 5152,
"attr18": 5925,
"attr19": 6814,
"attr20": 7836,
"attr1": 236,
"attr2": 755,
"attr3": 996,
"attr4": 1178,
"attr5": 1394,
"attr6": 1649,
"attr7": 1951,
"attr8": 2308,
"attr9": 2731,
"attr10": 3231,
"attr11": 3823,
"attr12": 4522,
"attr13": 5350,
"attr14": 6330,
"attr15": 7489,
"attr16": 8860,
"attr17": 10482,
"attr18": 12401,
"attr19": 14671,
"attr20": 17357,
"attr21": 212,
"attr22": 221,
"attr23": 221,
@ -381,7 +403,9 @@
"attr25": 231,
"attr26": 1260,
"attr27": 11631,
"attr28": 1969
"attr28": 1969,
"attr29": 2228,
"attr30": 2946
},
{
"id": 13,
@ -413,7 +437,9 @@
"attr25": 250,
"attr26": 1515,
"attr27": 13750,
"attr28": 2083
"attr28": 2083,
"attr29": 2576,
"attr30": 3406
},
{
"id": 14,
@ -445,7 +471,9 @@
"attr25": 279,
"attr26": 1932,
"attr27": 17341,
"attr28": 2283
"attr28": 2283,
"attr29": 3191,
"attr30": 4221
},
{
"id": 15,
@ -477,7 +505,9 @@
"attr25": 269,
"attr26": 2022,
"attr27": 19428,
"attr28": 1993
"attr28": 1993,
"attr29": 2985,
"attr30": 3948
},
{
"id": 16,
@ -509,7 +539,9 @@
"attr25": 393,
"attr26": 2888,
"attr27": 28422,
"attr28": 2006
"attr28": 2006,
"attr29": 4848,
"attr30": 6411
},
{
"id": 17,
@ -541,7 +573,9 @@
"attr25": 416,
"attr26": 4061,
"attr27": 39309,
"attr28": 2823
"attr28": 2823,
"attr29": 6614,
"attr30": 8747
},
{
"id": 18,
@ -573,7 +607,9 @@
"attr25": 488,
"attr26": 4505,
"attr27": 46209,
"attr28": 3016
"attr28": 3016,
"attr29": 7681,
"attr30": 10158
},
{
"id": 19,
@ -605,7 +641,9 @@
"attr25": 513,
"attr26": 5444,
"attr27": 54855,
"attr28": 3390
"attr28": 3390,
"attr29": 9000,
"attr30": 11903
},
{
"id": 20,
@ -637,7 +675,9 @@
"attr25": 450,
"attr26": 5970,
"attr27": 59261,
"attr28": 3797
"attr28": 3797,
"attr29": 9589,
"attr30": 12681
},
{
"id": 21,
@ -669,7 +709,9 @@
"attr25": 1014,
"attr26": 5970,
"attr27": 79015,
"attr28": 4449
"attr28": 4449,
"attr29": 18818,
"attr30": 28934
},
{
"id": 22,
@ -701,7 +743,9 @@
"attr25": 1080,
"attr26": 5970,
"attr27": 79015,
"attr28": 4449
"attr28": 4449,
"attr29": 20212,
"attr30": 31078
},
{
"id": 23,
@ -733,7 +777,9 @@
"attr25": 1205,
"attr26": 5970,
"attr27": 79015,
"attr28": 4720
"attr28": 4720,
"attr29": 23846,
"attr30": 36666
},
{
"id": 24,
@ -765,7 +811,9 @@
"attr25": 1276,
"attr26": 5970,
"attr27": 79015,
"attr28": 4985
"attr28": 4985,
"attr29": 24640,
"attr30": 37887
},
{
"id": 25,
@ -797,7 +845,9 @@
"attr25": 764,
"attr26": 5970,
"attr27": 79015,
"attr28": 3009
"attr28": 3009,
"attr29": 15515,
"attr30": 23856
},
{
"id": 26,
@ -829,7 +879,9 @@
"attr25": 1611,
"attr26": 5970,
"attr27": 79015,
"attr28": 7034
"attr28": 7034,
"attr29": 31977,
"attr30": 49167
},
{
"id": 27,
@ -861,7 +913,9 @@
"attr25": 1769,
"attr26": 5970,
"attr27": 79015,
"attr28": 7428
"attr28": 7428,
"attr29": 34310,
"attr30": 52755
},
{
"id": 28,
@ -893,7 +947,9 @@
"attr25": 1856,
"attr26": 5970,
"attr27": 79015,
"attr28": 7428
"attr28": 7428,
"attr29": 35288,
"attr30": 54259
},
{
"id": 29,
@ -925,7 +981,9 @@
"attr25": 2027,
"attr26": 5970,
"attr27": 79015,
"attr28": 7428
"attr28": 7428,
"attr29": 38760,
"attr30": 59597
},
{
"id": 30,
@ -957,6 +1015,8 @@
"attr25": 1268,
"attr26": 5970,
"attr27": 79015,
"attr28": 4234
"attr28": 4234,
"attr29": 21417,
"attr30": 32931
}
]

@ -292,15 +292,14 @@
"constValue": 0,
"constgroup": [
1,
3,
5
3
],
"constString": ""
},
{
"id": 72,
"desc": "20波之后,怪物血量系数增长基础值",
"constValue": 1.1,
"constValue": 1.15,
"constgroup": [],
"constString": ""
},
@ -324,9 +323,9 @@
1,
1,
1,
0,
1,
0,
1,
1,
1,
0,
1,

@ -192,8 +192,8 @@
"__expectedType__": "sp.SkeletonData"
},
"defaultSkin": "default",
"defaultAnimation": "",
"_premultipliedAlpha": false,
"defaultAnimation": "<None>",
"_premultipliedAlpha": true,
"_timeScale": 1,
"_preCacheMode": 0,
"_cacheMode": 0,

@ -110,6 +110,7 @@ export class ResourcesManager extends Singleton {
private async _loadPrefabInternal(name: string, path: string, bundle: string): Promise<Prefab | null> {
const b = await this.loadBundleAsync(bundle);
if (!b) {
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', bundle);
return null;
}
const assetPath = path + name;
@ -123,6 +124,7 @@ export class ResourcesManager extends Singleton {
b.load(assetPath, Prefab, (err, asset) => {
if (err || !asset) {
console.warn(`[Res] load prefab failed ${bundle}/${assetPath}`, err);
gg.sdk?.pfLogKey?.('RES:PREFAB_FAIL', `${bundle}/${assetPath}`);
resolve(null);
return;
}
@ -174,6 +176,7 @@ export class ResourcesManager extends Singleton {
return null;
} catch (error) {
console.error("UIBase getNode error", key, error);
gg.sdk?.pfLogKey?.('RES:GET_NODE_FAIL', key);
return null;
}
}
@ -261,6 +264,7 @@ export class ResourcesManager extends Singleton {
}
bundleTasks.get(conf.bundle)?.then((b) => {
if (!b) {
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', conf.bundle);
fun();
return;
}
@ -341,6 +345,7 @@ export class ResourcesManager extends Singleton {
}
bundleTasks.get(conf.bundle)?.then((b) => {
if (!b) {
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', conf.bundle);
done();
return;
}
@ -387,6 +392,7 @@ export class ResourcesManager extends Singleton {
assetManager.loadBundle(bundleName, (err, bundle) => {
if (err) {
console.log(`加载Bundle:${bundleName}错误`, err);
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', bundleName);
if (onComplete) onComplete(null);
} else {
if (onComplete) onComplete(bundle);
@ -407,6 +413,7 @@ export class ResourcesManager extends Singleton {
assetManager.loadBundle(bundleName, (err, bundle) => {
if (err) {
console.log(`加载Bundle:${bundleName}错误`, err);
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', bundleName);
resolve(null);
} else {
resolve(bundle);
@ -431,6 +438,7 @@ export class ResourcesManager extends Singleton {
bundle.load(assetPath, type, (err, asset) => {
if (err) {
console.log(`加载Bundle:${bundle.name},path:${assetPath}资源错误`, err);
gg.sdk?.pfLogKey?.('RES:ASSET_FAIL', `${bundle.name}/${assetPath}`);
if (onComplete) onComplete(null);
} else {
if (onComplete) onComplete(asset);
@ -474,6 +482,7 @@ export class ResourcesManager extends Singleton {
}
bundleTasks.get(conf.bundle)?.then((b) => {
if (!b) {
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', conf.bundle);
fun();
return;
}

@ -12,8 +12,13 @@ import { PfDiagnostics } from "db://assets/script/diagnostics/PfDiagnostics";
/** 与 ConfigRes.UIID.SkillSelect 一致;不可 import ConfigRes,否则会与 ConfigRes→UIManager 循环引用 */
const SKILL_SELECT_PANEL_ID = 651;
/** SkillSelect 分包真重载冷却(避免连续失败时反复 removeBundle) */
/** 与 ConfigRes.UIID 一致 */
const BATTLE_SUCCESS_PANEL_ID = 605;
const BATTLE_FAIL_PANEL_ID = 606;
const BATTLE_REVIVE_PANEL_ID = 607;
/** SkillSelect / 结算分包真重载冷却(避免连续失败时反复 removeBundle) */
const SKILL_SELECT_BUNDLE_RELOAD_COOLDOWN_MS = 30_000;
const SETTLEMENT_BUNDLE_RELOAD_COOLDOWN_MS = 30_000;
/** loadPrefab 结果:失败时带 stage(bundle/asset)与引擎 err 摘要 */
type LoadPrefabResult = {
@ -122,6 +127,8 @@ export class UIManager {
private _panelOpenInflight: Map<number, Promise<void>> = new Map();
private _wait_popIds: any[] = [];
private _curent_pop = null;
/** 上次结算分包真重载时间戳(ms) */
private _settlementBundleReloadMs = 0;
/** 上次 SkillSelect 分包真重载时间戳(ms) */
private _skillSelectBundleReloadMs = 0;
@ -366,6 +373,7 @@ export class UIManager {
console.error(
`[UIManager] openPanel prefab load failed id=${id} ${failDetail}`,
);
gg.sdk?.pfLogKey?.('UI:OPEN_FAIL', `id=${id}|${failDetail}`);
this._ui_map.delete(id);
if (this.isSkillSelectPanel(id)) {
this._reportSkillSelectOpenFail('prefab_load_failed', param, failDetail);
@ -690,6 +698,12 @@ export class UIManager {
return id === SKILL_SELECT_PANEL_ID;
}
private isSettlementPanel(id: number): boolean {
return id === BATTLE_SUCCESS_PANEL_ID
|| id === BATTLE_FAIL_PANEL_ID
|| id === BATTLE_REVIVE_PANEL_ID;
}
/** 战斗已结束或正在回大厅时,不再打开 SkillSelect(避免异步 load 完成后访问 null 的 CurentBattle) */
private shouldAbortSkillSelectOpen(id: number): boolean {
return this.isSkillSelectPanel(id) && !gg.game?.isBattleContextActive?.();
@ -833,6 +847,31 @@ export class UIManager {
if (this.panelIsShow(id)) {
return true;
}
const conf = this.uiConfigs[id];
if (conf?.bundle && this.isSettlementPanel(id)) {
const reloadAgoMs = Date.now() - this._settlementBundleReloadMs;
const reloadOnCooldown = this._settlementBundleReloadMs > 0
&& reloadAgoMs < SETTLEMENT_BUNDLE_RELOAD_COOLDOWN_MS;
if (!reloadOnCooldown) {
console.warn(`[UIManager] openBattleSettlementPanel bundle reload id=${id}`);
if (this._ui_map.has(id)) {
this._ui_map.delete(id);
}
this._settlementBundleReloadMs = Date.now();
const path = conf.path + conf.name;
const loadResult = await this._reloadBundleAndLoadPrefab(conf.bundle, path);
await this.openPanel(id, param);
if (this.panelIsShow(id)) {
return true;
}
console.error(
`[UIManager] openBattleSettlementPanel reload failed id=${id} stage=${loadResult.stage ?? 'unknown'}`,
);
gg.sdk?.pfLogKey?.('UI:SETTLE_RELOAD_FAIL', `id=${id}|stage=${loadResult.stage ?? 'unknown'}`);
}
}
console.error(`[UIManager] openBattleSettlementPanel retry id=${id}`);
if (this._ui_map.has(id)) {
this._ui_map.delete(id);
@ -842,9 +881,10 @@ export class UIManager {
return true;
}
console.error(`[UIManager] openBattleSettlementPanel failed id=${id}`);
const conf = this.uiConfigs[id];
const panel = conf?.name ?? String(id);
const bundleLoaded = conf?.bundle && assetManager.getBundle(conf.bundle) ? 1 : 0;
gg.sdk?.pfLogKey?.('UI:SETTLE_OPEN_FAIL', `id=${id}`);
const failConf = conf ?? this.uiConfigs[id];
const panel = failConf?.name ?? String(id);
const bundleLoaded = failConf?.bundle && assetManager.getBundle(failConf.bundle) ? 1 : 0;
const battle = gg.game?.CurentBattle;
const memPf = this._memWarnFailPf();
PfDiagnostics.write(

@ -116,6 +116,7 @@ export class GameLaunch extends Component {
assetManager.loadBundle(bundleName, (err) => {
if (err) {
console.error(`[GameLaunch] loadBundle ${bundleName} 失败`, err);
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', `launch:${bundleName}`);
resolve(false);
} else {
resolve(true);

@ -78,16 +78,8 @@ export class PfDiagnostics {
this._lastMsByKey[key] = now;
try {
gg.sdk?.pfLog?.(msg);
gg.sdk?.pfLogKey?.(`${domain}:${tag}`, detail, cooldownMs);
} catch { /* ignore */ }
const logger = this._realtimeLogger();
if (logger) {
const fn = logger[level] ?? logger.warn ?? logger.log;
try {
fn?.call(logger, msg);
} catch { /* ignore */ }
}
return true;
}

@ -1,5 +1,5 @@
import { assetManager } from 'cc';
import { GBundle } from './ConfigRes';
import { GBundle, GPath } from './ConfigRes';
import { BattleType } from '../manager/ChapterDataManager';
/**
@ -41,6 +41,14 @@ const TETRI_BATTLE_CORE_BUNDLES: string[] = [
GBundle.BattleGameMonster,
];
/** 局内弹窗分包:波次宝箱/转盘/暂停/结算/玩法说明(进局前 loadBundle + ensurePrefab) */
const TETRI_BATTLE_POPUP_BUNDLES: string[] = [
GBundle.BattleGameResult,
GBundle.SkillSelect,
GBundle.TurnTable,
GBundle.tetriDoc,
];
/**
* 战斗/大厅分包与战斗内增量资源加载。
* 目标:缩减启动与进局内峰值内存,怪物按波次再拉。
@ -60,6 +68,7 @@ export class BattleResLoader {
assetManager.loadBundle(bundleName, (err) => {
if (err) {
console.warn(`[BattleResLoader] loadBundle ${bundleName} 失败`, err);
gg.sdk?.pfLogKey?.('RES:BUNDLE_FAIL', `battle:${bundleName}`);
resolve(false);
} else {
resolve(true);
@ -89,10 +98,18 @@ export class BattleResLoader {
/** 打开战斗界面前:确保局内 UI/地图/卡牌等核心分包已 loadBundle */
static async ensureBattleCoreBundles(): Promise<void> {
if (gg?.game?.CurentSelectBattleType === BattleType.OrangeWeaponMode_Ya) {
await this.ensureBundles(JIUJIUWOYA_BUNDLE_GROUP);
await this.ensureBundles([
...JIUJIUWOYA_BUNDLE_GROUP,
...TETRI_BATTLE_CORE_BUNDLES,
...TETRI_BATTLE_POPUP_BUNDLES,
GBundle.BattleGameWeapon,
]);
return;
}
await this.ensureBundles(TETRI_BATTLE_CORE_BUNDLES);
await this.ensureBundles([
...TETRI_BATTLE_CORE_BUNDLES,
...TETRI_BATTLE_POPUP_BUNDLES,
]);
}
/** @deprecated 请用 ensureBattleEntry;保留别名避免遗漏调用点 */
@ -190,6 +207,30 @@ export class BattleResLoader {
}
}
/**
* 局内弹窗 prefab 预热(进局 await,避免波次宝箱/转盘/暂停/结算首次弹出冷加载)。
* 不含大厅页、机制专用页(如 xiufuBox),那些仍按需加载。
*/
static async preloadBattlePopupAssets(): Promise<void> {
if (!gg?.res) return;
await this.ensureBundles(TETRI_BATTLE_POPUP_BUNDLES);
await Promise.all([
gg.res.ensurePrefab('BattleRevive', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleFail', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleSuccess', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattlePause', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('BattleGetCoin', GPath.UIPrefab, GBundle.BattleGameResult),
gg.res.ensurePrefab('SkillSelect', GPath.UIPrefab, GBundle.SkillSelect),
gg.res.ensurePrefab('TurnTable', GPath.UIPrefab, GBundle.TurnTable),
gg.res.ensurePrefab('PopTetriDoc', GPath.UIPrefab, GBundle.tetriDoc),
]);
}
/** @deprecated 请用 {@link preloadBattlePopupAssets} */
static async preloadBattleSettlementAssets(): Promise<void> {
return this.preloadBattlePopupAssets();
}
/** Boss 副本投掷物 / 爆炸特效(同步 getNode 前需已 ensure) */
static async preloadBossSkillAssets(): Promise<void> {
if (!gg?.res) return;

@ -536,7 +536,10 @@ export class GameController extends Component {
}
// 单个 weapon1 + hitEffect,耗时通常远小于地图/界面;await 可避免首枪空等
await BattleResLoader.preloadWeapon1Bullet();
await Promise.all([
BattleResLoader.preloadWeapon1Bullet(),
BattleResLoader.preloadBattlePopupAssets(),
]);
await gg.ui.openPanel(panelId);
const duration = new Date().getTime() - sdkConfig.enterBattleTime;

@ -4,7 +4,7 @@ import { GBundle, getBattleMapResItemByChapter, ResPkgName } from './ConfigRes';
/**
* Home 页后台预加载战斗资源:
* - 当前选中关卡地图
* - 局内相关 bundle(tetriGame/Map/Cards/Weapon + battleGameWeapon)
* - 局内相关 bundle(tetriGame/Map/Cards/Weapon + battleGameWeapon + 弹窗包)
* - weapon1 子弹 + hitEffect 爆炸
* 其它资源进局后再按需加载。
*
@ -23,6 +23,10 @@ export class HomeBattlePreload {
GBundle.tetriCards,
GBundle.tetriWeapon,
GBundle.BattleGameWeapon,
GBundle.BattleGameResult,
GBundle.SkillSelect,
GBundle.TurnTable,
GBundle.tetriDoc,
];
private static _paused = false;

@ -0,0 +1,51 @@
import { PhysicsSystem2D } from 'cc';
/**
* 2D 物理开关唯一入口。
*
* 根因:广告 / 离局 / 分辨率稳定等各自 `enable = false` 再按本地 wasEnabled 恢复,
* 快照会互相覆盖,最终永久关物理 → 子弹无碰撞 → 怪不掉血。
*
* 规则:任一 reason 挂起则关;集合空才开。开战/离局走 beginBattle/endBattle。
*/
export class Physics2DGate {
private static _reasons = new Set<string>();
static readonly VideoAd = 'videoAd';
static suspend(reason: string): void {
if (!reason) return;
this._reasons.add(reason);
PhysicsSystem2D.instance.enable = false;
}
static resume(reason: string): void {
if (!reason) return;
this._reasons.delete(reason);
this._applyEnable();
}
static isSuspended(reason?: string): boolean {
if (reason) return this._reasons.has(reason);
return this._reasons.size > 0;
}
private static _applyEnable(): void {
const on = this._reasons.size === 0;
PhysicsSystem2D.instance.enable = on;
if (on) PhysicsSystem2D.instance.maxSubSteps = 1;
}
/** 进入战斗:清空挂起并开物理 */
static beginBattle(): void {
this._reasons.clear();
PhysicsSystem2D.instance.enable = true;
PhysicsSystem2D.instance.maxSubSteps = 1;
}
/** 离开战斗:清空其它挂起并关物理(避免离局后广告回调误开) */
static endBattle(): void {
this._reasons.clear();
PhysicsSystem2D.instance.enable = false;
}
}

@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "f5600a10-5853-426a-845c-8a6ad18a2e6c",
"files": [],
"subMetas": {},
"userData": {}
}

@ -251,6 +251,7 @@ export class SmartPreload {
console.log(`[SmartPreload] ready ${conf.bundle}/${path}`);
} else {
console.warn(`[SmartPreload] load fail ${conf.bundle}/${path}`, err);
gg.sdk?.pfLogKey?.('RES:PREFAB_FAIL', `preload:${conf.bundle}/${path}`);
}
resolve();
});

@ -107,7 +107,6 @@ declare global {
/** 奖励 */
readonly reward: string;
}
/**
* Box 配置数据
*/
@ -131,7 +130,6 @@ declare global {
道具id,数量 */
readonly fragment: string;
}
/**
* Shop 配置数据
*/
@ -155,7 +153,6 @@ declare global {
/** 权重 */
readonly weight: number;
}
/**
* Const 配置数据
*/
@ -171,7 +168,6 @@ declare global {
/** 组 */
readonly constString: string;
}
/**
* Monster 配置数据
*/
@ -272,8 +268,11 @@ declare global {
readonly attr27: number;
/** 难度28 */
readonly attr28: number;
/** 难度29 */
readonly attr29: number;
/** 难度30 */
readonly attr30: number;
}
/**
* Skill 配置数据
*/
@ -475,7 +474,6 @@ declare global {
*/
readonly fall_effect: string;
}
/**
* NewYearSign 配置数据
*/
@ -489,7 +487,6 @@ declare global {
/** 道具id,数量 */
readonly reward: string;
}
/**
* Weapon2 配置数据
*/
@ -635,7 +632,6 @@ declare global {
/** 升级道具数 */
readonly item_num4: number;
}
/**
* Task 配置数据
*/
@ -669,7 +665,6 @@ declare global {
4、武器 */
readonly location: string;
}
/**
* TaskReward 配置数据
*/
@ -690,7 +685,6 @@ declare global {
/** 数量 */
readonly quantity: number;
}
/**
* Chapter 配置数据
*/
@ -913,7 +907,6 @@ declare global {
/** 出现权重 */
readonly weight: number;
}
/**
* Sign 配置数据
*/
@ -923,7 +916,6 @@ declare global {
/** 类型,道具id,数量 */
readonly reward: string;
}
/**
* Role 配置数据
*/
@ -967,7 +959,6 @@ declare global {
/** 金币消耗系数3 */
readonly coefficient_coin3: number;
}
/**
* Character 配置数据
*/
@ -989,7 +980,6 @@ declare global {
/** 数量 */
readonly num: number;
}
/**
* lang_cn 配置数据
*/
@ -1012,7 +1002,6 @@ declare global {
/** 英文 */
readonly en: string;
}
/**
* Lottery 配置数据
*/
@ -1048,7 +1037,6 @@ declare global {
/** 首次权重 */
readonly weight: string;
}
/**
* Item 配置数据
*/
@ -1109,7 +1097,6 @@ declare global {
/** 字段说明 */
readonly reward: string;
}
/**
* LimitActivity 配置数据
*/
@ -1135,7 +1122,6 @@ declare global {
/** 活动未开始的提示文字 */
readonly tips: string;
}
/**
* Obstacle 配置数据
*/
@ -1866,6 +1852,8 @@ declare global {
@ -2071,6 +2059,7 @@ declare global {
/** 解锁弹出描述 */
readonly popDesc: string;
}
/**
* Replica 配置数据
*/
@ -2125,7 +2114,6 @@ declare global {
/** 排序 */
readonly sort: number;
}
/**
* Playmode 配置数据
*/
@ -2161,7 +2149,6 @@ declare global {
/** 参数2 */
readonly par2: string;
}
/**
* TarTask 配置数据
*/
@ -2184,7 +2171,6 @@ declare global {
/** 跳转 */
readonly jumpto: number;
}
/**
* Skin 配置数据
*/
@ -2230,6 +2216,7 @@ declare global {
/** 升级属性 */
readonly upattr: string;
}
/**
* Pet 配置数据
*/
@ -2329,6 +2316,7 @@ declare global {
/** 权重 */
readonly hight: number;
}
/**
* StoryComic 配置数据
*/
@ -2352,7 +2340,6 @@ declare global {
/** 引导 */
readonly guide: number;
}
/**
* Guide 配置数据
*/
@ -2374,6 +2361,7 @@ declare global {
/** 描述(部分引导需要展示的文字提示内容) */
readonly desc: string;
}
/**
* Skill_pool 配置数据
*/
@ -2447,6 +2435,7 @@ declare global {
/** 181-240 */
readonly refresh_game_time4: string;
}
/**
* Const_weight 配置数据
*/
@ -2460,7 +2449,6 @@ declare global {
/** 组 */
readonly constString: string;
}
/**
* chapter_reward 配置数据
*/
@ -2471,6 +2459,7 @@ declare global {
道具id,数量 */
readonly reward: string;
}
/**
* zombie 配置数据
*/
@ -2502,7 +2491,6 @@ declare global {
/** 升级奖励 */
readonly levelRewardNum: string;
}
/**
* Gift 配置数据
*/
@ -2512,6 +2500,7 @@ declare global {
/** 类型,道具id,数量 */
readonly reward: string;
}
/**
* BattleCube 配置数据
*/
@ -2560,6 +2549,7 @@ declare global {
/** 权重 */
readonly typeweight4: string;
}
/**
* ReplicaLevel 配置数据
*/
@ -2616,6 +2606,8 @@ declare global {

@ -88,6 +88,7 @@ export class jiujiuwoMap extends Component {
}
//美女待机
showMeinvIdleEffect() {
if (this.meinvAnimState === '胜利') return;
const wallTarget1 = find('目标点/wallTarget1', this.node);
if(!wallTarget1 || !wallTarget1.isValid){
return;
@ -201,6 +202,7 @@ export class jiujiuwoMap extends Component {
}
//美女受击动画
showMeinvBeHitEffect() {
if (this.meinvAnimState === '胜利') return;
const wallTarget1 = find('目标点/wallTarget1', this.node);
if(!wallTarget1 || !wallTarget1.isValid){
return;
@ -212,6 +214,7 @@ export class jiujiuwoMap extends Component {
}
}
_onPlayerHit(){
if (gg.game?.CurentBattle?.IsGameOver) return;
this.showMeinvBeHitEffect();
if(this.timeHit){
clearTimeout(this.timeHit);

@ -1,4 +1,4 @@
import { assetManager, game, PhysicsSystem2D, sys } from "cc";
import { assetManager, game, sys } from "cc";
import { BYTEDANCE, DEBUG, DEV, EDITOR, WECHAT } from "cc/env";
import { RewardedVideoAd } from "./functions/RewardedVideoAd";
import { sdkConfig } from "./sdkConfig";
@ -14,6 +14,7 @@ import { update } from "../../../extensions/tools/source/scene";
import { PfDiagnostics } from "../diagnostics/PfDiagnostics";
import { BattlePerformance } from "../ui/BattleGame/BattlePerformance";
import { MiniGameKind, MiniGamePlatform, MiniGameHostApi } from "./platform/MiniGamePlatform";
import { Physics2DGate } from "../game/Physics2DGate";
/**
* SDK管理器
@ -66,10 +67,8 @@ export class SDKManager extends Singleton {
/** 激励视频嵌套计数(加载失败重试等场景) */
private _videoAdDepth = 0;
/** 看广告前物理是否启用 */
private _physicsWasEnabledBeforeVideoAd = true;
/** 看广告期间是否由 SDK 额外 pause 了战斗 */
private _battlePausedForVideoAd = false;
/** 本次广告是否由 SDK 调用了 battle.pause('videoAd') */
private _videoAdBattlePausedBySdk = false;
/**
* 初始化
@ -91,6 +90,7 @@ export class SDKManager extends Singleton {
this.pf.onShow(this.onPfShow.bind(this));
this.pf.onHide(this.onPfHide.bind(this));
this._bindGlobalErrorReporter();
this.serverTime = new Date().getTime();
@ -179,6 +179,7 @@ export class SDKManager extends Singleton {
this.checkTime((curentTime) => {
if (curentTime) {
console.error("时间异常");
this.pfLogKey('SYS:TIME_INVALID', { local: curentTime });
gg.ui.showToast("时间异常,请检查时间设置");
gg.sdk.reportDY("inLevel", `时间异常-本地时间${curentTime}`);
}
@ -235,6 +236,7 @@ export class SDKManager extends Singleton {
}
}, () => {
console.error("获取时间失败");
this.pfLogKey('SYS:TIME_FETCH_FAIL');
});
}
@ -269,6 +271,7 @@ export class SDKManager extends Singleton {
resolve(null);
}
}, () => {
this.pfLogKey('SYS:SERVER_TIME_FAIL');
resolve(null);
});
});
@ -298,6 +301,7 @@ export class SDKManager extends Singleton {
updateManager.onUpdateFailed((err) => {
// 新的版本下载失败
console.log("版本下载失败原因", err);
this.pfLogKey('SYS:UPDATE_FAIL', err);
this.pf.showToast({
title: "新版本下载失败,请稍后再试",
icon: "none",
@ -339,6 +343,7 @@ export class SDKManager extends Singleton {
console.log("onPfShow", res);
this.query = res.query;
this._show_time = new Date().getTime();
//this._restoreVideoAdRuntimeOnForeground();
if (this._onShare) {
let t = new Date().getTime() - this._share_time;
this._onShare(t);
@ -379,48 +384,56 @@ export class SDKManager extends Singleton {
}
/**
* 激励视频播放前冻结音频/物理;战斗仅在「未 pause」时额外 pause('videoAd')。
* 激励视频播放前:挂起物理/音频/(微信)主循环;战斗由 SDK 统一 pause('videoAd')。
* 调用方不得在 showVideoAd 前自行 battle.pause,否则 leave 无法对称恢复战斗与物理。
*/
private _enterVideoAdPause(): void {
if (this._videoAdDepth === 0) {
const ph = PhysicsSystem2D.instance;
this._physicsWasEnabledBeforeVideoAd = ph.enable;
ph.enable = false;
gg.audio.pause();
if (this._shouldPauseEngineForVideoAd()) {
game.pause();
}
Physics2DGate.suspend(Physics2DGate.VideoAd);
const battle = gg.game?.CurentBattle;
if (battle && !battle.IsGamePause) {
battle.pause('videoAd');
this._battlePausedForVideoAd = true;
} else {
this._battlePausedForVideoAd = false;
}
gg.audio.pause();
// if (this._shouldPauseEngineForVideoAd()) {
// }
game.pause();
this._videoAdBattlePausedBySdk = false;
const battle = gg.game?.CurentBattle;
if (battle && !battle.IsGameOver && !battle.IsGamePause) {
battle.pause('videoAd');
this._videoAdBattlePausedBySdk = true;
}
this._videoAdDepth++;
}
private _isVideoAdPaused = false;
/** 激励视频 onClose 后对称恢复;嵌套播放时仅在最外层恢复。 */
/** 激励视频 onClose:与 enter 对称恢复;仅恢复本链路 pause 的战斗。 */
private _leaveVideoAdPause(): void {
if (this._videoAdDepth <= 0) return;
this._videoAdDepth--;
if (this._videoAdDepth > 0) return;
// if (this._shouldPauseEngineForVideoAd()) {
// }
game.resume();
Physics2DGate.resume(Physics2DGate.VideoAd);
gg.audio.resume();
const ph = PhysicsSystem2D.instance;
ph.enable = this._physicsWasEnabledBeforeVideoAd;
ph.maxSubSteps = 1;
if (this._videoAdBattlePausedBySdk) {
gg.game?.CurentBattle?.resumeFromVideoAdSdk();
this._videoAdBattlePausedBySdk = false;
}
}
/**
* 全屏激励视频:从小程序广告页回到游戏画布时,onShow 往往早于 onClose。
* 须先恢复物理/音频/战斗,避免「弹道飞到怪身上却无碰撞」;onClose 仍负责 depth-- 与发奖。
*/
private _restoreVideoAdRuntimeOnForeground(): void {
if (this._videoAdDepth <= 0) return;
if (!RewardedVideoAd.hasPendingCallback()) return;
Physics2DGate.resume(Physics2DGate.VideoAd);
if (this._shouldPauseEngineForVideoAd()) {
game.resume();
}
gg.audio.resume();
if (this._battlePausedForVideoAd) {
gg.game?.CurentBattle?.resume();
this._battlePausedForVideoAd = false;
if (this._videoAdBattlePausedBySdk) {
gg.game?.CurentBattle?.resumeFromVideoAdSdk();
}
}
@ -430,32 +443,53 @@ export class SDKManager extends Singleton {
*/
showVideoAd(callback: Function) {
console.log('显示视频广告');
if (this._isVideoAdPaused) return;
if (gg.data.getItemNum(ItemNumType.skipAd) > 0) {
gg.data.subItemNum(ItemNumType.skipAd, 1);
callback(true, "skipAd");
return;
}
else if (EDITOR || !this.showAd) {
gg.data.addDayTaskProgressData(TaskType.videoAd, 1)
if (EDITOR || !this.showAd) {
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
callback(true, "editor");
} else if (sys.isBrowser) {
gg.data.addDayTaskProgressData(TaskType.videoAd, 1)
return;
}
if (sys.isBrowser) {
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
callback(true, window.location.href);
return;
}
else {
this._isVideoAdPaused = true;
void MiniGamePlatform.checkNetworkForRewardedVideo().then((online) => {
if (!online) {
this._isVideoAdPaused = false;
gg.ui.showToast('网络请求失败,请稍后再试');
this.pfLogKey('AD:NETWORK_OFFLINE');
callback(false);
return;
}
this._enterVideoAdPause();
RewardedVideoAd.show((res) => {
this._leaveVideoAdPause();
if (res) {
callback(true);
gg.data.addStatistics(StatisticsType.TodayAdCount, 1);
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
ZYSDK.ZYSDK.reportVideo(true);
} else {
callback(false);
ZYSDK.ZYSDK.reportVideo(false);
try {
this._isVideoAdPaused = false;
this._leaveVideoAdPause();
if (res) {
callback(true);
gg.data.addStatistics(StatisticsType.TodayAdCount, 1);
gg.data.addDayTaskProgressData(TaskType.videoAd, 1);
ZYSDK.ZYSDK.reportVideo(true);
} else {
this.pfLogKey('AD:REWARD_USER_FAIL');
callback(false);
ZYSDK.ZYSDK.reportVideo(false);
}
} catch (e) {
console.error('[SDKManager] showVideoAd callback error', e);
this.pfLogKey('AD:REWARD_CALLBACK_ERR', e);
}
});
}
});
}
/**
@ -960,6 +994,24 @@ export class SDKManager extends Singleton {
}
}
private _bindGlobalErrorReporter(): void {
try {
const pf = this.pf as MiniGameHostApi & {
onError?: (cb: (err: unknown) => void) => void;
onUnhandledRejection?: (cb: (res: { reason?: unknown }) => void) => void;
};
pf?.onError?.((err) => {
this.pfLogKey('RUNTIME:UNCAUGHT', err, 300000);
});
pf?.onUnhandledRejection?.((res) => {
this.pfLogKey('RUNTIME:PROMISE_REJECT', res?.reason, 300000);
});
} catch { /* ignore */ }
}
/** 同 tag 平台日志冷却(毫秒),避免刷爆配额 */
private _pfLogKeyLastMs: Record<string, number> = {};
/**平台记录日志 */
pfLog(msg: string) {
if (this.pf && this.pf.getLogManager) {
@ -968,6 +1020,58 @@ export class SDKManager extends Singleton {
console.log(msg);
}
}
/**
* 关键行为 / 失败 / 异常 → 平台日志(getLogManager + RealtimeLogManager)。
* @param tag 短标签,如 RES:PREFAB_FAIL、AD:REWARD_SHOW_FAIL
* @param detail 附加信息(字符串、Error 或可 JSON 序列化对象)
* @param cooldownMs 同 tag 冷却,默认 60s
*/
pfLogKey(tag: string, detail?: unknown, cooldownMs = 60000): void {
if (!tag) return;
const now = Date.now();
const last = this._pfLogKeyLastMs[tag] ?? 0;
if (now - last < cooldownMs) return;
this._pfLogKeyLastMs[tag] = now;
const ctx = this._formatPfLogContext();
const detailStr = this._stringifyPfDetail(detail);
let msg = detailStr ? `[${tag}] ${ctx} ${detailStr}` : `[${tag}] ${ctx}`;
if (msg.length > 250) msg = msg.slice(0, 250);
this.pfLog(msg);
try {
const pfAny = this.pf as { getRealtimeLogManager?: (opts?: { syncToConsole?: boolean }) => { error?: (m: string) => void } } | null;
const getRt = pfAny?.getRealtimeLogManager;
if (typeof getRt === 'function') {
getRt.call(pfAny, { syncToConsole: !!DEV })?.error?.(msg);
}
} catch { /* ignore */ }
if (DEV) {
console.warn('[pfLogKey]', msg);
}
}
private _formatPfLogContext(): string {
const b = gg.game?.CurentBattle;
const ch = b?.getReportDYChapterId?.() ?? gg.data?.doc?.chapter ?? 0;
const wave = b?.CurentWaveNum ?? 0;
return `ch${ch}_w${wave}`;
}
private _stringifyPfDetail(detail: unknown): string {
if (detail == null) return '';
if (typeof detail === 'string') return detail;
if (detail instanceof Error) return `${detail.name}:${detail.message}`;
try {
const s = JSON.stringify(detail);
return s.length > 140 ? s.slice(0, 140) : s;
} catch {
return String(detail);
}
}
/**平台记录调试日志 */
pfDebug(msg: string) {
if (this.pf && this.pf.getLogManager) {

@ -7,7 +7,7 @@ import { IConfigData } from "./IConfigData";
export const XiaoXiaoFangkuaiZhen: IConfigData = {
id: "",
name: "tt_xxfkz",
vsion: "2.1.1",
vsion: "2.1.5",
chanId: "tt",
themeId: "1",
useDoc: true,

@ -13,10 +13,12 @@ export class InterstitialAd {
MiniGamePlatform.init();
const pf = MiniGamePlatform.pf;
if (!pf?.createInterstitialAd) {
gg.sdk?.pfLogKey?.('AD:INTER_NO_API');
onAdClose?.(true);
return;
}
if (this._keys.length === 0) {
gg.sdk?.pfLogKey?.('AD:INTER_NO_UNIT');
onAdClose?.(false);
return;
}
@ -25,6 +27,7 @@ export class InterstitialAd {
adUnitId: this._keys[keyIndex],
});
if (!interstitialAd) {
gg.sdk?.pfLogKey?.('AD:INTER_CREATE_FAIL', this._keys[keyIndex]);
onFail?.();
return;
}
@ -46,6 +49,7 @@ export class InterstitialAd {
onSuccess?.();
}).catch((err: unknown) => {
console.log('[InterstitialAd] show fail', err);
gg.sdk?.pfLogKey?.('AD:INTER_SHOW_FAIL', err);
onFail?.();
});
};
@ -53,6 +57,7 @@ export class InterstitialAd {
if (loadResult?.then) {
loadResult.then(afterLoad).catch((err: unknown) => {
console.log('[InterstitialAd] load fail', err);
gg.sdk?.pfLogKey?.('AD:INTER_LOAD_FAIL', err);
onFail?.();
});
} else {

@ -21,6 +21,8 @@ export class RewardedVideoAd {
/** 微信/快手:adUnitId → 单例实例 */
private static _singletonAds = new Map<string, any>();
private static _eventsBound = new WeakSet<object>();
/** 防止 onClose 重复触发导致业务回调执行两次 */
private static _sessionFinished = false;
static init(keys: string[]) {
this.keys = keys ?? [];
@ -32,20 +34,24 @@ export class RewardedVideoAd {
}
static show(callback?: Function) {
this._sessionFinished = false;
this._callback = callback ?? null;
this._errCount = 0;
const pf = MiniGamePlatform.pf;
if (!pf?.createRewardedVideoAd) {
this._logFail('AD:REWARD_NO_API');
this._finish(false);
return;
}
const adUnitId = this.keys[this._keyIndex];
if (!adUnitId) {
this._logFail('AD:REWARD_NO_UNIT');
this._finish(false);
return;
}
const ad = this._ensureAd(adUnitId);
if (!ad) {
this._logFail('AD:REWARD_CREATE_FAIL', adUnitId);
this._finish(false);
return;
}
@ -54,6 +60,11 @@ export class RewardedVideoAd {
this._showWithReload(ad);
}
/** 激励视频会话是否仍在等待 onClose(用于广告挂起态判断) */
static hasPendingCallback(): boolean {
return this._callback != null && !this._sessionFinished;
}
private static _ensureAd(adUnitId: string): any {
const pf = MiniGamePlatform.pf;
if (!pf?.createRewardedVideoAd) return null;
@ -92,6 +103,7 @@ export class RewardedVideoAd {
});
ad.onError?.((err: unknown) => {
console.log('[RewardedVideoAd] onError', err);
this._logFail('AD:REWARD_ON_ERROR', err);
this._onLoadError();
});
ad.onClose?.((res?: { isEnded?: boolean }) => {
@ -104,19 +116,42 @@ export class RewardedVideoAd {
/** show 失败时 load→show(微信/快手/B站官方示例) */
private static _showWithReload(ad: any): void {
const showResult = ad.show?.();
if (!showResult?.then) return;
let showResult: { then?: Function; catch?: Function } | undefined;
try {
showResult = ad.show?.();
} catch (err) {
console.log('[RewardedVideoAd] show throw', err);
this._logFail('AD:REWARD_SHOW_THROW', err);
this._onLoadError();
return;
}
if (!showResult?.then) {
return;
}
showResult.catch((err: unknown) => {
console.log('[RewardedVideoAd] show fail, load then show', err);
this._logFail('AD:REWARD_SHOW_FAIL', err);
const loadResult = ad.load?.();
if (!loadResult?.then) {
ad.load?.();
return;
}
loadResult.then(() => ad.show?.()).catch((err2: unknown) => {
console.log('[RewardedVideoAd] load+show fail', err2);
this._onLoadError();
});
loadResult
.then(() => {
const retry = ad.show?.();
if (retry?.then) {
retry.catch((err2: unknown) => {
console.log('[RewardedVideoAd] load+show fail', err2);
this._logFail('AD:REWARD_LOAD_SHOW_FAIL', err2);
this._onLoadError();
});
}
})
.catch((err2: unknown) => {
console.log('[RewardedVideoAd] load+show fail', err2);
this._logFail('AD:REWARD_LOAD_FAIL', err2);
this._onLoadError();
});
});
}
@ -138,6 +173,7 @@ export class RewardedVideoAd {
}
const ad = this._ensureAd(newKey);
if (!ad) {
this._logFail('AD:REWARD_SWITCH_FAIL', newKey);
this._finish(false);
return;
}
@ -156,8 +192,19 @@ export class RewardedVideoAd {
}
private static _finish(success: boolean): void {
if (this._sessionFinished) return;
this._sessionFinished = true;
if (!success) {
this._logFail('AD:REWARD_FINISH_FAIL');
}
const cb = this._callback;
this._callback = null;
if (cb) cb(success);
}
private static _logFail(tag: string, detail?: unknown): void {
try {
gg.sdk?.pfLogKey?.(tag, detail);
} catch { /* ignore */ }
}
}

@ -144,6 +144,37 @@ export class MiniGamePlatform {
return this.kind === MiniGameKind.WeChat;
}
/**
* 激励视频拉取前检查网络(微信/抖音等均提供 getNetworkType,networkType=none 表示无网)。
* 无 API 时默认 true,避免误拦。
*/
static checkNetworkForRewardedVideo(): Promise<boolean> {
if (sys.isBrowser) {
const online = typeof navigator === 'undefined' ? true : navigator.onLine !== false;
return Promise.resolve(online);
}
this.init();
const pf = this._pf as MiniGameHostApi & {
getNetworkType?: (opts: {
success?: (res: { networkType?: string }) => void;
fail?: () => void;
}) => void;
};
const getNetworkType = pf?.getNetworkType;
if (typeof getNetworkType !== 'function') {
return Promise.resolve(true);
}
return new Promise((resolve) => {
getNetworkType.call(pf, {
success: (res) => {
const type = (res?.networkType ?? '').toLowerCase();
resolve(type !== 'none');
},
fail: () => resolve(false),
});
});
}
/**
* 微信 / 快手:createRewardedVideoAd 为全局单例,禁止 destroy 后频繁重建。
* @see https://developers.weixin.qq.com/minigame/dev/guide/open-ability/ad/rewarded-video-ad.html

@ -109,8 +109,15 @@ export class tetriAbility extends Component {
if(conf){
roleNode.setPosition(conf.mainPosi.x, conf.mainPosi.y)
roleNode.setScale(conf.scalex, conf.scaley)
roleNode.getChildByName('anim').getComponent(sp.Skeleton).setAnimation(0, '待机', true)
roleNode.getChildByName('anim').getComponent(sp.Skeleton).setSkin(conf.animSkin)
const anim = roleNode.getChildByName('anim');
const animSk = anim?.getComponent(sp.Skeleton);
if (animSk) {
animSk.setSkin(conf.animSkin);
}
// 产币/回血改用方块根节点 spine 三态表情;旧「角色/anim」仅产效时短暂播放
if (anim?.isValid) {
anim.active = false;
}
}
const roleComp = roleNode.getComponent(role);
@ -206,9 +213,13 @@ export class tetriAbility extends Component {
return
}
let anim = find('anim', this.roleNode)
if (!anim?.isValid) return;
anim.active = true;
anim.getComponent(sp.Skeleton).setAnimation(0, '攻击', false)
this.scheduleOnce(()=>{
if (!anim?.isValid) return;
anim.getComponent(sp.Skeleton).setAnimation(0, '待机', true)
anim.active = false;
},1)

@ -17,7 +17,7 @@ const { ccclass, property } = _decorator;
/***方块摆放的item节点, 放在tetraGame上的ui_card的子节点上,会自动根据方块的类型创建对应的方块节点
* 可以进行触摸移动
*
* 点击item节点0秒后item上创建的方块的父节点到_gameRoot的ui_card的子节点上,位置更新一下然后设置item节点opacity==0保证触摸事件继续
* 点击卡牌后从模板克隆纯视觉预览;松手时再从模板实例化全新战场方块(与预览彻底分离,无物理开关竞态)。
* 根据类型不同,所以方块有两个脚本
* tetriCardItem: 自由下落的方块节点
* tetraFloorNode: 无重力方块,墙体。接触到其他方块会立马停住做为底板使用
@ -50,8 +50,8 @@ export class tetriCardItem extends Component {
/**绑定地图脚本(用于读取镜头缩放,做 x 坐标换算) */
private _boundMap: tetriMap | null = null;
/**外部回调:松手且满足放置条件时触发 */
onPlaceRequested: ((self: tetriCardItem, worldPos: Vec3) => void) | null = null;
/**外部回调:松手且满足放置条件时触发;返回 true 表示战场已接管 battleNode */
onPlaceRequested: ((self: tetriCardItem, battleNode: Node, worldPos: Vec3) => boolean) | null = null;
/**外部回调:卡牌被成功消耗(放置成功) */
onConsumed: (() => void) | null = null;
@ -63,10 +63,7 @@ export class tetriCardItem extends Component {
private _pieceNode: Node | null = null;
/***预览二号 */
private _pieceNodeCopyMove: Node | null = null;
private _pieceNodeCopyMoveFixedWorldY: number | null = null;
private _pieceOriginLocalPos: Vec3 = new Vec3();
/** 拿起预览时 schedule 的「下一帧关物理」令牌;放置后递增以作废,避免松手后把下落块又设回 Static */
private _previewPhysicsDisableToken = 0;
private _holdScheduled: boolean = false;
private _lastTouchEvent: EventTouch | null = null;
private _startPos: Vec2 = new Vec2();
@ -123,61 +120,22 @@ export class tetriCardItem extends Component {
}
}
/** 预览态彻底退出物理世界:禁用刚体与全部碰撞体 */
private _disablePhysicsForPreview(node: Node | null): void {
if (!node?.isValid) return;
// 已交给战场下落(挂在 GameRoot / Tetris 地图下)时禁止再关物理
if (this._isNodeReleasedToBattlefield(node)) return;
for (const rb of node.getComponentsInChildren(RigidBody2D)) {
rb.linearVelocity = new Vec2(0, 0);
rb.angularVelocity = 0;
rb.gravityScale = 0;
rb.type = ERigidBody2DType.Static;
rb.fixedRotation = true;
rb.enabledContactListener = false;
rb.enabled = false;
}
for (const col of node.getComponentsInChildren(Collider2D)) {
col.enabled = false;
/**
* 拖拽预览专用:剥玩法脚本并移除刚体,仅保留渲染与 Polygon(重叠检测)。
* 预览节点用完即毁,永不进入战场,从设计上避免「关物理 → 再开物理」竞态。
*/
private _stripPreviewForDrag(root: Node | null): void {
if (!root?.isValid) return;
this._stripAlignPreviewGameplay(root);
for (const rb of root.getComponentsInChildren(RigidBody2D)) {
rb.destroy();
}
}
/** 某些脚本会在 next tick 反开 collider,这里做一帧后的兜底再禁用 */
private _disablePhysicsForPreviewNextFrame(node: Node | null): void {
const token = this._previewPhysicsDisableToken;
this.scheduleOnce(() => {
if (token !== this._previewPhysicsDisableToken) return;
this._disablePhysicsForPreview(node);
}, 0);
}
/** 放置瞬间作废所有「预览关物理」延迟任务 */
private _invalidatePreviewPhysicsDisable(): void {
this._previewPhysicsDisableToken++;
}
/** 节点是否已移交战场(不再是 chooseMask 上的拖拽预览) */
private _isNodeReleasedToBattlefield(node: Node | null): boolean {
if (!node?.isValid) return false;
const map = this._boundMap ?? (gg as any)?.game?.CurentBattle?.TetraMap;
const gameRoot = map?._gameRoot as Node | null | undefined;
if (gameRoot?.isValid) {
let cur: Node | null = node;
while (cur) {
if (cur === gameRoot) return true;
cur = cur.parent;
}
}
// 已不在卡牌/遮罩下,也视为已释放
if (this.chooseMask?.isValid) {
let cur: Node | null = node;
while (cur) {
if (cur === this.chooseMask || cur === this.node) return false;
cur = cur.parent;
}
return true;
}
return false;
/** 从卡牌模板实例化战场方块(与拖拽预览彻底分离) */
public createBattlePiece(): Node | null {
if (!this._pieceNode?.isValid) return null;
return instantiate(this._pieceNode);
}
/** 递归销毁名为「角色」的子树(预览二号禁止挂角色/武器) */
@ -194,7 +152,7 @@ export class tetriCardItem extends Component {
}
/**
* 预览二号:在进入场景树之前剥掉玩法脚本,避免 start/onEnable 里创建角色、武器或注册战斗逻辑。
* 预览:剥掉下落/碰撞玩法脚本,保留 tetriWeaponCarrier 仅作展示(禁止射击)。
*/
private _stripAlignPreviewGameplay(root: Node | null): void {
if (!root?.isValid) return;
@ -202,7 +160,7 @@ export class tetriCardItem extends Component {
c.destroy();
}
for (const c of root.getComponentsInChildren(tetriWeaponCarrier)) {
c.destroy();
c.setIsShootCan(false);
}
this._destroyRoleNamedSubtrees(root);
for (const c of root.getComponentsInChildren(tetriNode)) {
@ -314,8 +272,6 @@ export class tetriCardItem extends Component {
protected onDestroy(): void {
this.node.off(Node.EventType.TOUCH_START, this._onTouchStart, this);
this.node.off(Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
this.node.off(Node.EventType.TOUCH_END, this._onTouchEnd, this);
@ -443,27 +399,15 @@ export class tetriCardItem extends Component {
return;
}
if (this.onPlaceRequested) {
// 交给战场前恢复预制 scale,避免下落块带拖拽期的镜头缩放系数
this._restorePreviewPieceBaseScale();
// 作废「下一帧关物理」:HOLD_SECONDS=0 时 createPreview 与松手常隔 1 帧,否则会把已下落块又冻成 Static 悬在白线
this._invalidatePreviewPhysicsDisable();
//注册的回调
this.onPlaceRequested(this, pos);
if (this._pieceNodeCopyMove && this._pieceNodeCopyMove.isValid) {
this._pieceNodeCopyMove.destroy();
console.log('回收拖拽预览副 this._pieceNodeCopyMove333');
//gg.res.putNode(this._pieceNodeCopyMove);
}
this._pieceNodeCopyMove = null;
this._pieceNodeCopyMoveFixedWorldY = null;
this._destroyAlignPreview();
if(gg.data.doc.chapter == 1 ){
GEvent.Ins.emit(GEvent.TetriGameTouchEnd);
const battleNode = this.createBattlePiece();
if (!battleNode) {
this.cancelPlace();
return;
}
//扣钱(雷电副本不消耗银币)
if (gg.game.CurentBattle.BattleType !== BattleType.OrangeWeaponMode_Dian) {
gg.game.CurentBattle.subCoinNum(this.config.price);
const placed = this.onPlaceRequested(this, battleNode, pos) === true;
if (!placed) {
if (battleNode.isValid) battleNode.destroy();
return;
}
} else {
this.consume();
@ -473,17 +417,16 @@ export class tetriCardItem extends Component {
}
/**放置成功:预览所有权移交游戏逻辑,卡牌隐藏 */
/**放置成功:销毁预览、扣银币、隐藏卡牌 */
public consume(): void {
this._snapTween?.stop();
this._destroyGuideLine();
this._destroyAlignPreview();
this._restorePreviewPieceBaseScale();
this._previewDragBaseScale = null;
this._previewNode = null;
this._tearDownDragPreview();
if (gg.game.CurentBattle.BattleType !== BattleType.OrangeWeaponMode_Dian) {
gg.game.CurentBattle.subCoinNum(this.config?.price ?? 0);
}
this.node.active = false;
this.onConsumed?.();
if(gg.data.doc.chapter == 1){
if (gg.data.doc.chapter == 1) {
GEvent.Ins.emit(GEvent.TetriGameTouchEnd);
}
}
@ -653,72 +596,48 @@ export class tetriCardItem extends Component {
return false;
}
/**创建预览 */
/**创建预览:从模板克隆纯视觉节点;模板 _pieceNode 始终留在卡牌上 */
private _createPreview(): void {
if (!this._pieceNode || !this.chooseMask) return;
// 将方块子节点移入 chooseMask,成为独立预览(最上层)
this.chooseMask.active = true
this.cancleNode.active = true
this.chooseMask.addChild(this._pieceNode);
this.chooseMask.active = true;
this.cancleNode.active = true;
// 确保挪到 GameRoot 后仍会被正确相机渲染(layer 必须与 GameRoot 一致)
this._setLayerRecursively(this._pieceNode, this.chooseMask.layer);
this._pieceNode.active = true;
// 预览阶段:彻底禁用物理,防止任何接触/推挤
this._disablePhysicsForPreview(this._pieceNode);
this._disablePhysicsForPreviewNextFrame(this._pieceNode);
const preview = instantiate(this._pieceNode);
this.chooseMask.addChild(preview);
this._setLayerRecursively(preview, this.chooseMask.layer);
preview.active = true;
preview.angle = this._pieceNode.angle;
this._stripPreviewForDrag(preview);
this._previewDragBaseScale = this._pieceNode.scale.clone();
//this.config.cubename 在创建一个方块节点,位置放在当前方块的白线正上方,保持一致,用于拖动预览,禁用碰撞器,禁用重力,禁用刚体
//赋值到this._pieceNodeCopyMove上,用于拖动预览结束的时候回收,原来的逻辑保留
this._previewNode = preview;
// 额外的“拖拽预览副本”(不影响原有 _pieceNode 逻辑);isShowBlockDrop 时不在白线展示副本,落点跟手
if (this._pieceNodeCopyMove && this._pieceNodeCopyMove.isValid) {
console.log('回收拖拽预览副 this._pieceNodeCopyMove1111');
//gg.res.putNode(this._pieceNodeCopyMove);
this._pieceNodeCopyMove.destroy();
this._pieceNodeCopyMove = null;
}
if (!gg.game.isShowBlockDrop) {
this._pieceNodeCopyMove = instantiate(this._pieceNode);
if (this._pieceNode.getComponent(tetriNode)) {
this._pieceNodeCopyMove.getOrAddComponent(tetriNode).setConfig(this.config);
}
if (this._pieceNode.getComponent(tetriWeaponCarrier)) {
this._pieceNodeCopyMove.getOrAddComponent(tetriWeaponCarrier).setConfig(this.config);
}
//this._pieceNodeCopyMove.scale = this._pieceNode.scale
this._pieceNodeCopyMove.angle = 0
this._stripPreviewForDrag(this._pieceNodeCopyMove);
this._pieceNodeCopyMove.angle = 0;
this.chooseMask.addChild(this._pieceNodeCopyMove);
//this._pieceNodeCopyMove.scale = this._pieceNode.scale.clone();
const mapLayer = gg.game.CurentBattle.TetraMap?.node?.layer ?? this.chooseMask.layer;
this._setLayerRecursively(this._pieceNodeCopyMove, mapLayer);
this._pieceNodeCopyMove.active = true;
// 固定在白线处(底边贴线),X 跟随主方块
const fixedY = this._getWhiteLineFixedWorldYFor(this._pieceNodeCopyMove);
const startX = this._pieceNode.worldPosition.x;
const startX = preview.worldPosition.x;
const startZ = this._pieceNodeCopyMove.worldPosition.z;
this._pieceNodeCopyMoveFixedWorldY = fixedY ?? this._pieceNode.worldPosition.y;
this._pieceNodeCopyMove.setWorldPosition(new Vec3(startX, this._pieceNodeCopyMoveFixedWorldY, startZ));
// 白线副本同样彻底禁用物理,保证仅作为视觉引导
this._disablePhysicsForPreview(this._pieceNodeCopyMove);
this._disablePhysicsForPreviewNextFrame(this._pieceNodeCopyMove);
if (fixedY != null) {
this._pieceNodeCopyMove.setWorldPosition(new Vec3(startX, fixedY, startZ));
}
} else {
this._pieceNodeCopyMove = null;
this._pieceNodeCopyMoveFixedWorldY = null;
}
this._previewNode = this._pieceNode;
this._createAlignPreview();
this._playPreviewBlockSpine(TetriBlockSpineState.Falling);
// 主预览叠在最上层,避免被预览二号挡住
if (this._pieceNode?.isValid && this._pieceNode.parent === this.chooseMask) {
this._pieceNode.setSiblingIndex(this.chooseMask.children.length - 1);
if (preview?.isValid && preview.parent === this.chooseMask) {
preview.setSiblingIndex(this.chooseMask.children.length - 1);
}
this._createGuideLine();
}
@ -735,58 +654,50 @@ export class tetriCardItem extends Component {
this._setLayerRecursively(n, this.chooseMask.layer);
n.active = true;
n.angle = this._pieceNode.angle;
this._disablePhysicsForPreview(n);
this._disablePhysicsForPreviewNextFrame(n);
this._stripPreviewForDrag(n);
this._previewAlignNode = n;
}
/** 堆叠升高镜头拉远时与 tetriMap 一致,主预览与预览二号同比例缩小,贴近场上/白线副本观感 */
private _syncPreviewDragScaleForMapZoom(): void {
if (!this._previewDragBaseScale || !this._pieceNode?.isValid) return;
if (!this._previewDragBaseScale || !this._previewNode?.isValid) return;
const z = this._boundMap?.getVisualZoomScale?.() ?? 1;
const b = this._previewDragBaseScale;
const sx = b.x * z;
const sy = b.y * z;
const sz = b.z * z;
this._pieceNode.setScale(sx, sy, sz);
this._previewNode.setScale(sx, sy, sz);
if (this._previewAlignNode?.isValid) {
this._previewAlignNode.setScale(sx, sy, sz);
}
}
private _restorePreviewPieceBaseScale(): void {
if (this._pieceNode?.isValid && this._previewDragBaseScale) {
const b = this._previewDragBaseScale;
this._pieceNode.setScale(b.x, b.y, b.z);
}
}
private _destroyPreview(): void {
this._invalidatePreviewPhysicsDisable();
/** 结束拖拽:销毁预览克隆,模板方块始终留在卡牌上 */
private _tearDownDragPreview(): void {
this._destroyGuideLine();
this._destroyAlignPreview();
if (this._pieceNodeCopyMove && this._pieceNodeCopyMove.isValid) {
if (this._pieceNodeCopyMove?.isValid) {
this._pieceNodeCopyMove.destroy();
console.log('回收拖拽预览副 this._pieceNodeCopyMove1111');
//gg.res.putNode(this._pieceNodeCopyMove);
}
this._pieceNodeCopyMove = null;
this._pieceNodeCopyMoveFixedWorldY = null;
if (this._pieceNode && this._previewNode === this._pieceNode) {
this._restorePreviewPieceBaseScale();
// 将方块归还给卡牌
this.node.addChild(this._pieceNode);
this._pieceNode.setPosition(this._pieceOriginLocalPos);
playTetriBlockSpine(this._pieceNode, TetriBlockSpineState.Shop);
if (this._previewNode?.isValid) {
this._previewNode.destroy();
}
this._previewNode = null;
this._previewDragBaseScale = null;
this.chooseMask.active = false;
this.cancleNode.active = false
if (this._pieceNode?.isValid) {
playTetriBlockSpine(this._pieceNode, TetriBlockSpineState.Shop);
}
if (this.chooseMask) this.chooseMask.active = false;
if (this.cancleNode) this.cancleNode.active = false;
this._restoreCancelNodeScale();
this._clampRelAABB = null;
}
private _destroyPreview(): void {
this._tearDownDragPreview();
}
/**拖入取消区时做一次轻微放大反馈 */
private _playCancelHoverFeedback(): void {
if (!this.cancleNode?.isValid) return;
@ -1023,6 +934,26 @@ export class tetriCardItem extends Component {
}
}
/** 白线预览块:每帧按当前高度线重算 Y(堆叠升高时白线会平滑上移) */
private _syncWhiteLinePreviewPosition(): void {
if (gg.game.isShowBlockDrop) return;
if (!this._pieceNodeCopyMove?.isValid || !this._previewNode?.isValid) return;
const fixedY = this._getWhiteLineFixedWorldYFor(this._pieceNodeCopyMove);
if (fixedY == null) return;
const x = this._toGameWorldXFromUiWorldX(this._previewNode.worldPosition.x);
const z = this._pieceNodeCopyMove.worldPosition.z;
this._pieceNodeCopyMove.setWorldPosition(new Vec3(x, fixedY, z));
}
protected lateUpdate(): void {
if (!this._previewNode?.isValid) return;
this._syncPreviewDragScaleForMapZoom();
this._syncWhiteLinePreviewPosition();
if (this._guideLine?.active) {
this._updateGuideLine();
}
}
private _syncPreviewToTouch(event: EventTouch): void {
if (!this._previewNode || !this.chooseMask) return;
const uiPos = event.getUILocation();
@ -1063,14 +994,8 @@ export class tetriCardItem extends Component {
// this._clampPreviewByPosition(maskUT, this._previewNode);
// }
// 白线预览块:Y 固定在白线上,X 跟随主拖拽方块(受钳制后的最终位置)
if (!gg.game.isShowBlockDrop && this._pieceNodeCopyMove && this._pieceNodeCopyMove.isValid && this._pieceNodeCopyMoveFixedWorldY != null) {
const x = this._toGameWorldXFromUiWorldX(this._previewNode.worldPosition.x);
const z = this._pieceNodeCopyMove.worldPosition.z;
this._pieceNodeCopyMove.setWorldPosition(new Vec3(x, this._pieceNodeCopyMoveFixedWorldY, z));
}
this._syncPreviewDragScaleForMapZoom();
this._syncWhiteLinePreviewPosition();
let time = Date.now() - this._startTime;
const wasOverCancel = this._isOverCancel;
@ -1272,9 +1197,10 @@ export class tetriCardItem extends Component {
}
}
/** 同步主预览、白线副本、对齐预览的表情 Spine */
/** 同步主预览、白线副本、对齐预览的方块表情 Spine */
private _playPreviewBlockSpine(state: TetriBlockSpineState): void {
playTetriBlockSpine(this._pieceNode, state);
playTetriBlockSpine(this._previewNode, state);
playTetriBlockSpine(this._pieceNodeCopyMove, state);
playTetriBlockSpine(this._previewAlignNode, state);
}

@ -1,6 +1,7 @@
import { _decorator, BoxCollider2D, Collider2D, Component, Mat4, PolygonCollider2D, tween, Tween, UITransform, Vec2, Vec3, Node } from 'cc';
import { GEvent } from '../../mx/module/event/GEvent';
import { playTetriBlockSpine, TetriBlockSpineState } from './tetriBlockSpine';
import { tetriNode } from './tetriNode';
const { ccclass } = _decorator;
/**无重力方块,墙体。接触到其他方块会立马停住做为底板使用
*
@ -46,20 +47,32 @@ export class tetriFloorNode extends Component {
/** 钉住前为消除穿插而微抬:上限过小会叠模,过大易与「地面」子树装饰体严格重叠后一路抬成悬空 */
private static readonly _PIN_SEPARATE_MAX_RISE_PX = 10;
/** onLoad 前已请求脚本下落(inactive 入树时 onLoad 晚于 beginPlacementFall) */
private _placementFallPending = false;
private _stackSupportStable = false;
public setConfig(cfg: ITableBattleCube | null) {
this.config = cfg;
this._curentWeaponId = cfg?.weaponid ?? 0;
}
protected onLoad(): void {
// 默认视为“已稳定”(例如卡牌展示态);放置到地图时由 beginPlacementFall() 解锁并开始下落
this._settled = true;
this._simFallActive = false;
this._simVy = 0;
this._simVx = 0;
if (this._placementFallPending) {
this._settled = false;
this._simFallActive = true;
this._simVy = 0;
this._simVx = 0;
} else {
// 默认视为“已稳定”(例如卡牌展示态);放置到地图时由 beginPlacementFall() 解锁并开始下落
this._settled = true;
this._simFallActive = false;
this._simVy = 0;
this._simVx = 0;
}
this._ensureFloorCollidersSolidNoBounce();
this.scheduleOnce(() => {
if (!this.node?.isValid) return;
if (!this._settled || this._simFallActive) return;
playTetriBlockSpine(this.node, TetriBlockSpineState.Shop);
}, 0);
}
@ -83,6 +96,7 @@ export class tetriFloorNode extends Component {
}
get isSettled(): boolean { return this._settled; }
public hasStackSupportStable(): boolean { return this._stackSupportStable; }
/**是否正在脚本下落(用于 tetriMap 在 contact 时判定立即钉住) */
public get isSimFalling(): boolean { return this._simFallActive && !this._settled; }
@ -104,6 +118,7 @@ export class tetriFloorNode extends Component {
/**放置到地图:解锁“未企稳”并开始脚本下落 */
public beginPlacementFall(): void {
if (!this.node?.isValid) return;
this._placementFallPending = true;
this._settled = false;
playTetriBlockSpine(this.node, TetriBlockSpineState.Falling);
this.startSimulatedFall();
@ -149,10 +164,21 @@ export class tetriFloorNode extends Component {
private _pinNow(): void {
if (this._settled) return;
this._settled = true;
this._placementFallPending = false;
this._simFallActive = false;
this._simVy = 0;
this._simVx = 0;
playTetriBlockSpine(this.node, TetriBlockSpineState.Settled);
if (this._isOverlappingSupportOnTaizi()) {
this._stackSupportStable = true;
} else {
this._inheritStackSupportFromTableOverlap();
}
if (this._stackSupportStable) {
const map = (gg as any)?.game?.CurentBattle?.TetraMap;
map?.invalidateStackTopCache?.();
map?._updateHighLine?.();
}
this.scheduleOnce(() => {
if (!this.node?.isValid) return;
@ -694,6 +720,34 @@ export class tetriFloorNode extends Component {
return roots.length ? this._overlapCollidersUnderRoots(roots, sx, sy, true, true) : false;
}
private _isOverlappingSupportOnTaizi(): boolean {
const map = (gg as any)?.game?.CurentBattle?.TetraMap;
if (!map?._floorTaizi?.isValid) return false;
const sx = tetriFloorNode._SIM_CONTACT_SKIN_X;
const sy = tetriFloorNode._SIM_CONTACT_SKIN_Y;
return this._overlapCollidersUnderRoots([map._floorTaizi], sx, sy, true, true);
}
/** 钉在已有稳定塔顶的无重力板才计入堆高 */
private _inheritStackSupportFromTableOverlap(): void {
const map = (gg as any)?.game?.CurentBattle?.TetraMap;
const table = map?._tetrisTable;
if (!table?.isValid) return;
for (const child of table.children) {
if (!child?.isValid || child === this.node) continue;
const t = child.getComponent(tetriNode) ?? child.getComponentInChildren(tetriNode);
if (t?.hasStackSupportStable()) {
this._stackSupportStable = true;
return;
}
const f = child.getComponent(tetriFloorNode) ?? child.getComponentInChildren(tetriFloorNode);
if (f && f !== this && f.hasStackSupportStable()) {
this._stackSupportStable = true;
return;
}
}
}
/**
* 销毁区:地面(与脚本挡停同一套:真实碰撞体 + UI 合成地表 + skin)→ 与 tetriMap 里普通块碰地面一致,红闪 + skill。
* 怪物/落出区:仍仅严格重叠,避免容差误触杀区。

@ -181,6 +181,19 @@ export class tetriNode extends Component {
/**已落稳*/
get isSettled(): boolean { return this._settled; }
/** 场景预置在 TetrisTable 上的块(不经下落流程),参与堆高/白线 */
public markScenePlacedSettled(): void {
this._settled = true;
this._settledAtMs = Date.now();
this._stackSupportStable = true;
this.addWeapon();
}
/** 是否计入堆叠高度 / 白线(落稳且支撑链稳定) */
public shouldCountTowardStackHeight(): boolean {
return this._settled && this._stackSupportStable;
}
/**
* 是否正与至少一块「已落稳」的普通方块有物理接触(BEGIN/END 维护的计数 > 0)。
* 供 tetriMap PRE_SOLVE 识别「同时压在 floor 与堆叠块上」:与 floor 接触面很小时约束易打架,需更强衰减。
@ -386,9 +399,38 @@ export class tetriNode extends Component {
this._destroyForRemovalWithDiag(source);
}
/** 已接到地图的「着地/碰支撑」通知,尚在落稳计时中(供 tetriMap 等做叠塔角度收敛) */
private _stackSupportStable = false;
/** 是否已接到地图的「着地/碰支撑」通知,尚在落稳计时中(供 tetriMap 等做叠塔角度收敛) */
get hasSettleContact(): boolean { return this._contactNotified; }
/** 已落稳且支撑链可追溯到地面/台子/已归档稳定塔(悬空叠在另一块下落体上不计入白线) */
public hasStackSupportStable(): boolean { return this._stackSupportStable; }
/** 碰到地面/台子,或落在已有稳定支撑的块上时标记(并向上传递) */
public markStackSupportStable(): void {
if (this._stackSupportStable) return;
this._stackSupportStable = true;
const map = gg.game?.CurentBattle?.TetraMap;
map?.invalidateStackTopCache?.();
map?._updateHighLine?.();
for (const [partner, cnt] of this._touchingByContactCount) {
if (cnt > 0 && partner?.isSettled) {
partner.markStackSupportStable();
}
}
}
private _inheritStackSupportFromContacts(): void {
if (this._stackSupportStable) return;
for (const [partner, cnt] of this._touchingByContactCount) {
if (cnt > 0 && partner?.hasStackSupportStable()) {
this.markStackSupportStable();
return;
}
}
}
protected onLoad(): void {
this.settledDamp = 0
@ -433,8 +475,6 @@ export class tetriNode extends Component {
this.playBlockSpine(TetriBlockSpineState.Settled);
}
}, 0);
}
protected onEnable(): void {
@ -531,6 +571,7 @@ export class tetriNode extends Component {
this._settled = true;
this._settledAtMs = Date.now();
this.playBlockSpine(TetriBlockSpineState.Settled);
this._inheritStackSupportFromContacts();
this.onSettled?.();
if (this._isVineMode()) {
this._enterVineGroup(this._ensureVineGroupId());

@ -84,6 +84,21 @@ export class tetriWeaponCarrier extends Component {
return this._config?.weapontype ?? 0;
}
/** 放置到战场:若 start 早于 setConfig 或武器未挂上,在此补初始化(武器仍挂方块「武器」节点) */
ensureBattleReady(): void {
if (!this._config || !this._isBattleScene) return;
this.removeLegacyRoleNodes();
const conf = this.roleConfig.find(item => item.name === this._config.cubename);
if (conf) {
if (conf.handName) applyRoleHand(this.node, conf.handName);
this.applyBlockWeaponVisual(conf);
}
this._isCanShoot = true;
if (!this.weaponNode?.isValid) {
this.setView();
}
}
/** 救救我鸭:直接换武器(不走路径方块 / weapontype) */
applyWeaponId(weaponId: number, playReplaceEffect = false) {
if (!Number.isFinite(weaponId) || weaponId <= 0) return;
@ -443,10 +458,13 @@ export class tetriWeaponCarrier extends Component {
return null;
}
/** 方块未落稳不发射;无方块载体(副本直挂等)视为可随时发射 */
/** 方块未落稳不发射;无 tetriNode 时仅救救我鸭直挂角色可发射(预览/卡牌模板有 carrier 无 node 不算) */
isSettledForWeaponShoot(): boolean {
const tetri = this.findCarrierTetriNode();
return !tetri || tetri.isSettled;
if (!tetri) {
return gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya;
}
return tetri.isSettled;
}
getCarrierTetriLevel(): number {
@ -1252,6 +1270,10 @@ export class tetriWeaponCarrier extends Component {
setIsShootCan(can: boolean) {
this._isCanShoot = can
}
getIsShootCan(): boolean {
return this._isCanShoot;
}
}

@ -8,6 +8,7 @@ import { StatusType } from '../game/GameData';
import { TableNames } from '../game/ConfigTableData';
import { TetriStackDiagnostics } from './TetriStackDiagnostics';
import { BattlePerformance } from '../ui/BattleGame/BattlePerformance';
import { Physics2DGate } from '../game/Physics2DGate';
const { ccclass, property } = _decorator;
/**俄罗斯方块地图, _gameRoot用于管理方块的摆放和移动
@ -20,7 +21,7 @@ export class tetriMap extends Component {
/**出怪口 */
public spawnPointParent: Node = null;
/**最新一次放下去、正在自由下落的方块节点(由 cardItem 的 previewNode 接管而来) */
/**最新一次放下去、正在自由下落的方块节点(由 createBattlePiece 新实例化后接管) */
public latestFallingNode: Node | null = null;
/**记录 latestFallingNode 设置时间,用于避免“放下瞬间触发点击推力” */
@ -154,13 +155,14 @@ export class tetriMap extends Component {
/** 堆顶 world Y 缓存,落稳时增量更新,方块销毁时失效 */
private _cachedStackTopWorldYMax = 0;
private _stackTopCacheDirty = true;
/** 上次用于白线/镜头的已提交堆顶(塔顶未变则不移动白线) */
private _committedStackTopWorldYMax = Number.NaN;
/** 离地 0 基准:开战时锁定的地面上沿,不跟地面 Sprite 反缩放漂移 */
private _groundHeightBaselineWorldY = Number.NaN;
public invalidateStackTopCache(): void {
this._stackTopCacheDirty = true;
}
/**避免 floor 重复落稳 */
private _floorSettledSet = new Set<Node>();
/**同一对方块在销毁前可能触发多次 BEGIN_CONTACT,用 key 去重避免重复升级 */
private _mergeOnceKeys = new Set<string>();
@ -283,7 +285,7 @@ export class tetriMap extends Component {
// 确保外部在 start 之前读取时也已初始化,但这里再兜底一次
this._initNodes();
PhysicsSystem2D.instance.enable = true;
Physics2DGate.beginBattle();
console.log("2D Physics Enabled:", PhysicsSystem2D.instance.enable);
PhysicsSystem2D.instance.on(Contact2DType.BEGIN_CONTACT, this._onPhysicsContact, this);
// 地面节点在多数地图仅是 Sprite 铺底(碰撞在台子等节点);开战时归 1,堆高后再反缩放补画面
@ -780,6 +782,10 @@ export class tetriMap extends Component {
}
if (t.isSettled) return;
t.floorRbHardClearPhyStepsLeft = Math.max(t.floorRbHardClearPhyStepsLeft, 18);
const otherF = this._findInParents(other.node, (n) => n.getComponent(tetriFloorNode)).comp;
if (otherF?.isSettled && otherF.hasStackSupportStable()) {
t.markStackSupportStable();
}
// 地板块下一帧会切 Kinematic + 归位,容易在单点接触上被解算甩出角速度导致「翘起来」:先清角速度并暂时禁止旋转,落稳回调里会再打开
this._freezeTetriRotationForFloorContact(t.node);
this._ensureReparentOnSettled(t);
@ -1034,14 +1040,18 @@ export class tetriMap extends Component {
return;
}
if (t.isSettled) return;
const otherT = this._findInParents(other.node, (n) => n.getComponent(tetriNode)).comp;
if (this._floorTaizi && this._isNodeSelfOrDescendantOf(other.node, this._floorTaizi)) {
t.floorRbHardClearPhyStepsLeft = Math.max(t.floorRbHardClearPhyStepsLeft, 18);
t.markStackSupportStable();
}
if (otherT && otherT.isSettled && otherT.hasStackSupportStable()) {
t.markStackSupportStable();
}
if (t.isSettled) return;
// 之前为了避免“两块同时下落互相触发落稳”这里会跳过未落稳对方。
// 但会导致一种卡死:A/B 初次接触时都未落稳 → 永远不再触发 BEGIN_CONTACT → 后续即使其中一块先稳定,另一块也收不到 notifyContact(),从而一直 isSettled=false(不发射武器)。
// 因此:只要发生了方块接触(无论对方是否已落稳),都允许进入落稳检测;真正 settle 仍由速度/角速度阈值 + 静止帧数控制,不会“空中瞬落”。
const otherT = this._findInParents(other.node, (n) => n.getComponent(tetriNode)).comp;
// 砸到“已落稳”的块时:仅衰减下落速度,不直接清零,保留更自然的翻倒力矩
if (otherT && otherT.isSettled) {
if (BattlePerformance.shouldFreezeTetriPhysics() && otherT.isPhysicsFrozen) {
@ -1250,12 +1260,10 @@ export class tetriMap extends Component {
if (this._physicsStabilizeScheduled) return;
this._physicsStabilizeScheduled = true;
const phy = PhysicsSystem2D.instance;
const wasEnabled = phy.enable;
phy.enable = false;
// 不要开关全局 PhysicsSystem2D.enable:与激励视频等其它挂起互相覆盖,
// 会导致 enable 永久为 false → 子弹碰撞失效、怪不掉血。此处只刹速度即可。
this.scheduleOnce(() => {
this._physicsStabilizeScheduled = false;
// 只刹住已落稳 Dynamic,不 syncSceneToPhysics(避免把错误世界坐标写进刚体)
if (this._tetrisTable?.isValid) {
const rbs = this._tetrisTable.getComponentsInChildren(RigidBody2D) ?? [];
for (let i = 0; i < rbs.length; i++) {
@ -1267,9 +1275,7 @@ export class tetriMap extends Component {
rb.angularVelocity = 0;
}
}
if (wasEnabled) phy.enable = true;
this._updateHighLine();
// 立刻对齐镜头,避免数帧插值期间画面与物理错位
if (this._gameCamera?.isValid && this._cameraTargetOrthoHeight > 0) {
this._gameCamera.orthoHeight = this._cameraTargetOrthoHeight;
const camPos = this._gameCamera.node.position;
@ -1450,6 +1456,9 @@ export class tetriMap extends Component {
public setLatestFallingNode(node: Node | null) {
this.latestFallingNode = node?.isValid ? node : null;
this._latestFallingSetMs = Date.now();
if (this.latestFallingNode) {
this.invalidateStackTopCache();
}
}
/**当前镜头对应的视觉缩放(1=正常,<1=镜头拉远后视觉变小);优先用实时 ortho,与画面一致 */
@ -1688,6 +1697,13 @@ export class tetriMap extends Component {
const stackTopWorldYMax = this._resolveStackTopWorldYMax(groundWorldYMax);
const stackTopChanged = !Number.isFinite(this._committedStackTopWorldYMax)
|| Math.abs(stackTopWorldYMax - this._committedStackTopWorldYMax) > 0.5;
if (!stackTopChanged) {
return;
}
this._committedStackTopWorldYMax = stackTopWorldYMax;
this._syncBattleStackHeightMeters(stackTopWorldYMax, groundWorldYMax, curScale);
GEvent.Ins.emit(GEvent.TetriStackHeightNeedRefresh);
@ -1739,17 +1755,9 @@ export class tetriMap extends Component {
return worldYMax;
}
/** 单块落稳时增量更新堆顶缓存,避免每 0.3s 全表 getBoundingBoxToWorld */
private _noteBlockStackTop(child: Node): void {
if (!child?.isValid || !this.shouldTetrisChildCountForStackHeight(child)) {
this.invalidateStackTopCache();
return;
}
const groundWorldYMax = this._getGroundHeightBaselineWorldY();
const y = this._worldYMaxOfTetrisChild(child);
const nextTop = Math.max(this._cachedStackTopWorldYMax, y, groundWorldYMax);
this._cachedStackTopWorldYMax = nextTop;
this._stackTopCacheDirty = false;
/** 单块落稳归档后:全量重算堆顶,避免增量缓存把下落中高度锁死 */
private _noteBlockStackTop(_child: Node): void {
this.invalidateStackTopCache();
}
private _resolveStackTopWorldYMax(groundWorldYMax: number): number {
@ -1757,10 +1765,12 @@ export class tetriMap extends Component {
return this._cachedStackTopWorldYMax;
}
let stackTopWorldYMax = groundWorldYMax;
for (const child of this._tetrisTable.children) {
if (!this.shouldTetrisChildCountForStackHeight(child)) continue;
const worldYMax = this._worldYMaxOfTetrisChild(child);
if (worldYMax > stackTopWorldYMax) stackTopWorldYMax = worldYMax;
if (this._tetrisTable?.isValid) {
for (const child of this._tetrisTable.children) {
if (!this.shouldTetrisChildCountForStackHeight(child)) continue;
const worldYMax = this._worldYMaxOfTetrisChild(child);
if (worldYMax > stackTopWorldYMax) stackTopWorldYMax = worldYMax;
}
}
this._cachedStackTopWorldYMax = stackTopWorldYMax;
this._stackTopCacheDirty = false;
@ -1949,12 +1959,15 @@ export class tetriMap extends Component {
return this._tetrisTable.children.length;
}
/** 堆顶/高度统计:未激活、无重力地板未钉住(含引导「尚未下落」阶段)不参与 */
/** 堆顶/高度统计:仅 TetrisTable 上已落稳归档的块 */
public shouldTetrisChildCountForStackHeight(child: Node | null): boolean {
if (!child?.isValid || !child.active) return false;
const floor = child.getComponent(tetriFloorNode);
if (floor && !floor.isSettled) return false;
return true;
if (!this._tetrisTable?.isValid || child.parent !== this._tetrisTable) return false;
const floor = child.getComponent(tetriFloorNode) ?? child.getComponentInChildren(tetriFloorNode);
if (floor) return floor.isSettled && floor.hasStackSupportStable();
const tetri = child.getComponent(tetriNode) ?? child.getComponentInChildren(tetriNode);
if (tetri) return tetri.shouldCountTowardStackHeight();
return false;
}
//初始章节TetrisTable上有方块添加对应的角色和武器
@ -1968,12 +1981,16 @@ export class tetriMap extends Component {
// console.log('tetriConf2', tetriConf2);
let Tetris_att_J = this._tetrisTable.getChildByName('Tetris_att_J');
Tetris_att_J.getOrAddComponent(tetriNode).setConfig(tetriConf2)
Tetris_att_J.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConf2, false, true)
const jNode = Tetris_att_J.getOrAddComponent(tetriNode);
jNode.setConfig(tetriConf2);
Tetris_att_J.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConf2, false, true);
jNode.addWeapon();
let Tetris_att_L = this._tetrisTable.getChildByName('Tetris_att_L');
Tetris_att_L.getOrAddComponent(tetriNode).setConfig(tetriConf)
Tetris_att_L.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConf, false, true)
const lNode = Tetris_att_L.getOrAddComponent(tetriNode);
lNode.setConfig(tetriConf);
Tetris_att_L.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConf, false, true);
lNode.addWeapon();
let Tetris_floor_7d = this._tetrisTable.getChildByName('Tetris_floor_7d');
let tetriConf3 = battleCubeList.find(item => item.id == 37);
@ -2017,7 +2034,6 @@ export class tetriMap extends Component {
Tetris_att_J.getOrAddComponent(tetriWeaponCarrier).setIsShootCan(true);
Tetris_att_L.getOrAddComponent(tetriWeaponCarrier).setIsShootCan(true);
}
})
@ -2126,6 +2142,7 @@ export class tetriMap extends Component {
//美女待机
showMeinvIdleEffect() {
if (this.meinvAnimState === '胜利') return;
const wallTarget1 = find('目标点/wallTarget1', this.node);
if(!wallTarget1 || !wallTarget1.isValid){
return;
@ -2208,6 +2225,7 @@ export class tetriMap extends Component {
}
_onPlayerHit(){
if (gg.game?.CurentBattle?.IsGameOver) return;
this.showMeinvBeHitEffect();
if(this.timeHit){
clearTimeout(this.timeHit);
@ -2223,6 +2241,7 @@ export class tetriMap extends Component {
//美女受击动画
showMeinvBeHitEffect() {
if (this.meinvAnimState === '胜利') return;
const wallTarget1 = find('目标点/wallTarget1', this.node);
if(!wallTarget1 || !wallTarget1.isValid){
return;

@ -639,16 +639,32 @@ export class BattleCore {
}
/** 打开失败/复活结算(强制弹层,并关闭会挡住的交互弹窗) */
private showBattleSettlementPanel(useRevive: boolean) {
const panelId = useRevive ? UIID.BattleRevive : UIID.BattleFail;
if (useRevive) {
this.pause('BattleRevive');
this.IsGameOver = true;
} else {
this.gameOver(false);
}
private async showBattleSettlementPanel(useRevive: boolean): Promise<void> {
let panelId = useRevive ? UIID.BattleRevive : UIID.BattleFail;
const pauseReason = useRevive ? 'BattleRevive' : 'BattleFail';
this.pause(pauseReason);
this._closeSettlementBlockers();
gg.ui.openBattleSettlementPanel(panelId);
let ok = await gg.ui.openBattleSettlementPanel(panelId);
if (!ok && useRevive) {
console.warn('[BattleCore] BattleRevive open failed, fallback BattleFail');
panelId = UIID.BattleFail;
useRevive = false;
ok = await gg.ui.openBattleSettlementPanel(panelId);
}
if (ok) {
if (useRevive) {
this.IsGameOver = true;
} else {
this.gameOver(false);
}
return;
}
console.error('[BattleCore] settlement panel open failed, rollback pause');
this.resume();
gg.ui.showToast('界面加载失败,请重试');
}
/** 关闭会挡住战斗结算弹窗的交互层 */
@ -663,10 +679,18 @@ export class BattleCore {
/** 通关胜利:2s 后直连打开 BattleSuccess(不走 pop 队列,避免被转盘/技能箱挡住) */
private async _finishWinSettle(): Promise<void> {
this._winSettleTimer = null;
this.gameOver(true);
this.pause('BattleSuccess');
this._closeSettlementBlockers();
await gg.ui.openBattleSettlementPanel(UIID.BattleSuccess);
gg.data.saveToServer(null, true);
const ok = await gg.ui.openBattleSettlementPanel(UIID.BattleSuccess);
if (ok) {
this.gameOver(true);
gg.data.saveToServer(null, true);
return;
}
console.error('[BattleCore] BattleSuccess open failed, rollback pause');
this._isWinSettling = false;
this.resume();
gg.ui.showToast('界面加载失败,请重试');
}
/** 复活等:重置雷电爱心计数 */
@ -1049,9 +1073,12 @@ export class BattleCore {
// Boss 模式:预警期间不 pause,且仍派发武器事件,便于继续攻击场上怪物;出怪时间仍在预警结束后再走(不推进 CurentWaveTime)。
const allowCombatWhileWaveWarn =
this.BattleType == BattleType.BossMode && this.IsWarnTimeActive;
// 第一章引导:出怪后 J/L 需射击,但 isSpwanMonster 要等 startWarnTime 才 true
const allowGuideCombat = gg.game.IsPlayingGuideStory
&& (this.MonsterSpawner?.node?.children?.length ?? 0) > 0;
if (
this.canUpdateFrame() &&
this.isSpwanMonster &&
(this.isSpwanMonster || allowGuideCombat) &&
(!this.IsWarnTimeActive || allowCombatWhileWaveWarn)
) {
let dTime = dt * this.TimeScale;
@ -1335,6 +1362,19 @@ export class BattleCore {
BattleDiagnostics.onResumeSuccess();
}
/**
* 激励视频 onClose / 从全屏广告回到画布:仅当 pause 原因为 videoAd 时恢复。
* 不走 getResumeBlocker,避免广告期间 UI 状态残留误挡 resume。
*/
resumeFromVideoAdSdk(): void {
if (!this.IsGamePause) return;
if (BattleDiagnostics.getPauseReason() !== 'videoAd') return;
this.IsGamePause = false;
this.CanMove = true;
BattleDiagnostics.onResumeSuccess();
this.SkillController?.refreshDroneOrLaserLaunch?.();
}
/**
* 拆层渲染节点的统一清理入口(方块移除 / 周期兜底)。
* @param blockRoot 指定方块时清 followTarget 绑定该方块(及子树)的 ui_roleLayer 角色

@ -23,6 +23,8 @@ import MTools from 'db://assets/mx/tools/MTools';
import { GEvent } from 'db://assets/mx/module/event/GEvent';
import { tetriWeaponCarrier } from '../../tetriCard/tetriWeaponCarrier';
import { TableNames } from '../../game/ConfigTableData';
import { Physics2DGate } from '../../game/Physics2DGate';
import { tetriMap } from '../../tetriMap/tetriMap';
const { ccclass, property } = _decorator;
@ccclass('UIjiujiuwoyaGame')
@ -67,13 +69,14 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
onEnable(): void {
super.onEnable();
this._ensureJiujiuwoyaPhysics();
GEvent.Ins.on(GEvent.playerHit, this.onPlayerHit, this);
GEvent.Ins.on(GEvent.tetriGameStart, this.onTetriGameStart, this);
GEvent.Ins.on(GEvent.GameReborn, this.gameReborn, this);
GEvent.Ins.on(GEvent.UpdateDps, this.updateDps, this);
GEvent.Ins.on(GEvent.UpdateWallHp, this.refreshWallHp, this);
GEvent.Ins.on(GEvent.BattleWaveRefresh, this.onBattleWaveRefresh, this);
//PhysicsSystem2D.instance.on(Contact2DType.PRE_SOLVE, this._onMubanPreSolve, this);
PhysicsSystem2D.instance.on(Contact2DType.PRE_SOLVE, this._onMubanPreSolve, this);
}
onDisable(): void {
@ -84,7 +87,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
GEvent.Ins.off(GEvent.UpdateDps, this.updateDps, this);
GEvent.Ins.off(GEvent.UpdateWallHp, this.refreshWallHp, this);
GEvent.Ins.off(GEvent.BattleWaveRefresh, this.onBattleWaveRefresh, this);
//PhysicsSystem2D.instance.off(Contact2DType.PRE_SOLVE, this._onMubanPreSolve, this);
PhysicsSystem2D.instance.off(Contact2DType.PRE_SOLVE, this._onMubanPreSolve, this);
if (gg.game.CurentBattle) {
gg.game.CurentBattle.onJiujiuwoyaLuosiEnteredSlot = null;
gg.game.CurentBattle.onJiujiuwoyaLuosiPairRemovedFromSlot = null;
@ -97,6 +100,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
//如果卡槽里面有螺丝就消除最多2组
this.scheduleOnce(() => {
this._ensureJiujiuwoyaPhysics();
this.CurentBattle.jiujiuwoyaSoltFullFail = false;
this.CurentBattle.startGame()
this.eliminateTwoScrewPairs();
@ -178,6 +182,10 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
private _lastDps: number = -1;
/** 当前分支离局走 Physics2DGate.endBattle,仅救救我鸭开战时恢复物理 */
private _ensureJiujiuwoyaPhysics() {
Physics2DGate.beginBattle();
}
/** 向上找到挂 Aelement 的木板根节点 */
private _findMubanRoot(from: Node): Node | null {
@ -196,8 +204,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
}
/**
* muban1 分组全开时,仅同 layer 节点下的木板互相碰撞;
* 不同 layer 的接触在物理解算前关闭(与 tetriMap PRE_SOLVE 用法一致)。
* muban1 分层碰撞:不同 layer 互不碰撞;掉落板穿透其它木板,仅与 floor 等作用。
*/
private _onMubanPreSolve(self: Collider2D, other: Collider2D, contact: IPhysics2DContact | null): void {
if (!contact) return;
@ -206,12 +213,23 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
const aRoot = this._findMubanRoot(self.node);
const bRoot = this._findMubanRoot(other.node);
if (!aRoot || !bRoot) return;
if (!aRoot || !bRoot || aRoot === bRoot) return;
const aElem = aRoot.getComponent(Aelement);
const bElem = bRoot.getComponent(Aelement);
if (aElem?.isFalling || bElem?.isFalling) {
this._disableMubanContact(contact);
return;
}
const layerA = this._getMubanLogicLayerNode(aRoot);
const layerB = this._getMubanLogicLayerNode(bRoot);
if (!layerA || !layerB || layerA === layerB) return;
this._disableMubanContact(contact);
}
private _disableMubanContact(contact: IPhysics2DContact): void {
try {
const se = (contact as { setEnabled?: (e: boolean) => void }).setEnabled;
if (typeof se === 'function') se.call(contact, false);
@ -244,11 +262,16 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
let mapNode = gg.res.getNode(`prefab/`, `tetriMap${gg.game.CurentBattle.MapId}`, GBundle.tetriMap);
if (!mapNode) {
console.error(`tetriMap${this.CurentBattle.MapId} prefab load failed`);
gg.sdk?.pfLogKey?.('BATTLE:MAP_FAIL', `jiujiuwo:tetriMap${this.CurentBattle.MapId}`);
return;
}
mapNode.parent = this.ui_mapParentNode
this.setLayerMask(mapNode, this.ui_mapParentNode.layer);
const staleTetriMap = mapNode.getComponent(tetriMap);
if (staleTetriMap) staleTetriMap.enabled = false;
this.CurentBattle.TetraMap = null;
let jMap = mapNode.getComponent(jiujiuwoMap);
if (!jMap) {
jMap = mapNode.addComponent(jiujiuwoMap);
@ -348,6 +371,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
/** 斜坡地板:确保 Static 刚体与碰撞体已刷新进物理世界 */
private initFloorPhysics() {
this._ensureJiujiuwoyaPhysics();
const root = this.ui_floorNode;
if (!root?.isValid) return;
const floorGroup = 1 << 6;
@ -439,6 +463,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
const boardNode = gg.res.getNode('prefab/', prefabName, GBundle.jiujiuwoyaCard);
if (!boardNode?.isValid) {
console.error(`muban prefab load failed: ${prefabName}`);
gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', `muban:${prefabName}`);
this.onMubanSpawnFinished();
return;
}
@ -494,6 +519,7 @@ export class UIjiujiuwoyaGame extends Auto_jiujiuwoyaGame {
const screwNode = gg.res.getNode('prefab/', '螺丝', GBundle.jiujiuwoyaCard);
if (!screwNode?.isValid) {
console.error('螺丝 prefab load failed');
gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', '螺丝');
continue;
}
screwNode.parent = hole;

@ -207,6 +207,7 @@ export class UItetraGame extends Auto_tetriGame {
}
if (!mapNode?.isValid || !this.node?.isValid) {
console.error(`${mapName} prefab load failed`);
gg.sdk?.pfLogKey?.('BATTLE:MAP_FAIL', mapName);
gg.game.hideLoading();
return;
}
@ -573,6 +574,7 @@ export class UItetraGame extends Auto_tetriGame {
const cardItem = await gg.res.getNodeAsync(`prefab/`, 'cardItem', GBundle.tetriCards)
if (!cardItem?.isValid) {
console.error('[UItetraGame] dealCards: cardItem 加载失败')
gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', 'cardItem');
continue
}
cardItem.setParent(parentNode, false)
@ -599,6 +601,7 @@ export class UItetraGame extends Auto_tetriGame {
}
if (!cardNode?.isValid) {
console.error('[UItetraGame] dealCards: 方块预制体加载失败', tetriConfig.cubename)
gg.sdk?.pfLogKey?.('BATTLE:PREFAB_FAIL', tetriConfig.cubename);
continue
}
cardNode.setParent(cardItem, false)
@ -652,10 +655,10 @@ export class UItetraGame extends Auto_tetriGame {
}
}
if (cardComp) {
cardComp.onPlaceRequested = (self, worldPos) => {
cardComp.onPlaceRequested = (self, battleNode, worldPos) => {
this.fangkuaiNum++;
const preview = self.previewNode
const preview = battleNode;
this.ui_guideHuadong.active = false;
if (gg.data.getStatus(StatusType.IsShowPlayGuidePop) == 0) {
@ -679,122 +682,37 @@ export class UItetraGame extends Auto_tetriGame {
}
}
if (!preview) {
if (!preview?.isValid) {
self.cancelPlace()
return
return false
}
if (this.CurentBattle?.TetraMap?._gameRoot) {
this.CurentBattle.TetraMap._gameRoot.addChild(preview)
this.setLayerMask(preview, this.CurentBattle.TetraMap._gameRoot.layer)
}
for (const c of preview.getComponentsInChildren(tetriNode)) {
c.setConfig(tetriConfig)
}
for (const c of preview.getComponentsInChildren(tetriFloorNode)) {
c.setConfig(tetriConfig)
const gameRoot = this.CurentBattle?.TetraMap?._gameRoot;
if (!gameRoot?.isValid) {
self.cancelPlace()
return false
}
// floor:先进入下落态(会立刻禁用其 PolygonCollider2D),避免同帧物理步进产生一次“顶开/冲量”
const map = this.CurentBattle?.TetraMap
// 先入树前写配置/物理,避免 addChild 触发 start 时 _config 仍为空
preview.active = false
this._applyBattlePieceConfig(preview, tetriConfig)
this._primeBattlePieceRigidbodies(preview, map)
gameRoot.addChild(preview)
this.setLayerMask(preview, gameRoot.layer)
this._enableBattlePieceColliders(preview)
preview.setWorldPosition(worldPos)
preview.active = true
const floorNodes = preview.getComponentsInChildren(tetriFloorNode) ?? []
for (const f of floorNodes) {
f.beginPlacementFall()
}
// 注意:floorNode 下落阶段不应进入物理世界(否则即使没有刚体也会顶开动态块并提前触发 contact)
// 因此这里不要“一刀切”开启所有 Collider2D;floor 子树的 collider 交给 tetriFloorNode 自己在落稳时再启用。
for (const col of preview.getComponentsInChildren(PolygonCollider2D)) {
let isFloorCollider = false
let cur: Node | null = col.node
while (cur) {
if (cur.getComponent(tetriFloorNode)) { isFloorCollider = true; break }
if (cur === preview) break
cur = cur.parent
}
col.enabled = !isFloorCollider
}
for (const rb of preview.getComponentsInChildren(RigidBody2D)) {
rb.enabled = true
// floor prefab:不走 Dynamic 物理下落,保持 Static,由 tetriFloorNode 脚本模拟下落
const isFloor = preview.getComponentsInChildren(tetriFloorNode)?.some(f => {
const fn = f?.node;
if (!fn?.isValid) return false;
// rb 可能在子节点上:只要 rb 所在节点属于 floor 子树就算 floor
let cur: Node | null = rb.node;
while (cur) {
if (cur === fn) return true;
cur = cur.parent;
}
return false;
}) ?? false;
if (isFloor) {
// 下落阶段用 Kinematic(不是 Dynamic),落稳后由 tetriFloorNode/tetriMap 切回 Static
// rb.enabled = false
// rb.type = ERigidBody2DType.Static
// rb.gravityScale = 0
// rb.linearVelocity = new Vec2(0, 0)
// rb.angularVelocity = 0
// rb.fixedRotation = true
// rb.enabledContactListener = true
// rb.allowSleep = true
} else {
rb.type = ERigidBody2DType.Dynamic
const map = this.CurentBattle?.TetraMap
if (map) {
rb.gravityScale = map.fallGravityScale
rb.linearDamping = map.fallLinearDamping
} else {
if (rb.gravityScale == 0) rb.gravityScale = 1
}
rb.fixedRotation = false
rb.enabledContactListener = true
rb.allowSleep = false
rb.wakeUp()
}
this._primeBattlePieceRigidbodies(preview, map)
for (const wc of preview.getComponentsInChildren(tetriWeaponCarrier)) {
wc.ensureBattleReady()
}
preview.setWorldPosition(worldPos)
for (const t of preview.getComponentsInChildren(tetriNode)) {
t.playBlockSpine(TetriBlockSpineState.Falling);
}
this.CurentBattle?.TetraMap?.setLatestFallingNode(preview)
// 再延迟一帧强制确认下落物理:挡住任何同帧/次帧误关物理的残留
this.scheduleOnce(() => {
if (!preview?.isValid) return
for (const rb of preview.getComponentsInChildren(RigidBody2D)) {
if (!rb?.isValid) continue
const isFloor = preview.getComponentsInChildren(tetriFloorNode)?.some(f => {
const fn = f?.node
if (!fn?.isValid) return false
let cur: Node | null = rb.node
while (cur) {
if (cur === fn) return true
cur = cur.parent
}
return false
}) ?? false
if (isFloor) continue
if (rb.type !== ERigidBody2DType.Dynamic || rb.gravityScale === 0 || !rb.enabled) {
rb.enabled = true
rb.type = ERigidBody2DType.Dynamic
const map = this.CurentBattle?.TetraMap
rb.gravityScale = map?.fallGravityScale || 1
rb.linearDamping = map?.fallLinearDamping ?? rb.linearDamping
rb.fixedRotation = false
rb.allowSleep = false
rb.enabledContactListener = true
rb.wakeUp()
}
}
for (const col of preview.getComponentsInChildren(PolygonCollider2D)) {
if (!col?.isValid) continue
let isFloorCollider = false
let cur: Node | null = col.node
while (cur) {
if (cur.getComponent(tetriFloorNode)) { isFloorCollider = true; break }
if (cur === preview) break
cur = cur.parent
}
if (!isFloorCollider && !col.enabled) col.enabled = true
}
}, 0)
// 第一块方块:落地企稳后再开始 battle(只触发一次)
if (!this._startedAfterFirstPlacedSettled) {
@ -832,6 +750,7 @@ export class UItetraGame extends Auto_tetriGame {
self.consume()
this.ui_choosezhezhao.active = false
this.ui_cancleNode.active = false
return true
}
}
}
@ -1283,6 +1202,78 @@ export class UItetraGame extends Auto_tetriGame {
gg.ui.popPanel(UIID.TurnTable, { data: 3 });
}
/** 战场方块:在 addChild 之前写入玩法配置(含武器/技能),与 dealCards 一致 */
private _applyBattlePieceConfig(preview: Node, tetriConfig: ITableBattleCube): void {
if (tetriConfig.type === TetriType.Tetris_Basefloor) {
preview.getOrAddComponent(tetriFloorNode).setConfig(tetriConfig)
for (const c of preview.getComponentsInChildren(tetriFloorNode)) {
c.setConfig(tetriConfig)
}
return
}
preview.angle = 0
preview.getOrAddComponent(tetriNode).setConfig(tetriConfig)
for (const c of preview.getComponentsInChildren(tetriNode)) {
c.setConfig(tetriConfig)
}
if (tetriConfig.type === TetriType.Tetris_BaseGold || tetriConfig.type === TetriType.Tetris_Baseheal) {
preview.getOrAddComponent(tetriAbility).setConfig(tetriConfig)
for (const c of preview.getComponentsInChildren(tetriAbility)) {
c.setConfig(tetriConfig)
}
}
if (tetriConfig.type === TetriType.Tetris_Baseattk || tetriConfig.type === TetriType.Tetris_Basevine) {
preview.getOrAddComponent(tetriWeaponCarrier).setConfig(tetriConfig, true, true)
for (const c of preview.getComponentsInChildren(tetriWeaponCarrier)) {
c.setConfig(tetriConfig, true, true)
}
}
}
/** floor 子树碰撞体落稳前保持关闭,普通块全开 */
private _enableBattlePieceColliders(preview: Node): void {
for (const col of preview.getComponentsInChildren(PolygonCollider2D)) {
let isFloorCollider = false
let cur: Node | null = col.node
while (cur) {
if (cur.getComponent(tetriFloorNode)) { isFloorCollider = true; break }
if (cur === preview) break
cur = cur.parent
}
col.enabled = !isFloorCollider
}
}
/** 卡牌模板上的 Static/零重力须在进入物理世界前改回 Dynamic */
private _primeBattlePieceRigidbodies(preview: Node, map: tetriMap | null): void {
const floorComps = preview.getComponentsInChildren(tetriFloorNode) ?? []
for (const rb of preview.getComponentsInChildren(RigidBody2D)) {
rb.enabled = true
const isFloor = floorComps.some(f => {
const fn = f?.node
if (!fn?.isValid) return false
let cur: Node | null = rb.node
while (cur) {
if (cur === fn) return true
cur = cur.parent
}
return false
})
if (isFloor) continue
rb.type = ERigidBody2DType.Dynamic
if (map) {
rb.gravityScale = map.fallGravityScale
rb.linearDamping = map.fallLinearDamping
} else if (rb.gravityScale === 0) {
rb.gravityScale = 1
}
rb.fixedRotation = false
rb.enabledContactListener = true
rb.allowSleep = false
rb.wakeUp()
}
}
setLayerMask(n: Node, layerMask: number) {
n.layer = layerMask;
for (let i = 0; i < n.children.length; i++) {
@ -2218,19 +2209,15 @@ export class UItetraGame extends Auto_tetriGame {
click_btnRecoveyHp(){
//
this.CurentBattle.pause('adRecoverHp');
// 战斗暂停由 showVideoAd → _enter/_leaveVideoAdPause 对称处理,勿在此先 pause
gg.sdk.showVideoAd((res) => {
if (res) {
let curWaveNum = gg.game.CurentBattle.CurentWaveNum
gg.sdk.reportDY("inLevel", `章节${this.CurentBattle.getReportDYChapterId()}_${curWaveNum}-AD-满血`)
this.CurentBattle.setCurentWallHp(this.CurentBattle.WallMaxHp)
this.CurentBattle.curAdAddHpLeftNum++
this.CurentBattle.resume();
this.refreshWallHp()
} else {
this.CurentBattle.resume();
gg.ui.showToast('观看视频失败');
}
})

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -7,7 +7,7 @@
"enable": true,
"customSplash": {
"complete": true,
"form": "https://creator-api.cocos.com/api/form/show?sid=ae99089b0485f9e24974b28033d93cce"
"form": "https://creator-api.cocos.com/api/form/show?sid=7f63ccee4d2794e371b231be1d413914"
}
},
"removeSplash": {
@ -16,7 +16,7 @@
"enable": true,
"removeSplash": {
"complete": true,
"form": "https://creator-api.cocos.com/api/form/show?sid=ae99089b0485f9e24974b28033d93cce"
"form": "https://creator-api.cocos.com/api/form/show?sid=7f63ccee4d2794e371b231be1d413914"
}
}
}

Loading…
Cancel
Save