Zinx下MMO游戏具体实现-03世界聊天、上线的位置信息同步、移动位置信息广播、玩家下线
1.世界聊天
1.1.在msg.proto中添加一个message Talk
//聊天数据(client 发送给 server)
message Talk{
string Content=1;
}
运行build.go生成go文件
1.2.在创建player的时候,给conn配置一个链接属性,作用是从conn中得到pid从而得到player对象
//当前客户端建立链接之后触发Hook函数
func OnConnectionAdd(conn ziface.IConnection) {
......
//给conn添加一个属性 pid属性
conn.SetProperty("pid", p.Pid)
......
}
1.3.给player添加广播消息的方法
func (p *Player) SendTalkMsgToAll(content string) {
/*
message BroadCast{
int32 Pid=1;
int32 Tp=2;
oneof Data {
string Content=3;
Position P=4;
int32 ActionData=5;
}
}
*/
//定义一个广播的proto消息数据类型
proto_msg := &pb.BroadCast{
Pid:p.Pid,
Tp:1,
Data: &pb.BroadCast_Content{
Content:content,
},
}
//获取全部的在线玩家有哪些
players := WorldMgrObj.GetAllPlayers()
//向全部的玩家进行广播 proto_msg 数据
for _, player := range players {
player.SendMsg(200, proto_msg)
}
}
1.4.在main中添加一个路由,放在apis文件夹中,改变Handle方法
在Handle中将客户端发来的message进行解析并广播
func main() {
......
//针对MsgID2 建立路由业务
s.AddRouter(2, &apis.WorldChat{})
......
}
type WorldChat struct {
net.BaseRouter
}
func (wc *WorldChat) Handle(request ziface.IRequest) {
//1 解析客户端传递进来的protobuf数据
proto_msg := &pb.Talk{}
if err := proto.Unmarshal(request.GetMsg().GetMsgData(), proto_msg);err != nil {
fmt.Println("Talk message unmarshal error ", err)
return
}
//通过获取链接属性,得到当前的玩家ID
pid, err := request.GetConnection().GetProperty("pid")
if err != nil {
fmt.Println("get Pid error ", err)
return
}
//通过pid 来得到对应的player对象
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
// 当前的聊天数据广播给全部的在线玩家
//当前玩家的windows客户端发送过来的消息
player.SendTalkMsgToAll(proto_msg.GetContent())
}
2.上线位置信息同步
2.1.添加一个message SyncPlayers用于同步周围人的位置消息
//告知当前玩家 周边都有哪些玩家的位置信息
message SyncPlayers{
repeated Player ps=1;
}
//其中一个玩家的信息
message Player{
int32 Pid=1;
Position P=2;
}
2.2.在player中添加同步位置消息的方法
//将自己的消息同步给周边的玩家
func (p *Player) SyncSurrounding() {
//获取当前玩家的周边九宫格的玩家有哪些?
players := p.GetSurroundingPlayers()
//构建一个广播消息200, 循环全部players 分别给player对应的客户端发送200消息(让其他玩家看见当前玩家)
proto_msg := &pb.BroadCast{
Pid:p.Pid,
Tp:2,
Data: &pb.BroadCast_P{
P:&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
V:p.V,
},
},
}
//将当前玩家id和位置消息发送给周边玩家(发送多次)
for _, player := range players {
player.SendMsg(200, proto_msg)
}
//将其他玩家告诉当前玩家 (让当前玩家能够看见周边玩家的坐标)
//构建一个202消息 players的信息 告知当前玩家 p.send(202, ... )
//得到全部周边玩家的player集合message Player
players_proto_msg := make([]*pb.Player, 0, len(players))
for _, player := range players {
//制作一个message Player 消息
p := &pb.Player{
Pid:player.Pid,
P:&pb.Position{
X:player.X,
Y:player.Y,
Z:player.Z,
V:player.V,
},
}
players_proto_msg = append(players_proto_msg, p)
}
//创建一个 Message SyncPlayers
syncPlayers_proto_msg := &pb.SyncPlayers{
Ps: players_proto_msg[:],
}
//将当前的周边的全部的玩家信息 发送给当前的客户端
p.SendMsg(202,syncPlayers_proto_msg)
}
2.3.在建立连接之后(建立链接后的Hook函数)调用同步位置消息的方法
//当前客户端建立链接之后触发Hook函数
func OnConnectionAdd(conn ziface.IConnection) {
......
//同步周边玩家,告知他们当前玩家已经上线,广播当前的玩家的位置信息
p.SyncSurrounding()
......
}
3.移动位置信息的广播
3.1.注册一个针对MsgID为3的路由
func main() {
s := net.NewServer("MMO Game Server")
......
s.AddRouter(3, &apis.Move{})
......
}
3.2.解析客户端传过来的message
//业务更新坐标 路由业务
type Move struct {
net.BaseRouter
}
func(m *Move) Handle(request ziface.IRequest) {
//解析客户端发送过来的proto协议 msgID:3
proto_msg := &pb.Position{}
proto.Unmarshal(request.GetMsg().GetMsgData(), proto_msg)
//通过链接属性 得到当前玩家的ID
pid , _ := request.GetConnection().GetProperty("pid")
fmt.Println("player id = ",pid.(int32), " move --> ", proto_msg.X, ", ", proto_msg.Z, ", ", proto_msg.V)
//通过pid 得到当前的玩家对象
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
//玩家对象方法(将当前的新坐标位置 发送给全部的周边玩家)
player.UpdatePosition(proto_msg.X, proto_msg.Y, proto_msg.Z,proto_msg.V)
}
3.3.将最新坐标更新到当前玩家(实现player的UpdatePosition方法),并进行广播
//更新广播当前玩家的最新位置
func (p *Player) UpdatePosition(x, y, z, v float32) {
//需要将最新的坐标 更新给当前玩家
p.X = x
p.Y = y
p.Z = z
p.V = v
//组建广播proto协议 MSGID:200, Tp-4
proto_msg := &pb.BroadCast{
Pid:p.Pid,
Tp:4, //更新坐标
Data:&pb.BroadCast_P{
P:&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
V:p.V,
},
},
}
//获取当前玩家周边的AOI九宫格之内的玩家 player
players := p.GetSurroundingPlayers()
//依次调用Player对象 Send方法将200消息发过去
for _, player := range players {
player.SendMsg(200, proto_msg) //每个玩家都会给格子的 client客户端发送200消息
}
}
4.玩家下线
4.1.注册一个Hook函数,在链接断开之前执行,进行玩家下线通知
func OnConnectionLost(conn ziface.IConnection) {
//客户端已经关闭
//得到当前下线的是哪个玩家
pid, _ := conn.GetProperty("pid")
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
//玩家的下线业务(发送消息)
player.OffLine()
}
func main() {
s := net.NewServer("MMO Game Server")
......
s.AddOnConnStop(OnConnectionLost)
......
}
4.2.在player中添加一个处理玩家下线的方法
func (p *Player ) OffLine() {
// 得到当前玩家的周边的玩家有哪些 players
players := p.GetSurroundingPlayers()
//制作一个消息MSgID:201
proto_msg := &pb.SyncPid{
Pid:p.Pid,
}
// 给周边的玩家广播一个消息 MsgID:201
for _, player := range players {
player.SendMsg(201, proto_msg)//客户端就会将当前的玩家从视野中删除
}
// 将该下线的玩家 从世界管理器移除
WorldMgrObj.RemovePlayerByPid(p.Pid)
}