This section introduces the real-time display of a moving player’s position on the client of surrounding players when the player is moving. This means that not only can the client of the moving player see the movement, but also the clients of other players around them. The specific processing flow is shown in Figure 17.1.

Figure 17.1

The above process involves two messages, MsgID 3 and MsgID 200, with Tp (Type) 4. When a player moves, the client will actively send a Position message with MsgID 3 to the server.

Before the server, “Server,” starts, it should register a router business handler for MsgID 3. Here’s the specific implementation:

//mmo_game/server.go

func main() {
// Create a server instance
s := znet.NewServer()
// Register client connection start and loss functions
s.SetOnConnStart(OnConnectionAdd)
// Register routers
s.AddRouter(2, &api.WorldChatApi{}) // Chat
s.AddRouter(3, &api.MoveApi{}) // Movement
// Start the server
s.Serve()
}

The code above registers the business handling logic for MsgID 3 (Movement) before starting the server. Next, let’s implement the MoveApi logic in the mmo_game/api directory by creating a move.go file:

//mmo_game/api/move.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"
)

// Player movement
type MoveApi struct {
znet.BaseRouter
}

func (*MoveApi) Handle(request ziface.IRequest) {
// 1. Decode the incoming proto protocol from the client
msg := &pb.Position{}

err := proto.Unmarshal(request.GetData(), msg)
if err != nil {
fmt.Println("Move: Position Unmarshal error ", err)
return
}

// 2. Determine which player sent the current message by obtaining it from the connection property "pid"
pid, err := request.GetConnection().GetProperty("pid")
if err != nil {
fmt.Println("GetProperty pid error", err)
request.GetConnection().Stop()
return
}

fmt.Printf("User pid = %d, move(%f,%f,%f,%f)", pid, msg.X, msg.Y, msg.Z, msg.V)

// 3. Get the player object based on pid
player := core.WorldMgrObj.GetPlayerByPid(pid.(int32))

// 4. Have the player object broadcast the position update information
player.UpdatePos(msg.X, msg.Y, msg.Z, msg.V)
}

The movement business logic is quite similar to the previous chat business logic. Firstly, it decodes the data protocol received from the client. Secondly, it identifies which player’s client the request came from and retrieves the corresponding backend Player’s Pid. Finally, it calls the Player.UpdatePos() method, which is primarily responsible for processing and sending synchronization messages. Let’s take a look at how Player.UpdatePos() should be implemented:

//mmo_game/core/player.go

// Broadcast player position movement
func (p *Player) UpdatePos(x float32, y float32, z float32, v float32) {
// Update the player's position information
p.X = x
p.Y = y
p.Z = z
p.V = v
// Assemble the protobuf protocol and send the position to surrounding players
msg := &pb.BroadCast{
Pid: p.Pid,
Tp: 4, // 4 - Coordinate information after movement
Data: &pb.BroadCast_P{
P: &pb.Position{
X: p.X,
Y: p.Y,
Z: p.Z,
V: p.V,
},
},
}
// Get all players surrounding the current player
players := p.GetSurroundingPlayers()
// Send MsgID 200 message (movement position update message) to each player in the vicinity
for _, player := range players {
player.SendMsg(200, msg)
}
}

The UpdatePos() method’s flow is relatively clear. It assembles a BroadCast protocol with MsgID 200 and sets Tp to 4. Then, it uses the GetSurroundingPlayers() method to determine which players are surrounding the current player. It iterates through each surrounding player and uses the player’s SendMsg() method to send the current player’s latest coordinates to each player’s corresponding remote client.

The implementation of GetSurroundingPlayers() is as follows:

//mmo_game/core/player.go

// Get information about players surrounding the current player's AOI
func (p *Player) GetSurroundingPlayers() []*Player {

// Get all Pids in the current AOI area
pids := WorldMgrObj.AoiMgr.GetPidsByPos(p.X, p.Z)

// Put all players corresponding to the Pids into a Player slice
players := make([]*Player, 0, len(pids))

for _, pid := range pids {
players = append(players, WorldMgrObj.GetPlayerByPid(int32(pid)))
}

return players
}

This method is used to retrieve the Player objects of other players surrounding the current player in the AOI (Area of Interest).

Once all these functionalities have been developed, compile and run the server, and start three client instances to observe the final results.

The results confirm that three clients can now achieve synchronized movement. Up to this point, the basic framework of a large-scale MMO online game has been established. For those interested, additional game mechanics such as battles, scoring, etc., can be added based on the previously developed framework and processes.

--

--