国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當前位置: 首頁 > news >正文

網(wǎng)站開發(fā)人員要求科技網(wǎng)站建設(shè)公司

網(wǎng)站開發(fā)人員要求,科技網(wǎng)站建設(shè)公司,重慶做網(wǎng)站哪家靠譜,網(wǎng)站開發(fā) 方案概要從0到1開發(fā)go-tcp框架【實戰(zhàn)片— — 開發(fā)MMO】 MMO(MassiveMultiplayerOnlineGame):大型多人在線游戲(多人在線網(wǎng)游) 1 AOI興趣點的算法 游戲中的坐標模型: 場景相關(guān)數(shù)值計算 ● 場景大小: 250…

從0到1開發(fā)go-tcp框架【實戰(zhàn)片— — 開發(fā)MMO】

MMO(MassiveMultiplayerOnlineGame):大型多人在線游戲(多人在線網(wǎng)游)

1 AOI興趣點的算法

游戲中的坐標模型:

在這里插入圖片描述
場景相關(guān)數(shù)值計算

● 場景大小: 250*250 , w(x軸寬度) = 250,l(y軸長度) = 250
● x軸格子數(shù)量:nx = 5
● y軸格子數(shù)量:ny = 5
● 格子寬度: dx = w / nx = 250 / 5 = 50
● 格子長度: dy = l / ny = 250 / 5 = 50
● 格子的x軸坐標:idx
● 格子的y軸坐標:idy
● 格子編號:id = idy *nx + idx (利用格子坐標得到格子編號)
● 格子坐標:idx = id % nx , idy = id / nx (利用格子id得到格子坐標)
● 格子的x軸坐標: idx = id % nx (利用格子id得到x軸坐標編號)
● 格子的y軸坐標: idy = id / nx (利用格子id得到y(tǒng)軸坐標編號)

1.1 定義AOI格子(Grid)

AOI格子:

  • 格子ID
  • 格子的左邊界坐標
  • 格子的右邊界坐標
  • 格子的上邊界坐標
  • 格子的下邊界坐標
  • 當前格子內(nèi)玩家/物體成員的ID集合
  • 保護當前集合的鎖

AOI格子應(yīng)該有的方法:

  • 初始化當前格子
  • 給格子添加一個玩家
  • 從格子中刪除一個玩家
  • 得到當前格子中所有的玩家
  • 調(diào)試使用:打印出格子的基本信息

mmo_game_zinx/core/grid.go

package coreimport ("fmt""sync"
)/*
一個AOI地圖中的格子類型
*/
type Grid struct {//格子IDGID int//格子的左邊界坐標MinX int//格子的右邊界坐標MaxX int//格子的上邊界坐標MinY int//格子的下邊界坐標MaxY int//當前格子內(nèi)玩家或物體成員對的ID集合playerIDs map[int]bool//保護當前集合的鎖pIDLock sync.RWMutex
}//初始化當前格子的方法
func NewGrid(gId, minX, maxX, minY, maxY int) *Grid {return &Grid{GID:       gId,MinX:      minX,MaxX:      maxX,MinY:      minY,MaxY:      maxY,playerIDs: make(map[int]bool),}
}//給格子添加一個玩家
func (g *Grid) Add(playerId int) {g.pIDLock.Lock()defer g.pIDLock.Unlock()g.playerIDs[playerId] = true
}//從格子中刪除一個玩家
func (g *Grid) Remove(playerId int) {g.pIDLock.Lock()defer g.pIDLock.Unlock()delete(g.playerIDs, playerId)
}//得到當前格子中所有玩家的id
func (g *Grid) GetPlayerIds() (playerIds []int) {g.pIDLock.RLock()defer g.pIDLock.RUnlock()for k, _ := range g.playerIDs {playerIds = append(playerIds, k)}return
}func (g *Grid) String() string {return fmt.Sprintf("Grid id: %d, minX: %d, maxX: %d, minY: %d, maxY: %d, playerIds: %v",g.GID, g.MinX, g.MaxX, g.MinY, g.MaxY, g.playerIDs)
}

1.2 AOI管理模塊

