AccueilBlogsProjetsFavorisPoèmesImagesÀ proposCommentairesAdmin

Supabase Realtime - How to deal with multiplayers in Next.js

Supabase realtime is one of the best tools to add multiplayers activity in your project / game. In this blog, we will learn just that.

01st May, 2024
10 min read
Supabase
Supabase Realtime - How to deal with multiplayers in Next.js

Gone are the days when you’d have to configure web sockets for days and night before you can actually build the logic and multiplayers activity in your project / game.

In this blog we will see how easy it is to implement Supabase realtime. Here’s a summary of what we will make:

  • Setup a room
  • Subscribe to the room
  • Alert current users when someone joins the room
  • Track total users with supabase presence
  • Alert current users when someone leaves

I assume you already do have project with authentication and supabase integrated and installed in it. So we will just focus on what matters.

1. Setup a room

// import what's necessary

export default function GameBoard({ user, gameId }) {
  const supabase = createClient();
  const userId = user.id; /* Id of the current logged in user */
  const room = useRef(null);
  const id = gameId; /* unique game id shareable to other users */
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const gameRoom = supabase.channel(`game:${gameId}`, {
      config: {
        broadcast: {
          self: true /* recommended */,
        },
        presence: {
          key: userId /* will be used to recognize user later */,
        },
      },
    });
    room.current = gameRoom;
    
    // cleanup
    return async () => {
      if (room.current) {
        await supabase.removeChannel(room.current);
      }
    };
  }, []);

  return <div>Hello world from my game room.</div>;
}

Now that we have a room in place we can subscribe the users to it.

2. Subscribe the users to the room

export default function GameBoard({ user, gameId }) {
  const supabase = createClient();
  const userId = user.id; /* Id of the current logged in user */
  const room = useRef(null);
  const id = gameId; /* unique game id shareable to other users */
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const gameRoom = supabase.channel(`game:${gameId}`, {
      config: {
        broadcast: {
          self: true /* recommended */,
        },
        presence: {
          key: userId /* will be used to recognize user later */,
        },
      },
    });
    room.current = gameRoom;

    room.current.subscribe(async (status) => {
      if (status !== "SUBSCRIBED") {
        return null;
      }
      // track the presence of the user once subscribed to the channel
      // this is necessary to keep a list of the users when they join
      await room.current.track({
        user: {
          ...user, // whatever you pass here will be accessible to the presence
        },
      });
      
    });
    
    // cleanup
    return async () => {
      if (room.current) {
        await supabase.removeChannel(room.current);
      }
    };
    
  }, []);

  return <div>Hello world from my game room.</div>;
}

3. Alert current users when someone joins

export default function GameBoard({ user, gameId }) {
  const supabase = createClient();
  const userId = user.id; /* Id of the current logged in user */
  const room = useRef(null);
  const id = gameId; /* unique game id shareable to other users */
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const gameRoom = supabase.channel(`game:${gameId}`, {
      config: {
        broadcast: {
          self: true /* recommended */,
        },
        presence: {
          key: userId /* will be used to recognize user later */,
        },
      },
    });
    room.current = gameRoom;

    room.current.subscribe(async (status) => {
      if (status !== "SUBSCRIBED") {
        return null;
      }
      // track the presence of the user once subscribed to the channel
      await room.current.track({
        user: {
          ...user, // whatever you pass here will be accessible to the presence
        },
      });

      // send an event whenever a user joins the room with their information
      room.current.send({
        type: "broadcast",
        event: "newUserJoined",
        payload: {
          userId,
          user,
        },
      });
    });

    // listen to the newUserJoined event
    room.current.on("newUserJoined", ({ payload }) => {
      const name = payload.user.name;
      const id = payload.userId; // this id will be the id of the newly joined user
      if (id !== userId) {
        // alert everyone excepts the newly joined user
        // use a toast library or any other way to alert the user
        toast(`${name} has joined the room!`);
      }
    });
    
    // cleanup
    return async () => {
      if (room.current) {
        await supabase.removeChannel(room.current);
      }
    };
    
  }, []);

  return <div>Hello world from my game room.</div>;
}

