Strix Action Game Sample

If you have downloaded Strix Unity SDK and completed your initial Strix setup, you may want to read through the rest of the documentation to learn more in-depth information about the SDK and how to use it.

However, for those wishing to jump right into a basic explanation of an existing project that uses Strix, start here. This page gives you a sort of a tutorial using a sample game called StrixActionGameSample, which is included in the zip file of Strix Unity SDK.

You need to run Unity to try this tutorial, though I’m sure you already have it!

Sample Folder

The sample is stored in the StrixUnitySDK > samples folder. You can load the StrixActionGameSample unity project with your Unity Editor.

Note

The Strix plugin is already imported into StrixActionGameSample project, so you don’t need to take the SDK import steps by yourself.

Unity gurus should prepare the StrixActionGameSample project in their own favorite ways. If you need assistance on opening a Unity project, you can follow this page.

Sample Explanation

Let’s start the tutorial by opening the SampleScene.

View of the sample game

The game included in this sample is a simple third-person shooter with a player character, an NPC, and a basic environment. The connection logic is handled by the StrixConnectUI prefab for simplicity.

Note

You can consult an appendix if you have difficulty locating SampleScene.

Server Information

Properties for StrixConnectGUI

Select the StrixConnectUI > StrixConnectPanel object and find the StrixConnectGUI script.

You will see the Host, Port, and Application Id properties. These properties are the server information that the script uses to connect to your server. If you haven’t already, follow the steps in Strix Cloud Setup to set up a server. Then, take the relevant values from Strix Cloud, and set the values to the properties of the component.

Note

Host is called Master hostname on Strix Cloud and is available on SERVERS tab on your application dashboard. (It is an Internet domain name such as 0123.game.strixcloud.net.)

Port is always 9122 when using Strix Cloud.

Application ID is available on INFORMATION tab on your application dashboard on Strix Cloud.

Initial Play

With your server information added, you can now test out the sample. To do so, hit play and add a player name in the text box. Next, hit the Connect button to connect to the server. You can now run around and shoot at the NPC as well as other players who join the game.

Connection

Once you have finished testing the game out, exit.

The connection to the server is straightforward. Open the StrixConnectGUI.cs script in your preferred editor.

// Several lines omitted for clarity

public class StrixConnectGUI : MonoBehaviour {
    public string host = "127.0.0.1";
    public int port = 9122;
    public string applicationId = "00000000-0000-0000-0000-000000000000";
    public Level logLevel = Level.INFO;
    public InputField playerNameInputField;
    public Text statusText;
    public Button connectButton;
    public UnityEvent OnConnect;

    public void Connect() {
        LogManager.Instance.Filter = logLevel;

        StrixNetwork.instance.applicationId = applicationId;
        StrixNetwork.instance.playerName = playerNameInputField.text;
        StrixNetwork.instance.ConnectMasterServer(host, port, OnConnectCallback, OnConnectFailedCallback);

        statusText.text = "Connecting MasterServer " + host + ":" + port;

        connectButton.interactable = false;
    }

    private void OnConnectCallback(StrixNetworkConnectEventArgs args)
    {
        statusText.text = "Connection established";

        OnConnect.Invoke();

        gameObject.SetActive(false);
    }

    private void OnConnectFailedCallback(StrixNetworkConnectFailedEventArgs args) {
        string error = "";

        if (args.cause != null) {
            error = args.cause.Message;
        }

        statusText.text = "Connect " + host + ":" + port + " failed. " + error;
        connectButton.interactable = true;
    }
}

The script begins by inheriting from MonoBehaviour and defining a number of properties for the user.

The Connect method sets the applicationId and playerName values on the StrixNetwork. Then, it calls the ConnectMasterServer method with the host and port arguments. This performs the connection to your application’s Master Server.

Of interest are the other two arguments to the ConnectMasterServer function: OnConnectCallback and OnConnectFailedCallback. These are the methods that will be called on either a success or failure to connect. As we can see, the OnConnectCallback will invoke the OnConnect UnityEvent to advance gameplay.

OnClick event of ConnectButton

Back in the Unity editor, look at the StrixConnectUI > StrixConnectPanel > Horizontal > ConnectButton to see the On Click event that calls the above script’s Connect method.

OnConnect event on StrixConnectGUI

In the StrixConnectPanel we can see in the StrixConnectGUI script component the OnConnect UnityEvent. In the script we can see this is triggered on successful connection. Bound to this is the StrixEnterRoom.EnterRoom method, which we can view by opening the Strix Enter Room script in the script component below.

// Lines omitted for clarity

public class StrixEnterRoom : MonoBehaviour {

