# 梗中作乐 V1.0 - 源代码文档(第 10 部分) 软件名称:梗中作乐 版本号:V1.0 --- ## 第 10 部分:PK 战绩与挑战结算 本部分包含 PK 战绩榜单页与 PK 挑战结算页两个核心结算页面。 ### 10.1 PK 战绩页 (assets/prefabs/PagePKData.ts) ```typescript 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, 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') 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; @property({ type: Node, tooltip: '我创建的挑战列表 content 节点' }) createdListContent: Node | null = null; @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; onViewLoad(): void { this._resolveNodes(); if (this.backBtn) { this.backBtn.on(Button.EventType.CLICK, this._onBackClick, this); } this._hideCreatedItemTemplate(); this._hideParticipatedItemTemplate(); } onViewShow(): void { this._resolveNodes(); void this._loadShareLists(); } onViewHide(): void { this._renderVersion++; } private _onBackClick(): void { AudioManager.instance.playButtonClick(); ViewManager.instance.back(); } private async _loadShareLists(): Promise { if (this._isLoading) { return; } this._isLoading = true; try { const [createdItems, participatedItems] = await Promise.all([ ShareManager.instance.fetchCreatedShares(), ShareManager.instance.fetchParticipatedShares(), ]); if (!createdItems || !participatedItems) { ToastManager.instance.show('获取挑战列表失败,请稍后重试'); } this._createdShares = createdItems ?? []; this._participatedShares = participatedItems ?? []; console.log('[PagePKData] 我创建的挑战列表:', this._createdShares); console.log('[PagePKData] 我参与的挑战列表:', this._participatedShares); this._renderCreatedShares(); this._renderParticipatedShares(); } finally { this._isLoading = false; } } onViewDestroy(): void { if (this.backBtn) { this.backBtn.off(Button.EventType.CLICK, this._onBackClick, this); } this._clearCreatedItems(); this._clearParticipatedItems(); } private _resolveNodes(): void { if (!this.backBtn || !this.backBtn.isValid) { this.backBtn = this.node.getChildByName('BtnBack'); } const createdList = this.node.getChildByName('FriendsPKRankList'); const view = createdList?.getChildByName('view'); this.createdListContent = this.createdListContent ?? view?.getChildByName('content') ?? null; this.createdListItemTemplate = this.createdListItemTemplate ?? 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 { this._renderVersion++; this._clearCreatedItems(); if (!this.createdListContent || !this.createdListItemTemplate) { return; } this._layoutCreatedList(this._createdShares.length); this._hideCreatedItemTemplate(); this._createdShares.forEach((share, index) => { const item = instantiate(this.createdListItemTemplate!); item.name = `FriendsPKRankListItem_${index + 1}`; item.active = true; this.createdListContent!.addChild(item); this._createdItemNodes.push(item); this._positionCreatedItem(item, index); this._applyCreatedShare(item, share, this._renderVersion); }); 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}人参与`); const firstParticipant = this._getFirstParticipant(share); const firstName = this._getParticipantName(firstParticipant); const rankNumber = firstParticipant?.rank ?? 1; this._setLabel(this._findLabelIn(item, 'NameLabel'), firstName || (share.participantCount > 0 ? '微信用户' : '暂无参与')); 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 = () => { 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; } private _getParticipantName(participant: ShareParticipantRankSummary | null): string { return participant?.nickname || participant?.nickName || ''; } private _layoutCreatedList(itemCount: number): void { if (!this.createdListContent || !this.createdListItemTemplate) { return; } const contentTransform = this.createdListContent.getComponent(UITransform); const viewTransform = this.createdListContent.parent?.getComponent(UITransform) ?? null; const itemTransform = this.createdListItemTemplate.getComponent(UITransform); if (!contentTransform || !viewTransform || !itemTransform) { return; } const contentHeight = Math.max( viewTransform.height, PagePKData.CREATED_ITEM_TOP_PADDING + PagePKData.CREATED_ITEM_BOTTOM_PADDING + itemCount * itemTransform.height + Math.max(0, itemCount - 1) * PagePKData.CREATED_ITEM_SPACING, ); contentTransform.setContentSize(contentTransform.width, contentHeight); this.createdListContent.setPosition( this.createdListContent.position.x, viewTransform.height / 2, this.createdListContent.position.z, ); } 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) { return; } const y = -PagePKData.CREATED_ITEM_TOP_PADDING - itemTransform.height / 2 - index * (itemTransform.height + PagePKData.CREATED_ITEM_SPACING); 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; } private _loadAvatar(url: string, sprite: Sprite | null, version: number): void { if (!url || !sprite) { return; } assetManager.loadRemote(url, (err, imageAsset) => { if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) { if (err) { console.error('[PagePKData] 加载第一名头像失败:', url, err); } return; } const texture = new Texture2D(); texture.image = imageAsset; const spriteFrame = new SpriteFrame(); spriteFrame.texture = texture; sprite.spriteFrame = spriteFrame; }); } private _clearCreatedItems(): void { this._unbindCreatedButtons(); for (const item of this._createdItemNodes) { if (item.isValid) { item.removeFromParent(); item.destroy(); } } this._createdItemNodes = []; 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) { binding.node.off(Button.EventType.CLICK, binding.handler, this); } } this._createdButtonBindings = []; } private _hideCreatedItemTemplate(): void { if (this.createdListItemTemplate?.isValid) { this.createdListItemTemplate.active = false; } } private _hideParticipatedItemTemplate(): void { if (this.participatedListItemTemplate?.isValid) { this.participatedListItemTemplate.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; } } ``` ### 10.2 PK 结算页 (assets/prefabs/PagePKEnd.ts) ```typescript 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 { 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') export class PagePKEnd extends BaseView { private static readonly ANSWER_ITEM_TOP_PADDING = 16; private static readonly ANSWER_ITEM_BOTTOM_PADDING = 16; private static readonly ANSWER_ITEM_SPACING = 16; private static readonly COVER_IMAGE_WIDTH = 1299; private static readonly COVER_IMAGE_HEIGHT = 1004; @property({ type: Node, tooltip: '返回首页按钮' }) settingButton: Node | null = null; @property({ type: Label, tooltip: '顶部排名文案,例如:获得了第1名' }) rankLabel: Label | null = null; @property({ type: Label, tooltip: '答对题数文案,例如:答对了4题' }) rightNumberLabel: Label | null = null; @property({ type: Label, tooltip: '本人排名文案,例如:您获得了第1名' }) rankNumberLabel: Label | null = null; @property({ type: Label, tooltip: '参与人数文案,例如:一共66人参与了挑战' }) participateNumberLabel: Label | null = null; @property({ type: Label, tooltip: '答案列表标题' }) answerTitleLabel: Label | null = null; @property({ type: Node, tooltip: '答案列表 content 节点' }) answerListContent: Node | null = null; @property({ type: Node, tooltip: '答案列表条目模板 AnswerItem' }) answerItemTemplate: Node | null = null; private _answerItemNodes: Node[] = []; private _answerButtonBindings: Array<{ node: Node; handler: () => void }> = []; private _renderVersion: number = 0; onViewLoad(): void { this._resolveNodes(); this._bindEvents(); this._hideAnswerTemplate(); } onViewShow(): void { console.log('[PagePKEnd] onViewShow'); this._resolveNodes(); this._renderResult(this.getParams()?.result ?? null); } onViewDestroy(): void { this._unbindEvents(); this._clearAnswerItems(); } private _resolveNodes(): void { if (!this.settingButton || !this.settingButton.isValid) { this.settingButton = this.node.getChildByName('SettingButton'); } this.rankLabel = this.rankLabel ?? this._findLabel('RankLabel'); this.rightNumberLabel = this.rightNumberLabel ?? this._findLabel('RightNumberLabel'); this.rankNumberLabel = this.rankNumberLabel ?? this._findLabel('RankNumberLabel'); this.participateNumberLabel = this.participateNumberLabel ?? this._findLabel('PartipateNumberLabel'); this.answerTitleLabel = this.answerTitleLabel ?? this._findLabel('AnswerTitle'); const answerList = this.node.getChildByName('AnswerList'); const view = answerList?.getChildByName('view'); this.answerListContent = this.answerListContent ?? view?.getChildByName('content') ?? null; this.answerItemTemplate = this.answerItemTemplate ?? this.answerListContent?.getChildByName('AnswerItem') ?? null; if (!this.settingButton) { console.warn('[PagePKEnd] 未找到 SettingButton 节点'); } if (!this.answerListContent) { console.warn('[PagePKEnd] 未找到 AnswerList/content 节点'); } if (!this.answerItemTemplate) { console.warn('[PagePKEnd] 未找到 AnswerItem 模板节点'); } } private _bindEvents(): void { const button = this.settingButton?.getComponent(Button); if (!button) { console.warn('[PagePKEnd] SettingButton 缺少 Button 组件'); return; } this.settingButton?.on(Button.EventType.CLICK, this._onHomeClick, this); } private _unbindEvents(): void { if (this.settingButton && this.settingButton.isValid) { this.settingButton.off(Button.EventType.CLICK, this._onHomeClick, this); } this._unbindAnswerButtons(); } private _onHomeClick(): void { AudioManager.instance.playButtonClick(); ShareManager.instance.clearShareMode(); ViewManager.instance.replace('PageHome'); } private _renderResult(result: SubmitShareData | null): void { this._renderVersion++; this._clearAnswerItems(); if (!result) { this._setLabel(this.rankLabel, '暂无排名'); this._setLabel(this.rightNumberLabel, '答对了0题'); this._setLabel(this.rankNumberLabel, '您暂未上榜'); this._setLabel(this.participateNumberLabel, '暂无参与数据'); this._setLabel(this.answerTitleLabel, '暂无挑战结果'); this._hideAnswerTemplate(); return; } this._setLabel(this.rankLabel, `获得了第${result.rank}名`); this._setLabel(this.rightNumberLabel, `答对了${result.correctCount}题`); this._setLabel(this.rankNumberLabel, `您获得了第${result.rank}名`); this._setLabel(this.participateNumberLabel, `一共${result.participantCount}人参与了挑战`); this._setLabel(this.answerTitleLabel, `共用时${result.totalTimeSpent}s,揭晓答案吧`); this._renderAnswerList(result.levels ?? []); } private _renderAnswerList(levels: SubmittedShareLevelData[]): void { if (!this.answerListContent || !this.answerItemTemplate) { return; } this._hideAnswerTemplate(); const version = this._renderVersion; this._layoutAnswerContent(levels.length); levels.forEach((level, index) => { const item = instantiate(this.answerItemTemplate!); item.name = `AnswerItem_${index + 1}`; item.active = true; this.answerListContent!.addChild(item); this._answerItemNodes.push(item); this._positionAnswerItem(item, index); this._applyAnswerState(item, level); const coverSprite = this._findChild(item, 'CoverImage')?.getComponent(Sprite) ?? null; this._prepareCoverSprite(coverSprite); this._loadCoverImage(level.image1Url, coverSprite, version); }); this._scrollAnswerListToTop(); } private _layoutAnswerContent(itemCount: number): void { if (!this.answerListContent || !this.answerItemTemplate) { return; } const contentTransform = this.answerListContent.getComponent(UITransform); const viewTransform = this.answerListContent.parent?.getComponent(UITransform) ?? null; const itemTransform = this.answerItemTemplate.getComponent(UITransform); if (!contentTransform || !viewTransform || !itemTransform) { return; } const itemHeight = itemTransform.height; const contentHeight = Math.max( viewTransform.height, PagePKEnd.ANSWER_ITEM_TOP_PADDING + PagePKEnd.ANSWER_ITEM_BOTTOM_PADDING + itemCount * itemHeight + Math.max(0, itemCount - 1) * PagePKEnd.ANSWER_ITEM_SPACING, ); contentTransform.setContentSize(contentTransform.width, contentHeight); this.answerListContent.setPosition( this.answerListContent.position.x, viewTransform.height / 2, this.answerListContent.position.z, ); } private _positionAnswerItem(item: Node, index: number): void { const itemTransform = item.getComponent(UITransform); if (!itemTransform) { return; } const y = -PagePKEnd.ANSWER_ITEM_TOP_PADDING - itemTransform.height / 2 - index * (itemTransform.height + PagePKEnd.ANSWER_ITEM_SPACING); item.setPosition(0, y, item.position.z); } private _applyAnswerState(item: Node, level: SubmittedShareLevelData): void { const answerButton = this._findChild(item, 'ButtonViewAnswer'); const buttonLabel = answerButton?.getChildByName('Label')?.getComponent(Label) ?? null; const answerLabelNode = this._findChild(item, 'AnswerLabel'); const answerLabel = answerLabelNode?.getComponent(Label) ?? null; this._setLabel(buttonLabel, '查看答案'); this._setLabel(answerLabel, level.answer || '-'); if (level.isCorrect) { if (answerButton) { answerButton.active = false; } if (answerLabelNode) { answerLabelNode.active = true; } return; } if (answerButton) { answerButton.active = true; } if (answerLabelNode) { answerLabelNode.active = false; } const handler = () => { AudioManager.instance.playButtonClick(); if (answerButton?.isValid) { answerButton.active = false; } if (answerLabelNode?.isValid) { answerLabelNode.active = true; } }; if (answerButton) { answerButton.on(Button.EventType.CLICK, handler, this); this._answerButtonBindings.push({ node: answerButton, handler }); } } private _scrollAnswerListToTop(): void { const scrollView = this.node.getChildByName('AnswerList')?.getComponent(ScrollView); scrollView?.scrollToTop(0); } private _loadCoverImage(url: string, sprite: Sprite | null, version: number): void { if (!url || !sprite) { return; } this._prepareCoverSprite(sprite); assetManager.loadRemote(url, (err, imageAsset) => { if (err || !imageAsset || version !== this._renderVersion || !sprite.node.isValid) { if (err) { console.error('[PagePKEnd] 加载答案封面失败:', url, err); } return; } const texture = new Texture2D(); texture.image = imageAsset; const spriteFrame = new SpriteFrame(); spriteFrame.texture = texture; this._prepareCoverSprite(sprite); sprite.spriteFrame = spriteFrame; this._prepareCoverSprite(sprite); }); } private _prepareCoverSprite(sprite: Sprite | null): void { if (!sprite?.node.isValid) { return; } sprite.sizeMode = Sprite.SizeMode.CUSTOM; const transform = sprite.node.getComponent(UITransform); if (transform) { transform.setContentSize(PagePKEnd.COVER_IMAGE_WIDTH, PagePKEnd.COVER_IMAGE_HEIGHT); } sprite.node.setScale(0.242, 0.242, 0.242); } private _clearAnswerItems(): void { this._unbindAnswerButtons(); for (const item of this._answerItemNodes) { if (item.isValid) { item.removeFromParent(); item.destroy(); } } this._answerItemNodes = []; this._hideAnswerTemplate(); } private _unbindAnswerButtons(): void { for (const binding of this._answerButtonBindings) { if (binding.node.isValid) { binding.node.off(Button.EventType.CLICK, binding.handler, this); } } this._answerButtonBindings = []; } private _hideAnswerTemplate(): void { if (this.answerItemTemplate?.isValid) { this.answerItemTemplate.active = false; } } private _setLabel(label: Label | null, text: string): void { if (label) { label.string = text; } } private _findLabel(nodeName: string): Label | null { return this._findChild(this.node, 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; } } ```