DEV Community

Aceld
Aceld

Posted on

17. Moving position and non-crossing grid AOI broadcasting

[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 Application - MMO Game Case Study]

<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>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


MMO-GAME Source Code

https://github.com/aceld/zinx/tree/master/zinx_app_demo/mmo_game


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

Image description

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()
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.


MMO-GAME Source Code

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 Application - MMO Game Case Study]

<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>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


Author:
discord: https://discord.gg/xQ8Xxfyfcz
zinx: https://github.com/aceld/zinx
github: https://github.com/aceld
aceld's home: https://yuque.com/aceld

Top comments (0)