Shared AR Host & Peers Communication: A Peer-to-Peer Solution

These steps are intended to be a temporary alternative solution while we update the existing guide.

Hello Lightship development community!

There are a few known issues you may encounter if you follow the Shared AR Host & Peers Communication guide that can result in…

…the host device not sharing tap events with peers…

…the peer car persisting on the host device after closing the app…

…the peer car persisting on the host device when the peer loads into another scene, such as the SceneSelector published with ARDK Examples…

…or the peer car not loading in a pre-existing host car after connecting.

If you’re trying to create a network multiplayer sample scene but have run into the issues with the published guide, you can follow these steps as an alternative peer-to-peer solution that addresses the issues listed above. Let’s build!

NOTE: These steps demonstrate creating the ARDK components manually rather than using the manager components.

  1. First, begin with a fresh scene.

  1. Then, alter the Main Camera GameObject’s Camera component so that the Clear Flags field is set to Solid Color, and the Background field is set to black.

  1. Also, add the AR Camera Position Helper, AR Rendering Manager, and Android Permission Requester components to the Main Camera, and ensure their camera references and Android permissions are set.

  1. Next, add a new C# script to the Main Camera named P2PGameLogic.

Note: The following steps go through the script creation process in detail. The resulting script – with in-line comments – is available for download, attached below this post.

  1. Open the script and start by adding the following using statements to the top of the file:
using Niantic.ARDK.AR;
using Niantic.ARDK.AR.Configuration;
using Niantic.ARDK.AR.Networking;
using Niantic.ARDK.AR.HitTest;
using Niantic.ARDK.Networking;
using Niantic.ARDK.Networking.MultipeerNetworkingEventArgs;
using Niantic.ARDK.Utilities;
using Niantic.ARDK.Utilities.BinarySerialization;
using Niantic.ARDK.Utilities.Input.Legacy;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;


  1. Within the scope of the P2PGameLogic class – but outside the scope of a method – declare private fields of the following types:
  • IARWorldTrackingConfiguration
  • IMultipeerNetworking
  • IARSession
  • IARNetworking

  1. Within the P2PGameLogic class, create a private void OnEnable() method and initialize the four ARDK objects that were declared in the previous step during this callback.

  1. Alongside the OnEnable() method, create a private void OnDisable() method, and dispose of the four aforementioned objects during this callback.

  1. Within the scope of the P2PGameLogic class – but outside the scope of a method – declare and initialize a byte array to represent the session identifier.

Note: In this example, we’ve hardcoded a string “SampleSessionID” to represent the session identifier.

  1. During the Start() method of the P2PGameLogic class, call the Run() method of the ARSession field belonging to the IARNetworking object, passing in the IARWorldTrackingConfiguration object. Afterwards, join the networking session by passing the byte array that represents the session identifier into the Join() method of the Networking field that belongs to the IARNetworking object.

  1. Within the scope of the P2PGameLogic class, create a series of callback methods that will listen for networking-related events raised by the IMultipeerNetworking object. We’ll write the methods first – including some logging statements to assist with potential troubleshooting later – and then we’ll subscribe the callbacks to the relevant event handlers in the next step.

  1. With the callback methods written, subscribe them to the relevant IMultipeerNetworking events during the OnEnable() method…

  1. …and be sure to unsubscribe the callback methods during the OnDisable() method, prior to disposing of the objects.

  1. Within the scope of the P2PGameLogic class, declare a pair of public fields – one to store the scene’s Camera component, and another to store the GameObject that represents the car prefab. Also, declare a private GameObject field that will contain a reference to the car that belongs to the local device, as well as a constant unsigned integer to represent the car positioning event that gets broadcast to other devices in the network session.

  1. After saving the P2PGameLogic script, return to the scene and set the references to the Camera component of the Main Camera GameObject and the CAR prefab that comes with the Lightship Hub samples.

  1. Ensure you’re setting the Car Prefab field to the .prefab object, and not the .fbx object of the same name.

  1. Within the Update() method belonging to the P2PGameLogic class, include the following logic that first checks for a tap on the screen, then performs a hit test. If the hit-test is successful, create a car for the local player at the tapped location if the car doesn’t already exist, or set its destination if it already does exist. Finally, broadcast the car’s position to other players on the network.

