Turn your phone into a wireless Joy-Con with SyncoPath
Are you tired of those old school AWSD keyboard controls? It's time to take your game controls to the next level. How so? Just point your phone to the QR code below: (note: if this didn't work stop reading now :-P, sorry for wasting 1 min of your life) Did it work? Pfeww... Keep reading. How does this work? This can be done by simply passing your phone data through WebSocket. To be accurate, the demo above uses both WebSocket and WebRTC, passing your phone's accelerometer data to the computer. The codepen snippet embedded in this article connects to my server through WebSocket. A QR code gets generated so the phone can connect to that same server and same room. The phone then passes accelerometer data to the web page which can replicate the movement of the phone. That's the gist of it. It's a bit complicated, and I can explain the details in another post, but if you just want to use that in your game, I have a library that makes it a bit easier. I named it SyncoPath. What is SyncoPath? This is a funky name I used for my library that facilitates the use of WebSocket and WebRTC. WebSocket enables multiplayer games by connecting them to a server in real-time. Those ".io" games like slither.io likely use that technology. WebRTC is a real-time peer to peer technology that allows real-time communication without a server. You can send data or video through it, so apps like Zoom use that. The thing with WebRTC is that it needs a server to initiate the handshake. It's like making a phone call: You need the number of the person you want to talk to. The problem is that for WebRTC, the number is very long (and it keeps changing), so you need to initiate that with a server. Once the communication is established, you can disconnect from the server, or keep it around as backup. The SyncoPath library wraps that around a protocol where you simply share data between two entities. So rather than sending messages around (which is how WebSocket communicates), you have this big chunk of common data, and anything you change on one device will be updated on the other device. You can also observe the data and act accordingly when it gets modified. import { provideSocketClient, } from "https://esm.sh/@dobuki/syncopath@1.0.23"; // Connect to a server and a room const socketClient = provideSocketClient({ host: "demo.dobuki.net", room: "dev.to/jacklehamster", }); socketClient.setData("greeting", "hello, now is " + new Date()); socketClient.observe("greeting").onChange(value => { document.querySelector("#label").textContent = value; }); See code on CodePen The code above: connects to a room using "demo.dobuki.net" as the host. sets the variable "greeting" to "hello, now is ". Note that it uses "setData". This is needed because we also need to inform SyncoPath that the data got modified so that it can be synchronized. listens to any change to the "greeting" variable. For your small project, I'm ok if you use my server, but if I might limit it if I see it gets used too much. (I do pay monthly for it). It's best if you find a cheap server and spin your own. The SyncoPath library should have instructions on hooking up the library to your server. From WebSocket to WebRTC You could still pass data around with WebSocket, but if you send tons of data, it's taxing on the server, it starts costing money, and if you're doing that on a server generously provided for free, the owner might not be so happy. WebRTC doesn't go through the server, and it's faster. So it's best to use for data to be passed one-to-one. Still, we need WebSocket to initiate the connection. So what we can do is simply use the common data of SyncoPath to pass around some WebRTC info that will allow the two devices to connect peer-to-peer. handleUsersChanged(socketClient) .onUserAdded((clientId, isSelf, observers) => { if (isSelf) { return; } const peerData = socketClient.peerData(clientId); // ... use peerData to set common data via WebRTC peerData.setData("path/to/value", someValue); // ... listen to the peerData for changes peerData.observe("path/to/value").onChange(...); }) .onUserRemoved(user => { // cleanup }); socketClient.peerData gives direct access to a particular path within the common data. That path is "peer/user-1:user-2". It's a known path used for sharing data between user-1 and user-2. The WebSocket server also knows to share that data only between those two users. Note: user-1 and user-2 are the ids of the two users. It varies as more users enter the same room. The object at that path can also be accessed through this property: const peerData = socketClient.state.peer["user-1:user-2"]; Once some data is written inside that path, a webRTC connection is initiated, then any subsequent update to that data is communicated peer to peer through this new protocol. The whole WebRTC connection is a bit complex. It involves a bit

