• 一款小游戏引擎设计


    前言

    本文为后续引擎提炼定下了一个大致的方向,没有给出完整的引擎架构。这就够了!让我们在具体开发过程中再来从底向上设计吧!

    本文目的

    1、进行引擎提炼的前期规划,明确引擎提炼的整体流程和引擎的非功能性需求。
    2、从炸弹人领域模型中提炼出精简的领域模型,作为炸弹人的参考模型。
    3、从炸弹人参考模型中提炼出抽象的领域模型,作为引擎的初步领域模型。

    本文主要内容

    前期规划

    开发流程

    引擎提炼的整个流程如下图所示:

    说明

    • 回顾炸弹人游戏
    介绍炸弹人游戏的基本情况,回顾炸弹人游戏的设计

    • 初步设计引擎
    给出引擎的初步设计,从炸弹人领域模型中提炼出一个精简的领域模型,作为炸弹人的参考模型,再从中提炼出一个抽象的领域模型,作为引擎的初步领域模型

    • 第一次迭代
    参考引擎的初步领域模型,从炸弹人参考模型中提炼出对应的通用类和基础的引擎框架,使引擎具备游戏入口、预加载、主循环、层、精灵、动画、事件管理等基本功能,并将炸弹人游戏改造为基于引擎实现,通过测试。
    • 第二次迭代
    进一步提炼炸弹人类和引擎类,提炼通用模式,消除引擎中的用户逻辑,进行引擎的整体梳理和修改,对应修改炸弹人游戏,通过测试。

    本文进行“回顾炸弹人游戏”和“初步设计引擎”这两个步骤。

    引擎非功能性需求

    1、可测试性
    引擎是一个可复用的组件,应该保证正确和可测试。
    方案:编写全覆盖引擎的单元测试。
    炸弹人游戏有完整的单元测试,可以将其修改为引擎的单元测试。

    注:本系列不会讨论测试。
    2、可扩展性
    本系列提炼的引擎还不完善,后续会加入更多的功能,因此引擎需要具有灵活的架构,满足开闭原则,方便以后的扩展。
    引擎应该支持插件化开发,独立功能的模块可以作为通用插件从引擎中独立出去,由用户选择是否引入到引擎中。
    方案:基于高内聚低耦合的总体思想,使用面向对象思想,复用炸弹人游戏中可扩展性的模块,从中提炼引擎,保持良好的引擎架构。

    3、可读性
    引擎应该具备良好的可读性,易于后续开发时理解之前的设计,方便他人理解引擎的实现。
    方案:
    (1)保持代码整洁
    (2)只保留必要的注释
    (3)编写必要的文档
    (4)通过良好的命名和测试用例来辅助读者理解代码
    (5)代码风格应该统一

    回顾炸弹人设计

    本节会介绍炸弹人游戏的基本情况,让大家有个整体印象。

    炸弹人系列博文

    炸弹人游戏开发系列

    炸弹人源码下载

    炸弹人源码下载

    炸弹人外部依赖

    在炸弹人游戏中,我使用了以下的库:
    第三方库
    • jQuery
    使用它的选择器,进行dom操作。
    • progressBar
    这是一个jQuery的进度条插件,我用它来显示预加载图片的进度。
    • jasmine
    这是一个测试框架,使用它可以进行Javascript单元测试。
    我的库
    • YOOP(命名空间:YYC.Class、YYC.AClass、YYC.Interface)
    这是我的Javascript的oop框架。具体可参见发布我的Javascript OOP框架YOOP
    • 图片预加载控件PreLoadImg(命名空间:YYC.Control)
    • 工具库YTool(命名空间:YYC.Tool)
    我的工具方法库。
    • jsExtend
    Javascript原生对象扩展,对js的String和Array对象进行了扩展。
    • 模式库(命名空间:YYC.Pattern)
    包括创建对象模式的命名空间方法namespace和观察者模式的Observer.js

    炸弹人的概念层次结构

    炸弹人领域模型

    炸弹人游戏的领域模型如下图所示:

    查看大图

    初步设计引擎

    本节提出了我在实践过程中总结的引擎设计原则,以及炸弹人的参考模型和引擎的初步领域模型,为后面的引擎提炼打下了基础。

    引擎设计原则

    1、引擎不应该依赖用户,用户应该依赖引擎

    引擎应该保持通用性,不应该包含用户逻辑。

    2、尽量减少引擎依赖的外部文件
    因为:
    (1)增加引擎的不稳定性
    依赖的外部文件变化时,引擎也需要对应修改。
    (2)外部文件不一定完全适合引擎
    外部文件不是基于引擎开发的,可能需要对其进行改造,从而适合引擎的需要。但是由于外部文件不稳定或者对外部文件实现不了解等原因,想要针对引擎的具体情况进行改造比较困难。
    (3)加大用户负担
    引擎可能只需要使用外部文件的一小部分,但是却需要引入整个外部文件,这会增加引擎的整个文件大小,加大用户负担。

    所以:
    (1)如果必须要依赖,也尽量依赖自己开发的库,而不要依赖第三库。
    (2)第三方库可以作为插件引入到引擎中,由用户自行选择。
    (3)可考虑将第三方库改造为引擎的内部库。
    a.如果引擎依赖的是自己开发的、没有发布的库,则可以直接引入,作为引擎的内部库
    因为自己开发的、独立发布的库需要独立变化,不应该与引擎绑到一起。
    b.如果引擎只依赖第三方库的部分内容,则可将依赖的第三方库的最小集提取为引擎的内部库。

    3、引擎应该具有很好的可扩展性
    这里可扩展性包括两个方面:引擎可扩展性和用户可扩展性。
    (1)引擎可扩展性
    在前面的非功能性需求中已经说过了,引擎应该具有灵活的架构,方便以后的扩展。
    (2)用户可扩展性
    用户可扩展性指用户可以插入自己的逻辑到引擎中,实现引擎的变化点。

    4、尽量减少用户负担
    引擎应该实现底层逻辑,用户只负责实现业务逻辑。
    引擎应该尽量封装高层API,提供给用户使用,减少用户的工作量。

    代码组织方式

    文件组织方式一般有两种:
    1、使用js模块加载器(如sea.js)。在沙箱环境中,将需要引用的文件加载进来,然后通过局部变量名来使用。
    2、使用命名空间。

    因为:
    (1)引擎文件数量不是很多,还不需要用模块加载器。
    (2)如果使用模块加载器,则引擎必须依赖模块加载器,用户使用引擎时也必须按照模块加载器的方式来引用引擎文件,这样会增加复杂度,加大用户负担。

    所以引擎采用命名空间的方式来组织文件,引擎的顶级命名空间为YE。

    引擎名

    该引擎命名为YEngine2D。

    代码结构

    炸弹人和引擎代码结构如下图所示:

    • Content
      炸弹人游戏的资源文件

      • Image
        图片资源文件
    • Scripts
      js文件

      • bomber
        炸弹人js文件
      • myTool
        工具

        • frame
          框架文件
        • pattern
          模式文件
        • tool
          工具文件
      • plugin
        外部插件
      • yEngine2D
        引擎文件
    • Views
      页面

    思考

    1、炸弹人改造为基于引擎实现后,是否需要通过炸弹人的单元测试?
    因为:
    (1)在提炼引擎的过程中,引擎变动频繁,引擎变动会导致使用引擎的炸弹人变动,对应的炸弹人单元测试也会可能跟着变动。这样就需要经常修改单元测试,工作量太大。
    (2)我不会二次开发炸弹人游戏,不需要维护炸弹人的单元测试。
    (3)本系列的重点是提炼引擎,应该把精力都集中在引擎上。
    综上所述,只对引擎进行单元测试,而不再维护炸弹人的单元测试,改为直接通过浏览器运行炸弹人游戏来进行运行测试。

    2、引擎应该是通用的,还是只针对“炸弹人游戏”所属的RPG类型?
    因为提炼引擎的目的是为了更快地开发游戏,不仅仅只有RPG类型,也包括其它类型的游戏,所以应该提出一个通用的引擎。
    然而本系列并不能提出一个完全通用的引擎,因为我是从炸弹人游戏中提炼引擎的,该引擎只能保证适应炸弹人这种RPG类型的游戏。提炼通用引擎是一个长期任务,目前我只能在提炼引擎时尽可能地消除炸弹人游戏的用户逻辑,提高通用性。在以后的实践中,需要将该引擎尽可能多地应用到不同类型的游戏中,这样才能最终得到一个通用的游戏引擎。

    领域模型分析

    炸弹人参考模型

    对炸弹人的领域模型进行精简,去掉具体的实现类,只保留必要的、能体现整个概念层次结构的和游戏框架的类:

    精简后的领域模型即为本系列的炸弹人参考模型。

    引擎初步领域模型

    对炸弹人参考模型进行抽象,提出抽象角色类(一个角色类可代表多个具体类,如DataOperate类,在炸弹人游戏中代表了MapDataOperate、GetPath等类),作为引擎的初步领域模型。

    • Config
      全局配置类,存放游戏中的常量、枚举值、配置信息。

    • LoadResource
      加载资源的类,负责加载各种资源

    • Main
      入口类,是整个系统的入口,负责游戏初始化和启动,页面只与该类耦合,该类是整个系统的入口。

    • Director
      游戏主逻辑类,负责游戏的统一调度。

    为什么命名不沿用炸弹人的“Game”?
    因为“Game”这个名字范围太大,不能突出“统一调度”的职责,因此命名为“Director”更为合适

    • Scene
      场景类,为集合类,从炸弹人的LayerManager抽象而来,负责管理场景。

    在炸弹人游戏开发中,我从“如何统一调度各个层”的逻辑出发,提出了LayerManager类。
    现在可以进一步抽象,提出“场景”这个概念,游戏中可能包含多个场景(至少一个),场景类Scene与“场景”是1对1的关系,Scene不仅负责统一调度场景内各个层,还包含与场景的相关的属性和方法
    两者的相同点
    1、抽象层面相同。
    两者都是位于层之上,导演类之下的层面。

    两者的不同点
    1、类型不同
    LayerManager是一个功能类,只封装了场景的逻辑;Scene是一个实体类,保存了场景的所有层,一个Scene对应一个场景。
    2、粒度不同
    LayerManager只负责“统一调度层”的,而Scene不仅负责统一调度场景的层,还包含了场景的相关属性和方法。可以说LayerManager是Scene的一个功能子集。

    • Hash
      具有哈希结构的集合类。

    • Layer
      层类,为集合类,负责层内精灵的统一管理。

    该类对应“分层渲染”的概念,一个Layer对应一个画布canvas。

    • Collection
      具有线性结构的集合类。

    • Sprite
      精灵类。

    每一个单独的个体都是一个精灵类。如玩家、敌人、炸弹等,与该个体密切相关的属性和方法都放到该类中。

    • AI
      人工智能类,具体可以包括寻路算法、敌人的移动模式和行为设置等。

    • Factory
      工厂类,负责创建类的实例,封装类的创建逻辑。

    • Animation
      帧动画控制类,负责控制帧动画的播放。

    • DataOperate
      数据操作类,负责对数据进行读、写操作。

    • Data
      数据类,保存游戏数据

    • EventManager
      事件管理类,负责事件的监听和移除。

    最新的引擎版本

    有兴趣的话您可以看下最新的引擎版本(这个不是本系列博文提出的引擎版本,而是最新修改后的引擎版本):
    发布HTML5 2D游戏引擎YEngine2D

    参考资料

    炸弹人游戏系列

    上一篇博文

    提炼游戏引擎系列:开篇介绍

    下一篇博文

    提炼游戏引擎系列:第一次迭代

     http://www.cnblogs.com/chaogex/p/4152381.html

     
  • 相关阅读:
    js中面向对象的写法
    js中防止全局变量被污染的方法
    移动端的头部标签和meta
    励志
    UX是什么?
    HTTP
    Django RestFramework (DRF)
    Vue(一)
    Vue-基础
    Vue-es6基础语法
  • 原文地址:https://www.cnblogs.com/pengkunfan/p/4152903.html
Copyright © 2020-2023  润新知