Compare commits

..

12 Commits

Author SHA1 Message Date
richarjiang
411ca8c772 feat: 每次进入 write level 页面都拉取最新的数据 2026-06-01 11:40:05 +08:00
richarjiang
b9f3dce173 feat: 优化 write 页面的列表展示 2026-06-01 11:36:44 +08:00
richarjiang
8d2fbbbcf0 feat: 优化通关效果 2026-05-31 21:15:21 +08:00
richarjiang
7355cb9c2e feat: 优化关卡以及选关 UI 2026-05-30 22:37:07 +08:00
richarjiang
332ec0081d perf: 优化关卡布局 2026-05-25 11:10:57 +08:00
richarjiang
3bfce30607 feat: 优化通关逻辑 2026-05-25 11:00:44 +08:00
richarjiang
2a599b0356 fix: 修复一系列 bug 2026-05-19 22:56:31 +08:00
richarjiang
43afe6085d feat: 支持音频管线 2026-05-19 21:41:10 +08:00
richarjiang
165fef318f fix: 修复分享模式进入主线冲突的 bug 2026-05-19 19:30:37 +08:00
richarjiang
d78c29000d feat: 挑战详情支持挑战人数展示 2026-05-14 17:01:56 +08:00
richarjiang
40f7be5200 feat: 接入我参与的挑战接口以及UI 2026-05-14 16:55:26 +08:00
richarjiang
dcbd32b0cd feat: 添加挑战详情页面预制体,优化分享功能与数据展示 2026-05-13 09:04:59 +08:00
55 changed files with 13196 additions and 1195 deletions

BIN
.DS_Store vendored

Binary file not shown.

122
AGENTS.md
View File

