Michał Kmiecik

Michał Kmiecik

Poznań, Polska

I’m a full-time dev building indie startups after hours. I’ve just started making my first small profits — and I’m sharing everything I learn along the way 👇

See behind the scenes ↓

I recently made my first few $ from indie projects. Follow the journey as I build more — and try to make it sustainable.

Building an indie MMORPG – part 2: WebSocket setup

July 10, 2024

4 min read

How I connected the client and server using WebSockets — the first real-time communication feature in my online game.

Early development screenshot of a 2D MMORPG game I started building

Introduction

Finally, it's time for specifics and the first bit of code, which excites me immensely. As I mentioned in the previous post, I've been working very intensively on the game for about four months now, since the time of creating this entry, so I already have quite a lot of functionalities working. However, I'm documenting the entire progress only now because at the beginning, I wasn't sure if anything would come out of it and if I would even be able to manage. Fortunately, I managed to get through the initial problems, so I decided to start describing the entire progress. I won't hide the fact that I might have already forgotten some stages because a lot has happened with the code (countless refactors), but now I'm trying to analyze my actions commit by commit as accurately as possible, and I hope to present the entire progress I have made so far as best as I can.

Until I finish publishing posts up to the current state of the project, I promise that I won't write a line of code (even though my fingers are itching), so that future progress is described on an ongoing basis and you can learn about every success and problem I encounter, without omitting anything.

I must also note upfront that I will only paste the most important parts of the code because if I published everything, the length of the posts would be several kilometers. Moreover, I want to keep some algorithms to myself or for a potential online course.

Setting up WebSocket communication (simple echo logic)

I find it easiest to tackle difficult things when I start with simple steps, take the time to understand every aspect, and gradually add more complexity. The first task for the MMORPG game was to configure WebSocket communication, as everything will be built around this element. As a connection test, I wanted to implement a simple mechanism where the client (browser) sends a text message to the server, and the server responds to that same client (and only to that client) with the same message but in uppercase letters.

Backend part

On the server side, we currently have a simple code that registers a specific handler under the path /chat:

@Configuration
public class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        final Map<String, WebSocketHandler> websocketHandlers = new HashMap<>();
        websocketHandlers.put("/chat", new ChatWebsocketHandler());
        return new SimpleUrlHandlerMapping(websocketHandlers, -1);
    }
}

The handler itself works in such a way that for each connected client, it listens for incoming messages, transforms them into text, logs the reception, converts them to uppercase, wraps them in a WebSocketMessage object, and sends them back to the client:

@Slf4j
public class ChatWebsocketHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        final var webSocketMessageFlux = session.receive()
                .map(WebSocketMessage::getPayloadAsText)
                .doOnNext(it -> log.info("Received msg: " + it))
                .map(String::toUpperCase)
                .map(session::textMessage);

        return session.send(webSocketMessageFlux);
    }
}

Frontend part

For WebSocket communication on the frontend side, I am using the react-use-websocket library. The code is rather simple:

const CHAT_URL = "ws://localhost:8080/chat";

const Game = (props: GameProps) => {
  const [messages, setMessages] = useState<string[]>([]);
  const { sendMessage, lastMessage } = useWebSocket(CHAT_URL, {
    onOpen: () => console.log("Connected with server!"),
    shouldReconnect: (closeEvent) => true,
  });

  useEffect(() => {
    if (lastMessage !== null) {
      setMessages((prev) => [...prev, lastMessage.data]);
    }
  }, [lastMessage]);

  return (
    <div>
      {`Welcome ${props.username}`}
      <Chat
        username={props.username}
        messages={messages}
        onSendMessage={sendMessage}
      />
    </div>
  );
};

We connect to our local server, log the success, and listen for incoming messages from the backend. Each new message is saved to the messages array, which is then passed to the Chat component for display. Below is the result of the whole setup in action :).

Summary

Today was a simple and short warm-up. We have working communication between the server and the client, and we can continue expanding the project by adding the actual game logic. In the next post, I will show how I transitioned from text messages to binary, what the preliminary architecture of the server application looks like, and how the general chat, the first real interaction in the game, was implemented. See you next time.