Are you tired of those old school AWSD keyboard controls? It's time to take your game controls to the next level. How so?
Just point your phone to the QR code below:
(note: if this didn't work stop reading now :-P, sorry for wasting 1 min of your life)
Did it work? Pfeww... Keep reading.
How does this work?
This can be done by simply passing your phone data through WebSocket. To be accurate, the demo above uses both WebSocket and WebRTC, passing your phone's accelerometer data to the computer.
- The codepen snippet embedded in this article connects to my server through WebSocket.
- A QR code gets generated so the phone can connect to that same server and same room.
- The phone then passes accelerometer data to the web page which can replicate the movement of the phone.
That's the gist of it. It's a bit complicated, and I can explain the details in another post, but if you just want to use that in your game, I have a library that makes it a bit easier. I named it SyncoPath.
What is SyncoPath?
This is a funky name I used for my library that facilitates the use of WebSocket and WebRTC.
- WebSocket enables multiplayer games by connecting them to a server in real-time. Those ".io" games like slither.io likely use that technology.
- WebRTC is a real-time peer to peer technology that allows real-time communication without a server. You can send data or video through it, so apps like Zoom use that.
The thing with WebRTC is that it needs a server to initiate the handshake. It's like making a phone call: You need the number of the person you want to talk to. The problem is that for WebRTC, the number is very long (and it keeps changing), so you need to initiate that with a server.
Once the communication is established, you can disconnect from the server, or keep it around as backup.
The SyncoPath library wraps that around a protocol where you simply share data between two entities. So rather than sending messages around (which is how WebSocket communicates), you have this big chunk of common data, and anything you change on one device will be updated on the other device. You can also observe the data and act accordingly when it gets modified.
import {
provideSocketClient,
} from "https://esm.sh/@dobuki/syncopath@1.0.23";
// Connect to a server and a room
const socketClient = provideSocketClient({
host: "demo.dobuki.net", room: "dev.to/jacklehamster",
});
socketClient.setData("greeting", "hello, now is " + new Date());
socketClient.observe("greeting").onChange(value => {
document.querySelector("#label").textContent = value;
});
The code above:
- connects to a room using "demo.dobuki.net" as the host.
- sets the variable "greeting" to "hello, now is ". Note that it uses "setData". This is needed because we also need to inform SyncoPath that the data got modified so that it can be synchronized.
- listens to any change to the "greeting" variable.
For your small project, I'm ok if you use my server, but if I might limit it if I see it gets used too much. (I do pay monthly for it).
It's best if you find a cheap server and spin your own. The SyncoPath library should have instructions on hooking up the library to your server.
From WebSocket to WebRTC
You could still pass data around with WebSocket, but if you send tons of data, it's taxing on the server, it starts costing money, and if you're doing that on a server generously provided for free, the owner might not be so happy.
WebRTC doesn't go through the server, and it's faster. So it's best to use for data to be passed one-to-one. Still, we need WebSocket to initiate the connection.
So what we can do is simply use the common data of SyncoPath to pass around some WebRTC info that will allow the two devices to connect peer-to-peer.
handleUsersChanged(socketClient)
.onUserAdded((clientId, isSelf, observers) => {
if (isSelf) {
return;
}
const peerData = socketClient.peerData(clientId);
// ... use peerData to set common data via WebRTC
peerData.setData("path/to/value", someValue);
// ... listen to the peerData for changes
peerData.observe("path/to/value").onChange(...);
})
.onUserRemoved(user => {
// cleanup
});
socketClient.peerData
gives direct access to a particular path within the common data. That path is "peer/user-1:user-2". It's a known path used for sharing data between user-1 and user-2. The WebSocket server also knows to share that data only between those two users.
Note: user-1
and user-2
are the ids of the two users. It varies as more users enter the same room.
The object at that path can also be accessed through this property:
const peerData = socketClient.state.peer["user-1:user-2"];
Once some data is written inside that path, a webRTC connection is initiated, then any subsequent update to that data is communicated peer to peer through this new protocol.
The whole WebRTC connection is a bit complex. It involves a bit of back and forth. SyncoPath handles that through this code, but this is something you don't have to worry about if you just use the library.
Passing around accelerometer data
Once the connection is established, the phone listens to accelerometer events and send them through SyncoPath.
const handleOrientationEvent = (event) => {
if (event.alpha === null) {
return;
}
peerData.setData("acceleration", {
x: event.alpha,
y: event.beta,
z: event.gamma,
});
};
window.addEventListener("deviceorientation",
handleOrientationEvent, true);
On the page, we observe to the "acceleration" path for changes and update the visuals accordingly:
observers.add(
peerData.observe("acceleration").onChange(
(acceleration) => {
if (acceleration) {
const { x, y, z } = acceleration;
rotatePhone(x, y, z);
}
})
);
The phone in the codepath sample got rendered using Three.js. For fun, I also added some beep sound generated using ZZfx when you tap.
Okay but how is that a game?!
You want a game? Ok, might as well make a game out of this.
Check it below:
About Me
More interesting stuff
GameDev.js: https://gamedevjs.com
Big Nuts Games: https://bignutsgames.com
Humans In a Box: An upcoming game that's going to blow your socks off!