赞
踩
MMO(MassiveMultiplayerOnlineGame):大型多人在线游戏(多人在线网游)
游戏中的坐标模型:
场景相关数值计算
● 场景大小: 250*250 , w(x轴宽度) = 250,l(y轴长度) = 250
● x轴格子数量:nx = 5
● y轴格子数量: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轴坐标编号)
AOI格子:
- 格子ID
- 格子的左边界坐标
- 格子的右边界坐标
- 格子的上边界坐标
- 格子的下边界坐标
- 当前格子内玩家/物体成员的ID集合
- 保护当前集合的锁
AOI格子应该有的方法:
- 初始化当前格子
- 给格子添加一个玩家
- 从格子中删除一个玩家
- 得到当前格子中所有的玩家
- 调试使用:打印出格子的基本信息
package core import ( "fmt" "sync" ) /* 一个AOI地图中的格子类型 */ type Grid struct { //格子ID GID int //格子的左边界坐标 MinX int //格子的右边界坐标 MaxX int //格子的上边界坐标 MinY int //格子的下边界坐标 MaxY int //当前格子内玩家或物体成员对的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) }
AOIManager:
- 初始化一个AOI管理区域模块
- 得到每个格子在X轴方向的宽度
- 通过横纵轴得到GID
- 添加一个PlayerId到一个格子中
- 移除一个格子中的playerId
- 通过GID获取全部的PlayerID
- 通过坐标将Player添加到一个格子中
- 通过坐标把一个Player从一个格子中删除
- 通过Player坐标得到当前Player周边九宫格内全部的PlayerIDs
- 通过坐标获取对应玩家所在的GID
- 通过横纵轴得到周围的九宫格
- 根据GID获取GID周围的九宫格
![]()
- 获取思路:先求x,再求y。先根据GID判断该GID左边和右边是否有格子 。然后将X轴上的格子添加到集合中,再遍历集合判断集合中的上下是否有格子。
package core import "fmt" // 定义AOI地图大小 const ( AOI_MIN_X int = 85 AOI_MAX_X int = 410 AOI_CNTS_X int = 10 AOI_MIN_Y int = 75 AOI_MAX_Y int = 400 AOI_CNTS_Y int = 20 ) type AOIManager struct { //区域的左边界坐标 MinX int //区域的右界坐标 MaxX int //X方向格子的数量 CountsX int //区域的上边界坐标 MinY int //区域的下边界坐标 MaxY int //Y方向上格子的数量 CountsY int //当前区域中有哪些格子map-key=格子的ID grids 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初始化区域的所有格子进行编号和初始化 for y := 0; y < countsY; y++ { for x := 0; x < countsX; x++ { //计算格子的ID 根据x,y编号 //格子的编号:id = idy * countsX + idx gid := 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 } // 根据格子的gID得到当前周边的九宫格信息 func (m *AOIManager) GetSurroundGridsByGid(gID int) (grids []*Grid) { //判断gID是否存在 if _, ok := m.grids[gID]; !ok { return } //将当前gid添加到九宫格中 grids = append(grids, m.grids[gID]) //根据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 } // 通过横纵坐标获取对应的格子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 } // 通过横纵坐标得到周边九宫格内的全部PlayerIDs func (m *AOIManager) GetPIDsByPos(x, y float32) (playerIDs []int) { //根据横纵坐标得到当前坐标属于哪个格子ID gID := m.GetGIDByPos(x, y) //根据格子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) }
package core import ( "fmt" "testing" ) func TestNewAOIManager(t *testing.T) { //初始化AOIManager aoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5) //打印AOIManager fmt.Println(aoiMgr) } //根据GID获取九宫格 func TestAOIManager_GetSurroundGridsByGid(t *testing.T) { //初始化AOIManager aoiMgr := 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) } }
常见的传输格式:json、xml、protobuf
- json:可读性比较强;编解码比较耗时【web领域】
- xml:基于标签【前端/网页】
- protobuf(Google开发的):编解码很快、体积小、跨平台;可读性不强,传输过程中不是明文,是二进制(已经序列化完毕的)【后端应用/微服务/服务器】
我这里以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
person.proto
syntax = "proto3"; //指定版本信息,不指定会报错 package pb; //后期生成go文件的包名 option go_package = "./;proto"; //配置包依赖路径 //message为关键字,作用为定义一种消息类型 message Person { string name = 1; //姓名 int32 age = 2; //年龄 repeated string emails = 3; //电子邮件(repeated表示字段允许重复)【类比go中的切片】 repeated PhoneNumber phones = 4; //手机号 } //enum为关键字,作用为定义一种枚举类型 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } //message为关键字,作用为定义一种消息类型可以被另外的消息类型嵌套使用 message PhoneNumber { string number = 1; PhoneType type = 2; }
- 定义一个go的与protobuf对应的结构体
- proto.Marshal进行编码序列化,得到二进制数据data
- 将data进行传输,或者发送给对方
- 对方收到data数据,将data通过proto.UnMarshal得到person结构体数据
通过如下方式调用protocol编译器,把 .proto 文件编译成代码
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
其中:
option go_package = "./;proto"; //配置包依赖路径
data, err := proto2.Marshal(person)
err = proto2.Unmarshal(data, &newPerson)
在myDemo/protobuf文件夹下编写main.go进行测试
main.go
package main import ( "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数组 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("传输的数据:", person) fmt.Println("接收到的数据:", &newPerson) }
- MsgID:1
- 同步玩家本地登录的ID(用来标识玩家),玩家登录之后,由Server端主动生成玩家ID发送给客户端
- 发起者:Server
- Pid:玩家ID
对应proto:
message SyncPid{
int32 Pid=1;
}
● 同步玩家本次登录的ID(用来标识玩家), 玩家登陆之后,由Server端主动生成玩家ID发送给客户端
● 发起者: Client
● Content: 聊天信息
message Talk{
string Content=1;
}
● 移动的坐标数据
● 发起者: Client
● P: Position类型,地图的左边点
message Position{
float X=1;
float Y=2;
float Z=3;
float V=4;
}
● 广播消息
● 发起者: 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;
}
}
● 广播消息 掉线/aoi消失在视野
● 发起者: Server
● Pid: 玩家ID
message SyncPid{
int32 Pid=1;
}
● 同步周围的人位置信息(包括自己)
● 发起者: Server
● ps: Player 集合,需要同步的玩家
message SyncPlayers{
repeated Player ps=1;
}
message Player{
int32 Pid=1;
Position P=2;
}
mmo_game_zinx
- apis:存放基本用户的自定义路由业务,一个msgId对应一个业务
- conf:存放zinx.json(自定义框架的配置文件)
- pb:protobuf相关文件
- core:存放核心功能
- main.go:服务器的主入口
- game_client:unity客户端
最终项目结构
. └── 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
- 创建一个玩家的方法:
- 编写proto文件
- 定义玩家对象player.go
- 玩家可以和客户端通信的发送消息的方法:
- 将msg的proto格式进行序列化改成二进制
- 通过zinx框架提供的sendMsg将数据进行TLV格式的打包发包
- 实现上线业务功能:
- 给server注册一个创建连接之后的hook函数
- 给Player提供两个方法:将PlayerID同步给客户端、将Player上线的初始位置同步给客户端
定义proto文件(消息类型)
syntax = "proto3"; //Proto协议 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; } //玩家广播数据 message BroadCast{ int32 Pid=1; int32 Tp=2;//1 世界聊天, 2 坐标, 3 动作, 4 移动之后坐标信息更新 oneof Data { string Content=3; Position P=4; int32 ActionData=5; } }
为了方便后续更新proto文件,我们这里直接编写一个脚本
mmo_game_zinx/pb/build.sh:
#!/bin/bash
protoc --go_out=. *.proto
package core import ( "fmt" "google.golang.org/protobuf/proto" "math/rand" pb "myTest/mmo_game_zinx/pb" "myTest/zinx/ziface" "sync" ) // 玩家 type Player struct { Pid int32 //玩家ID Conn ziface.IConnection //当前玩家的连接(用于和客户端的连接) X float32 //平面的X坐标 Y float32 //高度 Z float32 //平面y坐标(注意:Z字段才是玩家的平面y坐标,因为unity的客户端已经定义好了) V float32 //旋转的0-360角度 } var PidGen int32 = 1 //用于生成玩家id var IdLock sync.Mutex //保护PidGen的锁 func NewPlayer(conn ziface.IConnection) *Player { IdLock.Lock() id := PidGen PidGen++ 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 } /* 提供一个发送给客户端消息的方法 主要是将pb的protobuf数据序列化后,再调用zinx的sendMsg方法 */ func (p *Player) SendMsg(msgId uint32, data proto.Message) { //将proto Message结构体序列化 转换成二进制 msg, err := proto.Marshal(data) if err != nil { fmt.Println("marshal msg err: ", err) return } //将二进制文件 通过zinx框架的sendMsg将数据发送给客户端 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,同步已经生成的玩家ID给客户端 func (p *Player) SyncPid() { //组件MsgID:0的proto数据 proto_msg := &pb.SyncPid{ Pid: p.Pid, } //将消息发送给客户端 p.SendMsg(1, proto_msg) } // 广播玩家自己的出生地点 func (p *Player) BroadCastStartPosition() { //组建MsgID:200 的proto数据 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, }, }, } //将消息发送给客户端 p.SendMsg(200, proto_msg) }
package main import ( "fmt" "myTest/mmo_game_zinx/core" "myTest/zinx/ziface" "myTest/zinx/znet" ) // 当前客户端建立连接之后的hook函数 func OnConnectionAdd(conn ziface.IConnection) { //创建一个player对象 player := core.NewPlayer(conn) //给客户端发送MsgID:1的消息,同步当前的playerID给客户端 player.SyncPid() //给客户端发送MsgID:200的消息,同步当前Player的初始位置给客户端 player.BroadCastStartPosition() fmt.Println("======>Player pid = ", player.Pid, " is arrived ====") } func main() { //创建服务句柄 s := znet.NewServer("MMO Game Zinx") s.SetOnConnStart(OnConnectionAdd) s.Serve() }
连续启动多个查看效果:
服务端控制台打印:
- proto3聊天协议的定义
- 聊天业务的实现:
- 解析聊天的proto协议
- 将聊天数据广播给全部在线玩家->创建一个世界管理模块
- 初始化管理模块
- 添加一个玩家
- 删除一个玩家
- 通过玩家ID查询Player对象
- 获取全部的在线玩家
在之前的基础上,在末尾追加:
message Talk{
string Content=1;
}
执行build.sh脚本重新编译
package apis import ( "fmt" "google.golang.org/protobuf/proto" "myTest/mmo_game_zinx/core" pb "myTest/mmo_game_zinx/pb" "myTest/zinx/ziface" "myTest/zinx/znet" ) // 世界聊天路由业务 type WorldChatApi struct { znet.BaseRouter } // 重写handler方法 func (wc *WorldChatApi) Handler(request ziface.IRequest) { //1 解析客户端传递进来的proto协议 proto_msg := &pb.Talk{} err := proto.Unmarshal(request.GetData(), proto_msg) if err != nil { fmt.Println("Talk Unmarshal err ", err) return } //2 当前的聊天数据 属于哪个玩家发送的 pid, err := request.GetConnection().GetProperty("pid") //3 根据pid得到对应的player对象 player := core.WorldMgrObj.GetPlayerByPid(pid.(int32)) //4 将这个消息广播给其他全部在线的用户 player.Talk(proto_msg.Content) }
package main import ( "fmt" "myTest/mmo_game_zinx/apis" "myTest/mmo_game_zinx/core" "myTest/zinx/ziface" "myTest/zinx/znet" ) // 当前客户端建立连接之后的hook函数 func OnConnectionAdd(conn ziface.IConnection) { //创建一个player对象 player := core.NewPlayer(conn) //给客户端发送MsgID:1的消息,同步当前的playerID给客户端 player.SyncPid() //给客户端发送MsgID:200的消息,同步当前Player的初始位置给客户端 player.BroadCastStartPosition() //将当前新上线的玩家添加到WorldManager中 core.WorldMgrObj.AddPlayer(player) //将playerId添加到连接属性中,方便后续广播知道是哪个玩家发送的消息 conn.SetProperty("pid", player.Pid) fmt.Println("======>Player pid = ", player.Pid, " is arrived ====") } func main() { //创建服务句柄 s := znet.NewServer("MMO Game Zinx") s.SetOnConnStart(OnConnectionAdd) //注册一些路由业务 s.AddRouter(2, &apis.WorldChatApi{}) s.Serve() }
package core import "sync" /* 当前游戏的世界总管理模块 */ type WorldManager struct { //AOIManager 当前世界地图AOI的管理模块 AoiMgr *AOIManager //当前全部在线的players集合 Players map[int32]*Player //保护Players集合的锁 pLock sync.RWMutex } // 提供一个对外的世界管理模块的句柄 var WorldMgrObj *WorldManager func init() { WorldMgrObj = &WorldManager{ //创建世界AOI地图规划 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] = player wm.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 }
package core import ( "fmt" "google.golang.org/protobuf/proto" "math/rand" pb "myTest/mmo_game_zinx/pb" "myTest/zinx/ziface" "sync" ) // 玩家 type Player struct { Pid int32 //玩家ID Conn ziface.IConnection //当前玩家的连接(用于和客户端的连接) X float32 //平面的X坐标 Y float32 //高度 Z float32 //平面y坐标(注意:Z字段才是玩家的平面y坐标,因为unity的客户端已经定义好了) V float32 //旋转的0-360角度 } var PidGen int32 = 1 //用于生成玩家id var IdLock sync.Mutex //保护PidGen的锁 func NewPlayer(conn ziface.IConnection) *Player { IdLock.Lock() id := PidGen PidGen++ 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 } /* 提供一个发送给客户端消息的方法 主要是将pb的protobuf数据序列化后,再调用zinx的sendMsg方法 */ func (p *Player) SendMsg(msgId uint32, data proto.Message) { //将proto Message结构体序列化 转换成二进制 msg, err := proto.Marshal(data) if err != nil { fmt.Println("marshal msg err: ", err) return } //将二进制文件 通过zinx框架的sendMsg将数据发送给客户端 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,同步已经生成的玩家ID给客户端 func (p *Player) SyncPid() { //组件MsgID:0的proto数据 proto_msg := &pb.SyncPid{ Pid: p.Pid, } //将消息发送给客户端 p.SendMsg(1, proto_msg) } // 广播玩家自己的出生地点 func (p *Player) BroadCastStartPosition() { //组建MsgID:200 的proto数据 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, }, }, } //将消息发送给客户端 p.SendMsg(200, proto_msg) } // 玩家广播世界聊天消息 func (p *Player) Talk(content string) { //1 组建MsgID:200 proto数据 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分别给对应的客户端发送消息 player.SendMsg(200, proto_msg) } }
启动服务
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。