AOIManager:

  • 初始化一個AOI管理區(qū)域模塊
  • 得到每個格子在X軸方向的寬度
  • 通過橫縱軸得到GID
  • 添加一個PlayerId到一個格子中
  • 移除一個格子中的playerId
  • 通過GID獲取全部的PlayerID
  • 通過坐標將Player添加到一個格子中
  • 通過坐標把一個Player從一個格子中刪除
  • 通過Player坐標得到當前Player周邊九宮格內(nèi)全部的PlayerIDs
  • 通過坐標獲取對應(yīng)玩家所在的GID
  • 通過橫縱軸得到周圍的九宮格
  • 根據(jù)GID獲取GID周圍的九宮格
    在這里插入圖片描述
    • 獲取思路:先求x,再求y。先根據(jù)GID判斷該GID左邊和右邊是否有格子 。然后將X軸上的格子添加到集合中,再遍歷集合判斷集合中的上下是否有格子。

mmo_game_zinx/core/aoi.go

package coreimport "fmt"// 定義AOI地圖大小
const (AOI_MIN_X  int = 85AOI_MAX_X  int = 410AOI_CNTS_X int = 10AOI_MIN_Y  int = 75AOI_MAX_Y  int = 400AOI_CNTS_Y int = 20
)type AOIManager struct {//區(qū)域的左邊界坐標MinX int//區(qū)域的右界坐標MaxX int//X方向格子的數(shù)量CountsX int//區(qū)域的上邊界坐標MinY int//區(qū)域的下邊界坐標MaxY int//Y方向上格子的數(shù)量CountsY int//當前區(qū)域中有哪些格子map-key=格子的IDgrids map[int]*Grid
}func NewAOIManager(minX, maxX, countsX, minY, maxY, countsY int) *AOIManager {aoiMgr := &AOIManager{MinX:    minX,MaxX:    maxX,CountsX: countsX,MinY:    minY,MaxY:    maxY,CountsY: countsY,grids:   make(map[int]*Grid),}//給AOI初始化區(qū)域的所有格子進行編號和初始化for y := 0; y < countsY; y++ {for x := 0; x < countsX; x++ {//計算格子的ID 根據(jù)x,y編號//格子的編號:id = idy * countsX + idxgid := y*countsX + x//初始化gid格子aoiMgr.grids[gid] = NewGrid(gid,aoiMgr.MinX+x*aoiMgr.gridXWidth(),aoiMgr.MinX+(x+1)*aoiMgr.gridXWidth(),aoiMgr.MinY+y*aoiMgr.gridYLength(),aoiMgr.MinY+(y+1)*aoiMgr.gridYLength())}}return aoiMgr
}// 得到每個格子在x軸方向的寬度
func (m *AOIManager) gridXWidth() int {return (m.MaxX - m.MinX) / m.CountsX
}// 得到每個格子在y軸方向的寬度
func (m *AOIManager) gridYLength() int {return (m.MaxY - m.MinY) / m.CountsY
}// 打印信息方法
func (m *AOIManager) String() string {s := fmt.Sprintf("AOIManagr:\nminX:%d, maxX:%d, cntsX:%d, minY:%d, maxY:%d, cntsY:%d\n Grids in AOI Manager:\n",m.MinX, m.MaxX, m.CountsX, m.MinY, m.MaxY, m.CountsY)for _, grid := range m.grids {s += fmt.Sprintln(grid)}return s
}// 根據(jù)格子的gID得到當前周邊的九宮格信息
func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) {//判斷gID是否存在if _, ok := m.grids[gID]; !ok {return}//將當前gid添加到九宮格中grids = append(grids, m.grids[gID])//根據(jù)gid得到當前格子所在的X軸編號idx := gID % m.CountsX//判斷當前idx左邊是否還有格子if idx > 0 {grids = append(grids, m.grids[gID-1])}//判斷當前的idx右邊是否還有格子if idx < m.CountsX-1 {grids = append(grids, m.grids[gID+1])}//將x軸當前的格子都取出,進行遍歷,再分別得到每個格子的上下是否有格子//得到當前x軸的格子id集合gidsX := make([]int, 0, len(grids))for _, v := range grids {gidsX = append(gidsX, v.GID)}//遍歷x軸格子for _, v := range gidsX {//計算該格子處于第幾列idy := v / m.CountsX//判斷當前的idy上邊是否還有格子if idy > 0 {grids = append(grids, m.grids[v-m.CountsX])}//判斷當前的idy下邊是否還有格子if idy < m.CountsY-1 {grids = append(grids, m.grids[v+m.CountsX])}}return
}// 通過橫縱坐標獲取對應(yīng)的格子ID
func (m *AOIManager) GetGIDByPos(x, y float32) int {gx := (int(x) - m.MinX) / m.gridXWidth()gy := (int(y) - m.MinY) / m.gridYLength()return gy*m.CountsX + gx
}// 通過橫縱坐標得到周邊九宮格內(nèi)的全部PlayerIDs
func (m *AOIManager) GetPIDsByPos(x, y float32) (playerIDs []int) {//根據(jù)橫縱坐標得到當前坐標屬于哪個格子IDgID := m.GetGIDByPos(x, y)//根據(jù)格子ID得到周邊九宮格的信息grids := m.GetSurroundGridsByGid(gID)for _, v := range grids {playerIDs = append(playerIDs, v.GetPlayerIds()...)fmt.Printf("===> grid ID : %d, pids : %v  ====\n", v.GID, v.GetPlayerIds())}return
}// 添加一個PlayerId到一個格子中
func (m *AOIManager) AddPidToGrid(pId, gId int) {m.grids[gId].Add(pId)
}// 移除一個格子中的playerID
func (m *AOIManager) RemovePidFromGrid(pId, gId int) {m.grids[gId].Remove(pId)
}// 通過GID獲取全部的playerID
func (m *AOIManager) GetPidsByGid(gId int) (playerIds []int) {playerIds = m.grids[gId].GetPlayerIds()return
}// 通過坐標將一個Player添加到一個格子中
func (m *AOIManager) AddToGridByPos(pId int, x, y float32) {gId := m.GetGIDByPos(x, y)grid := m.grids[gId]grid.Add(pId)
}// 通過一個坐標把一個player從一個格子中刪除
func (m *AOIManager) RemoveFromGridByPos(pId int, x, y float32) {gId := m.GetGIDByPos(x, y)grid := m.grids[gId]grid.Remove(pId)
}

