KeiferGu的博客

微型游戏引擎开发阶段总结

前言

写这个小型游戏引擎的目的,是为了深入学习 JavaScript ,现在功能已经有了一定的雏形,所以作一个阶段性的总结。

架构

系统采用分层架构,主要分为 底层工具层 、 基础类层 、 功能层。

底层工具层

  1. 碰撞检测模块
    第一次重构,这时的碰撞检测模块是使用Object.assign()追加到Shape.prototype里面,然后分开为两个文件。包括求多边形的所有边的向量等函数,均是使用该种方法追加到各自的图形类中。这样,虽然在文件上是分开的,但是在逻辑上,两个模块是杂揉在一起的。

    第二次重构时,我的想法是使用#函数式编程#,将各个具体功能(例如求多边形的所有边的法向量)写成单独的函数,而实例对象作为参数传进去,返回计算结果,然后在主函数里面顺序调用。这样的写法能够满足对外只有#单一接口#。但是在后面我对图形类进行重构时,这里遇到的很大的问题。

    第三次重构,因为对图形类进行重构时,将数据接口进行了更改,导致了碰撞检测模块中的大量方法需要更改,这时我的想法是将碰撞检测模块重构为一个可复用的模块,对外提供单一接口并确定参数格式内容。这时,我将除Vector模块之外的依赖删除,在函数内部创建图形类,当接收到外部参数时,实例化图形类,然后进行计算。同时,为了便于以后对接口进行重构,添加一个数据转换函数,将外部的参数格式转换为内部使用的格式,同时也起着数据格式校验的功能,在后面的处理大量switch/case时,也起着重要的作用。

    第四次重构,此时数据格式,外部接口已经固定,但是有一个问题,碰撞检测需要对不同的图形进行不同的检测算法,那么需要使用switch/case或者if/else对图形的类型进行判断。但是当图形类别较多时,这里的代码会非常繁杂,及其不便于修改和扩展。这时,我想到了使用#查找表#来彻底解决这个问题。具体的做法是根据传入的图形类名构造函数名,然后动态的调用函数。可以看这个代码样例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var 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);
    }
  2. 图形绘制模块
    初始版本,此时的图形绘制函数是直接写在图形类里面的,直接调用canvas的接口进行绘制

    第一次重构, 直接将图形绘制函数拆分为单一模块,对外提供统一接口。这样不管外部图形类如何改变,只需要将其转换为标准接口即可继续正常使用图形绘制模块。第二个好处是,将数据表现分开,便于移植,也就是说如果我不在canvas上绘图,那么我可以直接修改图形绘制函数,使用目标平台的绘制API来实现以前的功能,而且对上层的代码没有任何影响。

基础类层

  1. 图形类
    图形类也经历过多次的重构,但大多数是更改数据接口。现在基本的图形基类抽象为四种,均继承与基类Shape

    • 点(Point)
    • 线段(Line)
    • 多边形(Polygon)
    • 圆(Circle)

    • 矩形(Rect),继承于Polygon

后记

架构以及接口

编写这个项目的过程中,有一大部分的时间都是在设计程序架构,设计接口。不断的推翻以前的设计,不断的重构。这个时候,真正的体会到,程序设计是一件需要创造力和想象力的事情,并不是简单的堆叠代码。

整个项目的开发流程大概类似“原型模式”,首先使用 JavaScript 完成了一个能够运行的原型,然后拆分功能,将图形、算法、视图分开,然后尝试扩展,抽象出高级的类。再然后尝试将某些功能独立出来,使其成为一个可复用的模块。虽然最后的展现形式没有改变,但是对于日后的维护和扩展,却有着不同的难度。

接口的设计,我认为这是最难的一部分。

设计模式

我认为,设计模式的使用就是为了便于对功能进行增删改。那么,当你在进行开发时,一直在想着如果这个需求后面改了怎么办,如果函数参数要改怎么办,如果接口要改怎么办。当你在这么想时,并且尽可能让你的代码能够从容应对这些改动需求时,你就在应用设计模式了。

接口模式的使用