Tutorial 10 - Spawning and Controlling Entities from the Client

Summary

This tutorial shows how to sync transform and animation state from one player to all other players using entity ownership and the ksAnimationSync, ksOwnershipScriptManager, and ksAutoSpawn components. This can be useful when you have an existing Unity-based character controller you want to use without porting it to a Reactor player controller to make it server authoritative. This tutorial uses the Third-Person Character Controller from Unity's starter assets, though the scripts could be used with other controllers as well.

There are drawbacks to using a client-authoritative controller instead of a server-authoritative one. Client-authoritative games are easier for players to cheat by syncing states that are not intended by the developer, and are not recommended for highly competitive play. This can be mitigated by writing server-side validation of client movement, but this is outside the scope of this tutorial. Client-authoritative controllers are also not well suited for dynamic physics interactions that affect gameplay.

Requirements

Scene and Prefab Setup

Open the Scene

  1. Open the Playground scene from 'Assets/StarterAssets/ThirdPersonController/Scenes/Playground'. If you want to preserve the orignal scene, save a copy of this scene.

Create a Room

  1. Right click in the hierarachy window and create a 'Reactor->Room' game object.
  2. Check 'Allow Player Spawning' in the ksRoomType inspector. This will allow players to spawn entities in the room that are synced to other players.

Create the Player Prefab

  1. Select the 'PlayerArmature' object.
  2. Add a ksEntityComponent.
  3. Uncheck 'Is Permanent' in the ksEntityComponent inspector.
  4. In the inspector for the CharacterController, expand the 'Reactor Collider Data' foldout and set the 'Existence' to 'Client-Only'. This prevents the component from being written to server configs.
  5. Add a ksAutoSpawn component. This will automatically spawn the game object as an entity for each player when they connect. It can only be used with prefabs in Resources or asset bundles, and requires player spawning be enabled in the ksRoomType inspector. We will make the game object a prefab variant in Resources later.
    • 'Spawn Owned' determines if the entity spawns with the player who created it as the owner (client authoritative) or without an owner (server authoritative). If unchecked, instances in the initial scene will not spawn per-player when they connect; only instances instantiated at runtime will spawn entities. Leave this checked.
    • 'Owner Permissions` determines what permissions the owner has. Leave this as 'Everything'.
      • 'Transform' the owner controls the transform.
      • 'Properties' the owner can set properties.
      • 'Destroy' the owner can destroy the game object to destroy the entity for all players.
  6. Add a ksAnimationSync component. This will sync the animator parameters as properties. It requires the entity to have an owner.
    • 'Animator' lets you set a reference to the Animator to use. If you leave it blank, it will look for an Animator on the same game object as the script. Leave it blank.
    • 'Animation Property Ids' shows the range of properties used to sync animator parameters and lets you change the start of the range. Make sure these do not overlap with other property ids you are using.
    • 'Sync Layer States' will optionally sync the layer states. This is only needed if your layer animation state changes are triggered programmatically instead of by animator properties with an animator controller. Leave this unchecked.
    • 'Animation Properties' can be expanded to view the synced animation property ids, types, and names.
  7. Add a ksOwnershipScriptManager component. This allows you to configure what happens to scripts on the object based on ownership state. It can also be added to children of the entity game object to configure their scripts.
    • Set 'Default' to 'Disable When Unowned'. This will disable scripts that don't have a different configuration on entities that are not owned locally.
    • Set 'Animator' to 'Leave Unchanged'. This will prevent the default 'Disable When Unowned' setting from disabling the Animator for non-locally owned entities.

Instead of 'Disable When Unowned' you could use 'Destroy When Unowned' to destroy the scripts for the remote players' entities. This is not desirable for entities whose ownership can change such as vehicles, since destroyed scripts cannot be recreated, but disabled scripts will be reenabled if the local player becomes the entity's owner. Sometimes destroying scripts is preferable, as disabling may not completey stop the script from running code as it does not remove the script or stop it from receiving messages from GameObject.SendMessage. In this case if you set 'Default' to 'Disable When Unowned', you will also need to change the order of the CharacterController and ThirdPersonController components or you will get an error saying the CharacterController cannot be destroyed because ThirdPersonController depends on it.

  1. Drag the object into a 'Resources' folder and make it a prefab variant.
  2. Rename the prefab to 'Player'.

Scripting

Create a Script to Play Footstep Sounds

The ThirdPersonController script plays footstep sounds when a character walks, however we disable this script for remote players since it also tries to move the character using player inputs, which we only want to do for the local player. The sounds are played in response to animation events, and disabling the script does not prevent it from receiving the animation event messages, however because Start wasn't called since the script is disabled, _controller won't be set and will cause a null reference exception. We will remove the footsteps code from ThirdPersonController and move it into a new script so we can disable or destroy the ThirdPersonController for remote players and still have the footsteps play properly.

  1. Select the 'Player' prefab.
  2. Click the 'Add Component' button in the inspector and create a new C# Script named 'Footsteps'.

Footsteps.cs

using UnityEngine;

public class Footsteps : MonoBehaviour
{
    public AudioClip LandingAudioClip;
    public AudioClip[] FootstepAudioClips;
    [Range(0, 1)] public float FootstepAudioVolume = 0.3f;
    private CharacterController _controller;

    private void Start()
    {
        _controller = GetComponent<CharacterController>();
    }

    private void OnFootstep(AnimationEvent animationEvent)
    {
        if (animationEvent.animatorClipInfo.weight > 0.5f)
        {
            if (FootstepAudioClips.Length > 0)
            {
                var index = Random.Range(0, FootstepAudioClips.Length);
                AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);
            }
        }
    }

    private void OnLand(AnimationEvent animationEvent)
    {
        if (animationEvent.animatorClipInfo.weight > 0.5f)
        {
            AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);
        }
    }
}
  1. Set the 'Landing Audio Clip' to 'Assets/StarterAssets/ThirdPersonController/Character/Sfx/Player_Land'
  2. Right-click the 'Footstep Audio Clips' field in the inspector for the ThirdPersonController script and click 'Copy'.
  3. Right-click the 'Footstep Audio Clips' field in the inspector for the Footsteps script and click 'Paste'.
  4. Set 'Footsteps' to 'Leave Unchanged' in the ksOwnershipScriptManager inspector.
  5. Remove the OnFootstep and OnLand functions from the ThirdPersonController script.
  6. Remove the LandingAudioClip, FootstepAudioClips, and FootstepAudioVolume fields from the ThirdPersonController.

Testing

You are now ready to test your game. You will need to use the Unity Multplayer Play Mode, ParrelSync, or build a game client with your scene added to the build in order to connect multiple clients at once. When you move your character on one client, you should see it move on other clients.