游戏中的新手引导模块开发 下

上篇中,我们讲工作模型中的题库环节进行详细设计并拆分,本节,我们继续后面的内容

在整个工作模型中,存在一个所谓“阅题”的环节。此环节设计如下: 8.png 阅题器会根据所输入的题目数据生成我们需要的游戏界面。但界面是什么样子的,针对不同数据格式的题目如何解析,需要我们通过“界面生成器”来完成。在抽象接口中,我们定义了ITopicReader(抽象阅读器)和ITopicRender(抽象渲染器)来完成这部分内容。与此同时,做了第一版实现。

抽象阅读器

/*
author: mebius
date:   2016.2.6

des:topic阅读器,用来渲染提示界面

*/

interface ITopicReader
{
    setRender(render:ITopicRender):void; //设置渲染器
    setRootLayer(layer:egret.DisplayObjectContainer):void; //设置根渲染器
    getFrames():egret.DisplayObject; //获取当前帧
    renderFrames(data:ITopic,auto:boolean):egret.DisplayObject; //渲染帧
    dispose();
}

阅读器实现

/*
author: mebius
date:   2016.2.8

des:默认topic阅读器

*/

class BaseTopicReader implements ITopicReader  {

    private _render:ITopicRender;
    private _rootLayer:egret.DisplayObjectContainer;
    private _frames:egret.DisplayObject;

    public setRender(render:ITopicRender):void
    {
        this._render = render;
    }
    public setRootLayer(layer:egret.DisplayObjectContainer):void
    {
        this._rootLayer = layer;
    }
    public getFrames():egret.DisplayObject
    {
        return this._frames;
    }
    public renderFrames(data:ITopic,auto:boolean):egret.DisplayObject
    {
        this._frames = this._render.render(data);
        if(auto)
        {
            this._rootLayer.addChild( this._frames );
        }
        return this._frames;
    }
    public dispose()
    {
        this._frames = null;
        this._render = null;
        this._rootLayer = null;
    }
}

抽象渲染器接口

/*
author: mebius
date:   2016.2.8

des:topic渲染器接口

*/

interface ITopicRender
{
    render(data:ITopic):egret.DisplayObject; //渲染帧
}

渲染器实现

/*
author: mebius
date:   2016.2.8

des:topic渲染器

*/

class TopicRender implements ITopicRender
{
    public render(data:ITopic):egret.DisplayObject
    {
        var obj:egret.DisplayObject;
        //...

        return obj;
    }
}

最后的渲染器实现我并没有编写实际代码,在项目中,这部分与设计部分较差内容非常大。

当题目阅读步骤完成后,我们来看一下后续的步骤。

作答部分本身难以独立成为一个系统,因为会和我们的业务逻辑关联非常密切。所以用了一个松耦合的设计,采用消息机制方便其他模块通知我们的新手引导进行动作响应。 9.png 在Egret中,此部分可使用事件完成。

具体的实现方式,我们可以设定一个“消息中心”,通过消息中心去转发我们需要的信息。实现如下:

/*
author: mebius
date:   2016.2.8

des:topic 事件

*/

class TopicEvent extends egret.Event
{
    public static TOPIC_COMPLETE:string = "topic_complete";
    public id:number = 0;
    public constructor(type:string, bubbles:boolean=false, cancelable:boolean=false, data?:any )
    {
        super(type, bubbles, cancelable, data );

    }
}

/*
author: mebius
date:   2016.2.8

des:消息中心

*/

class MessageCeneter extends egret.EventDispatcher
{
    private static _messageCenter:MessageCeneter;
    private static _init:boolean = false;
    public constructor()
    {
        super();
        if( MessageCeneter._init == false )
        {
            throw( new Error("单例模式") );
        }
    }

    public static getInstance():MessageCeneter
    {
        if( MessageCeneter._messageCenter == null )
        {
            MessageCeneter._init = true;
            MessageCeneter._messageCenter = new MessageCeneter();
            MessageCeneter._init = false;
        }
        return MessageCeneter._messageCenter;
    }

    public pushMessage(id:number):void
    {
        var evt:TopicEvent = new TopicEvent(TopicEvent.TOPIC_COMPLETE);
        evt.id = id;
        this.dispatchEvent(evt);
    }
}

有了以上内容,我们可以设定新手引导的管理器,所一个统一管理,将一些必要功能封装起来。

/*
author: mebius
date:   2016.2.5

des:topic模块管理器,负责整体新手引导模型流程

*/

class TopicManage
{

    private _topicParse:ITopicParse;
    private _topicLib:ITopicLib;

    public constructor(parse:ITopicParse)
    {
        this._topicLib = new BaseTopicLib();
        this._topicParse = parse;
        MessageCeneter.getInstance().addEventListener(TopicEvent.TOPIC_COMPLETE, this.topicComplete,this);
    }

    public setData(data:any):void
    {
        this._topicParse.parse(data);
        this._topicLib.addTopics( this._topicParse.getTopics() );
        this._topicParse.dispose();
    }

    //--
    private _reader:ITopicReader;
    public setRender(render:ITopicRender,rootLayer:egret.DisplayObjectContainer)
    {
        this._reader = new BaseTopicReader();
        this._reader.setRender(render);
        this._reader.setRootLayer(rootLayer);
    }

    //--

    private _id:number = 0;
    public getCurrentID():number
    {
        return this._id;
    }

    private topicComplete(evt:TopicEvent)
    {
        this._id++;
        this._reader.renderFrames( this._topicLib.getTopicByID(this._id), true );
    }

}

使用如下:

class Main extends egret.DisplayObjectContainer {

    public constructor() {
        super();
        RES.getResByUrl("resource/topics.json",this.topicLoadComplete,this);
    }

    private topicLoadComplete(data:any)
    {
        var a:TopicManage = new TopicManage(new GameTopicParse());
        a.setData(data);
    }
}

到此为止,你的新手引导系统,已经完成绝大部分,但仍然需要不同的进行扩展重构,以应对变化的需求。

最后两个步骤我并没有进行代码编写,而是像前面一样,设计其基本业务模型,因为此部分随着业务发展,变化非常大,很难做一个“有用”的设计。

评分阶段 10.png 理论上,我们应该将此部分放置与服务器端实现。如果在本地实现此部分逻辑,那么需要注意你的分数数据需要进行封装。同时,在数据部分,也要响应增加此新手引导环节分数评判标准。生成的结果作为输出进入到下一个工作模型环节中。

记录阶段 11.png 对于网络游戏而言,此过程我们要进行I/O同步操作。但也可能该环节由服务器完成,将计算后结果传回客户端中。这取决于项目的具体功能需求。

总结:新手引导模块在游戏开发中属于最为简单的模块系统,但它也是最为繁琐的模块系统。当策划修改,甚至大量功能性改动的时候,会涉及到非常多的业务逻辑修改。我们能保证的是,尽量将业务抽象化,保留可变环节,以适应游戏开发过程中的不确定因素。