### 6.2用户上线流程
好了,那么我们第一次就要尝试将客户端的MMO游戏和移动端做一次上线测试了。
我们第一个测试用户上线的流程比较简单:
![](https://img.kancloud.cn/52/3a/523a745e09356d53711bcc4b02e8a769_1024x768.jpeg)A\)定义proto协议
我们从图中可以看到,上线的业务会涉及到MsgID:1 和 MsgID:200 两个消息,根据我们上一个章节的介绍,我们需要在msg.proto中定义出两个proto类型,并且声称对应的go代码.
> mmo\_game/pb/msg.proto
```go
syntax="proto3"; //Proto协议
package pb; //当前包名
option csharp_namespace="Pb"; //给C#提供的选项
//同步客户端玩家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;
oneof Data {
string Content=3;
Position P=4;
int32 ActionData=5;
}
}
```
执行build.sh生成对应的`msg.pb.go`代码.
#### B\)创建Player模块
1. 首先我们先创建一个Player玩家模块
> mmo\_game/core/player.go
```go
//玩家对象
type Player struct {
Pid int32 //玩家ID
Conn ziface.IConnection //当前玩家的连接
X float32 //平面x坐标
Y float32 //高度
Z float32 //平面y坐标 (注意不是Y)
V float32 //旋转0-360度
}
/*
Player ID 生成器
*/
var PidGen int32 = 1 //用来生成玩家ID的计数器
var IdLock sync.Mutex //保护PidGen的互斥机制
//创建一个玩家对象
func NewPlayer(conn ziface.IConnection) *Player {
//生成一个PID
IdLock.Lock()
id := PidGen
PidGen ++
IdLock.Unlock()
p := &Player{
Pid : id,
Conn:conn,
X:float32(160 + rand.Intn(10)),//随机在160坐标点 基于X轴偏移若干坐标
Y:0, //高度为0
Z:float32(134 + rand.Intn(17)), //随机在134坐标点 基于Y轴偏移若干坐标
V:0, //角度为0,尚未实现
}
return p
}
```
Plyaer类中有当前玩家的ID,和当前玩家与客户端绑定的conn,还有就是地图的坐标信,`NewPlayer()`提供初始化玩家方法。
1. 由于`Player`经常需要和客户端发送消息,那么我们可以给`Player`提供一个`SendMsg()`方法,供客户端发送消息
> mmo\_game/core/player.go
```go
/*
发送消息给客户端,
主要是将pb的protobuf数据序列化之后发送
*/
func (p *Player) SendMsg(msgId uint32, data proto.Message) {
fmt.Printf("before Marshal data = %+v\n", data)
//将proto Message结构体序列化
msg, err := proto.Marshal(data)
if err != nil {
fmt.Println("marshal msg err: ", err)
return
}
fmt.Printf("after Marshal data = %+v\n", msg)
if p.Conn == nil {
fmt.Println("connection in player is nil")
return
}
//调用Zinx框架的SendMsg发包
if err := p.Conn.SendMsg(msgId, msg); err != nil {
fmt.Println("Player SendMsg error !")
return
}
return
}
```
这里要注意的是,`SendMsg()`是将发送的数据,通过proto序列化,然后再调用`Zinx`框架的SendMsg方法发送给对方客户端.
#### C\)实现上线业务
我们先在Server的main入口,给链接绑定一个创建之后的hook方法,因为上线的时候是服务器自动回复客户端玩家ID和坐标,那么需要我们在连接创建完毕之后,自动触发,正好我们可以利用`Zinx`框架的`SetOnConnStart`方法.
> mmo\_game/server.go
```go
package main
import (
"fmt"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/core"
"zinx/znet"
)
//当客户端建立连接的时候的hook函数
func OnConnecionAdd(conn ziface.IConnection) {
//创建一个玩家
player := core.NewPlayer(conn)
//同步当前的PlayerID给客户端, 走MsgID:1 消息
player.SyncPid()
//同步当前玩家的初始化坐标信息给客户端,走MsgID:200消息
player.BroadCastStartPosition()
fmt.Println("=====> Player pidId = ", player.Pid, " arrived ====")
}
func main() {
//创建服务器句柄
s := znet.NewServer()
//注册客户端连接建立和丢失函数
s.SetOnConnStart(OnConnecionAdd)
//启动服务
s.Serve()
}
```
根据我们之前的流程分析,那么在客户端建立连接过来之后,Server要自动的回复给客户端一个玩家ID,同时也要讲当前玩家的坐标发送给客户端。所以我们这里面给Player定制了两个方法`Player.SyncPid()`和`Player.BroadCastStartPosition()`
`SyncPid()`则为发送`MsgID:1`的消息,将当前上线的用户ID发送给客户端
> mmo\_game/core/player.go
```go
//告知客户端pid,同步已经生成的玩家ID给客户端
func (p *Player) SyncPid() {
//组建MsgId0 proto数据
data := &pb.SyncPid{
Pid:p.Pid,
}
//发送数据给客户端
p.SendMsg(1, data)
}
```
`BroadCastStartPosition()`则为发送`MsgID:200`的广播位置消息,虽然现在没有其他用户,不是广播,但是当前玩家自己的坐标也是要告知玩家的。
> mmo\_game/core/player.go
```go
//广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {
msg := &pb.BroadCast{
Pid:p.Pid,
Tp:2,//TP2 代表广播坐标
Data: &pb.BroadCast_P{
&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
V:p.V,
},
},
}
p.SendMsg(200, msg)
}
```
#### D\)测试用户上线业务
```bash
$cd mmo_game/
$go run server.go
```
启动服务端程序。
然后再windows终端打开`client.exe`
> 注意,要确保windows和启动服务器的Linux端要能够ping通,为了方便测试,建议将Linux的防火墙设置为关闭状态,或者要确保服务器的端口是开放的,以免耽误调试
![](/assets/15-zinx游戏案例-客户端登录.png)
在此处输入服务器的IP地址,和服务器`zinx.json`配置的端口号。然后点击Connect。
![](/assets/16-zinx游戏案例-上线登录成功.png)如果游戏界面顺利进入,并且已经显示为`Player_1`玩家ID,表示等录成功,并且我们在服务端也可以看到一些调试信息。操作WASD也可以进行玩家移动。如果没有显示玩家ID或者为`TextView`则为登录失败,我们需要再针对协议的匹配进行调试。
- 一、引言
- 1、写在前面
- 2、初探Zinx架构
- 二、初识Zinx框架
- 1. Zinx-V0.1-基础Server
- 2.Zinx-V0.2-简单的连接封装与业务绑定
- 三、Zinx框架基础路由模块
- 3.1 IRequest 消息请求抽象类
- 3.2 IRouter 路由配置抽象类
- 3.3 Zinx-V0.3-集成简单路由功能
- 3.4 Zinx-V0.3代码实现
- 3.5 使用Zinx-V0.3完成应用程序
- 四、Zinx的全局配置
- 4.1 Zinx-V0.4增添全局配置代码实现
- 4.2 使用Zinx-V0.4完成应用程序
- 五、Zinx的消息封装
- 5.1 创建消息封装类型
- 5.2 消息的封包与拆包
- 5.3 Zinx-V0.5代码实现
- 5.4 使用Zinx-V0.5完成应用程序
- 六、Zinx的多路由模式
- 6.1 创建消息管理模块
- 6.2 Zinx-V0.6代码实现
- 6.3 使用Zinx-V0.6完成应用程序
- 七、Zinx的读写分离模型
- 7.1 Zinx-V0.7代码实现
- 7.2 使用Zinx-V0.7完成应用程序
- 八、Zinx的消息队列及多任务机制
- 8.1 创建消息队列
- 8.2 创建及启动Worker工作池
- 8.3 发送消息给消息队列
- 8.4 Zinx-V0.8代码实现
- 8.5 使用Zinx-V0.8完成应用程序
- 九、Zinx的链接管理
- 9.1 创建链接管理模块
- 9.2 链接管理模块集成到Zinx中
- 9.3 链接的带缓冲的发包方法
- 9.4 注册链接启动/停止自定义Hook方法功能
- 9.5 使用Zinx-V0.9完成应用程序
- 十、Zinx的连接属性设置
- 10.1 给链接添加链接配置接口
- 10.2 链接属性方法实现
- 10.3 链接属性Zinx-V0.10单元测试
- 基于Zinx的应用案例
- 一、应用案例介绍
- 二、服务器应用基础协议
- 三、MMO多人在线游戏AOI算法
- 3.1 网络法实现AOI算法
- 3.2 实现AOI格子结构
- 3.3 实现AOI管理模块
- 3.4 求出九宫格
- 3.5 AOI格子添加删除操作
- 3.6 AOI模块单元测试
- 四、数据传输协议protocol buffer
- 4.1 简介
- 4.2 数据交换格式
- 4.3 protobuf环境安装
- 4.4 protobuf语法
- 4.5 编译protobuf
- 4.6 利用protobuf生成的类来编码
- 五、MMO游戏的Proto3协议
- 六、构建项目与用户上线
- 6.1 构建项目
- 6.2用户上线流程
- 七、世界聊天系统实现
- 7.1 世界管理模块
- 7.2 世界聊天系统实现
- 八、上线位置信息同步
- 九、移动位置与AOI广播(未跨越格子)
- 十、玩家下线
- 十一、移动与AOI广播(跨越格子)