Note: The CreateCar() and BroadcastCarPosition() methods will be written in the following steps, so disregard any error indicators suggesting that the methods do not yet exist.

  1. Within the scope of the P2PGameLogic class, write a CreateCar() method that takes in a Vector3 position, and returns the GameObject of the car that was created at that position.

  1. Within the scope of the P2PGameLogic class, write a BroadcastCarPosition() method that takes in a Vector3 position, serializes it into a MemoryStream object, and sends it to other peers in the networking session.

  1. Within the scope of the P2PGameLogic class, declare and initialize a dictionary where they key-value pair is a GUID and GameObject. This will be populated on each device with the players (the GUID) and the car associated with that player (the GameObject).

  1. Within the OnPeerDataReceived() callback method, respond to the signal by first copying the arguments into a new MemoryStream object and storing the sender’s identifier. Then, in the case of receiving a position event, deserialize the position from the MemoryStream as a Vector3, and either set the remote player’s car’s position if that player is already in the local dictionary or create a car for that remote player and add them to the dictionary if they’re not yet added.

  1. Within the OnPeerAdded() callback method, call the BroadcastCarPosition() method to ensure that peers that connect to the network are made aware of any pre-existing cars.

  1. Within the OnPeerRemoved() callback method, be sure to remove the player that left from the local dictionary, and destroy their car in the process.

  1. Within the scope of the P2PGameLogic class, write a LeaveNetworkingSession() method that calls the Leave() method of the IMultipeerNetworking object.

  1. Call the LeaveNetworkingSession() method at the beginning of OnDisable() to ensure the player leaves the session when loading into a different scene.

  1. Within the P2PGameLogic class, create a private void OnApplicationQuit() method and call the LeaveNetworkingSession() method within it to ensure the player leaves the session when they force-quit the app.

  1. Ensure your scene is added to your Build Settings, and try testing on a pair of test devices.

Note: Before building, don’t forget to set up any project-wide settings for ARDK, such as the ARDKAuthConfig object that requires a valid API key generated by your Lightship account.

Now, you should be able to observe…

…the host device shares its tap events with peers…

…the peer car is cleaned up when closing the app…

…the peer car is also cleaned up when loading into another scene…

…and the peer device loads a pre-existing host car after connecting.

Note: By default, the networking classes will emit a variety of debug output to the console window. However, the debug statements generated by the code written above are prefixed by “Lightship: “. If you’d like to see the debug statements specific to this code, you can filter your debug output with the “Lightship: “ prefix to only see the debug statements that we wrote as part of P2PGameLogic.cs.

Thanks for following along with this alternative solution, and we look forward to seeing any networking projects you build with ARDK! :rocket:

:page_facing_up: P2PGameLogic.cs (11.3 KB)


Thank you very much for your clear guide. This helps me a lot.


Thanks for this update. Could you share if all this new “P2P” code is meant as a giant bug fix to the guide, or would there be situations where you’d still want to do things similar to the original guide (i.e. the Host-Peer method)? Because from my understanding it just seems like the original guide is buggy (as you showcased at the top of this post) and there’s never a reason to do things that way. Could somebody at Niantic clarify?

Hi @Kang-An!

There are issues with the currently published guide that result in bugs, and the team is working on a resolution at this time. These steps are intended to be a temporary alternative to creating a network multiplayer game while the original guide – which follows the authoritative-host model – is undergoing updates.

Hi @rob_link thanks for the clarification. Can I just confirm that the mistakes are with the guide itself, and not bugs in ARDK? Looking at the guide (without testing the code myself) it does seem to lack some steps that would cause the bugs showcased in this thread. I think I’d be able to fix it and just want to confirm that there aren’t any inherent bugs with ARDK if I wanted to implement a host-client architecture myself.

Great post! Does anyone know how this P2P method would compare latency wise to the standard host-client architecture? It seems each individual is somewhat functioning as a host for their own car and a peer for all other players, similar to the normal sense. When I’ve done something similar by having each individual have authority over their own spawned networked objects regardless of being host or not, I would encounter a degrading accuracy with tracking and encounter more latency as the session persists.

That’s correct, @Kang-An. The issues are with the steps laid out in the guide and not the ARDK classes. It’s still possible to have the host device be responsible for message distribution if you’d prefer to implement that paradigm.

Hi @Connor_McClelland , thank you!

Just to be clear, this alternative guide still uses the host-client setup under the hood. However, the messaging between devices is handled in a peer-to-peer style where all users (regardless if they are the host or a client/peer) are responsible for sending their events to all other users, rather than event messages being passed to the host in order to be re-distributed to the remaining peers.

Latency-wise, the method described in this guide should theoretically result in lower latency overall, as a peer updating its car position is broadcasting a single message to all other users, rather than the message being first sent to the host, then re-broadcast to the remaining peers. However, I wasn’t able to test this in practice so I’d be interested in hearing from anyone experiencing different results.

It is worth noting that – according to the Lightship docs – multiplayer sessions with ARDK should generally consist of five or fewer participating devices with a single session lasting five or fewer minutes.

1 Like