We can now also use supabase presence to track the total users.

export default function GameBoard({ user, gameId }) {
  const supabase = createClient();
  const userId = user.id; /* Id of the current logged in user */
  const room = useRef(null);
  const id = gameId; /* unique game id shareable to other users */
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const gameRoom = supabase.channel(`game:${gameId}`, {
      config: {
        broadcast: {
          self: true /* recommended */,
        },
        presence: {
          key: userId /* will be used to recognize user later */,
        },
      },
    });
    room.current = gameRoom;

    room.current.subscribe(async (status) => {
      if (status !== "SUBSCRIBED") {
        return null;
      }
      // track the presence of the user once subscribed to the channel
      await room.current.track({
        user: {
          ...user, // whatever you pass here will be accessible to the presence
        },
      });

      // send an event whenever a user joins the room with their information
      room.current.send({
        type: "broadcast",
        event: "newUserJoined",
        payload: {
          userId,
          user,
        },
      });
    });

    // listen to the newUserJoined event
    room.current.on("newUserJoined", ({ payload }) => {
      const name = payload.user.name;
      const id = payload.userId; // this id will be the id of the newly joined user
      if (id !== userId) {
        // alert everyone excepts the newly joined user
        // use a toast library or any other way to alert the user
        toast(`${name} has joined the room!`);
      }
    });

    // sync the users in the room
    room.current.on("presence", { event: "sync" }, () => {
      const newState = room.current.presenceState();
      const values = Object.values(newState);
      const users = values.map((v) => ({ ...v[0].user }));
      setUsers(users); // this will set the total users in the room to the users state
    });
    
    // cleanup
    return async () => {
      if (room.current) {
        await supabase.removeChannel(room.current);
      }
    };
    
  }, []);

  return <div>Hello world from my game room.</div>;
}

4. Alert current users when someone leaves

export default function GameBoard({ user, gameId }) {
  const supabase = createClient();
  const userId = user.id; /* Id of the current logged in user */
  const room = useRef(null);
  const id = gameId; /* unique game id shareable to other users */
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const gameRoom = supabase.channel(`game:${gameId}`, {
      config: {
        broadcast: {
          self: true /* recommended */,
        },
        presence: {
          key: userId /* will be used to recognize user later */,
        },
      },
    });
    room.current = gameRoom;

    room.current.subscribe(async (status) => {
      if (status !== "SUBSCRIBED") {
        return null;
      }
      // track the presence of the user once subscribed to the channel
      await room.current.track({
        user: {
          ...user, // whatever you pass here will be accessible to the presence
        },
      });

      // send an event whenever a user joins the room with their information
      room.current.send({
        type: "broadcast",
        event: "newUserJoined",
        payload: {
          userId,
          user,
        },
      });
    });

    // listen to the newUserJoined event
    room.current.on("newUserJoined", ({ payload }) => {
      const name = payload.user.name;
      const id = payload.userId; // this id will be the id of the newly joined user
      if (id !== userId) {
        // alert everyone excepts the newly joined user
        // use a toast library or any other way to alert the user
        toast(`${name} has joined the room!`);
      }
    });

    // sync the users in the room
    room.current
      .on("presence", { event: "sync" }, () => {
        const newState = room.current.presenceState();
        const values = Object.values(newState);
        const users = values.map((v) => ({ ...v[0].user }));
        setUsers(users); // this will set the total users in the room to the users state
      })
      .on("presence", { event: "leave" }, ({ key }) => {
        // key is the userId of the user who left the room
        // get the user who left the room
        const user = users.find((u) => u.id === key);
        toast(`${user.name} has left the room!`);
      });
      
      // cleanup
    return async () => {
      if (room.current) {
        await supabase.removeChannel(room.current);
      }
    };
    
  }, []);

  return (
    <div>
      Hello world from my game room. <b>{users.length}</b> users are in the
      room.
    </div>
  );
}

I really hope this helps you how to implement supabase realtime in your next project!

Good luck :)