這是一篇經過一個簡單的 treasure 撿寶的例子講述如何使用 Bearcat 來快速, 高效的進行 pomelo game 開發node
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 都以 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 表明着數據和模型, 包括玩家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的模版, 並不會被實例化, 它經過對象屬性依賴注入了 dataApiUtil 和 utildom
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 的構造函數來進行調用
在沒有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 拿到
移動和撿寶是經過 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 裏面維護着當前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