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.
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¶
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.
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.
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:
Connect to the master server.
Join a room.
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.
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:
A player fires a bullet object in their world.
On impact with another character, Strix sends an RPC to the room owner, telling it that the respective character in its world was hit.
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.