mmo_game_zinx/core/aoi_test.go

package coreimport ("fmt""testing"
)func TestNewAOIManager(t *testing.T) {//初始化AOIManageraoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5)//打印AOIManagerfmt.Println(aoiMgr)
}//根據(jù)GID獲取九宮格
func TestAOIManager_GetSurroundGridsByGid(t *testing.T) {//初始化AOIManageraoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5)for gid, _ := range aoiMgr.grids {grids := aoiMgr.GetSurroundGridsByGid(gid)fmt.Println("gid : ", gid, " grids len = ", len(grids))gIds := make([]int, 0, len(grids))for _, grid := range grids {gIds = append(gIds, grid.GID)}fmt.Println("surrounding grid IDs are : ", gIds)}
}

2 數(shù)據(jù)傳輸協(xié)議的選擇(protobuf)

常見的傳輸格式:json、xml、protobuf

  1. json:可讀性比較強;編解碼比較耗時【web領(lǐng)域】
  2. xml:基于標簽【前端/網(wǎng)頁】
  3. protobuf(Google開發(fā)的):編解碼很快、體積小、跨平臺;可讀性不強,傳輸過程中不是明文,是二進制(已經(jīng)序列化完畢的)【后端應(yīng)用/微服務(wù)/服務(wù)器】

2.1 安裝

我這里以mac安裝為例,其他os自行百度即可

# 安裝protobuf
brew install protobuf# 安裝用于編譯生成go文件的插件
brew install protoc-gen-go
brew install protoc-gen-go-grpc# 查看版本
protoc --version
protoc-gen-go --version# 安裝golang插件
go get github.com/golang/protobuf/protoc-gen-go
go get -u -v github.com/golang/protobuf/protoc-gen-go

2.2 profobuf語法及使用

①語法

person.proto

syntax = "proto3"; 						//指定版本信息,不指定會報錯
package pb;						//后期生成go文件的包名option go_package = "./;proto"; //配置包依賴路徑
//message為關(guān)鍵字,作用為定義一種消息類型
message Person {string	name = 1;					//姓名int32	age = 2;					//年齡repeated string emails = 3; 		//電子郵件(repeated表示字段允許重復)【類比go中的切片】repeated PhoneNumber phones = 4;	//手機號
}//enum為關(guān)鍵字,作用為定義一種枚舉類型
enum PhoneType {MOBILE = 0;HOME = 1;WORK = 2;
}//message為關(guān)鍵字,作用為定義一種消息類型可以被另外的消息類型嵌套使用
message PhoneNumber {string number = 1;PhoneType type = 2;
}

