Creating Multiplayer Prototypes

12m

Intermediate

In this article, I’ll walk you through setting up a real time connection between two or more Framer X prototypes.

You could use this technique to build working chat apps, add push notifications to your prototype, or even explore “Find My”-style location syncing. You might also work a real-time connection into user testing: updating a database as users complete certain tasks, testing collaboration between multiple participants, or even enabling a member of your testing team to trigger events in the user’s experience.

While the real-time technology is pretty fun, we won’t be diving too deep into how it all works. Instead, I’ve created some flexible starter files that will help us skip to the good stuff.

Before we begin

You’ll need to set up a few things before starting this guide. The first, of course, is Framer X itself. If you’re new to Framer X, you can download a free 14-day trial.

You’ll also need to have Node.js installed on your computer. You might actually have this installed already — but if you’re not sure, or if you run into errors in the steps below, follow this guide to get set up.

For one of the later steps, you’ll also need a Github Account. If you don’t have an account already, go ahead and sign up — it’s free and, if you spend enough time with Framer X, you’ll eventually want an account anyway.

Getting started

For this tutorial, I’ve prepared a starter file that has a lot of the tricky parts worked out already. Let’s start with that.

  1. Download the starter file.
  2. Open the file in Framer X.
  3. Duplicate the file by selecting File > Duplicate in the Menu.
  4. Rename the file to something clever, like StarterSocketA.
  5. Close the original starter file.

Feel free to take a look around your new project. When you’re ready, we can continue to start our first connection.

Connecting through a local host

In this tutorial, we’ll be setting up a little network consisting of a host and several clients. The clients (our prototypes) will connect to the host, allowing the host to route messages between its clients. See here for a visual.

In the starter file, I’ve included a host as well as a server that will make this host available to our different prototypes. Before we can use the host, we’ll need to turn on the server.

  1. In Framer X, open your project’s Project Folder by selecting File > Show Project Folder. This should open a Finder window showing the project’s files.
  2. Now open the Terminal app. As a shortcut, press ⌘ + space, type “Terminal”, and select Terminal under Applications. Click here for an example.
  3. In Finder, click back into the Project Folder’s window.
  4. Open the Project Folder’s host folder in the Terminal app. Click on the folder named host, drag it to the dock, and release it on top of the Terminal app’s icon. If you’re stuck, click here for a 5 second video.
  5. In the Terminal window, type npm run start and press Enter.

You should see the Server started message as shown above. If you instead see a message like command not found: npm, you’ll need to install Node.js. Complete that guide, then come back to the Terminal and try again.

If you do see the Server started message, then congratulations — your server is running and the host is listening for connections. (You’ve also completed the hardest part of this tutorial).

You can now connect to the host at the URL shown in the terminal. Even better: if you’re connected to a network (such as a Wi-Fi network), then you can connect from any other computer on that network, too.

Connecting to the host

Now that our server is running, let’s connect to it from our prototype.

  1. Click back into Framer X.
  2. On the canvas, select the Frame named Template.
  3. Open the Preview Window by pressing ⌘ + p or by clicking the play icon in the upper-right corner of the app.
  4. Open the Console by pressing ⌘ + i or by clicking on the Preview’s hamburger menu icon and clicking Show Console.
  5. Refresh the Preview by pressing ⌘ + r or by clicking the refresh icon next to the Preview window’s menu icon.

You should see a message in the console that reads Connected as shown above. This is good news: it means our prototype has successfully connected to the host and is ready to send and receive messages.

However, only one connected prototype won’t make for such a good demo, so let’s add a second prototype, too.

Connecting a second prototype

Let’s create a second prototype and connect it to the host, too.

  1. In Framer X, duplicate your StarterSocketA project by selecting File > Duplicate in the Menu.
  2. Rename the file something sharp, like StarterSocketB.
  3. Repeat the steps from the last section until you see the Connected message for StarterSocketB.
  4. Arrange your windows so that you can see both the StarterSocketA preview window and the StarterSocketB window.
  5. In the Preview, flip either Switch and watch the magic.

Pretty, cool, right? But what if we wanted to connect a prototype running on some other machine? Let’s try it.

Connecting a third prototype — from a second computer

For this next step, I’ll assume that you have a second computer with Framer X installed and that both computers are connected to the same Wi-Fi network. (Feel free to commandeer a colleague’s laptop.)

  1. On your first computer (where your server is running), open the Terminal.
  2. You should see a history of the server’s connections so far.
  3. Scroll back to the top to the line beginning Socket host is listening.
  4. Write down the IP address shown.
