NetworkBehaviours And NetworkObjects - CaptainToTo/owl-tree-unity GitHub Wiki
NetworkBehaviour is a component type that can be inherited from instead of MonoBehaviour. It provides getters to the NetworkGameObject and UnityConnection the component is associated with. It also has OnSpawn() and OnDespawn() callbacks that will be invoked by the NetworkGameObject that the component is associated with. Read more about NetworkGameObjects here.
NetworkBehaviours do not require a networked condition. The getters will simply return null, and the callbacks will not be invoked if the component is used like a normal MonoBehaviour.
In order to create Remote Procedure Calls (RPCs), OwlTree's primary method of sending state within a session, you will need to create NetworkObjects. This means that objects containing game logic (Network and MonoBehaviours) will be separate from netcode (NetworkObjects).
In the below example, an enemy nest is responsible for managing enemies it controls. Game logic is handled by the NetworkBehaviour. The netcode NetworkObject then subscribes to various events the enemy nest invokes in order to synchronize state across clients.
namespace Game.Enemies
{
// drives game logic
public class EnemyNest : NetworkBehaviour
{
// enemies reference stats about their nest to drive decision making
[SerializeField] private int _level = 10;
public int Level => _level;
[SerializeField] private Enemy[] _enemies;
[SerializeField] private float _maxHealth = 100f;
public float Health { get; private set; }
// updating health invokes an event
public UnityEvent<float> OnHealthChange;
public void SetHealth(float health)
{
Health = Mathf.Clamp(health, 0f, _maxHealth);
OnHealthChange?.Invoke(Health);
}
// initialization needed whether single- or multiplayer
void Awake()
{
health = _maxHealth;
foreach (var enemy in _enemies)
enemy.nest = this;
}
// initialization specific to multiplayer, spawns and attaches netcode
private EnemyNestNetcode _netcode = null;
public override void OnSpawn()
{
if (Connection.IsAuthority)
{
_netcode = Connection.Spawn<EnemyNestNetcode>();
_netcode.SetComponent(this);
}
}
// multiplayer specific clean-up
public override void OnDespawn()
{
if (Connection.IsAuthority)
Connection.Despawn(_netcode);
}
// represents different goals for the enemies managed by a nest
public enum NestGoals
{
Attack,
Defend,
Rebuild,
Idle
}
public NestGoals CurGoal { get; private set; } = NestGoals.Idle;
void FixedUpdate()
{
// if this is in a multiplayer game, only allow the authority to change nest goals
if (Connection && !Connection.IsAuthority)
return;
if (Health < _maxHealth && CurGoal != NestGoals.Rebuild)
SetGoal(NestGoals.Rebuild);
// other goal change logic
}
// updating goals invokes an event
public UnityEvent<NestGoals> OnGoalChange;
internal void SetGoal(NestGoals goal)
{
CurGoal = goal;
OnGoalChange?.Invoke(CurGoal);
}
}
// synchronizes state
public class EnemyNestNetcode : NetworkObject
{
private EnemyNest _component = null;
internal SetComponent(EnemyNest component)
{
_component = component;
// listen for state changes, send new state when a change happens
_component.OnHealthChange.AddListener(OnHealthChange);
_component.OnGoalChange.AddListener(OnGoalChange);
}
public override void OnSpawn()
{
// clients don't have access to the enemy nest until they know its GameObjectId
if (!Connection.IsAuthority)
RequestComponent();
}
// authority sends clients the GameObjectId they need, and the latest state
[Rpc(RpcPerms.ClientsToAuthority)]
public virtual void RequestComponent([CallerId] ClientId caller = default)
{
SendComponent(caller, _component.NetObject.Id, _component.Health, (int)_component.CurGoal);
}
[Rpc(RpcPerms.AuthorityToClients)]
public virtual void SendComponent([CalleeId] ClientId callee, GameObjectId id, float health, int goal)
{
Connection.WaitForObject<GameObjectId, NetworkGameObject>(id, (obj) => {
// cache the component
SetComponent(obj.GetComponent<EnemyNest>());
// and set it to the most up-to-date state
_component.SetHealth(health);
_component.SetGoal((EnemyNest.NestGoals)goal);
});
}
// handle state changes
private void OnHealthChange(float health)
{
if (Connection.IsAuthority)
SendHealth(health);
}
[Rpc(RpcPerms.AuthorityToClients)]
public virtual void SendHealth(float health)
{
_component?.SetHealth(health);
}
private void OnGoalChange(EnemyNest.NestGoals goal)
{
if (Connection.IsAuthority)
SendGoal((int)goal);
}
[Rpc(RpcPerms.AuthorityToClients)]
public virtual void SendGoal(int goal)
{
_component?.SetGoal((EnemyNest.NestGoals)goal);
}
}
}Read more about NetworkObjects here. Read more about RPCs here.