Coordinating

In this section, we will tie everything together by completing the AppManager; as implied by the title of this section, the AppManager is concerned with coordinating the activities of the game and ensuring that dependencies are fulfilled before starting a new task. In this project, the AppManager is also responsible for the spawning of the player entities, which is the main focus of this section. With that being said; let's jump into it and start putting things in place; open the script by either double-clicking on the file from within the Unity Editor (located in the /PaintWarz/Scripts folder within the Project panel) or from within Visual Studio.

Within the AppManager class, we have assigned all relevant prefabs accessible via the public variables:

    public GameObject playerSPrefab;
public GameObject playerCPrefab;

Within the AppManager Start method, we register to events associated with the PlaySpaceManager finishing scanning, AnchorSyncManager state updating, and when the user performs a tap via the InputManager; the assigned handlers are probably a good place to start our conversation. Let's first preview the Start method, where these handlers are assigned:

    void Start () {
...
networkManager.OnNetworkReady += NetworkManager_OnNetworkReady;
networkManager.OnConnectionOpen += NetworkManager_OnConnectionOpen;

PlaySpaceManager.Instance.OnPlaySpaceFinished += PlaySpaceManager_OnPlaySpaceFinished;

AnchorSyncManager.Instance.OnStateChanged += AnchorSyncManager_OnStateChanged;

InputManager.Instance.OnTap += InputManager_OnTap;
}

Let's work our way through each handler, starting with the NetworkManager_OnNetworkReady method. To recap, the NetworkManager raises the OnNetworkReady event when either the server is ready to receive connections or the client has connected to the server, while the OnConnectionOpen event is raised when a connection is made between the server and the client. Find and replace the NetworkManager_OnNetworkReady method with the following code snippet:

    void NetworkManager_OnNetworkReady(PWNetworkManager networkManager)
{
if (networkManager.IsServer)
{
NetworkServer.RegisterHandler(MsgTeamSpawnRequest, OnRecievedRequestToSpawnTeam);
}
}

If this instance is acting as a server, we register the OnRecievedRequestToSpawnTeam handler for the MsgTeamSpawnRequest message type. Before continuing, let's take a quick detour to discuss the concept of authority.

You may recall a previous discussion about using a single point of control (or authority) to simplify the synchronization and control of networked objects. Unity supports this through the concept of authority, whereby only the instance with the authority of a specific object can update its properties and, once updated, will send through the updated state to its ghost self within the other instances. An error will be raised if the instance doesn't have authority to modify its properties.

The following figure illustrates this concept, where a single instance owns the object and a representation, or ghost, exists on the other instances:

 

You may be wondering what exactly a networked object is, is it just a plain old GameObject? Almost, it's a GameObject with a NetworkIdentity, which is responsible for creating a unique identifier for an object across the network; for example, in the preceding image, the instance of PlayerS on the server would have the same identify as the PlayerS object on the client, along with details of which instance owns it and so on. For more details, refer to the official documentation at https://docs.unity3d.com/Manual/class-NetworkIdentity.html.

Before jumping back into the code, it's worth noting that only the server can spawn networked objects; it is for this reason that the client sends the MsgTeamSpawnRequest message.

Let's now return our focus to the task at hand and resume where we left off, with implementing the OnRecievedRequestToSpawnTeam method; replace this method with the following code snippet:

    void OnRecievedRequestToSpawnTeam(NetworkMessage netMsg)
{
var teamSpawnRequestMessage = netMsg.ReadMessage<TeamSpawnRequestMessage>();

CreateTeam(netMsg.conn, teamSpawnRequestMessage.position);
}

When the server receives this message, it first deserializes the message to an instance of TeamSpawnRequestMessage before calling CreateTeam, and passing the connection and position variable bundled with the received message.

Just to clarify, this method is only called on the server, so we currently reside within the instance of the server, satisfying a request by the client.

Find and replace the CreateTeam method with the following code snippet:

    public void CreateTeam(NetworkConnection connection, Vector3 teamCenterPosition)
{
const int teamSize = 3;

string team = connection == null ? "TeamS" : "TeamC";

for (int i = 0; i < teamSize; i++)
{
GameObject player = null;

if (connection == null)
{
player = Instantiate(playerSPrefab);
}
else
{
player = Instantiate(playerCPrefab);
}

player.GetComponent<Player>().Init(team, string.Format("Player_{0}_{1}", team, i));

var playerOffset = UnityEngine.Random.insideUnitSphere * 0.5f;
playerOffset.y = 0;
player.transform.position = teamCenterPosition + playerOffset;

if (connection == null)
{
player.GetComponent<NetworkIdentity>().localPlayerAuthority = false;
NetworkServer.Spawn(player);
}
else
{
player.GetComponent<NetworkIdentity>().localPlayerAuthority = true;
NetworkServer.SpawnWithClientAuthority(player, connection);
}
}
}

We first determine whether this is for a client or server; from this, we instantiate the appropriate prefab. Next, we iterate through a loop of teamSize to create the desired size squad. For each player, we apply a little jitter to the position to avoid having each one spawned on top of another. The final, and most critical part, is spawning the instance across the network. If the connection is null, then this call was for the server; therefore, the authority resides with the server, in which case we use NetworkServer.Spawn(player) to spawn the player. Otherwise, we call NetworkServer.SpawnWithClientAuthority(player, connection), passing in the connection that hands over the authority to the client, that is, the client is responsible for updating this object.

At this stage, we have seen how the server handles a request to create a team; what are remaining are the actions required to create that message. Creating a team is triggered by the user performing a tap gesture when gazing at the floor; jump back to the InputManager_OnTap(GameObject target, Vector3 hitPosition, Vector3 hitNormal) method and replace it with the following code snippet:

    private void InputManager_OnTap(GameObject target, Vector3 hitPosition, Vector3 hitNormal)
{
if (!teamCreated && PlaySpaceManager.Instance.IsCloseToFloor(hitPosition))
{
InitTeam(hitPosition);
}
else
{
if (!string.IsNullOrEmpty(Team) && PlaySpaceManager.Instance.IsCloseToFloor(hitPosition))
{
tapIndicator.Show(hitPosition, hitNormal);
TeamsManager.Instance.SetTeamsTarget(Team, hitPosition);
}
}
}

When a tap gesture is detected, we first determine whether a team has been created; if not, we call InitTeam, which handles the process of creating the team. If a team has already been created, we update the team's position via the TeamsManager.Instance.SetTeamsTarget method, passing in the team's name and target position.

We are almost finished; at this point, we have an application that scans the environment, discovers and connects with its pairs, shares the coordinate system, and instantiates the team of the player's squad. The last task is to parent each of the players to the anchored floor. This means that any adjustment to the floor will have a consequence for the children, a desirable side effect. We will perform this with the space that has been scanned; find and replace the PlaySpaceManager_OnPlaySpaceFinished(GameObject floorPlane) method with the following code snippet:

    private void PlaySpaceManager_OnPlaySpaceFinished(GameObject floorPlane)
{
var players = FindObjectsOfType<Player>();
foreach (var player in players)
{
player.transform.parent = floorPlane == null ? null : floorPlane.transform;
}
}

Here, we simply find all objects of the Player type, and set their parent to the anchored floor, as discussed earlier. That's it! This concludes this project, and now is a perfect opportunity to test it. Grab a friend nearby with the device and build and deploy to test:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.142.146