123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- 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!;
- }
- }
|