[zinx]
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>
[ZINX应用程序 - MMO游戏案例研究]
<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
MMO游戏源代码
https://github.com/aceld/zinx/tree/master/zinx_app_demo/mmo_game
成功登录用户后,它表明已经建立了前端和后端之间的初始连接。接下来,我们只需要实现聊天系统功能,允许玩家登录到服务器进行通信并互相发送消息。
15.1世界经理模块
要实现世界聊天系统,我们需要定义一个可以处理世界上所有玩家的经理。该经理应拥有有关当前所有在线玩家的信息以及全世界的AOI(感兴趣领域)部门规则。这种设置使玩家可以方便地互相聊天并同步他们的位置。
在mmo_game/core/
目录中创建一个world_manager.go
文件。该文件将包含世界经理模块的设计和实现。
首先,让我们定义Worldmanager World Manager类的成员结构,如以下代码段所示:
// mmo_game/core/world_manager.go
package core
import (
"sync"
)
/*
The overall management module of the current game world
*/
type WorldManager struct {
AoiMgr *AOIManager // The AOI planner manager for the current world map
Players map[int32]*Player // Collection of currently online players
pLock sync.RWMutex // Mutex for protecting Players for concurrent read and write
}
Worldmanager结构包括:
(1)AoiMgr
,一个代表当前世界地图的AOI(感兴趣领域)经理的AOIManager
。
(2)Players
,一个用于存储世界上所有玩家的信息的变量。这是一张键是PlayerID
的地图,值是播放器对象。
(3)pLock
,一种读写的静音,用于确保在阅读和写入Players
时并发安全。
WorldManager
结构包含用于管理游戏世界中玩家的各种方法。这些方法包括将玩家添加到世界上,通过ID删除玩家,通过ID检索玩家,并获取有关世界上所有玩家的信息。此外,还有用于配置AOI(感兴趣区域)网格的常数。
// mmo_game/core/world_manager.go
// Provide an external world management module handle
var WorldMgrObj *WorldManager
// Provide the initialization method for WorldManager
func init() {
WorldMgrObj = &WorldManager{
Players: make(map[int32]*Player),
AoiMgr: NewAOIManager(AOI_MIN_X, AOI_MAX_X, AOI_CNTS_X, AOI_MIN_Y, AOI_MAX_Y, AOI_CNTS_Y),
}
}
// The minimum and maximum coordinates and grid counts for AOI
const (
AOI_MIN_X int = 85
AOI_MAX_X int = 410
AOI_CNTS_X int = 10
AOI_MIN_Y int = 75
AOI_MAX_Y int = 400
AOI_CNTS_Y int = 20
)
// WorldManager is the overall management module of the current game world
type WorldManager struct {
AoiMgr *AOIManager // The AOI planner manager for the current world map
Players map[int32]*Player // Collection of currently online players
pLock sync.RWMutex // Mutex for protecting Players for concurrent read and write
}
// AddPlayer provides a function to add a player to the player information table Players
func (wm *WorldManager) AddPlayer(player *Player) {
// Add the player to the world manager
wm.pLock.Lock()
wm.Players[player.Pid] = player
wm.pLock.Unlock()
// Add the player to the AOI network planning
wm.AoiMgr.AddToGridByPos(int(player.Pid), player.X, player.Z)
}
// RemovePlayerByPid removes a player from the player information table by player ID
func (wm *WorldManager) RemovePlayerByPid(pid int32) {
wm.pLock.Lock()
delete(wm.Players, pid)
wm.pLock.Unlock()
}
// GetPlayerByPid retrieves player information by player ID
func (wm *WorldManager) GetPlayerByPid(pid int32) *Player {
wm.pLock.RLock()
defer wm.pLock.RUnlock()
return wm.Players[pid]
}
// GetAllPlayers retrieves information about all players
func (wm *WorldManager) GetAllPlayers() []*Player {
wm.pLock.RLock()
defer wm.pLock.RUnlock()
// Create a return player slice
players := make([]*Player, 0)
// Append to the slice
for _, v := range wm.Players {
players = append(players, v)
}
// Return
return players
}
Worldmanager模块主要充当统一AOI(感兴趣领域)和参与者的管理的中介。它在其他模块中起着协调作用。一个重要方面是全局变量WorldMgrObj
,它是管理模块的外部可访问的手柄,可用于其他模块。
使用WorldManager
模块,每次播放器登录时,都应将它们添加到WorldMgrObj
对象中。添加播放器的相关代码如下:
//mmo_game/server.go
// Hook function when a client establishes a connection
func OnConnecionAdd(conn ziface.IConnection) {
// Create a player
player := core.NewPlayer(conn)
// Synchronize the current PlayerID to the client using MsgID: 1
player.SyncPid()
// Synchronize the initial coordinates of the current player to the client using MsgID: 200
player.BroadCastStartPosition()
// Add the currently newly logged-in player to the WorldManager
core.WorldMgrObj.AddPlayer(player)
fmt.Println("=====> Player pidId = ", player.Pid, " arrived ====")
}
以这种方式,现在在服务器中实现了用于创建和添加玩家的代码。
15.2世界聊天系统实施
接下来,利用世界经理,我们将为当前服务器实施世界聊天广播功能。相互作用过程如图15.1。
图15.1
15.2.1原始协议定义
此步骤涉及定义MSGID 2和200的指令的原始结构,以及谈话和广播原始协议。在msg.proto文件中,定义这些协议的格式如下:
//mmo_game/pb/msg.proto
syntax = "proto3"; // Proto protocol
package pb; // Current package name
option csharp_namespace = "Pb"; // Option provided for C#
// Synchronize the client's player ID
message SyncPid {
int32 Pid = 1;
}
// Player position
message Position {
float X = 1;
float Y = 2;
float Z = 3;
float V = 4;
}
// Player broadcast data
message BroadCast {
int32 Pid = 1;
int32 Tp = 2; // 1 - World chat, 2 - Player position
oneof Data {
string Content = 3; // Chat content
Position P = 4; // Location of the broadcasting user
int32 ActionData = 5;
}
}
// Player chat data
message Talk {
string Content = 1; // Chat content
}
定义后,执行预先编写的build.sh
脚本以生成新的msg.proto.go
文件。
15.2.2。建立聊天业务API
接下来,创建MMO_GAME/API目录,该目录主要用于存储不同MSGID的服务器端业务逻辑代码。在API目录中,创建World_chat.go文件,该文件将包含World Chat System中聊天业务的逻辑。
遵循Zinx的格式,编写MSGID 2的处理程序逻辑代码。这是特定的实现:
//mmo_game/api/world_chat.go
package api
import (
"fmt"
"github.com/golang/protobuf/proto"
"zinx/ziface"
"zinx/zinx_app_demo/mmo_game/core"
"zinx/zinx_app_demo/mmo_game/pb"
"zinx/znet"
)
// WorldChat API for world chat routing business
type WorldChatApi struct {
znet.BaseRouter
}
func (*WorldChatApi) Handle(request ziface.IRequest) {
// 1. Decode the proto protocol sent by the client
msg := &pb.Talk{}
err := proto.Unmarshal(request.GetData(), msg)
if err != nil {
fmt.Println("Talk Unmarshal error ", err)
return
}
// 2. Determine which player sent the message, get it from the connection's "pid" attribute
pid, err := request.GetConnection().GetProperty("pid")
if err != nil {
fmt.Println("GetProperty pid error", err)
request.GetConnection().Stop()
return
}
// 3. Get the player object based on pid
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))
// 4. Have the player object initiate a chat broadcast request
player.Talk(msg.Content)
}
对于MSGID 2的路由业务,有一个小细节要注意。在上面代码的步骤2中,根据连接conn
检索播放器的PID属性。这需要将PID和conn
绑定为玩家登录过程中的属性。您应该在OnConnecionAdd()
方法中添加此绑定,该方法在建立客户端连接时将其作为钩函数执行。这是您可以绑定pid和conn
的方式:
//mmo_game/server.go
// Hook function executed when a client connection is established
func OnConnecionAdd(conn ziface.IConnection) {
// Create a player
player := core.NewPlayer(conn)
// Synchronize the current PlayerID to the client using MsgID:1 message
player.SyncPid()
// Synchronize the initial coordinates of the current player to the client using MsgID:200 message
player.BroadCastStartPosition()
// Add the newly logged-in player to the worldManager
core.WorldMgrObj.AddPlayer(player)
// Add: Bind the PID to this connection as an attribute
conn.SetProperty("pid", player.Pid)
fmt.Println("=====> Player pidId = ", player.Pid, " arrived ====")
}
接下来,让我们为播放器实施谈话方法,该方法将广播消息发送给相应的客户端。代码实现如下:
// mmo_game/core/player.go
// Broadcast player chat
func (p *Player) Talk(content string) {
// 1. Assemble the MsgId200 proto data
msg := &pb.BroadCast{
Pid: p.Pid,
Tp: 1, // TP 1 represents chat broadcast
Data: &pb.BroadCast_Content{
Content: content,
},
}
// 2. Get all currently online players in the world
players := WorldMgrObj.GetAllPlayers()
// 3. Send MsgId:200 messages to all players
for _, player := range players {
player.SendMsg(200, msg)
}
}
主要过程涉及创建MSGID 200的原始数据协议结构。然后,它通过世界经理检索所有在线玩家的信息,并通过所有在线玩家迭代,将MSGID 200消息发送给每个播放器的相应客户端程序。
15.2.3测试世界聊天功能
我们现在已经实现了世界聊天消息的基本功能。让我们继续执行一个简单的测试,以确保此功能运行平稳。
在服务器端,使用以下命令运行服务器并观察结果:
$ go run server.go
Add api msgId = 2
[START] Server name: Zinx Game, listener at IP: 0.0.0.0, Port 8999 is starting
[Zinx] Version: V0.11, MaxConn: 3000, MaxPacketSize: 4096
Start Zinx server Zinx Game successfully, now listening...
Worker ID = 9 is started.
Worker ID = 4 is started.
Worker ID = 5 is started.
Worker ID = 6 is started.
Worker ID = 7 is started.
Worker ID = 8 is started.
Worker ID = 0 is started.
Worker ID = 1 is started.
Worker ID = 2 is started.
Worker ID = 3 is started.
然后,在Windows系统上打开两个客户端应用程序,并相互进行聊天对话。通过这样做,您已成功测试了聊天功能,如图15.2所示。
图15.2
MMO游戏源代码
https://github.com/aceld/zinx/tree/master/zinx_app_demo/mmo_game
[zinx]
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>
[ZINX应用程序 - MMO游戏案例研究]
<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
作者:
不和谐:https://discord.gg/xQ8Xxfyfcz
zinx:https://github.com/aceld/zinx
github:https://github.com/aceld
Aceld的家:https://yuque.com/aceld