②使用步驟

  • 定義一個go的與protobuf對應(yīng)的結(jié)構(gòu)體
  • proto.Marshal進行編碼序列化,得到二進制數(shù)據(jù)data
  • 將data進行傳輸,或者發(fā)送給對方
  • 對方收到data數(shù)據(jù),將data通過proto.UnMarshal得到person結(jié)構(gòu)體數(shù)據(jù)

1. 編寫.proto文件

2. 執(zhí)行protoc編譯出對應(yīng)go代碼

通過如下方式調(diào)用protocol編譯器,把 .proto 文件編譯成代碼

 protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto

其中:

  1. –proto_path,指定了 .proto 文件導包時的路徑,可以有多個,如果忽略則默認當前目錄。
  2. –go_out, 指定了生成的go語言代碼文件放入的文件夾
  3. 允許使用protoc --go_out=./ *.proto的方式一次性編譯多個 .proto 文件 【.proto中需要添加option go_package選項】,否則會報:protoc-gen-go: unable to determine Go import path for “xxx.proto”
option go_package = "./;proto"; //配置包依賴路徑

在這里插入圖片描述

  1. 編譯時,protobuf 編譯器會把 .proto 文件編譯成 .pd.go 文件
    在這里插入圖片描述

3. 通過proto.Marshal進行序列化(發(fā)數(shù)據(jù))

data, err := proto2.Marshal(person)

4. 通過proto.UnMarshal進行反序列話(收數(shù)據(jù))

err = proto2.Unmarshal(data, &newPerson)

③測試傳輸

在myDemo/protobuf文件夾下編寫main.go進行測試

main.go

package mainimport ("fmt"proto2 "google.golang.org/protobuf/proto"pb "myTest/myDemo/protobuf/pb"
)func main() {person := &pb.Person{Name:   "ziyi",Age:    18,Emails: []string{"ziyi.atgmai.com", "ziyi_at163.com"},Phones: []*pb.PhoneNumber{&pb.PhoneNumber{Number: "181234567",Type:   pb.PhoneType_MOBILE,},&pb.PhoneNumber{Number: "33331111",Type:   pb.PhoneType_HOME,},},}//編碼:將person對象編碼,將protobuf的message進行序列化,得到一個[]byte數(shù)組data, err := proto2.Marshal(person)if err != nil {fmt.Println("protobuf marshal err =", err)return}//解碼newPerson := pb.Person{}err = proto2.Unmarshal(data, &newPerson)if err != nil {fmt.Println("protobuf unmarshal err =", err)return}fmt.Println("傳輸?shù)臄?shù)據(jù):", person)fmt.Println("接收到的數(shù)據(jù):", &newPerson)
}

在這里插入圖片描述

3 游戲相關(guān)業(yè)務(wù)

3.1 業(yè)務(wù)消息格式定義

在這里插入圖片描述

MsgID:1(同步玩家本地登錄ID)

  1. MsgID:1
  • 同步玩家本地登錄的ID(用來標識玩家),玩家登錄之后,由Server端主動生成玩家ID發(fā)送給客戶端
  • 發(fā)起者:Server
  • Pid:玩家ID

對應(yīng)proto:

message SyncPid{int32 Pid=1;
}

MsgID:2(世界聊天)

● 同步玩家本次登錄的ID(用來標識玩家), 玩家登陸之后,由Server端主動生成玩家ID發(fā)送給客戶端
● 發(fā)起者: Client
● Content: 聊天信息

message Talk{string Content=1;
}

MsgID:3(移動信息)

● 移動的坐標數(shù)據(jù)
● 發(fā)起者: Client
● P: Position類型,地圖的左邊點

message Position{float X=1;float Y=2;float Z=3;float V=4;
}

MsgID:200(廣播聊天、坐標、動作)

● 廣播消息
● 發(fā)起者: Server
● Tp: 1 世界聊天, 2 坐標, 3 動作, 4 移動之后坐標信息更新
● Pid: 玩家ID

message BroadCast{int32 Pid=1;int32 Tp=2;//oneof表示只能選三個中的一個oneof Data {string Content=3;Position P=4;int32 ActionData=5;}
}

MsgID:201

● 廣播消息 掉線/aoi消失在視野
● 發(fā)起者: Server
● Pid: 玩家ID

message SyncPid{int32 Pid=1;
}

MsgID:202(同步位置信息)

● 同步周圍的人位置信息(包括自己)
● 發(fā)起者: Server
● ps: Player 集合,需要同步的玩家