The server, showing a history of connections and the server’s local URL (highlighted).
The server, showing a history of connections and the server’s local URL (highlighted).

Now let’s open a prototype on your other machine.

  1. Download the starter file on the second computer.
  2. Open the file in Framer X.
  3. Duplicate the file by selecting File > Duplicate in the Menu.
  4. Name the file something witty, like SocketStarterC.

We already have a host running on our first computer, so no need to start a new one. However, before we can connect to that host, we’ll need to make a change to this new project’s code.

  1. In Framer X, open the Code Panel by clicking on the {} icon at the left hand side, or by selecting View > Panel > Code in the menu.
  2. In the files list, select the App.tsx file.
  3. Near the top of the file, you should see a line that reads: const serverUrl = "http://localhost:8080".
  4. In this line, replace localhost with the IP address that we wrote down from the Terminal.
Using the server’s local IP address (highlighted).
Using the server’s local IP address (highlighted).

Why the change? The localhost URL is special: it always points to the machine’s own URL on the local network. That was fine for our first two prototypes because they were on the same machine as the server. However, because we’re now trying to connect to the server from a different machine, we’ll need to use the host’s local IP address instead.

Let’s see it in action.

  1. On the canvas, select the Frame named Template.
  2. Open up the Preview.
  3. Open up the Console.
  4. Refresh the Preview.
  5. Confirm that you can see the Connected message.
  6. Flip the switch and glance over at the preview on the other computer.

If we’ve set everything up correctly, then the switch should flip in both previews on both computers. If not, you may need to refresh the preview on the first computer, too.

Connecting through an online server

Let’s recap on what we have so far:

  • We have a host running on a local server
  • We can connect to the host from prototypes that are on the same computer as the server using the localhost URL
  • We can connect to the host from other computers that are on the same local network as the server using the server’s local network URL

What about connecting to the host from other computers that aren’t not on the same network? That’s a little trickier —but again, I’ve prepared something that we can use without much hassle.

  1. In your browser, visit this CodeSandbox link.
  2. At the top-right corner, click the Fork button.
  3. If you’re not already signed in, click Sign in with Github.

On the odd chance that you don’t already have a Github account, click Create an Account to make one of those, too. On CodeSandbox, “forking” a project will create a duplicate that you can edit. In this case, we’re duplicating a server much like the one included in the starter file, though modified slightly to work online.

Once you’ve created your fork, the project should start up on its own. You’ll know it’s ready when you see something like this in the Browser preview:

  1. Copy the server URL shown in the Preview.
  2. Click back into Framer X.
  3. Open up SocketStarterA.
  4. Create (yet another) duplicate by selecting File > Duplicate in the Menu.
  5. Name it something smart and funny like SocketStarterC.
  6. In the project’s App.tsx, replace the entire serverUrl with the value copied from the CodeSandbox.
Using the server’s URL (highlighted).
Using the server’s URL (highlighted).

Now let’s do the Preview thing again.

  1. On the canvas, select the Frame named Template.
  2. Open up the Preview.
  3. Open up the Console.
  4. Refresh the Preview.
  5. Confirm that you can see the Connected message.

If you don’t see the Connected message, open up the serverUrl address in your browser. The CodeSandbox server may have gone to sleep: it will shut down automatically after a few minutes of being idle, however we can wake them back up by visiting the URL ourselves.

Let’s test a remote connection.

  1. In Framer, open the Live Preview by clicking the mobile device icon in the upper-right corner of the app.
  2. In the modal that opens, click Start.
  3. If you have a mobile phone on hand, scan the barcode with your phone’s camera to open the Preview link.
  4. If not, click the open in browser link.
  5. When the Live Preview opens, flip the switch on either Preview.

As long as the server is still running, both switches should flip. Note that you could send this Live Preview link to anyone — anywhere on the planet — and your two switches would stay in sync.

One caveat: if you sent the Live Preview to more than one person, then it would only work for one person at a time. The most recent connection would boot the previous connection. That’s because our host is set up to prevent too many connections from the same origin. We can, however, disable this protection.

Chaos mode

Let’s turn off our host’s guard against multiple connections from the same origin.

  1. Open your forked CodeSandbox project.
  2. Change the value of UNIQUE_CLIENTS to false.

Note: I only recommend setting UNIQUE_CLIENTS to false when not actively working on your Framer X prototype. Without this protection, each change to your project will create a new client connection. If you’re planning on editing your prototype, I highly recommend turning this feature back on.