    public int capacity = 4;
    public string roomName = "New Room";
    public UnityEvent onRoomEntered;
    public UnityEvent onRoomEnterFailed;

    public void EnterRoom() {
        StrixNetwork.instance.JoinRandomRoom(StrixNetwork.instance.playerName, args => {
            onRoomEntered.Invoke();
        }, args => {
            CreateRoom();
        });
    }

    private void CreateRoom() {
        RoomProperties roomProperties = new RoomProperties {
            capacity = capacity,
            name = roomName
        };

        RoomMemberProperties memberProperties = new RoomMemberProperties {
            name = StrixNetwork.instance.playerName
        };


        StrixNetwork.instance.CreateRoom(roomProperties, memberProperties, args => {
            onRoomEntered.Invoke();
        }, args => {
            onRoomEnterFailed.Invoke();
        });
    }
}

The EnterRoom method is called when connection to the Master Server is successful, and it immediately attempts to connect to a room by calling JoinRandomRoom. This call takes the player name that was set previously, and success and failure handlers. On success, it invokes the onRoomEntered UnityEvent. On failure, it calls the CreateRoom method.

The CreateRoom method creates a RoomProperties object with the set capacity and room name, and a RoomMemberProperties object with the player name. These are the properties required for creating a room in Strix. It then calls CreateRoom on the StrixNetwork instance with the relevant arguments.

Again, success and failure callbacks are used (you will see these a lot in Strix functions). (For this sample, the events they trigger don’t contain any logic).

This is a straightforward method of connection:

  1. Connect to the master server.

  2. Join a room.

  3. If that fails, create a room (creation of a room always automatically joins the creator to the room).

Replication

Replicating objects across clients in Strix is very easy. On the unitychan character, we have attached a Strix Replicator component. This will ensure this character is replicated across clients. As this is the player character, other players will be able to see us in the game world.

There are two other objects in the scene that are replicated: the NPC and the Ball. These also have Strix Replicator components. However, there are two crucial differences.

Instantiable By set to Room Owner

Firstly, the replicator on these two objects has the Instantiable By property set to Room Owner. This tells Strix that only the room owner should replicate the object. When another player joins the room, they will not replicate their object to other clients; rather, they will only see the replica of the room owner’s object.

Secondly, the Connection Closed Behaviour is set to Change Ownership. When the connection is closed for a client instantiating this object, Strix will change the owner of this object.

These two settings are important for the Ball and the NPC. We want there to be one character per client, in every client’s game. But we only want one Ball and one NPC in every client’s game, as these are objects in the shared world of every player. The above options ensure only one of each is instantiated per client (as only the room owner owns these objects) and also that should the room owner lose connection, their ownership will be transferred so that they are not lost.

This is an important distinction between each player’s objects and the world’s/server’s objects.

Movement

Strix replicates objects that have a Strix Replicator component, but it does not update their location automatically. For this, the NPC and unitychan characters have the Strix Movement Synchronizer, which does just that. The Strix Movement Synchronizer is used for smooth movement such as that of characters.

The Ball has the Strix Transform Sync instead. This component provides simpler movement logic that is better suited to simple objects as it does not perform any interpolation/extrapolation of movement.

Feel free to play around with the settings on these components to see what they do. Your own games will likely require some configuration to make movement smooth across clients.

Animation

Both unitychan and the NPC have Animators and Strix provides a way to synchronize this too. The Strix Animation Sync component performs animation synchronization when given an Animator.

Gameplay Synchronization

Now we understand how Strix can replicate objects, movement, and animation, we can look into how Strix replicates gameplay actions. This is a third person shooter, and the players surely want to shoot.

The game has two items of interest: health, and bullets. When the player clicks the left mouse button their character fires a bullet. These bullets can impact with characters and decrease their health.

On the unitychan object there is a script called FireBullet:

public class FireBullet : StrixBehaviour {
    public GameObject bullet;
    private PlayerStatus playerStatus;

    // Use this for initialization
    void Start () {
        playerStatus = GetComponent<PlayerStatus>();
    }

    // Update is called once per frame
    void Update () {
        if (!isLocal) {
            return;
        }

        if (playerStatus != null && playerStatus.health <= 0) {
            return;
        }

        if (Input.GetButtonDown("Fire1")) {
            GameObject instance = Instantiate(bullet);
            Transform firePos = transform.Find("FirePos");

            BulletControl bulletControl = instance.GetComponent<BulletControl>();
            bulletControl.owner = gameObject;

            instance.transform.position = firePos.position;
            instance.transform.rotation = firePos.rotation;
        }
    }
}

