Sling Academy
Home/Golang/Using WebSockets for Multiplayer Game Development in Go

Using WebSockets for Multiplayer Game Development in Go

Last updated: November 26, 2024

WebSockets provide a great way to implement real-time communication in multiplayer games, allowing the server and players to communicate back and forth with minimal latency. This article will guide you through setting up a basic multiplayer game server using WebSockets in Go.

What Are WebSockets?

WebSockets are a protocol that provides full-duplex communication channels over a single TCP connection. They are especially useful for applications like games, where rapid two-way communication is essential.

Setting Up Your Environment

Before diving into code, you'll need to install Go from the official Go website, and you might want to set up your environment to work with Go using an IDE like VSCode or GoLand.

Installing Gorilla WebSocket

In Go, one of the most popular libraries for WebSockets is Gorilla WebSocket. It’s robust, well-documented, and widely used in production-quality applications.

Use Go modules to install Gorilla WebSocket:

go get -u github.com/gorilla/websocket

Creating a Simple Game Server

Let’s start building a simple WebSocket server using Gorilla WebSocket.

package main

import (
    "log"
    "net/http"
    "github.com/gorilla/websocket"
)

// Define an upgrader that will upgrade our HTTP server connection to a websocket connection
var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // Allow any origin for simplicity
    },
}

// Function that will handle websocket requests
func handleConnections(w http.ResponseWriter, r *http.Request) {
    // Upgrade initial GET request to a websocket
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer ws.Close()

    for {
        // Read in a new message as JSON and map it to a struct
        var msg map[string]interface{}
        err := ws.ReadJSON(&msg)
        if err != nil {
            log.Printf("error: %v", err)
            break
        }
        log.Printf("Received message: %v", msg)

        // Write the message back to the client
        err = ws.WriteJSON(msg)
        if err != nil {
            log.Printf("error: %v", err)
            break
        }
    }
}

func main() {
    // Create a simple file server
    fs := http.FileServer(http.Dir("./public"))
    http.Handle("/", fs)

    // Set up a websocket route
    http.HandleFunc("/ws", handleConnections)

    // Start the server on localhost:8080
    log.Println("[INFO] Server started on :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

Handling WebSocket Connections

The code above establishes a basic WebSocket server. Each connected client communicates through a persistent open WebSocket. The `handleConnections` function reads incoming messages and simply echoes them back to the client.

Building a Client

For this demonstration, we’ll build a simple HTML/JavaScript client to connect to our WebSocket server. Create an `index.html` file in your `public` directory:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Game Client</title>
</head>
<body>
    <h1>WebSocket Game Client</h1>
    <div>Connection Status: <span id="status">Disconnected</span></div>
    <input type="text" id="message" placeholder="Enter a message"/>
    <button onclick="sendMessage()">Send Message</button>
    <script>
        let ws;
        function connect() {
            ws = new WebSocket('ws://localhost:8080/ws');

            ws.onopen = () => {
                document.getElementById('status').textContent = 'Connected';
                console.log('Connected');
            };

            ws.onmessage = (message) => {
                console.log('Received:', message.data);
            };

            ws.onclose = () => {
                document.getElementById('status').textContent = 'Disconnected';
                console.log('Disconnected');
                // Attempt to reconnect in 2 seconds
                setTimeout(connect, 2000);
            };
        }

        function sendMessage() {
            const message = document.getElementById('message').value;
            ws.send(message);
            console.log('Sent:', message);
        }

        connect();
    </script>
</body>
</html>

Running Your Server and Client

To see everything in action, first run your Go server:

go run main.go

Open your browser and navigate to http://localhost:8080. Here, you can send messages using your client interface, and you’ll see the messages being echoed back through the WebSocket connection.

Adding Game Logic

To utilize WebSockets effectively in a multiplayer game, you’ll eventually want to replace the echo message logic with real game state updates, player actions, and broadcasts to all connected players. This involves implementing separate goroutines for each connection and using channels for concurrency and synchronization within Go.

For instance, if implementing a chat feature, you might maintain a list of all connected players and broadcast any player message to the entire list. This will ensure each player receives real-time updates from others.

Here’s a simple example of a broadcast mechanism:

// Assume a global "clients" map and a global "broadcast" channel
var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan map[string]interface{})

func handleConnections(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer ws.Close()
    clients[ws] = true

    for {
        var msg map[string]interface{}
        err := ws.ReadJSON(&msg)
        if err != nil {
            delete(clients, ws)
            break
        }
        broadcast <- msg
    }
}

func handleMessages() {
    for {
        msg := <-broadcast
        for client := range clients {
            err := client.WriteJSON(msg)
            if err != nil {
                client.Close()
                delete(clients, client)
            }
        }
    }
}

With these fundamentals, you can build a robust multiplayer game using WebSockets in Go, taking advantage of the language’s concurrency model to efficiently handle multiple connections and messages with low latency.

Next Article: Creating a Scalable WebSocket Backend with Goroutines in Go

Previous Article: Implementing Typing Indicators in a Chat Application Using WebSockets in Go

Series: Websocket & Chat Programs in Go

Golang

Related Articles

You May Also Like

  • How to remove HTML tags in a string in Go
  • How to remove special characters in a string in Go
  • How to remove consecutive whitespace in a string in Go
  • How to count words and characters in a string in Go
  • Relative imports in Go: Tutorial & Examples
  • How to run Python code with Go
  • How to generate slug from title in Go
  • How to create an XML sitemap in Go
  • How to redirect in Go (301, 302, etc)
  • Using Go with MongoDB: CRUD example
  • Auto deploy Go apps with CI/ CD and GitHub Actions
  • Fixing Go error: method redeclared with different receiver type
  • Fixing Go error: copy argument must have slice type
  • Fixing Go error: attempted to use nil slice
  • Fixing Go error: assignment to constant variable
  • Fixing Go error: cannot compare X (type Y) with Z (type W)
  • Fixing Go error: method has pointer receiver, not called with pointer
  • Fixing Go error: assignment mismatch: X variables but Y values
  • Fixing Go error: array index must be non-negative integer constant