import { SpriteFrame, Vec3, Node, Vec2, Button, v2, sys, instantiate, game, Game, v3, Sprite, misc, UITransform, Layers, Camera, Canvas, director, Director, ImageAsset, renderer, RenderTexture, Size, Texture2D, view, sp } from "cc"; import { PlatformManager } from "../manager/PlatformManager"; import { ResModel } from "../model/ResModel"; import { LoadTools } from "./LoadTools"; import { AStar } from "./AStar"; import { BFSPathfinding } from "./BFS"; import { SplitRenderHelper } from "../SplitRender/SplitRenderHelper"; export type index = { x: number; y: number; } //@ts-ignore game._calculateDT = function _calculateDT(useFixedDeltaTime: boolean): number { this._useFixedDeltaTime = useFixedDeltaTime; if (useFixedDeltaTime) { this._startTime = performance.now(); return this.frameTime / 1000; } const now = performance.now(); this._deltaTime = now > this._startTime ? (now - this._startTime) / 1000 : 0; if (this._deltaTime > Game.DEBUG_DT_THRESHOLD) { this._deltaTime = this.frameTime / 1000; } this._startTime = now; return this._deltaTime * Tools.getTimeScale(); } /** 游戏常用工具类 */ export class Tools { private static timeScale = 1; private static aStar: AStar = new AStar(); private static bfs: BFSPathfinding = new BFSPathfinding(); /** * 使用A*算法进行搜索。 * @param map 二维数字数组,代表地图或其他需要搜索的结构。 * @param start 搜索的起始点,其格式为 index 类型。 * @param end 搜索的终点,其格式为 index 类型。 * @param type 搜索类型,用数字表示,默认为 0。不同的类型可能影响搜索算法的行为。 * @returns 返回搜索结果,通常是一个路径数组或者搜索到的目标。 */ public static aStarSearch(map: number[][], start: index, end: index, type: number = 0) { this.aStar.setMap(map); return this.aStar.search(start, end, type); } public static setaStarValue(startValue: number, endValue: number, pathValue: number) { this.aStar.setValue(startValue, endValue, pathValue); } public static setaStarEightDirections(isEightDirections: boolean) { this.aStar.setEightDirections(isEightDirections); } public static setaStarWeightMap(weightMap: number[][]) { this.aStar.setWeightMap(weightMap); } public static setaStarNeighborMap(neighborMap: Map) { this.aStar.setNeighborMap(neighborMap); } /** * 使用广度优先搜索算法在地图上寻找从起点到终点的路径。 * @param map 二维数字数组表示的地图。 * @param start 起点的索引。 * @param end 终点的索引。 * @returns 返回一个路径数组,如果不存在路径则返回空数组。 */ public static bfsSearch(map: number[][], start: index, end: index) { this.bfs.setMap(map); // 设置广度优先搜索对象的地图 return this.bfs.findPath(start, end); // 执行搜索并返回路径 } /** * 存储本地数据 * @param {*} isObject 是否是一个对象或者数组 */ public static setStorage(key: string, value: any, isObject = false) { PlatformManager.setStorage(key, value, isObject); } /** * 获取存储数据 * @param {*} isObject 是否是一个对象或者数组 */ public static getStorage(key: string, isObject = false) { return PlatformManager.getStorage(key, isObject); }; /** * 获取时间缩放 * @returns 时间缩放 */ public static getTimeScale() { return Tools.timeScale; } /** * 设置时间缩放 * @param scale 时间缩放 */ public static setTimeScale(scale: number) { Tools.timeScale = scale; } /** * 获取截图 * @param node 截图的节点 * @param parent 截图的父节点 * @param pos 截图的位置 * @param canvas 默认摄像机渲染的canvas * @param normalCamera 默认摄像机 */ public static getScreenShot(node: Node, parent: Node, pos: Vec3, canvas: Canvas, normalCamera: Camera) { const newCamera = new Node("PhotoCamera"); newCamera.setParent(canvas.node); newCamera.setPosition(0, 0, 1000); newCamera.addComponent(Camera); newCamera.getComponent(Camera).visibility = normalCamera.visibility; newCamera.getComponent(Camera).clearFlags = normalCamera.clearFlags; newCamera.getComponent(Camera).near = normalCamera.near; newCamera.getComponent(Camera).far = normalCamera.far; newCamera.getComponent(Camera).projection = renderer.scene.CameraProjection.ORTHO; newCamera.getComponent(Camera).orthoHeight = normalCamera.orthoHeight; const newRenderTexture = new RenderTexture(); newRenderTexture.reset({ width: view.getVisibleSize().width, height: view.getVisibleSize().height }); newCamera.getComponent(Camera).targetTexture = newRenderTexture; director.once(Director.EVENT_AFTER_DRAW, () => { const newSpriteFrame = new SpriteFrame(); let data = newRenderTexture.readPixels(Math.round(node.getWorldPosition().x - node.getComponent(UITransform).width / 2 * node.scale.x), Math.round(node.getWorldPosition().y - node.getComponent(UITransform).height / 2 * node.scale.y), node.getComponent(UITransform).width * node.scale.x, node.getComponent(UITransform).height * node.scale.y) let image = new ImageAsset(); image.reset({ _data: data, _compressed: false, width: node.getComponent(UITransform).width * node.scale.x, height: node.getComponent(UITransform).height * node.scale.y, format: Texture2D.PixelFormat.RGBA8888 }) let newTexture = new Texture2D(); newTexture.image = image; newSpriteFrame.texture = newTexture; newSpriteFrame.packable = false; if (sys.isNative && (sys.os === sys.OS.IOS || sys.os === sys.OS.OSX)) { newSpriteFrame.flipUVY = false; } else { newSpriteFrame.flipUVY = true; } let newSprite = new Node("PhotoSprite"); newSprite.addComponent(Sprite); newSprite.setParent(parent); newSprite.setPosition(pos); newSprite.layer = Layers.Enum.UI_2D; newSprite.getComponent(UITransform) .setContentSize(new Size(node.getComponent(UITransform).width, node.getComponent(UITransform).height)); newSprite.getComponent(Sprite).spriteFrame = newSpriteFrame; newCamera.getComponent(Camera).targetTexture = null; }) } /** * 保存文件。 * @param textToWrite 要保存的文件内容 * @param fileNameToSaveAs 要保存的文件名 * @param fileType 要保存的文件格式,默认为Json */ public static saveForBrowser(textToWrite: any, fileNameToSaveAs: string, fileType: string = 'application/json') { if (sys.isBrowser) { console.log("浏览器"); let textFileAsBlob = new Blob([textToWrite], { type: fileType }); let downloadLink = document.createElement("a"); downloadLink.download = fileNameToSaveAs; downloadLink.innerHTML = "Download File"; downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob); downloadLink.click(); } } /** * 获取spine指定动画时间,如果没有该动画 则返回0 * @param spine 骨骼动画 * @param aniName 动画名称 * @returns 动画时间 */ public static getSpineAnimationTime(spine: sp.Skeleton, aniName: string) { const state = spine.getState(); if (state == undefined) throw `[ERROR SPINE ANIMATION] 无法获取获取动画状态`; const { animations } = state.data.skeletonData; let result = 0; for (const key in animations) { if (Object.prototype.hasOwnProperty.call(animations, key)) { const element = animations[key]; if (element.name == aniName) { result = element.duration; } } } return result; } //-----------------------------节点预制体相关------------------------------- /** * 新建一个预制体在场景里 * @param preName 预制体名字或url * @param callFunc 加载预制体 完成后回调 * @param parent 存放预制体的父节点 * @param Pos 预制体的坐标 * @param zIndex 预制体的层级 */ public static newPrefab(preName: string, parent?: Node, Pos?: Vec3, callFunc?: (age?: Node) => void): Node { return NodeTools._ins.newPrefab(preName, callFunc, parent, Pos); } /** * 新建一个图片在场景里 * @param sprName 图片名字或url * @param callFunc 加载预制体 完成后回调 * @param parent 存放预制体的父节点 * @param Pos 预制体的坐标 * @param zIndex 预制体的层级 */ public static newSprite(sprName: string, parent?: Node, Pos?: Vec3, callFunc?: (age?: Node) => void): Node { return NodeTools._ins.newSprite(sprName, callFunc, parent, Pos); } /** * 设置一个节点的SpriteFrame * @param nodeT 节点的Node * @param sprUrl 图片的url或者存放到resArr的名字 */ public static setSpriteFrame(nodeT: Node, sprUrl: string) { NodeTools._ins.setSpriteFrame(nodeT, sprUrl); } /** 设置一个节点的 groupIndex 包含子物体 */ public static setNodeLayerIndex(nodeT: Node, layer: Layers.Enum) { NodeTools._ins.setNodeLayerIndex(nodeT, layer); } /** * 设置一个Button的按下和松开的SpriteFrame * @param norUrl 默认状态的名字或者路径 * @param preUrl 按下状态的名字或者路径 */ public static setBtnClickSpr(Btn: Button, norUrl: string, preUrl: string) { NodeTools._ins.setBtnClickSpr(Btn, norUrl, preUrl); }; /** 切换父物体 不改变显示位置*/ public static setNodeParent(node: Node, parent: Node) { NodeTools._ins.setNodeParent(node, parent); }; //----------------------------------数学数组相关---------------------------------- /** * 将多维数组展平 * @param arr 需要展平的多维数组 * @returns 展平后的数组 */ public static flatArray(arr: any[]) { return arr.reduce((acc, curr) => { return acc.concat(Array.isArray(curr) ? Tools.flatArray(curr) : curr); }, []); } /** * 从数组中随机获取元素 * @param arr 需要随机获取元素的数组 * @param num 需要获取的元素数量 * @param sortFunc 排序函数,可选 * @returns 返回随机获取的元素数组 */ public static randomGetElementFromArray(arr: any[], num: number, sortFunc: (a: any, b: any) => number = null) { const tempArr = arr.slice(); const resultArr = []; while (resultArr.length < num) { const randomIndex = Tools.random(0, tempArr.length - 1); resultArr.push(tempArr[randomIndex]); tempArr.splice(randomIndex, 1); } if (sortFunc) { resultArr.sort(sortFunc); } return resultArr; } /** * 获取随机数 * @param isInteger 是否随机整数 默认整数 */ public static random(x1: number, x2: number, isInteger = true): number { return MathTools._ins.random(x1, x2, isInteger); } /** * 根据概率数组 随机概率 返回数组的index * @param chooseArr 需要随机概率的数组 例如[0.05,0.1,0.2,0.3] */ public static chooseRandom(chooseArr: Array) { return MathTools._ins.chooseRandom(chooseArr); } /** 传入一个弧度 返回一个Y轴折射后的弧度 */ public static refractionY(rad: number) { return MathTools._ins.refractionY(rad); }; /** 传入一个弧度 返回一个轴折射后的弧度 */ public static refractionX(rad: number) { return MathTools._ins.refractionX(rad); }; /** 重新打乱一个数组的顺序 洗牌 */ public static againSortArr(Arr: Array) { MathTools._ins.againSortArr(Arr); }; /** * 将一个数组 按照里面的对象排序 * @param tempArr 传入的数组 * @param sortName 对象属性名字 * @param isReverse 是否倒序 */ public static sortArrForObject(tempArr: Array, sortName: string, isReverse = false) { MathTools._ins.sortArrForObject(tempArr, sortName, isReverse); }; /** * 取一定范围内不重复的数字 * @param minNum 最小取值范围 * @param maxNum 最大取值范围 * @param getNum 取几个数字 */ public static getDiffNumRandom(minNum: number, maxNum: number, getNum: number) { return MathTools._ins.getDiffNumRandom(minNum, maxNum, getNum); }; //--------------------------------向量坐标计算相关------------------------------------ /** * 根据两个点 求角度 * @param pos1 起始点坐标 * @param pos2 结束点坐标 * @param isVertical 是否以竖直方向为0度开始 */ public static getAngleForPos(pos1: Vec2, pos2: Vec2, isVertical = false): number { return VecTools._ins.getAngleForPos(pos1, pos2, isVertical); }; /** 获取两个坐标之间的距离 */ public static getDistance(pos1: Vec3 | Vec2, pos2: Vec3 | Vec2): number { if (pos1 instanceof Vec2 && pos2 instanceof Vec2) { return Vec2.distance(pos1, pos2); } if (pos1 instanceof Vec3 && pos2 instanceof Vec3) { return Vec3.distance(pos1, pos2); } }; /** * 根据一个角度和长度 计算相对应的坐标 * @param angle 角度 * @param len 该角度上的长度 * @param startPos 初始的坐标 */ public static getPosForAngleLen(angle: number, len: number, startPos: Vec2 = v2(0, 0)) { return VecTools._ins.getPosForAngleLen(angle, len, startPos); } /** * 获取节点在另一个节点下的坐标 * @param obj 节点 * @param mainObj 相对于的另一个节点 */ public static getToNodePosForNode(obj: Node, mainObj: Node): Vec2 { return VecTools._ins.getToNodePosForNode(obj, mainObj); }; /** 获取节点的世界坐标 */ public static getToWorldPosAR(obj: Node) { return VecTools._ins.getToWorldPosAR(obj); } /** * 通过世界坐标 获取相对节点的坐标 * @param worldPos 世界坐标 * @param obj 相对节点下的 */ public static getToNodePosForWorld(worldPos: Vec3, obj: Node) { return VecTools._ins.getToNodePosForWorld(worldPos, obj); } /** 二维向量转三维向量 */ public static getVec3ForVec2(vec2: Vec2) { return VecTools._ins.getVec3ForVec2(vec2); } /** 三维向量转二维向量 */ public static getVec2ForVec3(vec3: Vec3) { return VecTools._ins.getVec2ForVec3(vec3); } /** 获取两向量方向 */ public static getDirection(startPos: Vec3 | Vec2, endPos: Vec3 | Vec2) { return VecTools._ins.getDirection(startPos, endPos); } /** 获取多边形面积 */ public static getPolygonArea(polygon: Vec2[]) { return VecTools._ins.getPolygonArea(polygon); } //--------------------------------数组操作相关------------------------------------ /** 根据value值 从数组里面移除 */ public static removeArrForValue(tempArr: Array, value: any) { return tempArr.splice(tempArr.indexOf(value), 1); } /** 从数组里面添加一个该数组里没有的元素 */ public static addArrNoValue(tempArr: Array, value: any) { if (tempArr.indexOf(value) < 0) { tempArr.push(value); return true; } return false; } /** 从数组指定位置 插入某个元素 */ public static addArrIndex(tempArr: Array, index: number, value: any) { return tempArr.splice(index, 0, value); } //--------------------------------其他------------------------------------ /** * 字符串指定位置插入新字符 * @param source 需要操作的字符串 * @param start 从那个位置开始插入 * @param newStr 插入的新的字符 */ public static insertStrForIndex(source: string, start: number, newStr: string): string { return source.slice(0, start) + newStr + source.slice(start); }; /** * 数字整数前边补零 并返回字符串 * @param num 传入的数字 * @param length 前边补几个零 */ public static prefixInteger(num: number, length = 2): string { return (Array(length).join('0') + num).slice(-length); }; /** 获取系统语言 */ public static getLanguageType(): string { var langNumType = "EN"; //默认英语 if (sys.language == sys.Language.CHINESE) { if (sys.languageCode.toLowerCase().indexOf("zh-cn") != -1 || sys.languageCode.toLowerCase().indexOf("zh_cn") != -1 || sys.languageCode.toLowerCase().indexOf("zh-hans-cn") != -1) { langNumType = "CN"; //简体 } else { langNumType = "CHT"; //繁体 } } // else if ( sys.language == sys.Language.KOREAN ) { // langNumType = "KOR"; //韩语 // } // else if (sys.language == sys.Language.JAPANESE) { // langNumType = "JP"; //日语 // } // else if ( window.navigator.language == "th-TH" ) { // langNumType = "TH"; //泰语 // } return langNumType; } } /** 节点相关 工具类 */ class NodeTools { /** 单例模式 */ private static _instance: NodeTools = new NodeTools(); private constructor() { } public static get _ins() { return this._instance; } /** 新建一个预制体在场景里 */ public newPrefab(preName: string, callFunc?: (age?: Node) => void, parent?: Node, Pos?: Vec3): Node { let prefab = ResModel._ins.PrefabDic.get(preName); let clone: Node = null; if (prefab != null) { clone = instantiate(prefab); if (parent) { parent.addChild(clone) }; if (Pos) { clone.position = v3(Pos.x, Pos.y, Pos.z) }; if (callFunc != null) { callFunc(clone); } } else { LoadTools._ins.loadResPrefab(preName, callFunc, parent, Pos); } return clone; } /** 新建一个图片在场景里 */ public newSprite(sprName: string, callFunc?: (age?: Node) => void, parent?: Node, Pos?: Vec3) { let sprite = new Node(); sprite.name = sprName; sprite.layer = Layers.Enum.UI_2D; if (ResModel._ins.SpriteFrameDic.get(sprName) != null) { sprite.addComponent(Sprite).spriteFrame = ResModel._ins.SpriteFrameDic.get(sprName); if (parent) { parent.addChild(sprite) }; if (Pos) { sprite.position = v3(Pos.x, Pos.y, Pos.z) }; if (callFunc != null) { callFunc(sprite); } } else { sprite.addComponent(Sprite); LoadTools._ins.loadResSpriteFrame(sprName, sprite, parent, Pos, callFunc); } return sprite; } /** 设置一个节点的SpriteFrame */ public setSpriteFrame(nodeT: Node, sprUrl: string) { if (nodeT.getComponent(Sprite) == null) { nodeT.addComponent(Sprite); } if (ResModel._ins.SpriteFrameDic.get(sprUrl)) { nodeT.getComponent(Sprite).spriteFrame = ResModel._ins.SpriteFrameDic.get(sprUrl) } else { LoadTools._ins.loadResAny(sprUrl, SpriteFrame, (spriteFrame: SpriteFrame) => { nodeT.getComponent(Sprite).spriteFrame = spriteFrame; }) } } /** 设置一个节点的 所属层 包含子物体 */ public setNodeLayerIndex(nodeT: Node, layer: Layers.Enum) { nodeT.layer = layer; for (let i = 0; i < nodeT.children.length; i++) { this.setNodeLayerIndex(nodeT.children[i], layer); } } /** 设置一个Button的按下和松开的SpriteFrame */ public setBtnClickSpr(Btn: Button, norUrl: string, preUrl: string) { if (ResModel._ins.SpriteFrameDic.get(norUrl)) { Btn.getComponent(Button).normalSprite = ResModel._ins.SpriteFrameDic.get(norUrl) Btn.getComponent(Button).hoverSprite = ResModel._ins.SpriteFrameDic.get(norUrl) Btn.getComponent(Button).pressedSprite = ResModel._ins.SpriteFrameDic.get(preUrl) } else { LoadTools._ins.loadResAny(norUrl, SpriteFrame, (spr: SpriteFrame) => { Btn.getComponent(Button).normalSprite = spr; Btn.getComponent(Button).hoverSprite = spr; }); LoadTools._ins.loadResAny(preUrl, SpriteFrame, (spr: SpriteFrame) => { Btn.getComponent(Button).pressedSprite = spr; }); } }; /** 切换父物体 不改变坐标 */ public setNodeParent(node: Node, parent: Node) { let Pos = VecTools._ins.getToNodePosForNode(node, parent); node.parent = parent; node.position = v3(Pos.x, Pos.y); }; } /** 数学数组计算相关 工具类 */ class MathTools { /** 单例模式 */ private static _instance: MathTools = new MathTools(); private constructor() { } public static get _ins() { return this._instance; } /** 获取随机数 */ public random(x1: number, x2: number, isInteger = true): number { if (isInteger) { return x1 + Math.floor(Math.random() * (x2 - x1 + 1)); } return Math.random() * (x2 - x1) + x1; } /** 根据概率数组 随机概率 返回数组的index */ public chooseRandom(chooseArr: Array) { let total = 0; //概率总值 //首先计算出概率的总值,用来计算随机范围 for (let i = 0; i < chooseArr.length; i++) { total += chooseArr[i]; } let randNum = this.random(0, total, false) for (let i = 0; i < chooseArr.length; i++) { if (randNum < chooseArr[i] && chooseArr[i] > 0) { return i; } else { randNum -= chooseArr[i]; } } return chooseArr.length - 1; } /** 弧度折射Y轴 */ public refractionY(rad: number) { return Math.atan2(Math.sin(rad), -Math.cos(rad)); }; /** 弧度折射X轴 */ public refractionX(rad: number) { return Math.atan2(-Math.sin(rad), Math.cos(rad)); }; /** 重新洗牌 一个数组 */ public againSortArr(Arr: Array) { for (let i = 0; i < Arr.length; i++) { let tempR = Tools.random(0, Arr.length - 1); if (tempR != i) { let temp = Arr[i]; Arr[i] = Arr[tempR]; Arr[tempR] = temp; } } } /** 数组 对象排序 对象属性 是否倒序 */ public sortArrForObject(tempArr: Array, sortName: string, isReverse = false) { if (!isReverse) { tempArr.sort((a, b) => { return a[sortName] - b[sortName]; }); } else { tempArr.sort((a, b) => { return b[sortName] - a[sortName]; }); } }; /** 取一定范围内不重复的数字 */ public getDiffNumRandom(minNum: number, maxNum: number, getNum: number) { var arr = []; for (let i = minNum; i <= maxNum; i++) { arr.push(i); } const tempLen = arr.length - getNum; for (let i = 0; i < tempLen; i++) { let tempI = Tools.random(0, arr.length - 1); arr.splice(tempI, 1); } return arr; }; } /** 向量坐标转换相关工具类 */ class VecTools { /** 单例模式 */ private static _instance: VecTools = new VecTools(); private constructor() { } public static get _ins() { return this._instance; } /** 根据两个点 求角度 */ public getAngleForPos(pos1: Vec2, pos2: Vec2, isVertical = false): number { let rad = 0; if (isVertical) { rad = -Math.atan2(pos2.x - pos1.x, pos2.y - pos1.y); } else { rad = Math.atan2(pos2.y - pos1.y, pos2.x - pos1.x); } return misc.radiansToDegrees(rad); } /** 根据一个角度和长度 计算相对应的坐标 */ public getPosForAngleLen(angle: number, len: number, startPos: Vec2 = v2(0, 0)) { let rad = misc.degreesToRadians(angle); return v2(startPos.x + Math.cos(rad) * len, startPos.y + Math.sin(rad) * len); } /** 获取节点在另一个节点下的坐标 */ public getToNodePosForNode(obj: Node, mainObj: Node): Vec2 { let worldPos = obj.parent.getComponent(UITransform).convertToWorldSpaceAR(obj.position); let nodePos = mainObj.getComponent(UITransform).convertToNodeSpaceAR(worldPos) return v2(nodePos.x, nodePos.y); }; /** 获取节点的世界坐标 */ public getToWorldPosAR(obj: Node) { return obj.parent.getComponent(UITransform).convertToWorldSpaceAR(obj.position); } /** 通过世界坐标 获取相对节点的坐标 */ public getToNodePosForWorld(worldPos: Vec3, obj: Node) { return obj.getComponent(UITransform).convertToNodeSpaceAR(worldPos); } /** 二维向量转三维向量 */ public getVec3ForVec2(vec2: Vec2) { return v3(vec2.x, vec2.y, 0); } /** 三维向量转二维向量 */ public getVec2ForVec3(vec3: Vec3) { return v2(vec3.x, vec3.y); } /** 获取两向量方向和距离 * @param startPos 起始位置 * @param endPos 结束位置 */ public getDirection(startPos: Vec3 | Vec2, endPos: Vec3 | Vec2) { if (startPos instanceof Vec2 && endPos instanceof Vec2) { const deltaPos = endPos.clone().subtract(startPos); const direction = deltaPos.normalize(); return direction; } else if (startPos instanceof Vec3 && endPos instanceof Vec3) { const deltaPos = endPos.clone().subtract(startPos); const direction = deltaPos.normalize(); return direction; } } public getPolygonArea(polygon: Vec2[]) { return SplitRenderHelper.calculatePolygonArea(polygon); } }