本文以使用origin框架(一款使用Go語言寫的開源游戲服務器框架)為例進行說明,當然也可以使用其它的框架或者自己寫。
在框架中PBProcessor用來處理Protobuf消息,在使用之前,需要使用Register函數注冊網絡消息:
func (pbProcessor *PBProcessor) Register(msgType uint16, msg proto.Message, handle MessageHandler) {var info MessageInfoinfo.msgType = reflect.TypeOf(msg.(proto.Message))info.msgHandler = handlepbProcessor.mapMsg[msgType] = info
}
網絡消息來時通過MsgRoute進行分發:
func (pbProcessor *PBProcessor) MsgRoute(clientId string, msg interface{}, recyclerReaderBytes func(data []byte)) error {pPackInfo := msg.(*PBPackInfo)defer recyclerReaderBytes(pPackInfo.rawMsg)v, ok := pbProcessor.mapMsg[pPackInfo.typ]if ok == false {return fmt.Errorf("cannot find msgtype %d is register", pPackInfo.typ)}v.msgHandler(clientId, pPackInfo.msg)return nil
}
這是框架提供的基礎功能。在平常使用中最方便的就是游戲中各個功能模塊相互獨立,減少耦合性。
Go語言中支持包,可以將包看作一個模塊,進行模塊化編程。游戲中常見的操作是玩家數據的存取以及網絡消息處理,可以定義接口:
package common// 游戲邏輯模塊的存取
type ISaveLoad interface {// 由于每個模塊使用的PB不一樣,在使用InitFromPB之前需要調用NewPB創建PBNewPB() proto.Message// 從PB中讀取模塊數據InitFromPB(pb proto.Message)// 完成數據讀取后的處理,主要解決模塊數據的相互依賴,比如模塊A可能會依賴模塊B,但模塊B的數據可能還沒讀取出來PostLoad(pb proto.Message)// 保存模塊數據到PB中,bSave2DB用于判斷是存數據庫,還是發送給客戶端,可能存在有些數據不能發給客戶端,可以通過此變量進行處理Save2PB(bSave2DB bool) proto.Message
}// 游戲邏輯模塊
type IGameModule interface {ISaveLoad// 獲取模塊ID,這些ID都可以寫在PB中,客戶端、服務器共用,玩家上線時,服務器可以根據模塊ID發送數據給客戶端GetModuleID() netmsg.ModuleID
}// 游戲邏輯模塊的工廠模式
type IModuleFactory interface {// 新建模塊,每個模塊中都有一個IRole歸屬,方便使用角色中的其它模塊數據NewModule(owner IRole) IModule// 注冊模塊中的網絡消息RegNetMsg()
}// 游戲角色
type IRole interface {// 賬號IDGetUserID() uint64// 角色IDGetRoleID() uint64// 發消息給客戶端SendMsg2Client(cmdId netmsg.NetCmdID, pb proto.Message)// 獲取角色中的游戲模塊GetGameModule(ID netmsg.ModuleID) IGameModule
}
定義好接口后,就可以將Go的包當然游戲模塊編寫邏輯了,這樣寫的邏輯會比較清晰。
下面以背包模塊為例來說明,創建一個bag目錄來作為游戲邏輯模塊,里面再分文件來區分是模塊注冊(bag_mod.go
),模塊IO(bag_io.go
),模塊邏輯(bag.go
)等等,為什么里面還要這么分,是因為當一個模塊比較大時,定位起來方便,比如在實際開發中經常需要定位要IO部分,可能需要修改與客戶端的通信。
bag.go
:
package bagtype bag struct{owner common.IRolecap uint16
}func newBag(owner common.IRole) {return &bag{owner: owner}
}func (slf *bag) addItem(pb *netmsg.BagAddItem) {
}
bag_mod.go
:
package bagfunc init() {// 這里向模塊管理器注冊模塊,模塊管理器會調用factory的NewModule創建模塊,調用RegNetMsg注冊網絡消息mod.RegModule(netmsg.ModuleID_Bag, factory{})
}type factory struct {
}func (f factory) NewModule(owner common.IRole) common.IGameModule {return newBag(owner)
}// 注冊本模塊中的所有網絡消息處理函數
func (f factory) RegNetMsg() {mod.RegNetMsg(netmsg.NetCmdID_AddItem, onAddItem)
}func onAddItem(p *bag, pb *netmsg.BagAddItem) {p.addItem(pb)
}
bag_io.go
:
package bagfunc (slf *Bag) GetModuleID() netmsg.ModuleID {return netmsg.ModuleID_Bag
}func (slf *Bag) NewPB() proto.Message {return &netmsg.Bag{}
}func (slf *Bag) InitFromPB(pb proto.Message) {msg := pb.(*netmsg.Bag)slf.cap = msg.Cap
}func (slf *Bag) Save2PB(isSave2DB bool) proto.Message {return &netmsg.Bag{Cap: slf.cap}
}func (slf *Bag) PostLoad(pb proto.Message) {
}
前面代碼中有使用到mod
包,它是模塊的管理包。
mod.go
package modvar (modules = map[netmsg.ModuleID]common.IModuleFactory{}mapNetMsg = map[netmsg.NetCmdID]netmsg.ModuleID{}process *processor.PBProcessormodType netmsg.ModuleID
)// 供各邏輯模塊調用以注冊模塊
func RegModule(moduleID netmsg.ModuleID, module common.IModuleFactory) {if _, ok := modules[id]; ok {log.Fatalf("Repeated RegModule Module ID:%s", moduleID.String())return}modules[id] = module
}// 供Service調用以注冊各模塊的網絡消息
func RegModuleNetMsg(p *processor.PBProcessor) {// 記錄下處理器process = pfor _, m := range modules {m.RegNetMsg()}
}// 供各模塊調用以注冊本模塊的網絡消息處理。M為模塊結構指針,T為處理函數使用的網絡消息結構指針
func RegNetMsg[T proto.Message, M any](cmdId netmsg.NetCmdID, handle func(M, T)) {f := func(p common.IRole, pb T) {// 根據網絡消息ID查模塊IDid, ok := mapNetMsg[cmdId]if !ok {return}// 根據模塊ID獲取取模塊m := p.GetGameModule(id)if m != nil {defer func() {if r := recover(); r != nil {buf := make([]byte, 4096)l := runtime.Stack(buf, false)errString := fmt.Sprint(r)log.Errorf("UserID:%d RoleID:%d Module:%v NetMsg:%v Core dump info[%s]\n%s",p.GetUserID(), p.GetRoleID(), id, cmdId, errString, string(buf[:l]))}}()// 調用處理函數時,把模塊接口轉為實際的模塊指針handle(m.(M), pb)}}if _, ok := mapNetMsg[cmdId ]; ok {panic("Repeated RegModule Module NetMsg:%s", id.String())} else {mapNetMsg[cmdId ] = modType}register(cmdId, f)
}// 注冊網絡消息處理器,UserData為游戲邏輯模塊結構的指針,T為模塊網絡消息處理函數中使用的網絡消息結構指針
func register[T proto.Message, UserData any](cmdId netmsg.NetCmdID, handle func(UserData, T)) {f := func(userData interface{}, msg proto.Message) {// 轉換為游戲邏輯模塊結構的指針p := userData.(UserData)// 轉換為消息處理函數中使用的網絡消息結構指針pb := msg.(T)handle(p, pb)}var pb T// 這里調用origin框架的PB處理器,注冊網絡消息處理函數process.Register(uint16(cmdId), pb, f)
}
在各個模塊中調用mod.RegModule
來注冊模塊,如bag_mod.go
所示。
然后在origin的服務中調用mod.RegModuleNetMsg
來注冊各模塊的網絡消息。比如:
package myServicefunc init() {// 注冊服務service
}type service struct {service.Serviceprocess *processor.PBProcessor
}func (slf *myService) OnInit() error {slf.process = processor.NewPBProcessor()mod.RegModuleNetMsg(slf.process)
}
這樣就可以清晰地寫游戲邏輯中的模塊了。
如果本文對你有幫助,歡迎點贊收藏!