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); }