@@ -40,62 +40,80 @@ Git 历史采用 Conventional Commits且摘要多为中文例如 `feat:
<claude-mem-context>
# Memory Context
# $CMEM mp-xieyingeng 2026-05-13 8:12am GMT+8
# [mp-xieyingeng] recent context, 2026-06-01 11:37am GMT+8
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision
Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖decision 🚨security_alert 🔐security_note
Format: ID TIME TYPE TITLE
Fetch details: get_observations([IDs]) | Search: mem-search skill
Stats: 47 obs (10,663t read) | 538,681t work | 98% savings
Stats: 50 obs (15,736t read) | 2,461,717t work | 99% savings
### Apr 24, 2026
101 8:46a 🟣 Live label display format updated to X/Y format
114 6:40p 🟣 Dynamic Input Layout Initialization in PageLevel Prefab
115 6:41p 🟣 Dynamic Punch Block Layout for PageLevel.prefab
116 " 🔵 Layout Component Configuration for Input and Punch Blocks
119 6:42p 🟣 Dynamic Input Blocks and Punch Layout System Implemented
121 " 🟣 PageLevel Prefab Updated with punchLayout Property
122 " 🔄 Cleanup After Dynamic Block Refactoring
124 " ✅ TypeScript Compilation Check in Progress
126 6:43p ✅ TypeScript Compilation Check Extended
127 " 🔄 Complete Diff of PageLevel.ts Dynamic Block System
128 " 🔵 PageLevel.prefab Changes Not Persisted
129 6:44p 🟣 PageLevel Prefab Correctly Updated with punchLayout
130 " ✅ TypeScript Compilation Blocked - Permission Required
133 6:45p 🔄 Extracted getPunchBlockLabel Helper Method
134 " 🔄 Template Node Hiding Logic Improved
136 6:48p ⚖️ TypeScript diagnostics disabled, using IDE/linter instead
138 " 🔄 PageLevel 输入方式从单框改为逐字格子
139 " 🔄 谐音梗展示从 Label 改为动态 Block 节点
140 " ✅ PageLevel.prefab 布局位置微调
165 8:08p 🟣 PageLevel input layout simplified to single-character boxes with auto-distribution
167 8:09p 🔵 PageLevel.ts input block structure and callback stubs discovered
168 " 🟣 Auto-distribute characters across input boxes and auto-submit on completion implemented
169 " 🔴 PageLevel.ts node cleanup now calls removeFromParent before destroy
170 8:10p 🔄 PageLevel.ts full diff: single EditBox replaced with per-character block system
171 " 🔴 distributeInputText() wrapped in try/finally to guarantee flag reset
179 8:23p ✅ Game011_3.ttf font relocated from resources/ to dedicated fonts/ bundle directory
180 " 🟣 PageLoading.ts now loads fonts as a dynamic bundle after level data, before UI
181 " 🔄 AssetManager import switched to type-only import in PageLoading.ts
182 8:31p 🟣 PageLevel input UX improvement: full-string editing support
183 " 🟣 PageLevel input UX: full-string editing implemented
186 8:34p 🔴 PageLevel: clear input text on wrong answer
187 8:35p 🟣 PageLevel: delay pass modal to show punchline
189 8:36p 🔄 PageLevel: level completion reporting made fire-and-forget
191 8:45p 🔴 PageLevel: punchline block cleanup improved
192 8:46p 🟣 PageLevel.ts TitleLevel Label variable reserved
193 8:47p 🔵 PageLevel.ts level number tracking mechanism discovered
195 " 🟣 PageLevel.ts TitleLevel dynamic label update implemented
196 8:53p 🔵 PageLevel punchline not updated from enterLevel API
198 " 🔴 LevelDataManager updateLevelDetails now includes punchline
199 " 🔴 PageLevel punchline flow and empty state layout fixed
200 8:54p 🔴 LevelDataManager preserves existing punchline when enter returns null
203 9:06p 🟣 PageLevel.ts 新增图片描述标签绑定字段
205 9:07p 🟣 PageLevel.ts 图片描述标签字段对接完成
206 9:09p ✅ PageLevel.ts 回退图片描述标签字段命名
208 9:11p 🟣 LevelDataManager 增加图片描述字段存储
214 9:40p 🟣 拼图游戏关卡内 Punch Layout 显隐控制
216 " 🟣 PageLevel.ts punchline 显隐控制逻辑对接完成
### Apr 26, 2026
S1309 移除 PageLevel.ts 进入关卡时弹出体力扣减 toast (Apr 26 at 5:16 PM)
### Apr 27, 2026
S1308 移除进入游戏关卡时弹出体力扣减的 toast 提示 (Apr 27 at 9:21 AM)
S1310 移除进入游戏体力扣减 toast 并在首页添加体力显示 (Apr 27 at 9:22 AM)
S1311 移除体力扣减 toast 并完善首页体验 (Apr 27 at 9:23 AM)
S1313 Implement object-fit: cover image scaling in Cocos Creator to prevent downloaded images from being distorted (Apr 27 at 9:26 AM)
S1312 Fix image distortion in PageLevel.ts by implementing object-fit: cover behavior for downloaded images (Apr 27 at 9:45 AM)
1363 9:45a ✅ Start game button integrated with stamina check and animation flow
1364 " 🟣 Stamina consumption animation fully implemented in PageHome
S1314 Implement stamina consumption animation in Cocos Creator game - when clicking StarGame button, IconLive node flies to button with floating "-1" text, then navigates to level (Apr 27 at 9:45 AM)
1365 9:46a 🔴 Animation completes but navigation to PageLevel fails
S1315 为 PageLevel.ts 的 mainImage 和 mainImage2 节点实现 CSS cover 模式图片缩放 (Apr 27 at 9:46 AM)
1366 9:50a 🔴 Cocos Creator tween chain broken by premature node destroy
1367 9:55a 🔴 Navigation still failing after first tween chain fix - further debugging needed
1368 " 🔴 Decoupled fly animation from float text timing using setTimeout
1369 9:56a 🔴 Cocos Creator tween chain broken by any node state change mid-animation
1370 9:59a 🟣 CSS cover-style image scaling for PageLevel images
1371 10:01a ✅ PageLevel.ts imports extended for cover-style image scaling
1372 " 🟣 CSS cover-mode image scaling implemented for PageLevel images
S1316 Debug cover mode image overflow in PageLevel.ts - image exceeds container bounds despite Mask applied (Apr 27 at 10:02 AM)
1373 10:02a 🔴 Cover mode image overflows container bounds
1374 10:06a 🔵 GRAPHICS_RECT vs GRAPHICS_STENCIL for Mask in Cocos Creator
1375 10:08a 🔴 Mask.Type.RECT does not exist in Cocos Creator API
1376 " 🔵 MaskType enum found in Cocos Creator 3.8 engine declarations
1378 " 🔵 MaskType enum values confirmed - GRAPHICS_RECT exists
1377 10:09a 🔵 MaskType enum definition found at line 46015
1379 10:10a 🔴 Reverted Mask.Type to GRAPHICS_RECT
S1317 Implement CSS cover-mode image scaling for PageLevel.ts mainImage nodes (Apr 27 at 10:10 AM)
### Apr 29, 2026
1481 6:33p 🟣 Implementing rounded corner images in PageLevel.ts
1482 " 🔵 RoundedRectMask component already exists for image corner rounding
1485 6:34p 🟣 Implemented rounded corners for PageLevel main images
1484 " 🔄 RoundedRectMask component improved with quality standards
1487 6:37p 🔵 TypeScript verification confirms no errors in new rounded corners implementation
### May 12, 2026
1542 4:39p 🟣 CommonModal prefab adds conditional dual-button support
1543 " 🔵 CommonModal prefab contains both button node variants
1544 4:40p 🟣 CommonModal prefab upgraded with dual-button support
1545 4:41p 🟣 CommonModal prefab restructured with dual-button ActionDouble container
1546 " 🔵 BaseModal provides modal animation infrastructure
1547 4:43p 🟣 CommonModal.ts upgraded with dual-button support and backward compatibility
1548 4:44p 🟣 CommonModal dual-button implementation completed
1549 " ✅ CommonModal dual-button feature ready for testing
1550 4:45p 🟣 CommonModal dual-button feature completed and validated
1551 4:46p 🔄 CommonModal API refactoring: remove buttonHint, clean button logic
1552 4:47p 🔄 CommonModal dual-button refactor complete with cleaner API
### May 14, 2026
1630 4:59p 🟣 PagePKDetail.prefab 新增参与人数标签节点
1631 " 🟣 PagePKDetail 新增参与人数标签
### Jun 1, 2026
1771 11:07a 🔵 List Height Adaptation Issue on Different Resolution Phones in PageWriteLevels
1772 " 🔵 PageWriteLevels ScrollView Height Calculation Architecture Fully Mapped
1773 " 🔵 LevelList ScrollView Widget Has Only Top Alignment — View Node Has No Widget
1774 " 🔵 Root Cause Confirmed: VIEW_HEIGHT=1300 Floor Causes List Overflow on Short Phones
1775 11:13a 🔵 PageWriteLevels Prefab Structure Confirmed Clean
1777 " 🔄 _resizeScrollViewport Delegates Layout to Cocos Widget System
1776 11:14a 🔵 PageWriteLevels Prefab Contains 233 JSON Array Entries
1778 11:15a 🟣 PageWriteLevels ScrollView View Node Gets cc.Widget for Auto-Layout
1779 " 🔵 PageWriteLevels Layout Node Tree Verified After Widget Changes
1780 11:16a 🔵 TypeScript Check Requires Cocos Creator to Generate temp/declarations First
1781 " 🔵 New Widget Bottom Inset Produces Shorter Viewport on Small Screens vs Old Minimum
1783 " 🔵 PageWriteLevels Bottom Control Layout Constraints Mapped
1784 " ✅ ScrollView Widget Bottom Inset Revised from 559.076 to 752.53
1782 " 🔵 PageWriteLevels Prefab __id__ Reference Integrity Confirmed Valid
1785 11:19a ✅ PageWriteLevels Widget Layout Refactor Complete — Final Diff Confirmed
Access 539k tokens of past work via get_observations([IDs]) or mem-search skill.
Access 2462k tokens of past work via get_observations([IDs]) or mem-search skill.
</claude-mem-context>

View File

@@ -29,6 +29,23 @@ assets/
- **运行预览**: 在 Cocos Creator 编辑器中点击 "Play" 按钮
- **构建**: 使用编辑器菜单 `Project > Build` 或快捷键 `Cmd+B`
## Editor Operations (强制约定)
**凡是涉及 prefab、scene、node、component 或 Cocos 编辑器内的任何操作,必须使用 `cocos-creator` MCP不要手工编辑 `.prefab` / `.scene` 的 YAML/JSON 文件。**
适用范围(非穷举):
- 创建 / 修改 / 删除 prefab`mcp__cocos-creator__prefab_*`
- 场景增删改、打开/保存场景:`mcp__cocos-creator__scene_*`
- 节点结构(增删、移动、改 transform、改属性`mcp__cocos-creator__node_*`
- 组件挂载、属性赋值、引用绑定Sprite/Label/Button/自定义脚本等):`mcp__cocos-creator__component_*`
- 资源管理(导入、查询 UUID、刷新、引用校验`mcp__cocos-creator__project_*` / `mcp__cocos-creator__assetAdvanced_*`
- 场景脚本执行、调试日志、性能数据:`mcp__cocos-creator__debug_*` / `mcp__cocos-creator__sceneAdvanced_*`
允许直接编辑的文件仍是:`.ts` 脚本源代码(`assets/**/*.ts`)、纯文本配置(`tsconfig.json``package.json` 等)。`.prefab` / `.scene` / `.meta` 一律走 MCP避免 UUID 错位、引用丢失、序列化格式被破坏。
操作前先用 `scene_get_current_scene` / `node_get_all_nodes` / `prefab_get_prefab_info` 等查询类工具确认当前编辑器状态,不要凭记忆操作。
## TypeScript Coding
遵循 Cocos Creator 3.x 组件系统架构:

BIN
assets/.DS_Store vendored

Binary file not shown.

View File

@@ -4,6 +4,7 @@ import { ViewManager } from './scripts/core/ViewManager';
import { LevelDataManager } from './scripts/utils/LevelDataManager';
import { AuthManager } from './scripts/utils/AuthManager';
import { ShareManager } from './scripts/utils/ShareManager';
import { ShareLaunchHandler } from './scripts/utils/ShareLaunchHandler';
import { WxSDK } from './scripts/utils/WxSDK';
const { ccclass, property } = _decorator;
@@ -92,6 +93,9 @@ export class PageLoading extends Component {
this._updateStatusLabel('正在加载挑战关卡...');
const joinSuccess = await ShareManager.instance.joinShare(shareCode);
if (joinSuccess) {
// 把启动 shareCode 同步给 ShareLaunchHandler
// 避免 wx.onShow 在初始展示时拿到同一个 code 又走一遍 join。
ShareLaunchHandler.instance.markActiveShareCode(shareCode);
this._updateProgress(1);
this._updateStatusLabel('加载完成');
// 跳过首页,直接进入分享挑战关卡
@@ -134,6 +138,20 @@ export class PageLoading extends Component {
this._updateProgress(1);
this._updateStatusLabel('加载完成');
// 兜底:如果在 PageLoading 还在 preload 期间wx.onShow 已经把游戏切到了分享态
// ShareLaunchHandler 已经 joinShare 成功),这里就不应再覆盖回首页,
// 否则用户点了好友分享卡片却看到 PageHome。
if (ShareManager.instance.isShareMode) {
console.log('[PageLoading] 检测到分享态已激活,跳过首页直达 PageLevel');
ViewManager.instance.open('PageLevel', {
params: { shareMode: true },
onComplete: () => {
this.node.destroy();
},
});
return;
}
ViewManager.instance.open('PageHome', {
onComplete: () => {
this.node.destroy();

View File

@@ -503,6 +503,10 @@
"__uuid__": "1b94d42b-a4db-4c0a-8281-4275786810af",
"__expectedType__": "cc.Prefab"
},
"pagePKDetailPrefab": {
"__uuid__": "335e525c-17ae-4608-8fe0-23b3fc8a5608",
"__expectedType__": "cc.Prefab"
},
"pagePKEndPrefab": {
"__uuid__": "4fc485cf-8c22-47f2-80d0-ae366a380cb6",
"__expectedType__": "cc.Prefab"
@@ -511,6 +515,10 @@
"__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54",
"__expectedType__": "cc.Prefab"
},
"buttonClickAudio": {
"__uuid__": "798824f1-0e20-48b7-ad8a-fb24d55bf986",
"__expectedType__": "cc.AudioClip"
},
"_id": "c2b3nbzv9JuZmP2jxQyN72"
},
{

View File

@@ -1,6 +1,8 @@
import { _decorator, Component, Prefab } from 'cc';
import { _decorator, Component, Prefab, AudioClip } from 'cc';
import { ViewManager } from './scripts/core/ViewManager';
import { ToastManager } from './scripts/utils/ToastManager';
import { AudioManager } from './scripts/utils/AudioManager';
import { ShareLaunchHandler } from './scripts/utils/ShareLaunchHandler';
const { ccclass, property } = _decorator;
/**
@@ -24,12 +26,18 @@ export class main extends Component {
@property({ type: Prefab, tooltip: '挑战数据页面预制体' })
pagePKDataPrefab: Prefab | null = null;
@property({ type: Prefab, tooltip: '挑战详情页面预制体' })
pagePKDetailPrefab: Prefab | null = null;
@property({ type: Prefab, tooltip: '挑战结算页面预制体' })
pagePKEndPrefab: Prefab | null = null;
@property({ type: Prefab, tooltip: 'Toast 预制体' })
toastPrefab: Prefab | null = null;
@property({ type: AudioClip, tooltip: '通用按钮点击音效' })
buttonClickAudio: AudioClip | null = null;
/**
* onLoad 比 start 更早执行
* 确保 ViewManager 在 PageLoading.start() 之前初始化
@@ -86,6 +94,14 @@ export class main extends Component {
});
}
if (this.pagePKDetailPrefab) {
ViewManager.instance.register('PagePKDetail', {
prefab: this.pagePKDetailPrefab,
cache: true,
zIndex: 4
});
}
if (this.pagePKEndPrefab) {
ViewManager.instance.register('PagePKEnd', {
prefab: this.pagePKEndPrefab,
@@ -98,5 +114,12 @@ export class main extends Component {
if (this.toastPrefab) {
ToastManager.instance.init(this.toastPrefab, this.node);
}
AudioManager.instance.init(this.buttonClickAudio, this.node);
// 注册 wx.onShow / wx.onHide
// 用户把小游戏退到后台后再点击好友分享卡片,能拿到最新的 shareCode 并直达分享挑战关卡。
// 必须在 PageLoading 跑之前注册,这样初始 launch 中的 shareCode 也会被作为种子记下。
ShareLaunchHandler.instance.init();
}
}

View File

@@ -5471,7 +5471,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 442.03125,
"width": 461.40625,
"height": 75.6
},
"_anchorPoint": {
@@ -5507,7 +5507,7 @@
"b": 0,
"a": 255
},
"_string": "还差3题获得冷场小白2级",
"_string": "还差3题,解锁新成就等级",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 40,

View File

@@ -7,6 +7,8 @@ import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { StaminaInfo } from 'db://assets/scripts/types/ApiTypes';
import { AuthManager } from 'db://assets/scripts/utils/AuthManager';
import { AchievementTitleManager } from 'db://assets/scripts/utils/AchievementTitleManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;
/**
@@ -59,7 +61,7 @@ export class PageHome extends BaseView {
/** 是否正在播放体力消耗动画 */
private _isAnimating: boolean = false;
/** 进度游标 0% 时的本地 X 坐标,使用 prefab 当前摆放位置作为起点 */
/** 进度游标 0% 时的本地 X 坐标,根据 ProgressBar Bar 子节点的左端推导出来 */
private _progressAnchorStartX: number | null = null;
/**
@@ -102,6 +104,7 @@ export class PageHome extends BaseView {
private _onStartGameClick(): void {
if (this._isAnimating) return;
AudioManager.instance.playButtonClick();
console.log('[PageHome] 开始游戏按钮点击');
// 体力检查
@@ -110,15 +113,23 @@ export class PageHome extends BaseView {
return;
}
// 兜底:清空可能残留的好友分享挑战状态,确保进入的是纯主线挑战
// 场景:用户从微信好友分享卡片进入挑战 → 退出回首页 → 再次点击开始游戏
// 若不清理,缓存的 PageLevel 仍会读到 ShareManager 的分享态数据
if (ShareManager.instance.isShareMode) {
console.log('[PageHome] 检测到残留的分享挑战状态,清理后再进入主线挑战');
ShareManager.instance.clearShareMode();
}
this._isAnimating = true;
this._playStaminaCostAnimation()
.then(() => {
ViewManager.instance.open('PageLevel');
ViewManager.instance.open('PageLevel', { params: { shareMode: false } });
})
.catch(err => {
console.error('[PageHome] 体力消耗动画异常:', err);
// 异常兜底:直接进入关卡
ViewManager.instance.open('PageLevel');
ViewManager.instance.open('PageLevel', { params: { shareMode: false } });
})
.finally(() => {
this._isAnimating = false;
@@ -129,6 +140,7 @@ export class PageHome extends BaseView {
* PK按钮点击回调
*/
private _onPkClick(): void {
AudioManager.instance.playButtonClick();
console.log('[PageHome] PK按钮点击');
ViewManager.instance.open('PageWriteLevels');
}
@@ -351,11 +363,23 @@ export class PageHome extends BaseView {
}
private _cacheProgressAnchorStartX(): void {
if (this._progressAnchorStartX !== null || !this.progressAnchor) {
if (this._progressAnchorStartX !== null || !this.titleProgressBar) {
return;
}
this._progressAnchorStartX = this.progressAnchor.position.x;
const barSprite = this.titleProgressBar.barSprite;
if (!barSprite) {
return;
}
// Bar 节点 anchor 为 (0, 0.5),其本地 position.x 即为进度条可视左端。
// ProgressBar 与 ProgressAnchor 共享同一父节点TitleLevel
// 因此把 Bar 的本地 X 按 ProgressBar 自身的位移与缩放映射到父节点空间,
// 才是真正的「0% 起点」。直接拿 progressAnchor.position.x 当起点会导致
// 气泡始终被 prefab 摆放偏移量带跑(实测偏右 ~24px
const progressBarNode = this.titleProgressBar.node;
const barLocalX = barSprite.node.position.x;
this._progressAnchorStartX = progressBarNode.position.x + barLocalX * progressBarNode.scale.x - 30;
}
private _updateProgressAnchor(progress: number): void {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -37,20 +37,20 @@
"__id__": 180
},
{
"__id__": 266
"__id__": 272
}
],
"_active": true,
"_components": [
{
"__id__": 282
"__id__": 288
},
{
"__id__": 284
"__id__": 290
}
],
"_prefab": {
"__id__": 286
"__id__": 292
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4347,20 +4347,20 @@
"__id__": 197
},
{
"__id__": 257
"__id__": 263
}
],
"_active": true,
"_components": [
{
"__id__": 263
"__id__": 269
},
{
"__id__": 194
}
],
"_prefab": {
"__id__": 265
"__id__": 271
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4417,7 +4417,7 @@
}
],
"_prefab": {
"__id__": 256
"__id__": 262
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4724,11 +4724,11 @@
"_active": true,
"_components": [
{
"__id__": 253
"__id__": 259
}
],
"_prefab": {
"__id__": 255
"__id__": 261
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -4937,27 +4937,27 @@
{
"__id__": 206
},
{
"__id__": 230
},
{
"__id__": 236
},
{
"__id__": 242
},
{
"__id__": 248
}
],
"_active": true,
"_components": [
{
"__id__": 248
"__id__": 254
},
{
"__id__": 250
"__id__": 256
}
],
"_prefab": {
"__id__": 252
"__id__": 258
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -5005,19 +5005,22 @@
},
{
"__id__": 219
},
{
"__id__": 225
}
],
"_active": true,
"_components": [
{
"__id__": 225
"__id__": 231
},
{
"__id__": 227
"__id__": 233
}
],
"_prefab": {
"__id__": 229
"__id__": 235
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -5456,6 +5459,165 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "RankNumber",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 206
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 226
},
{
"__id__": 228
}
],
"_prefab": {
"__id__": 230
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -9.887,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2.387,
"y": 2.387,
"z": 2.387
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 225
},
"_enabled": true,
"__prefab": {
"__id__": 227
},
"_contentSize": {
"__type__": "cc.Size",
"width": 65.615234375,
"height": 136
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "afc2A9N6FGc7UuXiK197iF"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 225
},
"_enabled": true,
"__prefab": {
"__id__": 229
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 236,
"g": 236,
"b": 236,
"a": 255
},
"_string": "4",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 100,
"_fontSize": 100,
"_fontFamily": "Arial",
"_lineHeight": 100,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": true,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 5,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "59LYxrApZB/bVeRXmT+WzJ"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "afXTpxGqpP+IgoyzMhucMS",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -5466,7 +5628,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 226
"__id__": 232
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5494,7 +5656,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 228
"__id__": 234
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5554,14 +5716,14 @@
"_active": true,
"_components": [
{
"__id__": 231
"__id__": 237
},
{
"__id__": 233
"__id__": 239
}
],
"_prefab": {
"__id__": 235
"__id__": 241
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -5598,15 +5760,15 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 230
"__id__": 236
},
"_enabled": true,
"__prefab": {
"__id__": 232
"__id__": 238
},
"_contentSize": {
"__type__": "cc.Size",
"width": 490,
"width": 549.0625,
"height": 88.2
},
"_anchorPoint": {
@@ -5626,11 +5788,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 230
"__id__": 236
},
"_enabled": true,
"__prefab": {
"__id__": 234
"__id__": 240
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5642,15 +5804,15 @@
"b": 29,
"a": 255
},
"_string": "西瓜太郎的挑战",
"_horizontalAlign": 1,
"_string": "西瓜太郎12",
"_horizontalAlign": 0,
"_verticalAlign": 1,
"_actualFontSize": 70,
"_fontSize": 70,
"_fontFamily": "Arial",
"_lineHeight": 70,
"_overflow": 0,
"_enableWrapText": true,
"_overflow": 1,
"_enableWrapText": false,
"_font": {
"__uuid__": "fb4acba6-6bc7-4eb3-be34-8f2ac9823a80",
"__expectedType__": "cc.TTFFont"
@@ -5716,14 +5878,14 @@
"_active": true,
"_components": [
{
"__id__": 237
"__id__": 243
},
{
"__id__": 239
"__id__": 245
}
],
"_prefab": {
"__id__": 241
"__id__": 247
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -5760,11 +5922,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 236
"__id__": 242
},
"_enabled": true,
"__prefab": {
"__id__": 238
"__id__": 244
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5788,11 +5950,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 236
"__id__": 242
},
"_enabled": true,
"__prefab": {
"__id__": 240
"__id__": 246
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -5852,14 +6014,14 @@
"_active": true,
"_components": [
{
"__id__": 243
"__id__": 249
},
{
"__id__": 245
"__id__": 251
}
],
"_prefab": {
"__id__": 247
"__id__": 253
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -5896,11 +6058,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 242
"__id__": 248
},
"_enabled": true,
"__prefab": {
"__id__": 244
"__id__": 250
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5924,11 +6086,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 242
"__id__": 248
},
"_enabled": true,
"__prefab": {
"__id__": 246
"__id__": 252
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -6012,7 +6174,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 249
"__id__": 255
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6040,7 +6202,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 251
"__id__": 257
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -6098,7 +6260,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 254
"__id__": 260
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6154,14 +6316,14 @@
"_active": true,
"_components": [
{
"__id__": 258
"__id__": 264
},
{
"__id__": 260
"__id__": 266
}
],
"_prefab": {
"__id__": 262
"__id__": 268
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -6198,11 +6360,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 257
"__id__": 263
},
"_enabled": true,
"__prefab": {
"__id__": 259
"__id__": 265
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6226,11 +6388,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 257
"__id__": 263
},
"_enabled": true,
"__prefab": {
"__id__": 261
"__id__": 267
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -6288,7 +6450,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 264
"__id__": 270
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6329,26 +6491,26 @@
},
"_children": [
{
"__id__": 267
"__id__": 273
}
],
"_active": true,
"_components": [
{
"__id__": 273
},
{
"__id__": 275
},
{
"__id__": 277
},
{
"__id__": 279
},
{
"__id__": 281
},
{
"__id__": 283
},
{
"__id__": 285
}
],
"_prefab": {
"__id__": 281
"__id__": 287
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -6385,20 +6547,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 266
"__id__": 272
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 268
"__id__": 274
},
{
"__id__": 270
"__id__": 276
}
],
"_prefab": {
"__id__": 272
"__id__": 278
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -6435,11 +6597,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 267
"__id__": 273
},
"_enabled": true,
"__prefab": {
"__id__": 269
"__id__": 275
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6463,11 +6625,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 267
"__id__": 273
},
"_enabled": true,
"__prefab": {
"__id__": 271
"__id__": 277
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -6547,11 +6709,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 266
"__id__": 272
},
"_enabled": true,
"__prefab": {
"__id__": 274
"__id__": 280
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6575,11 +6737,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 266
"__id__": 272
},
"_enabled": true,
"__prefab": {
"__id__": 276
"__id__": 282
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -6620,11 +6782,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 266
"__id__": 272
},
"_enabled": true,
"__prefab": {
"__id__": 278
"__id__": 284
},
"_alignFlags": 20,
"_target": null,
@@ -6656,11 +6818,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 266
"__id__": 272
},
"_enabled": true,
"__prefab": {
"__id__": 280
"__id__": 286
},
"clickEvents": [],
"_interactable": true,
@@ -6700,7 +6862,7 @@
"_duration": 0.1,
"_zoomScale": 1.2,
"_target": {
"__id__": 266
"__id__": 272
},
"_id": ""
},
@@ -6731,7 +6893,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 283
"__id__": 289
},
"_contentSize": {
"__type__": "cc.Size",
@@ -6759,10 +6921,10 @@
},
"_enabled": true,
"__prefab": {
"__id__": 285
"__id__": 291
},
"backBtn": {
"__id__": 266
"__id__": 272
},
"createdListContent": {
"__id__": 54
@@ -6770,6 +6932,12 @@
"createdListItemTemplate": {
"__id__": 63
},
"participatedListContent": {
"__id__": 196
},
"participatedListItemTemplate": {
"__id__": 205
},
"_id": ""
},
{

View File

@@ -1,9 +1,10 @@
import { _decorator, Node, Button, instantiate, Label, ScrollView, UITransform, Sprite, SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { CreatedShareItem, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes';
import { CreatedShareItem, ParticipatedShareItem, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;
@ccclass('PagePKData')
@@ -11,6 +12,9 @@ export class PagePKData extends BaseView {
private static readonly CREATED_ITEM_TOP_PADDING = 20;
private static readonly CREATED_ITEM_BOTTOM_PADDING = 20;
private static readonly CREATED_ITEM_SPACING = 20;
private static readonly PARTICIPATED_ITEM_TOP_PADDING = 20;
private static readonly PARTICIPATED_ITEM_BOTTOM_PADDING = 20;
private static readonly PARTICIPATED_ITEM_SPACING = 20;
@property({ type: Node, tooltip: '返回按钮' })
backBtn: Node | null = null;
@@ -21,8 +25,16 @@ export class PagePKData extends BaseView {
@property({ type: Node, tooltip: '我创建的挑战列表条目模板 FriendsPKRankListItem' })
createdListItemTemplate: Node | null = null;
@property({ type: Node, tooltip: '我参与的挑战列表 content 节点' })
participatedListContent: Node | null = null;
@property({ type: Node, tooltip: '我参与的挑战列表条目模板 MyPKListItem' })
participatedListItemTemplate: Node | null = null;
private _createdShares: CreatedShareItem[] = [];
private _participatedShares: ParticipatedShareItem[] = [];
private _createdItemNodes: Node[] = [];
private _participatedItemNodes: Node[] = [];
private _createdButtonBindings: Array<{ node: Node; handler: () => void }> = [];
private _isLoading: boolean = false;
private _renderVersion: number = 0;
@@ -33,36 +45,45 @@ export class PagePKData extends BaseView {
this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this);
}
this._hideCreatedItemTemplate();
this._hideParticipatedItemTemplate();
}
onViewShow(): void {
this._resolveNodes();
void this._loadCreatedShares();
void this._loadShareLists();
}
onViewHide(): void {
this._renderVersion++;
}
private _onBackClick(): void {
AudioManager.instance.playButtonClick();
ViewManager.instance.back();
}
private async _loadCreatedShares(): Promise<void> {
private async _loadShareLists(): Promise<void> {
if (this._isLoading) {
return;
}
this._isLoading = true;
try {
const items = await ShareManager.instance.fetchCreatedShares();
if (!items) {
const [createdItems, participatedItems] = await Promise.all([
ShareManager.instance.fetchCreatedShares(),
ShareManager.instance.fetchParticipatedShares(),
]);
if (!createdItems || !participatedItems) {
ToastManager.instance.show('获取挑战列表失败,请稍后重试');
return;
}
this._createdShares = items;
this._createdShares = createdItems ?? [];
this._participatedShares = participatedItems ?? [];
console.log('[PagePKData] 我创建的挑战列表:', this._createdShares);
console.log('[PagePKData] 我参与的挑战列表:', this._participatedShares);
this._renderCreatedShares();
this._renderParticipatedShares();
} finally {
this._isLoading = false;
}
@@ -73,6 +94,7 @@ export class PagePKData extends BaseView {
this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this);
}
this._clearCreatedItems();
this._clearParticipatedItems();
}
private _resolveNodes(): void {
@@ -87,12 +109,25 @@ export class PagePKData extends BaseView {
?? this.createdListContent?.getChildByName('FriendsPKRankListItem')
?? null;
const participatedList = this.node.getChildByName('MyPKList');
const participatedView = participatedList?.getChildByName('view');
this.participatedListContent = this.participatedListContent ?? participatedView?.getChildByName('content') ?? null;
this.participatedListItemTemplate = this.participatedListItemTemplate
?? this.participatedListContent?.getChildByName('MyPKListItem')
?? null;
if (!this.createdListContent) {
console.warn('[PagePKData] 未找到 FriendsPKRankList/content 节点');
}
if (!this.createdListItemTemplate) {
console.warn('[PagePKData] 未找到 FriendsPKRankListItem 模板节点');
}
if (!this.participatedListContent) {
console.warn('[PagePKData] 未找到 MyPKList/content 节点');
}
if (!this.participatedListItemTemplate) {
console.warn('[PagePKData] 未找到 MyPKListItem 模板节点');
}
}
private _renderCreatedShares(): void {
@@ -119,6 +154,29 @@ export class PagePKData extends BaseView {
this._scrollCreatedListToTop();
}
private _renderParticipatedShares(): void {
this._clearParticipatedItems();
if (!this.participatedListContent || !this.participatedListItemTemplate) {
return;
}
this._layoutParticipatedList(this._participatedShares.length);
this._hideParticipatedItemTemplate();
this._participatedShares.forEach((share, index) => {
const item = instantiate(this.participatedListItemTemplate!);
item.name = `MyPKListItem_${index + 1}`;
item.active = true;
this.participatedListContent!.addChild(item);
this._participatedItemNodes.push(item);
this._positionParticipatedItem(item, index);
this._applyParticipatedShare(item, share);
});
this._scrollParticipatedListToTop();
}
private _applyCreatedShare(item: Node, share: CreatedShareItem, version: number): void {
this._setLabel(this._findLabelIn(item, 'Name'), share.title || '未命名挑战');
this._setLabel(this._findLabelIn(item, 'ParticipateLabel'), `${share.participantCount ?? 0}人参与`);
@@ -131,14 +189,51 @@ export class PagePKData extends BaseView {
this._setLabel(this._findLabelIn(item, 'RankNumberLabel'), firstParticipant ? `${rankNumber}` : '暂无排名');
this._loadAvatar(firstParticipant?.avatarUrl ?? '', this._findAvatarSprite(item), version);
const viewButton = this._findChild(item, 'ViewButton');
if (viewButton) {
const handler = () => {
AudioManager.instance.playButtonClick();
this._openShareDetail(share);
};
viewButton.on(Button.EventType.CLICK, handler, this);
this._createdButtonBindings.push({ node: viewButton, handler });
}
const shareButton = this._findChild(item, 'ShareButton');
if (shareButton) {
const handler = () => ShareManager.instance.triggerWxShare(share.title, share.shareCode);
const handler = () => {
AudioManager.instance.playButtonClick();
ShareManager.instance.triggerWxShare(share.title, share.shareCode);
};
shareButton.on(Button.EventType.CLICK, handler, this);
this._createdButtonBindings.push({ node: shareButton, handler });
}
}
private _applyParticipatedShare(item: Node, share: ParticipatedShareItem): void {
this._setLabel(this._findLabelIn(item, 'ChallangeName'), share.title || '未命名挑战');
this._setLabel(this._findLabelIn(item, 'ParticipateLabel'), `${share.participantCount ?? 0}人参与`);
this._applyParticipatedRank(item, share.userRank);
}
private _openShareDetail(share: CreatedShareItem): void {
if (!share.shareCode) {
ToastManager.instance.show('挑战数据异常,请稍后重试');
return;
}
ViewManager.instance.open('PagePKDetail', {
params: {
share,
shareCode: share.shareCode,
},
onError: (err) => {
console.error('[PagePKData] 打开挑战详情失败:', err);
ToastManager.instance.show('打开挑战详情失败,请稍后重试');
},
});
}
private _getFirstParticipant(share: CreatedShareItem): ShareParticipantRankSummary | null {
return share.firstPlaceUser ?? share.topParticipant ?? share.firstParticipant ?? share.champion ?? null;
}
@@ -175,6 +270,34 @@ export class PagePKData extends BaseView {
);
}
private _layoutParticipatedList(itemCount: number): void {
if (!this.participatedListContent || !this.participatedListItemTemplate) {
return;
}
const contentTransform = this.participatedListContent.getComponent(UITransform);
const viewTransform = this.participatedListContent.parent?.getComponent(UITransform) ?? null;
const itemTransform = this.participatedListItemTemplate.getComponent(UITransform);
if (!contentTransform || !viewTransform || !itemTransform) {
return;
}
const contentHeight = Math.max(
viewTransform.height,
PagePKData.PARTICIPATED_ITEM_TOP_PADDING
+ PagePKData.PARTICIPATED_ITEM_BOTTOM_PADDING
+ itemCount * itemTransform.height
+ Math.max(0, itemCount - 1) * PagePKData.PARTICIPATED_ITEM_SPACING,
);
contentTransform.setContentSize(contentTransform.width, contentHeight);
this.participatedListContent.setPosition(
this.participatedListContent.position.x,
viewTransform.height / 2,
this.participatedListContent.position.z,
);
}
private _positionCreatedItem(item: Node, index: number): void {
const itemTransform = item.getComponent(UITransform);
if (!itemTransform) {
@@ -187,11 +310,55 @@ export class PagePKData extends BaseView {
item.setPosition(0, y, item.position.z);
}
private _positionParticipatedItem(item: Node, index: number): void {
const itemTransform = item.getComponent(UITransform);
if (!itemTransform) {
return;
}
const y = -PagePKData.PARTICIPATED_ITEM_TOP_PADDING
- itemTransform.height / 2
- index * (itemTransform.height + PagePKData.PARTICIPATED_ITEM_SPACING);
item.setPosition(0, y, item.position.z);
}
private _scrollCreatedListToTop(): void {
const scrollView = this.node.getChildByName('FriendsPKRankList')?.getComponent(ScrollView);
scrollView?.scrollToTop(0);
}
private _scrollParticipatedListToTop(): void {
const scrollView = this.node.getChildByName('MyPKList')?.getComponent(ScrollView);
scrollView?.scrollToTop(0);
}
private _applyParticipatedRank(item: Node, rank: number | null): void {
const rankRoot = this._findChild(item, 'Rank');
if (!rankRoot) {
return;
}
const rank1Badge = this._findChild(rankRoot, 'rank1badge');
const rank2Badge = this._findChild(rankRoot, 'rank2badge');
const rank3Badge = this._findChild(rankRoot, 'rank3badge');
const rankNumberNode = this._findChild(rankRoot, 'RankNumber');
const normalizedRank = rank && rank > 0 ? rank : null;
if (rank1Badge) {
rank1Badge.active = normalizedRank === 1;
}
if (rank2Badge) {
rank2Badge.active = normalizedRank === 2;
}
if (rank3Badge) {
rank3Badge.active = normalizedRank === 3;
}
if (rankNumberNode) {
rankNumberNode.active = normalizedRank !== 1 && normalizedRank !== 2 && normalizedRank !== 3;
this._setLabel(rankNumberNode.getComponent(Label), normalizedRank ? `${normalizedRank}` : '-');
}
}
private _findAvatarSprite(item: Node): Sprite | null {
const headNode = this._findChild(item, 'Head');
return headNode?.children[0]?.getComponent(Sprite) ?? headNode?.getComponent(Sprite) ?? null;
@@ -231,6 +398,18 @@ export class PagePKData extends BaseView {
this._hideCreatedItemTemplate();
}
private _clearParticipatedItems(): void {
for (const item of this._participatedItemNodes) {
if (item.isValid) {
item.removeFromParent();
item.destroy();
}
}
this._participatedItemNodes = [];
this._hideParticipatedItemTemplate();
}
private _unbindCreatedButtons(): void {
for (const binding of this._createdButtonBindings) {
if (binding.node.isValid) {
@@ -246,6 +425,12 @@ export class PagePKData extends BaseView {
}
}
private _hideParticipatedItemTemplate(): void {
if (this.participatedListItemTemplate?.isValid) {
this.participatedListItemTemplate.active = false;
}
}
private _setLabel(label: Label | null, text: string): void {
if (label) {
label.string = text;

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,411 @@
import { _decorator, Component, Node } from 'cc';
import { _decorator, assetManager, Button, ImageAsset, instantiate, Label, Node, ScrollView, Sprite, SpriteFrame, Texture2D, UITransform } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { CreatedShareItem, ShareDetailData, ShareParticipantRankSummary } from 'db://assets/scripts/types/ApiTypes';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
const { ccclass, property } = _decorator;
@ccclass('PagePKDetail')
export class PagePKDetail extends Component {
start() {
}
update(deltaTime: number) {
}
interface PagePKDetailParams {
share?: CreatedShareItem | null;
shareCode?: string | null;
detail?: ShareDetailData | null;
}
@ccclass('PagePKDetail')
export class PagePKDetail extends BaseView {
private static readonly RANK_ITEM_TOP_PADDING = 16;
private static readonly RANK_ITEM_BOTTOM_PADDING = 16;
private static readonly RANK_ITEM_SPACING = 16;
@property({ type: Label, tooltip: '参与人数文案例如66 人参与' })
participateLabel: Label | null = null;
private _backButton: Node | null = null;
private _titleLabel: Label | null = null;
private _championPanel: Node | null = null;
private _rankListContent: Node | null = null;
private _rankListItemTemplate: Node | null = null;
private _rankItemNodes: Node[] = [];
private _renderVersion: number = 0;
onViewLoad(): void {
this._resolveNodes();
this._bindEvents();
this._hideRankItemTemplate();
}
onViewShow(): void {
this._resolveNodes();
void this._loadAndRenderDetail();
}
onViewHide(): void {
this._renderVersion++;
}
onViewDestroy(): void {
this._unbindEvents();
this._clearRankItems();
}
private _resolveNodes(): void {
if (!this._backButton || !this._backButton.isValid) {
this._backButton = this.node.getChildByName('ButtonBack');
}
this._titleLabel = this._titleLabel
?? this.node.getChildByName('Title')?.getChildByName('Label')?.getComponent(Label)
?? null;
this.participateLabel = this.participateLabel ?? this._findLabelIn(this.node, 'ParticipateLabel');
this._championPanel = this._championPanel ?? this.node.getChildByName('ChampionPanel');
const rankList = this.node.getChildByName('RankList');
const view = rankList?.getChildByName('view');
this._rankListContent = this._rankListContent ?? view?.getChildByName('content') ?? null;
this._rankListItemTemplate = this._rankListItemTemplate
?? this._rankListContent?.getChildByName('RankListItem')
?? null;
if (!this._backButton) {
console.warn('[PagePKDetail] 未找到 ButtonBack 节点');
}
if (!this._rankListContent) {
console.warn('[PagePKDetail] 未找到 RankList/content 节点');
}
if (!this._rankListItemTemplate) {
console.warn('[PagePKDetail] 未找到 RankListItem 模板节点');
}
if (!this.participateLabel) {
console.warn('[PagePKDetail] 未找到 ParticipateLabel 节点');
}
}
private _bindEvents(): void {
if (this._backButton) {
this._backButton.on(Button.EventType.CLICK, this._onBackClick, this);
}
}
private _unbindEvents(): void {
if (this._backButton?.isValid) {
this._backButton.off(Button.EventType.CLICK, this._onBackClick, this);
}
}
private _onBackClick(): void {
ViewManager.instance.back();
}
private async _loadAndRenderDetail(): Promise<void> {
const params = this.getParams() as PagePKDetailParams | null;
const share = this._resolveShare(params);
const passedDetail = params?.detail ?? null;
const shareCode = passedDetail?.shareCode ?? params?.shareCode ?? share?.shareCode ?? null;
const version = ++this._renderVersion;
if (passedDetail) {
this._renderShareDetail(passedDetail, version);
return;
}
this._renderShareSummary(share, version);
if (!shareCode) {
ToastManager.instance.show('挑战数据异常,请稍后重试');
return;
}
const detail = await ShareManager.instance.fetchShareDetail(shareCode);
if (version !== this._renderVersion || !this.isShowing) {
return;
}
if (!detail) {
ToastManager.instance.show('获取挑战详情失败,请稍后重试');
this._renderShareSummary(share, version);
return;
}
this._renderShareDetail(detail, version);
}
private _resolveShare(params: PagePKDetailParams | null): CreatedShareItem | null {
if (params?.share) {
return params.share;
}
const shareCode = params?.shareCode ?? params?.detail?.shareCode;
if (!shareCode) {
return null;
}
return ShareManager.instance.createdShares.find((share) => share.shareCode === shareCode) ?? null;
}
private _renderShareSummary(share: CreatedShareItem | null, version: number): void {
this._clearRankItems();
this._setLabel(this._titleLabel, share?.title || '挑战详情');
this._renderParticipateCount(share);
this._renderChampion(share ? this._getFirstParticipant(share) : null, share, version);
this._layoutRankContent(0);
this._scrollRankListToTop();
}
private _renderShareDetail(detail: ShareDetailData, version: number): void {
this._clearRankItems();
const rankings = this._normalizeRankings(detail.rankings ?? []);
const champion = rankings.find((participant) => participant.rank === 1) ?? rankings[0] ?? null;
const restRankings = rankings.filter((participant) => participant !== champion);
this._setLabel(this._titleLabel, detail.title || '挑战详情');
this._renderParticipateCount(detail);
this._renderChampion(champion, detail, version);
this._renderRankList(restRankings, version);
}
private _renderParticipateCount(shareInfo: CreatedShareItem | ShareDetailData | null): void {
const participantCount = Math.max(0, shareInfo?.participantCount ?? 0);
this._setLabel(this.participateLabel, `${participantCount} 人参与`);
}
private _normalizeRankings(rankings: ShareParticipantRankSummary[]): ShareParticipantRankSummary[] {
return rankings
.map((participant, index) => ({
...participant,
rank: participant.rank ?? index + 1,
}))
.sort((a, b) => (a.rank ?? 0) - (b.rank ?? 0));
}
private _getFirstParticipant(share: CreatedShareItem): ShareParticipantRankSummary | null {
return share.firstPlaceUser ?? share.topParticipant ?? share.firstParticipant ?? share.champion ?? null;
}
private _renderChampion(
champion: ShareParticipantRankSummary | null,
shareInfo: CreatedShareItem | ShareDetailData | null,
version: number,
): void {
const panel = this._championPanel;
if (!panel) {
return;
}
this._setLabel(this._findLabelIn(panel, 'UserName'), this._getParticipantDisplayName(champion, '暂无参与'));
this._setLabel(this._findLabelIn(panel, 'RightInfo'), this._formatCorrectText(champion, shareInfo));
this._setLabel(this._findLabelIn(panel, 'UsedTime'), this._formatTimeText(champion, shareInfo));
this._loadAvatar(champion?.avatarUrl ?? '', this._findAvatarSprite(panel), version);
}
private _renderRankList(participants: ShareParticipantRankSummary[], version: number): void {
if (!this._rankListContent || !this._rankListItemTemplate) {
return;
}
this._hideRankItemTemplate();
this._layoutRankContent(participants.length);
participants.forEach((participant, index) => {
const item = instantiate(this._rankListItemTemplate!);
item.name = `RankListItem_${index + 1}`;
item.active = true;
this._rankListContent!.addChild(item);
this._rankItemNodes.push(item);
this._positionRankItem(item, index);
this._applyRankItem(item, participant, version);
});
this._scrollRankListToTop();
}
private _applyRankItem(item: Node, participant: ShareParticipantRankSummary, version: number): void {
const rank = participant.rank ?? 0;
const rank2Badge = this._findChild(item, 'rank2badge');
const rank3Badge = this._findChild(item, 'rank3badge');
const rankNumberNode = this._findChild(item, 'RankNumber');
if (rank2Badge) {
rank2Badge.active = rank === 2;
}
if (rank3Badge) {
rank3Badge.active = rank === 3;
}
if (rankNumberNode) {
rankNumberNode.active = rank !== 2 && rank !== 3;
this._setLabel(rankNumberNode.getComponent(Label), rank > 0 ? `${rank}` : '-');
}
this._setLabel(this._findLabelIn(item, 'UserName'), this._getParticipantDisplayName(participant, '微信用户'));
this._setLabel(this._findLabelIn(item, 'RightInfo'), this._formatCorrectText(participant, null));
this._setLabel(this._findLabelIn(item, 'UsedTime'), this._formatTimeText(participant));
this._loadAvatar(participant.avatarUrl ?? '', this._findAvatarSprite(item), version);
}
private _layoutRankContent(itemCount: number): void {
if (!this._rankListContent || !this._rankListItemTemplate) {
return;
}
const contentTransform = this._rankListContent.getComponent(UITransform);
const viewTransform = this._rankListContent.parent?.getComponent(UITransform) ?? null;
const itemTransform = this._rankListItemTemplate.getComponent(UITransform);
if (!contentTransform || !viewTransform || !itemTransform) {
return;
}
const contentHeight = Math.max(
viewTransform.height,
PagePKDetail.RANK_ITEM_TOP_PADDING
+ PagePKDetail.RANK_ITEM_BOTTOM_PADDING
+ itemCount * itemTransform.height
+ Math.max(0, itemCount - 1) * PagePKDetail.RANK_ITEM_SPACING,
);
contentTransform.setContentSize(contentTransform.width, contentHeight);
this._rankListContent.setPosition(
this._rankListContent.position.x,
viewTransform.height / 2,
this._rankListContent.position.z,
);
}
private _positionRankItem(item: Node, index: number): void {
const itemTransform = item.getComponent(UITransform);
if (!itemTransform) {
return;
}
const y = -PagePKDetail.RANK_ITEM_TOP_PADDING
- itemTransform.height / 2
- index * (itemTransform.height + PagePKDetail.RANK_ITEM_SPACING);
item.setPosition(0, y, item.position.z);
}
private _scrollRankListToTop(): void {
this.node.getChildByName('RankList')?.getComponent(ScrollView)?.scrollToTop(0);
}
private _formatCorrectText(
participant: ShareParticipantRankSummary | null,
shareInfo: CreatedShareItem | ShareDetailData | null,
): string {
if (participant?.correctCount !== undefined && participant.correctCount !== null) {
return `答对${participant.correctCount}`;
}
if (shareInfo && (shareInfo.participantCount ?? 0) <= 0) {
return '答对0道';
}
if (shareInfo) {
return `${shareInfo.participantCount ?? 0}人参与`;
}
return '暂无成绩';
}
private _formatTimeText(
participant: ShareParticipantRankSummary | null,
shareInfo: CreatedShareItem | ShareDetailData | null = null,
): string {
if (participant?.totalTimeSpent === undefined || participant.totalTimeSpent === null) {
if (shareInfo && (shareInfo.participantCount ?? 0) <= 0) {
return '用时0秒';
}
return '暂无用时';
}
const totalSeconds = Math.max(0, Math.round(participant.totalTimeSpent));
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `用时${minutes}:${seconds.toString().padStart(2, '0')}`;
}
private _getParticipantName(participant: ShareParticipantRankSummary | null): string {
return participant?.nickname || participant?.nickName || '';
}
private _getParticipantDisplayName(
participant: ShareParticipantRankSummary | null,
emptyParticipantFallback: string,
): string {
if (!participant) {
return emptyParticipantFallback;
}
return this._getParticipantName(participant) || '微信用户';
}
private _loadAvatar(url: string, sprite: Sprite | null, version: number): void {
if (!sprite) {
return;
}
if (!url) {
return;
}
assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) {
if (err) {
console.error('[PagePKDetail] 加载头像失败:', url, err);
}
return;
}
const texture = new Texture2D();
texture.image = imageAsset;
const spriteFrame = new SpriteFrame();
spriteFrame.texture = texture;
sprite.spriteFrame = spriteFrame;
});
}
private _findAvatarSprite(root: Node): Sprite | null {
const avatarNode = this._findChild(root, 'Avatar');
return avatarNode?.getComponent(Sprite) ?? null;
}
private _clearRankItems(): void {
for (const item of this._rankItemNodes) {
if (item.isValid) {
item.removeFromParent();
item.destroy();
}
}
this._rankItemNodes = [];
this._hideRankItemTemplate();
}
private _hideRankItemTemplate(): void {
if (this._rankListItemTemplate?.isValid) {
this._rankListItemTemplate.active = false;
}
}
private _setLabel(label: Label | null, text: string): void {
if (label) {
label.string = text;
}
}
private _findLabelIn(root: Node, nodeName: string): Label | null {
return this._findChild(root, nodeName)?.getComponent(Label) ?? null;
}
private _findChild(root: Node, nodeName: string): Node | null {
if (root.name === nodeName) {
return root;
}
for (const child of root.children) {
const found = this._findChild(child, nodeName);
if (found) {
return found;
}
}
return null;
}
}

View File

@@ -244,7 +244,7 @@
"__id__": 1
},
"_children": [],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 11
@@ -426,7 +426,7 @@
"__id__": 25
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 71
@@ -2106,8 +2106,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -172.179,
"y": -613.418,
"x": -142.654,
"y": 760.825,
"z": 0
},
"_lrot": {
@@ -2147,7 +2147,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 495,
"width": 660,
"height": 75.6
},
"_anchorPoint": {
@@ -2186,8 +2186,8 @@
"_string": "揭晓以下谐音梗的答案吧",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 45,
"_fontSize": 45,
"_actualFontSize": 60,
"_fontSize": 60,
"_fontFamily": "Arial",
"_lineHeight": 60,
"_overflow": 0,
@@ -2256,9 +2256,6 @@
"_children": [
{
"__id__": 93
},
{
"__id__": 147
}
],
"_active": true,
@@ -2276,7 +2273,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": -8.201,
"y": -852.319,
"y": -183.147,
"z": 0
},
"_lrot": {
@@ -2313,27 +2310,30 @@
"_children": [
{
"__id__": 94
},
{
"__id__": 140
}
],
"_active": true,
"_components": [
{
"__id__": 140
"__id__": 148
},
{
"__id__": 142
"__id__": 150
},
{
"__id__": 144
"__id__": 152
}
],
"_prefab": {
"__id__": 146
"__id__": 154
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": 46.995,
"z": 0
},
"_lrot": {
@@ -2345,9 +2345,9 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
"x": 1.363,
"y": 1.363,
"z": 1.363
},
"_mobility": 0,
"_layer": 1073741824,
@@ -2384,7 +2384,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": -10,
"y": 125,
"y": 540,
"z": 0
},
"_lrot": {
@@ -2494,7 +2494,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -248.28,
"x": -202.789,
"y": -12.833,
"z": 0
},
@@ -2507,9 +2507,9 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.537,
"y": 0.537,
"z": 0.767
"x": 0.678,
"y": 0.678,
"z": 0.968
},
"_mobility": 0,
"_layer": 1073741824,
@@ -2789,9 +2789,9 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.488,
"y": 0.488,
"z": 0.488
"x": 0.422,
"y": 0.422,
"z": 0.422
},
"_mobility": 0,
"_layer": 1073741824,
@@ -3461,7 +3461,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 780,
"height": 400
"height": 1080
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -3487,6 +3487,181 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "ScrolViewMask",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 93
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 141
},
{
"__id__": 143
},
{
"__id__": 145
}
],
"_prefab": {
"__id__": 147
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 9.242999999999938,
"y": -517.9897285399853,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 1,
"w": 6.123233995736766e-17
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.7336757153338225,
"y": 0.7336757153338225,
"z": 0.7336757153338225
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 180,
"y": 180,
"z": 7.016709298534876e-15
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 140
},
"_enabled": true,
"__prefab": {
"__id__": 142
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1444.78,
"height": 60
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cb61nstNg7YGomw8vXcp"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 140
},
"_enabled": true,
"__prefab": {
"__id__": 144
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 225,
"g": 245,
"b": 197,
"a": 255
},
"_spriteFrame": {
"__uuid__": "faab3d46-e885-4c46-8f19-9f872e7d6973@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d5kG67QW9Nqplq9kKPqg4X"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 140
},
"_enabled": true,
"__prefab": {
"__id__": 146
},
"_alignFlags": 44,
"_target": null,
"_left": -130.757,
"_right": -149.243,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 1080,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 4,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "60ik9u5ftG4oQNCowOqvJi"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "36lE6eex1I0Jo9+fLrBGoc",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -3497,12 +3672,12 @@
},
"_enabled": true,
"__prefab": {
"__id__": 141
"__id__": 149
},
"_contentSize": {
"__type__": "cc.Size",
"width": 780,
"height": 400
"height": 1080
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -3525,7 +3700,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 143
"__id__": 151
},
"_type": 0,
"_inverted": false,
@@ -3547,7 +3722,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 145
"__id__": 153
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -3596,181 +3771,6 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "ScrolViewMask",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 92
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 148
},
{
"__id__": 150
},
{
"__id__": 152
}
],
"_prefab": {
"__id__": 154
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 9.242999999999938,
"y": -173.667,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 1,
"w": 6.123233995736766e-17
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 180
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 147
},
"_enabled": true,
"__prefab": {
"__id__": 149
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1080,
"height": 60
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cb61nstNg7YGomw8vXcp"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 147
},
"_enabled": true,
"__prefab": {
"__id__": 151
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 225,
"g": 245,
"b": 197,
"a": 255
},
"_spriteFrame": {
"__uuid__": "faab3d46-e885-4c46-8f19-9f872e7d6973@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d5kG67QW9Nqplq9kKPqg4X"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 147
},
"_enabled": true,
"__prefab": {
"__id__": 153
},
"_alignFlags": 40,
"_target": null,
"_left": -130.757,
"_right": -149.243,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 1080,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "60ik9u5ftG4oQNCowOqvJi"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "36lE6eex1I0Jo9+fLrBGoc",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -3786,7 +3786,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 800,
"height": 400
"height": 1600
},
"_anchorPoint": {
"__type__": "cc.Vec2",

View File

@@ -3,6 +3,7 @@ import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
import { SubmitShareData, SubmittedShareLevelData } from 'db://assets/scripts/types/ApiTypes';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;
@ccclass('PagePKEnd')
@@ -105,6 +106,7 @@ export class PagePKEnd extends BaseView {
}
private _onHomeClick(): void {
AudioManager.instance.playButtonClick();
ShareManager.instance.clearShareMode();
ViewManager.instance.replace('PageHome');
}
@@ -226,6 +228,7 @@ export class PagePKEnd extends BaseView {
}
const handler = () => {
AudioManager.instance.playButtonClick();
if (answerButton?.isValid) {
answerButton.active = false;
}

View File

@@ -2,6 +2,7 @@ import { _decorator, Node, Button, Label, ScrollView, instantiate, UITransform }
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
import { PreviewLevelItem } from './PreviewLevelItem';
const { ccclass, property } = _decorator;
@@ -215,6 +216,7 @@ export class PagePreviewLevels extends BaseView {
// ─── 事件处理 ───────────────────────────────────────
private _onBackClick(): void {
AudioManager.instance.playButtonClick();
console.log('[PagePreviewLevels] 返回');
ViewManager.instance.back();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset, AudioClip, AudioSource } from 'cc';
import { _decorator, Node, Button, Sprite, Label, Toggle, ScrollView, EditBox, instantiate, UITransform, Vec2, EventTouch, EffectAsset, Prefab } from 'cc';
import { BaseView } from 'db://assets/scripts/core/BaseView';
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
import { CommonModal } from 'db://assets/prefabs/CommonModal';
import { CompletedLevelsManager } from 'db://assets/scripts/utils/CompletedLevelsManager';
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
import { ShareManager } from 'db://assets/scripts/utils/ShareManager';
@@ -11,11 +12,12 @@ import { API_ENDPOINTS, API_TIMEOUT } from 'db://assets/scripts/config/ApiConfig
import { HttpUtil } from 'db://assets/scripts/utils/HttpUtil';
import { ApiEnvelope, CompletedLevel } from 'db://assets/scripts/types/ApiTypes';
import { applyRoundedCorner } from 'db://assets/scripts/utils/roundedMaterial.utils';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;
/**
* 布局配置
* view (ScrollView 的可视窗口) 宽 900高 1000
* view (ScrollView 的可视窗口) 宽 900高 1100
* 关卡 item 固定两列,纵向滚动
*
* item 的实际显示尺寸从 ListTpl 的 UITransform * scale 派生,
@@ -28,7 +30,8 @@ const LAYOUT_CONFIG = {
EDGE_PADDING_Y: 32,
CENTER_ROWS: 2,
VIEW_WIDTH: 900,
VIEW_HEIGHT: 1000,
VIEW_HEIGHT: 1300,
LIST_BOTTOM_GAP_TO_TITLE: 54,
};
/** 必须选择的关卡数量 */
@@ -66,17 +69,18 @@ export class PageWriteLevels extends BaseView {
@property({ type: EffectAsset, tooltip: '关卡封面圆角材质 EffectAsset' })
roundedSpriteEffect: EffectAsset | null = null;
@property({ type: Prefab, tooltip: '通用弹窗预制体' })
commonModalPrefab: Prefab | null = null;
@property({ tooltip: '关卡封面圆角半径比例相对于短边0-0.5' })
coverCornerRadius: number = 0.1;
@property({ type: AudioClip, tooltip: '关卡项选中/取消选中音效' })
itemToggleAudio: AudioClip | null = null;
private _selectedIndices: Set<number> = new Set();
private _levels: CompletedLevel[] = [];
private _levelCount: number = 0;
private _itemNodes: Node[] = [];
private _scrollViewComp: ScrollView | null = null;
private _levelListLoadToken: number = 0;
/** 缓存 view 节点的 UITransform避免每次 _updateContentSize 重复查找 */
private _viewTransform: UITransform | null = null;
@@ -88,6 +92,7 @@ export class PageWriteLevels extends BaseView {
console.log('[PageWriteLevels] onViewLoad');
this._initButtons();
this._initScrollView();
this._resizeScrollViewport();
this._updateSelectionUI();
}
@@ -134,30 +139,60 @@ export class PageWriteLevels extends BaseView {
onViewShow(): void {
console.log('[PageWriteLevels] onViewShow');
// 仅首次初始化列表,从预览页返回时保留选中状态
if (this._itemNodes.length === 0) {
void this._initLevelList();
this._resizeScrollViewport();
this._updateContentSize();
void this._initLevelList();
}
private _resizeScrollViewport(): void {
if (!this.scrollView || !this._viewTransform) {
return;
}
const scrollTransform = this.scrollView.getComponent(UITransform);
const scrollWidget = this.scrollView.getComponent('cc.Widget') as any;
const viewWidget = this._viewTransform.node.getComponent('cc.Widget') as any;
if (!scrollTransform) {
return;
}
scrollWidget?.updateAlignment?.();
viewWidget?.updateAlignment?.();
this._viewTransform.setContentSize(
scrollTransform.contentSize.width,
scrollTransform.contentSize.height,
);
}
private async _initLevelList(): Promise<void> {
this._clearList();
const loadToken = ++this._levelListLoadToken;
const selectedLevelIds = this._getSelectedLevelIdSet();
// 拉取当前用户所有已通关关卡
const levels = await CompletedLevelsManager.instance.fetch();
const levels = await CompletedLevelsManager.instance.fetch(true);
if (loadToken !== this._levelListLoadToken) {
return;
}
if (levels === null) {
console.warn('[PageWriteLevels] 获取已通关关卡失败');
ToastManager.instance.show('获取关卡列表失败,请稍后重试');
return;
}
this._clearList();
this._levels = levels;
this._levelCount = this._levels.length;
this._restoreSelectedIndices(selectedLevelIds);
console.log('[PageWriteLevels] 已通关关卡总数:', this._levelCount);
if (this._levelCount === 0) {
console.warn('[PageWriteLevels] 用户尚未通关任何关卡');
ToastManager.instance.show('还没有已通关的关卡,快去玩几关吧');
this._updateContentSize();
this._updateSelectionUI();
return;
}
@@ -169,6 +204,29 @@ export class PageWriteLevels extends BaseView {
}
}
private _getSelectedLevelIdSet(): Set<string> {
const selectedLevelIds = new Set<string>();
for (const index of this._selectedIndices) {
const level = this._levels[index] ?? null;
if (level) {
selectedLevelIds.add(level.id);
}
}
return selectedLevelIds;
}
private _restoreSelectedIndices(selectedLevelIds: Set<string>): void {
if (selectedLevelIds.size === 0) {
return;
}
for (let index = 0; index < this._levels.length; index++) {
if (selectedLevelIds.has(this._levels[index].id)) {
this._selectedIndices.add(index);
}
}
}
private _clearList(): void {
for (const node of this._itemNodes) {
if (node && node.isValid) {
@@ -429,7 +487,7 @@ export class PageWriteLevels extends BaseView {
this._selectedIndices.delete(index);
}
this._playSound(this.itemToggleAudio);
AudioManager.instance.playButtonClick();
console.log('[PageWriteLevels] item切换选中:', index, selected, '当前已选:', this._selectedIndices.size);
@@ -476,11 +534,11 @@ export class PageWriteLevels extends BaseView {
}
}
// 更新 CompleteButton 和 PreviewButton 的可用状态
// 预览与分享数量不足时也要允许点击,统一弹出提示弹窗。
if (this.completeBtn) {
const btn = this.completeBtn.getComponent(Button);
if (btn) {
btn.interactable = isFull;
btn.interactable = true;
}
}
if (this.previewBtn) {
@@ -492,37 +550,44 @@ export class PageWriteLevels extends BaseView {
}
private _onBackClick(): void {
AudioManager.instance.playButtonClick();
console.log('[PageWriteLevels] 返回按钮点击');
ViewManager.instance.back();
}
private _onDataClick(): void {
AudioManager.instance.playButtonClick();
ViewManager.instance.open('PagePKData');
}
/**
* 校验是否已选满关卡,未满则 Toast 提示
* 校验是否已选满关卡,未满则弹出统一提示弹窗
* @returns true 表示校验通过
*/
private _validateSelection(): boolean {
if (this._selectedIndices.size < MAX_SELECTION) {
const remaining = MAX_SELECTION - this._selectedIndices.size;
ToastManager.instance.show(`还需选择${remaining}个关卡`);
this._showSelectionRequiredModal();
return false;
}
return true;
}
private _playSound(clip: AudioClip | null): void {
if (!clip) {
private _showSelectionRequiredModal(): void {
if (!this.commonModalPrefab) {
console.warn('[PageWriteLevels] commonModalPrefab 未设置,回退为 Toast 提示');
ToastManager.instance.show(`请选择${MAX_SELECTION}个关卡后再预览或分享`);
return;
}
const audioSource = this.node.getComponent(AudioSource) ?? this.node.addComponent(AudioSource);
audioSource?.playOneShot(clip);
CommonModal.show(this.commonModalPrefab, {
title: '提示',
content: `要选择${MAX_SELECTION}个关卡才能分享和预览`,
buttonConfirm: '知道了',
});
}
private _onPreviewClick(): void {
AudioManager.instance.playButtonClick();
if (!this._validateSelection()) return;
const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || '';
ViewManager.instance.open('PagePreviewLevels', {
@@ -534,6 +599,7 @@ export class PageWriteLevels extends BaseView {
}
private async _onCompleteClick(): Promise<void> {
AudioManager.instance.playButtonClick();
if (!this._validateSelection()) return;
const shareTitle = this.shareTitleEditBox?.getComponent(EditBox)?.string?.trim() || '';

View File

@@ -652,7 +652,7 @@
"__id__": 86
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 98
@@ -1632,7 +1632,7 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 442.03125,
"width": 461.40625,
"height": 75.6
},
"_anchorPoint": {
@@ -1668,7 +1668,7 @@
"b": 0,
"a": 255
},
"_string": "还差3题获得冷场小白2级",
"_string": "还差3题,解锁新成就等级",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 40,
@@ -4019,6 +4019,9 @@
"nextLevelButton": {
"__id__": 107
},
"settingButton": {
"__id__": 154
},
"shareButton": {
"__id__": 127
},

View File

@@ -1,6 +1,7 @@
import { _decorator, Node, Label, AudioClip, AudioSource, view, UITransform, Size, ProgressBar, tween, Tween } from 'cc';
import { BaseModal } from 'db://assets/scripts/core/BaseModal';
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
import { AudioManager } from 'db://assets/scripts/utils/AudioManager';
const { ccclass, property } = _decorator;
/**
@@ -11,6 +12,8 @@ export interface PassModalCallbacks {
onNextLevel?: () => void;
/** 点击分享回调 */
onShare?: () => void;
/** 点击返回主页回调 */
onHome?: () => void;
}
export interface PassModalTitleInfo {
@@ -45,6 +48,10 @@ export class PassModal extends BaseModal {
@property(Node)
nextLevelButton: Node | null = null;
/** 返回主页按钮 */
@property(Node)
settingButton: Node | null = null;
/** 分享按钮 */
@property(Node)
shareButton: Node | null = null;
@@ -86,7 +93,7 @@ export class PassModal extends BaseModal {
private _titleInfo: PassModalTitleInfo = {
titleText: '冷场小白1级',
nextTitleProgress: 0,
progressText: '还差3题获得冷场小白2级'
progressText: '还差3题,解锁新成就等级'
};
/** 动画起点。为 null 表示不做进度动画,直接展示终态 */
@@ -98,7 +105,7 @@ export class PassModal extends BaseModal {
/** 下一步按钮文案,为 null 时保留 prefab 默认值 */
private _nextButtonText: string | null = null;
/** 进度游标 0% 时的本地 X 坐标,使用 prefab 当前摆放位置作为起点 */
/** 进度游标 0% 时的本地 X 坐标,根据 ProgressBar Bar 子节点的左端推导出来 */
private _progressAnchorStartX: number | null = null;
setParams(params: PassModalParams): void {
@@ -142,6 +149,7 @@ export class PassModal extends BaseModal {
*/
onViewLoad(): void {
console.log('[PassModal] onViewLoad');
this._resolveNodes();
this._resolveProgressAnchor();
this._cacheProgressAnchorStartX();
this._bindButtonEvents();
@@ -198,6 +206,9 @@ export class PassModal extends BaseModal {
if (this.nextLevelButton) {
this.nextLevelButton.on(Node.EventType.TOUCH_END, this._onNextLevelClick, this);
}
if (this.settingButton) {
this.settingButton.on(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
if (this.shareButton) {
this.shareButton.on(Node.EventType.TOUCH_END, this._onShareClick, this);
}
@@ -211,11 +222,20 @@ export class PassModal extends BaseModal {
if (this.nextLevelButton && this.nextLevelButton.isValid) {
this.nextLevelButton.off(Node.EventType.TOUCH_END, this._onNextLevelClick, this);
}
if (this.settingButton && this.settingButton.isValid) {
this.settingButton.off(Node.EventType.TOUCH_END, this._onHomeClick, this);
}
if (this.shareButton && this.shareButton.isValid) {
this.shareButton.off(Node.EventType.TOUCH_END, this._onShareClick, this);
}
}
private _resolveNodes(): void {
this.nextLevelButton = this.nextLevelButton ?? this.node.getChildByName('Button') ?? null;
this.settingButton = this.settingButton ?? this.node.getChildByName('SettingButton') ?? null;
this.shareButton = this.shareButton ?? this.node.getChildByName('Share') ?? null;
}
/**
* 播放通关音效
*/
@@ -380,11 +400,24 @@ export class PassModal extends BaseModal {
}
private _cacheProgressAnchorStartX(): void {
if (this._progressAnchorStartX !== null || !this.progressAnchor) {
if (this._progressAnchorStartX !== null || !this.titleProgressBar) {
return;
}
this._progressAnchorStartX = this.progressAnchor.position.x;
const barSprite = this.titleProgressBar.barSprite;
if (!barSprite) {
return;
}
// Bar 节点 anchor 为 (0, 0.5),其本地 position.x 即为进度条可视左端。
// ProgressBar 与 ProgressAnchor 共享同一父节点TitleLevel
// 因此把 Bar 的本地 X 按 ProgressBar 自身的位移与缩放映射到父节点空间,
// 才是真正的「0% 起点」。直接拿 progressAnchor.position.x 当起点会导致
// 气泡始终被 prefab 摆放偏移量带跑(实测偏右 ~24px
// -40 为视觉微调,与 PageHome 保持一致。
const progressBarNode = this.titleProgressBar.node;
const barLocalX = barSprite.node.position.x;
this._progressAnchorStartX = progressBarNode.position.x + barLocalX * progressBarNode.scale.x - 30;
}
private _resolveProgressAnchor(): void {
@@ -481,4 +514,13 @@ export class PassModal extends BaseModal {
this._callbacks.onShare?.();
}
/**
* 返回主页按钮点击
*/
private _onHomeClick(): void {
console.log('[PassModal] 点击返回主页');
AudioManager.instance.playButtonClick();
this._callbacks.onHome?.();
}
}

BIN
assets/resources/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,14 @@
{
"ver": "1.0.0",
"importer": "audio-clip",
"imported": true,
"uuid": "f1b26185-7493-4c10-b45a-e35ecc86c507",
"files": [
".json",
".mp3"
],
"subMetas": {},
"userData": {
"downloadMode": 0
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "56ca3b87-0258-46ac-a7fe-6fa57bd4fdcd",
"files": [],
"subMetas": {},
"userData": {}
}

BIN
assets/resources/spine/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "62bb3395-0a86-43c7-8c4e-30dc272c9383",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,62 @@
skeleton.png
size: 281,409
format: RGBA8888
filter: Linear,Linear
repeat: none
good
rotate: false
xy: 2, 2
size: 260, 93
orig: 260, 93
offset: 0, 0
index: -1
亮点1 (1)
rotate: false
xy: 23, 385
size: 23, 22
orig: 26, 25
offset: 1, 2
index: -1
亮点1 (2)
rotate: false
xy: 2, 389
size: 19, 18
orig: 21, 21
offset: 1, 2
index: -1
亮点1 (3)
rotate: false
xy: 48, 375
size: 32, 32
orig: 43, 43
offset: 5, 6
index: -1
亮点大
rotate: false
xy: 147, 283
size: 125, 124
orig: 140, 140
offset: 7, 8
index: -1
图层 13 2
rotate: false
xy: 2, 97
size: 277, 92
orig: 277, 93
offset: 0, 1
index: -1
星星
rotate: true
xy: 82, 363
size: 44, 63
orig: 46, 65
offset: 1, 1
index: -1
组 1 2
rotate: false
xy: 2, 191
size: 267, 90
orig: 267, 90
offset: 0, 0
index: -1

View File

@@ -0,0 +1,12 @@
{
"ver": "1.0.0",
"importer": "*",
"imported": true,
"uuid": "87210cc5-bbb1-4b08-8723-ca58e073aa69",
"files": [
".atlas",
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,806 @@
{
"skeleton": {
"hash": "5g+pbV3em/XRMMTUCFs+FCta7b0=",
"spine": "3.8.75",
"x": -152.01,
"y": -87.03,
"width": 308.06,
"height": 170.6,
"images": "C:/Users/jihuiwang/Downloads/亮点",
"audio": ""
},
"bones": [
{ "name": "root" },
{ "name": "good", "parent": "root" },
{ "name": "亮点1 (1)", "parent": "root", "x": -47.75, "y": 51.34 },
{ "name": "亮点1 (2)", "parent": "root", "x": -101.75, "y": 46.85 },
{ "name": "亮点1 (3)", "parent": "root", "x": 53.8, "y": 51.61 },
{ "name": "亮点大", "parent": "root", "x": 11.71 },
{ "name": "星星", "parent": "root", "x": -53.44, "y": -54.53 },
{ "name": "星星2", "parent": "root", "x": 46.48, "y": -44.29, "scaleX": 0.7758, "scaleY": 0.7758 },
{ "name": "星星3", "parent": "root", "x": 81.98, "y": -69.17, "scaleX": 0.3525, "scaleY": 0.3525 },
{ "name": "星星4", "parent": "root", "x": -137.62, "y": 12.08, "scaleX": 0.6258, "scaleY": 0.6258 },
{ "name": "星星5", "parent": "root", "x": 141.64, "y": -19.76, "scaleX": 0.5363, "scaleY": 0.5363 },
{ "name": "亮点1 (2)2", "parent": "root", "x": 94.96, "y": 42.33 },
{ "name": "亮点1 (3)2", "parent": "root", "x": 134.55, "y": 10.91 },
{ "name": "bone2", "parent": "root" },
{ "name": "bone3", "parent": "root" }
],
"slots": [
{ "name": "good", "bone": "good", "attachment": "good" },
{ "name": "亮点1 (1)", "bone": "亮点1 (1)", "attachment": "亮点1 (1)", "blend": "screen" },
{ "name": "亮点1 (2)", "bone": "亮点1 (2)", "attachment": "亮点1 (2)", "blend": "screen" },
{ "name": "亮点1 (2)2", "bone": "亮点1 (2)2", "attachment": "亮点1 (2)", "blend": "screen" },
{ "name": "亮点1 (3)", "bone": "亮点1 (3)", "attachment": "亮点1 (3)", "blend": "screen" },
{ "name": "亮点1 (3)2", "bone": "亮点1 (3)2", "attachment": "亮点1 (3)", "blend": "screen" },
{ "name": "亮点大", "bone": "亮点大", "attachment": "亮点大", "blend": "additive" },
{ "name": "星星", "bone": "星星", "attachment": "星星", "blend": "screen" },
{ "name": "星星5", "bone": "星星5", "attachment": "星星", "blend": "screen" },
{ "name": "星星4", "bone": "星星4", "color": "ffffffc9", "attachment": "星星", "blend": "screen" },
{ "name": "星星3", "bone": "星星3", "attachment": "星星", "blend": "screen" },
{ "name": "星星2", "bone": "星星2", "color": "ffffffb2", "attachment": "星星", "blend": "screen" },
{ "name": "图层 13 2", "bone": "bone3", "attachment": "图层 13 2" },
{ "name": "组 1 2", "bone": "bone2", "attachment": "组 1 2" }
],
"skins": [
{
"name": "default",
"attachments": {
"亮点1 (2)": {
"亮点1 (2)": { "width": 21, "height": 21 }
},
"亮点1 (2)2": {
"亮点1 (2)": { "width": 21, "height": 21 }
},
"亮点1 (3)2": {
"亮点1 (3)": { "width": 43, "height": 43 }
},
"图层 13 2": {
"图层 13 2": { "width": 277, "height": 93 }
},
"星星2": {
"星星": { "width": 46, "height": 65 }
},
"星星3": {
"星星": { "width": 46, "height": 65 }
},
"星星4": {
"星星": { "width": 46, "height": 65 }
},
"星星5": {
"星星": { "width": 46, "height": 65 }
},
"星星": {
"星星": { "width": 46, "height": 65 }
},
"亮点1 (3)": {
"亮点1 (3)": { "width": 43, "height": 43 }
},
"亮点1 (1)": {
"亮点1 (1)": { "width": 26, "height": 25 }
},
"组 1 2": {
"组 1 2": { "width": 267, "height": 90 }
},
"亮点大": {
"亮点大": { "scaleX": 1.1937, "scaleY": 1.1937, "width": 140, "height": 140 }
},
"good": {
"good": { "width": 260, "height": 93 }
}
}
}
],
"animations": {
"1": {
"slots": {
"亮点1 (3)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"星星": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"图层 13 2": {
"attachment": [
{ "name": null }
]
},
"亮点1 (2)": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffff00" }
]
},
"good": {
"color": [
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "color": "ffffff00" }
]
},
"星星3": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"星星2": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffb2", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"组 1 2": {
"attachment": [
{ "name": null }
]
},
"星星5": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点1 (1)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1667, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": "stepped" },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"亮点1 (2)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点大": {
"color": [
{ "time": 0.1667, "color": "ffffffff", "curve": 0.442, "c2": 0.42, "c3": 0.819, "c4": 0.82 },
{ "time": 0.3333, "color": "ffffff00" }
]
},
"亮点1 (3)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"星星4": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6667, "color": "ffffff00" }
]
}
},
"bones": {
"亮点1 (1)": {
"translate": [
{ "time": 0.3, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 110.68 }
]
},
"亮点1 (2)": {
"translate": [
{ "time": 0.1667, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 89.43 }
]
},
"亮点1 (3)": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "x": 2.2, "y": 49.11 }
]
},
"亮点大": {
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 87.86 }
],
"scale": [
{ "x": 0.5, "y": 0.5 },
{ "time": 0.3333 },
{ "time": 0.3667, "x": 0.01, "y": 0.01 }
]
},
"星星": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 15.39 }
],
"scale": [
{ "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "x": 0.8, "y": 0.8 }
]
},
"星星2": {
"translate": [
{ "time": 0.1, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 13.19 }
]
},
"星星3": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "y": 35.88 }
]
},
"星星4": {
"translate": [
{ "time": 0.2333, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 43.25 }
]
},
"星星5": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 19.06 }
]
},
"亮点1 (2)2": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 57.91 }
]
},
"good": {
"rotate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "angle": -18.23, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "angle": 12.17, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "angle": -14.28, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "angle": 11.65, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "angle": -6.34, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "y": 87.86 }
]
}
}
},
"2": {
"slots": {
"亮点1 (3)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"星星": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"图层 13 2": {
"color": [
{ "time": 0.5, "color": "ffffffff" },
{ "time": 0.8667, "color": "ffffff00" }
]
},
"good": {
"color": [
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "color": "ffffff00" }
],
"attachment": [
{ "name": null }
]
},
"亮点1 (2)": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffff00" }
]
},
"星星3": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"星星2": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffb2", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"组 1 2": {
"attachment": [
{ "name": null }
]
},
"星星5": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点1 (1)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1667, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": "stepped" },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"亮点1 (2)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点大": {
"color": [
{ "time": 0.1667, "color": "ffffffff", "curve": 0.442, "c2": 0.42, "c3": 0.819, "c4": 0.82 },
{ "time": 0.3333, "color": "ffffff00" }
]
},
"亮点1 (3)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"星星4": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6667, "color": "ffffff00" }
]
}
},
"bones": {
"亮点大": {
"rotate": [
{ "angle": -0.41 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 87.86 }
],
"scale": [
{ "x": 0.5, "y": 0.5 },
{ "time": 0.3333 },
{ "time": 0.3667, "x": 0.01, "y": 0.01 }
]
},
"bone3": {
"rotate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "angle": -18.23, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "angle": 12.17, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "angle": -14.28, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "angle": 11.65, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "angle": -6.34, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "y": 87.86 }
]
},
"good": {
"rotate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "angle": -18.23, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "angle": 12.17, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "angle": -14.28, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "angle": 11.65, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "angle": -6.34, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "y": 87.86 }
]
},
"星星3": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "y": 35.88 }
]
},
"亮点1 (2)2": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 57.91 }
]
},
"星星": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 15.39 }
],
"scale": [
{ "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "x": 0.8, "y": 0.8 }
]
},
"亮点1 (3)": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "x": 2.2, "y": 49.11 }
]
},
"星星2": {
"translate": [
{ "time": 0.1, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 13.19 }
]
},
"亮点1 (1)": {
"translate": [
{ "time": 0.3, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 110.68 }
]
},
"星星4": {
"translate": [
{ "time": 0.2333, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 43.25 }
]
},
"亮点1 (2)": {
"translate": [
{ "time": 0.1667, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 89.43 }
]
},
"星星5": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 19.06 }
]
}
}
},
"3": {
"slots": {
"亮点1 (3)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"星星": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"图层 13 2": {
"attachment": [
{ "name": null }
]
},
"good": {
"color": [
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "color": "ffffff00" }
],
"attachment": [
{ "name": null }
]
},
"亮点1 (2)": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffff00" }
]
},
"星星3": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"星星2": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffb2", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"组 1 2": {
"color": [
{ "time": 0.5, "color": "ffffffff" },
{ "time": 0.8667, "color": "ffffff00" }
]
},
"星星5": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点1 (1)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1667, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": "stepped" },
{ "time": 0.5, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "color": "ffffff00" }
]
},
"亮点1 (2)2": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "color": "ffffff00" }
]
},
"亮点大": {
"color": [
{ "time": 0.1667, "color": "ffffffff", "curve": 0.442, "c2": 0.42, "c3": 0.819, "c4": 0.82 },
{ "time": 0.3333, "color": "ffffff00" }
]
},
"亮点1 (3)": {
"color": [
{ "color": "ffffff00", "curve": "stepped" },
{ "time": 0.1, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "color": "ffffff00" }
]
},
"星星4": {
"color": [
{ "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3333, "color": "ffffffc9", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "color": "ffffff00", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5667, "color": "ffffffff", "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6667, "color": "ffffff00" }
]
}
},
"bones": {
"good": {
"rotate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "angle": -18.23, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "angle": 12.17, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "angle": -14.28, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "angle": 11.65, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "angle": -6.34, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "y": 87.86 }
]
},
"星星3": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.7333, "y": 35.88 }
]
},
"星星": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 15.39 }
],
"scale": [
{ "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4333, "x": 0.8, "y": 0.8, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "x": 1.313, "y": 1.313, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "x": 0.8, "y": 0.8 }
]
},
"星星5": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 19.06 }
]
},
"亮点大": {
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 87.86 }
],
"scale": [
{ "x": 0.5, "y": 0.5 },
{ "time": 0.3333 },
{ "time": 0.3667, "x": 0.01, "y": 0.01 }
]
},
"亮点1 (2)": {
"translate": [
{ "time": 0.1667, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 89.43 }
]
},
"星星2": {
"translate": [
{ "time": 0.1, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5333, "y": 13.19 }
]
},
"亮点1 (1)": {
"translate": [
{ "time": 0.3, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "y": 110.68 }
]
},
"亮点1 (3)": {
"translate": [
{ "time": 0.2, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8333, "x": 2.2, "y": 49.11 }
]
},
"星星4": {
"translate": [
{ "time": 0.2333, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 43.25 }
]
},
"亮点1 (2)2": {
"translate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6333, "y": 57.91 }
]
},
"bone2": {
"rotate": [
{ "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.1, "angle": -18.23, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.2, "angle": 12.17, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.3, "angle": -14.28, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.4, "angle": 11.65, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.5, "angle": -6.34, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.6 }
],
"translate": [
{ "time": 0.5, "curve": 0.25, "c3": 0.846, "c4": 0.81 },
{ "time": 0.8667, "y": 87.86 }
]
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
{
"ver": "1.2.7",
"importer": "spine-data",
"imported": true,
"uuid": "443e4aca-50d0-4d49-9c21-c1daeb44116d",
"files": [
".json"
],
"subMetas": {},
"userData": {
"atlasUuid": "87210cc5-bbb1-4b08-8723-ca58e073aa69"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "051410b7-cb90-4a14-98be-109bd75eabcd",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "051410b7-cb90-4a14-98be-109bd75eabcd@6c48a",
"displayName": "skeleton",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "051410b7-cb90-4a14-98be-109bd75eabcd",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "051410b7-cb90-4a14-98be-109bd75eabcd@f9941",
"displayName": "skeleton",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 2,
"trimY": 2,
"width": 277,
"height": 405,
"rawWidth": 281,
"rawHeight": 409,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-138.5,
-202.5,
0,
138.5,
-202.5,
0,
-138.5,
202.5,
0,
138.5,
202.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
2,
407,
279,
407,
2,
2,
279,
2
],
"nuv": [
0.0071174377224199285,
0.004889975550122249,
0.9928825622775801,
0.004889975550122249,
0.0071174377224199285,
0.9951100244498777,
0.9928825622775801,
0.9951100244498777
],
"minPos": [
-138.5,
-202.5,
0
],
"maxPos": [
138.5,
202.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "051410b7-cb90-4a14-98be-109bd75eabcd@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "051410b7-cb90-4a14-98be-109bd75eabcd@6c48a"
}
}

View File

@@ -0,0 +1,125 @@
prize_fx.png
size: 668,525
format: RGBA8888
filter: Linear,Linear
repeat: none
caidai01
rotate: true
xy: 313, 234
size: 42, 33
orig: 42, 33
offset: 0, 0
index: -1
caidai02
rotate: true
xy: 638, 265
size: 25, 28
orig: 25, 28
offset: 0, 0
index: -1
cd1
rotate: true
xy: 238, 2
size: 26, 150
orig: 37, 160
offset: 6, 0
index: -1
cd2
rotate: false
xy: 289, 235
size: 22, 145
orig: 37, 160
offset: 12, 4
index: -1
cd3
rotate: true
xy: 390, 2
size: 26, 150
orig: 37, 160
offset: 6, 0
index: -1
cd4
rotate: true
xy: 391, 247
size: 29, 66
orig: 46, 80
offset: 10, 7
index: -1
gh_0
rotate: false
xy: 238, 30
size: 193, 195
orig: 200, 200
offset: 3, 2
index: -1
guang
rotate: false
xy: 348, 235
size: 41, 41
orig: 66, 69
offset: 17, 16
index: -1
guang1
rotate: false
xy: 433, 50
size: 65, 66
orig: 66, 69
offset: 0, 1
index: -1
ks
rotate: false
xy: 289, 382
size: 141, 141
orig: 155, 155
offset: 7, 7
index: -1
light_glow
rotate: false
xy: 433, 292
size: 233, 231
orig: 279, 278
offset: 23, 24
index: -1
light_line1
rotate: true
xy: 2, 5
size: 220, 234
orig: 285, 263
offset: 43, 23
index: -1
light_line2
rotate: true
xy: 2, 227
size: 296, 285
orig: 309, 298
offset: 9, 12
index: -1
star1
rotate: false
xy: 433, 200
size: 20, 19
orig: 26, 26
offset: 3, 3
index: -1
xx02
rotate: false
xy: 433, 221
size: 24, 24
orig: 32, 32
offset: 4, 4
index: -1
yd_c1_light
rotate: true
xy: 459, 118
size: 172, 177
orig: 200, 200
offset: 17, 12
index: -1
yd_light
rotate: false
xy: 313, 278
size: 102, 102
orig: 112, 112
offset: 5, 5
index: -1

View File

@@ -0,0 +1,12 @@
{
"ver": "1.0.0",
"importer": "*",
"imported": true,
"uuid": "4b5c9209-f28d-4e6a-9eae-905c0e9dbbb7",
"files": [
".atlas",
".json"
],
"subMetas": {},
"userData": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"ver": "1.2.7",
"importer": "spine-data",
"imported": true,
"uuid": "fff92c24-e843-4fca-b0aa-6e4e4f2caef5",
"files": [
".json"
],
"subMetas": {},
"userData": {
"atlasUuid": "4b5c9209-f28d-4e6a-9eae-905c0e9dbbb7"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "4d9aae14-3f9b-44b6-96a9-3535d097fed4",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "4d9aae14-3f9b-44b6-96a9-3535d097fed4@6c48a",
"displayName": "prize_fx",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "4d9aae14-3f9b-44b6-96a9-3535d097fed4",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "4d9aae14-3f9b-44b6-96a9-3535d097fed4@f9941",
"displayName": "prize_fx",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 2,
"trimY": 2,
"width": 664,
"height": 521,
"rawWidth": 668,
"rawHeight": 525,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-332,
-260.5,
0,
332,
-260.5,
0,
-332,
260.5,
0,
332,
260.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
2,
523,
666,
523,
2,
2,
666,
2
],
"nuv": [
0.0029940119760479044,
0.0038095238095238095,
0.9970059880239521,
0.0038095238095238095,
0.0029940119760479044,
0.9961904761904762,
0.9970059880239521,
0.9961904761904762
],
"minPos": [
-332,
-260.5,
0
],
"maxPos": [
332,
260.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "4d9aae14-3f9b-44b6-96a9-3535d097fed4@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "4d9aae14-3f9b-44b6-96a9-3535d097fed4@6c48a"
}
}

View File

@@ -19,6 +19,7 @@ export const API_ENDPOINTS = {
/** 分享相关 */
SHARE_CREATE: `${API_BASE}/share`,
SHARE_CREATED: `${API_BASE}/share/created`,
SHARE_PARTICIPATED: `${API_BASE}/share/participated`,
/** 用户信息 */
USER_INFO: `${API_BASE}/user/info`,
/** 用户所有已通关的关卡(成就墙 / 关卡回看) */
@@ -37,6 +38,10 @@ export function getShareJoinUrl(code: string): string {
return `${API_BASE}/share/${code}/join`;
}
export function getShareDetailUrl(code: string): string {
return `${API_BASE}/share/${code}`;
}
export function getShareSubmitUrl(code: string): string {
return `${API_BASE}/share/${code}/submit`;
}

View File

@@ -165,6 +165,7 @@ export interface SubmitShareData {
/** 分享挑战参与者排行摘要 */
export interface ShareParticipantRankSummary {
userId?: string | null;
participantId?: string | null;
nickname?: string | null;
nickName?: string | null;
avatarUrl?: string | null;
@@ -193,6 +194,30 @@ export interface CreatedShareListData {
items: CreatedShareItem[];
}
/** 我参与的分享挑战条目 */
export interface ParticipatedShareItem {
title: string;
participantCount: number;
userRank: number | null;
}
/** 我参与的分享挑战列表响应 */
export interface ParticipatedShareListData {
items: ParticipatedShareItem[];
}
/** 分享挑战详情响应 */
export interface ShareDetailData {
id: string;
shareCode: string;
title: string;
levelCount: number;
participantCount: number;
userRank: number | null;
createdAt: string;
rankings: ShareParticipantRankSummary[];
}
/** 已通关关卡数据(成就墙 / 关卡回看场景) */
export interface CompletedLevel {
/** 关卡 ID */

View File

@@ -0,0 +1,252 @@
import { Label, Node, ProgressBar, tween, Tween } from 'cc';
/**
* 称号进度展示数据
* 字段对应 AchievementTitleManager.getTitleInfo 的产物,但所有字段都是可选的,
* 调用方可以只更新需要的部分(例如分享模式只想清空文字)。
*/
export interface TitleProgressData {
/** 当前称号文案如「冷场小白1级」 */
titleText?: string;
/** 进度提示文案如「还差3题解锁新成就等级」 */
progressText?: string;
/** 当前称号到下一称号的进度0-1 */
nextTitleProgress?: number;
}
/**
* 进度条 / 称号视图所需的节点引用集合
*/
export interface TitleAnimatorBindings {
/** 称号文案 Label如「冷场小白1级」可空 */
titleLabel?: Label | null;
/** 进度提示 Label如「还差3题解锁新成就等级」可空 */
progressLabel?: Label | null;
/** 进度条组件 */
progressBar?: ProgressBar | null;
/** 进度条上跟随移动的 anchor 节点(若其下有 Label 子节点会自动写百分比) */
progressAnchor?: Node | null;
}
/**
* 进度条动画起始前的等待时长(秒)。让弹窗 / 弹起动画稳定后再开播
*/
const PROGRESS_ANIM_START_DELAY = 0.4;
/** 单段进度条填充动画时长(秒) */
const PROGRESS_ANIM_SEGMENT_DURATION = 0.6;
/** 跨称号切换时的等级信息刷新停顿(秒),让玩家看清称号变更 */
const PROGRESS_ANIM_LEVELUP_PAUSE = 0.12;
/**
* 九宫格 Bar Left+Right border = 240px、totalLength = 925px。
* width < 240px 时圆角会畸变,因此 progress > 0 时强制最小值。
*/
const MIN_PROGRESS_RATIO = 240 / 925;
/** anchor 起点的视觉微调(与 PageHome 等其他页面保持一致) */
const PROGRESS_ANCHOR_VISUAL_OFFSET = -30;
/**
* 把称号文字、进度条、跟随气泡这三件事打包成一个可复用的动画/展示工具。
* 没有引擎组件依赖(不是 cc.Component可以被任何持有节点引用的对象 new 一个出来用。
*
* 起源:原本只在 PassModal 内部实现。PassNode 替换 PassModal 后,
* PageLevel 也需要同一套行为,所以抽出来共用。
*/
export class AchievementTitleAnimator {
private _bindings: TitleAnimatorBindings = {};
/** anchor 起点 X在 progressBar 父节点空间下),首次解析后缓存 */
private _progressAnchorStartX: number | null = null;
/** Tween 共享的目标对象,方便 stopAllByTarget */
private readonly _tweenTarget: { progress: number } = { progress: 0 };
/** 绑定 / 重新绑定节点引用。任何重新绑定都会清掉缓存的 anchor 起点 */
bind(bindings: TitleAnimatorBindings): void {
this._bindings = bindings;
this._progressAnchorStartX = null;
}
/** 直接展示终态,无动画 */
setTarget(data: TitleProgressData): void {
this.stop();
this._applyTitleText(data.titleText);
this._applyProgressText(data.progressText);
this._applyProgressValue(data.nextTitleProgress);
}
/**
* 从 prev → current 播过渡动画
* - prev 为空:直接展示 current 终态
* - 同称号:单段 tween
* - 跨称号:先填满旧称号,再切到新称号 + 进度从 0 涨到 current
*/
playTransition(prev: TitleProgressData | null | undefined, current: TitleProgressData): void {
if (!prev) {
this.setTarget(current);
return;
}
const startProgress = prev.nextTitleProgress;
const endProgress = current.nextTitleProgress;
if (startProgress === undefined || endProgress === undefined) {
this.setTarget(current);
return;
}
const isSameTitle = prev.titleText === undefined
|| current.titleText === undefined
|| prev.titleText === current.titleText;
// 同称号且起止相同,没必要播动画
if (isSameTitle && Math.abs(startProgress - endProgress) < 1e-4) {
this.setTarget(current);
return;
}
this.stop();
if (isSameTitle) {
// 先展示文字 + 起点进度,再 tween 到终点
this._applyTitleText(current.titleText);
this._applyProgressText(current.progressText);
this._applyProgressValue(startProgress);
this._runSegmentTween(startProgress, endProgress, PROGRESS_ANIM_START_DELAY);
return;
}
// 跨称号:先展示旧称号 + 起点进度
this._applyTitleText(prev.titleText);
this._applyProgressText(prev.progressText);
this._applyProgressValue(startProgress);
const target = this._tweenTarget;
target.progress = this._clamp(startProgress);
const onUpdate = () => this._applyAnimatedProgress(target.progress);
tween(target)
.delay(PROGRESS_ANIM_START_DELAY)
.to(PROGRESS_ANIM_SEGMENT_DURATION, { progress: 1 }, { easing: 'sineOut', onUpdate })
.call(() => {
this._applyTitleText(current.titleText);
this._applyProgressText(current.progressText);
target.progress = 0;
this._applyAnimatedProgress(0);
})
.delay(PROGRESS_ANIM_LEVELUP_PAUSE)
.to(
PROGRESS_ANIM_SEGMENT_DURATION,
{ progress: this._clamp(endProgress) },
{ easing: 'sineOut', onUpdate },
)
.start();
}
/** 停止当前动画(不影响已展示的进度值) */
stop(): void {
Tween.stopAllByTarget(this._tweenTarget);
}
private _runSegmentTween(from: number, to: number, delay: number): void {
const target = this._tweenTarget;
target.progress = this._clamp(from);
this._applyAnimatedProgress(from);
const chain = tween(target);
if (delay > 0) {
chain.delay(delay);
}
chain.to(
PROGRESS_ANIM_SEGMENT_DURATION,
{ progress: this._clamp(to) },
{
easing: 'sineOut',
onUpdate: () => this._applyAnimatedProgress(target.progress),
},
).start();
}
private _applyTitleText(text: string | undefined): void {
if (text === undefined) return;
const label = this._bindings.titleLabel;
if (label?.isValid) {
label.string = text;
}
}
private _applyProgressText(text: string | undefined): void {
if (text === undefined) return;
const label = this._bindings.progressLabel;
if (label?.isValid) {
label.string = text;
}
}
private _applyProgressValue(progress: number | undefined): void {
if (progress === undefined) return;
this._applyAnimatedProgress(progress);
}
private _applyAnimatedProgress(progress: number): void {
const clamped = this._clamp(progress);
const bar = this._bindings.progressBar;
if (bar?.isValid) {
bar.progress = this._normalize(clamped);
}
this._updateProgressAnchor(clamped);
}
private _updateProgressAnchor(progress: number): void {
const anchor = this._bindings.progressAnchor;
if (!anchor?.isValid) return;
this._cacheProgressAnchorStartX();
const startX = this._progressAnchorStartX ?? anchor.position.x;
const travelWidth = this._getProgressAnchorTravelWidth();
anchor.setPosition(
startX + travelWidth * progress,
anchor.position.y,
anchor.position.z,
);
const percentLabel = anchor.getChildByName('Label')?.getComponent(Label);
if (percentLabel) {
percentLabel.string = `${Math.round(progress * 100)}%`;
}
}
private _getProgressAnchorTravelWidth(): number {
const bar = this._bindings.progressBar;
if (!bar) return 0;
return Math.abs(bar.totalLength * bar.node.scale.x);
}
/**
* Bar 节点 anchor 为 (0, 0.5),其本地 position.x 即进度条可视左端。
* ProgressBar 与 ProgressAnchor 共享同一父节点,因此把 Bar 的本地 X
* 按 ProgressBar 自身的位移与缩放映射到父节点空间才是真正的「0% 起点」。
* 直接拿 anchor.position.x 当起点会被 prefab 摆放偏移量带跑。
*/
private _cacheProgressAnchorStartX(): void {
if (this._progressAnchorStartX !== null) return;
const bar = this._bindings.progressBar;
const barSprite = bar?.barSprite;
if (!bar || !barSprite) return;
const barLocalX = barSprite.node.position.x;
this._progressAnchorStartX = bar.node.position.x
+ barLocalX * bar.node.scale.x
+ PROGRESS_ANCHOR_VISUAL_OFFSET;
}
private _normalize(progress: number): number {
if (!Number.isFinite(progress) || progress <= 0) return 0;
return Math.max(MIN_PROGRESS_RATIO, Math.min(1, progress));
}
private _clamp(progress: number): number {
if (!Number.isFinite(progress) || progress <= 0) return 0;
return Math.min(1, progress);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "54354f70-d976-4008-b1a9-40473c99e872",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -101,7 +101,7 @@ export class AchievementTitleManager {
titleText: currentStage.titleText,
nextTitleText: nextStage.titleText,
nextTitleProgress,
progressText: `还差${remainingToNextTitle}获得${nextStage.titleText}`,
progressText: `还差${remainingToNextTitle},解锁新成就等级`,
completedLevelCount,
currentTitleStartCount: currentStage.startCount,
nextTitleRequiredCount: nextStage.startCount,

View File

@@ -0,0 +1,55 @@
import { AudioClip, AudioSource, Node } from 'cc';
/**
* 音效管理器
* 统一管理全局按钮点击等通用音效,避免页面各自维护 AudioSource。
*/
export class AudioManager {
private static _instance: AudioManager | null = null;
private _clickAudio: AudioClip | null = null;
private _hostNode: Node | null = null;
private _audioSource: AudioSource | null = null;
static get instance(): AudioManager {
if (!this._instance) {
this._instance = new AudioManager();
}
return this._instance;
}
private constructor() {}
init(clickAudio: AudioClip | null, hostNode: Node | null): void {
this._clickAudio = clickAudio;
this._hostNode = hostNode;
this._audioSource = null;
}
playButtonClick(): void {
this._playOneShot(this._clickAudio);
}
private _playOneShot(clip: AudioClip | null): void {
if (!clip) {
return;
}
const audioSource = this._ensureAudioSource();
audioSource?.playOneShot(clip);
}
private _ensureAudioSource(): AudioSource | null {
if (this._audioSource?.isValid) {
return this._audioSource;
}
if (!this._hostNode?.isValid) {
console.warn('[AudioManager] 未初始化宿主节点,无法播放音效');
return null;
}
this._audioSource = this._hostNode.getComponent(AudioSource) ?? this._hostNode.addComponent(AudioSource);
return this._audioSource;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2dc839e1-6bb2-4d35-a2c4-bf90f85f40e8",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,147 @@
import { WxSDK } from './WxSDK';
import { ShareManager } from './ShareManager';
import { AuthManager } from './AuthManager';
import { ViewManager } from '../core/ViewManager';
/**
* 分享启动监听器
*
* 微信小游戏未被杀掉、只是退到后台时,再次通过好友分享卡片打开小游戏,
* 不会重新走启动链路PageLoading 不会再跑),因此 `wx.getLaunchOptionsSync()`
* 取到的 query 可能是上一次启动的旧值。这个 handler 通过 `wx.onShow`
* 拿到最新的 query检测 shareCode 变化后:
* 1. 清掉旧的分享态
* 2. 调用 `ShareManager.joinShare(code)` 拉取新的题单
* 3. 直接打开 `PageLevel` 进入分享挑战
*
* 对应在 PageLevel 那侧通过 `onViewShow` 检测到 ShareManager.shareCode
* 变化,重新走 `_reinitLevelSession`。
*/
export class ShareLaunchHandler {
private static _instance: ShareLaunchHandler | null = null;
static get instance(): ShareLaunchHandler {
if (!this._instance) {
this._instance = new ShareLaunchHandler();
}
return this._instance;
}
/** 已经处理过的 shareCode相同则不再重复 join */
private _activeShareCode: string | null = null;
/** 是否正在处理一次 onShow 触发的 join 流程,避免并发 */
private _isHandlingShow: boolean = false;
/** 是否已经初始化 */
private _initialized: boolean = false;
private _showHandler = (res: { query?: Record<string, any> } | undefined) => {
const code = WxSDK.extractShareCodeFromQuery(res?.query);
if (!code) {
return;
}
// 同一个 shareCode 且当前已经处于该分享态,无需重复处理
if (code === this._activeShareCode && ShareManager.instance.isShareMode) {
return;
}
// 即使不是新的 code但如果 ShareManager 已经丢失了分享态(例如挑战完成被 clear
// 用户重新点同一个分享卡片仍然应当重新加入。
void this._handleShareCode(code);
};
private _hideHandler = () => {
// 目前不在 onHide 时做任何破坏性操作;保留监听只为方便后续扩展
// (比如:暂停倒计时、上报埋点)。
console.log('[ShareLaunchHandler] 小游戏切到后台');
};
/**
* 在 main.onLoad 中调用,注册 wx.onShow / wx.onHide。
* 同时把当前启动参数中的 shareCode 作为种子,避免初次冷启动时
* 因 wx.onShow 也会被调用一次而重复触发分享流程。
*/
init(): void {
if (this._initialized) {
return;
}
this._initialized = true;
if (!WxSDK.isWechat()) {
return;
}
// 冷启动时先把当前 launch 中的 shareCode 标记成已处理,
// 避免 wx.onShow 在初始展示时拿到同一个 code 又走一遍 join。
this._activeShareCode = WxSDK.getShareCodeFromLaunch();
WxSDK.onAppShow(this._showHandler);
WxSDK.onAppHide(this._hideHandler);
console.log('[ShareLaunchHandler] 已注册 onShow/onHide 监听');
}
/**
* 由 PageLoading 在初始 join 之后调用,把已处理的 shareCode 显式同步过来,
* 让 onShow 收到相同 code 时不会重复 join。
*/
markActiveShareCode(code: string | null): void {
this._activeShareCode = code;
}
/**
* 主动取消监听(一般无需调用,留作扩展)
*/
dispose(): void {
if (!this._initialized) return;
this._initialized = false;
WxSDK.offAppShow(this._showHandler);
WxSDK.offAppHide(this._hideHandler);
}
private async _handleShareCode(code: string): Promise<void> {
if (this._isHandlingShow) {
console.log('[ShareLaunchHandler] 已有 onShow 分享流程在执行,跳过', code);
return;
}
this._isHandlingShow = true;
try {
console.log('[ShareLaunchHandler] 检测到新的 shareCode准备切换:', code);
// 确保已登录initialize 内部对已有 token 做了校验,幂等可重复调用)
const loginOk = await AuthManager.instance.initialize();
if (!loginOk) {
console.warn('[ShareLaunchHandler] 登录失败,放弃 onShow 分享切换');
return;
}
// 切到新的分享前清掉旧分享态,避免 ShareManager 内残留旧题单导致 PageLevel 错位
if (ShareManager.instance.isShareMode) {
ShareManager.instance.clearShareMode();
}
const joinOk = await ShareManager.instance.joinShare(code);
if (!joinOk) {
console.warn('[ShareLaunchHandler] 加入分享失败:', code);
return;
}
// 标记当前激活的分享码
this._activeShareCode = code;
// 跳过中间页,直接打开 PageLevel 进入分享挑战。
// PageLevel 会在 onViewShow 中根据 ShareManager.shareCode 与本地缓存比对,
// 决定是否需要 `_reinitLevelSession`。
ViewManager.instance.open('PageLevel', {
params: { shareMode: true },
});
console.log('[ShareLaunchHandler] 已切换到分享挑战:', code);
} catch (err) {
console.error('[ShareLaunchHandler] 处理 shareCode 异常:', err);
} finally {
this._isHandlingShow = false;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "0709f849-66c8-4e4c-aec5-044c3c414c3e",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,7 +1,7 @@
import { SpriteFrame, Texture2D, ImageAsset, assetManager } from 'cc';
import { HttpUtil } from './HttpUtil';
import { WxSDK } from './WxSDK';
import { API_ENDPOINTS, getShareJoinUrl, getShareSubmitUrl, API_TIMEOUT } from '../config/ApiConfig';
import { API_ENDPOINTS, getShareDetailUrl, getShareJoinUrl, getShareSubmitUrl, API_TIMEOUT } from '../config/ApiConfig';
import {
ApiEnvelope,
CreateShareData,
@@ -9,6 +9,9 @@ import {
ShareLevelData,
CreatedShareItem,
CreatedShareListData,
ParticipatedShareItem,
ParticipatedShareListData,
ShareDetailData,
SubmitShareData,
SubmitShareLevel,
} from '../types/ApiTypes';
@@ -30,6 +33,7 @@ export class ShareManager {
private _shareTitle: string = '';
private _shareCode: string | null = null;
private _createdShares: CreatedShareItem[] = [];
private _participatedShares: ParticipatedShareItem[] = [];
/** 图片缓存URL -> SpriteFrame */
private _imageCache: Map<string, SpriteFrame> = new Map();
@@ -51,6 +55,10 @@ export class ShareManager {
return [...this._createdShares];
}
get participatedShares(): ParticipatedShareItem[] {
return [...this._participatedShares];
}
get shareCode(): string | null {
return this._shareCode;
}
@@ -157,6 +165,47 @@ export class ShareManager {
}
}
async fetchParticipatedShares(): Promise<ParticipatedShareItem[] | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<ParticipatedShareListData>>(
API_ENDPOINTS.SHARE_PARTICIPATED,
API_TIMEOUT.DEFAULT,
);
if (!response.success || !response.data) {
console.error('[ShareManager] 获取我参与的挑战列表失败:', response.message);
return null;
}
this._participatedShares = response.data.items ?? [];
console.log(`[ShareManager] 获取我参与的挑战列表成功: ${this._participatedShares.length}`);
return this.participatedShares;
} catch (err) {
console.error('[ShareManager] 获取我参与的挑战列表异常:', err);
return null;
}
}
async fetchShareDetail(code: string): Promise<ShareDetailData | null> {
try {
const response = await HttpUtil.get<ApiEnvelope<ShareDetailData>>(
getShareDetailUrl(code),
API_TIMEOUT.DEFAULT,
);
if (!response.success || !response.data) {
console.error('[ShareManager] 获取分享挑战详情失败:', response.message);
return null;
}
console.log(`[ShareManager] 获取分享挑战详情成功: ${response.data.title}, ${response.data.rankings?.length ?? 0} 条排行`);
return response.data;
} catch (err) {
console.error('[ShareManager] 获取分享挑战详情异常:', err);
return null;
}
}
async ensureShareLevelReady(index: number): Promise<RuntimeLevelConfig | null> {
if (!this._shareLevels || index < 0 || index >= this._shareLevels.length) {
return null;

View File

@@ -398,15 +398,82 @@ export class WxSDK {
try {
const options = wxApi.getLaunchOptionsSync();
if (options?.query?.shareCode) {
console.log('[WxSDK] 检测到分享码:', options.query.shareCode);
return options.query.shareCode;
const code = WxSDK.extractShareCodeFromQuery(options?.query);
if (code) {
console.log('[WxSDK] 检测到分享码:', code);
return code;
}
} catch (err) {
console.warn('[WxSDK] 获取启动参数失败:', err);
}
return null;
}
/**
* 从查询对象(来自 launch options 或 onShow 回调)中提取 shareCode
*/
static extractShareCodeFromQuery(query: Record<string, any> | null | undefined): string | null {
const code = query?.shareCode;
return typeof code === 'string' && code.length > 0 ? code : null;
}
// ==================== 前后台生命周期 ====================
/**
* 监听小游戏切到前台事件。
* 同一个回调可重复注册多次:内部用 wx.onShow请确保业务层做幂等处理或在卸载时调用 offAppShow。
* @param callback 切前台时触发,包含本次显示对应的 query / scene 等参数
*/
static onAppShow(callback: (res: { query?: Record<string, any>; scene?: number; path?: string } | undefined) => void): void {
const wxApi = WxSDK.getWx();
if (!wxApi) return;
if (typeof wxApi.onShow !== 'function') {
console.warn('[WxSDK] 当前微信版本不支持 onShow');
return;
}
wxApi.onShow(callback);
}
/**
* 取消监听小游戏切到前台事件
*/
static offAppShow(callback: (res: any) => void): void {
const wxApi = WxSDK.getWx();
if (!wxApi) return;
if (typeof wxApi.offShow === 'function') {
wxApi.offShow(callback);
}
}
/**
* 监听小游戏切到后台事件
*/
static onAppHide(callback: () => void): void {
const wxApi = WxSDK.getWx();
if (!wxApi) return;
if (typeof wxApi.onHide !== 'function') {
console.warn('[WxSDK] 当前微信版本不支持 onHide');
return;
}
wxApi.onHide(callback);
}
/**
* 取消监听小游戏切到后台事件
*/
static offAppHide(callback: () => void): void {
const wxApi = WxSDK.getWx();
if (!wxApi) return;
if (typeof wxApi.offHide === 'function') {
wxApi.offHide(callback);
}
}
}
// ==================== 隐私授权相关 ====================

BIN
extensions/.DS_Store vendored Normal file

Binary file not shown.

Submodule extensions/cocos-mcp-server added at 754adecdb8

6
settings/mcp-server.json Normal file
View File

@@ -0,0 +1,6 @@
{
"port": 3008,
"autoStart": false,
"debugLog": false,
"maxConnections": 10
}

957
settings/tool-manager.json Normal file
View File

@@ -0,0 +1,957 @@
{
"configurations": [
{
"id": "4e401ac4-fa11-4f3b-9531-bb09687de4df",
"name": "默认配置",
"description": "自动创建的默认工具配置",
"tools": [
{
"category": "scene",
"name": "get_current_scene",
"enabled": true,
"description": "Get current scene information"
},
{
"category": "scene",
"name": "get_scene_list",
"enabled": true,
"description": "Get all scenes in the project"
},
{
"category": "scene",
"name": "open_scene",
"enabled": true,
"description": "Open a scene by path"
},
{
"category": "scene",
"name": "save_scene",
"enabled": true,
"description": "Save current scene"
},
{
"category": "scene",
"name": "create_scene",
"enabled": true,
"description": "Create a new scene asset"
},
{
"category": "scene",
"name": "save_scene_as",
"enabled": true,
"description": "Save scene as new file"
},
{
"category": "scene",
"name": "close_scene",
"enabled": true,
"description": "Close current scene"
},
{
"category": "scene",
"name": "get_scene_hierarchy",
"enabled": true,
"description": "Get the complete hierarchy of current scene"
},
{
"category": "node",
"name": "create_node",
"enabled": true,
"description": "Create a new node in the scene. Supports creating empty nodes, nodes with components, or instantiating from assets (prefabs, etc.). IMPORTANT: You should always provide parentUuid to specify where to create the node."
},
{
"category": "node",
"name": "get_node_info",
"enabled": true,
"description": "Get node information by UUID"
},
{
"category": "node",
"name": "find_nodes",
"enabled": true,
"description": "Find nodes by name pattern"
},
{
"category": "node",
"name": "find_node_by_name",
"enabled": true,
"description": "Find first node by exact name"
},
{
"category": "node",
"name": "get_all_nodes",
"enabled": true,
"description": "Get all nodes in the scene with their UUIDs"
},
{
"category": "node",
"name": "set_node_property",
"enabled": true,
"description": "Set node property value (prefer using set_node_transform for active/layer/mobility/position/rotation/scale)"
},
{
"category": "node",
"name": "set_node_transform",
"enabled": true,
"description": "Set node transform properties (position, rotation, scale) with unified interface. Automatically handles 2D/3D node differences."
},
{
"category": "node",
"name": "delete_node",
"enabled": true,
"description": "Delete a node from scene"
},
{
"category": "node",
"name": "move_node",
"enabled": true,
"description": "Move node to new parent"
},
{
"category": "node",
"name": "duplicate_node",
"enabled": true,
"description": "Duplicate a node"
},
{
"category": "node",
"name": "detect_node_type",
"enabled": true,
"description": "Detect if a node is 2D or 3D based on its components and properties"
},
{
"category": "component",
"name": "add_component",
"enabled": true,
"description": "Add a component to a specific node. IMPORTANT: You must provide the nodeUuid parameter to specify which node to add the component to."
},
{
"category": "component",
"name": "remove_component",
"enabled": true,
"description": "Remove a component from a node. componentType must be the component's classId (cid, i.e. the type field from getComponents), not the script name or class name. Use getComponents to get the correct cid."
},
{
"category": "component",
"name": "get_components",
"enabled": true,
"description": "Get all components of a node"
},
{
"category": "component",
"name": "get_component_info",
"enabled": true,
"description": "Get specific component information"
},
{
"category": "component",
"name": "set_component_property",
"enabled": true,
"description": "Set component property values for UI components or custom script components. Supports setting properties of built-in UI components (e.g., cc.Label, cc.Sprite) and custom script components. Note: For node basic properties (name, active, layer, etc.), use set_node_property. For node transform properties (position, rotation, scale, etc.), use set_node_transform."
},
{
"category": "component",
"name": "attach_script",
"enabled": true,
"description": "Attach a script component to a node"
},
{
"category": "component",
"name": "get_available_components",
"enabled": true,
"description": "Get list of available component types"
},
{
"category": "prefab",
"name": "get_prefab_list",
"enabled": true,
"description": "Get all prefabs in the project"
},
{
"category": "prefab",
"name": "load_prefab",
"enabled": true,
"description": "Load a prefab by path"
},
{
"category": "prefab",
"name": "instantiate_prefab",
"enabled": true,
"description": "Instantiate a prefab in the scene"
},
{
"category": "prefab",
"name": "create_prefab",
"enabled": true,
"description": "Create a prefab from a node with all children and components"
},
{
"category": "prefab",
"name": "update_prefab",
"enabled": true,
"description": "Update an existing prefab"
},
{
"category": "prefab",
"name": "revert_prefab",
"enabled": true,
"description": "Revert prefab instance to original"
},
{
"category": "prefab",
"name": "get_prefab_info",
"enabled": true,
"description": "Get detailed prefab information"
},
{
"category": "prefab",
"name": "validate_prefab",
"enabled": true,
"description": "Validate a prefab file format"
},
{
"category": "prefab",
"name": "duplicate_prefab",
"enabled": true,
"description": "Duplicate an existing prefab"
},
{
"category": "prefab",
"name": "restore_prefab_node",
"enabled": true,
"description": "Restore prefab node using prefab asset (built-in undo record)"
},
{
"category": "project",
"name": "run_project",
"enabled": true,
"description": "Run the project in preview mode"
},
{
"category": "project",
"name": "build_project",
"enabled": true,
"description": "Build the project"
},
{
"category": "project",
"name": "get_project_info",
"enabled": true,
"description": "Get project information"
},
{
"category": "project",
"name": "get_project_settings",
"enabled": true,
"description": "Get project settings"
},
{
"category": "project",
"name": "refresh_assets",
"enabled": true,
"description": "Refresh asset database"
},
{
"category": "project",
"name": "import_asset",
"enabled": true,
"description": "Import an asset file"
},
{
"category": "project",
"name": "get_asset_info",
"enabled": true,
"description": "Get asset information"
},
{
"category": "project",
"name": "get_assets",
"enabled": true,
"description": "Get assets by type"
},
{
"category": "project",
"name": "get_build_settings",
"enabled": true,
"description": "Get build settings - shows current limitations"
},
{
"category": "project",
"name": "open_build_panel",
"enabled": true,
"description": "Open the build panel in the editor"
},
{
"category": "project",
"name": "check_builder_status",
"enabled": true,
"description": "Check if builder worker is ready"
},
{
"category": "project",
"name": "start_preview_server",
"enabled": true,
"description": "Start preview server"
},
{
"category": "project",
"name": "stop_preview_server",
"enabled": true,
"description": "Stop preview server"
},
{
"category": "project",
"name": "create_asset",
"enabled": true,
"description": "Create a new asset file or folder"
},
{
"category": "project",
"name": "copy_asset",
"enabled": true,
"description": "Copy an asset to another location"
},
{
"category": "project",
"name": "move_asset",
"enabled": true,
"description": "Move an asset to another location"
},
{
"category": "project",
"name": "delete_asset",
"enabled": true,
"description": "Delete an asset"
},
{
"category": "project",
"name": "save_asset",
"enabled": true,
"description": "Save asset content"
},
{
"category": "project",
"name": "reimport_asset",
"enabled": true,
"description": "Reimport an asset"
},
{
"category": "project",
"name": "query_asset_path",
"enabled": true,
"description": "Get asset disk path"
},
{
"category": "project",
"name": "query_asset_uuid",
"enabled": true,
"description": "Get asset UUID from URL"
},
{
"category": "project",
"name": "query_asset_url",
"enabled": true,
"description": "Get asset URL from UUID"
},
{
"category": "project",
"name": "find_asset_by_name",
"enabled": true,
"description": "Find assets by name (supports partial matching and multiple results)"
},
{
"category": "project",
"name": "get_asset_details",
"enabled": true,
"description": "Get detailed asset information including spriteFrame sub-assets"
},
{
"category": "debug",
"name": "get_console_logs",
"enabled": true,
"description": "Get editor console logs"
},
{
"category": "debug",
"name": "clear_console",
"enabled": true,
"description": "Clear editor console"
},
{
"category": "debug",
"name": "execute_script",
"enabled": true,
"description": "Execute JavaScript in scene context"
},
{
"category": "debug",
"name": "get_node_tree",
"enabled": true,
"description": "Get detailed node tree for debugging"
},
{
"category": "debug",
"name": "get_performance_stats",
"enabled": true,
"description": "Get performance statistics"
},
{
"category": "debug",
"name": "validate_scene",
"enabled": true,
"description": "Validate current scene for issues"
},
{
"category": "debug",
"name": "get_editor_info",
"enabled": true,
"description": "Get editor and environment information"
},
{
"category": "debug",
"name": "get_project_logs",
"enabled": true,
"description": "Get project logs from temp/logs/project.log file"
},
{
"category": "debug",
"name": "get_log_file_info",
"enabled": true,
"description": "Get information about the project log file"
},
{
"category": "debug",
"name": "search_project_logs",
"enabled": true,
"description": "Search for specific patterns or errors in project logs"
},
{
"category": "preferences",
"name": "open_preferences_settings",
"enabled": true,
"description": "Open preferences settings panel"
},
{
"category": "preferences",
"name": "query_preferences_config",
"enabled": true,
"description": "Query preferences configuration"
},
{
"category": "preferences",
"name": "set_preferences_config",
"enabled": true,
"description": "Set preferences configuration"
},
{
"category": "preferences",
"name": "get_all_preferences",
"enabled": true,
"description": "Get all available preferences categories"
},
{
"category": "preferences",
"name": "reset_preferences",
"enabled": true,
"description": "Reset preferences to default values"
},
{
"category": "preferences",
"name": "export_preferences",
"enabled": true,
"description": "Export current preferences configuration"
},
{
"category": "preferences",
"name": "import_preferences",
"enabled": true,
"description": "Import preferences configuration from file"
},
{
"category": "server",
"name": "query_server_ip_list",
"enabled": true,
"description": "Query server IP list"
},
{
"category": "server",
"name": "query_sorted_server_ip_list",
"enabled": true,
"description": "Get sorted server IP list"
},
{
"category": "server",
"name": "query_server_port",
"enabled": true,
"description": "Query editor server current port"
},
{
"category": "server",
"name": "get_server_status",
"enabled": true,
"description": "Get comprehensive server status information"
},
{
"category": "server",
"name": "check_server_connectivity",
"enabled": true,
"description": "Check server connectivity and network status"
},
{
"category": "server",
"name": "get_network_interfaces",
"enabled": true,
"description": "Get available network interfaces"
},
{
"category": "broadcast",
"name": "get_broadcast_log",
"enabled": true,
"description": "Get recent broadcast messages log"
},
{
"category": "broadcast",
"name": "listen_broadcast",
"enabled": true,
"description": "Start listening for specific broadcast messages"
},
{
"category": "broadcast",
"name": "stop_listening",
"enabled": true,
"description": "Stop listening for specific broadcast messages"
},
{
"category": "broadcast",
"name": "clear_broadcast_log",
"enabled": true,
"description": "Clear the broadcast messages log"
},
{
"category": "broadcast",
"name": "get_active_listeners",
"enabled": true,
"description": "Get list of active broadcast listeners"
},
{
"category": "sceneAdvanced",
"name": "reset_node_property",
"enabled": true,
"description": "Reset node property to default value"
},
{
"category": "sceneAdvanced",
"name": "move_array_element",
"enabled": true,
"description": "Move array element position"
},
{
"category": "sceneAdvanced",
"name": "remove_array_element",
"enabled": true,
"description": "Remove array element at specific index"
},
{
"category": "sceneAdvanced",
"name": "copy_node",
"enabled": true,
"description": "Copy node for later paste operation"
},
{
"category": "sceneAdvanced",
"name": "paste_node",
"enabled": true,
"description": "Paste previously copied nodes"
},
{
"category": "sceneAdvanced",
"name": "cut_node",
"enabled": true,
"description": "Cut node (copy + mark for move)"
},
{
"category": "sceneAdvanced",
"name": "reset_node_transform",
"enabled": true,
"description": "Reset node position, rotation and scale"
},
{
"category": "sceneAdvanced",
"name": "reset_component",
"enabled": true,
"description": "Reset component to default values"
},
{
"category": "sceneAdvanced",
"name": "restore_prefab",
"enabled": true,
"description": "Restore prefab instance from asset"
},
{
"category": "sceneAdvanced",
"name": "execute_component_method",
"enabled": true,
"description": "Execute method on component"
},
{
"category": "sceneAdvanced",
"name": "execute_scene_script",
"enabled": true,
"description": "Execute scene script method"
},
{
"category": "sceneAdvanced",
"name": "scene_snapshot",
"enabled": true,
"description": "Create scene state snapshot"
},
{
"category": "sceneAdvanced",
"name": "scene_snapshot_abort",
"enabled": true,
"description": "Abort scene snapshot creation"
},
{
"category": "sceneAdvanced",
"name": "begin_undo_recording",
"enabled": true,
"description": "Begin recording undo data"
},
{
"category": "sceneAdvanced",
"name": "end_undo_recording",
"enabled": true,
"description": "End recording undo data"
},
{
"category": "sceneAdvanced",
"name": "cancel_undo_recording",
"enabled": true,
"description": "Cancel undo recording"
},
{
"category": "sceneAdvanced",
"name": "soft_reload_scene",
"enabled": true,
"description": "Soft reload current scene"
},
{
"category": "sceneAdvanced",
"name": "query_scene_ready",
"enabled": true,
"description": "Check if scene is ready"
},
{
"category": "sceneAdvanced",
"name": "query_scene_dirty",
"enabled": true,
"description": "Check if scene has unsaved changes"
},
{
"category": "sceneAdvanced",
"name": "query_scene_classes",
"enabled": true,
"description": "Query all registered classes"
},
{
"category": "sceneAdvanced",
"name": "query_scene_components",
"enabled": true,
"description": "Query available scene components"
},
{
"category": "sceneAdvanced",
"name": "query_component_has_script",
"enabled": true,
"description": "Check if component has script"
},
{
"category": "sceneAdvanced",
"name": "query_nodes_by_asset_uuid",
"enabled": true,
"description": "Find nodes that use specific asset UUID"
},
{
"category": "sceneView",
"name": "change_gizmo_tool",
"enabled": true,
"description": "Change Gizmo tool"
},
{
"category": "sceneView",
"name": "query_gizmo_tool_name",
"enabled": true,
"description": "Get current Gizmo tool name"
},
{
"category": "sceneView",
"name": "change_gizmo_pivot",
"enabled": true,
"description": "Change transform pivot point"
},
{
"category": "sceneView",
"name": "query_gizmo_pivot",
"enabled": true,
"description": "Get current Gizmo pivot point"
},
{
"category": "sceneView",
"name": "query_gizmo_view_mode",
"enabled": true,
"description": "Query view mode (view/select)"
},
{
"category": "sceneView",
"name": "change_gizmo_coordinate",
"enabled": true,
"description": "Change coordinate system"
},
{
"category": "sceneView",
"name": "query_gizmo_coordinate",
"enabled": true,
"description": "Get current coordinate system"
},
{
"category": "sceneView",
"name": "change_view_mode_2d_3d",
"enabled": true,
"description": "Change 2D/3D view mode"
},
{
"category": "sceneView",
"name": "query_view_mode_2d_3d",
"enabled": true,
"description": "Get current view mode"
},
{
"category": "sceneView",
"name": "set_grid_visible",
"enabled": true,
"description": "Show/hide grid"
},
{
"category": "sceneView",
"name": "query_grid_visible",
"enabled": true,
"description": "Query grid visibility status"
},
{
"category": "sceneView",
"name": "set_icon_gizmo_3d",
"enabled": true,
"description": "Set IconGizmo to 3D or 2D mode"
},
{
"category": "sceneView",
"name": "query_icon_gizmo_3d",
"enabled": true,
"description": "Query IconGizmo mode"
},
{
"category": "sceneView",
"name": "set_icon_gizmo_size",
"enabled": true,
"description": "Set IconGizmo size"
},
{
"category": "sceneView",
"name": "query_icon_gizmo_size",
"enabled": true,
"description": "Query IconGizmo size"
},
{
"category": "sceneView",
"name": "focus_camera_on_nodes",
"enabled": true,
"description": "Focus scene camera on nodes"
},
{
"category": "sceneView",
"name": "align_camera_with_view",
"enabled": true,
"description": "Apply scene camera position and angle to selected node"
},
{
"category": "sceneView",
"name": "align_view_with_node",
"enabled": true,
"description": "Apply selected node position and angle to current view"
},
{
"category": "sceneView",
"name": "get_scene_view_status",
"enabled": true,
"description": "Get comprehensive scene view status"
},
{
"category": "sceneView",
"name": "reset_scene_view",
"enabled": true,
"description": "Reset scene view to default settings"
},
{
"category": "referenceImage",
"name": "add_reference_image",
"enabled": true,
"description": "Add reference image(s) to scene"
},
{
"category": "referenceImage",
"name": "remove_reference_image",
"enabled": true,
"description": "Remove reference image(s)"
},
{
"category": "referenceImage",
"name": "switch_reference_image",
"enabled": true,
"description": "Switch to specific reference image"
},
{
"category": "referenceImage",
"name": "set_reference_image_data",
"enabled": true,
"description": "Set reference image transform and display properties"
},
{
"category": "referenceImage",
"name": "query_reference_image_config",
"enabled": true,
"description": "Query reference image configuration"
},
{
"category": "referenceImage",
"name": "query_current_reference_image",
"enabled": true,
"description": "Query current reference image data"
},
{
"category": "referenceImage",
"name": "refresh_reference_image",
"enabled": true,
"description": "Refresh reference image display"
},
{
"category": "referenceImage",
"name": "set_reference_image_position",
"enabled": true,
"description": "Set reference image position"
},
{
"category": "referenceImage",
"name": "set_reference_image_scale",
"enabled": true,
"description": "Set reference image scale"
},
{
"category": "referenceImage",
"name": "set_reference_image_opacity",
"enabled": true,
"description": "Set reference image opacity"
},
{
"category": "referenceImage",
"name": "list_reference_images",
"enabled": true,
"description": "List all available reference images"
},
{
"category": "referenceImage",
"name": "clear_all_reference_images",
"enabled": true,
"description": "Clear all reference images"
},
{
"category": "assetAdvanced",
"name": "save_asset_meta",
"enabled": true,
"description": "Save asset meta information"
},
{
"category": "assetAdvanced",
"name": "generate_available_url",
"enabled": true,
"description": "Generate an available URL based on input URL"
},
{
"category": "assetAdvanced",
"name": "query_asset_db_ready",
"enabled": true,
"description": "Check if asset database is ready"
},
{
"category": "assetAdvanced",
"name": "open_asset_external",
"enabled": true,
"description": "Open asset with external program"
},
{
"category": "assetAdvanced",
"name": "batch_import_assets",
"enabled": true,
"description": "Import multiple assets in batch"
},
{
"category": "assetAdvanced",
"name": "batch_delete_assets",
"enabled": true,
"description": "Delete multiple assets in batch"
},
{
"category": "assetAdvanced",
"name": "validate_asset_references",
"enabled": true,
"description": "Validate asset references and find broken links"
},
{
"category": "assetAdvanced",
"name": "get_asset_dependencies",
"enabled": true,
"description": "Get asset dependency tree"
},
{
"category": "assetAdvanced",
"name": "get_unused_assets",
"enabled": true,
"description": "Find unused assets in project"
},
{
"category": "assetAdvanced",
"name": "compress_textures",
"enabled": true,
"description": "Batch compress texture assets"
},
{
"category": "assetAdvanced",
"name": "export_asset_manifest",
"enabled": true,
"description": "Export asset manifest/inventory"
},
{
"category": "validation",
"name": "validate_json_params",
"enabled": true,
"description": "Validate and fix JSON parameters before sending to other tools"
},
{
"category": "validation",
"name": "safe_string_value",
"enabled": true,
"description": "Create a safe string value that won't cause JSON parsing issues"
},
{
"category": "validation",
"name": "format_mcp_request",
"enabled": true,
"description": "Format a complete MCP request with proper JSON escaping"
}
],
"createdAt": "2026-05-19T14:37:17.528Z",
"updatedAt": "2026-05-19T14:37:17.528Z"
}
],
"currentConfigId": "4e401ac4-fa11-4f3b-9531-bb09687de4df",
"maxConfigSlots": 5
}