【Zinx应用-MMO游戏案例-(5)构建项目及用户上线】Golang轻量级并发服务器框架

Zinx源代码

github
https://github.com/aceld/zinx
gitee码云
https://gitee.com/Aceld/zinx


在线开发教程

【B站】
zinx视频教程-Golang轻量级TCP服务器框架-适合自学者

【YouTube】
zinx开发YouTube中国版

微信端文档

技术资源分享.jpg

【Zinx教程目录】
完整教程电子版(在线高清)-下载
Zinx框架视频教程(框架篇)(完整版下载)链接在下面正文
Zinx框架视频教程(应用篇)(完整版下载)链接在下面正文
Zinx开发API文档
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
Zinx第七章-读写分离模型
Zinx第八章-消息队列及多任务
Zinx第九章-链接管理
Zinx第十章-连接属性设置


【Zinx应用案例-MMO多人在线游戏】
(1)案例介绍
(2)AOI兴趣点算法
(3)数据传输协议protocol buffer
(4)Proto3协议定义
(5)构建项目及用户上线
(6)世界聊天
(7)上线位置信息同步
(8)移动位置与AOI广播
(9)玩家下线
(10)模拟客户端AI模块


六、构建项目与用户上线

​ 现在,我们应该基于Zinx框架来构建一个MMO的游戏服务器应用程序的项目了。

我们这里创建一个项目mmo_game,在项目内分别创建几个文件夹api,conf,core,game_client,pb

6.1 构建项目

api:主要是注册一些mmo业务的一些Router处理业务。

conf:存放mmo_game的一些配置文件,比如"zinx.json"。

core:存放一些核心算法,或者游戏控制等模块。

game_client:存放游戏客户端。

pb:存放一些protobuf的协议文件和go文件。

1、我们在mmo_game下,创建一个server.go作为我们main包,主要作为服务器程序的主入口。

mmo_game/server.go

package main

import (
    "zinx/znet"
)

func main() {
    //创建服务器句柄
    s := znet.NewServer()

    //启动服务
    s.Serve()
}

2、在conf文件添加zinx.conf

mmo_game/conf/zinx.conf

{
  "Name":"Zinx Game",
  "Host":"0.0.0.0",
  "TcpPort":8999,
  "MaxConn":3000,
  "WorkerPoolSize":10
}

3、在pb下创建msg.proto文件和build.sh编译指令脚本

mmo_game/pb/msg.proto

syntax="proto3";                //Proto协议
package pb;                     //当前包名
option csharp_namespace="Pb";   //给C#提供的选项

mmo_game/pb/build.sh

#!/bin/bash
protoc --go_out=. *.proto

当前我们的项目路径应该结构如下:

.
└── mmo_game
    ├── api
    ├── conf
    │   └── zinx.json
    ├── core
    │   ├── aoi.go
    │   ├── aoi_test.go
    │   ├── grid.go
    ├── game_client
    │   └── client.exe
    ├── pb
    │   ├── build.sh
    │   └── msg.proto
    ├── README.md
    └── server.go

6.2用户上线流程

​ 好了,那么我们第一次就要尝试将客户端的MMO游戏和移动端做一次上线测试了。

我们第一个测试用户上线的流程比较简单:


14-Zinx游戏-上线流程.jpeg

A)定义proto协议

我们从图中可以看到,上线的业务会涉及到MsgID:1 和 MsgID:200 两个消息,根据我们上一个章节的介绍,我们需要在msg.proto中定义出两个proto类型,并且声称对应的go代码.

mmo_game/pb/msg.proto

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

//玩家对象
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

/*
    发送消息给客户端,
    主要是将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

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

//告知客户端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

//广播玩家自己的出生地点
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)测试用户上线业务

$cd mmo_game/
$go run server.go

启动服务端程序。

​ 然后再windows终端打开client.exe

注意,要确保windows和启动服务器的Linux端要能够ping通,为了方便测试,建议将Linux的防火墙设置为关闭状态,或者要确保服务器的端口是开放的,以免耽误调试

15-zinx游戏案例-客户端登录.png

在此处输入服务器的IP地址,和服务器zinx.json配置的端口号。然后点击Connect。
16-zinx游戏案例-上线登录成功.png

如果游戏界面顺利进入,并且已经显示为Player_1玩家ID,表示等录成功,并且我们在服务端也可以看到一些调试信息。操作WASD也可以进行玩家移动。如果没有显示玩家ID或者为TextView则为登录失败,我们需要再针对协议的匹配进行调试。



关于作者:

作者:Aceld(刘丹冰)
简书号:IT无崖子

mail: danbing.at@gmail.com
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld

原创声明:未经作者允许请勿转载, 如果转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267