During the week of WWDC I kept wondering to myself if it would be possible to create a multiplayer game using SharePlay, aka GroupActivities. Apple advertised the upcoming feature of iOS 15 as a way to watch movies and listen to music with your friends on a FaceTime call, but after looking at the API I saw that it supports custom activities that can be used for sharing any activity you can imagine.
Like many others, I played quite a few multiplayer games over video calls with friends and family during the bulk of the pandemic when we are all desperate to connect with people that we couldn’t visit in person. I knew that, although far from a perfect substitute for in-person interactions, being able to see and talk with friends while competing in video games together was still a really fun experience. So I set out to see if it would be possible to create a fun, engaging, multiplayer game that was designed to play along with a FaceTime call once WWDC was over.
I spent the whole summer, and then even more time after SharePlay was delayed until iOS 15.1, building a prototype and slowly getting all of the pieces working reasonably well enough to prove to myself that it could be done. I released SharePlay Guessing Game on October 25, 2021, the same day that iOS 15.1 and SharePlay were released to the world.
Along the way I learned the ins and outs of the SharePlay API in a way that few others have (based on the relatively small number of SharePlay-enabled apps on the App Store) so I thought it might be interesting to share some of the things I learned about SharePlay.
What is SharePlay?
For those who may not already know, SharePlay is a new framework that Apple introduced in iOS 15.1 which allows people on a FaceTime call to watch movies or listen to music together. One person initiates the SharePlay session and everyone else on the call is invited to join. Once everyone has joined, the movie or music is automatically synced up for everyone in the session, which includes pausing and seeking the content. Since you’re on a FaceTime call you can see and hear everyone else on the call while you watch the movie or listen to the music.
If you use Apple’s built-in audio/video player in your app then implementing SharePlay is trivial. If you have a custom player, then you just have to handle callbacks for play/pause/seek in your media playing app. However, there is a third ‘custom’ mode available which has endless possibilities as it allows your app or game to share data with each other through the FaceTime connection.
This post won’t be a coding tutorial on implementing custom SharePlay experiences. Apple has a code sample and lots of documentation on this. Instead I just hope to share some interesting observations and issues that I encountered when creating my SharePlay game.
How To Share Data Between Players?
When you first dig into the SharePlay API, you will notice that there are two different ways that you can share data between the participants. How do you know which method to use?
The first way that data can be shared is through the struct (or class) that you create that implements the GroupActivity protocol. You can put anything you want in this entity and it is Codable and automatically shared among all participants, so it might be tempting to store state in it, but I recommend storing the minimum amount of data possible in it. It is better just to store mostly immutable properties that you need to start the activity on each device, such as the movie to watch or the settings for the current game. I first attempted to store game scores in it, for instance, but I found that if multiple participants are constantly updating the stored data in the GroupActivity, it will be continuously copied and sent to all participants and while it will eventually converge, there will be times where some participants have inconsistent views of the data. And you don’t want participants to all have their own unique view on the current state of the game.
The second way to share data is through peer-to-peer message passing. This is the preferred way to send data to other participants because it gives you more control over exactly what you are sending and to whom you are sending it. In my game, I use message passing for keeping all of the game state up to date: when each round is started, when a question is answered correctly, every drawing stroke, etc. The one downside to using message passing rather than sharing state in the GroupActivity entity is that the GroupActivity is stored in the SharePlay session so it persists as long as the FaceTime call is still going. So, theoretically if the game scores were stored in the GroupActivity and my game hit a bug and crashed for all participants, they could just rejoin the activity and the scores would still be there. In my case, though, my game is super casual so if the scores are lost and a new activity has to be restarted it’s not that big of a deal.
No Server Required: A Double-Edged Sword
When designing a new app, I spend an inordinate amount of time thinking about the tradeoffs of building a back-end for it. Most of the time the back-end will cost you more money as it scales. What if this freemium app takes off and the server costs bankrupt me because the business model doesn’t work? Or what if I build it on a service that goes away next year (remember Parse?) and I’m forced to reimplement the whole thing?
One of the nice things about SharePlay is that you don’t necessarily need to run a server in order to create a multiplayer game. But SharePlay being peer-to-peer presents its own complications that you should be aware of. The biggest complication is that there isn’t one arbiter of truth like you would have with a server-based game engine that keeps track of the game state, scores, etc. So you have to run the same game on all of the devices and synchronize them through message passing and shared state and make sure they all agree on what is going on.
Figuring out how to make different instances of the game all agree on the same game state and scoring was the most challenging part of making the game for me. How do you progress from one round to another for everyone at the same time? What happens if two players guess the answer at the same time? How does everyone agree on the scores? This post is intended to be an overview of SharePlay development, but if you would like to hear more details about how I solved these issues, I can go into more details in a followup post. Let me know if you would be interested in that.
Needless to say, it takes a different mindset to figure out how to coalesce a shared game state instead of having one single game state contained within a server. However, the benefit of not having to provide your own back-end in order to provide a multiplayer game or activity is a huge win.
Handling the Chaos
Another interesting aspect of the peer-to-peer nature of SharePlay is the fact that anyone on the FaceTime call can initiate or replace a shared activity at any time. And anyone can drop out or come back at any point. This adds a special kind of chaos to a multiplayer game, which I think lends itself more to a casual game than a more serious one. If you keep track of scores, but in the end they don’t really matter that much, then the stakes are much lower when some joker on your call starts sharing a new episode of Ted Lasso and your game comes to an abrupt end. Conversely, imagine what would happen if any of the 100 participants in a Fortnite Battle Royale game could just end the game for everyone playing on a whim.
All of the chaotic actions a group of people on a FaceTime call can initiate makes for lots of different strange edge cases that can be difficult to debug and subsequently reproduce. What do you do if a new player comes in half-way through? What if you have a Guess The Drawing game and the person drawing leaves before the round is over? What happens if everyone leaves the activity at the same time? The best way to cover all possibilities is to test with multiple players as much as you can, which brings me to my next topic.
SharePlay Testing is Hard
It’s never easy to test new APIs on iOS betas, but the difficulty is increased to nightmare mode when you need to have multiple people and devices all on the new half-baked iOS beta in order to test a multiplayer game. You can use iOS simulators to see how the UI functions on different devices, but since SharePlay requires an active FaceTime call, it is not possible to test everything on simulators.
Trying to test on two devices by yourself is also difficult because you have to mute and turn the volume down on all but one of the devices to avoid brain-searing audio feedback issues. Luckily my son, like all tweens his age, is borderline obsessed with video games, and so he was always up to help me test my game as long as it didn’t count against his carefully allotted screen time.
Now that iOS 15.1 has been released to the general public, you likely won’t run into the problem that I had initially. Not only did I have to find people to help me test that were on the iOS 15.1 beta, but they had to have friends who were on the iOS 15.1 beta as well. And the more people you have testing simultaneously, the more chaotic things get, which is great for testing edge cases, but is not great for being able to properly log any discovered bugs. Special shoutout to the iOS Folks Slack #sf-coffee crew who spent many evenings doing a very good job helping me break the game in so many different ways.
Hopefully this post gives you some idea of the benefits and challenges of adopting SharePlay for your app or game. Despite the limitations and difficulty in debugging and testing, overall I found the framework to be quite powerful and relatively easy to use once you understand its properties. The ability to create custom shared activities has tons of potential to open up very interesting shared interactions over FaceTime. I’m excited to see what other ideas developers come up with to utilize this new framework.
Please let me know if you would like to see more detailed posts about how I solved some of the issues mentioned in this post. Feel free to add a comment here or find me at @gregggreg2 on Twitter.