Bearcat pomelo game 實戰 -- treasures

概述

這是一篇經過一個簡單的 treasure 撿寶的例子講述如何使用 Bearcat 來快速, 高效的進行 pomelo game 開發node

起步

添加 bearcat

npm install bearcat --save

添加context.json, 並指定 scan 掃描路徑, 來自動掃描 POJOs
context.jsongit

{
    "name": "bearcat-treasures",
    "scan": "app",
    "beans": []
}

修改app.js, 添加 bearcat 啓動代碼
app.jsgithub

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 應用npm

途中

handler, remote 交由 bearcat 管理

handler, remote 都以 POJO 的形式編寫
因爲以前handler, remote在pomelo裏面是經過 pomelo-loader 來管理的, 所以須要作一下適配轉化json

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 依賴session

domain 對象編寫

domain 表明着數據和模型, 包括玩家player, 寶物treasure, 移動move等等
domain 裏的數據要被序列化, 須要定義序列化方法, 好比toJSONapp

entity.jsfrontend

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的模版, 並不會被實例化, 它經過對象屬性依賴注入了 dataApiUtilutildom

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

相關文章
相關標籤/搜索