首页 > 代码库 > 一款小游戏引擎设计

一款小游戏引擎设计

前言

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

本文目的

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

一款小游戏引擎设计