message SyncPlayers{repeated Player ps=1;
}message Player{int32 Pid=1;Position P=2;
}

3.2 項目模塊搭建

mmo_game_zinx

  • apis:存放基本用戶的自定義路由業(yè)務(wù),一個msgId對應(yīng)一個業(yè)務(wù)
  • conf:存放zinx.json(自定義框架的配置文件)
  • pb:protobuf相關(guān)文件
  • core:存放核心功能
  • main.go:服務(wù)器的主入口
  • game_client:unity客戶端

最終項目結(jié)構(gòu)

.
└── mmo_game_zinx├── apis├── conf│   └── zinx.json├── core│   ├── aoi.go│   ├── aoi_test.go│   ├── grid.go├── game_client│   └── client.exe├── pb│   ├── build.sh│   └── msg.proto├── README.md└── server.go

在這里插入圖片描述

①玩家上線

在這里插入圖片描述

  • 創(chuàng)建一個玩家的方法:
  1. 編寫proto文件
  2. 定義玩家對象player.go
  • 玩家可以和客戶端通信的發(fā)送消息的方法:
  1. 將msg的proto格式進行序列化改成二進制
  2. 通過zinx框架提供的sendMsg將數(shù)據(jù)進行TLV格式的打包發(fā)包
  • 實現(xiàn)上線業(yè)務(wù)功能:
  1. 給server注冊一個創(chuàng)建連接之后的hook函數(shù)
  2. 給Player提供兩個方法:將PlayerID同步給客戶端、將Player上線的初始位置同步給客戶端
1. mmo_game_zinx/pb/msg.proto

定義proto文件(消息類型)

syntax = "proto3";                //Proto協(xié)議
package pb;                     //當前包名
option csharp_namespace = "Pb";   //給C#提供的選項[因為我們的游戲畫面采用unity3D,基于C#的]
option go_package = "./;pb"; //配置包依賴路徑//同步客戶端玩家ID
message SyncPid{int32 Pid=1;
}//玩家位置
message Position{float X=1;float Y=2;float Z=3;float V=4;
}//玩家廣播數(shù)據(jù)
message BroadCast{int32 Pid=1;int32 Tp=2;//1 世界聊天, 2 坐標, 3 動作, 4 移動之后坐標信息更新oneof Data {string Content=3;Position P=4;int32 ActionData=5;}
}

為了方便后續(xù)更新proto文件,我們這里直接編寫一個腳本

mmo_game_zinx/pb/build.sh:

