是一种设计程序的思路/套路.
总结内涵:拿数据-根据逻辑-刷新界面
防止一个全局使用的类频繁的实现与销毁
控制实例数目,节省系统资源
/**单例类 */ export default class InstanceDemo { /**全局唯一的对象 */ private static _instance: InstanceDemo = null; //private的变量名前一般加个_ /**获取单例对象 */ public static getInstance(): InstanceDemo{ if (InstanceDemo._instance == null){ InstanceDemo._instance = new InstanceDemo(); } return InstanceDemo._instance; } /**防止创建第二个对象 */ constructor(){ if (InstanceDemo._instance != null) { throw Error("Already has InstanceDemo._instance"); } } num: number = 0; trace(){ this.num ++; console.log("trace",this.num); } }
import InstanceDemo from "./instanceDemo"; const {ccclass, property} = cc._decorator; @ccclass export default class HelloWorld extends cc.Component { start () { /**获取单例对象 */ let instance = InstanceDemo.getInstance(); /**调用单例方法 */ instance.trace(); //创建对象 new InstanceDemo(); } // update (dt) {} }
实现单例模式,将构造函数私有化就好了
/**私有化构造函数*/ private constructor(){}
特点:A不管什么时候出发,只负责触发时接受,B不知道谁注册了事件,只负责触发.
将WebStorm改为ES6:File→Setting→Languages&Framework→JavaScript→选择ES6
还要将typescriptconfig.json里的“ES5”改为“ES6”
EventCenter.ts 事件控制中心
EventHandler 记录事件信息
/**观察者模式 */ export default class EventCenter { //事件数据 存放事件名和注册该事件的注册信息们 private static events: Map<string,Array<EventHandler>> = new Map<string,Array<EventHandler>>(); /**注册事件 * eventName: string 事件名 * target: object 谁注册的事件 用于回调绑定 * callBack: Function 回调 */ static registerEvent(eventName: string,target: object,callBack: Function): void { if (eventName == undefined || target == undefined || callBack == undefined) { throw Error("regsiter event error"); } /**判断是否已经有该事件被注册过 */ if (EventCenter.events[eventName] == undefined){ EventCenter.events[eventName] = new Array<EventHandler>(); } /**将此次注册事件的信息存入Map中 */ let handler = new EventHandler(target,callBack); EventCenter.events[eventName].push(handler); } /**触发事件 * eventName: string 事件名 * param?: any 回调参数 */ static postEvent(eventName: string,param?: any): void { let handlers = EventCenter.events[eventName]; if (handlers == undefined){ return; } //遍历所有注册了该事件的eventHandler for (let i = 0; i < handlers.length; i++){ let handler = handlers[i]; if (handler){ //调用事件回调 //使用try-catch防止回调有报错但没有信息 try { //.call(绑定的this,参数) 调用方法 handler.function.call(handler.target,param); }catch (e){ console.log(e.message); console.log(e.stack.toString()); //输出堆栈信息 } } } } /**移除注册事件 * eventName: string 事件名 * target: object 谁注册的事件 用于回调绑定 * callBack: Function 注册事件回调 */ static removeEvent(eventName: string,target: object,callBack: Function): void { if (eventName == undefined || target == undefined || callBack == undefined) { throw Error("destory event failed"); } let handlers = EventCenter.events[eventName]; if (handlers){ for (let i = 0; i < handlers.length; i++){ let handler = handlers[i]; if (handler && target == handler.target && callBack == handler.function){ //有两种移除方法 "= undefined"性能要好 "splice"要省内存空间 handlers[i] = undefined; // handlers.splice(i,1); break; } } } } } /**注册信息类 */ class EventHandler { /**记录谁注册了事件 */ target: object; /**记录事件触发时调用的方法 */ function: Function; constructor(target: object,func: Function){ this.target = target; this.function = func; } }
Panel.ts 用来注册事件
import EventCenter from "./EventCenter"; /**用来注册事件 检验观察者模式 */ const {ccclass, property} = cc._decorator; @ccclass export default class Panel extends cc.Component { @property(cc.Label) label: cc.Label = null; onl oad () { EventCenter.registerEvent("gameStart",this,this.onGameStart); //5s后移除事件注册 this.scheduleOnce(function() { this.onDestroy(); }.bind(this),5) } /**注册事件回调 */ onGameStart(str: string){ console.log("event callBack"); this.label.string = str; } onDestroy(){ //移除gameStart事件 console.log("remove event gameStart"); EventCenter.removeEvent("gameStart",this,this.onGameStart); } }
触发事件
EventCenter.postEvent("gameStart","game is start!");
点击按钮
输出
注意:target节点destory后,一定要记得注销其注册的事件。否则callBack会出错
操作的对象本身只实现功能方法,具体的操作由工厂实现
这样不会暴露对象及创建逻辑
/**工厂模式 */ //c: {new ():T} 告诉ide这个T类型的c是可以被实例化的 export function createAttack<T extends IActor>(c: {new (): T},life: number): T { let object = new c(); object.attack(); object.life = life; return object; } export function createDie<T extends IActor>(c: {new (): T}): T { let object = new c(); object.die(); return object; } /**角色接口 */ interface IActor { attack: Function; die: Function; life: number; } /**盗贼 */ export class Thief implements IActor { life: number; attack() { console.log("thief attack"); } die() { console.log("thief is die"); } } /**战士 */ export class Warrior implements IActor { life: number; attack() { console.log("warrior attack"); } die() { console.log("warrior is die"); } }
调用
createAttack<Thief>(Thief,10); createDie<Warrior>(Warrior);
需要从①走到②,要怎么走(可以斜着走,褐色是不能走的)
import FindPath from "./FindPath"; /**寻路地图格子 */ const {ccclass, property} = cc._decorator; /**格子 显示层 */ @ccclass export default class NodeGrid extends cc.Component { dataGrid: DataGrid = null; findPathController: FindPath; onl oad () { this.node.on(cc.Node.EventType.TOUCH_END,this.onBtnGrid,this); } /**点击格子 确定起点终点 生成路线 */ onBtnGrid(){ this.findPathController.onTouch(this); } /**刷新格子颜色 */ updateGridColor(){ if (this.dataGrid.type == GrideType.Normal){ this.node.color = new cc.Color().fromHEX("#fffff9"); } else if (this.dataGrid.type == GrideType.Wall){ this.node.color = new cc.Color().fromHEX("#151513"); } else if (this.dataGrid.type == GrideType.Road){ this.node.color = new cc.Color().fromHEX("#41ff0b"); } else { this.node.color = new cc.Color().fromHEX("#fff42d"); } } } /**格子数据 数据层 */ export class DataGrid { type: GrideType; //坐标 x: number; y: number; /**是否为当前节点 */ inOpenList: boolean = false; /**路径节点标记 */ inCloseList: boolean = false; } /**格子类型枚举 */ export enum GrideType { Normal, //普通 Wall, //墙 Start, //起点,当前节点 End, //终点 Road, //路线 }
在场景下创建一个40x40的格子,并挂载NodeGrid.ts
/**随机生成8x8地图 */ generateMap () { for (let x = 0;x < 8;x ++){ this.dataGrids[x] = []; this.nodeGrids[x] = []; for (let y = 0;y < 8;y ++){ let rand = Math.random(); let grideType: GrideType = GrideType.Normal; if (rand < 0.2) { //1/5的概率生成墙 grideType = GrideType.Wall; } //数据层 let grid: DataGrid = new DataGrid(); grid.x = x; grid.y = y; grid.type = grideType; this.dataGrids[x][y] = grid; //视图层 let gridNode: NodeGrid = cc.instantiate(this.nodeGridPrefab).getComponent(NodeGrid); gridNode.node.position = cc.v3(50 * (x - 4),50 * (y - 4),0); this.nodeGrids[x][y] = gridNode; gridNode.dataGrid = grid; gridNode.findPathController = this; gridNode.updateGridColor(); gridNode.node.parent = this.node; } } }
import NodeGrid, { DataGrid, GrideType } from "./NodeGrid"; /**递归寻路 */ const {ccclass, property} = cc._decorator; @ccclass export default class NewClass extends cc.Component { /**格子节点 */ @property(cc.Node) nodeGridPrefab: cc.Node = null; dataGrids: DataGrid[][] = []; nodeGrids: NodeGrid[][] = []; /**记录起点 */ startGrid: DataGrid = null; /**记录终点 */ endGrid: DataGrid = null; onl oad () { this.generateMap(); } /**随机生成8x8地图 */ generateMap () { for (let x = 0;x < 8;x ++){ this.dataGrids[x] = []; this.nodeGrids[x] = []; for (let y = 0;y < 8;y ++){ let rand = Math.random(); let grideType: GrideType = GrideType.Normal; if (rand < 0.2) { //1/5的概率生成墙 grideType = GrideType.Wall; } //数据层 let grid: DataGrid = new DataGrid(); grid.x = x; grid.y = y; grid.type = grideType; this.dataGrids[x][y] = grid; //视图层 let gridNode: NodeGrid = cc.instantiate(this.nodeGridPrefab).getComponent(NodeGrid); gridNode.node.position = cc.v3(50 * (x - 4),50 * (y - 4),0); this.nodeGrids[x][y] = gridNode; gridNode.dataGrid = grid; gridNode.findPathController = this; gridNode.updateGridColor(); gridNode.node.parent = this.node; } } } /**点击格子 */ onTouch(nodeGrid: NodeGrid){ if (!this.startGrid) { //设置起点 this.startGrid = nodeGrid.dataGrid; this.startGrid.type = GrideType.Start; nodeGrid.updateGridColor(); }else if (!this.endGrid) { //设置终点 this.endGrid = nodeGrid.dataGrid; this.endGrid.type = GrideType.End; nodeGrid.updateGridColor(); //寻路 this.startFindPath(); } } openPath: DataGrid[] = []; /**寻路 */ startFindPath(){ if (this.find(this.startGrid)) { for (let i = 0; i < this.openPath.length; i++) { let path = this.openPath[i]; path.type = GrideType.Road; this.nodeGrids[path.x][path.y].updateGridColor(); } }else { console.log("无法走到终点"); } } find(base: DataGrid) { this.openPath.push(base); base.inOpenList = true; if (base == this.endGrid){ //寻路结束 return true; } let round = this.getRoundGrid(base); for (let i = 0;i < round.length;i ++) { let nextBaseGride = round[i]; if (this.find(nextBaseGride)) { return true; } } base.inCloseList = true; this.openPath.splice(this.openPath.length - 1,1); return false; } /**获取当前节点周围可走的节点 */ getRoundGrid(grid: DataGrid): DataGrid[] { let arr: DataGrid[] = []; //周围的格子 this.addToRoundIfNeed(arr,this.getGrid(grid.x,grid.y + 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y)); this.addToRoundIfNeed(arr,this.getGrid(grid.x,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y + 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y + 1)); //会将数组里元素两两进行比较,自定义方法里返回-1就不交换位置 返回1交换位置 arr.sort(this.compareGrids.bind(this)); return arr; } /**将格子放到数组里 */ addToRoundIfNeed(arr: DataGrid[],roundGrid: DataGrid) { //当前节点和路径节点都不计入 if (!roundGrid || roundGrid.type == GrideType.Wall || roundGrid.inCloseList || roundGrid.inOpenList){ return; } if (roundGrid) { arr.push(roundGrid); } } /**根据坐标获取格子数据 */ getGrid(x: number,y: number): DataGrid { //边界判断 if (x < 0 || x >= 8 || y < 0 || y >=8){ return null; } return this.dataGrids[x][y]; } /**格子比较和终点的距离 * 距离小的放在前面 */ compareGrids(grid0: DataGrid,grid1: DataGrid): number{ let grid0Dis = this.getDistance(grid0); let grid1Dis = this.getDistance(grid1); if (grid0Dis > grid1Dis) { return 1; }else{ return -1; } } /**获取节点到终点距离 */ getDistance(grid: DataGrid){ return Math.abs(grid.x - this.endGrid.x) + Math.abs(grid.y - this.endGrid.y); } /**点击重新开始*/ onBtnRestart(){ for (let x = 0;x < this.dataGrids.length;x ++){ for (let y = 0;y < this.dataGrids[x].length;y ++){ let dataGrid = this.dataGrids[x][y]; dataGrid.inOpenList = false; dataGrid.inCloseList = false; if (dataGrid.type != GrideType.Wall){ dataGrid.type = GrideType.Normal; } this.nodeGrids[x][y].updateGridColor(); } } this.startGrid = null; this.endGrid = null; this.openPath = []; } }
当前算法只是寻找下一步的最优解,并不是全局的最优解,有时并不是最优路径。
可以看下大神写的A星寻路算法原理
DataGrid添加一个字段
/**父节点 用于A星寻路 */ fatherGrid: DataGrid = null;
FindPathAX.ts完整代码
/**A星寻路 */ import NodeGrid, { DataGrid, GrideType } from "./NodeGrid"; const {ccclass, property} = cc._decorator; @ccclass export default class FindPathAX extends cc.Component { /**格子节点 */ @property(cc.Node) nodeGridPrefab: cc.Node = null; dataGrids: DataGrid[][] = []; nodeGrids: NodeGrid[][] = []; /**记录起点 */ startGrid: DataGrid = null; /**记录终点 */ endGrid: DataGrid = null; onl oad () { this.generateMap(); } /**随机生成8x8地图 */ generateMap () { for (let x = 0;x < 8;x ++){ this.dataGrids[x] = []; this.nodeGrids[x] = []; for (let y = 0;y < 8;y ++){ let rand = Math.random(); let grideType: GrideType = GrideType.Normal; if (rand < 0.2) { //1/5的概率生成墙 grideType = GrideType.Wall; } //数据层 let grid: DataGrid = new DataGrid(); grid.x = x; grid.y = y; grid.type = grideType; this.dataGrids[x][y] = grid; //视图层 let gridNode: NodeGrid = cc.instantiate(this.nodeGridPrefab).getComponent(NodeGrid); gridNode.node.position = cc.v3(50 * (x - 4),50 * (y - 4),0); this.nodeGrids[x][y] = gridNode; gridNode.dataGrid = grid; gridNode.findPathController = this; gridNode.updateGridColor(); gridNode.node.parent = this.node; } } } /**点击格子 */ onTouch(nodeGrid: NodeGrid){ if (!this.startGrid) { //设置起点 this.startGrid = nodeGrid.dataGrid; this.startGrid.type = GrideType.Start; nodeGrid.updateGridColor(); }else if (!this.endGrid) { //设置终点 this.endGrid = nodeGrid.dataGrid; this.endGrid.type = GrideType.End; nodeGrid.updateGridColor(); //寻路 this.startFindPathAStar(); } } /**待考虑的节点列表 */ openPath: DataGrid[] = []; /**A星寻路 */ startFindPathAStar(){ this.openPath.push(this.startGrid); this.startGrid.inOpenList = true; while (this.openPath.length > 0) { let current = this.openPath.shift(); //shift--取出数组中首个元素 if (current == this.endGrid) { break; } let round = this.getRoundGrid(current); for (let i = 0;i < round.length;i ++) { let r = round[i]; r.fatherGrid = current; r.inOpenList = true; } this.openPath = this.openPath.concat(round); //拼接数组 this.openPath.sort(this.compareGridsAStar.bind(this)); current.inCloseList = true; } if (this.endGrid.fatherGrid) { let pathGrid = this.endGrid; while (pathGrid) { pathGrid.type == GrideType.Road; this.nodeGrids[pathGrid.x][pathGrid.y].updateGridColor(); pathGrid = pathGrid.fatherGrid; } }else { console.log("没有路径可走"); } } /**获取当前节点周围可走的节点 */ getRoundGrid(grid: DataGrid): DataGrid[] { let arr: DataGrid[] = []; //周围的格子 this.addToRoundIfNeed(arr,this.getGrid(grid.x,grid.y + 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y)); this.addToRoundIfNeed(arr,this.getGrid(grid.x,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y + 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x + 1,grid.y - 1)); this.addToRoundIfNeed(arr,this.getGrid(grid.x - 1,grid.y + 1)); //会将数组里元素两两进行比较,自定义方法里返回-1就不交换位置 返回1交换位置 arr.sort(this.compareGridsAStar.bind(this)); return arr; } /**将格子放到数组里 */ addToRoundIfNeed(arr: DataGrid[],roundGrid: DataGrid) { //当前节点和路径节点都不计入 if (!roundGrid || roundGrid.type == GrideType.Wall || roundGrid.inCloseList || roundGrid.inOpenList){ return; } if (roundGrid) { arr.push(roundGrid); } } /**根据坐标获取格子数据 */ getGrid(x: number,y: number): DataGrid { //边界判断 if (x < 0 || x >= 8 || y < 0 || y >=8){ return null; } return this.dataGrids[x][y]; } /**格子排序 优化 * 距离小的放在前面 */ compareGridsAStar(grid0: DataGrid,grid1: DataGrid): number{ let grid0Dis = this.getDistanceAStar(grid0,this.startGrid,this.endGrid); let grid1Dis = this.getDistanceAStar(grid1,this.startGrid,this.endGrid); if (grid0Dis > grid1Dis) { return 1; }else{ return -1; } } /**获取综合距离 优化 * grid 当前节点 * start 起始节点 * end 目标节点 */ getDistanceAStar(grid: DataGrid,start: DataGrid,end: DataGrid) { let endDis = Math.abs(grid.x - end.x) + Math.abs(grid.y - end.y); let startDis = Math.abs(grid.x - start.x) + Math.abs(grid.y - start.y); return endDis + startDis; } /**点击重新开始*/ onBtnRestart(){ for (let x = 0;x < this.dataGrids.length;x ++){ for (let y = 0;y < this.dataGrids[x].length;y ++){ let dataGrid = this.dataGrids[x][y]; dataGrid.inOpenList = false; dataGrid.inCloseList = false; if (dataGrid.type != GrideType.Wall){ dataGrid.type = GrideType.Normal; } this.nodeGrids[x][y].updateGridColor(); } } this.startGrid = null; this.endGrid = null; this.openPath = []; } }
用于解决当需要创建大量相同对象的时候,避免重复创建,节能.
/**对象池模式 */ const {ccclass, property} = cc._decorator; @ccclass export default class PoolDemo extends cc.Component { @property(cc.Node) nodeIcon: cc.Node = null; onl oad () { } shoot() { let node = cc.instantiate(this.nodeIcon); //创建完节点还要从父节点上移出,太费事了 node.runAction(cc.sequence(cc.moveBy(1,0,300),cc.removeSelf())); node.parent = this.node; } update() { this.shoot(); } }
会不断创建节点,并将节点从父节点移出,耗费性能,内存.
/**对象池模式 */ const {ccclass, property} = cc._decorator; @ccclass export default class PoolDemo extends cc.Component { @property(cc.Node) nodeIcon: cc.Node = null; /**对象池 */ pool: cc.Node[] = []; onl oad () { } shoot() { let node = this.getNode(); //创建完节点还要从父节点上移出,太费事了 node.runAction(cc.sequence(cc.moveBy(1,0,300),cc.removeSelf(),cc.callFunc(function () { node.position = cc.Vec2.ZERO; //节点位置重置 //用完后将节点放回对象池 this.pool.push(node); }.bind(this)))); node.parent = this.node; } /**获取节点 * 如果对象池里有节点的话就取出来用 * 没有的话就实例化一个 */ getNode(): cc.Node { if (this.pool.length > 0) { return this.pool.shift(); }else { console.log("创建了一个节点"); return cc.instantiate(this.nodeIcon); } } update() { this.shoot(); } }
只需创建了62个节点