import { _decorator, Component, Node, Prefab, instantiate, Canvas, Vec3, find, director, UITransform, resources, tween, Tween, } from "cc"; import { TipItem } from "./TipItem"; const { ccclass, property } = _decorator; @ccclass("Tips") export class Tips extends Component { public static show(content: string, worldPos?: Vec3) { Tips.ins.showTip(content, worldPos); } private static _ins: Tips = null!; // TipItem预制体 @property(Prefab) public tipPrefab: Prefab = null!; private tipContainer: Node = null!; // 对象池 private tipPool: Node[] = []; private activeTips: Node[] = []; // 配置参数 private readonly MAX_POOL_SIZE = 20; // 对象池最大数量 private readonly MAX_ACTIVE_TIPS = 10; // 同时显示的最大提示数量 private offsetY = 0; // Y轴偏移,用于错开显示位置 public static get ins(): Tips { return this._ins ??= new Tips(); } protected onLoad(): void { Tips._ins = this; director.addPersistRootNode(this.node); this.createContainer(this.node); this.preCreateTipItems(); } /** * 创建提示容器 */ private createContainer(parent: Node): void { // 创建TipContainer this.tipContainer = new Node("TipContainer"); this.tipContainer.addComponent(UITransform); this.tipContainer.parent = parent; // 设置容器层级较高,确保在最前面显示 this.tipContainer.setSiblingIndex(999); } /** * 预创建TipItem到对象池 */ private preCreateTipItems(): void { for (let i = 0; i < 5; i++) { const tipNode = this.createTipItem(); tipNode.active = false; this.tipPool.push(tipNode); } } /** * 创建TipItem节点 */ private createTipItem(): Node { if (!this.tipPrefab) { console.error("TipItem预制体未加载"); return null!; } const tipNode = instantiate(this.tipPrefab); tipNode.parent = this.tipContainer; // 确保有TipItem组件 let tipComponent = tipNode.getComponent(TipItem); if (!tipComponent) { tipComponent = tipNode.addComponent(TipItem); } return tipNode; } /** * 从对象池获取TipItem */ private getTipItemFromPool(): Node { // 先检查是否有可用的池对象 for (let i = 0; i < this.tipPool.length; i++) { const tipNode = this.tipPool[i]; const tipComponent = tipNode.getComponent(TipItem); if (tipComponent && !tipComponent.getIsInUse()) { this.tipPool.splice(i, 1); return tipNode; } } // 如果池中没有可用对象,且总数未达到上限,创建新的 const totalCount = this.tipPool.length + this.activeTips.length; if (totalCount < this.MAX_POOL_SIZE) { return this.createTipItem(); } // 如果达到最大数量,强制回收最老的提示 if (this.activeTips.length > 0) { const oldestTip = this.activeTips[0]; const tipComponent = oldestTip.getComponent(TipItem); if (tipComponent) { tipComponent.forceRecycle(); } // 回收后应该有可用的对象了,再次检查池 for (let i = 0; i < this.tipPool.length; i++) { const tipNode = this.tipPool[i]; const tipComponent = tipNode.getComponent(TipItem); if (tipComponent && !tipComponent.getIsInUse()) { this.tipPool.splice(i, 1); return tipNode; } } } // 最后的备选方案,创建新的(但这种情况应该很少发生) console.warn("对象池逻辑异常,创建新的TipItem"); return this.createTipItem(); } /** * 回收TipItem到对象池 */ public recycleTipItem(tipNode: Node): void { // 从激活列表中移除 const index = this.activeTips.indexOf(tipNode); if (index >= 0) { this.activeTips.splice(index, 1); } // 添加到池中(节点已经在TipItem.recycle()中设置为inactive) if (this.tipPool.length < this.MAX_POOL_SIZE) { this.tipPool.push(tipNode); } else { // 池已满,销毁节点 tipNode.destroy(); } } /** * 调整Y轴偏移 */ private adjustOffsetY(): void { this.offsetY = Math.max(0, this.offsetY - 80); } /** * 将所有现有的提示向上推移 */ private pushExistingTipsUp(): void { for (const tipNode of this.activeTips) { // 先停止该节点上所有的tween动画,避免动画冲突 Tween.stopAllByTarget(tipNode); const currentPos = tipNode.getPosition(); const newPos = new Vec3(currentPos.x, currentPos.y + 80, currentPos.z); // 使用缓动动画让推移更平滑 tween(tipNode) .to(0.2, { position: newPos }, { easing: "quadOut" }) .start(); } } /** * 显示提示 * @param content 提示内容 * @param worldPos 世界坐标位置,如果不传则显示在屏幕中心上方 */ public showTip(content: string, worldPos?: Vec3): void { if (!this.tipContainer) { console.error("TipManager未初始化"); return; } // 检查是否超过最大显示数量,如果超过则强制回收最老的提示 if (this.activeTips.length >= this.MAX_ACTIVE_TIPS) { const oldestTip = this.activeTips[0]; const tipComponent = oldestTip.getComponent(TipItem); if (tipComponent) { tipComponent.forceRecycle(); } // 注意:forceRecycle会调用recycle,recycle会调用recycleTipItem, // recycleTipItem会从activeTips中移除,所以这里不需要手动移除 } // 将所有现有的提示向上推移 //this.pushExistingTipsUp(); // 获取TipItem const tipNode = this.getTipItemFromPool(); if (!tipNode) { console.error("无法获取TipItem"); return; } // 计算显示位置 - 新提示始终在基础位置显示 let displayPos: Vec3; if (worldPos) { // 转换世界坐标到UI坐标 displayPos = this.worldToUIPos(worldPos); } else { // 新提示始终在固定位置显示 displayPos = new Vec3(0, 200, 0); } // 添加到激活列表 this.activeTips.push(tipNode); // 初始化并显示 const tipComponent = tipNode.getComponent(TipItem); if (tipComponent) { tipComponent.init(content, displayPos); } } /** * 将世界坐标转换为UI坐标 */ private worldToUIPos(worldPos: Vec3): Vec3 { // 这里需要根据实际的坐标系统来转换 // 简单实现,可以根据需要调整 return new Vec3(worldPos.x, worldPos.y + 100, 0); } /** * 清除所有提示 */ public clearAllTips(): void { // 强制回收所有激活的提示 for (const tipNode of this.activeTips) { const tipComponent = tipNode.getComponent(TipItem); if (tipComponent) { tipComponent.forceRecycle(); } } } /** * 销毁管理器 */ public onDestroy(): void { if (2 > 1) { return; } this.clearAllTips(); // 销毁对象池中的所有节点 for (const tipNode of this.tipPool) { tipNode.destroy(); } this.tipPool = []; this.activeTips = []; // 销毁容器 if (this.tipContainer) { this.tipContainer.destroy(); this.tipContainer = null!; } Tips._ins = null!; } }