Server development for multiplayer game with nodejs and magx

Many developers are starting to develop a multiuser online server based on the socket.io library . This library makes it very simple to implement data exchange between the client and the server in real time, but you still have to think over and implement all the logic and interface of the server-client interaction, as well as the architecture for scaling and traffic optimization.







I want to talk about the magx library , using which you can not think about the network component (communication between the server and clients), but immediately concentrate on developing the game logic and client UI.







When designing the architecture of a multiplayer game, 2 approaches are usually considered: with an authoritarian server and a non-authoritarian (authoritarian client). Both of these approaches are supported by the magx library. Let's start with a simpler approach - non-authoritarian.







Non-authoritative server



Its essence is that the server does not control the input results of each player. Clients independently track the actions entered by the player and the game logic locally, after which they send the result of a certain action to the server. After that, the server synchronizes all performed actions with the game state of other clients.







It is easier to implement from an architectural point of view, since the server is only responsible for communication between clients, without doing any additional calculations that clients do.







Using the magx library, such a server can be implemented in just a few lines of code:







import * as http from "http"
import { Server, RelayRoom } from "magx"

const server = http.createServer()
const magx = new Server(server)

magx.define("relay", RelayRoom)

// start server
const port = process.env.PORT || 3001
server.listen(port, () => {
  console.info(`Server started on http://localhost:${port}`)
})
      
      





Now, to connect clients to this server and start their interaction, just install the js library:







npm install --save magx-client









and connect it to the project:







import { Client } from "magx-client"
      
      





Alternatively, you can use a direct link for HTML use:







<script src="https://cdn.jsdelivr.net/npm/magx-client@0.7.1/dist/magx.js"></script>
      
      





After connecting, just a few lines will allow you to configure the connection and interaction with the server:







// authenticate to server
await client.authenticate()

// create or join room
const rooms = await client.getRooms("relay")
room = rooms.length 
  ? await client.joinRoom(rooms[0].id)
  : await client.createRoom("relay")

console.log("you joined room", name)

// handle state patches
room.onPatch((patch) => updateState(patch))

// handle state snapshot
room.onSnapshot((snapshot) => setState(snapshot))

// handle joined players
room.onMessage("player_join", (id) => console.log("player join", id))

// handle left players
room.onMessage("player_leave", (id) => console.log("player leave", id))
      
      





A detailed example of how to build interaction between clients and a non-authoritative server is described in the corresponding example in the magx-examples project .







Authoritative server



, , - .







- . , , , , .







, . , (cheating).







. magx , (worker). , .







/ . — . Mosx — , magx - , .









, . mosx — @mx.Object, , @mx. :







@mx.Object
export class Player {
  @mx public x = Math.floor(Math.random() * 400)
  @mx public y = Math.floor(Math.random() * 400)
}

@mx.Object
export class State {
  @mx public players = new Map<string, Player>()

  public createPlayer(id: string) {
    this.players.set(id, new Player())
  }

  public removePlayer(id: string) {
    this.players.delete(id)
  }

  public movePlayer(id: string, movement: any) {
    const player = this.players.get(id)
    if (!player) { return }
    player.x += movement.x ? movement.x * 10 : 0
    player.y += movement.y ? movement.y * 10 : 0
  }
}
      
      





, — Map() . (Array) (number, string, boolean) .









. :







export class MosxStateRoom extends Room<State> {

  public createState(): any {
    // create state
    return new State()
  }

  public createPatchTracker(state: State) {
    // create state change tracker
    return Mosx.createTracker(state)
  }

  public onCreate(params: any) {
    console.log("MosxStateRoom created!", params)
  }

  public onMessage(client: Client, type: string, data: any) {
    if (type === "move") {
      console.log(`MosxStateRoom received message from ${client.id}`, data)
      this.state.movePlayer(client.id, data)
    }
  }

  public onJoin(client: Client, params: any) {
    console.log(`Player ${client.id} joined MosxStateRoom`, params)
    client.send("hello", "world")
    this.state.createPlayer(client.id)
  }

  public onLeave(client: Client) {
    this.state.removePlayer(client.id)
  }

  public onClose() {
    console.log("MosxStateRoom closed!")
  }
}
      
      







— .







const magx = new Server(server, params)

magx.define("mosx-state", MosxStateRoom)
      
      





magx-examples.







?



:







Mosx



  1. @mx
  2. @mx .
  3. @mx.Object.private @mx.private, .
  4. .
  5. Typescript
  6. ( MobX patchpack — )


Magx



  1. API.
  2. :

    • ( webockets)
    • ( )
    • ( )
    • ( )
  3. Built-in rooms: lobby and relay (for non-authoritative server)
  4. JS Magx-client library for working with the server
  5. Monitoring console magx-monitor for managing server rooms, their clients and viewing status
  6. Full Typescript support
  7. Minimum dependencies (for the notepack.io library - to reduce network traffic)


This project is quite young and I hope the attention of the community will help it develop much faster.








All Articles