However, with this feature turned off, you’re now able to send your Live Preview link to all of your friends and together share the experience of collectively controlling a switch.

Social switch flipping — what more could you possibly need?

Doing more than flipping a switch

So far, all we’ve done is controlled a switch from multiple devices. In this last section, I’ll introduce the starter file’s action system and show how you can use the system to do more interesting things.

How it works

As mentioned earlier in this article, our network is made up of two parts: a single host and any number of clients. When a client connects to the host, the host adds the client to a list. When a client disconnects, the host removes it from the list.

Actions

The connection between a host and client allows either to dispatch actions that will be received by the other. When a client connects to the host, the host will listen for actions dispatched from that client; and likewise, the client will listen for actions dispatched from the host.

An action is a message with two parts: a name and (optionally) some data.

Here’s an example of an action:

"FLIP_SWITCH", { isOn: value }
COPY

And here it is being dispatched from our starter file’s Switch override.

export function Switch(): Override {
return {
value: appState.switchIsOn,
onValueChange: value => {
dispatch("FLIP_SWITCH", { isOn: value })
},
}
}
COPY

Responses

Clients have a set of responses for different actions. Responses are callback functions that take the action’s data as an argument and (usually) update the prototype’s state.

Here’s an example of a response:

FLIP_SWITCH: data => (appState.switchIsOn = data.isOn)
COPY

And here it is again in our starter file’s responses:

const responses = {
CONNECT: () => console.log("Connected"),
DISCONNECT: () => console.log("Disconnected"),
FLIP_SWITCH: data => (appState.switchIsOn = data.isOn),
}
COPY

The host forwards actions to clients

Whenever the host receives an action from a client, it will dispatch that action to each client on its list of connected clients. If the client has a response for this action, the client will call that response’s function with the action’s data as an argument.

That’s how it works.

For Example: Syncing an Input

Now that we know the theory, let’s see it in practice. For this example, we’ll be creating a shared text input.

  1. Click back into to Framer X.
  2. Open up SocketStarterA.
  3. Create (yet another) duplicate by selecting File > Duplicate in the Menu.
  4. Name it something cool yet approachable like SocketStarterE.

Next, let’s add a TextInput component to our design.

  1. Open the the Components Panel by clicking on the grid icon on the far-left, or by selecting View > Panels > Components in the menu.
  2. Click on the Learn Design System title to expand it.
  3. In the list of components, select the TextInput component and drag it out onto the canvas.
  4. Place the TextInput inside of the same Stack as the Switch component.

Now let’s update our App.tsx file to use this TextInput.

We’ll first add a new property named inputValue to our prototype’s state.

const appState = Data({
switchIsOn: false,
inputValue: "",
})
COPY

Next, we’ll create a response that updates this state.

const responses = {
CONNECT: () => console.log("Connected"),
DISCONNECT: () => console.log("Disconnected"),
FLIP_SWITCH: data => (appState.switchIsOn = data.isOn),
INPUT_CHANGE: data => (appState.inputValue = data.value),
}
COPY
export function Input(): Override {
return {
value: appState.inputValue,
onValueChange: value => {
dispatch("INPUT_CHANGE", { value: value })
},
}
}
COPY

And now all that’s left to do is to connect the override to our Input.

When we type in the Input, we’ll dispatch an INPUT_CHANGE action to the host. The host will forward this action to all of its connected prototypes. If any of the other prototypes have an INPUT_CHANGE response, then the prototype will call its INPUT_CHANGE response with the new value we’ve sent in the action.

It’s worth mentioning that our prototypes don’t have to be exactly the same. For example, we might have a second prototype with a INPUT_CHANGE response that will respond to the same action, but do something entirely different from what we’ve written above.

That said, if the second prototype does have the same INPUT_CHANGE response, then its response function will use the new value to update the prototype’s state. This will cause all of its overrides to refresh, thereby updating the input’s value to match the value in its state — which will be the value we dispatched from our other prototype.

And in that way, the two (or more) inputs will stay in sync.

Wrapping Up

Ok, that’s everything I know! If you’re still curious about the code, take a look through the starter file’s client.ts file and the different files in the host folder. And if you want to learn more about the technology we’re using, check out socket.io.

I hope you’re able to take the starter files that I’ve provided and turn them into something amazing. And if you do, please let me know! You can find me on Twitter or on the Framer Slack or Spectrum channels.

Good luck!