网站怎么申请,免费商城源码,单位建立一个官网多少钱,天津建设工程信息网专家库官网从0到1开发go-tcp框架【实战片— — 开发MMO】 MMO#xff08;MassiveMultiplayerOnlineGame#xff09;#xff1a;大型多人在线游戏#xff08;多人在线网游#xff09; 1 AOI兴趣点的算法 游戏中的坐标模型#xff1a; 场景相关数值计算
● 场景大小#xff1a; 250…从0到1开发go-tcp框架【实战片— — 开发MMO】 MMOMassiveMultiplayerOnlineGame大型多人在线游戏多人在线网游 1 AOI兴趣点的算法 游戏中的坐标模型 场景相关数值计算
● 场景大小 250*250 w(x轴宽度) 250l(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轴坐标编号)
1.1 定义AOI格子Grid AOI格子 格子ID格子的左边界坐标格子的右边界坐标格子的上边界坐标格子的下边界坐标当前格子内玩家/物体成员的ID集合保护当前集合的锁 AOI格子应该有的方法 初始化当前格子给格子添加一个玩家从格子中删除一个玩家得到当前格子中所有的玩家调试使用打印出格子的基本信息 mmo_game_zinx/core/grid.go
package coreimport (fmtsync
)/*
一个AOI地图中的格子类型
*/
type Grid struct {//格子IDGID 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)
}1.2 AOI管理模块 AOIManager 初始化一个AOI管理区域模块得到每个格子在X轴方向的宽度通过横纵轴得到GID添加一个PlayerId到一个格子中移除一个格子中的playerId通过GID获取全部的PlayerID通过坐标将Player添加到一个格子中通过坐标把一个Player从一个格子中删除通过Player坐标得到当前Player周边九宫格内全部的PlayerIDs通过坐标获取对应玩家所在的GID通过横纵轴得到周围的九宫格根据GID获取GID周围的九宫格 获取思路先求x再求y。先根据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 {//区域的左边界坐标MinX int//区域的右界坐标MaxX int//X方向格子的数量CountsX int//区域的上边界坐标MinY int//区域的下边界坐标MaxY int//Y方向上格子的数量CountsY int//当前区域中有哪些格子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初始化区域的所有格子进行编号和初始化for y : 0; y countsY; y {for x : 0; x countsX; x {//计算格子的ID 根据xy编号//格子的编号id idy * countsX idxgid : y*countsX x//初始化gid格子aoiMgr.grids[gid] NewGrid(gid,aoiMgr.MinXx*aoiMgr.gridXWidth(),aoiMgr.MinX(x1)*aoiMgr.gridXWidth(),aoiMgr.MinYy*aoiMgr.gridYLength(),aoiMgr.MinY(y1)*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[gID1])}//将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[vm.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) {//根据横纵坐标得到当前坐标属于哪个格子IDgID : 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)
}
mmo_game_zinx/core/aoi_test.go
package coreimport (fmttesting
)func TestNewAOIManager(t *testing.T) {//初始化AOIManageraoiMgr : NewAOIManager(0, 250, 5, 0, 250, 5)//打印AOIManagerfmt.Println(aoiMgr)
}//根据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 数据传输协议的选择protobuf 常见的传输格式json、xml、protobuf json可读性比较强编解码比较耗时【web领域】xml基于标签【前端/网页】protobufGoogle开发的编解码很快、体积小、跨平台可读性不强传输过程中不是明文是二进制已经序列化完毕的【后端应用/微服务/服务器】 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-go2.2 profobuf语法及使用
①语法
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结构体数据 1. 编写.proto文件
2. 执行protoc编译出对应go代码 通过如下方式调用protocol编译器把 .proto 文件编译成代码 protoc --proto_pathIMPORT_PATH --go_outDST_DIR path/to/file.proto其中
–proto_path指定了 .proto 文件导包时的路径可以有多个如果忽略则默认当前目录。–go_out 指定了生成的go语言代码文件放入的文件夹允许使用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; //配置包依赖路径编译时protobuf 编译器会把 .proto 文件编译成 .pd.go 文件
3. 通过proto.Marshal进行序列化发数据
data, err : proto2.Marshal(person)4. 通过proto.UnMarshal进行反序列话收数据
err proto2.Unmarshal(data, newPerson)③测试传输 在myDemo/protobuf文件夹下编写main.go进行测试 main.go
package mainimport (fmtproto2 google.golang.org/protobuf/protopb 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)
}3 游戏相关业务
3.1 业务消息格式定义 MsgID1同步玩家本地登录ID MsgID:1 同步玩家本地登录的ID用来标识玩家玩家登录之后由Server端主动生成玩家ID发送给客户端发起者ServerPid玩家ID 对应proto
message SyncPid{int32 Pid1;
}MsgID2世界聊天 ● 同步玩家本次登录的ID(用来标识玩家), 玩家登陆之后由Server端主动生成玩家ID发送给客户端 ● 发起者 Client ● Content: 聊天信息 message Talk{string Content1;
}MsgID3移动信息 ● 移动的坐标数据 ● 发起者 Client ● P: Position类型地图的左边点 message Position{float X1;float Y2;float Z3;float V4;
}MsgID200广播聊天、坐标、动作 ● 广播消息 ● 发起者 Server ● Tp: 1 世界聊天, 2 坐标, 3 动作, 4 移动之后坐标信息更新 ● Pid: 玩家ID message BroadCast{int32 Pid1;int32 Tp2;//oneof表示只能选三个中的一个oneof Data {string Content3;Position P4;int32 ActionData5;}
}MsgID201 ● 广播消息 掉线/aoi消失在视野 ● 发起者 Server ● Pid: 玩家ID message SyncPid{int32 Pid1;
}MsgID202同步位置信息 ● 同步周围的人位置信息(包括自己) ● 发起者 Server ● ps: Player 集合,需要同步的玩家 message SyncPlayers{repeated Player ps1;
}message Player{int32 Pid1;Position P2;
}3.2 项目模块搭建 mmo_game_zinx apis:存放基本用户的自定义路由业务一个msgId对应一个业务conf存放zinx.json自定义框架的配置文件pbprotobuf相关文件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上线的初始位置同步给客户端 1. mmo_game_zinx/pb/msg.proto 定义proto文件消息类型 syntax proto3; //Proto协议
package pb; //当前包名
option csharp_namespace Pb; //给C#提供的选项[因为我们的游戏画面采用unity3D基于C#的]
option go_package ./;pb; //配置包依赖路径//同步客户端玩家ID
message SyncPid{int32 Pid1;
}//玩家位置
message Position{float X1;float Y2;float Z3;float V4;
}//玩家广播数据
message BroadCast{int32 Pid1;int32 Tp2;//1 世界聊天, 2 坐标, 3 动作, 4 移动之后坐标信息更新oneof Data {string Content3;Position P4;int32 ActionData5;}
}为了方便后续更新proto文件我们这里直接编写一个脚本 mmo_game_zinx/pb/build.sh:
#!/bin/bash
protoc --go_out. *.proto2. mmo_game_zinx/core/player.go
package coreimport (fmtgoogle.golang.org/protobuf/protomath/randpb myTest/mmo_game_zinx/pbmyTest/zinx/zifacesync
)// 玩家
type Player struct {Pid int32 //玩家IDConn 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 : PidGenPidGenIdLock.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() {//组件MsgID0的proto数据proto_msg : pb.SyncPid{Pid: p.Pid,}//将消息发送给客户端p.SendMsg(1, proto_msg)
}// 广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {//组建MsgID200 的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)
}3. mmo_game_zinx/main.go
package mainimport (fmtmyTest/mmo_game_zinx/coremyTest/zinx/zifacemyTest/zinx/znet
)// 当前客户端建立连接之后的hook函数
func OnConnectionAdd(conn ziface.IConnection) {//创建一个player对象player : core.NewPlayer(conn)//给客户端发送MsgID1的消息同步当前的playerID给客户端player.SyncPid()//给客户端发送MsgID200的消息同步当前Player的初始位置给客户端player.BroadCastStartPosition()fmt.Println(Player pid , player.Pid, is arrived )
}func main() {//创建服务句柄s : znet.NewServer(MMO Game Zinx)s.SetOnConnStart(OnConnectionAdd)s.Serve()
}测试效果
启动服务端启动客户端client.exe 连续启动多个查看效果 服务端控制台打印
②世界聊天 proto3聊天协议的定义聊天业务的实现 解析聊天的proto协议将聊天数据广播给全部在线玩家-创建一个世界管理模块 - 初始化管理模块 - 添加一个玩家 - 删除一个玩家 - 通过玩家ID查询Player对象 - 获取全部的在线玩家 1. mmo_game_zinx/pb/msg.proto 在之前的基础上在末尾追加 message Talk{string Content1;
}执行build.sh脚本重新编译
2. mmo_game_zinx/apis/world_chat.go
package apisimport (fmtgoogle.golang.org/protobuf/protomyTest/mmo_game_zinx/corepb myTest/mmo_game_zinx/pbmyTest/zinx/zifacemyTest/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)
}3. mmo_game_zinx/main.go
package mainimport (fmtmyTest/mmo_game_zinx/apismyTest/mmo_game_zinx/coremyTest/zinx/zifacemyTest/zinx/znet
)// 当前客户端建立连接之后的hook函数
func OnConnectionAdd(conn ziface.IConnection) {//创建一个player对象player : core.NewPlayer(conn)//给客户端发送MsgID1的消息同步当前的playerID给客户端player.SyncPid()//给客户端发送MsgID200的消息同步当前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()
}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{//创建世界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] 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 (fmtgoogle.golang.org/protobuf/protomath/randpb myTest/mmo_game_zinx/pbmyTest/zinx/zifacesync
)// 玩家
type Player struct {Pid int32 //玩家IDConn 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 : PidGenPidGenIdLock.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() {//组件MsgID0的proto数据proto_msg : pb.SyncPid{Pid: p.Pid,}//将消息发送给客户端p.SendMsg(1, proto_msg)
}// 广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {//组建MsgID200 的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 组建MsgID200 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)}
}测试效果 启动服务