This script inherits from the StrixBehaviour class. The StrixBehaviour class itself inherits from Unity’s standard MonoBehaviour and provides some additional functionality for Strix usage.

The script checks every frame update and uses the isLocal property to determine if it is running on a local object, or a replicated one. This is important because we don’t want other clients to access this logic on a replica owned by a different player; access should be restricted to the player that owns the object.

The script checks if the fire button is pressed, and if so, creates a bullet and a BulletControl object.

Looking at BulletControl.cs, we see that BulletControl takes care of moving the bullet forward, but also contains the important hit logic:

// lines 41-43
if (playerStatus != null && playerStatus.isLocal) {
    playerStatus.RpcToRoomOwner("OnHit");
}

It checks if the hit is on a local object and then calls RpcToRoomOwner("OnHit").

RPCs (Remote Procedure Calls) send a message to other clients, instructing them to call a method on an object on their machines. This is incredibly useful for synchronizing actions across clients.

Here, the BulletControl class is sending a message to the owner of the room, instructing it to call the OnHit method of the PlayerStatus object on the hitObject. This hitObject could be on a different machine from the room owner, but as PlayerStatus is a StrixBehaviour, and the character has a StrixReplicator, Strix knows what specific object to call the method on, in the room owner’s game.

This means that, when a shot is fired and hits a character, it will call the OnHit method for the PlayerStatus of the replicated character in the room owner’s game.

Now let’s take a look at the PlayerStatus script:

// Lines omitted for clarity

public class PlayerStatus : StrixBehaviour {
    [StrixSyncField]
    public int health = 100;
    public int maxHealth = 100;
    public float recoverTime = 3;
    private Animator animator;
    private float deadTime = 0;

    // Use this for initialization
    void Start() {
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update() {
        if (!isLocal) {
            return;
        }

        if (health <= 0 && Time.time >= deadTime + recoverTime) {
            RpcToAll("SetHealth", maxHealth);
        }
    }

    [StrixRpc]
    public void OnHit() {
        int value = health - 10;

        if (value < 0) {
            value = 0;
        }

        RpcToAll("SetHealth", value);
    }

    [StrixRpc]
    public void SetHealth(int value) {
        if (value < 0) {
            value = 0;
        } else if (value > maxHealth) {
            value = maxHealth;
        }

        animator.SetInteger("Health", value);

        if (animator != null) {
            if (value < health) {
                if (value <= 0) {
                    animator.SetTrigger("Dead");
                } else {
                    animator.SetTrigger("Damaged");
                }
            }

            if (value > 0 && health <= 0) {
                Respawn();
            }
        }

        if(health != value) {
            if (value <= 0) {
                deadTime = Time.time;
            }
        }

        health = value;
    }

    private void Respawn() {
        if (!CompareTag("Player") && isLocal) {
            transform.position = new Vector3(Random.Range(-40.0f, 40.0f), 2, Random.Range(-40.0f, 40.0f));
        }
    }
}

Before we look at the OnHit method, take a look at the health field. This variable represents the health of the character. Above it, we have the [StrixSyncField] attribute. [StrixSyncField] tells Strix to synchronize this variable between an object and its replicas. This ensures that the original objects health value is replicated to its replicas. This is a really easy way to synchronize values using Strix.

Now, looking at the OnHit method, we can see that it too has an attribute: [StrixRpc]. This registers the method with Strix, allowing it to be called using RPCs.

OnHit is called on the replica character in the room owner’s world. It subtracts some damage from the health value, and then sends a different RPC, calling SetHealth, this time to all clients. As this is calling on a replica, this health value update will not be synchronized to all clients automatically unless this is actually a character owned by the room owner.

Note

The RpcToAll method sends an RPC to all clients, including the one who sent the RPC.

Strix RPCs can take arguments, and so the SetHealth sends the new health value for the hit character. This method is called on all the PlayerStatus-es of the character’s replicas. It sets the health value and handles the death and respawn logic.

To recap:

  1. A player fires a bullet object in their world.

  2. On impact with another character, Strix sends an RPC to the room owner, telling it that the respective character in its world was hit.

  3. The room owner subtracts the damage from the character, and broadcasts this change to all replicas and the original of that character.

The hit detection is kept local, which ensures accuracy, and the damage calculation is limited to the room owner, preventing desynchronization issues. In total, the hit takes n (the number of clients in the game) + 1 messages to handle.

Conclusion

Strix provides multiple features for networked games. This sample is a brief look at these features and does not cover everything in-depth. If you have any more questions about how Strix works, or how to implement something in your game with Strix, feel free to continue reading this documentation.