THREE练习写了几个简单小游戏的总结
THREE练习写了几个简单小游戏的总结
- 象棋
- 设计
- 隔离
- 订阅发布
- 单例模式
- 单一职责
- 数据
- 渲染
- 填充棋子
- 移动和吃子
- 移动判断
- 记录
- 悔棋
- 回放
- 后续
- 街道建设游戏
- 我造的城市
看了一个国外大佬写的游戏教程,跟着做了下。
https://javascriptgametutorials.com/tutorials/three-js/crossy-road
又自己练习做了几个小游戏。感觉收获挺多的。
象棋
实现了人机对战,回放,悔棋等。虽然人机有点傻,后面慢慢改。
3D视角
设计
比着之前想感觉完成一个练习,这个练习我思考了一点。
隔离
渲染部分只负责渲染,通过修改数据来完成各种操作。
订阅发布
主类Game类负责初始化其他类以及事件传递。
EventBus为订阅发布类。
这里我给挂在到了window上。
单例模式
应该是单例模式?
画布,相机,配置项,资源加载器都设计为单例模式,为了方便操作,挂在到了global上,也可以挂在到别的地方或者不挂载。构建器变成私有的,通过init来初始化实例。如Renderer。其他类如Scene和Camera都类似。
export class Renderer {private static instance: Renderer;private renderer!: THREE.WebGLRenderer;constructor() {if (Renderer.instance) {return Renderer.instance;}this.renderer = genRenderer(this.getOptions());Renderer.instance = this;this.bindEvents();}public static init(): Renderer {if (!Renderer.instance) {Renderer.instance = new Renderer();}return Renderer.instance;}getCanvas() {return Options.getInstance().getCanvas();}getOptions() {return Options.getInstance().getOptions();}getRender() {return this.renderer;}public static getInstance(): Renderer {return Renderer.instance;}resize() {const canvas = this.getCanvas();this.renderer.setPixelRatio(window.devicePixelRatio);this.renderer.setSize(canvas.clientWidth, canvas.clientHeight);}private bindEvents() {window.$on(GameEvents.UpdateRender, () => {this.renderer.render(Scene.getInstance().getScene(),Camera.getInstance().getCamera());});window.$on(GameEvents.Resize, () => {this.resize();});}
}
各自负责各自的功能。
通过在主类Game里面注册使用。
比如渲染控制。
不在animate里面引入多个。
通过发布事件来传递渲染。
画布的渲染
还有窗口尺寸大小这个,这个我还没完善。
在Game类中绑定事件 发布窗口大小改变事件。
单一职责
我吹牛逼的,我也不知道是不是哈哈。
基本上所有的方法只处理自己的事情。
移动方法只负责交换数据,然后调用game的update方法,update方法,只负责渲染。
额外的,高亮标记也不是改变棋子的材质,全局只会有一个,直接丢到scene,大小为棋子的1.5倍,然后直接塞上去。看着像棋子高亮了就行。
数据
每个棋子的数据就是这个。
export interface XiangQiMetaDataItem {rowIndex: number;colIndex: number;type?: ChessPieceType;camp?: 'black' | 'red';
}
使用pinia来管理。
初始化数据,填充默认象棋棋盘。
渲染
方便读取坐标,写了一个XQGroup
export class XQGroup extends THREE.Group {rowIndex: number;colIndex: number;constructor(rowIndex: number, colIndex: number) {super();this.name = 'piece';this.rowIndex = rowIndex;this.colIndex = colIndex;}
}
每一个metadata的数据就会渲染出来一个XQGroup;
填充棋子
根据数据填充,象棋棋盘是8*9所以中心坐标为4.5,4
根据中心坐标来计算position。
棋子就是圆柱体加文字。
没有棋子的位置,填充的为
一个透明的圆柱体,和棋子大小一样,方便点击操作。
移动和吃子
移动的话,其实就是交换metadata的坐标数据。
比如车到对方的炮上。就是把炮对应的数据,改成车的。
而炮的也就不需要了。
比如车在metadata[1][2];
数据为{rowIndex:1,colIndex:2,type:‘车的那个’,camp:‘red’}
炮的为metadata[2][2];数据为{rowIndex:2,colIndex:2,type:‘炮的那个’,camp:‘black’}
车移动完后metadata[2][2];就变成了{rowIndex:2,colIndex:2,type:‘车的那个’,camp:‘red’}
而metadata[1][2];只需要保留坐标就行了。
移动判断
传入两个点的坐标数据来判断
炮的话只需要计算两点是否在一条直线上,以及两点之间的有type也就是有棋子的点的个数,大于一个就不能走,等于一个且目标点有棋子则可走,如果是0个那肯定可以走。
记录
通过记录每一步的交换的坐标点的数据,来记录下棋。
optionHistory就代表了步长。
悔棋
悔棋,通过操作metadata来完成。
前面记录了optionHistory,而optionHistory是从基础的metadata上走的每一步,可以通过metadata基础数据加optionHistory来还原。
在XiangQiGame类中完善。
pop移除最后两条数据,然后合并成最新的数据,就实现了悔棋。
回放
回放和上面的悔棋一样,悔棋是合并所有optionHistory后渲染,而回放是,每合并一条optionHistory的数据就渲染一次。如果做优化,也可以只渲染改变的坐标的棋子。
后续
基础版基本上完成了,除了人机还有点问题,权重没做好。
打算搞一个炫酷模型版的,把炮换成迫击炮,车换成坦克,将换成指挥部,士换成哨所,相换成卫队营区,卒子换成大兵,马换成步兵战车,然后炮吃子的时候,在炮原位置加上烟雾,在目标位置加上火,空中加一个飞线炮弹。
街道建设游戏
在象棋游戏之前的练习。
玩法很简单,资金买建筑,电厂建筑提供电,生产建筑挣钱,没电无法生产且屏幕变暗色。
有电了。
工厂会赚钱
有钱后可以打造自己喜欢的街道。
当前很少资源。
做象棋游戏之前这里思考了下职责分离。注册插件的那种形式。这里的建筑资源引入,就是统一格式。
我造的城市
真丑