Bearcat pomelo game 实战 -- treasures - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fantasyni
V2EX    Node.js

Bearcat pomelo game 实战 -- treasures

  •  
  •   fantasyni 2014-05-21 15:07:49 +08:00 3344 次点击
    这是一个创建于 4235 天前的主题,其中的信息可能已经有所发展或是发生改变。
    ## 概述
    这是一篇通过一个简单的 treasure 捡宝的例子讲述如何使用 [Bearcat](https://github.com/bearcatnode/bearcat) 来快速, 高效的进行 [pomelo](https://github.com/NetEase/pomelo) game 开发

    ## 起步
    ### 添加 bearcat

    ```
    npm install bearcat --save
    ```

    添加context.json, 并指定 ***scan*** 扫描路径, 来自动扫描 POJOs
    context.json
    ```
    {
    "name": "bearcat-treasures",
    "scan": "app",
    "beans": []
    }
    ```

    修改app.js, 添加 bearcat 启动代码
    app.js
    ```
    var cOntextPath= require.resolve('./context.json');
    bearcat.createApp([contextPath]);

    bearcat.start(function() {
    Configure(); // pomelo configure in app.js
    app.set('bearcat', bearcat);
    // start app
    app.start();
    });
    ```

    就是这么简单, bearcat 开发环境就已经搭建完毕, 之后就可以利用 bearcat 所提供的 IoC, AOP, 一致性配置等特性来编写简单, 可维护的 pomelo 应用

    ## 途中
    ### handler, remote 交由 bearcat 管理
    handler, remote 都以 POJO 的形式编写
    由于之前handler, remote在pomelo里面是通过 pomelo-loader 来管理的, 因此需要做一下适配转化

    ```
    module.exports = function(app) {
    return bearcat.getBean({
    id: "gateHandler",
    func: GateHandler,
    args: [{
    name: "app",
    value: app
    }],
    props: [{
    name: "dispatcher",
    ref: "dispatcher"
    }]
    });
    };
    ```

    通过适配, gateHandler 就交给了 bearcat 来进行管理, 之后 gateHandler 需要什么依赖, 仅仅在 getBean 的 metadata 配置中描述好依赖关系就行了
    上面的gateHandler例子中, 就向 bearcat 容器描述了, gateHandler 需要在构造函数中传入一个 ***app*** 对象, 在对象属性中需要一个 ***dispatcher*** 依赖

    ### domain 对象编写
    domain 代表着数据和模型, 包括玩家player, 宝物treasure, 移动move等等
    domain 里的数据要被序列化, 需要定义序列化方法, 比如toJSON

    entity.js
    ```
    var EventEmitter = require('events').EventEmitter;
    var util = require('util');

    var id = 1;

    function Entity(opts) {
    EventEmitter.call(this);
    this.opts = opts || {};
    this.entityId = id++;
    this.kindId = opts.kindId;
    this.kindName = opts.kindName;
    this.areaId = opts.areaId || 1;
    this.x = 0;
    this.y = 0;
    }

    util.inherits(Entity, EventEmitter);

    Entity.prototype._init = function() {
    var opts = this.opts;
    if (opts.x === undefined || opts.y === undefined) {
    this.randPos();
    } else {
    this.x = opts.x;
    this.y = opts.y;
    }
    }

    Entity.prototype._toJSON = function() {
    return {
    x: this.x,
    y: this.y,
    entityId: this.entityId,
    kindId: this.kindId,
    kindName: this.kindName,
    areaId: this.areaId
    }
    }

    // random position
    Entity.prototype.randPos = function() {
    };

    module.exports = {
    id: "entity",
    func: Entity,
    abstract: true,
    props: [{
    name: "dataApiUtil",
    ref: "dataApiUtil"
    }, {
    name: "utils",
    ref: "utils"
    }]
    }
    ```

    ***entity*** 是一个抽象的bean, 意味着它只是作为子bean的模版, 并不会被实例化, 它通过对象属性依赖注入了 ***dataApiUtil*** 和 ***util***

    player.js
    ```
    var logger = require('pomelo-logger').getLogger('bearcat-treasures', 'Player');
    var bearcat = require('bearcat');
    var util = require('util');

    function Player(opts) {
    this.opts = opts;
    this.id = opts.id;
    this.type = null;
    this.name = opts.name;
    this.walkSpeed = 240;
    this.score = opts.score || 0;
    this.target = null;
    }

    Player.prototype.init = function() {
    this.type = this.consts.EntityType.PLAYER;
    var Entity = bearcat.getFunction('entity');
    Entity.call(this, this.opts);
    this._init();
    }

    Player.prototype.addScore = function(score) {
    this.score += score;
    };

    Player.prototype.toJSON = function() {
    var r = this._toJSON();

    r['id'] = this.id;
    r['type'] = this.type;
    r['name'] = this.name;
    r['walkSpeed'] = this.walkSpeed;
    r['score'] = this.score;

    return r;
    };

    module.exports = {
    id: "player",
    func: Player,
    scope: "prototype",
    parent: "entity",
    init: "init",
    args: [{
    name: "opts",
    type: "Object"
    }],
    props: [{
    name: "consts",
    ref: "consts"
    }]
    }
    ```

    player 是 entity 的一个子类, 它通过在metadata配置中的 ***parent*** 继承了 entity prototype 里的方法
    player 的scope是 prototype 的, 并且需要定义一个 init 方法, 来调用 entity 的构造函数以及 entity 的 init 方法 ***_init***

    ```
    var Entity = bearcat.getFunction('entity');
    Entity.call(this, this.opts);
    this._init();
    ```
    这里通过 bearcat.getFunction 来拿到 entity 的构造函数来进行调用

    ### 使用 domain
    在没有bearcat的情况下, 使用domain需要自己先require进来, 然后再 new domain(), 现在你可以直接通过 getBean 来得到相应 domain 的实例

    playerHandler enterScene
    ```
    PlayerHandler.prototype.enterScene = function(msg, session, next) {
    var role = this.dataApiUtil.role().random();
    var player = bearcat.getBean('player', {
    id: msg.playerId,
    name: msg.name,
    kindId: role.id
    });

    player.serverId = session.frontendId;
    if (!this.areaService.addEntity(player)) {
    logger.error("Add player to area faild! areaId : " + player.areaId);
    next(new Error('fail to add user into area'), {
    route: msg.route,
    code: this.consts.MESSAGE.ERR
    });
    return;
    }

    var r = {
    code: this.consts.MESSAGE.RES,
    data: {
    area: this.areaService.getAreaInfo(),
    playerId: player.id
    }
    };

    next(null, r);
    };
    ```

    ```
    var player = bearcat.getBean('player', {
    id: msg.playerId,
    name: msg.name,
    kindId: role.id
    });
    ```

    player 通过 bearcat.getBean 拿到

    ### domain 事件的处理
    移动和捡宝是通过 event 事件来处理的, 在一个 tick 时间内, 当移动到宝物的捡宝范围之内, 就会出发 ***pickItem*** 事件

    ```
    Move.prototype.update = function() {
    var time = Date.now() - this.time;
    var speed = this.entity.walkSpeed;
    var moveLength = speed * time / 1000;
    var dis = getDis(this.entity.getPos(), this.endPos);
    if (dis <= moveLength / 2) {
    this.finished = true;
    this.entity.setPos(this.endPos.x, this.endPos.y);
    return;
    } else if (dis < 55 && this.entity.target) {
    this.entity.emit('pickItem', {
    entityId: this.entity.entityId,
    target: this.entity.target
    });
    }
    var curPos = getPos(this.entity.getPos(), this.endPos, moveLength, dis);
    this.entity.setPos(curPos.x, curPos.y);

    this.time = Date.now();
    };
    ```

    触发时间后, 就向channel广播捡宝这个事件
    ```
    player.on('pickItem', function(args) {
    var player = self.getEntity(args.entityId);
    var treasure = self.getEntity(args.target);
    player.target = null;
    if (treasure) {
    player.addScore(treasure.score);
    self.removeEntity(args.target);
    self.getChannel().pushMessage({
    route: 'onPickItem',
    entityId: args.entityId,
    target: args.target,
    score: treasure.score
    });
    }
    });
    ```

    ## areaService 编写
    areaService 里面维护着当前area里面的玩家, 排名, 宝物等数据
    它在tick时间内, 向channel广播更新着area里面的最新数据

    ```
    AreaService.prototype.tick = function() {
    //run all the action
    this.actionManagerService.update();
    this.entityUpdate();
    this.rankUpdate();
    }
    ```

    entityUpdate 更新着area里面的entity情况
    ```
    AreaService.prototype.entityUpdate = function() {
    if (this.reduced.length > 0) {
    this.getChannel().pushMessage({
    route: 'removeEntities',
    entities: this.reduced
    });
    this.reduced = [];
    }
    if (this.added.length > 0) {
    var added = this.added;
    var r = [];
    for (var i = 0; i < added.length; i++) {
    r.push(added[i].toJSON());
    }

    this.getChannel().pushMessage({
    route: 'addEntities',
    entities: r
    });
    this.added = [];
    }
    };
    ```

    ## 总结
    在bearcat的统一管理协调下, 去除了烦人的require直接依赖关系, 可以放心大胆的进行编码甚至重构, bearcat 里面的任一组件都被有序的管理维护着, 使用时不再是一个个单一的个体, 而是一个集体

    项目代码在 [bearcat-treasures](https://github.com/bearcatnode/treasures)
    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     921 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 20:59 PVG 04:59 LAX 12:59 JFK 15:59
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86