#!/bin/bash
protoc --go_out=. *.proto
2. mmo_game_zinx/core/player.go
package coreimport ("fmt""google.golang.org/protobuf/proto""math/rand"pb "myTest/mmo_game_zinx/pb""myTest/zinx/ziface""sync"
)// 玩家
type Player struct {Pid  int32              //玩家IDConn ziface.IConnection //當前玩家的連接(用于和客戶端的連接)X    float32            //平面的X坐標Y    float32            //高度Z    float32            //平面y坐標(注意:Z字段才是玩家的平面y坐標,因為unity的客戶端已經(jīng)定義好了)V    float32            //旋轉(zhuǎn)的0-360角度
}var PidGen int32 = 1  //用于生成玩家id
var IdLock sync.Mutex //保護PidGen的鎖func NewPlayer(conn ziface.IConnection) *Player {IdLock.Lock()id := PidGenPidGen++IdLock.Unlock()p := &Player{Pid:  id,Conn: conn,X:    float32(160 + rand.Intn(10)), //隨機在160坐標點,基于X軸若干便宜Y:    0,Z:    float32(140 + rand.Intn(20)), //隨機在140坐標點,基于Y軸若干偏移V:    0,}return p
}/*
提供一個發(fā)送給客戶端消息的方法
主要是將pb的protobuf數(shù)據(jù)序列化后,再調(diào)用zinx的sendMsg方法
*/
func (p *Player) SendMsg(msgId uint32, data proto.Message) {//將proto Message結(jié)構(gòu)體序列化 轉(zhuǎn)換成二進制msg, err := proto.Marshal(data)if err != nil {fmt.Println("marshal msg err: ", err)return}//將二進制文件 通過zinx框架的sendMsg將數(shù)據(jù)發(fā)送給客戶端if p.Conn == nil {fmt.Println("connection in player is nil")return}if err := p.Conn.SendMsg(msgId, msg); err != nil {fmt.Println("player send msg is err, ", err)return}
}// 告知客戶端玩家的pid,同步已經(jīng)生成的玩家ID給客戶端
func (p *Player) SyncPid() {//組件MsgID:0的proto數(shù)據(jù)proto_msg := &pb.SyncPid{Pid: p.Pid,}//將消息發(fā)送給客戶端p.SendMsg(1, proto_msg)
}// 廣播玩家自己的出生地點
func (p *Player) BroadCastStartPosition() {//組建MsgID:200 的proto數(shù)據(jù)proto_msg := &pb.BroadCast{Pid: p.Pid,Tp:  2, //Tp2 代表廣播位置的坐標Data: &pb.BroadCast_P{P: &pb.Position{X: p.X,Y: p.Y,Z: p.Z,V: p.V,},},}//將消息發(fā)送給客戶端p.SendMsg(200, proto_msg)
}
3. mmo_game_zinx/main.go
package mainimport ("fmt""myTest/mmo_game_zinx/core""myTest/zinx/ziface""myTest/zinx/znet"
)// 當前客戶端建立連接之后的hook函數(shù)
func OnConnectionAdd(conn ziface.IConnection) {//創(chuàng)建一個player對象player := core.NewPlayer(conn)//給客戶端發(fā)送MsgID:1的消息,同步當前的playerID給客戶端player.SyncPid()//給客戶端發(fā)送MsgID:200的消息,同步當前Player的初始位置給客戶端player.BroadCastStartPosition()fmt.Println("======>Player pid = ", player.Pid, " is arrived ====")
}func main() {//創(chuàng)建服務(wù)句柄s := znet.NewServer("MMO Game Zinx")s.SetOnConnStart(OnConnectionAdd)s.Serve()
}
測試效果
  1. 啟動服務(wù)端
  2. 啟動客戶端(client.exe)
    在這里插入圖片描述

連續(xù)啟動多個查看效果:

在這里插入圖片描述
服務(wù)端控制臺打印:
在這里插入圖片描述

②世界聊天

在這里插入圖片描述

  1. proto3聊天協(xié)議的定義
  2. 聊天業(yè)務(wù)的實現(xiàn):
  • 解析聊天的proto協(xié)議
  • 將聊天數(shù)據(jù)廣播給全部在線玩家->創(chuàng)建一個世界管理模塊
    - 初始化管理模塊
    - 添加一個玩家
    - 刪除一個玩家
    - 通過玩家ID查詢Player對象
    - 獲取全部的在線玩家
1. mmo_game_zinx/pb/msg.proto

在之前的基礎(chǔ)上,在末尾追加:

message Talk{string Content=1;
}

執(zhí)行build.sh腳本重新編譯

