Zinx源代碼
github
https://github.com/aceld/zinx
gitee碼云
https://gitee.com/Aceld/zinx
在線開(kāi)發(fā)教程
【B站】
zinx視頻教程-Golang輕量級(jí)TCP服務(wù)器框架-適合自學(xué)者
【YouTube】
zinx開(kāi)發(fā)YouTube中國(guó)版
微信端文檔
【Zinx教程目錄】
完整教程電子版(在線高清)-下載
Zinx框架視頻教程(框架篇)(完整版下載)鏈接在下面正文
Zinx框架視頻教程(應(yīng)用篇)(完整版下載)鏈接在下面正文
Zinx開(kāi)發(fā)API文檔
Zinx第一章-引言
Zinx第二章-初識(shí)Zinx框架
Zinx第三章-基礎(chǔ)路由模塊
Zinx第四章-全局配置
Zinx第五章-消息封裝
Zinx第六章-多路由模式
Zinx第七章-讀寫(xiě)分離模型
Zinx第八章-消息隊(duì)列及多任務(wù)
Zinx第九章-鏈接管理
Zinx第十章-連接屬性設(shè)置
【Zinx應(yīng)用案例-MMO多人在線游戲】
(1)案例介紹
(2)AOI興趣點(diǎn)算法
(3)數(shù)據(jù)傳輸協(xié)議protocol buffer
(4)Proto3協(xié)議定義
(5)構(gòu)建項(xiàng)目及用戶上線
(6)世界聊天
(7)上線位置信息同步
(8)移動(dòng)位置與AOI廣播
(9)玩家下線
(10)模擬客戶端AI模塊
六蓝晒、構(gòu)建項(xiàng)目與用戶上線
? 現(xiàn)在辆床,我們應(yīng)該基于Zinx框架來(lái)構(gòu)建一個(gè)MMO的游戲服務(wù)器應(yīng)用程序的項(xiàng)目了。
我們這里創(chuàng)建一個(gè)項(xiàng)目mmo_game
,在項(xiàng)目?jī)?nèi)分別創(chuàng)建幾個(gè)文件夾api
,conf
,core
,game_client
,pb
等
6.1 構(gòu)建項(xiàng)目
api
:主要是注冊(cè)一些mmo業(yè)務(wù)的一些Router處理業(yè)務(wù)死宣。
conf
:存放mmo_game的一些配置文件,比如"zinx.json"奸笤。
core
:存放一些核心算法惋啃,或者游戲控制等模塊。
game_client
:存放游戲客戶端监右。
pb
:存放一些protobuf的協(xié)議文件和go文件边灭。
1、我們?cè)?code>mmo_game下健盒,創(chuàng)建一個(gè)server.go
作為我們main包绒瘦,主要作為服務(wù)器程序的主入口。
mmo_game/server.go
package main
import (
"zinx/znet"
)
func main() {
//創(chuàng)建服務(wù)器句柄
s := znet.NewServer()
//啟動(dòng)服務(wù)
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
下創(chuàng)建msg.proto文件和build.sh編譯指令腳本
mmo_game/pb/msg.proto
syntax="proto3"; //Proto協(xié)議
package pb; //當(dāng)前包名
option csharp_namespace="Pb"; //給C#提供的選項(xiàng)
mmo_game/pb/build.sh
#!/bin/bash
protoc --go_out=. *.proto
當(dāng)前我們的項(xiàng)目路徑應(yīng)該結(jié)構(gòu)如下:
.
└── 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游戲和移動(dòng)端做一次上線測(cè)試了父虑。
我們第一個(gè)測(cè)試用戶上線的流程比較簡(jiǎn)單:
A)定義proto協(xié)議
我們從圖中可以看到该酗,上線的業(yè)務(wù)會(huì)涉及到MsgID:1 和 MsgID:200 兩個(gè)消息,根據(jù)我們上一個(gè)章節(jié)的介紹士嚎,我們需要在msg.proto中定義出兩個(gè)proto類(lèi)型呜魄,并且聲稱對(duì)應(yīng)的go代碼.
mmo_game/pb/msg.proto
syntax="proto3"; //Proto協(xié)議
package pb; //當(dāng)前包名
option csharp_namespace="Pb"; //給C#提供的選項(xiàng)
//同步客戶端玩家ID
message SyncPid{
int32 Pid=1;
}
//玩家位置
message Position{
float X=1;
float Y=2;
float Z=3;
float V=4;
}
//玩家廣播數(shù)據(jù)
message BroadCast{
int32 Pid=1;
int32 Tp=2;
oneof Data {
string Content=3;
Position P=4;
int32 ActionData=5;
}
}
執(zhí)行build.sh生成對(duì)應(yīng)的msg.pb.go
代碼.
B)創(chuàng)建Player模塊
- 首先我們先創(chuàng)建一個(gè)Player玩家模塊
mmo_game/core/player.go
//玩家對(duì)象
type Player struct {
Pid int32 //玩家ID
Conn ziface.IConnection //當(dāng)前玩家的連接
X float32 //平面x坐標(biāo)
Y float32 //高度
Z float32 //平面y坐標(biāo) (注意不是Y)
V float32 //旋轉(zhuǎn)0-360度
}
/*
Player ID 生成器
*/
var PidGen int32 = 1 //用來(lái)生成玩家ID的計(jì)數(shù)器
var IdLock sync.Mutex //保護(hù)PidGen的互斥機(jī)制
//創(chuàng)建一個(gè)玩家對(duì)象
func NewPlayer(conn ziface.IConnection) *Player {
//生成一個(gè)PID
IdLock.Lock()
id := PidGen
PidGen ++
IdLock.Unlock()
p := &Player{
Pid : id,
Conn:conn,
X:float32(160 + rand.Intn(10)),//隨機(jī)在160坐標(biāo)點(diǎn) 基于X軸偏移若干坐標(biāo)
Y:0, //高度為0
Z:float32(134 + rand.Intn(17)), //隨機(jī)在134坐標(biāo)點(diǎn) 基于Y軸偏移若干坐標(biāo)
V:0, //角度為0,尚未實(shí)現(xiàn)
}
return p
}
Plyaer類(lèi)中有當(dāng)前玩家的ID莱衩,和當(dāng)前玩家與客戶端綁定的conn爵嗅,還有就是地圖的坐標(biāo)信,NewPlayer()
提供初始化玩家方法。
- 由于
Player
經(jīng)常需要和客戶端發(fā)送消息膳殷,那么我們可以給Player
提供一個(gè)SendMsg()
方法操骡,供客戶端發(fā)送消息
mmo_game/core/player.go
/*
發(fā)送消息給客戶端九火,
主要是將pb的protobuf數(shù)據(jù)序列化之后發(fā)送
*/
func (p *Player) SendMsg(msgId uint32, data proto.Message) {
fmt.Printf("before Marshal data = %+v\n", data)
//將proto Message結(jié)構(gòu)體序列化
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
}
//調(diào)用Zinx框架的SendMsg發(fā)包
if err := p.Conn.SendMsg(msgId, msg); err != nil {
fmt.Println("Player SendMsg error !")
return
}
return
}
這里要注意的是,SendMsg()
是將發(fā)送的數(shù)據(jù)册招,通過(guò)proto序列化岔激,然后再調(diào)用Zinx
框架的SendMsg方法發(fā)送給對(duì)方客戶端.
C)實(shí)現(xiàn)上線業(yè)務(wù)
我們先在Server的main入口,給鏈接綁定一個(gè)創(chuàng)建之后的hook方法是掰,因?yàn)樯暇€的時(shí)候是服務(wù)器自動(dòng)回復(fù)客戶端玩家ID和坐標(biāo)虑鼎,那么需要我們?cè)谶B接創(chuàng)建完畢之后,自動(dòng)觸發(fā)键痛,正好我們可以利用Zinx
框架的SetOnConnStart
方法.
mmo_game/server.go
package main
import (
"fmt"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/core"
"zinx/znet"
)
//當(dāng)客戶端建立連接的時(shí)候的hook函數(shù)
func OnConnecionAdd(conn ziface.IConnection) {
//創(chuàng)建一個(gè)玩家
player := core.NewPlayer(conn)
//同步當(dāng)前的PlayerID給客戶端炫彩, 走M(jìn)sgID:1 消息
player.SyncPid()
//同步當(dāng)前玩家的初始化坐標(biāo)信息給客戶端,走M(jìn)sgID:200消息
player.BroadCastStartPosition()
fmt.Println("=====> Player pidId = ", player.Pid, " arrived ====")
}
func main() {
//創(chuàng)建服務(wù)器句柄
s := znet.NewServer()
//注冊(cè)客戶端連接建立和丟失函數(shù)
s.SetOnConnStart(OnConnecionAdd)
//啟動(dòng)服務(wù)
s.Serve()
}
根據(jù)我們之前的流程分析絮短,那么在客戶端建立連接過(guò)來(lái)之后江兢,Server要自動(dòng)的回復(fù)給客戶端一個(gè)玩家ID,同時(shí)也要講當(dāng)前玩家的坐標(biāo)發(fā)送給客戶端丁频。所以我們這里面給Player定制了兩個(gè)方法Player.SyncPid()
和Player.BroadCastStartPosition()
SyncPid()
則為發(fā)送MsgID:1
的消息杉允,將當(dāng)前上線的用戶ID發(fā)送給客戶端
mmo_game/core/player.go
//告知客戶端pid,同步已經(jīng)生成的玩家ID給客戶端
func (p *Player) SyncPid() {
//組建MsgId0 proto數(shù)據(jù)
data := &pb.SyncPid{
Pid:p.Pid,
}
//發(fā)送數(shù)據(jù)給客戶端
p.SendMsg(1, data)
}
BroadCastStartPosition()
則為發(fā)送MsgID:200
的廣播位置消息,雖然現(xiàn)在沒(méi)有其他用戶席里,不是廣播叔磷,但是當(dāng)前玩家自己的坐標(biāo)也是要告知玩家的。
mmo_game/core/player.go
//廣播玩家自己的出生地點(diǎn)
func (p *Player) BroadCastStartPosition() {
msg := &pb.BroadCast{
Pid:p.Pid,
Tp:2,//TP2 代表廣播坐標(biāo)
Data: &pb.BroadCast_P{
&pb.Position{
X:p.X,
Y:p.Y,
Z:p.Z,
V:p.V,
},
},
}
p.SendMsg(200, msg)
}
D)測(cè)試用戶上線業(yè)務(wù)
$cd mmo_game/
$go run server.go
啟動(dòng)服務(wù)端程序奖磁。
? 然后再windows終端打開(kāi)client.exe
注意改基,要確保windows和啟動(dòng)服務(wù)器的Linux端要能夠ping通,為了方便測(cè)試咖为,建議將Linux的防火墻設(shè)置為關(guān)閉狀態(tài)秕狰,或者要確保服務(wù)器的端口是開(kāi)放的,以免耽誤調(diào)試
在此處輸入服務(wù)器的IP地址案疲,和服務(wù)器
zinx.json
配置的端口號(hào)封恰。然后點(diǎn)擊Connect。如果游戲界面順利進(jìn)入褐啡,并且已經(jīng)顯示為Player_1
玩家ID诺舔,表示等錄成功,并且我們?cè)诜?wù)端也可以看到一些調(diào)試信息备畦。操作WASD也可以進(jìn)行玩家移動(dòng)低飒。如果沒(méi)有顯示玩家ID或者為TextView
則為登錄失敗,我們需要再針對(duì)協(xié)議的匹配進(jìn)行調(diào)試懂盐。
關(guān)于作者:
作者:Aceld(劉丹冰)
簡(jiǎn)書(shū)號(hào):IT無(wú)崖子
mail: danbing.at@gmail.com
github: https://github.com/aceld
原創(chuàng)書(shū)籍gitbook: http://legacy.gitbook.com/@aceld
原創(chuàng)聲明:未經(jīng)作者允許請(qǐng)勿轉(zhuǎn)載, 如果轉(zhuǎn)載請(qǐng)注明出處