完整代码:
仓库地址:https://gitee.com/Zifasdfa/zi-zinx
MMO(Massively Multiplayer Online):指的是一种游戏类型,可以容纳大量玩家同时在线,并且可以在游戏中进行大规模的多人互动。
- 在 MMO(Massively Multiplayer Online)游戏开发中,AOI(Area of Interest)是一种常用的空间管理技术,用于处理玩家和游戏世界中其他实体之间的交互。
AOI 是一个虚拟的区域,通常是一个矩形或圆形区域,用于表示一个玩家或一个实体感兴趣的区域。当一个玩家或实体进入某个玩家的 AOI 区域时,他们会被视为在同一区域内,可以相互感知和交互。AOI 技术可以有效地减少服务器处理的工作量,提高游戏的性能和可扩展性。
上线位置的信息同步(玩家上线广播)
- 定义proto协议
- 获取当前玩家周围的玩家有哪些
- 将当前玩家的位置信息通过MsgID:200 发送给周围的玩家(让其他玩家看到自己)
- 将周围的全部玩家位置信息发送给当前玩家的客户端(让自己看到其他的玩家)
这里涉及到了MsgID:202消息,我们应该在proto文件中,再添加两个消息
msg.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;
}
}
message Talk{
string Content=1;
}
//玩家信息
message Player{
int32 Pid=1;
Position P=2;
}
//同步玩家显示数据
message SyncPlayers{
repeated Player ps=1;
}
添加完信息后,执行build.sh脚本,更新protoc生成的go代码
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)
}
}
// 同步玩家上线的位置信息
func (p *Player) SyncSurrounding() {
//1 获取当前玩家周围的玩家有哪些(九宫格)
pids := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)
players := make([]*Player, 0, len(pids))
for _, pid := range pids {
players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
}
//2 将当前玩家的位置信息通过MsgID:200 发送给周围的玩家(让其他玩家看到自己)
//2.1 组件MsgID:200 proto数据
proto_msg := &pb.BroadCast{
Pid: p.Pid,
Tp: 2, //TP-2代表广播坐标
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
//2.2 周围全部的玩家都向各自的客户端发送200消息, proto_msg
for _, player := range players {
player.SendMsg(200, proto_msg)
}
//3 将周围全部玩家的位置信息发送给当前的MsgID:202 客户端(让自己看到其他玩家)
//3.1 组建MsgID:202 proto数据
//3.1.1 制作pb.Player slice
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)
}
//3.1.2 封装SyncPlayer protobuf数据
SyncPlayers_proto_msg := &pb.SyncPlayers{
Ps: players_proto_msg[:],
}
//3.2 将组建好的数据发送给当前玩家的客户端
p.SendMsg(202, SyncPlayers_proto_msg)
}
- 启动main.go(Server端)
- 启动game_client/client.exe(Client端,启动三个),查看是否能看到其他上线的用户
注册一个业务路由,针对处理MsgID:3
- 解析客户端传递进来的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)
}
// 玩家广播世界聊天消息
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)
}
}
// 同步玩家上线的位置信息
func (p *Player) SyncSurrounding() {
//1 获取当前玩家周围的玩家有哪些(九宫格)
pids := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)
players := make([]*Player, 0, len(pids))
for _, pid := range pids {
players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
}
//2 将当前玩家的位置信息通过MsgID:200 发送给周围的玩家(让其他玩家看到自己)
//2.1 组件MsgID:200 proto数据
proto_msg := &pb.BroadCast{
Pid: p.Pid,
Tp: 2, //TP-2代表广播坐标
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
//2.2 周围全部的玩家都向各自的客户端发送200消息, proto_msg
for _, player := range players {
player.SendMsg(200, proto_msg)
}
//3 将周围全部玩家的位置信息发送给当前的MsgID:202 客户端(让自己看到其他玩家)
//3.1 组建MsgID:202 proto数据
//3.1.1 制作pb.Player slice
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)
}
//3.1.2 封装SyncPlayer protobuf数据
SyncPlayers_proto_msg := &pb.SyncPlayers{
Ps: players_proto_msg[:],
}
//3.2 将组建好的数据发送给当前玩家的客户端
p.SendMsg(202, SyncPlayers_proto_msg)
}
// 广播当前玩家的位置移动信息
func (p *Player) UpdatePos(x, y, z, v float32) {
//更新当前玩家player的位置坐标
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, //类型4代表 移动之后的坐标信息(已经与unity的客户端规定好了)
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
//获取当前玩家的周边玩家的AOI九宫格之内的玩家
players := p.GetSurroundingPlayers()
//依次给每个玩家对应的客户端发送当前玩家位置更新的消息(让其他玩家看到当前玩家移动的状态)
for _, player := range players {
player.SendMsg(200, proto_msg)
}
}
// 获取当前玩家AOI九宫格之内的其他玩家
func (p *Player) GetSurroundingPlayers() []*Player {
//得到当前AOI九宫格内所有玩家的id
pids := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)
//将所有的pid对应的player放到Players切片中
players := make([]*Player, 0, len(pids))
for _, pid := range pids {
player := WorldMgrObj.GetPlayerByPid(int32(pid))
players = append(players, player)
}
return players
}
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 MoveApi struct {
znet.BaseRouter
}
func (m *MoveApi) Handler(request ziface.IRequest) {
//解析用户传进来的proto(客户端)
proto_msg := &pb.Position{}
err := proto.Unmarshal(request.GetData(), proto_msg)
if err != nil {
fmt.Println("Move: Position Unmarshal error ", err)
return
}
//得到当前发送位置的是哪个玩家
pid, err := request.GetConnection().GetProperty("pid")
if err != nil {
fmt.Println("get request property pid error, ", err)
return
}
fmt.Printf("Player pid=%d, move(%f,%f,%f,%f)\n", pid, proto_msg.X, proto_msg.Y, proto_msg.Z, proto_msg.V)
//给其他玩家进行当前玩家的位置信息广播
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
//广播并更新当前玩家的坐标
player.UpdatePos(proto_msg.X, proto_msg.Y, proto_msg.Z, proto_msg.V)
}
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)
//同步玩家位置信息(我能看到其他玩家)
player.SyncSurrounding()
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.AddRouter(3, &apis.MoveApi{})
s.Serve()
}
客户端操作:
操作client2查看移动效果和控制台打印,发现达到预期
玩家下线:在链接断开之前处理玩家下线业务
- 通过链接属性得到当前链接所绑定的pid
- 得到当前玩家周边的九宫格都有哪些玩家
- 给周围玩家广播MsgID:201消息
- 将当前玩家从世界管理器删除
- 将当前管家从AOI管理器中删除
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)
}
}
// 同步玩家上线的位置信息
func (p *Player) SyncSurrounding() {
//1 获取当前玩家周围的玩家有哪些(九宫格)
pids := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)
players := make([]*Player, 0, len(pids))
for _, pid := range pids {
players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
}
//2 将当前玩家的位置信息通过MsgID:200 发送给周围的玩家(让其他玩家看到自己)
//2.1 组件MsgID:200 proto数据
proto_msg := &pb.BroadCast{
Pid: p.Pid,
Tp: 2, //TP-2代表广播坐标
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
//2.2 周围全部的玩家都向各自的客户端发送200消息, proto_msg
for _, player := range players {
player.SendMsg(200, proto_msg)
}
//3 将周围全部玩家的位置信息发送给当前的MsgID:202 客户端(让自己看到其他玩家)
//3.1 组建MsgID:202 proto数据
//3.1.1 制作pb.Player slice
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)
}
//3.1.2 封装SyncPlayer protobuf数据
SyncPlayers_proto_msg := &pb.SyncPlayers{
Ps: players_proto_msg[:],
}
//3.2 将组建好的数据发送给当前玩家的客户端
p.SendMsg(202, SyncPlayers_proto_msg)
}
// 广播当前玩家的位置移动信息
func (p *Player) UpdatePos(x, y, z, v float32) {
//更新当前玩家player的位置坐标
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, //类型4代表 移动之后的坐标信息(已经与unity的客户端规定好了)
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
//获取当前玩家的周边玩家的AOI九宫格之内的玩家
players := p.GetSurroundingPlayers()
//依次给每个玩家对应的客户端发送当前玩家位置更新的消息(让其他玩家看到当前玩家移动的状态)
for _, player := range players {
player.SendMsg(200, proto_msg)
}
}
// 获取当前玩家AOI九宫格之内的其他玩家
func (p *Player) GetSurroundingPlayers() []*Player {
//得到当前AOI九宫格内所有玩家的id
pids := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)
//将所有的pid对应的player放到Players切片中
players := make([]*Player, 0, len(pids))
for _, pid := range pids {
player := WorldMgrObj.GetPlayerByPid(int32(pid))
players = append(players, player)
}
return players
}
// 玩家下线
func (p *Player) Offline() {
//得到当前玩家周边的九宫格内都有哪些玩家
players := p.GetSurroundingPlayers()
//给周围玩家广播MsgID:201消息
proto_msg := &pb.SyncPid{
Pid: p.Pid,
}
for _, player := range players {
player.SendMsg(201, proto_msg)
}
//从AOI格子中移除玩家
WorldMgrObj.AoiMgr.RemoveFromGridByPos(int(p.Pid), p.X, p.Z)
//从WorldMgr的map中移除玩家
WorldMgrObj.RemovePlayerByPid(p.Pid)
}
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)
//同步玩家位置信息(我能看到其他玩家)
player.SyncSurrounding()
fmt.Println("======>Player pid = ", player.Pid, " is arrived ====")
}
// 当客户端断开连接时候的hook函数
func OnConnectionLost(connection ziface.IConnection) {
//获取当前连接的pid属性
pid, _ := connection.GetProperty("pid")
//根据pid获取对应的玩家对象
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
//触发玩家下线业务
if pid != nil {
player.Offline()
}
fmt.Println("=====> Player ", pid, " offline=====")
}
func main() {
//创建服务句柄
s := znet.NewServer("MMO Game Zinx")
s.SetOnConnStart(OnConnectionAdd)
//注册一些路由业务
s.AddRouter(2, &apis.WorldChatApi{})
//保证玩家能实时移动
s.AddRouter(3, &apis.MoveApi{})
//保证玩家下线
s.SetOnConnStop(OnConnectionLost)
s.Serve()
}
此时关闭client1,查看效果:
后续:
大家可以根据自己需求进行二开:
- 添加日志模块
- 添加定时器
- Server与Server之间的通信-分布式
参考:https://www.yuque.com/aceld/npyr8s/xgi5xp