2. mmo_game_zinx/apis/world_chat.go
package apisimport ("fmt""google.golang.org/protobuf/proto""myTest/mmo_game_zinx/core"pb "myTest/mmo_game_zinx/pb""myTest/zinx/ziface""myTest/zinx/znet"
)// 世界聊天路由業(yè)務(wù)
type WorldChatApi struct {znet.BaseRouter
}// 重寫handler方法
func (wc *WorldChatApi) Handler(request ziface.IRequest) {//1 解析客戶端傳遞進來的proto協(xié)議proto_msg := &pb.Talk{}err := proto.Unmarshal(request.GetData(), proto_msg)if err != nil {fmt.Println("Talk Unmarshal err ", err)return}//2 當前的聊天數(shù)據(jù) 屬于哪個玩家發(fā)送的pid, err := request.GetConnection().GetProperty("pid")//3 根據(jù)pid得到對應(yīng)的player對象player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))//4 將這個消息廣播給其他全部在線的用戶player.Talk(proto_msg.Content)
}
3. mmo_game_zinx/main.go
package mainimport ("fmt""myTest/mmo_game_zinx/apis""myTest/mmo_game_zinx/core""myTest/zinx/ziface""myTest/zinx/znet"
)// 當前客戶端建立連接之后的hook函數(shù)
func OnConnectionAdd(conn ziface.IConnection) {//創(chuàng)建一個player對象player := core.NewPlayer(conn)//給客戶端發(fā)送MsgID:1的消息,同步當前的playerID給客戶端player.SyncPid()//給客戶端發(fā)送MsgID:200的消息,同步當前Player的初始位置給客戶端player.BroadCastStartPosition()//將當前新上線的玩家添加到WorldManager中core.WorldMgrObj.AddPlayer(player)//將playerId添加到連接屬性中,方便后續(xù)廣播知道是哪個玩家發(fā)送的消息conn.SetProperty("pid", player.Pid)fmt.Println("======>Player pid = ", player.Pid, " is arrived ====")
}func main() {//創(chuàng)建服務(wù)句柄s := znet.NewServer("MMO Game Zinx")s.SetOnConnStart(OnConnectionAdd)//注冊一些路由業(yè)務(wù)s.AddRouter(2, &apis.WorldChatApi{})s.Serve()
}
4. mmo_game_zinx/core/world_manager.go
package coreimport "sync"/*
當前游戲的世界總管理模塊
*/
type WorldManager struct {//AOIManager 當前世界地圖AOI的管理模塊AoiMgr *AOIManager//當前全部在線的players集合Players map[int32]*Player//保護Players集合的鎖pLock sync.RWMutex
}// 提供一個對外的世界管理模塊的句柄
var WorldMgrObj *WorldManagerfunc init() {WorldMgrObj = &WorldManager{//創(chuàng)建世界AOI地圖規(guī)劃AoiMgr: NewAOIManager(AOI_MIN_X, AOI_MAX_X, AOI_CNTS_X, AOI_MIN_Y, AOI_MAX_Y, AOI_CNTS_Y),//初始化player集合Players: make(map[int32]*Player),}
}// 添加一個玩家
func (wm *WorldManager) AddPlayer(player *Player) {wm.pLock.Lock()wm.Players[player.Pid] = playerwm.pLock.Unlock()//將player添加到AOIManager中wm.AoiMgr.AddToGridByPos(int(player.Pid), player.X, player.Z)
}// 刪除一個玩家
func (wm *WorldManager) RemovePlayerByPid(pid int32) {//得到當前的玩家player := wm.Players[pid]//將玩家從AOIManager中刪除wm.AoiMgr.RemoveFromGridByPos(int(pid), player.X, player.Z)//將玩家從世界管理中刪除wm.pLock.Lock()delete(wm.Players, pid)wm.pLock.Unlock()
}// 通過玩家ID查詢player對象
func (wm *WorldManager) GetPlayerByPid(pid int32) *Player {wm.pLock.RLock()defer wm.pLock.RUnlock()return wm.Players[pid]
}// 獲取全部的在線玩家
func (wm *WorldManager) GetAllPlayers() []*Player {wm.pLock.Lock()defer wm.pLock.Unlock()players := make([]*Player, 0)//遍歷集合,將玩家添加到players切片中for _, p := range wm.Players {players = append(players, p)}return players
}
5. mmo_game_zinx/core/player.go
package coreimport ("fmt""google.golang.org/protobuf/proto""math/rand"pb "myTest/mmo_game_zinx/pb""myTest/zinx/ziface""sync"
)// 玩家
type Player struct {Pid  int32              //玩家IDConn ziface.IConnection //當前玩家的連接(用于和客戶端的連接)X    float32            //平面的X坐標Y    float32            //高度Z    float32            //平面y坐標(注意:Z字段才是玩家的平面y坐標,因為unity的客戶端已經(jīng)定義好了)V    float32            //旋轉(zhuǎn)的0-360角度
}var PidGen int32 = 1  //用于生成玩家id
var IdLock sync.Mutex //保護PidGen的鎖func NewPlayer(conn ziface.IConnection) *Player {IdLock.Lock()id := PidGenPidGen++IdLock.Unlock()p := &Player{Pid:  id,Conn: conn,X:    float32(160 + rand.Intn(10)), //隨機在160坐標點,基于X軸若干便宜Y:    0,Z:    float32(140 + rand.Intn(20)), //隨機在140坐標點,基于Y軸若干偏移V:    0,}return p
}/*
提供一個發(fā)送給客戶端消息的方法
主要是將pb的protobuf數(shù)據(jù)序列化后,再調(diào)用zinx的sendMsg方法
*/
func (p *Player) SendMsg(msgId uint32, data proto.Message) {//將proto Message結(jié)構(gòu)體序列化 轉(zhuǎn)換成二進制msg, err := proto.Marshal(data)if err != nil {fmt.Println("marshal msg err: ", err)return}//將二進制文件 通過zinx框架的sendMsg將數(shù)據(jù)發(fā)送給客戶端if p.Conn == nil {fmt.Println("connection in player is nil")return}if err := p.Conn.SendMsg(msgId, msg); err != nil {fmt.Println("player send msg is err, ", err)return}
}// 告知客戶端玩家的pid,同步已經(jīng)生成的玩家ID給客戶端
func (p *Player) SyncPid() {//組件MsgID:0的proto數(shù)據(jù)proto_msg := &pb.SyncPid{Pid: p.Pid,}//將消息發(fā)送給客戶端p.SendMsg(1, proto_msg)
}// 廣播玩家自己的出生地點
func (p *Player) BroadCastStartPosition() {//組建MsgID:200 的proto數(shù)據(jù)proto_msg := &pb.BroadCast{Pid: p.Pid,Tp:  2, //Tp2 代表廣播位置的坐標Data: &pb.BroadCast_P{P: &pb.Position{X: p.X,Y: p.Y,Z: p.Z,V: p.V,},},}//將消息發(fā)送給客戶端p.SendMsg(200, proto_msg)
}// 玩家廣播世界聊天消息
func (p *Player) Talk(content string) {//1 組建MsgID:200 proto數(shù)據(jù)proto_msg := &pb.BroadCast{Pid: p.Pid,Tp:  1, //tp-1 代表聊天廣播Data: &pb.BroadCast_Content{Content: content,},}//2 得到當前世界所有在線的玩家players := WorldMgrObj.GetAllPlayers()for _, player := range players {//player分別給對應(yīng)的客戶端發(fā)送消息player.SendMsg(200, proto_msg)}
}
測試效果

