Tutorial: Pong - Using ARDK and Game Logic

This is an example Unity project in which ARDK’s features are used to create an AR Multiplayer version of Pong. This tutorial will cover all of the Unity steps as well as C# script implementations in order to make the project work. Please note that this implementation uses low level messages to send data between players. Another version of this project will demonstrate the setup and use of High Level API objects (HLAPI) to streamline the message sending and receiving process for synchronizing players (here).

Using ARDK and Game Logic

GameController

After the ARNetworking and ARSession objects are created and successfully connected by the ARNetworkingManager, it is time to move to the GameController (located on the GameManagers GameObject), which handles most of the game setup and logic.

Setting up Event Listeners

Two callbacks are set in Start. The PreloadProgressUpdated callback enables the Join button once the preloader has finished caching AR shared data. The OnAnyARNetworkingSessionInitialized callback handles additional setup once ARNetworking is initialized:

private void Start()
{
  startGameButton.SetActive(false);
  ARNetworkingFactory.ARNetworkingInitialized += OnAnyARNetworkingSessionInitialized;
  preloadManager.ProgressUpdated += PreloadProgressUpdated;
}

private void PreloadProgressUpdated(FeaturePreloadManager.PreloadProgressUpdatedArgs args)
{
  if (args.PreloadAttemptFinished)
  {
    if (args.FailedPreloads.Count > 0)
    {
      Debug.LogError("Failed to download resources needed to run AR Multiplayer");
      return;
    }

    joinButton.interactable = true;
    preloadManager.ProgressUpdated -= PreloadProgressUpdated;
  }
}

private void OnAnyARNetworkingSessionInitialized(AnyARNetworkingInitializedArgs args)
{
  _arNetworking = args.ARNetworking;
  _arNetworking.PeerPoseReceived += OnPeerPoseReceived;
  _arNetworking.PeerStateReceived += OnPeerStateReceived;

  _arNetworking.ARSession.FrameUpdated += OnFrameUpdated;
  _arNetworking.Networking.Connected += OnDidConnect;

  _messagingManager = new MessagingManager();
  _messagingManager.InitializeMessagingManager(args.ARNetworking.Networking, this);
}

OnAnyARNetworkingSessionInitialized will cache references to the networking object, which are used to handle networking and AR events. Some other events are subscribed to, which correspond to events upon which some game action will be taken or a game state will change. Finally, a MessagingManager is created and initialized with a reference to the networking object. MessagingManager is a manager created for the Pong example, described in Sending and Receiving Messages with MessagingManager.

Starting the Game

As seen above, the StartGame button will be disabled until a Peer is successfully synced with the host. Upon sync, the StartGame button becomes enabled for the host, and a hit test to spawn the game objects becomes available. Tapping the screen at any location (other than the StartGame button) will run a hit test for planes detected by the ARSession. If a plane is hit (the floor, a desk, etc), the game objects will be spawned centered at the location of the hit. The host will then send a message to the non-host to spawn the objects at the same location.

// When all players are ready, create the game. Only the host will have the option to call this
public void StartGame()
{
  if (!_objectsSpawned)
    InstantiateObjects(_location);

  startGameButton.SetActive(false);
  _isGameStarted = true;
  _ballBehaviour.GameStart(_isHost, _messagingManager);
}

// Instantiate game objects
internal void InstantiateObjects(Vector3 position)
{
  if (_playingField != null)
  {
    Debug.Log("Relocating the playing field!");
    _playingField.transform.position = position;

    var offset = _isHost ? new Vector3(0, 0, -2) : new Vector3(0, 0, 2);

    // Instantiate the player and opponent avatars at opposite sides of the field
    _player.transform.position = position + offset;
    offset.z *= -1;
    _opponent.transform.position = position + offset;
    _ball.transform.position = position;

    if (_isHost)
      _messagingManager.SpawnGameObjects(position);

    return;
  }

  score.text = "Score: 0 - 0";

  // Instantiate the playing field at floor level
  Debug.Log("Instantiating the playing field!");
  _playingField = Instantiate(playingFieldPrefab, position, Quaternion.identity);

  // Determine the starting location for the local player based on whether or not it is host
  var startingOffset = _isHost ? new Vector3(0, 0, -2) : new Vector3(0, 0, 2);

  // Instantiate the player and opponent avatars at opposite sides of the field
  _player = Instantiate(playerPrefab, position + startingOffset, Quaternion.identity);
  startingOffset.z *= -1;
  _opponent = Instantiate(playerPrefab, position + startingOffset, Quaternion.identity);

  // Instantiate the ball at floor level, and hook up all references correctly
  _ball = Instantiate(ballPrefab, position, Quaternion.identity);
  _ballBehaviour = _ball.GetComponent<BallBehaviour>();
  _messagingManager.SetBallReference(_ballBehaviour);
  _ballBehaviour.Controller = this;

  _objectsSpawned = true;

  if (!_isHost)
    return;

  _messagingManager.SpawnGameObjects(position);
}

private void FindFieldLocation(Touch touch)
{
  var currentFrame = _arNetworking.ARSession.CurrentFrame;
  if (currentFrame == null)
    return;

  var results =
    currentFrame.HitTest
    (
    _camera.pixelWidth,
    _camera.pixelHeight,
    touch.position,
    ARHitTestResultType.ExistingPlaneUsingExtent
  );

  if (results.Count <= 0)
  {
    Debug.Log("Unable to place the field at the chosen location. Can't find a valid surface");
    return;
  }

  // Get the closest result
  var result = results[0];
  var hitPosition = result.WorldTransform.ToPosition();
  InstantiateObjects(hitPosition);
}

Previous page: Getting Started

Continued in: Game Logic and AR Events