前言
写这个小型游戏引擎的目的,是为了深入学习 JavaScript ,现在功能已经有了一定的雏形,所以作一个阶段性的总结。
架构
系统采用分层架构,主要分为 底层工具层 、 基础类层 、 功能层。
底层工具层
碰撞检测模块
第一次重构,这时的碰撞检测模块
是使用Object.assign()
追加到Shape.prototype
里面,然后分开为两个文件。包括求多边形的所有边的向量等函数,均是使用该种方法追加到各自的图形类中。这样,虽然在文件上是分开的,但是在逻辑上,两个模块是杂揉在一起的。第二次重构时,我的想法是使用#函数式编程#,将各个具体功能(例如求多边形的所有边的法向量)写成单独的函数,而实例对象作为参数传进去,返回计算结果,然后在主函数里面顺序调用。这样的写法能够满足对外只有#单一接口#。但是在后面我对
图形类
进行重构时,这里遇到的很大的问题。第三次重构,因为对
图形类
进行重构时,将数据接口进行了更改,导致了碰撞检测模块
中的大量方法需要更改,这时我的想法是将碰撞检测模块
重构为一个可复用的模块,对外提供单一接口并确定参数格式内容。这时,我将除Vector模块
之外的依赖删除,在函数内部创建图形类
,当接收到外部参数时,实例化图形类,然后进行计算。同时,为了便于以后对接口进行重构,添加一个数据转换
函数,将外部的参数格式转换为内部使用的格式,同时也起着数据格式校验的功能,在后面的处理大量switch/case
时,也起着重要的作用。第四次重构,此时数据格式,外部接口已经固定,但是有一个问题,
碰撞检测
需要对不同的图形进行不同的检测算法,那么需要使用switch/case
或者if/else
对图形的类型进行判断。但是当图形类别较多时,这里的代码会非常繁杂,及其不便于修改和扩展。这时,我想到了使用#查找表#来彻底解决这个问题。具体的做法是根据传入的图形类名构造函数名,然后动态的调用函数。可以看这个代码样例:1234567891011121314151617181920var collisionObject = {polygon_circle: function(polygon, circle){// 碰撞检测的主程序代码},}collison(s1, s2){/**s1{type: "polygon",data: [Object Array]}s2{type: "circle",data: [Object Array]}**/// 动态的构造参数,来确定调用的函数return collisionObject[s1.type + '_' + s2.type](s1, s2);}图形绘制模块
初始版本,此时的图形绘制函数
是直接写在图形类
里面的,直接调用canvas
的接口进行绘制第一次重构, 直接将
图形绘制函数
拆分为单一模块,对外提供统一接口。这样不管外部图形类
如何改变,只需要将其转换为标准接口即可继续正常使用图形绘制模块
。第二个好处是,将数据和表现分开,便于移植,也就是说如果我不在canvas
上绘图,那么我可以直接修改图形绘制函数
,使用目标平台的绘制API
来实现以前的功能,而且对上层的代码没有任何影响。
基础类层
图形类
图形类
也经历过多次的重构,但大多数是更改数据接口。现在基本的图形基类抽象为四种,均继承与基类Shape
:- 点(Point)
- 线段(Line)
- 多边形(Polygon)
圆(Circle)
矩形(Rect),继承于
Polygon
后记
架构以及接口
编写这个项目的过程中,有一大部分的时间都是在设计程序架构,设计接口。不断的推翻以前的设计,不断的重构。这个时候,真正的体会到,程序设计是一件需要创造力和想象力的事情,并不是简单的堆叠代码。
整个项目的开发流程大概类似“原型模式”,首先使用 JavaScript 完成了一个能够运行的原型,然后拆分功能,将图形、算法、视图分开,然后尝试扩展,抽象出高级的类。再然后尝试将某些功能独立出来,使其成为一个可复用的模块。虽然最后的展现形式没有改变,但是对于日后的维护和扩展,却有着不同的难度。
接口的设计,我认为这是最难的一部分。
设计模式
我认为,设计模式的使用就是为了便于对功能进行增删改。那么,当你在进行开发时,一直在想着如果这个需求后面改了怎么办,如果函数参数要改怎么办,如果接口要改怎么办。当你在这么想时,并且尽可能让你的代码能够从容应对这些改动需求时,你就在应用设计模式了。