在這里插入圖片描述

啟動服務(wù)

http://m.aloenet.com.cn/news/36740.html

相關(guān)文章:

  • 綁定網(wǎng)站品牌策劃與推廣方案
  • 建一個購物網(wǎng)站需要多少錢邯鄲今日頭條最新消息
  • 免費個人網(wǎng)站建設(shè)可口可樂軟文營銷案例
  • 惠州網(wǎng)站開發(fā)公司網(wǎng)頁
  • 建網(wǎng)站怎么避免備案百度推廣課程
  • 可以做哪些網(wǎng)站我想自己建立一個網(wǎng)站
  • 做網(wǎng)站怎么找優(yōu)質(zhì)客戶軟文寫作技巧有哪些
  • 公安內(nèi)網(wǎng)網(wǎng)站建設(shè)方案站群seo
  • 個人網(wǎng)站備案需要哪些資料網(wǎng)站單向外鏈推廣工具
  • 做網(wǎng)站 單頁數(shù)量網(wǎng)絡(luò)營銷的步驟
  • 網(wǎng)站建設(shè)費用細項廣州seo推廣公司
  • 中英網(wǎng)站的設(shè)計app開發(fā)費用一覽表
  • 上海網(wǎng)站建設(shè)百家號廣告投放推廣平臺
  • 網(wǎng)站建設(shè)石家莊今天國際新聞大事
  • 制作網(wǎng)站接單seo關(guān)鍵詞排名如何
  • 有哪些做買家秀的網(wǎng)站企業(yè)營銷平臺
  • 酒店網(wǎng)站策劃書網(wǎng)站打開
  • cms系統(tǒng)javaseo快速排名軟件app
  • 網(wǎng)新科技做網(wǎng)站怎么樣武漢百度seo排名
  • 專業(yè)營銷網(wǎng)站費用企業(yè)seo關(guān)鍵字優(yōu)化
  • 做房產(chǎn)的網(wǎng)站排名中國互聯(lián)網(wǎng)協(xié)會
  • 門戶網(wǎng)站內(nèi)容公眾號推廣費用一般多少
  • 華為云建站和阿里云建站區(qū)別搜外滴滴友鏈
  • 云虛擬主機怎么做網(wǎng)站太原模板建站定制網(wǎng)站
  • 淄博著名網(wǎng)站開發(fā)方法年度關(guān)鍵詞
  • 揭陽做網(wǎng)站哪個好搜索引擎優(yōu)化是指什么
  • 如何做網(wǎng)站編輯 沒技術(shù)媒體吧軟文平臺
  • php網(wǎng)站開發(fā)員工資邵陽做網(wǎng)站的公司
  • 網(wǎng)站建設(shè)教程app今日新聞事件
  • 沒有網(wǎng)站怎么做淘寶客seo優(yōu)化技術(shù)培訓