爆款游戏开发零基础·第六季-常用编程框架和算法
2021/6/7 20:58:30
本文主要是介绍爆款游戏开发零基础·第六季-常用编程框架和算法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
第六季-常用编程框架和算法
- 01.MVC架构
- MVC的含义
- 02.单例模式
- 单例(instance)的特点
- 作用
- 实现
- 调用
- 结果
- 补充
- 03.观察者模式-订阅发布模式
- 流程
- 实现
- 结果
- 04.工厂模式
- 特点和作用
- 实现
- 05.代理模式
- 代理 (额外的控制器)
- 06.递归寻路
- 题目
- 步骤
- 实战
- 先制作格子 NodeGrid.ts
- 制作地图
- 寻路(完整代码 FindPath.ts)
- 运行结果
- 不足
- 07.A星寻路
- 步骤
- 实现
- 08.对象池模式
- 意义
- 流程图
- 直接创建对象
- 使用对象池
- 运行效果
01.MVC架构
是一种设计程序的思路/套路.
MVC的含义
- M-Model模型(数据)
FlappyBird中小鸟的位置,当前激活的节点等等 - V-View视觉层
展示出来的界面,通常是引擎进行处理 - C-Controller控制器(逻辑)
写下的代码,小鸟碰到管子判断游戏结束
总结内涵:拿数据-根据逻辑-刷新界面
02.单例模式
单例(instance)的特点
- 单例类,全局只有一个对象,不可能产生多个
- 在代码任意位置容易获取这个对象
作用
防止一个全局使用的类频繁的实现与销毁
控制实例数目,节省系统资源
实现
/**单例类 */ 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(){}
03.观察者模式-订阅发布模式
流程
- A订阅了开始游戏事件
- B抛出(发布)了开始游戏事件
- A响应事件
特点: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会出错
04.工厂模式
特点和作用
操作的对象本身只实现功能方法,具体的操作由工厂实现
这样不会暴露对象及创建逻辑
实现
/**工厂模式 */ //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);
05.代理模式
代理 (额外的控制器)
- 单一原则,不给一个类太多功能.
自己的功能留在类里面,将一些逻辑控制放到外面.
高内聚,低耦合. - 不方便访问一个类的时候,给个代理
例如:明星和经纪人的关系;
快递中发货人-快递-收货人;
cc.loader.load(…)内部非常复杂,但对于调用的人来说并不关 心内部逻辑.
06.递归寻路
题目
需要从①走到②,要怎么走(可以斜着走,褐色是不能走的)
步骤
- 把起点当作当前节点
- 重复以下步骤
a. 把当前节点加入openList,标记Open
b.查找可以走的下一步节点
c.把下一步可以走的节点排序
d.把下一步可以走的离终点最近的点,当做当前的寻路节点
e.把走过的节点标记为Close - 直到查找到目标节点
实战
先制作格子 NodeGrid.ts
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; } } }
寻路(完整代码 FindPath.ts)
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 = []; } }
运行结果
不足
当前算法只是寻找下一步的最优解,并不是全局的最优解,有时并不是最优路径。
07.A星寻路
可以看下大神写的A星寻路算法原理
步骤
- 把起点当做当前节点
- 重复以下步骤
a.把当前节点加入openList,标记Open
b.查找可以走的下一步节点
c.把下一步可以走的节点的父节点,设置为当前节点
d.把下一步可以走的节点加入到openList,排序
e.把openList中的第一个节点,当做当前节点
f.把走过的节点标记为Close - 直到查找到目标节点
实现
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 = []; } }
08.对象池模式
意义
用于解决当需要创建大量相同对象的时候,避免重复创建,节能.
流程图
直接创建对象
/**对象池模式 */ 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个节点
这篇关于爆款游戏开发零基础·第六季-常用编程框架和算法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27消息中间件底层原理资料详解
- 2024-11-27RocketMQ底层原理资料详解:新手入门教程
- 2024-11-27MQ底层原理资料详解:新手入门教程
- 2024-11-27MQ项目开发资料入门教程
- 2024-11-27RocketMQ源码资料详解:新手入门教程
- 2024-11-27本地多文件上传简易教程
- 2024-11-26消息中间件源码剖析教程
- 2024-11-26JAVA语音识别项目资料的收集与应用
- 2024-11-26Java语音识别项目资料:入门级教程与实战指南
- 2024-11-26SpringAI:Java 开发的智能新利器